Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions giturlparse/platforms/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,30 @@ class GitHubPlatform(BasePlatform):
PATTERNS = {
"https": (
r"(?P<protocols>(git\+)?(?P<protocol>https))://"
r"((?P<username>[^/]+?):(?P<access_token>[^/]+?)@)?(?P<domain>[^/]+?)"
r"((?P<username>[^/]+?):(?P<access_token>[^/]+?)@)?(?P<domain>[^:/]+)(?::(?P<port>[0-9]+))?"
r"(?P<pathname>/(?P<owner>[^/]+?)/(?P<repo>[^/]+?)(?:(\.git)?(/)?)(?P<path_raw>(/blob/|/tree/).+)?)$"
),
"ssh": (
r"(?P<protocols>(git\+)?(?P<protocol>ssh))?(://)?git@(?P<domain>.+?)(?P<pathname>(:|/)"
r"(?P<owner>[^/]+)/(?P<repo>[^/]+?)(?:(\.git)?(/)?)"
r"(?P<protocols>(git\+)?(?P<protocol>ssh))?(://)?git@(?P<domain>[^:/]+)(:)?(?P<port>[0-9]+)?(?(port))?"
r"(?P<pathname>/?(?P<owner>[^/]+)/(?P<repo>[^/]+?)(?:(\.git)?(/)?)"
r"(?P<path_raw>(/blob/|/tree/).+)?)$"
),
"git": (
r"(?P<protocols>(?P<protocol>git))://(?P<domain>.+?)"
r"(?P<protocols>(?P<protocol>git))://(?P<domain>[^:/]+)(?::(?P<port>[0-9]+))?"
r"(?P<pathname>/(?P<owner>[^/]+)/(?P<repo>[^/]+?)(?:(\.git)?(/)?)"
r"(?P<path_raw>(/blob/|/tree/).+)?)$"
),
}
FORMATS = {
"https": r"https://%(domain)s/%(owner)s/%(repo)s%(dot_git)s%(path_raw)s",
"ssh": r"git@%(domain)s:%(owner)s/%(repo)s%(dot_git)s%(path_raw)s",
"git": r"git://%(domain)s/%(owner)s/%(repo)s%(dot_git)s%(path_raw)s",
"https": r"https://%(domain)s%(port_colon)s/%(owner)s/%(repo)s%(dot_git)s%(path_raw)s",
"ssh": r"git@%(domain)s:%(port_slash)s%(owner)s/%(repo)s%(dot_git)s%(path_raw)s",
"git": r"git://%(domain)s%(port_colon)s/%(owner)s/%(repo)s%(dot_git)s%(path_raw)s",
}
DOMAINS = (
"github.com",
"gist.github.com",
)
DEFAULTS = {"_user": "git"}
DEFAULTS = {"_user": "git", "port": ""}

