xref: /illumos-gate/usr/src/tools/scripts/git-pbchk.py (revision 972282a0c9782b1780223d9eb8b11cdba5236348)
1ca13eaa5SAndy Fiddaman#!@TOOLS_PYTHON@
28bcea973SRichard Lowe#
38bcea973SRichard Lowe#  This program is free software; you can redistribute it and/or modify
48bcea973SRichard Lowe#  it under the terms of the GNU General Public License version 2
58bcea973SRichard Lowe#  as published by the Free Software Foundation.
68bcea973SRichard Lowe#
78bcea973SRichard Lowe#  This program is distributed in the hope that it will be useful,
88bcea973SRichard Lowe#  but WITHOUT ANY WARRANTY; without even the implied warranty of
98bcea973SRichard Lowe#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
108bcea973SRichard Lowe#  GNU General Public License for more details.
118bcea973SRichard Lowe#
128bcea973SRichard Lowe#  You should have received a copy of the GNU General Public License
138bcea973SRichard Lowe#  along with this program; if not, write to the Free Software
148bcea973SRichard Lowe#  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
158bcea973SRichard Lowe#
168bcea973SRichard Lowe
178bcea973SRichard Lowe#
188bcea973SRichard Lowe# Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
198bcea973SRichard Lowe# Copyright 2008, 2012 Richard Lowe
2095c635efSGarrett D'Amore# Copyright 2014 Garrett D'Amore <garrett@damore.org>
2193d2a904SPaul Dagnelie# Copyright (c) 2015, 2016 by Delphix. All rights reserved.
2228e2b3adSHans Rosenfeld# Copyright 2016 Nexenta Systems, Inc.
23*972282a0SJohn Levon# Copyright (c) 2019, Joyent, Inc.
24ca13eaa5SAndy Fiddaman# Copyright 2018 OmniOS Community Edition (OmniOSce) Association.
258bcea973SRichard Lowe#
268bcea973SRichard Lowe
27ca13eaa5SAndy Fiddamanfrom __future__ import print_function
28ca13eaa5SAndy Fiddaman
298bcea973SRichard Loweimport getopt
30ca13eaa5SAndy Fiddamanimport io
318bcea973SRichard Loweimport os
328bcea973SRichard Loweimport re
338bcea973SRichard Loweimport subprocess
348bcea973SRichard Loweimport sys
35ff50e8e5SRichard Loweimport tempfile
368bcea973SRichard Lowe
37ca13eaa5SAndy Fiddamanif sys.version_info[0] < 3:
388bcea973SRichard Lowe    from cStringIO import StringIO
39ca13eaa5SAndy Fiddamanelse:
40ca13eaa5SAndy Fiddaman    from io import StringIO
418bcea973SRichard Lowe
428bcea973SRichard Lowe#
438bcea973SRichard Lowe# Adjust the load path based on our location and the version of python into
448bcea973SRichard Lowe# which it is being loaded.  This assumes the normal onbld directory
458bcea973SRichard Lowe# structure, where we are in bin/ and the modules are in
468bcea973SRichard Lowe# lib/python(version)?/onbld/Scm/.  If that changes so too must this.
478bcea973SRichard Lowe#
488bcea973SRichard Lowesys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "lib",
498bcea973SRichard Lowe                                "python%d.%d" % sys.version_info[:2]))
508bcea973SRichard Lowe
518bcea973SRichard Lowe#
528bcea973SRichard Lowe# Add the relative path to usr/src/tools to the load path, such that when run
538bcea973SRichard Lowe# from the source tree we use the modules also within the source tree.
548bcea973SRichard Lowe#
558bcea973SRichard Lowesys.path.insert(2, os.path.join(os.path.dirname(__file__), ".."))
568bcea973SRichard Lowe
57e5587435SJoshua M. Clulowfrom onbld.Scm import Ignore
584ff15898SGordon Rossfrom onbld.Checks import Comments, Copyright, CStyle, HdrChk, WsCheck
5971af3be3SCody Peter Mellofrom onbld.Checks import JStyle, Keywords, ManLint, Mapfile, SpellCheck
608bcea973SRichard Lowe
618bcea973SRichard Loweclass GitError(Exception):
628bcea973SRichard Lowe    pass
638bcea973SRichard Lowe
648bcea973SRichard Lowedef git(command):
658bcea973SRichard Lowe    """Run a command and return a stream containing its stdout (and write its
668bcea973SRichard Lowe    stderr to its stdout)"""
678bcea973SRichard Lowe
688bcea973SRichard Lowe    if type(command) != list:
698bcea973SRichard Lowe        command = command.split()
708bcea973SRichard Lowe
718bcea973SRichard Lowe    command = ["git"] + command
728bcea973SRichard Lowe
73ff50e8e5SRichard Lowe    try:
74ca13eaa5SAndy Fiddaman        tmpfile = tempfile.TemporaryFile(prefix="git-nits", mode="w+b")
75ca13eaa5SAndy Fiddaman    except EnvironmentError as e:
76ff50e8e5SRichard Lowe        raise GitError("Could not create temporary file: %s\n" % e)
77ff50e8e5SRichard Lowe
78ff50e8e5SRichard Lowe    try:
798bcea973SRichard Lowe        p = subprocess.Popen(command,
80ff50e8e5SRichard Lowe                             stdout=tmpfile,
81380fd671SMatthew Ahrens                             stderr=subprocess.PIPE)
82ca13eaa5SAndy Fiddaman    except OSError as e:
83709afb1dSDillon Amburgey        raise GitError("could not execute %s: %s\n" % (command, e))
848bcea973SRichard Lowe
858bcea973SRichard Lowe    err = p.wait()
868bcea973SRichard Lowe    if err != 0:
87380fd671SMatthew Ahrens        raise GitError(p.stderr.read())
888bcea973SRichard Lowe
89ff50e8e5SRichard Lowe    tmpfile.seek(0)
90ca13eaa5SAndy Fiddaman    lines = []
91ca13eaa5SAndy Fiddaman    for l in tmpfile:
92ca13eaa5SAndy Fiddaman        lines.append(l.decode('utf-8', 'replace'))
93ca13eaa5SAndy Fiddaman    return lines
948bcea973SRichard Lowe
958bcea973SRichard Lowedef git_root():
968bcea973SRichard Lowe    """Return the root of the current git workspace"""
978bcea973SRichard Lowe
988bcea973SRichard Lowe    p = git('rev-parse --git-dir')
99ca13eaa5SAndy Fiddaman    dir = p[0]
1008bcea973SRichard Lowe
101ca13eaa5SAndy Fiddaman    return os.path.abspath(os.path.join(dir, os.path.pardir))
1028bcea973SRichard Lowe
1038bcea973SRichard Lowedef git_branch():
1048bcea973SRichard Lowe    """Return the current git branch"""
1058bcea973SRichard Lowe
1068bcea973SRichard Lowe    p = git('branch')
1078bcea973SRichard Lowe
1088bcea973SRichard Lowe    for elt in p:
1098bcea973SRichard Lowe        if elt[0] == '*':
1108bcea973SRichard Lowe            if elt.endswith('(no branch)'):
1118bcea973SRichard Lowe                return None
1128bcea973SRichard Lowe            return elt.split()[1]
1138bcea973SRichard Lowe
1148bcea973SRichard Lowedef git_parent_branch(branch):
1158bcea973SRichard Lowe    """Return the parent of the current git branch.
1168bcea973SRichard Lowe
1178bcea973SRichard Lowe    If this branch tracks a remote branch, return the remote branch which is
1188bcea973SRichard Lowe    tracked.  If not, default to origin/master."""
1198bcea973SRichard Lowe
1208bcea973SRichard Lowe    if not branch:
1218bcea973SRichard Lowe        return None
1228bcea973SRichard Lowe
12328e2b3adSHans Rosenfeld    p = git(["for-each-ref", "--format=%(refname:short) %(upstream:short)",
12428e2b3adSHans Rosenfeld            "refs/heads/"])
1258bcea973SRichard Lowe
1268bcea973SRichard Lowe    if not p:
1278bcea973SRichard Lowe        sys.stderr.write("Failed finding git parent branch\n")
128*972282a0SJohn Levon        sys.exit(1)
1298bcea973SRichard Lowe
1308bcea973SRichard Lowe    for line in p:
1318bcea973SRichard Lowe        # Git 1.7 will leave a ' ' trailing any non-tracking branch
1328bcea973SRichard Lowe        if ' ' in line and not line.endswith(' \n'):
1338bcea973SRichard Lowe            local, remote = line.split()
1348bcea973SRichard Lowe            if local == branch:
1358bcea973SRichard Lowe                return remote
1368bcea973SRichard Lowe    return 'origin/master'
1378bcea973SRichard Lowe
1388bcea973SRichard Lowedef git_comments(parent):
1398bcea973SRichard Lowe    """Return a list of any checkin comments on this git branch"""
1408bcea973SRichard Lowe
14127495383SRichard Lowe    p = git('log --pretty=tformat:%%B:SEP: %s..' % parent)
1428bcea973SRichard Lowe
1438bcea973SRichard Lowe    if not p:
144*972282a0SJohn Levon        sys.stderr.write("No outgoing changesets found - missing -p option?\n");
145*972282a0SJohn Levon        sys.exit(1)
1468bcea973SRichard Lowe
147ca13eaa5SAndy Fiddaman    return [x.strip() for x in p if x != ':SEP:\n']
1488bcea973SRichard Lowe
1498bcea973SRichard Lowedef git_file_list(parent, paths=None):
1508bcea973SRichard Lowe    """Return the set of files which have ever changed on this branch.
1518bcea973SRichard Lowe
1528bcea973SRichard Lowe    NB: This includes files which no longer exist, or no longer actually
1538bcea973SRichard Lowe    differ."""
1548bcea973SRichard Lowe
1558bcea973SRichard Lowe    p = git("log --name-only --pretty=format: %s.. %s" %
1568bcea973SRichard Lowe             (parent, ' '.join(paths)))
1578bcea973SRichard Lowe
1588bcea973SRichard Lowe    if not p:
1598bcea973SRichard Lowe        sys.stderr.write("Failed building file-list from git\n")
160*972282a0SJohn Levon        sys.exit(1)
1618bcea973SRichard Lowe
1628bcea973SRichard Lowe    ret = set()
1638bcea973SRichard Lowe    for fname in p:
16493d2a904SPaul Dagnelie        if fname and not fname.isspace() and fname not in ret:
1658bcea973SRichard Lowe            ret.add(fname.strip())
1668bcea973SRichard Lowe
1678bcea973SRichard Lowe    return ret
1688bcea973SRichard Lowe
1698bcea973SRichard Lowedef not_check(root, cmd):
1708bcea973SRichard Lowe    """Return a function which returns True if a file given as an argument
1718bcea973SRichard Lowe    should be excluded from the check named by 'cmd'"""
1728bcea973SRichard Lowe
173ca13eaa5SAndy Fiddaman    ignorefiles = list(filter(os.path.exists,
1748bcea973SRichard Lowe                         [os.path.join(root, ".git", "%s.NOT" % cmd),
175ca13eaa5SAndy Fiddaman                          os.path.join(root, "exception_lists", cmd)]))
176e5587435SJoshua M. Clulow    return Ignore.ignore(root, ignorefiles)
1778bcea973SRichard Lowe
1788bcea973SRichard Lowedef gen_files(root, parent, paths, exclude):
1798bcea973SRichard Lowe    """Return a function producing file names, relative to the current
1808bcea973SRichard Lowe    directory, of any file changed on this branch (limited to 'paths' if
1818bcea973SRichard Lowe    requested), and excluding files for which exclude returns a true value """
1828bcea973SRichard Lowe
1838bcea973SRichard Lowe    # Taken entirely from Python 2.6's os.path.relpath which we would use if we
1848bcea973SRichard Lowe    # could.
1858bcea973SRichard Lowe    def relpath(path, here):
1868bcea973SRichard Lowe        c = os.path.abspath(os.path.join(root, path)).split(os.path.sep)
1878bcea973SRichard Lowe        s = os.path.abspath(here).split(os.path.sep)
1888bcea973SRichard Lowe        l = len(os.path.commonprefix((s, c)))
1898bcea973SRichard Lowe        return os.path.join(*[os.path.pardir] * (len(s)-l) + c[l:])
1908bcea973SRichard Lowe
1918bcea973SRichard Lowe    def ret(select=None):
1928bcea973SRichard Lowe        if not select:
1938bcea973SRichard Lowe            select = lambda x: True
1948bcea973SRichard Lowe
19538e36c53SJohn Levon        for abspath in git_file_list(parent, paths):
19638e36c53SJohn Levon            path = relpath(abspath, '.')
19793d2a904SPaul Dagnelie            try:
19838e36c53SJohn Levon                res = git("diff %s HEAD %s" % (parent, path))
199ca13eaa5SAndy Fiddaman            except GitError as e:
20038e36c53SJohn Levon                # This ignores all the errors that can be thrown. Usually, this
20138e36c53SJohn Levon                # means that git returned non-zero because the file doesn't
20238e36c53SJohn Levon                # exist, but it could also fail if git can't create a new file
20338e36c53SJohn Levon                # or it can't be executed.  Such errors are 1) unlikely, and 2)
20438e36c53SJohn Levon                # will be caught by other invocations of git().
20593d2a904SPaul Dagnelie                continue
206ca13eaa5SAndy Fiddaman            empty = not res
20738e36c53SJohn Levon            if (os.path.isfile(path) and not empty and
20838e36c53SJohn Levon                select(path) and not exclude(abspath)):
20938e36c53SJohn Levon                yield path
2108bcea973SRichard Lowe    return ret
2118bcea973SRichard Lowe
2128bcea973SRichard Lowedef comchk(root, parent, flist, output):
2138bcea973SRichard Lowe    output.write("Comments:\n")
2148bcea973SRichard Lowe
2158bcea973SRichard Lowe    return Comments.comchk(git_comments(parent), check_db=True,
2168bcea973SRichard Lowe                           output=output)
2178bcea973SRichard Lowe
2188bcea973SRichard Lowe
2198bcea973SRichard Lowedef mapfilechk(root, parent, flist, output):
2208bcea973SRichard Lowe    ret = 0
2218bcea973SRichard Lowe
2228bcea973SRichard Lowe    # We are interested in examining any file that has the following
2238bcea973SRichard Lowe    # in its final path segment:
2248bcea973SRichard Lowe    #    - Contains the word 'mapfile'
2258bcea973SRichard Lowe    #    - Begins with 'map.'
2268bcea973SRichard Lowe    #    - Ends with '.map'
2278bcea973SRichard Lowe    # We don't want to match unless these things occur in final path segment
2288bcea973SRichard Lowe    # because directory names with these strings don't indicate a mapfile.
2298bcea973SRichard Lowe    # We also ignore files with suffixes that tell us that the files
2308bcea973SRichard Lowe    # are not mapfiles.
2318bcea973SRichard Lowe    MapfileRE = re.compile(r'.*((mapfile[^/]*)|(/map\.+[^/]*)|(\.map))$',
2328bcea973SRichard Lowe        re.IGNORECASE)
2338bcea973SRichard Lowe    NotMapSuffixRE = re.compile(r'.*\.[ch]$', re.IGNORECASE)
2348bcea973SRichard Lowe
2358bcea973SRichard Lowe    output.write("Mapfile comments:\n")
2368bcea973SRichard Lowe
2378bcea973SRichard Lowe    for f in flist(lambda x: MapfileRE.match(x) and not
2388bcea973SRichard Lowe                   NotMapSuffixRE.match(x)):
239ca13eaa5SAndy Fiddaman        with io.open(f, encoding='utf-8', errors='replace') as fh:
2408bcea973SRichard Lowe            ret |= Mapfile.mapfilechk(fh, output=output)
2418bcea973SRichard Lowe    return ret
2428bcea973SRichard Lowe
2438bcea973SRichard Lowedef copyright(root, parent, flist, output):
2448bcea973SRichard Lowe    ret = 0
2458bcea973SRichard Lowe    output.write("Copyrights:\n")
2468bcea973SRichard Lowe    for f in flist():
247ca13eaa5SAndy Fiddaman        with io.open(f, encoding='utf-8', errors='replace') as fh:
2488bcea973SRichard Lowe            ret |= Copyright.copyright(fh, output=output)
2498bcea973SRichard Lowe    return ret
2508bcea973SRichard Lowe
2518bcea973SRichard Lowedef hdrchk(root, parent, flist, output):
2528bcea973SRichard Lowe    ret = 0
2538bcea973SRichard Lowe    output.write("Header format:\n")
2548bcea973SRichard Lowe    for f in flist(lambda x: x.endswith('.h')):
255ca13eaa5SAndy Fiddaman        with io.open(f, encoding='utf-8', errors='replace') as fh:
2568bcea973SRichard Lowe            ret |= HdrChk.hdrchk(fh, lenient=True, output=output)
2578bcea973SRichard Lowe    return ret
2588bcea973SRichard Lowe
2598bcea973SRichard Lowedef cstyle(root, parent, flist, output):
2608bcea973SRichard Lowe    ret = 0
2618bcea973SRichard Lowe    output.write("C style:\n")
2628bcea973SRichard Lowe    for f in flist(lambda x: x.endswith('.c') or x.endswith('.h')):
263ca13eaa5SAndy Fiddaman        with io.open(f, encoding='utf-8', errors='replace') as fh:
2648bcea973SRichard Lowe            ret |= CStyle.cstyle(fh, output=output, picky=True,
2658bcea973SRichard Lowe                             check_posix_types=True,
2668bcea973SRichard Lowe                             check_continuation=True)
2678bcea973SRichard Lowe    return ret
2688bcea973SRichard Lowe
2698bcea973SRichard Lowedef jstyle(root, parent, flist, output):
2708bcea973SRichard Lowe    ret = 0
2718bcea973SRichard Lowe    output.write("Java style:\n")
2728bcea973SRichard Lowe    for f in flist(lambda x: x.endswith('.java')):
273ca13eaa5SAndy Fiddaman        with io.open(f, encoding='utf-8', errors='replace') as fh:
2748bcea973SRichard Lowe            ret |= JStyle.jstyle(fh, output=output, picky=True)
2758bcea973SRichard Lowe    return ret
2768bcea973SRichard Lowe
27795c635efSGarrett D'Amoredef manlint(root, parent, flist, output):
27895c635efSGarrett D'Amore    ret = 0
27971af3be3SCody Peter Mello    output.write("Man page format/spelling:\n")
28095c635efSGarrett D'Amore    ManfileRE = re.compile(r'.*\.[0-9][a-z]*$', re.IGNORECASE)
28195c635efSGarrett D'Amore    for f in flist(lambda x: ManfileRE.match(x)):
282ca13eaa5SAndy Fiddaman        with io.open(f, encoding='utf-8', errors='replace') as fh:
28395c635efSGarrett D'Amore            ret |= ManLint.manlint(fh, output=output, picky=True)
28471af3be3SCody Peter Mello            ret |= SpellCheck.spellcheck(fh, output=output)
28595c635efSGarrett D'Amore    return ret
28695c635efSGarrett D'Amore
2878bcea973SRichard Lowedef keywords(root, parent, flist, output):
2888bcea973SRichard Lowe    ret = 0
2898bcea973SRichard Lowe    output.write("SCCS Keywords:\n")
2908bcea973SRichard Lowe    for f in flist():
291ca13eaa5SAndy Fiddaman        with io.open(f, encoding='utf-8', errors='replace') as fh:
2928bcea973SRichard Lowe            ret |= Keywords.keywords(fh, output=output)
2938bcea973SRichard Lowe    return ret
2948bcea973SRichard Lowe
2954ff15898SGordon Rossdef wscheck(root, parent, flist, output):
2964ff15898SGordon Ross    ret = 0
2974ff15898SGordon Ross    output.write("white space nits:\n")
2984ff15898SGordon Ross    for f in flist():
299ca13eaa5SAndy Fiddaman        with io.open(f, encoding='utf-8', errors='replace') as fh:
3004ff15898SGordon Ross            ret |= WsCheck.wscheck(fh, output=output)
3014ff15898SGordon Ross    return ret
3028bcea973SRichard Lowe
3038bcea973SRichard Lowedef run_checks(root, parent, cmds, paths='', opts={}):
3048bcea973SRichard Lowe    """Run the checks given in 'cmds', expected to have well-known signatures,
3058bcea973SRichard Lowe    and report results for any which fail.
3068bcea973SRichard Lowe
3078bcea973SRichard Lowe    Return failure if any of them did.
3088bcea973SRichard Lowe
3098bcea973SRichard Lowe    NB: the function name of the commands passed in is used to name the NOT
3108bcea973SRichard Lowe    file which excepts files from them."""
3118bcea973SRichard Lowe
3128bcea973SRichard Lowe    ret = 0
3138bcea973SRichard Lowe
3148bcea973SRichard Lowe    for cmd in cmds:
3158bcea973SRichard Lowe        s = StringIO()
3168bcea973SRichard Lowe
317ca13eaa5SAndy Fiddaman        exclude = not_check(root, cmd.__name__)
3188bcea973SRichard Lowe        result = cmd(root, parent, gen_files(root, parent, paths, exclude),
3198bcea973SRichard Lowe                     output=s)
3208bcea973SRichard Lowe        ret |= result
3218bcea973SRichard Lowe
3228bcea973SRichard Lowe        if result != 0:
323ca13eaa5SAndy Fiddaman            print(s.getvalue())
3248bcea973SRichard Lowe
3258bcea973SRichard Lowe    return ret
3268bcea973SRichard Lowe
3278bcea973SRichard Lowedef nits(root, parent, paths):
3288bcea973SRichard Lowe    cmds = [copyright,
3298bcea973SRichard Lowe            cstyle,
3308bcea973SRichard Lowe            hdrchk,
3318bcea973SRichard Lowe            jstyle,
3328bcea973SRichard Lowe            keywords,
33395c635efSGarrett D'Amore            manlint,
3344ff15898SGordon Ross            mapfilechk,
3354ff15898SGordon Ross            wscheck]
3368bcea973SRichard Lowe    run_checks(root, parent, cmds, paths)
3378bcea973SRichard Lowe
3388bcea973SRichard Lowedef pbchk(root, parent, paths):
3398bcea973SRichard Lowe    cmds = [comchk,
3408bcea973SRichard Lowe            copyright,
3418bcea973SRichard Lowe            cstyle,
3428bcea973SRichard Lowe            hdrchk,
3438bcea973SRichard Lowe            jstyle,
3448bcea973SRichard Lowe            keywords,
34595c635efSGarrett D'Amore            manlint,
3464ff15898SGordon Ross            mapfilechk,
3474ff15898SGordon Ross            wscheck]
3488bcea973SRichard Lowe    run_checks(root, parent, cmds)
3498bcea973SRichard Lowe
3508bcea973SRichard Lowedef main(cmd, args):
3518bcea973SRichard Lowe    parent_branch = None
352eabe844aSJohn Levon    checkname = None
3538bcea973SRichard Lowe
3548bcea973SRichard Lowe    try:
35542a3762dSJoshua M. Clulow        opts, args = getopt.getopt(args, 'b:c:p:')
356ca13eaa5SAndy Fiddaman    except getopt.GetoptError as e:
3578bcea973SRichard Lowe        sys.stderr.write(str(e) + '\n')
358eabe844aSJohn Levon        sys.stderr.write("Usage: %s [-c check] [-p branch] [path...]\n" % cmd)
3598bcea973SRichard Lowe        sys.exit(1)
3608bcea973SRichard Lowe
3618bcea973SRichard Lowe    for opt, arg in opts:
36242a3762dSJoshua M. Clulow        # We accept "-b" as an alias of "-p" for backwards compatibility.
36342a3762dSJoshua M. Clulow        if opt == '-p' or opt == '-b':
3648bcea973SRichard Lowe            parent_branch = arg
365eabe844aSJohn Levon        elif opt == '-c':
366eabe844aSJohn Levon            checkname = arg
3678bcea973SRichard Lowe
3688bcea973SRichard Lowe    if not parent_branch:
3698bcea973SRichard Lowe        parent_branch = git_parent_branch(git_branch())
3708bcea973SRichard Lowe
371eabe844aSJohn Levon    if checkname is None:
3728bcea973SRichard Lowe        if cmd == 'git-pbchk':
373eabe844aSJohn Levon            checkname = 'pbchk'
374eabe844aSJohn Levon        else:
375eabe844aSJohn Levon            checkname = 'nits'
376eabe844aSJohn Levon
377eabe844aSJohn Levon    if checkname == 'pbchk':
3788bcea973SRichard Lowe        if args:
3798bcea973SRichard Lowe            sys.stderr.write("only complete workspaces may be pbchk'd\n");
3808bcea973SRichard Lowe            sys.exit(1)
381eabe844aSJohn Levon        pbchk(git_root(), parent_branch, None)
382eabe844aSJohn Levon    elif checkname == 'nits':
383eabe844aSJohn Levon        nits(git_root(), parent_branch, args)
384eabe844aSJohn Levon    else:
385eabe844aSJohn Levon        run_checks(git_root(), parent_branch, [eval(checkname)], args)
3868bcea973SRichard Lowe
3878bcea973SRichard Loweif __name__ == '__main__':
3888bcea973SRichard Lowe    try:
3898bcea973SRichard Lowe        main(os.path.basename(sys.argv[0]), sys.argv[1:])
390ca13eaa5SAndy Fiddaman    except GitError as e:
3918bcea973SRichard Lowe        sys.stderr.write("failed to run git:\n %s\n" % str(e))
3928bcea973SRichard Lowe        sys.exit(1)
393