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