Print this page
9803 pbchk could use a -c option
9825 pbchk -b option should be -p
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.
↓ open down ↓ |
10 lines elided |
↑ open up ↑ |
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 -# Copyright (c) 2014, Joyent, Inc.
22 21 # Copyright (c) 2015, 2016 by Delphix. All rights reserved.
23 22 # Copyright 2016 Nexenta Systems, Inc.
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.
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 201 for f in git_file_list(parent, paths):
202 202 f = relpath(f, '.')
203 203 try:
204 204 res = git("diff %s HEAD %s" % (parent, f))
205 205 except GitError, e:
206 206 # This ignores all the errors that can be thrown. Usually, this means
207 207 # that git returned non-zero because the file doesn't exist, but it
208 208 # could also fail if git can't create a new file or it can't be
209 209 # executed. Such errors are 1) unlikely, and 2) will be caught by other
210 210 # invocations of git().
211 211 continue
212 212 empty = not res.readline()
213 213 if (os.path.isfile(f) and not empty and select(f) and not exclude(f)):
214 214 yield f
215 215 return ret
216 216
217 217
218 218 def comchk(root, parent, flist, output):
219 219 output.write("Comments:\n")
220 220
221 221 return Comments.comchk(git_comments(parent), check_db=True,
222 222 output=output)
223 223
224 224
225 225 def mapfilechk(root, parent, flist, output):
226 226 ret = 0
227 227
228 228 # We are interested in examining any file that has the following
229 229 # in its final path segment:
230 230 # - Contains the word 'mapfile'
231 231 # - Begins with 'map.'
232 232 # - Ends with '.map'
233 233 # We don't want to match unless these things occur in final path segment
234 234 # because directory names with these strings don't indicate a mapfile.
235 235 # We also ignore files with suffixes that tell us that the files
236 236 # are not mapfiles.
237 237 MapfileRE = re.compile(r'.*((mapfile[^/]*)|(/map\.+[^/]*)|(\.map))$',
238 238 re.IGNORECASE)
239 239 NotMapSuffixRE = re.compile(r'.*\.[ch]$', re.IGNORECASE)
240 240
241 241 output.write("Mapfile comments:\n")
242 242
243 243 for f in flist(lambda x: MapfileRE.match(x) and not
244 244 NotMapSuffixRE.match(x)):
245 245 fh = open(f, 'r')
246 246 ret |= Mapfile.mapfilechk(fh, output=output)
247 247 fh.close()
248 248 return ret
249 249
250 250
251 251 def copyright(root, parent, flist, output):
252 252 ret = 0
253 253 output.write("Copyrights:\n")
254 254 for f in flist():
255 255 fh = open(f, 'r')
256 256 ret |= Copyright.copyright(fh, output=output)
257 257 fh.close()
258 258 return ret
259 259
260 260
261 261 def hdrchk(root, parent, flist, output):
262 262 ret = 0
263 263 output.write("Header format:\n")
264 264 for f in flist(lambda x: x.endswith('.h')):
265 265 fh = open(f, 'r')
266 266 ret |= HdrChk.hdrchk(fh, lenient=True, output=output)
267 267 fh.close()
268 268 return ret
269 269
270 270
271 271 def cstyle(root, parent, flist, output):
272 272 ret = 0
273 273 output.write("C style:\n")
274 274 for f in flist(lambda x: x.endswith('.c') or x.endswith('.h')):
275 275 fh = open(f, 'r')
276 276 ret |= CStyle.cstyle(fh, output=output, picky=True,
277 277 check_posix_types=True,
278 278 check_continuation=True)
279 279 fh.close()
280 280 return ret
281 281
282 282
283 283 def jstyle(root, parent, flist, output):
284 284 ret = 0
285 285 output.write("Java style:\n")
286 286 for f in flist(lambda x: x.endswith('.java')):
287 287 fh = open(f, 'r')
288 288 ret |= JStyle.jstyle(fh, output=output, picky=True)
289 289 fh.close()
290 290 return ret
291 291
292 292
293 293 def manlint(root, parent, flist, output):
294 294 ret = 0
295 295 output.write("Man page format/spelling:\n")
296 296 ManfileRE = re.compile(r'.*\.[0-9][a-z]*$', re.IGNORECASE)
297 297 for f in flist(lambda x: ManfileRE.match(x)):
298 298 fh = open(f, 'r')
299 299 ret |= ManLint.manlint(fh, output=output, picky=True)
300 300 ret |= SpellCheck.spellcheck(fh, output=output)
301 301 fh.close()
302 302 return ret
303 303
304 304 def keywords(root, parent, flist, output):
305 305 ret = 0
306 306 output.write("SCCS Keywords:\n")
307 307 for f in flist():
308 308 fh = open(f, 'r')
309 309 ret |= Keywords.keywords(fh, output=output)
310 310 fh.close()
311 311 return ret
312 312
313 313 def wscheck(root, parent, flist, output):
314 314 ret = 0
315 315 output.write("white space nits:\n")
316 316 for f in flist():
317 317 fh = open(f, 'r')
318 318 ret |= WsCheck.wscheck(fh, output=output)
319 319 fh.close()
320 320 return ret
321 321
322 322 def run_checks(root, parent, cmds, paths='', opts={}):
323 323 """Run the checks given in 'cmds', expected to have well-known signatures,
324 324 and report results for any which fail.
325 325
326 326 Return failure if any of them did.
327 327
328 328 NB: the function name of the commands passed in is used to name the NOT
329 329 file which excepts files from them."""
330 330
331 331 ret = 0
332 332
333 333 for cmd in cmds:
334 334 s = StringIO()
335 335
336 336 exclude = not_check(root, cmd.func_name)
337 337 result = cmd(root, parent, gen_files(root, parent, paths, exclude),
338 338 output=s)
339 339 ret |= result
340 340
341 341 if result != 0:
342 342 print s.getvalue()
343 343
344 344 return ret
345 345
346 346
347 347 def nits(root, parent, paths):
348 348 cmds = [copyright,
349 349 cstyle,
350 350 hdrchk,
351 351 jstyle,
352 352 keywords,
353 353 manlint,
354 354 mapfilechk,
355 355 wscheck]
356 356 run_checks(root, parent, cmds, paths)
357 357
358 358
359 359 def pbchk(root, parent, paths):
360 360 cmds = [comchk,
361 361 copyright,
362 362 cstyle,
363 363 hdrchk,
↓ open down ↓ |
330 lines elided |
↑ open up ↑ |
364 364 jstyle,
365 365 keywords,
366 366 manlint,
367 367 mapfilechk,
368 368 wscheck]
369 369 run_checks(root, parent, cmds)
370 370
371 371
372 372 def main(cmd, args):
373 373 parent_branch = None
374 + checkname = None
374 375
375 376 try:
376 - opts, args = getopt.getopt(args, 'b:')
377 + opts, args = getopt.getopt(args, 'c:p:')
377 378 except getopt.GetoptError, e:
378 379 sys.stderr.write(str(e) + '\n')
379 - sys.stderr.write("Usage: %s [-b branch] [path...]\n" % cmd)
380 + sys.stderr.write("Usage: %s [-c check] [-p branch] [path...]\n" % cmd)
380 381 sys.exit(1)
381 382
382 383 for opt, arg in opts:
384 + # backwards compatibility
383 385 if opt == '-b':
384 386 parent_branch = arg
387 + elif opt == '-c':
388 + checkname = arg
389 + elif opt == '-p':
390 + parent_branch = arg
385 391
386 392 if not parent_branch:
387 393 parent_branch = git_parent_branch(git_branch())
388 394
389 - func = nits
390 - if cmd == 'git-pbchk':
391 - func = pbchk
395 + if checkname is None:
396 + if cmd == 'git-pbchk':
397 + checkname= 'pbchk'
398 + else:
399 + checkname = 'nits'
400 +
401 + if checkname == 'pbchk':
392 402 if args:
393 403 sys.stderr.write("only complete workspaces may be pbchk'd\n");
394 404 sys.exit(1)
405 + pbchk(git_root(), parent_branch, None)
406 + elif checkname == 'nits':
407 + nits(git_root(), parent_branch, args)
408 + else:
409 + run_checks(git_root(), parent_branch, [eval(checkname)], args)
395 410
396 - func(git_root(), parent_branch, args)
397 -
398 411 if __name__ == '__main__':
399 412 try:
400 413 main(os.path.basename(sys.argv[0]), sys.argv[1:])
401 414 except GitError, e:
402 415 sys.stderr.write("failed to run git:\n %s\n" % str(e))
403 416 sys.exit(1)
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX