From 0364586a43f4748d1fa7e9e739f4a36b5264ab87 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Sangi Date: Sun, 11 Dec 2016 06:47:37 +0530 Subject: [PATCH] GitCommitBear.py: Require URL in commit body This ensures that the git commit contains a URL that relates to an issue. Fixes https://github.com/coala/coala-bears/issues/1112 --- .coafile | 1 + bears/vcs/git/GitCommitBear.py | 40 +++++++++++++++++ tests/vcs/git/GitCommitBearTest.py | 70 ++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+) diff --git a/.coafile b/.coafile index c7ea2eff8d..341cdbc70d 100644 --- a/.coafile +++ b/.coafile @@ -29,6 +29,7 @@ bears = SpaceConsistencyBear [commit] bears = GitCommitBear +host_site = github shortlog_trailing_period = False shortlog_regex = ([^:]*|[^:]+: [A-Z0-9*].*) diff --git a/bears/vcs/git/GitCommitBear.py b/bears/vcs/git/GitCommitBear.py index 9279be4265..ec1d5623fc 100644 --- a/bears/vcs/git/GitCommitBear.py +++ b/bears/vcs/git/GitCommitBear.py @@ -3,6 +3,8 @@ import shutil import os +from urllib.parse import urlparse + from coalib.bears.GlobalBear import GlobalBear from coalib.bears.requirements.PipRequirement import PipRequirement from coala_utils.ContextManagers import change_directory @@ -21,6 +23,9 @@ class GitCommitBear(GlobalBear): LICENSE = 'AGPL-3.0' ASCIINEMA_URL = 'https://asciinema.org/a/e146c9739ojhr8396wedsvf0d' CAN_DETECT = {'Formatting'} + GITHUB_KEYS = {'close', 'closes', 'closed', 'resolve', + 'resolves', 'resolved', 'fix', 'fixes', 'fixed'} + GITLAB_KEYS = GITHUB_KEYS.union({'closing', 'resolving', 'fixing'}) @classmethod def check_prerequisites(cls): @@ -175,6 +180,7 @@ def check_imperative(self, paragraph): def check_body(self, body, body_line_length: int=72, force_body: bool=False, + host_site: str=None, ignore_length_regex: typed_list(str)=()): """ Checks the given commit body. @@ -183,6 +189,10 @@ def check_body(self, body, :param body_line_length: The maximum line-length of the body. The newline character at each line end does not count to the length. + :param host_site: The website used for hosting the git + repository. Choosing this value checks the + presence of full URLs in issue related + commits. :param force_body: Whether a body shall exist or not. :param ignore_length_regex: Lines matching each of the regular expressions in this list will be ignored. @@ -197,6 +207,36 @@ def check_body(self, body, 'HEAD commit. Please add one.') return + if host_site: + body_string = '\n'.join(body).lower() + keys = set() + + if 'github' in host_site.lower(): + keys = self.GITHUB_KEYS + elif 'gitlab' in host_site.lower(): + keys = self.GITLAB_KEYS + + if any(key in body_string for key in keys): + urls = re.findall(r'(https?://\S+)', body_string) + good_url = False + for url in urls: + (scheme, netloc, path) = urlparse(url)[:3] + try: + if all([scheme, netloc, path]): + if host_site in netloc and '/issues/' in path: + int(path.split('/')[-1]) + good_url = True + except ValueError: + yield Result(self, 'Invalid issue number present ' + 'in the body at HEAD commit.') + return + + if not good_url: + yield Result(self, 'No issue related URL found in ' + 'the body at HEAD commit. ' + 'Please add one.') + return + ignore_regexes = [re.compile(regex) for regex in ignore_length_regex] if any((len(line) > body_line_length and not any(regex.search(line) for regex in ignore_regexes)) diff --git a/tests/vcs/git/GitCommitBearTest.py b/tests/vcs/git/GitCommitBearTest.py index 06c52bf245..867afab5b1 100644 --- a/tests/vcs/git/GitCommitBearTest.py +++ b/tests/vcs/git/GitCommitBearTest.py @@ -267,6 +267,76 @@ def test_body_checks(self): []) self.assertTrue(self.msg_queue.empty()) + # Commit has neither keywords nor issues + self.git_commit('Shortlog\n\n' + 'This line is ok.\n' + 'This line is by far too long (in this case).\n' + 'This one too, blablablablablablablablabla.') + self.assertEqual(self.run_uut(host_site='github',), []) + + # GitHub chosen and has an issue + self.git_commit('Shortlog\n\n' + 'First line, blablablablablabla.\n' + 'Another line, blablablablablabla.\n' + 'Fix https://github.com/user/repo/issues/1234') + self.assertEqual(self.run_uut(host_site='github',), []) + + # GitLab chosen and has an issue + self.git_commit('Shortlog\n\n' + 'First line, blablablablablabla.\n' + 'Another line, blablablablablabla.\n' + 'Closing https://gitlab.com/user/repo/issues/100') + self.assertEqual(self.run_uut(host_site='gitlab',), []) + + # Invalid issue number in URL + self.git_commit('Shortlog\n\n' + 'First line, blablablablablabla.\n' + 'Another line, blablablablablabla.\n' + 'Closing https://gitlab.com/user/repo/issues/1af121') + self.assertEqual(self.run_uut(host_site='gitlab',), + ['Invalid issue number present in the body ' + 'at HEAD commit.']) + self.assert_no_msgs() + + # GitHub chosen and has keywords but no url + self.git_commit('Shortlog\n\n' + 'First line, blablablablablabla.\n' + 'Another line, blablablablablabla.\n' + 'Fix #123') + self.assertEqual(self.run_uut(host_site='github',), + ['No issue related URL found in ' + 'the body at HEAD commit. ' + 'Please add one.']) + self.assert_no_msgs() + + # Git Host with no support + self.git_commit('Shortlog\n\n' + 'First line, blablablablablabla.\n' + 'Another line, blablablablablabla.\n' + 'Fix #123') + self.assertEqual(self.run_uut(host_site='bitbucket',), + []) + + # Has an invalid URL + self.git_commit('Shortlog\n\n' + 'First line, blablablablablabla.\n' + 'Another line, blablablablablabla.\n' + 'Fix http://google.com') + self.assertEqual(self.run_uut(host_site='github',), + ['No issue related URL found in ' + 'the body at HEAD commit. ' + 'Please add one.']) + + # Has a URL but not issue related + self.git_commit('Shortlog\n\n' + 'First line, blablablablablabla.\n' + 'Another line, blablablablablabla.\n' + 'Fix https://gitlab.com/user/repo') + self.assertEqual(self.run_uut(host_site='github',), + ['No issue related URL found in ' + 'the body at HEAD commit. ' + 'Please add one.']) + def test_different_path(self): no_git_dir = mkdtemp() self.git_commit('Add a very long shortlog for a bad project history.')