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