Gentoo Archives: gentoo-portage-dev

From: "Michał Górny" <mgorny@g.o>
To: gentoo-portage-dev@l.g.o
Cc: "Michał Górny" <mgorny@g.o>
Subject: [gentoo-portage-dev] [PATCH] repoman: Add commit message verification
Date: Fri, 02 Feb 2018 22:53:13
Message-Id: 20180202225305.9252-1-mgorny@gentoo.org
1 Add a check for common mistakes in commit messages. For now, it is
2 pretty rough and exits immediately but it should be integrated with
3 the editor in the future.
4 ---
5 repoman/pym/repoman/actions.py | 68 ++++++++++++++++++++++++++++++++++++++++++
6 1 file changed, 68 insertions(+)
7
8 diff --git a/repoman/pym/repoman/actions.py b/repoman/pym/repoman/actions.py
9 index b76a6e466..97a71d0f5 100644
10 --- a/repoman/pym/repoman/actions.py
11 +++ b/repoman/pym/repoman/actions.py
12 @@ -124,6 +124,15 @@ class Actions(object):
13
14 commitmessage = commitmessage.rstrip()
15
16 + res, expl = self.verify_commit_message(commitmessage)
17 + if not res:
18 + print(bad("RepoMan does not like your commit message:"))
19 + print(expl)
20 + if self.options.force:
21 + print('(but proceeding due to --force)')
22 + else:
23 + sys.exit(1)
24 +
25 # Update copyright for new and changed files
26 year = time.strftime('%Y', time.gmtime())
27 for fn in chain(mynew, mychanged):
28 @@ -585,3 +594,62 @@ class Actions(object):
29 print("* no commit message? aborting commit.")
30 sys.exit(1)
31 return commitmessage
32 +
33 + footer_re = re.compile(r'^[\w-]+:')
34 +
35 + def verify_commit_message(self, msg):
36 + """
37 + Check whether the message roughly complies with GLEP66. Returns
38 + (True, None) if it does, (False, <explanation>) if it does not.
39 + """
40 +
41 + problems = []
42 + paras = msg.strip().split('\n\n')
43 + summary = paras.pop(0)
44 +
45 + if '\n' in summary.strip():
46 + problems.append('commit message must start with a *single* line of summary, followed by empty line')
47 + if ':' not in summary:
48 + problems.append('summary line must start with a logical unit name, e.g. "cat/pkg:"')
49 + # accept 69 overall or unit+50, in case of very long package names
50 + if len(summary.strip()) > 69 and len(summary.split(':', 1)[-1]) > 50:
51 + problems.append('summary line is too long (max 69 characters)')
52 +
53 + multiple_footers = False
54 + gentoo_bug_used = False
55 + bug_closes_without_url = False
56 + body_too_long = False
57 +
58 + found_footer = False
59 + for p in paras:
60 + lines = p.splitlines()
61 + # if all lines look like footer material, we assume it's footer
62 + # else, we assume it's body text
63 + if all(self.footer_re.match(l) for l in lines if l.strip()):
64 + # multiple footer-like blocks?
65 + if found_footer:
66 + multiple_footers = True
67 + found_footer = True
68 + for l in lines:
69 + if l.startswith('Gentoo-Bug'):
70 + gentoo_bug_used = True
71 + elif l.startswith('Bug:') or l.startswith('Closes:'):
72 + if 'http://' not in l and 'https://' not in l:
73 + bug_closes_without_url = True
74 + else:
75 + for l in lines:
76 + if len(l.strip()) > 72:
77 + body_too_long = True
78 +
79 + if multiple_footers:
80 + problems.append('multiple footer-style blocks found, please combine them into one')
81 + if gentoo_bug_used:
82 + problems.append('please replace Gentoo-Bug with GLEP 66-compliant Bug/Closes')
83 + if bug_closes_without_url:
84 + problems.append('Bug/Closes footers require full URL')
85 + if body_too_long:
86 + problems.append('body lines should be wrapped at 72 characters')
87 +
88 + if problems:
89 + return (False, '\n'.join('- %s' % x for x in problems))
90 + return (True, None)
91 --
92 2.16.1

Replies