diff --git a/poetry/vcs/git.py b/poetry/vcs/git.py index 780a687f40b..baa957ed8a6 100644 --- a/poetry/vcs/git.py +++ b/poetry/vcs/git.py @@ -7,46 +7,84 @@ from poetry.utils._compat import decode +pattern_formats = { + "protocol": r"\w+", + "user": r"[a-zA-Z0-9_.-]+", + "resource": r"[a-zA-Z0-9_.-]+", + "port": r"\d+", + "path": r"[\w\-/\\]+", + "name": r"[\w\-]+", + "rev": r"[^@#]+", +} + PATTERNS = [ - re.compile( - r"(git\+)?" - r"((?P\w+)://)" - r"((?P\w+)@)?" - r"(?P[\w.\-]+)" - r"(:(?P\d+))?" - r"(?P(/(?P\w+)/)" - r"((?P([\w\-/]+)/)?(?P[\w\-]+)(\.git|/)?)?)" - r"([@#](?P[^@#]+))?" - r"$" - ), re.compile( r"^(git\+)?" r"(?Phttps?|git|ssh|rsync|file)://" - r"(?:(?P.+)@)*" - r"(?P[a-z0-9_.-]*)" - r"(:?P[\d]+)?" - r"(?P[:/]((?P[\w\-]+)/(?P([\w\-/]+)/)?)?" - r"((?P[\w\-.]+?)(\.git|/)?)?)" - r"([@#](?P[^@#]+))?" - r"$" + r"(?:(?P{user})@)?" + r"(?P{resource})?" + r"(:(?P{port}))?" + r"(?P[:/\\]({path}[/\\])?" + r"((?P{name}?)(\.git|[/\\])?)?)" + r"([@#](?P{rev}))?" + r"$".format( + user=pattern_formats["user"], + resource=pattern_formats["resource"], + port=pattern_formats["port"], + path=pattern_formats["path"], + name=pattern_formats["name"], + rev=pattern_formats["rev"], + ) ), re.compile( - r"^(?:(?P.+)@)*" - r"(?P[a-z0-9_.-]*)[:]*" - r"(?P[\d]+)?" - r"(?P/?(?P.+)/(?P([\w\-/]+)/)?(?P.+).git)" - r"([@#](?P[^@#]+))?" - r"$" + r"(git\+)?" + r"((?P{protocol})://)" + r"(?:(?P{user})@)?" + r"(?P{resource}:?)" + r"(:(?P{port}))?" + r"(?P({path})" + r"(?P{name})(\.git|/)?)" + r"([@#](?P{rev}))?" + r"$".format( + protocol=pattern_formats["protocol"], + user=pattern_formats["user"], + resource=pattern_formats["resource"], + port=pattern_formats["port"], + path=pattern_formats["path"], + name=pattern_formats["name"], + rev=pattern_formats["rev"], + ) ), re.compile( - r"((?P\w+)@)?" - r"(?P[\w.\-]+)" - r"[:/]{1,2}" - r"(?P((?P\w+)/)?" - r"(?P([\w\-/]+)/)?" - r"((?P[\w\-]+)(\.git|/)?)?)" - r"([@#](?P[^@#]+))?" - r"$" + r"^(?:(?P{user})@)?" + r"(?P{resource})" + r"(:(?P{port}))?" + r"(?P([:/]{path}/)" + r"(?P{name})(\.git|/)?)" + r"([@#](?P{rev}))?" + r"$".format( + user=pattern_formats["user"], + resource=pattern_formats["resource"], + port=pattern_formats["port"], + path=pattern_formats["path"], + name=pattern_formats["name"], + rev=pattern_formats["rev"], + ) + ), + re.compile( + r"((?P{user})@)?" + r"(?P{resource})" + r"[:/]{{1,2}}" + r"(?P({path})" + r"(?P{name})(\.git|/)?)" + r"([@#](?P{rev}))?" + r"$".format( + user=pattern_formats["user"], + resource=pattern_formats["resource"], + path=pattern_formats["path"], + name=pattern_formats["name"], + rev=pattern_formats["rev"], + ) ), ] diff --git a/tests/vcs/test_git.py b/tests/vcs/test_git.py index 667294ee614..f0157db364e 100644 --- a/tests/vcs/test_git.py +++ b/tests/vcs/test_git.py @@ -2,6 +2,7 @@ from poetry.vcs.git import Git from poetry.vcs.git import GitUrl +from poetry.vcs.git import ParsedUrl @pytest.mark.parametrize( @@ -74,3 +75,171 @@ ) def test_normalize_url(url, normalized): assert normalized == Git.normalize_url(url) + + +@pytest.mark.parametrize( + "url, parsed", + [ + ( + "git+ssh://user@hostname:project.git#commit", + ParsedUrl( + "ssh", "hostname", ":project.git", "user", None, "project", "commit" + ), + ), + ( + "git+http://user@hostname/project/blah.git@commit", + ParsedUrl( + "http", "hostname", "/project/blah.git", "user", None, "blah", "commit" + ), + ), + ( + "git+https://user@hostname/project/blah.git", + ParsedUrl( + "https", "hostname", "/project/blah.git", "user", None, "blah", None + ), + ), + ( + "git+https://user@hostname:project/blah.git", + ParsedUrl( + "https", "hostname", ":project/blah.git", "user", None, "blah", None + ), + ), + ( + "git+ssh://git@github.com:sdispater/poetry.git#v1.0.27", + ParsedUrl( + "ssh", + "github.com", + ":sdispater/poetry.git", + "git", + None, + "poetry", + "v1.0.27", + ), + ), + ( + "git+ssh://git@github.com:/sdispater/poetry.git", + ParsedUrl( + "ssh", + "github.com", + ":/sdispater/poetry.git", + "git", + None, + "poetry", + None, + ), + ), + ( + "git+ssh://git@github.com:org/repo", + ParsedUrl("ssh", "github.com", ":org/repo", "git", None, "repo", None), + ), + ( + "git+ssh://git@github.com/org/repo", + ParsedUrl("ssh", "github.com", "/org/repo", "git", None, "repo", None), + ), + ( + "git+ssh://foo:22/some/path", + ParsedUrl("ssh", "foo", "/some/path", None, "22", "path", None), + ), + ( + "git@github.com:org/repo", + ParsedUrl(None, "github.com", ":org/repo", "git", None, "repo", None), + ), + ( + "git+https://github.com/sdispater/pendulum", + ParsedUrl( + "https", + "github.com", + "/sdispater/pendulum", + None, + None, + "pendulum", + None, + ), + ), + ( + "git+https://github.com/sdispater/pendulum#7a018f2d075b03a73409e8356f9b29c9ad4ea2c5", + ParsedUrl( + "https", + "github.com", + "/sdispater/pendulum", + None, + None, + "pendulum", + "7a018f2d075b03a73409e8356f9b29c9ad4ea2c5", + ), + ), + ( + "git+ssh://git@git.example.com:b/b.git#v1.0.0", + ParsedUrl("ssh", "git.example.com", ":b/b.git", "git", None, "b", "v1.0.0"), + ), + ( + "git+ssh://git@github.com:sdispater/pendulum.git#foo/bar", + ParsedUrl( + "ssh", + "github.com", + ":sdispater/pendulum.git", + "git", + None, + "pendulum", + "foo/bar", + ), + ), + ( + "git+file:///foo/bar.git", + ParsedUrl("file", None, "/foo/bar.git", None, None, "bar", None), + ), + ( + "git+file://C:\\Users\\hello\\testing.git#zkat/windows-files", + ParsedUrl( + "file", + "C", + ":\\Users\\hello\\testing.git", + None, + None, + "testing", + "zkat/windows-files", + ), + ), + ( + "git+https://git.example.com/sdispater/project/my_repo.git", + ParsedUrl( + "https", + "git.example.com", + "/sdispater/project/my_repo.git", + None, + None, + "my_repo", + None, + ), + ), + ( + "git+ssh://git@git.example.com:sdispater/project/my_repo.git", + ParsedUrl( + "ssh", + "git.example.com", + ":sdispater/project/my_repo.git", + "git", + None, + "my_repo", + None, + ), + ), + ], +) +def test_parse_url(url, parsed): + result = ParsedUrl.parse(url) + assert parsed.name == result.name + assert parsed.pathname == result.pathname + assert parsed.port == result.port + assert parsed.protocol == result.protocol + assert parsed.resource == result.resource + assert parsed.rev == result.rev + assert parsed.url == result.url + assert parsed.user == result.user + + +def test_parse_url_should_fail(): + url = "https://" + "@" * 64 + "!" + + with pytest.raises(ValueError): + ParsedUrl.parse(url)