Another draft.

I've changed all checks so they check per-cset.  In draft 1 I had things like
cstyle checking on the cumulative result, so that's fixed now.  I've also
stopped importing cdm.py, and I'm searching for merge-csets.

My <repo>/.hg/hgrc file looks like this:

----cut---
[cdmgate]
comchk.summaryonly = true
cstylechk.cstyleskipsfile = misc/cstyle/cstyle.cf


[hooks]
pretxnchangegroup.A = python:hgext.cdmgate.runchecks
pretxnchangegroup.Yexit1 = exit 1
----cut---


You can see I've added two config params.  The first allows a project team to
have descriptions on their csets, and causes comchk to verify only the summary
line.  Our repo has a list of files that we know we don't want the cstyle
command to touch, so the second parameter allows us to tell the hooks how to
find that list.

Can someone please tell me what kind of enforcement checks are run on a
TeamWare gate?  Obviously comchk isn't one of them... :)

These hooks can take a while to run, especially comchk, and that leaves enough
time for me to sneak in an 'hg clone'.  I'm able to clone the pushed csets
before it aborts the transaction in pretxnchangegroup.Yexit1.

If the user issues ^C while runchecks() is running then I have to find the
remote-side hg process and kill it to release the repository lock.  It's not
clear to me that adding signal handling for SIGHUP to runchecks() would
resolve this issue.  Something to try, anyway.  Or maybe I can do it in
hg-ssh?

Dean




