diff --git a/swh/loader/svn/ra.py b/swh/loader/svn/ra.py --- a/swh/loader/svn/ra.py +++ b/swh/loader/svn/ra.py @@ -150,7 +150,7 @@ elif key == properties.PROP_SPECIAL: # Possibly a symbolic link. We cannot check further at # that moment though, patch(s) not being applied yet - self.link = True + self.link = value is not None elif key == SVN_PROPERTY_EOL: # backup end of line style for file EOL_STYLE[self.fullpath] = value @@ -227,6 +227,8 @@ self.__make_symlink(src) else: # not a real link... self.link = False + elif os.path.islink(self.fullpath): + self.__make_svnlink() if not is_link: # if a link, do nothing regarding flag if self.executable == EXEC_FLAG: diff --git a/swh/loader/svn/tests/test_loader.py b/swh/loader/svn/tests/test_loader.py --- a/swh/loader/svn/tests/test_loader.py +++ b/swh/loader/svn/tests/test_loader.py @@ -932,3 +932,86 @@ ) == b"../file_with_crlf_eol.txt" ) + + +def test_loader_svn_special_property_unset(swh_storage, tmp_path): + # create a repository + repo_path = os.path.join(tmp_path, "tmprepo") + repos.create(repo_path) + + # connect to the "remote" repository using the file transport. + repo_url = f"file://{repo_path}" + conn = RemoteAccess(repo_url, auth=Auth([get_username_provider()])) + + # first commit + with conn.get_commit_editor( + { + "svn:log": ( + "Create a regular file, a link to a file and a link to an " + "external file. Set the svn:special property on the links." + ) + } + ) as editor: + with editor.open_root() as root: + with root.add_file("file.txt") as file: + txdelta = file.apply_textdelta() + delta.send_stream(BytesIO(b"Hello world!\n"), txdelta) + with root.add_file("link.txt") as file: + file.change_prop("svn:special", "*") + txdelta = file.apply_textdelta() + delta.send_stream(BytesIO(b"link ./file.txt"), txdelta) + with root.add_file("external_link.txt") as file: + file.change_prop("svn:special", "*") + txdelta = file.apply_textdelta() + delta.send_stream(BytesIO(b"link /home/user/data.txt"), txdelta) + + # second commit + with conn.get_commit_editor( + {"svn:log": ("Unset the svn:special property on the links.")} + ) as editor: + with editor.open_root() as root: + with root.open_file("link.txt") as file: + file.change_prop("svn:special", None) + with root.open_file("external_link.txt") as file: + file.change_prop("svn:special", None) + + # instantiate a svn loader checking after each processed revision that + # the repository filesystem it reconstructed does not differ from a subversion + # export of that revision + loader = SvnLoader( + swh_storage, repo_url, destination_path=tmp_path, check_revision=1 + ) + + assert loader.load() == {"status": "eventful"} + assert loader.visit_status() == "full" + + # check loaded objects are those expected + assert get_stats(loader.storage) == { + "content": 5, + "directory": 2, + "origin": 1, + "origin_visit": 1, + "release": 0, + "revision": 2, + "skipped_content": 0, + "snapshot": 1, + } + + root_dir = loader.snapshot.branches[b"HEAD"].target + revision = loader.storage.revision_get([root_dir])[0] + + paths = {} + for entry in loader.storage.directory_ls(revision.directory, recursive=True): + paths[entry["name"]] = entry + + assert paths[b"link.txt"]["perms"] == DentryPerms.content + assert ( + loader.storage.content_get_data(paths[b"link.txt"]["sha1"]) + == b"link ./file.txt" + ) + + assert paths[b"external_link.txt"]["perms"] == DentryPerms.content + assert ( + loader.storage.content_get_data(paths[b"external_link.txt"]["sha1"]) + == b"link /home/user/data.txt" + )