xref: /illumos-gate/usr/src/tools/scripts/git-pbchk.py (revision 3f8945a7885826cfc497736d70b1fd0a999d65e8)
13f770aabSAndy Fiddaman#!@TOOLS_PYTHON@ -Es
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
20955eb5e1SGarrett D'Amore# Copyright 2019 Garrett D'Amore <garrett@damore.org>
2193d2a904SPaul Dagnelie# Copyright (c) 2015, 2016 by Delphix. All rights reserved.
2228e2b3adSHans Rosenfeld# Copyright 2016 Nexenta Systems, Inc.
23972282a0SJohn Levon# Copyright (c) 2019, Joyent, Inc.
2413904da8SAndy Fiddaman# Copyright 2021 OmniOS Community Edition (OmniOSce) Association.
259af2fe54SBill Sommerfeld# Copyright 2024 Bill Sommerfeld
268bcea973SRichard Lowe#
278bcea973SRichard Lowe
28ca13eaa5SAndy Fiddamanfrom __future__ import print_function
29ca13eaa5SAndy Fiddaman
308bcea973SRichard Loweimport getopt
31ca13eaa5SAndy Fiddamanimport io
328bcea973SRichard Loweimport os
338bcea973SRichard Loweimport re
348bcea973SRichard Loweimport subprocess
358bcea973SRichard Loweimport sys
36ff50e8e5SRichard Loweimport tempfile
378bcea973SRichard Lowe
38ca13eaa5SAndy Fiddamanif sys.version_info[0] < 3:
398bcea973SRichard Lowe    from cStringIO import StringIO
40ca13eaa5SAndy Fiddamanelse:
41ca13eaa5SAndy Fiddaman    from io import StringIO
428bcea973SRichard Lowe
438bcea973SRichard Lowe#
448bcea973SRichard Lowe# Adjust the load path based on our location and the version of python into
458bcea973SRichard Lowe# which it is being loaded.  This assumes the normal onbld directory
468bcea973SRichard Lowe# structure, where we are in bin/ and the modules are in
478bcea973SRichard Lowe# lib/python(version)?/onbld/Scm/.  If that changes so too must this.
488bcea973SRichard Lowe#
498bcea973SRichard Lowesys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "lib",
508bcea973SRichard Lowe                                "python%d.%d" % sys.version_info[:2]))
518bcea973SRichard Lowe
528bcea973SRichard Lowe#
538bcea973SRichard Lowe# Add the relative path to usr/src/tools to the load path, such that when run
548bcea973SRichard Lowe# from the source tree we use the modules also within the source tree.
558bcea973SRichard Lowe#
568bcea973SRichard Lowesys.path.insert(2, os.path.join(os.path.dirname(__file__), ".."))
578bcea973SRichard Lowe
58e5587435SJoshua M. Clulowfrom onbld.Scm import Ignore
594ff15898SGordon Rossfrom onbld.Checks import Comments, Copyright, CStyle, HdrChk, WsCheck
6071af3be3SCody Peter Mellofrom onbld.Checks import JStyle, Keywords, ManLint, Mapfile, SpellCheck
6186d41711SAndy Fiddamanfrom onbld.Checks import ShellLint, PkgFmt
628bcea973SRichard Lowe
638bcea973SRichard Loweclass GitError(Exception):
648bcea973SRichard Lowe    pass
658bcea973SRichard Lowe
668bcea973SRichard Lowedef git(command):
678bcea973SRichard Lowe    """Run a command and return a stream containing its stdout (and write its
688bcea973SRichard Lowe    stderr to its stdout)"""
698bcea973SRichard Lowe
708bcea973SRichard Lowe    if type(command) != list:
718bcea973SRichard Lowe        command = command.split()
728bcea973SRichard Lowe
738bcea973SRichard Lowe    command = ["git"] + command
748bcea973SRichard Lowe
75ff50e8e5SRichard Lowe    try:
76ca13eaa5SAndy Fiddaman        tmpfile = tempfile.TemporaryFile(prefix="git-nits", mode="w+b")
77ca13eaa5SAndy Fiddaman    except EnvironmentError as e:
78ff50e8e5SRichard Lowe        raise GitError("Could not create temporary file: %s\n" % e)
79ff50e8e5SRichard Lowe
80ff50e8e5SRichard Lowe    try:
818bcea973SRichard Lowe        p = subprocess.Popen(command,
82ff50e8e5SRichard Lowe                             stdout=tmpfile,
83380fd671SMatthew Ahrens                             stderr=subprocess.PIPE)
84ca13eaa5SAndy Fiddaman    except OSError as e:
85709afb1dSDillon Amburgey        raise GitError("could not execute %s: %s\n" % (command, e))
868bcea973SRichard Lowe
878bcea973SRichard Lowe    err = p.wait()
888bcea973SRichard Lowe    if err != 0:
89380fd671SMatthew Ahrens        raise GitError(p.stderr.read())
908bcea973SRichard Lowe
91ff50e8e5SRichard Lowe    tmpfile.seek(0)
92ca13eaa5SAndy Fiddaman    lines = []
93ca13eaa5SAndy Fiddaman    for l in tmpfile:
94ca13eaa5SAndy Fiddaman        lines.append(l.decode('utf-8', 'replace'))
95ca13eaa5SAndy Fiddaman    return lines
968bcea973SRichard Lowe
978bcea973SRichard Lowedef git_root():
988bcea973SRichard Lowe    """Return the root of the current git workspace"""
998bcea973SRichard Lowe
1009af2fe54SBill Sommerfeld    p = git('rev-parse --show-toplevel')
1019af2fe54SBill Sommerfeld    dir = p[0].strip()
1028bcea973SRichard Lowe
1039af2fe54SBill Sommerfeld    return os.path.abspath(dir)
1048bcea973SRichard Lowe
1058bcea973SRichard Lowedef git_branch():
1068bcea973SRichard Lowe    """Return the current git branch"""
1078bcea973SRichard Lowe
1088bcea973SRichard Lowe    p = git('branch')
1098bcea973SRichard Lowe
1108bcea973SRichard Lowe    for elt in p:
1118bcea973SRichard Lowe        if elt[0] == '*':
1128bcea973SRichard Lowe            if elt.endswith('(no branch)'):
1138bcea973SRichard Lowe                return None
1148bcea973SRichard Lowe            return elt.split()[1]
1158bcea973SRichard Lowe
1168bcea973SRichard Lowedef git_parent_branch(branch):
1178bcea973SRichard Lowe    """Return the parent of the current git branch.
1188bcea973SRichard Lowe
1198bcea973SRichard Lowe    If this branch tracks a remote branch, return the remote branch which is
1208bcea973SRichard Lowe    tracked.  If not, default to origin/master."""
1218bcea973SRichard Lowe
1228bcea973SRichard Lowe    if not branch:
1238bcea973SRichard Lowe        return None
1248bcea973SRichard Lowe
12528e2b3adSHans Rosenfeld    p = git(["for-each-ref", "--format=%(refname:short) %(upstream:short)",
12628e2b3adSHans Rosenfeld            "refs/heads/"])
1278bcea973SRichard Lowe
1288bcea973SRichard Lowe    if not p:
1298bcea973SRichard Lowe        sys.stderr.write("Failed finding git parent branch\n")
130972282a0SJohn Levon        sys.exit(1)
1318bcea973SRichard Lowe
1328bcea973SRichard Lowe    for line in p:
1338bcea973SRichard Lowe        # Git 1.7 will leave a ' ' trailing any non-tracking branch
1348bcea973SRichard Lowe        if ' ' in line and not line.endswith(' \n'):
1358bcea973SRichard Lowe            local, remote = line.split()
1368bcea973SRichard Lowe            if local == branch:
1378bcea973SRichard Lowe                return remote
1388bcea973SRichard Lowe    return 'origin/master'
1398bcea973SRichard Lowe
140*3f8945a7SBill Sommerfelddef slices(strlist, sep):
141*3f8945a7SBill Sommerfeld    """Yield start & end of each commit within the list of comments"""
142*3f8945a7SBill Sommerfeld    low = 0
143*3f8945a7SBill Sommerfeld    for i, v in enumerate(strlist):
144*3f8945a7SBill Sommerfeld        if v == sep:
145*3f8945a7SBill Sommerfeld            yield(low, i)
146*3f8945a7SBill Sommerfeld            low = i+1
147*3f8945a7SBill Sommerfeld
148*3f8945a7SBill Sommerfeld    if low != len(strlist):
149*3f8945a7SBill Sommerfeld        yield(low, len(strlist))
150*3f8945a7SBill Sommerfeld
1518bcea973SRichard Lowedef git_comments(parent):
152*3f8945a7SBill Sommerfeld    """Return the checkin comments for each commit on this git branch,
153*3f8945a7SBill Sommerfeld    structured as a list of lists of lines."""
1548bcea973SRichard Lowe
15527495383SRichard Lowe    p = git('log --pretty=tformat:%%B:SEP: %s..' % parent)
1568bcea973SRichard Lowe
1578bcea973SRichard Lowe    if not p:
158972282a0SJohn Levon        sys.stderr.write("No outgoing changesets found - missing -p option?\n");
159972282a0SJohn Levon        sys.exit(1)
1608bcea973SRichard Lowe
161*3f8945a7SBill Sommerfeld    return [ [line.strip() for line in p[a:b]]
162*3f8945a7SBill Sommerfeld             for (a, b) in slices(p, ':SEP:\n')]
1638bcea973SRichard Lowe
1648bcea973SRichard Lowedef git_file_list(parent, paths=None):
1658bcea973SRichard Lowe    """Return the set of files which have ever changed on this branch.
1668bcea973SRichard Lowe
1678bcea973SRichard Lowe    NB: This includes files which no longer exist, or no longer actually
1688bcea973SRichard Lowe    differ."""
1698bcea973SRichard Lowe
1708bcea973SRichard Lowe    p = git("log --name-only --pretty=format: %s.. %s" %
1718bcea973SRichard Lowe             (parent, ' '.join(paths)))
1728bcea973SRichard Lowe
1738bcea973SRichard Lowe    if not p:
1748bcea973SRichard Lowe        sys.stderr.write("Failed building file-list from git\n")
175972282a0SJohn Levon        sys.exit(1)
1768bcea973SRichard Lowe
1778bcea973SRichard Lowe    ret = set()
1788bcea973SRichard Lowe    for fname in p:
179da88d39fSBill Sommerfeld        fname = fname.strip()
180da88d39fSBill Sommerfeld        if fname and not fname.isspace():
181da88d39fSBill Sommerfeld            ret.add(fname)
1828bcea973SRichard Lowe
183da88d39fSBill Sommerfeld    return sorted(ret)
1848bcea973SRichard Lowe
1858bcea973SRichard Lowedef not_check(root, cmd):
1868bcea973SRichard Lowe    """Return a function which returns True if a file given as an argument
1878bcea973SRichard Lowe    should be excluded from the check named by 'cmd'"""
1888bcea973SRichard Lowe
189ca13eaa5SAndy Fiddaman    ignorefiles = list(filter(os.path.exists,
190c82c4676SGordon Ross                         [os.path.join(root, ".git/info", "%s.NOT" % cmd),
191ca13eaa5SAndy Fiddaman                          os.path.join(root, "exception_lists", cmd)]))
192e5587435SJoshua M. Clulow    return Ignore.ignore(root, ignorefiles)
1938bcea973SRichard Lowe
194955eb5e1SGarrett D'Amoredef gen_files(root, parent, paths, exclude, filter=None):
1958bcea973SRichard Lowe    """Return a function producing file names, relative to the current
1968bcea973SRichard Lowe    directory, of any file changed on this branch (limited to 'paths' if
1978bcea973SRichard Lowe    requested), and excluding files for which exclude returns a true value """
1988bcea973SRichard Lowe
199955eb5e1SGarrett D'Amore    if filter is None:
200955eb5e1SGarrett D'Amore        filter = lambda x: os.path.isfile(x)
201955eb5e1SGarrett D'Amore
2028bcea973SRichard Lowe    def ret(select=None):
2038bcea973SRichard Lowe        if not select:
2048bcea973SRichard Lowe            select = lambda x: True
2058bcea973SRichard Lowe
20638e36c53SJohn Levon        for abspath in git_file_list(parent, paths):
2079af2fe54SBill Sommerfeld            path = os.path.relpath(os.path.join(root, abspath), '.')
20893d2a904SPaul Dagnelie            try:
20938e36c53SJohn Levon                res = git("diff %s HEAD %s" % (parent, path))
210ca13eaa5SAndy Fiddaman            except GitError as e:
21138e36c53SJohn Levon                # This ignores all the errors that can be thrown. Usually, this
21238e36c53SJohn Levon                # means that git returned non-zero because the file doesn't
21338e36c53SJohn Levon                # exist, but it could also fail if git can't create a new file
21438e36c53SJohn Levon                # or it can't be executed.  Such errors are 1) unlikely, and 2)
21538e36c53SJohn Levon                # will be caught by other invocations of git().
21693d2a904SPaul Dagnelie                continue
217ca13eaa5SAndy Fiddaman            empty = not res
218955eb5e1SGarrett D'Amore            if (filter(path) and not empty and
21938e36c53SJohn Levon                select(path) and not exclude(abspath)):
22038e36c53SJohn Levon                yield path
2218bcea973SRichard Lowe    return ret
2228bcea973SRichard Lowe
223955eb5e1SGarrett D'Amoredef gen_links(root, parent, paths, exclude):
224955eb5e1SGarrett D'Amore    """Return a function producing symbolic link names, relative to the current
225955eb5e1SGarrett D'Amore    directory, of any file changed on this branch (limited to 'paths' if
226955eb5e1SGarrett D'Amore    requested), and excluding files for which exclude returns a true value """
227955eb5e1SGarrett D'Amore
228955eb5e1SGarrett D'Amore    return gen_files(root, parent, paths, exclude, lambda x: os.path.islink(x))
229955eb5e1SGarrett D'Amore
2308bcea973SRichard Lowedef comchk(root, parent, flist, output):
2318bcea973SRichard Lowe    output.write("Comments:\n")
2328bcea973SRichard Lowe
2338d226a82SBill Sommerfeld    comments = git_comments(parent)
234*3f8945a7SBill Sommerfeld    multi = len(comments) > 1
235*3f8945a7SBill Sommerfeld    state = {}
2368d226a82SBill Sommerfeld
237*3f8945a7SBill Sommerfeld    ret = 0
238*3f8945a7SBill Sommerfeld    for commit in comments:
2398bcea973SRichard Lowe
240*3f8945a7SBill Sommerfeld        s = StringIO()
241*3f8945a7SBill Sommerfeld
242*3f8945a7SBill Sommerfeld        result = Comments.comchk(commit, check_db=True,
243*3f8945a7SBill Sommerfeld                                 output=s, bugs=state)
244*3f8945a7SBill Sommerfeld        ret |= result
245*3f8945a7SBill Sommerfeld
246*3f8945a7SBill Sommerfeld        if result != 0:
247*3f8945a7SBill Sommerfeld            if multi:
248*3f8945a7SBill Sommerfeld                output.write('\n%s\n' % commit[0])
249*3f8945a7SBill Sommerfeld            output.write(s.getvalue())
250*3f8945a7SBill Sommerfeld
251*3f8945a7SBill Sommerfeld    return ret
2528bcea973SRichard Lowe
2538bcea973SRichard Lowedef mapfilechk(root, parent, flist, output):
2548bcea973SRichard Lowe    ret = 0
2558bcea973SRichard Lowe
2568bcea973SRichard Lowe    # We are interested in examining any file that has the following
2578bcea973SRichard Lowe    # in its final path segment:
2588bcea973SRichard Lowe    #    - Contains the word 'mapfile'
2598bcea973SRichard Lowe    #    - Begins with 'map.'
2608bcea973SRichard Lowe    #    - Ends with '.map'
2618bcea973SRichard Lowe    # We don't want to match unless these things occur in final path segment
2628bcea973SRichard Lowe    # because directory names with these strings don't indicate a mapfile.
2638bcea973SRichard Lowe    # We also ignore files with suffixes that tell us that the files
2648bcea973SRichard Lowe    # are not mapfiles.
2658bcea973SRichard Lowe    MapfileRE = re.compile(r'.*((mapfile[^/]*)|(/map\.+[^/]*)|(\.map))$',
2668bcea973SRichard Lowe        re.IGNORECASE)
2678bcea973SRichard Lowe    NotMapSuffixRE = re.compile(r'.*\.[ch]$', re.IGNORECASE)
2688bcea973SRichard Lowe
2698bcea973SRichard Lowe    output.write("Mapfile comments:\n")
2708bcea973SRichard Lowe
2718bcea973SRichard Lowe    for f in flist(lambda x: MapfileRE.match(x) and not
2728bcea973SRichard Lowe                   NotMapSuffixRE.match(x)):
273ca13eaa5SAndy Fiddaman        with io.open(f, encoding='utf-8', errors='replace') as fh:
2748bcea973SRichard Lowe            ret |= Mapfile.mapfilechk(fh, output=output)
2758bcea973SRichard Lowe    return ret
2768bcea973SRichard Lowe
2778bcea973SRichard Lowedef copyright(root, parent, flist, output):
2788bcea973SRichard Lowe    ret = 0
2798bcea973SRichard Lowe    output.write("Copyrights:\n")
2808bcea973SRichard Lowe    for f in flist():
281ca13eaa5SAndy Fiddaman        with io.open(f, encoding='utf-8', errors='replace') as fh:
2828bcea973SRichard Lowe            ret |= Copyright.copyright(fh, output=output)
2838bcea973SRichard Lowe    return ret
2848bcea973SRichard Lowe
2858bcea973SRichard Lowedef hdrchk(root, parent, flist, output):
2868bcea973SRichard Lowe    ret = 0
2878bcea973SRichard Lowe    output.write("Header format:\n")
2888bcea973SRichard Lowe    for f in flist(lambda x: x.endswith('.h')):
289ca13eaa5SAndy Fiddaman        with io.open(f, encoding='utf-8', errors='replace') as fh:
2908bcea973SRichard Lowe            ret |= HdrChk.hdrchk(fh, lenient=True, output=output)
2918bcea973SRichard Lowe    return ret
2928bcea973SRichard Lowe
2938bcea973SRichard Lowedef cstyle(root, parent, flist, output):
2948bcea973SRichard Lowe    ret = 0
2958bcea973SRichard Lowe    output.write("C style:\n")
2968bcea973SRichard Lowe    for f in flist(lambda x: x.endswith('.c') or x.endswith('.h')):
297a90997d2SAndy Fiddaman        with io.open(f, mode='rb') as fh:
2988bcea973SRichard Lowe            ret |= CStyle.cstyle(fh, output=output, picky=True,
2998bcea973SRichard Lowe                             check_posix_types=True,
3008bcea973SRichard Lowe                             check_continuation=True)
3018bcea973SRichard Lowe    return ret
3028bcea973SRichard Lowe
3038bcea973SRichard Lowedef jstyle(root, parent, flist, output):
3048bcea973SRichard Lowe    ret = 0
3058bcea973SRichard Lowe    output.write("Java style:\n")
3068bcea973SRichard Lowe    for f in flist(lambda x: x.endswith('.java')):
30710811751SAndy Fiddaman        with io.open(f, mode='rb') as fh:
3088bcea973SRichard Lowe            ret |= JStyle.jstyle(fh, output=output, picky=True)
3098bcea973SRichard Lowe    return ret
3108bcea973SRichard Lowe
31195c635efSGarrett D'Amoredef manlint(root, parent, flist, output):
31295c635efSGarrett D'Amore    ret = 0
31371af3be3SCody Peter Mello    output.write("Man page format/spelling:\n")
31495c635efSGarrett D'Amore    ManfileRE = re.compile(r'.*\.[0-9][a-z]*$', re.IGNORECASE)
31595c635efSGarrett D'Amore    for f in flist(lambda x: ManfileRE.match(x)):
316a90997d2SAndy Fiddaman        with io.open(f, mode='rb') as fh:
31795c635efSGarrett D'Amore            ret |= ManLint.manlint(fh, output=output, picky=True)
31871af3be3SCody Peter Mello            ret |= SpellCheck.spellcheck(fh, output=output)
31995c635efSGarrett D'Amore    return ret
32095c635efSGarrett D'Amore
32113904da8SAndy Fiddamandef shelllint(root, parent, flist, output):
32213904da8SAndy Fiddaman    ret = 0
32313904da8SAndy Fiddaman    output.write("Shell lint:\n")
32413904da8SAndy Fiddaman
32513904da8SAndy Fiddaman    def isshell(x):
32613904da8SAndy Fiddaman        (_, ext) = os.path.splitext(x)
32713904da8SAndy Fiddaman        if ext in ['.sh', '.ksh']:
32813904da8SAndy Fiddaman            return True
32913904da8SAndy Fiddaman        if ext == '':
33013904da8SAndy Fiddaman            with io.open(x, mode='r', errors='ignore') as fh:
33113904da8SAndy Fiddaman                if re.match(r'^#.*\bk?sh\b', fh.readline()):
33213904da8SAndy Fiddaman                    return True
33313904da8SAndy Fiddaman        return False
33413904da8SAndy Fiddaman
33513904da8SAndy Fiddaman    for f in flist(isshell):
33613904da8SAndy Fiddaman        with io.open(f, mode='rb') as fh:
33713904da8SAndy Fiddaman            ret |= ShellLint.lint(fh, output=output)
33813904da8SAndy Fiddaman
33913904da8SAndy Fiddaman    return ret
34013904da8SAndy Fiddaman
34186d41711SAndy Fiddamandef pkgfmt(root, parent, flist, output):
34286d41711SAndy Fiddaman    ret = 0
34386d41711SAndy Fiddaman    output.write("Package manifests:\n")
34486d41711SAndy Fiddaman
34525b05a3eSAndy Fiddaman    for f in flist(lambda x: x.endswith('.p5m')):
34686d41711SAndy Fiddaman        with io.open(f, mode='rb') as fh:
34786d41711SAndy Fiddaman            ret |= PkgFmt.check(fh, output=output)
34886d41711SAndy Fiddaman
34986d41711SAndy Fiddaman    return ret
35086d41711SAndy Fiddaman
3518bcea973SRichard Lowedef keywords(root, parent, flist, output):
3528bcea973SRichard Lowe    ret = 0
3538bcea973SRichard Lowe    output.write("SCCS Keywords:\n")
3548bcea973SRichard Lowe    for f in flist():
355ca13eaa5SAndy Fiddaman        with io.open(f, encoding='utf-8', errors='replace') as fh:
3568bcea973SRichard Lowe            ret |= Keywords.keywords(fh, output=output)
3578bcea973SRichard Lowe    return ret
3588bcea973SRichard Lowe
3594ff15898SGordon Rossdef wscheck(root, parent, flist, output):
3604ff15898SGordon Ross    ret = 0
3614ff15898SGordon Ross    output.write("white space nits:\n")
3624ff15898SGordon Ross    for f in flist():
363ca13eaa5SAndy Fiddaman        with io.open(f, encoding='utf-8', errors='replace') as fh:
3644ff15898SGordon Ross            ret |= WsCheck.wscheck(fh, output=output)
3654ff15898SGordon Ross    return ret
3668bcea973SRichard Lowe
367955eb5e1SGarrett D'Amoredef symlinks(root, parent, flist, output):
368955eb5e1SGarrett D'Amore    ret = 0
369955eb5e1SGarrett D'Amore    output.write("Symbolic links:\n")
370955eb5e1SGarrett D'Amore    for f in flist():
371955eb5e1SGarrett D'Amore        output.write("  "+f+"\n")
372955eb5e1SGarrett D'Amore        ret |= 1
373955eb5e1SGarrett D'Amore    return ret
374955eb5e1SGarrett D'Amore
375955eb5e1SGarrett D'Amoredef iswinreserved(name):
376955eb5e1SGarrett D'Amore    reserved = [
377955eb5e1SGarrett D'Amore        'con', 'prn', 'aux', 'nul',
378955eb5e1SGarrett D'Amore        'com1', 'com2', 'com3', 'com4', 'com5',
379955eb5e1SGarrett D'Amore        'com6', 'com7', 'com8', 'com9', 'com0',
380955eb5e1SGarrett D'Amore        'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5',
381955eb5e1SGarrett D'Amore        'lpt6', 'lpt7', 'lpt8', 'lpt9', 'lpt0' ]
382955eb5e1SGarrett D'Amore    l = name.lower()
383955eb5e1SGarrett D'Amore    for r in reserved:
384955eb5e1SGarrett D'Amore        if l == r or l.startswith(r+"."):
385955eb5e1SGarrett D'Amore            return True
386955eb5e1SGarrett D'Amore    return False
387955eb5e1SGarrett D'Amore
388955eb5e1SGarrett D'Amoredef haswinspecial(name):
389955eb5e1SGarrett D'Amore    specials = '<>:"\\|?*'
390955eb5e1SGarrett D'Amore    for c in name:
391955eb5e1SGarrett D'Amore        if c in specials:
392955eb5e1SGarrett D'Amore            return True
393955eb5e1SGarrett D'Amore    return False
394955eb5e1SGarrett D'Amore
395955eb5e1SGarrett D'Amoredef winnames(root, parent, flist, output):
396955eb5e1SGarrett D'Amore    ret = 0
397955eb5e1SGarrett D'Amore    output.write("Illegal filenames (Windows):\n")
398955eb5e1SGarrett D'Amore    for f in flist():
399955eb5e1SGarrett D'Amore        if haswinspecial(f):
400955eb5e1SGarrett D'Amore            output.write("  "+f+": invalid character in name\n")
401955eb5e1SGarrett D'Amore            ret |= 1
402955eb5e1SGarrett D'Amore            continue
403955eb5e1SGarrett D'Amore
404955eb5e1SGarrett D'Amore        parts = f.split('/')
405955eb5e1SGarrett D'Amore        for p in parts:
406955eb5e1SGarrett D'Amore            if iswinreserved(p):
407955eb5e1SGarrett D'Amore                output.write("  "+f+": reserved file name\n")
408955eb5e1SGarrett D'Amore                ret |= 1
409955eb5e1SGarrett D'Amore                break
410955eb5e1SGarrett D'Amore
411955eb5e1SGarrett D'Amore    return ret
412955eb5e1SGarrett D'Amore
413955eb5e1SGarrett D'Amoredef run_checks(root, parent, cmds, scmds, paths='', opts={}):
4148bcea973SRichard Lowe    """Run the checks given in 'cmds', expected to have well-known signatures,
4158bcea973SRichard Lowe    and report results for any which fail.
4168bcea973SRichard Lowe
4178bcea973SRichard Lowe    Return failure if any of them did.
4188bcea973SRichard Lowe
4198bcea973SRichard Lowe    NB: the function name of the commands passed in is used to name the NOT
4208bcea973SRichard Lowe    file which excepts files from them."""
4218bcea973SRichard Lowe
4228bcea973SRichard Lowe    ret = 0
4238bcea973SRichard Lowe
4248bcea973SRichard Lowe    for cmd in cmds:
4258bcea973SRichard Lowe        s = StringIO()
4268bcea973SRichard Lowe
427ca13eaa5SAndy Fiddaman        exclude = not_check(root, cmd.__name__)
4288bcea973SRichard Lowe        result = cmd(root, parent, gen_files(root, parent, paths, exclude),
4298bcea973SRichard Lowe                     output=s)
4308bcea973SRichard Lowe        ret |= result
4318bcea973SRichard Lowe
4328bcea973SRichard Lowe        if result != 0:
433ca13eaa5SAndy Fiddaman            print(s.getvalue())
4348bcea973SRichard Lowe
435955eb5e1SGarrett D'Amore    for cmd in scmds:
436955eb5e1SGarrett D'Amore        s = StringIO()
437955eb5e1SGarrett D'Amore
438955eb5e1SGarrett D'Amore        exclude = not_check(root, cmd.__name__)
439955eb5e1SGarrett D'Amore        result = cmd(root, parent, gen_links(root, parent, paths, exclude),
440955eb5e1SGarrett D'Amore                     output=s)
441955eb5e1SGarrett D'Amore        ret |= result
442955eb5e1SGarrett D'Amore
443955eb5e1SGarrett D'Amore        if result != 0:
444955eb5e1SGarrett D'Amore            print(s.getvalue())
445955eb5e1SGarrett D'Amore
4468bcea973SRichard Lowe    return ret
4478bcea973SRichard Lowe
4488bcea973SRichard Lowedef nits(root, parent, paths):
4498bcea973SRichard Lowe    cmds = [copyright,
4508bcea973SRichard Lowe            cstyle,
4518bcea973SRichard Lowe            hdrchk,
4528bcea973SRichard Lowe            jstyle,
4538bcea973SRichard Lowe            keywords,
45495c635efSGarrett D'Amore            manlint,
4554ff15898SGordon Ross            mapfilechk,
45613904da8SAndy Fiddaman            shelllint,
45786d41711SAndy Fiddaman            pkgfmt,
458955eb5e1SGarrett D'Amore            winnames,
4594ff15898SGordon Ross            wscheck]
460955eb5e1SGarrett D'Amore    scmds = [symlinks]
461955eb5e1SGarrett D'Amore    run_checks(root, parent, cmds, scmds, paths)
4628bcea973SRichard Lowe
4638bcea973SRichard Lowedef pbchk(root, parent, paths):
4648bcea973SRichard Lowe    cmds = [comchk,
4658bcea973SRichard Lowe            copyright,
4668bcea973SRichard Lowe            cstyle,
4678bcea973SRichard Lowe            hdrchk,
4688bcea973SRichard Lowe            jstyle,
4698bcea973SRichard Lowe            keywords,
47095c635efSGarrett D'Amore            manlint,
4714ff15898SGordon Ross            mapfilechk,
47213904da8SAndy Fiddaman            shelllint,
47386d41711SAndy Fiddaman            pkgfmt,
474955eb5e1SGarrett D'Amore            winnames,
4754ff15898SGordon Ross            wscheck]
476955eb5e1SGarrett D'Amore    scmds = [symlinks]
477955eb5e1SGarrett D'Amore    run_checks(root, parent, cmds, scmds)
4788bcea973SRichard Lowe
4798bcea973SRichard Lowedef main(cmd, args):
4808bcea973SRichard Lowe    parent_branch = None
481eabe844aSJohn Levon    checkname = None
4828bcea973SRichard Lowe
4838bcea973SRichard Lowe    try:
48442a3762dSJoshua M. Clulow        opts, args = getopt.getopt(args, 'b:c:p:')
485ca13eaa5SAndy Fiddaman    except getopt.GetoptError as e:
4868bcea973SRichard Lowe        sys.stderr.write(str(e) + '\n')
487eabe844aSJohn Levon        sys.stderr.write("Usage: %s [-c check] [-p branch] [path...]\n" % cmd)
4888bcea973SRichard Lowe        sys.exit(1)
4898bcea973SRichard Lowe
4908bcea973SRichard Lowe    for opt, arg in opts:
49142a3762dSJoshua M. Clulow        # We accept "-b" as an alias of "-p" for backwards compatibility.
49242a3762dSJoshua M. Clulow        if opt == '-p' or opt == '-b':
4938bcea973SRichard Lowe            parent_branch = arg
494eabe844aSJohn Levon        elif opt == '-c':
495eabe844aSJohn Levon            checkname = arg
4968bcea973SRichard Lowe
4978bcea973SRichard Lowe    if not parent_branch:
4988bcea973SRichard Lowe        parent_branch = git_parent_branch(git_branch())
4998bcea973SRichard Lowe
500eabe844aSJohn Levon    if checkname is None:
5018bcea973SRichard Lowe        if cmd == 'git-pbchk':
502eabe844aSJohn Levon            checkname = 'pbchk'
503eabe844aSJohn Levon        else:
504eabe844aSJohn Levon            checkname = 'nits'
505eabe844aSJohn Levon
506eabe844aSJohn Levon    if checkname == 'pbchk':
5078bcea973SRichard Lowe        if args:
5088bcea973SRichard Lowe            sys.stderr.write("only complete workspaces may be pbchk'd\n");
5098bcea973SRichard Lowe            sys.exit(1)
510eabe844aSJohn Levon        pbchk(git_root(), parent_branch, None)
511eabe844aSJohn Levon    elif checkname == 'nits':
512eabe844aSJohn Levon        nits(git_root(), parent_branch, args)
513eabe844aSJohn Levon    else:
514eabe844aSJohn Levon        run_checks(git_root(), parent_branch, [eval(checkname)], args)
5158bcea973SRichard Lowe
5168bcea973SRichard Loweif __name__ == '__main__':
5178bcea973SRichard Lowe    try:
5188bcea973SRichard Lowe        main(os.path.basename(sys.argv[0]), sys.argv[1:])
519ca13eaa5SAndy Fiddaman    except GitError as e:
5208bcea973SRichard Lowe        sys.stderr.write("failed to run git:\n %s\n" % str(e))
5218bcea973SRichard Lowe        sys.exit(1)
522