diff --git a/swh/loader/svn/replay.py b/swh/loader/svn/replay.py --- a/swh/loader/svn/replay.py +++ b/swh/loader/svn/replay.py @@ -631,6 +631,9 @@ path = root_path if root_path else self.path fullpath = os.path.join(path, external_path) + if self.editor.debug: + logger.debug("Removing external path %s", fullpath) + # decrement number of references for external path when we really remove it # (when remove_subpaths is False, we just cleanup the external path before # copying exported paths in it) diff --git a/swh/loader/svn/tests/test_externals.py b/swh/loader/svn/tests/test_externals.py --- a/swh/loader/svn/tests/test_externals.py +++ b/swh/loader/svn/tests/test_externals.py @@ -1703,3 +1703,98 @@ type="svn", ) check_snapshot(loader.snapshot, loader.storage) + + +def test_loader_with_unparsable_external_on_path( + swh_storage, repo_url, external_repo_url, tmp_path +): + # first commit on external + add_commit( + external_repo_url, + "Create some directories and files in an external repository", + [ + CommitChange( + change_type=CommitChangeType.AddOrUpdate, + path="code/hello/hello-world", + properties={"svn:executable": "*"}, + data=b"#!/bin/bash\necho Hello World !", + ), + CommitChange( + change_type=CommitChangeType.AddOrUpdate, + path="code/foo/foo.sh", + properties={"svn:executable": "*"}, + data=b"#!/bin/bash\necho foo", + ), + ], + ) + + # first commit + add_commit( + repo_url, + ( + "Set parsable svn:externals property on project1 path of repository to load." + "Add a code directory with a file in it." + ), + [ + CommitChange( + change_type=CommitChangeType.AddOrUpdate, + path="project1/", + properties={ + "svn:externals": ( + f"{svn_urljoin(external_repo_url, 'code/hello')} hello\n" + ) + }, + ), + CommitChange( + change_type=CommitChangeType.AddOrUpdate, + path="code/foo.sh", + properties={"svn:executable": "*"}, + data=b"#!/bin/bash\necho foo", + ), + ], + ) + + # second commit + add_commit( + repo_url, + ( + "Set unparsable svn:externals property on project2 path of repository to load." + ), + [ + CommitChange( + change_type=CommitChangeType.AddOrUpdate, + path="project2/", + properties={"svn:externals": ("^code/foo foo\n")}, + ), + ], + ) + + # third commit + add_commit( + repo_url, + ( + "Fix unparsable svn:externals property on project2 path of repository to load." + ), + [ + CommitChange( + change_type=CommitChangeType.AddOrUpdate, + path="project2/", + properties={"svn:externals": ("^/code/foo foo\n")}, + ), + ], + ) + + loader = SvnLoader( + swh_storage, + repo_url, + temp_directory=tmp_path, + check_revision=1, + ) + assert loader.load() == {"status": "eventful"} + assert_last_visit_matches( + loader.storage, + repo_url, + status="full", + type="svn", + ) + check_snapshot(loader.snapshot, loader.storage) diff --git a/swh/loader/svn/tests/test_utils.py b/swh/loader/svn/tests/test_utils.py --- a/swh/loader/svn/tests/test_utils.py +++ b/swh/loader/svn/tests/test_utils.py @@ -479,3 +479,16 @@ assert ( utils.parse_external_definition(external, dir_path, repo_url) == expected_result ) + + +@pytest.mark.parametrize( + "invalid_external", + [ + "^tests@21 tests", + ], +) +def test_parse_invalid_external_definition(invalid_external): + with pytest.raises(ValueError, match="Failed to parse external definition"): + utils.parse_external_definition( + invalid_external, "/trunk/externals", "http://svn.example.org/repo" + ) diff --git a/swh/loader/svn/utils.py b/swh/loader/svn/utils.py --- a/swh/loader/svn/utils.py +++ b/swh/loader/svn/utils.py @@ -310,6 +310,10 @@ except ValueError: # handle URL like http://user@svn.example.org/ pass + + if not external_url or not path: + raise ValueError(f"Failed to parse external definition '{external}'") + return (path.rstrip("/"), external_url, revision, relative_url) @@ -329,6 +333,7 @@ Returns: Whether the external definition is recursive """ + assert external_url parsed_origin_url = urlparse(origin_url) parsed_external_url = urlparse(external_url) external_url = urlunparse(