Index: onbld/hgext/cdmgate.py
===================================================================
--- /dev/null   1970-01-01 00:00:00.000000000 +0000
+++ onbld/hgext/cdmgate.py      2008-04-24 11:33:43.274302000 -0500
@@ -0,0 +1,259 @@
+import os, re, logging
+from fnmatch import fnmatch
+from mercurial import cmdutil, node
+from onbld.Checks import Comments, CStyle
+
+
+
+def comchk(ui, repo, revs):
+    '''check checkin comments for active files
+
+    Check that checkin comments conform to O/N rules, however at this
+    point we just verify the summary line.  If users really wanted to
+    include more text in the description, then let them do that.'''
+
+    ui.write('Checking comments:\n')
+
+    tagre = re.compile(r'^Added tag .* for changeset ')
+    summaryonly = ui.configbool('cdmgate', 'comchk.summaryonly', False)
+
+    ret = 0
+    rev = min(revs)
+    while rev <= max(revs):
+        cs = repo.changelog.node(rev)
+        changes = repo.changelog.read(cs)
+        description = changes[4].strip()
+        ui.pushbuffer()
+        if not summaryonly:
+            ret |= Comments.comchk(description.splitlines(), check_db=True,
+                output=ui)
+        else:
+            summary = []
+            summary.append(description.splitlines()[0])
+            if not tagre.match(summary[0]):
+                ret |= Comments.comchk(summary, check_db=True, output=ui)
+
+        # Find out if it printed warnings.
+        output = ui.popbuffer()
+        lines = output.splitlines()
+        if len(lines) != 0:
+            ui.write("  Rev %s\n" % node.short(cs))
+            ui.write(output, '\n')
+
+        rev += 1
+
+    return ret
+
+
+def headchk(ui, repo):
+    '''check if multiple heads (or branches) are present'''
+
+    ui.write('Checking for heads:\n')
+
+    ret = 0
+    heads = repo.changelog.heads()
+    if len(heads) > 1:
+        ui.write("  Only 1 head is allowed.  (%d found)\n" % len(heads))
+        return 1
+    return 0
+
+
+
+def branchchk(ui, repo, revs):
+    '''check if multiple branches are present'''
+
+    ui.write('Checking for branches:\n')
+
+    ret = 0
+    rev = min(revs)
+    while rev <= max(revs):
+        cs = repo.changelog.node(rev)
+        parents = []
+        for par in repo.changelog.parentrevs(rev):
+            if par != node.nullrev:
+                parents.append(par)
+        if len(parents) != 1:
+            ui.write("  Rev %s has %d parents.\n" %
+                (node.short(cs), len(parents)))
+            ret += 1
+        rev += 1
+
+    return ret
+
+
+def tagchk(ui, repo, revs):
+    '''check if .hgtags is active and issue warning
+
+    Tag sharing among repositories is restricted to administrators'''
+
+    def allowtagpush():
+        '''Is this user allowed to push tags?'''
+
+        allowtagfile = "".join([os.environ['CALLED_REPO'],"-allowtagpush"])
+
+        try:
+            fh = open(allowtagfile, 'r')
+        except:
+            return 0
+
+        at_user = {}
+        for line in fh.readlines():
+            at_user[line.strip()] = 1
+        fh.close()
+        if os.environ['CALLING_USER'] in at_user:
+            return 1
+        return 0
+
+
+    ui.write("Checking for new tags:\n")
+
+    allowtags = allowtagpush()
+    ret = 0
+    rev = min(revs)
+    while rev <= max(revs):
+        cs = repo.changelog.node(rev)
+        changes = repo.changelog.read(cs)
+        if (".hgtags" in changes[3]) and not allowtags:
+            ui.write("  Tag push in %s by user %s is denied.\n" %
+                (node.short(cs), os.environ['CALLING_USER']))
+            ret += 1
+        rev += 1
+
+    return ret
+
+
+
+def cstylechk(ui, repo, revs):
+    '''check active C source files conform to the C Style Guide
+
+    See 
http://opensolaris.org/os/community/documentation/getting_started_docs/cstyle.ms.pdf'''
+
+    ui.write("C style check:\n")
+
+    # This repo may have a list of files that should not be cstyled.
+    cstyleskipsfile = ui.config('cdmgate', 'cstylechk.cstyleskipsfile')
+
+    ret = 0
+    rev = min(revs)
+    while rev <= max(revs):
+        cs1 = repo.changelog.node(rev-1)
+        cs2 = repo.changelog.node(rev)
+        files_changed = repo.status(cs1, cs2)[:2]
+        ctx = repo.changectx(rev)
+
+        files = {}
+        for f in files_changed[0]:
+            # files modified
+            files[f] = 1
+        for f in files_changed[1]:
+            # files added
+            files[f] = 1
+
+        nocstyle = {}
+        if cstyleskipsfile:
+            data = ctx.filectx(cstyleskipsfile).data()
+            for line in data.splitlines():
+                nocstyle[line] = 1
+
+        ui.pushbuffer()
+        for f in files:
+           if f in nocstyle:
+               continue
+           if fnmatch(f, '*.c') or fnmatch(f, '*.h'):
+               data = ctx.filectx(f).data()
+               # ON cstyle exit status is zero, even if it printed warnings.
+               ret |= CStyle.cstyle(data, filename=f, output=ui,
+                          picky=True, check_posix_types=True,
+                          check_continuation=True)
+
+        # Find out if it printed warnings.
+        output = ui.popbuffer()
+        lines = output.splitlines()
+        if len(lines) != 0:
+            ui.write("  Rev %s\n" % node.short(cs2))
+            ui.write(output, '\n')
+            ret |= 1
+
+        rev += 1
+
+    return ret
+
+
+def allowpushchk(ui, repo):
+    '''Does the user have permission to push?  They must be listed
+    in the repo's allowpush file.  If they aren't listed, then
+    print the motd in the hope that it will explain the situation
+    to the user.'''
+
+    def getmotd(ui):
+        '''Print a message-of-the-day for the given repository'''
+
+        motd = "".join([os.environ['CALLED_REPO'],"-motd"])
+        try:
+            fh = open(motd, 'r')
+        except:
+            return
+
+        for line in fh.readlines():
+            ui.write(line)
+        fh.close()
+
+
+    allowfile = "".join([os.environ['CALLED_REPO'],"-allowpush"])
+    try:
+        fh = open(allowfile, 'r')
+    except:
+        ui.write("  This repository is not allowing pushes.\n")
+        getmotd(ui)
+        return 1
+
+    af_user = {}
+    for line in fh.readlines():
+        af_user[line.strip()] = 1
+    fh.close()
+    if os.environ['CALLING_USER'] in af_user:
+        return 0
+    ui.write("  This repository is not allowing pushes from you (%s).\n" %
+        os.environ['CALLING_USER'])
+    getmotd(ui)
+    return 1
+
+
+def runchecks(ui, repo, **opts):
+    '''It can take a long time to get through all the hooks, so we will
+    run allowpushchk() after each long-running hook.'''
+
+
+    logfile = "".join([os.environ['CALLED_REPO'],"-pushlog"])
+    logging.basicConfig(format='%(asctime)s %(levelname)8s %(message)s',
+        filename=logfile, filemode='a')
+    logging.getLogger(0).setLevel(logging.INFO)
+
+
+    ret = allowpushchk(ui, repo)
+    if ret != 0:
+        return 1
+
+    logging.info('%s begin', os.environ['CALLING_USER'])
+
+    revs = cmdutil.revrange(repo, (opts['node'], 'tip'))
+
+    ret = 0
+    ret |= headchk(ui, repo)
+    ret |= branchchk(ui, repo, revs)
+    ret |= tagchk(ui, repo, revs)
+
+    ret |= comchk(ui, repo, revs)
+    aret = allowpushchk(ui, repo)
+    if aret != 0:
+        logging.info('%s exit %d', os.environ['CALLING_USER'], aret)
+        return 1
+
+    ret |= cstylechk(ui, repo, revs)
+    aret = allowpushchk(ui, repo)
+    if aret != 0:
+        logging.info('%s exit %d', os.environ['CALLING_USER'], aret)
+        return 1
+
+    logging.info('%s exit %d', os.environ['CALLING_USER'], ret)
+    return ret

Reply via email to