xref: /illumos-gate/usr/src/tools/scripts/git-pbchk.py (revision 380fd671753dd199e149f10e9f73ec52cdfe4230)
16066d16eSPiotr Jasiukajtis#!/usr/bin/python2.6
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>
21e5587435SJoshua M. Clulow# Copyright (c) 2014, Joyent, Inc.
2293d2a904SPaul Dagnelie# Copyright (c) 2015, 2016 by Delphix. All rights reserved.
238bcea973SRichard Lowe#
248bcea973SRichard Lowe
258bcea973SRichard Loweimport getopt
268bcea973SRichard Loweimport os
278bcea973SRichard Loweimport re
288bcea973SRichard Loweimport subprocess
298bcea973SRichard Loweimport sys
30ff50e8e5SRichard Loweimport tempfile
318bcea973SRichard Lowe
328bcea973SRichard Lowefrom cStringIO import StringIO
338bcea973SRichard Lowe
348bcea973SRichard Lowe#
358bcea973SRichard Lowe# Adjust the load path based on our location and the version of python into
368bcea973SRichard Lowe# which it is being loaded.  This assumes the normal onbld directory
378bcea973SRichard Lowe# structure, where we are in bin/ and the modules are in
388bcea973SRichard Lowe# lib/python(version)?/onbld/Scm/.  If that changes so too must this.
398bcea973SRichard Lowe#
408bcea973SRichard Lowesys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "lib",
418bcea973SRichard Lowe                                "python%d.%d" % sys.version_info[:2]))
428bcea973SRichard Lowe
438bcea973SRichard Lowe#
448bcea973SRichard Lowe# Add the relative path to usr/src/tools to the load path, such that when run
458bcea973SRichard Lowe# from the source tree we use the modules also within the source tree.
468bcea973SRichard Lowe#
478bcea973SRichard Lowesys.path.insert(2, os.path.join(os.path.dirname(__file__), ".."))
488bcea973SRichard Lowe
49e5587435SJoshua M. Clulowfrom onbld.Scm import Ignore
508bcea973SRichard Lowefrom onbld.Checks import Comments, Copyright, CStyle, HdrChk
5195c635efSGarrett D'Amorefrom onbld.Checks import JStyle, Keywords, ManLint, Mapfile
528bcea973SRichard Lowe
538bcea973SRichard Lowe
548bcea973SRichard Loweclass GitError(Exception):
558bcea973SRichard Lowe    pass
568bcea973SRichard Lowe
578bcea973SRichard Lowedef git(command):
588bcea973SRichard Lowe    """Run a command and return a stream containing its stdout (and write its
598bcea973SRichard Lowe    stderr to its stdout)"""
608bcea973SRichard Lowe
618bcea973SRichard Lowe    if type(command) != list:
628bcea973SRichard Lowe        command = command.split()
638bcea973SRichard Lowe
648bcea973SRichard Lowe    command = ["git"] + command
658bcea973SRichard Lowe
66ff50e8e5SRichard Lowe    try:
67ff50e8e5SRichard Lowe        tmpfile = tempfile.TemporaryFile(prefix="git-nits")
68ff50e8e5SRichard Lowe    except EnvironmentError, e:
69ff50e8e5SRichard Lowe        raise GitError("Could not create temporary file: %s\n" % e)
70ff50e8e5SRichard Lowe
71ff50e8e5SRichard Lowe    try:
728bcea973SRichard Lowe        p = subprocess.Popen(command,
73ff50e8e5SRichard Lowe                             stdout=tmpfile,
74*380fd671SMatthew Ahrens                             stderr=subprocess.PIPE)
75ff50e8e5SRichard Lowe    except OSError, e:
76ff50e8e5SRichard Lowe        raise GitError("could not execute %s: %s\n" (command, e))
778bcea973SRichard Lowe
788bcea973SRichard Lowe    err = p.wait()
798bcea973SRichard Lowe    if err != 0:
80*380fd671SMatthew Ahrens        raise GitError(p.stderr.read())
818bcea973SRichard Lowe
82ff50e8e5SRichard Lowe    tmpfile.seek(0)
83ff50e8e5SRichard Lowe    return tmpfile
848bcea973SRichard Lowe
858bcea973SRichard Lowe
868bcea973SRichard Lowedef git_root():
878bcea973SRichard Lowe    """Return the root of the current git workspace"""
888bcea973SRichard Lowe
898bcea973SRichard Lowe    p = git('rev-parse --git-dir')
908bcea973SRichard Lowe
918bcea973SRichard Lowe    if not p:
928bcea973SRichard Lowe        sys.stderr.write("Failed finding git workspace\n")
938bcea973SRichard Lowe        sys.exit(err)
948bcea973SRichard Lowe
958bcea973SRichard Lowe    return os.path.abspath(os.path.join(p.readlines()[0],
968bcea973SRichard Lowe                                        os.path.pardir))
978bcea973SRichard Lowe
988bcea973SRichard Lowe
998bcea973SRichard Lowedef git_branch():
1008bcea973SRichard Lowe    """Return the current git branch"""
1018bcea973SRichard Lowe
1028bcea973SRichard Lowe    p = git('branch')
1038bcea973SRichard Lowe
1048bcea973SRichard Lowe    if not p:
1058bcea973SRichard Lowe        sys.stderr.write("Failed finding git branch\n")
1068bcea973SRichard Lowe        sys.exit(err)
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 Lowe
1158bcea973SRichard Lowedef git_parent_branch(branch):
1168bcea973SRichard Lowe    """Return the parent of the current git branch.
1178bcea973SRichard Lowe
1188bcea973SRichard Lowe    If this branch tracks a remote branch, return the remote branch which is
1198bcea973SRichard Lowe    tracked.  If not, default to origin/master."""
1208bcea973SRichard Lowe
1218bcea973SRichard Lowe    if not branch:
1228bcea973SRichard Lowe        return None
1238bcea973SRichard Lowe
1248bcea973SRichard Lowe    p = git("for-each-ref --format=%(refname:short) %(upstream:short) " +
1258bcea973SRichard Lowe            "refs/heads/")
1268bcea973SRichard Lowe
1278bcea973SRichard Lowe    if not p:
1288bcea973SRichard Lowe        sys.stderr.write("Failed finding git parent branch\n")
1298bcea973SRichard Lowe        sys.exit(err)
1308bcea973SRichard Lowe
1318bcea973SRichard Lowe    for line in p:
1328bcea973SRichard Lowe        # Git 1.7 will leave a ' ' trailing any non-tracking branch
1338bcea973SRichard Lowe        if ' ' in line and not line.endswith(' \n'):
1348bcea973SRichard Lowe            local, remote = line.split()
1358bcea973SRichard Lowe            if local == branch:
1368bcea973SRichard Lowe                return remote
1378bcea973SRichard Lowe    return 'origin/master'
1388bcea973SRichard Lowe
1398bcea973SRichard Lowe
1408bcea973SRichard Lowedef git_comments(parent):
1418bcea973SRichard Lowe    """Return a list of any checkin comments on this git branch"""
1428bcea973SRichard Lowe
14327495383SRichard Lowe    p = git('log --pretty=tformat:%%B:SEP: %s..' % parent)
1448bcea973SRichard Lowe
1458bcea973SRichard Lowe    if not p:
1468bcea973SRichard Lowe        sys.stderr.write("Failed getting git comments\n")
1478bcea973SRichard Lowe        sys.exit(err)
1488bcea973SRichard Lowe
14927495383SRichard Lowe    return [x.strip() for x in p.readlines() if x != ':SEP:\n']
1508bcea973SRichard Lowe
1518bcea973SRichard Lowe
1528bcea973SRichard Lowedef git_file_list(parent, paths=None):
1538bcea973SRichard Lowe    """Return the set of files which have ever changed on this branch.
1548bcea973SRichard Lowe
1558bcea973SRichard Lowe    NB: This includes files which no longer exist, or no longer actually
1568bcea973SRichard Lowe    differ."""
1578bcea973SRichard Lowe
1588bcea973SRichard Lowe    p = git("log --name-only --pretty=format: %s.. %s" %
1598bcea973SRichard Lowe             (parent, ' '.join(paths)))
1608bcea973SRichard Lowe
1618bcea973SRichard Lowe    if not p:
1628bcea973SRichard Lowe        sys.stderr.write("Failed building file-list from git\n")
1638bcea973SRichard Lowe        sys.exit(err)
1648bcea973SRichard Lowe
1658bcea973SRichard Lowe    ret = set()
1668bcea973SRichard Lowe    for fname in p:
16793d2a904SPaul Dagnelie        if fname and not fname.isspace() and fname not in ret:
1688bcea973SRichard Lowe            ret.add(fname.strip())
1698bcea973SRichard Lowe
1708bcea973SRichard Lowe    return ret
1718bcea973SRichard Lowe
1728bcea973SRichard Lowe
1738bcea973SRichard Lowedef not_check(root, cmd):
1748bcea973SRichard Lowe    """Return a function which returns True if a file given as an argument
1758bcea973SRichard Lowe    should be excluded from the check named by 'cmd'"""
1768bcea973SRichard Lowe
1778bcea973SRichard Lowe    ignorefiles = filter(os.path.exists,
1788bcea973SRichard Lowe                         [os.path.join(root, ".git", "%s.NOT" % cmd),
1798bcea973SRichard Lowe                          os.path.join(root, "exception_lists", cmd)])
180e5587435SJoshua M. Clulow    return Ignore.ignore(root, ignorefiles)
1818bcea973SRichard Lowe
1828bcea973SRichard Lowe
1838bcea973SRichard Lowedef gen_files(root, parent, paths, exclude):
1848bcea973SRichard Lowe    """Return a function producing file names, relative to the current
1858bcea973SRichard Lowe    directory, of any file changed on this branch (limited to 'paths' if
1868bcea973SRichard Lowe    requested), and excluding files for which exclude returns a true value """
1878bcea973SRichard Lowe
1888bcea973SRichard Lowe    # Taken entirely from Python 2.6's os.path.relpath which we would use if we
1898bcea973SRichard Lowe    # could.
1908bcea973SRichard Lowe    def relpath(path, here):
1918bcea973SRichard Lowe        c = os.path.abspath(os.path.join(root, path)).split(os.path.sep)
1928bcea973SRichard Lowe        s = os.path.abspath(here).split(os.path.sep)
1938bcea973SRichard Lowe        l = len(os.path.commonprefix((s, c)))
1948bcea973SRichard Lowe        return os.path.join(*[os.path.pardir] * (len(s)-l) + c[l:])
1958bcea973SRichard Lowe
1968bcea973SRichard Lowe    def ret(select=None):
1978bcea973SRichard Lowe        if not select:
1988bcea973SRichard Lowe            select = lambda x: True
1998bcea973SRichard Lowe
2008bcea973SRichard Lowe        for f in git_file_list(parent, paths):
2018bcea973SRichard Lowe            f = relpath(f, '.')
20293d2a904SPaul Dagnelie            try:
20393d2a904SPaul Dagnelie                res = git("diff %s HEAD %s" % (parent, f))
20493d2a904SPaul Dagnelie            except GitError, e:
20593d2a904SPaul Dagnelie                # This ignores all the errors that can be thrown. Usually, this means
20693d2a904SPaul Dagnelie                # that git returned non-zero because the file doesn't exist, but it
20793d2a904SPaul Dagnelie                # could also fail if git can't create a new file or it can't be
20893d2a904SPaul Dagnelie                # executed.  Such errors are 1) unlikely, and 2) will be caught by other
20993d2a904SPaul Dagnelie                # invocations of git().
21093d2a904SPaul Dagnelie                continue
21193d2a904SPaul Dagnelie            empty = not res.readline()
21293d2a904SPaul Dagnelie            if (os.path.exists(f) and not empty and select(f) and not exclude(f)):
2138bcea973SRichard Lowe                yield f
2148bcea973SRichard Lowe    return ret
2158bcea973SRichard Lowe
2168bcea973SRichard Lowe
2178bcea973SRichard Lowedef comchk(root, parent, flist, output):
2188bcea973SRichard Lowe    output.write("Comments:\n")
2198bcea973SRichard Lowe
2208bcea973SRichard Lowe    return Comments.comchk(git_comments(parent), check_db=True,
2218bcea973SRichard Lowe                           output=output)
2228bcea973SRichard Lowe
2238bcea973SRichard Lowe
2248bcea973SRichard Lowedef mapfilechk(root, parent, flist, output):
2258bcea973SRichard Lowe    ret = 0
2268bcea973SRichard Lowe
2278bcea973SRichard Lowe    # We are interested in examining any file that has the following
2288bcea973SRichard Lowe    # in its final path segment:
2298bcea973SRichard Lowe    #    - Contains the word 'mapfile'
2308bcea973SRichard Lowe    #    - Begins with 'map.'
2318bcea973SRichard Lowe    #    - Ends with '.map'
2328bcea973SRichard Lowe    # We don't want to match unless these things occur in final path segment
2338bcea973SRichard Lowe    # because directory names with these strings don't indicate a mapfile.
2348bcea973SRichard Lowe    # We also ignore files with suffixes that tell us that the files
2358bcea973SRichard Lowe    # are not mapfiles.
2368bcea973SRichard Lowe    MapfileRE = re.compile(r'.*((mapfile[^/]*)|(/map\.+[^/]*)|(\.map))$',
2378bcea973SRichard Lowe        re.IGNORECASE)
2388bcea973SRichard Lowe    NotMapSuffixRE = re.compile(r'.*\.[ch]$', re.IGNORECASE)
2398bcea973SRichard Lowe
2408bcea973SRichard Lowe    output.write("Mapfile comments:\n")
2418bcea973SRichard Lowe
2428bcea973SRichard Lowe    for f in flist(lambda x: MapfileRE.match(x) and not
2438bcea973SRichard Lowe                   NotMapSuffixRE.match(x)):
2448bcea973SRichard Lowe        fh = open(f, 'r')
2458bcea973SRichard Lowe        ret |= Mapfile.mapfilechk(fh, output=output)
2468bcea973SRichard Lowe        fh.close()
2478bcea973SRichard Lowe    return ret
2488bcea973SRichard Lowe
2498bcea973SRichard Lowe
2508bcea973SRichard Lowedef copyright(root, parent, flist, output):
2518bcea973SRichard Lowe    ret = 0
2528bcea973SRichard Lowe    output.write("Copyrights:\n")
2538bcea973SRichard Lowe    for f in flist():
2548bcea973SRichard Lowe        fh = open(f, 'r')
2558bcea973SRichard Lowe        ret |= Copyright.copyright(fh, output=output)
2568bcea973SRichard Lowe        fh.close()
2578bcea973SRichard Lowe    return ret
2588bcea973SRichard Lowe
2598bcea973SRichard Lowe
2608bcea973SRichard Lowedef hdrchk(root, parent, flist, output):
2618bcea973SRichard Lowe    ret = 0
2628bcea973SRichard Lowe    output.write("Header format:\n")
2638bcea973SRichard Lowe    for f in flist(lambda x: x.endswith('.h')):
2648bcea973SRichard Lowe        fh = open(f, 'r')
2658bcea973SRichard Lowe        ret |= HdrChk.hdrchk(fh, lenient=True, output=output)
2668bcea973SRichard Lowe        fh.close()
2678bcea973SRichard Lowe    return ret
2688bcea973SRichard Lowe
2698bcea973SRichard Lowe
2708bcea973SRichard Lowedef cstyle(root, parent, flist, output):
2718bcea973SRichard Lowe    ret = 0
2728bcea973SRichard Lowe    output.write("C style:\n")
2738bcea973SRichard Lowe    for f in flist(lambda x: x.endswith('.c') or x.endswith('.h')):
2748bcea973SRichard Lowe        fh = open(f, 'r')
2758bcea973SRichard Lowe        ret |= CStyle.cstyle(fh, output=output, picky=True,
2768bcea973SRichard Lowe                             check_posix_types=True,
2778bcea973SRichard Lowe                             check_continuation=True)
2788bcea973SRichard Lowe        fh.close()
2798bcea973SRichard Lowe    return ret
2808bcea973SRichard Lowe
2818bcea973SRichard Lowe
2828bcea973SRichard Lowedef jstyle(root, parent, flist, output):
2838bcea973SRichard Lowe    ret = 0
2848bcea973SRichard Lowe    output.write("Java style:\n")
2858bcea973SRichard Lowe    for f in flist(lambda x: x.endswith('.java')):
2868bcea973SRichard Lowe        fh = open(f, 'r')
2878bcea973SRichard Lowe        ret |= JStyle.jstyle(fh, output=output, picky=True)
2888bcea973SRichard Lowe        fh.close()
2898bcea973SRichard Lowe    return ret
2908bcea973SRichard Lowe
2918bcea973SRichard Lowe
29295c635efSGarrett D'Amoredef manlint(root, parent, flist, output):
29395c635efSGarrett D'Amore    ret = 0
29495c635efSGarrett D'Amore    output.write("Man page format:\n")
29595c635efSGarrett D'Amore    ManfileRE = re.compile(r'.*\.[0-9][a-z]*$', re.IGNORECASE)
29695c635efSGarrett D'Amore    for f in flist(lambda x: ManfileRE.match(x)):
29795c635efSGarrett D'Amore        fh = open(f, 'r')
29895c635efSGarrett D'Amore        ret |= ManLint.manlint(fh, output=output, picky=True)
29995c635efSGarrett D'Amore	fh.close()
30095c635efSGarrett D'Amore    return ret
30195c635efSGarrett D'Amore
3028bcea973SRichard Lowedef keywords(root, parent, flist, output):
3038bcea973SRichard Lowe    ret = 0
3048bcea973SRichard Lowe    output.write("SCCS Keywords:\n")
3058bcea973SRichard Lowe    for f in flist():
3068bcea973SRichard Lowe        fh = open(f, 'r')
3078bcea973SRichard Lowe        ret |= Keywords.keywords(fh, output=output)
3088bcea973SRichard Lowe        fh.close()
3098bcea973SRichard Lowe    return ret
3108bcea973SRichard Lowe
3118bcea973SRichard Lowe
3128bcea973SRichard Lowedef run_checks(root, parent, cmds, paths='', opts={}):
3138bcea973SRichard Lowe    """Run the checks given in 'cmds', expected to have well-known signatures,
3148bcea973SRichard Lowe    and report results for any which fail.
3158bcea973SRichard Lowe
3168bcea973SRichard Lowe    Return failure if any of them did.
3178bcea973SRichard Lowe
3188bcea973SRichard Lowe    NB: the function name of the commands passed in is used to name the NOT
3198bcea973SRichard Lowe    file which excepts files from them."""
3208bcea973SRichard Lowe
3218bcea973SRichard Lowe    ret = 0
3228bcea973SRichard Lowe
3238bcea973SRichard Lowe    for cmd in cmds:
3248bcea973SRichard Lowe        s = StringIO()
3258bcea973SRichard Lowe
3268bcea973SRichard Lowe        exclude = not_check(root, cmd.func_name)
3278bcea973SRichard Lowe        result = cmd(root, parent, gen_files(root, parent, paths, exclude),
3288bcea973SRichard Lowe                     output=s)
3298bcea973SRichard Lowe        ret |= result
3308bcea973SRichard Lowe
3318bcea973SRichard Lowe        if result != 0:
3328bcea973SRichard Lowe            print s.getvalue()
3338bcea973SRichard Lowe
3348bcea973SRichard Lowe    return ret
3358bcea973SRichard Lowe
3368bcea973SRichard Lowe
3378bcea973SRichard Lowedef nits(root, parent, paths):
3388bcea973SRichard Lowe    cmds = [copyright,
3398bcea973SRichard Lowe            cstyle,
3408bcea973SRichard Lowe            hdrchk,
3418bcea973SRichard Lowe            jstyle,
3428bcea973SRichard Lowe            keywords,
34395c635efSGarrett D'Amore	    manlint,
3448bcea973SRichard Lowe            mapfilechk]
3458bcea973SRichard Lowe    run_checks(root, parent, cmds, paths)
3468bcea973SRichard Lowe
3478bcea973SRichard Lowe
3488bcea973SRichard Lowedef pbchk(root, parent, paths):
3498bcea973SRichard Lowe    cmds = [comchk,
3508bcea973SRichard Lowe            copyright,
3518bcea973SRichard Lowe            cstyle,
3528bcea973SRichard Lowe            hdrchk,
3538bcea973SRichard Lowe            jstyle,
3548bcea973SRichard Lowe            keywords,
35595c635efSGarrett D'Amore	    manlint,
3568bcea973SRichard Lowe            mapfilechk]
3578bcea973SRichard Lowe    run_checks(root, parent, cmds)
3588bcea973SRichard Lowe
3598bcea973SRichard Lowe
3608bcea973SRichard Lowedef main(cmd, args):
3618bcea973SRichard Lowe    parent_branch = None
3628bcea973SRichard Lowe
3638bcea973SRichard Lowe    try:
3648bcea973SRichard Lowe        opts, args = getopt.getopt(args, 'b:')
3658bcea973SRichard Lowe    except getopt.GetoptError, e:
3668bcea973SRichard Lowe        sys.stderr.write(str(e) + '\n')
3678bcea973SRichard Lowe        sys.stderr.write("Usage: %s [-b branch] [path...]\n" % cmd)
3688bcea973SRichard Lowe        sys.exit(1)
3698bcea973SRichard Lowe
3708bcea973SRichard Lowe    for opt, arg in opts:
3718bcea973SRichard Lowe        if opt == '-b':
3728bcea973SRichard Lowe            parent_branch = arg
3738bcea973SRichard Lowe
3748bcea973SRichard Lowe    if not parent_branch:
3758bcea973SRichard Lowe        parent_branch = git_parent_branch(git_branch())
3768bcea973SRichard Lowe
3778bcea973SRichard Lowe    func = nits
3788bcea973SRichard Lowe    if cmd == 'git-pbchk':
3798bcea973SRichard Lowe        func = pbchk
3808bcea973SRichard Lowe        if args:
3818bcea973SRichard Lowe            sys.stderr.write("only complete workspaces may be pbchk'd\n");
3828bcea973SRichard Lowe            sys.exit(1)
3838bcea973SRichard Lowe
3848bcea973SRichard Lowe    func(git_root(), parent_branch, args)
3858bcea973SRichard Lowe
3868bcea973SRichard Loweif __name__ == '__main__':
3878bcea973SRichard Lowe    try:
3888bcea973SRichard Lowe        main(os.path.basename(sys.argv[0]), sys.argv[1:])
3898bcea973SRichard Lowe    except GitError, e:
3908bcea973SRichard Lowe        sys.stderr.write("failed to run git:\n %s\n" % str(e))
3918bcea973SRichard Lowe        sys.exit(1)
392