contrib/check-commit
author timeless <timeless@mozdev.org>
Thu, 07 Jan 2016 01:28:59 +0000
changeset 27781 2af351bd289c
parent 27780 f47185f09533
child 27782 7291c8165e33
permissions -rwxr-xr-x
check-commit: support REVs as commandline arguments usage: * HG_NODE=REV check-commit * hg export REV | check-commit * check-commit REV ...

#!/usr/bin/env python
#
# Copyright 2014 Matt Mackall <mpm@selenic.com>
#
# 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
#
# See also: https://mercurial-scm.org/wiki/ContributingChanges

import re, sys, os

errors = [
    (r"[(]bc[)]", "(BC) needs to be uppercase"),
    (r"[(]issue \d\d\d", "no space allowed between issue and number"),
    (r"[(]bug(\d|\s)", "use (issueDDDD) instead of bug"),
    (r"^# User [^@\n]+$", "username is not an email address"),
    (r"^# .*\n(?!merge with )[^#]\S+[^:] ",
     "summary line doesn't start with 'topic: '"),
    (r"^# .*\n[A-Z][a-z]\S+", "don't capitalize summary lines"),
    (r"^# .*\n[^\n]*: *[A-Z][a-z]\S+", "don't capitalize summary lines"),
    (r"^# [^\n]*\n\S*[^A-Za-z0-9-]\S*: ",
     "summary keyword should be most user-relevant one-word command or topic"),
    (r"^# .*\n.*\.\s+$", "don't add trailing period on summary line"),
    (r"^# .*\n[^#].{78,}", "summary line too long (limit is 78)"),
    (r"^\+\n \n", "adds double empty line"),
    (r"^ \n\+\n", "adds double empty line"),
    (r"^\+[ \t]+def [a-z]+_[a-z]", "adds a function with foo_bar naming"),
]

def checkcommit(commit, node = None):
    exitcode = 0
    printed = node is None
    for exp, msg in errors:
        m = re.search(exp, commit, re.MULTILINE)
        if m:
            pos = 0
            for n, l in enumerate(commit.splitlines(True)):
                pos += len(l)
                if pos >= m.end():
                    if not printed:
                        printed = True
                        print "node: %s" % node
                    print "%d: %s" % (n, msg)
                    print " %s" % l[:-1]
                    if "BYPASS" not in os.environ:
                        exitcode = 1
                    break
    return exitcode

def readcommit(node):
    return os.popen("hg export %s" % node).read()

if __name__ == "__main__":
    exitcode = 0
    node = os.environ.get("HG_NODE")

    if node:
        commit = readcommit(node)
        exitcode = checkcommit(commit)
    elif sys.argv[1:]:
        for node in sys.argv[1:]:
            exitcode |= checkcommit(readcommit(node), node)
    else:
        commit = sys.stdin.read()
        exitcode = checkcommit(commit)
    sys.exit(exitcode)