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