summaryrefslogtreecommitdiff
path: root/usr/src/tools/scripts/git-pbchk.py
diff options
context:
space:
mode:
authorRichard Lowe <richlowe@richlowe.net>2010-09-15 22:49:45 -0400
committerRichard Lowe <richlowe@richlowe.net>2010-09-15 22:49:45 -0400
commit8bcea973790ad3e762bf78b7c6ad5776e463fd51 (patch)
treeaa45b26b7f95e77bdc960c477515b740daffae14 /usr/src/tools/scripts/git-pbchk.py
parent13a426762210070b1e2dd1184ea1a88ec0d227e8 (diff)
downloadillumos-joyent-8bcea973790ad3e762bf78b7c6ad5776e463fd51.tar.gz
1960 developer tools should support git
Reviewed by: Joshua M. Clulow <josh@sysmgr.org> Reviewed by: John Sonnenschein <johns@joyent.com> Approved by: Garrett D'Amore <garrett@damore.org>
Diffstat (limited to 'usr/src/tools/scripts/git-pbchk.py')
-rw-r--r--usr/src/tools/scripts/git-pbchk.py362
1 files changed, 362 insertions, 0 deletions
diff --git a/usr/src/tools/scripts/git-pbchk.py b/usr/src/tools/scripts/git-pbchk.py
new file mode 100644
index 0000000000..59af2f3832
--- /dev/null
+++ b/usr/src/tools/scripts/git-pbchk.py
@@ -0,0 +1,362 @@
+#!/usr/bin/python2.4
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2
+# as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+
+#
+# Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright 2008, 2012 Richard Lowe
+#
+
+import getopt
+import os
+import re
+import subprocess
+import sys
+
+from cStringIO import StringIO
+
+# This is necessary because, in a fit of pique, we used hg-format ignore lists
+# for NOT files.
+from mercurial import ignore
+
+#
+# Adjust the load path based on our location and the version of python into
+# which it is being loaded. This assumes the normal onbld directory
+# structure, where we are in bin/ and the modules are in
+# lib/python(version)?/onbld/Scm/. If that changes so too must this.
+#
+sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "lib",
+ "python%d.%d" % sys.version_info[:2]))
+
+#
+# Add the relative path to usr/src/tools to the load path, such that when run
+# from the source tree we use the modules also within the source tree.
+#
+sys.path.insert(2, os.path.join(os.path.dirname(__file__), ".."))
+
+from onbld.Checks import Comments, Copyright, CStyle, HdrChk
+from onbld.Checks import JStyle, Keywords, Mapfile
+
+
+class GitError(Exception):
+ pass
+
+def git(command):
+ """Run a command and return a stream containing its stdout (and write its
+ stderr to its stdout)"""
+
+ if type(command) != list:
+ command = command.split()
+
+ command = ["git"] + command
+
+ p = subprocess.Popen(command,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+
+ err = p.wait()
+ if err != 0:
+ raise GitError(p.stdout.read())
+
+ return p.stdout
+
+
+def git_root():
+ """Return the root of the current git workspace"""
+
+ p = git('rev-parse --git-dir')
+
+ if not p:
+ sys.stderr.write("Failed finding git workspace\n")
+ sys.exit(err)
+
+ return os.path.abspath(os.path.join(p.readlines()[0],
+ os.path.pardir))
+
+
+def git_branch():
+ """Return the current git branch"""
+
+ p = git('branch')
+
+ if not p:
+ sys.stderr.write("Failed finding git branch\n")
+ sys.exit(err)
+
+ for elt in p:
+ if elt[0] == '*':
+ if elt.endswith('(no branch)'):
+ return None
+ return elt.split()[1]
+
+
+def git_parent_branch(branch):
+ """Return the parent of the current git branch.
+
+ If this branch tracks a remote branch, return the remote branch which is
+ tracked. If not, default to origin/master."""
+
+ if not branch:
+ return None
+
+ p = git("for-each-ref --format=%(refname:short) %(upstream:short) " +
+ "refs/heads/")
+
+ if not p:
+ sys.stderr.write("Failed finding git parent branch\n")
+ sys.exit(err)
+
+ for line in p:
+ # Git 1.7 will leave a ' ' trailing any non-tracking branch
+ if ' ' in line and not line.endswith(' \n'):
+ local, remote = line.split()
+ if local == branch:
+ return remote
+ return 'origin/master'
+
+
+def git_comments(parent):
+ """Return a list of any checkin comments on this git branch"""
+
+ p = git('log --pretty=format:%%B %s..' % parent)
+
+ if not p:
+ sys.stderr.write("Failed getting git comments\n")
+ sys.exit(err)
+
+ return map(lambda x: x.strip(), p.readlines())
+
+
+def git_file_list(parent, paths=None):
+ """Return the set of files which have ever changed on this branch.
+
+ NB: This includes files which no longer exist, or no longer actually
+ differ."""
+
+ p = git("log --name-only --pretty=format: %s.. %s" %
+ (parent, ' '.join(paths)))
+
+ if not p:
+ sys.stderr.write("Failed building file-list from git\n")
+ sys.exit(err)
+
+ ret = set()
+ for fname in p:
+ if fname and not fname.isspace() and fname not in ret:
+ ret.add(fname.strip())
+
+ return ret
+
+
+def not_check(root, cmd):
+ """Return a function which returns True if a file given as an argument
+ should be excluded from the check named by 'cmd'"""
+
+ ignorefiles = filter(os.path.exists,
+ [os.path.join(root, ".git", "%s.NOT" % cmd),
+ os.path.join(root, "exception_lists", cmd)])
+ if len(ignorefiles) > 0:
+ return ignore.ignore(root, ignorefiles, sys.stderr.write)
+ else:
+ return lambda x: False
+
+
+def gen_files(root, parent, paths, exclude):
+ """Return a function producing file names, relative to the current
+ directory, of any file changed on this branch (limited to 'paths' if
+ requested), and excluding files for which exclude returns a true value """
+
+ # Taken entirely from Python 2.6's os.path.relpath which we would use if we
+ # could.
+ def relpath(path, here):
+ c = os.path.abspath(os.path.join(root, path)).split(os.path.sep)
+ s = os.path.abspath(here).split(os.path.sep)
+ l = len(os.path.commonprefix((s, c)))
+ return os.path.join(*[os.path.pardir] * (len(s)-l) + c[l:])
+
+ def ret(select=None):
+ if not select:
+ select = lambda x: True
+
+ for f in git_file_list(parent, paths):
+ f = relpath(f, '.')
+ if (os.path.exists(f) and select(f) and not exclude(f)):
+ yield f
+ return ret
+
+
+def comchk(root, parent, flist, output):
+ output.write("Comments:\n")
+
+ return Comments.comchk(git_comments(parent), check_db=True,
+ output=output)
+
+
+def mapfilechk(root, parent, flist, output):
+ ret = 0
+
+ # We are interested in examining any file that has the following
+ # in its final path segment:
+ # - Contains the word 'mapfile'
+ # - Begins with 'map.'
+ # - Ends with '.map'
+ # We don't want to match unless these things occur in final path segment
+ # because directory names with these strings don't indicate a mapfile.
+ # We also ignore files with suffixes that tell us that the files
+ # are not mapfiles.
+ MapfileRE = re.compile(r'.*((mapfile[^/]*)|(/map\.+[^/]*)|(\.map))$',
+ re.IGNORECASE)
+ NotMapSuffixRE = re.compile(r'.*\.[ch]$', re.IGNORECASE)
+
+ output.write("Mapfile comments:\n")
+
+ for f in flist(lambda x: MapfileRE.match(x) and not
+ NotMapSuffixRE.match(x)):
+ fh = open(f, 'r')
+ ret |= Mapfile.mapfilechk(fh, output=output)
+ fh.close()
+ return ret
+
+
+def copyright(root, parent, flist, output):
+ ret = 0
+ output.write("Copyrights:\n")
+ for f in flist():
+ fh = open(f, 'r')
+ ret |= Copyright.copyright(fh, output=output)
+ fh.close()
+ return ret
+
+
+def hdrchk(root, parent, flist, output):
+ ret = 0
+ output.write("Header format:\n")
+ for f in flist(lambda x: x.endswith('.h')):
+ fh = open(f, 'r')
+ ret |= HdrChk.hdrchk(fh, lenient=True, output=output)
+ fh.close()
+ return ret
+
+
+def cstyle(root, parent, flist, output):
+ ret = 0
+ output.write("C style:\n")
+ for f in flist(lambda x: x.endswith('.c') or x.endswith('.h')):
+ fh = open(f, 'r')
+ ret |= CStyle.cstyle(fh, output=output, picky=True,
+ check_posix_types=True,
+ check_continuation=True)
+ fh.close()
+ return ret
+
+
+def jstyle(root, parent, flist, output):
+ ret = 0
+ output.write("Java style:\n")
+ for f in flist(lambda x: x.endswith('.java')):
+ fh = open(f, 'r')
+ ret |= JStyle.jstyle(fh, output=output, picky=True)
+ fh.close()
+ return ret
+
+
+def keywords(root, parent, flist, output):
+ ret = 0
+ output.write("SCCS Keywords:\n")
+ for f in flist():
+ fh = open(f, 'r')
+ ret |= Keywords.keywords(fh, output=output)
+ fh.close()
+ return ret
+
+
+def run_checks(root, parent, cmds, paths='', opts={}):
+ """Run the checks given in 'cmds', expected to have well-known signatures,
+ and report results for any which fail.
+
+ Return failure if any of them did.
+
+ NB: the function name of the commands passed in is used to name the NOT
+ file which excepts files from them."""
+
+ ret = 0
+
+ for cmd in cmds:
+ s = StringIO()
+
+ exclude = not_check(root, cmd.func_name)
+ result = cmd(root, parent, gen_files(root, parent, paths, exclude),
+ output=s)
+ ret |= result
+
+ if result != 0:
+ print s.getvalue()
+
+ return ret
+
+
+def nits(root, parent, paths):
+ cmds = [copyright,
+ cstyle,
+ hdrchk,
+ jstyle,
+ keywords,
+ mapfilechk]
+ run_checks(root, parent, cmds, paths)
+
+
+def pbchk(root, parent, paths):
+ cmds = [comchk,
+ copyright,
+ cstyle,
+ hdrchk,
+ jstyle,
+ keywords,
+ mapfilechk]
+ run_checks(root, parent, cmds)
+
+
+def main(cmd, args):
+ parent_branch = None
+
+ try:
+ opts, args = getopt.getopt(args, 'b:')
+ except getopt.GetoptError, e:
+ sys.stderr.write(str(e) + '\n')
+ sys.stderr.write("Usage: %s [-b branch] [path...]\n" % cmd)
+ sys.exit(1)
+
+ for opt, arg in opts:
+ if opt == '-b':
+ parent_branch = arg
+
+ if not parent_branch:
+ parent_branch = git_parent_branch(git_branch())
+
+ func = nits
+ if cmd == 'git-pbchk':
+ func = pbchk
+ if args:
+ sys.stderr.write("only complete workspaces may be pbchk'd\n");
+ sys.exit(1)
+
+ func(git_root(), parent_branch, args)
+
+if __name__ == '__main__':
+ try:
+ main(os.path.basename(sys.argv[0]), sys.argv[1:])
+ except GitError, e:
+ sys.stderr.write("failed to run git:\n %s\n" % str(e))
+ sys.exit(1)