Print this page
manpage lint.
Split |
Close |
Expand all |
Collapse all |
--- old/usr/src/tools/onbld/hgext/cdm.py
+++ new/usr/src/tools/onbld/hgext/cdm.py
1 1 #
2 2 # This program is free software; you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License version 2
4 4 # as published by the Free Software Foundation.
5 5 #
6 6 # This program is distributed in the hope that it will be useful,
7 7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
↓ open down ↓ |
8 lines elided |
↑ open up ↑ |
9 9 # GNU General Public License for more details.
10 10 #
11 11 # You should have received a copy of the GNU General Public License
12 12 # along with this program; if not, write to the Free Software
13 13 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
14 14 #
15 15
16 16 #
17 17 # Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
18 18 # Copyright 2008, 2011 Richard Lowe
19 +# Copyright 2014 Garrett D'Amore <garrett@damore.org>
19 20 #
20 21
21 22 '''OpenSolaris extensions to Mercurial
22 23
23 24 This extension contains a number of commands to help you work with
24 25 the OpenSolaris consolidations. It provides commands to check your
25 26 changes against the various style rules used for OpenSolaris, to
26 27 backup and restore your changes, to generate code reviews, and to
27 28 prepare your changes for integration.
28 29
29 30
30 31 The Parent
31 32
32 33 To provide a uniform notion of parent workspace regardless of
33 34 filesystem-based access, Cadmium uses the highest numbered changeset
34 35 on the current branch that is also in the parent workspace to
35 36 represent the parent workspace.
36 37
37 38
38 39 The Active List
39 40
40 41 Many Cadmium commands operate on the active list, the set of
41 42 files ('active files') you have changed in this workspace in relation
42 43 to its parent workspace, and the metadata (commentary, primarily)
43 44 associated with those changes.
44 45
45 46
46 47 NOT Files
47 48
48 49 Many of Cadmium's commands to check that your work obeys the
49 50 various stylistic rules of the OpenSolaris consolidations (such as
50 51 those run by 'hg nits') allow files to be excluded from this checking
51 52 by means of NOT files kept in the .hg/cdm/ directory of the Mercurial
52 53 repository for one-time exceptions, and in the exception_lists
53 54 directory at the repository root for permanent exceptions. (For ON,
54 55 these would mean one in $CODEMGR_WS and one in
55 56 $CODEMGR_WS/usr/closed).
56 57
57 58 These files are in the same format as the Mercurial hgignore
58 59 file, a description of which is available in the hgignore(5) manual
59 60 page.
60 61
61 62
62 63 Common Tasks
63 64
64 65 - Show diffs relative to parent workspace - pdiffs
65 66 - Check source style rules - nits
66 67 - Run pre-integration checks - pbchk
67 68 - Collapse all your changes into a single changeset - recommit
68 69 '''
69 70
70 71 import atexit, os, re, sys, stat, termios
71 72
72 73
73 74 #
74 75 # Adjust the load path based on the location of cdm.py and the version
75 76 # of python into which it is being loaded. This assumes the normal
76 77 # onbld directory structure, where cdm.py is in
77 78 # lib/python(version)?/onbld/hgext/. If that changes so too must
78 79 # this.
79 80 #
80 81 # This and the case below are not equivalent. In this case we may be
81 82 # loading a cdm.py in python2.X/ via the lib/python/ symlink but need
82 83 # python2.Y in sys.path.
83 84 #
84 85 sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "..", "..",
85 86 "python%d.%d" % sys.version_info[:2]))
86 87
87 88 #
88 89 # Add the relative path from cdm.py to usr/src/tools to the load path,
89 90 # such that a cdm.py loaded from the source tree uses the modules also
90 91 # within the source tree.
91 92 #
92 93 sys.path.insert(2, os.path.join(os.path.dirname(__file__), "..", ".."))
93 94
94 95 from onbld.Scm import Version
95 96 from mercurial import util
96 97
↓ open down ↓ |
68 lines elided |
↑ open up ↑ |
97 98 try:
98 99 Version.check_version()
99 100 except Version.VersionMismatch, badversion:
100 101 raise util.Abort("Version Mismatch:\n %s\n" % badversion)
101 102
102 103 from mercurial import cmdutil, ignore, node, patch
103 104
104 105 from onbld.Scm.WorkSpace import WorkSpace, WorkList
105 106 from onbld.Scm.Backup import CdmBackup
106 107 from onbld.Checks import Cddl, Comments, Copyright, CStyle, HdrChk
107 -from onbld.Checks import JStyle, Keywords, Mapfile
108 +from onbld.Checks import JStyle, Keywords, ManLint, Mapfile
108 109
109 110
110 111 def yes_no(ui, msg, default):
111 112 if default:
112 113 prompt = ' [Y/n]:'
113 114 defanswer = 'y'
114 115 else:
115 116 prompt = ' [y/N]:'
116 117 defanswer = 'n'
117 118
118 119 if Version.at_least("1.4"):
119 120 index = ui.promptchoice(msg + prompt, ['&yes', '&no'],
120 121 default=['y', 'n'].index(defanswer))
121 122 resp = ('y', 'n')[index]
122 123 else:
123 124 resp = ui.prompt(msg + prompt, ['&yes', '&no'], default=defanswer)
124 125
125 126 return resp[0] in ('Y', 'y')
126 127
127 128
128 129 def buildfilelist(ws, parent, files):
129 130 '''Build a list of files in which we're interested.
130 131
131 132 If no files are specified take files from the active list relative
132 133 to 'parent'.
133 134
134 135 Return a list of 2-tuples the first element being a path relative
135 136 to the current directory and the second an entry from the active
136 137 list, or None if an explicit file list was given.'''
137 138
138 139 if files:
139 140 return [(path, None) for path in sorted(files)]
140 141 else:
141 142 active = ws.active(parent=parent)
142 143 return [(ws.filepath(e.name), e) for e in sorted(active)]
143 144 buildfilelist = util.cachefunc(buildfilelist)
144 145
145 146
146 147 def not_check(repo, cmd):
147 148 '''return a function which returns boolean indicating whether a file
148 149 should be skipped for CMD.'''
149 150
150 151 #
151 152 # The ignore routines need a canonical path to the file (relative to the
152 153 # repo root), whereas the check commands get paths relative to the cwd.
153 154 #
154 155 # Wrap our argument such that the path is canonified before it is checked.
155 156 #
156 157 def canonified_check(ignfunc):
157 158 def f(path):
158 159 cpath = util.canonpath(repo.root, repo.getcwd(), path)
159 160 return ignfunc(cpath)
160 161 return f
161 162
162 163 ignorefiles = []
163 164
164 165 for f in [repo.join('cdm/%s.NOT' % cmd),
165 166 repo.wjoin('exception_lists/%s' % cmd)]:
166 167 if os.path.exists(f):
167 168 ignorefiles.append(f)
168 169
169 170 if ignorefiles:
170 171 ign = ignore.ignore(repo.root, ignorefiles, repo.ui.warn)
171 172 return canonified_check(ign)
172 173 else:
173 174 return util.never
174 175
175 176
176 177 def abort_if_dirty(ws):
177 178 '''Abort if the workspace has uncommitted changes, merges,
178 179 branches, or has Mq patches applied'''
179 180
180 181 if ws.modified():
181 182 raise util.Abort('workspace has uncommitted changes')
182 183 if ws.merged():
183 184 raise util.Abort('workspace contains uncommitted merge')
184 185 if ws.branched():
185 186 raise util.Abort('workspace contains uncommitted branch')
186 187 if ws.mq_applied():
187 188 raise util.Abort('workspace has Mq patches applied')
188 189
189 190
190 191 #
191 192 # Adding a reference to WorkSpace from a repo causes a circular reference
192 193 # repo <-> WorkSpace.
193 194 #
194 195 # This prevents repo, WorkSpace and members thereof from being garbage
195 196 # collected. Since transactions are aborted when the transaction object
196 197 # is collected, and localrepo holds a reference to the most recently created
197 198 # transaction, this prevents transactions from cleanly aborting.
198 199 #
199 200 # Instead, we hold the repo->WorkSpace association in a dictionary, breaking
200 201 # that dependence.
201 202 #
202 203 wslist = {}
203 204
204 205
205 206 def reposetup(ui, repo):
206 207 if repo.local() and repo not in wslist:
207 208 wslist[repo] = WorkSpace(repo)
208 209
209 210 if ui.interactive() and sys.stdin.isatty():
210 211 ui.setconfig('hooks', 'preoutgoing.cdm_pbconfirm',
211 212 'python:hgext_cdm.pbconfirm')
212 213
213 214
214 215 def pbconfirm(ui, repo, hooktype, source):
215 216 def wrapper(settings=None):
216 217 termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, settings)
217 218
218 219 if source == 'push':
219 220 if not yes_no(ui, "Are you sure you wish to push?", False):
220 221 return 1
221 222 else:
222 223 settings = termios.tcgetattr(sys.stdin.fileno())
223 224 orig = list(settings)
224 225 atexit.register(wrapper, orig)
225 226 settings[3] = settings[3] & (~termios.ISIG) # c_lflag
226 227 termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, settings)
227 228
228 229
229 230 def cdm_pdiffs(ui, repo, *pats, **opts):
230 231 '''diff workspace against its parent
231 232
232 233 Show differences between this workspace and its parent workspace
233 234 in the same manner as 'hg diff'.
234 235
235 236 For a description of the changeset used to represent the parent
236 237 workspace, see The Parent in the extension documentation ('hg help
237 238 cdm').
238 239 '''
239 240
240 241 act = wslist[repo].active(opts.get('parent'))
241 242 if not act.revs:
242 243 return
243 244
244 245 #
245 246 # If no patterns were specified, either explicitly or via -I or -X
246 247 # use the active list files to avoid a workspace walk.
247 248 #
248 249 if pats or opts.get('include') or opts.get('exclude'):
249 250 matchfunc = wslist[repo].matcher(pats=pats, opts=opts)
250 251 else:
251 252 matchfunc = wslist[repo].matcher(files=act.files())
252 253
253 254 opts = patch.diffopts(ui, opts)
254 255 diffs = wslist[repo].diff(act.parenttip.node(), act.localtip.node(),
255 256 match=matchfunc, opts=opts)
256 257 if diffs:
257 258 ui.write(diffs)
258 259
259 260
260 261 def cdm_list(ui, repo, **opts):
261 262 '''list active files (those changed in this workspace)
262 263
263 264 Display a list of files changed in this workspace as compared to
264 265 its parent workspace.
265 266
266 267 File names are displayed one-per line, grouped by manner in which
267 268 they changed (added, modified, removed). Information about
268 269 renames or copies is output in parentheses following the file
269 270 name.
270 271
271 272 For a description of the changeset used to represent the parent
272 273 workspace, see The Parent in the extension documentation ('hg help
273 274 cdm').
274 275
275 276 Output can be filtered by change type with --added, --modified,
276 277 and --removed. By default, all files are shown.
277 278 '''
278 279
279 280 act = wslist[repo].active(opts['parent'])
280 281 wanted = set(x for x in ('added', 'modified', 'removed') if opts[x])
281 282 changes = {}
282 283
283 284 for entry in act:
284 285 if wanted and (entry.change not in wanted):
285 286 continue
286 287
287 288 if entry.change not in changes:
288 289 changes[entry.change] = []
289 290 changes[entry.change].append(entry)
290 291
291 292 for change in sorted(changes.keys()):
292 293 ui.write(change + ':\n')
293 294
294 295 for entry in sorted(changes[change]):
295 296 if entry.is_renamed():
296 297 ui.write('\t%s (renamed from %s)\n' % (entry.name,
297 298 entry.parentname))
298 299 elif entry.is_copied():
299 300 ui.write('\t%s (copied from %s)\n' % (entry.name,
300 301 entry.parentname))
301 302 else:
302 303 ui.write('\t%s\n' % entry.name)
303 304
304 305
305 306 def cdm_bugs(ui, repo, parent=None):
306 307 '''show all bug IDs referenced in changeset comments'''
307 308
308 309 act = wslist[repo].active(parent)
309 310
310 311 for elt in set(filter(Comments.isBug, act.comments())):
311 312 ui.write(elt + '\n')
312 313
313 314
314 315 def cdm_comments(ui, repo, parent=None):
315 316 '''show changeset commentary for all active changesets'''
316 317 act = wslist[repo].active(parent)
317 318
318 319 for elt in act.comments():
319 320 ui.write(elt + '\n')
320 321
321 322
322 323 def cdm_renamed(ui, repo, parent=None):
323 324 '''show renamed active files
324 325
325 326 Renamed files are shown in the format::
326 327
327 328 new-name old-name
328 329
329 330 One pair per-line.
330 331 '''
331 332
332 333 act = wslist[repo].active(parent)
333 334
334 335 for entry in sorted(filter(lambda x: x.is_renamed(), act)):
335 336 ui.write('%s %s\n' % (entry.name, entry.parentname))
336 337
337 338
338 339 def cdm_comchk(ui, repo, **opts):
339 340 '''check active changeset comment formatting
340 341
341 342 Check that active changeset comments conform to O/N rules.
342 343
343 344 Each comment line must contain either one bug or ARC case ID
344 345 followed by its synopsis, or credit an external contributor.
345 346 '''
346 347
347 348 active = wslist[repo].active(opts.get('parent'))
348 349
349 350 ui.write('Comments check:\n')
350 351
351 352 check_db = not opts.get('nocheck')
352 353 return Comments.comchk(active.comments(), check_db=check_db, output=ui)
353 354
354 355
355 356 def cdm_cddlchk(ui, repo, *args, **opts):
356 357 '''check for a valid CDDL header comment in all active files.
357 358
358 359 Check active files for a valid Common Development and Distribution
359 360 License (CDDL) block comment.
360 361
361 362 Newly added files are checked for a copy of the CDDL header
362 363 comment. Modified files are only checked if they contain what
363 364 appears to be an existing CDDL header comment.
364 365
365 366 Files can be excluded from this check using the cddlchk.NOT file.
366 367 See NOT Files in the extension documentation ('hg help cdm').
367 368 '''
368 369
369 370 filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
370 371 exclude = not_check(repo, 'cddlchk')
371 372 lenient = True
372 373 ret = 0
373 374
374 375 ui.write('CDDL block check:\n')
375 376
376 377 for f, e in filelist:
377 378 if e and e.is_removed():
378 379 continue
379 380 elif (e or opts.get('honour_nots')) and exclude(f):
380 381 ui.status('Skipping %s...\n' % f)
381 382 continue
382 383 elif e and e.is_added():
↓ open down ↓ |
265 lines elided |
↑ open up ↑ |
383 384 lenient = False
384 385 else:
385 386 lenient = True
386 387
387 388 fh = open(f, 'r')
388 389 ret |= Cddl.cddlchk(fh, lenient=lenient, output=ui)
389 390 fh.close()
390 391 return ret
391 392
392 393
394 +def cdm_manlintchk(ui, repo, *args, **opts):
395 + '''check for mandoc lint
396 +
397 + Check for man page formatting errors.
398 +
399 + Files can be excluded from this check using the manlint.NOT
400 + file. See NOT Files in the extension documentation ('hg help
401 + cdm').
402 + '''
403 +
404 + filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
405 + exclude = not_check(repo, 'manlint')
406 + ret = 0
407 +
408 + # Man pages are identified as having a suffix starting with a digit.
409 + ManfileRE = re.compile(r'.*\.[0-9][a-z]*$', re.IGNORECASE)
410 +
411 + ui.write('Man format check:\n')
412 +
413 + for f, e in filelist:
414 + if e and e.is_removed():
415 + continue
416 + elif (not ManfileRE.match(f)):
417 + continue
418 + elif (e or opts.get('honour_nots')) and exclude(f):
419 + ui.status('Skipping %s...\n' % f)
420 + continue
421 +
422 + fh = open(f, 'r')
423 + ret |= ManLint.manlint(fh, output=ui, picky=True)
424 + fh.close()
425 + return ret
426 +
427 +
393 428 def cdm_mapfilechk(ui, repo, *args, **opts):
394 429 '''check for a valid mapfile header block in active files
395 430
396 431 Check that all link-editor mapfiles contain the standard mapfile
397 432 header comment directing the reader to the document containing
398 433 Solaris object versioning rules (README.mapfile).
399 434
400 435 Files can be excluded from this check using the mapfilechk.NOT
401 436 file. See NOT Files in the extension documentation ('hg help
402 437 cdm').
403 438 '''
404 439
405 440 filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
406 441 exclude = not_check(repo, 'mapfilechk')
407 442 ret = 0
408 443
409 444 # We are interested in examining any file that has the following
410 445 # in its final path segment:
411 446 # - Contains the word 'mapfile'
412 447 # - Begins with 'map.'
413 448 # - Ends with '.map'
414 449 # We don't want to match unless these things occur in final path segment
415 450 # because directory names with these strings don't indicate a mapfile.
416 451 # We also ignore files with suffixes that tell us that the files
417 452 # are not mapfiles.
418 453 MapfileRE = re.compile(r'.*((mapfile[^/]*)|(/map\.+[^/]*)|(\.map))$',
419 454 re.IGNORECASE)
420 455 NotMapSuffixRE = re.compile(r'.*\.[ch]$', re.IGNORECASE)
421 456
422 457 ui.write('Mapfile comment check:\n')
423 458
424 459 for f, e in filelist:
425 460 if e and e.is_removed():
426 461 continue
427 462 elif (not MapfileRE.match(f)) or NotMapSuffixRE.match(f):
428 463 continue
429 464 elif (e or opts.get('honour_nots')) and exclude(f):
430 465 ui.status('Skipping %s...\n' % f)
431 466 continue
432 467
433 468 fh = open(f, 'r')
434 469 ret |= Mapfile.mapfilechk(fh, output=ui)
435 470 fh.close()
436 471 return ret
437 472
438 473
439 474 def cdm_copyright(ui, repo, *args, **opts):
440 475 '''check each active file for a current and correct copyright notice
441 476
442 477 Check that all active files have a correctly formed copyright
443 478 notice containing the current year.
444 479
445 480 See the Non-Formatting Considerations section of the OpenSolaris
446 481 Developer's Reference for more info on the correct form of
447 482 copyright notice.
448 483 (http://hub.opensolaris.org/bin/view/Community+Group+on/devref_7#H723NonFormattingConsiderations)
449 484
450 485 Files can be excluded from this check using the copyright.NOT file.
451 486 See NOT Files in the extension documentation ('hg help cdm').
452 487 '''
453 488
454 489 filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
455 490 exclude = not_check(repo, 'copyright')
456 491 ret = 0
457 492
458 493 ui.write('Copyright check:\n')
459 494
460 495 for f, e in filelist:
461 496 if e and e.is_removed():
462 497 continue
463 498 elif (e or opts.get('honour_nots')) and exclude(f):
464 499 ui.status('Skipping %s...\n' % f)
465 500 continue
466 501
467 502 fh = open(f, 'r')
468 503 ret |= Copyright.copyright(fh, output=ui)
469 504 fh.close()
470 505 return ret
471 506
472 507
473 508 def cdm_hdrchk(ui, repo, *args, **opts):
474 509 '''check active C header files conform to the O/N header rules
475 510
476 511 Check that any added or modified C header files conform to the O/N
477 512 header rules.
478 513
479 514 See the section 'HEADER STANDARDS' in the hdrchk(1) manual page
480 515 for more information on the rules for O/N header file formatting.
481 516
482 517 Files can be excluded from this check using the hdrchk.NOT file.
483 518 See NOT Files in the extension documentation ('hg help cdm').
484 519 '''
485 520
486 521 filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
487 522 exclude = not_check(repo, 'hdrchk')
488 523 ret = 0
489 524
490 525 ui.write('Header format check:\n')
491 526
492 527 for f, e in filelist:
493 528 if e and e.is_removed():
494 529 continue
495 530 elif not f.endswith('.h'):
496 531 continue
497 532 elif (e or opts.get('honour_nots')) and exclude(f):
498 533 ui.status('Skipping %s...\n' % f)
499 534 continue
500 535
501 536 fh = open(f, 'r')
502 537 ret |= HdrChk.hdrchk(fh, lenient=True, output=ui)
503 538 fh.close()
504 539 return ret
505 540
506 541
507 542 def cdm_cstyle(ui, repo, *args, **opts):
508 543 '''check active C source files conform to the C Style Guide
509 544
510 545 Check that any added or modified C source file conform to the C
511 546 Style Guide.
512 547
513 548 See the C Style Guide for more information about correct C source
514 549 formatting.
515 550 (http://hub.opensolaris.org/bin/download/Community+Group+on/WebHome/cstyle.ms.pdf)
516 551
517 552 Files can be excluded from this check using the cstyle.NOT file.
518 553 See NOT Files in the extension documentation ('hg help cdm').
519 554 '''
520 555
521 556 filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
522 557 exclude = not_check(repo, 'cstyle')
523 558 ret = 0
524 559
525 560 ui.write('C style check:\n')
526 561
527 562 for f, e in filelist:
528 563 if e and e.is_removed():
529 564 continue
530 565 elif not (f.endswith('.c') or f.endswith('.h')):
531 566 continue
532 567 elif (e or opts.get('honour_nots')) and exclude(f):
533 568 ui.status('Skipping %s...\n' % f)
534 569 continue
535 570
536 571 fh = open(f, 'r')
537 572 ret |= CStyle.cstyle(fh, output=ui,
538 573 picky=True, check_posix_types=True,
539 574 check_continuation=True)
540 575 fh.close()
541 576 return ret
542 577
543 578
544 579 def cdm_jstyle(ui, repo, *args, **opts):
545 580 '''check active Java source files for common stylistic errors
546 581
547 582 Files can be excluded from this check using the jstyle.NOT file.
548 583 See NOT Files in the extension documentation ('hg help cdm').
549 584 '''
550 585
551 586 filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
552 587 exclude = not_check(repo, 'jstyle')
553 588 ret = 0
554 589
555 590 ui.write('Java style check:\n')
556 591
557 592 for f, e in filelist:
558 593 if e and e.is_removed():
559 594 continue
560 595 elif not f.endswith('.java'):
561 596 continue
562 597 elif (e or opts.get('honour_nots')) and exclude(f):
563 598 ui.status('Skipping %s...\n' % f)
564 599 continue
565 600
566 601 fh = open(f, 'r')
567 602 ret |= JStyle.jstyle(fh, output=ui, picky=True)
568 603 fh.close()
569 604 return ret
570 605
571 606
572 607 def cdm_permchk(ui, repo, *args, **opts):
573 608 '''check the permissions of each active file
574 609
575 610 Check that the file permissions of each added or modified file do not
576 611 contain the executable bit.
577 612
578 613 Files can be excluded from this check using the permchk.NOT file.
579 614 See NOT Files in the extension documentation ('hg help cdm').
580 615 '''
581 616
582 617 filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
583 618 exclude = not_check(repo, 'permchk')
584 619 exeFiles = []
585 620
586 621 ui.write('File permission check:\n')
587 622
588 623 for f, e in filelist:
589 624 if e and e.is_removed():
590 625 continue
591 626 elif (e or opts.get('honour_nots')) and exclude(f):
592 627 ui.status('Skipping %s...\n' % f)
593 628 continue
594 629
595 630 mode = stat.S_IMODE(os.stat(f)[stat.ST_MODE])
596 631 if mode & stat.S_IEXEC:
597 632 exeFiles.append(f)
598 633
599 634 if len(exeFiles) > 0:
600 635 ui.write('Warning: the following active file(s) have executable mode '
601 636 '(+x) permission set,\nremove unless intentional:\n')
602 637 for fname in exeFiles:
603 638 ui.write(" %s\n" % fname)
604 639
605 640 return len(exeFiles) > 0
606 641
607 642
608 643 def cdm_tagchk(ui, repo, **opts):
609 644 '''check modification of workspace tags
610 645
611 646 Check for any modification of the repository's .hgtags file.
612 647
613 648 With the exception of the gatekeepers, nobody should introduce or
614 649 modify a repository's tags.
615 650 '''
616 651
617 652 active = wslist[repo].active(opts.get('parent'))
618 653
619 654 ui.write('Checking for new tags:\n')
620 655
621 656 if ".hgtags" in active:
622 657 tfile = wslist[repo].filepath('.hgtags')
623 658 ptip = active.parenttip.rev()
624 659
625 660 ui.write('Warning: Workspace contains new non-local tags.\n'
626 661 'Only gatekeepers should add or modify such tags.\n'
627 662 'Use the following commands to revert these changes:\n'
628 663 ' hg revert -r%d %s\n'
629 664 ' hg commit %s\n'
630 665 'You should also recommit before integration\n' %
631 666 (ptip, tfile, tfile))
632 667
633 668 return 1
634 669
635 670 return 0
636 671
637 672
638 673 def cdm_branchchk(ui, repo, **opts):
639 674 '''check for changes in number or name of branches
640 675
641 676 Check that the workspace contains only a single head, that it is
642 677 on the branch 'default', and that no new branches have been
643 678 introduced.
644 679 '''
645 680
646 681 ui.write('Checking for multiple heads (or branches):\n')
647 682
648 683 heads = set(repo.heads())
649 684 parents = set([x.node() for x in wslist[repo].workingctx().parents()])
650 685
651 686 #
652 687 # We care if there's more than one head, and those heads aren't
653 688 # identical to the dirstate parents (if they are identical, it's
654 689 # an uncommitted merge which mergechk will catch, no need to
655 690 # complain twice).
656 691 #
657 692 if len(heads) > 1 and heads != parents:
658 693 ui.write('Workspace has multiple heads (or branches):\n')
659 694 for head in [repo.changectx(head) for head in heads]:
660 695 ui.write(" %d:%s\t%s\n" %
661 696 (head.rev(), str(head), head.description().splitlines()[0]))
662 697 ui.write('You must merge and recommit.\n')
663 698 return 1
664 699
665 700 ui.write('\nChecking for branch changes:\n')
666 701
667 702 if repo.dirstate.branch() != 'default':
668 703 ui.write("Warning: Workspace tip has named branch: '%s'\n"
669 704 "Only gatekeepers should push new branches.\n"
670 705 "Use the following commands to restore the branch name:\n"
671 706 " hg branch [-f] default\n"
672 707 " hg commit\n"
673 708 "You should also recommit before integration\n" %
674 709 (repo.dirstate.branch()))
675 710 return 1
676 711
677 712 branches = repo.branchtags().keys()
678 713 if len(branches) > 1:
679 714 ui.write('Warning: Workspace has named branches:\n')
680 715 for t in branches:
681 716 if t == 'default':
682 717 continue
683 718 ui.write("\t%s\n" % t)
684 719
685 720 ui.write("Only gatekeepers should push new branches.\n"
686 721 "Use the following commands to remove extraneous branches.\n"
687 722 " hg branch [-f] default\n"
688 723 " hg commit"
689 724 "You should also recommit before integration\n")
690 725 return 1
691 726
692 727 return 0
693 728
694 729
695 730 def cdm_keywords(ui, repo, *args, **opts):
696 731 '''check active files for SCCS keywords
697 732
698 733 Check that any added or modified files do not contain SCCS keywords
699 734 (#ident lines, etc.).
700 735
701 736 Files can be excluded from this check using the keywords.NOT file.
702 737 See NOT Files in the extension documentation ('hg help cdm').
703 738 '''
704 739
705 740 filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
706 741 exclude = not_check(repo, 'keywords')
707 742 ret = 0
708 743
709 744 ui.write('Keywords check:\n')
710 745
711 746 for f, e in filelist:
712 747 if e and e.is_removed():
713 748 continue
714 749 elif (e or opts.get('honour_nots')) and exclude(f):
715 750 ui.status('Skipping %s...\n' % f)
716 751 continue
717 752
718 753 fh = open(f, 'r')
719 754 ret |= Keywords.keywords(fh, output=ui)
720 755 fh.close()
721 756 return ret
722 757
723 758
724 759 #
725 760 # NB:
726 761 # There's no reason to hook this up as an invokable command, since
727 762 # we have 'hg status', but it must accept the same arguments.
728 763 #
729 764 def cdm_outchk(ui, repo, **opts):
730 765 '''Warn the user if they have uncommitted changes'''
731 766
732 767 ui.write('Checking for uncommitted changes:\n')
733 768
734 769 st = wslist[repo].modified()
735 770 if st:
736 771 ui.write('Warning: the following files have uncommitted changes:\n')
737 772 for elt in st:
738 773 ui.write(' %s\n' % elt)
739 774 return 1
740 775 return 0
741 776
742 777
743 778 def cdm_mergechk(ui, repo, **opts):
744 779 '''Warn the user if their workspace contains merges'''
745 780
746 781 active = wslist[repo].active(opts.get('parent'))
747 782
748 783 ui.write('Checking for merges:\n')
749 784
750 785 merges = filter(lambda x: len(x.parents()) == 2 and x.parents()[1],
751 786 active.revs)
752 787
753 788 if merges:
754 789 ui.write('Workspace contains the following merges:\n')
755 790 for rev in merges:
756 791 desc = rev.description().splitlines()
757 792 ui.write(' %s:%s\t%s\n' %
758 793 (rev.rev() or "working", str(rev),
759 794 desc and desc[0] or "*** uncommitted change ***"))
760 795 return 1
761 796 return 0
762 797
763 798
764 799 def run_checks(ws, cmds, *args, **opts):
765 800 '''Run CMDS (with OPTS) over active files in WS'''
766 801
767 802 ret = 0
768 803
769 804 for cmd in cmds:
770 805 name = cmd.func_name.split('_')[1]
771 806 if not ws.ui.configbool('cdm', name, True):
772 807 ws.ui.status('Skipping %s check...\n' % name)
773 808 else:
774 809 ws.ui.pushbuffer()
775 810 result = cmd(ws.ui, ws.repo, honour_nots=True, *args, **opts)
776 811 output = ws.ui.popbuffer()
777 812
778 813 ret |= result
779 814
780 815 if not ws.ui.quiet or result != 0:
781 816 ws.ui.write(output, '\n')
782 817 return ret
783 818
784 819
785 820 def cdm_nits(ui, repo, *args, **opts):
786 821 '''check for stylistic nits in active files
↓ open down ↓ |
384 lines elided |
↑ open up ↑ |
787 822
788 823 Check each active file for basic stylistic errors.
789 824
790 825 The following checks are run over each active file (see 'hg help
791 826 <check>' for more information about each):
792 827
793 828 - copyright (copyright statements)
794 829 - cstyle (C source style)
795 830 - hdrchk (C header style)
796 831 - jstyle (java source style)
832 + - manlint (man page formatting)
797 833 - mapfilechk (link-editor mapfiles)
798 834 - permchk (file permissions)
799 835 - keywords (SCCS keywords)
800 836
801 837 With the global -q/--quiet option, only provide output for those
802 838 checks which fail.
803 839 '''
804 840
805 841 cmds = [cdm_copyright,
806 842 cdm_cstyle,
807 843 cdm_hdrchk,
808 844 cdm_jstyle,
845 + cmd_manlintchk,
809 846 cdm_mapfilechk,
810 847 cdm_permchk,
811 848 cdm_keywords]
812 849
813 850 return run_checks(wslist[repo], cmds, *args, **opts)
814 851
815 852
816 853 def cdm_pbchk(ui, repo, **opts):
817 854 '''run pre-integration checks on this workspace
818 855
819 856 Check this workspace for common errors prior to integration.
820 857
↓ open down ↓ |
2 lines elided |
↑ open up ↑ |
821 858 The following checks are run over the active list (see 'hg help
822 859 <check>' for more information about each):
823 860
824 861 - branchchk (addition/modification of branches)
825 862 - comchk (changeset descriptions)
826 863 - copyright (copyright statements)
827 864 - cstyle (C source style)
828 865 - hdrchk (C header style)
829 866 - jstyle (java source style)
830 867 - keywords (SCCS keywords)
868 + - manlint (man page formatting)
831 869 - mapfilechk (link-editor mapfiles)
832 870 - permchk (file permissions)
833 871 - tagchk (addition/modification of tags)
834 872
835 873 Additionally, the workspace is checked for outgoing merges (which
836 874 should be removed with 'hg recommit'), and uncommitted changes.
837 875
838 876 With the global -q/--quiet option, only provide output for those
839 877 checks which fail.
840 878 '''
841 879
842 880 #
843 881 # The current ordering of these is that the commands from cdm_nits
844 882 # run first in the same order as they would in cdm_nits, then the
845 883 # pbchk specifics are run.
846 884 #
847 885 cmds = [cdm_copyright,
848 886 cdm_cstyle,
849 887 cdm_hdrchk,
850 888 cdm_jstyle,
889 + cdm_manlintchk,
851 890 cdm_mapfilechk,
852 891 cdm_permchk,
853 892 cdm_keywords,
854 893 cdm_comchk,
855 894 cdm_tagchk,
856 895 cdm_branchchk,
857 896 cdm_outchk,
858 897 cdm_mergechk]
859 898
860 899 return run_checks(wslist[repo], cmds, **opts)
861 900
862 901
863 902 def cdm_recommit(ui, repo, **opts):
864 903 '''replace outgoing changesets with a single equivalent changeset
865 904
866 905 Replace all outgoing changesets with a single changeset containing
867 906 equivalent changes. This removes uninteresting changesets created
868 907 during development that would only serve as noise in the gate.
869 908
870 909 Any changed file that is now identical in content to that in the
871 910 parent workspace (whether identical in history or otherwise) will
872 911 not be included in the new changeset. Any merges information will
873 912 also be removed.
874 913
875 914 If no files are changed in comparison to the parent workspace, the
876 915 outgoing changesets will be removed, but no new changeset created.
877 916
878 917 recommit will refuse to run if the workspace contains more than
879 918 one outgoing head, even if those heads are on the same branch. To
880 919 recommit with only one branch containing outgoing changesets, your
881 920 workspace must be on that branch and at that branch head.
882 921
883 922 recommit will prompt you to take a backup if your workspace has
884 923 been changed since the last backup was taken. In almost all
885 924 cases, you should allow it to take one (the default).
886 925
887 926 recommit cannot be run if the workspace contains any uncommitted
888 927 changes, applied Mq patches, or has multiple outgoing heads (or
889 928 branches).
890 929 '''
891 930
892 931 ws = wslist[repo]
893 932
894 933 if not os.getcwd().startswith(repo.root):
895 934 raise util.Abort('recommit is not safe to run with -R')
896 935
897 936 abort_if_dirty(ws)
898 937
899 938 wlock = repo.wlock()
900 939 lock = repo.lock()
901 940
902 941 try:
903 942 parent = ws.parent(opts['parent'])
904 943 between = repo.changelog.nodesbetween(ws.findoutgoing(parent))[2]
905 944 heads = set(between) & set(repo.heads())
906 945
907 946 if len(heads) > 1:
908 947 ui.warn('Workspace has multiple outgoing heads (or branches):\n')
909 948 for head in sorted(map(repo.changelog.rev, heads), reverse=True):
910 949 ui.warn('\t%d\n' % head)
911 950 raise util.Abort('you must merge before recommitting')
912 951
913 952 #
914 953 # We can safely use the worklist here, as we know (from the
915 954 # abort_if_dirty() check above) that the working copy has not been
916 955 # modified.
917 956 #
918 957 active = ws.active(parent)
919 958
920 959 if filter(lambda b: len(b.parents()) > 1, active.bases()):
921 960 raise util.Abort('Cannot recommit a merge of two non-outgoing '
922 961 'changesets')
923 962
924 963 if len(active.revs) <= 0:
925 964 raise util.Abort("no changes to recommit")
926 965
927 966 if len(active.files()) <= 0:
928 967 ui.warn("Recommitting %d active changesets, but no active files\n" %
929 968 len(active.revs))
930 969
931 970 #
932 971 # During the course of a recommit, any file bearing a name
933 972 # matching the source name of any renamed file will be
934 973 # clobbered by the operation.
935 974 #
936 975 # As such, we ask the user before proceeding.
937 976 #
938 977 bogosity = [f.parentname for f in active if f.is_renamed() and
939 978 os.path.exists(repo.wjoin(f.parentname))]
940 979 if bogosity:
941 980 ui.warn("The following file names are the original name of a "
942 981 "rename and also present\n"
943 982 "in the working directory:\n")
944 983
945 984 for fname in bogosity:
946 985 ui.warn(" %s\n" % fname)
947 986
948 987 if not yes_no(ui, "These files will be removed by recommit."
949 988 " Continue?",
950 989 False):
951 990 raise util.Abort("recommit would clobber files")
952 991
953 992 user = opts['user'] or ui.username()
954 993 comments = '\n'.join(active.comments())
955 994
956 995 message = cmdutil.logmessage(opts) or ui.edit(comments, user)
957 996 if not message:
958 997 raise util.Abort('empty commit message')
959 998
960 999 bk = CdmBackup(ui, ws, backup_name(repo.root))
961 1000 if bk.need_backup():
962 1001 if yes_no(ui, 'Do you want to backup files first?', True):
963 1002 bk.backup()
964 1003
965 1004 oldtags = repo.tags()
966 1005 clearedtags = [(name, nd, repo.changelog.rev(nd), local)
967 1006 for name, nd, local in active.tags()]
968 1007
969 1008 ws.squishdeltas(active, message, user=user)
970 1009 finally:
971 1010 lock.release()
972 1011 wlock.release()
973 1012
974 1013 if clearedtags:
975 1014 ui.write("Removed tags:\n")
976 1015 for name, nd, rev, local in sorted(clearedtags,
977 1016 key=lambda x: x[0].lower()):
978 1017 ui.write(" %5s:%s:\t%s%s\n" % (rev, node.short(nd),
979 1018 name, (local and ' (local)' or '')))
980 1019
981 1020 for ntag, nnode in sorted(repo.tags().items(),
982 1021 key=lambda x: x[0].lower()):
983 1022 if ntag in oldtags and ntag != "tip":
984 1023 if oldtags[ntag] != nnode:
985 1024 ui.write("tag '%s' now refers to revision %d:%s\n" %
986 1025 (ntag, repo.changelog.rev(nnode),
987 1026 node.short(nnode)))
988 1027
989 1028
990 1029 def do_eval(cmd, files, root, changedir=True):
991 1030 if not changedir:
992 1031 os.chdir(root)
993 1032
994 1033 for path in sorted(files):
995 1034 dirn, base = os.path.split(path)
996 1035
997 1036 if changedir:
998 1037 os.chdir(os.path.join(root, dirn))
999 1038
1000 1039 os.putenv('workspace', root)
1001 1040 os.putenv('filepath', path)
1002 1041 os.putenv('dir', dirn)
1003 1042 os.putenv('file', base)
1004 1043 os.system(cmd)
1005 1044
1006 1045
1007 1046 def cdm_eval(ui, repo, *command, **opts):
1008 1047 '''run specified command for each active file
1009 1048
1010 1049 Run the command specified on the command line for each active
1011 1050 file, with the following variables present in the environment:
1012 1051
1013 1052 :$file: - active file basename.
1014 1053 :$dir: - active file dirname.
1015 1054 :$filepath: - path from workspace root to active file.
1016 1055 :$workspace: - full path to workspace root.
1017 1056
1018 1057 For example:
1019 1058
1020 1059 hg eval 'echo $dir; hg log -l3 $file'
1021 1060
1022 1061 will show the last the 3 log entries for each active file,
1023 1062 preceded by its directory.
1024 1063 '''
1025 1064
1026 1065 act = wslist[repo].active(opts['parent'])
1027 1066 cmd = ' '.join(command)
1028 1067 files = [x.name for x in act if not x.is_removed()]
1029 1068
1030 1069 do_eval(cmd, files, repo.root, not opts['remain'])
1031 1070
1032 1071
1033 1072 def cdm_apply(ui, repo, *command, **opts):
1034 1073 '''apply specified command to all active files
1035 1074
1036 1075 Run the command specified on the command line over each active
1037 1076 file.
1038 1077
1039 1078 For example 'hg apply "wc -l"' will output a count of the lines in
1040 1079 each active file.
1041 1080 '''
1042 1081
1043 1082 act = wslist[repo].active(opts['parent'])
1044 1083
1045 1084 if opts['remain']:
1046 1085 appnd = ' $filepath'
1047 1086 else:
1048 1087 appnd = ' $file'
1049 1088
1050 1089 cmd = ' '.join(command) + appnd
1051 1090 files = [x.name for x in act if not x.is_removed()]
1052 1091
1053 1092 do_eval(cmd, files, repo.root, not opts['remain'])
1054 1093
1055 1094
1056 1095 def cdm_reparent(ui, repo, parent):
1057 1096 '''reparent your workspace
1058 1097
1059 1098 Update the 'default' path alias that is used as the default source
1060 1099 for 'hg pull' and the default destination for 'hg push' (unless
1061 1100 there is a 'default-push' alias). This is also the path all
1062 1101 Cadmium commands treat as your parent workspace.
1063 1102 '''
1064 1103
1065 1104 def append_new_parent(parent):
1066 1105 fp = None
1067 1106 try:
1068 1107 fp = repo.opener('hgrc', 'a', atomictemp=True)
1069 1108 if fp.tell() != 0:
1070 1109 fp.write('\n')
1071 1110 fp.write('[paths]\n'
1072 1111 'default = %s\n\n' % parent)
1073 1112 fp.rename()
1074 1113 finally:
1075 1114 if fp and not fp.closed:
1076 1115 fp.close()
1077 1116
1078 1117 def update_parent(path, line, parent):
1079 1118 line = line - 1 # The line number we're passed will be 1-based
1080 1119 fp = None
1081 1120
1082 1121 try:
1083 1122 fp = open(path)
1084 1123 data = fp.readlines()
1085 1124 finally:
1086 1125 if fp and not fp.closed:
1087 1126 fp.close()
1088 1127
1089 1128 #
1090 1129 # line will be the last line of any continued block, go back
1091 1130 # to the first removing the continuation as we go.
1092 1131 #
1093 1132 while data[line][0].isspace():
1094 1133 data.pop(line)
1095 1134 line -= 1
1096 1135
1097 1136 assert data[line].startswith('default')
1098 1137
1099 1138 data[line] = "default = %s\n" % parent
1100 1139 if data[-1] != '\n':
1101 1140 data.append('\n')
1102 1141
1103 1142 try:
1104 1143 fp = util.atomictempfile(path, 'w', 0644)
1105 1144 fp.writelines(data)
1106 1145 fp.rename()
1107 1146 finally:
1108 1147 if fp and not fp.closed:
1109 1148 fp.close()
1110 1149
1111 1150 from mercurial import config
1112 1151 parent = ui.expandpath(parent)
1113 1152
1114 1153 if not os.path.exists(repo.join('hgrc')):
1115 1154 append_new_parent(parent)
1116 1155 return
1117 1156
1118 1157 cfg = config.config()
1119 1158 cfg.read(repo.join('hgrc'))
1120 1159 source = cfg.source('paths', 'default')
1121 1160
1122 1161 if not source:
1123 1162 append_new_parent(parent)
1124 1163 return
1125 1164 else:
1126 1165 path, target = source.rsplit(':', 1)
1127 1166
1128 1167 if path != repo.join('hgrc'):
1129 1168 raise util.Abort("Cannot edit path specification not in repo hgrc\n"
1130 1169 "default path is from: %s" % source)
1131 1170
1132 1171 update_parent(path, int(target), parent)
1133 1172
1134 1173
1135 1174 def backup_name(fullpath):
1136 1175 '''Create a backup directory name based on the specified path.
1137 1176
1138 1177 In most cases this is the basename of the path specified, but
1139 1178 certain cases are handled specially to create meaningful names'''
1140 1179
1141 1180 special = ['usr/closed']
1142 1181
1143 1182 fullpath = fullpath.rstrip(os.path.sep).split(os.path.sep)
1144 1183
1145 1184 #
1146 1185 # If a path is 'special', we append the basename of the path to
1147 1186 # the path element preceding the constant, special, part.
1148 1187 #
1149 1188 # Such that for instance:
1150 1189 # /foo/bar/onnv-fixes/usr/closed
1151 1190 # has a backup name of:
1152 1191 # onnv-fixes-closed
1153 1192 #
1154 1193 for elt in special:
1155 1194 elt = elt.split(os.path.sep)
1156 1195 pathpos = len(elt)
1157 1196
1158 1197 if fullpath[-pathpos:] == elt:
1159 1198 return "%s-%s" % (fullpath[-pathpos - 1], elt[-1])
1160 1199 else:
1161 1200 return fullpath[-1]
1162 1201
1163 1202
1164 1203 def cdm_backup(ui, repo, if_newer=False):
1165 1204 '''backup workspace changes and metadata
1166 1205
1167 1206 Create a backup copy of changes made in this workspace as compared
1168 1207 to its parent workspace, as well as important metadata of this
1169 1208 workspace.
1170 1209
1171 1210 NOTE: Only changes as compared to the parent workspace are backed
1172 1211 up. If you lose this workspace and its parent, you will not be
1173 1212 able to restore a backup into a clone of the grandparent
1174 1213 workspace.
1175 1214
1176 1215 By default, backups are stored in the cdm.backup/ directory in
1177 1216 your home directory. This is configurable using the cdm.backupdir
1178 1217 configuration variable, for example:
1179 1218
1180 1219 hg backup --config cdm.backupdir=/net/foo/backups
1181 1220
1182 1221 or place the following in an appropriate hgrc file::
1183 1222
1184 1223 [cdm]
1185 1224 backupdir = /net/foo/backups
1186 1225
1187 1226 Backups have the same name as the workspace in which they were
1188 1227 taken, with '-closed' appended in the case of O/N's usr/closed.
1189 1228 '''
1190 1229
1191 1230 name = backup_name(repo.root)
1192 1231 bk = CdmBackup(ui, wslist[repo], name)
1193 1232
1194 1233 wlock = repo.wlock()
1195 1234 lock = repo.lock()
1196 1235
1197 1236 try:
1198 1237 if if_newer and not bk.need_backup():
1199 1238 ui.status('backup is up-to-date\n')
1200 1239 else:
1201 1240 bk.backup()
1202 1241 finally:
1203 1242 lock.release()
1204 1243 wlock.release()
1205 1244
1206 1245
1207 1246 def cdm_restore(ui, repo, backup, **opts):
1208 1247 '''restore workspace from backup
1209 1248
1210 1249 Restore this workspace from a backup (taken by 'hg backup').
1211 1250
1212 1251 If the specified backup directory does not exist, it is assumed to
1213 1252 be relative to the cadmium backup directory (~/cdm.backup/ by
1214 1253 default).
1215 1254
1216 1255 For example::
1217 1256
1218 1257 % hg restore on-rfe - Restore the latest backup of ~/cdm.backup/on-rfe
1219 1258 % hg restore -g3 on-rfe - Restore the 3rd backup of ~/cdm.backup/on-rfe
1220 1259 % hg restore /net/foo/backup/on-rfe - Restore from an explicit path
1221 1260 '''
1222 1261
1223 1262 if not os.getcwd().startswith(repo.root):
1224 1263 raise util.Abort('restore is not safe to run with -R')
1225 1264
1226 1265 abort_if_dirty(wslist[repo])
1227 1266
1228 1267 if opts['generation']:
1229 1268 gen = int(opts['generation'])
1230 1269 else:
1231 1270 gen = None
1232 1271
1233 1272 if os.path.exists(backup):
1234 1273 backup = os.path.abspath(backup)
1235 1274
1236 1275 wlock = repo.wlock()
1237 1276 lock = repo.lock()
1238 1277
1239 1278 try:
1240 1279 bk = CdmBackup(ui, wslist[repo], backup)
1241 1280 bk.restore(gen)
1242 1281 finally:
1243 1282 lock.release()
1244 1283 wlock.release()
1245 1284
1246 1285
1247 1286 def cdm_webrev(ui, repo, **opts):
1248 1287 '''generate web-based code review and optionally upload it
1249 1288
1250 1289 Generate a web-based code review using webrev(1) and optionally
1251 1290 upload it. All known arguments are passed through to webrev(1).
1252 1291 '''
1253 1292
1254 1293 webrev_args = ""
1255 1294 for key in opts.keys():
1256 1295 if opts[key]:
1257 1296 if type(opts[key]) == type(True):
1258 1297 webrev_args += '-' + key + ' '
1259 1298 else:
1260 1299 webrev_args += '-' + key + ' ' + opts[key] + ' '
1261 1300
1262 1301 retval = os.system('webrev ' + webrev_args)
1263 1302 if retval != 0:
1264 1303 return retval - 255
1265 1304
1266 1305 return 0
1267 1306
1268 1307
1269 1308 def cdm_debugcdmal(ui, repo, *pats, **opts):
1270 1309 '''dump the active list for the sake of debugging/testing'''
1271 1310
1272 1311 ui.write(wslist[repo].active(opts['parent']).as_text(pats))
1273 1312
1274 1313
1275 1314 def cdm_changed(ui, repo, *pats, **opts):
1276 1315 '''mark a file as changed in the working copy
1277 1316
1278 1317 Maintain a list of files checked for modification in the working
1279 1318 copy. If the list exists, most cadmium commands will only check
1280 1319 the working copy for changes to those files, rather than checking
1281 1320 the whole workspace (this does not apply to committed changes,
1282 1321 which are always seen).
1283 1322
1284 1323 Since this list functions only as a hint as to where in the
1285 1324 working copy to look for changes, entries that have not actually
1286 1325 been modified (in the working copy, or in general) are not
1287 1326 problematic.
1288 1327
1289 1328
1290 1329 Note: If such a list exists, it must be kept up-to-date.
1291 1330
1292 1331
1293 1332 Renamed files can be added with reference only to their new name:
1294 1333 $ hg mv foo bar
1295 1334 $ hg changed bar
1296 1335
1297 1336 Without arguments, 'hg changed' will list all files recorded as
1298 1337 altered, such that, for instance:
1299 1338 $ hg status $(hg changed)
1300 1339 $ hg diff $(hg changed)
1301 1340 Become useful (generally faster than their unadorned counterparts)
1302 1341
1303 1342 To create an initially empty list:
1304 1343 $ hg changed -i
1305 1344 Until files are added to the list it is equivalent to saying
1306 1345 "Nothing has been changed"
1307 1346
1308 1347 Update the list based on the current active list:
1309 1348 $ hg changed -u
1310 1349 The old list is emptied, and replaced with paths from the
1311 1350 current active list.
1312 1351
1313 1352 Remove the list entirely:
1314 1353 $ hg changed -d
1315 1354 '''
1316 1355
1317 1356 def modded_files(repo, parent):
1318 1357 out = wslist[repo].findoutgoing(wslist[repo].parent(parent))
1319 1358 outnodes = repo.changelog.nodesbetween(out)[0]
1320 1359
1321 1360 files = set()
1322 1361 for n in outnodes:
1323 1362 files.update(repo.changectx(n).files())
1324 1363
1325 1364 files.update(wslist[repo].status().keys())
1326 1365 return files
1327 1366
1328 1367 #
1329 1368 # specced_pats is convenient to treat as a boolean indicating
1330 1369 # whether any file patterns or paths were specified.
1331 1370 #
1332 1371 specced_pats = pats or opts['include'] or opts['exclude']
1333 1372 if len(filter(None, [opts['delete'], opts['update'], opts['init'],
1334 1373 specced_pats])) > 1:
1335 1374 raise util.Abort("-d, -u, -i and patterns are mutually exclusive")
1336 1375
1337 1376 wl = WorkList(wslist[repo])
1338 1377
1339 1378 if (not wl and specced_pats) or opts['init']:
1340 1379 wl.delete()
1341 1380 if yes_no(ui, "Create a list based on your changes thus far?", True):
1342 1381 map(wl.add, modded_files(repo, opts.get('parent')))
1343 1382
1344 1383 if opts['delete']:
1345 1384 wl.delete()
1346 1385 elif opts['update']:
1347 1386 wl.delete()
1348 1387 map(wl.add, modded_files(repo, opts.get('parent')))
1349 1388 wl.write()
1350 1389 elif opts['init']: # Any possible old list was deleted above
1351 1390 wl.write()
1352 1391 elif specced_pats:
1353 1392 sources = []
1354 1393
1355 1394 match = wslist[repo].matcher(pats=pats, opts=opts)
1356 1395 for abso in repo.walk(match):
1357 1396 if abso in repo.dirstate:
1358 1397 wl.add(abso)
1359 1398 #
1360 1399 # Store the source name of any copy. We use this so
1361 1400 # both the add and delete of a rename can be entered
1362 1401 # into the WorkList with only the destination name
1363 1402 # explicitly being mentioned.
1364 1403 #
1365 1404 fctx = wslist[repo].workingctx().filectx(abso)
1366 1405 rn = fctx.renamed()
1367 1406 if rn:
1368 1407 sources.append(rn[0])
1369 1408 else:
1370 1409 ui.warn("%s is not version controlled -- skipping\n" %
1371 1410 match.rel(abso))
1372 1411
1373 1412 if sources:
1374 1413 for fname, chng in wslist[repo].status(files=sources).iteritems():
1375 1414 if chng == 'removed':
1376 1415 wl.add(fname)
1377 1416 wl.write()
1378 1417 else:
1379 1418 for elt in sorted(wl.list()):
1380 1419 ui.write("%s\n" % wslist[repo].filepath(elt))
1381 1420
1382 1421
1383 1422 cmdtable = {
1384 1423 'apply': (cdm_apply, [('p', 'parent', '', 'parent workspace'),
1385 1424 ('r', 'remain', None, 'do not change directory')],
1386 1425 'hg apply [-p PARENT] [-r] command...'),
1387 1426 '^backup|bu': (cdm_backup, [('t', 'if-newer', None,
1388 1427 'only backup if workspace files are newer')],
1389 1428 'hg backup [-t]'),
1390 1429 'branchchk': (cdm_branchchk, [('p', 'parent', '', 'parent workspace')],
1391 1430 'hg branchchk [-p PARENT]'),
1392 1431 'bugs': (cdm_bugs, [('p', 'parent', '', 'parent workspace')],
1393 1432 'hg bugs [-p PARENT]'),
1394 1433 'cddlchk': (cdm_cddlchk, [('p', 'parent', '', 'parent workspace')],
1395 1434 'hg cddlchk [-p PARENT]'),
1396 1435 'changed': (cdm_changed, [('d', 'delete', None, 'delete the file list'),
1397 1436 ('u', 'update', None, 'mark all changed files'),
1398 1437 ('i', 'init', None, 'create an empty file list'),
1399 1438 ('p', 'parent', '', 'parent workspace'),
1400 1439 ('I', 'include', [],
1401 1440 'include names matching the given patterns'),
1402 1441 ('X', 'exclude', [],
1403 1442 'exclude names matching the given patterns')],
1404 1443 'hg changed -d\n'
1405 1444 'hg changed -u\n'
1406 1445 'hg changed -i\n'
1407 1446 'hg changed [-I PATTERN...] [-X PATTERN...] [FILE...]'),
1408 1447 'comchk': (cdm_comchk, [('p', 'parent', '', 'parent workspace'),
1409 1448 ('N', 'nocheck', None,
1410 1449 'do not compare comments with databases')],
1411 1450 'hg comchk [-p PARENT]'),
1412 1451 'comments': (cdm_comments, [('p', 'parent', '', 'parent workspace')],
1413 1452 'hg comments [-p PARENT]'),
1414 1453 'copyright': (cdm_copyright, [('p', 'parent', '', 'parent workspace')],
1415 1454 'hg copyright [-p PARENT]'),
1416 1455 'cstyle': (cdm_cstyle, [('p', 'parent', '', 'parent workspace')],
1417 1456 'hg cstyle [-p PARENT]'),
1418 1457 'debugcdmal': (cdm_debugcdmal, [('p', 'parent', '', 'parent workspace')],
1419 1458 'hg debugcdmal [-p PARENT] [FILE...]'),
1420 1459 'eval': (cdm_eval, [('p', 'parent', '', 'parent workspace'),
1421 1460 ('r', 'remain', None, 'do not change directory')],
1422 1461 'hg eval [-p PARENT] [-r] command...'),
1423 1462 'hdrchk': (cdm_hdrchk, [('p', 'parent', '', 'parent workspace')],
↓ open down ↓ |
563 lines elided |
↑ open up ↑ |
1424 1463 'hg hdrchk [-p PARENT]'),
1425 1464 'jstyle': (cdm_jstyle, [('p', 'parent', '', 'parent workspace')],
1426 1465 'hg jstyle [-p PARENT]'),
1427 1466 'keywords': (cdm_keywords, [('p', 'parent', '', 'parent workspace')],
1428 1467 'hg keywords [-p PARENT]'),
1429 1468 '^list|active': (cdm_list, [('p', 'parent', '', 'parent workspace'),
1430 1469 ('a', 'added', None, 'show added files'),
1431 1470 ('m', 'modified', None, 'show modified files'),
1432 1471 ('r', 'removed', None, 'show removed files')],
1433 1472 'hg list [-amrRu] [-p PARENT]'),
1473 + 'manlint': (cdm_manlintchk, [('p', 'parent', '', 'parent workspace')],
1474 + 'hg manlint [-p PARENT]'),
1434 1475 'mapfilechk': (cdm_mapfilechk, [('p', 'parent', '', 'parent workspace')],
1435 1476 'hg mapfilechk [-p PARENT]'),
1436 1477 '^nits': (cdm_nits, [('p', 'parent', '', 'parent workspace')],
1437 1478 'hg nits [-p PARENT]'),
1438 1479 '^pbchk': (cdm_pbchk, [('p', 'parent', '', 'parent workspace'),
1439 1480 ('N', 'nocheck', None, 'skip database checks')],
1440 1481 'hg pbchk [-N] [-p PARENT]'),
1441 1482 'permchk': (cdm_permchk, [('p', 'parent', '', 'parent workspace')],
1442 1483 'hg permchk [-p PARENT]'),
1443 1484 '^pdiffs': (cdm_pdiffs, [('p', 'parent', '', 'parent workspace'),
1444 1485 ('a', 'text', None, 'treat all files as text'),
1445 1486 ('g', 'git', None, 'use extended git diff format'),
1446 1487 ('w', 'ignore-all-space', None,
1447 1488 'ignore white space when comparing lines'),
1448 1489 ('b', 'ignore-space-change', None,
1449 1490 'ignore changes in the amount of white space'),
1450 1491 ('B', 'ignore-blank-lines', None,
1451 1492 'ignore changes whose lines are all blank'),
1452 1493 ('U', 'unified', 3,
1453 1494 'number of lines of context to show'),
1454 1495 ('I', 'include', [],
1455 1496 'include names matching the given patterns'),
1456 1497 ('X', 'exclude', [],
1457 1498 'exclude names matching the given patterns')],
1458 1499 'hg pdiffs [OPTION...] [-p PARENT] [FILE...]'),
1459 1500 '^recommit|reci': (cdm_recommit, [('p', 'parent', '', 'parent workspace'),
1460 1501 ('m', 'message', '',
1461 1502 'use <text> as commit message'),
1462 1503 ('l', 'logfile', '',
1463 1504 'read commit message from file'),
1464 1505 ('u', 'user', '',
1465 1506 'record user as committer')],
1466 1507 'hg recommit [-m TEXT] [-l FILE] [-u USER] [-p PARENT]'),
1467 1508 'renamed': (cdm_renamed, [('p', 'parent', '', 'parent workspace')],
1468 1509 'hg renamed [-p PARENT]'),
1469 1510 'reparent': (cdm_reparent, [], 'hg reparent PARENT'),
1470 1511 '^restore': (cdm_restore, [('g', 'generation', '', 'generation number')],
1471 1512 'hg restore [-g GENERATION] BACKUP'),
1472 1513 'tagchk': (cdm_tagchk, [('p', 'parent', '', 'parent workspace')],
1473 1514 'hg tagchk [-p PARENT]'),
1474 1515 'webrev': (cdm_webrev, [('C', 'C', '', 'ITS priority file'),
1475 1516 ('D', 'D', '', 'delete remote webrev'),
1476 1517 ('I', 'I', '', 'ITS configuration file'),
1477 1518 ('i', 'i', '', 'include file'),
1478 1519 ('N', 'N', None, 'suppress comments'),
1479 1520 ('n', 'n', None, 'do not generate webrev'),
1480 1521 ('O', 'O', None, 'OpenSolaris mode'),
1481 1522 ('o', 'o', '', 'output directory'),
1482 1523 ('p', 'p', '', 'use specified parent'),
1483 1524 ('t', 't', '', 'upload target'),
1484 1525 ('U', 'U', None, 'upload the webrev'),
1485 1526 ('w', 'w', '', 'use wx active file')],
1486 1527 'hg webrev [WEBREV_OPTIONS]'),
1487 1528 }
↓ open down ↓ |
44 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX