##// END OF EJS Templates
check-commit: disallow capitalization only right after topic...
Martin von Zweigbergk -
r40988:811f772b default
parent child Browse files
Show More
@@ -1,109 +1,109
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # Copyright 2014 Matt Mackall <mpm@selenic.com>
3 # Copyright 2014 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # A tool/hook to run basic sanity checks on commits/patches for
5 # A tool/hook to run basic sanity checks on commits/patches for
6 # submission to Mercurial. Install by adding the following to your
6 # submission to Mercurial. Install by adding the following to your
7 # .hg/hgrc:
7 # .hg/hgrc:
8 #
8 #
9 # [hooks]
9 # [hooks]
10 # pretxncommit = contrib/check-commit
10 # pretxncommit = contrib/check-commit
11 #
11 #
12 # The hook can be temporarily bypassed with:
12 # The hook can be temporarily bypassed with:
13 #
13 #
14 # $ BYPASS= hg commit
14 # $ BYPASS= hg commit
15 #
15 #
16 # See also: https://mercurial-scm.org/wiki/ContributingChanges
16 # See also: https://mercurial-scm.org/wiki/ContributingChanges
17
17
18 from __future__ import absolute_import, print_function
18 from __future__ import absolute_import, print_function
19
19
20 import os
20 import os
21 import re
21 import re
22 import sys
22 import sys
23
23
24 commitheader = r"^(?:# [^\n]*\n)*"
24 commitheader = r"^(?:# [^\n]*\n)*"
25 afterheader = commitheader + r"(?!#)"
25 afterheader = commitheader + r"(?!#)"
26 beforepatch = afterheader + r"(?!\n(?!@@))"
26 beforepatch = afterheader + r"(?!\n(?!@@))"
27
27
28 errors = [
28 errors = [
29 (beforepatch + r".*[(]bc[)]", "(BC) needs to be uppercase"),
29 (beforepatch + r".*[(]bc[)]", "(BC) needs to be uppercase"),
30 (beforepatch + r".*[(]issue \d\d\d",
30 (beforepatch + r".*[(]issue \d\d\d",
31 "no space allowed between issue and number"),
31 "no space allowed between issue and number"),
32 (beforepatch + r".*[(]bug(\d|\s)", "use (issueDDDD) instead of bug"),
32 (beforepatch + r".*[(]bug(\d|\s)", "use (issueDDDD) instead of bug"),
33 (commitheader + r"# User [^@\n]+\n", "username is not an email address"),
33 (commitheader + r"# User [^@\n]+\n", "username is not an email address"),
34 (commitheader + r"(?!merge with )[^#]\S+[^:] ",
34 (commitheader + r"(?!merge with )[^#]\S+[^:] ",
35 "summary line doesn't start with 'topic: '"),
35 "summary line doesn't start with 'topic: '"),
36 (afterheader + r"[A-Z][a-z]\S+", "don't capitalize summary lines"),
36 (afterheader + r"[A-Z][a-z]\S+", "don't capitalize summary lines"),
37 (afterheader + r"[^\n]*: *[A-Z][a-z]\S+", "don't capitalize summary lines"),
37 (afterheader + r"^\S+: *[A-Z][a-z]\S+", "don't capitalize summary lines"),
38 (afterheader + r"\S*[^A-Za-z0-9-_]\S*: ",
38 (afterheader + r"\S*[^A-Za-z0-9-_]\S*: ",
39 "summary keyword should be most user-relevant one-word command or topic"),
39 "summary keyword should be most user-relevant one-word command or topic"),
40 (afterheader + r".*\.\s*\n", "don't add trailing period on summary line"),
40 (afterheader + r".*\.\s*\n", "don't add trailing period on summary line"),
41 (afterheader + r".{79,}", "summary line too long (limit is 78)"),
41 (afterheader + r".{79,}", "summary line too long (limit is 78)"),
42 # Forbid "_" in function name.
42 # Forbid "_" in function name.
43 #
43 #
44 # We skip the check for cffi related functions. They use names mapping the
44 # We skip the check for cffi related functions. They use names mapping the
45 # name of the C function. C function names may contain "_".
45 # name of the C function. C function names may contain "_".
46 (r"\n\+[ \t]+def (?!cffi)[a-z]+_[a-z]",
46 (r"\n\+[ \t]+def (?!cffi)[a-z]+_[a-z]",
47 "adds a function with foo_bar naming"),
47 "adds a function with foo_bar naming"),
48 ]
48 ]
49
49
50 word = re.compile('\S')
50 word = re.compile('\S')
51 def nonempty(first, second):
51 def nonempty(first, second):
52 if word.search(first):
52 if word.search(first):
53 return first
53 return first
54 return second
54 return second
55
55
56 def checkcommit(commit, node=None):
56 def checkcommit(commit, node=None):
57 exitcode = 0
57 exitcode = 0
58 printed = node is None
58 printed = node is None
59 hits = []
59 hits = []
60 signtag = (afterheader +
60 signtag = (afterheader +
61 r'Added (tag [^ ]+|signature) for changeset [a-f0-9]{12}')
61 r'Added (tag [^ ]+|signature) for changeset [a-f0-9]{12}')
62 if re.search(signtag, commit):
62 if re.search(signtag, commit):
63 return 0
63 return 0
64 for exp, msg in errors:
64 for exp, msg in errors:
65 for m in re.finditer(exp, commit):
65 for m in re.finditer(exp, commit):
66 end = m.end()
66 end = m.end()
67 trailing = re.search(r'(\\n)+$', exp)
67 trailing = re.search(r'(\\n)+$', exp)
68 if trailing:
68 if trailing:
69 end -= len(trailing.group()) / 2
69 end -= len(trailing.group()) / 2
70 hits.append((end, exp, msg))
70 hits.append((end, exp, msg))
71 if hits:
71 if hits:
72 hits.sort()
72 hits.sort()
73 pos = 0
73 pos = 0
74 last = ''
74 last = ''
75 for n, l in enumerate(commit.splitlines(True)):
75 for n, l in enumerate(commit.splitlines(True)):
76 pos += len(l)
76 pos += len(l)
77 while len(hits):
77 while len(hits):
78 end, exp, msg = hits[0]
78 end, exp, msg = hits[0]
79 if pos < end:
79 if pos < end:
80 break
80 break
81 if not printed:
81 if not printed:
82 printed = True
82 printed = True
83 print("node: %s" % node)
83 print("node: %s" % node)
84 print("%d: %s" % (n, msg))
84 print("%d: %s" % (n, msg))
85 print(" %s" % nonempty(l, last)[:-1])
85 print(" %s" % nonempty(l, last)[:-1])
86 if "BYPASS" not in os.environ:
86 if "BYPASS" not in os.environ:
87 exitcode = 1
87 exitcode = 1
88 del hits[0]
88 del hits[0]
89 last = nonempty(l, last)
89 last = nonempty(l, last)
90
90
91 return exitcode
91 return exitcode
92
92
93 def readcommit(node):
93 def readcommit(node):
94 return os.popen("hg export %s" % node).read()
94 return os.popen("hg export %s" % node).read()
95
95
96 if __name__ == "__main__":
96 if __name__ == "__main__":
97 exitcode = 0
97 exitcode = 0
98 node = os.environ.get("HG_NODE")
98 node = os.environ.get("HG_NODE")
99
99
100 if node:
100 if node:
101 commit = readcommit(node)
101 commit = readcommit(node)
102 exitcode = checkcommit(commit)
102 exitcode = checkcommit(commit)
103 elif sys.argv[1:]:
103 elif sys.argv[1:]:
104 for node in sys.argv[1:]:
104 for node in sys.argv[1:]:
105 exitcode |= checkcommit(readcommit(node), node)
105 exitcode |= checkcommit(readcommit(node), node)
106 else:
106 else:
107 commit = sys.stdin.read()
107 commit = sys.stdin.read()
108 exitcode = checkcommit(commit)
108 exitcode = checkcommit(commit)
109 sys.exit(exitcode)
109 sys.exit(exitcode)
General Comments 0
You need to be logged in to leave comments. Login now