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