lowRISC Contributors | 802543a | 2019-08-31 12:12:56 +0100 | [diff] [blame] | 1 | #!/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 | |
| 6 | import argparse |
| 7 | import subprocess |
| 8 | import sys |
| 9 | |
Philipp Wagner | 1e64355 | 2019-09-04 15:39:14 +0100 | [diff] [blame] | 10 | from git import Repo |
| 11 | |
| 12 | error_msg_prefix = 'ERROR: ' |
| 13 | warning_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. |
| 18 | COMMIT_MSG_MAX_SUMMARAY_LEN = 100 |
lowRISC Contributors | 802543a | 2019-08-31 12:12:56 +0100 | [diff] [blame] | 19 | |
| 20 | |
Philipp Wagner | 1e64355 | 2019-09-04 15:39:14 +0100 | [diff] [blame] | 21 | def 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 | |
| 28 | def 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 | |
| 35 | def 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 | |
| 62 | def 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 Bradbury | 6d091ec | 2019-11-04 14:24:31 +0000 | [diff] [blame] | 82 | |
| 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 Wagner | 1e64355 | 2019-09-04 15:39:14 +0100 | [diff] [blame] | 96 | return success |
| 97 | |
| 98 | |
| 99 | def 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 Contributors | 802543a | 2019-08-31 12:12:56 +0100 | [diff] [blame] | 106 | |
| 107 | |
| 108 | def main(): |
Philipp Wagner | 1e64355 | 2019-09-04 15:39:14 +0100 | [diff] [blame] | 109 | global error_msg_prefix |
| 110 | global warning_msg_prefix |
| 111 | |
lowRISC Contributors | 802543a | 2019-08-31 12:12:56 +0100 | [diff] [blame] | 112 | parser = argparse.ArgumentParser( |
| 113 | description='Check commit meta data for common mistakes') |
| 114 | parser.add_argument('--error-msg-prefix', |
Philipp Wagner | 1e64355 | 2019-09-04 15:39:14 +0100 | [diff] [blame] | 115 | default=error_msg_prefix, |
lowRISC Contributors | 802543a | 2019-08-31 12:12:56 +0100 | [diff] [blame] | 116 | required=False, |
| 117 | help='string to prepend to all error messages') |
Philipp Wagner | 1e64355 | 2019-09-04 15:39:14 +0100 | [diff] [blame] | 118 | 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 Contributors | 802543a | 2019-08-31 12:12:56 +0100 | [diff] [blame] | 127 | metavar='commit-range', |
| 128 | help='git log-compatible commit range to check') |
| 129 | args = parser.parse_args() |
| 130 | |
lowRISC Contributors | 802543a | 2019-08-31 12:12:56 +0100 | [diff] [blame] | 131 | error_msg_prefix = args.error_msg_prefix |
Philipp Wagner | 1e64355 | 2019-09-04 15:39:14 +0100 | [diff] [blame] | 132 | warning_msg_prefix = args.warning_msg_prefix |
lowRISC Contributors | 802543a | 2019-08-31 12:12:56 +0100 | [diff] [blame] | 133 | |
Philipp Wagner | 1e64355 | 2019-09-04 15:39:14 +0100 | [diff] [blame] | 134 | lint_successful = True |
lowRISC Contributors | 802543a | 2019-08-31 12:12:56 +0100 | [diff] [blame] | 135 | |
Philipp Wagner | 1e64355 | 2019-09-04 15:39:14 +0100 | [diff] [blame] | 136 | 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 Wagner | dfd6ac6 | 2019-09-05 10:51:00 +0100 | [diff] [blame] | 143 | continue |
| 144 | |
Philipp Wagner | 1e64355 | 2019-09-04 15:39:14 +0100 | [diff] [blame] | 145 | if not lint_commit(commit): |
| 146 | lint_successful = False |
lowRISC Contributors | 802543a | 2019-08-31 12:12:56 +0100 | [diff] [blame] | 147 | |
Philipp Wagner | 1e64355 | 2019-09-04 15:39:14 +0100 | [diff] [blame] | 148 | if not lint_successful: |
lowRISC Contributors | 802543a | 2019-08-31 12:12:56 +0100 | [diff] [blame] | 149 | error('Commit lint failed.') |
| 150 | sys.exit(1) |
| 151 | |
| 152 | |
| 153 | if __name__ == '__main__': |
| 154 | main() |