diff --git a/tests/test_git.py b/tests/test_git.py new file mode 100644 index 00000000..3d51e3f7 --- /dev/null +++ b/tests/test_git.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +import pytest + +from validators import url, ValidationFailure + + +@pytest.mark.parametrize('address', [ + u'git@bitbucket.org:username/reponame.git', + u'https://username@bitbucket.org/otherusername/reponame.git', + u'https://github.com/username/reponame.git', + u'git@github.com:username/reponame.git', + u'git@gitlab.com:groupname/reponame.git', + u'https://gitlab.com/groupname/reponame.git', + u'git@bitbucket.org:workspace/reponame.git', + u'https://username@bitbucket.org/workspace/reponame.git', + u'https://bitbucket.org/workspace/reponame.git', + u'git@gitlab.com:username/reponame.git', + u'https://gitlab.com/username/reponame.git', + u'git://github.com/somthing/somthing.git#ff786f9f', + u'git://github.com/somthing/somthing.git#gh-pages', + u'git://github.com/somthing/somthing.git#master', + u'git://github.com/somthing/somthing.git#Quick-Fix', + u'git://github.com/somthing/somthing.git#quick_fix', + u'git://github.com/somthing/somthing.git#v0.1.0', + u'git://host.xz/path/to/repo.git/', + u'git://host.xz/~user/path/to/repo.git/', + u'git@192.168.101.127:user/project.git', + u'git@github.com:user/project.git', + u'git@github.com:user/some-project.git', + u'git@github.com:user/some-project.git', + u'git@github.com:user/some_project.git', + u'git@github.com:user/some_project.git', + u'http://github.com/user/project.git', + u'http://host.xz/path/to/repo.git/', + u'https://github.com/user/project.git', + u'https://host.xz/path/to/repo.git/', + u'https://username::;*%$:@github.com/username/repository.git', + u'https://username:$fooABC@:@github.com/username/repository.git', + u'https://username:password@github.com/username/repository.git', + u'ssh://host.xz/path/to/repo.git/', + u'ssh://host.xz/path/to/repo.git/', + u'ssh://host.xz/~/path/to/repo.git', + u'ssh://host.xz/~user/path/to/repo.git/', + u'ssh://user@host.xz/path/to/repo.git/', + u'ssh://user@host.xz/path/to/repo.git/', + u'ssh://user@host.xz/~/path/to/repo.git', + u'ssh://user@host.xz/~user/path/to/repo.git/', + u'ssh://user@host.xz:port/path/to/repo.git/', + u'https://username@bitbucket.org:8080/otherusername/reponame.git', + u'ssh://host.xz:port/path/to/repo.git/', + u'ssh://host.xz:1234/path/to/repo.git/', + u'https://192.168.1.127/user/project.git', + u'http://192.168.1.127/user/project.git', + u'http://192.168.1.127:port/user/project.git', +]) +def test_returns_true_on_valid_url(address): + assert url(address) + + +@pytest.mark.parametrize('address', [ + u'git@bitbucket.org:username/reponame', + u'https://username@bitbucket.org/otherusername/reponame', + u'https://github.com/username/reponame', + u'git@github.com:username/reponame', + u'git@gitlab.com:groupname/reponame', + u'https://gitlab.com/groupname/reponame', + u'git@bitbucket.org:workspace/reponame', + u'https://username@bitbucket.org/workspace/reponame', + u'https://bitbucket.org/workspace/reponame', + u'git@gitlab.com:username/reponame', + u'https://gitlab.com/username/reponame', + u'git://host.xz/path/to/repo', + u'git://host.xz/~user/path/to/repo.', + u'git@192.168.101.127:user/project', + u'git@github.com:user/project', + u'git@github.com:user/some-project', + u'git@github.com:user/some-project', + u'git@github.com:user/some_project', + u'git@github.com:user/some_project', + u'http://github.com/user/project', + u'http://host.xz/path/to/repo.', + u'https://github.com/user/project', + u'https://host.xz/path/to/repo', + u'https://username::;*%$:@github.com/username/repository', + u'https://username:$fooABC@:@github.com/username/repository', + u'https://username:password@github.com/username/repository', + u'ssh://host.xz/path/to/repo', + u'ssh://host.xz/path/to/repo', + u'ssh://host.xz/~/path/to/rep', + u'ssh://host.xz/~user/path/to/repo', + u'ssh://user@host.xz/path/to/repo', + u'ssh://user@host.xz/path/to/repo', + u'ssh://user@host.xz/~/path/to/repo', + u'ssh://user@host.xz/~user/path/to/repo', + u'ssh://user@host.xz:port/path/to/repo', + u'https://username@bitbucket.org:8080/otherusername/reponame', + u'ssh://host.xz:port/path/to/repo', + u'ssh://host.xz:1234/path/to/repo', + u'https://192.168.1.127/user/project', + u'http://192.168.1.127/user/project', + u'http://192.168.1.127:port/user/project', +]) +def test_returns_true_on_valid_loose_url(address): + assert url(address, strict=False) + + +@pytest.mark.parametrize('address', 'strict', [ + u'https://www.github.com', + u'git@bitbucket.org:username/repo name', + u'git@bitbucket.org:username/repo name.git', + u'https://gitlab.com/username/reponame.git -b branch', + u'https://gitlab.com/username/reponame.git --flag', + u'https://gitlab.com/username/reponame.git \\-f', + u'/path/to/repo.git/', + u'file:///path/to/repo.git/', + u'file://~/path/to/repo.git/', + u'git@github.com:user/some_project.git/foo', + u'git@github.com:user/some_project.gitfoo', + u'host.xz:/path/to/repo.git/', + u'host.xz:path/to/repo.git', + u'host.xz:path/to/repo', + u'host.xz:~user/path/to/repo.git/', + u'path/to/repo.git/', + u'rsync://host.xz/path/to/repo.git/', + u'user@host.xz:/path/to/repo.git/', + u'user@host.xz:path/to/repo.git', + u'user@host.xz:~user/path/to/repo.git/', + u'~/path/to/repo.git', + u'git@bitbucket.org:username/reponame.gi', +]) +def test_returns_failed_loose_validation_on_invalid_url(address): + assert isinstance(url(address), ValidationFailure) + + +@pytest.mark.parametrize('address', [ + u'https://www.github.com', + u'git@bitbucket.org:username/"repo name".git', + u'https://gitlab.com/username/"reponame".git', + u'https://gitlab.com/username/\'reponame\'.git', + u'git@bitbucket.org:username/repo name', + u'git@bitbucket.org:username/repo name.git', + u'https://gitlab.com/username/reponame.git -b branch', + u'https://gitlab.com/username/reponame.git --flag', + u'https://gitlab.com/username/reponame.git \\-f', + u'/path/to/repo.git/', + u'file:///path/to/repo.git/', + u'file://~/path/to/repo.git/', + u'git@github.com:user/some_project.git/foo', + u'git@github.com:user/some_project.gitfoo', + u'host.xz:/path/to/repo.git/', + u'host.xz:path/to/repo.git', + u'host.xz:path/to/repo', + u'host.xz:~user/path/to/repo.git/', + u'path/to/repo.git/', + u'rsync://host.xz/path/to/repo.git/', + u'user@host.xz:/path/to/repo.git/', + u'user@host.xz:path/to/repo.git', + u'user@host.xz:~user/path/to/repo.git/', + u'~/path/to/repo.git', + u'git@bitbucket.org:username/reponame.gi', +]) +def test_returns_failed_strict_validation_on_invalid_url(address): + assert isinstance(url(address, strict=True), ValidationFailure) diff --git a/validators/git.py b/validators/git.py new file mode 100644 index 00000000..70a42468 --- /dev/null +++ b/validators/git.py @@ -0,0 +1,70 @@ +import re + +from .utils import validator + +""" +sources: +https://github.com/git/git/blob/master/url.c +https://git-scm.com/docs/git-clone +https://stackoverflow.com/questions/23976019/how-to-verify-valid-format-of-url-as-a-git-repo/60000569#comment111902087_60000569 +https://github.com/jonschlinkert/is-git-url +""" +url_regex = re.compile( + # protocol + # either prot:// (protocol captured as group 1) or git@serv (server captured as group 2) + r"(?:(git|ssh|https?)|git@([A-Za-z0-9][A-Za-z0-9\+\.\-_]+)):(?:\/\/)?" + # username and maybe password, if either provided (both are captured as group 3) + r"(?:(\S*)@)?" + # the actual targeted URL as per STD66 (RFC3986) [A-Za-z][A-Za-z0-9+.-]* + # with some added leniency for IPs and allowed URI special chars (_~/:) in order to capture full path+ports + # (entire URI captured as group 4) + r"([A-Za-z0-9][A-Za-z0-9\+\.\-_\/\~\:]*?)" + # .git matched if provided + r"(?:\.git)" + # ending '/' OR branch name/commit id (captured as group 5) + r"(\/?|\#[-\d\w._]+?)", + re.UNICODE | re.IGNORECASE +) + + +@validator +def git(value: str, strict: bool = False): + """ + Return whether a given value is a valid git URL. + + If the value is valid git URL this function returns ``True``, otherwise + :class:`~validators.utils.ValidationFailure`. + + Examples:: + + >>> git('git@github.org:username/reponame.git') + True + + >>> git('git@github.com:username/reponame') + True + + >>> git('http://192.168.1.127/user/project.git') + True + + >>> git('git://bitbucket.org/org/repo.git#ff786f9f') + True + + >>> git('https://www.github.com') + ValidationFailure(func=git, ...) + + >>> git('git@github.com:username/reponame', strict=True) + ValidationFailure(func=git, ...) + + :param value: URL address string to validate + :param strict: (default=False) enfocre URL end with .git (if false, upon validation failure '.git; is added + to the end if the procided value and validation retried) + """ + result = url_regex.match(value) + + if result or strict: + return result + else: # not strict and failed to match + # try adding ".git" at end of value and retry + value = value[:-1] if value.endswith('/') else value + result = url_regex.match(value + ".git") + return result