##// END OF EJS Templates
bookmarks: use cmdutil.check_incompatible_arguments() for action+rev...
Martin von Zweigbergk -
r44353:5cde1648 default
parent child Browse files
Show More
@@ -1,7832 +1,7830 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 wdirhex,
23 23 wdirrev,
24 24 )
25 25 from .pycompat import open
26 26 from . import (
27 27 archival,
28 28 bookmarks,
29 29 bundle2,
30 30 changegroup,
31 31 cmdutil,
32 32 copies,
33 33 debugcommands as debugcommandsmod,
34 34 destutil,
35 35 dirstateguard,
36 36 discovery,
37 37 encoding,
38 38 error,
39 39 exchange,
40 40 extensions,
41 41 filemerge,
42 42 formatter,
43 43 graphmod,
44 44 hbisect,
45 45 help,
46 46 hg,
47 47 logcmdutil,
48 48 merge as mergemod,
49 49 narrowspec,
50 50 obsolete,
51 51 obsutil,
52 52 patch,
53 53 phases,
54 54 pycompat,
55 55 rcutil,
56 56 registrar,
57 57 revsetlang,
58 58 rewriteutil,
59 59 scmutil,
60 60 server,
61 61 shelve as shelvemod,
62 62 state as statemod,
63 63 streamclone,
64 64 tags as tagsmod,
65 65 ui as uimod,
66 66 util,
67 67 verify as verifymod,
68 68 wireprotoserver,
69 69 )
70 70 from .utils import (
71 71 dateutil,
72 72 stringutil,
73 73 )
74 74
75 75 table = {}
76 76 table.update(debugcommandsmod.command._table)
77 77
78 78 command = registrar.command(table)
79 79 INTENT_READONLY = registrar.INTENT_READONLY
80 80
81 81 # common command options
82 82
83 83 globalopts = [
84 84 (
85 85 b'R',
86 86 b'repository',
87 87 b'',
88 88 _(b'repository root directory or name of overlay bundle file'),
89 89 _(b'REPO'),
90 90 ),
91 91 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
92 92 (
93 93 b'y',
94 94 b'noninteractive',
95 95 None,
96 96 _(
97 97 b'do not prompt, automatically pick the first choice for all prompts'
98 98 ),
99 99 ),
100 100 (b'q', b'quiet', None, _(b'suppress output')),
101 101 (b'v', b'verbose', None, _(b'enable additional output')),
102 102 (
103 103 b'',
104 104 b'color',
105 105 b'',
106 106 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
107 107 # and should not be translated
108 108 _(b"when to colorize (boolean, always, auto, never, or debug)"),
109 109 _(b'TYPE'),
110 110 ),
111 111 (
112 112 b'',
113 113 b'config',
114 114 [],
115 115 _(b'set/override config option (use \'section.name=value\')'),
116 116 _(b'CONFIG'),
117 117 ),
118 118 (b'', b'debug', None, _(b'enable debugging output')),
119 119 (b'', b'debugger', None, _(b'start debugger')),
120 120 (
121 121 b'',
122 122 b'encoding',
123 123 encoding.encoding,
124 124 _(b'set the charset encoding'),
125 125 _(b'ENCODE'),
126 126 ),
127 127 (
128 128 b'',
129 129 b'encodingmode',
130 130 encoding.encodingmode,
131 131 _(b'set the charset encoding mode'),
132 132 _(b'MODE'),
133 133 ),
134 134 (b'', b'traceback', None, _(b'always print a traceback on exception')),
135 135 (b'', b'time', None, _(b'time how long the command takes')),
136 136 (b'', b'profile', None, _(b'print command execution profile')),
137 137 (b'', b'version', None, _(b'output version information and exit')),
138 138 (b'h', b'help', None, _(b'display help and exit')),
139 139 (b'', b'hidden', False, _(b'consider hidden changesets')),
140 140 (
141 141 b'',
142 142 b'pager',
143 143 b'auto',
144 144 _(b"when to paginate (boolean, always, auto, or never)"),
145 145 _(b'TYPE'),
146 146 ),
147 147 ]
148 148
149 149 dryrunopts = cmdutil.dryrunopts
150 150 remoteopts = cmdutil.remoteopts
151 151 walkopts = cmdutil.walkopts
152 152 commitopts = cmdutil.commitopts
153 153 commitopts2 = cmdutil.commitopts2
154 154 commitopts3 = cmdutil.commitopts3
155 155 formatteropts = cmdutil.formatteropts
156 156 templateopts = cmdutil.templateopts
157 157 logopts = cmdutil.logopts
158 158 diffopts = cmdutil.diffopts
159 159 diffwsopts = cmdutil.diffwsopts
160 160 diffopts2 = cmdutil.diffopts2
161 161 mergetoolopts = cmdutil.mergetoolopts
162 162 similarityopts = cmdutil.similarityopts
163 163 subrepoopts = cmdutil.subrepoopts
164 164 debugrevlogopts = cmdutil.debugrevlogopts
165 165
166 166 # Commands start here, listed alphabetically
167 167
168 168
169 169 @command(
170 170 b'abort',
171 171 dryrunopts,
172 172 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
173 173 helpbasic=True,
174 174 )
175 175 def abort(ui, repo, **opts):
176 176 """abort an unfinished operation (EXPERIMENTAL)
177 177
178 178 Aborts a multistep operation like graft, histedit, rebase, merge,
179 179 and unshelve if they are in an unfinished state.
180 180
181 181 use --dry-run/-n to dry run the command.
182 182 """
183 183 dryrun = opts.get('dry_run')
184 184 abortstate = cmdutil.getunfinishedstate(repo)
185 185 if not abortstate:
186 186 raise error.Abort(_(b'no operation in progress'))
187 187 if not abortstate.abortfunc:
188 188 raise error.Abort(
189 189 (
190 190 _(b"%s in progress but does not support 'hg abort'")
191 191 % (abortstate._opname)
192 192 ),
193 193 hint=abortstate.hint(),
194 194 )
195 195 if dryrun:
196 196 ui.status(
197 197 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
198 198 )
199 199 return
200 200 return abortstate.abortfunc(ui, repo)
201 201
202 202
203 203 @command(
204 204 b'add',
205 205 walkopts + subrepoopts + dryrunopts,
206 206 _(b'[OPTION]... [FILE]...'),
207 207 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
208 208 helpbasic=True,
209 209 inferrepo=True,
210 210 )
211 211 def add(ui, repo, *pats, **opts):
212 212 """add the specified files on the next commit
213 213
214 214 Schedule files to be version controlled and added to the
215 215 repository.
216 216
217 217 The files will be added to the repository at the next commit. To
218 218 undo an add before that, see :hg:`forget`.
219 219
220 220 If no names are given, add all files to the repository (except
221 221 files matching ``.hgignore``).
222 222
223 223 .. container:: verbose
224 224
225 225 Examples:
226 226
227 227 - New (unknown) files are added
228 228 automatically by :hg:`add`::
229 229
230 230 $ ls
231 231 foo.c
232 232 $ hg status
233 233 ? foo.c
234 234 $ hg add
235 235 adding foo.c
236 236 $ hg status
237 237 A foo.c
238 238
239 239 - Specific files to be added can be specified::
240 240
241 241 $ ls
242 242 bar.c foo.c
243 243 $ hg status
244 244 ? bar.c
245 245 ? foo.c
246 246 $ hg add bar.c
247 247 $ hg status
248 248 A bar.c
249 249 ? foo.c
250 250
251 251 Returns 0 if all files are successfully added.
252 252 """
253 253
254 254 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
255 255 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
256 256 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
257 257 return rejected and 1 or 0
258 258
259 259
260 260 @command(
261 261 b'addremove',
262 262 similarityopts + subrepoopts + walkopts + dryrunopts,
263 263 _(b'[OPTION]... [FILE]...'),
264 264 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
265 265 inferrepo=True,
266 266 )
267 267 def addremove(ui, repo, *pats, **opts):
268 268 """add all new files, delete all missing files
269 269
270 270 Add all new files and remove all missing files from the
271 271 repository.
272 272
273 273 Unless names are given, new files are ignored if they match any of
274 274 the patterns in ``.hgignore``. As with add, these changes take
275 275 effect at the next commit.
276 276
277 277 Use the -s/--similarity option to detect renamed files. This
278 278 option takes a percentage between 0 (disabled) and 100 (files must
279 279 be identical) as its parameter. With a parameter greater than 0,
280 280 this compares every removed file with every added file and records
281 281 those similar enough as renames. Detecting renamed files this way
282 282 can be expensive. After using this option, :hg:`status -C` can be
283 283 used to check which files were identified as moved or renamed. If
284 284 not specified, -s/--similarity defaults to 100 and only renames of
285 285 identical files are detected.
286 286
287 287 .. container:: verbose
288 288
289 289 Examples:
290 290
291 291 - A number of files (bar.c and foo.c) are new,
292 292 while foobar.c has been removed (without using :hg:`remove`)
293 293 from the repository::
294 294
295 295 $ ls
296 296 bar.c foo.c
297 297 $ hg status
298 298 ! foobar.c
299 299 ? bar.c
300 300 ? foo.c
301 301 $ hg addremove
302 302 adding bar.c
303 303 adding foo.c
304 304 removing foobar.c
305 305 $ hg status
306 306 A bar.c
307 307 A foo.c
308 308 R foobar.c
309 309
310 310 - A file foobar.c was moved to foo.c without using :hg:`rename`.
311 311 Afterwards, it was edited slightly::
312 312
313 313 $ ls
314 314 foo.c
315 315 $ hg status
316 316 ! foobar.c
317 317 ? foo.c
318 318 $ hg addremove --similarity 90
319 319 removing foobar.c
320 320 adding foo.c
321 321 recording removal of foobar.c as rename to foo.c (94% similar)
322 322 $ hg status -C
323 323 A foo.c
324 324 foobar.c
325 325 R foobar.c
326 326
327 327 Returns 0 if all files are successfully added.
328 328 """
329 329 opts = pycompat.byteskwargs(opts)
330 330 if not opts.get(b'similarity'):
331 331 opts[b'similarity'] = b'100'
332 332 matcher = scmutil.match(repo[None], pats, opts)
333 333 relative = scmutil.anypats(pats, opts)
334 334 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
335 335 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
336 336
337 337
338 338 @command(
339 339 b'annotate|blame',
340 340 [
341 341 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
342 342 (
343 343 b'',
344 344 b'follow',
345 345 None,
346 346 _(b'follow copies/renames and list the filename (DEPRECATED)'),
347 347 ),
348 348 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
349 349 (b'a', b'text', None, _(b'treat all files as text')),
350 350 (b'u', b'user', None, _(b'list the author (long with -v)')),
351 351 (b'f', b'file', None, _(b'list the filename')),
352 352 (b'd', b'date', None, _(b'list the date (short with -q)')),
353 353 (b'n', b'number', None, _(b'list the revision number (default)')),
354 354 (b'c', b'changeset', None, _(b'list the changeset')),
355 355 (
356 356 b'l',
357 357 b'line-number',
358 358 None,
359 359 _(b'show line number at the first appearance'),
360 360 ),
361 361 (
362 362 b'',
363 363 b'skip',
364 364 [],
365 365 _(b'revset to not display (EXPERIMENTAL)'),
366 366 _(b'REV'),
367 367 ),
368 368 ]
369 369 + diffwsopts
370 370 + walkopts
371 371 + formatteropts,
372 372 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
373 373 helpcategory=command.CATEGORY_FILE_CONTENTS,
374 374 helpbasic=True,
375 375 inferrepo=True,
376 376 )
377 377 def annotate(ui, repo, *pats, **opts):
378 378 """show changeset information by line for each file
379 379
380 380 List changes in files, showing the revision id responsible for
381 381 each line.
382 382
383 383 This command is useful for discovering when a change was made and
384 384 by whom.
385 385
386 386 If you include --file, --user, or --date, the revision number is
387 387 suppressed unless you also include --number.
388 388
389 389 Without the -a/--text option, annotate will avoid processing files
390 390 it detects as binary. With -a, annotate will annotate the file
391 391 anyway, although the results will probably be neither useful
392 392 nor desirable.
393 393
394 394 .. container:: verbose
395 395
396 396 Template:
397 397
398 398 The following keywords are supported in addition to the common template
399 399 keywords and functions. See also :hg:`help templates`.
400 400
401 401 :lines: List of lines with annotation data.
402 402 :path: String. Repository-absolute path of the specified file.
403 403
404 404 And each entry of ``{lines}`` provides the following sub-keywords in
405 405 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
406 406
407 407 :line: String. Line content.
408 408 :lineno: Integer. Line number at that revision.
409 409 :path: String. Repository-absolute path of the file at that revision.
410 410
411 411 See :hg:`help templates.operators` for the list expansion syntax.
412 412
413 413 Returns 0 on success.
414 414 """
415 415 opts = pycompat.byteskwargs(opts)
416 416 if not pats:
417 417 raise error.Abort(_(b'at least one filename or pattern is required'))
418 418
419 419 if opts.get(b'follow'):
420 420 # --follow is deprecated and now just an alias for -f/--file
421 421 # to mimic the behavior of Mercurial before version 1.5
422 422 opts[b'file'] = True
423 423
424 424 if (
425 425 not opts.get(b'user')
426 426 and not opts.get(b'changeset')
427 427 and not opts.get(b'date')
428 428 and not opts.get(b'file')
429 429 ):
430 430 opts[b'number'] = True
431 431
432 432 linenumber = opts.get(b'line_number') is not None
433 433 if (
434 434 linenumber
435 435 and (not opts.get(b'changeset'))
436 436 and (not opts.get(b'number'))
437 437 ):
438 438 raise error.Abort(_(b'at least one of -n/-c is required for -l'))
439 439
440 440 rev = opts.get(b'rev')
441 441 if rev:
442 442 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
443 443 ctx = scmutil.revsingle(repo, rev)
444 444
445 445 ui.pager(b'annotate')
446 446 rootfm = ui.formatter(b'annotate', opts)
447 447 if ui.debugflag:
448 448 shorthex = pycompat.identity
449 449 else:
450 450
451 451 def shorthex(h):
452 452 return h[:12]
453 453
454 454 if ui.quiet:
455 455 datefunc = dateutil.shortdate
456 456 else:
457 457 datefunc = dateutil.datestr
458 458 if ctx.rev() is None:
459 459 if opts.get(b'changeset'):
460 460 # omit "+" suffix which is appended to node hex
461 461 def formatrev(rev):
462 462 if rev == wdirrev:
463 463 return b'%d' % ctx.p1().rev()
464 464 else:
465 465 return b'%d' % rev
466 466
467 467 else:
468 468
469 469 def formatrev(rev):
470 470 if rev == wdirrev:
471 471 return b'%d+' % ctx.p1().rev()
472 472 else:
473 473 return b'%d ' % rev
474 474
475 475 def formathex(h):
476 476 if h == wdirhex:
477 477 return b'%s+' % shorthex(hex(ctx.p1().node()))
478 478 else:
479 479 return b'%s ' % shorthex(h)
480 480
481 481 else:
482 482 formatrev = b'%d'.__mod__
483 483 formathex = shorthex
484 484
485 485 opmap = [
486 486 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
487 487 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
488 488 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
489 489 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
490 490 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
491 491 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
492 492 ]
493 493 opnamemap = {
494 494 b'rev': b'number',
495 495 b'node': b'changeset',
496 496 b'path': b'file',
497 497 b'lineno': b'line_number',
498 498 }
499 499
500 500 if rootfm.isplain():
501 501
502 502 def makefunc(get, fmt):
503 503 return lambda x: fmt(get(x))
504 504
505 505 else:
506 506
507 507 def makefunc(get, fmt):
508 508 return get
509 509
510 510 datahint = rootfm.datahint()
511 511 funcmap = [
512 512 (makefunc(get, fmt), sep)
513 513 for fn, sep, get, fmt in opmap
514 514 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
515 515 ]
516 516 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
517 517 fields = b' '.join(
518 518 fn
519 519 for fn, sep, get, fmt in opmap
520 520 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
521 521 )
522 522
523 523 def bad(x, y):
524 524 raise error.Abort(b"%s: %s" % (x, y))
525 525
526 526 m = scmutil.match(ctx, pats, opts, badfn=bad)
527 527
528 528 follow = not opts.get(b'no_follow')
529 529 diffopts = patch.difffeatureopts(
530 530 ui, opts, section=b'annotate', whitespace=True
531 531 )
532 532 skiprevs = opts.get(b'skip')
533 533 if skiprevs:
534 534 skiprevs = scmutil.revrange(repo, skiprevs)
535 535
536 536 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
537 537 for abs in ctx.walk(m):
538 538 fctx = ctx[abs]
539 539 rootfm.startitem()
540 540 rootfm.data(path=abs)
541 541 if not opts.get(b'text') and fctx.isbinary():
542 542 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
543 543 continue
544 544
545 545 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
546 546 lines = fctx.annotate(
547 547 follow=follow, skiprevs=skiprevs, diffopts=diffopts
548 548 )
549 549 if not lines:
550 550 fm.end()
551 551 continue
552 552 formats = []
553 553 pieces = []
554 554
555 555 for f, sep in funcmap:
556 556 l = [f(n) for n in lines]
557 557 if fm.isplain():
558 558 sizes = [encoding.colwidth(x) for x in l]
559 559 ml = max(sizes)
560 560 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
561 561 else:
562 562 formats.append([b'%s' for x in l])
563 563 pieces.append(l)
564 564
565 565 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
566 566 fm.startitem()
567 567 fm.context(fctx=n.fctx)
568 568 fm.write(fields, b"".join(f), *p)
569 569 if n.skip:
570 570 fmt = b"* %s"
571 571 else:
572 572 fmt = b": %s"
573 573 fm.write(b'line', fmt, n.text)
574 574
575 575 if not lines[-1].text.endswith(b'\n'):
576 576 fm.plain(b'\n')
577 577 fm.end()
578 578
579 579 rootfm.end()
580 580
581 581
582 582 @command(
583 583 b'archive',
584 584 [
585 585 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
586 586 (
587 587 b'p',
588 588 b'prefix',
589 589 b'',
590 590 _(b'directory prefix for files in archive'),
591 591 _(b'PREFIX'),
592 592 ),
593 593 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
594 594 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
595 595 ]
596 596 + subrepoopts
597 597 + walkopts,
598 598 _(b'[OPTION]... DEST'),
599 599 helpcategory=command.CATEGORY_IMPORT_EXPORT,
600 600 )
601 601 def archive(ui, repo, dest, **opts):
602 602 '''create an unversioned archive of a repository revision
603 603
604 604 By default, the revision used is the parent of the working
605 605 directory; use -r/--rev to specify a different revision.
606 606
607 607 The archive type is automatically detected based on file
608 608 extension (to override, use -t/--type).
609 609
610 610 .. container:: verbose
611 611
612 612 Examples:
613 613
614 614 - create a zip file containing the 1.0 release::
615 615
616 616 hg archive -r 1.0 project-1.0.zip
617 617
618 618 - create a tarball excluding .hg files::
619 619
620 620 hg archive project.tar.gz -X ".hg*"
621 621
622 622 Valid types are:
623 623
624 624 :``files``: a directory full of files (default)
625 625 :``tar``: tar archive, uncompressed
626 626 :``tbz2``: tar archive, compressed using bzip2
627 627 :``tgz``: tar archive, compressed using gzip
628 628 :``txz``: tar archive, compressed using lzma (only in Python 3)
629 629 :``uzip``: zip archive, uncompressed
630 630 :``zip``: zip archive, compressed using deflate
631 631
632 632 The exact name of the destination archive or directory is given
633 633 using a format string; see :hg:`help export` for details.
634 634
635 635 Each member added to an archive file has a directory prefix
636 636 prepended. Use -p/--prefix to specify a format string for the
637 637 prefix. The default is the basename of the archive, with suffixes
638 638 removed.
639 639
640 640 Returns 0 on success.
641 641 '''
642 642
643 643 opts = pycompat.byteskwargs(opts)
644 644 rev = opts.get(b'rev')
645 645 if rev:
646 646 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
647 647 ctx = scmutil.revsingle(repo, rev)
648 648 if not ctx:
649 649 raise error.Abort(_(b'no working directory: please specify a revision'))
650 650 node = ctx.node()
651 651 dest = cmdutil.makefilename(ctx, dest)
652 652 if os.path.realpath(dest) == repo.root:
653 653 raise error.Abort(_(b'repository root cannot be destination'))
654 654
655 655 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
656 656 prefix = opts.get(b'prefix')
657 657
658 658 if dest == b'-':
659 659 if kind == b'files':
660 660 raise error.Abort(_(b'cannot archive plain files to stdout'))
661 661 dest = cmdutil.makefileobj(ctx, dest)
662 662 if not prefix:
663 663 prefix = os.path.basename(repo.root) + b'-%h'
664 664
665 665 prefix = cmdutil.makefilename(ctx, prefix)
666 666 match = scmutil.match(ctx, [], opts)
667 667 archival.archive(
668 668 repo,
669 669 dest,
670 670 node,
671 671 kind,
672 672 not opts.get(b'no_decode'),
673 673 match,
674 674 prefix,
675 675 subrepos=opts.get(b'subrepos'),
676 676 )
677 677
678 678
679 679 @command(
680 680 b'backout',
681 681 [
682 682 (
683 683 b'',
684 684 b'merge',
685 685 None,
686 686 _(b'merge with old dirstate parent after backout'),
687 687 ),
688 688 (
689 689 b'',
690 690 b'commit',
691 691 None,
692 692 _(b'commit if no conflicts were encountered (DEPRECATED)'),
693 693 ),
694 694 (b'', b'no-commit', None, _(b'do not commit')),
695 695 (
696 696 b'',
697 697 b'parent',
698 698 b'',
699 699 _(b'parent to choose when backing out merge (DEPRECATED)'),
700 700 _(b'REV'),
701 701 ),
702 702 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
703 703 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
704 704 ]
705 705 + mergetoolopts
706 706 + walkopts
707 707 + commitopts
708 708 + commitopts2,
709 709 _(b'[OPTION]... [-r] REV'),
710 710 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
711 711 )
712 712 def backout(ui, repo, node=None, rev=None, **opts):
713 713 '''reverse effect of earlier changeset
714 714
715 715 Prepare a new changeset with the effect of REV undone in the
716 716 current working directory. If no conflicts were encountered,
717 717 it will be committed immediately.
718 718
719 719 If REV is the parent of the working directory, then this new changeset
720 720 is committed automatically (unless --no-commit is specified).
721 721
722 722 .. note::
723 723
724 724 :hg:`backout` cannot be used to fix either an unwanted or
725 725 incorrect merge.
726 726
727 727 .. container:: verbose
728 728
729 729 Examples:
730 730
731 731 - Reverse the effect of the parent of the working directory.
732 732 This backout will be committed immediately::
733 733
734 734 hg backout -r .
735 735
736 736 - Reverse the effect of previous bad revision 23::
737 737
738 738 hg backout -r 23
739 739
740 740 - Reverse the effect of previous bad revision 23 and
741 741 leave changes uncommitted::
742 742
743 743 hg backout -r 23 --no-commit
744 744 hg commit -m "Backout revision 23"
745 745
746 746 By default, the pending changeset will have one parent,
747 747 maintaining a linear history. With --merge, the pending
748 748 changeset will instead have two parents: the old parent of the
749 749 working directory and a new child of REV that simply undoes REV.
750 750
751 751 Before version 1.7, the behavior without --merge was equivalent
752 752 to specifying --merge followed by :hg:`update --clean .` to
753 753 cancel the merge and leave the child of REV as a head to be
754 754 merged separately.
755 755
756 756 See :hg:`help dates` for a list of formats valid for -d/--date.
757 757
758 758 See :hg:`help revert` for a way to restore files to the state
759 759 of another revision.
760 760
761 761 Returns 0 on success, 1 if nothing to backout or there are unresolved
762 762 files.
763 763 '''
764 764 with repo.wlock(), repo.lock():
765 765 return _dobackout(ui, repo, node, rev, **opts)
766 766
767 767
768 768 def _dobackout(ui, repo, node=None, rev=None, **opts):
769 769 opts = pycompat.byteskwargs(opts)
770 770 if opts.get(b'commit') and opts.get(b'no_commit'):
771 771 raise error.Abort(_(b"cannot use --commit with --no-commit"))
772 772 if opts.get(b'merge') and opts.get(b'no_commit'):
773 773 raise error.Abort(_(b"cannot use --merge with --no-commit"))
774 774
775 775 if rev and node:
776 776 raise error.Abort(_(b"please specify just one revision"))
777 777
778 778 if not rev:
779 779 rev = node
780 780
781 781 if not rev:
782 782 raise error.Abort(_(b"please specify a revision to backout"))
783 783
784 784 date = opts.get(b'date')
785 785 if date:
786 786 opts[b'date'] = dateutil.parsedate(date)
787 787
788 788 cmdutil.checkunfinished(repo)
789 789 cmdutil.bailifchanged(repo)
790 790 node = scmutil.revsingle(repo, rev).node()
791 791
792 792 op1, op2 = repo.dirstate.parents()
793 793 if not repo.changelog.isancestor(node, op1):
794 794 raise error.Abort(_(b'cannot backout change that is not an ancestor'))
795 795
796 796 p1, p2 = repo.changelog.parents(node)
797 797 if p1 == nullid:
798 798 raise error.Abort(_(b'cannot backout a change with no parents'))
799 799 if p2 != nullid:
800 800 if not opts.get(b'parent'):
801 801 raise error.Abort(_(b'cannot backout a merge changeset'))
802 802 p = repo.lookup(opts[b'parent'])
803 803 if p not in (p1, p2):
804 804 raise error.Abort(
805 805 _(b'%s is not a parent of %s') % (short(p), short(node))
806 806 )
807 807 parent = p
808 808 else:
809 809 if opts.get(b'parent'):
810 810 raise error.Abort(_(b'cannot use --parent on non-merge changeset'))
811 811 parent = p1
812 812
813 813 # the backout should appear on the same branch
814 814 branch = repo.dirstate.branch()
815 815 bheads = repo.branchheads(branch)
816 816 rctx = scmutil.revsingle(repo, hex(parent))
817 817 if not opts.get(b'merge') and op1 != node:
818 818 with dirstateguard.dirstateguard(repo, b'backout'):
819 819 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
820 820 with ui.configoverride(overrides, b'backout'):
821 821 stats = mergemod.update(
822 822 repo,
823 823 parent,
824 824 branchmerge=True,
825 825 force=True,
826 826 ancestor=node,
827 827 mergeancestor=False,
828 828 )
829 829 repo.setparents(op1, op2)
830 830 hg._showstats(repo, stats)
831 831 if stats.unresolvedcount:
832 832 repo.ui.status(
833 833 _(b"use 'hg resolve' to retry unresolved file merges\n")
834 834 )
835 835 return 1
836 836 else:
837 837 hg.clean(repo, node, show_stats=False)
838 838 repo.dirstate.setbranch(branch)
839 839 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
840 840
841 841 if opts.get(b'no_commit'):
842 842 msg = _(b"changeset %s backed out, don't forget to commit.\n")
843 843 ui.status(msg % short(node))
844 844 return 0
845 845
846 846 def commitfunc(ui, repo, message, match, opts):
847 847 editform = b'backout'
848 848 e = cmdutil.getcommiteditor(
849 849 editform=editform, **pycompat.strkwargs(opts)
850 850 )
851 851 if not message:
852 852 # we don't translate commit messages
853 853 message = b"Backed out changeset %s" % short(node)
854 854 e = cmdutil.getcommiteditor(edit=True, editform=editform)
855 855 return repo.commit(
856 856 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
857 857 )
858 858
859 859 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
860 860 if not newnode:
861 861 ui.status(_(b"nothing changed\n"))
862 862 return 1
863 863 cmdutil.commitstatus(repo, newnode, branch, bheads)
864 864
865 865 def nice(node):
866 866 return b'%d:%s' % (repo.changelog.rev(node), short(node))
867 867
868 868 ui.status(
869 869 _(b'changeset %s backs out changeset %s\n')
870 870 % (nice(repo.changelog.tip()), nice(node))
871 871 )
872 872 if opts.get(b'merge') and op1 != node:
873 873 hg.clean(repo, op1, show_stats=False)
874 874 ui.status(
875 875 _(b'merging with changeset %s\n') % nice(repo.changelog.tip())
876 876 )
877 877 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
878 878 with ui.configoverride(overrides, b'backout'):
879 879 return hg.merge(repo, hex(repo.changelog.tip()))
880 880 return 0
881 881
882 882
883 883 @command(
884 884 b'bisect',
885 885 [
886 886 (b'r', b'reset', False, _(b'reset bisect state')),
887 887 (b'g', b'good', False, _(b'mark changeset good')),
888 888 (b'b', b'bad', False, _(b'mark changeset bad')),
889 889 (b's', b'skip', False, _(b'skip testing changeset')),
890 890 (b'e', b'extend', False, _(b'extend the bisect range')),
891 891 (
892 892 b'c',
893 893 b'command',
894 894 b'',
895 895 _(b'use command to check changeset state'),
896 896 _(b'CMD'),
897 897 ),
898 898 (b'U', b'noupdate', False, _(b'do not update to target')),
899 899 ],
900 900 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
901 901 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
902 902 )
903 903 def bisect(
904 904 ui,
905 905 repo,
906 906 rev=None,
907 907 extra=None,
908 908 command=None,
909 909 reset=None,
910 910 good=None,
911 911 bad=None,
912 912 skip=None,
913 913 extend=None,
914 914 noupdate=None,
915 915 ):
916 916 """subdivision search of changesets
917 917
918 918 This command helps to find changesets which introduce problems. To
919 919 use, mark the earliest changeset you know exhibits the problem as
920 920 bad, then mark the latest changeset which is free from the problem
921 921 as good. Bisect will update your working directory to a revision
922 922 for testing (unless the -U/--noupdate option is specified). Once
923 923 you have performed tests, mark the working directory as good or
924 924 bad, and bisect will either update to another candidate changeset
925 925 or announce that it has found the bad revision.
926 926
927 927 As a shortcut, you can also use the revision argument to mark a
928 928 revision as good or bad without checking it out first.
929 929
930 930 If you supply a command, it will be used for automatic bisection.
931 931 The environment variable HG_NODE will contain the ID of the
932 932 changeset being tested. The exit status of the command will be
933 933 used to mark revisions as good or bad: status 0 means good, 125
934 934 means to skip the revision, 127 (command not found) will abort the
935 935 bisection, and any other non-zero exit status means the revision
936 936 is bad.
937 937
938 938 .. container:: verbose
939 939
940 940 Some examples:
941 941
942 942 - start a bisection with known bad revision 34, and good revision 12::
943 943
944 944 hg bisect --bad 34
945 945 hg bisect --good 12
946 946
947 947 - advance the current bisection by marking current revision as good or
948 948 bad::
949 949
950 950 hg bisect --good
951 951 hg bisect --bad
952 952
953 953 - mark the current revision, or a known revision, to be skipped (e.g. if
954 954 that revision is not usable because of another issue)::
955 955
956 956 hg bisect --skip
957 957 hg bisect --skip 23
958 958
959 959 - skip all revisions that do not touch directories ``foo`` or ``bar``::
960 960
961 961 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
962 962
963 963 - forget the current bisection::
964 964
965 965 hg bisect --reset
966 966
967 967 - use 'make && make tests' to automatically find the first broken
968 968 revision::
969 969
970 970 hg bisect --reset
971 971 hg bisect --bad 34
972 972 hg bisect --good 12
973 973 hg bisect --command "make && make tests"
974 974
975 975 - see all changesets whose states are already known in the current
976 976 bisection::
977 977
978 978 hg log -r "bisect(pruned)"
979 979
980 980 - see the changeset currently being bisected (especially useful
981 981 if running with -U/--noupdate)::
982 982
983 983 hg log -r "bisect(current)"
984 984
985 985 - see all changesets that took part in the current bisection::
986 986
987 987 hg log -r "bisect(range)"
988 988
989 989 - you can even get a nice graph::
990 990
991 991 hg log --graph -r "bisect(range)"
992 992
993 993 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
994 994
995 995 Returns 0 on success.
996 996 """
997 997 # backward compatibility
998 998 if rev in b"good bad reset init".split():
999 999 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1000 1000 cmd, rev, extra = rev, extra, None
1001 1001 if cmd == b"good":
1002 1002 good = True
1003 1003 elif cmd == b"bad":
1004 1004 bad = True
1005 1005 else:
1006 1006 reset = True
1007 1007 elif extra:
1008 1008 raise error.Abort(_(b'incompatible arguments'))
1009 1009
1010 1010 incompatibles = {
1011 1011 b'--bad': bad,
1012 1012 b'--command': bool(command),
1013 1013 b'--extend': extend,
1014 1014 b'--good': good,
1015 1015 b'--reset': reset,
1016 1016 b'--skip': skip,
1017 1017 }
1018 1018
1019 1019 enabled = [x for x in incompatibles if incompatibles[x]]
1020 1020
1021 1021 if len(enabled) > 1:
1022 1022 raise error.Abort(
1023 1023 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1024 1024 )
1025 1025
1026 1026 if reset:
1027 1027 hbisect.resetstate(repo)
1028 1028 return
1029 1029
1030 1030 state = hbisect.load_state(repo)
1031 1031
1032 1032 # update state
1033 1033 if good or bad or skip:
1034 1034 if rev:
1035 1035 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
1036 1036 else:
1037 1037 nodes = [repo.lookup(b'.')]
1038 1038 if good:
1039 1039 state[b'good'] += nodes
1040 1040 elif bad:
1041 1041 state[b'bad'] += nodes
1042 1042 elif skip:
1043 1043 state[b'skip'] += nodes
1044 1044 hbisect.save_state(repo, state)
1045 1045 if not (state[b'good'] and state[b'bad']):
1046 1046 return
1047 1047
1048 1048 def mayupdate(repo, node, show_stats=True):
1049 1049 """common used update sequence"""
1050 1050 if noupdate:
1051 1051 return
1052 1052 cmdutil.checkunfinished(repo)
1053 1053 cmdutil.bailifchanged(repo)
1054 1054 return hg.clean(repo, node, show_stats=show_stats)
1055 1055
1056 1056 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1057 1057
1058 1058 if command:
1059 1059 changesets = 1
1060 1060 if noupdate:
1061 1061 try:
1062 1062 node = state[b'current'][0]
1063 1063 except LookupError:
1064 1064 raise error.Abort(
1065 1065 _(
1066 1066 b'current bisect revision is unknown - '
1067 1067 b'start a new bisect to fix'
1068 1068 )
1069 1069 )
1070 1070 else:
1071 1071 node, p2 = repo.dirstate.parents()
1072 1072 if p2 != nullid:
1073 1073 raise error.Abort(_(b'current bisect revision is a merge'))
1074 1074 if rev:
1075 1075 node = repo[scmutil.revsingle(repo, rev, node)].node()
1076 1076 with hbisect.restore_state(repo, state, node):
1077 1077 while changesets:
1078 1078 # update state
1079 1079 state[b'current'] = [node]
1080 1080 hbisect.save_state(repo, state)
1081 1081 status = ui.system(
1082 1082 command,
1083 1083 environ={b'HG_NODE': hex(node)},
1084 1084 blockedtag=b'bisect_check',
1085 1085 )
1086 1086 if status == 125:
1087 1087 transition = b"skip"
1088 1088 elif status == 0:
1089 1089 transition = b"good"
1090 1090 # status < 0 means process was killed
1091 1091 elif status == 127:
1092 1092 raise error.Abort(_(b"failed to execute %s") % command)
1093 1093 elif status < 0:
1094 1094 raise error.Abort(_(b"%s killed") % command)
1095 1095 else:
1096 1096 transition = b"bad"
1097 1097 state[transition].append(node)
1098 1098 ctx = repo[node]
1099 1099 ui.status(
1100 1100 _(b'changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition)
1101 1101 )
1102 1102 hbisect.checkstate(state)
1103 1103 # bisect
1104 1104 nodes, changesets, bgood = hbisect.bisect(repo, state)
1105 1105 # update to next check
1106 1106 node = nodes[0]
1107 1107 mayupdate(repo, node, show_stats=False)
1108 1108 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1109 1109 return
1110 1110
1111 1111 hbisect.checkstate(state)
1112 1112
1113 1113 # actually bisect
1114 1114 nodes, changesets, good = hbisect.bisect(repo, state)
1115 1115 if extend:
1116 1116 if not changesets:
1117 1117 extendnode = hbisect.extendrange(repo, state, nodes, good)
1118 1118 if extendnode is not None:
1119 1119 ui.write(
1120 1120 _(b"Extending search to changeset %d:%s\n")
1121 1121 % (extendnode.rev(), extendnode)
1122 1122 )
1123 1123 state[b'current'] = [extendnode.node()]
1124 1124 hbisect.save_state(repo, state)
1125 1125 return mayupdate(repo, extendnode.node())
1126 1126 raise error.Abort(_(b"nothing to extend"))
1127 1127
1128 1128 if changesets == 0:
1129 1129 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1130 1130 else:
1131 1131 assert len(nodes) == 1 # only a single node can be tested next
1132 1132 node = nodes[0]
1133 1133 # compute the approximate number of remaining tests
1134 1134 tests, size = 0, 2
1135 1135 while size <= changesets:
1136 1136 tests, size = tests + 1, size * 2
1137 1137 rev = repo.changelog.rev(node)
1138 1138 ui.write(
1139 1139 _(
1140 1140 b"Testing changeset %d:%s "
1141 1141 b"(%d changesets remaining, ~%d tests)\n"
1142 1142 )
1143 1143 % (rev, short(node), changesets, tests)
1144 1144 )
1145 1145 state[b'current'] = [node]
1146 1146 hbisect.save_state(repo, state)
1147 1147 return mayupdate(repo, node)
1148 1148
1149 1149
1150 1150 @command(
1151 1151 b'bookmarks|bookmark',
1152 1152 [
1153 1153 (b'f', b'force', False, _(b'force')),
1154 1154 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1155 1155 (b'd', b'delete', False, _(b'delete a given bookmark')),
1156 1156 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1157 1157 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1158 1158 (b'l', b'list', False, _(b'list existing bookmarks')),
1159 1159 ]
1160 1160 + formatteropts,
1161 1161 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1162 1162 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1163 1163 )
1164 1164 def bookmark(ui, repo, *names, **opts):
1165 1165 '''create a new bookmark or list existing bookmarks
1166 1166
1167 1167 Bookmarks are labels on changesets to help track lines of development.
1168 1168 Bookmarks are unversioned and can be moved, renamed and deleted.
1169 1169 Deleting or moving a bookmark has no effect on the associated changesets.
1170 1170
1171 1171 Creating or updating to a bookmark causes it to be marked as 'active'.
1172 1172 The active bookmark is indicated with a '*'.
1173 1173 When a commit is made, the active bookmark will advance to the new commit.
1174 1174 A plain :hg:`update` will also advance an active bookmark, if possible.
1175 1175 Updating away from a bookmark will cause it to be deactivated.
1176 1176
1177 1177 Bookmarks can be pushed and pulled between repositories (see
1178 1178 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1179 1179 diverged, a new 'divergent bookmark' of the form 'name@path' will
1180 1180 be created. Using :hg:`merge` will resolve the divergence.
1181 1181
1182 1182 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1183 1183 the active bookmark's name.
1184 1184
1185 1185 A bookmark named '@' has the special property that :hg:`clone` will
1186 1186 check it out by default if it exists.
1187 1187
1188 1188 .. container:: verbose
1189 1189
1190 1190 Template:
1191 1191
1192 1192 The following keywords are supported in addition to the common template
1193 1193 keywords and functions such as ``{bookmark}``. See also
1194 1194 :hg:`help templates`.
1195 1195
1196 1196 :active: Boolean. True if the bookmark is active.
1197 1197
1198 1198 Examples:
1199 1199
1200 1200 - create an active bookmark for a new line of development::
1201 1201
1202 1202 hg book new-feature
1203 1203
1204 1204 - create an inactive bookmark as a place marker::
1205 1205
1206 1206 hg book -i reviewed
1207 1207
1208 1208 - create an inactive bookmark on another changeset::
1209 1209
1210 1210 hg book -r .^ tested
1211 1211
1212 1212 - rename bookmark turkey to dinner::
1213 1213
1214 1214 hg book -m turkey dinner
1215 1215
1216 1216 - move the '@' bookmark from another branch::
1217 1217
1218 1218 hg book -f @
1219 1219
1220 1220 - print only the active bookmark name::
1221 1221
1222 1222 hg book -ql .
1223 1223 '''
1224 1224 opts = pycompat.byteskwargs(opts)
1225 1225 force = opts.get(b'force')
1226 1226 rev = opts.get(b'rev')
1227 1227 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1228 1228
1229 1229 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1230 1230 if action:
1231 pass
1231 cmdutil.check_incompatible_arguments(opts, action, b'rev')
1232 1232 elif names or rev:
1233 1233 action = b'add'
1234 1234 elif inactive:
1235 1235 action = b'inactive' # meaning deactivate
1236 1236 else:
1237 1237 action = b'list'
1238 1238
1239 if rev and action in {b'delete', b'rename', b'list'}:
1240 raise error.Abort(_(b"--rev is incompatible with --%s") % action)
1241 1239 if inactive and action in {b'delete', b'list'}:
1242 1240 raise error.Abort(_(b"--inactive is incompatible with --%s") % action)
1243 1241 if not names and action in {b'add', b'delete'}:
1244 1242 raise error.Abort(_(b"bookmark name required"))
1245 1243
1246 1244 if action in {b'add', b'delete', b'rename', b'inactive'}:
1247 1245 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1248 1246 if action == b'delete':
1249 1247 names = pycompat.maplist(repo._bookmarks.expandname, names)
1250 1248 bookmarks.delete(repo, tr, names)
1251 1249 elif action == b'rename':
1252 1250 if not names:
1253 1251 raise error.Abort(_(b"new bookmark name required"))
1254 1252 elif len(names) > 1:
1255 1253 raise error.Abort(_(b"only one new bookmark name allowed"))
1256 1254 oldname = repo._bookmarks.expandname(opts[b'rename'])
1257 1255 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1258 1256 elif action == b'add':
1259 1257 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1260 1258 elif action == b'inactive':
1261 1259 if len(repo._bookmarks) == 0:
1262 1260 ui.status(_(b"no bookmarks set\n"))
1263 1261 elif not repo._activebookmark:
1264 1262 ui.status(_(b"no active bookmark\n"))
1265 1263 else:
1266 1264 bookmarks.deactivate(repo)
1267 1265 elif action == b'list':
1268 1266 names = pycompat.maplist(repo._bookmarks.expandname, names)
1269 1267 with ui.formatter(b'bookmarks', opts) as fm:
1270 1268 bookmarks.printbookmarks(ui, repo, fm, names)
1271 1269 else:
1272 1270 raise error.ProgrammingError(b'invalid action: %s' % action)
1273 1271
1274 1272
1275 1273 @command(
1276 1274 b'branch',
1277 1275 [
1278 1276 (
1279 1277 b'f',
1280 1278 b'force',
1281 1279 None,
1282 1280 _(b'set branch name even if it shadows an existing branch'),
1283 1281 ),
1284 1282 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1285 1283 (
1286 1284 b'r',
1287 1285 b'rev',
1288 1286 [],
1289 1287 _(b'change branches of the given revs (EXPERIMENTAL)'),
1290 1288 ),
1291 1289 ],
1292 1290 _(b'[-fC] [NAME]'),
1293 1291 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1294 1292 )
1295 1293 def branch(ui, repo, label=None, **opts):
1296 1294 """set or show the current branch name
1297 1295
1298 1296 .. note::
1299 1297
1300 1298 Branch names are permanent and global. Use :hg:`bookmark` to create a
1301 1299 light-weight bookmark instead. See :hg:`help glossary` for more
1302 1300 information about named branches and bookmarks.
1303 1301
1304 1302 With no argument, show the current branch name. With one argument,
1305 1303 set the working directory branch name (the branch will not exist
1306 1304 in the repository until the next commit). Standard practice
1307 1305 recommends that primary development take place on the 'default'
1308 1306 branch.
1309 1307
1310 1308 Unless -f/--force is specified, branch will not let you set a
1311 1309 branch name that already exists.
1312 1310
1313 1311 Use -C/--clean to reset the working directory branch to that of
1314 1312 the parent of the working directory, negating a previous branch
1315 1313 change.
1316 1314
1317 1315 Use the command :hg:`update` to switch to an existing branch. Use
1318 1316 :hg:`commit --close-branch` to mark this branch head as closed.
1319 1317 When all heads of a branch are closed, the branch will be
1320 1318 considered closed.
1321 1319
1322 1320 Returns 0 on success.
1323 1321 """
1324 1322 opts = pycompat.byteskwargs(opts)
1325 1323 revs = opts.get(b'rev')
1326 1324 if label:
1327 1325 label = label.strip()
1328 1326
1329 1327 if not opts.get(b'clean') and not label:
1330 1328 if revs:
1331 1329 raise error.Abort(_(b"no branch name specified for the revisions"))
1332 1330 ui.write(b"%s\n" % repo.dirstate.branch())
1333 1331 return
1334 1332
1335 1333 with repo.wlock():
1336 1334 if opts.get(b'clean'):
1337 1335 label = repo[b'.'].branch()
1338 1336 repo.dirstate.setbranch(label)
1339 1337 ui.status(_(b'reset working directory to branch %s\n') % label)
1340 1338 elif label:
1341 1339
1342 1340 scmutil.checknewlabel(repo, label, b'branch')
1343 1341 if revs:
1344 1342 return cmdutil.changebranch(ui, repo, revs, label)
1345 1343
1346 1344 if not opts.get(b'force') and label in repo.branchmap():
1347 1345 if label not in [p.branch() for p in repo[None].parents()]:
1348 1346 raise error.Abort(
1349 1347 _(b'a branch of the same name already exists'),
1350 1348 # i18n: "it" refers to an existing branch
1351 1349 hint=_(b"use 'hg update' to switch to it"),
1352 1350 )
1353 1351
1354 1352 repo.dirstate.setbranch(label)
1355 1353 ui.status(_(b'marked working directory as branch %s\n') % label)
1356 1354
1357 1355 # find any open named branches aside from default
1358 1356 for n, h, t, c in repo.branchmap().iterbranches():
1359 1357 if n != b"default" and not c:
1360 1358 return 0
1361 1359 ui.status(
1362 1360 _(
1363 1361 b'(branches are permanent and global, '
1364 1362 b'did you want a bookmark?)\n'
1365 1363 )
1366 1364 )
1367 1365
1368 1366
1369 1367 @command(
1370 1368 b'branches',
1371 1369 [
1372 1370 (
1373 1371 b'a',
1374 1372 b'active',
1375 1373 False,
1376 1374 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1377 1375 ),
1378 1376 (b'c', b'closed', False, _(b'show normal and closed branches')),
1379 1377 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1380 1378 ]
1381 1379 + formatteropts,
1382 1380 _(b'[-c]'),
1383 1381 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1384 1382 intents={INTENT_READONLY},
1385 1383 )
1386 1384 def branches(ui, repo, active=False, closed=False, **opts):
1387 1385 """list repository named branches
1388 1386
1389 1387 List the repository's named branches, indicating which ones are
1390 1388 inactive. If -c/--closed is specified, also list branches which have
1391 1389 been marked closed (see :hg:`commit --close-branch`).
1392 1390
1393 1391 Use the command :hg:`update` to switch to an existing branch.
1394 1392
1395 1393 .. container:: verbose
1396 1394
1397 1395 Template:
1398 1396
1399 1397 The following keywords are supported in addition to the common template
1400 1398 keywords and functions such as ``{branch}``. See also
1401 1399 :hg:`help templates`.
1402 1400
1403 1401 :active: Boolean. True if the branch is active.
1404 1402 :closed: Boolean. True if the branch is closed.
1405 1403 :current: Boolean. True if it is the current branch.
1406 1404
1407 1405 Returns 0.
1408 1406 """
1409 1407
1410 1408 opts = pycompat.byteskwargs(opts)
1411 1409 revs = opts.get(b'rev')
1412 1410 selectedbranches = None
1413 1411 if revs:
1414 1412 revs = scmutil.revrange(repo, revs)
1415 1413 getbi = repo.revbranchcache().branchinfo
1416 1414 selectedbranches = {getbi(r)[0] for r in revs}
1417 1415
1418 1416 ui.pager(b'branches')
1419 1417 fm = ui.formatter(b'branches', opts)
1420 1418 hexfunc = fm.hexfunc
1421 1419
1422 1420 allheads = set(repo.heads())
1423 1421 branches = []
1424 1422 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1425 1423 if selectedbranches is not None and tag not in selectedbranches:
1426 1424 continue
1427 1425 isactive = False
1428 1426 if not isclosed:
1429 1427 openheads = set(repo.branchmap().iteropen(heads))
1430 1428 isactive = bool(openheads & allheads)
1431 1429 branches.append((tag, repo[tip], isactive, not isclosed))
1432 1430 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1433 1431
1434 1432 for tag, ctx, isactive, isopen in branches:
1435 1433 if active and not isactive:
1436 1434 continue
1437 1435 if isactive:
1438 1436 label = b'branches.active'
1439 1437 notice = b''
1440 1438 elif not isopen:
1441 1439 if not closed:
1442 1440 continue
1443 1441 label = b'branches.closed'
1444 1442 notice = _(b' (closed)')
1445 1443 else:
1446 1444 label = b'branches.inactive'
1447 1445 notice = _(b' (inactive)')
1448 1446 current = tag == repo.dirstate.branch()
1449 1447 if current:
1450 1448 label = b'branches.current'
1451 1449
1452 1450 fm.startitem()
1453 1451 fm.write(b'branch', b'%s', tag, label=label)
1454 1452 rev = ctx.rev()
1455 1453 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1456 1454 fmt = b' ' * padsize + b' %d:%s'
1457 1455 fm.condwrite(
1458 1456 not ui.quiet,
1459 1457 b'rev node',
1460 1458 fmt,
1461 1459 rev,
1462 1460 hexfunc(ctx.node()),
1463 1461 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1464 1462 )
1465 1463 fm.context(ctx=ctx)
1466 1464 fm.data(active=isactive, closed=not isopen, current=current)
1467 1465 if not ui.quiet:
1468 1466 fm.plain(notice)
1469 1467 fm.plain(b'\n')
1470 1468 fm.end()
1471 1469
1472 1470
1473 1471 @command(
1474 1472 b'bundle',
1475 1473 [
1476 1474 (
1477 1475 b'f',
1478 1476 b'force',
1479 1477 None,
1480 1478 _(b'run even when the destination is unrelated'),
1481 1479 ),
1482 1480 (
1483 1481 b'r',
1484 1482 b'rev',
1485 1483 [],
1486 1484 _(b'a changeset intended to be added to the destination'),
1487 1485 _(b'REV'),
1488 1486 ),
1489 1487 (
1490 1488 b'b',
1491 1489 b'branch',
1492 1490 [],
1493 1491 _(b'a specific branch you would like to bundle'),
1494 1492 _(b'BRANCH'),
1495 1493 ),
1496 1494 (
1497 1495 b'',
1498 1496 b'base',
1499 1497 [],
1500 1498 _(b'a base changeset assumed to be available at the destination'),
1501 1499 _(b'REV'),
1502 1500 ),
1503 1501 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1504 1502 (
1505 1503 b't',
1506 1504 b'type',
1507 1505 b'bzip2',
1508 1506 _(b'bundle compression type to use'),
1509 1507 _(b'TYPE'),
1510 1508 ),
1511 1509 ]
1512 1510 + remoteopts,
1513 1511 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1514 1512 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1515 1513 )
1516 1514 def bundle(ui, repo, fname, dest=None, **opts):
1517 1515 """create a bundle file
1518 1516
1519 1517 Generate a bundle file containing data to be transferred to another
1520 1518 repository.
1521 1519
1522 1520 To create a bundle containing all changesets, use -a/--all
1523 1521 (or --base null). Otherwise, hg assumes the destination will have
1524 1522 all the nodes you specify with --base parameters. Otherwise, hg
1525 1523 will assume the repository has all the nodes in destination, or
1526 1524 default-push/default if no destination is specified, where destination
1527 1525 is the repository you provide through DEST option.
1528 1526
1529 1527 You can change bundle format with the -t/--type option. See
1530 1528 :hg:`help bundlespec` for documentation on this format. By default,
1531 1529 the most appropriate format is used and compression defaults to
1532 1530 bzip2.
1533 1531
1534 1532 The bundle file can then be transferred using conventional means
1535 1533 and applied to another repository with the unbundle or pull
1536 1534 command. This is useful when direct push and pull are not
1537 1535 available or when exporting an entire repository is undesirable.
1538 1536
1539 1537 Applying bundles preserves all changeset contents including
1540 1538 permissions, copy/rename information, and revision history.
1541 1539
1542 1540 Returns 0 on success, 1 if no changes found.
1543 1541 """
1544 1542 opts = pycompat.byteskwargs(opts)
1545 1543 revs = None
1546 1544 if b'rev' in opts:
1547 1545 revstrings = opts[b'rev']
1548 1546 revs = scmutil.revrange(repo, revstrings)
1549 1547 if revstrings and not revs:
1550 1548 raise error.Abort(_(b'no commits to bundle'))
1551 1549
1552 1550 bundletype = opts.get(b'type', b'bzip2').lower()
1553 1551 try:
1554 1552 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1555 1553 except error.UnsupportedBundleSpecification as e:
1556 1554 raise error.Abort(
1557 1555 pycompat.bytestr(e),
1558 1556 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1559 1557 )
1560 1558 cgversion = bundlespec.contentopts[b"cg.version"]
1561 1559
1562 1560 # Packed bundles are a pseudo bundle format for now.
1563 1561 if cgversion == b's1':
1564 1562 raise error.Abort(
1565 1563 _(b'packed bundles cannot be produced by "hg bundle"'),
1566 1564 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1567 1565 )
1568 1566
1569 1567 if opts.get(b'all'):
1570 1568 if dest:
1571 1569 raise error.Abort(
1572 1570 _(b"--all is incompatible with specifying a destination")
1573 1571 )
1574 1572 if opts.get(b'base'):
1575 1573 ui.warn(_(b"ignoring --base because --all was specified\n"))
1576 1574 base = [nullrev]
1577 1575 else:
1578 1576 base = scmutil.revrange(repo, opts.get(b'base'))
1579 1577 if cgversion not in changegroup.supportedoutgoingversions(repo):
1580 1578 raise error.Abort(
1581 1579 _(b"repository does not support bundle version %s") % cgversion
1582 1580 )
1583 1581
1584 1582 if base:
1585 1583 if dest:
1586 1584 raise error.Abort(
1587 1585 _(b"--base is incompatible with specifying a destination")
1588 1586 )
1589 1587 common = [repo[rev].node() for rev in base]
1590 1588 heads = [repo[r].node() for r in revs] if revs else None
1591 1589 outgoing = discovery.outgoing(repo, common, heads)
1592 1590 else:
1593 1591 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1594 1592 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1595 1593 other = hg.peer(repo, opts, dest)
1596 1594 revs = [repo[r].hex() for r in revs]
1597 1595 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1598 1596 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1599 1597 outgoing = discovery.findcommonoutgoing(
1600 1598 repo,
1601 1599 other,
1602 1600 onlyheads=heads,
1603 1601 force=opts.get(b'force'),
1604 1602 portable=True,
1605 1603 )
1606 1604
1607 1605 if not outgoing.missing:
1608 1606 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1609 1607 return 1
1610 1608
1611 1609 if cgversion == b'01': # bundle1
1612 1610 bversion = b'HG10' + bundlespec.wirecompression
1613 1611 bcompression = None
1614 1612 elif cgversion in (b'02', b'03'):
1615 1613 bversion = b'HG20'
1616 1614 bcompression = bundlespec.wirecompression
1617 1615 else:
1618 1616 raise error.ProgrammingError(
1619 1617 b'bundle: unexpected changegroup version %s' % cgversion
1620 1618 )
1621 1619
1622 1620 # TODO compression options should be derived from bundlespec parsing.
1623 1621 # This is a temporary hack to allow adjusting bundle compression
1624 1622 # level without a) formalizing the bundlespec changes to declare it
1625 1623 # b) introducing a command flag.
1626 1624 compopts = {}
1627 1625 complevel = ui.configint(
1628 1626 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1629 1627 )
1630 1628 if complevel is None:
1631 1629 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1632 1630 if complevel is not None:
1633 1631 compopts[b'level'] = complevel
1634 1632
1635 1633 # Allow overriding the bundling of obsmarker in phases through
1636 1634 # configuration while we don't have a bundle version that include them
1637 1635 if repo.ui.configbool(b'experimental', b'evolution.bundle-obsmarker'):
1638 1636 bundlespec.contentopts[b'obsolescence'] = True
1639 1637 if repo.ui.configbool(b'experimental', b'bundle-phases'):
1640 1638 bundlespec.contentopts[b'phases'] = True
1641 1639
1642 1640 bundle2.writenewbundle(
1643 1641 ui,
1644 1642 repo,
1645 1643 b'bundle',
1646 1644 fname,
1647 1645 bversion,
1648 1646 outgoing,
1649 1647 bundlespec.contentopts,
1650 1648 compression=bcompression,
1651 1649 compopts=compopts,
1652 1650 )
1653 1651
1654 1652
1655 1653 @command(
1656 1654 b'cat',
1657 1655 [
1658 1656 (
1659 1657 b'o',
1660 1658 b'output',
1661 1659 b'',
1662 1660 _(b'print output to file with formatted name'),
1663 1661 _(b'FORMAT'),
1664 1662 ),
1665 1663 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1666 1664 (b'', b'decode', None, _(b'apply any matching decode filter')),
1667 1665 ]
1668 1666 + walkopts
1669 1667 + formatteropts,
1670 1668 _(b'[OPTION]... FILE...'),
1671 1669 helpcategory=command.CATEGORY_FILE_CONTENTS,
1672 1670 inferrepo=True,
1673 1671 intents={INTENT_READONLY},
1674 1672 )
1675 1673 def cat(ui, repo, file1, *pats, **opts):
1676 1674 """output the current or given revision of files
1677 1675
1678 1676 Print the specified files as they were at the given revision. If
1679 1677 no revision is given, the parent of the working directory is used.
1680 1678
1681 1679 Output may be to a file, in which case the name of the file is
1682 1680 given using a template string. See :hg:`help templates`. In addition
1683 1681 to the common template keywords, the following formatting rules are
1684 1682 supported:
1685 1683
1686 1684 :``%%``: literal "%" character
1687 1685 :``%s``: basename of file being printed
1688 1686 :``%d``: dirname of file being printed, or '.' if in repository root
1689 1687 :``%p``: root-relative path name of file being printed
1690 1688 :``%H``: changeset hash (40 hexadecimal digits)
1691 1689 :``%R``: changeset revision number
1692 1690 :``%h``: short-form changeset hash (12 hexadecimal digits)
1693 1691 :``%r``: zero-padded changeset revision number
1694 1692 :``%b``: basename of the exporting repository
1695 1693 :``\\``: literal "\\" character
1696 1694
1697 1695 .. container:: verbose
1698 1696
1699 1697 Template:
1700 1698
1701 1699 The following keywords are supported in addition to the common template
1702 1700 keywords and functions. See also :hg:`help templates`.
1703 1701
1704 1702 :data: String. File content.
1705 1703 :path: String. Repository-absolute path of the file.
1706 1704
1707 1705 Returns 0 on success.
1708 1706 """
1709 1707 opts = pycompat.byteskwargs(opts)
1710 1708 rev = opts.get(b'rev')
1711 1709 if rev:
1712 1710 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1713 1711 ctx = scmutil.revsingle(repo, rev)
1714 1712 m = scmutil.match(ctx, (file1,) + pats, opts)
1715 1713 fntemplate = opts.pop(b'output', b'')
1716 1714 if cmdutil.isstdiofilename(fntemplate):
1717 1715 fntemplate = b''
1718 1716
1719 1717 if fntemplate:
1720 1718 fm = formatter.nullformatter(ui, b'cat', opts)
1721 1719 else:
1722 1720 ui.pager(b'cat')
1723 1721 fm = ui.formatter(b'cat', opts)
1724 1722 with fm:
1725 1723 return cmdutil.cat(
1726 1724 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1727 1725 )
1728 1726
1729 1727
1730 1728 @command(
1731 1729 b'clone',
1732 1730 [
1733 1731 (
1734 1732 b'U',
1735 1733 b'noupdate',
1736 1734 None,
1737 1735 _(
1738 1736 b'the clone will include an empty working '
1739 1737 b'directory (only a repository)'
1740 1738 ),
1741 1739 ),
1742 1740 (
1743 1741 b'u',
1744 1742 b'updaterev',
1745 1743 b'',
1746 1744 _(b'revision, tag, or branch to check out'),
1747 1745 _(b'REV'),
1748 1746 ),
1749 1747 (
1750 1748 b'r',
1751 1749 b'rev',
1752 1750 [],
1753 1751 _(
1754 1752 b'do not clone everything, but include this changeset'
1755 1753 b' and its ancestors'
1756 1754 ),
1757 1755 _(b'REV'),
1758 1756 ),
1759 1757 (
1760 1758 b'b',
1761 1759 b'branch',
1762 1760 [],
1763 1761 _(
1764 1762 b'do not clone everything, but include this branch\'s'
1765 1763 b' changesets and their ancestors'
1766 1764 ),
1767 1765 _(b'BRANCH'),
1768 1766 ),
1769 1767 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1770 1768 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1771 1769 (b'', b'stream', None, _(b'clone with minimal data processing')),
1772 1770 ]
1773 1771 + remoteopts,
1774 1772 _(b'[OPTION]... SOURCE [DEST]'),
1775 1773 helpcategory=command.CATEGORY_REPO_CREATION,
1776 1774 helpbasic=True,
1777 1775 norepo=True,
1778 1776 )
1779 1777 def clone(ui, source, dest=None, **opts):
1780 1778 """make a copy of an existing repository
1781 1779
1782 1780 Create a copy of an existing repository in a new directory.
1783 1781
1784 1782 If no destination directory name is specified, it defaults to the
1785 1783 basename of the source.
1786 1784
1787 1785 The location of the source is added to the new repository's
1788 1786 ``.hg/hgrc`` file, as the default to be used for future pulls.
1789 1787
1790 1788 Only local paths and ``ssh://`` URLs are supported as
1791 1789 destinations. For ``ssh://`` destinations, no working directory or
1792 1790 ``.hg/hgrc`` will be created on the remote side.
1793 1791
1794 1792 If the source repository has a bookmark called '@' set, that
1795 1793 revision will be checked out in the new repository by default.
1796 1794
1797 1795 To check out a particular version, use -u/--update, or
1798 1796 -U/--noupdate to create a clone with no working directory.
1799 1797
1800 1798 To pull only a subset of changesets, specify one or more revisions
1801 1799 identifiers with -r/--rev or branches with -b/--branch. The
1802 1800 resulting clone will contain only the specified changesets and
1803 1801 their ancestors. These options (or 'clone src#rev dest') imply
1804 1802 --pull, even for local source repositories.
1805 1803
1806 1804 In normal clone mode, the remote normalizes repository data into a common
1807 1805 exchange format and the receiving end translates this data into its local
1808 1806 storage format. --stream activates a different clone mode that essentially
1809 1807 copies repository files from the remote with minimal data processing. This
1810 1808 significantly reduces the CPU cost of a clone both remotely and locally.
1811 1809 However, it often increases the transferred data size by 30-40%. This can
1812 1810 result in substantially faster clones where I/O throughput is plentiful,
1813 1811 especially for larger repositories. A side-effect of --stream clones is
1814 1812 that storage settings and requirements on the remote are applied locally:
1815 1813 a modern client may inherit legacy or inefficient storage used by the
1816 1814 remote or a legacy Mercurial client may not be able to clone from a
1817 1815 modern Mercurial remote.
1818 1816
1819 1817 .. note::
1820 1818
1821 1819 Specifying a tag will include the tagged changeset but not the
1822 1820 changeset containing the tag.
1823 1821
1824 1822 .. container:: verbose
1825 1823
1826 1824 For efficiency, hardlinks are used for cloning whenever the
1827 1825 source and destination are on the same filesystem (note this
1828 1826 applies only to the repository data, not to the working
1829 1827 directory). Some filesystems, such as AFS, implement hardlinking
1830 1828 incorrectly, but do not report errors. In these cases, use the
1831 1829 --pull option to avoid hardlinking.
1832 1830
1833 1831 Mercurial will update the working directory to the first applicable
1834 1832 revision from this list:
1835 1833
1836 1834 a) null if -U or the source repository has no changesets
1837 1835 b) if -u . and the source repository is local, the first parent of
1838 1836 the source repository's working directory
1839 1837 c) the changeset specified with -u (if a branch name, this means the
1840 1838 latest head of that branch)
1841 1839 d) the changeset specified with -r
1842 1840 e) the tipmost head specified with -b
1843 1841 f) the tipmost head specified with the url#branch source syntax
1844 1842 g) the revision marked with the '@' bookmark, if present
1845 1843 h) the tipmost head of the default branch
1846 1844 i) tip
1847 1845
1848 1846 When cloning from servers that support it, Mercurial may fetch
1849 1847 pre-generated data from a server-advertised URL or inline from the
1850 1848 same stream. When this is done, hooks operating on incoming changesets
1851 1849 and changegroups may fire more than once, once for each pre-generated
1852 1850 bundle and as well as for any additional remaining data. In addition,
1853 1851 if an error occurs, the repository may be rolled back to a partial
1854 1852 clone. This behavior may change in future releases.
1855 1853 See :hg:`help -e clonebundles` for more.
1856 1854
1857 1855 Examples:
1858 1856
1859 1857 - clone a remote repository to a new directory named hg/::
1860 1858
1861 1859 hg clone https://www.mercurial-scm.org/repo/hg/
1862 1860
1863 1861 - create a lightweight local clone::
1864 1862
1865 1863 hg clone project/ project-feature/
1866 1864
1867 1865 - clone from an absolute path on an ssh server (note double-slash)::
1868 1866
1869 1867 hg clone ssh://user@server//home/projects/alpha/
1870 1868
1871 1869 - do a streaming clone while checking out a specified version::
1872 1870
1873 1871 hg clone --stream http://server/repo -u 1.5
1874 1872
1875 1873 - create a repository without changesets after a particular revision::
1876 1874
1877 1875 hg clone -r 04e544 experimental/ good/
1878 1876
1879 1877 - clone (and track) a particular named branch::
1880 1878
1881 1879 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1882 1880
1883 1881 See :hg:`help urls` for details on specifying URLs.
1884 1882
1885 1883 Returns 0 on success.
1886 1884 """
1887 1885 opts = pycompat.byteskwargs(opts)
1888 1886 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1889 1887
1890 1888 # --include/--exclude can come from narrow or sparse.
1891 1889 includepats, excludepats = None, None
1892 1890
1893 1891 # hg.clone() differentiates between None and an empty set. So make sure
1894 1892 # patterns are sets if narrow is requested without patterns.
1895 1893 if opts.get(b'narrow'):
1896 1894 includepats = set()
1897 1895 excludepats = set()
1898 1896
1899 1897 if opts.get(b'include'):
1900 1898 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1901 1899 if opts.get(b'exclude'):
1902 1900 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1903 1901
1904 1902 r = hg.clone(
1905 1903 ui,
1906 1904 opts,
1907 1905 source,
1908 1906 dest,
1909 1907 pull=opts.get(b'pull'),
1910 1908 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1911 1909 revs=opts.get(b'rev'),
1912 1910 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1913 1911 branch=opts.get(b'branch'),
1914 1912 shareopts=opts.get(b'shareopts'),
1915 1913 storeincludepats=includepats,
1916 1914 storeexcludepats=excludepats,
1917 1915 depth=opts.get(b'depth') or None,
1918 1916 )
1919 1917
1920 1918 return r is None
1921 1919
1922 1920
1923 1921 @command(
1924 1922 b'commit|ci',
1925 1923 [
1926 1924 (
1927 1925 b'A',
1928 1926 b'addremove',
1929 1927 None,
1930 1928 _(b'mark new/missing files as added/removed before committing'),
1931 1929 ),
1932 1930 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1933 1931 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1934 1932 (b's', b'secret', None, _(b'use the secret phase for committing')),
1935 1933 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1936 1934 (
1937 1935 b'',
1938 1936 b'force-close-branch',
1939 1937 None,
1940 1938 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1941 1939 ),
1942 1940 (b'i', b'interactive', None, _(b'use interactive mode')),
1943 1941 ]
1944 1942 + walkopts
1945 1943 + commitopts
1946 1944 + commitopts2
1947 1945 + subrepoopts,
1948 1946 _(b'[OPTION]... [FILE]...'),
1949 1947 helpcategory=command.CATEGORY_COMMITTING,
1950 1948 helpbasic=True,
1951 1949 inferrepo=True,
1952 1950 )
1953 1951 def commit(ui, repo, *pats, **opts):
1954 1952 """commit the specified files or all outstanding changes
1955 1953
1956 1954 Commit changes to the given files into the repository. Unlike a
1957 1955 centralized SCM, this operation is a local operation. See
1958 1956 :hg:`push` for a way to actively distribute your changes.
1959 1957
1960 1958 If a list of files is omitted, all changes reported by :hg:`status`
1961 1959 will be committed.
1962 1960
1963 1961 If you are committing the result of a merge, do not provide any
1964 1962 filenames or -I/-X filters.
1965 1963
1966 1964 If no commit message is specified, Mercurial starts your
1967 1965 configured editor where you can enter a message. In case your
1968 1966 commit fails, you will find a backup of your message in
1969 1967 ``.hg/last-message.txt``.
1970 1968
1971 1969 The --close-branch flag can be used to mark the current branch
1972 1970 head closed. When all heads of a branch are closed, the branch
1973 1971 will be considered closed and no longer listed.
1974 1972
1975 1973 The --amend flag can be used to amend the parent of the
1976 1974 working directory with a new commit that contains the changes
1977 1975 in the parent in addition to those currently reported by :hg:`status`,
1978 1976 if there are any. The old commit is stored in a backup bundle in
1979 1977 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1980 1978 on how to restore it).
1981 1979
1982 1980 Message, user and date are taken from the amended commit unless
1983 1981 specified. When a message isn't specified on the command line,
1984 1982 the editor will open with the message of the amended commit.
1985 1983
1986 1984 It is not possible to amend public changesets (see :hg:`help phases`)
1987 1985 or changesets that have children.
1988 1986
1989 1987 See :hg:`help dates` for a list of formats valid for -d/--date.
1990 1988
1991 1989 Returns 0 on success, 1 if nothing changed.
1992 1990
1993 1991 .. container:: verbose
1994 1992
1995 1993 Examples:
1996 1994
1997 1995 - commit all files ending in .py::
1998 1996
1999 1997 hg commit --include "set:**.py"
2000 1998
2001 1999 - commit all non-binary files::
2002 2000
2003 2001 hg commit --exclude "set:binary()"
2004 2002
2005 2003 - amend the current commit and set the date to now::
2006 2004
2007 2005 hg commit --amend --date now
2008 2006 """
2009 2007 with repo.wlock(), repo.lock():
2010 2008 return _docommit(ui, repo, *pats, **opts)
2011 2009
2012 2010
2013 2011 def _docommit(ui, repo, *pats, **opts):
2014 2012 if opts.get('interactive'):
2015 2013 opts.pop('interactive')
2016 2014 ret = cmdutil.dorecord(
2017 2015 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2018 2016 )
2019 2017 # ret can be 0 (no changes to record) or the value returned by
2020 2018 # commit(), 1 if nothing changed or None on success.
2021 2019 return 1 if ret == 0 else ret
2022 2020
2023 2021 opts = pycompat.byteskwargs(opts)
2024 2022 if opts.get(b'subrepos'):
2025 2023 if opts.get(b'amend'):
2026 2024 raise error.Abort(_(b'cannot amend with --subrepos'))
2027 2025 # Let --subrepos on the command line override config setting.
2028 2026 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2029 2027
2030 2028 cmdutil.checkunfinished(repo, commit=True)
2031 2029
2032 2030 branch = repo[None].branch()
2033 2031 bheads = repo.branchheads(branch)
2034 2032
2035 2033 extra = {}
2036 2034 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2037 2035 extra[b'close'] = b'1'
2038 2036
2039 2037 if repo[b'.'].closesbranch():
2040 2038 raise error.Abort(
2041 2039 _(b'current revision is already a branch closing head')
2042 2040 )
2043 2041 elif not bheads:
2044 2042 raise error.Abort(_(b'branch "%s" has no heads to close') % branch)
2045 2043 elif (
2046 2044 branch == repo[b'.'].branch()
2047 2045 and repo[b'.'].node() not in bheads
2048 2046 and not opts.get(b'force_close_branch')
2049 2047 ):
2050 2048 hint = _(
2051 2049 b'use --force-close-branch to close branch from a non-head'
2052 2050 b' changeset'
2053 2051 )
2054 2052 raise error.Abort(_(b'can only close branch heads'), hint=hint)
2055 2053 elif opts.get(b'amend'):
2056 2054 if (
2057 2055 repo[b'.'].p1().branch() != branch
2058 2056 and repo[b'.'].p2().branch() != branch
2059 2057 ):
2060 2058 raise error.Abort(_(b'can only close branch heads'))
2061 2059
2062 2060 if opts.get(b'amend'):
2063 2061 if ui.configbool(b'ui', b'commitsubrepos'):
2064 2062 raise error.Abort(_(b'cannot amend with ui.commitsubrepos enabled'))
2065 2063
2066 2064 old = repo[b'.']
2067 2065 rewriteutil.precheck(repo, [old.rev()], b'amend')
2068 2066
2069 2067 # Currently histedit gets confused if an amend happens while histedit
2070 2068 # is in progress. Since we have a checkunfinished command, we are
2071 2069 # temporarily honoring it.
2072 2070 #
2073 2071 # Note: eventually this guard will be removed. Please do not expect
2074 2072 # this behavior to remain.
2075 2073 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2076 2074 cmdutil.checkunfinished(repo)
2077 2075
2078 2076 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2079 2077 if node == old.node():
2080 2078 ui.status(_(b"nothing changed\n"))
2081 2079 return 1
2082 2080 else:
2083 2081
2084 2082 def commitfunc(ui, repo, message, match, opts):
2085 2083 overrides = {}
2086 2084 if opts.get(b'secret'):
2087 2085 overrides[(b'phases', b'new-commit')] = b'secret'
2088 2086
2089 2087 baseui = repo.baseui
2090 2088 with baseui.configoverride(overrides, b'commit'):
2091 2089 with ui.configoverride(overrides, b'commit'):
2092 2090 editform = cmdutil.mergeeditform(
2093 2091 repo[None], b'commit.normal'
2094 2092 )
2095 2093 editor = cmdutil.getcommiteditor(
2096 2094 editform=editform, **pycompat.strkwargs(opts)
2097 2095 )
2098 2096 return repo.commit(
2099 2097 message,
2100 2098 opts.get(b'user'),
2101 2099 opts.get(b'date'),
2102 2100 match,
2103 2101 editor=editor,
2104 2102 extra=extra,
2105 2103 )
2106 2104
2107 2105 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2108 2106
2109 2107 if not node:
2110 2108 stat = cmdutil.postcommitstatus(repo, pats, opts)
2111 2109 if stat.deleted:
2112 2110 ui.status(
2113 2111 _(
2114 2112 b"nothing changed (%d missing files, see "
2115 2113 b"'hg status')\n"
2116 2114 )
2117 2115 % len(stat.deleted)
2118 2116 )
2119 2117 else:
2120 2118 ui.status(_(b"nothing changed\n"))
2121 2119 return 1
2122 2120
2123 2121 cmdutil.commitstatus(repo, node, branch, bheads, opts)
2124 2122
2125 2123 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2126 2124 status(
2127 2125 ui,
2128 2126 repo,
2129 2127 modified=True,
2130 2128 added=True,
2131 2129 removed=True,
2132 2130 deleted=True,
2133 2131 unknown=True,
2134 2132 subrepos=opts.get(b'subrepos'),
2135 2133 )
2136 2134
2137 2135
2138 2136 @command(
2139 2137 b'config|showconfig|debugconfig',
2140 2138 [
2141 2139 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2142 2140 (b'e', b'edit', None, _(b'edit user config')),
2143 2141 (b'l', b'local', None, _(b'edit repository config')),
2144 2142 (b'g', b'global', None, _(b'edit global config')),
2145 2143 ]
2146 2144 + formatteropts,
2147 2145 _(b'[-u] [NAME]...'),
2148 2146 helpcategory=command.CATEGORY_HELP,
2149 2147 optionalrepo=True,
2150 2148 intents={INTENT_READONLY},
2151 2149 )
2152 2150 def config(ui, repo, *values, **opts):
2153 2151 """show combined config settings from all hgrc files
2154 2152
2155 2153 With no arguments, print names and values of all config items.
2156 2154
2157 2155 With one argument of the form section.name, print just the value
2158 2156 of that config item.
2159 2157
2160 2158 With multiple arguments, print names and values of all config
2161 2159 items with matching section names or section.names.
2162 2160
2163 2161 With --edit, start an editor on the user-level config file. With
2164 2162 --global, edit the system-wide config file. With --local, edit the
2165 2163 repository-level config file.
2166 2164
2167 2165 With --debug, the source (filename and line number) is printed
2168 2166 for each config item.
2169 2167
2170 2168 See :hg:`help config` for more information about config files.
2171 2169
2172 2170 .. container:: verbose
2173 2171
2174 2172 Template:
2175 2173
2176 2174 The following keywords are supported. See also :hg:`help templates`.
2177 2175
2178 2176 :name: String. Config name.
2179 2177 :source: String. Filename and line number where the item is defined.
2180 2178 :value: String. Config value.
2181 2179
2182 2180 Returns 0 on success, 1 if NAME does not exist.
2183 2181
2184 2182 """
2185 2183
2186 2184 opts = pycompat.byteskwargs(opts)
2187 2185 if opts.get(b'edit') or opts.get(b'local') or opts.get(b'global'):
2188 2186 if opts.get(b'local') and opts.get(b'global'):
2189 2187 raise error.Abort(_(b"can't use --local and --global together"))
2190 2188
2191 2189 if opts.get(b'local'):
2192 2190 if not repo:
2193 2191 raise error.Abort(_(b"can't use --local outside a repository"))
2194 2192 paths = [repo.vfs.join(b'hgrc')]
2195 2193 elif opts.get(b'global'):
2196 2194 paths = rcutil.systemrcpath()
2197 2195 else:
2198 2196 paths = rcutil.userrcpath()
2199 2197
2200 2198 for f in paths:
2201 2199 if os.path.exists(f):
2202 2200 break
2203 2201 else:
2204 2202 if opts.get(b'global'):
2205 2203 samplehgrc = uimod.samplehgrcs[b'global']
2206 2204 elif opts.get(b'local'):
2207 2205 samplehgrc = uimod.samplehgrcs[b'local']
2208 2206 else:
2209 2207 samplehgrc = uimod.samplehgrcs[b'user']
2210 2208
2211 2209 f = paths[0]
2212 2210 fp = open(f, b"wb")
2213 2211 fp.write(util.tonativeeol(samplehgrc))
2214 2212 fp.close()
2215 2213
2216 2214 editor = ui.geteditor()
2217 2215 ui.system(
2218 2216 b"%s \"%s\"" % (editor, f),
2219 2217 onerr=error.Abort,
2220 2218 errprefix=_(b"edit failed"),
2221 2219 blockedtag=b'config_edit',
2222 2220 )
2223 2221 return
2224 2222 ui.pager(b'config')
2225 2223 fm = ui.formatter(b'config', opts)
2226 2224 for t, f in rcutil.rccomponents():
2227 2225 if t == b'path':
2228 2226 ui.debug(b'read config from: %s\n' % f)
2229 2227 elif t == b'items':
2230 2228 for section, name, value, source in f:
2231 2229 ui.debug(b'set config by: %s\n' % source)
2232 2230 else:
2233 2231 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2234 2232 untrusted = bool(opts.get(b'untrusted'))
2235 2233
2236 2234 selsections = selentries = []
2237 2235 if values:
2238 2236 selsections = [v for v in values if b'.' not in v]
2239 2237 selentries = [v for v in values if b'.' in v]
2240 2238 uniquesel = len(selentries) == 1 and not selsections
2241 2239 selsections = set(selsections)
2242 2240 selentries = set(selentries)
2243 2241
2244 2242 matched = False
2245 2243 for section, name, value in ui.walkconfig(untrusted=untrusted):
2246 2244 source = ui.configsource(section, name, untrusted)
2247 2245 value = pycompat.bytestr(value)
2248 2246 defaultvalue = ui.configdefault(section, name)
2249 2247 if fm.isplain():
2250 2248 source = source or b'none'
2251 2249 value = value.replace(b'\n', b'\\n')
2252 2250 entryname = section + b'.' + name
2253 2251 if values and not (section in selsections or entryname in selentries):
2254 2252 continue
2255 2253 fm.startitem()
2256 2254 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2257 2255 if uniquesel:
2258 2256 fm.data(name=entryname)
2259 2257 fm.write(b'value', b'%s\n', value)
2260 2258 else:
2261 2259 fm.write(b'name value', b'%s=%s\n', entryname, value)
2262 2260 if formatter.isprintable(defaultvalue):
2263 2261 fm.data(defaultvalue=defaultvalue)
2264 2262 elif isinstance(defaultvalue, list) and all(
2265 2263 formatter.isprintable(e) for e in defaultvalue
2266 2264 ):
2267 2265 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2268 2266 # TODO: no idea how to process unsupported defaultvalue types
2269 2267 matched = True
2270 2268 fm.end()
2271 2269 if matched:
2272 2270 return 0
2273 2271 return 1
2274 2272
2275 2273
2276 2274 @command(
2277 2275 b'continue',
2278 2276 dryrunopts,
2279 2277 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2280 2278 helpbasic=True,
2281 2279 )
2282 2280 def continuecmd(ui, repo, **opts):
2283 2281 """resumes an interrupted operation (EXPERIMENTAL)
2284 2282
2285 2283 Finishes a multistep operation like graft, histedit, rebase, merge,
2286 2284 and unshelve if they are in an interrupted state.
2287 2285
2288 2286 use --dry-run/-n to dry run the command.
2289 2287 """
2290 2288 dryrun = opts.get('dry_run')
2291 2289 contstate = cmdutil.getunfinishedstate(repo)
2292 2290 if not contstate:
2293 2291 raise error.Abort(_(b'no operation in progress'))
2294 2292 if not contstate.continuefunc:
2295 2293 raise error.Abort(
2296 2294 (
2297 2295 _(b"%s in progress but does not support 'hg continue'")
2298 2296 % (contstate._opname)
2299 2297 ),
2300 2298 hint=contstate.continuemsg(),
2301 2299 )
2302 2300 if dryrun:
2303 2301 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2304 2302 return
2305 2303 return contstate.continuefunc(ui, repo)
2306 2304
2307 2305
2308 2306 @command(
2309 2307 b'copy|cp',
2310 2308 [
2311 2309 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2312 2310 (
2313 2311 b'f',
2314 2312 b'force',
2315 2313 None,
2316 2314 _(b'forcibly copy over an existing managed file'),
2317 2315 ),
2318 2316 ]
2319 2317 + walkopts
2320 2318 + dryrunopts,
2321 2319 _(b'[OPTION]... SOURCE... DEST'),
2322 2320 helpcategory=command.CATEGORY_FILE_CONTENTS,
2323 2321 )
2324 2322 def copy(ui, repo, *pats, **opts):
2325 2323 """mark files as copied for the next commit
2326 2324
2327 2325 Mark dest as having copies of source files. If dest is a
2328 2326 directory, copies are put in that directory. If dest is a file,
2329 2327 the source must be a single file.
2330 2328
2331 2329 By default, this command copies the contents of files as they
2332 2330 exist in the working directory. If invoked with -A/--after, the
2333 2331 operation is recorded, but no copying is performed.
2334 2332
2335 2333 This command takes effect with the next commit. To undo a copy
2336 2334 before that, see :hg:`revert`.
2337 2335
2338 2336 Returns 0 on success, 1 if errors are encountered.
2339 2337 """
2340 2338 opts = pycompat.byteskwargs(opts)
2341 2339 with repo.wlock(False):
2342 2340 return cmdutil.copy(ui, repo, pats, opts)
2343 2341
2344 2342
2345 2343 @command(
2346 2344 b'debugcommands',
2347 2345 [],
2348 2346 _(b'[COMMAND]'),
2349 2347 helpcategory=command.CATEGORY_HELP,
2350 2348 norepo=True,
2351 2349 )
2352 2350 def debugcommands(ui, cmd=b'', *args):
2353 2351 """list all available commands and options"""
2354 2352 for cmd, vals in sorted(pycompat.iteritems(table)):
2355 2353 cmd = cmd.split(b'|')[0]
2356 2354 opts = b', '.join([i[1] for i in vals[1]])
2357 2355 ui.write(b'%s: %s\n' % (cmd, opts))
2358 2356
2359 2357
2360 2358 @command(
2361 2359 b'debugcomplete',
2362 2360 [(b'o', b'options', None, _(b'show the command options'))],
2363 2361 _(b'[-o] CMD'),
2364 2362 helpcategory=command.CATEGORY_HELP,
2365 2363 norepo=True,
2366 2364 )
2367 2365 def debugcomplete(ui, cmd=b'', **opts):
2368 2366 """returns the completion list associated with the given command"""
2369 2367
2370 2368 if opts.get('options'):
2371 2369 options = []
2372 2370 otables = [globalopts]
2373 2371 if cmd:
2374 2372 aliases, entry = cmdutil.findcmd(cmd, table, False)
2375 2373 otables.append(entry[1])
2376 2374 for t in otables:
2377 2375 for o in t:
2378 2376 if b"(DEPRECATED)" in o[3]:
2379 2377 continue
2380 2378 if o[0]:
2381 2379 options.append(b'-%s' % o[0])
2382 2380 options.append(b'--%s' % o[1])
2383 2381 ui.write(b"%s\n" % b"\n".join(options))
2384 2382 return
2385 2383
2386 2384 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2387 2385 if ui.verbose:
2388 2386 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2389 2387 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2390 2388
2391 2389
2392 2390 @command(
2393 2391 b'diff',
2394 2392 [
2395 2393 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
2396 2394 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2397 2395 ]
2398 2396 + diffopts
2399 2397 + diffopts2
2400 2398 + walkopts
2401 2399 + subrepoopts,
2402 2400 _(b'[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
2403 2401 helpcategory=command.CATEGORY_FILE_CONTENTS,
2404 2402 helpbasic=True,
2405 2403 inferrepo=True,
2406 2404 intents={INTENT_READONLY},
2407 2405 )
2408 2406 def diff(ui, repo, *pats, **opts):
2409 2407 """diff repository (or selected files)
2410 2408
2411 2409 Show differences between revisions for the specified files.
2412 2410
2413 2411 Differences between files are shown using the unified diff format.
2414 2412
2415 2413 .. note::
2416 2414
2417 2415 :hg:`diff` may generate unexpected results for merges, as it will
2418 2416 default to comparing against the working directory's first
2419 2417 parent changeset if no revisions are specified.
2420 2418
2421 2419 When two revision arguments are given, then changes are shown
2422 2420 between those revisions. If only one revision is specified then
2423 2421 that revision is compared to the working directory, and, when no
2424 2422 revisions are specified, the working directory files are compared
2425 2423 to its first parent.
2426 2424
2427 2425 Alternatively you can specify -c/--change with a revision to see
2428 2426 the changes in that changeset relative to its first parent.
2429 2427
2430 2428 Without the -a/--text option, diff will avoid generating diffs of
2431 2429 files it detects as binary. With -a, diff will generate a diff
2432 2430 anyway, probably with undesirable results.
2433 2431
2434 2432 Use the -g/--git option to generate diffs in the git extended diff
2435 2433 format. For more information, read :hg:`help diffs`.
2436 2434
2437 2435 .. container:: verbose
2438 2436
2439 2437 Examples:
2440 2438
2441 2439 - compare a file in the current working directory to its parent::
2442 2440
2443 2441 hg diff foo.c
2444 2442
2445 2443 - compare two historical versions of a directory, with rename info::
2446 2444
2447 2445 hg diff --git -r 1.0:1.2 lib/
2448 2446
2449 2447 - get change stats relative to the last change on some date::
2450 2448
2451 2449 hg diff --stat -r "date('may 2')"
2452 2450
2453 2451 - diff all newly-added files that contain a keyword::
2454 2452
2455 2453 hg diff "set:added() and grep(GNU)"
2456 2454
2457 2455 - compare a revision and its parents::
2458 2456
2459 2457 hg diff -c 9353 # compare against first parent
2460 2458 hg diff -r 9353^:9353 # same using revset syntax
2461 2459 hg diff -r 9353^2:9353 # compare against the second parent
2462 2460
2463 2461 Returns 0 on success.
2464 2462 """
2465 2463
2466 2464 opts = pycompat.byteskwargs(opts)
2467 2465 revs = opts.get(b'rev')
2468 2466 change = opts.get(b'change')
2469 2467 stat = opts.get(b'stat')
2470 2468 reverse = opts.get(b'reverse')
2471 2469
2472 2470 if revs and change:
2473 2471 msg = _(b'cannot specify --rev and --change at the same time')
2474 2472 raise error.Abort(msg)
2475 2473 elif change:
2476 2474 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2477 2475 ctx2 = scmutil.revsingle(repo, change, None)
2478 2476 ctx1 = ctx2.p1()
2479 2477 else:
2480 2478 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2481 2479 ctx1, ctx2 = scmutil.revpair(repo, revs)
2482 2480 node1, node2 = ctx1.node(), ctx2.node()
2483 2481
2484 2482 if reverse:
2485 2483 node1, node2 = node2, node1
2486 2484
2487 2485 diffopts = patch.diffallopts(ui, opts)
2488 2486 m = scmutil.match(ctx2, pats, opts)
2489 2487 m = repo.narrowmatch(m)
2490 2488 ui.pager(b'diff')
2491 2489 logcmdutil.diffordiffstat(
2492 2490 ui,
2493 2491 repo,
2494 2492 diffopts,
2495 2493 node1,
2496 2494 node2,
2497 2495 m,
2498 2496 stat=stat,
2499 2497 listsubrepos=opts.get(b'subrepos'),
2500 2498 root=opts.get(b'root'),
2501 2499 )
2502 2500
2503 2501
2504 2502 @command(
2505 2503 b'export',
2506 2504 [
2507 2505 (
2508 2506 b'B',
2509 2507 b'bookmark',
2510 2508 b'',
2511 2509 _(b'export changes only reachable by given bookmark'),
2512 2510 _(b'BOOKMARK'),
2513 2511 ),
2514 2512 (
2515 2513 b'o',
2516 2514 b'output',
2517 2515 b'',
2518 2516 _(b'print output to file with formatted name'),
2519 2517 _(b'FORMAT'),
2520 2518 ),
2521 2519 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2522 2520 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2523 2521 ]
2524 2522 + diffopts
2525 2523 + formatteropts,
2526 2524 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2527 2525 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2528 2526 helpbasic=True,
2529 2527 intents={INTENT_READONLY},
2530 2528 )
2531 2529 def export(ui, repo, *changesets, **opts):
2532 2530 """dump the header and diffs for one or more changesets
2533 2531
2534 2532 Print the changeset header and diffs for one or more revisions.
2535 2533 If no revision is given, the parent of the working directory is used.
2536 2534
2537 2535 The information shown in the changeset header is: author, date,
2538 2536 branch name (if non-default), changeset hash, parent(s) and commit
2539 2537 comment.
2540 2538
2541 2539 .. note::
2542 2540
2543 2541 :hg:`export` may generate unexpected diff output for merge
2544 2542 changesets, as it will compare the merge changeset against its
2545 2543 first parent only.
2546 2544
2547 2545 Output may be to a file, in which case the name of the file is
2548 2546 given using a template string. See :hg:`help templates`. In addition
2549 2547 to the common template keywords, the following formatting rules are
2550 2548 supported:
2551 2549
2552 2550 :``%%``: literal "%" character
2553 2551 :``%H``: changeset hash (40 hexadecimal digits)
2554 2552 :``%N``: number of patches being generated
2555 2553 :``%R``: changeset revision number
2556 2554 :``%b``: basename of the exporting repository
2557 2555 :``%h``: short-form changeset hash (12 hexadecimal digits)
2558 2556 :``%m``: first line of the commit message (only alphanumeric characters)
2559 2557 :``%n``: zero-padded sequence number, starting at 1
2560 2558 :``%r``: zero-padded changeset revision number
2561 2559 :``\\``: literal "\\" character
2562 2560
2563 2561 Without the -a/--text option, export will avoid generating diffs
2564 2562 of files it detects as binary. With -a, export will generate a
2565 2563 diff anyway, probably with undesirable results.
2566 2564
2567 2565 With -B/--bookmark changesets reachable by the given bookmark are
2568 2566 selected.
2569 2567
2570 2568 Use the -g/--git option to generate diffs in the git extended diff
2571 2569 format. See :hg:`help diffs` for more information.
2572 2570
2573 2571 With the --switch-parent option, the diff will be against the
2574 2572 second parent. It can be useful to review a merge.
2575 2573
2576 2574 .. container:: verbose
2577 2575
2578 2576 Template:
2579 2577
2580 2578 The following keywords are supported in addition to the common template
2581 2579 keywords and functions. See also :hg:`help templates`.
2582 2580
2583 2581 :diff: String. Diff content.
2584 2582 :parents: List of strings. Parent nodes of the changeset.
2585 2583
2586 2584 Examples:
2587 2585
2588 2586 - use export and import to transplant a bugfix to the current
2589 2587 branch::
2590 2588
2591 2589 hg export -r 9353 | hg import -
2592 2590
2593 2591 - export all the changesets between two revisions to a file with
2594 2592 rename information::
2595 2593
2596 2594 hg export --git -r 123:150 > changes.txt
2597 2595
2598 2596 - split outgoing changes into a series of patches with
2599 2597 descriptive names::
2600 2598
2601 2599 hg export -r "outgoing()" -o "%n-%m.patch"
2602 2600
2603 2601 Returns 0 on success.
2604 2602 """
2605 2603 opts = pycompat.byteskwargs(opts)
2606 2604 bookmark = opts.get(b'bookmark')
2607 2605 changesets += tuple(opts.get(b'rev', []))
2608 2606
2609 2607 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2610 2608
2611 2609 if bookmark:
2612 2610 if bookmark not in repo._bookmarks:
2613 2611 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
2614 2612
2615 2613 revs = scmutil.bookmarkrevs(repo, bookmark)
2616 2614 else:
2617 2615 if not changesets:
2618 2616 changesets = [b'.']
2619 2617
2620 2618 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2621 2619 revs = scmutil.revrange(repo, changesets)
2622 2620
2623 2621 if not revs:
2624 2622 raise error.Abort(_(b"export requires at least one changeset"))
2625 2623 if len(revs) > 1:
2626 2624 ui.note(_(b'exporting patches:\n'))
2627 2625 else:
2628 2626 ui.note(_(b'exporting patch:\n'))
2629 2627
2630 2628 fntemplate = opts.get(b'output')
2631 2629 if cmdutil.isstdiofilename(fntemplate):
2632 2630 fntemplate = b''
2633 2631
2634 2632 if fntemplate:
2635 2633 fm = formatter.nullformatter(ui, b'export', opts)
2636 2634 else:
2637 2635 ui.pager(b'export')
2638 2636 fm = ui.formatter(b'export', opts)
2639 2637 with fm:
2640 2638 cmdutil.export(
2641 2639 repo,
2642 2640 revs,
2643 2641 fm,
2644 2642 fntemplate=fntemplate,
2645 2643 switch_parent=opts.get(b'switch_parent'),
2646 2644 opts=patch.diffallopts(ui, opts),
2647 2645 )
2648 2646
2649 2647
2650 2648 @command(
2651 2649 b'files',
2652 2650 [
2653 2651 (
2654 2652 b'r',
2655 2653 b'rev',
2656 2654 b'',
2657 2655 _(b'search the repository as it is in REV'),
2658 2656 _(b'REV'),
2659 2657 ),
2660 2658 (
2661 2659 b'0',
2662 2660 b'print0',
2663 2661 None,
2664 2662 _(b'end filenames with NUL, for use with xargs'),
2665 2663 ),
2666 2664 ]
2667 2665 + walkopts
2668 2666 + formatteropts
2669 2667 + subrepoopts,
2670 2668 _(b'[OPTION]... [FILE]...'),
2671 2669 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2672 2670 intents={INTENT_READONLY},
2673 2671 )
2674 2672 def files(ui, repo, *pats, **opts):
2675 2673 """list tracked files
2676 2674
2677 2675 Print files under Mercurial control in the working directory or
2678 2676 specified revision for given files (excluding removed files).
2679 2677 Files can be specified as filenames or filesets.
2680 2678
2681 2679 If no files are given to match, this command prints the names
2682 2680 of all files under Mercurial control.
2683 2681
2684 2682 .. container:: verbose
2685 2683
2686 2684 Template:
2687 2685
2688 2686 The following keywords are supported in addition to the common template
2689 2687 keywords and functions. See also :hg:`help templates`.
2690 2688
2691 2689 :flags: String. Character denoting file's symlink and executable bits.
2692 2690 :path: String. Repository-absolute path of the file.
2693 2691 :size: Integer. Size of the file in bytes.
2694 2692
2695 2693 Examples:
2696 2694
2697 2695 - list all files under the current directory::
2698 2696
2699 2697 hg files .
2700 2698
2701 2699 - shows sizes and flags for current revision::
2702 2700
2703 2701 hg files -vr .
2704 2702
2705 2703 - list all files named README::
2706 2704
2707 2705 hg files -I "**/README"
2708 2706
2709 2707 - list all binary files::
2710 2708
2711 2709 hg files "set:binary()"
2712 2710
2713 2711 - find files containing a regular expression::
2714 2712
2715 2713 hg files "set:grep('bob')"
2716 2714
2717 2715 - search tracked file contents with xargs and grep::
2718 2716
2719 2717 hg files -0 | xargs -0 grep foo
2720 2718
2721 2719 See :hg:`help patterns` and :hg:`help filesets` for more information
2722 2720 on specifying file patterns.
2723 2721
2724 2722 Returns 0 if a match is found, 1 otherwise.
2725 2723
2726 2724 """
2727 2725
2728 2726 opts = pycompat.byteskwargs(opts)
2729 2727 rev = opts.get(b'rev')
2730 2728 if rev:
2731 2729 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2732 2730 ctx = scmutil.revsingle(repo, rev, None)
2733 2731
2734 2732 end = b'\n'
2735 2733 if opts.get(b'print0'):
2736 2734 end = b'\0'
2737 2735 fmt = b'%s' + end
2738 2736
2739 2737 m = scmutil.match(ctx, pats, opts)
2740 2738 ui.pager(b'files')
2741 2739 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2742 2740 with ui.formatter(b'files', opts) as fm:
2743 2741 return cmdutil.files(
2744 2742 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2745 2743 )
2746 2744
2747 2745
2748 2746 @command(
2749 2747 b'forget',
2750 2748 [(b'i', b'interactive', None, _(b'use interactive mode')),]
2751 2749 + walkopts
2752 2750 + dryrunopts,
2753 2751 _(b'[OPTION]... FILE...'),
2754 2752 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2755 2753 helpbasic=True,
2756 2754 inferrepo=True,
2757 2755 )
2758 2756 def forget(ui, repo, *pats, **opts):
2759 2757 """forget the specified files on the next commit
2760 2758
2761 2759 Mark the specified files so they will no longer be tracked
2762 2760 after the next commit.
2763 2761
2764 2762 This only removes files from the current branch, not from the
2765 2763 entire project history, and it does not delete them from the
2766 2764 working directory.
2767 2765
2768 2766 To delete the file from the working directory, see :hg:`remove`.
2769 2767
2770 2768 To undo a forget before the next commit, see :hg:`add`.
2771 2769
2772 2770 .. container:: verbose
2773 2771
2774 2772 Examples:
2775 2773
2776 2774 - forget newly-added binary files::
2777 2775
2778 2776 hg forget "set:added() and binary()"
2779 2777
2780 2778 - forget files that would be excluded by .hgignore::
2781 2779
2782 2780 hg forget "set:hgignore()"
2783 2781
2784 2782 Returns 0 on success.
2785 2783 """
2786 2784
2787 2785 opts = pycompat.byteskwargs(opts)
2788 2786 if not pats:
2789 2787 raise error.Abort(_(b'no files specified'))
2790 2788
2791 2789 m = scmutil.match(repo[None], pats, opts)
2792 2790 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2793 2791 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2794 2792 rejected = cmdutil.forget(
2795 2793 ui,
2796 2794 repo,
2797 2795 m,
2798 2796 prefix=b"",
2799 2797 uipathfn=uipathfn,
2800 2798 explicitonly=False,
2801 2799 dryrun=dryrun,
2802 2800 interactive=interactive,
2803 2801 )[0]
2804 2802 return rejected and 1 or 0
2805 2803
2806 2804
2807 2805 @command(
2808 2806 b'graft',
2809 2807 [
2810 2808 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2811 2809 (
2812 2810 b'',
2813 2811 b'base',
2814 2812 b'',
2815 2813 _(b'base revision when doing the graft merge (ADVANCED)'),
2816 2814 _(b'REV'),
2817 2815 ),
2818 2816 (b'c', b'continue', False, _(b'resume interrupted graft')),
2819 2817 (b'', b'stop', False, _(b'stop interrupted graft')),
2820 2818 (b'', b'abort', False, _(b'abort interrupted graft')),
2821 2819 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2822 2820 (b'', b'log', None, _(b'append graft info to log message')),
2823 2821 (
2824 2822 b'',
2825 2823 b'no-commit',
2826 2824 None,
2827 2825 _(b"don't commit, just apply the changes in working directory"),
2828 2826 ),
2829 2827 (b'f', b'force', False, _(b'force graft')),
2830 2828 (
2831 2829 b'D',
2832 2830 b'currentdate',
2833 2831 False,
2834 2832 _(b'record the current date as commit date'),
2835 2833 ),
2836 2834 (
2837 2835 b'U',
2838 2836 b'currentuser',
2839 2837 False,
2840 2838 _(b'record the current user as committer'),
2841 2839 ),
2842 2840 ]
2843 2841 + commitopts2
2844 2842 + mergetoolopts
2845 2843 + dryrunopts,
2846 2844 _(b'[OPTION]... [-r REV]... REV...'),
2847 2845 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2848 2846 )
2849 2847 def graft(ui, repo, *revs, **opts):
2850 2848 '''copy changes from other branches onto the current branch
2851 2849
2852 2850 This command uses Mercurial's merge logic to copy individual
2853 2851 changes from other branches without merging branches in the
2854 2852 history graph. This is sometimes known as 'backporting' or
2855 2853 'cherry-picking'. By default, graft will copy user, date, and
2856 2854 description from the source changesets.
2857 2855
2858 2856 Changesets that are ancestors of the current revision, that have
2859 2857 already been grafted, or that are merges will be skipped.
2860 2858
2861 2859 If --log is specified, log messages will have a comment appended
2862 2860 of the form::
2863 2861
2864 2862 (grafted from CHANGESETHASH)
2865 2863
2866 2864 If --force is specified, revisions will be grafted even if they
2867 2865 are already ancestors of, or have been grafted to, the destination.
2868 2866 This is useful when the revisions have since been backed out.
2869 2867
2870 2868 If a graft merge results in conflicts, the graft process is
2871 2869 interrupted so that the current merge can be manually resolved.
2872 2870 Once all conflicts are addressed, the graft process can be
2873 2871 continued with the -c/--continue option.
2874 2872
2875 2873 The -c/--continue option reapplies all the earlier options.
2876 2874
2877 2875 .. container:: verbose
2878 2876
2879 2877 The --base option exposes more of how graft internally uses merge with a
2880 2878 custom base revision. --base can be used to specify another ancestor than
2881 2879 the first and only parent.
2882 2880
2883 2881 The command::
2884 2882
2885 2883 hg graft -r 345 --base 234
2886 2884
2887 2885 is thus pretty much the same as::
2888 2886
2889 2887 hg diff -r 234 -r 345 | hg import
2890 2888
2891 2889 but using merge to resolve conflicts and track moved files.
2892 2890
2893 2891 The result of a merge can thus be backported as a single commit by
2894 2892 specifying one of the merge parents as base, and thus effectively
2895 2893 grafting the changes from the other side.
2896 2894
2897 2895 It is also possible to collapse multiple changesets and clean up history
2898 2896 by specifying another ancestor as base, much like rebase --collapse
2899 2897 --keep.
2900 2898
2901 2899 The commit message can be tweaked after the fact using commit --amend .
2902 2900
2903 2901 For using non-ancestors as the base to backout changes, see the backout
2904 2902 command and the hidden --parent option.
2905 2903
2906 2904 .. container:: verbose
2907 2905
2908 2906 Examples:
2909 2907
2910 2908 - copy a single change to the stable branch and edit its description::
2911 2909
2912 2910 hg update stable
2913 2911 hg graft --edit 9393
2914 2912
2915 2913 - graft a range of changesets with one exception, updating dates::
2916 2914
2917 2915 hg graft -D "2085::2093 and not 2091"
2918 2916
2919 2917 - continue a graft after resolving conflicts::
2920 2918
2921 2919 hg graft -c
2922 2920
2923 2921 - show the source of a grafted changeset::
2924 2922
2925 2923 hg log --debug -r .
2926 2924
2927 2925 - show revisions sorted by date::
2928 2926
2929 2927 hg log -r "sort(all(), date)"
2930 2928
2931 2929 - backport the result of a merge as a single commit::
2932 2930
2933 2931 hg graft -r 123 --base 123^
2934 2932
2935 2933 - land a feature branch as one changeset::
2936 2934
2937 2935 hg up -cr default
2938 2936 hg graft -r featureX --base "ancestor('featureX', 'default')"
2939 2937
2940 2938 See :hg:`help revisions` for more about specifying revisions.
2941 2939
2942 2940 Returns 0 on successful completion.
2943 2941 '''
2944 2942 with repo.wlock():
2945 2943 return _dograft(ui, repo, *revs, **opts)
2946 2944
2947 2945
2948 2946 def _dograft(ui, repo, *revs, **opts):
2949 2947 opts = pycompat.byteskwargs(opts)
2950 2948 if revs and opts.get(b'rev'):
2951 2949 ui.warn(
2952 2950 _(
2953 2951 b'warning: inconsistent use of --rev might give unexpected '
2954 2952 b'revision ordering!\n'
2955 2953 )
2956 2954 )
2957 2955
2958 2956 revs = list(revs)
2959 2957 revs.extend(opts.get(b'rev'))
2960 2958 basectx = None
2961 2959 if opts.get(b'base'):
2962 2960 basectx = scmutil.revsingle(repo, opts[b'base'], None)
2963 2961 # a dict of data to be stored in state file
2964 2962 statedata = {}
2965 2963 # list of new nodes created by ongoing graft
2966 2964 statedata[b'newnodes'] = []
2967 2965
2968 2966 cmdutil.resolvecommitoptions(ui, opts)
2969 2967
2970 2968 editor = cmdutil.getcommiteditor(
2971 2969 editform=b'graft', **pycompat.strkwargs(opts)
2972 2970 )
2973 2971
2974 2972 cont = False
2975 2973 if opts.get(b'no_commit'):
2976 2974 if opts.get(b'edit'):
2977 2975 raise error.Abort(
2978 2976 _(b"cannot specify --no-commit and --edit together")
2979 2977 )
2980 2978 if opts.get(b'currentuser'):
2981 2979 raise error.Abort(
2982 2980 _(b"cannot specify --no-commit and --currentuser together")
2983 2981 )
2984 2982 if opts.get(b'currentdate'):
2985 2983 raise error.Abort(
2986 2984 _(b"cannot specify --no-commit and --currentdate together")
2987 2985 )
2988 2986 if opts.get(b'log'):
2989 2987 raise error.Abort(
2990 2988 _(b"cannot specify --no-commit and --log together")
2991 2989 )
2992 2990
2993 2991 graftstate = statemod.cmdstate(repo, b'graftstate')
2994 2992
2995 2993 if opts.get(b'stop'):
2996 2994 if opts.get(b'continue'):
2997 2995 raise error.Abort(
2998 2996 _(b"cannot use '--continue' and '--stop' together")
2999 2997 )
3000 2998 if opts.get(b'abort'):
3001 2999 raise error.Abort(_(b"cannot use '--abort' and '--stop' together"))
3002 3000
3003 3001 if any(
3004 3002 (
3005 3003 opts.get(b'edit'),
3006 3004 opts.get(b'log'),
3007 3005 opts.get(b'user'),
3008 3006 opts.get(b'date'),
3009 3007 opts.get(b'currentdate'),
3010 3008 opts.get(b'currentuser'),
3011 3009 opts.get(b'rev'),
3012 3010 )
3013 3011 ):
3014 3012 raise error.Abort(_(b"cannot specify any other flag with '--stop'"))
3015 3013 return _stopgraft(ui, repo, graftstate)
3016 3014 elif opts.get(b'abort'):
3017 3015 if opts.get(b'continue'):
3018 3016 raise error.Abort(
3019 3017 _(b"cannot use '--continue' and '--abort' together")
3020 3018 )
3021 3019 if any(
3022 3020 (
3023 3021 opts.get(b'edit'),
3024 3022 opts.get(b'log'),
3025 3023 opts.get(b'user'),
3026 3024 opts.get(b'date'),
3027 3025 opts.get(b'currentdate'),
3028 3026 opts.get(b'currentuser'),
3029 3027 opts.get(b'rev'),
3030 3028 )
3031 3029 ):
3032 3030 raise error.Abort(
3033 3031 _(b"cannot specify any other flag with '--abort'")
3034 3032 )
3035 3033
3036 3034 return cmdutil.abortgraft(ui, repo, graftstate)
3037 3035 elif opts.get(b'continue'):
3038 3036 cont = True
3039 3037 if revs:
3040 3038 raise error.Abort(_(b"can't specify --continue and revisions"))
3041 3039 # read in unfinished revisions
3042 3040 if graftstate.exists():
3043 3041 statedata = cmdutil.readgraftstate(repo, graftstate)
3044 3042 if statedata.get(b'date'):
3045 3043 opts[b'date'] = statedata[b'date']
3046 3044 if statedata.get(b'user'):
3047 3045 opts[b'user'] = statedata[b'user']
3048 3046 if statedata.get(b'log'):
3049 3047 opts[b'log'] = True
3050 3048 if statedata.get(b'no_commit'):
3051 3049 opts[b'no_commit'] = statedata.get(b'no_commit')
3052 3050 nodes = statedata[b'nodes']
3053 3051 revs = [repo[node].rev() for node in nodes]
3054 3052 else:
3055 3053 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3056 3054 else:
3057 3055 if not revs:
3058 3056 raise error.Abort(_(b'no revisions specified'))
3059 3057 cmdutil.checkunfinished(repo)
3060 3058 cmdutil.bailifchanged(repo)
3061 3059 revs = scmutil.revrange(repo, revs)
3062 3060
3063 3061 skipped = set()
3064 3062 if basectx is None:
3065 3063 # check for merges
3066 3064 for rev in repo.revs(b'%ld and merge()', revs):
3067 3065 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3068 3066 skipped.add(rev)
3069 3067 revs = [r for r in revs if r not in skipped]
3070 3068 if not revs:
3071 3069 return -1
3072 3070 if basectx is not None and len(revs) != 1:
3073 3071 raise error.Abort(_(b'only one revision allowed with --base '))
3074 3072
3075 3073 # Don't check in the --continue case, in effect retaining --force across
3076 3074 # --continues. That's because without --force, any revisions we decided to
3077 3075 # skip would have been filtered out here, so they wouldn't have made their
3078 3076 # way to the graftstate. With --force, any revisions we would have otherwise
3079 3077 # skipped would not have been filtered out, and if they hadn't been applied
3080 3078 # already, they'd have been in the graftstate.
3081 3079 if not (cont or opts.get(b'force')) and basectx is None:
3082 3080 # check for ancestors of dest branch
3083 3081 crev = repo[b'.'].rev()
3084 3082 ancestors = repo.changelog.ancestors([crev], inclusive=True)
3085 3083 # XXX make this lazy in the future
3086 3084 # don't mutate while iterating, create a copy
3087 3085 for rev in list(revs):
3088 3086 if rev in ancestors:
3089 3087 ui.warn(
3090 3088 _(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev])
3091 3089 )
3092 3090 # XXX remove on list is slow
3093 3091 revs.remove(rev)
3094 3092 if not revs:
3095 3093 return -1
3096 3094
3097 3095 # analyze revs for earlier grafts
3098 3096 ids = {}
3099 3097 for ctx in repo.set(b"%ld", revs):
3100 3098 ids[ctx.hex()] = ctx.rev()
3101 3099 n = ctx.extra().get(b'source')
3102 3100 if n:
3103 3101 ids[n] = ctx.rev()
3104 3102
3105 3103 # check ancestors for earlier grafts
3106 3104 ui.debug(b'scanning for duplicate grafts\n')
3107 3105
3108 3106 # The only changesets we can be sure doesn't contain grafts of any
3109 3107 # revs, are the ones that are common ancestors of *all* revs:
3110 3108 for rev in repo.revs(b'only(%d,ancestor(%ld))', crev, revs):
3111 3109 ctx = repo[rev]
3112 3110 n = ctx.extra().get(b'source')
3113 3111 if n in ids:
3114 3112 try:
3115 3113 r = repo[n].rev()
3116 3114 except error.RepoLookupError:
3117 3115 r = None
3118 3116 if r in revs:
3119 3117 ui.warn(
3120 3118 _(
3121 3119 b'skipping revision %d:%s '
3122 3120 b'(already grafted to %d:%s)\n'
3123 3121 )
3124 3122 % (r, repo[r], rev, ctx)
3125 3123 )
3126 3124 revs.remove(r)
3127 3125 elif ids[n] in revs:
3128 3126 if r is None:
3129 3127 ui.warn(
3130 3128 _(
3131 3129 b'skipping already grafted revision %d:%s '
3132 3130 b'(%d:%s also has unknown origin %s)\n'
3133 3131 )
3134 3132 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3135 3133 )
3136 3134 else:
3137 3135 ui.warn(
3138 3136 _(
3139 3137 b'skipping already grafted revision %d:%s '
3140 3138 b'(%d:%s also has origin %d:%s)\n'
3141 3139 )
3142 3140 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3143 3141 )
3144 3142 revs.remove(ids[n])
3145 3143 elif ctx.hex() in ids:
3146 3144 r = ids[ctx.hex()]
3147 3145 if r in revs:
3148 3146 ui.warn(
3149 3147 _(
3150 3148 b'skipping already grafted revision %d:%s '
3151 3149 b'(was grafted from %d:%s)\n'
3152 3150 )
3153 3151 % (r, repo[r], rev, ctx)
3154 3152 )
3155 3153 revs.remove(r)
3156 3154 if not revs:
3157 3155 return -1
3158 3156
3159 3157 if opts.get(b'no_commit'):
3160 3158 statedata[b'no_commit'] = True
3161 3159 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3162 3160 desc = b'%d:%s "%s"' % (
3163 3161 ctx.rev(),
3164 3162 ctx,
3165 3163 ctx.description().split(b'\n', 1)[0],
3166 3164 )
3167 3165 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3168 3166 if names:
3169 3167 desc += b' (%s)' % b' '.join(names)
3170 3168 ui.status(_(b'grafting %s\n') % desc)
3171 3169 if opts.get(b'dry_run'):
3172 3170 continue
3173 3171
3174 3172 source = ctx.extra().get(b'source')
3175 3173 extra = {}
3176 3174 if source:
3177 3175 extra[b'source'] = source
3178 3176 extra[b'intermediate-source'] = ctx.hex()
3179 3177 else:
3180 3178 extra[b'source'] = ctx.hex()
3181 3179 user = ctx.user()
3182 3180 if opts.get(b'user'):
3183 3181 user = opts[b'user']
3184 3182 statedata[b'user'] = user
3185 3183 date = ctx.date()
3186 3184 if opts.get(b'date'):
3187 3185 date = opts[b'date']
3188 3186 statedata[b'date'] = date
3189 3187 message = ctx.description()
3190 3188 if opts.get(b'log'):
3191 3189 message += b'\n(grafted from %s)' % ctx.hex()
3192 3190 statedata[b'log'] = True
3193 3191
3194 3192 # we don't merge the first commit when continuing
3195 3193 if not cont:
3196 3194 # perform the graft merge with p1(rev) as 'ancestor'
3197 3195 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3198 3196 base = ctx.p1() if basectx is None else basectx
3199 3197 with ui.configoverride(overrides, b'graft'):
3200 3198 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3201 3199 # report any conflicts
3202 3200 if stats.unresolvedcount > 0:
3203 3201 # write out state for --continue
3204 3202 nodes = [repo[rev].hex() for rev in revs[pos:]]
3205 3203 statedata[b'nodes'] = nodes
3206 3204 stateversion = 1
3207 3205 graftstate.save(stateversion, statedata)
3208 3206 hint = _(b"use 'hg resolve' and 'hg graft --continue'")
3209 3207 raise error.Abort(
3210 3208 _(b"unresolved conflicts, can't continue"), hint=hint
3211 3209 )
3212 3210 else:
3213 3211 cont = False
3214 3212
3215 3213 # commit if --no-commit is false
3216 3214 if not opts.get(b'no_commit'):
3217 3215 node = repo.commit(
3218 3216 text=message, user=user, date=date, extra=extra, editor=editor
3219 3217 )
3220 3218 if node is None:
3221 3219 ui.warn(
3222 3220 _(b'note: graft of %d:%s created no changes to commit\n')
3223 3221 % (ctx.rev(), ctx)
3224 3222 )
3225 3223 # checking that newnodes exist because old state files won't have it
3226 3224 elif statedata.get(b'newnodes') is not None:
3227 3225 statedata[b'newnodes'].append(node)
3228 3226
3229 3227 # remove state when we complete successfully
3230 3228 if not opts.get(b'dry_run'):
3231 3229 graftstate.delete()
3232 3230
3233 3231 return 0
3234 3232
3235 3233
3236 3234 def _stopgraft(ui, repo, graftstate):
3237 3235 """stop the interrupted graft"""
3238 3236 if not graftstate.exists():
3239 3237 raise error.Abort(_(b"no interrupted graft found"))
3240 3238 pctx = repo[b'.']
3241 3239 hg.updaterepo(repo, pctx.node(), overwrite=True)
3242 3240 graftstate.delete()
3243 3241 ui.status(_(b"stopped the interrupted graft\n"))
3244 3242 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3245 3243 return 0
3246 3244
3247 3245
3248 3246 statemod.addunfinished(
3249 3247 b'graft',
3250 3248 fname=b'graftstate',
3251 3249 clearable=True,
3252 3250 stopflag=True,
3253 3251 continueflag=True,
3254 3252 abortfunc=cmdutil.hgabortgraft,
3255 3253 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3256 3254 )
3257 3255
3258 3256
3259 3257 @command(
3260 3258 b'grep',
3261 3259 [
3262 3260 (b'0', b'print0', None, _(b'end fields with NUL')),
3263 3261 (b'', b'all', None, _(b'print all revisions that match (DEPRECATED) ')),
3264 3262 (
3265 3263 b'',
3266 3264 b'diff',
3267 3265 None,
3268 3266 _(
3269 3267 b'search revision differences for when the pattern was added '
3270 3268 b'or removed'
3271 3269 ),
3272 3270 ),
3273 3271 (b'a', b'text', None, _(b'treat all files as text')),
3274 3272 (
3275 3273 b'f',
3276 3274 b'follow',
3277 3275 None,
3278 3276 _(
3279 3277 b'follow changeset history,'
3280 3278 b' or file history across copies and renames'
3281 3279 ),
3282 3280 ),
3283 3281 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3284 3282 (
3285 3283 b'l',
3286 3284 b'files-with-matches',
3287 3285 None,
3288 3286 _(b'print only filenames and revisions that match'),
3289 3287 ),
3290 3288 (b'n', b'line-number', None, _(b'print matching line numbers')),
3291 3289 (
3292 3290 b'r',
3293 3291 b'rev',
3294 3292 [],
3295 3293 _(b'search files changed within revision range'),
3296 3294 _(b'REV'),
3297 3295 ),
3298 3296 (
3299 3297 b'',
3300 3298 b'all-files',
3301 3299 None,
3302 3300 _(
3303 3301 b'include all files in the changeset while grepping (DEPRECATED)'
3304 3302 ),
3305 3303 ),
3306 3304 (b'u', b'user', None, _(b'list the author (long with -v)')),
3307 3305 (b'd', b'date', None, _(b'list the date (short with -q)')),
3308 3306 ]
3309 3307 + formatteropts
3310 3308 + walkopts,
3311 3309 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3312 3310 helpcategory=command.CATEGORY_FILE_CONTENTS,
3313 3311 inferrepo=True,
3314 3312 intents={INTENT_READONLY},
3315 3313 )
3316 3314 def grep(ui, repo, pattern, *pats, **opts):
3317 3315 """search for a pattern in specified files
3318 3316
3319 3317 Search the working directory or revision history for a regular
3320 3318 expression in the specified files for the entire repository.
3321 3319
3322 3320 By default, grep searches the repository files in the working
3323 3321 directory and prints the files where it finds a match. To specify
3324 3322 historical revisions instead of the working directory, use the
3325 3323 --rev flag.
3326 3324
3327 3325 To search instead historical revision differences that contains a
3328 3326 change in match status ("-" for a match that becomes a non-match,
3329 3327 or "+" for a non-match that becomes a match), use the --diff flag.
3330 3328
3331 3329 PATTERN can be any Python (roughly Perl-compatible) regular
3332 3330 expression.
3333 3331
3334 3332 If no FILEs are specified and the --rev flag isn't supplied, all
3335 3333 files in the working directory are searched. When using the --rev
3336 3334 flag and specifying FILEs, use the --follow argument to also
3337 3335 follow the specified FILEs across renames and copies.
3338 3336
3339 3337 .. container:: verbose
3340 3338
3341 3339 Template:
3342 3340
3343 3341 The following keywords are supported in addition to the common template
3344 3342 keywords and functions. See also :hg:`help templates`.
3345 3343
3346 3344 :change: String. Character denoting insertion ``+`` or removal ``-``.
3347 3345 Available if ``--diff`` is specified.
3348 3346 :lineno: Integer. Line number of the match.
3349 3347 :path: String. Repository-absolute path of the file.
3350 3348 :texts: List of text chunks.
3351 3349
3352 3350 And each entry of ``{texts}`` provides the following sub-keywords.
3353 3351
3354 3352 :matched: Boolean. True if the chunk matches the specified pattern.
3355 3353 :text: String. Chunk content.
3356 3354
3357 3355 See :hg:`help templates.operators` for the list expansion syntax.
3358 3356
3359 3357 Returns 0 if a match is found, 1 otherwise.
3360 3358
3361 3359 """
3362 3360 opts = pycompat.byteskwargs(opts)
3363 3361 diff = opts.get(b'all') or opts.get(b'diff')
3364 3362 if diff and opts.get(b'all_files'):
3365 3363 raise error.Abort(_(b'--diff and --all-files are mutually exclusive'))
3366 3364 if opts.get(b'all_files') is None and not diff:
3367 3365 opts[b'all_files'] = True
3368 3366 plaingrep = opts.get(b'all_files') and not opts.get(b'rev')
3369 3367 all_files = opts.get(b'all_files')
3370 3368 if plaingrep:
3371 3369 opts[b'rev'] = [b'wdir()']
3372 3370
3373 3371 reflags = re.M
3374 3372 if opts.get(b'ignore_case'):
3375 3373 reflags |= re.I
3376 3374 try:
3377 3375 regexp = util.re.compile(pattern, reflags)
3378 3376 except re.error as inst:
3379 3377 ui.warn(
3380 3378 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3381 3379 )
3382 3380 return 1
3383 3381 sep, eol = b':', b'\n'
3384 3382 if opts.get(b'print0'):
3385 3383 sep = eol = b'\0'
3386 3384
3387 3385 getfile = util.lrucachefunc(repo.file)
3388 3386
3389 3387 def matchlines(body):
3390 3388 begin = 0
3391 3389 linenum = 0
3392 3390 while begin < len(body):
3393 3391 match = regexp.search(body, begin)
3394 3392 if not match:
3395 3393 break
3396 3394 mstart, mend = match.span()
3397 3395 linenum += body.count(b'\n', begin, mstart) + 1
3398 3396 lstart = body.rfind(b'\n', begin, mstart) + 1 or begin
3399 3397 begin = body.find(b'\n', mend) + 1 or len(body) + 1
3400 3398 lend = begin - 1
3401 3399 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3402 3400
3403 3401 class linestate(object):
3404 3402 def __init__(self, line, linenum, colstart, colend):
3405 3403 self.line = line
3406 3404 self.linenum = linenum
3407 3405 self.colstart = colstart
3408 3406 self.colend = colend
3409 3407
3410 3408 def __hash__(self):
3411 3409 return hash((self.linenum, self.line))
3412 3410
3413 3411 def __eq__(self, other):
3414 3412 return self.line == other.line
3415 3413
3416 3414 def findpos(self):
3417 3415 """Iterate all (start, end) indices of matches"""
3418 3416 yield self.colstart, self.colend
3419 3417 p = self.colend
3420 3418 while p < len(self.line):
3421 3419 m = regexp.search(self.line, p)
3422 3420 if not m:
3423 3421 break
3424 3422 yield m.span()
3425 3423 p = m.end()
3426 3424
3427 3425 matches = {}
3428 3426 copies = {}
3429 3427
3430 3428 def grepbody(fn, rev, body):
3431 3429 matches[rev].setdefault(fn, [])
3432 3430 m = matches[rev][fn]
3433 3431 if body is None:
3434 3432 return
3435 3433
3436 3434 for lnum, cstart, cend, line in matchlines(body):
3437 3435 s = linestate(line, lnum, cstart, cend)
3438 3436 m.append(s)
3439 3437
3440 3438 def difflinestates(a, b):
3441 3439 sm = difflib.SequenceMatcher(None, a, b)
3442 3440 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3443 3441 if tag == 'insert':
3444 3442 for i in pycompat.xrange(blo, bhi):
3445 3443 yield (b'+', b[i])
3446 3444 elif tag == 'delete':
3447 3445 for i in pycompat.xrange(alo, ahi):
3448 3446 yield (b'-', a[i])
3449 3447 elif tag == 'replace':
3450 3448 for i in pycompat.xrange(alo, ahi):
3451 3449 yield (b'-', a[i])
3452 3450 for i in pycompat.xrange(blo, bhi):
3453 3451 yield (b'+', b[i])
3454 3452
3455 3453 uipathfn = scmutil.getuipathfn(repo)
3456 3454
3457 3455 def display(fm, fn, ctx, pstates, states):
3458 3456 rev = scmutil.intrev(ctx)
3459 3457 if fm.isplain():
3460 3458 formatuser = ui.shortuser
3461 3459 else:
3462 3460 formatuser = pycompat.bytestr
3463 3461 if ui.quiet:
3464 3462 datefmt = b'%Y-%m-%d'
3465 3463 else:
3466 3464 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3467 3465 found = False
3468 3466
3469 3467 @util.cachefunc
3470 3468 def binary():
3471 3469 flog = getfile(fn)
3472 3470 try:
3473 3471 return stringutil.binary(flog.read(ctx.filenode(fn)))
3474 3472 except error.WdirUnsupported:
3475 3473 return ctx[fn].isbinary()
3476 3474
3477 3475 fieldnamemap = {b'linenumber': b'lineno'}
3478 3476 if diff:
3479 3477 iter = difflinestates(pstates, states)
3480 3478 else:
3481 3479 iter = [(b'', l) for l in states]
3482 3480 for change, l in iter:
3483 3481 fm.startitem()
3484 3482 fm.context(ctx=ctx)
3485 3483 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3486 3484 fm.plain(uipathfn(fn), label=b'grep.filename')
3487 3485
3488 3486 cols = [
3489 3487 (b'rev', b'%d', rev, not plaingrep, b''),
3490 3488 (
3491 3489 b'linenumber',
3492 3490 b'%d',
3493 3491 l.linenum,
3494 3492 opts.get(b'line_number'),
3495 3493 b'',
3496 3494 ),
3497 3495 ]
3498 3496 if diff:
3499 3497 cols.append(
3500 3498 (
3501 3499 b'change',
3502 3500 b'%s',
3503 3501 change,
3504 3502 True,
3505 3503 b'grep.inserted '
3506 3504 if change == b'+'
3507 3505 else b'grep.deleted ',
3508 3506 )
3509 3507 )
3510 3508 cols.extend(
3511 3509 [
3512 3510 (
3513 3511 b'user',
3514 3512 b'%s',
3515 3513 formatuser(ctx.user()),
3516 3514 opts.get(b'user'),
3517 3515 b'',
3518 3516 ),
3519 3517 (
3520 3518 b'date',
3521 3519 b'%s',
3522 3520 fm.formatdate(ctx.date(), datefmt),
3523 3521 opts.get(b'date'),
3524 3522 b'',
3525 3523 ),
3526 3524 ]
3527 3525 )
3528 3526 for name, fmt, data, cond, extra_label in cols:
3529 3527 if cond:
3530 3528 fm.plain(sep, label=b'grep.sep')
3531 3529 field = fieldnamemap.get(name, name)
3532 3530 label = extra_label + (b'grep.%s' % name)
3533 3531 fm.condwrite(cond, field, fmt, data, label=label)
3534 3532 if not opts.get(b'files_with_matches'):
3535 3533 fm.plain(sep, label=b'grep.sep')
3536 3534 if not opts.get(b'text') and binary():
3537 3535 fm.plain(_(b" Binary file matches"))
3538 3536 else:
3539 3537 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3540 3538 fm.plain(eol)
3541 3539 found = True
3542 3540 if opts.get(b'files_with_matches'):
3543 3541 break
3544 3542 return found
3545 3543
3546 3544 def displaymatches(fm, l):
3547 3545 p = 0
3548 3546 for s, e in l.findpos():
3549 3547 if p < s:
3550 3548 fm.startitem()
3551 3549 fm.write(b'text', b'%s', l.line[p:s])
3552 3550 fm.data(matched=False)
3553 3551 fm.startitem()
3554 3552 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3555 3553 fm.data(matched=True)
3556 3554 p = e
3557 3555 if p < len(l.line):
3558 3556 fm.startitem()
3559 3557 fm.write(b'text', b'%s', l.line[p:])
3560 3558 fm.data(matched=False)
3561 3559 fm.end()
3562 3560
3563 3561 skip = set()
3564 3562 revfiles = {}
3565 3563 match = scmutil.match(repo[None], pats, opts)
3566 3564 found = False
3567 3565 follow = opts.get(b'follow')
3568 3566
3569 3567 getrenamed = scmutil.getrenamedfn(repo)
3570 3568
3571 3569 def get_file_content(filename, filelog, filenode, context, revision):
3572 3570 try:
3573 3571 content = filelog.read(filenode)
3574 3572 except error.WdirUnsupported:
3575 3573 content = context[filename].data()
3576 3574 except error.CensoredNodeError:
3577 3575 content = None
3578 3576 ui.warn(
3579 3577 _(b'cannot search in censored file: %(filename)s:%(revnum)s\n')
3580 3578 % {b'filename': filename, b'revnum': pycompat.bytestr(revision)}
3581 3579 )
3582 3580 return content
3583 3581
3584 3582 def prep(ctx, fns):
3585 3583 rev = ctx.rev()
3586 3584 pctx = ctx.p1()
3587 3585 parent = pctx.rev()
3588 3586 matches.setdefault(rev, {})
3589 3587 matches.setdefault(parent, {})
3590 3588 files = revfiles.setdefault(rev, [])
3591 3589 for fn in fns:
3592 3590 flog = getfile(fn)
3593 3591 try:
3594 3592 fnode = ctx.filenode(fn)
3595 3593 except error.LookupError:
3596 3594 continue
3597 3595
3598 3596 copy = None
3599 3597 if follow:
3600 3598 copy = getrenamed(fn, rev)
3601 3599 if copy:
3602 3600 copies.setdefault(rev, {})[fn] = copy
3603 3601 if fn in skip:
3604 3602 skip.add(copy)
3605 3603 if fn in skip:
3606 3604 continue
3607 3605 files.append(fn)
3608 3606
3609 3607 if fn not in matches[rev]:
3610 3608 content = get_file_content(fn, flog, fnode, ctx, rev)
3611 3609 grepbody(fn, rev, content)
3612 3610
3613 3611 pfn = copy or fn
3614 3612 if pfn not in matches[parent]:
3615 3613 try:
3616 3614 pfnode = pctx.filenode(pfn)
3617 3615 pcontent = get_file_content(pfn, flog, pfnode, pctx, parent)
3618 3616 grepbody(pfn, parent, pcontent)
3619 3617 except error.LookupError:
3620 3618 pass
3621 3619
3622 3620 ui.pager(b'grep')
3623 3621 fm = ui.formatter(b'grep', opts)
3624 3622 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
3625 3623 rev = ctx.rev()
3626 3624 parent = ctx.p1().rev()
3627 3625 for fn in sorted(revfiles.get(rev, [])):
3628 3626 states = matches[rev][fn]
3629 3627 copy = copies.get(rev, {}).get(fn)
3630 3628 if fn in skip:
3631 3629 if copy:
3632 3630 skip.add(copy)
3633 3631 continue
3634 3632 pstates = matches.get(parent, {}).get(copy or fn, [])
3635 3633 if pstates or states:
3636 3634 r = display(fm, fn, ctx, pstates, states)
3637 3635 found = found or r
3638 3636 if r and not diff and not all_files:
3639 3637 skip.add(fn)
3640 3638 if copy:
3641 3639 skip.add(copy)
3642 3640 del revfiles[rev]
3643 3641 # We will keep the matches dict for the duration of the window
3644 3642 # clear the matches dict once the window is over
3645 3643 if not revfiles:
3646 3644 matches.clear()
3647 3645 fm.end()
3648 3646
3649 3647 return not found
3650 3648
3651 3649
3652 3650 @command(
3653 3651 b'heads',
3654 3652 [
3655 3653 (
3656 3654 b'r',
3657 3655 b'rev',
3658 3656 b'',
3659 3657 _(b'show only heads which are descendants of STARTREV'),
3660 3658 _(b'STARTREV'),
3661 3659 ),
3662 3660 (b't', b'topo', False, _(b'show topological heads only')),
3663 3661 (
3664 3662 b'a',
3665 3663 b'active',
3666 3664 False,
3667 3665 _(b'show active branchheads only (DEPRECATED)'),
3668 3666 ),
3669 3667 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3670 3668 ]
3671 3669 + templateopts,
3672 3670 _(b'[-ct] [-r STARTREV] [REV]...'),
3673 3671 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3674 3672 intents={INTENT_READONLY},
3675 3673 )
3676 3674 def heads(ui, repo, *branchrevs, **opts):
3677 3675 """show branch heads
3678 3676
3679 3677 With no arguments, show all open branch heads in the repository.
3680 3678 Branch heads are changesets that have no descendants on the
3681 3679 same branch. They are where development generally takes place and
3682 3680 are the usual targets for update and merge operations.
3683 3681
3684 3682 If one or more REVs are given, only open branch heads on the
3685 3683 branches associated with the specified changesets are shown. This
3686 3684 means that you can use :hg:`heads .` to see the heads on the
3687 3685 currently checked-out branch.
3688 3686
3689 3687 If -c/--closed is specified, also show branch heads marked closed
3690 3688 (see :hg:`commit --close-branch`).
3691 3689
3692 3690 If STARTREV is specified, only those heads that are descendants of
3693 3691 STARTREV will be displayed.
3694 3692
3695 3693 If -t/--topo is specified, named branch mechanics will be ignored and only
3696 3694 topological heads (changesets with no children) will be shown.
3697 3695
3698 3696 Returns 0 if matching heads are found, 1 if not.
3699 3697 """
3700 3698
3701 3699 opts = pycompat.byteskwargs(opts)
3702 3700 start = None
3703 3701 rev = opts.get(b'rev')
3704 3702 if rev:
3705 3703 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3706 3704 start = scmutil.revsingle(repo, rev, None).node()
3707 3705
3708 3706 if opts.get(b'topo'):
3709 3707 heads = [repo[h] for h in repo.heads(start)]
3710 3708 else:
3711 3709 heads = []
3712 3710 for branch in repo.branchmap():
3713 3711 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3714 3712 heads = [repo[h] for h in heads]
3715 3713
3716 3714 if branchrevs:
3717 3715 branches = set(
3718 3716 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3719 3717 )
3720 3718 heads = [h for h in heads if h.branch() in branches]
3721 3719
3722 3720 if opts.get(b'active') and branchrevs:
3723 3721 dagheads = repo.heads(start)
3724 3722 heads = [h for h in heads if h.node() in dagheads]
3725 3723
3726 3724 if branchrevs:
3727 3725 haveheads = set(h.branch() for h in heads)
3728 3726 if branches - haveheads:
3729 3727 headless = b', '.join(b for b in branches - haveheads)
3730 3728 msg = _(b'no open branch heads found on branches %s')
3731 3729 if opts.get(b'rev'):
3732 3730 msg += _(b' (started at %s)') % opts[b'rev']
3733 3731 ui.warn((msg + b'\n') % headless)
3734 3732
3735 3733 if not heads:
3736 3734 return 1
3737 3735
3738 3736 ui.pager(b'heads')
3739 3737 heads = sorted(heads, key=lambda x: -(x.rev()))
3740 3738 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3741 3739 for ctx in heads:
3742 3740 displayer.show(ctx)
3743 3741 displayer.close()
3744 3742
3745 3743
3746 3744 @command(
3747 3745 b'help',
3748 3746 [
3749 3747 (b'e', b'extension', None, _(b'show only help for extensions')),
3750 3748 (b'c', b'command', None, _(b'show only help for commands')),
3751 3749 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3752 3750 (
3753 3751 b's',
3754 3752 b'system',
3755 3753 [],
3756 3754 _(b'show help for specific platform(s)'),
3757 3755 _(b'PLATFORM'),
3758 3756 ),
3759 3757 ],
3760 3758 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3761 3759 helpcategory=command.CATEGORY_HELP,
3762 3760 norepo=True,
3763 3761 intents={INTENT_READONLY},
3764 3762 )
3765 3763 def help_(ui, name=None, **opts):
3766 3764 """show help for a given topic or a help overview
3767 3765
3768 3766 With no arguments, print a list of commands with short help messages.
3769 3767
3770 3768 Given a topic, extension, or command name, print help for that
3771 3769 topic.
3772 3770
3773 3771 Returns 0 if successful.
3774 3772 """
3775 3773
3776 3774 keep = opts.get('system') or []
3777 3775 if len(keep) == 0:
3778 3776 if pycompat.sysplatform.startswith(b'win'):
3779 3777 keep.append(b'windows')
3780 3778 elif pycompat.sysplatform == b'OpenVMS':
3781 3779 keep.append(b'vms')
3782 3780 elif pycompat.sysplatform == b'plan9':
3783 3781 keep.append(b'plan9')
3784 3782 else:
3785 3783 keep.append(b'unix')
3786 3784 keep.append(pycompat.sysplatform.lower())
3787 3785 if ui.verbose:
3788 3786 keep.append(b'verbose')
3789 3787
3790 3788 commands = sys.modules[__name__]
3791 3789 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3792 3790 ui.pager(b'help')
3793 3791 ui.write(formatted)
3794 3792
3795 3793
3796 3794 @command(
3797 3795 b'identify|id',
3798 3796 [
3799 3797 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3800 3798 (b'n', b'num', None, _(b'show local revision number')),
3801 3799 (b'i', b'id', None, _(b'show global revision id')),
3802 3800 (b'b', b'branch', None, _(b'show branch')),
3803 3801 (b't', b'tags', None, _(b'show tags')),
3804 3802 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3805 3803 ]
3806 3804 + remoteopts
3807 3805 + formatteropts,
3808 3806 _(b'[-nibtB] [-r REV] [SOURCE]'),
3809 3807 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3810 3808 optionalrepo=True,
3811 3809 intents={INTENT_READONLY},
3812 3810 )
3813 3811 def identify(
3814 3812 ui,
3815 3813 repo,
3816 3814 source=None,
3817 3815 rev=None,
3818 3816 num=None,
3819 3817 id=None,
3820 3818 branch=None,
3821 3819 tags=None,
3822 3820 bookmarks=None,
3823 3821 **opts
3824 3822 ):
3825 3823 """identify the working directory or specified revision
3826 3824
3827 3825 Print a summary identifying the repository state at REV using one or
3828 3826 two parent hash identifiers, followed by a "+" if the working
3829 3827 directory has uncommitted changes, the branch name (if not default),
3830 3828 a list of tags, and a list of bookmarks.
3831 3829
3832 3830 When REV is not given, print a summary of the current state of the
3833 3831 repository including the working directory. Specify -r. to get information
3834 3832 of the working directory parent without scanning uncommitted changes.
3835 3833
3836 3834 Specifying a path to a repository root or Mercurial bundle will
3837 3835 cause lookup to operate on that repository/bundle.
3838 3836
3839 3837 .. container:: verbose
3840 3838
3841 3839 Template:
3842 3840
3843 3841 The following keywords are supported in addition to the common template
3844 3842 keywords and functions. See also :hg:`help templates`.
3845 3843
3846 3844 :dirty: String. Character ``+`` denoting if the working directory has
3847 3845 uncommitted changes.
3848 3846 :id: String. One or two nodes, optionally followed by ``+``.
3849 3847 :parents: List of strings. Parent nodes of the changeset.
3850 3848
3851 3849 Examples:
3852 3850
3853 3851 - generate a build identifier for the working directory::
3854 3852
3855 3853 hg id --id > build-id.dat
3856 3854
3857 3855 - find the revision corresponding to a tag::
3858 3856
3859 3857 hg id -n -r 1.3
3860 3858
3861 3859 - check the most recent revision of a remote repository::
3862 3860
3863 3861 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3864 3862
3865 3863 See :hg:`log` for generating more information about specific revisions,
3866 3864 including full hash identifiers.
3867 3865
3868 3866 Returns 0 if successful.
3869 3867 """
3870 3868
3871 3869 opts = pycompat.byteskwargs(opts)
3872 3870 if not repo and not source:
3873 3871 raise error.Abort(
3874 3872 _(b"there is no Mercurial repository here (.hg not found)")
3875 3873 )
3876 3874
3877 3875 default = not (num or id or branch or tags or bookmarks)
3878 3876 output = []
3879 3877 revs = []
3880 3878
3881 3879 if source:
3882 3880 source, branches = hg.parseurl(ui.expandpath(source))
3883 3881 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3884 3882 repo = peer.local()
3885 3883 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3886 3884
3887 3885 fm = ui.formatter(b'identify', opts)
3888 3886 fm.startitem()
3889 3887
3890 3888 if not repo:
3891 3889 if num or branch or tags:
3892 3890 raise error.Abort(
3893 3891 _(b"can't query remote revision number, branch, or tags")
3894 3892 )
3895 3893 if not rev and revs:
3896 3894 rev = revs[0]
3897 3895 if not rev:
3898 3896 rev = b"tip"
3899 3897
3900 3898 remoterev = peer.lookup(rev)
3901 3899 hexrev = fm.hexfunc(remoterev)
3902 3900 if default or id:
3903 3901 output = [hexrev]
3904 3902 fm.data(id=hexrev)
3905 3903
3906 3904 @util.cachefunc
3907 3905 def getbms():
3908 3906 bms = []
3909 3907
3910 3908 if b'bookmarks' in peer.listkeys(b'namespaces'):
3911 3909 hexremoterev = hex(remoterev)
3912 3910 bms = [
3913 3911 bm
3914 3912 for bm, bmr in pycompat.iteritems(
3915 3913 peer.listkeys(b'bookmarks')
3916 3914 )
3917 3915 if bmr == hexremoterev
3918 3916 ]
3919 3917
3920 3918 return sorted(bms)
3921 3919
3922 3920 if fm.isplain():
3923 3921 if bookmarks:
3924 3922 output.extend(getbms())
3925 3923 elif default and not ui.quiet:
3926 3924 # multiple bookmarks for a single parent separated by '/'
3927 3925 bm = b'/'.join(getbms())
3928 3926 if bm:
3929 3927 output.append(bm)
3930 3928 else:
3931 3929 fm.data(node=hex(remoterev))
3932 3930 if bookmarks or b'bookmarks' in fm.datahint():
3933 3931 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3934 3932 else:
3935 3933 if rev:
3936 3934 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3937 3935 ctx = scmutil.revsingle(repo, rev, None)
3938 3936
3939 3937 if ctx.rev() is None:
3940 3938 ctx = repo[None]
3941 3939 parents = ctx.parents()
3942 3940 taglist = []
3943 3941 for p in parents:
3944 3942 taglist.extend(p.tags())
3945 3943
3946 3944 dirty = b""
3947 3945 if ctx.dirty(missing=True, merge=False, branch=False):
3948 3946 dirty = b'+'
3949 3947 fm.data(dirty=dirty)
3950 3948
3951 3949 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3952 3950 if default or id:
3953 3951 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3954 3952 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3955 3953
3956 3954 if num:
3957 3955 numoutput = [b"%d" % p.rev() for p in parents]
3958 3956 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3959 3957
3960 3958 fm.data(
3961 3959 parents=fm.formatlist(
3962 3960 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3963 3961 )
3964 3962 )
3965 3963 else:
3966 3964 hexoutput = fm.hexfunc(ctx.node())
3967 3965 if default or id:
3968 3966 output = [hexoutput]
3969 3967 fm.data(id=hexoutput)
3970 3968
3971 3969 if num:
3972 3970 output.append(pycompat.bytestr(ctx.rev()))
3973 3971 taglist = ctx.tags()
3974 3972
3975 3973 if default and not ui.quiet:
3976 3974 b = ctx.branch()
3977 3975 if b != b'default':
3978 3976 output.append(b"(%s)" % b)
3979 3977
3980 3978 # multiple tags for a single parent separated by '/'
3981 3979 t = b'/'.join(taglist)
3982 3980 if t:
3983 3981 output.append(t)
3984 3982
3985 3983 # multiple bookmarks for a single parent separated by '/'
3986 3984 bm = b'/'.join(ctx.bookmarks())
3987 3985 if bm:
3988 3986 output.append(bm)
3989 3987 else:
3990 3988 if branch:
3991 3989 output.append(ctx.branch())
3992 3990
3993 3991 if tags:
3994 3992 output.extend(taglist)
3995 3993
3996 3994 if bookmarks:
3997 3995 output.extend(ctx.bookmarks())
3998 3996
3999 3997 fm.data(node=ctx.hex())
4000 3998 fm.data(branch=ctx.branch())
4001 3999 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4002 4000 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4003 4001 fm.context(ctx=ctx)
4004 4002
4005 4003 fm.plain(b"%s\n" % b' '.join(output))
4006 4004 fm.end()
4007 4005
4008 4006
4009 4007 @command(
4010 4008 b'import|patch',
4011 4009 [
4012 4010 (
4013 4011 b'p',
4014 4012 b'strip',
4015 4013 1,
4016 4014 _(
4017 4015 b'directory strip option for patch. This has the same '
4018 4016 b'meaning as the corresponding patch option'
4019 4017 ),
4020 4018 _(b'NUM'),
4021 4019 ),
4022 4020 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4023 4021 (b'', b'secret', None, _(b'use the secret phase for committing')),
4024 4022 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4025 4023 (
4026 4024 b'f',
4027 4025 b'force',
4028 4026 None,
4029 4027 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4030 4028 ),
4031 4029 (
4032 4030 b'',
4033 4031 b'no-commit',
4034 4032 None,
4035 4033 _(b"don't commit, just update the working directory"),
4036 4034 ),
4037 4035 (
4038 4036 b'',
4039 4037 b'bypass',
4040 4038 None,
4041 4039 _(b"apply patch without touching the working directory"),
4042 4040 ),
4043 4041 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4044 4042 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4045 4043 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4046 4044 (
4047 4045 b'',
4048 4046 b'import-branch',
4049 4047 None,
4050 4048 _(b'use any branch information in patch (implied by --exact)'),
4051 4049 ),
4052 4050 ]
4053 4051 + commitopts
4054 4052 + commitopts2
4055 4053 + similarityopts,
4056 4054 _(b'[OPTION]... PATCH...'),
4057 4055 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4058 4056 )
4059 4057 def import_(ui, repo, patch1=None, *patches, **opts):
4060 4058 """import an ordered set of patches
4061 4059
4062 4060 Import a list of patches and commit them individually (unless
4063 4061 --no-commit is specified).
4064 4062
4065 4063 To read a patch from standard input (stdin), use "-" as the patch
4066 4064 name. If a URL is specified, the patch will be downloaded from
4067 4065 there.
4068 4066
4069 4067 Import first applies changes to the working directory (unless
4070 4068 --bypass is specified), import will abort if there are outstanding
4071 4069 changes.
4072 4070
4073 4071 Use --bypass to apply and commit patches directly to the
4074 4072 repository, without affecting the working directory. Without
4075 4073 --exact, patches will be applied on top of the working directory
4076 4074 parent revision.
4077 4075
4078 4076 You can import a patch straight from a mail message. Even patches
4079 4077 as attachments work (to use the body part, it must have type
4080 4078 text/plain or text/x-patch). From and Subject headers of email
4081 4079 message are used as default committer and commit message. All
4082 4080 text/plain body parts before first diff are added to the commit
4083 4081 message.
4084 4082
4085 4083 If the imported patch was generated by :hg:`export`, user and
4086 4084 description from patch override values from message headers and
4087 4085 body. Values given on command line with -m/--message and -u/--user
4088 4086 override these.
4089 4087
4090 4088 If --exact is specified, import will set the working directory to
4091 4089 the parent of each patch before applying it, and will abort if the
4092 4090 resulting changeset has a different ID than the one recorded in
4093 4091 the patch. This will guard against various ways that portable
4094 4092 patch formats and mail systems might fail to transfer Mercurial
4095 4093 data or metadata. See :hg:`bundle` for lossless transmission.
4096 4094
4097 4095 Use --partial to ensure a changeset will be created from the patch
4098 4096 even if some hunks fail to apply. Hunks that fail to apply will be
4099 4097 written to a <target-file>.rej file. Conflicts can then be resolved
4100 4098 by hand before :hg:`commit --amend` is run to update the created
4101 4099 changeset. This flag exists to let people import patches that
4102 4100 partially apply without losing the associated metadata (author,
4103 4101 date, description, ...).
4104 4102
4105 4103 .. note::
4106 4104
4107 4105 When no hunks apply cleanly, :hg:`import --partial` will create
4108 4106 an empty changeset, importing only the patch metadata.
4109 4107
4110 4108 With -s/--similarity, hg will attempt to discover renames and
4111 4109 copies in the patch in the same way as :hg:`addremove`.
4112 4110
4113 4111 It is possible to use external patch programs to perform the patch
4114 4112 by setting the ``ui.patch`` configuration option. For the default
4115 4113 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4116 4114 See :hg:`help config` for more information about configuration
4117 4115 files and how to use these options.
4118 4116
4119 4117 See :hg:`help dates` for a list of formats valid for -d/--date.
4120 4118
4121 4119 .. container:: verbose
4122 4120
4123 4121 Examples:
4124 4122
4125 4123 - import a traditional patch from a website and detect renames::
4126 4124
4127 4125 hg import -s 80 http://example.com/bugfix.patch
4128 4126
4129 4127 - import a changeset from an hgweb server::
4130 4128
4131 4129 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4132 4130
4133 4131 - import all the patches in an Unix-style mbox::
4134 4132
4135 4133 hg import incoming-patches.mbox
4136 4134
4137 4135 - import patches from stdin::
4138 4136
4139 4137 hg import -
4140 4138
4141 4139 - attempt to exactly restore an exported changeset (not always
4142 4140 possible)::
4143 4141
4144 4142 hg import --exact proposed-fix.patch
4145 4143
4146 4144 - use an external tool to apply a patch which is too fuzzy for
4147 4145 the default internal tool.
4148 4146
4149 4147 hg import --config ui.patch="patch --merge" fuzzy.patch
4150 4148
4151 4149 - change the default fuzzing from 2 to a less strict 7
4152 4150
4153 4151 hg import --config ui.fuzz=7 fuzz.patch
4154 4152
4155 4153 Returns 0 on success, 1 on partial success (see --partial).
4156 4154 """
4157 4155
4158 4156 opts = pycompat.byteskwargs(opts)
4159 4157 if not patch1:
4160 4158 raise error.Abort(_(b'need at least one patch to import'))
4161 4159
4162 4160 patches = (patch1,) + patches
4163 4161
4164 4162 date = opts.get(b'date')
4165 4163 if date:
4166 4164 opts[b'date'] = dateutil.parsedate(date)
4167 4165
4168 4166 exact = opts.get(b'exact')
4169 4167 update = not opts.get(b'bypass')
4170 4168 if not update and opts.get(b'no_commit'):
4171 4169 raise error.Abort(_(b'cannot use --no-commit with --bypass'))
4172 4170 if opts.get(b'secret') and opts.get(b'no_commit'):
4173 4171 raise error.Abort(_(b'cannot use --no-commit with --secret'))
4174 4172 try:
4175 4173 sim = float(opts.get(b'similarity') or 0)
4176 4174 except ValueError:
4177 4175 raise error.Abort(_(b'similarity must be a number'))
4178 4176 if sim < 0 or sim > 100:
4179 4177 raise error.Abort(_(b'similarity must be between 0 and 100'))
4180 4178 if sim and not update:
4181 4179 raise error.Abort(_(b'cannot use --similarity with --bypass'))
4182 4180 if exact:
4183 4181 if opts.get(b'edit'):
4184 4182 raise error.Abort(_(b'cannot use --exact with --edit'))
4185 4183 if opts.get(b'prefix'):
4186 4184 raise error.Abort(_(b'cannot use --exact with --prefix'))
4187 4185
4188 4186 base = opts[b"base"]
4189 4187 msgs = []
4190 4188 ret = 0
4191 4189
4192 4190 with repo.wlock():
4193 4191 if update:
4194 4192 cmdutil.checkunfinished(repo)
4195 4193 if exact or not opts.get(b'force'):
4196 4194 cmdutil.bailifchanged(repo)
4197 4195
4198 4196 if not opts.get(b'no_commit'):
4199 4197 lock = repo.lock
4200 4198 tr = lambda: repo.transaction(b'import')
4201 4199 dsguard = util.nullcontextmanager
4202 4200 else:
4203 4201 lock = util.nullcontextmanager
4204 4202 tr = util.nullcontextmanager
4205 4203 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4206 4204 with lock(), tr(), dsguard():
4207 4205 parents = repo[None].parents()
4208 4206 for patchurl in patches:
4209 4207 if patchurl == b'-':
4210 4208 ui.status(_(b'applying patch from stdin\n'))
4211 4209 patchfile = ui.fin
4212 4210 patchurl = b'stdin' # for error message
4213 4211 else:
4214 4212 patchurl = os.path.join(base, patchurl)
4215 4213 ui.status(_(b'applying %s\n') % patchurl)
4216 4214 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4217 4215
4218 4216 haspatch = False
4219 4217 for hunk in patch.split(patchfile):
4220 4218 with patch.extract(ui, hunk) as patchdata:
4221 4219 msg, node, rej = cmdutil.tryimportone(
4222 4220 ui, repo, patchdata, parents, opts, msgs, hg.clean
4223 4221 )
4224 4222 if msg:
4225 4223 haspatch = True
4226 4224 ui.note(msg + b'\n')
4227 4225 if update or exact:
4228 4226 parents = repo[None].parents()
4229 4227 else:
4230 4228 parents = [repo[node]]
4231 4229 if rej:
4232 4230 ui.write_err(_(b"patch applied partially\n"))
4233 4231 ui.write_err(
4234 4232 _(
4235 4233 b"(fix the .rej files and run "
4236 4234 b"`hg commit --amend`)\n"
4237 4235 )
4238 4236 )
4239 4237 ret = 1
4240 4238 break
4241 4239
4242 4240 if not haspatch:
4243 4241 raise error.Abort(_(b'%s: no diffs found') % patchurl)
4244 4242
4245 4243 if msgs:
4246 4244 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4247 4245 return ret
4248 4246
4249 4247
4250 4248 @command(
4251 4249 b'incoming|in',
4252 4250 [
4253 4251 (
4254 4252 b'f',
4255 4253 b'force',
4256 4254 None,
4257 4255 _(b'run even if remote repository is unrelated'),
4258 4256 ),
4259 4257 (b'n', b'newest-first', None, _(b'show newest record first')),
4260 4258 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4261 4259 (
4262 4260 b'r',
4263 4261 b'rev',
4264 4262 [],
4265 4263 _(b'a remote changeset intended to be added'),
4266 4264 _(b'REV'),
4267 4265 ),
4268 4266 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4269 4267 (
4270 4268 b'b',
4271 4269 b'branch',
4272 4270 [],
4273 4271 _(b'a specific branch you would like to pull'),
4274 4272 _(b'BRANCH'),
4275 4273 ),
4276 4274 ]
4277 4275 + logopts
4278 4276 + remoteopts
4279 4277 + subrepoopts,
4280 4278 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4281 4279 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4282 4280 )
4283 4281 def incoming(ui, repo, source=b"default", **opts):
4284 4282 """show new changesets found in source
4285 4283
4286 4284 Show new changesets found in the specified path/URL or the default
4287 4285 pull location. These are the changesets that would have been pulled
4288 4286 by :hg:`pull` at the time you issued this command.
4289 4287
4290 4288 See pull for valid source format details.
4291 4289
4292 4290 .. container:: verbose
4293 4291
4294 4292 With -B/--bookmarks, the result of bookmark comparison between
4295 4293 local and remote repositories is displayed. With -v/--verbose,
4296 4294 status is also displayed for each bookmark like below::
4297 4295
4298 4296 BM1 01234567890a added
4299 4297 BM2 1234567890ab advanced
4300 4298 BM3 234567890abc diverged
4301 4299 BM4 34567890abcd changed
4302 4300
4303 4301 The action taken locally when pulling depends on the
4304 4302 status of each bookmark:
4305 4303
4306 4304 :``added``: pull will create it
4307 4305 :``advanced``: pull will update it
4308 4306 :``diverged``: pull will create a divergent bookmark
4309 4307 :``changed``: result depends on remote changesets
4310 4308
4311 4309 From the point of view of pulling behavior, bookmark
4312 4310 existing only in the remote repository are treated as ``added``,
4313 4311 even if it is in fact locally deleted.
4314 4312
4315 4313 .. container:: verbose
4316 4314
4317 4315 For remote repository, using --bundle avoids downloading the
4318 4316 changesets twice if the incoming is followed by a pull.
4319 4317
4320 4318 Examples:
4321 4319
4322 4320 - show incoming changes with patches and full description::
4323 4321
4324 4322 hg incoming -vp
4325 4323
4326 4324 - show incoming changes excluding merges, store a bundle::
4327 4325
4328 4326 hg in -vpM --bundle incoming.hg
4329 4327 hg pull incoming.hg
4330 4328
4331 4329 - briefly list changes inside a bundle::
4332 4330
4333 4331 hg in changes.hg -T "{desc|firstline}\\n"
4334 4332
4335 4333 Returns 0 if there are incoming changes, 1 otherwise.
4336 4334 """
4337 4335 opts = pycompat.byteskwargs(opts)
4338 4336 if opts.get(b'graph'):
4339 4337 logcmdutil.checkunsupportedgraphflags([], opts)
4340 4338
4341 4339 def display(other, chlist, displayer):
4342 4340 revdag = logcmdutil.graphrevs(other, chlist, opts)
4343 4341 logcmdutil.displaygraph(
4344 4342 ui, repo, revdag, displayer, graphmod.asciiedges
4345 4343 )
4346 4344
4347 4345 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4348 4346 return 0
4349 4347
4350 4348 if opts.get(b'bundle') and opts.get(b'subrepos'):
4351 4349 raise error.Abort(_(b'cannot combine --bundle and --subrepos'))
4352 4350
4353 4351 if opts.get(b'bookmarks'):
4354 4352 source, branches = hg.parseurl(
4355 4353 ui.expandpath(source), opts.get(b'branch')
4356 4354 )
4357 4355 other = hg.peer(repo, opts, source)
4358 4356 if b'bookmarks' not in other.listkeys(b'namespaces'):
4359 4357 ui.warn(_(b"remote doesn't support bookmarks\n"))
4360 4358 return 0
4361 4359 ui.pager(b'incoming')
4362 4360 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4363 4361 return bookmarks.incoming(ui, repo, other)
4364 4362
4365 4363 repo._subtoppath = ui.expandpath(source)
4366 4364 try:
4367 4365 return hg.incoming(ui, repo, source, opts)
4368 4366 finally:
4369 4367 del repo._subtoppath
4370 4368
4371 4369
4372 4370 @command(
4373 4371 b'init',
4374 4372 remoteopts,
4375 4373 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4376 4374 helpcategory=command.CATEGORY_REPO_CREATION,
4377 4375 helpbasic=True,
4378 4376 norepo=True,
4379 4377 )
4380 4378 def init(ui, dest=b".", **opts):
4381 4379 """create a new repository in the given directory
4382 4380
4383 4381 Initialize a new repository in the given directory. If the given
4384 4382 directory does not exist, it will be created.
4385 4383
4386 4384 If no directory is given, the current directory is used.
4387 4385
4388 4386 It is possible to specify an ``ssh://`` URL as the destination.
4389 4387 See :hg:`help urls` for more information.
4390 4388
4391 4389 Returns 0 on success.
4392 4390 """
4393 4391 opts = pycompat.byteskwargs(opts)
4394 4392 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4395 4393
4396 4394
4397 4395 @command(
4398 4396 b'locate',
4399 4397 [
4400 4398 (
4401 4399 b'r',
4402 4400 b'rev',
4403 4401 b'',
4404 4402 _(b'search the repository as it is in REV'),
4405 4403 _(b'REV'),
4406 4404 ),
4407 4405 (
4408 4406 b'0',
4409 4407 b'print0',
4410 4408 None,
4411 4409 _(b'end filenames with NUL, for use with xargs'),
4412 4410 ),
4413 4411 (
4414 4412 b'f',
4415 4413 b'fullpath',
4416 4414 None,
4417 4415 _(b'print complete paths from the filesystem root'),
4418 4416 ),
4419 4417 ]
4420 4418 + walkopts,
4421 4419 _(b'[OPTION]... [PATTERN]...'),
4422 4420 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4423 4421 )
4424 4422 def locate(ui, repo, *pats, **opts):
4425 4423 """locate files matching specific patterns (DEPRECATED)
4426 4424
4427 4425 Print files under Mercurial control in the working directory whose
4428 4426 names match the given patterns.
4429 4427
4430 4428 By default, this command searches all directories in the working
4431 4429 directory. To search just the current directory and its
4432 4430 subdirectories, use "--include .".
4433 4431
4434 4432 If no patterns are given to match, this command prints the names
4435 4433 of all files under Mercurial control in the working directory.
4436 4434
4437 4435 If you want to feed the output of this command into the "xargs"
4438 4436 command, use the -0 option to both this command and "xargs". This
4439 4437 will avoid the problem of "xargs" treating single filenames that
4440 4438 contain whitespace as multiple filenames.
4441 4439
4442 4440 See :hg:`help files` for a more versatile command.
4443 4441
4444 4442 Returns 0 if a match is found, 1 otherwise.
4445 4443 """
4446 4444 opts = pycompat.byteskwargs(opts)
4447 4445 if opts.get(b'print0'):
4448 4446 end = b'\0'
4449 4447 else:
4450 4448 end = b'\n'
4451 4449 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4452 4450
4453 4451 ret = 1
4454 4452 m = scmutil.match(
4455 4453 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4456 4454 )
4457 4455
4458 4456 ui.pager(b'locate')
4459 4457 if ctx.rev() is None:
4460 4458 # When run on the working copy, "locate" includes removed files, so
4461 4459 # we get the list of files from the dirstate.
4462 4460 filesgen = sorted(repo.dirstate.matches(m))
4463 4461 else:
4464 4462 filesgen = ctx.matches(m)
4465 4463 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4466 4464 for abs in filesgen:
4467 4465 if opts.get(b'fullpath'):
4468 4466 ui.write(repo.wjoin(abs), end)
4469 4467 else:
4470 4468 ui.write(uipathfn(abs), end)
4471 4469 ret = 0
4472 4470
4473 4471 return ret
4474 4472
4475 4473
4476 4474 @command(
4477 4475 b'log|history',
4478 4476 [
4479 4477 (
4480 4478 b'f',
4481 4479 b'follow',
4482 4480 None,
4483 4481 _(
4484 4482 b'follow changeset history, or file history across copies and renames'
4485 4483 ),
4486 4484 ),
4487 4485 (
4488 4486 b'',
4489 4487 b'follow-first',
4490 4488 None,
4491 4489 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4492 4490 ),
4493 4491 (
4494 4492 b'd',
4495 4493 b'date',
4496 4494 b'',
4497 4495 _(b'show revisions matching date spec'),
4498 4496 _(b'DATE'),
4499 4497 ),
4500 4498 (b'C', b'copies', None, _(b'show copied files')),
4501 4499 (
4502 4500 b'k',
4503 4501 b'keyword',
4504 4502 [],
4505 4503 _(b'do case-insensitive search for a given text'),
4506 4504 _(b'TEXT'),
4507 4505 ),
4508 4506 (
4509 4507 b'r',
4510 4508 b'rev',
4511 4509 [],
4512 4510 _(b'show the specified revision or revset'),
4513 4511 _(b'REV'),
4514 4512 ),
4515 4513 (
4516 4514 b'L',
4517 4515 b'line-range',
4518 4516 [],
4519 4517 _(b'follow line range of specified file (EXPERIMENTAL)'),
4520 4518 _(b'FILE,RANGE'),
4521 4519 ),
4522 4520 (
4523 4521 b'',
4524 4522 b'removed',
4525 4523 None,
4526 4524 _(b'include revisions where files were removed'),
4527 4525 ),
4528 4526 (
4529 4527 b'm',
4530 4528 b'only-merges',
4531 4529 None,
4532 4530 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4533 4531 ),
4534 4532 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4535 4533 (
4536 4534 b'',
4537 4535 b'only-branch',
4538 4536 [],
4539 4537 _(
4540 4538 b'show only changesets within the given named branch (DEPRECATED)'
4541 4539 ),
4542 4540 _(b'BRANCH'),
4543 4541 ),
4544 4542 (
4545 4543 b'b',
4546 4544 b'branch',
4547 4545 [],
4548 4546 _(b'show changesets within the given named branch'),
4549 4547 _(b'BRANCH'),
4550 4548 ),
4551 4549 (
4552 4550 b'P',
4553 4551 b'prune',
4554 4552 [],
4555 4553 _(b'do not display revision or any of its ancestors'),
4556 4554 _(b'REV'),
4557 4555 ),
4558 4556 ]
4559 4557 + logopts
4560 4558 + walkopts,
4561 4559 _(b'[OPTION]... [FILE]'),
4562 4560 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4563 4561 helpbasic=True,
4564 4562 inferrepo=True,
4565 4563 intents={INTENT_READONLY},
4566 4564 )
4567 4565 def log(ui, repo, *pats, **opts):
4568 4566 """show revision history of entire repository or files
4569 4567
4570 4568 Print the revision history of the specified files or the entire
4571 4569 project.
4572 4570
4573 4571 If no revision range is specified, the default is ``tip:0`` unless
4574 4572 --follow is set, in which case the working directory parent is
4575 4573 used as the starting revision.
4576 4574
4577 4575 File history is shown without following rename or copy history of
4578 4576 files. Use -f/--follow with a filename to follow history across
4579 4577 renames and copies. --follow without a filename will only show
4580 4578 ancestors of the starting revision.
4581 4579
4582 4580 By default this command prints revision number and changeset id,
4583 4581 tags, non-trivial parents, user, date and time, and a summary for
4584 4582 each commit. When the -v/--verbose switch is used, the list of
4585 4583 changed files and full commit message are shown.
4586 4584
4587 4585 With --graph the revisions are shown as an ASCII art DAG with the most
4588 4586 recent changeset at the top.
4589 4587 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
4590 4588 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4591 4589 changeset from the lines below is a parent of the 'o' merge on the same
4592 4590 line.
4593 4591 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4594 4592 of a '|' indicates one or more revisions in a path are omitted.
4595 4593
4596 4594 .. container:: verbose
4597 4595
4598 4596 Use -L/--line-range FILE,M:N options to follow the history of lines
4599 4597 from M to N in FILE. With -p/--patch only diff hunks affecting
4600 4598 specified line range will be shown. This option requires --follow;
4601 4599 it can be specified multiple times. Currently, this option is not
4602 4600 compatible with --graph. This option is experimental.
4603 4601
4604 4602 .. note::
4605 4603
4606 4604 :hg:`log --patch` may generate unexpected diff output for merge
4607 4605 changesets, as it will only compare the merge changeset against
4608 4606 its first parent. Also, only files different from BOTH parents
4609 4607 will appear in files:.
4610 4608
4611 4609 .. note::
4612 4610
4613 4611 For performance reasons, :hg:`log FILE` may omit duplicate changes
4614 4612 made on branches and will not show removals or mode changes. To
4615 4613 see all such changes, use the --removed switch.
4616 4614
4617 4615 .. container:: verbose
4618 4616
4619 4617 .. note::
4620 4618
4621 4619 The history resulting from -L/--line-range options depends on diff
4622 4620 options; for instance if white-spaces are ignored, respective changes
4623 4621 with only white-spaces in specified line range will not be listed.
4624 4622
4625 4623 .. container:: verbose
4626 4624
4627 4625 Some examples:
4628 4626
4629 4627 - changesets with full descriptions and file lists::
4630 4628
4631 4629 hg log -v
4632 4630
4633 4631 - changesets ancestral to the working directory::
4634 4632
4635 4633 hg log -f
4636 4634
4637 4635 - last 10 commits on the current branch::
4638 4636
4639 4637 hg log -l 10 -b .
4640 4638
4641 4639 - changesets showing all modifications of a file, including removals::
4642 4640
4643 4641 hg log --removed file.c
4644 4642
4645 4643 - all changesets that touch a directory, with diffs, excluding merges::
4646 4644
4647 4645 hg log -Mp lib/
4648 4646
4649 4647 - all revision numbers that match a keyword::
4650 4648
4651 4649 hg log -k bug --template "{rev}\\n"
4652 4650
4653 4651 - the full hash identifier of the working directory parent::
4654 4652
4655 4653 hg log -r . --template "{node}\\n"
4656 4654
4657 4655 - list available log templates::
4658 4656
4659 4657 hg log -T list
4660 4658
4661 4659 - check if a given changeset is included in a tagged release::
4662 4660
4663 4661 hg log -r "a21ccf and ancestor(1.9)"
4664 4662
4665 4663 - find all changesets by some user in a date range::
4666 4664
4667 4665 hg log -k alice -d "may 2008 to jul 2008"
4668 4666
4669 4667 - summary of all changesets after the last tag::
4670 4668
4671 4669 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4672 4670
4673 4671 - changesets touching lines 13 to 23 for file.c::
4674 4672
4675 4673 hg log -L file.c,13:23
4676 4674
4677 4675 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4678 4676 main.c with patch::
4679 4677
4680 4678 hg log -L file.c,13:23 -L main.c,2:6 -p
4681 4679
4682 4680 See :hg:`help dates` for a list of formats valid for -d/--date.
4683 4681
4684 4682 See :hg:`help revisions` for more about specifying and ordering
4685 4683 revisions.
4686 4684
4687 4685 See :hg:`help templates` for more about pre-packaged styles and
4688 4686 specifying custom templates. The default template used by the log
4689 4687 command can be customized via the ``ui.logtemplate`` configuration
4690 4688 setting.
4691 4689
4692 4690 Returns 0 on success.
4693 4691
4694 4692 """
4695 4693 opts = pycompat.byteskwargs(opts)
4696 4694 linerange = opts.get(b'line_range')
4697 4695
4698 4696 if linerange and not opts.get(b'follow'):
4699 4697 raise error.Abort(_(b'--line-range requires --follow'))
4700 4698
4701 4699 if linerange and pats:
4702 4700 # TODO: take pats as patterns with no line-range filter
4703 4701 raise error.Abort(
4704 4702 _(b'FILE arguments are not compatible with --line-range option')
4705 4703 )
4706 4704
4707 4705 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4708 4706 revs, differ = logcmdutil.getrevs(repo, pats, opts)
4709 4707 if linerange:
4710 4708 # TODO: should follow file history from logcmdutil._initialrevs(),
4711 4709 # then filter the result by logcmdutil._makerevset() and --limit
4712 4710 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4713 4711
4714 4712 getcopies = None
4715 4713 if opts.get(b'copies'):
4716 4714 endrev = None
4717 4715 if revs:
4718 4716 endrev = revs.max() + 1
4719 4717 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4720 4718
4721 4719 ui.pager(b'log')
4722 4720 displayer = logcmdutil.changesetdisplayer(
4723 4721 ui, repo, opts, differ, buffered=True
4724 4722 )
4725 4723 if opts.get(b'graph'):
4726 4724 displayfn = logcmdutil.displaygraphrevs
4727 4725 else:
4728 4726 displayfn = logcmdutil.displayrevs
4729 4727 displayfn(ui, repo, revs, displayer, getcopies)
4730 4728
4731 4729
4732 4730 @command(
4733 4731 b'manifest',
4734 4732 [
4735 4733 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4736 4734 (b'', b'all', False, _(b"list files from all revisions")),
4737 4735 ]
4738 4736 + formatteropts,
4739 4737 _(b'[-r REV]'),
4740 4738 helpcategory=command.CATEGORY_MAINTENANCE,
4741 4739 intents={INTENT_READONLY},
4742 4740 )
4743 4741 def manifest(ui, repo, node=None, rev=None, **opts):
4744 4742 """output the current or given revision of the project manifest
4745 4743
4746 4744 Print a list of version controlled files for the given revision.
4747 4745 If no revision is given, the first parent of the working directory
4748 4746 is used, or the null revision if no revision is checked out.
4749 4747
4750 4748 With -v, print file permissions, symlink and executable bits.
4751 4749 With --debug, print file revision hashes.
4752 4750
4753 4751 If option --all is specified, the list of all files from all revisions
4754 4752 is printed. This includes deleted and renamed files.
4755 4753
4756 4754 Returns 0 on success.
4757 4755 """
4758 4756 opts = pycompat.byteskwargs(opts)
4759 4757 fm = ui.formatter(b'manifest', opts)
4760 4758
4761 4759 if opts.get(b'all'):
4762 4760 if rev or node:
4763 4761 raise error.Abort(_(b"can't specify a revision with --all"))
4764 4762
4765 4763 res = set()
4766 4764 for rev in repo:
4767 4765 ctx = repo[rev]
4768 4766 res |= set(ctx.files())
4769 4767
4770 4768 ui.pager(b'manifest')
4771 4769 for f in sorted(res):
4772 4770 fm.startitem()
4773 4771 fm.write(b"path", b'%s\n', f)
4774 4772 fm.end()
4775 4773 return
4776 4774
4777 4775 if rev and node:
4778 4776 raise error.Abort(_(b"please specify just one revision"))
4779 4777
4780 4778 if not node:
4781 4779 node = rev
4782 4780
4783 4781 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4784 4782 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4785 4783 if node:
4786 4784 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4787 4785 ctx = scmutil.revsingle(repo, node)
4788 4786 mf = ctx.manifest()
4789 4787 ui.pager(b'manifest')
4790 4788 for f in ctx:
4791 4789 fm.startitem()
4792 4790 fm.context(ctx=ctx)
4793 4791 fl = ctx[f].flags()
4794 4792 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4795 4793 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4796 4794 fm.write(b'path', b'%s\n', f)
4797 4795 fm.end()
4798 4796
4799 4797
4800 4798 @command(
4801 4799 b'merge',
4802 4800 [
4803 4801 (
4804 4802 b'f',
4805 4803 b'force',
4806 4804 None,
4807 4805 _(b'force a merge including outstanding changes (DEPRECATED)'),
4808 4806 ),
4809 4807 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4810 4808 (
4811 4809 b'P',
4812 4810 b'preview',
4813 4811 None,
4814 4812 _(b'review revisions to merge (no merge is performed)'),
4815 4813 ),
4816 4814 (b'', b'abort', None, _(b'abort the ongoing merge')),
4817 4815 ]
4818 4816 + mergetoolopts,
4819 4817 _(b'[-P] [[-r] REV]'),
4820 4818 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4821 4819 helpbasic=True,
4822 4820 )
4823 4821 def merge(ui, repo, node=None, **opts):
4824 4822 """merge another revision into working directory
4825 4823
4826 4824 The current working directory is updated with all changes made in
4827 4825 the requested revision since the last common predecessor revision.
4828 4826
4829 4827 Files that changed between either parent are marked as changed for
4830 4828 the next commit and a commit must be performed before any further
4831 4829 updates to the repository are allowed. The next commit will have
4832 4830 two parents.
4833 4831
4834 4832 ``--tool`` can be used to specify the merge tool used for file
4835 4833 merges. It overrides the HGMERGE environment variable and your
4836 4834 configuration files. See :hg:`help merge-tools` for options.
4837 4835
4838 4836 If no revision is specified, the working directory's parent is a
4839 4837 head revision, and the current branch contains exactly one other
4840 4838 head, the other head is merged with by default. Otherwise, an
4841 4839 explicit revision with which to merge must be provided.
4842 4840
4843 4841 See :hg:`help resolve` for information on handling file conflicts.
4844 4842
4845 4843 To undo an uncommitted merge, use :hg:`merge --abort` which
4846 4844 will check out a clean copy of the original merge parent, losing
4847 4845 all changes.
4848 4846
4849 4847 Returns 0 on success, 1 if there are unresolved files.
4850 4848 """
4851 4849
4852 4850 opts = pycompat.byteskwargs(opts)
4853 4851 abort = opts.get(b'abort')
4854 4852 if abort and repo.dirstate.p2() == nullid:
4855 4853 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4856 4854 if abort:
4857 4855 state = cmdutil.getunfinishedstate(repo)
4858 4856 if state and state._opname != b'merge':
4859 4857 raise error.Abort(
4860 4858 _(b'cannot abort merge with %s in progress') % (state._opname),
4861 4859 hint=state.hint(),
4862 4860 )
4863 4861 if node:
4864 4862 raise error.Abort(_(b"cannot specify a node with --abort"))
4865 4863 if opts.get(b'rev'):
4866 4864 raise error.Abort(_(b"cannot specify both --rev and --abort"))
4867 4865 if opts.get(b'preview'):
4868 4866 raise error.Abort(_(b"cannot specify --preview with --abort"))
4869 4867 if opts.get(b'rev') and node:
4870 4868 raise error.Abort(_(b"please specify just one revision"))
4871 4869 if not node:
4872 4870 node = opts.get(b'rev')
4873 4871
4874 4872 if node:
4875 4873 node = scmutil.revsingle(repo, node).node()
4876 4874
4877 4875 if not node and not abort:
4878 4876 if ui.configbool(b'commands', b'merge.require-rev'):
4879 4877 raise error.Abort(
4880 4878 _(
4881 4879 b'configuration requires specifying revision to merge '
4882 4880 b'with'
4883 4881 )
4884 4882 )
4885 4883 node = repo[destutil.destmerge(repo)].node()
4886 4884
4887 4885 if opts.get(b'preview'):
4888 4886 # find nodes that are ancestors of p2 but not of p1
4889 4887 p1 = repo.lookup(b'.')
4890 4888 p2 = node
4891 4889 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4892 4890
4893 4891 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4894 4892 for node in nodes:
4895 4893 displayer.show(repo[node])
4896 4894 displayer.close()
4897 4895 return 0
4898 4896
4899 4897 # ui.forcemerge is an internal variable, do not document
4900 4898 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4901 4899 with ui.configoverride(overrides, b'merge'):
4902 4900 force = opts.get(b'force')
4903 4901 labels = [b'working copy', b'merge rev']
4904 4902 return hg.merge(
4905 4903 repo,
4906 4904 node,
4907 4905 force=force,
4908 4906 mergeforce=force,
4909 4907 labels=labels,
4910 4908 abort=abort,
4911 4909 )
4912 4910
4913 4911
4914 4912 statemod.addunfinished(
4915 4913 b'merge',
4916 4914 fname=None,
4917 4915 clearable=True,
4918 4916 allowcommit=True,
4919 4917 cmdmsg=_(b'outstanding uncommitted merge'),
4920 4918 abortfunc=hg.abortmerge,
4921 4919 statushint=_(
4922 4920 b'To continue: hg commit\nTo abort: hg merge --abort'
4923 4921 ),
4924 4922 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4925 4923 )
4926 4924
4927 4925
4928 4926 @command(
4929 4927 b'outgoing|out',
4930 4928 [
4931 4929 (
4932 4930 b'f',
4933 4931 b'force',
4934 4932 None,
4935 4933 _(b'run even when the destination is unrelated'),
4936 4934 ),
4937 4935 (
4938 4936 b'r',
4939 4937 b'rev',
4940 4938 [],
4941 4939 _(b'a changeset intended to be included in the destination'),
4942 4940 _(b'REV'),
4943 4941 ),
4944 4942 (b'n', b'newest-first', None, _(b'show newest record first')),
4945 4943 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4946 4944 (
4947 4945 b'b',
4948 4946 b'branch',
4949 4947 [],
4950 4948 _(b'a specific branch you would like to push'),
4951 4949 _(b'BRANCH'),
4952 4950 ),
4953 4951 ]
4954 4952 + logopts
4955 4953 + remoteopts
4956 4954 + subrepoopts,
4957 4955 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4958 4956 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4959 4957 )
4960 4958 def outgoing(ui, repo, dest=None, **opts):
4961 4959 """show changesets not found in the destination
4962 4960
4963 4961 Show changesets not found in the specified destination repository
4964 4962 or the default push location. These are the changesets that would
4965 4963 be pushed if a push was requested.
4966 4964
4967 4965 See pull for details of valid destination formats.
4968 4966
4969 4967 .. container:: verbose
4970 4968
4971 4969 With -B/--bookmarks, the result of bookmark comparison between
4972 4970 local and remote repositories is displayed. With -v/--verbose,
4973 4971 status is also displayed for each bookmark like below::
4974 4972
4975 4973 BM1 01234567890a added
4976 4974 BM2 deleted
4977 4975 BM3 234567890abc advanced
4978 4976 BM4 34567890abcd diverged
4979 4977 BM5 4567890abcde changed
4980 4978
4981 4979 The action taken when pushing depends on the
4982 4980 status of each bookmark:
4983 4981
4984 4982 :``added``: push with ``-B`` will create it
4985 4983 :``deleted``: push with ``-B`` will delete it
4986 4984 :``advanced``: push will update it
4987 4985 :``diverged``: push with ``-B`` will update it
4988 4986 :``changed``: push with ``-B`` will update it
4989 4987
4990 4988 From the point of view of pushing behavior, bookmarks
4991 4989 existing only in the remote repository are treated as
4992 4990 ``deleted``, even if it is in fact added remotely.
4993 4991
4994 4992 Returns 0 if there are outgoing changes, 1 otherwise.
4995 4993 """
4996 4994 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4997 4995 # style URLs, so don't overwrite dest.
4998 4996 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
4999 4997 if not path:
5000 4998 raise error.Abort(
5001 4999 _(b'default repository not configured!'),
5002 5000 hint=_(b"see 'hg help config.paths'"),
5003 5001 )
5004 5002
5005 5003 opts = pycompat.byteskwargs(opts)
5006 5004 if opts.get(b'graph'):
5007 5005 logcmdutil.checkunsupportedgraphflags([], opts)
5008 5006 o, other = hg._outgoing(ui, repo, dest, opts)
5009 5007 if not o:
5010 5008 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5011 5009 return
5012 5010
5013 5011 revdag = logcmdutil.graphrevs(repo, o, opts)
5014 5012 ui.pager(b'outgoing')
5015 5013 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
5016 5014 logcmdutil.displaygraph(
5017 5015 ui, repo, revdag, displayer, graphmod.asciiedges
5018 5016 )
5019 5017 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5020 5018 return 0
5021 5019
5022 5020 if opts.get(b'bookmarks'):
5023 5021 dest = path.pushloc or path.loc
5024 5022 other = hg.peer(repo, opts, dest)
5025 5023 if b'bookmarks' not in other.listkeys(b'namespaces'):
5026 5024 ui.warn(_(b"remote doesn't support bookmarks\n"))
5027 5025 return 0
5028 5026 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
5029 5027 ui.pager(b'outgoing')
5030 5028 return bookmarks.outgoing(ui, repo, other)
5031 5029
5032 5030 repo._subtoppath = path.pushloc or path.loc
5033 5031 try:
5034 5032 return hg.outgoing(ui, repo, dest, opts)
5035 5033 finally:
5036 5034 del repo._subtoppath
5037 5035
5038 5036
5039 5037 @command(
5040 5038 b'parents',
5041 5039 [
5042 5040 (
5043 5041 b'r',
5044 5042 b'rev',
5045 5043 b'',
5046 5044 _(b'show parents of the specified revision'),
5047 5045 _(b'REV'),
5048 5046 ),
5049 5047 ]
5050 5048 + templateopts,
5051 5049 _(b'[-r REV] [FILE]'),
5052 5050 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5053 5051 inferrepo=True,
5054 5052 )
5055 5053 def parents(ui, repo, file_=None, **opts):
5056 5054 """show the parents of the working directory or revision (DEPRECATED)
5057 5055
5058 5056 Print the working directory's parent revisions. If a revision is
5059 5057 given via -r/--rev, the parent of that revision will be printed.
5060 5058 If a file argument is given, the revision in which the file was
5061 5059 last changed (before the working directory revision or the
5062 5060 argument to --rev if given) is printed.
5063 5061
5064 5062 This command is equivalent to::
5065 5063
5066 5064 hg log -r "p1()+p2()" or
5067 5065 hg log -r "p1(REV)+p2(REV)" or
5068 5066 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5069 5067 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5070 5068
5071 5069 See :hg:`summary` and :hg:`help revsets` for related information.
5072 5070
5073 5071 Returns 0 on success.
5074 5072 """
5075 5073
5076 5074 opts = pycompat.byteskwargs(opts)
5077 5075 rev = opts.get(b'rev')
5078 5076 if rev:
5079 5077 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5080 5078 ctx = scmutil.revsingle(repo, rev, None)
5081 5079
5082 5080 if file_:
5083 5081 m = scmutil.match(ctx, (file_,), opts)
5084 5082 if m.anypats() or len(m.files()) != 1:
5085 5083 raise error.Abort(_(b'can only specify an explicit filename'))
5086 5084 file_ = m.files()[0]
5087 5085 filenodes = []
5088 5086 for cp in ctx.parents():
5089 5087 if not cp:
5090 5088 continue
5091 5089 try:
5092 5090 filenodes.append(cp.filenode(file_))
5093 5091 except error.LookupError:
5094 5092 pass
5095 5093 if not filenodes:
5096 5094 raise error.Abort(_(b"'%s' not found in manifest!") % file_)
5097 5095 p = []
5098 5096 for fn in filenodes:
5099 5097 fctx = repo.filectx(file_, fileid=fn)
5100 5098 p.append(fctx.node())
5101 5099 else:
5102 5100 p = [cp.node() for cp in ctx.parents()]
5103 5101
5104 5102 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5105 5103 for n in p:
5106 5104 if n != nullid:
5107 5105 displayer.show(repo[n])
5108 5106 displayer.close()
5109 5107
5110 5108
5111 5109 @command(
5112 5110 b'paths',
5113 5111 formatteropts,
5114 5112 _(b'[NAME]'),
5115 5113 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5116 5114 optionalrepo=True,
5117 5115 intents={INTENT_READONLY},
5118 5116 )
5119 5117 def paths(ui, repo, search=None, **opts):
5120 5118 """show aliases for remote repositories
5121 5119
5122 5120 Show definition of symbolic path name NAME. If no name is given,
5123 5121 show definition of all available names.
5124 5122
5125 5123 Option -q/--quiet suppresses all output when searching for NAME
5126 5124 and shows only the path names when listing all definitions.
5127 5125
5128 5126 Path names are defined in the [paths] section of your
5129 5127 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5130 5128 repository, ``.hg/hgrc`` is used, too.
5131 5129
5132 5130 The path names ``default`` and ``default-push`` have a special
5133 5131 meaning. When performing a push or pull operation, they are used
5134 5132 as fallbacks if no location is specified on the command-line.
5135 5133 When ``default-push`` is set, it will be used for push and
5136 5134 ``default`` will be used for pull; otherwise ``default`` is used
5137 5135 as the fallback for both. When cloning a repository, the clone
5138 5136 source is written as ``default`` in ``.hg/hgrc``.
5139 5137
5140 5138 .. note::
5141 5139
5142 5140 ``default`` and ``default-push`` apply to all inbound (e.g.
5143 5141 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5144 5142 and :hg:`bundle`) operations.
5145 5143
5146 5144 See :hg:`help urls` for more information.
5147 5145
5148 5146 .. container:: verbose
5149 5147
5150 5148 Template:
5151 5149
5152 5150 The following keywords are supported. See also :hg:`help templates`.
5153 5151
5154 5152 :name: String. Symbolic name of the path alias.
5155 5153 :pushurl: String. URL for push operations.
5156 5154 :url: String. URL or directory path for the other operations.
5157 5155
5158 5156 Returns 0 on success.
5159 5157 """
5160 5158
5161 5159 opts = pycompat.byteskwargs(opts)
5162 5160 ui.pager(b'paths')
5163 5161 if search:
5164 5162 pathitems = [
5165 5163 (name, path)
5166 5164 for name, path in pycompat.iteritems(ui.paths)
5167 5165 if name == search
5168 5166 ]
5169 5167 else:
5170 5168 pathitems = sorted(pycompat.iteritems(ui.paths))
5171 5169
5172 5170 fm = ui.formatter(b'paths', opts)
5173 5171 if fm.isplain():
5174 5172 hidepassword = util.hidepassword
5175 5173 else:
5176 5174 hidepassword = bytes
5177 5175 if ui.quiet:
5178 5176 namefmt = b'%s\n'
5179 5177 else:
5180 5178 namefmt = b'%s = '
5181 5179 showsubopts = not search and not ui.quiet
5182 5180
5183 5181 for name, path in pathitems:
5184 5182 fm.startitem()
5185 5183 fm.condwrite(not search, b'name', namefmt, name)
5186 5184 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5187 5185 for subopt, value in sorted(path.suboptions.items()):
5188 5186 assert subopt not in (b'name', b'url')
5189 5187 if showsubopts:
5190 5188 fm.plain(b'%s:%s = ' % (name, subopt))
5191 5189 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5192 5190
5193 5191 fm.end()
5194 5192
5195 5193 if search and not pathitems:
5196 5194 if not ui.quiet:
5197 5195 ui.warn(_(b"not found!\n"))
5198 5196 return 1
5199 5197 else:
5200 5198 return 0
5201 5199
5202 5200
5203 5201 @command(
5204 5202 b'phase',
5205 5203 [
5206 5204 (b'p', b'public', False, _(b'set changeset phase to public')),
5207 5205 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5208 5206 (b's', b'secret', False, _(b'set changeset phase to secret')),
5209 5207 (b'f', b'force', False, _(b'allow to move boundary backward')),
5210 5208 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5211 5209 ],
5212 5210 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5213 5211 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5214 5212 )
5215 5213 def phase(ui, repo, *revs, **opts):
5216 5214 """set or show the current phase name
5217 5215
5218 5216 With no argument, show the phase name of the current revision(s).
5219 5217
5220 5218 With one of -p/--public, -d/--draft or -s/--secret, change the
5221 5219 phase value of the specified revisions.
5222 5220
5223 5221 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5224 5222 lower phase to a higher phase. Phases are ordered as follows::
5225 5223
5226 5224 public < draft < secret
5227 5225
5228 5226 Returns 0 on success, 1 if some phases could not be changed.
5229 5227
5230 5228 (For more information about the phases concept, see :hg:`help phases`.)
5231 5229 """
5232 5230 opts = pycompat.byteskwargs(opts)
5233 5231 # search for a unique phase argument
5234 5232 targetphase = None
5235 5233 for idx, name in enumerate(phases.cmdphasenames):
5236 5234 if opts[name]:
5237 5235 if targetphase is not None:
5238 5236 raise error.Abort(_(b'only one phase can be specified'))
5239 5237 targetphase = idx
5240 5238
5241 5239 # look for specified revision
5242 5240 revs = list(revs)
5243 5241 revs.extend(opts[b'rev'])
5244 5242 if not revs:
5245 5243 # display both parents as the second parent phase can influence
5246 5244 # the phase of a merge commit
5247 5245 revs = [c.rev() for c in repo[None].parents()]
5248 5246
5249 5247 revs = scmutil.revrange(repo, revs)
5250 5248
5251 5249 ret = 0
5252 5250 if targetphase is None:
5253 5251 # display
5254 5252 for r in revs:
5255 5253 ctx = repo[r]
5256 5254 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5257 5255 else:
5258 5256 with repo.lock(), repo.transaction(b"phase") as tr:
5259 5257 # set phase
5260 5258 if not revs:
5261 5259 raise error.Abort(_(b'empty revision set'))
5262 5260 nodes = [repo[r].node() for r in revs]
5263 5261 # moving revision from public to draft may hide them
5264 5262 # We have to check result on an unfiltered repository
5265 5263 unfi = repo.unfiltered()
5266 5264 getphase = unfi._phasecache.phase
5267 5265 olddata = [getphase(unfi, r) for r in unfi]
5268 5266 phases.advanceboundary(repo, tr, targetphase, nodes)
5269 5267 if opts[b'force']:
5270 5268 phases.retractboundary(repo, tr, targetphase, nodes)
5271 5269 getphase = unfi._phasecache.phase
5272 5270 newdata = [getphase(unfi, r) for r in unfi]
5273 5271 changes = sum(newdata[r] != olddata[r] for r in unfi)
5274 5272 cl = unfi.changelog
5275 5273 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5276 5274 if rejected:
5277 5275 ui.warn(
5278 5276 _(
5279 5277 b'cannot move %i changesets to a higher '
5280 5278 b'phase, use --force\n'
5281 5279 )
5282 5280 % len(rejected)
5283 5281 )
5284 5282 ret = 1
5285 5283 if changes:
5286 5284 msg = _(b'phase changed for %i changesets\n') % changes
5287 5285 if ret:
5288 5286 ui.status(msg)
5289 5287 else:
5290 5288 ui.note(msg)
5291 5289 else:
5292 5290 ui.warn(_(b'no phases changed\n'))
5293 5291 return ret
5294 5292
5295 5293
5296 5294 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5297 5295 """Run after a changegroup has been added via pull/unbundle
5298 5296
5299 5297 This takes arguments below:
5300 5298
5301 5299 :modheads: change of heads by pull/unbundle
5302 5300 :optupdate: updating working directory is needed or not
5303 5301 :checkout: update destination revision (or None to default destination)
5304 5302 :brev: a name, which might be a bookmark to be activated after updating
5305 5303 """
5306 5304 if modheads == 0:
5307 5305 return
5308 5306 if optupdate:
5309 5307 try:
5310 5308 return hg.updatetotally(ui, repo, checkout, brev)
5311 5309 except error.UpdateAbort as inst:
5312 5310 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5313 5311 hint = inst.hint
5314 5312 raise error.UpdateAbort(msg, hint=hint)
5315 5313 if modheads is not None and modheads > 1:
5316 5314 currentbranchheads = len(repo.branchheads())
5317 5315 if currentbranchheads == modheads:
5318 5316 ui.status(
5319 5317 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5320 5318 )
5321 5319 elif currentbranchheads > 1:
5322 5320 ui.status(
5323 5321 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5324 5322 )
5325 5323 else:
5326 5324 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5327 5325 elif not ui.configbool(b'commands', b'update.requiredest'):
5328 5326 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5329 5327
5330 5328
5331 5329 @command(
5332 5330 b'pull',
5333 5331 [
5334 5332 (
5335 5333 b'u',
5336 5334 b'update',
5337 5335 None,
5338 5336 _(b'update to new branch head if new descendants were pulled'),
5339 5337 ),
5340 5338 (
5341 5339 b'f',
5342 5340 b'force',
5343 5341 None,
5344 5342 _(b'run even when remote repository is unrelated'),
5345 5343 ),
5346 5344 (
5347 5345 b'r',
5348 5346 b'rev',
5349 5347 [],
5350 5348 _(b'a remote changeset intended to be added'),
5351 5349 _(b'REV'),
5352 5350 ),
5353 5351 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5354 5352 (
5355 5353 b'b',
5356 5354 b'branch',
5357 5355 [],
5358 5356 _(b'a specific branch you would like to pull'),
5359 5357 _(b'BRANCH'),
5360 5358 ),
5361 5359 ]
5362 5360 + remoteopts,
5363 5361 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5364 5362 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5365 5363 helpbasic=True,
5366 5364 )
5367 5365 def pull(ui, repo, source=b"default", **opts):
5368 5366 """pull changes from the specified source
5369 5367
5370 5368 Pull changes from a remote repository to a local one.
5371 5369
5372 5370 This finds all changes from the repository at the specified path
5373 5371 or URL and adds them to a local repository (the current one unless
5374 5372 -R is specified). By default, this does not update the copy of the
5375 5373 project in the working directory.
5376 5374
5377 5375 When cloning from servers that support it, Mercurial may fetch
5378 5376 pre-generated data. When this is done, hooks operating on incoming
5379 5377 changesets and changegroups may fire more than once, once for each
5380 5378 pre-generated bundle and as well as for any additional remaining
5381 5379 data. See :hg:`help -e clonebundles` for more.
5382 5380
5383 5381 Use :hg:`incoming` if you want to see what would have been added
5384 5382 by a pull at the time you issued this command. If you then decide
5385 5383 to add those changes to the repository, you should use :hg:`pull
5386 5384 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5387 5385
5388 5386 If SOURCE is omitted, the 'default' path will be used.
5389 5387 See :hg:`help urls` for more information.
5390 5388
5391 5389 Specifying bookmark as ``.`` is equivalent to specifying the active
5392 5390 bookmark's name.
5393 5391
5394 5392 Returns 0 on success, 1 if an update had unresolved files.
5395 5393 """
5396 5394
5397 5395 opts = pycompat.byteskwargs(opts)
5398 5396 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5399 5397 b'update'
5400 5398 ):
5401 5399 msg = _(b'update destination required by configuration')
5402 5400 hint = _(b'use hg pull followed by hg update DEST')
5403 5401 raise error.Abort(msg, hint=hint)
5404 5402
5405 5403 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5406 5404 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5407 5405 other = hg.peer(repo, opts, source)
5408 5406 try:
5409 5407 revs, checkout = hg.addbranchrevs(
5410 5408 repo, other, branches, opts.get(b'rev')
5411 5409 )
5412 5410
5413 5411 pullopargs = {}
5414 5412
5415 5413 nodes = None
5416 5414 if opts.get(b'bookmark') or revs:
5417 5415 # The list of bookmark used here is the same used to actually update
5418 5416 # the bookmark names, to avoid the race from issue 4689 and we do
5419 5417 # all lookup and bookmark queries in one go so they see the same
5420 5418 # version of the server state (issue 4700).
5421 5419 nodes = []
5422 5420 fnodes = []
5423 5421 revs = revs or []
5424 5422 if revs and not other.capable(b'lookup'):
5425 5423 err = _(
5426 5424 b"other repository doesn't support revision lookup, "
5427 5425 b"so a rev cannot be specified."
5428 5426 )
5429 5427 raise error.Abort(err)
5430 5428 with other.commandexecutor() as e:
5431 5429 fremotebookmarks = e.callcommand(
5432 5430 b'listkeys', {b'namespace': b'bookmarks'}
5433 5431 )
5434 5432 for r in revs:
5435 5433 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5436 5434 remotebookmarks = fremotebookmarks.result()
5437 5435 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5438 5436 pullopargs[b'remotebookmarks'] = remotebookmarks
5439 5437 for b in opts.get(b'bookmark', []):
5440 5438 b = repo._bookmarks.expandname(b)
5441 5439 if b not in remotebookmarks:
5442 5440 raise error.Abort(_(b'remote bookmark %s not found!') % b)
5443 5441 nodes.append(remotebookmarks[b])
5444 5442 for i, rev in enumerate(revs):
5445 5443 node = fnodes[i].result()
5446 5444 nodes.append(node)
5447 5445 if rev == checkout:
5448 5446 checkout = node
5449 5447
5450 5448 wlock = util.nullcontextmanager()
5451 5449 if opts.get(b'update'):
5452 5450 wlock = repo.wlock()
5453 5451 with wlock:
5454 5452 pullopargs.update(opts.get(b'opargs', {}))
5455 5453 modheads = exchange.pull(
5456 5454 repo,
5457 5455 other,
5458 5456 heads=nodes,
5459 5457 force=opts.get(b'force'),
5460 5458 bookmarks=opts.get(b'bookmark', ()),
5461 5459 opargs=pullopargs,
5462 5460 ).cgresult
5463 5461
5464 5462 # brev is a name, which might be a bookmark to be activated at
5465 5463 # the end of the update. In other words, it is an explicit
5466 5464 # destination of the update
5467 5465 brev = None
5468 5466
5469 5467 if checkout:
5470 5468 checkout = repo.unfiltered().changelog.rev(checkout)
5471 5469
5472 5470 # order below depends on implementation of
5473 5471 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5474 5472 # because 'checkout' is determined without it.
5475 5473 if opts.get(b'rev'):
5476 5474 brev = opts[b'rev'][0]
5477 5475 elif opts.get(b'branch'):
5478 5476 brev = opts[b'branch'][0]
5479 5477 else:
5480 5478 brev = branches[0]
5481 5479 repo._subtoppath = source
5482 5480 try:
5483 5481 ret = postincoming(
5484 5482 ui, repo, modheads, opts.get(b'update'), checkout, brev
5485 5483 )
5486 5484 except error.FilteredRepoLookupError as exc:
5487 5485 msg = _(b'cannot update to target: %s') % exc.args[0]
5488 5486 exc.args = (msg,) + exc.args[1:]
5489 5487 raise
5490 5488 finally:
5491 5489 del repo._subtoppath
5492 5490
5493 5491 finally:
5494 5492 other.close()
5495 5493 return ret
5496 5494
5497 5495
5498 5496 @command(
5499 5497 b'push',
5500 5498 [
5501 5499 (b'f', b'force', None, _(b'force push')),
5502 5500 (
5503 5501 b'r',
5504 5502 b'rev',
5505 5503 [],
5506 5504 _(b'a changeset intended to be included in the destination'),
5507 5505 _(b'REV'),
5508 5506 ),
5509 5507 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5510 5508 (
5511 5509 b'b',
5512 5510 b'branch',
5513 5511 [],
5514 5512 _(b'a specific branch you would like to push'),
5515 5513 _(b'BRANCH'),
5516 5514 ),
5517 5515 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5518 5516 (
5519 5517 b'',
5520 5518 b'pushvars',
5521 5519 [],
5522 5520 _(b'variables that can be sent to server (ADVANCED)'),
5523 5521 ),
5524 5522 (
5525 5523 b'',
5526 5524 b'publish',
5527 5525 False,
5528 5526 _(b'push the changeset as public (EXPERIMENTAL)'),
5529 5527 ),
5530 5528 ]
5531 5529 + remoteopts,
5532 5530 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5533 5531 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5534 5532 helpbasic=True,
5535 5533 )
5536 5534 def push(ui, repo, dest=None, **opts):
5537 5535 """push changes to the specified destination
5538 5536
5539 5537 Push changesets from the local repository to the specified
5540 5538 destination.
5541 5539
5542 5540 This operation is symmetrical to pull: it is identical to a pull
5543 5541 in the destination repository from the current one.
5544 5542
5545 5543 By default, push will not allow creation of new heads at the
5546 5544 destination, since multiple heads would make it unclear which head
5547 5545 to use. In this situation, it is recommended to pull and merge
5548 5546 before pushing.
5549 5547
5550 5548 Use --new-branch if you want to allow push to create a new named
5551 5549 branch that is not present at the destination. This allows you to
5552 5550 only create a new branch without forcing other changes.
5553 5551
5554 5552 .. note::
5555 5553
5556 5554 Extra care should be taken with the -f/--force option,
5557 5555 which will push all new heads on all branches, an action which will
5558 5556 almost always cause confusion for collaborators.
5559 5557
5560 5558 If -r/--rev is used, the specified revision and all its ancestors
5561 5559 will be pushed to the remote repository.
5562 5560
5563 5561 If -B/--bookmark is used, the specified bookmarked revision, its
5564 5562 ancestors, and the bookmark will be pushed to the remote
5565 5563 repository. Specifying ``.`` is equivalent to specifying the active
5566 5564 bookmark's name.
5567 5565
5568 5566 Please see :hg:`help urls` for important details about ``ssh://``
5569 5567 URLs. If DESTINATION is omitted, a default path will be used.
5570 5568
5571 5569 .. container:: verbose
5572 5570
5573 5571 The --pushvars option sends strings to the server that become
5574 5572 environment variables prepended with ``HG_USERVAR_``. For example,
5575 5573 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5576 5574 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5577 5575
5578 5576 pushvars can provide for user-overridable hooks as well as set debug
5579 5577 levels. One example is having a hook that blocks commits containing
5580 5578 conflict markers, but enables the user to override the hook if the file
5581 5579 is using conflict markers for testing purposes or the file format has
5582 5580 strings that look like conflict markers.
5583 5581
5584 5582 By default, servers will ignore `--pushvars`. To enable it add the
5585 5583 following to your configuration file::
5586 5584
5587 5585 [push]
5588 5586 pushvars.server = true
5589 5587
5590 5588 Returns 0 if push was successful, 1 if nothing to push.
5591 5589 """
5592 5590
5593 5591 opts = pycompat.byteskwargs(opts)
5594 5592 if opts.get(b'bookmark'):
5595 5593 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5596 5594 for b in opts[b'bookmark']:
5597 5595 # translate -B options to -r so changesets get pushed
5598 5596 b = repo._bookmarks.expandname(b)
5599 5597 if b in repo._bookmarks:
5600 5598 opts.setdefault(b'rev', []).append(b)
5601 5599 else:
5602 5600 # if we try to push a deleted bookmark, translate it to null
5603 5601 # this lets simultaneous -r, -b options continue working
5604 5602 opts.setdefault(b'rev', []).append(b"null")
5605 5603
5606 5604 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5607 5605 if not path:
5608 5606 raise error.Abort(
5609 5607 _(b'default repository not configured!'),
5610 5608 hint=_(b"see 'hg help config.paths'"),
5611 5609 )
5612 5610 dest = path.pushloc or path.loc
5613 5611 branches = (path.branch, opts.get(b'branch') or [])
5614 5612 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5615 5613 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5616 5614 other = hg.peer(repo, opts, dest)
5617 5615
5618 5616 if revs:
5619 5617 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5620 5618 if not revs:
5621 5619 raise error.Abort(
5622 5620 _(b"specified revisions evaluate to an empty set"),
5623 5621 hint=_(b"use different revision arguments"),
5624 5622 )
5625 5623 elif path.pushrev:
5626 5624 # It doesn't make any sense to specify ancestor revisions. So limit
5627 5625 # to DAG heads to make discovery simpler.
5628 5626 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5629 5627 revs = scmutil.revrange(repo, [expr])
5630 5628 revs = [repo[rev].node() for rev in revs]
5631 5629 if not revs:
5632 5630 raise error.Abort(
5633 5631 _(b'default push revset for path evaluates to an empty set')
5634 5632 )
5635 5633 elif ui.configbool(b'commands', b'push.require-revs'):
5636 5634 raise error.Abort(
5637 5635 _(b'no revisions specified to push'),
5638 5636 hint=_(b'did you mean "hg push -r ."?'),
5639 5637 )
5640 5638
5641 5639 repo._subtoppath = dest
5642 5640 try:
5643 5641 # push subrepos depth-first for coherent ordering
5644 5642 c = repo[b'.']
5645 5643 subs = c.substate # only repos that are committed
5646 5644 for s in sorted(subs):
5647 5645 result = c.sub(s).push(opts)
5648 5646 if result == 0:
5649 5647 return not result
5650 5648 finally:
5651 5649 del repo._subtoppath
5652 5650
5653 5651 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5654 5652 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5655 5653
5656 5654 pushop = exchange.push(
5657 5655 repo,
5658 5656 other,
5659 5657 opts.get(b'force'),
5660 5658 revs=revs,
5661 5659 newbranch=opts.get(b'new_branch'),
5662 5660 bookmarks=opts.get(b'bookmark', ()),
5663 5661 publish=opts.get(b'publish'),
5664 5662 opargs=opargs,
5665 5663 )
5666 5664
5667 5665 result = not pushop.cgresult
5668 5666
5669 5667 if pushop.bkresult is not None:
5670 5668 if pushop.bkresult == 2:
5671 5669 result = 2
5672 5670 elif not result and pushop.bkresult:
5673 5671 result = 2
5674 5672
5675 5673 return result
5676 5674
5677 5675
5678 5676 @command(
5679 5677 b'recover',
5680 5678 [(b'', b'verify', True, b"run `hg verify` after succesful recover"),],
5681 5679 helpcategory=command.CATEGORY_MAINTENANCE,
5682 5680 )
5683 5681 def recover(ui, repo, **opts):
5684 5682 """roll back an interrupted transaction
5685 5683
5686 5684 Recover from an interrupted commit or pull.
5687 5685
5688 5686 This command tries to fix the repository status after an
5689 5687 interrupted operation. It should only be necessary when Mercurial
5690 5688 suggests it.
5691 5689
5692 5690 Returns 0 if successful, 1 if nothing to recover or verify fails.
5693 5691 """
5694 5692 ret = repo.recover()
5695 5693 if ret:
5696 5694 if opts['verify']:
5697 5695 return hg.verify(repo)
5698 5696 else:
5699 5697 msg = _(
5700 5698 b"(verify step skipped, run `hg verify` to check your "
5701 5699 b"repository content)\n"
5702 5700 )
5703 5701 ui.warn(msg)
5704 5702 return 0
5705 5703 return 1
5706 5704
5707 5705
5708 5706 @command(
5709 5707 b'remove|rm',
5710 5708 [
5711 5709 (b'A', b'after', None, _(b'record delete for missing files')),
5712 5710 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5713 5711 ]
5714 5712 + subrepoopts
5715 5713 + walkopts
5716 5714 + dryrunopts,
5717 5715 _(b'[OPTION]... FILE...'),
5718 5716 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5719 5717 helpbasic=True,
5720 5718 inferrepo=True,
5721 5719 )
5722 5720 def remove(ui, repo, *pats, **opts):
5723 5721 """remove the specified files on the next commit
5724 5722
5725 5723 Schedule the indicated files for removal from the current branch.
5726 5724
5727 5725 This command schedules the files to be removed at the next commit.
5728 5726 To undo a remove before that, see :hg:`revert`. To undo added
5729 5727 files, see :hg:`forget`.
5730 5728
5731 5729 .. container:: verbose
5732 5730
5733 5731 -A/--after can be used to remove only files that have already
5734 5732 been deleted, -f/--force can be used to force deletion, and -Af
5735 5733 can be used to remove files from the next revision without
5736 5734 deleting them from the working directory.
5737 5735
5738 5736 The following table details the behavior of remove for different
5739 5737 file states (columns) and option combinations (rows). The file
5740 5738 states are Added [A], Clean [C], Modified [M] and Missing [!]
5741 5739 (as reported by :hg:`status`). The actions are Warn, Remove
5742 5740 (from branch) and Delete (from disk):
5743 5741
5744 5742 ========= == == == ==
5745 5743 opt/state A C M !
5746 5744 ========= == == == ==
5747 5745 none W RD W R
5748 5746 -f R RD RD R
5749 5747 -A W W W R
5750 5748 -Af R R R R
5751 5749 ========= == == == ==
5752 5750
5753 5751 .. note::
5754 5752
5755 5753 :hg:`remove` never deletes files in Added [A] state from the
5756 5754 working directory, not even if ``--force`` is specified.
5757 5755
5758 5756 Returns 0 on success, 1 if any warnings encountered.
5759 5757 """
5760 5758
5761 5759 opts = pycompat.byteskwargs(opts)
5762 5760 after, force = opts.get(b'after'), opts.get(b'force')
5763 5761 dryrun = opts.get(b'dry_run')
5764 5762 if not pats and not after:
5765 5763 raise error.Abort(_(b'no files specified'))
5766 5764
5767 5765 m = scmutil.match(repo[None], pats, opts)
5768 5766 subrepos = opts.get(b'subrepos')
5769 5767 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5770 5768 return cmdutil.remove(
5771 5769 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5772 5770 )
5773 5771
5774 5772
5775 5773 @command(
5776 5774 b'rename|move|mv',
5777 5775 [
5778 5776 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5779 5777 (
5780 5778 b'f',
5781 5779 b'force',
5782 5780 None,
5783 5781 _(b'forcibly move over an existing managed file'),
5784 5782 ),
5785 5783 ]
5786 5784 + walkopts
5787 5785 + dryrunopts,
5788 5786 _(b'[OPTION]... SOURCE... DEST'),
5789 5787 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5790 5788 )
5791 5789 def rename(ui, repo, *pats, **opts):
5792 5790 """rename files; equivalent of copy + remove
5793 5791
5794 5792 Mark dest as copies of sources; mark sources for deletion. If dest
5795 5793 is a directory, copies are put in that directory. If dest is a
5796 5794 file, there can only be one source.
5797 5795
5798 5796 By default, this command copies the contents of files as they
5799 5797 exist in the working directory. If invoked with -A/--after, the
5800 5798 operation is recorded, but no copying is performed.
5801 5799
5802 5800 This command takes effect at the next commit. To undo a rename
5803 5801 before that, see :hg:`revert`.
5804 5802
5805 5803 Returns 0 on success, 1 if errors are encountered.
5806 5804 """
5807 5805 opts = pycompat.byteskwargs(opts)
5808 5806 with repo.wlock(False):
5809 5807 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5810 5808
5811 5809
5812 5810 @command(
5813 5811 b'resolve',
5814 5812 [
5815 5813 (b'a', b'all', None, _(b'select all unresolved files')),
5816 5814 (b'l', b'list', None, _(b'list state of files needing merge')),
5817 5815 (b'm', b'mark', None, _(b'mark files as resolved')),
5818 5816 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5819 5817 (b'n', b'no-status', None, _(b'hide status prefix')),
5820 5818 (b'', b're-merge', None, _(b're-merge files')),
5821 5819 ]
5822 5820 + mergetoolopts
5823 5821 + walkopts
5824 5822 + formatteropts,
5825 5823 _(b'[OPTION]... [FILE]...'),
5826 5824 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5827 5825 inferrepo=True,
5828 5826 )
5829 5827 def resolve(ui, repo, *pats, **opts):
5830 5828 """redo merges or set/view the merge status of files
5831 5829
5832 5830 Merges with unresolved conflicts are often the result of
5833 5831 non-interactive merging using the ``internal:merge`` configuration
5834 5832 setting, or a command-line merge tool like ``diff3``. The resolve
5835 5833 command is used to manage the files involved in a merge, after
5836 5834 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5837 5835 working directory must have two parents). See :hg:`help
5838 5836 merge-tools` for information on configuring merge tools.
5839 5837
5840 5838 The resolve command can be used in the following ways:
5841 5839
5842 5840 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5843 5841 the specified files, discarding any previous merge attempts. Re-merging
5844 5842 is not performed for files already marked as resolved. Use ``--all/-a``
5845 5843 to select all unresolved files. ``--tool`` can be used to specify
5846 5844 the merge tool used for the given files. It overrides the HGMERGE
5847 5845 environment variable and your configuration files. Previous file
5848 5846 contents are saved with a ``.orig`` suffix.
5849 5847
5850 5848 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5851 5849 (e.g. after having manually fixed-up the files). The default is
5852 5850 to mark all unresolved files.
5853 5851
5854 5852 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5855 5853 default is to mark all resolved files.
5856 5854
5857 5855 - :hg:`resolve -l`: list files which had or still have conflicts.
5858 5856 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5859 5857 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5860 5858 the list. See :hg:`help filesets` for details.
5861 5859
5862 5860 .. note::
5863 5861
5864 5862 Mercurial will not let you commit files with unresolved merge
5865 5863 conflicts. You must use :hg:`resolve -m ...` before you can
5866 5864 commit after a conflicting merge.
5867 5865
5868 5866 .. container:: verbose
5869 5867
5870 5868 Template:
5871 5869
5872 5870 The following keywords are supported in addition to the common template
5873 5871 keywords and functions. See also :hg:`help templates`.
5874 5872
5875 5873 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5876 5874 :path: String. Repository-absolute path of the file.
5877 5875
5878 5876 Returns 0 on success, 1 if any files fail a resolve attempt.
5879 5877 """
5880 5878
5881 5879 opts = pycompat.byteskwargs(opts)
5882 5880 confirm = ui.configbool(b'commands', b'resolve.confirm')
5883 5881 flaglist = b'all mark unmark list no_status re_merge'.split()
5884 5882 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5885 5883
5886 5884 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5887 5885 if actioncount > 1:
5888 5886 raise error.Abort(_(b"too many actions specified"))
5889 5887 elif actioncount == 0 and ui.configbool(
5890 5888 b'commands', b'resolve.explicit-re-merge'
5891 5889 ):
5892 5890 hint = _(b'use --mark, --unmark, --list or --re-merge')
5893 5891 raise error.Abort(_(b'no action specified'), hint=hint)
5894 5892 if pats and all:
5895 5893 raise error.Abort(_(b"can't specify --all and patterns"))
5896 5894 if not (all or pats or show or mark or unmark):
5897 5895 raise error.Abort(
5898 5896 _(b'no files or directories specified'),
5899 5897 hint=b'use --all to re-merge all unresolved files',
5900 5898 )
5901 5899
5902 5900 if confirm:
5903 5901 if all:
5904 5902 if ui.promptchoice(
5905 5903 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5906 5904 ):
5907 5905 raise error.Abort(_(b'user quit'))
5908 5906 if mark and not pats:
5909 5907 if ui.promptchoice(
5910 5908 _(
5911 5909 b'mark all unresolved files as resolved (yn)?'
5912 5910 b'$$ &Yes $$ &No'
5913 5911 )
5914 5912 ):
5915 5913 raise error.Abort(_(b'user quit'))
5916 5914 if unmark and not pats:
5917 5915 if ui.promptchoice(
5918 5916 _(
5919 5917 b'mark all resolved files as unresolved (yn)?'
5920 5918 b'$$ &Yes $$ &No'
5921 5919 )
5922 5920 ):
5923 5921 raise error.Abort(_(b'user quit'))
5924 5922
5925 5923 uipathfn = scmutil.getuipathfn(repo)
5926 5924
5927 5925 if show:
5928 5926 ui.pager(b'resolve')
5929 5927 fm = ui.formatter(b'resolve', opts)
5930 5928 ms = mergemod.mergestate.read(repo)
5931 5929 wctx = repo[None]
5932 5930 m = scmutil.match(wctx, pats, opts)
5933 5931
5934 5932 # Labels and keys based on merge state. Unresolved path conflicts show
5935 5933 # as 'P'. Resolved path conflicts show as 'R', the same as normal
5936 5934 # resolved conflicts.
5937 5935 mergestateinfo = {
5938 5936 mergemod.MERGE_RECORD_UNRESOLVED: (b'resolve.unresolved', b'U'),
5939 5937 mergemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
5940 5938 mergemod.MERGE_RECORD_UNRESOLVED_PATH: (
5941 5939 b'resolve.unresolved',
5942 5940 b'P',
5943 5941 ),
5944 5942 mergemod.MERGE_RECORD_RESOLVED_PATH: (b'resolve.resolved', b'R'),
5945 5943 mergemod.MERGE_RECORD_DRIVER_RESOLVED: (
5946 5944 b'resolve.driverresolved',
5947 5945 b'D',
5948 5946 ),
5949 5947 }
5950 5948
5951 5949 for f in ms:
5952 5950 if not m(f):
5953 5951 continue
5954 5952
5955 5953 label, key = mergestateinfo[ms[f]]
5956 5954 fm.startitem()
5957 5955 fm.context(ctx=wctx)
5958 5956 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
5959 5957 fm.data(path=f)
5960 5958 fm.plain(b'%s\n' % uipathfn(f), label=label)
5961 5959 fm.end()
5962 5960 return 0
5963 5961
5964 5962 with repo.wlock():
5965 5963 ms = mergemod.mergestate.read(repo)
5966 5964
5967 5965 if not (ms.active() or repo.dirstate.p2() != nullid):
5968 5966 raise error.Abort(
5969 5967 _(b'resolve command not applicable when not merging')
5970 5968 )
5971 5969
5972 5970 wctx = repo[None]
5973 5971
5974 5972 if (
5975 5973 ms.mergedriver
5976 5974 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED
5977 5975 ):
5978 5976 proceed = mergemod.driverpreprocess(repo, ms, wctx)
5979 5977 ms.commit()
5980 5978 # allow mark and unmark to go through
5981 5979 if not mark and not unmark and not proceed:
5982 5980 return 1
5983 5981
5984 5982 m = scmutil.match(wctx, pats, opts)
5985 5983 ret = 0
5986 5984 didwork = False
5987 5985 runconclude = False
5988 5986
5989 5987 tocomplete = []
5990 5988 hasconflictmarkers = []
5991 5989 if mark:
5992 5990 markcheck = ui.config(b'commands', b'resolve.mark-check')
5993 5991 if markcheck not in [b'warn', b'abort']:
5994 5992 # Treat all invalid / unrecognized values as 'none'.
5995 5993 markcheck = False
5996 5994 for f in ms:
5997 5995 if not m(f):
5998 5996 continue
5999 5997
6000 5998 didwork = True
6001 5999
6002 6000 # don't let driver-resolved files be marked, and run the conclude
6003 6001 # step if asked to resolve
6004 6002 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
6005 6003 exact = m.exact(f)
6006 6004 if mark:
6007 6005 if exact:
6008 6006 ui.warn(
6009 6007 _(b'not marking %s as it is driver-resolved\n')
6010 6008 % uipathfn(f)
6011 6009 )
6012 6010 elif unmark:
6013 6011 if exact:
6014 6012 ui.warn(
6015 6013 _(b'not unmarking %s as it is driver-resolved\n')
6016 6014 % uipathfn(f)
6017 6015 )
6018 6016 else:
6019 6017 runconclude = True
6020 6018 continue
6021 6019
6022 6020 # path conflicts must be resolved manually
6023 6021 if ms[f] in (
6024 6022 mergemod.MERGE_RECORD_UNRESOLVED_PATH,
6025 6023 mergemod.MERGE_RECORD_RESOLVED_PATH,
6026 6024 ):
6027 6025 if mark:
6028 6026 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
6029 6027 elif unmark:
6030 6028 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
6031 6029 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
6032 6030 ui.warn(
6033 6031 _(b'%s: path conflict must be resolved manually\n')
6034 6032 % uipathfn(f)
6035 6033 )
6036 6034 continue
6037 6035
6038 6036 if mark:
6039 6037 if markcheck:
6040 6038 fdata = repo.wvfs.tryread(f)
6041 6039 if (
6042 6040 filemerge.hasconflictmarkers(fdata)
6043 6041 and ms[f] != mergemod.MERGE_RECORD_RESOLVED
6044 6042 ):
6045 6043 hasconflictmarkers.append(f)
6046 6044 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
6047 6045 elif unmark:
6048 6046 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
6049 6047 else:
6050 6048 # backup pre-resolve (merge uses .orig for its own purposes)
6051 6049 a = repo.wjoin(f)
6052 6050 try:
6053 6051 util.copyfile(a, a + b".resolve")
6054 6052 except (IOError, OSError) as inst:
6055 6053 if inst.errno != errno.ENOENT:
6056 6054 raise
6057 6055
6058 6056 try:
6059 6057 # preresolve file
6060 6058 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6061 6059 with ui.configoverride(overrides, b'resolve'):
6062 6060 complete, r = ms.preresolve(f, wctx)
6063 6061 if not complete:
6064 6062 tocomplete.append(f)
6065 6063 elif r:
6066 6064 ret = 1
6067 6065 finally:
6068 6066 ms.commit()
6069 6067
6070 6068 # replace filemerge's .orig file with our resolve file, but only
6071 6069 # for merges that are complete
6072 6070 if complete:
6073 6071 try:
6074 6072 util.rename(
6075 6073 a + b".resolve", scmutil.backuppath(ui, repo, f)
6076 6074 )
6077 6075 except OSError as inst:
6078 6076 if inst.errno != errno.ENOENT:
6079 6077 raise
6080 6078
6081 6079 if hasconflictmarkers:
6082 6080 ui.warn(
6083 6081 _(
6084 6082 b'warning: the following files still have conflict '
6085 6083 b'markers:\n'
6086 6084 )
6087 6085 + b''.join(
6088 6086 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6089 6087 )
6090 6088 )
6091 6089 if markcheck == b'abort' and not all and not pats:
6092 6090 raise error.Abort(
6093 6091 _(b'conflict markers detected'),
6094 6092 hint=_(b'use --all to mark anyway'),
6095 6093 )
6096 6094
6097 6095 for f in tocomplete:
6098 6096 try:
6099 6097 # resolve file
6100 6098 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6101 6099 with ui.configoverride(overrides, b'resolve'):
6102 6100 r = ms.resolve(f, wctx)
6103 6101 if r:
6104 6102 ret = 1
6105 6103 finally:
6106 6104 ms.commit()
6107 6105
6108 6106 # replace filemerge's .orig file with our resolve file
6109 6107 a = repo.wjoin(f)
6110 6108 try:
6111 6109 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6112 6110 except OSError as inst:
6113 6111 if inst.errno != errno.ENOENT:
6114 6112 raise
6115 6113
6116 6114 ms.commit()
6117 6115 ms.recordactions()
6118 6116
6119 6117 if not didwork and pats:
6120 6118 hint = None
6121 6119 if not any([p for p in pats if p.find(b':') >= 0]):
6122 6120 pats = [b'path:%s' % p for p in pats]
6123 6121 m = scmutil.match(wctx, pats, opts)
6124 6122 for f in ms:
6125 6123 if not m(f):
6126 6124 continue
6127 6125
6128 6126 def flag(o):
6129 6127 if o == b're_merge':
6130 6128 return b'--re-merge '
6131 6129 return b'-%s ' % o[0:1]
6132 6130
6133 6131 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6134 6132 hint = _(b"(try: hg resolve %s%s)\n") % (
6135 6133 flags,
6136 6134 b' '.join(pats),
6137 6135 )
6138 6136 break
6139 6137 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6140 6138 if hint:
6141 6139 ui.warn(hint)
6142 6140 elif ms.mergedriver and ms.mdstate() != b's':
6143 6141 # run conclude step when either a driver-resolved file is requested
6144 6142 # or there are no driver-resolved files
6145 6143 # we can't use 'ret' to determine whether any files are unresolved
6146 6144 # because we might not have tried to resolve some
6147 6145 if (runconclude or not list(ms.driverresolved())) and not list(
6148 6146 ms.unresolved()
6149 6147 ):
6150 6148 proceed = mergemod.driverconclude(repo, ms, wctx)
6151 6149 ms.commit()
6152 6150 if not proceed:
6153 6151 return 1
6154 6152
6155 6153 # Nudge users into finishing an unfinished operation
6156 6154 unresolvedf = list(ms.unresolved())
6157 6155 driverresolvedf = list(ms.driverresolved())
6158 6156 if not unresolvedf and not driverresolvedf:
6159 6157 ui.status(_(b'(no more unresolved files)\n'))
6160 6158 cmdutil.checkafterresolved(repo)
6161 6159 elif not unresolvedf:
6162 6160 ui.status(
6163 6161 _(
6164 6162 b'(no more unresolved files -- '
6165 6163 b'run "hg resolve --all" to conclude)\n'
6166 6164 )
6167 6165 )
6168 6166
6169 6167 return ret
6170 6168
6171 6169
6172 6170 @command(
6173 6171 b'revert',
6174 6172 [
6175 6173 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6176 6174 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6177 6175 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6178 6176 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6179 6177 (b'i', b'interactive', None, _(b'interactively select the changes')),
6180 6178 ]
6181 6179 + walkopts
6182 6180 + dryrunopts,
6183 6181 _(b'[OPTION]... [-r REV] [NAME]...'),
6184 6182 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6185 6183 )
6186 6184 def revert(ui, repo, *pats, **opts):
6187 6185 """restore files to their checkout state
6188 6186
6189 6187 .. note::
6190 6188
6191 6189 To check out earlier revisions, you should use :hg:`update REV`.
6192 6190 To cancel an uncommitted merge (and lose your changes),
6193 6191 use :hg:`merge --abort`.
6194 6192
6195 6193 With no revision specified, revert the specified files or directories
6196 6194 to the contents they had in the parent of the working directory.
6197 6195 This restores the contents of files to an unmodified
6198 6196 state and unschedules adds, removes, copies, and renames. If the
6199 6197 working directory has two parents, you must explicitly specify a
6200 6198 revision.
6201 6199
6202 6200 Using the -r/--rev or -d/--date options, revert the given files or
6203 6201 directories to their states as of a specific revision. Because
6204 6202 revert does not change the working directory parents, this will
6205 6203 cause these files to appear modified. This can be helpful to "back
6206 6204 out" some or all of an earlier change. See :hg:`backout` for a
6207 6205 related method.
6208 6206
6209 6207 Modified files are saved with a .orig suffix before reverting.
6210 6208 To disable these backups, use --no-backup. It is possible to store
6211 6209 the backup files in a custom directory relative to the root of the
6212 6210 repository by setting the ``ui.origbackuppath`` configuration
6213 6211 option.
6214 6212
6215 6213 See :hg:`help dates` for a list of formats valid for -d/--date.
6216 6214
6217 6215 See :hg:`help backout` for a way to reverse the effect of an
6218 6216 earlier changeset.
6219 6217
6220 6218 Returns 0 on success.
6221 6219 """
6222 6220
6223 6221 opts = pycompat.byteskwargs(opts)
6224 6222 if opts.get(b"date"):
6225 6223 if opts.get(b"rev"):
6226 6224 raise error.Abort(_(b"you can't specify a revision and a date"))
6227 6225 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6228 6226
6229 6227 parent, p2 = repo.dirstate.parents()
6230 6228 if not opts.get(b'rev') and p2 != nullid:
6231 6229 # revert after merge is a trap for new users (issue2915)
6232 6230 raise error.Abort(
6233 6231 _(b'uncommitted merge with no revision specified'),
6234 6232 hint=_(b"use 'hg update' or see 'hg help revert'"),
6235 6233 )
6236 6234
6237 6235 rev = opts.get(b'rev')
6238 6236 if rev:
6239 6237 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6240 6238 ctx = scmutil.revsingle(repo, rev)
6241 6239
6242 6240 if not (
6243 6241 pats
6244 6242 or opts.get(b'include')
6245 6243 or opts.get(b'exclude')
6246 6244 or opts.get(b'all')
6247 6245 or opts.get(b'interactive')
6248 6246 ):
6249 6247 msg = _(b"no files or directories specified")
6250 6248 if p2 != nullid:
6251 6249 hint = _(
6252 6250 b"uncommitted merge, use --all to discard all changes,"
6253 6251 b" or 'hg update -C .' to abort the merge"
6254 6252 )
6255 6253 raise error.Abort(msg, hint=hint)
6256 6254 dirty = any(repo.status())
6257 6255 node = ctx.node()
6258 6256 if node != parent:
6259 6257 if dirty:
6260 6258 hint = (
6261 6259 _(
6262 6260 b"uncommitted changes, use --all to discard all"
6263 6261 b" changes, or 'hg update %d' to update"
6264 6262 )
6265 6263 % ctx.rev()
6266 6264 )
6267 6265 else:
6268 6266 hint = (
6269 6267 _(
6270 6268 b"use --all to revert all files,"
6271 6269 b" or 'hg update %d' to update"
6272 6270 )
6273 6271 % ctx.rev()
6274 6272 )
6275 6273 elif dirty:
6276 6274 hint = _(b"uncommitted changes, use --all to discard all changes")
6277 6275 else:
6278 6276 hint = _(b"use --all to revert all files")
6279 6277 raise error.Abort(msg, hint=hint)
6280 6278
6281 6279 return cmdutil.revert(
6282 6280 ui, repo, ctx, (parent, p2), *pats, **pycompat.strkwargs(opts)
6283 6281 )
6284 6282
6285 6283
6286 6284 @command(
6287 6285 b'rollback',
6288 6286 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6289 6287 helpcategory=command.CATEGORY_MAINTENANCE,
6290 6288 )
6291 6289 def rollback(ui, repo, **opts):
6292 6290 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6293 6291
6294 6292 Please use :hg:`commit --amend` instead of rollback to correct
6295 6293 mistakes in the last commit.
6296 6294
6297 6295 This command should be used with care. There is only one level of
6298 6296 rollback, and there is no way to undo a rollback. It will also
6299 6297 restore the dirstate at the time of the last transaction, losing
6300 6298 any dirstate changes since that time. This command does not alter
6301 6299 the working directory.
6302 6300
6303 6301 Transactions are used to encapsulate the effects of all commands
6304 6302 that create new changesets or propagate existing changesets into a
6305 6303 repository.
6306 6304
6307 6305 .. container:: verbose
6308 6306
6309 6307 For example, the following commands are transactional, and their
6310 6308 effects can be rolled back:
6311 6309
6312 6310 - commit
6313 6311 - import
6314 6312 - pull
6315 6313 - push (with this repository as the destination)
6316 6314 - unbundle
6317 6315
6318 6316 To avoid permanent data loss, rollback will refuse to rollback a
6319 6317 commit transaction if it isn't checked out. Use --force to
6320 6318 override this protection.
6321 6319
6322 6320 The rollback command can be entirely disabled by setting the
6323 6321 ``ui.rollback`` configuration setting to false. If you're here
6324 6322 because you want to use rollback and it's disabled, you can
6325 6323 re-enable the command by setting ``ui.rollback`` to true.
6326 6324
6327 6325 This command is not intended for use on public repositories. Once
6328 6326 changes are visible for pull by other users, rolling a transaction
6329 6327 back locally is ineffective (someone else may already have pulled
6330 6328 the changes). Furthermore, a race is possible with readers of the
6331 6329 repository; for example an in-progress pull from the repository
6332 6330 may fail if a rollback is performed.
6333 6331
6334 6332 Returns 0 on success, 1 if no rollback data is available.
6335 6333 """
6336 6334 if not ui.configbool(b'ui', b'rollback'):
6337 6335 raise error.Abort(
6338 6336 _(b'rollback is disabled because it is unsafe'),
6339 6337 hint=b'see `hg help -v rollback` for information',
6340 6338 )
6341 6339 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6342 6340
6343 6341
6344 6342 @command(
6345 6343 b'root',
6346 6344 [] + formatteropts,
6347 6345 intents={INTENT_READONLY},
6348 6346 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6349 6347 )
6350 6348 def root(ui, repo, **opts):
6351 6349 """print the root (top) of the current working directory
6352 6350
6353 6351 Print the root directory of the current repository.
6354 6352
6355 6353 .. container:: verbose
6356 6354
6357 6355 Template:
6358 6356
6359 6357 The following keywords are supported in addition to the common template
6360 6358 keywords and functions. See also :hg:`help templates`.
6361 6359
6362 6360 :hgpath: String. Path to the .hg directory.
6363 6361 :storepath: String. Path to the directory holding versioned data.
6364 6362
6365 6363 Returns 0 on success.
6366 6364 """
6367 6365 opts = pycompat.byteskwargs(opts)
6368 6366 with ui.formatter(b'root', opts) as fm:
6369 6367 fm.startitem()
6370 6368 fm.write(b'reporoot', b'%s\n', repo.root)
6371 6369 fm.data(hgpath=repo.path, storepath=repo.spath)
6372 6370
6373 6371
6374 6372 @command(
6375 6373 b'serve',
6376 6374 [
6377 6375 (
6378 6376 b'A',
6379 6377 b'accesslog',
6380 6378 b'',
6381 6379 _(b'name of access log file to write to'),
6382 6380 _(b'FILE'),
6383 6381 ),
6384 6382 (b'd', b'daemon', None, _(b'run server in background')),
6385 6383 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6386 6384 (
6387 6385 b'E',
6388 6386 b'errorlog',
6389 6387 b'',
6390 6388 _(b'name of error log file to write to'),
6391 6389 _(b'FILE'),
6392 6390 ),
6393 6391 # use string type, then we can check if something was passed
6394 6392 (
6395 6393 b'p',
6396 6394 b'port',
6397 6395 b'',
6398 6396 _(b'port to listen on (default: 8000)'),
6399 6397 _(b'PORT'),
6400 6398 ),
6401 6399 (
6402 6400 b'a',
6403 6401 b'address',
6404 6402 b'',
6405 6403 _(b'address to listen on (default: all interfaces)'),
6406 6404 _(b'ADDR'),
6407 6405 ),
6408 6406 (
6409 6407 b'',
6410 6408 b'prefix',
6411 6409 b'',
6412 6410 _(b'prefix path to serve from (default: server root)'),
6413 6411 _(b'PREFIX'),
6414 6412 ),
6415 6413 (
6416 6414 b'n',
6417 6415 b'name',
6418 6416 b'',
6419 6417 _(b'name to show in web pages (default: working directory)'),
6420 6418 _(b'NAME'),
6421 6419 ),
6422 6420 (
6423 6421 b'',
6424 6422 b'web-conf',
6425 6423 b'',
6426 6424 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6427 6425 _(b'FILE'),
6428 6426 ),
6429 6427 (
6430 6428 b'',
6431 6429 b'webdir-conf',
6432 6430 b'',
6433 6431 _(b'name of the hgweb config file (DEPRECATED)'),
6434 6432 _(b'FILE'),
6435 6433 ),
6436 6434 (
6437 6435 b'',
6438 6436 b'pid-file',
6439 6437 b'',
6440 6438 _(b'name of file to write process ID to'),
6441 6439 _(b'FILE'),
6442 6440 ),
6443 6441 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6444 6442 (
6445 6443 b'',
6446 6444 b'cmdserver',
6447 6445 b'',
6448 6446 _(b'for remote clients (ADVANCED)'),
6449 6447 _(b'MODE'),
6450 6448 ),
6451 6449 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6452 6450 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6453 6451 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6454 6452 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6455 6453 (b'', b'print-url', None, _(b'start and print only the URL')),
6456 6454 ]
6457 6455 + subrepoopts,
6458 6456 _(b'[OPTION]...'),
6459 6457 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6460 6458 helpbasic=True,
6461 6459 optionalrepo=True,
6462 6460 )
6463 6461 def serve(ui, repo, **opts):
6464 6462 """start stand-alone webserver
6465 6463
6466 6464 Start a local HTTP repository browser and pull server. You can use
6467 6465 this for ad-hoc sharing and browsing of repositories. It is
6468 6466 recommended to use a real web server to serve a repository for
6469 6467 longer periods of time.
6470 6468
6471 6469 Please note that the server does not implement access control.
6472 6470 This means that, by default, anybody can read from the server and
6473 6471 nobody can write to it by default. Set the ``web.allow-push``
6474 6472 option to ``*`` to allow everybody to push to the server. You
6475 6473 should use a real web server if you need to authenticate users.
6476 6474
6477 6475 By default, the server logs accesses to stdout and errors to
6478 6476 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6479 6477 files.
6480 6478
6481 6479 To have the server choose a free port number to listen on, specify
6482 6480 a port number of 0; in this case, the server will print the port
6483 6481 number it uses.
6484 6482
6485 6483 Returns 0 on success.
6486 6484 """
6487 6485
6488 6486 opts = pycompat.byteskwargs(opts)
6489 6487 if opts[b"stdio"] and opts[b"cmdserver"]:
6490 6488 raise error.Abort(_(b"cannot use --stdio with --cmdserver"))
6491 6489 if opts[b"print_url"] and ui.verbose:
6492 6490 raise error.Abort(_(b"cannot use --print-url with --verbose"))
6493 6491
6494 6492 if opts[b"stdio"]:
6495 6493 if repo is None:
6496 6494 raise error.RepoError(
6497 6495 _(b"there is no Mercurial repository here (.hg not found)")
6498 6496 )
6499 6497 s = wireprotoserver.sshserver(ui, repo)
6500 6498 s.serve_forever()
6501 6499
6502 6500 service = server.createservice(ui, repo, opts)
6503 6501 return server.runservice(opts, initfn=service.init, runfn=service.run)
6504 6502
6505 6503
6506 6504 @command(
6507 6505 b'shelve',
6508 6506 [
6509 6507 (
6510 6508 b'A',
6511 6509 b'addremove',
6512 6510 None,
6513 6511 _(b'mark new/missing files as added/removed before shelving'),
6514 6512 ),
6515 6513 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6516 6514 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6517 6515 (
6518 6516 b'',
6519 6517 b'date',
6520 6518 b'',
6521 6519 _(b'shelve with the specified commit date'),
6522 6520 _(b'DATE'),
6523 6521 ),
6524 6522 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6525 6523 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6526 6524 (
6527 6525 b'k',
6528 6526 b'keep',
6529 6527 False,
6530 6528 _(b'shelve, but keep changes in the working directory'),
6531 6529 ),
6532 6530 (b'l', b'list', None, _(b'list current shelves')),
6533 6531 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6534 6532 (
6535 6533 b'n',
6536 6534 b'name',
6537 6535 b'',
6538 6536 _(b'use the given name for the shelved commit'),
6539 6537 _(b'NAME'),
6540 6538 ),
6541 6539 (
6542 6540 b'p',
6543 6541 b'patch',
6544 6542 None,
6545 6543 _(
6546 6544 b'output patches for changes (provide the names of the shelved '
6547 6545 b'changes as positional arguments)'
6548 6546 ),
6549 6547 ),
6550 6548 (b'i', b'interactive', None, _(b'interactive mode')),
6551 6549 (
6552 6550 b'',
6553 6551 b'stat',
6554 6552 None,
6555 6553 _(
6556 6554 b'output diffstat-style summary of changes (provide the names of '
6557 6555 b'the shelved changes as positional arguments)'
6558 6556 ),
6559 6557 ),
6560 6558 ]
6561 6559 + cmdutil.walkopts,
6562 6560 _(b'hg shelve [OPTION]... [FILE]...'),
6563 6561 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6564 6562 )
6565 6563 def shelve(ui, repo, *pats, **opts):
6566 6564 '''save and set aside changes from the working directory
6567 6565
6568 6566 Shelving takes files that "hg status" reports as not clean, saves
6569 6567 the modifications to a bundle (a shelved change), and reverts the
6570 6568 files so that their state in the working directory becomes clean.
6571 6569
6572 6570 To restore these changes to the working directory, using "hg
6573 6571 unshelve"; this will work even if you switch to a different
6574 6572 commit.
6575 6573
6576 6574 When no files are specified, "hg shelve" saves all not-clean
6577 6575 files. If specific files or directories are named, only changes to
6578 6576 those files are shelved.
6579 6577
6580 6578 In bare shelve (when no files are specified, without interactive,
6581 6579 include and exclude option), shelving remembers information if the
6582 6580 working directory was on newly created branch, in other words working
6583 6581 directory was on different branch than its first parent. In this
6584 6582 situation unshelving restores branch information to the working directory.
6585 6583
6586 6584 Each shelved change has a name that makes it easier to find later.
6587 6585 The name of a shelved change defaults to being based on the active
6588 6586 bookmark, or if there is no active bookmark, the current named
6589 6587 branch. To specify a different name, use ``--name``.
6590 6588
6591 6589 To see a list of existing shelved changes, use the ``--list``
6592 6590 option. For each shelved change, this will print its name, age,
6593 6591 and description; use ``--patch`` or ``--stat`` for more details.
6594 6592
6595 6593 To delete specific shelved changes, use ``--delete``. To delete
6596 6594 all shelved changes, use ``--cleanup``.
6597 6595 '''
6598 6596 opts = pycompat.byteskwargs(opts)
6599 6597 allowables = [
6600 6598 (b'addremove', {b'create'}), # 'create' is pseudo action
6601 6599 (b'unknown', {b'create'}),
6602 6600 (b'cleanup', {b'cleanup'}),
6603 6601 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6604 6602 (b'delete', {b'delete'}),
6605 6603 (b'edit', {b'create'}),
6606 6604 (b'keep', {b'create'}),
6607 6605 (b'list', {b'list'}),
6608 6606 (b'message', {b'create'}),
6609 6607 (b'name', {b'create'}),
6610 6608 (b'patch', {b'patch', b'list'}),
6611 6609 (b'stat', {b'stat', b'list'}),
6612 6610 ]
6613 6611
6614 6612 def checkopt(opt):
6615 6613 if opts.get(opt):
6616 6614 for i, allowable in allowables:
6617 6615 if opts[i] and opt not in allowable:
6618 6616 raise error.Abort(
6619 6617 _(
6620 6618 b"options '--%s' and '--%s' may not be "
6621 6619 b"used together"
6622 6620 )
6623 6621 % (opt, i)
6624 6622 )
6625 6623 return True
6626 6624
6627 6625 if checkopt(b'cleanup'):
6628 6626 if pats:
6629 6627 raise error.Abort(_(b"cannot specify names when using '--cleanup'"))
6630 6628 return shelvemod.cleanupcmd(ui, repo)
6631 6629 elif checkopt(b'delete'):
6632 6630 return shelvemod.deletecmd(ui, repo, pats)
6633 6631 elif checkopt(b'list'):
6634 6632 return shelvemod.listcmd(ui, repo, pats, opts)
6635 6633 elif checkopt(b'patch') or checkopt(b'stat'):
6636 6634 return shelvemod.patchcmds(ui, repo, pats, opts)
6637 6635 else:
6638 6636 return shelvemod.createcmd(ui, repo, pats, opts)
6639 6637
6640 6638
6641 6639 _NOTTERSE = b'nothing'
6642 6640
6643 6641
6644 6642 @command(
6645 6643 b'status|st',
6646 6644 [
6647 6645 (b'A', b'all', None, _(b'show status of all files')),
6648 6646 (b'm', b'modified', None, _(b'show only modified files')),
6649 6647 (b'a', b'added', None, _(b'show only added files')),
6650 6648 (b'r', b'removed', None, _(b'show only removed files')),
6651 6649 (b'd', b'deleted', None, _(b'show only deleted (but tracked) files')),
6652 6650 (b'c', b'clean', None, _(b'show only files without changes')),
6653 6651 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6654 6652 (b'i', b'ignored', None, _(b'show only ignored files')),
6655 6653 (b'n', b'no-status', None, _(b'hide status prefix')),
6656 6654 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6657 6655 (b'C', b'copies', None, _(b'show source of copied files')),
6658 6656 (
6659 6657 b'0',
6660 6658 b'print0',
6661 6659 None,
6662 6660 _(b'end filenames with NUL, for use with xargs'),
6663 6661 ),
6664 6662 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6665 6663 (
6666 6664 b'',
6667 6665 b'change',
6668 6666 b'',
6669 6667 _(b'list the changed files of a revision'),
6670 6668 _(b'REV'),
6671 6669 ),
6672 6670 ]
6673 6671 + walkopts
6674 6672 + subrepoopts
6675 6673 + formatteropts,
6676 6674 _(b'[OPTION]... [FILE]...'),
6677 6675 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6678 6676 helpbasic=True,
6679 6677 inferrepo=True,
6680 6678 intents={INTENT_READONLY},
6681 6679 )
6682 6680 def status(ui, repo, *pats, **opts):
6683 6681 """show changed files in the working directory
6684 6682
6685 6683 Show status of files in the repository. If names are given, only
6686 6684 files that match are shown. Files that are clean or ignored or
6687 6685 the source of a copy/move operation, are not listed unless
6688 6686 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6689 6687 Unless options described with "show only ..." are given, the
6690 6688 options -mardu are used.
6691 6689
6692 6690 Option -q/--quiet hides untracked (unknown and ignored) files
6693 6691 unless explicitly requested with -u/--unknown or -i/--ignored.
6694 6692
6695 6693 .. note::
6696 6694
6697 6695 :hg:`status` may appear to disagree with diff if permissions have
6698 6696 changed or a merge has occurred. The standard diff format does
6699 6697 not report permission changes and diff only reports changes
6700 6698 relative to one merge parent.
6701 6699
6702 6700 If one revision is given, it is used as the base revision.
6703 6701 If two revisions are given, the differences between them are
6704 6702 shown. The --change option can also be used as a shortcut to list
6705 6703 the changed files of a revision from its first parent.
6706 6704
6707 6705 The codes used to show the status of files are::
6708 6706
6709 6707 M = modified
6710 6708 A = added
6711 6709 R = removed
6712 6710 C = clean
6713 6711 ! = missing (deleted by non-hg command, but still tracked)
6714 6712 ? = not tracked
6715 6713 I = ignored
6716 6714 = origin of the previous file (with --copies)
6717 6715
6718 6716 .. container:: verbose
6719 6717
6720 6718 The -t/--terse option abbreviates the output by showing only the directory
6721 6719 name if all the files in it share the same status. The option takes an
6722 6720 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6723 6721 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6724 6722 for 'ignored' and 'c' for clean.
6725 6723
6726 6724 It abbreviates only those statuses which are passed. Note that clean and
6727 6725 ignored files are not displayed with '--terse ic' unless the -c/--clean
6728 6726 and -i/--ignored options are also used.
6729 6727
6730 6728 The -v/--verbose option shows information when the repository is in an
6731 6729 unfinished merge, shelve, rebase state etc. You can have this behavior
6732 6730 turned on by default by enabling the ``commands.status.verbose`` option.
6733 6731
6734 6732 You can skip displaying some of these states by setting
6735 6733 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6736 6734 'histedit', 'merge', 'rebase', or 'unshelve'.
6737 6735
6738 6736 Template:
6739 6737
6740 6738 The following keywords are supported in addition to the common template
6741 6739 keywords and functions. See also :hg:`help templates`.
6742 6740
6743 6741 :path: String. Repository-absolute path of the file.
6744 6742 :source: String. Repository-absolute path of the file originated from.
6745 6743 Available if ``--copies`` is specified.
6746 6744 :status: String. Character denoting file's status.
6747 6745
6748 6746 Examples:
6749 6747
6750 6748 - show changes in the working directory relative to a
6751 6749 changeset::
6752 6750
6753 6751 hg status --rev 9353
6754 6752
6755 6753 - show changes in the working directory relative to the
6756 6754 current directory (see :hg:`help patterns` for more information)::
6757 6755
6758 6756 hg status re:
6759 6757
6760 6758 - show all changes including copies in an existing changeset::
6761 6759
6762 6760 hg status --copies --change 9353
6763 6761
6764 6762 - get a NUL separated list of added files, suitable for xargs::
6765 6763
6766 6764 hg status -an0
6767 6765
6768 6766 - show more information about the repository status, abbreviating
6769 6767 added, removed, modified, deleted, and untracked paths::
6770 6768
6771 6769 hg status -v -t mardu
6772 6770
6773 6771 Returns 0 on success.
6774 6772
6775 6773 """
6776 6774
6777 6775 opts = pycompat.byteskwargs(opts)
6778 6776 revs = opts.get(b'rev')
6779 6777 change = opts.get(b'change')
6780 6778 terse = opts.get(b'terse')
6781 6779 if terse is _NOTTERSE:
6782 6780 if revs:
6783 6781 terse = b''
6784 6782 else:
6785 6783 terse = ui.config(b'commands', b'status.terse')
6786 6784
6787 6785 if revs and change:
6788 6786 msg = _(b'cannot specify --rev and --change at the same time')
6789 6787 raise error.Abort(msg)
6790 6788 elif revs and terse:
6791 6789 msg = _(b'cannot use --terse with --rev')
6792 6790 raise error.Abort(msg)
6793 6791 elif change:
6794 6792 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6795 6793 ctx2 = scmutil.revsingle(repo, change, None)
6796 6794 ctx1 = ctx2.p1()
6797 6795 else:
6798 6796 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6799 6797 ctx1, ctx2 = scmutil.revpair(repo, revs)
6800 6798
6801 6799 forcerelativevalue = None
6802 6800 if ui.hasconfig(b'commands', b'status.relative'):
6803 6801 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6804 6802 uipathfn = scmutil.getuipathfn(
6805 6803 repo,
6806 6804 legacyrelativevalue=bool(pats),
6807 6805 forcerelativevalue=forcerelativevalue,
6808 6806 )
6809 6807
6810 6808 if opts.get(b'print0'):
6811 6809 end = b'\0'
6812 6810 else:
6813 6811 end = b'\n'
6814 6812 states = b'modified added removed deleted unknown ignored clean'.split()
6815 6813 show = [k for k in states if opts.get(k)]
6816 6814 if opts.get(b'all'):
6817 6815 show += ui.quiet and (states[:4] + [b'clean']) or states
6818 6816
6819 6817 if not show:
6820 6818 if ui.quiet:
6821 6819 show = states[:4]
6822 6820 else:
6823 6821 show = states[:5]
6824 6822
6825 6823 m = scmutil.match(ctx2, pats, opts)
6826 6824 if terse:
6827 6825 # we need to compute clean and unknown to terse
6828 6826 stat = repo.status(
6829 6827 ctx1.node(),
6830 6828 ctx2.node(),
6831 6829 m,
6832 6830 b'ignored' in show or b'i' in terse,
6833 6831 clean=True,
6834 6832 unknown=True,
6835 6833 listsubrepos=opts.get(b'subrepos'),
6836 6834 )
6837 6835
6838 6836 stat = cmdutil.tersedir(stat, terse)
6839 6837 else:
6840 6838 stat = repo.status(
6841 6839 ctx1.node(),
6842 6840 ctx2.node(),
6843 6841 m,
6844 6842 b'ignored' in show,
6845 6843 b'clean' in show,
6846 6844 b'unknown' in show,
6847 6845 opts.get(b'subrepos'),
6848 6846 )
6849 6847
6850 6848 changestates = zip(
6851 6849 states,
6852 6850 pycompat.iterbytestr(b'MAR!?IC'),
6853 6851 [getattr(stat, s.decode('utf8')) for s in states],
6854 6852 )
6855 6853
6856 6854 copy = {}
6857 6855 if (
6858 6856 opts.get(b'all')
6859 6857 or opts.get(b'copies')
6860 6858 or ui.configbool(b'ui', b'statuscopies')
6861 6859 ) and not opts.get(b'no_status'):
6862 6860 copy = copies.pathcopies(ctx1, ctx2, m)
6863 6861
6864 6862 morestatus = None
6865 6863 if (
6866 6864 ui.verbose or ui.configbool(b'commands', b'status.verbose')
6867 6865 ) and not ui.plain():
6868 6866 morestatus = cmdutil.readmorestatus(repo)
6869 6867
6870 6868 ui.pager(b'status')
6871 6869 fm = ui.formatter(b'status', opts)
6872 6870 fmt = b'%s' + end
6873 6871 showchar = not opts.get(b'no_status')
6874 6872
6875 6873 for state, char, files in changestates:
6876 6874 if state in show:
6877 6875 label = b'status.' + state
6878 6876 for f in files:
6879 6877 fm.startitem()
6880 6878 fm.context(ctx=ctx2)
6881 6879 fm.data(itemtype=b'file', path=f)
6882 6880 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6883 6881 fm.plain(fmt % uipathfn(f), label=label)
6884 6882 if f in copy:
6885 6883 fm.data(source=copy[f])
6886 6884 fm.plain(
6887 6885 (b' %s' + end) % uipathfn(copy[f]),
6888 6886 label=b'status.copied',
6889 6887 )
6890 6888 if morestatus:
6891 6889 morestatus.formatfile(f, fm)
6892 6890
6893 6891 if morestatus:
6894 6892 morestatus.formatfooter(fm)
6895 6893 fm.end()
6896 6894
6897 6895
6898 6896 @command(
6899 6897 b'summary|sum',
6900 6898 [(b'', b'remote', None, _(b'check for push and pull'))],
6901 6899 b'[--remote]',
6902 6900 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6903 6901 helpbasic=True,
6904 6902 intents={INTENT_READONLY},
6905 6903 )
6906 6904 def summary(ui, repo, **opts):
6907 6905 """summarize working directory state
6908 6906
6909 6907 This generates a brief summary of the working directory state,
6910 6908 including parents, branch, commit status, phase and available updates.
6911 6909
6912 6910 With the --remote option, this will check the default paths for
6913 6911 incoming and outgoing changes. This can be time-consuming.
6914 6912
6915 6913 Returns 0 on success.
6916 6914 """
6917 6915
6918 6916 opts = pycompat.byteskwargs(opts)
6919 6917 ui.pager(b'summary')
6920 6918 ctx = repo[None]
6921 6919 parents = ctx.parents()
6922 6920 pnode = parents[0].node()
6923 6921 marks = []
6924 6922
6925 6923 try:
6926 6924 ms = mergemod.mergestate.read(repo)
6927 6925 except error.UnsupportedMergeRecords as e:
6928 6926 s = b' '.join(e.recordtypes)
6929 6927 ui.warn(
6930 6928 _(b'warning: merge state has unsupported record types: %s\n') % s
6931 6929 )
6932 6930 unresolved = []
6933 6931 else:
6934 6932 unresolved = list(ms.unresolved())
6935 6933
6936 6934 for p in parents:
6937 6935 # label with log.changeset (instead of log.parent) since this
6938 6936 # shows a working directory parent *changeset*:
6939 6937 # i18n: column positioning for "hg summary"
6940 6938 ui.write(
6941 6939 _(b'parent: %d:%s ') % (p.rev(), p),
6942 6940 label=logcmdutil.changesetlabels(p),
6943 6941 )
6944 6942 ui.write(b' '.join(p.tags()), label=b'log.tag')
6945 6943 if p.bookmarks():
6946 6944 marks.extend(p.bookmarks())
6947 6945 if p.rev() == -1:
6948 6946 if not len(repo):
6949 6947 ui.write(_(b' (empty repository)'))
6950 6948 else:
6951 6949 ui.write(_(b' (no revision checked out)'))
6952 6950 if p.obsolete():
6953 6951 ui.write(_(b' (obsolete)'))
6954 6952 if p.isunstable():
6955 6953 instabilities = (
6956 6954 ui.label(instability, b'trouble.%s' % instability)
6957 6955 for instability in p.instabilities()
6958 6956 )
6959 6957 ui.write(b' (' + b', '.join(instabilities) + b')')
6960 6958 ui.write(b'\n')
6961 6959 if p.description():
6962 6960 ui.status(
6963 6961 b' ' + p.description().splitlines()[0].strip() + b'\n',
6964 6962 label=b'log.summary',
6965 6963 )
6966 6964
6967 6965 branch = ctx.branch()
6968 6966 bheads = repo.branchheads(branch)
6969 6967 # i18n: column positioning for "hg summary"
6970 6968 m = _(b'branch: %s\n') % branch
6971 6969 if branch != b'default':
6972 6970 ui.write(m, label=b'log.branch')
6973 6971 else:
6974 6972 ui.status(m, label=b'log.branch')
6975 6973
6976 6974 if marks:
6977 6975 active = repo._activebookmark
6978 6976 # i18n: column positioning for "hg summary"
6979 6977 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
6980 6978 if active is not None:
6981 6979 if active in marks:
6982 6980 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
6983 6981 marks.remove(active)
6984 6982 else:
6985 6983 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
6986 6984 for m in marks:
6987 6985 ui.write(b' ' + m, label=b'log.bookmark')
6988 6986 ui.write(b'\n', label=b'log.bookmark')
6989 6987
6990 6988 status = repo.status(unknown=True)
6991 6989
6992 6990 c = repo.dirstate.copies()
6993 6991 copied, renamed = [], []
6994 6992 for d, s in pycompat.iteritems(c):
6995 6993 if s in status.removed:
6996 6994 status.removed.remove(s)
6997 6995 renamed.append(d)
6998 6996 else:
6999 6997 copied.append(d)
7000 6998 if d in status.added:
7001 6999 status.added.remove(d)
7002 7000
7003 7001 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7004 7002
7005 7003 labels = [
7006 7004 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7007 7005 (ui.label(_(b'%d added'), b'status.added'), status.added),
7008 7006 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7009 7007 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7010 7008 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7011 7009 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7012 7010 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7013 7011 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7014 7012 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7015 7013 ]
7016 7014 t = []
7017 7015 for l, s in labels:
7018 7016 if s:
7019 7017 t.append(l % len(s))
7020 7018
7021 7019 t = b', '.join(t)
7022 7020 cleanworkdir = False
7023 7021
7024 7022 if repo.vfs.exists(b'graftstate'):
7025 7023 t += _(b' (graft in progress)')
7026 7024 if repo.vfs.exists(b'updatestate'):
7027 7025 t += _(b' (interrupted update)')
7028 7026 elif len(parents) > 1:
7029 7027 t += _(b' (merge)')
7030 7028 elif branch != parents[0].branch():
7031 7029 t += _(b' (new branch)')
7032 7030 elif parents[0].closesbranch() and pnode in repo.branchheads(
7033 7031 branch, closed=True
7034 7032 ):
7035 7033 t += _(b' (head closed)')
7036 7034 elif not (
7037 7035 status.modified
7038 7036 or status.added
7039 7037 or status.removed
7040 7038 or renamed
7041 7039 or copied
7042 7040 or subs
7043 7041 ):
7044 7042 t += _(b' (clean)')
7045 7043 cleanworkdir = True
7046 7044 elif pnode not in bheads:
7047 7045 t += _(b' (new branch head)')
7048 7046
7049 7047 if parents:
7050 7048 pendingphase = max(p.phase() for p in parents)
7051 7049 else:
7052 7050 pendingphase = phases.public
7053 7051
7054 7052 if pendingphase > phases.newcommitphase(ui):
7055 7053 t += b' (%s)' % phases.phasenames[pendingphase]
7056 7054
7057 7055 if cleanworkdir:
7058 7056 # i18n: column positioning for "hg summary"
7059 7057 ui.status(_(b'commit: %s\n') % t.strip())
7060 7058 else:
7061 7059 # i18n: column positioning for "hg summary"
7062 7060 ui.write(_(b'commit: %s\n') % t.strip())
7063 7061
7064 7062 # all ancestors of branch heads - all ancestors of parent = new csets
7065 7063 new = len(
7066 7064 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7067 7065 )
7068 7066
7069 7067 if new == 0:
7070 7068 # i18n: column positioning for "hg summary"
7071 7069 ui.status(_(b'update: (current)\n'))
7072 7070 elif pnode not in bheads:
7073 7071 # i18n: column positioning for "hg summary"
7074 7072 ui.write(_(b'update: %d new changesets (update)\n') % new)
7075 7073 else:
7076 7074 # i18n: column positioning for "hg summary"
7077 7075 ui.write(
7078 7076 _(b'update: %d new changesets, %d branch heads (merge)\n')
7079 7077 % (new, len(bheads))
7080 7078 )
7081 7079
7082 7080 t = []
7083 7081 draft = len(repo.revs(b'draft()'))
7084 7082 if draft:
7085 7083 t.append(_(b'%d draft') % draft)
7086 7084 secret = len(repo.revs(b'secret()'))
7087 7085 if secret:
7088 7086 t.append(_(b'%d secret') % secret)
7089 7087
7090 7088 if draft or secret:
7091 7089 ui.status(_(b'phases: %s\n') % b', '.join(t))
7092 7090
7093 7091 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7094 7092 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7095 7093 numtrouble = len(repo.revs(trouble + b"()"))
7096 7094 # We write all the possibilities to ease translation
7097 7095 troublemsg = {
7098 7096 b"orphan": _(b"orphan: %d changesets"),
7099 7097 b"contentdivergent": _(b"content-divergent: %d changesets"),
7100 7098 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7101 7099 }
7102 7100 if numtrouble > 0:
7103 7101 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7104 7102
7105 7103 cmdutil.summaryhooks(ui, repo)
7106 7104
7107 7105 if opts.get(b'remote'):
7108 7106 needsincoming, needsoutgoing = True, True
7109 7107 else:
7110 7108 needsincoming, needsoutgoing = False, False
7111 7109 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7112 7110 if i:
7113 7111 needsincoming = True
7114 7112 if o:
7115 7113 needsoutgoing = True
7116 7114 if not needsincoming and not needsoutgoing:
7117 7115 return
7118 7116
7119 7117 def getincoming():
7120 7118 source, branches = hg.parseurl(ui.expandpath(b'default'))
7121 7119 sbranch = branches[0]
7122 7120 try:
7123 7121 other = hg.peer(repo, {}, source)
7124 7122 except error.RepoError:
7125 7123 if opts.get(b'remote'):
7126 7124 raise
7127 7125 return source, sbranch, None, None, None
7128 7126 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7129 7127 if revs:
7130 7128 revs = [other.lookup(rev) for rev in revs]
7131 7129 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7132 7130 repo.ui.pushbuffer()
7133 7131 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7134 7132 repo.ui.popbuffer()
7135 7133 return source, sbranch, other, commoninc, commoninc[1]
7136 7134
7137 7135 if needsincoming:
7138 7136 source, sbranch, sother, commoninc, incoming = getincoming()
7139 7137 else:
7140 7138 source = sbranch = sother = commoninc = incoming = None
7141 7139
7142 7140 def getoutgoing():
7143 7141 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7144 7142 dbranch = branches[0]
7145 7143 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7146 7144 if source != dest:
7147 7145 try:
7148 7146 dother = hg.peer(repo, {}, dest)
7149 7147 except error.RepoError:
7150 7148 if opts.get(b'remote'):
7151 7149 raise
7152 7150 return dest, dbranch, None, None
7153 7151 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7154 7152 elif sother is None:
7155 7153 # there is no explicit destination peer, but source one is invalid
7156 7154 return dest, dbranch, None, None
7157 7155 else:
7158 7156 dother = sother
7159 7157 if source != dest or (sbranch is not None and sbranch != dbranch):
7160 7158 common = None
7161 7159 else:
7162 7160 common = commoninc
7163 7161 if revs:
7164 7162 revs = [repo.lookup(rev) for rev in revs]
7165 7163 repo.ui.pushbuffer()
7166 7164 outgoing = discovery.findcommonoutgoing(
7167 7165 repo, dother, onlyheads=revs, commoninc=common
7168 7166 )
7169 7167 repo.ui.popbuffer()
7170 7168 return dest, dbranch, dother, outgoing
7171 7169
7172 7170 if needsoutgoing:
7173 7171 dest, dbranch, dother, outgoing = getoutgoing()
7174 7172 else:
7175 7173 dest = dbranch = dother = outgoing = None
7176 7174
7177 7175 if opts.get(b'remote'):
7178 7176 t = []
7179 7177 if incoming:
7180 7178 t.append(_(b'1 or more incoming'))
7181 7179 o = outgoing.missing
7182 7180 if o:
7183 7181 t.append(_(b'%d outgoing') % len(o))
7184 7182 other = dother or sother
7185 7183 if b'bookmarks' in other.listkeys(b'namespaces'):
7186 7184 counts = bookmarks.summary(repo, other)
7187 7185 if counts[0] > 0:
7188 7186 t.append(_(b'%d incoming bookmarks') % counts[0])
7189 7187 if counts[1] > 0:
7190 7188 t.append(_(b'%d outgoing bookmarks') % counts[1])
7191 7189
7192 7190 if t:
7193 7191 # i18n: column positioning for "hg summary"
7194 7192 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7195 7193 else:
7196 7194 # i18n: column positioning for "hg summary"
7197 7195 ui.status(_(b'remote: (synced)\n'))
7198 7196
7199 7197 cmdutil.summaryremotehooks(
7200 7198 ui,
7201 7199 repo,
7202 7200 opts,
7203 7201 (
7204 7202 (source, sbranch, sother, commoninc),
7205 7203 (dest, dbranch, dother, outgoing),
7206 7204 ),
7207 7205 )
7208 7206
7209 7207
7210 7208 @command(
7211 7209 b'tag',
7212 7210 [
7213 7211 (b'f', b'force', None, _(b'force tag')),
7214 7212 (b'l', b'local', None, _(b'make the tag local')),
7215 7213 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7216 7214 (b'', b'remove', None, _(b'remove a tag')),
7217 7215 # -l/--local is already there, commitopts cannot be used
7218 7216 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7219 7217 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7220 7218 ]
7221 7219 + commitopts2,
7222 7220 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7223 7221 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7224 7222 )
7225 7223 def tag(ui, repo, name1, *names, **opts):
7226 7224 """add one or more tags for the current or given revision
7227 7225
7228 7226 Name a particular revision using <name>.
7229 7227
7230 7228 Tags are used to name particular revisions of the repository and are
7231 7229 very useful to compare different revisions, to go back to significant
7232 7230 earlier versions or to mark branch points as releases, etc. Changing
7233 7231 an existing tag is normally disallowed; use -f/--force to override.
7234 7232
7235 7233 If no revision is given, the parent of the working directory is
7236 7234 used.
7237 7235
7238 7236 To facilitate version control, distribution, and merging of tags,
7239 7237 they are stored as a file named ".hgtags" which is managed similarly
7240 7238 to other project files and can be hand-edited if necessary. This
7241 7239 also means that tagging creates a new commit. The file
7242 7240 ".hg/localtags" is used for local tags (not shared among
7243 7241 repositories).
7244 7242
7245 7243 Tag commits are usually made at the head of a branch. If the parent
7246 7244 of the working directory is not a branch head, :hg:`tag` aborts; use
7247 7245 -f/--force to force the tag commit to be based on a non-head
7248 7246 changeset.
7249 7247
7250 7248 See :hg:`help dates` for a list of formats valid for -d/--date.
7251 7249
7252 7250 Since tag names have priority over branch names during revision
7253 7251 lookup, using an existing branch name as a tag name is discouraged.
7254 7252
7255 7253 Returns 0 on success.
7256 7254 """
7257 7255 opts = pycompat.byteskwargs(opts)
7258 7256 with repo.wlock(), repo.lock():
7259 7257 rev_ = b"."
7260 7258 names = [t.strip() for t in (name1,) + names]
7261 7259 if len(names) != len(set(names)):
7262 7260 raise error.Abort(_(b'tag names must be unique'))
7263 7261 for n in names:
7264 7262 scmutil.checknewlabel(repo, n, b'tag')
7265 7263 if not n:
7266 7264 raise error.Abort(
7267 7265 _(b'tag names cannot consist entirely of whitespace')
7268 7266 )
7269 7267 if opts.get(b'rev') and opts.get(b'remove'):
7270 7268 raise error.Abort(_(b"--rev and --remove are incompatible"))
7271 7269 if opts.get(b'rev'):
7272 7270 rev_ = opts[b'rev']
7273 7271 message = opts.get(b'message')
7274 7272 if opts.get(b'remove'):
7275 7273 if opts.get(b'local'):
7276 7274 expectedtype = b'local'
7277 7275 else:
7278 7276 expectedtype = b'global'
7279 7277
7280 7278 for n in names:
7281 7279 if repo.tagtype(n) == b'global':
7282 7280 alltags = tagsmod.findglobaltags(ui, repo)
7283 7281 if alltags[n][0] == nullid:
7284 7282 raise error.Abort(_(b"tag '%s' is already removed") % n)
7285 7283 if not repo.tagtype(n):
7286 7284 raise error.Abort(_(b"tag '%s' does not exist") % n)
7287 7285 if repo.tagtype(n) != expectedtype:
7288 7286 if expectedtype == b'global':
7289 7287 raise error.Abort(
7290 7288 _(b"tag '%s' is not a global tag") % n
7291 7289 )
7292 7290 else:
7293 7291 raise error.Abort(_(b"tag '%s' is not a local tag") % n)
7294 7292 rev_ = b'null'
7295 7293 if not message:
7296 7294 # we don't translate commit messages
7297 7295 message = b'Removed tag %s' % b', '.join(names)
7298 7296 elif not opts.get(b'force'):
7299 7297 for n in names:
7300 7298 if n in repo.tags():
7301 7299 raise error.Abort(
7302 7300 _(b"tag '%s' already exists (use -f to force)") % n
7303 7301 )
7304 7302 if not opts.get(b'local'):
7305 7303 p1, p2 = repo.dirstate.parents()
7306 7304 if p2 != nullid:
7307 7305 raise error.Abort(_(b'uncommitted merge'))
7308 7306 bheads = repo.branchheads()
7309 7307 if not opts.get(b'force') and bheads and p1 not in bheads:
7310 7308 raise error.Abort(
7311 7309 _(
7312 7310 b'working directory is not at a branch head '
7313 7311 b'(use -f to force)'
7314 7312 )
7315 7313 )
7316 7314 node = scmutil.revsingle(repo, rev_).node()
7317 7315
7318 7316 if not message:
7319 7317 # we don't translate commit messages
7320 7318 message = b'Added tag %s for changeset %s' % (
7321 7319 b', '.join(names),
7322 7320 short(node),
7323 7321 )
7324 7322
7325 7323 date = opts.get(b'date')
7326 7324 if date:
7327 7325 date = dateutil.parsedate(date)
7328 7326
7329 7327 if opts.get(b'remove'):
7330 7328 editform = b'tag.remove'
7331 7329 else:
7332 7330 editform = b'tag.add'
7333 7331 editor = cmdutil.getcommiteditor(
7334 7332 editform=editform, **pycompat.strkwargs(opts)
7335 7333 )
7336 7334
7337 7335 # don't allow tagging the null rev
7338 7336 if (
7339 7337 not opts.get(b'remove')
7340 7338 and scmutil.revsingle(repo, rev_).rev() == nullrev
7341 7339 ):
7342 7340 raise error.Abort(_(b"cannot tag null revision"))
7343 7341
7344 7342 tagsmod.tag(
7345 7343 repo,
7346 7344 names,
7347 7345 node,
7348 7346 message,
7349 7347 opts.get(b'local'),
7350 7348 opts.get(b'user'),
7351 7349 date,
7352 7350 editor=editor,
7353 7351 )
7354 7352
7355 7353
7356 7354 @command(
7357 7355 b'tags',
7358 7356 formatteropts,
7359 7357 b'',
7360 7358 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7361 7359 intents={INTENT_READONLY},
7362 7360 )
7363 7361 def tags(ui, repo, **opts):
7364 7362 """list repository tags
7365 7363
7366 7364 This lists both regular and local tags. When the -v/--verbose
7367 7365 switch is used, a third column "local" is printed for local tags.
7368 7366 When the -q/--quiet switch is used, only the tag name is printed.
7369 7367
7370 7368 .. container:: verbose
7371 7369
7372 7370 Template:
7373 7371
7374 7372 The following keywords are supported in addition to the common template
7375 7373 keywords and functions such as ``{tag}``. See also
7376 7374 :hg:`help templates`.
7377 7375
7378 7376 :type: String. ``local`` for local tags.
7379 7377
7380 7378 Returns 0 on success.
7381 7379 """
7382 7380
7383 7381 opts = pycompat.byteskwargs(opts)
7384 7382 ui.pager(b'tags')
7385 7383 fm = ui.formatter(b'tags', opts)
7386 7384 hexfunc = fm.hexfunc
7387 7385
7388 7386 for t, n in reversed(repo.tagslist()):
7389 7387 hn = hexfunc(n)
7390 7388 label = b'tags.normal'
7391 7389 tagtype = b''
7392 7390 if repo.tagtype(t) == b'local':
7393 7391 label = b'tags.local'
7394 7392 tagtype = b'local'
7395 7393
7396 7394 fm.startitem()
7397 7395 fm.context(repo=repo)
7398 7396 fm.write(b'tag', b'%s', t, label=label)
7399 7397 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7400 7398 fm.condwrite(
7401 7399 not ui.quiet,
7402 7400 b'rev node',
7403 7401 fmt,
7404 7402 repo.changelog.rev(n),
7405 7403 hn,
7406 7404 label=label,
7407 7405 )
7408 7406 fm.condwrite(
7409 7407 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7410 7408 )
7411 7409 fm.plain(b'\n')
7412 7410 fm.end()
7413 7411
7414 7412
7415 7413 @command(
7416 7414 b'tip',
7417 7415 [
7418 7416 (b'p', b'patch', None, _(b'show patch')),
7419 7417 (b'g', b'git', None, _(b'use git extended diff format')),
7420 7418 ]
7421 7419 + templateopts,
7422 7420 _(b'[-p] [-g]'),
7423 7421 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7424 7422 )
7425 7423 def tip(ui, repo, **opts):
7426 7424 """show the tip revision (DEPRECATED)
7427 7425
7428 7426 The tip revision (usually just called the tip) is the changeset
7429 7427 most recently added to the repository (and therefore the most
7430 7428 recently changed head).
7431 7429
7432 7430 If you have just made a commit, that commit will be the tip. If
7433 7431 you have just pulled changes from another repository, the tip of
7434 7432 that repository becomes the current tip. The "tip" tag is special
7435 7433 and cannot be renamed or assigned to a different changeset.
7436 7434
7437 7435 This command is deprecated, please use :hg:`heads` instead.
7438 7436
7439 7437 Returns 0 on success.
7440 7438 """
7441 7439 opts = pycompat.byteskwargs(opts)
7442 7440 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7443 7441 displayer.show(repo[b'tip'])
7444 7442 displayer.close()
7445 7443
7446 7444
7447 7445 @command(
7448 7446 b'unbundle',
7449 7447 [
7450 7448 (
7451 7449 b'u',
7452 7450 b'update',
7453 7451 None,
7454 7452 _(b'update to new branch head if changesets were unbundled'),
7455 7453 )
7456 7454 ],
7457 7455 _(b'[-u] FILE...'),
7458 7456 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7459 7457 )
7460 7458 def unbundle(ui, repo, fname1, *fnames, **opts):
7461 7459 """apply one or more bundle files
7462 7460
7463 7461 Apply one or more bundle files generated by :hg:`bundle`.
7464 7462
7465 7463 Returns 0 on success, 1 if an update has unresolved files.
7466 7464 """
7467 7465 fnames = (fname1,) + fnames
7468 7466
7469 7467 with repo.lock():
7470 7468 for fname in fnames:
7471 7469 f = hg.openpath(ui, fname)
7472 7470 gen = exchange.readbundle(ui, f, fname)
7473 7471 if isinstance(gen, streamclone.streamcloneapplier):
7474 7472 raise error.Abort(
7475 7473 _(
7476 7474 b'packed bundles cannot be applied with '
7477 7475 b'"hg unbundle"'
7478 7476 ),
7479 7477 hint=_(b'use "hg debugapplystreamclonebundle"'),
7480 7478 )
7481 7479 url = b'bundle:' + fname
7482 7480 try:
7483 7481 txnname = b'unbundle'
7484 7482 if not isinstance(gen, bundle2.unbundle20):
7485 7483 txnname = b'unbundle\n%s' % util.hidepassword(url)
7486 7484 with repo.transaction(txnname) as tr:
7487 7485 op = bundle2.applybundle(
7488 7486 repo, gen, tr, source=b'unbundle', url=url
7489 7487 )
7490 7488 except error.BundleUnknownFeatureError as exc:
7491 7489 raise error.Abort(
7492 7490 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7493 7491 hint=_(
7494 7492 b"see https://mercurial-scm.org/"
7495 7493 b"wiki/BundleFeature for more "
7496 7494 b"information"
7497 7495 ),
7498 7496 )
7499 7497 modheads = bundle2.combinechangegroupresults(op)
7500 7498
7501 7499 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7502 7500
7503 7501
7504 7502 @command(
7505 7503 b'unshelve',
7506 7504 [
7507 7505 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7508 7506 (
7509 7507 b'c',
7510 7508 b'continue',
7511 7509 None,
7512 7510 _(b'continue an incomplete unshelve operation'),
7513 7511 ),
7514 7512 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7515 7513 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7516 7514 (
7517 7515 b'n',
7518 7516 b'name',
7519 7517 b'',
7520 7518 _(b'restore shelved change with given name'),
7521 7519 _(b'NAME'),
7522 7520 ),
7523 7521 (b't', b'tool', b'', _(b'specify merge tool')),
7524 7522 (
7525 7523 b'',
7526 7524 b'date',
7527 7525 b'',
7528 7526 _(b'set date for temporary commits (DEPRECATED)'),
7529 7527 _(b'DATE'),
7530 7528 ),
7531 7529 ],
7532 7530 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7533 7531 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7534 7532 )
7535 7533 def unshelve(ui, repo, *shelved, **opts):
7536 7534 """restore a shelved change to the working directory
7537 7535
7538 7536 This command accepts an optional name of a shelved change to
7539 7537 restore. If none is given, the most recent shelved change is used.
7540 7538
7541 7539 If a shelved change is applied successfully, the bundle that
7542 7540 contains the shelved changes is moved to a backup location
7543 7541 (.hg/shelve-backup).
7544 7542
7545 7543 Since you can restore a shelved change on top of an arbitrary
7546 7544 commit, it is possible that unshelving will result in a conflict
7547 7545 between your changes and the commits you are unshelving onto. If
7548 7546 this occurs, you must resolve the conflict, then use
7549 7547 ``--continue`` to complete the unshelve operation. (The bundle
7550 7548 will not be moved until you successfully complete the unshelve.)
7551 7549
7552 7550 (Alternatively, you can use ``--abort`` to abandon an unshelve
7553 7551 that causes a conflict. This reverts the unshelved changes, and
7554 7552 leaves the bundle in place.)
7555 7553
7556 7554 If bare shelved change (without interactive, include and exclude
7557 7555 option) was done on newly created branch it would restore branch
7558 7556 information to the working directory.
7559 7557
7560 7558 After a successful unshelve, the shelved changes are stored in a
7561 7559 backup directory. Only the N most recent backups are kept. N
7562 7560 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7563 7561 configuration option.
7564 7562
7565 7563 .. container:: verbose
7566 7564
7567 7565 Timestamp in seconds is used to decide order of backups. More
7568 7566 than ``maxbackups`` backups are kept, if same timestamp
7569 7567 prevents from deciding exact order of them, for safety.
7570 7568
7571 7569 Selected changes can be unshelved with ``--interactive`` flag.
7572 7570 The working directory is updated with the selected changes, and
7573 7571 only the unselected changes remain shelved.
7574 7572 Note: The whole shelve is applied to working directory first before
7575 7573 running interactively. So, this will bring up all the conflicts between
7576 7574 working directory and the shelve, irrespective of which changes will be
7577 7575 unshelved.
7578 7576 """
7579 7577 with repo.wlock():
7580 7578 return shelvemod.dounshelve(ui, repo, *shelved, **opts)
7581 7579
7582 7580
7583 7581 statemod.addunfinished(
7584 7582 b'unshelve',
7585 7583 fname=b'shelvedstate',
7586 7584 continueflag=True,
7587 7585 abortfunc=shelvemod.hgabortunshelve,
7588 7586 continuefunc=shelvemod.hgcontinueunshelve,
7589 7587 cmdmsg=_(b'unshelve already in progress'),
7590 7588 )
7591 7589
7592 7590
7593 7591 @command(
7594 7592 b'update|up|checkout|co',
7595 7593 [
7596 7594 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7597 7595 (b'c', b'check', None, _(b'require clean working directory')),
7598 7596 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7599 7597 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7600 7598 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7601 7599 ]
7602 7600 + mergetoolopts,
7603 7601 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7604 7602 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7605 7603 helpbasic=True,
7606 7604 )
7607 7605 def update(ui, repo, node=None, **opts):
7608 7606 """update working directory (or switch revisions)
7609 7607
7610 7608 Update the repository's working directory to the specified
7611 7609 changeset. If no changeset is specified, update to the tip of the
7612 7610 current named branch and move the active bookmark (see :hg:`help
7613 7611 bookmarks`).
7614 7612
7615 7613 Update sets the working directory's parent revision to the specified
7616 7614 changeset (see :hg:`help parents`).
7617 7615
7618 7616 If the changeset is not a descendant or ancestor of the working
7619 7617 directory's parent and there are uncommitted changes, the update is
7620 7618 aborted. With the -c/--check option, the working directory is checked
7621 7619 for uncommitted changes; if none are found, the working directory is
7622 7620 updated to the specified changeset.
7623 7621
7624 7622 .. container:: verbose
7625 7623
7626 7624 The -C/--clean, -c/--check, and -m/--merge options control what
7627 7625 happens if the working directory contains uncommitted changes.
7628 7626 At most of one of them can be specified.
7629 7627
7630 7628 1. If no option is specified, and if
7631 7629 the requested changeset is an ancestor or descendant of
7632 7630 the working directory's parent, the uncommitted changes
7633 7631 are merged into the requested changeset and the merged
7634 7632 result is left uncommitted. If the requested changeset is
7635 7633 not an ancestor or descendant (that is, it is on another
7636 7634 branch), the update is aborted and the uncommitted changes
7637 7635 are preserved.
7638 7636
7639 7637 2. With the -m/--merge option, the update is allowed even if the
7640 7638 requested changeset is not an ancestor or descendant of
7641 7639 the working directory's parent.
7642 7640
7643 7641 3. With the -c/--check option, the update is aborted and the
7644 7642 uncommitted changes are preserved.
7645 7643
7646 7644 4. With the -C/--clean option, uncommitted changes are discarded and
7647 7645 the working directory is updated to the requested changeset.
7648 7646
7649 7647 To cancel an uncommitted merge (and lose your changes), use
7650 7648 :hg:`merge --abort`.
7651 7649
7652 7650 Use null as the changeset to remove the working directory (like
7653 7651 :hg:`clone -U`).
7654 7652
7655 7653 If you want to revert just one file to an older revision, use
7656 7654 :hg:`revert [-r REV] NAME`.
7657 7655
7658 7656 See :hg:`help dates` for a list of formats valid for -d/--date.
7659 7657
7660 7658 Returns 0 on success, 1 if there are unresolved files.
7661 7659 """
7662 7660 rev = opts.get('rev')
7663 7661 date = opts.get('date')
7664 7662 clean = opts.get('clean')
7665 7663 check = opts.get('check')
7666 7664 merge = opts.get('merge')
7667 7665 if rev and node:
7668 7666 raise error.Abort(_(b"please specify just one revision"))
7669 7667
7670 7668 if ui.configbool(b'commands', b'update.requiredest'):
7671 7669 if not node and not rev and not date:
7672 7670 raise error.Abort(
7673 7671 _(b'you must specify a destination'),
7674 7672 hint=_(b'for example: hg update ".::"'),
7675 7673 )
7676 7674
7677 7675 if rev is None or rev == b'':
7678 7676 rev = node
7679 7677
7680 7678 if date and rev is not None:
7681 7679 raise error.Abort(_(b"you can't specify a revision and a date"))
7682 7680
7683 7681 if len([x for x in (clean, check, merge) if x]) > 1:
7684 7682 raise error.Abort(
7685 7683 _(
7686 7684 b"can only specify one of -C/--clean, -c/--check, "
7687 7685 b"or -m/--merge"
7688 7686 )
7689 7687 )
7690 7688
7691 7689 updatecheck = None
7692 7690 if check:
7693 7691 updatecheck = b'abort'
7694 7692 elif merge:
7695 7693 updatecheck = b'none'
7696 7694
7697 7695 with repo.wlock():
7698 7696 cmdutil.clearunfinished(repo)
7699 7697 if date:
7700 7698 rev = cmdutil.finddate(ui, repo, date)
7701 7699
7702 7700 # if we defined a bookmark, we have to remember the original name
7703 7701 brev = rev
7704 7702 if rev:
7705 7703 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7706 7704 ctx = scmutil.revsingle(repo, rev, default=None)
7707 7705 rev = ctx.rev()
7708 7706 hidden = ctx.hidden()
7709 7707 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7710 7708 with ui.configoverride(overrides, b'update'):
7711 7709 ret = hg.updatetotally(
7712 7710 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7713 7711 )
7714 7712 if hidden:
7715 7713 ctxstr = ctx.hex()[:12]
7716 7714 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7717 7715
7718 7716 if ctx.obsolete():
7719 7717 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7720 7718 ui.warn(b"(%s)\n" % obsfatemsg)
7721 7719 return ret
7722 7720
7723 7721
7724 7722 @command(
7725 7723 b'verify',
7726 7724 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7727 7725 helpcategory=command.CATEGORY_MAINTENANCE,
7728 7726 )
7729 7727 def verify(ui, repo, **opts):
7730 7728 """verify the integrity of the repository
7731 7729
7732 7730 Verify the integrity of the current repository.
7733 7731
7734 7732 This will perform an extensive check of the repository's
7735 7733 integrity, validating the hashes and checksums of each entry in
7736 7734 the changelog, manifest, and tracked files, as well as the
7737 7735 integrity of their crosslinks and indices.
7738 7736
7739 7737 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7740 7738 for more information about recovery from corruption of the
7741 7739 repository.
7742 7740
7743 7741 Returns 0 on success, 1 if errors are encountered.
7744 7742 """
7745 7743 opts = pycompat.byteskwargs(opts)
7746 7744
7747 7745 level = None
7748 7746 if opts[b'full']:
7749 7747 level = verifymod.VERIFY_FULL
7750 7748 return hg.verify(repo, level)
7751 7749
7752 7750
7753 7751 @command(
7754 7752 b'version',
7755 7753 [] + formatteropts,
7756 7754 helpcategory=command.CATEGORY_HELP,
7757 7755 norepo=True,
7758 7756 intents={INTENT_READONLY},
7759 7757 )
7760 7758 def version_(ui, **opts):
7761 7759 """output version and copyright information
7762 7760
7763 7761 .. container:: verbose
7764 7762
7765 7763 Template:
7766 7764
7767 7765 The following keywords are supported. See also :hg:`help templates`.
7768 7766
7769 7767 :extensions: List of extensions.
7770 7768 :ver: String. Version number.
7771 7769
7772 7770 And each entry of ``{extensions}`` provides the following sub-keywords
7773 7771 in addition to ``{ver}``.
7774 7772
7775 7773 :bundled: Boolean. True if included in the release.
7776 7774 :name: String. Extension name.
7777 7775 """
7778 7776 opts = pycompat.byteskwargs(opts)
7779 7777 if ui.verbose:
7780 7778 ui.pager(b'version')
7781 7779 fm = ui.formatter(b"version", opts)
7782 7780 fm.startitem()
7783 7781 fm.write(
7784 7782 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7785 7783 )
7786 7784 license = _(
7787 7785 b"(see https://mercurial-scm.org for more information)\n"
7788 7786 b"\nCopyright (C) 2005-2019 Matt Mackall and others\n"
7789 7787 b"This is free software; see the source for copying conditions. "
7790 7788 b"There is NO\nwarranty; "
7791 7789 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7792 7790 )
7793 7791 if not ui.quiet:
7794 7792 fm.plain(license)
7795 7793
7796 7794 if ui.verbose:
7797 7795 fm.plain(_(b"\nEnabled extensions:\n\n"))
7798 7796 # format names and versions into columns
7799 7797 names = []
7800 7798 vers = []
7801 7799 isinternals = []
7802 7800 for name, module in extensions.extensions():
7803 7801 names.append(name)
7804 7802 vers.append(extensions.moduleversion(module) or None)
7805 7803 isinternals.append(extensions.ismoduleinternal(module))
7806 7804 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7807 7805 if names:
7808 7806 namefmt = b" %%-%ds " % max(len(n) for n in names)
7809 7807 places = [_(b"external"), _(b"internal")]
7810 7808 for n, v, p in zip(names, vers, isinternals):
7811 7809 fn.startitem()
7812 7810 fn.condwrite(ui.verbose, b"name", namefmt, n)
7813 7811 if ui.verbose:
7814 7812 fn.plain(b"%s " % places[p])
7815 7813 fn.data(bundled=p)
7816 7814 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7817 7815 if ui.verbose:
7818 7816 fn.plain(b"\n")
7819 7817 fn.end()
7820 7818 fm.end()
7821 7819
7822 7820
7823 7821 def loadcmdtable(ui, name, cmdtable):
7824 7822 """Load command functions from specified cmdtable
7825 7823 """
7826 7824 overrides = [cmd for cmd in cmdtable if cmd in table]
7827 7825 if overrides:
7828 7826 ui.warn(
7829 7827 _(b"extension '%s' overrides commands: %s\n")
7830 7828 % (name, b" ".join(overrides))
7831 7829 )
7832 7830 table.update(cmdtable)
@@ -1,1257 +1,1257 b''
1 1
2 2 $ hg init repo
3 3 $ cd repo
4 4
5 5 $ cat > $TESTTMP/hook.sh <<'EOF'
6 6 > echo "test-hook-bookmark: $HG_BOOKMARK: $HG_OLDNODE -> $HG_NODE"
7 7 > EOF
8 8 $ TESTHOOK="hooks.txnclose-bookmark.test=sh $TESTTMP/hook.sh"
9 9
10 10 no bookmarks
11 11
12 12 $ hg bookmarks
13 13 no bookmarks set
14 14
15 15 $ hg bookmarks -Tjson
16 16 [
17 17 ]
18 18
19 19 bookmark rev -1
20 20
21 21 $ hg bookmark X --config "$TESTHOOK"
22 22 test-hook-bookmark: X: -> 0000000000000000000000000000000000000000
23 23
24 24 list bookmarks
25 25
26 26 $ hg bookmarks
27 27 * X -1:000000000000
28 28
29 29 list bookmarks with color
30 30
31 31 $ hg --config extensions.color= --config color.mode=ansi \
32 32 > bookmarks --color=always
33 33 \x1b[0;32m * \x1b[0m\x1b[0;32mX\x1b[0m\x1b[0;32m -1:000000000000\x1b[0m (esc)
34 34
35 35 $ echo a > a
36 36 $ hg add a
37 37 $ hg commit -m 0 --config "$TESTHOOK"
38 38 test-hook-bookmark: X: 0000000000000000000000000000000000000000 -> f7b1eb17ad24730a1651fccd46c43826d1bbc2ac
39 39
40 40 bookmark X moved to rev 0
41 41
42 42 $ hg bookmarks
43 43 * X 0:f7b1eb17ad24
44 44
45 45 look up bookmark
46 46
47 47 $ hg log -r X
48 48 changeset: 0:f7b1eb17ad24
49 49 bookmark: X
50 50 tag: tip
51 51 user: test
52 52 date: Thu Jan 01 00:00:00 1970 +0000
53 53 summary: 0
54 54
55 55
56 56 second bookmark for rev 0, command should work even with ui.strict on
57 57
58 58 $ hg --config ui.strict=1 bookmark X2 --config "$TESTHOOK"
59 59 test-hook-bookmark: X2: -> f7b1eb17ad24730a1651fccd46c43826d1bbc2ac
60 60
61 61 bookmark rev -1 again
62 62
63 63 $ hg bookmark -r null Y
64 64
65 65 list bookmarks
66 66
67 67 $ hg bookmarks
68 68 X 0:f7b1eb17ad24
69 69 * X2 0:f7b1eb17ad24
70 70 Y -1:000000000000
71 71 $ hg bookmarks -l
72 72 X 0:f7b1eb17ad24
73 73 * X2 0:f7b1eb17ad24
74 74 Y -1:000000000000
75 75 $ hg bookmarks -l X Y
76 76 X 0:f7b1eb17ad24
77 77 Y -1:000000000000
78 78 $ hg bookmarks -l .
79 79 * X2 0:f7b1eb17ad24
80 80 $ hg bookmarks -l X A Y
81 81 abort: bookmark 'A' does not exist
82 82 [255]
83 83 $ hg bookmarks -l -r0
84 abort: --rev is incompatible with --list
84 abort: cannot specify both --list and --rev
85 85 [255]
86 86 $ hg bookmarks -l --inactive
87 87 abort: --inactive is incompatible with --list
88 88 [255]
89 89
90 90 $ hg log -T '{bookmarks % "{rev} {bookmark}\n"}'
91 91 0 X
92 92 0 X2
93 93
94 94 $ echo b > b
95 95 $ hg add b
96 96 $ hg commit -m 1 --config "$TESTHOOK"
97 97 test-hook-bookmark: X2: f7b1eb17ad24730a1651fccd46c43826d1bbc2ac -> 925d80f479bb026b0fb3deb27503780b13f74123
98 98
99 99 $ hg bookmarks -T '{rev}:{node|shortest} {bookmark} {desc|firstline}\n'
100 100 0:f7b1 X 0
101 101 1:925d X2 1
102 102 -1:0000 Y
103 103
104 104 $ hg bookmarks -Tjson
105 105 [
106 106 {
107 107 "active": false,
108 108 "bookmark": "X",
109 109 "node": "f7b1eb17ad24730a1651fccd46c43826d1bbc2ac",
110 110 "rev": 0
111 111 },
112 112 {
113 113 "active": true,
114 114 "bookmark": "X2",
115 115 "node": "925d80f479bb026b0fb3deb27503780b13f74123",
116 116 "rev": 1
117 117 },
118 118 {
119 119 "active": false,
120 120 "bookmark": "Y",
121 121 "node": "0000000000000000000000000000000000000000",
122 122 "rev": -1
123 123 }
124 124 ]
125 125
126 126 bookmarks revset
127 127
128 128 $ hg log -r 'bookmark()'
129 129 changeset: 0:f7b1eb17ad24
130 130 bookmark: X
131 131 user: test
132 132 date: Thu Jan 01 00:00:00 1970 +0000
133 133 summary: 0
134 134
135 135 changeset: 1:925d80f479bb
136 136 bookmark: X2
137 137 tag: tip
138 138 user: test
139 139 date: Thu Jan 01 00:00:00 1970 +0000
140 140 summary: 1
141 141
142 142 $ hg log -r 'bookmark(Y)'
143 143 $ hg log -r 'bookmark(X2)'
144 144 changeset: 1:925d80f479bb
145 145 bookmark: X2
146 146 tag: tip
147 147 user: test
148 148 date: Thu Jan 01 00:00:00 1970 +0000
149 149 summary: 1
150 150
151 151 $ hg log -r 'bookmark("re:X")'
152 152 changeset: 0:f7b1eb17ad24
153 153 bookmark: X
154 154 user: test
155 155 date: Thu Jan 01 00:00:00 1970 +0000
156 156 summary: 0
157 157
158 158 changeset: 1:925d80f479bb
159 159 bookmark: X2
160 160 tag: tip
161 161 user: test
162 162 date: Thu Jan 01 00:00:00 1970 +0000
163 163 summary: 1
164 164
165 165 $ hg log -r 'bookmark("literal:X")'
166 166 changeset: 0:f7b1eb17ad24
167 167 bookmark: X
168 168 user: test
169 169 date: Thu Jan 01 00:00:00 1970 +0000
170 170 summary: 0
171 171
172 172
173 173 "." is expanded to the active bookmark:
174 174
175 175 $ hg log -r 'bookmark(.)'
176 176 changeset: 1:925d80f479bb
177 177 bookmark: X2
178 178 tag: tip
179 179 user: test
180 180 date: Thu Jan 01 00:00:00 1970 +0000
181 181 summary: 1
182 182
183 183
184 184 but "literal:." is not since "." seems not a literal bookmark:
185 185
186 186 $ hg log -r 'bookmark("literal:.")'
187 187 abort: bookmark '.' does not exist!
188 188 [255]
189 189
190 190 "." should fail if there's no active bookmark:
191 191
192 192 $ hg bookmark --inactive
193 193 $ hg log -r 'bookmark(.)'
194 194 abort: no active bookmark!
195 195 [255]
196 196 $ hg log -r 'present(bookmark(.))'
197 197
198 198 $ hg log -r 'bookmark(unknown)'
199 199 abort: bookmark 'unknown' does not exist!
200 200 [255]
201 201 $ hg log -r 'bookmark("literal:unknown")'
202 202 abort: bookmark 'unknown' does not exist!
203 203 [255]
204 204 $ hg log -r 'bookmark("re:unknown")'
205 205 $ hg log -r 'present(bookmark("literal:unknown"))'
206 206 $ hg log -r 'present(bookmark("re:unknown"))'
207 207
208 208 $ hg help revsets | grep 'bookmark('
209 209 "bookmark([name])"
210 210
211 211 reactivate "X2"
212 212
213 213 $ hg update X2
214 214 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
215 215 (activating bookmark X2)
216 216
217 217 bookmarks X and X2 moved to rev 1, Y at rev -1
218 218
219 219 $ hg bookmarks
220 220 X 0:f7b1eb17ad24
221 221 * X2 1:925d80f479bb
222 222 Y -1:000000000000
223 223
224 224 bookmark rev 0 again
225 225
226 226 $ hg bookmark -r 0 Z
227 227
228 228 $ hg update X
229 229 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
230 230 (activating bookmark X)
231 231 $ echo c > c
232 232 $ hg add c
233 233 $ hg commit -m 2
234 234 created new head
235 235
236 236 bookmarks X moved to rev 2, Y at rev -1, Z at rev 0
237 237
238 238 $ hg bookmarks
239 239 * X 2:db815d6d32e6
240 240 X2 1:925d80f479bb
241 241 Y -1:000000000000
242 242 Z 0:f7b1eb17ad24
243 243
244 244 rename nonexistent bookmark
245 245
246 246 $ hg bookmark -m A B
247 247 abort: bookmark 'A' does not exist
248 248 [255]
249 249
250 250 rename to existent bookmark
251 251
252 252 $ hg bookmark -m X Y
253 253 abort: bookmark 'Y' already exists (use -f to force)
254 254 [255]
255 255
256 256 force rename to existent bookmark
257 257
258 258 $ hg bookmark -f -m X Y
259 259
260 260 rename bookmark using .
261 261
262 262 $ hg book rename-me
263 263 $ hg book -m . renamed --config "$TESTHOOK"
264 264 test-hook-bookmark: rename-me: db815d6d32e69058eadefc8cffbad37675707975 ->
265 265 test-hook-bookmark: renamed: -> db815d6d32e69058eadefc8cffbad37675707975
266 266 $ hg bookmark
267 267 X2 1:925d80f479bb
268 268 Y 2:db815d6d32e6
269 269 Z 0:f7b1eb17ad24
270 270 * renamed 2:db815d6d32e6
271 271 $ hg up -q Y
272 272 $ hg book -d renamed --config "$TESTHOOK"
273 273 test-hook-bookmark: renamed: db815d6d32e69058eadefc8cffbad37675707975 ->
274 274
275 275 rename bookmark using . with no active bookmark
276 276
277 277 $ hg book rename-me
278 278 $ hg book -i rename-me
279 279 $ hg book -m . renamed
280 280 abort: no active bookmark!
281 281 [255]
282 282 $ hg up -q Y
283 283 $ hg book -d rename-me
284 284
285 285 delete bookmark using .
286 286
287 287 $ hg book delete-me
288 288 $ hg book -d .
289 289 $ hg bookmark
290 290 X2 1:925d80f479bb
291 291 Y 2:db815d6d32e6
292 292 Z 0:f7b1eb17ad24
293 293 $ hg up -q Y
294 294
295 295 delete bookmark using . with no active bookmark
296 296
297 297 $ hg book delete-me
298 298 $ hg book -i delete-me
299 299 $ hg book -d .
300 300 abort: no active bookmark!
301 301 [255]
302 302 $ hg up -q Y
303 303 $ hg book -d delete-me
304 304
305 305 list bookmarks
306 306
307 307 $ hg bookmark
308 308 X2 1:925d80f479bb
309 309 * Y 2:db815d6d32e6
310 310 Z 0:f7b1eb17ad24
311 311
312 312 bookmarks from a revset
313 313 $ hg bookmark -r '.^1' REVSET
314 314 $ hg bookmark -r ':tip' TIP
315 315 $ hg up -q TIP
316 316 $ hg bookmarks
317 317 REVSET 0:f7b1eb17ad24
318 318 * TIP 2:db815d6d32e6
319 319 X2 1:925d80f479bb
320 320 Y 2:db815d6d32e6
321 321 Z 0:f7b1eb17ad24
322 322
323 323 $ hg bookmark -d REVSET
324 324 $ hg bookmark -d TIP
325 325
326 326 rename without new name or multiple names
327 327
328 328 $ hg bookmark -m Y
329 329 abort: new bookmark name required
330 330 [255]
331 331 $ hg bookmark -m Y Y2 Y3
332 332 abort: only one new bookmark name allowed
333 333 [255]
334 334
335 335 delete without name
336 336
337 337 $ hg bookmark -d
338 338 abort: bookmark name required
339 339 [255]
340 340
341 341 delete nonexistent bookmark
342 342
343 343 $ hg bookmark -d A
344 344 abort: bookmark 'A' does not exist
345 345 [255]
346 346
347 347 delete with --inactive
348 348
349 349 $ hg bookmark -d --inactive Y
350 350 abort: --inactive is incompatible with --delete
351 351 [255]
352 352
353 353 bookmark name with spaces should be stripped
354 354
355 355 $ hg bookmark ' x y '
356 356
357 357 list bookmarks
358 358
359 359 $ hg bookmarks
360 360 X2 1:925d80f479bb
361 361 Y 2:db815d6d32e6
362 362 Z 0:f7b1eb17ad24
363 363 * x y 2:db815d6d32e6
364 364 $ hg log -T '{bookmarks % "{rev} {bookmark}\n"}'
365 365 2 Y
366 366 2 x y
367 367 1 X2
368 368 0 Z
369 369
370 370 look up stripped bookmark name
371 371
372 372 $ hg log -r '"x y"'
373 373 changeset: 2:db815d6d32e6
374 374 bookmark: Y
375 375 bookmark: x y
376 376 tag: tip
377 377 parent: 0:f7b1eb17ad24
378 378 user: test
379 379 date: Thu Jan 01 00:00:00 1970 +0000
380 380 summary: 2
381 381
382 382
383 383 reject bookmark name with newline
384 384
385 385 $ hg bookmark '
386 386 > '
387 387 abort: bookmark names cannot consist entirely of whitespace
388 388 [255]
389 389
390 390 $ hg bookmark -m Z '
391 391 > '
392 392 abort: bookmark names cannot consist entirely of whitespace
393 393 [255]
394 394
395 395 bookmark with reserved name
396 396
397 397 $ hg bookmark tip
398 398 abort: the name 'tip' is reserved
399 399 [255]
400 400
401 401 $ hg bookmark .
402 402 abort: the name '.' is reserved
403 403 [255]
404 404
405 405 $ hg bookmark null
406 406 abort: the name 'null' is reserved
407 407 [255]
408 408
409 409
410 410 bookmark with existing name
411 411
412 412 $ hg bookmark X2
413 413 abort: bookmark 'X2' already exists (use -f to force)
414 414 [255]
415 415
416 416 $ hg bookmark -m Y Z
417 417 abort: bookmark 'Z' already exists (use -f to force)
418 418 [255]
419 419
420 420 bookmark with name of branch
421 421
422 422 $ hg bookmark default
423 423 abort: a bookmark cannot have the name of an existing branch
424 424 [255]
425 425
426 426 $ hg bookmark -m Y default
427 427 abort: a bookmark cannot have the name of an existing branch
428 428 [255]
429 429
430 430 bookmark with integer name
431 431
432 432 $ hg bookmark 10
433 433 abort: cannot use an integer as a name
434 434 [255]
435 435
436 436 bookmark with a name that matches a node id
437 437 $ hg bookmark 925d80f479bb db815d6d32e6 --config "$TESTHOOK"
438 438 bookmark 925d80f479bb matches a changeset hash
439 439 (did you leave a -r out of an 'hg bookmark' command?)
440 440 bookmark db815d6d32e6 matches a changeset hash
441 441 (did you leave a -r out of an 'hg bookmark' command?)
442 442 test-hook-bookmark: 925d80f479bb: -> db815d6d32e69058eadefc8cffbad37675707975
443 443 test-hook-bookmark: db815d6d32e6: -> db815d6d32e69058eadefc8cffbad37675707975
444 444 $ hg bookmark -d 925d80f479bb
445 445 $ hg bookmark -d db815d6d32e6
446 446
447 447 $ cd ..
448 448
449 449 bookmark with a name that matches an ambiguous node id
450 450
451 451 $ hg init ambiguous
452 452 $ cd ambiguous
453 453 $ echo 0 > a
454 454 $ hg ci -qAm 0
455 455 $ for i in 1057 2857 4025; do
456 456 > hg up -q 0
457 457 > echo $i > a
458 458 > hg ci -qm $i
459 459 > done
460 460 $ hg up -q null
461 461 $ hg log -r0: -T '{rev}:{node}\n'
462 462 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
463 463 1:c56256a09cd28e5764f32e8e2810d0f01e2e357a
464 464 2:c5623987d205cd6d9d8389bfc40fff9dbb670b48
465 465 3:c562ddd9c94164376c20b86b0b4991636a3bf84f
466 466
467 467 $ hg bookmark -r0 c562
468 468 $ hg bookmarks
469 469 c562 0:b4e73ffab476
470 470
471 471 $ cd ..
472 472
473 473 incompatible options
474 474
475 475 $ cd repo
476 476
477 477 $ hg bookmark -m Y -d Z
478 478 abort: cannot specify both --delete and --rename
479 479 [255]
480 480
481 481 $ hg bookmark -r 1 -d Z
482 abort: --rev is incompatible with --delete
482 abort: cannot specify both --delete and --rev
483 483 [255]
484 484
485 485 $ hg bookmark -r 1 -m Z Y
486 abort: --rev is incompatible with --rename
486 abort: cannot specify both --rename and --rev
487 487 [255]
488 488
489 489 force bookmark with existing name
490 490
491 491 $ hg bookmark -f X2 --config "$TESTHOOK"
492 492 test-hook-bookmark: X2: 925d80f479bb026b0fb3deb27503780b13f74123 -> db815d6d32e69058eadefc8cffbad37675707975
493 493
494 494 force bookmark back to where it was, should deactivate it
495 495
496 496 $ hg bookmark -fr1 X2
497 497 $ hg bookmarks
498 498 X2 1:925d80f479bb
499 499 Y 2:db815d6d32e6
500 500 Z 0:f7b1eb17ad24
501 501 x y 2:db815d6d32e6
502 502
503 503 forward bookmark to descendant without --force
504 504
505 505 $ hg bookmark Z
506 506 moving bookmark 'Z' forward from f7b1eb17ad24
507 507
508 508 list bookmarks
509 509
510 510 $ hg bookmark
511 511 X2 1:925d80f479bb
512 512 Y 2:db815d6d32e6
513 513 * Z 2:db815d6d32e6
514 514 x y 2:db815d6d32e6
515 515 $ hg log -T '{bookmarks % "{rev} {bookmark}\n"}'
516 516 2 Y
517 517 2 Z
518 518 2 x y
519 519 1 X2
520 520
521 521 revision but no bookmark name
522 522
523 523 $ hg bookmark -r .
524 524 abort: bookmark name required
525 525 [255]
526 526
527 527 bookmark name with whitespace only
528 528
529 529 $ hg bookmark ' '
530 530 abort: bookmark names cannot consist entirely of whitespace
531 531 [255]
532 532
533 533 $ hg bookmark -m Y ' '
534 534 abort: bookmark names cannot consist entirely of whitespace
535 535 [255]
536 536
537 537 invalid bookmark
538 538
539 539 $ hg bookmark 'foo:bar'
540 540 abort: ':' cannot be used in a name
541 541 [255]
542 542
543 543 $ hg bookmark 'foo
544 544 > bar'
545 545 abort: '\n' cannot be used in a name
546 546 [255]
547 547
548 548 the bookmark extension should be ignored now that it is part of core
549 549
550 550 $ echo "[extensions]" >> $HGRCPATH
551 551 $ echo "bookmarks=" >> $HGRCPATH
552 552 $ hg bookmarks
553 553 X2 1:925d80f479bb
554 554 Y 2:db815d6d32e6
555 555 * Z 2:db815d6d32e6
556 556 x y 2:db815d6d32e6
557 557
558 558 test summary
559 559
560 560 $ hg summary
561 561 parent: 2:db815d6d32e6 tip
562 562 2
563 563 branch: default
564 564 bookmarks: *Z Y x y
565 565 commit: (clean)
566 566 update: 1 new changesets, 2 branch heads (merge)
567 567 phases: 3 draft
568 568
569 569 test id
570 570
571 571 $ hg id
572 572 db815d6d32e6 tip Y/Z/x y
573 573
574 574 test rollback
575 575
576 576 $ echo foo > f1
577 577 $ hg bookmark tmp-rollback
578 578 $ hg ci -Amr
579 579 adding f1
580 580 $ hg bookmarks
581 581 X2 1:925d80f479bb
582 582 Y 2:db815d6d32e6
583 583 Z 2:db815d6d32e6
584 584 * tmp-rollback 3:2bf5cfec5864
585 585 x y 2:db815d6d32e6
586 586 $ hg rollback
587 587 repository tip rolled back to revision 2 (undo commit)
588 588 working directory now based on revision 2
589 589 $ hg bookmarks
590 590 X2 1:925d80f479bb
591 591 Y 2:db815d6d32e6
592 592 Z 2:db815d6d32e6
593 593 * tmp-rollback 2:db815d6d32e6
594 594 x y 2:db815d6d32e6
595 595 $ hg bookmark -f Z -r 1
596 596 $ hg rollback
597 597 repository tip rolled back to revision 2 (undo bookmark)
598 598 $ hg bookmarks
599 599 X2 1:925d80f479bb
600 600 Y 2:db815d6d32e6
601 601 Z 2:db815d6d32e6
602 602 * tmp-rollback 2:db815d6d32e6
603 603 x y 2:db815d6d32e6
604 604 $ hg bookmark -d tmp-rollback
605 605
606 606 activate bookmark on working dir parent without --force
607 607
608 608 $ hg bookmark --inactive Z
609 609 $ hg bookmark Z
610 610
611 611 deactivate current 'Z', but also add 'Y'
612 612
613 613 $ hg bookmark -d Y
614 614 $ hg bookmark --inactive Z Y
615 615 $ hg bookmark -l
616 616 X2 1:925d80f479bb
617 617 Y 2:db815d6d32e6
618 618 Z 2:db815d6d32e6
619 619 x y 2:db815d6d32e6
620 620 $ hg bookmark Z
621 621
622 622 bookmark wdir to activate it (issue6218)
623 623
624 624 $ hg bookmark -d Z
625 625 $ hg bookmark -r 'wdir()' Z
626 626 $ hg bookmark -l
627 627 X2 1:925d80f479bb
628 628 Y 2:db815d6d32e6
629 629 * Z 2:db815d6d32e6
630 630 x y 2:db815d6d32e6
631 631
632 632 test clone
633 633
634 634 $ hg bookmark -r 2 -i @
635 635 $ hg bookmark -r 2 -i a@
636 636 $ hg bookmarks
637 637 @ 2:db815d6d32e6
638 638 X2 1:925d80f479bb
639 639 Y 2:db815d6d32e6
640 640 * Z 2:db815d6d32e6
641 641 a@ 2:db815d6d32e6
642 642 x y 2:db815d6d32e6
643 643 $ hg clone . cloned-bookmarks
644 644 updating to bookmark @
645 645 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
646 646 $ hg -R cloned-bookmarks bookmarks
647 647 * @ 2:db815d6d32e6
648 648 X2 1:925d80f479bb
649 649 Y 2:db815d6d32e6
650 650 Z 2:db815d6d32e6
651 651 a@ 2:db815d6d32e6
652 652 x y 2:db815d6d32e6
653 653
654 654 test clone with pull protocol
655 655
656 656 $ hg clone --pull . cloned-bookmarks-pull
657 657 requesting all changes
658 658 adding changesets
659 659 adding manifests
660 660 adding file changes
661 661 added 3 changesets with 3 changes to 3 files (+1 heads)
662 662 new changesets f7b1eb17ad24:db815d6d32e6
663 663 updating to bookmark @
664 664 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
665 665 $ hg -R cloned-bookmarks-pull bookmarks
666 666 * @ 2:db815d6d32e6
667 667 X2 1:925d80f479bb
668 668 Y 2:db815d6d32e6
669 669 Z 2:db815d6d32e6
670 670 a@ 2:db815d6d32e6
671 671 x y 2:db815d6d32e6
672 672
673 673 delete multiple bookmarks at once
674 674
675 675 $ hg bookmark -d @ a@
676 676
677 677 test clone with a bookmark named "default" (issue3677)
678 678
679 679 $ hg bookmark -r 1 -f -i default
680 680 $ hg clone . cloned-bookmark-default
681 681 updating to branch default
682 682 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
683 683 $ hg -R cloned-bookmark-default bookmarks
684 684 X2 1:925d80f479bb
685 685 Y 2:db815d6d32e6
686 686 Z 2:db815d6d32e6
687 687 default 1:925d80f479bb
688 688 x y 2:db815d6d32e6
689 689 $ hg -R cloned-bookmark-default parents -q
690 690 2:db815d6d32e6
691 691 $ hg bookmark -d default
692 692
693 693 test clone with a specific revision
694 694
695 695 $ hg clone -r 925d80 . cloned-bookmarks-rev
696 696 adding changesets
697 697 adding manifests
698 698 adding file changes
699 699 added 2 changesets with 2 changes to 2 files
700 700 new changesets f7b1eb17ad24:925d80f479bb
701 701 updating to branch default
702 702 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
703 703 $ hg -R cloned-bookmarks-rev bookmarks
704 704 X2 1:925d80f479bb
705 705
706 706 test clone with update to a bookmark
707 707
708 708 $ hg clone -u Z . ../cloned-bookmarks-update
709 709 updating to branch default
710 710 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
711 711 $ hg -R ../cloned-bookmarks-update bookmarks
712 712 X2 1:925d80f479bb
713 713 Y 2:db815d6d32e6
714 714 * Z 2:db815d6d32e6
715 715 x y 2:db815d6d32e6
716 716
717 717 create bundle with two heads
718 718
719 719 $ hg clone . tobundle
720 720 updating to branch default
721 721 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
722 722 $ echo x > tobundle/x
723 723 $ hg -R tobundle add tobundle/x
724 724 $ hg -R tobundle commit -m'x'
725 725 $ hg -R tobundle update -r -2
726 726 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
727 727 $ echo y > tobundle/y
728 728 $ hg -R tobundle branch test
729 729 marked working directory as branch test
730 730 (branches are permanent and global, did you want a bookmark?)
731 731 $ hg -R tobundle add tobundle/y
732 732 $ hg -R tobundle commit -m'y'
733 733 $ hg -R tobundle bundle tobundle.hg
734 734 searching for changes
735 735 2 changesets found
736 736 $ hg unbundle tobundle.hg
737 737 adding changesets
738 738 adding manifests
739 739 adding file changes
740 740 added 2 changesets with 2 changes to 2 files (+1 heads)
741 741 new changesets 125c9a1d6df6:9ba5f110a0b3 (2 drafts)
742 742 (run 'hg heads' to see heads, 'hg merge' to merge)
743 743
744 744 update to active bookmark if it's not the parent
745 745
746 746 (it is known issue that fsmonitor can't handle nested repositories. In
747 747 this test scenario, cloned-bookmark-default and tobundle exist in the
748 748 working directory of current repository)
749 749
750 750 $ hg summary
751 751 parent: 2:db815d6d32e6
752 752 2
753 753 branch: default
754 754 bookmarks: *Z Y x y
755 755 commit: 1 added, 1 unknown (new branch head) (no-fsmonitor !)
756 756 commit: 1 added, * unknown (new branch head) (glob) (fsmonitor !)
757 757 update: 2 new changesets (update)
758 758 phases: 5 draft
759 759 $ hg update
760 760 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
761 761 updating bookmark Z
762 762 $ hg bookmarks
763 763 X2 1:925d80f479bb
764 764 Y 2:db815d6d32e6
765 765 * Z 3:125c9a1d6df6
766 766 x y 2:db815d6d32e6
767 767
768 768 pull --update works the same as pull && update
769 769
770 770 $ hg bookmark -r3 Y
771 771 moving bookmark 'Y' forward from db815d6d32e6
772 772 $ cp -R ../cloned-bookmarks-update ../cloned-bookmarks-manual-update
773 773 $ cp -R ../cloned-bookmarks-update ../cloned-bookmarks-manual-update-with-divergence
774 774
775 775 (manual version)
776 776
777 777 $ hg -R ../cloned-bookmarks-manual-update update Y
778 778 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
779 779 (activating bookmark Y)
780 780 $ hg -R ../cloned-bookmarks-manual-update pull .
781 781 pulling from .
782 782 searching for changes
783 783 adding changesets
784 784 adding manifests
785 785 adding file changes
786 786 updating bookmark Y
787 787 updating bookmark Z
788 788 added 2 changesets with 2 changes to 2 files (+1 heads)
789 789 new changesets 125c9a1d6df6:9ba5f110a0b3
790 790 (run 'hg heads' to see heads, 'hg merge' to merge)
791 791
792 792 (# tests strange but with --date crashing when bookmark have to move)
793 793
794 794 $ hg -R ../cloned-bookmarks-manual-update update -d 1986
795 795 abort: revision matching date not found
796 796 [255]
797 797 $ hg -R ../cloned-bookmarks-manual-update update
798 798 updating to active bookmark Y
799 799 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
800 800
801 801 (all in one version)
802 802
803 803 $ hg -R ../cloned-bookmarks-update update Y
804 804 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
805 805 (activating bookmark Y)
806 806 $ hg -R ../cloned-bookmarks-update pull --update .
807 807 pulling from .
808 808 searching for changes
809 809 adding changesets
810 810 adding manifests
811 811 adding file changes
812 812 updating bookmark Y
813 813 updating bookmark Z
814 814 added 2 changesets with 2 changes to 2 files (+1 heads)
815 815 new changesets 125c9a1d6df6:9ba5f110a0b3
816 816 updating to active bookmark Y
817 817 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
818 818
819 819 We warn about divergent during bare update to the active bookmark
820 820
821 821 $ hg -R ../cloned-bookmarks-manual-update-with-divergence update Y
822 822 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
823 823 (activating bookmark Y)
824 824 $ hg -R ../cloned-bookmarks-manual-update-with-divergence bookmarks -r X2 Y@1
825 825 $ hg -R ../cloned-bookmarks-manual-update-with-divergence bookmarks
826 826 X2 1:925d80f479bb
827 827 * Y 2:db815d6d32e6
828 828 Y@1 1:925d80f479bb
829 829 Z 2:db815d6d32e6
830 830 x y 2:db815d6d32e6
831 831 $ hg -R ../cloned-bookmarks-manual-update-with-divergence pull
832 832 pulling from $TESTTMP/repo
833 833 searching for changes
834 834 adding changesets
835 835 adding manifests
836 836 adding file changes
837 837 updating bookmark Y
838 838 updating bookmark Z
839 839 added 2 changesets with 2 changes to 2 files (+1 heads)
840 840 new changesets 125c9a1d6df6:9ba5f110a0b3
841 841 (run 'hg heads' to see heads, 'hg merge' to merge)
842 842 $ hg -R ../cloned-bookmarks-manual-update-with-divergence update
843 843 updating to active bookmark Y
844 844 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
845 845 1 other divergent bookmarks for "Y"
846 846
847 847 test wrongly formated bookmark
848 848
849 849 $ echo '' >> .hg/bookmarks
850 850 $ hg bookmarks
851 851 X2 1:925d80f479bb
852 852 Y 3:125c9a1d6df6
853 853 * Z 3:125c9a1d6df6
854 854 x y 2:db815d6d32e6
855 855 $ echo "Ican'thasformatedlines" >> .hg/bookmarks
856 856 $ hg bookmarks
857 857 malformed line in .hg/bookmarks: "Ican'thasformatedlines"
858 858 X2 1:925d80f479bb
859 859 Y 3:125c9a1d6df6
860 860 * Z 3:125c9a1d6df6
861 861 x y 2:db815d6d32e6
862 862
863 863 test missing revisions
864 864
865 865 $ echo "925d80f479b925d80f479bc925d80f479bccabab z" > .hg/bookmarks
866 866 $ hg book
867 867 no bookmarks set
868 868
869 869 test stripping a non-checked-out but bookmarked revision
870 870
871 871 $ hg log --graph
872 872 o changeset: 4:9ba5f110a0b3
873 873 | branch: test
874 874 | tag: tip
875 875 | parent: 2:db815d6d32e6
876 876 | user: test
877 877 | date: Thu Jan 01 00:00:00 1970 +0000
878 878 | summary: y
879 879 |
880 880 | @ changeset: 3:125c9a1d6df6
881 881 |/ user: test
882 882 | date: Thu Jan 01 00:00:00 1970 +0000
883 883 | summary: x
884 884 |
885 885 o changeset: 2:db815d6d32e6
886 886 | parent: 0:f7b1eb17ad24
887 887 | user: test
888 888 | date: Thu Jan 01 00:00:00 1970 +0000
889 889 | summary: 2
890 890 |
891 891 | o changeset: 1:925d80f479bb
892 892 |/ user: test
893 893 | date: Thu Jan 01 00:00:00 1970 +0000
894 894 | summary: 1
895 895 |
896 896 o changeset: 0:f7b1eb17ad24
897 897 user: test
898 898 date: Thu Jan 01 00:00:00 1970 +0000
899 899 summary: 0
900 900
901 901 $ hg book should-end-on-two
902 902 $ hg co --clean 4
903 903 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
904 904 (leaving bookmark should-end-on-two)
905 905 $ hg book four
906 906 $ hg --config extensions.mq= strip 3
907 907 saved backup bundle to * (glob)
908 908 should-end-on-two should end up pointing to revision 2, as that's the
909 909 tipmost surviving ancestor of the stripped revision.
910 910 $ hg log --graph
911 911 @ changeset: 3:9ba5f110a0b3
912 912 | branch: test
913 913 | bookmark: four
914 914 | tag: tip
915 915 | user: test
916 916 | date: Thu Jan 01 00:00:00 1970 +0000
917 917 | summary: y
918 918 |
919 919 o changeset: 2:db815d6d32e6
920 920 | bookmark: should-end-on-two
921 921 | parent: 0:f7b1eb17ad24
922 922 | user: test
923 923 | date: Thu Jan 01 00:00:00 1970 +0000
924 924 | summary: 2
925 925 |
926 926 | o changeset: 1:925d80f479bb
927 927 |/ user: test
928 928 | date: Thu Jan 01 00:00:00 1970 +0000
929 929 | summary: 1
930 930 |
931 931 o changeset: 0:f7b1eb17ad24
932 932 user: test
933 933 date: Thu Jan 01 00:00:00 1970 +0000
934 934 summary: 0
935 935
936 936
937 937 no-op update doesn't deactivate bookmarks
938 938
939 939 (it is known issue that fsmonitor can't handle nested repositories. In
940 940 this test scenario, cloned-bookmark-default and tobundle exist in the
941 941 working directory of current repository)
942 942
943 943 $ hg bookmarks
944 944 * four 3:9ba5f110a0b3
945 945 should-end-on-two 2:db815d6d32e6
946 946 $ hg up four
947 947 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
948 948 $ hg up
949 949 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
950 950 $ hg sum
951 951 parent: 3:9ba5f110a0b3 tip
952 952 y
953 953 branch: test
954 954 bookmarks: *four
955 955 commit: 2 unknown (clean) (no-fsmonitor !)
956 956 commit: * unknown (clean) (glob) (fsmonitor !)
957 957 update: (current)
958 958 phases: 4 draft
959 959
960 960 test clearing divergent bookmarks of linear ancestors
961 961
962 962 $ hg bookmark Z -r 0
963 963 $ hg bookmark Z@1 -r 1
964 964 $ hg bookmark Z@2 -r 2
965 965 $ hg bookmark Z@3 -r 3
966 966 $ hg book
967 967 Z 0:f7b1eb17ad24
968 968 Z@1 1:925d80f479bb
969 969 Z@2 2:db815d6d32e6
970 970 Z@3 3:9ba5f110a0b3
971 971 * four 3:9ba5f110a0b3
972 972 should-end-on-two 2:db815d6d32e6
973 973 $ hg bookmark Z
974 974 moving bookmark 'Z' forward from f7b1eb17ad24
975 975 $ hg book
976 976 * Z 3:9ba5f110a0b3
977 977 Z@1 1:925d80f479bb
978 978 four 3:9ba5f110a0b3
979 979 should-end-on-two 2:db815d6d32e6
980 980
981 981 test clearing only a single divergent bookmark across branches
982 982
983 983 $ hg book foo -r 1
984 984 $ hg book foo@1 -r 0
985 985 $ hg book foo@2 -r 2
986 986 $ hg book foo@3 -r 3
987 987 $ hg book foo -r foo@3
988 988 $ hg book
989 989 * Z 3:9ba5f110a0b3
990 990 Z@1 1:925d80f479bb
991 991 foo 3:9ba5f110a0b3
992 992 foo@1 0:f7b1eb17ad24
993 993 foo@2 2:db815d6d32e6
994 994 four 3:9ba5f110a0b3
995 995 should-end-on-two 2:db815d6d32e6
996 996
997 997 pull --update works the same as pull && update (case #2)
998 998
999 999 It is assumed that "hg pull" itself doesn't update current active
1000 1000 bookmark ('Y' in tests below).
1001 1001
1002 1002 $ hg pull -q ../cloned-bookmarks-update
1003 1003 divergent bookmark Z stored as Z@2
1004 1004
1005 1005 (pulling revision on another named branch with --update updates
1006 1006 neither the working directory nor current active bookmark: "no-op"
1007 1007 case)
1008 1008
1009 1009 $ echo yy >> y
1010 1010 $ hg commit -m yy
1011 1011
1012 1012 $ hg -R ../cloned-bookmarks-update bookmarks | grep ' Y '
1013 1013 * Y 3:125c9a1d6df6
1014 1014 $ hg -R ../cloned-bookmarks-update pull . --update
1015 1015 pulling from .
1016 1016 searching for changes
1017 1017 adding changesets
1018 1018 adding manifests
1019 1019 adding file changes
1020 1020 divergent bookmark Z stored as Z@default
1021 1021 adding remote bookmark foo
1022 1022 adding remote bookmark four
1023 1023 adding remote bookmark should-end-on-two
1024 1024 added 1 changesets with 1 changes to 1 files
1025 1025 new changesets 5fb12f0f2d51
1026 1026 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1027 1027 $ hg -R ../cloned-bookmarks-update parents -T "{rev}:{node|short}\n"
1028 1028 3:125c9a1d6df6
1029 1029 $ hg -R ../cloned-bookmarks-update bookmarks | grep ' Y '
1030 1030 * Y 3:125c9a1d6df6
1031 1031
1032 1032 (pulling revision on current named/topological branch with --update
1033 1033 updates the working directory and current active bookmark)
1034 1034
1035 1035 $ hg update -C -q 125c9a1d6df6
1036 1036 $ echo xx >> x
1037 1037 $ hg commit -m xx
1038 1038
1039 1039 $ hg -R ../cloned-bookmarks-update bookmarks | grep ' Y '
1040 1040 * Y 3:125c9a1d6df6
1041 1041 $ hg -R ../cloned-bookmarks-update pull . --update
1042 1042 pulling from .
1043 1043 searching for changes
1044 1044 adding changesets
1045 1045 adding manifests
1046 1046 adding file changes
1047 1047 divergent bookmark Z stored as Z@default
1048 1048 added 1 changesets with 1 changes to 1 files
1049 1049 new changesets 81dcce76aa0b
1050 1050 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1051 1051 updating bookmark Y
1052 1052 $ hg -R ../cloned-bookmarks-update parents -T "{rev}:{node|short}\n"
1053 1053 6:81dcce76aa0b
1054 1054 $ hg -R ../cloned-bookmarks-update bookmarks | grep ' Y '
1055 1055 * Y 6:81dcce76aa0b
1056 1056
1057 1057 $ cd ..
1058 1058
1059 1059 ensure changelog is written before bookmarks
1060 1060 $ hg init orderrepo
1061 1061 $ cd orderrepo
1062 1062 $ touch a
1063 1063 $ hg commit -Aqm one
1064 1064 $ hg book mybook
1065 1065 $ echo a > a
1066 1066
1067 1067 $ cat > $TESTTMP/pausefinalize.py <<EOF
1068 1068 > from __future__ import absolute_import
1069 1069 > import os
1070 1070 > import time
1071 1071 > from mercurial import extensions, localrepo
1072 1072 > def transaction(orig, self, desc, report=None):
1073 1073 > tr = orig(self, desc, report)
1074 1074 > def sleep(*args, **kwargs):
1075 1075 > retry = 20
1076 1076 > while retry > 0 and not os.path.exists(b"$TESTTMP/unpause"):
1077 1077 > retry -= 1
1078 1078 > time.sleep(0.5)
1079 1079 > if os.path.exists(b"$TESTTMP/unpause"):
1080 1080 > os.remove(b"$TESTTMP/unpause")
1081 1081 > # It is important that this finalizer start with 'a', so it runs before
1082 1082 > # the changelog finalizer appends to the changelog.
1083 1083 > tr.addfinalize(b'a-sleep', sleep)
1084 1084 > return tr
1085 1085 >
1086 1086 > def extsetup(ui):
1087 1087 > # This extension inserts an artifical pause during the transaction
1088 1088 > # finalizer, so we can run commands mid-transaction-close.
1089 1089 > extensions.wrapfunction(localrepo.localrepository, 'transaction',
1090 1090 > transaction)
1091 1091 > EOF
1092 1092 $ hg commit -qm two --config extensions.pausefinalize=$TESTTMP/pausefinalize.py &
1093 1093 $ sleep 2
1094 1094 $ hg log -r .
1095 1095 changeset: 0:867bc5792c8c
1096 1096 bookmark: mybook
1097 1097 tag: tip
1098 1098 user: test
1099 1099 date: Thu Jan 01 00:00:00 1970 +0000
1100 1100 summary: one
1101 1101
1102 1102 $ hg bookmarks
1103 1103 * mybook 0:867bc5792c8c
1104 1104 $ touch $TESTTMP/unpause
1105 1105
1106 1106 $ cd ..
1107 1107
1108 1108 check whether HG_PENDING makes pending changes only in related
1109 1109 repositories visible to an external hook.
1110 1110
1111 1111 (emulate a transaction running concurrently by copied
1112 1112 .hg/bookmarks.pending in subsequent test)
1113 1113
1114 1114 $ cat > $TESTTMP/savepending.sh <<EOF
1115 1115 > cp .hg/bookmarks.pending .hg/bookmarks.pending.saved
1116 1116 > exit 1 # to avoid adding new bookmark for subsequent tests
1117 1117 > EOF
1118 1118
1119 1119 $ hg init unrelated
1120 1120 $ cd unrelated
1121 1121 $ echo a > a
1122 1122 $ hg add a
1123 1123 $ hg commit -m '#0'
1124 1124 $ hg --config hooks.pretxnclose="sh $TESTTMP/savepending.sh" bookmarks INVISIBLE
1125 1125 transaction abort!
1126 1126 rollback completed
1127 1127 abort: pretxnclose hook exited with status 1
1128 1128 [255]
1129 1129 $ cp .hg/bookmarks.pending.saved .hg/bookmarks.pending
1130 1130
1131 1131 (check visible bookmarks while transaction running in repo)
1132 1132
1133 1133 $ cat > $TESTTMP/checkpending.sh <<EOF
1134 1134 > echo "@repo"
1135 1135 > hg -R "$TESTTMP/repo" bookmarks
1136 1136 > echo "@unrelated"
1137 1137 > hg -R "$TESTTMP/unrelated" bookmarks
1138 1138 > exit 1 # to avoid adding new bookmark for subsequent tests
1139 1139 > EOF
1140 1140
1141 1141 $ cd ../repo
1142 1142 $ hg --config hooks.pretxnclose="sh $TESTTMP/checkpending.sh" bookmarks NEW
1143 1143 @repo
1144 1144 * NEW 6:81dcce76aa0b
1145 1145 X2 1:925d80f479bb
1146 1146 Y 4:125c9a1d6df6
1147 1147 Z 5:5fb12f0f2d51
1148 1148 Z@1 1:925d80f479bb
1149 1149 Z@2 4:125c9a1d6df6
1150 1150 foo 3:9ba5f110a0b3
1151 1151 foo@1 0:f7b1eb17ad24
1152 1152 foo@2 2:db815d6d32e6
1153 1153 four 3:9ba5f110a0b3
1154 1154 should-end-on-two 2:db815d6d32e6
1155 1155 x y 2:db815d6d32e6
1156 1156 @unrelated
1157 1157 no bookmarks set
1158 1158 transaction abort!
1159 1159 rollback completed
1160 1160 abort: pretxnclose hook exited with status 1
1161 1161 [255]
1162 1162
1163 1163 Check pretxnclose-bookmark can abort a transaction
1164 1164 --------------------------------------------------
1165 1165
1166 1166 add hooks:
1167 1167
1168 1168 * to prevent NEW bookmark on a non-public changeset
1169 1169 * to prevent non-forward move of NEW bookmark
1170 1170
1171 1171 $ cat << EOF >> .hg/hgrc
1172 1172 > [hooks]
1173 1173 > pretxnclose-bookmark.force-public = sh -c "(echo \$HG_BOOKMARK| grep -v NEW > /dev/null) || [ -z \"\$HG_NODE\" ] || (hg log -r \"\$HG_NODE\" -T '{phase}' | grep public > /dev/null)"
1174 1174 > pretxnclose-bookmark.force-forward = sh -c "(echo \$HG_BOOKMARK| grep -v NEW > /dev/null) || [ -z \"\$HG_NODE\" ] || (hg log -r \"max(\$HG_OLDNODE::\$HG_NODE)\" -T 'MATCH' | grep MATCH > /dev/null)"
1175 1175 > EOF
1176 1176
1177 1177 $ hg log -G -T phases
1178 1178 @ changeset: 6:81dcce76aa0b
1179 1179 | tag: tip
1180 1180 | phase: draft
1181 1181 | parent: 4:125c9a1d6df6
1182 1182 | user: test
1183 1183 | date: Thu Jan 01 00:00:00 1970 +0000
1184 1184 | summary: xx
1185 1185 |
1186 1186 | o changeset: 5:5fb12f0f2d51
1187 1187 | | branch: test
1188 1188 | | bookmark: Z
1189 1189 | | phase: draft
1190 1190 | | parent: 3:9ba5f110a0b3
1191 1191 | | user: test
1192 1192 | | date: Thu Jan 01 00:00:00 1970 +0000
1193 1193 | | summary: yy
1194 1194 | |
1195 1195 o | changeset: 4:125c9a1d6df6
1196 1196 | | bookmark: Y
1197 1197 | | bookmark: Z@2
1198 1198 | | phase: public
1199 1199 | | parent: 2:db815d6d32e6
1200 1200 | | user: test
1201 1201 | | date: Thu Jan 01 00:00:00 1970 +0000
1202 1202 | | summary: x
1203 1203 | |
1204 1204 | o changeset: 3:9ba5f110a0b3
1205 1205 |/ branch: test
1206 1206 | bookmark: foo
1207 1207 | bookmark: four
1208 1208 | phase: public
1209 1209 | user: test
1210 1210 | date: Thu Jan 01 00:00:00 1970 +0000
1211 1211 | summary: y
1212 1212 |
1213 1213 o changeset: 2:db815d6d32e6
1214 1214 | bookmark: foo@2
1215 1215 | bookmark: should-end-on-two
1216 1216 | bookmark: x y
1217 1217 | phase: public
1218 1218 | parent: 0:f7b1eb17ad24
1219 1219 | user: test
1220 1220 | date: Thu Jan 01 00:00:00 1970 +0000
1221 1221 | summary: 2
1222 1222 |
1223 1223 | o changeset: 1:925d80f479bb
1224 1224 |/ bookmark: X2
1225 1225 | bookmark: Z@1
1226 1226 | phase: public
1227 1227 | user: test
1228 1228 | date: Thu Jan 01 00:00:00 1970 +0000
1229 1229 | summary: 1
1230 1230 |
1231 1231 o changeset: 0:f7b1eb17ad24
1232 1232 bookmark: foo@1
1233 1233 phase: public
1234 1234 user: test
1235 1235 date: Thu Jan 01 00:00:00 1970 +0000
1236 1236 summary: 0
1237 1237
1238 1238
1239 1239 attempt to create on a default changeset
1240 1240
1241 1241 $ hg bookmark -r 81dcce76aa0b NEW
1242 1242 transaction abort!
1243 1243 rollback completed
1244 1244 abort: pretxnclose-bookmark.force-public hook exited with status 1
1245 1245 [255]
1246 1246
1247 1247 create on a public changeset
1248 1248
1249 1249 $ hg bookmark -r 9ba5f110a0b3 NEW
1250 1250
1251 1251 move to the other branch
1252 1252
1253 1253 $ hg bookmark -f -r 125c9a1d6df6 NEW
1254 1254 transaction abort!
1255 1255 rollback completed
1256 1256 abort: pretxnclose-bookmark.force-forward hook exited with status 1
1257 1257 [255]
General Comments 0
You need to be logged in to leave comments. Login now