@staticmethod
def clean_data(data):
Expand Down
1 change: 1 addition & 0 deletions giturlparse/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def format(self, protocol): # noqa : A0003
"""Reformat URL to protocol."""
items = copy(self._parsed)
items["port_slash"] = "%s/" % self.port if self.port else ""
items["port_colon"] = ":%s" % self.port if self.port else ""
items["groups_slash"] = "%s/" % self.groups_path if self.groups_path else ""
items["dot_git"] = "" if items["repo"].endswith(".git") else ".git"
return self._platform_obj.FORMATS[protocol] % items
Expand Down
219 changes: 219 additions & 0 deletions giturlparse/tests/test_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,225 @@
},
),
),
# GitHub with explicit ports
(
"HTTPS with port",
(
"https://github.com:8443/Org/Repo.git",
{
"host": "github.com",
"resource": "github.com",
"user": "git",
"port": "8443",
"owner": "Org",
"repo": "Repo",
"name": "Repo",
"groups": [],
"path": "",
"path_raw": "",
"pathname": "/Org/Repo.git",
"branch": "",
"protocol": "https",
"protocols": ["https"],
"platform": "github",
"github": True,
},
),
),
(
"GIT with port",
(
"git://github.com:9418/Org/Repo.git",
{
"host": "github.com",
"resource": "github.com",
"user": "git",
"port": "9418",
"owner": "Org",
"repo": "Repo",
"name": "Repo",
"groups": [],
"path": "",
"path_raw": "",
"pathname": "/Org/Repo.git",
"branch": "",
"protocol": "git",
"protocols": ["git"],
"platform": "github",
"github": True,
},
),
),
(
"SSH URL-style with port",
(
"ssh://git@github.com:2222/Org/Repo.git",
{
"host": "github.com",
"resource": "github.com",
"user": "git",
"port": "2222",
"owner": "Org",
"repo": "Repo",
"name": "Repo",
"groups": [],
"path": "",
"path_raw": "",
"pathname": "/Org/Repo.git",
"branch": "",
"protocol": "ssh",
"protocols": ["ssh"],
"platform": "github",
"github": True,
},
),
),
# GitHub paths with ports
(
"HTTPS blob with port",
(
"https://github.com:8443/Org/Repo/blob/master/path/to/file.py",
{
"host": "github.com",
"resource": "github.com",
"user": "git",
"port": "8443",
"owner": "Org",
"repo": "Repo",
"name": "Repo",
"groups": [],
"path": "master/path/to/file.py",
"path_raw": "/blob/master/path/to/file.py",
"pathname": "/Org/Repo/blob/master/path/to/file.py",
"branch": "",
"protocol": "https",
"protocols": ["https"],
"platform": "github",
"github": True,
},
),
),
(
"HTTPS tree with port",
(
"https://github.com:8443/Org/Repo/tree/feature/x",
{
"host": "github.com",
"resource": "github.com",
"user": "git",
"port": "8443",
"owner": "Org",
"repo": "Repo",
"name": "Repo",
"groups": [],
"path": "",
"path_raw": "/tree/feature/x",
"pathname": "/Org/Repo/tree/feature/x",
"branch": "feature/x",
"protocol": "https",
"protocols": ["https"],
"platform": "github",
"github": True,
},
),
),
(
"SSH with port and tree path",
(
"ssh://git@github.com:2222/Org/Repo/tree/feature/x",
{
"host": "github.com",
"resource": "github.com",
"user": "git",
"port": "2222",
"owner": "Org",
"repo": "Repo",
"name": "Repo",
"groups": [],
"path": "",
"path_raw": "/tree/feature/x",
"pathname": "/Org/Repo/tree/feature/x",
"branch": "feature/x",
"protocol": "ssh",
"protocols": ["ssh"],
"platform": "github",
"github": True,
},
),
),
# GitHub access token with port
(
"HTTPS access token with port",
(
"https://username:access_token@github.com:8443/Org/Repo.git",
{
"host": "github.com",
"resource": "github.com",
"user": "git",
"port": "8443",
"owner": "Org",
"repo": "Repo",
"name": "Repo",
"groups": [],
"path": "",
"path_raw": "",
"pathname": "/Org/Repo.git",
"branch": "",
"protocol": "https",
"protocols": ["https"],
"username": "username",
"access_token": "access_token",
"platform": "github",
"github": True,
},
),
),
# Platform disambiguation: gist.github.com with port stays GitHub
(
"Gist HTTPS with port",
(
"https://gist.github.com:8443/Org/Repo.git",
{
"host": "gist.github.com",
"resource": "gist.github.com",
"user": "git",
"port": "8443",
"owner": "Org",
"repo": "Repo",
"name": "Repo",
"groups": [],
"path": "",
"path_raw": "",
"pathname": "/Org/Repo.git",
"branch": "",
"protocol": "https",
"protocols": ["https"],
"platform": "github",
"github": True,
},
),
),
# Malformed port: not digits → must NOT parse as GitHub
(
"HTTPS malformed port (letters)",
(
"https://github.com:abc/Org/Repo.git",
{
"platform": "base",
"github": False,
},
),
),
(
"GIT malformed port (letters)",
(
"git://github.com:abc/Org/Repo.git",
{
"platform": "base",
"github": False,
},
),
),
)

INVALID_PARSE_URLS = (
Expand Down
29 changes: 29 additions & 0 deletions giturlparse/tests/test_rewrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@
"ssh",
"git@host.org:Org/Group/Repo.git/blob/master/file.py",
),
# GitHub HTTPS with port
("https://github.com:8443/Org/Repo.git", "https", "https://github.com:8443/Org/Repo.git"),
("https://github.com:8443/Org/Repo.git", "ssh", "git@github.com:8443/Org/Repo.git"),
("https://github.com:8443/Org/Repo.git", "git", "git://github.com:8443/Org/Repo.git"),
# GitHub GIT with port
("git://github.com:9418/Org/Repo.git", "git", "git://github.com:9418/Org/Repo.git"),
("git://github.com:9418/Org/Repo.git", "https", "https://github.com:9418/Org/Repo.git"),
("git://github.com:9418/Org/Repo.git", "ssh", "git@github.com:9418/Org/Repo.git"),
# GitHub SSH with port
("ssh://git@github.com:2222/Org/Repo.git", "ssh", "git@github.com:2222/Org/Repo.git"),
("ssh://git@github.com:2222/Org/Repo.git", "https", "https://github.com:2222/Org/Repo.git"),
("ssh://git@github.com:2222/Org/Repo.git", "git", "git://github.com:2222/Org/Repo.git"),
)

INVALID_PARSE_URLS = (
Expand All @@ -84,6 +96,19 @@
},
}

REWRITE_COMPONENTS_WITH_PORT = {
"name": {
"source": "ssh://git@github.com:2222/Org/Repo.git",
"value": "NewRepo",
"expected": "git@github.com:2222/Org/NewRepo.git",
},
"owner": {
"source": "ssh://git@github.com:2222/Org/Repo.git",
"value": "NewOrg",
"expected": "git@github.com:2222/NewOrg/Repo.git",
},
}


class UrlRewriteTestCase(unittest.TestCase):
def _test_rewrite(self, source, protocol, expected):
Expand All @@ -105,6 +130,10 @@ def test_rewrite_components(self):
for field, data in REWRITE_COMPONENTS.items():
self._test_rewrite_components(field, data)

def test_rewrite_components_with_port(self):
for field, data in REWRITE_COMPONENTS_WITH_PORT.items():
self._test_rewrite_components(field, data)


# Test Suite
suite = unittest.TestLoader().loadTestsFromTestCase(UrlRewriteTestCase)
Loading