blob: c1de74babede450168df5832cefb0974949c88d0 [file] [log] [blame]
lowRISC Contributors802543a2019-08-31 12:12:56 +01001#!/usr/bin/env python3
2# Copyright lowRISC contributors.
3# Licensed under the Apache License, Version 2.0, see LICENSE for details.
4# SPDX-License-Identifier: Apache-2.0
5
6import argparse
7import subprocess
8import sys
9
Philipp Wagner1e643552019-09-04 15:39:14 +010010from git import Repo
11
12error_msg_prefix = 'ERROR: '
13warning_msg_prefix = 'WARNING: '
14
15# Maximum length of the summary line in the commit message (the first line)
16# There is no hard limit, but a typical convention is to keep this line at or
17# below 50 characters, with occasional outliers.
18COMMIT_MSG_MAX_SUMMARAY_LEN = 100
lowRISC Contributors802543a2019-08-31 12:12:56 +010019
20
Philipp Wagner1e643552019-09-04 15:39:14 +010021def error(msg, commit=None):
22 full_msg = msg
23 if commit:
24 full_msg = "Commit %s: %s" % (commit.hexsha, msg)
25 print(error_msg_prefix + full_msg, file=sys.stderr)
26
27
28def warning(msg, commit=None):
29 full_msg = msg
30 if commit:
31 full_msg = "Commit %s: %s" % (commit.hexsha, msg)
32 print(warning_msg_prefix + full_msg, file=sys.stderr)
33
34
35def lint_commit_author(commit):
36 success = True
37 if commit.author.email.endswith('users.noreply.github.com'):
38 error(
39 'Commit author has no valid email address set: %s. '
40 'Use "git config user.email user@example.com" to '
41 'set a valid email address, and update the commit '
42 'with "git rebase -i" and/or '
43 '"git commit --amend --reset-author". '
44 'Also check your GitHub settings at '
45 'https://github.com/settings/emails: your email address '
46 'must be verified, and the option "Keep my email address '
47 'private" must be disabled.' % (commit.author.email, ), commit)
48 success = False
49
50 if not ' ' in commit.author.name:
51 warning(
52 'The commit author name "%s" does contain no space. '
53 'Use "git config user.name \'Johnny English\'" to '
54 'set your real name, and update the commit with "git rebase -i " '
55 'and/or "git commit --amend --reset-author".' %
56 (commit.author.name, ), commit)
57 # A warning doesn't fail lint.
58
59 return success
60
61
62def lint_commit_message(commit):
63 success = True
64 lines = commit.message.splitlines()
65
66 # Check length of summary line.
67 summary_line_len = len(lines[0])
68 if summary_line_len > COMMIT_MSG_MAX_SUMMARAY_LEN:
69 error(
70 "The summary line in the commit message %d characters long, "
71 "only %d characters are allowed." %
72 (summary_line_len, COMMIT_MSG_MAX_SUMMARAY_LEN), commit)
73 success = False
74
75 # Check for an empty line separating the summary line from the long
76 # description.
77 if len(lines) > 1 and lines[1] != "":
78 error(
79 "The second line of a commit message must be empty, as it "
80 "separates the summary from the long description.", commit)
81 success = False
Alex Bradbury6d091ec2019-11-04 14:24:31 +000082
83 # Check that the commit message contains a Signed-off-by line which
84 # matches the author name and email metadata.
85 expected_signoff_line = "Signed-off-by: %s <%s>" % (commit.author.name,
86 commit.author.email)
87 if expected_signoff_line not in lines:
88 error(
89 'The commit message must contain a Signed-off-by line that '
90 'matches the commit author name and email, indicating agreement '
91 'to the Contributor License Agreement. See CONTRIBUTING.md for '
92 'more details. "git commit -s" can be used to have git add this '
93 'line for you.')
94 success = False
95
Philipp Wagner1e643552019-09-04 15:39:14 +010096 return success
97
98
99def lint_commit(commit):
100 success = True
101 if not lint_commit_author(commit):
102 success = False
103 if not lint_commit_message(commit):
104 success = False
105 return success
lowRISC Contributors802543a2019-08-31 12:12:56 +0100106
107
108def main():
Philipp Wagner1e643552019-09-04 15:39:14 +0100109 global error_msg_prefix
110 global warning_msg_prefix
111
lowRISC Contributors802543a2019-08-31 12:12:56 +0100112 parser = argparse.ArgumentParser(
113 description='Check commit meta data for common mistakes')
114 parser.add_argument('--error-msg-prefix',
Philipp Wagner1e643552019-09-04 15:39:14 +0100115 default=error_msg_prefix,
lowRISC Contributors802543a2019-08-31 12:12:56 +0100116 required=False,
117 help='string to prepend to all error messages')
Philipp Wagner1e643552019-09-04 15:39:14 +0100118 parser.add_argument('--warning-msg-prefix',
119 default=warning_msg_prefix,
120 required=False,
121 help='string to prepend to all warning messages')
122 parser.add_argument('--no-merges',
123 required=False,
124 action="store_true",
125 help='do not check commits with more than one parent')
126 parser.add_argument('commit_range',
lowRISC Contributors802543a2019-08-31 12:12:56 +0100127 metavar='commit-range',
128 help='git log-compatible commit range to check')
129 args = parser.parse_args()
130
lowRISC Contributors802543a2019-08-31 12:12:56 +0100131 error_msg_prefix = args.error_msg_prefix
Philipp Wagner1e643552019-09-04 15:39:14 +0100132 warning_msg_prefix = args.warning_msg_prefix
lowRISC Contributors802543a2019-08-31 12:12:56 +0100133
Philipp Wagner1e643552019-09-04 15:39:14 +0100134 lint_successful = True
lowRISC Contributors802543a2019-08-31 12:12:56 +0100135
Philipp Wagner1e643552019-09-04 15:39:14 +0100136 repo = Repo()
137 commits = repo.iter_commits(args.commit_range)
138 for commit in commits:
139 print("Checking commit %s" % commit.hexsha)
140 is_merge = len(commit.parents) > 1
141 if is_merge and args.no_merges:
142 print("Skipping merge commit.")
Philipp Wagnerdfd6ac62019-09-05 10:51:00 +0100143 continue
144
Philipp Wagner1e643552019-09-04 15:39:14 +0100145 if not lint_commit(commit):
146 lint_successful = False
lowRISC Contributors802543a2019-08-31 12:12:56 +0100147
Philipp Wagner1e643552019-09-04 15:39:14 +0100148 if not lint_successful:
lowRISC Contributors802543a2019-08-31 12:12:56 +0100149 error('Commit lint failed.')
150 sys.exit(1)
151
152
153if __name__ == '__main__':
154 main()