xref: /illumos-gate/usr/src/tools/scripts/git-pbchk.py (revision 8bcea973790ad3e762bf78b7c6ad5776e463fd51)
1*8bcea973SRichard Lowe#!/usr/bin/python2.4
2*8bcea973SRichard Lowe#
3*8bcea973SRichard Lowe#  This program is free software; you can redistribute it and/or modify
4*8bcea973SRichard Lowe#  it under the terms of the GNU General Public License version 2
5*8bcea973SRichard Lowe#  as published by the Free Software Foundation.
6*8bcea973SRichard Lowe#
7*8bcea973SRichard Lowe#  This program is distributed in the hope that it will be useful,
8*8bcea973SRichard Lowe#  but WITHOUT ANY WARRANTY; without even the implied warranty of
9*8bcea973SRichard Lowe#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10*8bcea973SRichard Lowe#  GNU General Public License for more details.
11*8bcea973SRichard Lowe#
12*8bcea973SRichard Lowe#  You should have received a copy of the GNU General Public License
13*8bcea973SRichard Lowe#  along with this program; if not, write to the Free Software
14*8bcea973SRichard Lowe#  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
15*8bcea973SRichard Lowe#
16*8bcea973SRichard Lowe
17*8bcea973SRichard Lowe#
18*8bcea973SRichard Lowe# Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
19*8bcea973SRichard Lowe# Copyright 2008, 2012 Richard Lowe
20*8bcea973SRichard Lowe#
21*8bcea973SRichard Lowe
22*8bcea973SRichard Loweimport getopt
23*8bcea973SRichard Loweimport os
24*8bcea973SRichard Loweimport re
25*8bcea973SRichard Loweimport subprocess
26*8bcea973SRichard Loweimport sys
27*8bcea973SRichard Lowe
28*8bcea973SRichard Lowefrom cStringIO import StringIO
29*8bcea973SRichard Lowe
30*8bcea973SRichard Lowe# This is necessary because, in a fit of pique, we used hg-format ignore lists
31*8bcea973SRichard Lowe# for NOT files.
32*8bcea973SRichard Lowefrom mercurial import ignore
33*8bcea973SRichard Lowe
34*8bcea973SRichard Lowe#
35*8bcea973SRichard Lowe# Adjust the load path based on our location and the version of python into
36*8bcea973SRichard Lowe# which it is being loaded.  This assumes the normal onbld directory
37*8bcea973SRichard Lowe# structure, where we are in bin/ and the modules are in
38*8bcea973SRichard Lowe# lib/python(version)?/onbld/Scm/.  If that changes so too must this.
39*8bcea973SRichard Lowe#
40*8bcea973SRichard Lowesys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "lib",
41*8bcea973SRichard Lowe                                "python%d.%d" % sys.version_info[:2]))
42*8bcea973SRichard Lowe
43*8bcea973SRichard Lowe#
44*8bcea973SRichard Lowe# Add the relative path to usr/src/tools to the load path, such that when run
45*8bcea973SRichard Lowe# from the source tree we use the modules also within the source tree.
46*8bcea973SRichard Lowe#
47*8bcea973SRichard Lowesys.path.insert(2, os.path.join(os.path.dirname(__file__), ".."))
48*8bcea973SRichard Lowe
49*8bcea973SRichard Lowefrom onbld.Checks import Comments, Copyright, CStyle, HdrChk
50*8bcea973SRichard Lowefrom onbld.Checks import JStyle, Keywords, Mapfile
51*8bcea973SRichard Lowe
52*8bcea973SRichard Lowe
53*8bcea973SRichard Loweclass GitError(Exception):
54*8bcea973SRichard Lowe    pass
55*8bcea973SRichard Lowe
56*8bcea973SRichard Lowedef git(command):
57*8bcea973SRichard Lowe    """Run a command and return a stream containing its stdout (and write its
58*8bcea973SRichard Lowe    stderr to its stdout)"""
59*8bcea973SRichard Lowe
60*8bcea973SRichard Lowe    if type(command) != list:
61*8bcea973SRichard Lowe        command = command.split()
62*8bcea973SRichard Lowe
63*8bcea973SRichard Lowe    command = ["git"] + command
64*8bcea973SRichard Lowe
65*8bcea973SRichard Lowe    p = subprocess.Popen(command,
66*8bcea973SRichard Lowe                         stdout=subprocess.PIPE,
67*8bcea973SRichard Lowe                         stderr=subprocess.STDOUT)
68*8bcea973SRichard Lowe
69*8bcea973SRichard Lowe    err = p.wait()
70*8bcea973SRichard Lowe    if err != 0:
71*8bcea973SRichard Lowe        raise GitError(p.stdout.read())
72*8bcea973SRichard Lowe
73*8bcea973SRichard Lowe    return p.stdout
74*8bcea973SRichard Lowe
75*8bcea973SRichard Lowe
76*8bcea973SRichard Lowedef git_root():
77*8bcea973SRichard Lowe    """Return the root of the current git workspace"""
78*8bcea973SRichard Lowe
79*8bcea973SRichard Lowe    p = git('rev-parse --git-dir')
80*8bcea973SRichard Lowe
81*8bcea973SRichard Lowe    if not p:
82*8bcea973SRichard Lowe        sys.stderr.write("Failed finding git workspace\n")
83*8bcea973SRichard Lowe        sys.exit(err)
84*8bcea973SRichard Lowe
85*8bcea973SRichard Lowe    return os.path.abspath(os.path.join(p.readlines()[0],
86*8bcea973SRichard Lowe                                        os.path.pardir))
87*8bcea973SRichard Lowe
88*8bcea973SRichard Lowe
89*8bcea973SRichard Lowedef git_branch():
90*8bcea973SRichard Lowe    """Return the current git branch"""
91*8bcea973SRichard Lowe
92*8bcea973SRichard Lowe    p = git('branch')
93*8bcea973SRichard Lowe
94*8bcea973SRichard Lowe    if not p:
95*8bcea973SRichard Lowe        sys.stderr.write("Failed finding git branch\n")
96*8bcea973SRichard Lowe        sys.exit(err)
97*8bcea973SRichard Lowe
98*8bcea973SRichard Lowe    for elt in p:
99*8bcea973SRichard Lowe        if elt[0] == '*':
100*8bcea973SRichard Lowe            if elt.endswith('(no branch)'):
101*8bcea973SRichard Lowe                return None
102*8bcea973SRichard Lowe            return elt.split()[1]
103*8bcea973SRichard Lowe
104*8bcea973SRichard Lowe
105*8bcea973SRichard Lowedef git_parent_branch(branch):
106*8bcea973SRichard Lowe    """Return the parent of the current git branch.
107*8bcea973SRichard Lowe
108*8bcea973SRichard Lowe    If this branch tracks a remote branch, return the remote branch which is
109*8bcea973SRichard Lowe    tracked.  If not, default to origin/master."""
110*8bcea973SRichard Lowe
111*8bcea973SRichard Lowe    if not branch:
112*8bcea973SRichard Lowe        return None
113*8bcea973SRichard Lowe
114*8bcea973SRichard Lowe    p = git("for-each-ref --format=%(refname:short) %(upstream:short) " +
115*8bcea973SRichard Lowe            "refs/heads/")
116*8bcea973SRichard Lowe
117*8bcea973SRichard Lowe    if not p:
118*8bcea973SRichard Lowe        sys.stderr.write("Failed finding git parent branch\n")
119*8bcea973SRichard Lowe        sys.exit(err)
120*8bcea973SRichard Lowe
121*8bcea973SRichard Lowe    for line in p:
122*8bcea973SRichard Lowe        # Git 1.7 will leave a ' ' trailing any non-tracking branch
123*8bcea973SRichard Lowe        if ' ' in line and not line.endswith(' \n'):
124*8bcea973SRichard Lowe            local, remote = line.split()
125*8bcea973SRichard Lowe            if local == branch:
126*8bcea973SRichard Lowe                return remote
127*8bcea973SRichard Lowe    return 'origin/master'
128*8bcea973SRichard Lowe
129*8bcea973SRichard Lowe
130*8bcea973SRichard Lowedef git_comments(parent):
131*8bcea973SRichard Lowe    """Return a list of any checkin comments on this git branch"""
132*8bcea973SRichard Lowe
133*8bcea973SRichard Lowe    p = git('log --pretty=format:%%B %s..' % parent)
134*8bcea973SRichard Lowe
135*8bcea973SRichard Lowe    if not p:
136*8bcea973SRichard Lowe        sys.stderr.write("Failed getting git comments\n")
137*8bcea973SRichard Lowe        sys.exit(err)
138*8bcea973SRichard Lowe
139*8bcea973SRichard Lowe    return map(lambda x: x.strip(), p.readlines())
140*8bcea973SRichard Lowe
141*8bcea973SRichard Lowe
142*8bcea973SRichard Lowedef git_file_list(parent, paths=None):
143*8bcea973SRichard Lowe    """Return the set of files which have ever changed on this branch.
144*8bcea973SRichard Lowe
145*8bcea973SRichard Lowe    NB: This includes files which no longer exist, or no longer actually
146*8bcea973SRichard Lowe    differ."""
147*8bcea973SRichard Lowe
148*8bcea973SRichard Lowe    p = git("log --name-only --pretty=format: %s.. %s" %
149*8bcea973SRichard Lowe             (parent, ' '.join(paths)))
150*8bcea973SRichard Lowe
151*8bcea973SRichard Lowe    if not p:
152*8bcea973SRichard Lowe        sys.stderr.write("Failed building file-list from git\n")
153*8bcea973SRichard Lowe        sys.exit(err)
154*8bcea973SRichard Lowe
155*8bcea973SRichard Lowe    ret = set()
156*8bcea973SRichard Lowe    for fname in p:
157*8bcea973SRichard Lowe        if fname and not fname.isspace() and fname not in ret:
158*8bcea973SRichard Lowe            ret.add(fname.strip())
159*8bcea973SRichard Lowe
160*8bcea973SRichard Lowe    return ret
161*8bcea973SRichard Lowe
162*8bcea973SRichard Lowe
163*8bcea973SRichard Lowedef not_check(root, cmd):
164*8bcea973SRichard Lowe    """Return a function which returns True if a file given as an argument
165*8bcea973SRichard Lowe    should be excluded from the check named by 'cmd'"""
166*8bcea973SRichard Lowe
167*8bcea973SRichard Lowe    ignorefiles = filter(os.path.exists,
168*8bcea973SRichard Lowe                         [os.path.join(root, ".git", "%s.NOT" % cmd),
169*8bcea973SRichard Lowe                          os.path.join(root, "exception_lists", cmd)])
170*8bcea973SRichard Lowe    if len(ignorefiles) > 0:
171*8bcea973SRichard Lowe        return ignore.ignore(root, ignorefiles, sys.stderr.write)
172*8bcea973SRichard Lowe    else:
173*8bcea973SRichard Lowe        return lambda x: False
174*8bcea973SRichard Lowe
175*8bcea973SRichard Lowe
176*8bcea973SRichard Lowedef gen_files(root, parent, paths, exclude):
177*8bcea973SRichard Lowe    """Return a function producing file names, relative to the current
178*8bcea973SRichard Lowe    directory, of any file changed on this branch (limited to 'paths' if
179*8bcea973SRichard Lowe    requested), and excluding files for which exclude returns a true value """
180*8bcea973SRichard Lowe
181*8bcea973SRichard Lowe    # Taken entirely from Python 2.6's os.path.relpath which we would use if we
182*8bcea973SRichard Lowe    # could.
183*8bcea973SRichard Lowe    def relpath(path, here):
184*8bcea973SRichard Lowe        c = os.path.abspath(os.path.join(root, path)).split(os.path.sep)
185*8bcea973SRichard Lowe        s = os.path.abspath(here).split(os.path.sep)
186*8bcea973SRichard Lowe        l = len(os.path.commonprefix((s, c)))
187*8bcea973SRichard Lowe        return os.path.join(*[os.path.pardir] * (len(s)-l) + c[l:])
188*8bcea973SRichard Lowe
189*8bcea973SRichard Lowe    def ret(select=None):
190*8bcea973SRichard Lowe        if not select:
191*8bcea973SRichard Lowe            select = lambda x: True
192*8bcea973SRichard Lowe
193*8bcea973SRichard Lowe        for f in git_file_list(parent, paths):
194*8bcea973SRichard Lowe            f = relpath(f, '.')
195*8bcea973SRichard Lowe            if (os.path.exists(f) and select(f) and not exclude(f)):
196*8bcea973SRichard Lowe                yield f
197*8bcea973SRichard Lowe    return ret
198*8bcea973SRichard Lowe
199*8bcea973SRichard Lowe
200*8bcea973SRichard Lowedef comchk(root, parent, flist, output):
201*8bcea973SRichard Lowe    output.write("Comments:\n")
202*8bcea973SRichard Lowe
203*8bcea973SRichard Lowe    return Comments.comchk(git_comments(parent), check_db=True,
204*8bcea973SRichard Lowe                           output=output)
205*8bcea973SRichard Lowe
206*8bcea973SRichard Lowe
207*8bcea973SRichard Lowedef mapfilechk(root, parent, flist, output):
208*8bcea973SRichard Lowe    ret = 0
209*8bcea973SRichard Lowe
210*8bcea973SRichard Lowe    # We are interested in examining any file that has the following
211*8bcea973SRichard Lowe    # in its final path segment:
212*8bcea973SRichard Lowe    #    - Contains the word 'mapfile'
213*8bcea973SRichard Lowe    #    - Begins with 'map.'
214*8bcea973SRichard Lowe    #    - Ends with '.map'
215*8bcea973SRichard Lowe    # We don't want to match unless these things occur in final path segment
216*8bcea973SRichard Lowe    # because directory names with these strings don't indicate a mapfile.
217*8bcea973SRichard Lowe    # We also ignore files with suffixes that tell us that the files
218*8bcea973SRichard Lowe    # are not mapfiles.
219*8bcea973SRichard Lowe    MapfileRE = re.compile(r'.*((mapfile[^/]*)|(/map\.+[^/]*)|(\.map))$',
220*8bcea973SRichard Lowe        re.IGNORECASE)
221*8bcea973SRichard Lowe    NotMapSuffixRE = re.compile(r'.*\.[ch]$', re.IGNORECASE)
222*8bcea973SRichard Lowe
223*8bcea973SRichard Lowe    output.write("Mapfile comments:\n")
224*8bcea973SRichard Lowe
225*8bcea973SRichard Lowe    for f in flist(lambda x: MapfileRE.match(x) and not
226*8bcea973SRichard Lowe                   NotMapSuffixRE.match(x)):
227*8bcea973SRichard Lowe        fh = open(f, 'r')
228*8bcea973SRichard Lowe        ret |= Mapfile.mapfilechk(fh, output=output)
229*8bcea973SRichard Lowe        fh.close()
230*8bcea973SRichard Lowe    return ret
231*8bcea973SRichard Lowe
232*8bcea973SRichard Lowe
233*8bcea973SRichard Lowedef copyright(root, parent, flist, output):
234*8bcea973SRichard Lowe    ret = 0
235*8bcea973SRichard Lowe    output.write("Copyrights:\n")
236*8bcea973SRichard Lowe    for f in flist():
237*8bcea973SRichard Lowe        fh = open(f, 'r')
238*8bcea973SRichard Lowe        ret |= Copyright.copyright(fh, output=output)
239*8bcea973SRichard Lowe        fh.close()
240*8bcea973SRichard Lowe    return ret
241*8bcea973SRichard Lowe
242*8bcea973SRichard Lowe
243*8bcea973SRichard Lowedef hdrchk(root, parent, flist, output):
244*8bcea973SRichard Lowe    ret = 0
245*8bcea973SRichard Lowe    output.write("Header format:\n")
246*8bcea973SRichard Lowe    for f in flist(lambda x: x.endswith('.h')):
247*8bcea973SRichard Lowe        fh = open(f, 'r')
248*8bcea973SRichard Lowe        ret |= HdrChk.hdrchk(fh, lenient=True, output=output)
249*8bcea973SRichard Lowe        fh.close()
250*8bcea973SRichard Lowe    return ret
251*8bcea973SRichard Lowe
252*8bcea973SRichard Lowe
253*8bcea973SRichard Lowedef cstyle(root, parent, flist, output):
254*8bcea973SRichard Lowe    ret = 0
255*8bcea973SRichard Lowe    output.write("C style:\n")
256*8bcea973SRichard Lowe    for f in flist(lambda x: x.endswith('.c') or x.endswith('.h')):
257*8bcea973SRichard Lowe        fh = open(f, 'r')
258*8bcea973SRichard Lowe        ret |= CStyle.cstyle(fh, output=output, picky=True,
259*8bcea973SRichard Lowe                             check_posix_types=True,
260*8bcea973SRichard Lowe                             check_continuation=True)
261*8bcea973SRichard Lowe        fh.close()
262*8bcea973SRichard Lowe    return ret
263*8bcea973SRichard Lowe
264*8bcea973SRichard Lowe
265*8bcea973SRichard Lowedef jstyle(root, parent, flist, output):
266*8bcea973SRichard Lowe    ret = 0
267*8bcea973SRichard Lowe    output.write("Java style:\n")
268*8bcea973SRichard Lowe    for f in flist(lambda x: x.endswith('.java')):
269*8bcea973SRichard Lowe        fh = open(f, 'r')
270*8bcea973SRichard Lowe        ret |= JStyle.jstyle(fh, output=output, picky=True)
271*8bcea973SRichard Lowe        fh.close()
272*8bcea973SRichard Lowe    return ret
273*8bcea973SRichard Lowe
274*8bcea973SRichard Lowe
275*8bcea973SRichard Lowedef keywords(root, parent, flist, output):
276*8bcea973SRichard Lowe    ret = 0
277*8bcea973SRichard Lowe    output.write("SCCS Keywords:\n")
278*8bcea973SRichard Lowe    for f in flist():
279*8bcea973SRichard Lowe        fh = open(f, 'r')
280*8bcea973SRichard Lowe        ret |= Keywords.keywords(fh, output=output)
281*8bcea973SRichard Lowe        fh.close()
282*8bcea973SRichard Lowe    return ret
283*8bcea973SRichard Lowe
284*8bcea973SRichard Lowe
285*8bcea973SRichard Lowedef run_checks(root, parent, cmds, paths='', opts={}):
286*8bcea973SRichard Lowe    """Run the checks given in 'cmds', expected to have well-known signatures,
287*8bcea973SRichard Lowe    and report results for any which fail.
288*8bcea973SRichard Lowe
289*8bcea973SRichard Lowe    Return failure if any of them did.
290*8bcea973SRichard Lowe
291*8bcea973SRichard Lowe    NB: the function name of the commands passed in is used to name the NOT
292*8bcea973SRichard Lowe    file which excepts files from them."""
293*8bcea973SRichard Lowe
294*8bcea973SRichard Lowe    ret = 0
295*8bcea973SRichard Lowe
296*8bcea973SRichard Lowe    for cmd in cmds:
297*8bcea973SRichard Lowe        s = StringIO()
298*8bcea973SRichard Lowe
299*8bcea973SRichard Lowe        exclude = not_check(root, cmd.func_name)
300*8bcea973SRichard Lowe        result = cmd(root, parent, gen_files(root, parent, paths, exclude),
301*8bcea973SRichard Lowe                     output=s)
302*8bcea973SRichard Lowe        ret |= result
303*8bcea973SRichard Lowe
304*8bcea973SRichard Lowe        if result != 0:
305*8bcea973SRichard Lowe            print s.getvalue()
306*8bcea973SRichard Lowe
307*8bcea973SRichard Lowe    return ret
308*8bcea973SRichard Lowe
309*8bcea973SRichard Lowe
310*8bcea973SRichard Lowedef nits(root, parent, paths):
311*8bcea973SRichard Lowe    cmds = [copyright,
312*8bcea973SRichard Lowe            cstyle,
313*8bcea973SRichard Lowe            hdrchk,
314*8bcea973SRichard Lowe            jstyle,
315*8bcea973SRichard Lowe            keywords,
316*8bcea973SRichard Lowe            mapfilechk]
317*8bcea973SRichard Lowe    run_checks(root, parent, cmds, paths)
318*8bcea973SRichard Lowe
319*8bcea973SRichard Lowe
320*8bcea973SRichard Lowedef pbchk(root, parent, paths):
321*8bcea973SRichard Lowe    cmds = [comchk,
322*8bcea973SRichard Lowe            copyright,
323*8bcea973SRichard Lowe            cstyle,
324*8bcea973SRichard Lowe            hdrchk,
325*8bcea973SRichard Lowe            jstyle,
326*8bcea973SRichard Lowe            keywords,
327*8bcea973SRichard Lowe            mapfilechk]
328*8bcea973SRichard Lowe    run_checks(root, parent, cmds)
329*8bcea973SRichard Lowe
330*8bcea973SRichard Lowe
331*8bcea973SRichard Lowedef main(cmd, args):
332*8bcea973SRichard Lowe    parent_branch = None
333*8bcea973SRichard Lowe
334*8bcea973SRichard Lowe    try:
335*8bcea973SRichard Lowe        opts, args = getopt.getopt(args, 'b:')
336*8bcea973SRichard Lowe    except getopt.GetoptError, e:
337*8bcea973SRichard Lowe        sys.stderr.write(str(e) + '\n')
338*8bcea973SRichard Lowe        sys.stderr.write("Usage: %s [-b branch] [path...]\n" % cmd)
339*8bcea973SRichard Lowe        sys.exit(1)
340*8bcea973SRichard Lowe
341*8bcea973SRichard Lowe    for opt, arg in opts:
342*8bcea973SRichard Lowe        if opt == '-b':
343*8bcea973SRichard Lowe            parent_branch = arg
344*8bcea973SRichard Lowe
345*8bcea973SRichard Lowe    if not parent_branch:
346*8bcea973SRichard Lowe        parent_branch = git_parent_branch(git_branch())
347*8bcea973SRichard Lowe
348*8bcea973SRichard Lowe    func = nits
349*8bcea973SRichard Lowe    if cmd == 'git-pbchk':
350*8bcea973SRichard Lowe        func = pbchk
351*8bcea973SRichard Lowe        if args:
352*8bcea973SRichard Lowe            sys.stderr.write("only complete workspaces may be pbchk'd\n");
353*8bcea973SRichard Lowe            sys.exit(1)
354*8bcea973SRichard Lowe
355*8bcea973SRichard Lowe    func(git_root(), parent_branch, args)
356*8bcea973SRichard Lowe
357*8bcea973SRichard Loweif __name__ == '__main__':
358*8bcea973SRichard Lowe    try:
359*8bcea973SRichard Lowe        main(os.path.basename(sys.argv[0]), sys.argv[1:])
360*8bcea973SRichard Lowe    except GitError, e:
361*8bcea973SRichard Lowe        sys.stderr.write("failed to run git:\n %s\n" % str(e))
362*8bcea973SRichard Lowe        sys.exit(1)
363