##// END OF EJS Templates
dispatch: verify result of early command parsing...
Yuya Nishihara -
r35063:02845f74 stable
parent child Browse files
Show More
@@ -1,5587 +1,5591 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import difflib
11 11 import errno
12 12 import os
13 13 import re
14 14 import sys
15 15
16 16 from .i18n import _
17 17 from .node import (
18 18 hex,
19 19 nullid,
20 20 nullrev,
21 21 short,
22 22 )
23 23 from . import (
24 24 archival,
25 25 bookmarks,
26 26 bundle2,
27 27 changegroup,
28 28 cmdutil,
29 29 copies,
30 30 debugcommands as debugcommandsmod,
31 31 destutil,
32 32 dirstateguard,
33 33 discovery,
34 34 encoding,
35 35 error,
36 36 exchange,
37 37 extensions,
38 38 formatter,
39 39 graphmod,
40 40 hbisect,
41 41 help,
42 42 hg,
43 43 lock as lockmod,
44 44 merge as mergemod,
45 45 obsolete,
46 46 patch,
47 47 phases,
48 48 pycompat,
49 49 rcutil,
50 50 registrar,
51 51 revsetlang,
52 52 scmutil,
53 53 server,
54 54 sshserver,
55 55 streamclone,
56 56 tags as tagsmod,
57 57 templatekw,
58 58 ui as uimod,
59 59 util,
60 60 )
61 61
62 62 release = lockmod.release
63 63
64 64 table = {}
65 65 table.update(debugcommandsmod.command._table)
66 66
67 67 command = registrar.command(table)
68 68
69 69 # common command options
70 70
71 71 globalopts = [
72 72 ('R', 'repository', '',
73 73 _('repository root directory or name of overlay bundle file'),
74 74 _('REPO')),
75 75 ('', 'cwd', '',
76 76 _('change working directory'), _('DIR')),
77 77 ('y', 'noninteractive', None,
78 78 _('do not prompt, automatically pick the first choice for all prompts')),
79 79 ('q', 'quiet', None, _('suppress output')),
80 80 ('v', 'verbose', None, _('enable additional output')),
81 81 ('', 'color', '',
82 82 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
83 83 # and should not be translated
84 84 _("when to colorize (boolean, always, auto, never, or debug)"),
85 85 _('TYPE')),
86 86 ('', 'config', [],
87 87 _('set/override config option (use \'section.name=value\')'),
88 88 _('CONFIG')),
89 89 ('', 'debug', None, _('enable debugging output')),
90 90 ('', 'debugger', None, _('start debugger')),
91 91 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
92 92 _('ENCODE')),
93 93 ('', 'encodingmode', encoding.encodingmode,
94 94 _('set the charset encoding mode'), _('MODE')),
95 95 ('', 'traceback', None, _('always print a traceback on exception')),
96 96 ('', 'time', None, _('time how long the command takes')),
97 97 ('', 'profile', None, _('print command execution profile')),
98 98 ('', 'version', None, _('output version information and exit')),
99 99 ('h', 'help', None, _('display help and exit')),
100 100 ('', 'hidden', False, _('consider hidden changesets')),
101 101 ('', 'pager', 'auto',
102 102 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
103 103 ]
104 104
105 # options which must be pre-parsed before loading configs and extensions
106 # TODO: perhaps --debugger should be included
107 earlyoptflags = ("--cwd", "-R", "--repository", "--repo", "--config")
108
105 109 dryrunopts = cmdutil.dryrunopts
106 110 remoteopts = cmdutil.remoteopts
107 111 walkopts = cmdutil.walkopts
108 112 commitopts = cmdutil.commitopts
109 113 commitopts2 = cmdutil.commitopts2
110 114 formatteropts = cmdutil.formatteropts
111 115 templateopts = cmdutil.templateopts
112 116 logopts = cmdutil.logopts
113 117 diffopts = cmdutil.diffopts
114 118 diffwsopts = cmdutil.diffwsopts
115 119 diffopts2 = cmdutil.diffopts2
116 120 mergetoolopts = cmdutil.mergetoolopts
117 121 similarityopts = cmdutil.similarityopts
118 122 subrepoopts = cmdutil.subrepoopts
119 123 debugrevlogopts = cmdutil.debugrevlogopts
120 124
121 125 # Commands start here, listed alphabetically
122 126
123 127 @command('^add',
124 128 walkopts + subrepoopts + dryrunopts,
125 129 _('[OPTION]... [FILE]...'),
126 130 inferrepo=True)
127 131 def add(ui, repo, *pats, **opts):
128 132 """add the specified files on the next commit
129 133
130 134 Schedule files to be version controlled and added to the
131 135 repository.
132 136
133 137 The files will be added to the repository at the next commit. To
134 138 undo an add before that, see :hg:`forget`.
135 139
136 140 If no names are given, add all files to the repository (except
137 141 files matching ``.hgignore``).
138 142
139 143 .. container:: verbose
140 144
141 145 Examples:
142 146
143 147 - New (unknown) files are added
144 148 automatically by :hg:`add`::
145 149
146 150 $ ls
147 151 foo.c
148 152 $ hg status
149 153 ? foo.c
150 154 $ hg add
151 155 adding foo.c
152 156 $ hg status
153 157 A foo.c
154 158
155 159 - Specific files to be added can be specified::
156 160
157 161 $ ls
158 162 bar.c foo.c
159 163 $ hg status
160 164 ? bar.c
161 165 ? foo.c
162 166 $ hg add bar.c
163 167 $ hg status
164 168 A bar.c
165 169 ? foo.c
166 170
167 171 Returns 0 if all files are successfully added.
168 172 """
169 173
170 174 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
171 175 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
172 176 return rejected and 1 or 0
173 177
174 178 @command('addremove',
175 179 similarityopts + subrepoopts + walkopts + dryrunopts,
176 180 _('[OPTION]... [FILE]...'),
177 181 inferrepo=True)
178 182 def addremove(ui, repo, *pats, **opts):
179 183 """add all new files, delete all missing files
180 184
181 185 Add all new files and remove all missing files from the
182 186 repository.
183 187
184 188 Unless names are given, new files are ignored if they match any of
185 189 the patterns in ``.hgignore``. As with add, these changes take
186 190 effect at the next commit.
187 191
188 192 Use the -s/--similarity option to detect renamed files. This
189 193 option takes a percentage between 0 (disabled) and 100 (files must
190 194 be identical) as its parameter. With a parameter greater than 0,
191 195 this compares every removed file with every added file and records
192 196 those similar enough as renames. Detecting renamed files this way
193 197 can be expensive. After using this option, :hg:`status -C` can be
194 198 used to check which files were identified as moved or renamed. If
195 199 not specified, -s/--similarity defaults to 100 and only renames of
196 200 identical files are detected.
197 201
198 202 .. container:: verbose
199 203
200 204 Examples:
201 205
202 206 - A number of files (bar.c and foo.c) are new,
203 207 while foobar.c has been removed (without using :hg:`remove`)
204 208 from the repository::
205 209
206 210 $ ls
207 211 bar.c foo.c
208 212 $ hg status
209 213 ! foobar.c
210 214 ? bar.c
211 215 ? foo.c
212 216 $ hg addremove
213 217 adding bar.c
214 218 adding foo.c
215 219 removing foobar.c
216 220 $ hg status
217 221 A bar.c
218 222 A foo.c
219 223 R foobar.c
220 224
221 225 - A file foobar.c was moved to foo.c without using :hg:`rename`.
222 226 Afterwards, it was edited slightly::
223 227
224 228 $ ls
225 229 foo.c
226 230 $ hg status
227 231 ! foobar.c
228 232 ? foo.c
229 233 $ hg addremove --similarity 90
230 234 removing foobar.c
231 235 adding foo.c
232 236 recording removal of foobar.c as rename to foo.c (94% similar)
233 237 $ hg status -C
234 238 A foo.c
235 239 foobar.c
236 240 R foobar.c
237 241
238 242 Returns 0 if all files are successfully added.
239 243 """
240 244 opts = pycompat.byteskwargs(opts)
241 245 try:
242 246 sim = float(opts.get('similarity') or 100)
243 247 except ValueError:
244 248 raise error.Abort(_('similarity must be a number'))
245 249 if sim < 0 or sim > 100:
246 250 raise error.Abort(_('similarity must be between 0 and 100'))
247 251 matcher = scmutil.match(repo[None], pats, opts)
248 252 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
249 253
250 254 @command('^annotate|blame',
251 255 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
252 256 ('', 'follow', None,
253 257 _('follow copies/renames and list the filename (DEPRECATED)')),
254 258 ('', 'no-follow', None, _("don't follow copies and renames")),
255 259 ('a', 'text', None, _('treat all files as text')),
256 260 ('u', 'user', None, _('list the author (long with -v)')),
257 261 ('f', 'file', None, _('list the filename')),
258 262 ('d', 'date', None, _('list the date (short with -q)')),
259 263 ('n', 'number', None, _('list the revision number (default)')),
260 264 ('c', 'changeset', None, _('list the changeset')),
261 265 ('l', 'line-number', None, _('show line number at the first appearance')),
262 266 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
263 267 ] + diffwsopts + walkopts + formatteropts,
264 268 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
265 269 inferrepo=True)
266 270 def annotate(ui, repo, *pats, **opts):
267 271 """show changeset information by line for each file
268 272
269 273 List changes in files, showing the revision id responsible for
270 274 each line.
271 275
272 276 This command is useful for discovering when a change was made and
273 277 by whom.
274 278
275 279 If you include --file, --user, or --date, the revision number is
276 280 suppressed unless you also include --number.
277 281
278 282 Without the -a/--text option, annotate will avoid processing files
279 283 it detects as binary. With -a, annotate will annotate the file
280 284 anyway, although the results will probably be neither useful
281 285 nor desirable.
282 286
283 287 Returns 0 on success.
284 288 """
285 289 opts = pycompat.byteskwargs(opts)
286 290 if not pats:
287 291 raise error.Abort(_('at least one filename or pattern is required'))
288 292
289 293 if opts.get('follow'):
290 294 # --follow is deprecated and now just an alias for -f/--file
291 295 # to mimic the behavior of Mercurial before version 1.5
292 296 opts['file'] = True
293 297
294 298 ctx = scmutil.revsingle(repo, opts.get('rev'))
295 299
296 300 rootfm = ui.formatter('annotate', opts)
297 301 if ui.quiet:
298 302 datefunc = util.shortdate
299 303 else:
300 304 datefunc = util.datestr
301 305 if ctx.rev() is None:
302 306 def hexfn(node):
303 307 if node is None:
304 308 return None
305 309 else:
306 310 return rootfm.hexfunc(node)
307 311 if opts.get('changeset'):
308 312 # omit "+" suffix which is appended to node hex
309 313 def formatrev(rev):
310 314 if rev is None:
311 315 return '%d' % ctx.p1().rev()
312 316 else:
313 317 return '%d' % rev
314 318 else:
315 319 def formatrev(rev):
316 320 if rev is None:
317 321 return '%d+' % ctx.p1().rev()
318 322 else:
319 323 return '%d ' % rev
320 324 def formathex(hex):
321 325 if hex is None:
322 326 return '%s+' % rootfm.hexfunc(ctx.p1().node())
323 327 else:
324 328 return '%s ' % hex
325 329 else:
326 330 hexfn = rootfm.hexfunc
327 331 formatrev = formathex = pycompat.bytestr
328 332
329 333 opmap = [('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
330 334 ('number', ' ', lambda x: x.fctx.rev(), formatrev),
331 335 ('changeset', ' ', lambda x: hexfn(x.fctx.node()), formathex),
332 336 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
333 337 ('file', ' ', lambda x: x.fctx.path(), str),
334 338 ('line_number', ':', lambda x: x.lineno, str),
335 339 ]
336 340 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
337 341
338 342 if (not opts.get('user') and not opts.get('changeset')
339 343 and not opts.get('date') and not opts.get('file')):
340 344 opts['number'] = True
341 345
342 346 linenumber = opts.get('line_number') is not None
343 347 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
344 348 raise error.Abort(_('at least one of -n/-c is required for -l'))
345 349
346 350 ui.pager('annotate')
347 351
348 352 if rootfm.isplain():
349 353 def makefunc(get, fmt):
350 354 return lambda x: fmt(get(x))
351 355 else:
352 356 def makefunc(get, fmt):
353 357 return get
354 358 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
355 359 if opts.get(op)]
356 360 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
357 361 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
358 362 if opts.get(op))
359 363
360 364 def bad(x, y):
361 365 raise error.Abort("%s: %s" % (x, y))
362 366
363 367 m = scmutil.match(ctx, pats, opts, badfn=bad)
364 368
365 369 follow = not opts.get('no_follow')
366 370 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
367 371 whitespace=True)
368 372 skiprevs = opts.get('skip')
369 373 if skiprevs:
370 374 skiprevs = scmutil.revrange(repo, skiprevs)
371 375
372 376 for abs in ctx.walk(m):
373 377 fctx = ctx[abs]
374 378 rootfm.startitem()
375 379 rootfm.data(abspath=abs, path=m.rel(abs))
376 380 if not opts.get('text') and fctx.isbinary():
377 381 rootfm.plain(_("%s: binary file\n")
378 382 % ((pats and m.rel(abs)) or abs))
379 383 continue
380 384
381 385 fm = rootfm.nested('lines')
382 386 lines = fctx.annotate(follow=follow, linenumber=linenumber,
383 387 skiprevs=skiprevs, diffopts=diffopts)
384 388 if not lines:
385 389 fm.end()
386 390 continue
387 391 formats = []
388 392 pieces = []
389 393
390 394 for f, sep in funcmap:
391 395 l = [f(n) for n, dummy in lines]
392 396 if fm.isplain():
393 397 sizes = [encoding.colwidth(x) for x in l]
394 398 ml = max(sizes)
395 399 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
396 400 else:
397 401 formats.append(['%s' for x in l])
398 402 pieces.append(l)
399 403
400 404 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
401 405 fm.startitem()
402 406 fm.write(fields, "".join(f), *p)
403 407 if l[0].skip:
404 408 fmt = "* %s"
405 409 else:
406 410 fmt = ": %s"
407 411 fm.write('line', fmt, l[1])
408 412
409 413 if not lines[-1][1].endswith('\n'):
410 414 fm.plain('\n')
411 415 fm.end()
412 416
413 417 rootfm.end()
414 418
415 419 @command('archive',
416 420 [('', 'no-decode', None, _('do not pass files through decoders')),
417 421 ('p', 'prefix', '', _('directory prefix for files in archive'),
418 422 _('PREFIX')),
419 423 ('r', 'rev', '', _('revision to distribute'), _('REV')),
420 424 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
421 425 ] + subrepoopts + walkopts,
422 426 _('[OPTION]... DEST'))
423 427 def archive(ui, repo, dest, **opts):
424 428 '''create an unversioned archive of a repository revision
425 429
426 430 By default, the revision used is the parent of the working
427 431 directory; use -r/--rev to specify a different revision.
428 432
429 433 The archive type is automatically detected based on file
430 434 extension (to override, use -t/--type).
431 435
432 436 .. container:: verbose
433 437
434 438 Examples:
435 439
436 440 - create a zip file containing the 1.0 release::
437 441
438 442 hg archive -r 1.0 project-1.0.zip
439 443
440 444 - create a tarball excluding .hg files::
441 445
442 446 hg archive project.tar.gz -X ".hg*"
443 447
444 448 Valid types are:
445 449
446 450 :``files``: a directory full of files (default)
447 451 :``tar``: tar archive, uncompressed
448 452 :``tbz2``: tar archive, compressed using bzip2
449 453 :``tgz``: tar archive, compressed using gzip
450 454 :``uzip``: zip archive, uncompressed
451 455 :``zip``: zip archive, compressed using deflate
452 456
453 457 The exact name of the destination archive or directory is given
454 458 using a format string; see :hg:`help export` for details.
455 459
456 460 Each member added to an archive file has a directory prefix
457 461 prepended. Use -p/--prefix to specify a format string for the
458 462 prefix. The default is the basename of the archive, with suffixes
459 463 removed.
460 464
461 465 Returns 0 on success.
462 466 '''
463 467
464 468 opts = pycompat.byteskwargs(opts)
465 469 ctx = scmutil.revsingle(repo, opts.get('rev'))
466 470 if not ctx:
467 471 raise error.Abort(_('no working directory: please specify a revision'))
468 472 node = ctx.node()
469 473 dest = cmdutil.makefilename(repo, dest, node)
470 474 if os.path.realpath(dest) == repo.root:
471 475 raise error.Abort(_('repository root cannot be destination'))
472 476
473 477 kind = opts.get('type') or archival.guesskind(dest) or 'files'
474 478 prefix = opts.get('prefix')
475 479
476 480 if dest == '-':
477 481 if kind == 'files':
478 482 raise error.Abort(_('cannot archive plain files to stdout'))
479 483 dest = cmdutil.makefileobj(repo, dest)
480 484 if not prefix:
481 485 prefix = os.path.basename(repo.root) + '-%h'
482 486
483 487 prefix = cmdutil.makefilename(repo, prefix, node)
484 488 match = scmutil.match(ctx, [], opts)
485 489 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
486 490 match, prefix, subrepos=opts.get('subrepos'))
487 491
488 492 @command('backout',
489 493 [('', 'merge', None, _('merge with old dirstate parent after backout')),
490 494 ('', 'commit', None,
491 495 _('commit if no conflicts were encountered (DEPRECATED)')),
492 496 ('', 'no-commit', None, _('do not commit')),
493 497 ('', 'parent', '',
494 498 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
495 499 ('r', 'rev', '', _('revision to backout'), _('REV')),
496 500 ('e', 'edit', False, _('invoke editor on commit messages')),
497 501 ] + mergetoolopts + walkopts + commitopts + commitopts2,
498 502 _('[OPTION]... [-r] REV'))
499 503 def backout(ui, repo, node=None, rev=None, **opts):
500 504 '''reverse effect of earlier changeset
501 505
502 506 Prepare a new changeset with the effect of REV undone in the
503 507 current working directory. If no conflicts were encountered,
504 508 it will be committed immediately.
505 509
506 510 If REV is the parent of the working directory, then this new changeset
507 511 is committed automatically (unless --no-commit is specified).
508 512
509 513 .. note::
510 514
511 515 :hg:`backout` cannot be used to fix either an unwanted or
512 516 incorrect merge.
513 517
514 518 .. container:: verbose
515 519
516 520 Examples:
517 521
518 522 - Reverse the effect of the parent of the working directory.
519 523 This backout will be committed immediately::
520 524
521 525 hg backout -r .
522 526
523 527 - Reverse the effect of previous bad revision 23::
524 528
525 529 hg backout -r 23
526 530
527 531 - Reverse the effect of previous bad revision 23 and
528 532 leave changes uncommitted::
529 533
530 534 hg backout -r 23 --no-commit
531 535 hg commit -m "Backout revision 23"
532 536
533 537 By default, the pending changeset will have one parent,
534 538 maintaining a linear history. With --merge, the pending
535 539 changeset will instead have two parents: the old parent of the
536 540 working directory and a new child of REV that simply undoes REV.
537 541
538 542 Before version 1.7, the behavior without --merge was equivalent
539 543 to specifying --merge followed by :hg:`update --clean .` to
540 544 cancel the merge and leave the child of REV as a head to be
541 545 merged separately.
542 546
543 547 See :hg:`help dates` for a list of formats valid for -d/--date.
544 548
545 549 See :hg:`help revert` for a way to restore files to the state
546 550 of another revision.
547 551
548 552 Returns 0 on success, 1 if nothing to backout or there are unresolved
549 553 files.
550 554 '''
551 555 wlock = lock = None
552 556 try:
553 557 wlock = repo.wlock()
554 558 lock = repo.lock()
555 559 return _dobackout(ui, repo, node, rev, **opts)
556 560 finally:
557 561 release(lock, wlock)
558 562
559 563 def _dobackout(ui, repo, node=None, rev=None, **opts):
560 564 opts = pycompat.byteskwargs(opts)
561 565 if opts.get('commit') and opts.get('no_commit'):
562 566 raise error.Abort(_("cannot use --commit with --no-commit"))
563 567 if opts.get('merge') and opts.get('no_commit'):
564 568 raise error.Abort(_("cannot use --merge with --no-commit"))
565 569
566 570 if rev and node:
567 571 raise error.Abort(_("please specify just one revision"))
568 572
569 573 if not rev:
570 574 rev = node
571 575
572 576 if not rev:
573 577 raise error.Abort(_("please specify a revision to backout"))
574 578
575 579 date = opts.get('date')
576 580 if date:
577 581 opts['date'] = util.parsedate(date)
578 582
579 583 cmdutil.checkunfinished(repo)
580 584 cmdutil.bailifchanged(repo)
581 585 node = scmutil.revsingle(repo, rev).node()
582 586
583 587 op1, op2 = repo.dirstate.parents()
584 588 if not repo.changelog.isancestor(node, op1):
585 589 raise error.Abort(_('cannot backout change that is not an ancestor'))
586 590
587 591 p1, p2 = repo.changelog.parents(node)
588 592 if p1 == nullid:
589 593 raise error.Abort(_('cannot backout a change with no parents'))
590 594 if p2 != nullid:
591 595 if not opts.get('parent'):
592 596 raise error.Abort(_('cannot backout a merge changeset'))
593 597 p = repo.lookup(opts['parent'])
594 598 if p not in (p1, p2):
595 599 raise error.Abort(_('%s is not a parent of %s') %
596 600 (short(p), short(node)))
597 601 parent = p
598 602 else:
599 603 if opts.get('parent'):
600 604 raise error.Abort(_('cannot use --parent on non-merge changeset'))
601 605 parent = p1
602 606
603 607 # the backout should appear on the same branch
604 608 branch = repo.dirstate.branch()
605 609 bheads = repo.branchheads(branch)
606 610 rctx = scmutil.revsingle(repo, hex(parent))
607 611 if not opts.get('merge') and op1 != node:
608 612 dsguard = dirstateguard.dirstateguard(repo, 'backout')
609 613 try:
610 614 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
611 615 'backout')
612 616 stats = mergemod.update(repo, parent, True, True, node, False)
613 617 repo.setparents(op1, op2)
614 618 dsguard.close()
615 619 hg._showstats(repo, stats)
616 620 if stats[3]:
617 621 repo.ui.status(_("use 'hg resolve' to retry unresolved "
618 622 "file merges\n"))
619 623 return 1
620 624 finally:
621 625 ui.setconfig('ui', 'forcemerge', '', '')
622 626 lockmod.release(dsguard)
623 627 else:
624 628 hg.clean(repo, node, show_stats=False)
625 629 repo.dirstate.setbranch(branch)
626 630 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
627 631
628 632 if opts.get('no_commit'):
629 633 msg = _("changeset %s backed out, "
630 634 "don't forget to commit.\n")
631 635 ui.status(msg % short(node))
632 636 return 0
633 637
634 638 def commitfunc(ui, repo, message, match, opts):
635 639 editform = 'backout'
636 640 e = cmdutil.getcommiteditor(editform=editform,
637 641 **pycompat.strkwargs(opts))
638 642 if not message:
639 643 # we don't translate commit messages
640 644 message = "Backed out changeset %s" % short(node)
641 645 e = cmdutil.getcommiteditor(edit=True, editform=editform)
642 646 return repo.commit(message, opts.get('user'), opts.get('date'),
643 647 match, editor=e)
644 648 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
645 649 if not newnode:
646 650 ui.status(_("nothing changed\n"))
647 651 return 1
648 652 cmdutil.commitstatus(repo, newnode, branch, bheads)
649 653
650 654 def nice(node):
651 655 return '%d:%s' % (repo.changelog.rev(node), short(node))
652 656 ui.status(_('changeset %s backs out changeset %s\n') %
653 657 (nice(repo.changelog.tip()), nice(node)))
654 658 if opts.get('merge') and op1 != node:
655 659 hg.clean(repo, op1, show_stats=False)
656 660 ui.status(_('merging with changeset %s\n')
657 661 % nice(repo.changelog.tip()))
658 662 try:
659 663 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
660 664 'backout')
661 665 return hg.merge(repo, hex(repo.changelog.tip()))
662 666 finally:
663 667 ui.setconfig('ui', 'forcemerge', '', '')
664 668 return 0
665 669
666 670 @command('bisect',
667 671 [('r', 'reset', False, _('reset bisect state')),
668 672 ('g', 'good', False, _('mark changeset good')),
669 673 ('b', 'bad', False, _('mark changeset bad')),
670 674 ('s', 'skip', False, _('skip testing changeset')),
671 675 ('e', 'extend', False, _('extend the bisect range')),
672 676 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
673 677 ('U', 'noupdate', False, _('do not update to target'))],
674 678 _("[-gbsr] [-U] [-c CMD] [REV]"))
675 679 def bisect(ui, repo, rev=None, extra=None, command=None,
676 680 reset=None, good=None, bad=None, skip=None, extend=None,
677 681 noupdate=None):
678 682 """subdivision search of changesets
679 683
680 684 This command helps to find changesets which introduce problems. To
681 685 use, mark the earliest changeset you know exhibits the problem as
682 686 bad, then mark the latest changeset which is free from the problem
683 687 as good. Bisect will update your working directory to a revision
684 688 for testing (unless the -U/--noupdate option is specified). Once
685 689 you have performed tests, mark the working directory as good or
686 690 bad, and bisect will either update to another candidate changeset
687 691 or announce that it has found the bad revision.
688 692
689 693 As a shortcut, you can also use the revision argument to mark a
690 694 revision as good or bad without checking it out first.
691 695
692 696 If you supply a command, it will be used for automatic bisection.
693 697 The environment variable HG_NODE will contain the ID of the
694 698 changeset being tested. The exit status of the command will be
695 699 used to mark revisions as good or bad: status 0 means good, 125
696 700 means to skip the revision, 127 (command not found) will abort the
697 701 bisection, and any other non-zero exit status means the revision
698 702 is bad.
699 703
700 704 .. container:: verbose
701 705
702 706 Some examples:
703 707
704 708 - start a bisection with known bad revision 34, and good revision 12::
705 709
706 710 hg bisect --bad 34
707 711 hg bisect --good 12
708 712
709 713 - advance the current bisection by marking current revision as good or
710 714 bad::
711 715
712 716 hg bisect --good
713 717 hg bisect --bad
714 718
715 719 - mark the current revision, or a known revision, to be skipped (e.g. if
716 720 that revision is not usable because of another issue)::
717 721
718 722 hg bisect --skip
719 723 hg bisect --skip 23
720 724
721 725 - skip all revisions that do not touch directories ``foo`` or ``bar``::
722 726
723 727 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
724 728
725 729 - forget the current bisection::
726 730
727 731 hg bisect --reset
728 732
729 733 - use 'make && make tests' to automatically find the first broken
730 734 revision::
731 735
732 736 hg bisect --reset
733 737 hg bisect --bad 34
734 738 hg bisect --good 12
735 739 hg bisect --command "make && make tests"
736 740
737 741 - see all changesets whose states are already known in the current
738 742 bisection::
739 743
740 744 hg log -r "bisect(pruned)"
741 745
742 746 - see the changeset currently being bisected (especially useful
743 747 if running with -U/--noupdate)::
744 748
745 749 hg log -r "bisect(current)"
746 750
747 751 - see all changesets that took part in the current bisection::
748 752
749 753 hg log -r "bisect(range)"
750 754
751 755 - you can even get a nice graph::
752 756
753 757 hg log --graph -r "bisect(range)"
754 758
755 759 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
756 760
757 761 Returns 0 on success.
758 762 """
759 763 # backward compatibility
760 764 if rev in "good bad reset init".split():
761 765 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
762 766 cmd, rev, extra = rev, extra, None
763 767 if cmd == "good":
764 768 good = True
765 769 elif cmd == "bad":
766 770 bad = True
767 771 else:
768 772 reset = True
769 773 elif extra:
770 774 raise error.Abort(_('incompatible arguments'))
771 775
772 776 incompatibles = {
773 777 '--bad': bad,
774 778 '--command': bool(command),
775 779 '--extend': extend,
776 780 '--good': good,
777 781 '--reset': reset,
778 782 '--skip': skip,
779 783 }
780 784
781 785 enabled = [x for x in incompatibles if incompatibles[x]]
782 786
783 787 if len(enabled) > 1:
784 788 raise error.Abort(_('%s and %s are incompatible') %
785 789 tuple(sorted(enabled)[0:2]))
786 790
787 791 if reset:
788 792 hbisect.resetstate(repo)
789 793 return
790 794
791 795 state = hbisect.load_state(repo)
792 796
793 797 # update state
794 798 if good or bad or skip:
795 799 if rev:
796 800 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
797 801 else:
798 802 nodes = [repo.lookup('.')]
799 803 if good:
800 804 state['good'] += nodes
801 805 elif bad:
802 806 state['bad'] += nodes
803 807 elif skip:
804 808 state['skip'] += nodes
805 809 hbisect.save_state(repo, state)
806 810 if not (state['good'] and state['bad']):
807 811 return
808 812
809 813 def mayupdate(repo, node, show_stats=True):
810 814 """common used update sequence"""
811 815 if noupdate:
812 816 return
813 817 cmdutil.checkunfinished(repo)
814 818 cmdutil.bailifchanged(repo)
815 819 return hg.clean(repo, node, show_stats=show_stats)
816 820
817 821 displayer = cmdutil.show_changeset(ui, repo, {})
818 822
819 823 if command:
820 824 changesets = 1
821 825 if noupdate:
822 826 try:
823 827 node = state['current'][0]
824 828 except LookupError:
825 829 raise error.Abort(_('current bisect revision is unknown - '
826 830 'start a new bisect to fix'))
827 831 else:
828 832 node, p2 = repo.dirstate.parents()
829 833 if p2 != nullid:
830 834 raise error.Abort(_('current bisect revision is a merge'))
831 835 if rev:
832 836 node = repo[scmutil.revsingle(repo, rev, node)].node()
833 837 try:
834 838 while changesets:
835 839 # update state
836 840 state['current'] = [node]
837 841 hbisect.save_state(repo, state)
838 842 status = ui.system(command, environ={'HG_NODE': hex(node)},
839 843 blockedtag='bisect_check')
840 844 if status == 125:
841 845 transition = "skip"
842 846 elif status == 0:
843 847 transition = "good"
844 848 # status < 0 means process was killed
845 849 elif status == 127:
846 850 raise error.Abort(_("failed to execute %s") % command)
847 851 elif status < 0:
848 852 raise error.Abort(_("%s killed") % command)
849 853 else:
850 854 transition = "bad"
851 855 state[transition].append(node)
852 856 ctx = repo[node]
853 857 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
854 858 hbisect.checkstate(state)
855 859 # bisect
856 860 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
857 861 # update to next check
858 862 node = nodes[0]
859 863 mayupdate(repo, node, show_stats=False)
860 864 finally:
861 865 state['current'] = [node]
862 866 hbisect.save_state(repo, state)
863 867 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
864 868 return
865 869
866 870 hbisect.checkstate(state)
867 871
868 872 # actually bisect
869 873 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
870 874 if extend:
871 875 if not changesets:
872 876 extendnode = hbisect.extendrange(repo, state, nodes, good)
873 877 if extendnode is not None:
874 878 ui.write(_("Extending search to changeset %d:%s\n")
875 879 % (extendnode.rev(), extendnode))
876 880 state['current'] = [extendnode.node()]
877 881 hbisect.save_state(repo, state)
878 882 return mayupdate(repo, extendnode.node())
879 883 raise error.Abort(_("nothing to extend"))
880 884
881 885 if changesets == 0:
882 886 hbisect.printresult(ui, repo, state, displayer, nodes, good)
883 887 else:
884 888 assert len(nodes) == 1 # only a single node can be tested next
885 889 node = nodes[0]
886 890 # compute the approximate number of remaining tests
887 891 tests, size = 0, 2
888 892 while size <= changesets:
889 893 tests, size = tests + 1, size * 2
890 894 rev = repo.changelog.rev(node)
891 895 ui.write(_("Testing changeset %d:%s "
892 896 "(%d changesets remaining, ~%d tests)\n")
893 897 % (rev, short(node), changesets, tests))
894 898 state['current'] = [node]
895 899 hbisect.save_state(repo, state)
896 900 return mayupdate(repo, node)
897 901
898 902 @command('bookmarks|bookmark',
899 903 [('f', 'force', False, _('force')),
900 904 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
901 905 ('d', 'delete', False, _('delete a given bookmark')),
902 906 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
903 907 ('i', 'inactive', False, _('mark a bookmark inactive')),
904 908 ] + formatteropts,
905 909 _('hg bookmarks [OPTIONS]... [NAME]...'))
906 910 def bookmark(ui, repo, *names, **opts):
907 911 '''create a new bookmark or list existing bookmarks
908 912
909 913 Bookmarks are labels on changesets to help track lines of development.
910 914 Bookmarks are unversioned and can be moved, renamed and deleted.
911 915 Deleting or moving a bookmark has no effect on the associated changesets.
912 916
913 917 Creating or updating to a bookmark causes it to be marked as 'active'.
914 918 The active bookmark is indicated with a '*'.
915 919 When a commit is made, the active bookmark will advance to the new commit.
916 920 A plain :hg:`update` will also advance an active bookmark, if possible.
917 921 Updating away from a bookmark will cause it to be deactivated.
918 922
919 923 Bookmarks can be pushed and pulled between repositories (see
920 924 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
921 925 diverged, a new 'divergent bookmark' of the form 'name@path' will
922 926 be created. Using :hg:`merge` will resolve the divergence.
923 927
924 928 Specifying bookmark as '.' to -m or -d options is equivalent to specifying
925 929 the active bookmark's name.
926 930
927 931 A bookmark named '@' has the special property that :hg:`clone` will
928 932 check it out by default if it exists.
929 933
930 934 .. container:: verbose
931 935
932 936 Examples:
933 937
934 938 - create an active bookmark for a new line of development::
935 939
936 940 hg book new-feature
937 941
938 942 - create an inactive bookmark as a place marker::
939 943
940 944 hg book -i reviewed
941 945
942 946 - create an inactive bookmark on another changeset::
943 947
944 948 hg book -r .^ tested
945 949
946 950 - rename bookmark turkey to dinner::
947 951
948 952 hg book -m turkey dinner
949 953
950 954 - move the '@' bookmark from another branch::
951 955
952 956 hg book -f @
953 957 '''
954 958 force = opts.get(r'force')
955 959 rev = opts.get(r'rev')
956 960 delete = opts.get(r'delete')
957 961 rename = opts.get(r'rename')
958 962 inactive = opts.get(r'inactive')
959 963
960 964 if delete and rename:
961 965 raise error.Abort(_("--delete and --rename are incompatible"))
962 966 if delete and rev:
963 967 raise error.Abort(_("--rev is incompatible with --delete"))
964 968 if rename and rev:
965 969 raise error.Abort(_("--rev is incompatible with --rename"))
966 970 if not names and (delete or rev):
967 971 raise error.Abort(_("bookmark name required"))
968 972
969 973 if delete or rename or names or inactive:
970 974 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
971 975 if delete:
972 976 names = pycompat.maplist(repo._bookmarks.expandname, names)
973 977 bookmarks.delete(repo, tr, names)
974 978 elif rename:
975 979 if not names:
976 980 raise error.Abort(_("new bookmark name required"))
977 981 elif len(names) > 1:
978 982 raise error.Abort(_("only one new bookmark name allowed"))
979 983 rename = repo._bookmarks.expandname(rename)
980 984 bookmarks.rename(repo, tr, rename, names[0], force, inactive)
981 985 elif names:
982 986 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
983 987 elif inactive:
984 988 if len(repo._bookmarks) == 0:
985 989 ui.status(_("no bookmarks set\n"))
986 990 elif not repo._activebookmark:
987 991 ui.status(_("no active bookmark\n"))
988 992 else:
989 993 bookmarks.deactivate(repo)
990 994 else: # show bookmarks
991 995 bookmarks.printbookmarks(ui, repo, **opts)
992 996
993 997 @command('branch',
994 998 [('f', 'force', None,
995 999 _('set branch name even if it shadows an existing branch')),
996 1000 ('C', 'clean', None, _('reset branch name to parent branch name'))],
997 1001 _('[-fC] [NAME]'))
998 1002 def branch(ui, repo, label=None, **opts):
999 1003 """set or show the current branch name
1000 1004
1001 1005 .. note::
1002 1006
1003 1007 Branch names are permanent and global. Use :hg:`bookmark` to create a
1004 1008 light-weight bookmark instead. See :hg:`help glossary` for more
1005 1009 information about named branches and bookmarks.
1006 1010
1007 1011 With no argument, show the current branch name. With one argument,
1008 1012 set the working directory branch name (the branch will not exist
1009 1013 in the repository until the next commit). Standard practice
1010 1014 recommends that primary development take place on the 'default'
1011 1015 branch.
1012 1016
1013 1017 Unless -f/--force is specified, branch will not let you set a
1014 1018 branch name that already exists.
1015 1019
1016 1020 Use -C/--clean to reset the working directory branch to that of
1017 1021 the parent of the working directory, negating a previous branch
1018 1022 change.
1019 1023
1020 1024 Use the command :hg:`update` to switch to an existing branch. Use
1021 1025 :hg:`commit --close-branch` to mark this branch head as closed.
1022 1026 When all heads of a branch are closed, the branch will be
1023 1027 considered closed.
1024 1028
1025 1029 Returns 0 on success.
1026 1030 """
1027 1031 opts = pycompat.byteskwargs(opts)
1028 1032 if label:
1029 1033 label = label.strip()
1030 1034
1031 1035 if not opts.get('clean') and not label:
1032 1036 ui.write("%s\n" % repo.dirstate.branch())
1033 1037 return
1034 1038
1035 1039 with repo.wlock():
1036 1040 if opts.get('clean'):
1037 1041 label = repo[None].p1().branch()
1038 1042 repo.dirstate.setbranch(label)
1039 1043 ui.status(_('reset working directory to branch %s\n') % label)
1040 1044 elif label:
1041 1045 if not opts.get('force') and label in repo.branchmap():
1042 1046 if label not in [p.branch() for p in repo[None].parents()]:
1043 1047 raise error.Abort(_('a branch of the same name already'
1044 1048 ' exists'),
1045 1049 # i18n: "it" refers to an existing branch
1046 1050 hint=_("use 'hg update' to switch to it"))
1047 1051 scmutil.checknewlabel(repo, label, 'branch')
1048 1052 repo.dirstate.setbranch(label)
1049 1053 ui.status(_('marked working directory as branch %s\n') % label)
1050 1054
1051 1055 # find any open named branches aside from default
1052 1056 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1053 1057 if n != "default" and not c]
1054 1058 if not others:
1055 1059 ui.status(_('(branches are permanent and global, '
1056 1060 'did you want a bookmark?)\n'))
1057 1061
1058 1062 @command('branches',
1059 1063 [('a', 'active', False,
1060 1064 _('show only branches that have unmerged heads (DEPRECATED)')),
1061 1065 ('c', 'closed', False, _('show normal and closed branches')),
1062 1066 ] + formatteropts,
1063 1067 _('[-c]'))
1064 1068 def branches(ui, repo, active=False, closed=False, **opts):
1065 1069 """list repository named branches
1066 1070
1067 1071 List the repository's named branches, indicating which ones are
1068 1072 inactive. If -c/--closed is specified, also list branches which have
1069 1073 been marked closed (see :hg:`commit --close-branch`).
1070 1074
1071 1075 Use the command :hg:`update` to switch to an existing branch.
1072 1076
1073 1077 Returns 0.
1074 1078 """
1075 1079
1076 1080 opts = pycompat.byteskwargs(opts)
1077 1081 ui.pager('branches')
1078 1082 fm = ui.formatter('branches', opts)
1079 1083 hexfunc = fm.hexfunc
1080 1084
1081 1085 allheads = set(repo.heads())
1082 1086 branches = []
1083 1087 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1084 1088 isactive = False
1085 1089 if not isclosed:
1086 1090 openheads = set(repo.branchmap().iteropen(heads))
1087 1091 isactive = bool(openheads & allheads)
1088 1092 branches.append((tag, repo[tip], isactive, not isclosed))
1089 1093 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1090 1094 reverse=True)
1091 1095
1092 1096 for tag, ctx, isactive, isopen in branches:
1093 1097 if active and not isactive:
1094 1098 continue
1095 1099 if isactive:
1096 1100 label = 'branches.active'
1097 1101 notice = ''
1098 1102 elif not isopen:
1099 1103 if not closed:
1100 1104 continue
1101 1105 label = 'branches.closed'
1102 1106 notice = _(' (closed)')
1103 1107 else:
1104 1108 label = 'branches.inactive'
1105 1109 notice = _(' (inactive)')
1106 1110 current = (tag == repo.dirstate.branch())
1107 1111 if current:
1108 1112 label = 'branches.current'
1109 1113
1110 1114 fm.startitem()
1111 1115 fm.write('branch', '%s', tag, label=label)
1112 1116 rev = ctx.rev()
1113 1117 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1114 1118 fmt = ' ' * padsize + ' %d:%s'
1115 1119 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1116 1120 label='log.changeset changeset.%s' % ctx.phasestr())
1117 1121 fm.context(ctx=ctx)
1118 1122 fm.data(active=isactive, closed=not isopen, current=current)
1119 1123 if not ui.quiet:
1120 1124 fm.plain(notice)
1121 1125 fm.plain('\n')
1122 1126 fm.end()
1123 1127
1124 1128 @command('bundle',
1125 1129 [('f', 'force', None, _('run even when the destination is unrelated')),
1126 1130 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1127 1131 _('REV')),
1128 1132 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1129 1133 _('BRANCH')),
1130 1134 ('', 'base', [],
1131 1135 _('a base changeset assumed to be available at the destination'),
1132 1136 _('REV')),
1133 1137 ('a', 'all', None, _('bundle all changesets in the repository')),
1134 1138 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1135 1139 ] + remoteopts,
1136 1140 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1137 1141 def bundle(ui, repo, fname, dest=None, **opts):
1138 1142 """create a bundle file
1139 1143
1140 1144 Generate a bundle file containing data to be added to a repository.
1141 1145
1142 1146 To create a bundle containing all changesets, use -a/--all
1143 1147 (or --base null). Otherwise, hg assumes the destination will have
1144 1148 all the nodes you specify with --base parameters. Otherwise, hg
1145 1149 will assume the repository has all the nodes in destination, or
1146 1150 default-push/default if no destination is specified.
1147 1151
1148 1152 You can change bundle format with the -t/--type option. See
1149 1153 :hg:`help bundlespec` for documentation on this format. By default,
1150 1154 the most appropriate format is used and compression defaults to
1151 1155 bzip2.
1152 1156
1153 1157 The bundle file can then be transferred using conventional means
1154 1158 and applied to another repository with the unbundle or pull
1155 1159 command. This is useful when direct push and pull are not
1156 1160 available or when exporting an entire repository is undesirable.
1157 1161
1158 1162 Applying bundles preserves all changeset contents including
1159 1163 permissions, copy/rename information, and revision history.
1160 1164
1161 1165 Returns 0 on success, 1 if no changes found.
1162 1166 """
1163 1167 opts = pycompat.byteskwargs(opts)
1164 1168 revs = None
1165 1169 if 'rev' in opts:
1166 1170 revstrings = opts['rev']
1167 1171 revs = scmutil.revrange(repo, revstrings)
1168 1172 if revstrings and not revs:
1169 1173 raise error.Abort(_('no commits to bundle'))
1170 1174
1171 1175 bundletype = opts.get('type', 'bzip2').lower()
1172 1176 try:
1173 1177 bcompression, cgversion, params = exchange.parsebundlespec(
1174 1178 repo, bundletype, strict=False)
1175 1179 except error.UnsupportedBundleSpecification as e:
1176 1180 raise error.Abort(str(e),
1177 1181 hint=_("see 'hg help bundlespec' for supported "
1178 1182 "values for --type"))
1179 1183
1180 1184 # Packed bundles are a pseudo bundle format for now.
1181 1185 if cgversion == 's1':
1182 1186 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1183 1187 hint=_("use 'hg debugcreatestreamclonebundle'"))
1184 1188
1185 1189 if opts.get('all'):
1186 1190 if dest:
1187 1191 raise error.Abort(_("--all is incompatible with specifying "
1188 1192 "a destination"))
1189 1193 if opts.get('base'):
1190 1194 ui.warn(_("ignoring --base because --all was specified\n"))
1191 1195 base = ['null']
1192 1196 else:
1193 1197 base = scmutil.revrange(repo, opts.get('base'))
1194 1198 if cgversion not in changegroup.supportedoutgoingversions(repo):
1195 1199 raise error.Abort(_("repository does not support bundle version %s") %
1196 1200 cgversion)
1197 1201
1198 1202 if base:
1199 1203 if dest:
1200 1204 raise error.Abort(_("--base is incompatible with specifying "
1201 1205 "a destination"))
1202 1206 common = [repo.lookup(rev) for rev in base]
1203 1207 heads = revs and map(repo.lookup, revs) or None
1204 1208 outgoing = discovery.outgoing(repo, common, heads)
1205 1209 else:
1206 1210 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1207 1211 dest, branches = hg.parseurl(dest, opts.get('branch'))
1208 1212 other = hg.peer(repo, opts, dest)
1209 1213 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1210 1214 heads = revs and map(repo.lookup, revs) or revs
1211 1215 outgoing = discovery.findcommonoutgoing(repo, other,
1212 1216 onlyheads=heads,
1213 1217 force=opts.get('force'),
1214 1218 portable=True)
1215 1219
1216 1220 if not outgoing.missing:
1217 1221 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1218 1222 return 1
1219 1223
1220 1224 if cgversion == '01': #bundle1
1221 1225 if bcompression is None:
1222 1226 bcompression = 'UN'
1223 1227 bversion = 'HG10' + bcompression
1224 1228 bcompression = None
1225 1229 elif cgversion in ('02', '03'):
1226 1230 bversion = 'HG20'
1227 1231 else:
1228 1232 raise error.ProgrammingError(
1229 1233 'bundle: unexpected changegroup version %s' % cgversion)
1230 1234
1231 1235 # TODO compression options should be derived from bundlespec parsing.
1232 1236 # This is a temporary hack to allow adjusting bundle compression
1233 1237 # level without a) formalizing the bundlespec changes to declare it
1234 1238 # b) introducing a command flag.
1235 1239 compopts = {}
1236 1240 complevel = ui.configint('experimental', 'bundlecomplevel')
1237 1241 if complevel is not None:
1238 1242 compopts['level'] = complevel
1239 1243
1240 1244
1241 1245 contentopts = {'cg.version': cgversion}
1242 1246 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1243 1247 contentopts['obsolescence'] = True
1244 1248 if repo.ui.configbool('experimental', 'bundle-phases'):
1245 1249 contentopts['phases'] = True
1246 1250 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1247 1251 contentopts, compression=bcompression,
1248 1252 compopts=compopts)
1249 1253
1250 1254 @command('cat',
1251 1255 [('o', 'output', '',
1252 1256 _('print output to file with formatted name'), _('FORMAT')),
1253 1257 ('r', 'rev', '', _('print the given revision'), _('REV')),
1254 1258 ('', 'decode', None, _('apply any matching decode filter')),
1255 1259 ] + walkopts + formatteropts,
1256 1260 _('[OPTION]... FILE...'),
1257 1261 inferrepo=True)
1258 1262 def cat(ui, repo, file1, *pats, **opts):
1259 1263 """output the current or given revision of files
1260 1264
1261 1265 Print the specified files as they were at the given revision. If
1262 1266 no revision is given, the parent of the working directory is used.
1263 1267
1264 1268 Output may be to a file, in which case the name of the file is
1265 1269 given using a format string. The formatting rules as follows:
1266 1270
1267 1271 :``%%``: literal "%" character
1268 1272 :``%s``: basename of file being printed
1269 1273 :``%d``: dirname of file being printed, or '.' if in repository root
1270 1274 :``%p``: root-relative path name of file being printed
1271 1275 :``%H``: changeset hash (40 hexadecimal digits)
1272 1276 :``%R``: changeset revision number
1273 1277 :``%h``: short-form changeset hash (12 hexadecimal digits)
1274 1278 :``%r``: zero-padded changeset revision number
1275 1279 :``%b``: basename of the exporting repository
1276 1280
1277 1281 Returns 0 on success.
1278 1282 """
1279 1283 ctx = scmutil.revsingle(repo, opts.get('rev'))
1280 1284 m = scmutil.match(ctx, (file1,) + pats, opts)
1281 1285 fntemplate = opts.pop('output', '')
1282 1286 if cmdutil.isstdiofilename(fntemplate):
1283 1287 fntemplate = ''
1284 1288
1285 1289 if fntemplate:
1286 1290 fm = formatter.nullformatter(ui, 'cat')
1287 1291 else:
1288 1292 ui.pager('cat')
1289 1293 fm = ui.formatter('cat', opts)
1290 1294 with fm:
1291 1295 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '', **opts)
1292 1296
1293 1297 @command('^clone',
1294 1298 [('U', 'noupdate', None, _('the clone will include an empty working '
1295 1299 'directory (only a repository)')),
1296 1300 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1297 1301 _('REV')),
1298 1302 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1299 1303 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1300 1304 ('', 'pull', None, _('use pull protocol to copy metadata')),
1301 1305 ('', 'uncompressed', None,
1302 1306 _('an alias to --stream (DEPRECATED)')),
1303 1307 ('', 'stream', None,
1304 1308 _('clone with minimal data processing')),
1305 1309 ] + remoteopts,
1306 1310 _('[OPTION]... SOURCE [DEST]'),
1307 1311 norepo=True)
1308 1312 def clone(ui, source, dest=None, **opts):
1309 1313 """make a copy of an existing repository
1310 1314
1311 1315 Create a copy of an existing repository in a new directory.
1312 1316
1313 1317 If no destination directory name is specified, it defaults to the
1314 1318 basename of the source.
1315 1319
1316 1320 The location of the source is added to the new repository's
1317 1321 ``.hg/hgrc`` file, as the default to be used for future pulls.
1318 1322
1319 1323 Only local paths and ``ssh://`` URLs are supported as
1320 1324 destinations. For ``ssh://`` destinations, no working directory or
1321 1325 ``.hg/hgrc`` will be created on the remote side.
1322 1326
1323 1327 If the source repository has a bookmark called '@' set, that
1324 1328 revision will be checked out in the new repository by default.
1325 1329
1326 1330 To check out a particular version, use -u/--update, or
1327 1331 -U/--noupdate to create a clone with no working directory.
1328 1332
1329 1333 To pull only a subset of changesets, specify one or more revisions
1330 1334 identifiers with -r/--rev or branches with -b/--branch. The
1331 1335 resulting clone will contain only the specified changesets and
1332 1336 their ancestors. These options (or 'clone src#rev dest') imply
1333 1337 --pull, even for local source repositories.
1334 1338
1335 1339 In normal clone mode, the remote normalizes repository data into a common
1336 1340 exchange format and the receiving end translates this data into its local
1337 1341 storage format. --stream activates a different clone mode that essentially
1338 1342 copies repository files from the remote with minimal data processing. This
1339 1343 significantly reduces the CPU cost of a clone both remotely and locally.
1340 1344 However, it often increases the transferred data size by 30-40%. This can
1341 1345 result in substantially faster clones where I/O throughput is plentiful,
1342 1346 especially for larger repositories. A side-effect of --stream clones is
1343 1347 that storage settings and requirements on the remote are applied locally:
1344 1348 a modern client may inherit legacy or inefficient storage used by the
1345 1349 remote or a legacy Mercurial client may not be able to clone from a
1346 1350 modern Mercurial remote.
1347 1351
1348 1352 .. note::
1349 1353
1350 1354 Specifying a tag will include the tagged changeset but not the
1351 1355 changeset containing the tag.
1352 1356
1353 1357 .. container:: verbose
1354 1358
1355 1359 For efficiency, hardlinks are used for cloning whenever the
1356 1360 source and destination are on the same filesystem (note this
1357 1361 applies only to the repository data, not to the working
1358 1362 directory). Some filesystems, such as AFS, implement hardlinking
1359 1363 incorrectly, but do not report errors. In these cases, use the
1360 1364 --pull option to avoid hardlinking.
1361 1365
1362 1366 Mercurial will update the working directory to the first applicable
1363 1367 revision from this list:
1364 1368
1365 1369 a) null if -U or the source repository has no changesets
1366 1370 b) if -u . and the source repository is local, the first parent of
1367 1371 the source repository's working directory
1368 1372 c) the changeset specified with -u (if a branch name, this means the
1369 1373 latest head of that branch)
1370 1374 d) the changeset specified with -r
1371 1375 e) the tipmost head specified with -b
1372 1376 f) the tipmost head specified with the url#branch source syntax
1373 1377 g) the revision marked with the '@' bookmark, if present
1374 1378 h) the tipmost head of the default branch
1375 1379 i) tip
1376 1380
1377 1381 When cloning from servers that support it, Mercurial may fetch
1378 1382 pre-generated data from a server-advertised URL. When this is done,
1379 1383 hooks operating on incoming changesets and changegroups may fire twice,
1380 1384 once for the bundle fetched from the URL and another for any additional
1381 1385 data not fetched from this URL. In addition, if an error occurs, the
1382 1386 repository may be rolled back to a partial clone. This behavior may
1383 1387 change in future releases. See :hg:`help -e clonebundles` for more.
1384 1388
1385 1389 Examples:
1386 1390
1387 1391 - clone a remote repository to a new directory named hg/::
1388 1392
1389 1393 hg clone https://www.mercurial-scm.org/repo/hg/
1390 1394
1391 1395 - create a lightweight local clone::
1392 1396
1393 1397 hg clone project/ project-feature/
1394 1398
1395 1399 - clone from an absolute path on an ssh server (note double-slash)::
1396 1400
1397 1401 hg clone ssh://user@server//home/projects/alpha/
1398 1402
1399 1403 - do a streaming clone while checking out a specified version::
1400 1404
1401 1405 hg clone --stream http://server/repo -u 1.5
1402 1406
1403 1407 - create a repository without changesets after a particular revision::
1404 1408
1405 1409 hg clone -r 04e544 experimental/ good/
1406 1410
1407 1411 - clone (and track) a particular named branch::
1408 1412
1409 1413 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1410 1414
1411 1415 See :hg:`help urls` for details on specifying URLs.
1412 1416
1413 1417 Returns 0 on success.
1414 1418 """
1415 1419 opts = pycompat.byteskwargs(opts)
1416 1420 if opts.get('noupdate') and opts.get('updaterev'):
1417 1421 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1418 1422
1419 1423 r = hg.clone(ui, opts, source, dest,
1420 1424 pull=opts.get('pull'),
1421 1425 stream=opts.get('stream') or opts.get('uncompressed'),
1422 1426 rev=opts.get('rev'),
1423 1427 update=opts.get('updaterev') or not opts.get('noupdate'),
1424 1428 branch=opts.get('branch'),
1425 1429 shareopts=opts.get('shareopts'))
1426 1430
1427 1431 return r is None
1428 1432
1429 1433 @command('^commit|ci',
1430 1434 [('A', 'addremove', None,
1431 1435 _('mark new/missing files as added/removed before committing')),
1432 1436 ('', 'close-branch', None,
1433 1437 _('mark a branch head as closed')),
1434 1438 ('', 'amend', None, _('amend the parent of the working directory')),
1435 1439 ('s', 'secret', None, _('use the secret phase for committing')),
1436 1440 ('e', 'edit', None, _('invoke editor on commit messages')),
1437 1441 ('i', 'interactive', None, _('use interactive mode')),
1438 1442 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1439 1443 _('[OPTION]... [FILE]...'),
1440 1444 inferrepo=True)
1441 1445 def commit(ui, repo, *pats, **opts):
1442 1446 """commit the specified files or all outstanding changes
1443 1447
1444 1448 Commit changes to the given files into the repository. Unlike a
1445 1449 centralized SCM, this operation is a local operation. See
1446 1450 :hg:`push` for a way to actively distribute your changes.
1447 1451
1448 1452 If a list of files is omitted, all changes reported by :hg:`status`
1449 1453 will be committed.
1450 1454
1451 1455 If you are committing the result of a merge, do not provide any
1452 1456 filenames or -I/-X filters.
1453 1457
1454 1458 If no commit message is specified, Mercurial starts your
1455 1459 configured editor where you can enter a message. In case your
1456 1460 commit fails, you will find a backup of your message in
1457 1461 ``.hg/last-message.txt``.
1458 1462
1459 1463 The --close-branch flag can be used to mark the current branch
1460 1464 head closed. When all heads of a branch are closed, the branch
1461 1465 will be considered closed and no longer listed.
1462 1466
1463 1467 The --amend flag can be used to amend the parent of the
1464 1468 working directory with a new commit that contains the changes
1465 1469 in the parent in addition to those currently reported by :hg:`status`,
1466 1470 if there are any. The old commit is stored in a backup bundle in
1467 1471 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1468 1472 on how to restore it).
1469 1473
1470 1474 Message, user and date are taken from the amended commit unless
1471 1475 specified. When a message isn't specified on the command line,
1472 1476 the editor will open with the message of the amended commit.
1473 1477
1474 1478 It is not possible to amend public changesets (see :hg:`help phases`)
1475 1479 or changesets that have children.
1476 1480
1477 1481 See :hg:`help dates` for a list of formats valid for -d/--date.
1478 1482
1479 1483 Returns 0 on success, 1 if nothing changed.
1480 1484
1481 1485 .. container:: verbose
1482 1486
1483 1487 Examples:
1484 1488
1485 1489 - commit all files ending in .py::
1486 1490
1487 1491 hg commit --include "set:**.py"
1488 1492
1489 1493 - commit all non-binary files::
1490 1494
1491 1495 hg commit --exclude "set:binary()"
1492 1496
1493 1497 - amend the current commit and set the date to now::
1494 1498
1495 1499 hg commit --amend --date now
1496 1500 """
1497 1501 wlock = lock = None
1498 1502 try:
1499 1503 wlock = repo.wlock()
1500 1504 lock = repo.lock()
1501 1505 return _docommit(ui, repo, *pats, **opts)
1502 1506 finally:
1503 1507 release(lock, wlock)
1504 1508
1505 1509 def _docommit(ui, repo, *pats, **opts):
1506 1510 if opts.get(r'interactive'):
1507 1511 opts.pop(r'interactive')
1508 1512 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1509 1513 cmdutil.recordfilter, *pats,
1510 1514 **opts)
1511 1515 # ret can be 0 (no changes to record) or the value returned by
1512 1516 # commit(), 1 if nothing changed or None on success.
1513 1517 return 1 if ret == 0 else ret
1514 1518
1515 1519 opts = pycompat.byteskwargs(opts)
1516 1520 if opts.get('subrepos'):
1517 1521 if opts.get('amend'):
1518 1522 raise error.Abort(_('cannot amend with --subrepos'))
1519 1523 # Let --subrepos on the command line override config setting.
1520 1524 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1521 1525
1522 1526 cmdutil.checkunfinished(repo, commit=True)
1523 1527
1524 1528 branch = repo[None].branch()
1525 1529 bheads = repo.branchheads(branch)
1526 1530
1527 1531 extra = {}
1528 1532 if opts.get('close_branch'):
1529 1533 extra['close'] = 1
1530 1534
1531 1535 if not bheads:
1532 1536 raise error.Abort(_('can only close branch heads'))
1533 1537 elif opts.get('amend'):
1534 1538 if repo[None].parents()[0].p1().branch() != branch and \
1535 1539 repo[None].parents()[0].p2().branch() != branch:
1536 1540 raise error.Abort(_('can only close branch heads'))
1537 1541
1538 1542 if opts.get('amend'):
1539 1543 if ui.configbool('ui', 'commitsubrepos'):
1540 1544 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1541 1545
1542 1546 old = repo['.']
1543 1547 if not old.mutable():
1544 1548 raise error.Abort(_('cannot amend public changesets'))
1545 1549 if len(repo[None].parents()) > 1:
1546 1550 raise error.Abort(_('cannot amend while merging'))
1547 1551 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1548 1552 if not allowunstable and old.children():
1549 1553 raise error.Abort(_('cannot amend changeset with children'))
1550 1554
1551 1555 # Currently histedit gets confused if an amend happens while histedit
1552 1556 # is in progress. Since we have a checkunfinished command, we are
1553 1557 # temporarily honoring it.
1554 1558 #
1555 1559 # Note: eventually this guard will be removed. Please do not expect
1556 1560 # this behavior to remain.
1557 1561 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1558 1562 cmdutil.checkunfinished(repo)
1559 1563
1560 1564 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1561 1565 if node == old.node():
1562 1566 ui.status(_("nothing changed\n"))
1563 1567 return 1
1564 1568 else:
1565 1569 def commitfunc(ui, repo, message, match, opts):
1566 1570 overrides = {}
1567 1571 if opts.get('secret'):
1568 1572 overrides[('phases', 'new-commit')] = 'secret'
1569 1573
1570 1574 baseui = repo.baseui
1571 1575 with baseui.configoverride(overrides, 'commit'):
1572 1576 with ui.configoverride(overrides, 'commit'):
1573 1577 editform = cmdutil.mergeeditform(repo[None],
1574 1578 'commit.normal')
1575 1579 editor = cmdutil.getcommiteditor(
1576 1580 editform=editform, **pycompat.strkwargs(opts))
1577 1581 return repo.commit(message,
1578 1582 opts.get('user'),
1579 1583 opts.get('date'),
1580 1584 match,
1581 1585 editor=editor,
1582 1586 extra=extra)
1583 1587
1584 1588 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1585 1589
1586 1590 if not node:
1587 1591 stat = cmdutil.postcommitstatus(repo, pats, opts)
1588 1592 if stat[3]:
1589 1593 ui.status(_("nothing changed (%d missing files, see "
1590 1594 "'hg status')\n") % len(stat[3]))
1591 1595 else:
1592 1596 ui.status(_("nothing changed\n"))
1593 1597 return 1
1594 1598
1595 1599 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1596 1600
1597 1601 @command('config|showconfig|debugconfig',
1598 1602 [('u', 'untrusted', None, _('show untrusted configuration options')),
1599 1603 ('e', 'edit', None, _('edit user config')),
1600 1604 ('l', 'local', None, _('edit repository config')),
1601 1605 ('g', 'global', None, _('edit global config'))] + formatteropts,
1602 1606 _('[-u] [NAME]...'),
1603 1607 optionalrepo=True)
1604 1608 def config(ui, repo, *values, **opts):
1605 1609 """show combined config settings from all hgrc files
1606 1610
1607 1611 With no arguments, print names and values of all config items.
1608 1612
1609 1613 With one argument of the form section.name, print just the value
1610 1614 of that config item.
1611 1615
1612 1616 With multiple arguments, print names and values of all config
1613 1617 items with matching section names.
1614 1618
1615 1619 With --edit, start an editor on the user-level config file. With
1616 1620 --global, edit the system-wide config file. With --local, edit the
1617 1621 repository-level config file.
1618 1622
1619 1623 With --debug, the source (filename and line number) is printed
1620 1624 for each config item.
1621 1625
1622 1626 See :hg:`help config` for more information about config files.
1623 1627
1624 1628 Returns 0 on success, 1 if NAME does not exist.
1625 1629
1626 1630 """
1627 1631
1628 1632 opts = pycompat.byteskwargs(opts)
1629 1633 if opts.get('edit') or opts.get('local') or opts.get('global'):
1630 1634 if opts.get('local') and opts.get('global'):
1631 1635 raise error.Abort(_("can't use --local and --global together"))
1632 1636
1633 1637 if opts.get('local'):
1634 1638 if not repo:
1635 1639 raise error.Abort(_("can't use --local outside a repository"))
1636 1640 paths = [repo.vfs.join('hgrc')]
1637 1641 elif opts.get('global'):
1638 1642 paths = rcutil.systemrcpath()
1639 1643 else:
1640 1644 paths = rcutil.userrcpath()
1641 1645
1642 1646 for f in paths:
1643 1647 if os.path.exists(f):
1644 1648 break
1645 1649 else:
1646 1650 if opts.get('global'):
1647 1651 samplehgrc = uimod.samplehgrcs['global']
1648 1652 elif opts.get('local'):
1649 1653 samplehgrc = uimod.samplehgrcs['local']
1650 1654 else:
1651 1655 samplehgrc = uimod.samplehgrcs['user']
1652 1656
1653 1657 f = paths[0]
1654 1658 fp = open(f, "wb")
1655 1659 fp.write(util.tonativeeol(samplehgrc))
1656 1660 fp.close()
1657 1661
1658 1662 editor = ui.geteditor()
1659 1663 ui.system("%s \"%s\"" % (editor, f),
1660 1664 onerr=error.Abort, errprefix=_("edit failed"),
1661 1665 blockedtag='config_edit')
1662 1666 return
1663 1667 ui.pager('config')
1664 1668 fm = ui.formatter('config', opts)
1665 1669 for t, f in rcutil.rccomponents():
1666 1670 if t == 'path':
1667 1671 ui.debug('read config from: %s\n' % f)
1668 1672 elif t == 'items':
1669 1673 for section, name, value, source in f:
1670 1674 ui.debug('set config by: %s\n' % source)
1671 1675 else:
1672 1676 raise error.ProgrammingError('unknown rctype: %s' % t)
1673 1677 untrusted = bool(opts.get('untrusted'))
1674 1678 if values:
1675 1679 sections = [v for v in values if '.' not in v]
1676 1680 items = [v for v in values if '.' in v]
1677 1681 if len(items) > 1 or items and sections:
1678 1682 raise error.Abort(_('only one config item permitted'))
1679 1683 matched = False
1680 1684 for section, name, value in ui.walkconfig(untrusted=untrusted):
1681 1685 source = ui.configsource(section, name, untrusted)
1682 1686 value = pycompat.bytestr(value)
1683 1687 if fm.isplain():
1684 1688 source = source or 'none'
1685 1689 value = value.replace('\n', '\\n')
1686 1690 entryname = section + '.' + name
1687 1691 if values:
1688 1692 for v in values:
1689 1693 if v == section:
1690 1694 fm.startitem()
1691 1695 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1692 1696 fm.write('name value', '%s=%s\n', entryname, value)
1693 1697 matched = True
1694 1698 elif v == entryname:
1695 1699 fm.startitem()
1696 1700 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1697 1701 fm.write('value', '%s\n', value)
1698 1702 fm.data(name=entryname)
1699 1703 matched = True
1700 1704 else:
1701 1705 fm.startitem()
1702 1706 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1703 1707 fm.write('name value', '%s=%s\n', entryname, value)
1704 1708 matched = True
1705 1709 fm.end()
1706 1710 if matched:
1707 1711 return 0
1708 1712 return 1
1709 1713
1710 1714 @command('copy|cp',
1711 1715 [('A', 'after', None, _('record a copy that has already occurred')),
1712 1716 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1713 1717 ] + walkopts + dryrunopts,
1714 1718 _('[OPTION]... [SOURCE]... DEST'))
1715 1719 def copy(ui, repo, *pats, **opts):
1716 1720 """mark files as copied for the next commit
1717 1721
1718 1722 Mark dest as having copies of source files. If dest is a
1719 1723 directory, copies are put in that directory. If dest is a file,
1720 1724 the source must be a single file.
1721 1725
1722 1726 By default, this command copies the contents of files as they
1723 1727 exist in the working directory. If invoked with -A/--after, the
1724 1728 operation is recorded, but no copying is performed.
1725 1729
1726 1730 This command takes effect with the next commit. To undo a copy
1727 1731 before that, see :hg:`revert`.
1728 1732
1729 1733 Returns 0 on success, 1 if errors are encountered.
1730 1734 """
1731 1735 opts = pycompat.byteskwargs(opts)
1732 1736 with repo.wlock(False):
1733 1737 return cmdutil.copy(ui, repo, pats, opts)
1734 1738
1735 1739 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1736 1740 def debugcommands(ui, cmd='', *args):
1737 1741 """list all available commands and options"""
1738 1742 for cmd, vals in sorted(table.iteritems()):
1739 1743 cmd = cmd.split('|')[0].strip('^')
1740 1744 opts = ', '.join([i[1] for i in vals[1]])
1741 1745 ui.write('%s: %s\n' % (cmd, opts))
1742 1746
1743 1747 @command('debugcomplete',
1744 1748 [('o', 'options', None, _('show the command options'))],
1745 1749 _('[-o] CMD'),
1746 1750 norepo=True)
1747 1751 def debugcomplete(ui, cmd='', **opts):
1748 1752 """returns the completion list associated with the given command"""
1749 1753
1750 1754 if opts.get('options'):
1751 1755 options = []
1752 1756 otables = [globalopts]
1753 1757 if cmd:
1754 1758 aliases, entry = cmdutil.findcmd(cmd, table, False)
1755 1759 otables.append(entry[1])
1756 1760 for t in otables:
1757 1761 for o in t:
1758 1762 if "(DEPRECATED)" in o[3]:
1759 1763 continue
1760 1764 if o[0]:
1761 1765 options.append('-%s' % o[0])
1762 1766 options.append('--%s' % o[1])
1763 1767 ui.write("%s\n" % "\n".join(options))
1764 1768 return
1765 1769
1766 1770 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1767 1771 if ui.verbose:
1768 1772 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1769 1773 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1770 1774
1771 1775 @command('^diff',
1772 1776 [('r', 'rev', [], _('revision'), _('REV')),
1773 1777 ('c', 'change', '', _('change made by revision'), _('REV'))
1774 1778 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1775 1779 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1776 1780 inferrepo=True)
1777 1781 def diff(ui, repo, *pats, **opts):
1778 1782 """diff repository (or selected files)
1779 1783
1780 1784 Show differences between revisions for the specified files.
1781 1785
1782 1786 Differences between files are shown using the unified diff format.
1783 1787
1784 1788 .. note::
1785 1789
1786 1790 :hg:`diff` may generate unexpected results for merges, as it will
1787 1791 default to comparing against the working directory's first
1788 1792 parent changeset if no revisions are specified.
1789 1793
1790 1794 When two revision arguments are given, then changes are shown
1791 1795 between those revisions. If only one revision is specified then
1792 1796 that revision is compared to the working directory, and, when no
1793 1797 revisions are specified, the working directory files are compared
1794 1798 to its first parent.
1795 1799
1796 1800 Alternatively you can specify -c/--change with a revision to see
1797 1801 the changes in that changeset relative to its first parent.
1798 1802
1799 1803 Without the -a/--text option, diff will avoid generating diffs of
1800 1804 files it detects as binary. With -a, diff will generate a diff
1801 1805 anyway, probably with undesirable results.
1802 1806
1803 1807 Use the -g/--git option to generate diffs in the git extended diff
1804 1808 format. For more information, read :hg:`help diffs`.
1805 1809
1806 1810 .. container:: verbose
1807 1811
1808 1812 Examples:
1809 1813
1810 1814 - compare a file in the current working directory to its parent::
1811 1815
1812 1816 hg diff foo.c
1813 1817
1814 1818 - compare two historical versions of a directory, with rename info::
1815 1819
1816 1820 hg diff --git -r 1.0:1.2 lib/
1817 1821
1818 1822 - get change stats relative to the last change on some date::
1819 1823
1820 1824 hg diff --stat -r "date('may 2')"
1821 1825
1822 1826 - diff all newly-added files that contain a keyword::
1823 1827
1824 1828 hg diff "set:added() and grep(GNU)"
1825 1829
1826 1830 - compare a revision and its parents::
1827 1831
1828 1832 hg diff -c 9353 # compare against first parent
1829 1833 hg diff -r 9353^:9353 # same using revset syntax
1830 1834 hg diff -r 9353^2:9353 # compare against the second parent
1831 1835
1832 1836 Returns 0 on success.
1833 1837 """
1834 1838
1835 1839 opts = pycompat.byteskwargs(opts)
1836 1840 revs = opts.get('rev')
1837 1841 change = opts.get('change')
1838 1842 stat = opts.get('stat')
1839 1843 reverse = opts.get('reverse')
1840 1844
1841 1845 if revs and change:
1842 1846 msg = _('cannot specify --rev and --change at the same time')
1843 1847 raise error.Abort(msg)
1844 1848 elif change:
1845 1849 node2 = scmutil.revsingle(repo, change, None).node()
1846 1850 node1 = repo[node2].p1().node()
1847 1851 else:
1848 1852 node1, node2 = scmutil.revpair(repo, revs)
1849 1853
1850 1854 if reverse:
1851 1855 node1, node2 = node2, node1
1852 1856
1853 1857 diffopts = patch.diffallopts(ui, opts)
1854 1858 m = scmutil.match(repo[node2], pats, opts)
1855 1859 ui.pager('diff')
1856 1860 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1857 1861 listsubrepos=opts.get('subrepos'),
1858 1862 root=opts.get('root'))
1859 1863
1860 1864 @command('^export',
1861 1865 [('o', 'output', '',
1862 1866 _('print output to file with formatted name'), _('FORMAT')),
1863 1867 ('', 'switch-parent', None, _('diff against the second parent')),
1864 1868 ('r', 'rev', [], _('revisions to export'), _('REV')),
1865 1869 ] + diffopts,
1866 1870 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
1867 1871 def export(ui, repo, *changesets, **opts):
1868 1872 """dump the header and diffs for one or more changesets
1869 1873
1870 1874 Print the changeset header and diffs for one or more revisions.
1871 1875 If no revision is given, the parent of the working directory is used.
1872 1876
1873 1877 The information shown in the changeset header is: author, date,
1874 1878 branch name (if non-default), changeset hash, parent(s) and commit
1875 1879 comment.
1876 1880
1877 1881 .. note::
1878 1882
1879 1883 :hg:`export` may generate unexpected diff output for merge
1880 1884 changesets, as it will compare the merge changeset against its
1881 1885 first parent only.
1882 1886
1883 1887 Output may be to a file, in which case the name of the file is
1884 1888 given using a format string. The formatting rules are as follows:
1885 1889
1886 1890 :``%%``: literal "%" character
1887 1891 :``%H``: changeset hash (40 hexadecimal digits)
1888 1892 :``%N``: number of patches being generated
1889 1893 :``%R``: changeset revision number
1890 1894 :``%b``: basename of the exporting repository
1891 1895 :``%h``: short-form changeset hash (12 hexadecimal digits)
1892 1896 :``%m``: first line of the commit message (only alphanumeric characters)
1893 1897 :``%n``: zero-padded sequence number, starting at 1
1894 1898 :``%r``: zero-padded changeset revision number
1895 1899
1896 1900 Without the -a/--text option, export will avoid generating diffs
1897 1901 of files it detects as binary. With -a, export will generate a
1898 1902 diff anyway, probably with undesirable results.
1899 1903
1900 1904 Use the -g/--git option to generate diffs in the git extended diff
1901 1905 format. See :hg:`help diffs` for more information.
1902 1906
1903 1907 With the --switch-parent option, the diff will be against the
1904 1908 second parent. It can be useful to review a merge.
1905 1909
1906 1910 .. container:: verbose
1907 1911
1908 1912 Examples:
1909 1913
1910 1914 - use export and import to transplant a bugfix to the current
1911 1915 branch::
1912 1916
1913 1917 hg export -r 9353 | hg import -
1914 1918
1915 1919 - export all the changesets between two revisions to a file with
1916 1920 rename information::
1917 1921
1918 1922 hg export --git -r 123:150 > changes.txt
1919 1923
1920 1924 - split outgoing changes into a series of patches with
1921 1925 descriptive names::
1922 1926
1923 1927 hg export -r "outgoing()" -o "%n-%m.patch"
1924 1928
1925 1929 Returns 0 on success.
1926 1930 """
1927 1931 opts = pycompat.byteskwargs(opts)
1928 1932 changesets += tuple(opts.get('rev', []))
1929 1933 if not changesets:
1930 1934 changesets = ['.']
1931 1935 revs = scmutil.revrange(repo, changesets)
1932 1936 if not revs:
1933 1937 raise error.Abort(_("export requires at least one changeset"))
1934 1938 if len(revs) > 1:
1935 1939 ui.note(_('exporting patches:\n'))
1936 1940 else:
1937 1941 ui.note(_('exporting patch:\n'))
1938 1942 ui.pager('export')
1939 1943 cmdutil.export(repo, revs, fntemplate=opts.get('output'),
1940 1944 switch_parent=opts.get('switch_parent'),
1941 1945 opts=patch.diffallopts(ui, opts))
1942 1946
1943 1947 @command('files',
1944 1948 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
1945 1949 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
1946 1950 ] + walkopts + formatteropts + subrepoopts,
1947 1951 _('[OPTION]... [FILE]...'))
1948 1952 def files(ui, repo, *pats, **opts):
1949 1953 """list tracked files
1950 1954
1951 1955 Print files under Mercurial control in the working directory or
1952 1956 specified revision for given files (excluding removed files).
1953 1957 Files can be specified as filenames or filesets.
1954 1958
1955 1959 If no files are given to match, this command prints the names
1956 1960 of all files under Mercurial control.
1957 1961
1958 1962 .. container:: verbose
1959 1963
1960 1964 Examples:
1961 1965
1962 1966 - list all files under the current directory::
1963 1967
1964 1968 hg files .
1965 1969
1966 1970 - shows sizes and flags for current revision::
1967 1971
1968 1972 hg files -vr .
1969 1973
1970 1974 - list all files named README::
1971 1975
1972 1976 hg files -I "**/README"
1973 1977
1974 1978 - list all binary files::
1975 1979
1976 1980 hg files "set:binary()"
1977 1981
1978 1982 - find files containing a regular expression::
1979 1983
1980 1984 hg files "set:grep('bob')"
1981 1985
1982 1986 - search tracked file contents with xargs and grep::
1983 1987
1984 1988 hg files -0 | xargs -0 grep foo
1985 1989
1986 1990 See :hg:`help patterns` and :hg:`help filesets` for more information
1987 1991 on specifying file patterns.
1988 1992
1989 1993 Returns 0 if a match is found, 1 otherwise.
1990 1994
1991 1995 """
1992 1996
1993 1997 opts = pycompat.byteskwargs(opts)
1994 1998 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
1995 1999
1996 2000 end = '\n'
1997 2001 if opts.get('print0'):
1998 2002 end = '\0'
1999 2003 fmt = '%s' + end
2000 2004
2001 2005 m = scmutil.match(ctx, pats, opts)
2002 2006 ui.pager('files')
2003 2007 with ui.formatter('files', opts) as fm:
2004 2008 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2005 2009
2006 2010 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
2007 2011 def forget(ui, repo, *pats, **opts):
2008 2012 """forget the specified files on the next commit
2009 2013
2010 2014 Mark the specified files so they will no longer be tracked
2011 2015 after the next commit.
2012 2016
2013 2017 This only removes files from the current branch, not from the
2014 2018 entire project history, and it does not delete them from the
2015 2019 working directory.
2016 2020
2017 2021 To delete the file from the working directory, see :hg:`remove`.
2018 2022
2019 2023 To undo a forget before the next commit, see :hg:`add`.
2020 2024
2021 2025 .. container:: verbose
2022 2026
2023 2027 Examples:
2024 2028
2025 2029 - forget newly-added binary files::
2026 2030
2027 2031 hg forget "set:added() and binary()"
2028 2032
2029 2033 - forget files that would be excluded by .hgignore::
2030 2034
2031 2035 hg forget "set:hgignore()"
2032 2036
2033 2037 Returns 0 on success.
2034 2038 """
2035 2039
2036 2040 opts = pycompat.byteskwargs(opts)
2037 2041 if not pats:
2038 2042 raise error.Abort(_('no files specified'))
2039 2043
2040 2044 m = scmutil.match(repo[None], pats, opts)
2041 2045 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2042 2046 return rejected and 1 or 0
2043 2047
2044 2048 @command(
2045 2049 'graft',
2046 2050 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2047 2051 ('c', 'continue', False, _('resume interrupted graft')),
2048 2052 ('e', 'edit', False, _('invoke editor on commit messages')),
2049 2053 ('', 'log', None, _('append graft info to log message')),
2050 2054 ('f', 'force', False, _('force graft')),
2051 2055 ('D', 'currentdate', False,
2052 2056 _('record the current date as commit date')),
2053 2057 ('U', 'currentuser', False,
2054 2058 _('record the current user as committer'), _('DATE'))]
2055 2059 + commitopts2 + mergetoolopts + dryrunopts,
2056 2060 _('[OPTION]... [-r REV]... REV...'))
2057 2061 def graft(ui, repo, *revs, **opts):
2058 2062 '''copy changes from other branches onto the current branch
2059 2063
2060 2064 This command uses Mercurial's merge logic to copy individual
2061 2065 changes from other branches without merging branches in the
2062 2066 history graph. This is sometimes known as 'backporting' or
2063 2067 'cherry-picking'. By default, graft will copy user, date, and
2064 2068 description from the source changesets.
2065 2069
2066 2070 Changesets that are ancestors of the current revision, that have
2067 2071 already been grafted, or that are merges will be skipped.
2068 2072
2069 2073 If --log is specified, log messages will have a comment appended
2070 2074 of the form::
2071 2075
2072 2076 (grafted from CHANGESETHASH)
2073 2077
2074 2078 If --force is specified, revisions will be grafted even if they
2075 2079 are already ancestors of, or have been grafted to, the destination.
2076 2080 This is useful when the revisions have since been backed out.
2077 2081
2078 2082 If a graft merge results in conflicts, the graft process is
2079 2083 interrupted so that the current merge can be manually resolved.
2080 2084 Once all conflicts are addressed, the graft process can be
2081 2085 continued with the -c/--continue option.
2082 2086
2083 2087 .. note::
2084 2088
2085 2089 The -c/--continue option does not reapply earlier options, except
2086 2090 for --force.
2087 2091
2088 2092 .. container:: verbose
2089 2093
2090 2094 Examples:
2091 2095
2092 2096 - copy a single change to the stable branch and edit its description::
2093 2097
2094 2098 hg update stable
2095 2099 hg graft --edit 9393
2096 2100
2097 2101 - graft a range of changesets with one exception, updating dates::
2098 2102
2099 2103 hg graft -D "2085::2093 and not 2091"
2100 2104
2101 2105 - continue a graft after resolving conflicts::
2102 2106
2103 2107 hg graft -c
2104 2108
2105 2109 - show the source of a grafted changeset::
2106 2110
2107 2111 hg log --debug -r .
2108 2112
2109 2113 - show revisions sorted by date::
2110 2114
2111 2115 hg log -r "sort(all(), date)"
2112 2116
2113 2117 See :hg:`help revisions` for more about specifying revisions.
2114 2118
2115 2119 Returns 0 on successful completion.
2116 2120 '''
2117 2121 with repo.wlock():
2118 2122 return _dograft(ui, repo, *revs, **opts)
2119 2123
2120 2124 def _dograft(ui, repo, *revs, **opts):
2121 2125 opts = pycompat.byteskwargs(opts)
2122 2126 if revs and opts.get('rev'):
2123 2127 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2124 2128 'revision ordering!\n'))
2125 2129
2126 2130 revs = list(revs)
2127 2131 revs.extend(opts.get('rev'))
2128 2132
2129 2133 if not opts.get('user') and opts.get('currentuser'):
2130 2134 opts['user'] = ui.username()
2131 2135 if not opts.get('date') and opts.get('currentdate'):
2132 2136 opts['date'] = "%d %d" % util.makedate()
2133 2137
2134 2138 editor = cmdutil.getcommiteditor(editform='graft',
2135 2139 **pycompat.strkwargs(opts))
2136 2140
2137 2141 cont = False
2138 2142 if opts.get('continue'):
2139 2143 cont = True
2140 2144 if revs:
2141 2145 raise error.Abort(_("can't specify --continue and revisions"))
2142 2146 # read in unfinished revisions
2143 2147 try:
2144 2148 nodes = repo.vfs.read('graftstate').splitlines()
2145 2149 revs = [repo[node].rev() for node in nodes]
2146 2150 except IOError as inst:
2147 2151 if inst.errno != errno.ENOENT:
2148 2152 raise
2149 2153 cmdutil.wrongtooltocontinue(repo, _('graft'))
2150 2154 else:
2151 2155 cmdutil.checkunfinished(repo)
2152 2156 cmdutil.bailifchanged(repo)
2153 2157 if not revs:
2154 2158 raise error.Abort(_('no revisions specified'))
2155 2159 revs = scmutil.revrange(repo, revs)
2156 2160
2157 2161 skipped = set()
2158 2162 # check for merges
2159 2163 for rev in repo.revs('%ld and merge()', revs):
2160 2164 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2161 2165 skipped.add(rev)
2162 2166 revs = [r for r in revs if r not in skipped]
2163 2167 if not revs:
2164 2168 return -1
2165 2169
2166 2170 # Don't check in the --continue case, in effect retaining --force across
2167 2171 # --continues. That's because without --force, any revisions we decided to
2168 2172 # skip would have been filtered out here, so they wouldn't have made their
2169 2173 # way to the graftstate. With --force, any revisions we would have otherwise
2170 2174 # skipped would not have been filtered out, and if they hadn't been applied
2171 2175 # already, they'd have been in the graftstate.
2172 2176 if not (cont or opts.get('force')):
2173 2177 # check for ancestors of dest branch
2174 2178 crev = repo['.'].rev()
2175 2179 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2176 2180 # XXX make this lazy in the future
2177 2181 # don't mutate while iterating, create a copy
2178 2182 for rev in list(revs):
2179 2183 if rev in ancestors:
2180 2184 ui.warn(_('skipping ancestor revision %d:%s\n') %
2181 2185 (rev, repo[rev]))
2182 2186 # XXX remove on list is slow
2183 2187 revs.remove(rev)
2184 2188 if not revs:
2185 2189 return -1
2186 2190
2187 2191 # analyze revs for earlier grafts
2188 2192 ids = {}
2189 2193 for ctx in repo.set("%ld", revs):
2190 2194 ids[ctx.hex()] = ctx.rev()
2191 2195 n = ctx.extra().get('source')
2192 2196 if n:
2193 2197 ids[n] = ctx.rev()
2194 2198
2195 2199 # check ancestors for earlier grafts
2196 2200 ui.debug('scanning for duplicate grafts\n')
2197 2201
2198 2202 # The only changesets we can be sure doesn't contain grafts of any
2199 2203 # revs, are the ones that are common ancestors of *all* revs:
2200 2204 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2201 2205 ctx = repo[rev]
2202 2206 n = ctx.extra().get('source')
2203 2207 if n in ids:
2204 2208 try:
2205 2209 r = repo[n].rev()
2206 2210 except error.RepoLookupError:
2207 2211 r = None
2208 2212 if r in revs:
2209 2213 ui.warn(_('skipping revision %d:%s '
2210 2214 '(already grafted to %d:%s)\n')
2211 2215 % (r, repo[r], rev, ctx))
2212 2216 revs.remove(r)
2213 2217 elif ids[n] in revs:
2214 2218 if r is None:
2215 2219 ui.warn(_('skipping already grafted revision %d:%s '
2216 2220 '(%d:%s also has unknown origin %s)\n')
2217 2221 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2218 2222 else:
2219 2223 ui.warn(_('skipping already grafted revision %d:%s '
2220 2224 '(%d:%s also has origin %d:%s)\n')
2221 2225 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2222 2226 revs.remove(ids[n])
2223 2227 elif ctx.hex() in ids:
2224 2228 r = ids[ctx.hex()]
2225 2229 ui.warn(_('skipping already grafted revision %d:%s '
2226 2230 '(was grafted from %d:%s)\n') %
2227 2231 (r, repo[r], rev, ctx))
2228 2232 revs.remove(r)
2229 2233 if not revs:
2230 2234 return -1
2231 2235
2232 2236 for pos, ctx in enumerate(repo.set("%ld", revs)):
2233 2237 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2234 2238 ctx.description().split('\n', 1)[0])
2235 2239 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2236 2240 if names:
2237 2241 desc += ' (%s)' % ' '.join(names)
2238 2242 ui.status(_('grafting %s\n') % desc)
2239 2243 if opts.get('dry_run'):
2240 2244 continue
2241 2245
2242 2246 source = ctx.extra().get('source')
2243 2247 extra = {}
2244 2248 if source:
2245 2249 extra['source'] = source
2246 2250 extra['intermediate-source'] = ctx.hex()
2247 2251 else:
2248 2252 extra['source'] = ctx.hex()
2249 2253 user = ctx.user()
2250 2254 if opts.get('user'):
2251 2255 user = opts['user']
2252 2256 date = ctx.date()
2253 2257 if opts.get('date'):
2254 2258 date = opts['date']
2255 2259 message = ctx.description()
2256 2260 if opts.get('log'):
2257 2261 message += '\n(grafted from %s)' % ctx.hex()
2258 2262
2259 2263 # we don't merge the first commit when continuing
2260 2264 if not cont:
2261 2265 # perform the graft merge with p1(rev) as 'ancestor'
2262 2266 try:
2263 2267 # ui.forcemerge is an internal variable, do not document
2264 2268 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2265 2269 'graft')
2266 2270 stats = mergemod.graft(repo, ctx, ctx.p1(),
2267 2271 ['local', 'graft'])
2268 2272 finally:
2269 2273 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2270 2274 # report any conflicts
2271 2275 if stats and stats[3] > 0:
2272 2276 # write out state for --continue
2273 2277 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2274 2278 repo.vfs.write('graftstate', ''.join(nodelines))
2275 2279 extra = ''
2276 2280 if opts.get('user'):
2277 2281 extra += ' --user %s' % util.shellquote(opts['user'])
2278 2282 if opts.get('date'):
2279 2283 extra += ' --date %s' % util.shellquote(opts['date'])
2280 2284 if opts.get('log'):
2281 2285 extra += ' --log'
2282 2286 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2283 2287 raise error.Abort(
2284 2288 _("unresolved conflicts, can't continue"),
2285 2289 hint=hint)
2286 2290 else:
2287 2291 cont = False
2288 2292
2289 2293 # commit
2290 2294 node = repo.commit(text=message, user=user,
2291 2295 date=date, extra=extra, editor=editor)
2292 2296 if node is None:
2293 2297 ui.warn(
2294 2298 _('note: graft of %d:%s created no changes to commit\n') %
2295 2299 (ctx.rev(), ctx))
2296 2300
2297 2301 # remove state when we complete successfully
2298 2302 if not opts.get('dry_run'):
2299 2303 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
2300 2304
2301 2305 return 0
2302 2306
2303 2307 @command('grep',
2304 2308 [('0', 'print0', None, _('end fields with NUL')),
2305 2309 ('', 'all', None, _('print all revisions that match')),
2306 2310 ('a', 'text', None, _('treat all files as text')),
2307 2311 ('f', 'follow', None,
2308 2312 _('follow changeset history,'
2309 2313 ' or file history across copies and renames')),
2310 2314 ('i', 'ignore-case', None, _('ignore case when matching')),
2311 2315 ('l', 'files-with-matches', None,
2312 2316 _('print only filenames and revisions that match')),
2313 2317 ('n', 'line-number', None, _('print matching line numbers')),
2314 2318 ('r', 'rev', [],
2315 2319 _('only search files changed within revision range'), _('REV')),
2316 2320 ('u', 'user', None, _('list the author (long with -v)')),
2317 2321 ('d', 'date', None, _('list the date (short with -q)')),
2318 2322 ] + formatteropts + walkopts,
2319 2323 _('[OPTION]... PATTERN [FILE]...'),
2320 2324 inferrepo=True)
2321 2325 def grep(ui, repo, pattern, *pats, **opts):
2322 2326 """search revision history for a pattern in specified files
2323 2327
2324 2328 Search revision history for a regular expression in the specified
2325 2329 files or the entire project.
2326 2330
2327 2331 By default, grep prints the most recent revision number for each
2328 2332 file in which it finds a match. To get it to print every revision
2329 2333 that contains a change in match status ("-" for a match that becomes
2330 2334 a non-match, or "+" for a non-match that becomes a match), use the
2331 2335 --all flag.
2332 2336
2333 2337 PATTERN can be any Python (roughly Perl-compatible) regular
2334 2338 expression.
2335 2339
2336 2340 If no FILEs are specified (and -f/--follow isn't set), all files in
2337 2341 the repository are searched, including those that don't exist in the
2338 2342 current branch or have been deleted in a prior changeset.
2339 2343
2340 2344 Returns 0 if a match is found, 1 otherwise.
2341 2345 """
2342 2346 opts = pycompat.byteskwargs(opts)
2343 2347 reflags = re.M
2344 2348 if opts.get('ignore_case'):
2345 2349 reflags |= re.I
2346 2350 try:
2347 2351 regexp = util.re.compile(pattern, reflags)
2348 2352 except re.error as inst:
2349 2353 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2350 2354 return 1
2351 2355 sep, eol = ':', '\n'
2352 2356 if opts.get('print0'):
2353 2357 sep = eol = '\0'
2354 2358
2355 2359 getfile = util.lrucachefunc(repo.file)
2356 2360
2357 2361 def matchlines(body):
2358 2362 begin = 0
2359 2363 linenum = 0
2360 2364 while begin < len(body):
2361 2365 match = regexp.search(body, begin)
2362 2366 if not match:
2363 2367 break
2364 2368 mstart, mend = match.span()
2365 2369 linenum += body.count('\n', begin, mstart) + 1
2366 2370 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2367 2371 begin = body.find('\n', mend) + 1 or len(body) + 1
2368 2372 lend = begin - 1
2369 2373 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2370 2374
2371 2375 class linestate(object):
2372 2376 def __init__(self, line, linenum, colstart, colend):
2373 2377 self.line = line
2374 2378 self.linenum = linenum
2375 2379 self.colstart = colstart
2376 2380 self.colend = colend
2377 2381
2378 2382 def __hash__(self):
2379 2383 return hash((self.linenum, self.line))
2380 2384
2381 2385 def __eq__(self, other):
2382 2386 return self.line == other.line
2383 2387
2384 2388 def findpos(self):
2385 2389 """Iterate all (start, end) indices of matches"""
2386 2390 yield self.colstart, self.colend
2387 2391 p = self.colend
2388 2392 while p < len(self.line):
2389 2393 m = regexp.search(self.line, p)
2390 2394 if not m:
2391 2395 break
2392 2396 yield m.span()
2393 2397 p = m.end()
2394 2398
2395 2399 matches = {}
2396 2400 copies = {}
2397 2401 def grepbody(fn, rev, body):
2398 2402 matches[rev].setdefault(fn, [])
2399 2403 m = matches[rev][fn]
2400 2404 for lnum, cstart, cend, line in matchlines(body):
2401 2405 s = linestate(line, lnum, cstart, cend)
2402 2406 m.append(s)
2403 2407
2404 2408 def difflinestates(a, b):
2405 2409 sm = difflib.SequenceMatcher(None, a, b)
2406 2410 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2407 2411 if tag == 'insert':
2408 2412 for i in xrange(blo, bhi):
2409 2413 yield ('+', b[i])
2410 2414 elif tag == 'delete':
2411 2415 for i in xrange(alo, ahi):
2412 2416 yield ('-', a[i])
2413 2417 elif tag == 'replace':
2414 2418 for i in xrange(alo, ahi):
2415 2419 yield ('-', a[i])
2416 2420 for i in xrange(blo, bhi):
2417 2421 yield ('+', b[i])
2418 2422
2419 2423 def display(fm, fn, ctx, pstates, states):
2420 2424 rev = ctx.rev()
2421 2425 if fm.isplain():
2422 2426 formatuser = ui.shortuser
2423 2427 else:
2424 2428 formatuser = str
2425 2429 if ui.quiet:
2426 2430 datefmt = '%Y-%m-%d'
2427 2431 else:
2428 2432 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2429 2433 found = False
2430 2434 @util.cachefunc
2431 2435 def binary():
2432 2436 flog = getfile(fn)
2433 2437 return util.binary(flog.read(ctx.filenode(fn)))
2434 2438
2435 2439 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2436 2440 if opts.get('all'):
2437 2441 iter = difflinestates(pstates, states)
2438 2442 else:
2439 2443 iter = [('', l) for l in states]
2440 2444 for change, l in iter:
2441 2445 fm.startitem()
2442 2446 fm.data(node=fm.hexfunc(ctx.node()))
2443 2447 cols = [
2444 2448 ('filename', fn, True),
2445 2449 ('rev', rev, True),
2446 2450 ('linenumber', l.linenum, opts.get('line_number')),
2447 2451 ]
2448 2452 if opts.get('all'):
2449 2453 cols.append(('change', change, True))
2450 2454 cols.extend([
2451 2455 ('user', formatuser(ctx.user()), opts.get('user')),
2452 2456 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2453 2457 ])
2454 2458 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2455 2459 for name, data, cond in cols:
2456 2460 field = fieldnamemap.get(name, name)
2457 2461 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2458 2462 if cond and name != lastcol:
2459 2463 fm.plain(sep, label='grep.sep')
2460 2464 if not opts.get('files_with_matches'):
2461 2465 fm.plain(sep, label='grep.sep')
2462 2466 if not opts.get('text') and binary():
2463 2467 fm.plain(_(" Binary file matches"))
2464 2468 else:
2465 2469 displaymatches(fm.nested('texts'), l)
2466 2470 fm.plain(eol)
2467 2471 found = True
2468 2472 if opts.get('files_with_matches'):
2469 2473 break
2470 2474 return found
2471 2475
2472 2476 def displaymatches(fm, l):
2473 2477 p = 0
2474 2478 for s, e in l.findpos():
2475 2479 if p < s:
2476 2480 fm.startitem()
2477 2481 fm.write('text', '%s', l.line[p:s])
2478 2482 fm.data(matched=False)
2479 2483 fm.startitem()
2480 2484 fm.write('text', '%s', l.line[s:e], label='grep.match')
2481 2485 fm.data(matched=True)
2482 2486 p = e
2483 2487 if p < len(l.line):
2484 2488 fm.startitem()
2485 2489 fm.write('text', '%s', l.line[p:])
2486 2490 fm.data(matched=False)
2487 2491 fm.end()
2488 2492
2489 2493 skip = {}
2490 2494 revfiles = {}
2491 2495 match = scmutil.match(repo[None], pats, opts)
2492 2496 found = False
2493 2497 follow = opts.get('follow')
2494 2498
2495 2499 def prep(ctx, fns):
2496 2500 rev = ctx.rev()
2497 2501 pctx = ctx.p1()
2498 2502 parent = pctx.rev()
2499 2503 matches.setdefault(rev, {})
2500 2504 matches.setdefault(parent, {})
2501 2505 files = revfiles.setdefault(rev, [])
2502 2506 for fn in fns:
2503 2507 flog = getfile(fn)
2504 2508 try:
2505 2509 fnode = ctx.filenode(fn)
2506 2510 except error.LookupError:
2507 2511 continue
2508 2512
2509 2513 copied = flog.renamed(fnode)
2510 2514 copy = follow and copied and copied[0]
2511 2515 if copy:
2512 2516 copies.setdefault(rev, {})[fn] = copy
2513 2517 if fn in skip:
2514 2518 if copy:
2515 2519 skip[copy] = True
2516 2520 continue
2517 2521 files.append(fn)
2518 2522
2519 2523 if fn not in matches[rev]:
2520 2524 grepbody(fn, rev, flog.read(fnode))
2521 2525
2522 2526 pfn = copy or fn
2523 2527 if pfn not in matches[parent]:
2524 2528 try:
2525 2529 fnode = pctx.filenode(pfn)
2526 2530 grepbody(pfn, parent, flog.read(fnode))
2527 2531 except error.LookupError:
2528 2532 pass
2529 2533
2530 2534 ui.pager('grep')
2531 2535 fm = ui.formatter('grep', opts)
2532 2536 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2533 2537 rev = ctx.rev()
2534 2538 parent = ctx.p1().rev()
2535 2539 for fn in sorted(revfiles.get(rev, [])):
2536 2540 states = matches[rev][fn]
2537 2541 copy = copies.get(rev, {}).get(fn)
2538 2542 if fn in skip:
2539 2543 if copy:
2540 2544 skip[copy] = True
2541 2545 continue
2542 2546 pstates = matches.get(parent, {}).get(copy or fn, [])
2543 2547 if pstates or states:
2544 2548 r = display(fm, fn, ctx, pstates, states)
2545 2549 found = found or r
2546 2550 if r and not opts.get('all'):
2547 2551 skip[fn] = True
2548 2552 if copy:
2549 2553 skip[copy] = True
2550 2554 del matches[rev]
2551 2555 del revfiles[rev]
2552 2556 fm.end()
2553 2557
2554 2558 return not found
2555 2559
2556 2560 @command('heads',
2557 2561 [('r', 'rev', '',
2558 2562 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2559 2563 ('t', 'topo', False, _('show topological heads only')),
2560 2564 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2561 2565 ('c', 'closed', False, _('show normal and closed branch heads')),
2562 2566 ] + templateopts,
2563 2567 _('[-ct] [-r STARTREV] [REV]...'))
2564 2568 def heads(ui, repo, *branchrevs, **opts):
2565 2569 """show branch heads
2566 2570
2567 2571 With no arguments, show all open branch heads in the repository.
2568 2572 Branch heads are changesets that have no descendants on the
2569 2573 same branch. They are where development generally takes place and
2570 2574 are the usual targets for update and merge operations.
2571 2575
2572 2576 If one or more REVs are given, only open branch heads on the
2573 2577 branches associated with the specified changesets are shown. This
2574 2578 means that you can use :hg:`heads .` to see the heads on the
2575 2579 currently checked-out branch.
2576 2580
2577 2581 If -c/--closed is specified, also show branch heads marked closed
2578 2582 (see :hg:`commit --close-branch`).
2579 2583
2580 2584 If STARTREV is specified, only those heads that are descendants of
2581 2585 STARTREV will be displayed.
2582 2586
2583 2587 If -t/--topo is specified, named branch mechanics will be ignored and only
2584 2588 topological heads (changesets with no children) will be shown.
2585 2589
2586 2590 Returns 0 if matching heads are found, 1 if not.
2587 2591 """
2588 2592
2589 2593 opts = pycompat.byteskwargs(opts)
2590 2594 start = None
2591 2595 if 'rev' in opts:
2592 2596 start = scmutil.revsingle(repo, opts['rev'], None).node()
2593 2597
2594 2598 if opts.get('topo'):
2595 2599 heads = [repo[h] for h in repo.heads(start)]
2596 2600 else:
2597 2601 heads = []
2598 2602 for branch in repo.branchmap():
2599 2603 heads += repo.branchheads(branch, start, opts.get('closed'))
2600 2604 heads = [repo[h] for h in heads]
2601 2605
2602 2606 if branchrevs:
2603 2607 branches = set(repo[br].branch() for br in branchrevs)
2604 2608 heads = [h for h in heads if h.branch() in branches]
2605 2609
2606 2610 if opts.get('active') and branchrevs:
2607 2611 dagheads = repo.heads(start)
2608 2612 heads = [h for h in heads if h.node() in dagheads]
2609 2613
2610 2614 if branchrevs:
2611 2615 haveheads = set(h.branch() for h in heads)
2612 2616 if branches - haveheads:
2613 2617 headless = ', '.join(b for b in branches - haveheads)
2614 2618 msg = _('no open branch heads found on branches %s')
2615 2619 if opts.get('rev'):
2616 2620 msg += _(' (started at %s)') % opts['rev']
2617 2621 ui.warn((msg + '\n') % headless)
2618 2622
2619 2623 if not heads:
2620 2624 return 1
2621 2625
2622 2626 ui.pager('heads')
2623 2627 heads = sorted(heads, key=lambda x: -x.rev())
2624 2628 displayer = cmdutil.show_changeset(ui, repo, opts)
2625 2629 for ctx in heads:
2626 2630 displayer.show(ctx)
2627 2631 displayer.close()
2628 2632
2629 2633 @command('help',
2630 2634 [('e', 'extension', None, _('show only help for extensions')),
2631 2635 ('c', 'command', None, _('show only help for commands')),
2632 2636 ('k', 'keyword', None, _('show topics matching keyword')),
2633 2637 ('s', 'system', [], _('show help for specific platform(s)')),
2634 2638 ],
2635 2639 _('[-ecks] [TOPIC]'),
2636 2640 norepo=True)
2637 2641 def help_(ui, name=None, **opts):
2638 2642 """show help for a given topic or a help overview
2639 2643
2640 2644 With no arguments, print a list of commands with short help messages.
2641 2645
2642 2646 Given a topic, extension, or command name, print help for that
2643 2647 topic.
2644 2648
2645 2649 Returns 0 if successful.
2646 2650 """
2647 2651
2648 2652 keep = opts.get(r'system') or []
2649 2653 if len(keep) == 0:
2650 2654 if pycompat.sysplatform.startswith('win'):
2651 2655 keep.append('windows')
2652 2656 elif pycompat.sysplatform == 'OpenVMS':
2653 2657 keep.append('vms')
2654 2658 elif pycompat.sysplatform == 'plan9':
2655 2659 keep.append('plan9')
2656 2660 else:
2657 2661 keep.append('unix')
2658 2662 keep.append(pycompat.sysplatform.lower())
2659 2663 if ui.verbose:
2660 2664 keep.append('verbose')
2661 2665
2662 2666 commands = sys.modules[__name__]
2663 2667 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
2664 2668 ui.pager('help')
2665 2669 ui.write(formatted)
2666 2670
2667 2671
2668 2672 @command('identify|id',
2669 2673 [('r', 'rev', '',
2670 2674 _('identify the specified revision'), _('REV')),
2671 2675 ('n', 'num', None, _('show local revision number')),
2672 2676 ('i', 'id', None, _('show global revision id')),
2673 2677 ('b', 'branch', None, _('show branch')),
2674 2678 ('t', 'tags', None, _('show tags')),
2675 2679 ('B', 'bookmarks', None, _('show bookmarks')),
2676 2680 ] + remoteopts + formatteropts,
2677 2681 _('[-nibtB] [-r REV] [SOURCE]'),
2678 2682 optionalrepo=True)
2679 2683 def identify(ui, repo, source=None, rev=None,
2680 2684 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2681 2685 """identify the working directory or specified revision
2682 2686
2683 2687 Print a summary identifying the repository state at REV using one or
2684 2688 two parent hash identifiers, followed by a "+" if the working
2685 2689 directory has uncommitted changes, the branch name (if not default),
2686 2690 a list of tags, and a list of bookmarks.
2687 2691
2688 2692 When REV is not given, print a summary of the current state of the
2689 2693 repository.
2690 2694
2691 2695 Specifying a path to a repository root or Mercurial bundle will
2692 2696 cause lookup to operate on that repository/bundle.
2693 2697
2694 2698 .. container:: verbose
2695 2699
2696 2700 Examples:
2697 2701
2698 2702 - generate a build identifier for the working directory::
2699 2703
2700 2704 hg id --id > build-id.dat
2701 2705
2702 2706 - find the revision corresponding to a tag::
2703 2707
2704 2708 hg id -n -r 1.3
2705 2709
2706 2710 - check the most recent revision of a remote repository::
2707 2711
2708 2712 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2709 2713
2710 2714 See :hg:`log` for generating more information about specific revisions,
2711 2715 including full hash identifiers.
2712 2716
2713 2717 Returns 0 if successful.
2714 2718 """
2715 2719
2716 2720 opts = pycompat.byteskwargs(opts)
2717 2721 if not repo and not source:
2718 2722 raise error.Abort(_("there is no Mercurial repository here "
2719 2723 "(.hg not found)"))
2720 2724
2721 2725 if ui.debugflag:
2722 2726 hexfunc = hex
2723 2727 else:
2724 2728 hexfunc = short
2725 2729 default = not (num or id or branch or tags or bookmarks)
2726 2730 output = []
2727 2731 revs = []
2728 2732
2729 2733 if source:
2730 2734 source, branches = hg.parseurl(ui.expandpath(source))
2731 2735 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2732 2736 repo = peer.local()
2733 2737 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2734 2738
2735 2739 fm = ui.formatter('identify', opts)
2736 2740 fm.startitem()
2737 2741
2738 2742 if not repo:
2739 2743 if num or branch or tags:
2740 2744 raise error.Abort(
2741 2745 _("can't query remote revision number, branch, or tags"))
2742 2746 if not rev and revs:
2743 2747 rev = revs[0]
2744 2748 if not rev:
2745 2749 rev = "tip"
2746 2750
2747 2751 remoterev = peer.lookup(rev)
2748 2752 hexrev = hexfunc(remoterev)
2749 2753 if default or id:
2750 2754 output = [hexrev]
2751 2755 fm.data(id=hexrev)
2752 2756
2753 2757 def getbms():
2754 2758 bms = []
2755 2759
2756 2760 if 'bookmarks' in peer.listkeys('namespaces'):
2757 2761 hexremoterev = hex(remoterev)
2758 2762 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2759 2763 if bmr == hexremoterev]
2760 2764
2761 2765 return sorted(bms)
2762 2766
2763 2767 bms = getbms()
2764 2768 if bookmarks:
2765 2769 output.extend(bms)
2766 2770 elif default and not ui.quiet:
2767 2771 # multiple bookmarks for a single parent separated by '/'
2768 2772 bm = '/'.join(bms)
2769 2773 if bm:
2770 2774 output.append(bm)
2771 2775
2772 2776 fm.data(node=hex(remoterev))
2773 2777 fm.data(bookmarks=fm.formatlist(bms, name='bookmark'))
2774 2778 else:
2775 2779 ctx = scmutil.revsingle(repo, rev, None)
2776 2780
2777 2781 if ctx.rev() is None:
2778 2782 ctx = repo[None]
2779 2783 parents = ctx.parents()
2780 2784 taglist = []
2781 2785 for p in parents:
2782 2786 taglist.extend(p.tags())
2783 2787
2784 2788 dirty = ""
2785 2789 if ctx.dirty(missing=True, merge=False, branch=False):
2786 2790 dirty = '+'
2787 2791 fm.data(dirty=dirty)
2788 2792
2789 2793 hexoutput = [hexfunc(p.node()) for p in parents]
2790 2794 if default or id:
2791 2795 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
2792 2796 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
2793 2797
2794 2798 if num:
2795 2799 numoutput = ["%d" % p.rev() for p in parents]
2796 2800 output.append("%s%s" % ('+'.join(numoutput), dirty))
2797 2801
2798 2802 fn = fm.nested('parents')
2799 2803 for p in parents:
2800 2804 fn.startitem()
2801 2805 fn.data(rev=p.rev())
2802 2806 fn.data(node=p.hex())
2803 2807 fn.context(ctx=p)
2804 2808 fn.end()
2805 2809 else:
2806 2810 hexoutput = hexfunc(ctx.node())
2807 2811 if default or id:
2808 2812 output = [hexoutput]
2809 2813 fm.data(id=hexoutput)
2810 2814
2811 2815 if num:
2812 2816 output.append(pycompat.bytestr(ctx.rev()))
2813 2817 taglist = ctx.tags()
2814 2818
2815 2819 if default and not ui.quiet:
2816 2820 b = ctx.branch()
2817 2821 if b != 'default':
2818 2822 output.append("(%s)" % b)
2819 2823
2820 2824 # multiple tags for a single parent separated by '/'
2821 2825 t = '/'.join(taglist)
2822 2826 if t:
2823 2827 output.append(t)
2824 2828
2825 2829 # multiple bookmarks for a single parent separated by '/'
2826 2830 bm = '/'.join(ctx.bookmarks())
2827 2831 if bm:
2828 2832 output.append(bm)
2829 2833 else:
2830 2834 if branch:
2831 2835 output.append(ctx.branch())
2832 2836
2833 2837 if tags:
2834 2838 output.extend(taglist)
2835 2839
2836 2840 if bookmarks:
2837 2841 output.extend(ctx.bookmarks())
2838 2842
2839 2843 fm.data(node=ctx.hex())
2840 2844 fm.data(branch=ctx.branch())
2841 2845 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
2842 2846 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
2843 2847 fm.context(ctx=ctx)
2844 2848
2845 2849 fm.plain("%s\n" % ' '.join(output))
2846 2850 fm.end()
2847 2851
2848 2852 @command('import|patch',
2849 2853 [('p', 'strip', 1,
2850 2854 _('directory strip option for patch. This has the same '
2851 2855 'meaning as the corresponding patch option'), _('NUM')),
2852 2856 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2853 2857 ('e', 'edit', False, _('invoke editor on commit messages')),
2854 2858 ('f', 'force', None,
2855 2859 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2856 2860 ('', 'no-commit', None,
2857 2861 _("don't commit, just update the working directory")),
2858 2862 ('', 'bypass', None,
2859 2863 _("apply patch without touching the working directory")),
2860 2864 ('', 'partial', None,
2861 2865 _('commit even if some hunks fail')),
2862 2866 ('', 'exact', None,
2863 2867 _('abort if patch would apply lossily')),
2864 2868 ('', 'prefix', '',
2865 2869 _('apply patch to subdirectory'), _('DIR')),
2866 2870 ('', 'import-branch', None,
2867 2871 _('use any branch information in patch (implied by --exact)'))] +
2868 2872 commitopts + commitopts2 + similarityopts,
2869 2873 _('[OPTION]... PATCH...'))
2870 2874 def import_(ui, repo, patch1=None, *patches, **opts):
2871 2875 """import an ordered set of patches
2872 2876
2873 2877 Import a list of patches and commit them individually (unless
2874 2878 --no-commit is specified).
2875 2879
2876 2880 To read a patch from standard input (stdin), use "-" as the patch
2877 2881 name. If a URL is specified, the patch will be downloaded from
2878 2882 there.
2879 2883
2880 2884 Import first applies changes to the working directory (unless
2881 2885 --bypass is specified), import will abort if there are outstanding
2882 2886 changes.
2883 2887
2884 2888 Use --bypass to apply and commit patches directly to the
2885 2889 repository, without affecting the working directory. Without
2886 2890 --exact, patches will be applied on top of the working directory
2887 2891 parent revision.
2888 2892
2889 2893 You can import a patch straight from a mail message. Even patches
2890 2894 as attachments work (to use the body part, it must have type
2891 2895 text/plain or text/x-patch). From and Subject headers of email
2892 2896 message are used as default committer and commit message. All
2893 2897 text/plain body parts before first diff are added to the commit
2894 2898 message.
2895 2899
2896 2900 If the imported patch was generated by :hg:`export`, user and
2897 2901 description from patch override values from message headers and
2898 2902 body. Values given on command line with -m/--message and -u/--user
2899 2903 override these.
2900 2904
2901 2905 If --exact is specified, import will set the working directory to
2902 2906 the parent of each patch before applying it, and will abort if the
2903 2907 resulting changeset has a different ID than the one recorded in
2904 2908 the patch. This will guard against various ways that portable
2905 2909 patch formats and mail systems might fail to transfer Mercurial
2906 2910 data or metadata. See :hg:`bundle` for lossless transmission.
2907 2911
2908 2912 Use --partial to ensure a changeset will be created from the patch
2909 2913 even if some hunks fail to apply. Hunks that fail to apply will be
2910 2914 written to a <target-file>.rej file. Conflicts can then be resolved
2911 2915 by hand before :hg:`commit --amend` is run to update the created
2912 2916 changeset. This flag exists to let people import patches that
2913 2917 partially apply without losing the associated metadata (author,
2914 2918 date, description, ...).
2915 2919
2916 2920 .. note::
2917 2921
2918 2922 When no hunks apply cleanly, :hg:`import --partial` will create
2919 2923 an empty changeset, importing only the patch metadata.
2920 2924
2921 2925 With -s/--similarity, hg will attempt to discover renames and
2922 2926 copies in the patch in the same way as :hg:`addremove`.
2923 2927
2924 2928 It is possible to use external patch programs to perform the patch
2925 2929 by setting the ``ui.patch`` configuration option. For the default
2926 2930 internal tool, the fuzz can also be configured via ``patch.fuzz``.
2927 2931 See :hg:`help config` for more information about configuration
2928 2932 files and how to use these options.
2929 2933
2930 2934 See :hg:`help dates` for a list of formats valid for -d/--date.
2931 2935
2932 2936 .. container:: verbose
2933 2937
2934 2938 Examples:
2935 2939
2936 2940 - import a traditional patch from a website and detect renames::
2937 2941
2938 2942 hg import -s 80 http://example.com/bugfix.patch
2939 2943
2940 2944 - import a changeset from an hgweb server::
2941 2945
2942 2946 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
2943 2947
2944 2948 - import all the patches in an Unix-style mbox::
2945 2949
2946 2950 hg import incoming-patches.mbox
2947 2951
2948 2952 - import patches from stdin::
2949 2953
2950 2954 hg import -
2951 2955
2952 2956 - attempt to exactly restore an exported changeset (not always
2953 2957 possible)::
2954 2958
2955 2959 hg import --exact proposed-fix.patch
2956 2960
2957 2961 - use an external tool to apply a patch which is too fuzzy for
2958 2962 the default internal tool.
2959 2963
2960 2964 hg import --config ui.patch="patch --merge" fuzzy.patch
2961 2965
2962 2966 - change the default fuzzing from 2 to a less strict 7
2963 2967
2964 2968 hg import --config ui.fuzz=7 fuzz.patch
2965 2969
2966 2970 Returns 0 on success, 1 on partial success (see --partial).
2967 2971 """
2968 2972
2969 2973 opts = pycompat.byteskwargs(opts)
2970 2974 if not patch1:
2971 2975 raise error.Abort(_('need at least one patch to import'))
2972 2976
2973 2977 patches = (patch1,) + patches
2974 2978
2975 2979 date = opts.get('date')
2976 2980 if date:
2977 2981 opts['date'] = util.parsedate(date)
2978 2982
2979 2983 exact = opts.get('exact')
2980 2984 update = not opts.get('bypass')
2981 2985 if not update and opts.get('no_commit'):
2982 2986 raise error.Abort(_('cannot use --no-commit with --bypass'))
2983 2987 try:
2984 2988 sim = float(opts.get('similarity') or 0)
2985 2989 except ValueError:
2986 2990 raise error.Abort(_('similarity must be a number'))
2987 2991 if sim < 0 or sim > 100:
2988 2992 raise error.Abort(_('similarity must be between 0 and 100'))
2989 2993 if sim and not update:
2990 2994 raise error.Abort(_('cannot use --similarity with --bypass'))
2991 2995 if exact:
2992 2996 if opts.get('edit'):
2993 2997 raise error.Abort(_('cannot use --exact with --edit'))
2994 2998 if opts.get('prefix'):
2995 2999 raise error.Abort(_('cannot use --exact with --prefix'))
2996 3000
2997 3001 base = opts["base"]
2998 3002 wlock = dsguard = lock = tr = None
2999 3003 msgs = []
3000 3004 ret = 0
3001 3005
3002 3006
3003 3007 try:
3004 3008 wlock = repo.wlock()
3005 3009
3006 3010 if update:
3007 3011 cmdutil.checkunfinished(repo)
3008 3012 if (exact or not opts.get('force')):
3009 3013 cmdutil.bailifchanged(repo)
3010 3014
3011 3015 if not opts.get('no_commit'):
3012 3016 lock = repo.lock()
3013 3017 tr = repo.transaction('import')
3014 3018 else:
3015 3019 dsguard = dirstateguard.dirstateguard(repo, 'import')
3016 3020 parents = repo[None].parents()
3017 3021 for patchurl in patches:
3018 3022 if patchurl == '-':
3019 3023 ui.status(_('applying patch from stdin\n'))
3020 3024 patchfile = ui.fin
3021 3025 patchurl = 'stdin' # for error message
3022 3026 else:
3023 3027 patchurl = os.path.join(base, patchurl)
3024 3028 ui.status(_('applying %s\n') % patchurl)
3025 3029 patchfile = hg.openpath(ui, patchurl)
3026 3030
3027 3031 haspatch = False
3028 3032 for hunk in patch.split(patchfile):
3029 3033 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
3030 3034 parents, opts,
3031 3035 msgs, hg.clean)
3032 3036 if msg:
3033 3037 haspatch = True
3034 3038 ui.note(msg + '\n')
3035 3039 if update or exact:
3036 3040 parents = repo[None].parents()
3037 3041 else:
3038 3042 parents = [repo[node]]
3039 3043 if rej:
3040 3044 ui.write_err(_("patch applied partially\n"))
3041 3045 ui.write_err(_("(fix the .rej files and run "
3042 3046 "`hg commit --amend`)\n"))
3043 3047 ret = 1
3044 3048 break
3045 3049
3046 3050 if not haspatch:
3047 3051 raise error.Abort(_('%s: no diffs found') % patchurl)
3048 3052
3049 3053 if tr:
3050 3054 tr.close()
3051 3055 if msgs:
3052 3056 repo.savecommitmessage('\n* * *\n'.join(msgs))
3053 3057 if dsguard:
3054 3058 dsguard.close()
3055 3059 return ret
3056 3060 finally:
3057 3061 if tr:
3058 3062 tr.release()
3059 3063 release(lock, dsguard, wlock)
3060 3064
3061 3065 @command('incoming|in',
3062 3066 [('f', 'force', None,
3063 3067 _('run even if remote repository is unrelated')),
3064 3068 ('n', 'newest-first', None, _('show newest record first')),
3065 3069 ('', 'bundle', '',
3066 3070 _('file to store the bundles into'), _('FILE')),
3067 3071 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3068 3072 ('B', 'bookmarks', False, _("compare bookmarks")),
3069 3073 ('b', 'branch', [],
3070 3074 _('a specific branch you would like to pull'), _('BRANCH')),
3071 3075 ] + logopts + remoteopts + subrepoopts,
3072 3076 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3073 3077 def incoming(ui, repo, source="default", **opts):
3074 3078 """show new changesets found in source
3075 3079
3076 3080 Show new changesets found in the specified path/URL or the default
3077 3081 pull location. These are the changesets that would have been pulled
3078 3082 by :hg:`pull` at the time you issued this command.
3079 3083
3080 3084 See pull for valid source format details.
3081 3085
3082 3086 .. container:: verbose
3083 3087
3084 3088 With -B/--bookmarks, the result of bookmark comparison between
3085 3089 local and remote repositories is displayed. With -v/--verbose,
3086 3090 status is also displayed for each bookmark like below::
3087 3091
3088 3092 BM1 01234567890a added
3089 3093 BM2 1234567890ab advanced
3090 3094 BM3 234567890abc diverged
3091 3095 BM4 34567890abcd changed
3092 3096
3093 3097 The action taken locally when pulling depends on the
3094 3098 status of each bookmark:
3095 3099
3096 3100 :``added``: pull will create it
3097 3101 :``advanced``: pull will update it
3098 3102 :``diverged``: pull will create a divergent bookmark
3099 3103 :``changed``: result depends on remote changesets
3100 3104
3101 3105 From the point of view of pulling behavior, bookmark
3102 3106 existing only in the remote repository are treated as ``added``,
3103 3107 even if it is in fact locally deleted.
3104 3108
3105 3109 .. container:: verbose
3106 3110
3107 3111 For remote repository, using --bundle avoids downloading the
3108 3112 changesets twice if the incoming is followed by a pull.
3109 3113
3110 3114 Examples:
3111 3115
3112 3116 - show incoming changes with patches and full description::
3113 3117
3114 3118 hg incoming -vp
3115 3119
3116 3120 - show incoming changes excluding merges, store a bundle::
3117 3121
3118 3122 hg in -vpM --bundle incoming.hg
3119 3123 hg pull incoming.hg
3120 3124
3121 3125 - briefly list changes inside a bundle::
3122 3126
3123 3127 hg in changes.hg -T "{desc|firstline}\\n"
3124 3128
3125 3129 Returns 0 if there are incoming changes, 1 otherwise.
3126 3130 """
3127 3131 opts = pycompat.byteskwargs(opts)
3128 3132 if opts.get('graph'):
3129 3133 cmdutil.checkunsupportedgraphflags([], opts)
3130 3134 def display(other, chlist, displayer):
3131 3135 revdag = cmdutil.graphrevs(other, chlist, opts)
3132 3136 cmdutil.displaygraph(ui, repo, revdag, displayer,
3133 3137 graphmod.asciiedges)
3134 3138
3135 3139 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3136 3140 return 0
3137 3141
3138 3142 if opts.get('bundle') and opts.get('subrepos'):
3139 3143 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3140 3144
3141 3145 if opts.get('bookmarks'):
3142 3146 source, branches = hg.parseurl(ui.expandpath(source),
3143 3147 opts.get('branch'))
3144 3148 other = hg.peer(repo, opts, source)
3145 3149 if 'bookmarks' not in other.listkeys('namespaces'):
3146 3150 ui.warn(_("remote doesn't support bookmarks\n"))
3147 3151 return 0
3148 3152 ui.pager('incoming')
3149 3153 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3150 3154 return bookmarks.incoming(ui, repo, other)
3151 3155
3152 3156 repo._subtoppath = ui.expandpath(source)
3153 3157 try:
3154 3158 return hg.incoming(ui, repo, source, opts)
3155 3159 finally:
3156 3160 del repo._subtoppath
3157 3161
3158 3162
3159 3163 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3160 3164 norepo=True)
3161 3165 def init(ui, dest=".", **opts):
3162 3166 """create a new repository in the given directory
3163 3167
3164 3168 Initialize a new repository in the given directory. If the given
3165 3169 directory does not exist, it will be created.
3166 3170
3167 3171 If no directory is given, the current directory is used.
3168 3172
3169 3173 It is possible to specify an ``ssh://`` URL as the destination.
3170 3174 See :hg:`help urls` for more information.
3171 3175
3172 3176 Returns 0 on success.
3173 3177 """
3174 3178 opts = pycompat.byteskwargs(opts)
3175 3179 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3176 3180
3177 3181 @command('locate',
3178 3182 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3179 3183 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3180 3184 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3181 3185 ] + walkopts,
3182 3186 _('[OPTION]... [PATTERN]...'))
3183 3187 def locate(ui, repo, *pats, **opts):
3184 3188 """locate files matching specific patterns (DEPRECATED)
3185 3189
3186 3190 Print files under Mercurial control in the working directory whose
3187 3191 names match the given patterns.
3188 3192
3189 3193 By default, this command searches all directories in the working
3190 3194 directory. To search just the current directory and its
3191 3195 subdirectories, use "--include .".
3192 3196
3193 3197 If no patterns are given to match, this command prints the names
3194 3198 of all files under Mercurial control in the working directory.
3195 3199
3196 3200 If you want to feed the output of this command into the "xargs"
3197 3201 command, use the -0 option to both this command and "xargs". This
3198 3202 will avoid the problem of "xargs" treating single filenames that
3199 3203 contain whitespace as multiple filenames.
3200 3204
3201 3205 See :hg:`help files` for a more versatile command.
3202 3206
3203 3207 Returns 0 if a match is found, 1 otherwise.
3204 3208 """
3205 3209 opts = pycompat.byteskwargs(opts)
3206 3210 if opts.get('print0'):
3207 3211 end = '\0'
3208 3212 else:
3209 3213 end = '\n'
3210 3214 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3211 3215
3212 3216 ret = 1
3213 3217 ctx = repo[rev]
3214 3218 m = scmutil.match(ctx, pats, opts, default='relglob',
3215 3219 badfn=lambda x, y: False)
3216 3220
3217 3221 ui.pager('locate')
3218 3222 for abs in ctx.matches(m):
3219 3223 if opts.get('fullpath'):
3220 3224 ui.write(repo.wjoin(abs), end)
3221 3225 else:
3222 3226 ui.write(((pats and m.rel(abs)) or abs), end)
3223 3227 ret = 0
3224 3228
3225 3229 return ret
3226 3230
3227 3231 @command('^log|history',
3228 3232 [('f', 'follow', None,
3229 3233 _('follow changeset history, or file history across copies and renames')),
3230 3234 ('', 'follow-first', None,
3231 3235 _('only follow the first parent of merge changesets (DEPRECATED)')),
3232 3236 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3233 3237 ('C', 'copies', None, _('show copied files')),
3234 3238 ('k', 'keyword', [],
3235 3239 _('do case-insensitive search for a given text'), _('TEXT')),
3236 3240 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3237 3241 ('L', 'line-range', [],
3238 3242 _('follow line range of specified file (EXPERIMENTAL)'),
3239 3243 _('FILE,RANGE')),
3240 3244 ('', 'removed', None, _('include revisions where files were removed')),
3241 3245 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3242 3246 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3243 3247 ('', 'only-branch', [],
3244 3248 _('show only changesets within the given named branch (DEPRECATED)'),
3245 3249 _('BRANCH')),
3246 3250 ('b', 'branch', [],
3247 3251 _('show changesets within the given named branch'), _('BRANCH')),
3248 3252 ('P', 'prune', [],
3249 3253 _('do not display revision or any of its ancestors'), _('REV')),
3250 3254 ] + logopts + walkopts,
3251 3255 _('[OPTION]... [FILE]'),
3252 3256 inferrepo=True)
3253 3257 def log(ui, repo, *pats, **opts):
3254 3258 """show revision history of entire repository or files
3255 3259
3256 3260 Print the revision history of the specified files or the entire
3257 3261 project.
3258 3262
3259 3263 If no revision range is specified, the default is ``tip:0`` unless
3260 3264 --follow is set, in which case the working directory parent is
3261 3265 used as the starting revision.
3262 3266
3263 3267 File history is shown without following rename or copy history of
3264 3268 files. Use -f/--follow with a filename to follow history across
3265 3269 renames and copies. --follow without a filename will only show
3266 3270 ancestors or descendants of the starting revision.
3267 3271
3268 3272 By default this command prints revision number and changeset id,
3269 3273 tags, non-trivial parents, user, date and time, and a summary for
3270 3274 each commit. When the -v/--verbose switch is used, the list of
3271 3275 changed files and full commit message are shown.
3272 3276
3273 3277 With --graph the revisions are shown as an ASCII art DAG with the most
3274 3278 recent changeset at the top.
3275 3279 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
3276 3280 and '+' represents a fork where the changeset from the lines below is a
3277 3281 parent of the 'o' merge on the same line.
3278 3282 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3279 3283 of a '|' indicates one or more revisions in a path are omitted.
3280 3284
3281 3285 .. container:: verbose
3282 3286
3283 3287 Use -L/--line-range FILE,M:N options to follow the history of lines
3284 3288 from M to N in FILE. With -p/--patch only diff hunks affecting
3285 3289 specified line range will be shown. This option requires --follow;
3286 3290 it can be specified multiple times. Currently, this option is not
3287 3291 compatible with --graph. This option is experimental.
3288 3292
3289 3293 .. note::
3290 3294
3291 3295 :hg:`log --patch` may generate unexpected diff output for merge
3292 3296 changesets, as it will only compare the merge changeset against
3293 3297 its first parent. Also, only files different from BOTH parents
3294 3298 will appear in files:.
3295 3299
3296 3300 .. note::
3297 3301
3298 3302 For performance reasons, :hg:`log FILE` may omit duplicate changes
3299 3303 made on branches and will not show removals or mode changes. To
3300 3304 see all such changes, use the --removed switch.
3301 3305
3302 3306 .. container:: verbose
3303 3307
3304 3308 .. note::
3305 3309
3306 3310 The history resulting from -L/--line-range options depends on diff
3307 3311 options; for instance if white-spaces are ignored, respective changes
3308 3312 with only white-spaces in specified line range will not be listed.
3309 3313
3310 3314 .. container:: verbose
3311 3315
3312 3316 Some examples:
3313 3317
3314 3318 - changesets with full descriptions and file lists::
3315 3319
3316 3320 hg log -v
3317 3321
3318 3322 - changesets ancestral to the working directory::
3319 3323
3320 3324 hg log -f
3321 3325
3322 3326 - last 10 commits on the current branch::
3323 3327
3324 3328 hg log -l 10 -b .
3325 3329
3326 3330 - changesets showing all modifications of a file, including removals::
3327 3331
3328 3332 hg log --removed file.c
3329 3333
3330 3334 - all changesets that touch a directory, with diffs, excluding merges::
3331 3335
3332 3336 hg log -Mp lib/
3333 3337
3334 3338 - all revision numbers that match a keyword::
3335 3339
3336 3340 hg log -k bug --template "{rev}\\n"
3337 3341
3338 3342 - the full hash identifier of the working directory parent::
3339 3343
3340 3344 hg log -r . --template "{node}\\n"
3341 3345
3342 3346 - list available log templates::
3343 3347
3344 3348 hg log -T list
3345 3349
3346 3350 - check if a given changeset is included in a tagged release::
3347 3351
3348 3352 hg log -r "a21ccf and ancestor(1.9)"
3349 3353
3350 3354 - find all changesets by some user in a date range::
3351 3355
3352 3356 hg log -k alice -d "may 2008 to jul 2008"
3353 3357
3354 3358 - summary of all changesets after the last tag::
3355 3359
3356 3360 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3357 3361
3358 3362 - changesets touching lines 13 to 23 for file.c::
3359 3363
3360 3364 hg log -L file.c,13:23
3361 3365
3362 3366 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3363 3367 main.c with patch::
3364 3368
3365 3369 hg log -L file.c,13:23 -L main.c,2:6 -p
3366 3370
3367 3371 See :hg:`help dates` for a list of formats valid for -d/--date.
3368 3372
3369 3373 See :hg:`help revisions` for more about specifying and ordering
3370 3374 revisions.
3371 3375
3372 3376 See :hg:`help templates` for more about pre-packaged styles and
3373 3377 specifying custom templates. The default template used by the log
3374 3378 command can be customized via the ``ui.logtemplate`` configuration
3375 3379 setting.
3376 3380
3377 3381 Returns 0 on success.
3378 3382
3379 3383 """
3380 3384 opts = pycompat.byteskwargs(opts)
3381 3385 linerange = opts.get('line_range')
3382 3386
3383 3387 if linerange and not opts.get('follow'):
3384 3388 raise error.Abort(_('--line-range requires --follow'))
3385 3389
3386 3390 if linerange and pats:
3387 3391 raise error.Abort(
3388 3392 _('FILE arguments are not compatible with --line-range option')
3389 3393 )
3390 3394
3391 3395 if opts.get('follow') and opts.get('rev'):
3392 3396 opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))]
3393 3397 del opts['follow']
3394 3398
3395 3399 if opts.get('graph'):
3396 3400 if linerange:
3397 3401 raise error.Abort(_('graph not supported with line range patterns'))
3398 3402 return cmdutil.graphlog(ui, repo, pats, opts)
3399 3403
3400 3404 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
3401 3405 hunksfilter = None
3402 3406
3403 3407 if linerange:
3404 3408 revs, lrfilematcher, hunksfilter = cmdutil.getloglinerangerevs(
3405 3409 repo, revs, opts)
3406 3410
3407 3411 if filematcher is not None and lrfilematcher is not None:
3408 3412 basefilematcher = filematcher
3409 3413
3410 3414 def filematcher(rev):
3411 3415 files = (basefilematcher(rev).files()
3412 3416 + lrfilematcher(rev).files())
3413 3417 return scmutil.matchfiles(repo, files)
3414 3418
3415 3419 elif filematcher is None:
3416 3420 filematcher = lrfilematcher
3417 3421
3418 3422 limit = cmdutil.loglimit(opts)
3419 3423 count = 0
3420 3424
3421 3425 getrenamed = None
3422 3426 if opts.get('copies'):
3423 3427 endrev = None
3424 3428 if opts.get('rev'):
3425 3429 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3426 3430 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3427 3431
3428 3432 ui.pager('log')
3429 3433 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3430 3434 for rev in revs:
3431 3435 if count == limit:
3432 3436 break
3433 3437 ctx = repo[rev]
3434 3438 copies = None
3435 3439 if getrenamed is not None and rev:
3436 3440 copies = []
3437 3441 for fn in ctx.files():
3438 3442 rename = getrenamed(fn, rev)
3439 3443 if rename:
3440 3444 copies.append((fn, rename[0]))
3441 3445 if filematcher:
3442 3446 revmatchfn = filematcher(ctx.rev())
3443 3447 else:
3444 3448 revmatchfn = None
3445 3449 if hunksfilter:
3446 3450 revhunksfilter = hunksfilter(rev)
3447 3451 else:
3448 3452 revhunksfilter = None
3449 3453 displayer.show(ctx, copies=copies, matchfn=revmatchfn,
3450 3454 hunksfilterfn=revhunksfilter)
3451 3455 if displayer.flush(ctx):
3452 3456 count += 1
3453 3457
3454 3458 displayer.close()
3455 3459
3456 3460 @command('manifest',
3457 3461 [('r', 'rev', '', _('revision to display'), _('REV')),
3458 3462 ('', 'all', False, _("list files from all revisions"))]
3459 3463 + formatteropts,
3460 3464 _('[-r REV]'))
3461 3465 def manifest(ui, repo, node=None, rev=None, **opts):
3462 3466 """output the current or given revision of the project manifest
3463 3467
3464 3468 Print a list of version controlled files for the given revision.
3465 3469 If no revision is given, the first parent of the working directory
3466 3470 is used, or the null revision if no revision is checked out.
3467 3471
3468 3472 With -v, print file permissions, symlink and executable bits.
3469 3473 With --debug, print file revision hashes.
3470 3474
3471 3475 If option --all is specified, the list of all files from all revisions
3472 3476 is printed. This includes deleted and renamed files.
3473 3477
3474 3478 Returns 0 on success.
3475 3479 """
3476 3480 opts = pycompat.byteskwargs(opts)
3477 3481 fm = ui.formatter('manifest', opts)
3478 3482
3479 3483 if opts.get('all'):
3480 3484 if rev or node:
3481 3485 raise error.Abort(_("can't specify a revision with --all"))
3482 3486
3483 3487 res = []
3484 3488 prefix = "data/"
3485 3489 suffix = ".i"
3486 3490 plen = len(prefix)
3487 3491 slen = len(suffix)
3488 3492 with repo.lock():
3489 3493 for fn, b, size in repo.store.datafiles():
3490 3494 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3491 3495 res.append(fn[plen:-slen])
3492 3496 ui.pager('manifest')
3493 3497 for f in res:
3494 3498 fm.startitem()
3495 3499 fm.write("path", '%s\n', f)
3496 3500 fm.end()
3497 3501 return
3498 3502
3499 3503 if rev and node:
3500 3504 raise error.Abort(_("please specify just one revision"))
3501 3505
3502 3506 if not node:
3503 3507 node = rev
3504 3508
3505 3509 char = {'l': '@', 'x': '*', '': ''}
3506 3510 mode = {'l': '644', 'x': '755', '': '644'}
3507 3511 ctx = scmutil.revsingle(repo, node)
3508 3512 mf = ctx.manifest()
3509 3513 ui.pager('manifest')
3510 3514 for f in ctx:
3511 3515 fm.startitem()
3512 3516 fl = ctx[f].flags()
3513 3517 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3514 3518 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3515 3519 fm.write('path', '%s\n', f)
3516 3520 fm.end()
3517 3521
3518 3522 @command('^merge',
3519 3523 [('f', 'force', None,
3520 3524 _('force a merge including outstanding changes (DEPRECATED)')),
3521 3525 ('r', 'rev', '', _('revision to merge'), _('REV')),
3522 3526 ('P', 'preview', None,
3523 3527 _('review revisions to merge (no merge is performed)'))
3524 3528 ] + mergetoolopts,
3525 3529 _('[-P] [[-r] REV]'))
3526 3530 def merge(ui, repo, node=None, **opts):
3527 3531 """merge another revision into working directory
3528 3532
3529 3533 The current working directory is updated with all changes made in
3530 3534 the requested revision since the last common predecessor revision.
3531 3535
3532 3536 Files that changed between either parent are marked as changed for
3533 3537 the next commit and a commit must be performed before any further
3534 3538 updates to the repository are allowed. The next commit will have
3535 3539 two parents.
3536 3540
3537 3541 ``--tool`` can be used to specify the merge tool used for file
3538 3542 merges. It overrides the HGMERGE environment variable and your
3539 3543 configuration files. See :hg:`help merge-tools` for options.
3540 3544
3541 3545 If no revision is specified, the working directory's parent is a
3542 3546 head revision, and the current branch contains exactly one other
3543 3547 head, the other head is merged with by default. Otherwise, an
3544 3548 explicit revision with which to merge with must be provided.
3545 3549
3546 3550 See :hg:`help resolve` for information on handling file conflicts.
3547 3551
3548 3552 To undo an uncommitted merge, use :hg:`update --clean .` which
3549 3553 will check out a clean copy of the original merge parent, losing
3550 3554 all changes.
3551 3555
3552 3556 Returns 0 on success, 1 if there are unresolved files.
3553 3557 """
3554 3558
3555 3559 opts = pycompat.byteskwargs(opts)
3556 3560 if opts.get('rev') and node:
3557 3561 raise error.Abort(_("please specify just one revision"))
3558 3562 if not node:
3559 3563 node = opts.get('rev')
3560 3564
3561 3565 if node:
3562 3566 node = scmutil.revsingle(repo, node).node()
3563 3567
3564 3568 if not node:
3565 3569 node = repo[destutil.destmerge(repo)].node()
3566 3570
3567 3571 if opts.get('preview'):
3568 3572 # find nodes that are ancestors of p2 but not of p1
3569 3573 p1 = repo.lookup('.')
3570 3574 p2 = repo.lookup(node)
3571 3575 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3572 3576
3573 3577 displayer = cmdutil.show_changeset(ui, repo, opts)
3574 3578 for node in nodes:
3575 3579 displayer.show(repo[node])
3576 3580 displayer.close()
3577 3581 return 0
3578 3582
3579 3583 try:
3580 3584 # ui.forcemerge is an internal variable, do not document
3581 3585 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3582 3586 force = opts.get('force')
3583 3587 labels = ['working copy', 'merge rev']
3584 3588 return hg.merge(repo, node, force=force, mergeforce=force,
3585 3589 labels=labels)
3586 3590 finally:
3587 3591 ui.setconfig('ui', 'forcemerge', '', 'merge')
3588 3592
3589 3593 @command('outgoing|out',
3590 3594 [('f', 'force', None, _('run even when the destination is unrelated')),
3591 3595 ('r', 'rev', [],
3592 3596 _('a changeset intended to be included in the destination'), _('REV')),
3593 3597 ('n', 'newest-first', None, _('show newest record first')),
3594 3598 ('B', 'bookmarks', False, _('compare bookmarks')),
3595 3599 ('b', 'branch', [], _('a specific branch you would like to push'),
3596 3600 _('BRANCH')),
3597 3601 ] + logopts + remoteopts + subrepoopts,
3598 3602 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3599 3603 def outgoing(ui, repo, dest=None, **opts):
3600 3604 """show changesets not found in the destination
3601 3605
3602 3606 Show changesets not found in the specified destination repository
3603 3607 or the default push location. These are the changesets that would
3604 3608 be pushed if a push was requested.
3605 3609
3606 3610 See pull for details of valid destination formats.
3607 3611
3608 3612 .. container:: verbose
3609 3613
3610 3614 With -B/--bookmarks, the result of bookmark comparison between
3611 3615 local and remote repositories is displayed. With -v/--verbose,
3612 3616 status is also displayed for each bookmark like below::
3613 3617
3614 3618 BM1 01234567890a added
3615 3619 BM2 deleted
3616 3620 BM3 234567890abc advanced
3617 3621 BM4 34567890abcd diverged
3618 3622 BM5 4567890abcde changed
3619 3623
3620 3624 The action taken when pushing depends on the
3621 3625 status of each bookmark:
3622 3626
3623 3627 :``added``: push with ``-B`` will create it
3624 3628 :``deleted``: push with ``-B`` will delete it
3625 3629 :``advanced``: push will update it
3626 3630 :``diverged``: push with ``-B`` will update it
3627 3631 :``changed``: push with ``-B`` will update it
3628 3632
3629 3633 From the point of view of pushing behavior, bookmarks
3630 3634 existing only in the remote repository are treated as
3631 3635 ``deleted``, even if it is in fact added remotely.
3632 3636
3633 3637 Returns 0 if there are outgoing changes, 1 otherwise.
3634 3638 """
3635 3639 opts = pycompat.byteskwargs(opts)
3636 3640 if opts.get('graph'):
3637 3641 cmdutil.checkunsupportedgraphflags([], opts)
3638 3642 o, other = hg._outgoing(ui, repo, dest, opts)
3639 3643 if not o:
3640 3644 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3641 3645 return
3642 3646
3643 3647 revdag = cmdutil.graphrevs(repo, o, opts)
3644 3648 ui.pager('outgoing')
3645 3649 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3646 3650 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
3647 3651 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3648 3652 return 0
3649 3653
3650 3654 if opts.get('bookmarks'):
3651 3655 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3652 3656 dest, branches = hg.parseurl(dest, opts.get('branch'))
3653 3657 other = hg.peer(repo, opts, dest)
3654 3658 if 'bookmarks' not in other.listkeys('namespaces'):
3655 3659 ui.warn(_("remote doesn't support bookmarks\n"))
3656 3660 return 0
3657 3661 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3658 3662 ui.pager('outgoing')
3659 3663 return bookmarks.outgoing(ui, repo, other)
3660 3664
3661 3665 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3662 3666 try:
3663 3667 return hg.outgoing(ui, repo, dest, opts)
3664 3668 finally:
3665 3669 del repo._subtoppath
3666 3670
3667 3671 @command('parents',
3668 3672 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3669 3673 ] + templateopts,
3670 3674 _('[-r REV] [FILE]'),
3671 3675 inferrepo=True)
3672 3676 def parents(ui, repo, file_=None, **opts):
3673 3677 """show the parents of the working directory or revision (DEPRECATED)
3674 3678
3675 3679 Print the working directory's parent revisions. If a revision is
3676 3680 given via -r/--rev, the parent of that revision will be printed.
3677 3681 If a file argument is given, the revision in which the file was
3678 3682 last changed (before the working directory revision or the
3679 3683 argument to --rev if given) is printed.
3680 3684
3681 3685 This command is equivalent to::
3682 3686
3683 3687 hg log -r "p1()+p2()" or
3684 3688 hg log -r "p1(REV)+p2(REV)" or
3685 3689 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3686 3690 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3687 3691
3688 3692 See :hg:`summary` and :hg:`help revsets` for related information.
3689 3693
3690 3694 Returns 0 on success.
3691 3695 """
3692 3696
3693 3697 opts = pycompat.byteskwargs(opts)
3694 3698 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3695 3699
3696 3700 if file_:
3697 3701 m = scmutil.match(ctx, (file_,), opts)
3698 3702 if m.anypats() or len(m.files()) != 1:
3699 3703 raise error.Abort(_('can only specify an explicit filename'))
3700 3704 file_ = m.files()[0]
3701 3705 filenodes = []
3702 3706 for cp in ctx.parents():
3703 3707 if not cp:
3704 3708 continue
3705 3709 try:
3706 3710 filenodes.append(cp.filenode(file_))
3707 3711 except error.LookupError:
3708 3712 pass
3709 3713 if not filenodes:
3710 3714 raise error.Abort(_("'%s' not found in manifest!") % file_)
3711 3715 p = []
3712 3716 for fn in filenodes:
3713 3717 fctx = repo.filectx(file_, fileid=fn)
3714 3718 p.append(fctx.node())
3715 3719 else:
3716 3720 p = [cp.node() for cp in ctx.parents()]
3717 3721
3718 3722 displayer = cmdutil.show_changeset(ui, repo, opts)
3719 3723 for n in p:
3720 3724 if n != nullid:
3721 3725 displayer.show(repo[n])
3722 3726 displayer.close()
3723 3727
3724 3728 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True)
3725 3729 def paths(ui, repo, search=None, **opts):
3726 3730 """show aliases for remote repositories
3727 3731
3728 3732 Show definition of symbolic path name NAME. If no name is given,
3729 3733 show definition of all available names.
3730 3734
3731 3735 Option -q/--quiet suppresses all output when searching for NAME
3732 3736 and shows only the path names when listing all definitions.
3733 3737
3734 3738 Path names are defined in the [paths] section of your
3735 3739 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3736 3740 repository, ``.hg/hgrc`` is used, too.
3737 3741
3738 3742 The path names ``default`` and ``default-push`` have a special
3739 3743 meaning. When performing a push or pull operation, they are used
3740 3744 as fallbacks if no location is specified on the command-line.
3741 3745 When ``default-push`` is set, it will be used for push and
3742 3746 ``default`` will be used for pull; otherwise ``default`` is used
3743 3747 as the fallback for both. When cloning a repository, the clone
3744 3748 source is written as ``default`` in ``.hg/hgrc``.
3745 3749
3746 3750 .. note::
3747 3751
3748 3752 ``default`` and ``default-push`` apply to all inbound (e.g.
3749 3753 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3750 3754 and :hg:`bundle`) operations.
3751 3755
3752 3756 See :hg:`help urls` for more information.
3753 3757
3754 3758 Returns 0 on success.
3755 3759 """
3756 3760
3757 3761 opts = pycompat.byteskwargs(opts)
3758 3762 ui.pager('paths')
3759 3763 if search:
3760 3764 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3761 3765 if name == search]
3762 3766 else:
3763 3767 pathitems = sorted(ui.paths.iteritems())
3764 3768
3765 3769 fm = ui.formatter('paths', opts)
3766 3770 if fm.isplain():
3767 3771 hidepassword = util.hidepassword
3768 3772 else:
3769 3773 hidepassword = str
3770 3774 if ui.quiet:
3771 3775 namefmt = '%s\n'
3772 3776 else:
3773 3777 namefmt = '%s = '
3774 3778 showsubopts = not search and not ui.quiet
3775 3779
3776 3780 for name, path in pathitems:
3777 3781 fm.startitem()
3778 3782 fm.condwrite(not search, 'name', namefmt, name)
3779 3783 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3780 3784 for subopt, value in sorted(path.suboptions.items()):
3781 3785 assert subopt not in ('name', 'url')
3782 3786 if showsubopts:
3783 3787 fm.plain('%s:%s = ' % (name, subopt))
3784 3788 fm.condwrite(showsubopts, subopt, '%s\n', value)
3785 3789
3786 3790 fm.end()
3787 3791
3788 3792 if search and not pathitems:
3789 3793 if not ui.quiet:
3790 3794 ui.warn(_("not found!\n"))
3791 3795 return 1
3792 3796 else:
3793 3797 return 0
3794 3798
3795 3799 @command('phase',
3796 3800 [('p', 'public', False, _('set changeset phase to public')),
3797 3801 ('d', 'draft', False, _('set changeset phase to draft')),
3798 3802 ('s', 'secret', False, _('set changeset phase to secret')),
3799 3803 ('f', 'force', False, _('allow to move boundary backward')),
3800 3804 ('r', 'rev', [], _('target revision'), _('REV')),
3801 3805 ],
3802 3806 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3803 3807 def phase(ui, repo, *revs, **opts):
3804 3808 """set or show the current phase name
3805 3809
3806 3810 With no argument, show the phase name of the current revision(s).
3807 3811
3808 3812 With one of -p/--public, -d/--draft or -s/--secret, change the
3809 3813 phase value of the specified revisions.
3810 3814
3811 3815 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
3812 3816 lower phase to a higher phase. Phases are ordered as follows::
3813 3817
3814 3818 public < draft < secret
3815 3819
3816 3820 Returns 0 on success, 1 if some phases could not be changed.
3817 3821
3818 3822 (For more information about the phases concept, see :hg:`help phases`.)
3819 3823 """
3820 3824 opts = pycompat.byteskwargs(opts)
3821 3825 # search for a unique phase argument
3822 3826 targetphase = None
3823 3827 for idx, name in enumerate(phases.phasenames):
3824 3828 if opts[name]:
3825 3829 if targetphase is not None:
3826 3830 raise error.Abort(_('only one phase can be specified'))
3827 3831 targetphase = idx
3828 3832
3829 3833 # look for specified revision
3830 3834 revs = list(revs)
3831 3835 revs.extend(opts['rev'])
3832 3836 if not revs:
3833 3837 # display both parents as the second parent phase can influence
3834 3838 # the phase of a merge commit
3835 3839 revs = [c.rev() for c in repo[None].parents()]
3836 3840
3837 3841 revs = scmutil.revrange(repo, revs)
3838 3842
3839 3843 lock = None
3840 3844 ret = 0
3841 3845 if targetphase is None:
3842 3846 # display
3843 3847 for r in revs:
3844 3848 ctx = repo[r]
3845 3849 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3846 3850 else:
3847 3851 tr = None
3848 3852 lock = repo.lock()
3849 3853 try:
3850 3854 tr = repo.transaction("phase")
3851 3855 # set phase
3852 3856 if not revs:
3853 3857 raise error.Abort(_('empty revision set'))
3854 3858 nodes = [repo[r].node() for r in revs]
3855 3859 # moving revision from public to draft may hide them
3856 3860 # We have to check result on an unfiltered repository
3857 3861 unfi = repo.unfiltered()
3858 3862 getphase = unfi._phasecache.phase
3859 3863 olddata = [getphase(unfi, r) for r in unfi]
3860 3864 phases.advanceboundary(repo, tr, targetphase, nodes)
3861 3865 if opts['force']:
3862 3866 phases.retractboundary(repo, tr, targetphase, nodes)
3863 3867 tr.close()
3864 3868 finally:
3865 3869 if tr is not None:
3866 3870 tr.release()
3867 3871 lock.release()
3868 3872 getphase = unfi._phasecache.phase
3869 3873 newdata = [getphase(unfi, r) for r in unfi]
3870 3874 changes = sum(newdata[r] != olddata[r] for r in unfi)
3871 3875 cl = unfi.changelog
3872 3876 rejected = [n for n in nodes
3873 3877 if newdata[cl.rev(n)] < targetphase]
3874 3878 if rejected:
3875 3879 ui.warn(_('cannot move %i changesets to a higher '
3876 3880 'phase, use --force\n') % len(rejected))
3877 3881 ret = 1
3878 3882 if changes:
3879 3883 msg = _('phase changed for %i changesets\n') % changes
3880 3884 if ret:
3881 3885 ui.status(msg)
3882 3886 else:
3883 3887 ui.note(msg)
3884 3888 else:
3885 3889 ui.warn(_('no phases changed\n'))
3886 3890 return ret
3887 3891
3888 3892 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3889 3893 """Run after a changegroup has been added via pull/unbundle
3890 3894
3891 3895 This takes arguments below:
3892 3896
3893 3897 :modheads: change of heads by pull/unbundle
3894 3898 :optupdate: updating working directory is needed or not
3895 3899 :checkout: update destination revision (or None to default destination)
3896 3900 :brev: a name, which might be a bookmark to be activated after updating
3897 3901 """
3898 3902 if modheads == 0:
3899 3903 return
3900 3904 if optupdate:
3901 3905 try:
3902 3906 return hg.updatetotally(ui, repo, checkout, brev)
3903 3907 except error.UpdateAbort as inst:
3904 3908 msg = _("not updating: %s") % str(inst)
3905 3909 hint = inst.hint
3906 3910 raise error.UpdateAbort(msg, hint=hint)
3907 3911 if modheads > 1:
3908 3912 currentbranchheads = len(repo.branchheads())
3909 3913 if currentbranchheads == modheads:
3910 3914 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3911 3915 elif currentbranchheads > 1:
3912 3916 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3913 3917 "merge)\n"))
3914 3918 else:
3915 3919 ui.status(_("(run 'hg heads' to see heads)\n"))
3916 3920 elif not ui.configbool('commands', 'update.requiredest'):
3917 3921 ui.status(_("(run 'hg update' to get a working copy)\n"))
3918 3922
3919 3923 @command('^pull',
3920 3924 [('u', 'update', None,
3921 3925 _('update to new branch head if changesets were pulled')),
3922 3926 ('f', 'force', None, _('run even when remote repository is unrelated')),
3923 3927 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3924 3928 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3925 3929 ('b', 'branch', [], _('a specific branch you would like to pull'),
3926 3930 _('BRANCH')),
3927 3931 ] + remoteopts,
3928 3932 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3929 3933 def pull(ui, repo, source="default", **opts):
3930 3934 """pull changes from the specified source
3931 3935
3932 3936 Pull changes from a remote repository to a local one.
3933 3937
3934 3938 This finds all changes from the repository at the specified path
3935 3939 or URL and adds them to a local repository (the current one unless
3936 3940 -R is specified). By default, this does not update the copy of the
3937 3941 project in the working directory.
3938 3942
3939 3943 Use :hg:`incoming` if you want to see what would have been added
3940 3944 by a pull at the time you issued this command. If you then decide
3941 3945 to add those changes to the repository, you should use :hg:`pull
3942 3946 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3943 3947
3944 3948 If SOURCE is omitted, the 'default' path will be used.
3945 3949 See :hg:`help urls` for more information.
3946 3950
3947 3951 Specifying bookmark as ``.`` is equivalent to specifying the active
3948 3952 bookmark's name.
3949 3953
3950 3954 Returns 0 on success, 1 if an update had unresolved files.
3951 3955 """
3952 3956
3953 3957 opts = pycompat.byteskwargs(opts)
3954 3958 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
3955 3959 msg = _('update destination required by configuration')
3956 3960 hint = _('use hg pull followed by hg update DEST')
3957 3961 raise error.Abort(msg, hint=hint)
3958 3962
3959 3963 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3960 3964 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3961 3965 other = hg.peer(repo, opts, source)
3962 3966 try:
3963 3967 revs, checkout = hg.addbranchrevs(repo, other, branches,
3964 3968 opts.get('rev'))
3965 3969
3966 3970
3967 3971 pullopargs = {}
3968 3972 if opts.get('bookmark'):
3969 3973 if not revs:
3970 3974 revs = []
3971 3975 # The list of bookmark used here is not the one used to actually
3972 3976 # update the bookmark name. This can result in the revision pulled
3973 3977 # not ending up with the name of the bookmark because of a race
3974 3978 # condition on the server. (See issue 4689 for details)
3975 3979 remotebookmarks = other.listkeys('bookmarks')
3976 3980 pullopargs['remotebookmarks'] = remotebookmarks
3977 3981 for b in opts['bookmark']:
3978 3982 b = repo._bookmarks.expandname(b)
3979 3983 if b not in remotebookmarks:
3980 3984 raise error.Abort(_('remote bookmark %s not found!') % b)
3981 3985 revs.append(remotebookmarks[b])
3982 3986
3983 3987 if revs:
3984 3988 try:
3985 3989 # When 'rev' is a bookmark name, we cannot guarantee that it
3986 3990 # will be updated with that name because of a race condition
3987 3991 # server side. (See issue 4689 for details)
3988 3992 oldrevs = revs
3989 3993 revs = [] # actually, nodes
3990 3994 for r in oldrevs:
3991 3995 node = other.lookup(r)
3992 3996 revs.append(node)
3993 3997 if r == checkout:
3994 3998 checkout = node
3995 3999 except error.CapabilityError:
3996 4000 err = _("other repository doesn't support revision lookup, "
3997 4001 "so a rev cannot be specified.")
3998 4002 raise error.Abort(err)
3999 4003
4000 4004 pullopargs.update(opts.get('opargs', {}))
4001 4005 modheads = exchange.pull(repo, other, heads=revs,
4002 4006 force=opts.get('force'),
4003 4007 bookmarks=opts.get('bookmark', ()),
4004 4008 opargs=pullopargs).cgresult
4005 4009
4006 4010 # brev is a name, which might be a bookmark to be activated at
4007 4011 # the end of the update. In other words, it is an explicit
4008 4012 # destination of the update
4009 4013 brev = None
4010 4014
4011 4015 if checkout:
4012 4016 checkout = str(repo.changelog.rev(checkout))
4013 4017
4014 4018 # order below depends on implementation of
4015 4019 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4016 4020 # because 'checkout' is determined without it.
4017 4021 if opts.get('rev'):
4018 4022 brev = opts['rev'][0]
4019 4023 elif opts.get('branch'):
4020 4024 brev = opts['branch'][0]
4021 4025 else:
4022 4026 brev = branches[0]
4023 4027 repo._subtoppath = source
4024 4028 try:
4025 4029 ret = postincoming(ui, repo, modheads, opts.get('update'),
4026 4030 checkout, brev)
4027 4031
4028 4032 finally:
4029 4033 del repo._subtoppath
4030 4034
4031 4035 finally:
4032 4036 other.close()
4033 4037 return ret
4034 4038
4035 4039 @command('^push',
4036 4040 [('f', 'force', None, _('force push')),
4037 4041 ('r', 'rev', [],
4038 4042 _('a changeset intended to be included in the destination'),
4039 4043 _('REV')),
4040 4044 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4041 4045 ('b', 'branch', [],
4042 4046 _('a specific branch you would like to push'), _('BRANCH')),
4043 4047 ('', 'new-branch', False, _('allow pushing a new branch')),
4044 4048 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4045 4049 ] + remoteopts,
4046 4050 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4047 4051 def push(ui, repo, dest=None, **opts):
4048 4052 """push changes to the specified destination
4049 4053
4050 4054 Push changesets from the local repository to the specified
4051 4055 destination.
4052 4056
4053 4057 This operation is symmetrical to pull: it is identical to a pull
4054 4058 in the destination repository from the current one.
4055 4059
4056 4060 By default, push will not allow creation of new heads at the
4057 4061 destination, since multiple heads would make it unclear which head
4058 4062 to use. In this situation, it is recommended to pull and merge
4059 4063 before pushing.
4060 4064
4061 4065 Use --new-branch if you want to allow push to create a new named
4062 4066 branch that is not present at the destination. This allows you to
4063 4067 only create a new branch without forcing other changes.
4064 4068
4065 4069 .. note::
4066 4070
4067 4071 Extra care should be taken with the -f/--force option,
4068 4072 which will push all new heads on all branches, an action which will
4069 4073 almost always cause confusion for collaborators.
4070 4074
4071 4075 If -r/--rev is used, the specified revision and all its ancestors
4072 4076 will be pushed to the remote repository.
4073 4077
4074 4078 If -B/--bookmark is used, the specified bookmarked revision, its
4075 4079 ancestors, and the bookmark will be pushed to the remote
4076 4080 repository. Specifying ``.`` is equivalent to specifying the active
4077 4081 bookmark's name.
4078 4082
4079 4083 Please see :hg:`help urls` for important details about ``ssh://``
4080 4084 URLs. If DESTINATION is omitted, a default path will be used.
4081 4085
4082 4086 .. container:: verbose
4083 4087
4084 4088 The --pushvars option sends strings to the server that become
4085 4089 environment variables prepended with ``HG_USERVAR_``. For example,
4086 4090 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4087 4091 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4088 4092
4089 4093 pushvars can provide for user-overridable hooks as well as set debug
4090 4094 levels. One example is having a hook that blocks commits containing
4091 4095 conflict markers, but enables the user to override the hook if the file
4092 4096 is using conflict markers for testing purposes or the file format has
4093 4097 strings that look like conflict markers.
4094 4098
4095 4099 By default, servers will ignore `--pushvars`. To enable it add the
4096 4100 following to your configuration file::
4097 4101
4098 4102 [push]
4099 4103 pushvars.server = true
4100 4104
4101 4105 Returns 0 if push was successful, 1 if nothing to push.
4102 4106 """
4103 4107
4104 4108 opts = pycompat.byteskwargs(opts)
4105 4109 if opts.get('bookmark'):
4106 4110 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4107 4111 for b in opts['bookmark']:
4108 4112 # translate -B options to -r so changesets get pushed
4109 4113 b = repo._bookmarks.expandname(b)
4110 4114 if b in repo._bookmarks:
4111 4115 opts.setdefault('rev', []).append(b)
4112 4116 else:
4113 4117 # if we try to push a deleted bookmark, translate it to null
4114 4118 # this lets simultaneous -r, -b options continue working
4115 4119 opts.setdefault('rev', []).append("null")
4116 4120
4117 4121 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4118 4122 if not path:
4119 4123 raise error.Abort(_('default repository not configured!'),
4120 4124 hint=_("see 'hg help config.paths'"))
4121 4125 dest = path.pushloc or path.loc
4122 4126 branches = (path.branch, opts.get('branch') or [])
4123 4127 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4124 4128 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4125 4129 other = hg.peer(repo, opts, dest)
4126 4130
4127 4131 if revs:
4128 4132 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4129 4133 if not revs:
4130 4134 raise error.Abort(_("specified revisions evaluate to an empty set"),
4131 4135 hint=_("use different revision arguments"))
4132 4136 elif path.pushrev:
4133 4137 # It doesn't make any sense to specify ancestor revisions. So limit
4134 4138 # to DAG heads to make discovery simpler.
4135 4139 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4136 4140 revs = scmutil.revrange(repo, [expr])
4137 4141 revs = [repo[rev].node() for rev in revs]
4138 4142 if not revs:
4139 4143 raise error.Abort(_('default push revset for path evaluates to an '
4140 4144 'empty set'))
4141 4145
4142 4146 repo._subtoppath = dest
4143 4147 try:
4144 4148 # push subrepos depth-first for coherent ordering
4145 4149 c = repo['']
4146 4150 subs = c.substate # only repos that are committed
4147 4151 for s in sorted(subs):
4148 4152 result = c.sub(s).push(opts)
4149 4153 if result == 0:
4150 4154 return not result
4151 4155 finally:
4152 4156 del repo._subtoppath
4153 4157
4154 4158 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4155 4159 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4156 4160
4157 4161 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4158 4162 newbranch=opts.get('new_branch'),
4159 4163 bookmarks=opts.get('bookmark', ()),
4160 4164 opargs=opargs)
4161 4165
4162 4166 result = not pushop.cgresult
4163 4167
4164 4168 if pushop.bkresult is not None:
4165 4169 if pushop.bkresult == 2:
4166 4170 result = 2
4167 4171 elif not result and pushop.bkresult:
4168 4172 result = 2
4169 4173
4170 4174 return result
4171 4175
4172 4176 @command('recover', [])
4173 4177 def recover(ui, repo):
4174 4178 """roll back an interrupted transaction
4175 4179
4176 4180 Recover from an interrupted commit or pull.
4177 4181
4178 4182 This command tries to fix the repository status after an
4179 4183 interrupted operation. It should only be necessary when Mercurial
4180 4184 suggests it.
4181 4185
4182 4186 Returns 0 if successful, 1 if nothing to recover or verify fails.
4183 4187 """
4184 4188 if repo.recover():
4185 4189 return hg.verify(repo)
4186 4190 return 1
4187 4191
4188 4192 @command('^remove|rm',
4189 4193 [('A', 'after', None, _('record delete for missing files')),
4190 4194 ('f', 'force', None,
4191 4195 _('forget added files, delete modified files')),
4192 4196 ] + subrepoopts + walkopts,
4193 4197 _('[OPTION]... FILE...'),
4194 4198 inferrepo=True)
4195 4199 def remove(ui, repo, *pats, **opts):
4196 4200 """remove the specified files on the next commit
4197 4201
4198 4202 Schedule the indicated files for removal from the current branch.
4199 4203
4200 4204 This command schedules the files to be removed at the next commit.
4201 4205 To undo a remove before that, see :hg:`revert`. To undo added
4202 4206 files, see :hg:`forget`.
4203 4207
4204 4208 .. container:: verbose
4205 4209
4206 4210 -A/--after can be used to remove only files that have already
4207 4211 been deleted, -f/--force can be used to force deletion, and -Af
4208 4212 can be used to remove files from the next revision without
4209 4213 deleting them from the working directory.
4210 4214
4211 4215 The following table details the behavior of remove for different
4212 4216 file states (columns) and option combinations (rows). The file
4213 4217 states are Added [A], Clean [C], Modified [M] and Missing [!]
4214 4218 (as reported by :hg:`status`). The actions are Warn, Remove
4215 4219 (from branch) and Delete (from disk):
4216 4220
4217 4221 ========= == == == ==
4218 4222 opt/state A C M !
4219 4223 ========= == == == ==
4220 4224 none W RD W R
4221 4225 -f R RD RD R
4222 4226 -A W W W R
4223 4227 -Af R R R R
4224 4228 ========= == == == ==
4225 4229
4226 4230 .. note::
4227 4231
4228 4232 :hg:`remove` never deletes files in Added [A] state from the
4229 4233 working directory, not even if ``--force`` is specified.
4230 4234
4231 4235 Returns 0 on success, 1 if any warnings encountered.
4232 4236 """
4233 4237
4234 4238 opts = pycompat.byteskwargs(opts)
4235 4239 after, force = opts.get('after'), opts.get('force')
4236 4240 if not pats and not after:
4237 4241 raise error.Abort(_('no files specified'))
4238 4242
4239 4243 m = scmutil.match(repo[None], pats, opts)
4240 4244 subrepos = opts.get('subrepos')
4241 4245 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
4242 4246
4243 4247 @command('rename|move|mv',
4244 4248 [('A', 'after', None, _('record a rename that has already occurred')),
4245 4249 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4246 4250 ] + walkopts + dryrunopts,
4247 4251 _('[OPTION]... SOURCE... DEST'))
4248 4252 def rename(ui, repo, *pats, **opts):
4249 4253 """rename files; equivalent of copy + remove
4250 4254
4251 4255 Mark dest as copies of sources; mark sources for deletion. If dest
4252 4256 is a directory, copies are put in that directory. If dest is a
4253 4257 file, there can only be one source.
4254 4258
4255 4259 By default, this command copies the contents of files as they
4256 4260 exist in the working directory. If invoked with -A/--after, the
4257 4261 operation is recorded, but no copying is performed.
4258 4262
4259 4263 This command takes effect at the next commit. To undo a rename
4260 4264 before that, see :hg:`revert`.
4261 4265
4262 4266 Returns 0 on success, 1 if errors are encountered.
4263 4267 """
4264 4268 opts = pycompat.byteskwargs(opts)
4265 4269 with repo.wlock(False):
4266 4270 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4267 4271
4268 4272 @command('resolve',
4269 4273 [('a', 'all', None, _('select all unresolved files')),
4270 4274 ('l', 'list', None, _('list state of files needing merge')),
4271 4275 ('m', 'mark', None, _('mark files as resolved')),
4272 4276 ('u', 'unmark', None, _('mark files as unresolved')),
4273 4277 ('n', 'no-status', None, _('hide status prefix'))]
4274 4278 + mergetoolopts + walkopts + formatteropts,
4275 4279 _('[OPTION]... [FILE]...'),
4276 4280 inferrepo=True)
4277 4281 def resolve(ui, repo, *pats, **opts):
4278 4282 """redo merges or set/view the merge status of files
4279 4283
4280 4284 Merges with unresolved conflicts are often the result of
4281 4285 non-interactive merging using the ``internal:merge`` configuration
4282 4286 setting, or a command-line merge tool like ``diff3``. The resolve
4283 4287 command is used to manage the files involved in a merge, after
4284 4288 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4285 4289 working directory must have two parents). See :hg:`help
4286 4290 merge-tools` for information on configuring merge tools.
4287 4291
4288 4292 The resolve command can be used in the following ways:
4289 4293
4290 4294 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4291 4295 files, discarding any previous merge attempts. Re-merging is not
4292 4296 performed for files already marked as resolved. Use ``--all/-a``
4293 4297 to select all unresolved files. ``--tool`` can be used to specify
4294 4298 the merge tool used for the given files. It overrides the HGMERGE
4295 4299 environment variable and your configuration files. Previous file
4296 4300 contents are saved with a ``.orig`` suffix.
4297 4301
4298 4302 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4299 4303 (e.g. after having manually fixed-up the files). The default is
4300 4304 to mark all unresolved files.
4301 4305
4302 4306 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4303 4307 default is to mark all resolved files.
4304 4308
4305 4309 - :hg:`resolve -l`: list files which had or still have conflicts.
4306 4310 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4307 4311 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4308 4312 the list. See :hg:`help filesets` for details.
4309 4313
4310 4314 .. note::
4311 4315
4312 4316 Mercurial will not let you commit files with unresolved merge
4313 4317 conflicts. You must use :hg:`resolve -m ...` before you can
4314 4318 commit after a conflicting merge.
4315 4319
4316 4320 Returns 0 on success, 1 if any files fail a resolve attempt.
4317 4321 """
4318 4322
4319 4323 opts = pycompat.byteskwargs(opts)
4320 4324 flaglist = 'all mark unmark list no_status'.split()
4321 4325 all, mark, unmark, show, nostatus = \
4322 4326 [opts.get(o) for o in flaglist]
4323 4327
4324 4328 if (show and (mark or unmark)) or (mark and unmark):
4325 4329 raise error.Abort(_("too many options specified"))
4326 4330 if pats and all:
4327 4331 raise error.Abort(_("can't specify --all and patterns"))
4328 4332 if not (all or pats or show or mark or unmark):
4329 4333 raise error.Abort(_('no files or directories specified'),
4330 4334 hint=('use --all to re-merge all unresolved files'))
4331 4335
4332 4336 if show:
4333 4337 ui.pager('resolve')
4334 4338 fm = ui.formatter('resolve', opts)
4335 4339 ms = mergemod.mergestate.read(repo)
4336 4340 m = scmutil.match(repo[None], pats, opts)
4337 4341
4338 4342 # Labels and keys based on merge state. Unresolved path conflicts show
4339 4343 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4340 4344 # resolved conflicts.
4341 4345 mergestateinfo = {
4342 4346 'u': ('resolve.unresolved', 'U'),
4343 4347 'r': ('resolve.resolved', 'R'),
4344 4348 'pu': ('resolve.unresolved', 'P'),
4345 4349 'pr': ('resolve.resolved', 'R'),
4346 4350 'd': ('resolve.driverresolved', 'D'),
4347 4351 }
4348 4352
4349 4353 for f in ms:
4350 4354 if not m(f):
4351 4355 continue
4352 4356
4353 4357 label, key = mergestateinfo[ms[f]]
4354 4358 fm.startitem()
4355 4359 fm.condwrite(not nostatus, 'status', '%s ', key, label=label)
4356 4360 fm.write('path', '%s\n', f, label=label)
4357 4361 fm.end()
4358 4362 return 0
4359 4363
4360 4364 with repo.wlock():
4361 4365 ms = mergemod.mergestate.read(repo)
4362 4366
4363 4367 if not (ms.active() or repo.dirstate.p2() != nullid):
4364 4368 raise error.Abort(
4365 4369 _('resolve command not applicable when not merging'))
4366 4370
4367 4371 wctx = repo[None]
4368 4372
4369 4373 if ms.mergedriver and ms.mdstate() == 'u':
4370 4374 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4371 4375 ms.commit()
4372 4376 # allow mark and unmark to go through
4373 4377 if not mark and not unmark and not proceed:
4374 4378 return 1
4375 4379
4376 4380 m = scmutil.match(wctx, pats, opts)
4377 4381 ret = 0
4378 4382 didwork = False
4379 4383 runconclude = False
4380 4384
4381 4385 tocomplete = []
4382 4386 for f in ms:
4383 4387 if not m(f):
4384 4388 continue
4385 4389
4386 4390 didwork = True
4387 4391
4388 4392 # don't let driver-resolved files be marked, and run the conclude
4389 4393 # step if asked to resolve
4390 4394 if ms[f] == "d":
4391 4395 exact = m.exact(f)
4392 4396 if mark:
4393 4397 if exact:
4394 4398 ui.warn(_('not marking %s as it is driver-resolved\n')
4395 4399 % f)
4396 4400 elif unmark:
4397 4401 if exact:
4398 4402 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4399 4403 % f)
4400 4404 else:
4401 4405 runconclude = True
4402 4406 continue
4403 4407
4404 4408 # path conflicts must be resolved manually
4405 4409 if ms[f] in ("pu", "pr"):
4406 4410 if mark:
4407 4411 ms.mark(f, "pr")
4408 4412 elif unmark:
4409 4413 ms.mark(f, "pu")
4410 4414 elif ms[f] == "pu":
4411 4415 ui.warn(_('%s: path conflict must be resolved manually\n')
4412 4416 % f)
4413 4417 continue
4414 4418
4415 4419 if mark:
4416 4420 ms.mark(f, "r")
4417 4421 elif unmark:
4418 4422 ms.mark(f, "u")
4419 4423 else:
4420 4424 # backup pre-resolve (merge uses .orig for its own purposes)
4421 4425 a = repo.wjoin(f)
4422 4426 try:
4423 4427 util.copyfile(a, a + ".resolve")
4424 4428 except (IOError, OSError) as inst:
4425 4429 if inst.errno != errno.ENOENT:
4426 4430 raise
4427 4431
4428 4432 try:
4429 4433 # preresolve file
4430 4434 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4431 4435 'resolve')
4432 4436 complete, r = ms.preresolve(f, wctx)
4433 4437 if not complete:
4434 4438 tocomplete.append(f)
4435 4439 elif r:
4436 4440 ret = 1
4437 4441 finally:
4438 4442 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4439 4443 ms.commit()
4440 4444
4441 4445 # replace filemerge's .orig file with our resolve file, but only
4442 4446 # for merges that are complete
4443 4447 if complete:
4444 4448 try:
4445 4449 util.rename(a + ".resolve",
4446 4450 scmutil.origpath(ui, repo, a))
4447 4451 except OSError as inst:
4448 4452 if inst.errno != errno.ENOENT:
4449 4453 raise
4450 4454
4451 4455 for f in tocomplete:
4452 4456 try:
4453 4457 # resolve file
4454 4458 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4455 4459 'resolve')
4456 4460 r = ms.resolve(f, wctx)
4457 4461 if r:
4458 4462 ret = 1
4459 4463 finally:
4460 4464 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4461 4465 ms.commit()
4462 4466
4463 4467 # replace filemerge's .orig file with our resolve file
4464 4468 a = repo.wjoin(f)
4465 4469 try:
4466 4470 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4467 4471 except OSError as inst:
4468 4472 if inst.errno != errno.ENOENT:
4469 4473 raise
4470 4474
4471 4475 ms.commit()
4472 4476 ms.recordactions()
4473 4477
4474 4478 if not didwork and pats:
4475 4479 hint = None
4476 4480 if not any([p for p in pats if p.find(':') >= 0]):
4477 4481 pats = ['path:%s' % p for p in pats]
4478 4482 m = scmutil.match(wctx, pats, opts)
4479 4483 for f in ms:
4480 4484 if not m(f):
4481 4485 continue
4482 4486 flags = ''.join(['-%s ' % o[0] for o in flaglist
4483 4487 if opts.get(o)])
4484 4488 hint = _("(try: hg resolve %s%s)\n") % (
4485 4489 flags,
4486 4490 ' '.join(pats))
4487 4491 break
4488 4492 ui.warn(_("arguments do not match paths that need resolving\n"))
4489 4493 if hint:
4490 4494 ui.warn(hint)
4491 4495 elif ms.mergedriver and ms.mdstate() != 's':
4492 4496 # run conclude step when either a driver-resolved file is requested
4493 4497 # or there are no driver-resolved files
4494 4498 # we can't use 'ret' to determine whether any files are unresolved
4495 4499 # because we might not have tried to resolve some
4496 4500 if ((runconclude or not list(ms.driverresolved()))
4497 4501 and not list(ms.unresolved())):
4498 4502 proceed = mergemod.driverconclude(repo, ms, wctx)
4499 4503 ms.commit()
4500 4504 if not proceed:
4501 4505 return 1
4502 4506
4503 4507 # Nudge users into finishing an unfinished operation
4504 4508 unresolvedf = list(ms.unresolved())
4505 4509 driverresolvedf = list(ms.driverresolved())
4506 4510 if not unresolvedf and not driverresolvedf:
4507 4511 ui.status(_('(no more unresolved files)\n'))
4508 4512 cmdutil.checkafterresolved(repo)
4509 4513 elif not unresolvedf:
4510 4514 ui.status(_('(no more unresolved files -- '
4511 4515 'run "hg resolve --all" to conclude)\n'))
4512 4516
4513 4517 return ret
4514 4518
4515 4519 @command('revert',
4516 4520 [('a', 'all', None, _('revert all changes when no arguments given')),
4517 4521 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4518 4522 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4519 4523 ('C', 'no-backup', None, _('do not save backup copies of files')),
4520 4524 ('i', 'interactive', None,
4521 4525 _('interactively select the changes (EXPERIMENTAL)')),
4522 4526 ] + walkopts + dryrunopts,
4523 4527 _('[OPTION]... [-r REV] [NAME]...'))
4524 4528 def revert(ui, repo, *pats, **opts):
4525 4529 """restore files to their checkout state
4526 4530
4527 4531 .. note::
4528 4532
4529 4533 To check out earlier revisions, you should use :hg:`update REV`.
4530 4534 To cancel an uncommitted merge (and lose your changes),
4531 4535 use :hg:`update --clean .`.
4532 4536
4533 4537 With no revision specified, revert the specified files or directories
4534 4538 to the contents they had in the parent of the working directory.
4535 4539 This restores the contents of files to an unmodified
4536 4540 state and unschedules adds, removes, copies, and renames. If the
4537 4541 working directory has two parents, you must explicitly specify a
4538 4542 revision.
4539 4543
4540 4544 Using the -r/--rev or -d/--date options, revert the given files or
4541 4545 directories to their states as of a specific revision. Because
4542 4546 revert does not change the working directory parents, this will
4543 4547 cause these files to appear modified. This can be helpful to "back
4544 4548 out" some or all of an earlier change. See :hg:`backout` for a
4545 4549 related method.
4546 4550
4547 4551 Modified files are saved with a .orig suffix before reverting.
4548 4552 To disable these backups, use --no-backup. It is possible to store
4549 4553 the backup files in a custom directory relative to the root of the
4550 4554 repository by setting the ``ui.origbackuppath`` configuration
4551 4555 option.
4552 4556
4553 4557 See :hg:`help dates` for a list of formats valid for -d/--date.
4554 4558
4555 4559 See :hg:`help backout` for a way to reverse the effect of an
4556 4560 earlier changeset.
4557 4561
4558 4562 Returns 0 on success.
4559 4563 """
4560 4564
4561 4565 if opts.get("date"):
4562 4566 if opts.get("rev"):
4563 4567 raise error.Abort(_("you can't specify a revision and a date"))
4564 4568 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4565 4569
4566 4570 parent, p2 = repo.dirstate.parents()
4567 4571 if not opts.get('rev') and p2 != nullid:
4568 4572 # revert after merge is a trap for new users (issue2915)
4569 4573 raise error.Abort(_('uncommitted merge with no revision specified'),
4570 4574 hint=_("use 'hg update' or see 'hg help revert'"))
4571 4575
4572 4576 ctx = scmutil.revsingle(repo, opts.get('rev'))
4573 4577
4574 4578 if (not (pats or opts.get('include') or opts.get('exclude') or
4575 4579 opts.get('all') or opts.get('interactive'))):
4576 4580 msg = _("no files or directories specified")
4577 4581 if p2 != nullid:
4578 4582 hint = _("uncommitted merge, use --all to discard all changes,"
4579 4583 " or 'hg update -C .' to abort the merge")
4580 4584 raise error.Abort(msg, hint=hint)
4581 4585 dirty = any(repo.status())
4582 4586 node = ctx.node()
4583 4587 if node != parent:
4584 4588 if dirty:
4585 4589 hint = _("uncommitted changes, use --all to discard all"
4586 4590 " changes, or 'hg update %s' to update") % ctx.rev()
4587 4591 else:
4588 4592 hint = _("use --all to revert all files,"
4589 4593 " or 'hg update %s' to update") % ctx.rev()
4590 4594 elif dirty:
4591 4595 hint = _("uncommitted changes, use --all to discard all changes")
4592 4596 else:
4593 4597 hint = _("use --all to revert all files")
4594 4598 raise error.Abort(msg, hint=hint)
4595 4599
4596 4600 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
4597 4601
4598 4602 @command('rollback', dryrunopts +
4599 4603 [('f', 'force', False, _('ignore safety measures'))])
4600 4604 def rollback(ui, repo, **opts):
4601 4605 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4602 4606
4603 4607 Please use :hg:`commit --amend` instead of rollback to correct
4604 4608 mistakes in the last commit.
4605 4609
4606 4610 This command should be used with care. There is only one level of
4607 4611 rollback, and there is no way to undo a rollback. It will also
4608 4612 restore the dirstate at the time of the last transaction, losing
4609 4613 any dirstate changes since that time. This command does not alter
4610 4614 the working directory.
4611 4615
4612 4616 Transactions are used to encapsulate the effects of all commands
4613 4617 that create new changesets or propagate existing changesets into a
4614 4618 repository.
4615 4619
4616 4620 .. container:: verbose
4617 4621
4618 4622 For example, the following commands are transactional, and their
4619 4623 effects can be rolled back:
4620 4624
4621 4625 - commit
4622 4626 - import
4623 4627 - pull
4624 4628 - push (with this repository as the destination)
4625 4629 - unbundle
4626 4630
4627 4631 To avoid permanent data loss, rollback will refuse to rollback a
4628 4632 commit transaction if it isn't checked out. Use --force to
4629 4633 override this protection.
4630 4634
4631 4635 The rollback command can be entirely disabled by setting the
4632 4636 ``ui.rollback`` configuration setting to false. If you're here
4633 4637 because you want to use rollback and it's disabled, you can
4634 4638 re-enable the command by setting ``ui.rollback`` to true.
4635 4639
4636 4640 This command is not intended for use on public repositories. Once
4637 4641 changes are visible for pull by other users, rolling a transaction
4638 4642 back locally is ineffective (someone else may already have pulled
4639 4643 the changes). Furthermore, a race is possible with readers of the
4640 4644 repository; for example an in-progress pull from the repository
4641 4645 may fail if a rollback is performed.
4642 4646
4643 4647 Returns 0 on success, 1 if no rollback data is available.
4644 4648 """
4645 4649 if not ui.configbool('ui', 'rollback'):
4646 4650 raise error.Abort(_('rollback is disabled because it is unsafe'),
4647 4651 hint=('see `hg help -v rollback` for information'))
4648 4652 return repo.rollback(dryrun=opts.get(r'dry_run'),
4649 4653 force=opts.get(r'force'))
4650 4654
4651 4655 @command('root', [])
4652 4656 def root(ui, repo):
4653 4657 """print the root (top) of the current working directory
4654 4658
4655 4659 Print the root directory of the current repository.
4656 4660
4657 4661 Returns 0 on success.
4658 4662 """
4659 4663 ui.write(repo.root + "\n")
4660 4664
4661 4665 @command('^serve',
4662 4666 [('A', 'accesslog', '', _('name of access log file to write to'),
4663 4667 _('FILE')),
4664 4668 ('d', 'daemon', None, _('run server in background')),
4665 4669 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4666 4670 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4667 4671 # use string type, then we can check if something was passed
4668 4672 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4669 4673 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4670 4674 _('ADDR')),
4671 4675 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4672 4676 _('PREFIX')),
4673 4677 ('n', 'name', '',
4674 4678 _('name to show in web pages (default: working directory)'), _('NAME')),
4675 4679 ('', 'web-conf', '',
4676 4680 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4677 4681 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4678 4682 _('FILE')),
4679 4683 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4680 4684 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4681 4685 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4682 4686 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4683 4687 ('', 'style', '', _('template style to use'), _('STYLE')),
4684 4688 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4685 4689 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
4686 4690 + subrepoopts,
4687 4691 _('[OPTION]...'),
4688 4692 optionalrepo=True)
4689 4693 def serve(ui, repo, **opts):
4690 4694 """start stand-alone webserver
4691 4695
4692 4696 Start a local HTTP repository browser and pull server. You can use
4693 4697 this for ad-hoc sharing and browsing of repositories. It is
4694 4698 recommended to use a real web server to serve a repository for
4695 4699 longer periods of time.
4696 4700
4697 4701 Please note that the server does not implement access control.
4698 4702 This means that, by default, anybody can read from the server and
4699 4703 nobody can write to it by default. Set the ``web.allow_push``
4700 4704 option to ``*`` to allow everybody to push to the server. You
4701 4705 should use a real web server if you need to authenticate users.
4702 4706
4703 4707 By default, the server logs accesses to stdout and errors to
4704 4708 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4705 4709 files.
4706 4710
4707 4711 To have the server choose a free port number to listen on, specify
4708 4712 a port number of 0; in this case, the server will print the port
4709 4713 number it uses.
4710 4714
4711 4715 Returns 0 on success.
4712 4716 """
4713 4717
4714 4718 opts = pycompat.byteskwargs(opts)
4715 4719 if opts["stdio"] and opts["cmdserver"]:
4716 4720 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4717 4721
4718 4722 if opts["stdio"]:
4719 4723 if repo is None:
4720 4724 raise error.RepoError(_("there is no Mercurial repository here"
4721 4725 " (.hg not found)"))
4722 4726 s = sshserver.sshserver(ui, repo)
4723 4727 s.serve_forever()
4724 4728
4725 4729 service = server.createservice(ui, repo, opts)
4726 4730 return server.runservice(opts, initfn=service.init, runfn=service.run)
4727 4731
4728 4732 @command('^status|st',
4729 4733 [('A', 'all', None, _('show status of all files')),
4730 4734 ('m', 'modified', None, _('show only modified files')),
4731 4735 ('a', 'added', None, _('show only added files')),
4732 4736 ('r', 'removed', None, _('show only removed files')),
4733 4737 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4734 4738 ('c', 'clean', None, _('show only files without changes')),
4735 4739 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4736 4740 ('i', 'ignored', None, _('show only ignored files')),
4737 4741 ('n', 'no-status', None, _('hide status prefix')),
4738 4742 ('t', 'terse', '', _('show the terse output (EXPERIMENTAL)')),
4739 4743 ('C', 'copies', None, _('show source of copied files')),
4740 4744 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4741 4745 ('', 'rev', [], _('show difference from revision'), _('REV')),
4742 4746 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4743 4747 ] + walkopts + subrepoopts + formatteropts,
4744 4748 _('[OPTION]... [FILE]...'),
4745 4749 inferrepo=True)
4746 4750 def status(ui, repo, *pats, **opts):
4747 4751 """show changed files in the working directory
4748 4752
4749 4753 Show status of files in the repository. If names are given, only
4750 4754 files that match are shown. Files that are clean or ignored or
4751 4755 the source of a copy/move operation, are not listed unless
4752 4756 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4753 4757 Unless options described with "show only ..." are given, the
4754 4758 options -mardu are used.
4755 4759
4756 4760 Option -q/--quiet hides untracked (unknown and ignored) files
4757 4761 unless explicitly requested with -u/--unknown or -i/--ignored.
4758 4762
4759 4763 .. note::
4760 4764
4761 4765 :hg:`status` may appear to disagree with diff if permissions have
4762 4766 changed or a merge has occurred. The standard diff format does
4763 4767 not report permission changes and diff only reports changes
4764 4768 relative to one merge parent.
4765 4769
4766 4770 If one revision is given, it is used as the base revision.
4767 4771 If two revisions are given, the differences between them are
4768 4772 shown. The --change option can also be used as a shortcut to list
4769 4773 the changed files of a revision from its first parent.
4770 4774
4771 4775 The codes used to show the status of files are::
4772 4776
4773 4777 M = modified
4774 4778 A = added
4775 4779 R = removed
4776 4780 C = clean
4777 4781 ! = missing (deleted by non-hg command, but still tracked)
4778 4782 ? = not tracked
4779 4783 I = ignored
4780 4784 = origin of the previous file (with --copies)
4781 4785
4782 4786 .. container:: verbose
4783 4787
4784 4788 The -t/--terse option abbreviates the output by showing only the directory
4785 4789 name if all the files in it share the same status. The option takes an
4786 4790 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
4787 4791 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
4788 4792 for 'ignored' and 'c' for clean.
4789 4793
4790 4794 It abbreviates only those statuses which are passed. Note that clean and
4791 4795 ignored files are not displayed with '--terse ic' unless the -c/--clean
4792 4796 and -i/--ignored options are also used.
4793 4797
4794 4798 The -v/--verbose option shows information when the repository is in an
4795 4799 unfinished merge, shelve, rebase state etc. You can have this behavior
4796 4800 turned on by default by enabling the ``commands.status.verbose`` option.
4797 4801
4798 4802 You can skip displaying some of these states by setting
4799 4803 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
4800 4804 'histedit', 'merge', 'rebase', or 'unshelve'.
4801 4805
4802 4806 Examples:
4803 4807
4804 4808 - show changes in the working directory relative to a
4805 4809 changeset::
4806 4810
4807 4811 hg status --rev 9353
4808 4812
4809 4813 - show changes in the working directory relative to the
4810 4814 current directory (see :hg:`help patterns` for more information)::
4811 4815
4812 4816 hg status re:
4813 4817
4814 4818 - show all changes including copies in an existing changeset::
4815 4819
4816 4820 hg status --copies --change 9353
4817 4821
4818 4822 - get a NUL separated list of added files, suitable for xargs::
4819 4823
4820 4824 hg status -an0
4821 4825
4822 4826 - show more information about the repository status, abbreviating
4823 4827 added, removed, modified, deleted, and untracked paths::
4824 4828
4825 4829 hg status -v -t mardu
4826 4830
4827 4831 Returns 0 on success.
4828 4832
4829 4833 """
4830 4834
4831 4835 opts = pycompat.byteskwargs(opts)
4832 4836 revs = opts.get('rev')
4833 4837 change = opts.get('change')
4834 4838 terse = opts.get('terse')
4835 4839
4836 4840 if revs and change:
4837 4841 msg = _('cannot specify --rev and --change at the same time')
4838 4842 raise error.Abort(msg)
4839 4843 elif revs and terse:
4840 4844 msg = _('cannot use --terse with --rev')
4841 4845 raise error.Abort(msg)
4842 4846 elif change:
4843 4847 node2 = scmutil.revsingle(repo, change, None).node()
4844 4848 node1 = repo[node2].p1().node()
4845 4849 else:
4846 4850 node1, node2 = scmutil.revpair(repo, revs)
4847 4851
4848 4852 if pats or ui.configbool('commands', 'status.relative'):
4849 4853 cwd = repo.getcwd()
4850 4854 else:
4851 4855 cwd = ''
4852 4856
4853 4857 if opts.get('print0'):
4854 4858 end = '\0'
4855 4859 else:
4856 4860 end = '\n'
4857 4861 copy = {}
4858 4862 states = 'modified added removed deleted unknown ignored clean'.split()
4859 4863 show = [k for k in states if opts.get(k)]
4860 4864 if opts.get('all'):
4861 4865 show += ui.quiet and (states[:4] + ['clean']) or states
4862 4866
4863 4867 if not show:
4864 4868 if ui.quiet:
4865 4869 show = states[:4]
4866 4870 else:
4867 4871 show = states[:5]
4868 4872
4869 4873 m = scmutil.match(repo[node2], pats, opts)
4870 4874 if terse:
4871 4875 # we need to compute clean and unknown to terse
4872 4876 stat = repo.status(node1, node2, m,
4873 4877 'ignored' in show or 'i' in terse,
4874 4878 True, True, opts.get('subrepos'))
4875 4879
4876 4880 stat = cmdutil.tersedir(stat, terse)
4877 4881 else:
4878 4882 stat = repo.status(node1, node2, m,
4879 4883 'ignored' in show, 'clean' in show,
4880 4884 'unknown' in show, opts.get('subrepos'))
4881 4885
4882 4886 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4883 4887
4884 4888 if (opts.get('all') or opts.get('copies')
4885 4889 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4886 4890 copy = copies.pathcopies(repo[node1], repo[node2], m)
4887 4891
4888 4892 ui.pager('status')
4889 4893 fm = ui.formatter('status', opts)
4890 4894 fmt = '%s' + end
4891 4895 showchar = not opts.get('no_status')
4892 4896
4893 4897 for state, char, files in changestates:
4894 4898 if state in show:
4895 4899 label = 'status.' + state
4896 4900 for f in files:
4897 4901 fm.startitem()
4898 4902 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4899 4903 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4900 4904 if f in copy:
4901 4905 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4902 4906 label='status.copied')
4903 4907
4904 4908 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
4905 4909 and not ui.plain()):
4906 4910 cmdutil.morestatus(repo, fm)
4907 4911 fm.end()
4908 4912
4909 4913 @command('^summary|sum',
4910 4914 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
4911 4915 def summary(ui, repo, **opts):
4912 4916 """summarize working directory state
4913 4917
4914 4918 This generates a brief summary of the working directory state,
4915 4919 including parents, branch, commit status, phase and available updates.
4916 4920
4917 4921 With the --remote option, this will check the default paths for
4918 4922 incoming and outgoing changes. This can be time-consuming.
4919 4923
4920 4924 Returns 0 on success.
4921 4925 """
4922 4926
4923 4927 opts = pycompat.byteskwargs(opts)
4924 4928 ui.pager('summary')
4925 4929 ctx = repo[None]
4926 4930 parents = ctx.parents()
4927 4931 pnode = parents[0].node()
4928 4932 marks = []
4929 4933
4930 4934 ms = None
4931 4935 try:
4932 4936 ms = mergemod.mergestate.read(repo)
4933 4937 except error.UnsupportedMergeRecords as e:
4934 4938 s = ' '.join(e.recordtypes)
4935 4939 ui.warn(
4936 4940 _('warning: merge state has unsupported record types: %s\n') % s)
4937 4941 unresolved = []
4938 4942 else:
4939 4943 unresolved = list(ms.unresolved())
4940 4944
4941 4945 for p in parents:
4942 4946 # label with log.changeset (instead of log.parent) since this
4943 4947 # shows a working directory parent *changeset*:
4944 4948 # i18n: column positioning for "hg summary"
4945 4949 ui.write(_('parent: %d:%s ') % (p.rev(), p),
4946 4950 label=cmdutil._changesetlabels(p))
4947 4951 ui.write(' '.join(p.tags()), label='log.tag')
4948 4952 if p.bookmarks():
4949 4953 marks.extend(p.bookmarks())
4950 4954 if p.rev() == -1:
4951 4955 if not len(repo):
4952 4956 ui.write(_(' (empty repository)'))
4953 4957 else:
4954 4958 ui.write(_(' (no revision checked out)'))
4955 4959 if p.obsolete():
4956 4960 ui.write(_(' (obsolete)'))
4957 4961 if p.isunstable():
4958 4962 instabilities = (ui.label(instability, 'trouble.%s' % instability)
4959 4963 for instability in p.instabilities())
4960 4964 ui.write(' ('
4961 4965 + ', '.join(instabilities)
4962 4966 + ')')
4963 4967 ui.write('\n')
4964 4968 if p.description():
4965 4969 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
4966 4970 label='log.summary')
4967 4971
4968 4972 branch = ctx.branch()
4969 4973 bheads = repo.branchheads(branch)
4970 4974 # i18n: column positioning for "hg summary"
4971 4975 m = _('branch: %s\n') % branch
4972 4976 if branch != 'default':
4973 4977 ui.write(m, label='log.branch')
4974 4978 else:
4975 4979 ui.status(m, label='log.branch')
4976 4980
4977 4981 if marks:
4978 4982 active = repo._activebookmark
4979 4983 # i18n: column positioning for "hg summary"
4980 4984 ui.write(_('bookmarks:'), label='log.bookmark')
4981 4985 if active is not None:
4982 4986 if active in marks:
4983 4987 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
4984 4988 marks.remove(active)
4985 4989 else:
4986 4990 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
4987 4991 for m in marks:
4988 4992 ui.write(' ' + m, label='log.bookmark')
4989 4993 ui.write('\n', label='log.bookmark')
4990 4994
4991 4995 status = repo.status(unknown=True)
4992 4996
4993 4997 c = repo.dirstate.copies()
4994 4998 copied, renamed = [], []
4995 4999 for d, s in c.iteritems():
4996 5000 if s in status.removed:
4997 5001 status.removed.remove(s)
4998 5002 renamed.append(d)
4999 5003 else:
5000 5004 copied.append(d)
5001 5005 if d in status.added:
5002 5006 status.added.remove(d)
5003 5007
5004 5008 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5005 5009
5006 5010 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5007 5011 (ui.label(_('%d added'), 'status.added'), status.added),
5008 5012 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5009 5013 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5010 5014 (ui.label(_('%d copied'), 'status.copied'), copied),
5011 5015 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5012 5016 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5013 5017 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5014 5018 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5015 5019 t = []
5016 5020 for l, s in labels:
5017 5021 if s:
5018 5022 t.append(l % len(s))
5019 5023
5020 5024 t = ', '.join(t)
5021 5025 cleanworkdir = False
5022 5026
5023 5027 if repo.vfs.exists('graftstate'):
5024 5028 t += _(' (graft in progress)')
5025 5029 if repo.vfs.exists('updatestate'):
5026 5030 t += _(' (interrupted update)')
5027 5031 elif len(parents) > 1:
5028 5032 t += _(' (merge)')
5029 5033 elif branch != parents[0].branch():
5030 5034 t += _(' (new branch)')
5031 5035 elif (parents[0].closesbranch() and
5032 5036 pnode in repo.branchheads(branch, closed=True)):
5033 5037 t += _(' (head closed)')
5034 5038 elif not (status.modified or status.added or status.removed or renamed or
5035 5039 copied or subs):
5036 5040 t += _(' (clean)')
5037 5041 cleanworkdir = True
5038 5042 elif pnode not in bheads:
5039 5043 t += _(' (new branch head)')
5040 5044
5041 5045 if parents:
5042 5046 pendingphase = max(p.phase() for p in parents)
5043 5047 else:
5044 5048 pendingphase = phases.public
5045 5049
5046 5050 if pendingphase > phases.newcommitphase(ui):
5047 5051 t += ' (%s)' % phases.phasenames[pendingphase]
5048 5052
5049 5053 if cleanworkdir:
5050 5054 # i18n: column positioning for "hg summary"
5051 5055 ui.status(_('commit: %s\n') % t.strip())
5052 5056 else:
5053 5057 # i18n: column positioning for "hg summary"
5054 5058 ui.write(_('commit: %s\n') % t.strip())
5055 5059
5056 5060 # all ancestors of branch heads - all ancestors of parent = new csets
5057 5061 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5058 5062 bheads))
5059 5063
5060 5064 if new == 0:
5061 5065 # i18n: column positioning for "hg summary"
5062 5066 ui.status(_('update: (current)\n'))
5063 5067 elif pnode not in bheads:
5064 5068 # i18n: column positioning for "hg summary"
5065 5069 ui.write(_('update: %d new changesets (update)\n') % new)
5066 5070 else:
5067 5071 # i18n: column positioning for "hg summary"
5068 5072 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5069 5073 (new, len(bheads)))
5070 5074
5071 5075 t = []
5072 5076 draft = len(repo.revs('draft()'))
5073 5077 if draft:
5074 5078 t.append(_('%d draft') % draft)
5075 5079 secret = len(repo.revs('secret()'))
5076 5080 if secret:
5077 5081 t.append(_('%d secret') % secret)
5078 5082
5079 5083 if draft or secret:
5080 5084 ui.status(_('phases: %s\n') % ', '.join(t))
5081 5085
5082 5086 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5083 5087 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5084 5088 numtrouble = len(repo.revs(trouble + "()"))
5085 5089 # We write all the possibilities to ease translation
5086 5090 troublemsg = {
5087 5091 "orphan": _("orphan: %d changesets"),
5088 5092 "contentdivergent": _("content-divergent: %d changesets"),
5089 5093 "phasedivergent": _("phase-divergent: %d changesets"),
5090 5094 }
5091 5095 if numtrouble > 0:
5092 5096 ui.status(troublemsg[trouble] % numtrouble + "\n")
5093 5097
5094 5098 cmdutil.summaryhooks(ui, repo)
5095 5099
5096 5100 if opts.get('remote'):
5097 5101 needsincoming, needsoutgoing = True, True
5098 5102 else:
5099 5103 needsincoming, needsoutgoing = False, False
5100 5104 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5101 5105 if i:
5102 5106 needsincoming = True
5103 5107 if o:
5104 5108 needsoutgoing = True
5105 5109 if not needsincoming and not needsoutgoing:
5106 5110 return
5107 5111
5108 5112 def getincoming():
5109 5113 source, branches = hg.parseurl(ui.expandpath('default'))
5110 5114 sbranch = branches[0]
5111 5115 try:
5112 5116 other = hg.peer(repo, {}, source)
5113 5117 except error.RepoError:
5114 5118 if opts.get('remote'):
5115 5119 raise
5116 5120 return source, sbranch, None, None, None
5117 5121 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5118 5122 if revs:
5119 5123 revs = [other.lookup(rev) for rev in revs]
5120 5124 ui.debug('comparing with %s\n' % util.hidepassword(source))
5121 5125 repo.ui.pushbuffer()
5122 5126 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5123 5127 repo.ui.popbuffer()
5124 5128 return source, sbranch, other, commoninc, commoninc[1]
5125 5129
5126 5130 if needsincoming:
5127 5131 source, sbranch, sother, commoninc, incoming = getincoming()
5128 5132 else:
5129 5133 source = sbranch = sother = commoninc = incoming = None
5130 5134
5131 5135 def getoutgoing():
5132 5136 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5133 5137 dbranch = branches[0]
5134 5138 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5135 5139 if source != dest:
5136 5140 try:
5137 5141 dother = hg.peer(repo, {}, dest)
5138 5142 except error.RepoError:
5139 5143 if opts.get('remote'):
5140 5144 raise
5141 5145 return dest, dbranch, None, None
5142 5146 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5143 5147 elif sother is None:
5144 5148 # there is no explicit destination peer, but source one is invalid
5145 5149 return dest, dbranch, None, None
5146 5150 else:
5147 5151 dother = sother
5148 5152 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5149 5153 common = None
5150 5154 else:
5151 5155 common = commoninc
5152 5156 if revs:
5153 5157 revs = [repo.lookup(rev) for rev in revs]
5154 5158 repo.ui.pushbuffer()
5155 5159 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5156 5160 commoninc=common)
5157 5161 repo.ui.popbuffer()
5158 5162 return dest, dbranch, dother, outgoing
5159 5163
5160 5164 if needsoutgoing:
5161 5165 dest, dbranch, dother, outgoing = getoutgoing()
5162 5166 else:
5163 5167 dest = dbranch = dother = outgoing = None
5164 5168
5165 5169 if opts.get('remote'):
5166 5170 t = []
5167 5171 if incoming:
5168 5172 t.append(_('1 or more incoming'))
5169 5173 o = outgoing.missing
5170 5174 if o:
5171 5175 t.append(_('%d outgoing') % len(o))
5172 5176 other = dother or sother
5173 5177 if 'bookmarks' in other.listkeys('namespaces'):
5174 5178 counts = bookmarks.summary(repo, other)
5175 5179 if counts[0] > 0:
5176 5180 t.append(_('%d incoming bookmarks') % counts[0])
5177 5181 if counts[1] > 0:
5178 5182 t.append(_('%d outgoing bookmarks') % counts[1])
5179 5183
5180 5184 if t:
5181 5185 # i18n: column positioning for "hg summary"
5182 5186 ui.write(_('remote: %s\n') % (', '.join(t)))
5183 5187 else:
5184 5188 # i18n: column positioning for "hg summary"
5185 5189 ui.status(_('remote: (synced)\n'))
5186 5190
5187 5191 cmdutil.summaryremotehooks(ui, repo, opts,
5188 5192 ((source, sbranch, sother, commoninc),
5189 5193 (dest, dbranch, dother, outgoing)))
5190 5194
5191 5195 @command('tag',
5192 5196 [('f', 'force', None, _('force tag')),
5193 5197 ('l', 'local', None, _('make the tag local')),
5194 5198 ('r', 'rev', '', _('revision to tag'), _('REV')),
5195 5199 ('', 'remove', None, _('remove a tag')),
5196 5200 # -l/--local is already there, commitopts cannot be used
5197 5201 ('e', 'edit', None, _('invoke editor on commit messages')),
5198 5202 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5199 5203 ] + commitopts2,
5200 5204 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5201 5205 def tag(ui, repo, name1, *names, **opts):
5202 5206 """add one or more tags for the current or given revision
5203 5207
5204 5208 Name a particular revision using <name>.
5205 5209
5206 5210 Tags are used to name particular revisions of the repository and are
5207 5211 very useful to compare different revisions, to go back to significant
5208 5212 earlier versions or to mark branch points as releases, etc. Changing
5209 5213 an existing tag is normally disallowed; use -f/--force to override.
5210 5214
5211 5215 If no revision is given, the parent of the working directory is
5212 5216 used.
5213 5217
5214 5218 To facilitate version control, distribution, and merging of tags,
5215 5219 they are stored as a file named ".hgtags" which is managed similarly
5216 5220 to other project files and can be hand-edited if necessary. This
5217 5221 also means that tagging creates a new commit. The file
5218 5222 ".hg/localtags" is used for local tags (not shared among
5219 5223 repositories).
5220 5224
5221 5225 Tag commits are usually made at the head of a branch. If the parent
5222 5226 of the working directory is not a branch head, :hg:`tag` aborts; use
5223 5227 -f/--force to force the tag commit to be based on a non-head
5224 5228 changeset.
5225 5229
5226 5230 See :hg:`help dates` for a list of formats valid for -d/--date.
5227 5231
5228 5232 Since tag names have priority over branch names during revision
5229 5233 lookup, using an existing branch name as a tag name is discouraged.
5230 5234
5231 5235 Returns 0 on success.
5232 5236 """
5233 5237 opts = pycompat.byteskwargs(opts)
5234 5238 wlock = lock = None
5235 5239 try:
5236 5240 wlock = repo.wlock()
5237 5241 lock = repo.lock()
5238 5242 rev_ = "."
5239 5243 names = [t.strip() for t in (name1,) + names]
5240 5244 if len(names) != len(set(names)):
5241 5245 raise error.Abort(_('tag names must be unique'))
5242 5246 for n in names:
5243 5247 scmutil.checknewlabel(repo, n, 'tag')
5244 5248 if not n:
5245 5249 raise error.Abort(_('tag names cannot consist entirely of '
5246 5250 'whitespace'))
5247 5251 if opts.get('rev') and opts.get('remove'):
5248 5252 raise error.Abort(_("--rev and --remove are incompatible"))
5249 5253 if opts.get('rev'):
5250 5254 rev_ = opts['rev']
5251 5255 message = opts.get('message')
5252 5256 if opts.get('remove'):
5253 5257 if opts.get('local'):
5254 5258 expectedtype = 'local'
5255 5259 else:
5256 5260 expectedtype = 'global'
5257 5261
5258 5262 for n in names:
5259 5263 if not repo.tagtype(n):
5260 5264 raise error.Abort(_("tag '%s' does not exist") % n)
5261 5265 if repo.tagtype(n) != expectedtype:
5262 5266 if expectedtype == 'global':
5263 5267 raise error.Abort(_("tag '%s' is not a global tag") % n)
5264 5268 else:
5265 5269 raise error.Abort(_("tag '%s' is not a local tag") % n)
5266 5270 rev_ = 'null'
5267 5271 if not message:
5268 5272 # we don't translate commit messages
5269 5273 message = 'Removed tag %s' % ', '.join(names)
5270 5274 elif not opts.get('force'):
5271 5275 for n in names:
5272 5276 if n in repo.tags():
5273 5277 raise error.Abort(_("tag '%s' already exists "
5274 5278 "(use -f to force)") % n)
5275 5279 if not opts.get('local'):
5276 5280 p1, p2 = repo.dirstate.parents()
5277 5281 if p2 != nullid:
5278 5282 raise error.Abort(_('uncommitted merge'))
5279 5283 bheads = repo.branchheads()
5280 5284 if not opts.get('force') and bheads and p1 not in bheads:
5281 5285 raise error.Abort(_('working directory is not at a branch head '
5282 5286 '(use -f to force)'))
5283 5287 r = scmutil.revsingle(repo, rev_).node()
5284 5288
5285 5289 if not message:
5286 5290 # we don't translate commit messages
5287 5291 message = ('Added tag %s for changeset %s' %
5288 5292 (', '.join(names), short(r)))
5289 5293
5290 5294 date = opts.get('date')
5291 5295 if date:
5292 5296 date = util.parsedate(date)
5293 5297
5294 5298 if opts.get('remove'):
5295 5299 editform = 'tag.remove'
5296 5300 else:
5297 5301 editform = 'tag.add'
5298 5302 editor = cmdutil.getcommiteditor(editform=editform,
5299 5303 **pycompat.strkwargs(opts))
5300 5304
5301 5305 # don't allow tagging the null rev
5302 5306 if (not opts.get('remove') and
5303 5307 scmutil.revsingle(repo, rev_).rev() == nullrev):
5304 5308 raise error.Abort(_("cannot tag null revision"))
5305 5309
5306 5310 tagsmod.tag(repo, names, r, message, opts.get('local'),
5307 5311 opts.get('user'), date, editor=editor)
5308 5312 finally:
5309 5313 release(lock, wlock)
5310 5314
5311 5315 @command('tags', formatteropts, '')
5312 5316 def tags(ui, repo, **opts):
5313 5317 """list repository tags
5314 5318
5315 5319 This lists both regular and local tags. When the -v/--verbose
5316 5320 switch is used, a third column "local" is printed for local tags.
5317 5321 When the -q/--quiet switch is used, only the tag name is printed.
5318 5322
5319 5323 Returns 0 on success.
5320 5324 """
5321 5325
5322 5326 opts = pycompat.byteskwargs(opts)
5323 5327 ui.pager('tags')
5324 5328 fm = ui.formatter('tags', opts)
5325 5329 hexfunc = fm.hexfunc
5326 5330 tagtype = ""
5327 5331
5328 5332 for t, n in reversed(repo.tagslist()):
5329 5333 hn = hexfunc(n)
5330 5334 label = 'tags.normal'
5331 5335 tagtype = ''
5332 5336 if repo.tagtype(t) == 'local':
5333 5337 label = 'tags.local'
5334 5338 tagtype = 'local'
5335 5339
5336 5340 fm.startitem()
5337 5341 fm.write('tag', '%s', t, label=label)
5338 5342 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5339 5343 fm.condwrite(not ui.quiet, 'rev node', fmt,
5340 5344 repo.changelog.rev(n), hn, label=label)
5341 5345 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5342 5346 tagtype, label=label)
5343 5347 fm.plain('\n')
5344 5348 fm.end()
5345 5349
5346 5350 @command('tip',
5347 5351 [('p', 'patch', None, _('show patch')),
5348 5352 ('g', 'git', None, _('use git extended diff format')),
5349 5353 ] + templateopts,
5350 5354 _('[-p] [-g]'))
5351 5355 def tip(ui, repo, **opts):
5352 5356 """show the tip revision (DEPRECATED)
5353 5357
5354 5358 The tip revision (usually just called the tip) is the changeset
5355 5359 most recently added to the repository (and therefore the most
5356 5360 recently changed head).
5357 5361
5358 5362 If you have just made a commit, that commit will be the tip. If
5359 5363 you have just pulled changes from another repository, the tip of
5360 5364 that repository becomes the current tip. The "tip" tag is special
5361 5365 and cannot be renamed or assigned to a different changeset.
5362 5366
5363 5367 This command is deprecated, please use :hg:`heads` instead.
5364 5368
5365 5369 Returns 0 on success.
5366 5370 """
5367 5371 opts = pycompat.byteskwargs(opts)
5368 5372 displayer = cmdutil.show_changeset(ui, repo, opts)
5369 5373 displayer.show(repo['tip'])
5370 5374 displayer.close()
5371 5375
5372 5376 @command('unbundle',
5373 5377 [('u', 'update', None,
5374 5378 _('update to new branch head if changesets were unbundled'))],
5375 5379 _('[-u] FILE...'))
5376 5380 def unbundle(ui, repo, fname1, *fnames, **opts):
5377 5381 """apply one or more bundle files
5378 5382
5379 5383 Apply one or more bundle files generated by :hg:`bundle`.
5380 5384
5381 5385 Returns 0 on success, 1 if an update has unresolved files.
5382 5386 """
5383 5387 fnames = (fname1,) + fnames
5384 5388
5385 5389 with repo.lock():
5386 5390 for fname in fnames:
5387 5391 f = hg.openpath(ui, fname)
5388 5392 gen = exchange.readbundle(ui, f, fname)
5389 5393 if isinstance(gen, streamclone.streamcloneapplier):
5390 5394 raise error.Abort(
5391 5395 _('packed bundles cannot be applied with '
5392 5396 '"hg unbundle"'),
5393 5397 hint=_('use "hg debugapplystreamclonebundle"'))
5394 5398 url = 'bundle:' + fname
5395 5399 try:
5396 5400 txnname = 'unbundle'
5397 5401 if not isinstance(gen, bundle2.unbundle20):
5398 5402 txnname = 'unbundle\n%s' % util.hidepassword(url)
5399 5403 with repo.transaction(txnname) as tr:
5400 5404 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5401 5405 url=url)
5402 5406 except error.BundleUnknownFeatureError as exc:
5403 5407 raise error.Abort(
5404 5408 _('%s: unknown bundle feature, %s') % (fname, exc),
5405 5409 hint=_("see https://mercurial-scm.org/"
5406 5410 "wiki/BundleFeature for more "
5407 5411 "information"))
5408 5412 modheads = bundle2.combinechangegroupresults(op)
5409 5413
5410 5414 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5411 5415
5412 5416 @command('^update|up|checkout|co',
5413 5417 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5414 5418 ('c', 'check', None, _('require clean working directory')),
5415 5419 ('m', 'merge', None, _('merge uncommitted changes')),
5416 5420 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5417 5421 ('r', 'rev', '', _('revision'), _('REV'))
5418 5422 ] + mergetoolopts,
5419 5423 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5420 5424 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
5421 5425 merge=None, tool=None):
5422 5426 """update working directory (or switch revisions)
5423 5427
5424 5428 Update the repository's working directory to the specified
5425 5429 changeset. If no changeset is specified, update to the tip of the
5426 5430 current named branch and move the active bookmark (see :hg:`help
5427 5431 bookmarks`).
5428 5432
5429 5433 Update sets the working directory's parent revision to the specified
5430 5434 changeset (see :hg:`help parents`).
5431 5435
5432 5436 If the changeset is not a descendant or ancestor of the working
5433 5437 directory's parent and there are uncommitted changes, the update is
5434 5438 aborted. With the -c/--check option, the working directory is checked
5435 5439 for uncommitted changes; if none are found, the working directory is
5436 5440 updated to the specified changeset.
5437 5441
5438 5442 .. container:: verbose
5439 5443
5440 5444 The -C/--clean, -c/--check, and -m/--merge options control what
5441 5445 happens if the working directory contains uncommitted changes.
5442 5446 At most of one of them can be specified.
5443 5447
5444 5448 1. If no option is specified, and if
5445 5449 the requested changeset is an ancestor or descendant of
5446 5450 the working directory's parent, the uncommitted changes
5447 5451 are merged into the requested changeset and the merged
5448 5452 result is left uncommitted. If the requested changeset is
5449 5453 not an ancestor or descendant (that is, it is on another
5450 5454 branch), the update is aborted and the uncommitted changes
5451 5455 are preserved.
5452 5456
5453 5457 2. With the -m/--merge option, the update is allowed even if the
5454 5458 requested changeset is not an ancestor or descendant of
5455 5459 the working directory's parent.
5456 5460
5457 5461 3. With the -c/--check option, the update is aborted and the
5458 5462 uncommitted changes are preserved.
5459 5463
5460 5464 4. With the -C/--clean option, uncommitted changes are discarded and
5461 5465 the working directory is updated to the requested changeset.
5462 5466
5463 5467 To cancel an uncommitted merge (and lose your changes), use
5464 5468 :hg:`update --clean .`.
5465 5469
5466 5470 Use null as the changeset to remove the working directory (like
5467 5471 :hg:`clone -U`).
5468 5472
5469 5473 If you want to revert just one file to an older revision, use
5470 5474 :hg:`revert [-r REV] NAME`.
5471 5475
5472 5476 See :hg:`help dates` for a list of formats valid for -d/--date.
5473 5477
5474 5478 Returns 0 on success, 1 if there are unresolved files.
5475 5479 """
5476 5480 if rev and node:
5477 5481 raise error.Abort(_("please specify just one revision"))
5478 5482
5479 5483 if ui.configbool('commands', 'update.requiredest'):
5480 5484 if not node and not rev and not date:
5481 5485 raise error.Abort(_('you must specify a destination'),
5482 5486 hint=_('for example: hg update ".::"'))
5483 5487
5484 5488 if rev is None or rev == '':
5485 5489 rev = node
5486 5490
5487 5491 if date and rev is not None:
5488 5492 raise error.Abort(_("you can't specify a revision and a date"))
5489 5493
5490 5494 if len([x for x in (clean, check, merge) if x]) > 1:
5491 5495 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5492 5496 "or -m/--merge"))
5493 5497
5494 5498 updatecheck = None
5495 5499 if check:
5496 5500 updatecheck = 'abort'
5497 5501 elif merge:
5498 5502 updatecheck = 'none'
5499 5503
5500 5504 with repo.wlock():
5501 5505 cmdutil.clearunfinished(repo)
5502 5506
5503 5507 if date:
5504 5508 rev = cmdutil.finddate(ui, repo, date)
5505 5509
5506 5510 # if we defined a bookmark, we have to remember the original name
5507 5511 brev = rev
5508 5512 rev = scmutil.revsingle(repo, rev, rev).rev()
5509 5513
5510 5514 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
5511 5515
5512 5516 return hg.updatetotally(ui, repo, rev, brev, clean=clean,
5513 5517 updatecheck=updatecheck)
5514 5518
5515 5519 @command('verify', [])
5516 5520 def verify(ui, repo):
5517 5521 """verify the integrity of the repository
5518 5522
5519 5523 Verify the integrity of the current repository.
5520 5524
5521 5525 This will perform an extensive check of the repository's
5522 5526 integrity, validating the hashes and checksums of each entry in
5523 5527 the changelog, manifest, and tracked files, as well as the
5524 5528 integrity of their crosslinks and indices.
5525 5529
5526 5530 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5527 5531 for more information about recovery from corruption of the
5528 5532 repository.
5529 5533
5530 5534 Returns 0 on success, 1 if errors are encountered.
5531 5535 """
5532 5536 return hg.verify(repo)
5533 5537
5534 5538 @command('version', [] + formatteropts, norepo=True)
5535 5539 def version_(ui, **opts):
5536 5540 """output version and copyright information"""
5537 5541 opts = pycompat.byteskwargs(opts)
5538 5542 if ui.verbose:
5539 5543 ui.pager('version')
5540 5544 fm = ui.formatter("version", opts)
5541 5545 fm.startitem()
5542 5546 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5543 5547 util.version())
5544 5548 license = _(
5545 5549 "(see https://mercurial-scm.org for more information)\n"
5546 5550 "\nCopyright (C) 2005-2017 Matt Mackall and others\n"
5547 5551 "This is free software; see the source for copying conditions. "
5548 5552 "There is NO\nwarranty; "
5549 5553 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5550 5554 )
5551 5555 if not ui.quiet:
5552 5556 fm.plain(license)
5553 5557
5554 5558 if ui.verbose:
5555 5559 fm.plain(_("\nEnabled extensions:\n\n"))
5556 5560 # format names and versions into columns
5557 5561 names = []
5558 5562 vers = []
5559 5563 isinternals = []
5560 5564 for name, module in extensions.extensions():
5561 5565 names.append(name)
5562 5566 vers.append(extensions.moduleversion(module) or None)
5563 5567 isinternals.append(extensions.ismoduleinternal(module))
5564 5568 fn = fm.nested("extensions")
5565 5569 if names:
5566 5570 namefmt = " %%-%ds " % max(len(n) for n in names)
5567 5571 places = [_("external"), _("internal")]
5568 5572 for n, v, p in zip(names, vers, isinternals):
5569 5573 fn.startitem()
5570 5574 fn.condwrite(ui.verbose, "name", namefmt, n)
5571 5575 if ui.verbose:
5572 5576 fn.plain("%s " % places[p])
5573 5577 fn.data(bundled=p)
5574 5578 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5575 5579 if ui.verbose:
5576 5580 fn.plain("\n")
5577 5581 fn.end()
5578 5582 fm.end()
5579 5583
5580 5584 def loadcmdtable(ui, name, cmdtable):
5581 5585 """Load command functions from specified cmdtable
5582 5586 """
5583 5587 overrides = [cmd for cmd in cmdtable if cmd in table]
5584 5588 if overrides:
5585 5589 ui.warn(_("extension '%s' overrides commands: %s\n")
5586 5590 % (name, " ".join(overrides)))
5587 5591 table.update(cmdtable)
@@ -1,1089 +1,1103 b''
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import, print_function
9 9
10 10 import difflib
11 11 import errno
12 12 import getopt
13 13 import os
14 14 import pdb
15 15 import re
16 16 import signal
17 17 import sys
18 18 import time
19 19 import traceback
20 20
21 21
22 22 from .i18n import _
23 23
24 24 from . import (
25 25 cmdutil,
26 26 color,
27 27 commands,
28 28 demandimport,
29 29 encoding,
30 30 error,
31 31 extensions,
32 32 fancyopts,
33 33 help,
34 34 hg,
35 35 hook,
36 36 profiling,
37 37 pycompat,
38 38 registrar,
39 39 scmutil,
40 40 ui as uimod,
41 41 util,
42 42 )
43 43
44 44 unrecoverablewrite = registrar.command.unrecoverablewrite
45 45
46 46 class request(object):
47 47 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
48 48 ferr=None, prereposetups=None):
49 49 self.args = args
50 50 self.ui = ui
51 51 self.repo = repo
52 52
53 53 # input/output/error streams
54 54 self.fin = fin
55 55 self.fout = fout
56 56 self.ferr = ferr
57 57
58 58 # remember options pre-parsed by _earlyreqopt*()
59 59 self.earlyoptions = {}
60 60
61 61 # reposetups which run before extensions, useful for chg to pre-fill
62 62 # low-level repo state (for example, changelog) before extensions.
63 63 self.prereposetups = prereposetups or []
64 64
65 65 def _runexithandlers(self):
66 66 exc = None
67 67 handlers = self.ui._exithandlers
68 68 try:
69 69 while handlers:
70 70 func, args, kwargs = handlers.pop()
71 71 try:
72 72 func(*args, **kwargs)
73 73 except: # re-raises below
74 74 if exc is None:
75 75 exc = sys.exc_info()[1]
76 76 self.ui.warn(('error in exit handlers:\n'))
77 77 self.ui.traceback(force=True)
78 78 finally:
79 79 if exc is not None:
80 80 raise exc
81 81
82 82 def run():
83 83 "run the command in sys.argv"
84 84 _initstdio()
85 85 req = request(pycompat.sysargv[1:])
86 86 err = None
87 87 try:
88 88 status = (dispatch(req) or 0) & 255
89 89 except error.StdioError as e:
90 90 err = e
91 91 status = -1
92 92 if util.safehasattr(req.ui, 'fout'):
93 93 try:
94 94 req.ui.fout.flush()
95 95 except IOError as e:
96 96 err = e
97 97 status = -1
98 98 if util.safehasattr(req.ui, 'ferr'):
99 99 if err is not None and err.errno != errno.EPIPE:
100 100 req.ui.ferr.write('abort: %s\n' %
101 101 encoding.strtolocal(err.strerror))
102 102 req.ui.ferr.flush()
103 103 sys.exit(status & 255)
104 104
105 105 def _initstdio():
106 106 for fp in (sys.stdin, sys.stdout, sys.stderr):
107 107 util.setbinary(fp)
108 108
109 109 def _getsimilar(symbols, value):
110 110 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
111 111 # The cutoff for similarity here is pretty arbitrary. It should
112 112 # probably be investigated and tweaked.
113 113 return [s for s in symbols if sim(s) > 0.6]
114 114
115 115 def _reportsimilar(write, similar):
116 116 if len(similar) == 1:
117 117 write(_("(did you mean %s?)\n") % similar[0])
118 118 elif similar:
119 119 ss = ", ".join(sorted(similar))
120 120 write(_("(did you mean one of %s?)\n") % ss)
121 121
122 122 def _formatparse(write, inst):
123 123 similar = []
124 124 if isinstance(inst, error.UnknownIdentifier):
125 125 # make sure to check fileset first, as revset can invoke fileset
126 126 similar = _getsimilar(inst.symbols, inst.function)
127 127 if len(inst.args) > 1:
128 128 write(_("hg: parse error at %s: %s\n") %
129 129 (inst.args[1], inst.args[0]))
130 130 if (inst.args[0][0] == ' '):
131 131 write(_("unexpected leading whitespace\n"))
132 132 else:
133 133 write(_("hg: parse error: %s\n") % inst.args[0])
134 134 _reportsimilar(write, similar)
135 135 if inst.hint:
136 136 write(_("(%s)\n") % inst.hint)
137 137
138 138 def _formatargs(args):
139 139 return ' '.join(util.shellquote(a) for a in args)
140 140
141 141 def dispatch(req):
142 142 "run the command specified in req.args"
143 143 if req.ferr:
144 144 ferr = req.ferr
145 145 elif req.ui:
146 146 ferr = req.ui.ferr
147 147 else:
148 148 ferr = util.stderr
149 149
150 150 try:
151 151 if not req.ui:
152 152 req.ui = uimod.ui.load()
153 153 if _earlyreqoptbool(req, 'traceback', ['--traceback']):
154 154 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
155 155
156 156 # set ui streams from the request
157 157 if req.fin:
158 158 req.ui.fin = req.fin
159 159 if req.fout:
160 160 req.ui.fout = req.fout
161 161 if req.ferr:
162 162 req.ui.ferr = req.ferr
163 163 except error.Abort as inst:
164 164 ferr.write(_("abort: %s\n") % inst)
165 165 if inst.hint:
166 166 ferr.write(_("(%s)\n") % inst.hint)
167 167 return -1
168 168 except error.ParseError as inst:
169 169 _formatparse(ferr.write, inst)
170 170 return -1
171 171
172 172 msg = _formatargs(req.args)
173 173 starttime = util.timer()
174 174 ret = None
175 175 try:
176 176 ret = _runcatch(req)
177 177 except error.ProgrammingError as inst:
178 178 req.ui.warn(_('** ProgrammingError: %s\n') % inst)
179 179 if inst.hint:
180 180 req.ui.warn(_('** (%s)\n') % inst.hint)
181 181 raise
182 182 except KeyboardInterrupt as inst:
183 183 try:
184 184 if isinstance(inst, error.SignalInterrupt):
185 185 msg = _("killed!\n")
186 186 else:
187 187 msg = _("interrupted!\n")
188 188 req.ui.warn(msg)
189 189 except error.SignalInterrupt:
190 190 # maybe pager would quit without consuming all the output, and
191 191 # SIGPIPE was raised. we cannot print anything in this case.
192 192 pass
193 193 except IOError as inst:
194 194 if inst.errno != errno.EPIPE:
195 195 raise
196 196 ret = -1
197 197 finally:
198 198 duration = util.timer() - starttime
199 199 req.ui.flush()
200 200 if req.ui.logblockedtimes:
201 201 req.ui._blockedtimes['command_duration'] = duration * 1000
202 202 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
203 203 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
204 204 msg, ret or 0, duration)
205 205 try:
206 206 req._runexithandlers()
207 207 except: # exiting, so no re-raises
208 208 ret = ret or -1
209 209 return ret
210 210
211 211 def _runcatch(req):
212 212 def catchterm(*args):
213 213 raise error.SignalInterrupt
214 214
215 215 ui = req.ui
216 216 try:
217 217 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
218 218 num = getattr(signal, name, None)
219 219 if num:
220 220 signal.signal(num, catchterm)
221 221 except ValueError:
222 222 pass # happens if called in a thread
223 223
224 224 def _runcatchfunc():
225 225 realcmd = None
226 226 try:
227 227 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
228 228 cmd = cmdargs[0]
229 229 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
230 230 realcmd = aliases[0]
231 231 except (error.UnknownCommand, error.AmbiguousCommand,
232 232 IndexError, getopt.GetoptError):
233 233 # Don't handle this here. We know the command is
234 234 # invalid, but all we're worried about for now is that
235 235 # it's not a command that server operators expect to
236 236 # be safe to offer to users in a sandbox.
237 237 pass
238 238 if realcmd == 'serve' and '--stdio' in cmdargs:
239 239 # We want to constrain 'hg serve --stdio' instances pretty
240 240 # closely, as many shared-ssh access tools want to grant
241 241 # access to run *only* 'hg -R $repo serve --stdio'. We
242 242 # restrict to exactly that set of arguments, and prohibit
243 243 # any repo name that starts with '--' to prevent
244 244 # shenanigans wherein a user does something like pass
245 245 # --debugger or --config=ui.debugger=1 as a repo
246 246 # name. This used to actually run the debugger.
247 247 if (len(req.args) != 4 or
248 248 req.args[0] != '-R' or
249 249 req.args[1].startswith('--') or
250 250 req.args[2] != 'serve' or
251 251 req.args[3] != '--stdio'):
252 252 raise error.Abort(
253 253 _('potentially unsafe serve --stdio invocation: %r') %
254 254 (req.args,))
255 255
256 256 try:
257 257 debugger = 'pdb'
258 258 debugtrace = {
259 259 'pdb': pdb.set_trace
260 260 }
261 261 debugmortem = {
262 262 'pdb': pdb.post_mortem
263 263 }
264 264
265 265 # read --config before doing anything else
266 266 # (e.g. to change trust settings for reading .hg/hgrc)
267 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
267 cfgs = _parseconfig(req.ui,
268 _earlyreqopt(req, 'config', ['--config']))
268 269
269 270 if req.repo:
270 271 # copy configs that were passed on the cmdline (--config) to
271 272 # the repo ui
272 273 for sec, name, val in cfgs:
273 274 req.repo.ui.setconfig(sec, name, val, source='--config')
274 275
275 276 # developer config: ui.debugger
276 277 debugger = ui.config("ui", "debugger")
277 278 debugmod = pdb
278 279 if not debugger or ui.plain():
279 280 # if we are in HGPLAIN mode, then disable custom debugging
280 281 debugger = 'pdb'
281 282 elif _earlyreqoptbool(req, 'debugger', ['--debugger']):
282 283 # This import can be slow for fancy debuggers, so only
283 284 # do it when absolutely necessary, i.e. when actual
284 285 # debugging has been requested
285 286 with demandimport.deactivated():
286 287 try:
287 288 debugmod = __import__(debugger)
288 289 except ImportError:
289 290 pass # Leave debugmod = pdb
290 291
291 292 debugtrace[debugger] = debugmod.set_trace
292 293 debugmortem[debugger] = debugmod.post_mortem
293 294
294 295 # enter the debugger before command execution
295 296 if _earlyreqoptbool(req, 'debugger', ['--debugger']):
296 297 ui.warn(_("entering debugger - "
297 298 "type c to continue starting hg or h for help\n"))
298 299
299 300 if (debugger != 'pdb' and
300 301 debugtrace[debugger] == debugtrace['pdb']):
301 302 ui.warn(_("%s debugger specified "
302 303 "but its module was not found\n") % debugger)
303 304 with demandimport.deactivated():
304 305 debugtrace[debugger]()
305 306 try:
306 307 return _dispatch(req)
307 308 finally:
308 309 ui.flush()
309 310 except: # re-raises
310 311 # enter the debugger when we hit an exception
311 312 if _earlyreqoptbool(req, 'debugger', ['--debugger']):
312 313 traceback.print_exc()
313 314 debugmortem[debugger](sys.exc_info()[2])
314 315 raise
315 316
316 317 return _callcatch(ui, _runcatchfunc)
317 318
318 319 def _callcatch(ui, func):
319 320 """like scmutil.callcatch but handles more high-level exceptions about
320 321 config parsing and commands. besides, use handlecommandexception to handle
321 322 uncaught exceptions.
322 323 """
323 324 try:
324 325 return scmutil.callcatch(ui, func)
325 326 except error.AmbiguousCommand as inst:
326 327 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
327 328 (inst.args[0], " ".join(inst.args[1])))
328 329 except error.CommandError as inst:
329 330 if inst.args[0]:
330 331 ui.pager('help')
331 332 msgbytes = pycompat.bytestr(inst.args[1])
332 333 ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
333 334 commands.help_(ui, inst.args[0], full=False, command=True)
334 335 else:
335 336 ui.pager('help')
336 337 ui.warn(_("hg: %s\n") % inst.args[1])
337 338 commands.help_(ui, 'shortlist')
338 339 except error.ParseError as inst:
339 340 _formatparse(ui.warn, inst)
340 341 return -1
341 342 except error.UnknownCommand as inst:
342 343 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
343 344 try:
344 345 # check if the command is in a disabled extension
345 346 # (but don't check for extensions themselves)
346 347 formatted = help.formattedhelp(ui, commands, inst.args[0],
347 348 unknowncmd=True)
348 349 ui.warn(nocmdmsg)
349 350 ui.write(formatted)
350 351 except (error.UnknownCommand, error.Abort):
351 352 suggested = False
352 353 if len(inst.args) == 2:
353 354 sim = _getsimilar(inst.args[1], inst.args[0])
354 355 if sim:
355 356 ui.warn(nocmdmsg)
356 357 _reportsimilar(ui.warn, sim)
357 358 suggested = True
358 359 if not suggested:
359 360 ui.pager('help')
360 361 ui.warn(nocmdmsg)
361 362 commands.help_(ui, 'shortlist')
362 363 except IOError:
363 364 raise
364 365 except KeyboardInterrupt:
365 366 raise
366 367 except: # probably re-raises
367 368 if not handlecommandexception(ui):
368 369 raise
369 370
370 371 return -1
371 372
372 373 def aliasargs(fn, givenargs):
373 374 args = []
374 375 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
375 376 if not util.safehasattr(fn, '_origfunc'):
376 377 args = getattr(fn, 'args', args)
377 378 if args:
378 379 cmd = ' '.join(map(util.shellquote, args))
379 380
380 381 nums = []
381 382 def replacer(m):
382 383 num = int(m.group(1)) - 1
383 384 nums.append(num)
384 385 if num < len(givenargs):
385 386 return givenargs[num]
386 387 raise error.Abort(_('too few arguments for command alias'))
387 388 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
388 389 givenargs = [x for i, x in enumerate(givenargs)
389 390 if i not in nums]
390 391 args = pycompat.shlexsplit(cmd)
391 392 return args + givenargs
392 393
393 394 def aliasinterpolate(name, args, cmd):
394 395 '''interpolate args into cmd for shell aliases
395 396
396 397 This also handles $0, $@ and "$@".
397 398 '''
398 399 # util.interpolate can't deal with "$@" (with quotes) because it's only
399 400 # built to match prefix + patterns.
400 401 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
401 402 replacemap['$0'] = name
402 403 replacemap['$$'] = '$'
403 404 replacemap['$@'] = ' '.join(args)
404 405 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
405 406 # parameters, separated out into words. Emulate the same behavior here by
406 407 # quoting the arguments individually. POSIX shells will then typically
407 408 # tokenize each argument into exactly one word.
408 409 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
409 410 # escape '\$' for regex
410 411 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
411 412 r = re.compile(regex)
412 413 return r.sub(lambda x: replacemap[x.group()], cmd)
413 414
414 415 class cmdalias(object):
415 416 def __init__(self, name, definition, cmdtable, source):
416 417 self.name = self.cmd = name
417 418 self.cmdname = ''
418 419 self.definition = definition
419 420 self.fn = None
420 421 self.givenargs = []
421 422 self.opts = []
422 423 self.help = ''
423 424 self.badalias = None
424 425 self.unknowncmd = False
425 426 self.source = source
426 427
427 428 try:
428 429 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
429 430 for alias, e in cmdtable.iteritems():
430 431 if e is entry:
431 432 self.cmd = alias
432 433 break
433 434 self.shadows = True
434 435 except error.UnknownCommand:
435 436 self.shadows = False
436 437
437 438 if not self.definition:
438 439 self.badalias = _("no definition for alias '%s'") % self.name
439 440 return
440 441
441 442 if self.definition.startswith('!'):
442 443 self.shell = True
443 444 def fn(ui, *args):
444 445 env = {'HG_ARGS': ' '.join((self.name,) + args)}
445 446 def _checkvar(m):
446 447 if m.groups()[0] == '$':
447 448 return m.group()
448 449 elif int(m.groups()[0]) <= len(args):
449 450 return m.group()
450 451 else:
451 452 ui.debug("No argument found for substitution "
452 453 "of %i variable in alias '%s' definition."
453 454 % (int(m.groups()[0]), self.name))
454 455 return ''
455 456 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
456 457 cmd = aliasinterpolate(self.name, args, cmd)
457 458 return ui.system(cmd, environ=env,
458 459 blockedtag='alias_%s' % self.name)
459 460 self.fn = fn
460 461 return
461 462
462 463 try:
463 464 args = pycompat.shlexsplit(self.definition)
464 465 except ValueError as inst:
465 466 self.badalias = (_("error in definition for alias '%s': %s")
466 467 % (self.name, inst))
467 468 return
468 469 self.cmdname = cmd = args.pop(0)
469 470 self.givenargs = args
470 471
471 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
472 for invalidarg in commands.earlyoptflags:
472 473 if _earlygetopt([invalidarg], args):
473 474 self.badalias = (_("error in definition for alias '%s': %s may "
474 475 "only be given on the command line")
475 476 % (self.name, invalidarg))
476 477 return
477 478
478 479 try:
479 480 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
480 481 if len(tableentry) > 2:
481 482 self.fn, self.opts, self.help = tableentry
482 483 else:
483 484 self.fn, self.opts = tableentry
484 485
485 486 if self.help.startswith("hg " + cmd):
486 487 # drop prefix in old-style help lines so hg shows the alias
487 488 self.help = self.help[4 + len(cmd):]
488 489 self.__doc__ = self.fn.__doc__
489 490
490 491 except error.UnknownCommand:
491 492 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
492 493 % (self.name, cmd))
493 494 self.unknowncmd = True
494 495 except error.AmbiguousCommand:
495 496 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
496 497 % (self.name, cmd))
497 498
498 499 @property
499 500 def args(self):
500 501 args = pycompat.maplist(util.expandpath, self.givenargs)
501 502 return aliasargs(self.fn, args)
502 503
503 504 def __getattr__(self, name):
504 505 adefaults = {r'norepo': True, r'cmdtype': unrecoverablewrite,
505 506 r'optionalrepo': False, r'inferrepo': False}
506 507 if name not in adefaults:
507 508 raise AttributeError(name)
508 509 if self.badalias or util.safehasattr(self, 'shell'):
509 510 return adefaults[name]
510 511 return getattr(self.fn, name)
511 512
512 513 def __call__(self, ui, *args, **opts):
513 514 if self.badalias:
514 515 hint = None
515 516 if self.unknowncmd:
516 517 try:
517 518 # check if the command is in a disabled extension
518 519 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
519 520 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
520 521 except error.UnknownCommand:
521 522 pass
522 523 raise error.Abort(self.badalias, hint=hint)
523 524 if self.shadows:
524 525 ui.debug("alias '%s' shadows command '%s'\n" %
525 526 (self.name, self.cmdname))
526 527
527 528 ui.log('commandalias', "alias '%s' expands to '%s'\n",
528 529 self.name, self.definition)
529 530 if util.safehasattr(self, 'shell'):
530 531 return self.fn(ui, *args, **opts)
531 532 else:
532 533 try:
533 534 return util.checksignature(self.fn)(ui, *args, **opts)
534 535 except error.SignatureError:
535 536 args = ' '.join([self.cmdname] + self.args)
536 537 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
537 538 raise
538 539
539 540 class lazyaliasentry(object):
540 541 """like a typical command entry (func, opts, help), but is lazy"""
541 542
542 543 def __init__(self, name, definition, cmdtable, source):
543 544 self.name = name
544 545 self.definition = definition
545 546 self.cmdtable = cmdtable.copy()
546 547 self.source = source
547 548
548 549 @util.propertycache
549 550 def _aliasdef(self):
550 551 return cmdalias(self.name, self.definition, self.cmdtable, self.source)
551 552
552 553 def __getitem__(self, n):
553 554 aliasdef = self._aliasdef
554 555 if n == 0:
555 556 return aliasdef
556 557 elif n == 1:
557 558 return aliasdef.opts
558 559 elif n == 2:
559 560 return aliasdef.help
560 561 else:
561 562 raise IndexError
562 563
563 564 def __iter__(self):
564 565 for i in range(3):
565 566 yield self[i]
566 567
567 568 def __len__(self):
568 569 return 3
569 570
570 571 def addaliases(ui, cmdtable):
571 572 # aliases are processed after extensions have been loaded, so they
572 573 # may use extension commands. Aliases can also use other alias definitions,
573 574 # but only if they have been defined prior to the current definition.
574 575 for alias, definition in ui.configitems('alias'):
575 576 try:
576 577 if cmdtable[alias].definition == definition:
577 578 continue
578 579 except (KeyError, AttributeError):
579 580 # definition might not exist or it might not be a cmdalias
580 581 pass
581 582
582 583 source = ui.configsource('alias', alias)
583 584 entry = lazyaliasentry(alias, definition, cmdtable, source)
584 585 cmdtable[alias] = entry
585 586
586 587 def _parse(ui, args):
587 588 options = {}
588 589 cmdoptions = {}
589 590
590 591 try:
591 592 args = fancyopts.fancyopts(args, commands.globalopts, options)
592 593 except getopt.GetoptError as inst:
593 594 raise error.CommandError(None, inst)
594 595
595 596 if args:
596 597 cmd, args = args[0], args[1:]
597 598 aliases, entry = cmdutil.findcmd(cmd, commands.table,
598 599 ui.configbool("ui", "strict"))
599 600 cmd = aliases[0]
600 601 args = aliasargs(entry[0], args)
601 602 defaults = ui.config("defaults", cmd)
602 603 if defaults:
603 604 args = pycompat.maplist(
604 605 util.expandpath, pycompat.shlexsplit(defaults)) + args
605 606 c = list(entry[1])
606 607 else:
607 608 cmd = None
608 609 c = []
609 610
610 611 # combine global options into local
611 612 for o in commands.globalopts:
612 613 c.append((o[0], o[1], options[o[1]], o[3]))
613 614
614 615 try:
615 616 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
616 617 except getopt.GetoptError as inst:
617 618 raise error.CommandError(cmd, inst)
618 619
619 620 # separate global options back out
620 621 for o in commands.globalopts:
621 622 n = o[1]
622 623 options[n] = cmdoptions[n]
623 624 del cmdoptions[n]
624 625
625 626 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
626 627
627 628 def _parseconfig(ui, config):
628 629 """parse the --config options from the command line"""
629 630 configs = []
630 631
631 632 for cfg in config:
632 633 try:
633 634 name, value = [cfgelem.strip()
634 635 for cfgelem in cfg.split('=', 1)]
635 636 section, name = name.split('.', 1)
636 637 if not section or not name:
637 638 raise IndexError
638 639 ui.setconfig(section, name, value, '--config')
639 640 configs.append((section, name, value))
640 641 except (IndexError, ValueError):
641 642 raise error.Abort(_('malformed --config option: %r '
642 643 '(use --config section.name=value)') % cfg)
643 644
644 645 return configs
645 646
646 647 def _earlygetopt(aliases, args, strip=True):
647 648 """Return list of values for an option (or aliases).
648 649
649 650 The values are listed in the order they appear in args.
650 651 The options and values are removed from args if strip=True.
651 652
652 653 >>> args = [b'x', b'--cwd', b'foo', b'y']
653 654 >>> _earlygetopt([b'--cwd'], args), args
654 655 (['foo'], ['x', 'y'])
655 656
656 657 >>> args = [b'x', b'--cwd=bar', b'y']
657 658 >>> _earlygetopt([b'--cwd'], args), args
658 659 (['bar'], ['x', 'y'])
659 660
660 661 >>> args = [b'x', b'--cwd=bar', b'y']
661 662 >>> _earlygetopt([b'--cwd'], args, strip=False), args
662 663 (['bar'], ['x', '--cwd=bar', 'y'])
663 664
664 665 >>> args = [b'x', b'-R', b'foo', b'y']
665 666 >>> _earlygetopt([b'-R'], args), args
666 667 (['foo'], ['x', 'y'])
667 668
668 669 >>> args = [b'x', b'-R', b'foo', b'y']
669 670 >>> _earlygetopt([b'-R'], args, strip=False), args
670 671 (['foo'], ['x', '-R', 'foo', 'y'])
671 672
672 673 >>> args = [b'x', b'-Rbar', b'y']
673 674 >>> _earlygetopt([b'-R'], args), args
674 675 (['bar'], ['x', 'y'])
675 676
676 677 >>> args = [b'x', b'-Rbar', b'y']
677 678 >>> _earlygetopt([b'-R'], args, strip=False), args
678 679 (['bar'], ['x', '-Rbar', 'y'])
679 680
680 681 >>> args = [b'x', b'-R=bar', b'y']
681 682 >>> _earlygetopt([b'-R'], args), args
682 683 (['=bar'], ['x', 'y'])
683 684
684 685 >>> args = [b'x', b'-R', b'--', b'y']
685 686 >>> _earlygetopt([b'-R'], args), args
686 687 ([], ['x', '-R', '--', 'y'])
687 688 """
688 689 try:
689 690 argcount = args.index("--")
690 691 except ValueError:
691 692 argcount = len(args)
692 693 shortopts = [opt for opt in aliases if len(opt) == 2]
693 694 values = []
694 695 pos = 0
695 696 while pos < argcount:
696 697 fullarg = arg = args[pos]
697 698 equals = -1
698 699 if arg.startswith('--'):
699 700 equals = arg.find('=')
700 701 if equals > -1:
701 702 arg = arg[:equals]
702 703 if arg in aliases:
703 704 if equals > -1:
704 705 values.append(fullarg[equals + 1:])
705 706 if strip:
706 707 del args[pos]
707 708 argcount -= 1
708 709 else:
709 710 pos += 1
710 711 else:
711 712 if pos + 1 >= argcount:
712 713 # ignore and let getopt report an error if there is no value
713 714 break
714 715 values.append(args[pos + 1])
715 716 if strip:
716 717 del args[pos:pos + 2]
717 718 argcount -= 2
718 719 else:
719 720 pos += 2
720 721 elif arg[:2] in shortopts:
721 722 # short option can have no following space, e.g. hg log -Rfoo
722 723 values.append(args[pos][2:])
723 724 if strip:
724 725 del args[pos]
725 726 argcount -= 1
726 727 else:
727 728 pos += 1
728 729 else:
729 730 pos += 1
730 731 return values
731 732
733 def _earlyreqopt(req, name, aliases):
734 """Peek a list option without using a full options table"""
735 values = _earlygetopt(aliases, req.args, strip=False)
736 req.earlyoptions[name] = values
737 return values
738
739 def _earlyreqoptstr(req, name, aliases):
740 """Peek a string option without using a full options table"""
741 value = (_earlygetopt(aliases, req.args, strip=False) or [''])[-1]
742 req.earlyoptions[name] = value
743 return value
744
732 745 def _earlyreqoptbool(req, name, aliases):
733 746 """Peek a boolean option without using a full options table
734 747
735 748 >>> req = request([b'x', b'--debugger'])
736 749 >>> _earlyreqoptbool(req, b'debugger', [b'--debugger'])
737 750 True
738 751
739 752 >>> req = request([b'x', b'--', b'--debugger'])
740 753 >>> _earlyreqoptbool(req, b'debugger', [b'--debugger'])
741 754 """
742 755 try:
743 756 argcount = req.args.index("--")
744 757 except ValueError:
745 758 argcount = len(req.args)
746 759 value = None
747 760 pos = 0
748 761 while pos < argcount:
749 762 arg = req.args[pos]
750 763 if arg in aliases:
751 764 value = True
752 765 pos += 1
753 766 req.earlyoptions[name] = value
754 767 return value
755 768
756 769 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
757 770 # run pre-hook, and abort if it fails
758 771 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
759 772 pats=cmdpats, opts=cmdoptions)
760 773 try:
761 774 ret = _runcommand(ui, options, cmd, d)
762 775 # run post-hook, passing command result
763 776 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
764 777 result=ret, pats=cmdpats, opts=cmdoptions)
765 778 except Exception:
766 779 # run failure hook and re-raise
767 780 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
768 781 pats=cmdpats, opts=cmdoptions)
769 782 raise
770 783 return ret
771 784
772 785 def _getlocal(ui, rpath, wd=None):
773 786 """Return (path, local ui object) for the given target path.
774 787
775 788 Takes paths in [cwd]/.hg/hgrc into account."
776 789 """
777 790 if wd is None:
778 791 try:
779 792 wd = pycompat.getcwd()
780 793 except OSError as e:
781 794 raise error.Abort(_("error getting current working directory: %s") %
782 795 encoding.strtolocal(e.strerror))
783 796 path = cmdutil.findrepo(wd) or ""
784 797 if not path:
785 798 lui = ui
786 799 else:
787 800 lui = ui.copy()
788 801 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
789 802
790 803 if rpath:
791 804 path = lui.expandpath(rpath)
792 805 lui = ui.copy()
793 806 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
794 807
795 808 return path, lui
796 809
797 810 def _checkshellalias(lui, ui, args):
798 811 """Return the function to run the shell alias, if it is required"""
799 812 options = {}
800 813
801 814 try:
802 815 args = fancyopts.fancyopts(args, commands.globalopts, options)
803 816 except getopt.GetoptError:
804 817 return
805 818
806 819 if not args:
807 820 return
808 821
809 822 cmdtable = commands.table
810 823
811 824 cmd = args[0]
812 825 try:
813 826 strict = ui.configbool("ui", "strict")
814 827 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
815 828 except (error.AmbiguousCommand, error.UnknownCommand):
816 829 return
817 830
818 831 cmd = aliases[0]
819 832 fn = entry[0]
820 833
821 834 if cmd and util.safehasattr(fn, 'shell'):
835 # shell alias shouldn't receive early options which are consumed by hg
836 args = args[:]
837 _earlygetopt(commands.earlyoptflags, args, strip=True)
822 838 d = lambda: fn(ui, *args[1:])
823 839 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
824 840 [], {})
825 841
826 842 def _dispatch(req):
827 843 args = req.args
828 844 ui = req.ui
829 845
830 846 # check for cwd
831 cwd = _earlygetopt(['--cwd'], args)
832 cwd = cwd and cwd[-1] or ''
847 cwd = _earlyreqoptstr(req, 'cwd', ['--cwd'])
833 848 if cwd:
834 849 os.chdir(cwd)
835 850
836 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
837 rpath = rpath and rpath[-1] or ''
851 rpath = _earlyreqoptstr(req, 'repository', ["-R", "--repository", "--repo"])
838 852 path, lui = _getlocal(ui, rpath)
839 853
840 854 uis = {ui, lui}
841 855
842 856 if req.repo:
843 857 uis.add(req.repo.ui)
844 858
845 859 if _earlyreqoptbool(req, 'profile', ['--profile']):
846 860 for ui_ in uis:
847 861 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
848 862
849 863 profile = lui.configbool('profiling', 'enabled')
850 864 with profiling.profile(lui, enabled=profile) as profiler:
851 865 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
852 866 # reposetup
853 867 extensions.loadall(lui)
854 868 # Propagate any changes to lui.__class__ by extensions
855 869 ui.__class__ = lui.__class__
856 870
857 871 # (uisetup and extsetup are handled in extensions.loadall)
858 872
859 873 # (reposetup is handled in hg.repository)
860 874
861 875 addaliases(lui, commands.table)
862 876
863 877 # All aliases and commands are completely defined, now.
864 878 # Check abbreviation/ambiguity of shell alias.
865 879 shellaliasfn = _checkshellalias(lui, ui, args)
866 880 if shellaliasfn:
867 881 return shellaliasfn()
868 882
869 883 # check for fallback encoding
870 884 fallback = lui.config('ui', 'fallbackencoding')
871 885 if fallback:
872 886 encoding.fallbackencoding = fallback
873 887
874 888 fullargs = args
875 889 cmd, func, args, options, cmdoptions = _parse(lui, args)
876 890
877 if options["config"]:
891 if options["config"] != req.earlyoptions["config"]:
878 892 raise error.Abort(_("option --config may not be abbreviated!"))
879 if options["cwd"]:
893 if options["cwd"] != req.earlyoptions["cwd"]:
880 894 raise error.Abort(_("option --cwd may not be abbreviated!"))
881 if options["repository"]:
895 if options["repository"] != req.earlyoptions["repository"]:
882 896 raise error.Abort(_(
883 897 "option -R has to be separated from other options (e.g. not "
884 898 "-qR) and --repository may only be abbreviated as --repo!"))
885 899 if options["debugger"] != req.earlyoptions["debugger"]:
886 900 raise error.Abort(_("option --debugger may not be abbreviated!"))
887 901 # don't validate --profile/--traceback, which can be enabled from now
888 902
889 903 if options["encoding"]:
890 904 encoding.encoding = options["encoding"]
891 905 if options["encodingmode"]:
892 906 encoding.encodingmode = options["encodingmode"]
893 907 if options["time"]:
894 908 def get_times():
895 909 t = os.times()
896 910 if t[4] == 0.0:
897 911 # Windows leaves this as zero, so use time.clock()
898 912 t = (t[0], t[1], t[2], t[3], time.clock())
899 913 return t
900 914 s = get_times()
901 915 def print_time():
902 916 t = get_times()
903 917 ui.warn(
904 918 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
905 919 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
906 920 ui.atexit(print_time)
907 921 if options["profile"]:
908 922 profiler.start()
909 923
910 924 if options['verbose'] or options['debug'] or options['quiet']:
911 925 for opt in ('verbose', 'debug', 'quiet'):
912 926 val = str(bool(options[opt]))
913 927 if pycompat.ispy3:
914 928 val = val.encode('ascii')
915 929 for ui_ in uis:
916 930 ui_.setconfig('ui', opt, val, '--' + opt)
917 931
918 932 if options['traceback']:
919 933 for ui_ in uis:
920 934 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
921 935
922 936 if options['noninteractive']:
923 937 for ui_ in uis:
924 938 ui_.setconfig('ui', 'interactive', 'off', '-y')
925 939
926 940 if cmdoptions.get('insecure', False):
927 941 for ui_ in uis:
928 942 ui_.insecureconnections = True
929 943
930 944 # setup color handling before pager, because setting up pager
931 945 # might cause incorrect console information
932 946 coloropt = options['color']
933 947 for ui_ in uis:
934 948 if coloropt:
935 949 ui_.setconfig('ui', 'color', coloropt, '--color')
936 950 color.setup(ui_)
937 951
938 952 if util.parsebool(options['pager']):
939 953 # ui.pager() expects 'internal-always-' prefix in this case
940 954 ui.pager('internal-always-' + cmd)
941 955 elif options['pager'] != 'auto':
942 956 for ui_ in uis:
943 957 ui_.disablepager()
944 958
945 959 if options['version']:
946 960 return commands.version_(ui)
947 961 if options['help']:
948 962 return commands.help_(ui, cmd, command=cmd is not None)
949 963 elif not cmd:
950 964 return commands.help_(ui, 'shortlist')
951 965
952 966 repo = None
953 967 cmdpats = args[:]
954 968 if not func.norepo:
955 969 # use the repo from the request only if we don't have -R
956 970 if not rpath and not cwd:
957 971 repo = req.repo
958 972
959 973 if repo:
960 974 # set the descriptors of the repo ui to those of ui
961 975 repo.ui.fin = ui.fin
962 976 repo.ui.fout = ui.fout
963 977 repo.ui.ferr = ui.ferr
964 978 else:
965 979 try:
966 980 repo = hg.repository(ui, path=path,
967 981 presetupfuncs=req.prereposetups)
968 982 if not repo.local():
969 983 raise error.Abort(_("repository '%s' is not local")
970 984 % path)
971 985 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
972 986 'repo')
973 987 except error.RequirementError:
974 988 raise
975 989 except error.RepoError:
976 990 if rpath: # invalid -R path
977 991 raise
978 992 if not func.optionalrepo:
979 993 if func.inferrepo and args and not path:
980 994 # try to infer -R from command args
981 995 repos = map(cmdutil.findrepo, args)
982 996 guess = repos[0]
983 997 if guess and repos.count(guess) == len(repos):
984 998 req.args = ['--repository', guess] + fullargs
985 999 return _dispatch(req)
986 1000 if not path:
987 1001 raise error.RepoError(_("no repository found in"
988 1002 " '%s' (.hg not found)")
989 1003 % pycompat.getcwd())
990 1004 raise
991 1005 if repo:
992 1006 ui = repo.ui
993 1007 if options['hidden']:
994 1008 repo = repo.unfiltered()
995 1009 args.insert(0, repo)
996 1010 elif rpath:
997 1011 ui.warn(_("warning: --repository ignored\n"))
998 1012
999 1013 msg = _formatargs(fullargs)
1000 1014 ui.log("command", '%s\n', msg)
1001 1015 strcmdopt = pycompat.strkwargs(cmdoptions)
1002 1016 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1003 1017 try:
1004 1018 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
1005 1019 cmdpats, cmdoptions)
1006 1020 finally:
1007 1021 if repo and repo != req.repo:
1008 1022 repo.close()
1009 1023
1010 1024 def _runcommand(ui, options, cmd, cmdfunc):
1011 1025 """Run a command function, possibly with profiling enabled."""
1012 1026 try:
1013 1027 return cmdfunc()
1014 1028 except error.SignatureError:
1015 1029 raise error.CommandError(cmd, _('invalid arguments'))
1016 1030
1017 1031 def _exceptionwarning(ui):
1018 1032 """Produce a warning message for the current active exception"""
1019 1033
1020 1034 # For compatibility checking, we discard the portion of the hg
1021 1035 # version after the + on the assumption that if a "normal
1022 1036 # user" is running a build with a + in it the packager
1023 1037 # probably built from fairly close to a tag and anyone with a
1024 1038 # 'make local' copy of hg (where the version number can be out
1025 1039 # of date) will be clueful enough to notice the implausible
1026 1040 # version number and try updating.
1027 1041 ct = util.versiontuple(n=2)
1028 1042 worst = None, ct, ''
1029 1043 if ui.config('ui', 'supportcontact') is None:
1030 1044 for name, mod in extensions.extensions():
1031 1045 testedwith = getattr(mod, 'testedwith', '')
1032 1046 if pycompat.ispy3 and isinstance(testedwith, str):
1033 1047 testedwith = testedwith.encode(u'utf-8')
1034 1048 report = getattr(mod, 'buglink', _('the extension author.'))
1035 1049 if not testedwith.strip():
1036 1050 # We found an untested extension. It's likely the culprit.
1037 1051 worst = name, 'unknown', report
1038 1052 break
1039 1053
1040 1054 # Never blame on extensions bundled with Mercurial.
1041 1055 if extensions.ismoduleinternal(mod):
1042 1056 continue
1043 1057
1044 1058 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1045 1059 if ct in tested:
1046 1060 continue
1047 1061
1048 1062 lower = [t for t in tested if t < ct]
1049 1063 nearest = max(lower or tested)
1050 1064 if worst[0] is None or nearest < worst[1]:
1051 1065 worst = name, nearest, report
1052 1066 if worst[0] is not None:
1053 1067 name, testedwith, report = worst
1054 1068 if not isinstance(testedwith, (bytes, str)):
1055 1069 testedwith = '.'.join([str(c) for c in testedwith])
1056 1070 warning = (_('** Unknown exception encountered with '
1057 1071 'possibly-broken third-party extension %s\n'
1058 1072 '** which supports versions %s of Mercurial.\n'
1059 1073 '** Please disable %s and try your action again.\n'
1060 1074 '** If that fixes the bug please report it to %s\n')
1061 1075 % (name, testedwith, name, report))
1062 1076 else:
1063 1077 bugtracker = ui.config('ui', 'supportcontact')
1064 1078 if bugtracker is None:
1065 1079 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
1066 1080 warning = (_("** unknown exception encountered, "
1067 1081 "please report by visiting\n** ") + bugtracker + '\n')
1068 1082 if pycompat.ispy3:
1069 1083 sysversion = sys.version.encode(u'utf-8')
1070 1084 else:
1071 1085 sysversion = sys.version
1072 1086 sysversion = sysversion.replace('\n', '')
1073 1087 warning += ((_("** Python %s\n") % sysversion) +
1074 1088 (_("** Mercurial Distributed SCM (version %s)\n") %
1075 1089 util.version()) +
1076 1090 (_("** Extensions loaded: %s\n") %
1077 1091 ", ".join([x[0] for x in extensions.extensions()])))
1078 1092 return warning
1079 1093
1080 1094 def handlecommandexception(ui):
1081 1095 """Produce a warning message for broken commands
1082 1096
1083 1097 Called when handling an exception; the exception is reraised if
1084 1098 this function returns False, ignored otherwise.
1085 1099 """
1086 1100 warning = _exceptionwarning(ui)
1087 1101 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
1088 1102 ui.warn(warning)
1089 1103 return False # re-raise the exception
@@ -1,343 +1,343 b''
1 1 setup
2 2 $ cat >> $HGRCPATH <<EOF
3 3 > [extensions]
4 4 > blackbox=
5 5 > mock=$TESTDIR/mockblackbox.py
6 6 > mq=
7 7 > [alias]
8 8 > confuse = log --limit 3
9 9 > so-confusing = confuse --style compact
10 10 > EOF
11 11 $ hg init blackboxtest
12 12 $ cd blackboxtest
13 13
14 14 command, exit codes, and duration
15 15
16 16 $ echo a > a
17 17 $ hg add a
18 18 $ hg blackbox --config blackbox.dirty=True
19 19 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> init blackboxtest exited 0 after * seconds (glob)
20 20 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a
21 21 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a exited 0 after * seconds (glob)
22 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000+ (5000)> blackbox
22 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000+ (5000)> blackbox --config *blackbox.dirty=True* (glob)
23 23
24 24 alias expansion is logged
25 25 $ rm ./.hg/blackbox.log
26 26 $ hg confuse
27 27 $ hg blackbox
28 28 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> confuse
29 29 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'confuse' expands to 'log --limit 3'
30 30 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> confuse exited 0 after * seconds (glob)
31 31 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
32 32
33 33 recursive aliases work correctly
34 34 $ rm ./.hg/blackbox.log
35 35 $ hg so-confusing
36 36 $ hg blackbox
37 37 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> so-confusing
38 38 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'so-confusing' expands to 'confuse --style compact'
39 39 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'confuse' expands to 'log --limit 3'
40 40 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> so-confusing exited 0 after * seconds (glob)
41 41 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
42 42
43 43 incoming change tracking
44 44
45 45 create two heads to verify that we only see one change in the log later
46 46 $ hg commit -ma
47 47 $ hg up null
48 48 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
49 49 $ echo b > b
50 50 $ hg commit -Amb
51 51 adding b
52 52 created new head
53 53
54 54 clone, commit, pull
55 55 $ hg clone . ../blackboxtest2
56 56 updating to branch default
57 57 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
58 58 $ echo c > c
59 59 $ hg commit -Amc
60 60 adding c
61 61 $ cd ../blackboxtest2
62 62 $ hg pull
63 63 pulling from $TESTTMP/blackboxtest (glob)
64 64 searching for changes
65 65 adding changesets
66 66 adding manifests
67 67 adding file changes
68 68 added 1 changesets with 1 changes to 1 files
69 69 new changesets d02f48003e62
70 70 (run 'hg update' to get a working copy)
71 71 $ hg blackbox -l 6
72 72 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pull
73 73 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> updated served branch cache in * seconds (glob)
74 74 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> wrote served branch cache with 1 labels and 2 nodes
75 75 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> 1 incoming changes - new heads: d02f48003e62
76 76 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pull exited 0 after * seconds (glob)
77 77 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> blackbox -l 6
78 78
79 79 we must not cause a failure if we cannot write to the log
80 80
81 81 $ hg rollback
82 82 repository tip rolled back to revision 1 (undo pull)
83 83
84 84 $ mv .hg/blackbox.log .hg/blackbox.log-
85 85 $ mkdir .hg/blackbox.log
86 86 $ hg --debug incoming
87 87 warning: cannot write to blackbox.log: * (glob)
88 88 comparing with $TESTTMP/blackboxtest (glob)
89 89 query 1; heads
90 90 searching for changes
91 91 all local heads known remotely
92 92 changeset: 2:d02f48003e62c24e2659d97d30f2a83abe5d5d51
93 93 tag: tip
94 94 phase: draft
95 95 parent: 1:6563da9dcf87b1949716e38ff3e3dfaa3198eb06
96 96 parent: -1:0000000000000000000000000000000000000000
97 97 manifest: 2:ab9d46b053ebf45b7996f2922b9893ff4b63d892
98 98 user: test
99 99 date: Thu Jan 01 00:00:00 1970 +0000
100 100 files+: c
101 101 extra: branch=default
102 102 description:
103 103 c
104 104
105 105
106 106 $ hg pull
107 107 pulling from $TESTTMP/blackboxtest (glob)
108 108 searching for changes
109 109 adding changesets
110 110 adding manifests
111 111 adding file changes
112 112 added 1 changesets with 1 changes to 1 files
113 113 new changesets d02f48003e62
114 114 (run 'hg update' to get a working copy)
115 115
116 116 a failure reading from the log is fatal
117 117
118 118 $ hg blackbox -l 3
119 119 abort: *$TESTTMP/blackboxtest2/.hg/blackbox.log* (glob)
120 120 [255]
121 121
122 122 $ rmdir .hg/blackbox.log
123 123 $ mv .hg/blackbox.log- .hg/blackbox.log
124 124
125 125 backup bundles get logged
126 126
127 127 $ touch d
128 128 $ hg commit -Amd
129 129 adding d
130 130 created new head
131 131 $ hg strip tip
132 132 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
133 133 saved backup bundle to $TESTTMP/blackboxtest2/.hg/strip-backup/*-backup.hg (glob)
134 134 $ hg blackbox -l 6
135 135 1970/01/01 00:00:00 bob @73f6ee326b27d820b0472f1a825e3a50f3dc489b (5000)> strip tip
136 136 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> saved backup bundle to $TESTTMP/blackboxtest2/.hg/strip-backup/73f6ee326b27-7612e004-backup.hg (glob)
137 137 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> updated base branch cache in * seconds (glob)
138 138 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> wrote base branch cache with 1 labels and 2 nodes
139 139 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> strip tip exited 0 after * seconds (glob)
140 140 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> blackbox -l 6
141 141
142 142 extension and python hooks - use the eol extension for a pythonhook
143 143
144 144 $ echo '[extensions]' >> .hg/hgrc
145 145 $ echo 'eol=' >> .hg/hgrc
146 146 $ echo '[hooks]' >> .hg/hgrc
147 147 $ echo 'update = echo hooked' >> .hg/hgrc
148 148 $ hg update
149 149 The fsmonitor extension is incompatible with the eol extension and has been disabled. (fsmonitor !)
150 150 hooked
151 151 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
152 152 updated to "d02f48003e62: c"
153 153 1 other heads for branch "default"
154 154 $ cat >> .hg/hgrc <<EOF
155 155 > [extensions]
156 156 > # disable eol, because it is not needed for subsequent tests
157 157 > # (in addition, keeping it requires extra care for fsmonitor)
158 158 > eol=!
159 159 > EOF
160 160 $ hg blackbox -l 6
161 161 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> update (no-chg !)
162 162 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> writing .hg/cache/tags2-visible with 0 tags
163 163 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pythonhook-preupdate: hgext.eol.preupdate finished in * seconds (glob)
164 164 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> exthook-update: echo hooked finished in * seconds (glob)
165 165 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> update exited 0 after * seconds (glob)
166 166 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> serve --cmdserver chgunix --address $TESTTMP.chgsock/server.* --daemon-postexec 'chdir:/' (glob) (chg !)
167 167 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> blackbox -l 6
168 168
169 169 log rotation
170 170
171 171 $ echo '[blackbox]' >> .hg/hgrc
172 172 $ echo 'maxsize = 20 b' >> .hg/hgrc
173 173 $ echo 'maxfiles = 3' >> .hg/hgrc
174 174 $ hg status
175 175 $ hg status
176 176 $ hg status
177 177 $ hg tip -q
178 178 2:d02f48003e62
179 179 $ ls .hg/blackbox.log*
180 180 .hg/blackbox.log
181 181 .hg/blackbox.log.1
182 182 .hg/blackbox.log.2
183 183 $ cd ..
184 184
185 185 $ hg init blackboxtest3
186 186 $ cd blackboxtest3
187 187 $ hg blackbox
188 188 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> init blackboxtest3 exited 0 after * seconds (glob)
189 189 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
190 190 $ mv .hg/blackbox.log .hg/blackbox.log-
191 191 $ mkdir .hg/blackbox.log
192 192 $ sed -e 's/\(.*test1.*\)/#\1/; s#\(.*commit2.*\)#os.rmdir(".hg/blackbox.log")\
193 193 > os.rename(".hg/blackbox.log-", ".hg/blackbox.log")\
194 194 > \1#' $TESTDIR/test-dispatch.py > ../test-dispatch.py
195 195 $ $PYTHON $TESTDIR/blackbox-readonly-dispatch.py
196 196 running: add foo
197 197 result: 0
198 198 running: commit -m commit1 -d 2000-01-01 foo
199 199 result: None
200 200 running: commit -m commit2 -d 2000-01-02 foo
201 201 result: None
202 202 running: log -r 0
203 203 changeset: 0:0e4634943879
204 204 user: test
205 205 date: Sat Jan 01 00:00:00 2000 +0000
206 206 summary: commit1
207 207
208 208 result: None
209 209 running: log -r tip
210 210 changeset: 1:45589e459b2e
211 211 tag: tip
212 212 user: test
213 213 date: Sun Jan 02 00:00:00 2000 +0000
214 214 summary: commit2
215 215
216 216 result: None
217 217 $ hg blackbox
218 218 1970/01/01 00:00:00 bob @0e46349438790c460c5c9f7546bfcd39b267bbd2 (5000)> commit -m commit2 -d 2000-01-02 foo
219 219 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> updated served branch cache in * seconds (glob)
220 220 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> wrote served branch cache with 1 labels and 1 nodes
221 221 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> commit -m commit2 -d 2000-01-02 foo exited 0 after * seconds (glob)
222 222 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> log -r 0
223 223 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> writing .hg/cache/tags2-visible with 0 tags
224 224 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> log -r 0 exited 0 after * seconds (glob)
225 225 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> log -r tip
226 226 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> log -r tip exited 0 after * seconds (glob)
227 227 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> blackbox
228 228
229 229 Test log recursion from dirty status check
230 230
231 231 $ cat > ../r.py <<EOF
232 232 > from mercurial import context, error, extensions
233 233 > x=[False]
234 234 > def status(orig, *args, **opts):
235 235 > args[0].repo().ui.log("broken", "recursion?")
236 236 > return orig(*args, **opts)
237 237 > def reposetup(ui, repo):
238 238 > extensions.wrapfunction(context.basectx, 'status', status)
239 239 > EOF
240 240 $ hg id --config extensions.x=../r.py --config blackbox.dirty=True
241 241 45589e459b2e tip
242 242
243 243 cleanup
244 244 $ cd ..
245 245
246 246 #if chg
247 247
248 248 when using chg, blackbox.log should get rotated correctly
249 249
250 250 $ cat > $TESTTMP/noop.py << EOF
251 251 > from __future__ import absolute_import
252 252 > import time
253 253 > from mercurial import registrar, scmutil
254 254 > cmdtable = {}
255 255 > command = registrar.command(cmdtable)
256 256 > @command('noop')
257 257 > def noop(ui, repo):
258 258 > pass
259 259 > EOF
260 260
261 261 $ hg init blackbox-chg
262 262 $ cd blackbox-chg
263 263
264 264 $ cat > .hg/hgrc << EOF
265 265 > [blackbox]
266 266 > maxsize = 500B
267 267 > [extensions]
268 268 > # extension change forces chg to restart
269 269 > noop=$TESTTMP/noop.py
270 270 > EOF
271 271
272 272 $ $PYTHON -c 'print("a" * 400)' > .hg/blackbox.log
273 273 $ chg noop
274 274 $ chg noop
275 275 $ chg noop
276 276 $ chg noop
277 277 $ chg noop
278 278
279 279 $ cat > showsize.py << 'EOF'
280 280 > import os, sys
281 281 > limit = 500
282 282 > for p in sys.argv[1:]:
283 283 > size = os.stat(p).st_size
284 284 > if size >= limit:
285 285 > desc = '>='
286 286 > else:
287 287 > desc = '<'
288 288 > print('%s: %s %d' % (p, desc, limit))
289 289 > EOF
290 290
291 291 $ $PYTHON showsize.py .hg/blackbox*
292 292 .hg/blackbox.log: < 500
293 293 .hg/blackbox.log.1: >= 500
294 294 .hg/blackbox.log.2: >= 500
295 295
296 296 $ cd ..
297 297
298 298 With chg, blackbox should not create the log file if the repo is gone
299 299
300 300 $ hg init repo1
301 301 $ hg --config extensions.a=! -R repo1 log
302 302 $ rm -rf $TESTTMP/repo1
303 303 $ hg --config extensions.a=! init repo1
304 304
305 305 #endif
306 306
307 307 blackbox should work if repo.ui.log is not called (issue5518)
308 308
309 309 $ cat > $TESTTMP/raise.py << EOF
310 310 > from __future__ import absolute_import
311 311 > from mercurial import registrar, scmutil
312 312 > cmdtable = {}
313 313 > command = registrar.command(cmdtable)
314 314 > @command('raise')
315 315 > def raisecmd(*args):
316 316 > raise RuntimeError('raise')
317 317 > EOF
318 318
319 319 $ cat >> $HGRCPATH << EOF
320 320 > [blackbox]
321 321 > track = commandexception
322 322 > [extensions]
323 323 > raise=$TESTTMP/raise.py
324 324 > EOF
325 325
326 326 $ hg init $TESTTMP/blackbox-exception-only
327 327 $ cd $TESTTMP/blackbox-exception-only
328 328
329 329 #if chg
330 330 (chg exits 255 because it fails to receive an exit code)
331 331 $ hg raise 2>/dev/null
332 332 [255]
333 333 #else
334 334 (hg exits 1 because Python default exit code for uncaught exception is 1)
335 335 $ hg raise 2>/dev/null
336 336 [1]
337 337 #endif
338 338
339 339 $ head -1 .hg/blackbox.log
340 340 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> ** Unknown exception encountered with possibly-broken third-party extension mock
341 341 $ tail -2 .hg/blackbox.log
342 342 RuntimeError: raise
343 343
@@ -1,107 +1,163 b''
1 1 test command parsing and dispatch
2 2
3 3 $ hg init a
4 4 $ cd a
5 5
6 6 Redundant options used to crash (issue436):
7 7 $ hg -v log -v
8 8 $ hg -v log -v x
9 9
10 10 $ echo a > a
11 11 $ hg ci -Ama
12 12 adding a
13 13
14 14 Missing arg:
15 15
16 16 $ hg cat
17 17 hg cat: invalid arguments
18 18 hg cat [OPTION]... FILE...
19 19
20 20 output the current or given revision of files
21 21
22 22 options ([+] can be repeated):
23 23
24 24 -o --output FORMAT print output to file with formatted name
25 25 -r --rev REV print the given revision
26 26 --decode apply any matching decode filter
27 27 -I --include PATTERN [+] include names matching the given patterns
28 28 -X --exclude PATTERN [+] exclude names matching the given patterns
29 29
30 30 (use 'hg cat -h' to show more help)
31 31 [255]
32 32
33 33 Missing parameter for early option:
34 34
35 35 $ hg log -R 2>&1 | grep 'hg log'
36 36 hg log: option -R requires argument
37 37 hg log [OPTION]... [FILE]
38 38 (use 'hg log -h' to show more help)
39 39
40 40 $ hg log -R -- 2>&1 | grep 'hg log'
41 41 hg log: option -R requires argument
42 42 hg log [OPTION]... [FILE]
43 43 (use 'hg log -h' to show more help)
44 44
45 45 Parsing of early options should stop at "--":
46 46
47 47 $ hg cat -- --config=hooks.pre-cat=false
48 48 --config=hooks.pre-cat=false: no such file in rev cb9a9f314b8b
49 49 [1]
50 50 $ hg cat -- --debugger
51 51 --debugger: no such file in rev cb9a9f314b8b
52 52 [1]
53 53
54 54 Unparsable form of early options:
55 55
56 56 $ hg cat --debugg
57 57 abort: option --debugger may not be abbreviated!
58 58 [255]
59 59
60 Parsing failure of early options should be detected before executing the
61 command:
62
63 $ hg log -b '--config=hooks.pre-log=false' default
64 abort: option --config may not be abbreviated!
65 [255]
66 $ hg log -b -R. default
67 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
68 [255]
69 $ hg log --cwd .. -b --cwd=. default
70 abort: option --cwd may not be abbreviated!
71 [255]
72
73 However, we can't prevent it from loading extensions and configs:
74
75 $ cat <<EOF > bad.py
76 > raise Exception('bad')
77 > EOF
78 $ hg log -b '--config=extensions.bad=bad.py' default
79 *** failed to import extension bad from bad.py: bad
80 abort: option --config may not be abbreviated!
81 [255]
82
83 $ mkdir -p badrepo/.hg
84 $ echo 'invalid-syntax' > badrepo/.hg/hgrc
85 $ hg log -b -Rbadrepo default
86 hg: parse error at badrepo/.hg/hgrc:1: invalid-syntax
87 [255]
88
89 $ hg log -b --cwd=inexistent default
90 abort: No such file or directory: 'inexistent'
91 [255]
92
93 $ hg log -b '--config=ui.traceback=yes' 2>&1 | grep '^Traceback'
94 Traceback (most recent call last):
95 $ hg log -b '--config=profiling.enabled=yes' 2>&1 | grep -i sample
96 Sample count: .*|No samples recorded\. (re)
97
98 Early options can't be specified in [aliases] and [defaults] because they are
99 applied before the command name is resolved:
100
101 $ hg log -b '--config=alias.log=log --config=hooks.pre-log=false'
102 hg log: option -b not recognized
103 error in definition for alias 'log': --config may only be given on the command
104 line
105 [255]
106
107 $ hg log -b '--config=defaults.log=--config=hooks.pre-log=false'
108 abort: option --config may not be abbreviated!
109 [255]
110
111 Shell aliases bypass any command parsing rules but for the early one:
112
113 $ hg log -b '--config=alias.log=!echo howdy'
114 howdy
115
60 116 [defaults]
61 117
62 118 $ hg cat a
63 119 a
64 120 $ cat >> $HGRCPATH <<EOF
65 121 > [defaults]
66 122 > cat = -r null
67 123 > EOF
68 124 $ hg cat a
69 125 a: no such file in rev 000000000000
70 126 [1]
71 127
72 128 $ cd "$TESTTMP"
73 129
74 130 OSError "No such file or directory" / "The system cannot find the path
75 131 specified" should include filename even when it is empty
76 132
77 133 $ hg -R a archive ''
78 134 abort: *: '' (glob)
79 135 [255]
80 136
81 137 #if no-outer-repo
82 138
83 139 No repo:
84 140
85 141 $ hg cat
86 142 abort: no repository found in '$TESTTMP' (.hg not found)!
87 143 [255]
88 144
89 145 #endif
90 146
91 147 #if rmcwd
92 148
93 149 Current directory removed:
94 150
95 151 $ mkdir $TESTTMP/repo1
96 152 $ cd $TESTTMP/repo1
97 153 $ rm -rf $TESTTMP/repo1
98 154
99 155 The output could be one of the following and something else:
100 156 chg: abort: failed to getcwd (errno = *) (glob)
101 157 abort: error getting current working directory: * (glob)
102 158 sh: 0: getcwd() failed: No such file or directory
103 159 Since the exact behavior depends on the shell, only check it returns non-zero.
104 160 $ HGDEMANDIMPORT=disable hg version -q 2>/dev/null || false
105 161 [1]
106 162
107 163 #endif
@@ -1,139 +1,139 b''
1 1 $ hg init
2 2
3 3 $ echo a > a
4 4 $ hg ci -qAm 'add a'
5 5
6 6 $ hg init subrepo
7 7 $ echo 'subrepo = http://example.net/libfoo' > .hgsub
8 8 $ hg ci -qAm 'added subrepo'
9 9
10 10 $ hg up -qC 0
11 11 $ echo ax > a
12 12 $ hg ci -m 'changed a'
13 13 created new head
14 14
15 15 $ hg up -qC 1
16 16 $ cd subrepo
17 17 $ echo b > b
18 18 $ hg add b
19 19 $ cd ..
20 20
21 21 Should fail, since there are added files to subrepo:
22 22
23 23 $ hg merge
24 24 abort: uncommitted changes in subrepository "subrepo"
25 25 [255]
26 26
27 27 Deleted files trigger a '+' marker in top level repos. Deleted files are also
28 28 noticed by `update --check` in the top level repo.
29 29
30 30 $ hg ci -Sqm 'add b'
31 31 $ echo change > subrepo/b
32 32
33 33 $ hg ci -Sm 'change b'
34 34 committing subrepository subrepo
35 35
36 36 $ rm a
37 37 $ hg id
38 38 9bfe45a197d7+ tip
39 39 $ hg sum
40 40 parent: 4:9bfe45a197d7 tip
41 41 change b
42 42 branch: default
43 43 commit: 1 deleted (clean)
44 44 update: 1 new changesets, 2 branch heads (merge)
45 45 phases: 5 draft
46 46
47 47 $ hg up --check -r '.^'
48 48 abort: uncommitted changes
49 49 [255]
50 50 $ hg st -S
51 51 ! a
52 52 $ hg up -Cq .
53 53
54 54 Test that dirty is consistent through subrepos
55 55
56 56 $ rm subrepo/b
57 57
58 58 A deleted subrepo file is flagged as dirty, like the top level repo
59 59
60 60 $ hg id --config extensions.blackbox= --config blackbox.dirty=True
61 61 9bfe45a197d7+ tip
62 62 $ cat .hg/blackbox.log
63 63 * @9bfe45a197d7b0ab09bf287729dd57e9619c9da5+ (*)> serve --cmdserver chgunix * (glob) (chg !)
64 * @9bfe45a197d7b0ab09bf287729dd57e9619c9da5+ (*)> id (glob)
64 * @9bfe45a197d7b0ab09bf287729dd57e9619c9da5+ (*)> id --config *extensions.blackbox=* --config *blackbox.dirty=True* (glob)
65 65 * @9bfe45a197d7b0ab09bf287729dd57e9619c9da5+ (*)> id --config *extensions.blackbox=* --config *blackbox.dirty=True* exited 0 * (glob)
66 66
67 67 TODO: a deleted file should be listed as such, like the top level repo
68 68
69 69 $ hg sum
70 70 parent: 4:9bfe45a197d7 tip
71 71 change b
72 72 branch: default
73 73 commit: (clean)
74 74 update: 1 new changesets, 2 branch heads (merge)
75 75 phases: 5 draft
76 76
77 77 Modified subrepo files are noticed by `update --check` and `summary`
78 78
79 79 $ echo mod > subrepo/b
80 80 $ hg st -S
81 81 M subrepo/b
82 82
83 83 $ hg up -r '.^' --check
84 84 abort: uncommitted changes in subrepository "subrepo"
85 85 [255]
86 86
87 87 $ hg sum
88 88 parent: 4:9bfe45a197d7 tip
89 89 change b
90 90 branch: default
91 91 commit: 1 subrepos
92 92 update: 1 new changesets, 2 branch heads (merge)
93 93 phases: 5 draft
94 94
95 95 TODO: why is -R needed here? If it's because the subrepo is treated as a
96 96 discrete unit, then this should probably warn or something.
97 97 $ hg revert -R subrepo --no-backup subrepo/b -r .
98 98
99 99 $ rm subrepo/b
100 100 $ hg st -S
101 101 ! subrepo/b
102 102
103 103 `hg update --check` notices a subrepo with a missing file, like it notices a
104 104 missing file in the top level repo.
105 105
106 106 $ hg up -r '.^' --check
107 107 abort: uncommitted changes in subrepository "subrepo"
108 108 [255]
109 109
110 110 $ hg up -r '.^' --config ui.interactive=True << EOF
111 111 > d
112 112 > EOF
113 113 other [destination] changed b which local [working copy] deleted
114 114 use (c)hanged version, leave (d)eleted, or leave (u)nresolved? d
115 115 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
116 116
117 117 XXX: There's a difference between wdir() and '.', so there should be a status.
118 118 `hg files -S` from the top is also missing 'subrepo/b'.
119 119
120 120 $ hg st -S
121 121 $ hg st -R subrepo
122 122 $ hg files -R subrepo
123 123 [1]
124 124 $ hg files -R subrepo -r '.'
125 125 subrepo/b (glob)
126 126
127 127 $ hg bookmark -r tip @other
128 128 $ echo xyz > subrepo/c
129 129 $ hg ci -SAm 'add c'
130 130 adding subrepo/c
131 131 committing subrepository subrepo
132 132 created new head
133 133 $ rm subrepo/c
134 134
135 135 Merge sees deleted subrepo files as an uncommitted change
136 136
137 137 $ hg merge @other
138 138 abort: uncommitted changes in subrepository "subrepo"
139 139 [255]
@@ -1,413 +1,413 b''
1 1
2 2 Function to test discovery between two repos in both directions, using both the local shortcut
3 3 (which is currently not activated by default) and the full remotable protocol:
4 4
5 5 $ testdesc() { # revs_a, revs_b, dagdesc
6 6 > if [ -d foo ]; then rm -rf foo; fi
7 7 > hg init foo
8 8 > cd foo
9 9 > hg debugbuilddag "$3"
10 10 > hg clone . a $1 --quiet
11 11 > hg clone . b $2 --quiet
12 12 > echo
13 13 > echo "% -- a -> b tree"
14 14 > hg -R a debugdiscovery b --verbose --old
15 15 > echo
16 16 > echo "% -- a -> b set"
17 17 > hg -R a debugdiscovery b --verbose --debug --config progress.debug=true
18 18 > echo
19 19 > echo "% -- b -> a tree"
20 20 > hg -R b debugdiscovery a --verbose --old
21 21 > echo
22 22 > echo "% -- b -> a set"
23 23 > hg -R b debugdiscovery a --verbose --debug --config progress.debug=true
24 24 > cd ..
25 25 > }
26 26
27 27
28 28 Small superset:
29 29
30 30 $ testdesc '-ra1 -ra2' '-rb1 -rb2 -rb3' '
31 31 > +2:f +1:a1:b1
32 32 > <f +4 :a2
33 33 > +5 :b2
34 34 > <f +3 :b3'
35 35
36 36 % -- a -> b tree
37 37 comparing with b
38 38 searching for changes
39 39 unpruned common: 01241442b3c2 66f7d451a68b b5714e113bc0
40 40 common heads: 01241442b3c2 b5714e113bc0
41 41 local is subset
42 42
43 43 % -- a -> b set
44 44 comparing with b
45 45 query 1; heads
46 46 searching for changes
47 47 all local heads known remotely
48 48 common heads: 01241442b3c2 b5714e113bc0
49 49 local is subset
50 50
51 51 % -- b -> a tree
52 52 comparing with a
53 53 searching for changes
54 54 unpruned common: 01241442b3c2 b5714e113bc0
55 55 common heads: 01241442b3c2 b5714e113bc0
56 56 remote is subset
57 57
58 58 % -- b -> a set
59 59 comparing with a
60 60 query 1; heads
61 61 searching for changes
62 62 all remote heads known locally
63 63 common heads: 01241442b3c2 b5714e113bc0
64 64 remote is subset
65 65
66 66
67 67 Many new:
68 68
69 69 $ testdesc '-ra1 -ra2' '-rb' '
70 70 > +2:f +3:a1 +3:b
71 71 > <f +30 :a2'
72 72
73 73 % -- a -> b tree
74 74 comparing with b
75 75 searching for changes
76 76 unpruned common: bebd167eb94d
77 77 common heads: bebd167eb94d
78 78
79 79 % -- a -> b set
80 80 comparing with b
81 81 query 1; heads
82 82 searching for changes
83 83 taking initial sample
84 84 searching: 2 queries
85 85 query 2; still undecided: 29, sample size is: 29
86 86 2 total queries in *.????s (glob)
87 87 common heads: bebd167eb94d
88 88
89 89 % -- b -> a tree
90 90 comparing with a
91 91 searching for changes
92 92 unpruned common: 66f7d451a68b bebd167eb94d
93 93 common heads: bebd167eb94d
94 94
95 95 % -- b -> a set
96 96 comparing with a
97 97 query 1; heads
98 98 searching for changes
99 99 taking initial sample
100 100 searching: 2 queries
101 101 query 2; still undecided: 2, sample size is: 2
102 102 2 total queries in *.????s (glob)
103 103 common heads: bebd167eb94d
104 104
105 105 Both sides many new with stub:
106 106
107 107 $ testdesc '-ra1 -ra2' '-rb' '
108 108 > +2:f +2:a1 +30 :b
109 109 > <f +30 :a2'
110 110
111 111 % -- a -> b tree
112 112 comparing with b
113 113 searching for changes
114 114 unpruned common: 2dc09a01254d
115 115 common heads: 2dc09a01254d
116 116
117 117 % -- a -> b set
118 118 comparing with b
119 119 query 1; heads
120 120 searching for changes
121 121 taking initial sample
122 122 searching: 2 queries
123 123 query 2; still undecided: 29, sample size is: 29
124 124 2 total queries in *.????s (glob)
125 125 common heads: 2dc09a01254d
126 126
127 127 % -- b -> a tree
128 128 comparing with a
129 129 searching for changes
130 130 unpruned common: 2dc09a01254d 66f7d451a68b
131 131 common heads: 2dc09a01254d
132 132
133 133 % -- b -> a set
134 134 comparing with a
135 135 query 1; heads
136 136 searching for changes
137 137 taking initial sample
138 138 searching: 2 queries
139 139 query 2; still undecided: 29, sample size is: 29
140 140 2 total queries in *.????s (glob)
141 141 common heads: 2dc09a01254d
142 142
143 143
144 144 Both many new:
145 145
146 146 $ testdesc '-ra' '-rb' '
147 147 > +2:f +30 :b
148 148 > <f +30 :a'
149 149
150 150 % -- a -> b tree
151 151 comparing with b
152 152 searching for changes
153 153 unpruned common: 66f7d451a68b
154 154 common heads: 66f7d451a68b
155 155
156 156 % -- a -> b set
157 157 comparing with b
158 158 query 1; heads
159 159 searching for changes
160 160 taking quick initial sample
161 161 searching: 2 queries
162 162 query 2; still undecided: 31, sample size is: 31
163 163 2 total queries in *.????s (glob)
164 164 common heads: 66f7d451a68b
165 165
166 166 % -- b -> a tree
167 167 comparing with a
168 168 searching for changes
169 169 unpruned common: 66f7d451a68b
170 170 common heads: 66f7d451a68b
171 171
172 172 % -- b -> a set
173 173 comparing with a
174 174 query 1; heads
175 175 searching for changes
176 176 taking quick initial sample
177 177 searching: 2 queries
178 178 query 2; still undecided: 31, sample size is: 31
179 179 2 total queries in *.????s (glob)
180 180 common heads: 66f7d451a68b
181 181
182 182
183 183 Both many new skewed:
184 184
185 185 $ testdesc '-ra' '-rb' '
186 186 > +2:f +30 :b
187 187 > <f +50 :a'
188 188
189 189 % -- a -> b tree
190 190 comparing with b
191 191 searching for changes
192 192 unpruned common: 66f7d451a68b
193 193 common heads: 66f7d451a68b
194 194
195 195 % -- a -> b set
196 196 comparing with b
197 197 query 1; heads
198 198 searching for changes
199 199 taking quick initial sample
200 200 searching: 2 queries
201 201 query 2; still undecided: 51, sample size is: 51
202 202 2 total queries in *.????s (glob)
203 203 common heads: 66f7d451a68b
204 204
205 205 % -- b -> a tree
206 206 comparing with a
207 207 searching for changes
208 208 unpruned common: 66f7d451a68b
209 209 common heads: 66f7d451a68b
210 210
211 211 % -- b -> a set
212 212 comparing with a
213 213 query 1; heads
214 214 searching for changes
215 215 taking quick initial sample
216 216 searching: 2 queries
217 217 query 2; still undecided: 31, sample size is: 31
218 218 2 total queries in *.????s (glob)
219 219 common heads: 66f7d451a68b
220 220
221 221
222 222 Both many new on top of long history:
223 223
224 224 $ testdesc '-ra' '-rb' '
225 225 > +1000:f +30 :b
226 226 > <f +50 :a'
227 227
228 228 % -- a -> b tree
229 229 comparing with b
230 230 searching for changes
231 231 unpruned common: 7ead0cba2838
232 232 common heads: 7ead0cba2838
233 233
234 234 % -- a -> b set
235 235 comparing with b
236 236 query 1; heads
237 237 searching for changes
238 238 taking quick initial sample
239 239 searching: 2 queries
240 240 query 2; still undecided: 1049, sample size is: 11
241 241 sampling from both directions
242 242 searching: 3 queries
243 243 query 3; still undecided: 31, sample size is: 31
244 244 3 total queries in *.????s (glob)
245 245 common heads: 7ead0cba2838
246 246
247 247 % -- b -> a tree
248 248 comparing with a
249 249 searching for changes
250 250 unpruned common: 7ead0cba2838
251 251 common heads: 7ead0cba2838
252 252
253 253 % -- b -> a set
254 254 comparing with a
255 255 query 1; heads
256 256 searching for changes
257 257 taking quick initial sample
258 258 searching: 2 queries
259 259 query 2; still undecided: 1029, sample size is: 11
260 260 sampling from both directions
261 261 searching: 3 queries
262 262 query 3; still undecided: 15, sample size is: 15
263 263 3 total queries in *.????s (glob)
264 264 common heads: 7ead0cba2838
265 265
266 266
267 267 One with >200 heads, which used to use up all of the sample:
268 268
269 269 $ hg init manyheads
270 270 $ cd manyheads
271 271 $ echo "+300:r @a" >dagdesc
272 272 $ echo "*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3 *r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3" >>dagdesc # 20 heads
273 273 $ echo "*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3 *r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3" >>dagdesc # 20 heads
274 274 $ echo "*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3 *r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3" >>dagdesc # 20 heads
275 275 $ echo "*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3 *r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3" >>dagdesc # 20 heads
276 276 $ echo "*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3 *r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3" >>dagdesc # 20 heads
277 277 $ echo "*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3 *r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3" >>dagdesc # 20 heads
278 278 $ echo "*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3 *r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3" >>dagdesc # 20 heads
279 279 $ echo "*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3 *r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3" >>dagdesc # 20 heads
280 280 $ echo "*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3 *r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3" >>dagdesc # 20 heads
281 281 $ echo "*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3 *r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3" >>dagdesc # 20 heads
282 282 $ echo "*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3 *r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3" >>dagdesc # 20 heads
283 283 $ echo "*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3 *r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3" >>dagdesc # 20 heads
284 284 $ echo "*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3 *r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3*r+3" >>dagdesc # 20 heads
285 285 $ echo "@b *r+3" >>dagdesc # one more head
286 286 $ hg debugbuilddag <dagdesc
287 287 reading DAG from stdin
288 288
289 289 $ hg heads -t --template . | wc -c
290 290 \s*261 (re)
291 291
292 292 $ hg clone -b a . a
293 293 adding changesets
294 294 adding manifests
295 295 adding file changes
296 296 added 1340 changesets with 0 changes to 0 files (+259 heads)
297 297 new changesets 1ea73414a91b:1c51e2c80832
298 298 updating to branch a
299 299 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
300 300 $ hg clone -b b . b
301 301 adding changesets
302 302 adding manifests
303 303 adding file changes
304 304 added 304 changesets with 0 changes to 0 files
305 305 new changesets 1ea73414a91b:513314ca8b3a
306 306 updating to branch b
307 307 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
308 308
309 309 $ hg -R a debugdiscovery b --debug --verbose --config progress.debug=true
310 310 comparing with b
311 311 query 1; heads
312 312 searching for changes
313 313 taking quick initial sample
314 314 searching: 2 queries
315 315 query 2; still undecided: 1240, sample size is: 100
316 316 sampling from both directions
317 317 searching: 3 queries
318 318 query 3; still undecided: 1140, sample size is: 200
319 319 sampling from both directions
320 320 searching: 4 queries
321 321 query 4; still undecided: \d+, sample size is: 200 (re)
322 322 sampling from both directions
323 323 searching: 5 queries
324 324 query 5; still undecided: \d+, sample size is: 200 (re)
325 325 sampling from both directions
326 326 searching: 6 queries
327 327 query 6; still undecided: \d+, sample size is: \d+ (re)
328 328 6 total queries in *.????s (glob)
329 329 common heads: 3ee37d65064a
330 330
331 331 Test actual protocol when pulling one new head in addition to common heads
332 332
333 333 $ hg clone -U b c
334 334 $ hg -R c id -ir tip
335 335 513314ca8b3a
336 336 $ hg -R c up -qr default
337 337 $ touch c/f
338 338 $ hg -R c ci -Aqm "extra head"
339 339 $ hg -R c id -i
340 340 e64a39e7da8b
341 341
342 342 $ hg serve -R c -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
343 343 $ cat hg.pid >> $DAEMON_PIDS
344 344
345 345 $ hg -R b incoming http://localhost:$HGPORT/ -T '{node|short}\n'
346 346 comparing with http://localhost:$HGPORT/
347 347 searching for changes
348 348 e64a39e7da8b
349 349
350 350 $ killdaemons.py
351 351 $ cut -d' ' -f6- access.log | grep -v cmd=known # cmd=known uses random sampling
352 352 "GET /?cmd=capabilities HTTP/1.1" 200 -
353 353 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D513314ca8b3ae4dac8eec56966265b00fcf866db x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
354 354 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=513314ca8b3ae4dac8eec56966265b00fcf866db&heads=e64a39e7da8b0d54bc63e81169aff001c13b3477 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
355 355 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
356 356 $ cat errors.log
357 357
358 358 $ cd ..
359 359
360 360
361 361 Issue 4438 - test coverage for 3ef893520a85 issues.
362 362
363 363 $ mkdir issue4438
364 364 $ cd issue4438
365 365 #if false
366 366 generate new bundles:
367 367 $ hg init r1
368 368 $ for i in `$PYTHON $TESTDIR/seq.py 101`; do hg -R r1 up -qr null && hg -R r1 branch -q b$i && hg -R r1 ci -qmb$i; done
369 369 $ hg clone -q r1 r2
370 370 $ for i in `$PYTHON $TESTDIR/seq.py 10`; do hg -R r1 up -qr null && hg -R r1 branch -q c$i && hg -R r1 ci -qmc$i; done
371 371 $ hg -R r2 branch -q r2change && hg -R r2 ci -qmr2change
372 372 $ hg -R r1 bundle -qa $TESTDIR/bundles/issue4438-r1.hg
373 373 $ hg -R r2 bundle -qa $TESTDIR/bundles/issue4438-r2.hg
374 374 #else
375 375 use existing bundles:
376 376 $ hg clone -q $TESTDIR/bundles/issue4438-r1.hg r1
377 377 $ hg clone -q $TESTDIR/bundles/issue4438-r2.hg r2
378 378 #endif
379 379
380 380 Set iteration order could cause wrong and unstable results - fixed in 73cfaa348650:
381 381
382 382 $ hg -R r1 outgoing r2 -T'{rev} '
383 383 comparing with r2
384 384 searching for changes
385 385 101 102 103 104 105 106 107 108 109 110 (no-eol)
386 386
387 387 The case where all the 'initialsamplesize' samples already were common would
388 388 give 'all remote heads known locally' without checking the remaining heads -
389 389 fixed in 86c35b7ae300:
390 390
391 391 $ cat >> $TESTTMP/unrandomsample.py << EOF
392 392 > import random
393 393 > def sample(population, k):
394 394 > return sorted(population)[:k]
395 395 > random.sample = sample
396 396 > EOF
397 397
398 398 $ cat >> r1/.hg/hgrc << EOF
399 399 > [extensions]
400 400 > unrandomsample = $TESTTMP/unrandomsample.py
401 401 > EOF
402 402
403 403 $ hg -R r1 outgoing r2 -T'{rev} ' --config extensions.blackbox=
404 404 comparing with r2
405 405 searching for changes
406 406 101 102 103 104 105 106 107 108 109 110 (no-eol)
407 407 $ hg -R r1 --config extensions.blackbox= blackbox
408 408 * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> serve --cmdserver chgunix * (glob) (chg !)
409 * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> outgoing r2 *-T{rev} * (glob)
409 * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> -R r1 outgoing r2 *-T{rev} * --config *extensions.blackbox=* (glob)
410 410 * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> found 101 common and 1 unknown server heads, 2 roundtrips in *.????s (glob)
411 411 * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> -R r1 outgoing r2 *-T{rev} * --config *extensions.blackbox=* exited 0 after *.?? seconds (glob)
412 * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> blackbox (glob)
412 * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> -R r1 --config *extensions.blackbox=* blackbox (glob)
413 413 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now