xref: /titanic_52/usr/src/tools/scripts/git-pbchk.py (revision 93be19b94b8b631e9abb3b71f9415817c441c051)
1*93be19b9SAndy 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>
21e5587435SJoshua M. Clulow# Copyright (c) 2014, Joyent, Inc.
22*93be19b9SAndy Fiddaman# Copyright 2018 OmniOS Community Edition (OmniOSce) Association.
238bcea973SRichard Lowe#
248bcea973SRichard Lowe
25*93be19b9SAndy Fiddamanfrom __future__ import print_function
26*93be19b9SAndy Fiddaman
278bcea973SRichard Loweimport getopt
28*93be19b9SAndy Fiddamanimport io
298bcea973SRichard Loweimport os
308bcea973SRichard Loweimport re
318bcea973SRichard Loweimport subprocess
328bcea973SRichard Loweimport sys
33ff50e8e5SRichard Loweimport tempfile
348bcea973SRichard Lowe
35*93be19b9SAndy Fiddamanif sys.version_info[0] < 3:
368bcea973SRichard Lowe    from cStringIO import StringIO
37*93be19b9SAndy Fiddamanelse:
38*93be19b9SAndy Fiddaman    from io import StringIO
398bcea973SRichard Lowe
408bcea973SRichard Lowe#
418bcea973SRichard Lowe# Adjust the load path based on our location and the version of python into
428bcea973SRichard Lowe# which it is being loaded.  This assumes the normal onbld directory
438bcea973SRichard Lowe# structure, where we are in bin/ and the modules are in
448bcea973SRichard Lowe# lib/python(version)?/onbld/Scm/.  If that changes so too must this.
458bcea973SRichard Lowe#
468bcea973SRichard Lowesys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "lib",
478bcea973SRichard Lowe                                "python%d.%d" % sys.version_info[:2]))
488bcea973SRichard Lowe
498bcea973SRichard Lowe#
508bcea973SRichard Lowe# Add the relative path to usr/src/tools to the load path, such that when run
518bcea973SRichard Lowe# from the source tree we use the modules also within the source tree.
528bcea973SRichard Lowe#
538bcea973SRichard Lowesys.path.insert(2, os.path.join(os.path.dirname(__file__), ".."))
548bcea973SRichard Lowe
55e5587435SJoshua M. Clulowfrom onbld.Scm import Ignore
568bcea973SRichard Lowefrom onbld.Checks import Comments, Copyright, CStyle, HdrChk
5795c635efSGarrett D'Amorefrom onbld.Checks import JStyle, Keywords, ManLint, Mapfile
588bcea973SRichard Lowe
598bcea973SRichard Loweclass GitError(Exception):
608bcea973SRichard Lowe    pass
618bcea973SRichard Lowe
628bcea973SRichard Lowedef git(command):
638bcea973SRichard Lowe    """Run a command and return a stream containing its stdout (and write its
648bcea973SRichard Lowe    stderr to its stdout)"""
658bcea973SRichard Lowe
668bcea973SRichard Lowe    if type(command) != list:
678bcea973SRichard Lowe        command = command.split()
688bcea973SRichard Lowe
698bcea973SRichard Lowe    command = ["git"] + command
708bcea973SRichard Lowe
71ff50e8e5SRichard Lowe    try:
72*93be19b9SAndy Fiddaman        tmpfile = tempfile.TemporaryFile(prefix="git-nits", mode="w+b")
73*93be19b9SAndy Fiddaman    except EnvironmentError as e:
74ff50e8e5SRichard Lowe        raise GitError("Could not create temporary file: %s\n" % e)
75ff50e8e5SRichard Lowe
76ff50e8e5SRichard Lowe    try:
778bcea973SRichard Lowe        p = subprocess.Popen(command,
78ff50e8e5SRichard Lowe                             stdout=tmpfile,
798bcea973SRichard Lowe                             stderr=subprocess.STDOUT)
80*93be19b9SAndy Fiddaman    except OSError as e:
8165dc50eaSDillon Amburgey        raise GitError("could not execute %s: %s\n" % (command, e))
828bcea973SRichard Lowe
838bcea973SRichard Lowe    err = p.wait()
848bcea973SRichard Lowe    if err != 0:
858bcea973SRichard Lowe        raise GitError(p.stdout.read())
868bcea973SRichard Lowe
87ff50e8e5SRichard Lowe    tmpfile.seek(0)
88*93be19b9SAndy Fiddaman    lines = []
89*93be19b9SAndy Fiddaman    for l in tmpfile:
90*93be19b9SAndy Fiddaman        lines.append(l.decode('utf-8', 'replace'))
91*93be19b9SAndy Fiddaman    return lines
928bcea973SRichard Lowe
938bcea973SRichard Lowedef git_root():
948bcea973SRichard Lowe    """Return the root of the current git workspace"""
958bcea973SRichard Lowe
968bcea973SRichard Lowe    p = git('rev-parse --git-dir')
97*93be19b9SAndy Fiddaman    dir = p[0]
988bcea973SRichard Lowe
99*93be19b9SAndy Fiddaman    return os.path.abspath(os.path.join(dir, os.path.pardir))
1008bcea973SRichard Lowe
1018bcea973SRichard Lowedef git_branch():
1028bcea973SRichard Lowe    """Return the current git branch"""
1038bcea973SRichard Lowe
1048bcea973SRichard Lowe    p = git('branch')
1058bcea973SRichard Lowe
1068bcea973SRichard Lowe    for elt in p:
1078bcea973SRichard Lowe        if elt[0] == '*':
1088bcea973SRichard Lowe            if elt.endswith('(no branch)'):
1098bcea973SRichard Lowe                return None
1108bcea973SRichard Lowe            return elt.split()[1]
1118bcea973SRichard Lowe
1128bcea973SRichard Lowedef git_parent_branch(branch):
1138bcea973SRichard Lowe    """Return the parent of the current git branch.
1148bcea973SRichard Lowe
1158bcea973SRichard Lowe    If this branch tracks a remote branch, return the remote branch which is
1168bcea973SRichard Lowe    tracked.  If not, default to origin/master."""
1178bcea973SRichard Lowe
1188bcea973SRichard Lowe    if not branch:
1198bcea973SRichard Lowe        return None
1208bcea973SRichard Lowe
1218bcea973SRichard Lowe    p = git("for-each-ref --format=%(refname:short) %(upstream:short) " +
1228bcea973SRichard Lowe            "refs/heads/")
1238bcea973SRichard Lowe
1248bcea973SRichard Lowe    if not p:
1258bcea973SRichard Lowe        sys.stderr.write("Failed finding git parent branch\n")
1268bcea973SRichard Lowe        sys.exit(err)
1278bcea973SRichard Lowe
1288bcea973SRichard Lowe    for line in p:
1298bcea973SRichard Lowe        # Git 1.7 will leave a ' ' trailing any non-tracking branch
1308bcea973SRichard Lowe        if ' ' in line and not line.endswith(' \n'):
1318bcea973SRichard Lowe            local, remote = line.split()
1328bcea973SRichard Lowe            if local == branch:
1338bcea973SRichard Lowe                return remote
1348bcea973SRichard Lowe    return 'origin/master'
1358bcea973SRichard Lowe
1368bcea973SRichard Lowedef git_comments(parent):
1378bcea973SRichard Lowe    """Return a list of any checkin comments on this git branch"""
1388bcea973SRichard Lowe
13927495383SRichard Lowe    p = git('log --pretty=tformat:%%B:SEP: %s..' % parent)
1408bcea973SRichard Lowe
1418bcea973SRichard Lowe    if not p:
1428bcea973SRichard Lowe        sys.stderr.write("Failed getting git comments\n")
1438bcea973SRichard Lowe        sys.exit(err)
1448bcea973SRichard Lowe
145*93be19b9SAndy Fiddaman    return [x.strip() for x in p if x != ':SEP:\n']
1468bcea973SRichard Lowe
1478bcea973SRichard Lowedef git_file_list(parent, paths=None):
1488bcea973SRichard Lowe    """Return the set of files which have ever changed on this branch.
1498bcea973SRichard Lowe
1508bcea973SRichard Lowe    NB: This includes files which no longer exist, or no longer actually
1518bcea973SRichard Lowe    differ."""
1528bcea973SRichard Lowe
1538bcea973SRichard Lowe    p = git("log --name-only --pretty=format: %s.. %s" %
1548bcea973SRichard Lowe             (parent, ' '.join(paths)))
1558bcea973SRichard Lowe
1568bcea973SRichard Lowe    if not p:
1578bcea973SRichard Lowe        sys.stderr.write("Failed building file-list from git\n")
1588bcea973SRichard Lowe        sys.exit(err)
1598bcea973SRichard Lowe
1608bcea973SRichard Lowe    ret = set()
1618bcea973SRichard Lowe    for fname in p:
1628bcea973SRichard Lowe        if fname and not fname.isspace() and fname not in ret:
1638bcea973SRichard Lowe            ret.add(fname.strip())
1648bcea973SRichard Lowe
1658bcea973SRichard Lowe    return ret
1668bcea973SRichard Lowe
1678bcea973SRichard Lowedef not_check(root, cmd):
1688bcea973SRichard Lowe    """Return a function which returns True if a file given as an argument
1698bcea973SRichard Lowe    should be excluded from the check named by 'cmd'"""
1708bcea973SRichard Lowe
171*93be19b9SAndy Fiddaman    ignorefiles = list(filter(os.path.exists,
1728bcea973SRichard Lowe                         [os.path.join(root, ".git", "%s.NOT" % cmd),
173*93be19b9SAndy Fiddaman                          os.path.join(root, "exception_lists", cmd)]))
174e5587435SJoshua M. Clulow    return Ignore.ignore(root, ignorefiles)
1758bcea973SRichard Lowe
1768bcea973SRichard Lowedef gen_files(root, parent, paths, exclude):
1778bcea973SRichard Lowe    """Return a function producing file names, relative to the current
1788bcea973SRichard Lowe    directory, of any file changed on this branch (limited to 'paths' if
1798bcea973SRichard Lowe    requested), and excluding files for which exclude returns a true value """
1808bcea973SRichard Lowe
1818bcea973SRichard Lowe    # Taken entirely from Python 2.6's os.path.relpath which we would use if we
1828bcea973SRichard Lowe    # could.
1838bcea973SRichard Lowe    def relpath(path, here):
1848bcea973SRichard Lowe        c = os.path.abspath(os.path.join(root, path)).split(os.path.sep)
1858bcea973SRichard Lowe        s = os.path.abspath(here).split(os.path.sep)
1868bcea973SRichard Lowe        l = len(os.path.commonprefix((s, c)))
1878bcea973SRichard Lowe        return os.path.join(*[os.path.pardir] * (len(s)-l) + c[l:])
1888bcea973SRichard Lowe
1898bcea973SRichard Lowe    def ret(select=None):
1908bcea973SRichard Lowe        if not select:
1918bcea973SRichard Lowe            select = lambda x: True
1928bcea973SRichard Lowe
1938bcea973SRichard Lowe        for f in git_file_list(parent, paths):
1948bcea973SRichard Lowe            f = relpath(f, '.')
1958bcea973SRichard Lowe            if (os.path.exists(f) and select(f) and not exclude(f)):
1968bcea973SRichard Lowe                yield f
1978bcea973SRichard Lowe    return ret
1988bcea973SRichard Lowe
1998bcea973SRichard Lowedef comchk(root, parent, flist, output):
2008bcea973SRichard Lowe    output.write("Comments:\n")
2018bcea973SRichard Lowe
2028bcea973SRichard Lowe    return Comments.comchk(git_comments(parent), check_db=True,
2038bcea973SRichard Lowe                           output=output)
2048bcea973SRichard Lowe
2058bcea973SRichard Lowe
2068bcea973SRichard Lowedef mapfilechk(root, parent, flist, output):
2078bcea973SRichard Lowe    ret = 0
2088bcea973SRichard Lowe
2098bcea973SRichard Lowe    # We are interested in examining any file that has the following
2108bcea973SRichard Lowe    # in its final path segment:
2118bcea973SRichard Lowe    #    - Contains the word 'mapfile'
2128bcea973SRichard Lowe    #    - Begins with 'map.'
2138bcea973SRichard Lowe    #    - Ends with '.map'
2148bcea973SRichard Lowe    # We don't want to match unless these things occur in final path segment
2158bcea973SRichard Lowe    # because directory names with these strings don't indicate a mapfile.
2168bcea973SRichard Lowe    # We also ignore files with suffixes that tell us that the files
2178bcea973SRichard Lowe    # are not mapfiles.
2188bcea973SRichard Lowe    MapfileRE = re.compile(r'.*((mapfile[^/]*)|(/map\.+[^/]*)|(\.map))$',
2198bcea973SRichard Lowe        re.IGNORECASE)
2208bcea973SRichard Lowe    NotMapSuffixRE = re.compile(r'.*\.[ch]$', re.IGNORECASE)
2218bcea973SRichard Lowe
2228bcea973SRichard Lowe    output.write("Mapfile comments:\n")
2238bcea973SRichard Lowe
2248bcea973SRichard Lowe    for f in flist(lambda x: MapfileRE.match(x) and not
2258bcea973SRichard Lowe                   NotMapSuffixRE.match(x)):
226*93be19b9SAndy Fiddaman        with io.open(f, encoding='utf-8', errors='replace') as fh:
2278bcea973SRichard Lowe            ret |= Mapfile.mapfilechk(fh, output=output)
2288bcea973SRichard Lowe    return ret
2298bcea973SRichard Lowe
2308bcea973SRichard Lowedef copyright(root, parent, flist, output):
2318bcea973SRichard Lowe    ret = 0
2328bcea973SRichard Lowe    output.write("Copyrights:\n")
2338bcea973SRichard Lowe    for f in flist():
234*93be19b9SAndy Fiddaman        with io.open(f, encoding='utf-8', errors='replace') as fh:
2358bcea973SRichard Lowe            ret |= Copyright.copyright(fh, output=output)
2368bcea973SRichard Lowe    return ret
2378bcea973SRichard Lowe
2388bcea973SRichard Lowedef hdrchk(root, parent, flist, output):
2398bcea973SRichard Lowe    ret = 0
2408bcea973SRichard Lowe    output.write("Header format:\n")
2418bcea973SRichard Lowe    for f in flist(lambda x: x.endswith('.h')):
242*93be19b9SAndy Fiddaman        with io.open(f, encoding='utf-8', errors='replace') as fh:
2438bcea973SRichard Lowe            ret |= HdrChk.hdrchk(fh, lenient=True, output=output)
2448bcea973SRichard Lowe    return ret
2458bcea973SRichard Lowe
2468bcea973SRichard Lowedef cstyle(root, parent, flist, output):
2478bcea973SRichard Lowe    ret = 0
2488bcea973SRichard Lowe    output.write("C style:\n")
2498bcea973SRichard Lowe    for f in flist(lambda x: x.endswith('.c') or x.endswith('.h')):
250*93be19b9SAndy Fiddaman        with io.open(f, encoding='utf-8', errors='replace') as fh:
2518bcea973SRichard Lowe            ret |= CStyle.cstyle(fh, output=output, picky=True,
2528bcea973SRichard Lowe                             check_posix_types=True,
2538bcea973SRichard Lowe                             check_continuation=True)
2548bcea973SRichard Lowe    return ret
2558bcea973SRichard Lowe
2568bcea973SRichard Lowedef jstyle(root, parent, flist, output):
2578bcea973SRichard Lowe    ret = 0
2588bcea973SRichard Lowe    output.write("Java style:\n")
2598bcea973SRichard Lowe    for f in flist(lambda x: x.endswith('.java')):
260*93be19b9SAndy Fiddaman        with io.open(f, encoding='utf-8', errors='replace') as fh:
2618bcea973SRichard Lowe            ret |= JStyle.jstyle(fh, output=output, picky=True)
2628bcea973SRichard Lowe    return ret
2638bcea973SRichard Lowe
26495c635efSGarrett D'Amoredef manlint(root, parent, flist, output):
26595c635efSGarrett D'Amore    ret = 0
26695c635efSGarrett D'Amore    output.write("Man page format:\n")
26795c635efSGarrett D'Amore    ManfileRE = re.compile(r'.*\.[0-9][a-z]*$', re.IGNORECASE)
26895c635efSGarrett D'Amore    for f in flist(lambda x: ManfileRE.match(x)):
269*93be19b9SAndy Fiddaman        with io.open(f, encoding='utf-8', errors='replace') as fh:
27095c635efSGarrett D'Amore            ret |= ManLint.manlint(fh, output=output, picky=True)
27195c635efSGarrett D'Amore    return ret
27295c635efSGarrett D'Amore
2738bcea973SRichard Lowedef keywords(root, parent, flist, output):
2748bcea973SRichard Lowe    ret = 0
2758bcea973SRichard Lowe    output.write("SCCS Keywords:\n")
2768bcea973SRichard Lowe    for f in flist():
277*93be19b9SAndy Fiddaman        with io.open(f, encoding='utf-8', errors='replace') as fh:
2788bcea973SRichard Lowe            ret |= Keywords.keywords(fh, output=output)
2798bcea973SRichard Lowe    return ret
2808bcea973SRichard Lowe
2818bcea973SRichard Lowedef run_checks(root, parent, cmds, paths='', opts={}):
2828bcea973SRichard Lowe    """Run the checks given in 'cmds', expected to have well-known signatures,
2838bcea973SRichard Lowe    and report results for any which fail.
2848bcea973SRichard Lowe
2858bcea973SRichard Lowe    Return failure if any of them did.
2868bcea973SRichard Lowe
2878bcea973SRichard Lowe    NB: the function name of the commands passed in is used to name the NOT
2888bcea973SRichard Lowe    file which excepts files from them."""
2898bcea973SRichard Lowe
2908bcea973SRichard Lowe    ret = 0
2918bcea973SRichard Lowe
2928bcea973SRichard Lowe    for cmd in cmds:
2938bcea973SRichard Lowe        s = StringIO()
2948bcea973SRichard Lowe
295*93be19b9SAndy Fiddaman        exclude = not_check(root, cmd.__name__)
2968bcea973SRichard Lowe        result = cmd(root, parent, gen_files(root, parent, paths, exclude),
2978bcea973SRichard Lowe                     output=s)
2988bcea973SRichard Lowe        ret |= result
2998bcea973SRichard Lowe
3008bcea973SRichard Lowe        if result != 0:
301*93be19b9SAndy Fiddaman            print(s.getvalue())
3028bcea973SRichard Lowe
3038bcea973SRichard Lowe    return ret
3048bcea973SRichard Lowe
3058bcea973SRichard Lowedef nits(root, parent, paths):
3068bcea973SRichard Lowe    cmds = [copyright,
3078bcea973SRichard Lowe            cstyle,
3088bcea973SRichard Lowe            hdrchk,
3098bcea973SRichard Lowe            jstyle,
3108bcea973SRichard Lowe            keywords,
31195c635efSGarrett D'Amore	    manlint,
3128bcea973SRichard Lowe            mapfilechk]
3138bcea973SRichard Lowe    run_checks(root, parent, cmds, paths)
3148bcea973SRichard Lowe
3158bcea973SRichard Lowedef pbchk(root, parent, paths):
3168bcea973SRichard Lowe    cmds = [comchk,
3178bcea973SRichard Lowe            copyright,
3188bcea973SRichard Lowe            cstyle,
3198bcea973SRichard Lowe            hdrchk,
3208bcea973SRichard Lowe            jstyle,
3218bcea973SRichard Lowe            keywords,
32295c635efSGarrett D'Amore	    manlint,
3238bcea973SRichard Lowe            mapfilechk]
3248bcea973SRichard Lowe    run_checks(root, parent, cmds)
3258bcea973SRichard Lowe
3268bcea973SRichard Lowedef main(cmd, args):
3278bcea973SRichard Lowe    parent_branch = None
3288bcea973SRichard Lowe
3298bcea973SRichard Lowe    try:
3308bcea973SRichard Lowe        opts, args = getopt.getopt(args, 'b:')
331*93be19b9SAndy Fiddaman    except getopt.GetoptError as e:
3328bcea973SRichard Lowe        sys.stderr.write(str(e) + '\n')
3338bcea973SRichard Lowe        sys.stderr.write("Usage: %s [-b branch] [path...]\n" % cmd)
3348bcea973SRichard Lowe        sys.exit(1)
3358bcea973SRichard Lowe
3368bcea973SRichard Lowe    for opt, arg in opts:
3378bcea973SRichard Lowe        if opt == '-b':
3388bcea973SRichard Lowe            parent_branch = arg
3398bcea973SRichard Lowe
3408bcea973SRichard Lowe    if not parent_branch:
3418bcea973SRichard Lowe        parent_branch = git_parent_branch(git_branch())
3428bcea973SRichard Lowe
3438bcea973SRichard Lowe    func = nits
3448bcea973SRichard Lowe    if cmd == 'git-pbchk':
3458bcea973SRichard Lowe        func = pbchk
3468bcea973SRichard Lowe        if args:
3478bcea973SRichard Lowe            sys.stderr.write("only complete workspaces may be pbchk'd\n");
3488bcea973SRichard Lowe            sys.exit(1)
3498bcea973SRichard Lowe
3508bcea973SRichard Lowe    func(git_root(), parent_branch, args)
3518bcea973SRichard Lowe
3528bcea973SRichard Loweif __name__ == '__main__':
3538bcea973SRichard Lowe    try:
3548bcea973SRichard Lowe        main(os.path.basename(sys.argv[0]), sys.argv[1:])
355*93be19b9SAndy Fiddaman    except GitError as e:
3568bcea973SRichard Lowe        sys.stderr.write("failed to run git:\n %s\n" % str(e))
3578bcea973SRichard Lowe        sys.exit(1)
358