1 # Copyright (C) 2012 by the Massachusetts Institute of Technology.
4 # Export of this software from the United States of America may
5 # require a specific license from the United States Government.
6 # It is the responsibility of any person or organization contemplating
7 # export to obtain such a license before exporting.
9 # WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
10 # distribute this software and its documentation for any purpose and
11 # without fee is hereby granted, provided that the above copyright
12 # notice appear in all copies and that both that copyright notice and
13 # this permission notice appear in supporting documentation, and that
14 # the name of M.I.T. not be used in advertising or publicity pertaining
15 # to distribution of the software without specific, written prior
16 # permission. Furthermore if you modify this software you must label
17 # your software as modified software and not distribute it in such a
18 # fashion that it might be confused with the original M.I.T. software.
19 # M.I.T. makes no representations about the suitability of
20 # this software for any purpose. It is provided "as is" without express
21 # or implied warranty.
23 # This program attempts to detect MIT krb5 coding style violations
24 # attributable to the changes a series of git commits. It can be run
25 # from anywhere within a git working tree.
31 from subprocess import Popen, PIPE, call
34 u = ['Usage: cstyle [-w] [rev|rev1..rev2]',
36 'By default, checks working tree against HEAD, or checks changes in',
37 'HEAD if the working tree is clean. With a revision option, checks',
38 'changes in rev or the series rev1..rev2. With the -w option,',
39 'checks working tree against rev (defaults to HEAD).']
40 sys.stderr.write('\n'.join(u) + '\n')
44 # Run a command and return a list of its output lines.
46 # subprocess.check_output would be ideal here, but requires Python 2.7.
47 p = Popen(args, stdout=PIPE, stderr=PIPE, universal_newlines=True)
48 out, err = p.communicate()
50 sys.stderr.write('Failed command: ' + ' '.join(args) + '\n')
52 sys.stderr.write('stderr:\n' + err)
53 sys.stderr.write('Unexpected command failure, exiting\n')
55 return out.splitlines()
58 # Find the top level of the git working tree, or None if we're not in
61 # git doesn't seem to have a way to do this, so we search by hand.
64 if os.path.exists(os.path.join(dir, '.git')):
66 parent = os.path.dirname(dir)
73 # Check for style issues in a file within rev (or within the current
74 # checkout if rev is None). Report only problems on line numbers in
76 line_re = re.compile(r'^\s*(\d+) (.*)$')
77 def check_file(filename, rev, new_lines):
78 # Process only C source files under src.
79 root, ext = os.path.splitext(filename)
80 if not filename.startswith('src/') or ext not in ('.c', '.h', '.hin'):
82 dispname = filename[4:]
85 p1 = Popen(['cat', filename], stdout=PIPE)
87 p1 = Popen(['git', 'show', rev + ':' + filename], stdout=PIPE)
88 p2 = Popen([sys.executable, 'src/util/cstyle-file.py'], stdin=p1.stdout,
89 stdout=PIPE, universal_newlines=True)
91 out, err = p2.communicate()
92 if p2.returncode != 0:
96 for line in out.splitlines():
97 m = line_re.match(line)
98 if int(m.group(1)) in new_lines:
100 print(' ' + dispname + ':')
105 # Determine the lines of each file modified by diff (a sequence of
106 # strings) and check for style violations in those lines. rev
107 # indicates the version in which the new contents of each file can be
108 # found, or is None if the current contents are in the working copy.
109 chunk_header_re = re.compile(r'^@@ -\d+(,(\d+))? \+(\d+)(,(\d+))? @@')
110 def check_diff(diff, rev):
111 old_count, new_count, lineno = 0, 0, 0
114 if not line or line.startswith('\\ No newline'):
116 if old_count > 0 or new_count > 0:
119 new_lines.append(lineno)
120 if line[0] in ('+', ' '):
121 new_count = new_count - 1
123 if line[0] in ('-', ' '):
124 old_count = old_count - 1
125 elif line.startswith('+++ b/'):
126 # We're starting a new file. Check the last one.
128 check_file(filename, rev, new_lines)
132 m = chunk_header_re.match(line)
134 old_count = int(m.group(2) or '1')
135 lineno = int(m.group(3))
136 new_count = int(m.group(5) or '1')
138 # Check the last file in the diff.
140 check_file(filename, rev, new_lines)
143 # Check a sequence of revisions for style issues.
144 def check_series(revlist):
147 call(['git', 'show', '-s', '--oneline', rev])
148 diff = run(['git', 'diff-tree', '--no-commit-id', '--root', '-M',
150 check_diff(diff, rev)
155 opts, args = getopt.getopt(sys.argv[1:], 'w')
156 except getopt.GetoptError as err:
162 # Change to the top level of the working tree so we easily run the file
163 # checker and refer to working tree files.
164 toplevel = find_toplevel()
166 sys.stderr.write('%s must be run within a git working tree')
169 if ('-w', '') in opts:
170 # Check the working tree against a base revision.
174 check_diff(run(['git', 'diff', arg]), None)
176 # Check the differences in a rev or a series of revs.
178 check_series(run(['git', 'rev-list', '--reverse', args[0]]))
180 check_series([args[0]])
182 # No options or arguments. Check the differences against HEAD, or
183 # the differences in HEAD if the working tree is clean.
184 diff = run(['git', 'diff', 'HEAD'])
186 check_diff(diff, None)
188 check_series(['HEAD'])