Re: ChangeLogs in commit messages

Gary Benson <[email protected]>
Newsgroups gmane.comp.gdb.devel
Message-ID <[email protected]>
Joel Brobecker wrote:
> > Does anybody have any experience writing such checks?  Or, does
> > anybody know of any project that already uses such checks?  I can
> > look into doing it myself (it's a pre-receive hook, right?)
> 
> I have loads of experience writing git hooks, but none with
> this repository's implementation.
> 
> I would typically adjust the "update" hook for that, but it looks
> like the "pre-receive" hook would also work.

I've put together a quick pre-receive hook (inlined below).  Each
received commit on the "master" branch that touches the "gdb"
subdirectory gets its message checked.  The check itself is fairly
cursory: it splits the message using the "YYYY-MM-DD  NAME  <EMAIL>"
headers, checks each is preceeded by a path starting with "gdb/" and
ending with "/", and checks each is followed by more "NAME  <EMAIL>"
lines, blank lines, or lines starting with tab.  I don't know how
comprehensive we want to be here as the message should already have
been checked over by the reviewer.

I've never done anything server-side with git before, so there may
well be things I'm missing here.  I was mainly experimenting to see
how difficult this all was :)

Cheers,
Gary

-- 
#!/usr/bin/env python

import re
import subprocess
import sys

GIT = "/usr/bin/git"
SHA1HASH = re.compile(r"^[0-9a-f]{40}$", re.IGNORECASE)

name_mail = r".*  <[^@]+@[^>]+>$"
CL_HDR_A = re.compile(r"^\d{4}-\d{2}-\d{2}  " + name_mail)
CL_HDR_B = re.compile(r"^\s+" + name_mail)

class MessageChecker:
    def __init__(self, rev):
        self.rev = rev

    def check(self, condition, msg):
        if not condition:
            print "error: %s: bad commit message" % self.rev
            print "error: %s" % msg
            sys.exit(1)

    def check_message(self, lines):
        self.check(len(lines) > 2, "message is too short")
        self.check(lines[1] == "", "second line should be blank")
        splits = [index
                  for index in xrange(2, len(lines))
                  if CL_HDR_A.match(lines[index]) is not None]
        self.check(splits, "no ChangeLog entries found")
        for start, limit in zip(splits, splits[1:] + [0]):
            start -= 1
            limit -= 1
            self.check_changelog(lines[start:limit])

    def check_changelog(self, lines):
        self.check(len(lines) > 2, "ChangeLog entry is too short")
        path = lines.pop(0)
        self.check(path.startswith("gdb/") and path.endswith("/"),
                   "each ChangeLog entry should be preceeded by its path")
        lines.pop(0) # lines[1] has already been checked
        while lines and CL_HDR_B.match(lines[0]) is not None:
            lines.pop(0)
        self.check(lines, "only ChangeLog headers found (no data)")
        for line in lines:
            self.check(not line or line.startswith("\t"),
                       "bad ChangeLog line %s" % repr(line))

def check_hash(hash):
    assert SHA1HASH.match(hash) is not None

def git(*command):
    fp = subprocess.Popen((GIT,) + command, stdout=subprocess.PIPE)
    output = fp.communicate()[0]
    if fp.returncode:
        sys.exit(1)
    return output

def git_rev_list(oldrev, newrev):
    check_hash(oldrev)
    check_hash(newrev)
    return git("rev-list", oldrev + ".." + newrev)

def git_diff_tree(rev):
    check_hash(rev)
    return git("diff-tree", rev)

def git_cat_file_commit(rev):
    check_hash(rev)
    return git("cat-file", "commit", rev)

def commit_touches_gdb(rev):
    lines = git_diff_tree(rev).rstrip().split("\n")
    check = lines.pop(0)
    assert check == rev
    for line in lines:
        if line.split()[-1] == "gdb":
            return True
    return False

def check_commit(rev):
    lines = git_cat_file_commit(rev).rstrip().split("\n")
    while lines and lines[0]:
        lines.pop(0)
    assert lines
    lines.pop(0)
    MessageChecker(rev).check_message(lines)

def process_pack(oldrev, newrev, refname):
    if refname == "refs/heads/master":
        revs = git_rev_list(oldrev, newrev).rstrip().split("\n")
        revs.reverse()
        for rev in revs:
            if commit_touches_gdb(rev):
                check_commit(rev)
            print "%s: ok" % rev

def main():
    for line in sys.stdin.xreadlines():
        bits = line.rstrip().split()
        assert len(bits) == 3
        process_pack(*bits)

if __name__ == "__main__":
    main()
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.