Repository: coala/coala-bears

Issue:
GitCommitBear/body_regex: Use re.fullmatch instead of re.search?
I'm for using `re.fullmatch` to match the whole body of the commit. Currently you can work with anchors, but with the restriction that they work only on a single line, as `re.MULTILINE` is not activated for `body_regex`. Like for `shortlog_regex`, I would suggest to use `fullmatch` instead of `search`.

CC @nkprince007 


Discussion:
I do agree to this.


Resolving patch:
diff --git a/bears/vcs/git/GitCommitBear.py b/bears/vcs/git/GitCommitBear.py
--- a/bears/vcs/git/GitCommitBear.py
+++ b/bears/vcs/git/GitCommitBear.py
@@ -241,7 +241,7 @@ def check_body(self, body,
                                'HEAD commit. Please add one.')
             return
 
-        if body_regex and not re.search(body_regex, body):
+        if body_regex and not re.fullmatch(body_regex, body.strip()):
             yield Result(self, 'No match found in commit message for the '
                                'regular expression provided: %s' % body_regex)
 
@@ -257,21 +257,21 @@ def check_body(self, body,
     def check_issue_reference(self, body,
                               body_close_issue: bool=False,
                               body_close_issue_full_url: bool=False,
-                              body_close_issue_on_last_line: bool=False):
+                              body_close_issue_on_last_line: bool=False,
+                              body_enforce_issue_reference: bool=False):
         """
         Check for matching issue related references and URLs.
 
         :param body:
             Body of the commit message of HEAD.
         :param body_close_issue:
-            Whether to check for the presence of issue reference within
-            the commit body by retrieving host information from git
-            configuration. GitHub and GitLab support auto closing issues with
-            commit messages. Checks for matching keywords in the commit body.
-            By default, if none of ``body_close_issue_full_url`` and
-            ``body_close_issue_on_last_line`` are enabled, checks for presence
-            of short references like ``closes #213``. Otherwise behaves
-            according to other chosen flags.
+            GitHub and GitLab support auto closing issues with
+            commit messages. When enabled, this checks for matching keywords
+            in the commit body by retrieving host information from git
+            configuration. By default, if none of ``body_close_issue_full_url``
+            and ``body_close_issue_on_last_line`` are enabled, this checks for
+            presence of short references like ``closes #213``.
+            Otherwise behaves according to other chosen flags.
             More on keywords follows.
             [GitHub](https://help.github.com/articles/closing-issues-via-commit-messages/)
             [GitLab](https://docs.gitlab.com/ce/user/project/issues/automatic_issue_closing.html)
@@ -282,6 +282,9 @@ def check_issue_reference(self, body,
             When enabled, checks for issue close reference presence on the
             last line of the commit body. Works along with
             ``body_close_issue``.
+        :param body_enforce_issue_reference:
+            Whether to enforce presence of issue reference in the body of
+            commit message.
         """
         if not body_close_issue:
             return
@@ -304,7 +307,7 @@ def check_issue_reference(self, body,
                 r'https?://{}\S+/issues/(\S+)'.format(re.escape(host)))
         else:
             result_info = 'issue'
-            issue_ref_regex = r'#(\S+)'
+            issue_ref_regex = r'(?:\w+/\w+)?#(\S+)'
 
         concat_regex = '|'.join(kw for kw in self.CONCATENATION_KEYWORDS)
         compiled_joint_regex = re.compile(
@@ -326,7 +329,7 @@ def check_issue_reference(self, body,
 
         matches = compiled_joint_regex.findall(body)
 
-        if len(matches) == 0:
+        if body_enforce_issue_reference and len(matches) == 0:
             yield Result(self, result_message.format(result_info))
             return
 


Test patch:
diff --git a/tests/vcs/git/GitCommitBearTest.py b/tests/vcs/git/GitCommitBearTest.py
--- a/tests/vcs/git/GitCommitBearTest.py
+++ b/tests/vcs/git/GitCommitBearTest.py
@@ -269,24 +269,23 @@ def test_body_checks(self):
                          [])
         self.assertTrue(self.msg_queue.empty())
 
-        # Testing body_regex
+        # body_regex, not fully matched
         self.git_commit('Shortlog\n\n'
                         'First line, blablablablablabla.\n'
                         'Another line, blablablablablabla.\n'
                         'Fix 1112')
         self.assertEqual(self.run_uut(
-                             body_regex=r'Fix\s+[1-9][0-9]*\s*'), [])
+                             body_regex=r'Fix\s+[1-9][0-9]*\s*'),
+                         ['No match found in commit message for the regular '
+                          'expression provided: Fix\s+[1-9][0-9]*\s*'])
         self.assert_no_msgs()
 
-        # Matching with regexp, no match found
+        # Matching with regexp, fully matched
         self.git_commit('Shortlog\n\n'
-                        'First line, blablablablablabla.\n'
-                        'Another line, blablablablablabla.\n'
-                        'Closes #01112')
+                        'TICKER\n'
+                        'CLOSE 2017')
         self.assertEqual(self.run_uut(
-                             body_regex=r'Closes\s+#[1-9][0-9]*\s*'),
-                         ['No match found in commit message for the regular '
-                          'expression provided: Closes\s+#[1-9][0-9]*\s*'])
+                             body_regex=r'TICKER\s*CLOSE\s+[1-9][0-9]*'), [])
         self.assert_no_msgs()
 
     def test_check_issue_reference(self):
@@ -329,9 +328,10 @@ def test_check_issue_reference(self):
                         '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(body_close_issue=True,),
-                         ['Body of HEAD commit does not contain any issue '
-                          'reference.'])
+        self.assertEqual(self.run_uut(
+                             body_close_issue=True,
+                             body_close_issue_full_url=True,
+                             body_close_issue_last_line=True), [])
         self.assert_no_msgs()
 
         # Has keyword but no valid issue URL
@@ -432,6 +432,23 @@ def test_check_issue_reference(self):
                          ['Invalid issue number: #notnum'])
         self.assert_no_msgs()
 
+        # Close issues in other repos
+        self.git_commit('Shortlog\n\n'
+                        'First line, blablablablablabla.\n'
+                        'Another line, blablablablablabla.\n'
+                        'Resolve #11 and close github/gitter#32')
+        self.assertEqual(self.run_uut(body_close_issue=True,), [])
+        self.assert_no_msgs()
+
+        # Incorrect close issue other repo pattern
+        self.git_commit('Shortlog\n\n'
+                        'First line, blablablablablabla.\n'
+                        'Another line, blablablablablabla.\n'
+                        'Fix #11 and close github#32')
+        self.assertEqual(self.run_uut(body_close_issue=True,),
+                         ['Invalid issue reference: github#32'])
+        self.assert_no_msgs()
+
         # Last line enforce full URL
         self.git_commit('Shortlog\n\n'
                         'First line, blablablablablabla.\n'
@@ -440,7 +457,8 @@ def test_check_issue_reference(self):
         self.assertEqual(self.run_uut(
                              body_close_issue=True,
                              body_close_issue_full_url=True,
-                             body_close_issue_on_last_line=True),
+                             body_close_issue_on_last_line=True,
+                             body_enforce_issue_reference=True),
                          ['Body of HEAD commit does not contain any full issue'
                           ' reference in the last line.'])
         self.assert_no_msgs()