|
Packit |
fd8b60 |
# Copyright (C) 2012 by the Massachusetts Institute of Technology.
|
|
Packit |
fd8b60 |
# All rights reserved.
|
|
Packit |
fd8b60 |
#
|
|
Packit |
fd8b60 |
# Export of this software from the United States of America may
|
|
Packit |
fd8b60 |
# require a specific license from the United States Government.
|
|
Packit |
fd8b60 |
# It is the responsibility of any person or organization contemplating
|
|
Packit |
fd8b60 |
# export to obtain such a license before exporting.
|
|
Packit |
fd8b60 |
#
|
|
Packit |
fd8b60 |
# WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
|
|
Packit |
fd8b60 |
# distribute this software and its documentation for any purpose and
|
|
Packit |
fd8b60 |
# without fee is hereby granted, provided that the above copyright
|
|
Packit |
fd8b60 |
# notice appear in all copies and that both that copyright notice and
|
|
Packit |
fd8b60 |
# this permission notice appear in supporting documentation, and that
|
|
Packit |
fd8b60 |
# the name of M.I.T. not be used in advertising or publicity pertaining
|
|
Packit |
fd8b60 |
# to distribution of the software without specific, written prior
|
|
Packit |
fd8b60 |
# permission. Furthermore if you modify this software you must label
|
|
Packit |
fd8b60 |
# your software as modified software and not distribute it in such a
|
|
Packit |
fd8b60 |
# fashion that it might be confused with the original M.I.T. software.
|
|
Packit |
fd8b60 |
# M.I.T. makes no representations about the suitability of
|
|
Packit |
fd8b60 |
# this software for any purpose. It is provided "as is" without express
|
|
Packit |
fd8b60 |
# or implied warranty.
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
# This program attempts to detect MIT krb5 coding style violations
|
|
Packit |
fd8b60 |
# attributable to the changes a series of git commits. It can be run
|
|
Packit |
fd8b60 |
# from anywhere within a git working tree.
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
import getopt
|
|
Packit |
fd8b60 |
import os
|
|
Packit |
fd8b60 |
import re
|
|
Packit |
fd8b60 |
import sys
|
|
Packit |
fd8b60 |
from subprocess import Popen, PIPE, call
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
def usage():
|
|
Packit |
fd8b60 |
u = ['Usage: cstyle [-w] [rev|rev1..rev2]',
|
|
Packit |
fd8b60 |
'',
|
|
Packit |
fd8b60 |
'By default, checks working tree against HEAD, or checks changes in',
|
|
Packit |
fd8b60 |
'HEAD if the working tree is clean. With a revision option, checks',
|
|
Packit |
fd8b60 |
'changes in rev or the series rev1..rev2. With the -w option,',
|
|
Packit |
fd8b60 |
'checks working tree against rev (defaults to HEAD).']
|
|
Packit |
fd8b60 |
sys.stderr.write('\n'.join(u) + '\n')
|
|
Packit |
fd8b60 |
sys.exit(1)
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
# Run a command and return a list of its output lines.
|
|
Packit |
fd8b60 |
def run(args):
|
|
Packit |
fd8b60 |
# subprocess.check_output would be ideal here, but requires Python 2.7.
|
|
Packit |
fd8b60 |
p = Popen(args, stdout=PIPE, stderr=PIPE, universal_newlines=True)
|
|
Packit |
fd8b60 |
out, err = p.communicate()
|
|
Packit |
fd8b60 |
if p.returncode != 0:
|
|
Packit |
fd8b60 |
sys.stderr.write('Failed command: ' + ' '.join(args) + '\n')
|
|
Packit |
fd8b60 |
if err != '':
|
|
Packit |
fd8b60 |
sys.stderr.write('stderr:\n' + err)
|
|
Packit |
fd8b60 |
sys.stderr.write('Unexpected command failure, exiting\n')
|
|
Packit |
fd8b60 |
sys.exit(1)
|
|
Packit |
fd8b60 |
return out.splitlines()
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
# Find the top level of the git working tree, or None if we're not in
|
|
Packit |
fd8b60 |
# one.
|
|
Packit |
fd8b60 |
def find_toplevel():
|
|
Packit |
fd8b60 |
# git doesn't seem to have a way to do this, so we search by hand.
|
|
Packit |
fd8b60 |
dir = os.getcwd()
|
|
Packit |
fd8b60 |
while True:
|
|
Packit |
fd8b60 |
if os.path.exists(os.path.join(dir, '.git')):
|
|
Packit |
fd8b60 |
break
|
|
Packit |
fd8b60 |
parent = os.path.dirname(dir)
|
|
Packit |
fd8b60 |
if (parent == dir):
|
|
Packit |
fd8b60 |
return None
|
|
Packit |
fd8b60 |
dir = parent
|
|
Packit |
fd8b60 |
return dir
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
# Check for style issues in a file within rev (or within the current
|
|
Packit |
fd8b60 |
# checkout if rev is None). Report only problems on line numbers in
|
|
Packit |
fd8b60 |
# new_lines.
|
|
Packit |
fd8b60 |
line_re = re.compile(r'^\s*(\d+) (.*)$')
|
|
Packit |
fd8b60 |
def check_file(filename, rev, new_lines):
|
|
Packit |
fd8b60 |
# Process only C source files under src.
|
|
Packit |
fd8b60 |
root, ext = os.path.splitext(filename)
|
|
Packit |
fd8b60 |
if not filename.startswith('src/') or ext not in ('.c', '.h', '.hin'):
|
|
Packit |
fd8b60 |
return
|
|
Packit |
fd8b60 |
dispname = filename[4:]
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
if rev is None:
|
|
Packit |
fd8b60 |
p1 = Popen(['cat', filename], stdout=PIPE)
|
|
Packit |
fd8b60 |
else:
|
|
Packit |
fd8b60 |
p1 = Popen(['git', 'show', rev + ':' + filename], stdout=PIPE)
|
|
Packit |
fd8b60 |
p2 = Popen([sys.executable, 'src/util/cstyle-file.py'], stdin=p1.stdout,
|
|
Packit |
fd8b60 |
stdout=PIPE, universal_newlines=True)
|
|
Packit |
fd8b60 |
p1.stdout.close()
|
|
Packit |
fd8b60 |
out, err = p2.communicate()
|
|
Packit |
fd8b60 |
if p2.returncode != 0:
|
|
Packit |
fd8b60 |
sys.exit(1)
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
first = True
|
|
Packit |
fd8b60 |
for line in out.splitlines():
|
|
Packit |
fd8b60 |
m = line_re.match(line)
|
|
Packit |
fd8b60 |
if int(m.group(1)) in new_lines:
|
|
Packit |
fd8b60 |
if first:
|
|
Packit |
fd8b60 |
print(' ' + dispname + ':')
|
|
Packit |
fd8b60 |
first = False
|
|
Packit |
fd8b60 |
print(' ' + line)
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
# Determine the lines of each file modified by diff (a sequence of
|
|
Packit |
fd8b60 |
# strings) and check for style violations in those lines. rev
|
|
Packit |
fd8b60 |
# indicates the version in which the new contents of each file can be
|
|
Packit |
fd8b60 |
# found, or is None if the current contents are in the working copy.
|
|
Packit |
fd8b60 |
chunk_header_re = re.compile(r'^@@ -\d+(,(\d+))? \+(\d+)(,(\d+))? @@')
|
|
Packit |
fd8b60 |
def check_diff(diff, rev):
|
|
Packit |
fd8b60 |
old_count, new_count, lineno = 0, 0, 0
|
|
Packit |
fd8b60 |
filename = None
|
|
Packit |
fd8b60 |
for line in diff:
|
|
Packit |
fd8b60 |
if not line or line.startswith('\\ No newline'):
|
|
Packit |
fd8b60 |
continue
|
|
Packit |
fd8b60 |
if old_count > 0 or new_count > 0:
|
|
Packit |
fd8b60 |
# We're in a chunk.
|
|
Packit |
fd8b60 |
if line[0] == '+':
|
|
Packit |
fd8b60 |
new_lines.append(lineno)
|
|
Packit |
fd8b60 |
if line[0] in ('+', ' '):
|
|
Packit |
fd8b60 |
new_count = new_count - 1
|
|
Packit |
fd8b60 |
lineno = lineno + 1
|
|
Packit |
fd8b60 |
if line[0] in ('-', ' '):
|
|
Packit |
fd8b60 |
old_count = old_count - 1
|
|
Packit |
fd8b60 |
elif line.startswith('+++ b/'):
|
|
Packit |
fd8b60 |
# We're starting a new file. Check the last one.
|
|
Packit |
fd8b60 |
if filename:
|
|
Packit |
fd8b60 |
check_file(filename, rev, new_lines)
|
|
Packit |
fd8b60 |
filename = line[6:]
|
|
Packit |
fd8b60 |
new_lines = []
|
|
Packit |
fd8b60 |
else:
|
|
Packit |
fd8b60 |
m = chunk_header_re.match(line)
|
|
Packit |
fd8b60 |
if m:
|
|
Packit |
fd8b60 |
old_count = int(m.group(2) or '1')
|
|
Packit |
fd8b60 |
lineno = int(m.group(3))
|
|
Packit |
fd8b60 |
new_count = int(m.group(5) or '1')
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
# Check the last file in the diff.
|
|
Packit |
fd8b60 |
if filename:
|
|
Packit |
fd8b60 |
check_file(filename, rev, new_lines)
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
# Check a sequence of revisions for style issues.
|
|
Packit |
fd8b60 |
def check_series(revlist):
|
|
Packit |
fd8b60 |
for rev in revlist:
|
|
Packit |
fd8b60 |
sys.stdout.flush()
|
|
Packit |
fd8b60 |
call(['git', 'show', '-s', '--oneline', rev])
|
|
Packit |
fd8b60 |
diff = run(['git', 'diff-tree', '--no-commit-id', '--root', '-M',
|
|
Packit |
fd8b60 |
'--cc', rev])
|
|
Packit |
fd8b60 |
check_diff(diff, rev)
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
# Parse arguments.
|
|
Packit |
fd8b60 |
try:
|
|
Packit |
fd8b60 |
opts, args = getopt.getopt(sys.argv[1:], 'w')
|
|
Packit |
fd8b60 |
except getopt.GetoptError as err:
|
|
Packit |
fd8b60 |
print(str(err))
|
|
Packit |
fd8b60 |
usage()
|
|
Packit |
fd8b60 |
if len(args) > 1:
|
|
Packit |
fd8b60 |
usage()
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
# Change to the top level of the working tree so we easily run the file
|
|
Packit |
fd8b60 |
# checker and refer to working tree files.
|
|
Packit |
fd8b60 |
toplevel = find_toplevel()
|
|
Packit |
fd8b60 |
if toplevel is None:
|
|
Packit |
fd8b60 |
sys.stderr.write('%s must be run within a git working tree')
|
|
Packit |
fd8b60 |
os.chdir(toplevel)
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
if ('-w', '') in opts:
|
|
Packit |
fd8b60 |
# Check the working tree against a base revision.
|
|
Packit |
fd8b60 |
arg = 'HEAD'
|
|
Packit |
fd8b60 |
if args:
|
|
Packit |
fd8b60 |
arg = args[0]
|
|
Packit |
fd8b60 |
check_diff(run(['git', 'diff', arg]), None)
|
|
Packit |
fd8b60 |
elif args:
|
|
Packit |
fd8b60 |
# Check the differences in a rev or a series of revs.
|
|
Packit |
fd8b60 |
if '..' in args[0]:
|
|
Packit |
fd8b60 |
check_series(run(['git', 'rev-list', '--reverse', args[0]]))
|
|
Packit |
fd8b60 |
else:
|
|
Packit |
fd8b60 |
check_series([args[0]])
|
|
Packit |
fd8b60 |
else:
|
|
Packit |
fd8b60 |
# No options or arguments. Check the differences against HEAD, or
|
|
Packit |
fd8b60 |
# the differences in HEAD if the working tree is clean.
|
|
Packit |
fd8b60 |
diff = run(['git', 'diff', 'HEAD'])
|
|
Packit |
fd8b60 |
if diff:
|
|
Packit |
fd8b60 |
check_diff(diff, None)
|
|
Packit |
fd8b60 |
else:
|
|
Packit |
fd8b60 |
check_series(['HEAD'])
|