check-commit
114 lines
| 3.1 KiB
| text/plain
|
TextLexer
/ contrib / check-commit
Gregory Szorc
|
r46434 | #!/usr/bin/env python3 | ||
Matt Mackall
|
r22043 | # | ||
Raphaël Gomès
|
r47575 | # Copyright 2014 Olivia Mackall <olivia@selenic.com> | ||
Matt Mackall
|
r22043 | # | ||
# A tool/hook to run basic sanity checks on commits/patches for | ||||
# submission to Mercurial. Install by adding the following to your | ||||
# .hg/hgrc: | ||||
# | ||||
# [hooks] | ||||
# pretxncommit = contrib/check-commit | ||||
# | ||||
# The hook can be temporarily bypassed with: | ||||
# | ||||
# $ BYPASS= hg commit | ||||
# | ||||
Matt Mackall
|
r26421 | # See also: https://mercurial-scm.org/wiki/ContributingChanges | ||
Matt Mackall
|
r22043 | |||
Pulkit Goyal
|
r29163 | |||
import os | ||||
import re | ||||
import sys | ||||
Matt Mackall
|
r22043 | |||
timeless
|
r27782 | commitheader = r"^(?:# [^\n]*\n)*" | ||
afterheader = commitheader + r"(?!#)" | ||||
beforepatch = afterheader + r"(?!\n(?!@@))" | ||||
Matt Mackall
|
r22043 | errors = [ | ||
timeless
|
r27782 | (beforepatch + r".*[(]bc[)]", "(BC) needs to be uppercase"), | ||
Gregory Szorc
|
r44058 | ( | ||
beforepatch + r".*[(]issue \d\d\d", | ||||
"no space allowed between issue and number", | ||||
), | ||||
timeless
|
r27782 | (beforepatch + r".*[(]bug(\d|\s)", "use (issueDDDD) instead of bug"), | ||
(commitheader + r"# User [^@\n]+\n", "username is not an email address"), | ||||
Gregory Szorc
|
r44058 | ( | ||
commitheader + r"(?!merge with )[^#]\S+[^:] ", | ||||
"summary line doesn't start with 'topic: '", | ||||
), | ||||
timeless
|
r27782 | (afterheader + r"[A-Z][a-z]\S+", "don't capitalize summary lines"), | ||
Martin von Zweigbergk
|
r40988 | (afterheader + r"^\S+: *[A-Z][a-z]\S+", "don't capitalize summary lines"), | ||
Gregory Szorc
|
r44058 | ( | ||
afterheader + r"\S*[^A-Za-z0-9-_]\S*: ", | ||||
"summary keyword should be most user-relevant one-word command or topic", | ||||
), | ||||
timeless
|
r27782 | (afterheader + r".*\.\s*\n", "don't add trailing period on summary line"), | ||
(afterheader + r".{79,}", "summary line too long (limit is 78)"), | ||||
Matt Mackall
|
r22043 | ] | ||
Gregory Szorc
|
r41680 | word = re.compile(r'\S') | ||
Gregory Szorc
|
r44058 | |||
timeless
|
r27782 | def nonempty(first, second): | ||
if word.search(first): | ||||
return first | ||||
return second | ||||
Gregory Szorc
|
r44058 | |||
FUJIWARA Katsunori
|
r28043 | def checkcommit(commit, node=None): | ||
timeless
|
r27780 | exitcode = 0 | ||
timeless
|
r27781 | printed = node is None | ||
timeless
|
r27783 | hits = [] | ||
Gregory Szorc
|
r44058 | signtag = ( | ||
afterheader + r'Added (tag [^ ]+|signature) for changeset [a-f0-9]{12}' | ||||
) | ||||
Augie Fackler
|
r30843 | if re.search(signtag, commit): | ||
return 0 | ||||
timeless
|
r27780 | for exp, msg in errors: | ||
Matt Mackall
|
r28012 | for m in re.finditer(exp, commit): | ||
timeless
|
r27782 | end = m.end() | ||
trailing = re.search(r'(\\n)+$', exp) | ||||
if trailing: | ||||
end -= len(trailing.group()) / 2 | ||||
timeless
|
r27783 | hits.append((end, exp, msg)) | ||
if hits: | ||||
hits.sort() | ||||
pos = 0 | ||||
last = '' | ||||
for n, l in enumerate(commit.splitlines(True)): | ||||
pos += len(l) | ||||
while len(hits): | ||||
end, exp, msg = hits[0] | ||||
timeless
|
r27782 | if pos < end: | ||
timeless
|
r27780 | break | ||
timeless
|
r27783 | if not printed: | ||
printed = True | ||||
Pulkit Goyal
|
r29164 | print("node: %s" % node) | ||
print("%d: %s" % (n, msg)) | ||||
print(" %s" % nonempty(l, last)[:-1]) | ||||
timeless
|
r27783 | if "BYPASS" not in os.environ: | ||
exitcode = 1 | ||||
del hits[0] | ||||
last = nonempty(l, last) | ||||
timeless
|
r27780 | return exitcode | ||
Matt Mackall
|
r22043 | |||
Gregory Szorc
|
r44058 | |||
timeless
|
r27780 | def readcommit(node): | ||
return os.popen("hg export %s" % node).read() | ||||
Gregory Szorc
|
r44058 | |||
timeless
|
r27780 | if __name__ == "__main__": | ||
timeless
|
r27781 | exitcode = 0 | ||
timeless
|
r27780 | node = os.environ.get("HG_NODE") | ||
Matt Mackall
|
r22043 | |||
timeless
|
r27780 | if node: | ||
commit = readcommit(node) | ||||
timeless
|
r27781 | exitcode = checkcommit(commit) | ||
elif sys.argv[1:]: | ||||
for node in sys.argv[1:]: | ||||
exitcode |= checkcommit(readcommit(node), node) | ||||
timeless
|
r27780 | else: | ||
commit = sys.stdin.read() | ||||
timeless
|
r27781 | exitcode = checkcommit(commit) | ||
timeless
|
r27780 | sys.exit(exitcode) | ||