This is the mail archive of the gdb@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: ChangeLogs in commit messages


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()


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]