giturlparse
giturlparse is a small, dependency-free Python parser for common Git remote URL shapes. It recognizes HTTPS, HTTP, SSH, SCP-style, and git protocol forms for GitHub, GitLab, Bitbucket, Assembla, and FriendCode-style hosts, then exposes host, owner, repository, groups, port, branch, and path attributes. A parsed result can also rewrite the same repository into supported clone URL forms such as SSH or HTTPS.
A convenient parser for the common clone-URL cases, especially when zero dependencies matters. Guard invalid results, strip credentials immediately, and do not trust provider, branch, or path guesses for access control or exact web-URL interpretation.
Use it if
- You need to normalize user-supplied Git clone URLs before storing or comparing repository identities
- You accept both SCP-style SSH remotes and ordinary URLs and want one owner and repository extraction path
- You need quick SSH-to-HTTPS rewriting for known hosting services without invoking Git
- A pure-Python package with no runtime dependencies is more important than exhaustive forge support or strict URL standards compliance
- You need arbitrary Git hosting providers: the parser is a fixed collection of provider regexes, and unknown self-hosted domains may be rejected or guessed as GitLab depending on the URL shape
- You need precise web-page parsing: GitHub blob URLs put the branch and file together in path, while GitLab tree URLs can place the remaining path in branch, so these fields are explicitly only available when parseable
- Static typing is required: the published source has no annotations or py.typed marker, and its result object gains attributes dynamically from regex matches
- You expect standards-based URL behavior: SCP-style Git syntax is not a normal URI, and this package's generated SSH URL with an explicit port uses a host:port:path shape that may not be accepted by Git
- URLs containing credentials may reach logs or analytics: the README exposes username and access_token fields, and the result's data dictionary retains those secrets from HTTPS URLs
Setup reality
`pip install giturlparse` is the whole installation: version 0.15.0 requires Python 3.8 or newer and declares no runtime dependencies. The complexity is in deciding what inputs you trust. `parse()` always returns a GitUrlParsed object, even on failure, so check `.valid` before reading rewrite properties; an invalid result never gets the internal platform object that `url2https`, `urls`, and `normalized` require. Domain checking is on by default and helps select the right provider. Turning it off for a self-hosted forge is risky because the first broad regex can win: a Bitbucket-shaped URL can be labeled GitHub, while generic SSH URLs tend to be treated as GitLab. Web browsing URLs are not clone URLs. The package can capture GitHub and GitLab path fragments, but branch names containing slashes make clean separation impossible without a provider API. Rewrites add `.git` and drop embedded HTTPS credentials, which is often desirable but means the result is not a byte-for-byte transformation. Never persist or log `.data`, `.username`, `.access_token`, or the original URL until credentials are removed. There is no built-in redaction helper, URL allowlist, network validation, or check that the repository actually exists.
Patterns
Parse an SCP-style SSH remoteparse-ssh-remote
from giturlparse import parse
remote = parse('git@bitbucket.org:AaronO/some-repo.git')
if not remote.valid:
raise ValueError('unsupported Git URL')
print(remote.host, remote.owner, remote.repo)This Git syntax is not a conventional URI, which is the main reason to use a Git-aware parser instead of urllib.parse.
Reject unrecognized inputvalidate-before-use
from giturlparse import validate
if not validate(candidate):
raise ValueError('Expected a supported Git remote URL')Validation checks only whether required fields were parsed. It does not make a network request or prove that the repository exists.
Convert a clone URL to HTTPSrewrite-to-https
from giturlparse import parse
remote = parse('git@github.com:octo/example.git')
https_url = remote.url2https if remote.valid else None
print(https_url)Always test valid first; rewrite properties on an invalid parse can fail because there is no matched platform formatter.
Convert an HTTPS remote to SSHrewrite-to-ssh
from giturlparse import parse
remote = parse('https://github.com/octo/example')
ssh_url = remote.url2ssh
# git@github.com:octo/example.gitThe formatter adds .git when missing. It does not check whether SSH access or the repository itself is available.
List supported clone URL variantslist-clone-forms
from giturlparse import parse
remote = parse('https://github.com/octo/example.git')
if remote.valid:
for protocol, url in remote.urls.items():
print(protocol, url)The available keys depend on the matched provider. A result does not necessarily support all of http, https, ssh, and git.
Normalize while keeping the parsed protocolnormalize-remote
from giturlparse import parse
remote = parse('https://github.com/octo/example')
normalized = remote.normalized if remote.valid else None
# https://github.com/octo/example.gitNormalization can add .git and preserve provider-specific path fragments; it is not guaranteed to equal the canonical repository homepage.
Extract nested GitLab groupsread-gitlab-groups
from giturlparse import parse
remote = parse('https://gitlab.com/company/platform/api.git')
print(remote.owner) # company
print(remote.groups) # ['platform']
print(remote.repo) # apigroups is GitLab-specific. For other providers it normally returns an empty list.
Inspect a GitHub blob URLinspect-github-path
from giturlparse import parse
page = parse('https://github.com/octo/example/blob/main/src/app.py')
print(page.repo) # example
print(page.path) # main/src/app.pyThe path includes the branch segment. The parser leaves branch empty here because slash-containing refs cannot be separated reliably from file paths.
Attempt parsing a self-hosted forgeallow-self-hosted-domain
from giturlparse import parse
remote = parse('ssh://git@git.example.com/team/repo.git', check_domain=False)
if not remote.valid:
raise ValueError('unrecognized remote')
print(remote.platform, remote.owner, remote.repo)Disabling domain checks permits broader matches but can guess the wrong provider. Treat platform as a hint and verify allowed hosts separately.
Rewrite a repository under another ownerchange-repository-owner
from giturlparse import parse
remote = parse('https://github.com/old-org/example.git')
remote.owner = 'new-org'
print(remote.url)
# https://github.com/new-org/example.gitThe owner setter updates the stored URL using the current protocol; it does not rename or verify anything on the remote host.
Drop credentials before loggingstrip-embedded-credentials
from giturlparse import parse
remote = parse('https://build-user:secret@github.com/octo/example.git')
if not remote.valid:
raise ValueError('unsupported URL')
safe_url = remote.url2https
print(safe_url)The original url, username, access_token, and data attributes still contain or expose the secret. Do not log the result object itself.
Create a comparison keybuild-repository-key
from giturlparse import parse
def repository_key(url: str) -> tuple[str, str, str]:
remote = parse(url)
if not remote.valid:
raise ValueError('unsupported Git URL')
return remote.host.lower(), remote.owner.lower(), remote.repo.removesuffix('.git').lower()Case sensitivity varies by hosting service. Lowercasing is suitable only if your allowed providers treat repository identities case-insensitively.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| git-url-parse | PyPI | You want a similarly focused parser and are willing to compare its provider coverage and result model |
| GitPython | PyPI | You need to inspect real repositories and configured remotes, not just parse URL text |
| dulwich | PyPI | You need a pure-Python Git implementation that can validate or operate on repositories as well as their remotes |