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