##// END OF EJS Templates
check-commit: check for double-addition of blank lines...
Matt Mackall -
r28013:e529b5f1 default
parent child Browse files
Show More
@@ -1,97 +1,97
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 import re, sys, os
18 import re, sys, os
19
19
20 commitheader = r"^(?:# [^\n]*\n)*"
20 commitheader = r"^(?:# [^\n]*\n)*"
21 afterheader = commitheader + r"(?!#)"
21 afterheader = commitheader + r"(?!#)"
22 beforepatch = afterheader + r"(?!\n(?!@@))"
22 beforepatch = afterheader + r"(?!\n(?!@@))"
23
23
24 errors = [
24 errors = [
25 (beforepatch + r".*[(]bc[)]", "(BC) needs to be uppercase"),
25 (beforepatch + r".*[(]bc[)]", "(BC) needs to be uppercase"),
26 (beforepatch + r".*[(]issue \d\d\d", "no space allowed between issue and number"),
26 (beforepatch + r".*[(]issue \d\d\d", "no space allowed between issue and number"),
27 (beforepatch + r".*[(]bug(\d|\s)", "use (issueDDDD) instead of bug"),
27 (beforepatch + r".*[(]bug(\d|\s)", "use (issueDDDD) instead of bug"),
28 (commitheader + r"# User [^@\n]+\n", "username is not an email address"),
28 (commitheader + r"# User [^@\n]+\n", "username is not an email address"),
29 (commitheader + r"(?!merge with )[^#]\S+[^:] ",
29 (commitheader + r"(?!merge with )[^#]\S+[^:] ",
30 "summary line doesn't start with 'topic: '"),
30 "summary line doesn't start with 'topic: '"),
31 (afterheader + r"[A-Z][a-z]\S+", "don't capitalize summary lines"),
31 (afterheader + r"[A-Z][a-z]\S+", "don't capitalize summary lines"),
32 (afterheader + r"[^\n]*: *[A-Z][a-z]\S+", "don't capitalize summary lines"),
32 (afterheader + r"[^\n]*: *[A-Z][a-z]\S+", "don't capitalize summary lines"),
33 (afterheader + r"\S*[^A-Za-z0-9-]\S*: ",
33 (afterheader + r"\S*[^A-Za-z0-9-]\S*: ",
34 "summary keyword should be most user-relevant one-word command or topic"),
34 "summary keyword should be most user-relevant one-word command or topic"),
35 (afterheader + r".*\.\s*\n", "don't add trailing period on summary line"),
35 (afterheader + r".*\.\s*\n", "don't add trailing period on summary line"),
36 (afterheader + r".{79,}", "summary line too long (limit is 78)"),
36 (afterheader + r".{79,}", "summary line too long (limit is 78)"),
37 (r"\n\+\n \n", "adds double empty line"),
37 (r"\n\+\n( |\+)\n", "adds double empty line"),
38 (r"\n \n\+\n", "adds double empty line"),
38 (r"\n \n\+\n", "adds double empty line"),
39 (r"\n\+[ \t]+def [a-z]+_[a-z]", "adds a function with foo_bar naming"),
39 (r"\n\+[ \t]+def [a-z]+_[a-z]", "adds a function with foo_bar naming"),
40 ]
40 ]
41
41
42 word = re.compile('\S')
42 word = re.compile('\S')
43 def nonempty(first, second):
43 def nonempty(first, second):
44 if word.search(first):
44 if word.search(first):
45 return first
45 return first
46 return second
46 return second
47
47
48 def checkcommit(commit, node = None):
48 def checkcommit(commit, node = None):
49 exitcode = 0
49 exitcode = 0
50 printed = node is None
50 printed = node is None
51 hits = []
51 hits = []
52 for exp, msg in errors:
52 for exp, msg in errors:
53 for m in re.finditer(exp, commit):
53 for m in re.finditer(exp, commit):
54 end = m.end()
54 end = m.end()
55 trailing = re.search(r'(\\n)+$', exp)
55 trailing = re.search(r'(\\n)+$', exp)
56 if trailing:
56 if trailing:
57 end -= len(trailing.group()) / 2
57 end -= len(trailing.group()) / 2
58 hits.append((end, exp, msg))
58 hits.append((end, exp, msg))
59 if hits:
59 if hits:
60 hits.sort()
60 hits.sort()
61 pos = 0
61 pos = 0
62 last = ''
62 last = ''
63 for n, l in enumerate(commit.splitlines(True)):
63 for n, l in enumerate(commit.splitlines(True)):
64 pos += len(l)
64 pos += len(l)
65 while len(hits):
65 while len(hits):
66 end, exp, msg = hits[0]
66 end, exp, msg = hits[0]
67 if pos < end:
67 if pos < end:
68 break
68 break
69 if not printed:
69 if not printed:
70 printed = True
70 printed = True
71 print "node: %s" % node
71 print "node: %s" % node
72 print "%d: %s" % (n, msg)
72 print "%d: %s" % (n, msg)
73 print " %s" % nonempty(l, last)[:-1]
73 print " %s" % nonempty(l, last)[:-1]
74 if "BYPASS" not in os.environ:
74 if "BYPASS" not in os.environ:
75 exitcode = 1
75 exitcode = 1
76 del hits[0]
76 del hits[0]
77 last = nonempty(l, last)
77 last = nonempty(l, last)
78
78
79 return exitcode
79 return exitcode
80
80
81 def readcommit(node):
81 def readcommit(node):
82 return os.popen("hg export %s" % node).read()
82 return os.popen("hg export %s" % node).read()
83
83
84 if __name__ == "__main__":
84 if __name__ == "__main__":
85 exitcode = 0
85 exitcode = 0
86 node = os.environ.get("HG_NODE")
86 node = os.environ.get("HG_NODE")
87
87
88 if node:
88 if node:
89 commit = readcommit(node)
89 commit = readcommit(node)
90 exitcode = checkcommit(commit)
90 exitcode = checkcommit(commit)
91 elif sys.argv[1:]:
91 elif sys.argv[1:]:
92 for node in sys.argv[1:]:
92 for node in sys.argv[1:]:
93 exitcode |= checkcommit(readcommit(node), node)
93 exitcode |= checkcommit(readcommit(node), node)
94 else:
94 else:
95 commit = sys.stdin.read()
95 commit = sys.stdin.read()
96 exitcode = checkcommit(commit)
96 exitcode = checkcommit(commit)
97 sys.exit(exitcode)
97 sys.exit(exitcode)
@@ -1,111 +1,115
1 Test the 'check-commit' script
1 Test the 'check-commit' script
2 ==============================
2 ==============================
3
3
4 A fine patch:
4 A fine patch:
5
5
6 $ cat > patch-with-long-header.diff << EOF
6 $ cat > patch-with-long-header.diff << EOF
7 > # HG changeset patch
7 > # HG changeset patch
8 > # User timeless <timeless@mozdev.org>
8 > # User timeless <timeless@mozdev.org>
9 > # Date 1448911706 0
9 > # Date 1448911706 0
10 > # Mon Nov 30 19:28:26 2015 +0000
10 > # Mon Nov 30 19:28:26 2015 +0000
11 > # Node ID c41cb6d2b7dbd62b1033727f8606b8c09fc4aa88
11 > # Node ID c41cb6d2b7dbd62b1033727f8606b8c09fc4aa88
12 > # Parent 42aa0e570eaa364a622bc4443b0bcb79b1100a58
12 > # Parent 42aa0e570eaa364a622bc4443b0bcb79b1100a58
13 > # ClownJoke This is a veryly long header that should not be warned about because its not the description
13 > # ClownJoke This is a veryly long header that should not be warned about because its not the description
14 > bundle2: use Oxford comma (issue123) (BC)
14 > bundle2: use Oxford comma (issue123) (BC)
15 >
15 >
16 > diff --git a/hgext/transplant.py b/hgext/transplant.py
16 > diff --git a/hgext/transplant.py b/hgext/transplant.py
17 > --- a/hgext/transplant.py
17 > --- a/hgext/transplant.py
18 > +++ b/hgext/transplant.py
18 > +++ b/hgext/transplant.py
19 > @@ -599,7 +599,7 @@
19 > @@ -599,7 +599,7 @@
20 > return
20 > return
21 > if not (opts.get('source') or revs or
21 > if not (opts.get('source') or revs or
22 > opts.get('merge') or opts.get('branch')):
22 > opts.get('merge') or opts.get('branch')):
23 > - raise error.Abort(_('no source URL, branch revision or revision '
23 > - raise error.Abort(_('no source URL, branch revision or revision '
24 > + raise error.Abort(_('no source URL, branch revision, or revision '
24 > + raise error.Abort(_('no source URL, branch revision, or revision '
25 > 'list provided'))
25 > 'list provided'))
26 > if opts.get('all'):
26 > if opts.get('all'):
27 >
27 >
28 > + def blahblah(x):
28 > + def blahblah(x):
29 > + pass
29 > + pass
30 > EOF
30 > EOF
31 $ cat patch-with-long-header.diff | $TESTDIR/../contrib/check-commit
31 $ cat patch-with-long-header.diff | $TESTDIR/../contrib/check-commit
32
32
33 A patch with lots of errors:
33 A patch with lots of errors:
34
34
35 $ cat > patch-with-long-header.diff << EOF
35 $ cat > patch-with-long-header.diff << EOF
36 > # HG changeset patch
36 > # HG changeset patch
37 > # User timeless
37 > # User timeless
38 > # Date 1448911706 0
38 > # Date 1448911706 0
39 > # Mon Nov 30 19:28:26 2015 +0000
39 > # Mon Nov 30 19:28:26 2015 +0000
40 > # Node ID c41cb6d2b7dbd62b1033727f8606b8c09fc4aa88
40 > # Node ID c41cb6d2b7dbd62b1033727f8606b8c09fc4aa88
41 > # Parent 42aa0e570eaa364a622bc4443b0bcb79b1100a58
41 > # Parent 42aa0e570eaa364a622bc4443b0bcb79b1100a58
42 > # ClownJoke This is a veryly long header that should not be warned about because its not the description
42 > # ClownJoke This is a veryly long header that should not be warned about because its not the description
43 > transplant/foo: this summary is way too long use Oxford comma (bc) (bug123) (issue 244)
43 > transplant/foo: this summary is way too long use Oxford comma (bc) (bug123) (issue 244)
44 >
44 >
45 > diff --git a/hgext/transplant.py b/hgext/transplant.py
45 > diff --git a/hgext/transplant.py b/hgext/transplant.py
46 > --- a/hgext/transplant.py
46 > --- a/hgext/transplant.py
47 > +++ b/hgext/transplant.py
47 > +++ b/hgext/transplant.py
48 > @@ -599,7 +599,7 @@
48 > @@ -599,7 +599,7 @@
49 > return
49 > return
50 > if not (opts.get('source') or revs or
50 > if not (opts.get('source') or revs or
51 > opts.get('merge') or opts.get('branch')):
51 > opts.get('merge') or opts.get('branch')):
52 > - raise error.Abort(_('no source URL, branch revision or revision '
52 > - raise error.Abort(_('no source URL, branch revision or revision '
53 > + raise error.Abort(_('no source URL, branch revision, or revision '
53 > + raise error.Abort(_('no source URL, branch revision, or revision '
54 > 'list provided'))
54 > 'list provided'))
55 > if opts.get('all'):
55 > if opts.get('all'):
56 > EOF
56 > EOF
57 $ cat patch-with-long-header.diff | $TESTDIR/../contrib/check-commit
57 $ cat patch-with-long-header.diff | $TESTDIR/../contrib/check-commit
58 1: username is not an email address
58 1: username is not an email address
59 # User timeless
59 # User timeless
60 7: summary keyword should be most user-relevant one-word command or topic
60 7: summary keyword should be most user-relevant one-word command or topic
61 transplant/foo: this summary is way too long use Oxford comma (bc) (bug123) (issue 244)
61 transplant/foo: this summary is way too long use Oxford comma (bc) (bug123) (issue 244)
62 7: (BC) needs to be uppercase
62 7: (BC) needs to be uppercase
63 transplant/foo: this summary is way too long use Oxford comma (bc) (bug123) (issue 244)
63 transplant/foo: this summary is way too long use Oxford comma (bc) (bug123) (issue 244)
64 7: use (issueDDDD) instead of bug
64 7: use (issueDDDD) instead of bug
65 transplant/foo: this summary is way too long use Oxford comma (bc) (bug123) (issue 244)
65 transplant/foo: this summary is way too long use Oxford comma (bc) (bug123) (issue 244)
66 7: no space allowed between issue and number
66 7: no space allowed between issue and number
67 transplant/foo: this summary is way too long use Oxford comma (bc) (bug123) (issue 244)
67 transplant/foo: this summary is way too long use Oxford comma (bc) (bug123) (issue 244)
68 7: summary line too long (limit is 78)
68 7: summary line too long (limit is 78)
69 transplant/foo: this summary is way too long use Oxford comma (bc) (bug123) (issue 244)
69 transplant/foo: this summary is way too long use Oxford comma (bc) (bug123) (issue 244)
70 [1]
70 [1]
71
71
72 A patch with other errors:
72 A patch with other errors:
73
73
74 $ cat > patch-with-long-header.diff << EOF
74 $ cat > patch-with-long-header.diff << EOF
75 > # HG changeset patch
75 > # HG changeset patch
76 > # User timeless
76 > # User timeless
77 > # Date 1448911706 0
77 > # Date 1448911706 0
78 > # Mon Nov 30 19:28:26 2015 +0000
78 > # Mon Nov 30 19:28:26 2015 +0000
79 > # Node ID c41cb6d2b7dbd62b1033727f8606b8c09fc4aa88
79 > # Node ID c41cb6d2b7dbd62b1033727f8606b8c09fc4aa88
80 > # Parent 42aa0e570eaa364a622bc4443b0bcb79b1100a58
80 > # Parent 42aa0e570eaa364a622bc4443b0bcb79b1100a58
81 > # ClownJoke This is a veryly long header that should not be warned about because its not the description
81 > # ClownJoke This is a veryly long header that should not be warned about because its not the description
82 > This has no topic and ends with a period.
82 > This has no topic and ends with a period.
83 >
83 >
84 > diff --git a/hgext/transplant.py b/hgext/transplant.py
84 > diff --git a/hgext/transplant.py b/hgext/transplant.py
85 > --- a/hgext/transplant.py
85 > --- a/hgext/transplant.py
86 > +++ b/hgext/transplant.py
86 > +++ b/hgext/transplant.py
87 > @@ -599,7 +599,7 @@
87 > @@ -599,7 +599,7 @@
88 > if opts.get('all'):
88 > if opts.get('all'):
89 >
89 >
90 >
91 > +
92 > + some = otherjunk
93 > +
90 > +
94 > +
91 > + def blah_blah(x):
95 > + def blah_blah(x):
92 > + pass
96 > + pass
93 > +
97 > +
94 >
98 >
95 > EOF
99 > EOF
96 $ cat patch-with-long-header.diff | $TESTDIR/../contrib/check-commit
100 $ cat patch-with-long-header.diff | $TESTDIR/../contrib/check-commit
97 1: username is not an email address
101 1: username is not an email address
98 # User timeless
102 # User timeless
99 7: don't capitalize summary lines
103 7: don't capitalize summary lines
100 This has no topic and ends with a period.
104 This has no topic and ends with a period.
101 7: summary line doesn't start with 'topic: '
105 7: summary line doesn't start with 'topic: '
102 This has no topic and ends with a period.
106 This has no topic and ends with a period.
103 7: don't add trailing period on summary line
107 7: don't add trailing period on summary line
104 This has no topic and ends with a period.
108 This has no topic and ends with a period.
105 15: adds double empty line
106 +
107 16: adds a function with foo_bar naming
108 + def blah_blah(x):
109 19: adds double empty line
109 19: adds double empty line
110 +
110 +
111 20: adds a function with foo_bar naming
112 + def blah_blah(x):
113 23: adds double empty line
114 +
111 [1]
115 [1]
General Comments 0
You need to be logged in to leave comments. Login now