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