##// END OF EJS Templates
mergedriver: delete it...
Martin von Zweigbergk -
r46091:32ce4cba default
parent child Browse files
Show More
@@ -1,7871 +1,7814 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 793 node = scmutil.revsingle(repo, rev).node()
794 794
795 795 op1, op2 = repo.dirstate.parents()
796 796 if not repo.changelog.isancestor(node, op1):
797 797 raise error.Abort(_(b'cannot backout change that is not an ancestor'))
798 798
799 799 p1, p2 = repo.changelog.parents(node)
800 800 if p1 == nullid:
801 801 raise error.Abort(_(b'cannot backout a change with no parents'))
802 802 if p2 != nullid:
803 803 if not opts.get(b'parent'):
804 804 raise error.Abort(_(b'cannot backout a merge changeset'))
805 805 p = repo.lookup(opts[b'parent'])
806 806 if p not in (p1, p2):
807 807 raise error.Abort(
808 808 _(b'%s is not a parent of %s') % (short(p), short(node))
809 809 )
810 810 parent = p
811 811 else:
812 812 if opts.get(b'parent'):
813 813 raise error.Abort(_(b'cannot use --parent on non-merge changeset'))
814 814 parent = p1
815 815
816 816 # the backout should appear on the same branch
817 817 branch = repo.dirstate.branch()
818 818 bheads = repo.branchheads(branch)
819 819 rctx = scmutil.revsingle(repo, hex(parent))
820 820 if not opts.get(b'merge') and op1 != node:
821 821 with dirstateguard.dirstateguard(repo, b'backout'):
822 822 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
823 823 with ui.configoverride(overrides, b'backout'):
824 824 stats = mergemod.update(
825 825 repo,
826 826 parent,
827 827 branchmerge=True,
828 828 force=True,
829 829 ancestor=node,
830 830 mergeancestor=False,
831 831 )
832 832 repo.setparents(op1, op2)
833 833 hg._showstats(repo, stats)
834 834 if stats.unresolvedcount:
835 835 repo.ui.status(
836 836 _(b"use 'hg resolve' to retry unresolved file merges\n")
837 837 )
838 838 return 1
839 839 else:
840 840 hg.clean(repo, node, show_stats=False)
841 841 repo.dirstate.setbranch(branch)
842 842 cmdutil.revert(ui, repo, rctx)
843 843
844 844 if opts.get(b'no_commit'):
845 845 msg = _(b"changeset %s backed out, don't forget to commit.\n")
846 846 ui.status(msg % short(node))
847 847 return 0
848 848
849 849 def commitfunc(ui, repo, message, match, opts):
850 850 editform = b'backout'
851 851 e = cmdutil.getcommiteditor(
852 852 editform=editform, **pycompat.strkwargs(opts)
853 853 )
854 854 if not message:
855 855 # we don't translate commit messages
856 856 message = b"Backed out changeset %s" % short(node)
857 857 e = cmdutil.getcommiteditor(edit=True, editform=editform)
858 858 return repo.commit(
859 859 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
860 860 )
861 861
862 862 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
863 863 if not newnode:
864 864 ui.status(_(b"nothing changed\n"))
865 865 return 1
866 866 cmdutil.commitstatus(repo, newnode, branch, bheads)
867 867
868 868 def nice(node):
869 869 return b'%d:%s' % (repo.changelog.rev(node), short(node))
870 870
871 871 ui.status(
872 872 _(b'changeset %s backs out changeset %s\n')
873 873 % (nice(repo.changelog.tip()), nice(node))
874 874 )
875 875 if opts.get(b'merge') and op1 != node:
876 876 hg.clean(repo, op1, show_stats=False)
877 877 ui.status(
878 878 _(b'merging with changeset %s\n') % nice(repo.changelog.tip())
879 879 )
880 880 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
881 881 with ui.configoverride(overrides, b'backout'):
882 882 return hg.merge(repo[b'tip'])
883 883 return 0
884 884
885 885
886 886 @command(
887 887 b'bisect',
888 888 [
889 889 (b'r', b'reset', False, _(b'reset bisect state')),
890 890 (b'g', b'good', False, _(b'mark changeset good')),
891 891 (b'b', b'bad', False, _(b'mark changeset bad')),
892 892 (b's', b'skip', False, _(b'skip testing changeset')),
893 893 (b'e', b'extend', False, _(b'extend the bisect range')),
894 894 (
895 895 b'c',
896 896 b'command',
897 897 b'',
898 898 _(b'use command to check changeset state'),
899 899 _(b'CMD'),
900 900 ),
901 901 (b'U', b'noupdate', False, _(b'do not update to target')),
902 902 ],
903 903 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
904 904 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
905 905 )
906 906 def bisect(
907 907 ui,
908 908 repo,
909 909 rev=None,
910 910 extra=None,
911 911 command=None,
912 912 reset=None,
913 913 good=None,
914 914 bad=None,
915 915 skip=None,
916 916 extend=None,
917 917 noupdate=None,
918 918 ):
919 919 """subdivision search of changesets
920 920
921 921 This command helps to find changesets which introduce problems. To
922 922 use, mark the earliest changeset you know exhibits the problem as
923 923 bad, then mark the latest changeset which is free from the problem
924 924 as good. Bisect will update your working directory to a revision
925 925 for testing (unless the -U/--noupdate option is specified). Once
926 926 you have performed tests, mark the working directory as good or
927 927 bad, and bisect will either update to another candidate changeset
928 928 or announce that it has found the bad revision.
929 929
930 930 As a shortcut, you can also use the revision argument to mark a
931 931 revision as good or bad without checking it out first.
932 932
933 933 If you supply a command, it will be used for automatic bisection.
934 934 The environment variable HG_NODE will contain the ID of the
935 935 changeset being tested. The exit status of the command will be
936 936 used to mark revisions as good or bad: status 0 means good, 125
937 937 means to skip the revision, 127 (command not found) will abort the
938 938 bisection, and any other non-zero exit status means the revision
939 939 is bad.
940 940
941 941 .. container:: verbose
942 942
943 943 Some examples:
944 944
945 945 - start a bisection with known bad revision 34, and good revision 12::
946 946
947 947 hg bisect --bad 34
948 948 hg bisect --good 12
949 949
950 950 - advance the current bisection by marking current revision as good or
951 951 bad::
952 952
953 953 hg bisect --good
954 954 hg bisect --bad
955 955
956 956 - mark the current revision, or a known revision, to be skipped (e.g. if
957 957 that revision is not usable because of another issue)::
958 958
959 959 hg bisect --skip
960 960 hg bisect --skip 23
961 961
962 962 - skip all revisions that do not touch directories ``foo`` or ``bar``::
963 963
964 964 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
965 965
966 966 - forget the current bisection::
967 967
968 968 hg bisect --reset
969 969
970 970 - use 'make && make tests' to automatically find the first broken
971 971 revision::
972 972
973 973 hg bisect --reset
974 974 hg bisect --bad 34
975 975 hg bisect --good 12
976 976 hg bisect --command "make && make tests"
977 977
978 978 - see all changesets whose states are already known in the current
979 979 bisection::
980 980
981 981 hg log -r "bisect(pruned)"
982 982
983 983 - see the changeset currently being bisected (especially useful
984 984 if running with -U/--noupdate)::
985 985
986 986 hg log -r "bisect(current)"
987 987
988 988 - see all changesets that took part in the current bisection::
989 989
990 990 hg log -r "bisect(range)"
991 991
992 992 - you can even get a nice graph::
993 993
994 994 hg log --graph -r "bisect(range)"
995 995
996 996 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
997 997
998 998 Returns 0 on success.
999 999 """
1000 1000 # backward compatibility
1001 1001 if rev in b"good bad reset init".split():
1002 1002 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1003 1003 cmd, rev, extra = rev, extra, None
1004 1004 if cmd == b"good":
1005 1005 good = True
1006 1006 elif cmd == b"bad":
1007 1007 bad = True
1008 1008 else:
1009 1009 reset = True
1010 1010 elif extra:
1011 1011 raise error.Abort(_(b'incompatible arguments'))
1012 1012
1013 1013 incompatibles = {
1014 1014 b'--bad': bad,
1015 1015 b'--command': bool(command),
1016 1016 b'--extend': extend,
1017 1017 b'--good': good,
1018 1018 b'--reset': reset,
1019 1019 b'--skip': skip,
1020 1020 }
1021 1021
1022 1022 enabled = [x for x in incompatibles if incompatibles[x]]
1023 1023
1024 1024 if len(enabled) > 1:
1025 1025 raise error.Abort(
1026 1026 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1027 1027 )
1028 1028
1029 1029 if reset:
1030 1030 hbisect.resetstate(repo)
1031 1031 return
1032 1032
1033 1033 state = hbisect.load_state(repo)
1034 1034
1035 1035 # update state
1036 1036 if good or bad or skip:
1037 1037 if rev:
1038 1038 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
1039 1039 else:
1040 1040 nodes = [repo.lookup(b'.')]
1041 1041 if good:
1042 1042 state[b'good'] += nodes
1043 1043 elif bad:
1044 1044 state[b'bad'] += nodes
1045 1045 elif skip:
1046 1046 state[b'skip'] += nodes
1047 1047 hbisect.save_state(repo, state)
1048 1048 if not (state[b'good'] and state[b'bad']):
1049 1049 return
1050 1050
1051 1051 def mayupdate(repo, node, show_stats=True):
1052 1052 """common used update sequence"""
1053 1053 if noupdate:
1054 1054 return
1055 1055 cmdutil.checkunfinished(repo)
1056 1056 cmdutil.bailifchanged(repo)
1057 1057 return hg.clean(repo, node, show_stats=show_stats)
1058 1058
1059 1059 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1060 1060
1061 1061 if command:
1062 1062 changesets = 1
1063 1063 if noupdate:
1064 1064 try:
1065 1065 node = state[b'current'][0]
1066 1066 except LookupError:
1067 1067 raise error.Abort(
1068 1068 _(
1069 1069 b'current bisect revision is unknown - '
1070 1070 b'start a new bisect to fix'
1071 1071 )
1072 1072 )
1073 1073 else:
1074 1074 node, p2 = repo.dirstate.parents()
1075 1075 if p2 != nullid:
1076 1076 raise error.Abort(_(b'current bisect revision is a merge'))
1077 1077 if rev:
1078 1078 node = repo[scmutil.revsingle(repo, rev, node)].node()
1079 1079 with hbisect.restore_state(repo, state, node):
1080 1080 while changesets:
1081 1081 # update state
1082 1082 state[b'current'] = [node]
1083 1083 hbisect.save_state(repo, state)
1084 1084 status = ui.system(
1085 1085 command,
1086 1086 environ={b'HG_NODE': hex(node)},
1087 1087 blockedtag=b'bisect_check',
1088 1088 )
1089 1089 if status == 125:
1090 1090 transition = b"skip"
1091 1091 elif status == 0:
1092 1092 transition = b"good"
1093 1093 # status < 0 means process was killed
1094 1094 elif status == 127:
1095 1095 raise error.Abort(_(b"failed to execute %s") % command)
1096 1096 elif status < 0:
1097 1097 raise error.Abort(_(b"%s killed") % command)
1098 1098 else:
1099 1099 transition = b"bad"
1100 1100 state[transition].append(node)
1101 1101 ctx = repo[node]
1102 1102 ui.status(
1103 1103 _(b'changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition)
1104 1104 )
1105 1105 hbisect.checkstate(state)
1106 1106 # bisect
1107 1107 nodes, changesets, bgood = hbisect.bisect(repo, state)
1108 1108 # update to next check
1109 1109 node = nodes[0]
1110 1110 mayupdate(repo, node, show_stats=False)
1111 1111 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1112 1112 return
1113 1113
1114 1114 hbisect.checkstate(state)
1115 1115
1116 1116 # actually bisect
1117 1117 nodes, changesets, good = hbisect.bisect(repo, state)
1118 1118 if extend:
1119 1119 if not changesets:
1120 1120 extendnode = hbisect.extendrange(repo, state, nodes, good)
1121 1121 if extendnode is not None:
1122 1122 ui.write(
1123 1123 _(b"Extending search to changeset %d:%s\n")
1124 1124 % (extendnode.rev(), extendnode)
1125 1125 )
1126 1126 state[b'current'] = [extendnode.node()]
1127 1127 hbisect.save_state(repo, state)
1128 1128 return mayupdate(repo, extendnode.node())
1129 1129 raise error.Abort(_(b"nothing to extend"))
1130 1130
1131 1131 if changesets == 0:
1132 1132 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1133 1133 else:
1134 1134 assert len(nodes) == 1 # only a single node can be tested next
1135 1135 node = nodes[0]
1136 1136 # compute the approximate number of remaining tests
1137 1137 tests, size = 0, 2
1138 1138 while size <= changesets:
1139 1139 tests, size = tests + 1, size * 2
1140 1140 rev = repo.changelog.rev(node)
1141 1141 ui.write(
1142 1142 _(
1143 1143 b"Testing changeset %d:%s "
1144 1144 b"(%d changesets remaining, ~%d tests)\n"
1145 1145 )
1146 1146 % (rev, short(node), changesets, tests)
1147 1147 )
1148 1148 state[b'current'] = [node]
1149 1149 hbisect.save_state(repo, state)
1150 1150 return mayupdate(repo, node)
1151 1151
1152 1152
1153 1153 @command(
1154 1154 b'bookmarks|bookmark',
1155 1155 [
1156 1156 (b'f', b'force', False, _(b'force')),
1157 1157 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1158 1158 (b'd', b'delete', False, _(b'delete a given bookmark')),
1159 1159 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1160 1160 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1161 1161 (b'l', b'list', False, _(b'list existing bookmarks')),
1162 1162 ]
1163 1163 + formatteropts,
1164 1164 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1165 1165 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1166 1166 )
1167 1167 def bookmark(ui, repo, *names, **opts):
1168 1168 '''create a new bookmark or list existing bookmarks
1169 1169
1170 1170 Bookmarks are labels on changesets to help track lines of development.
1171 1171 Bookmarks are unversioned and can be moved, renamed and deleted.
1172 1172 Deleting or moving a bookmark has no effect on the associated changesets.
1173 1173
1174 1174 Creating or updating to a bookmark causes it to be marked as 'active'.
1175 1175 The active bookmark is indicated with a '*'.
1176 1176 When a commit is made, the active bookmark will advance to the new commit.
1177 1177 A plain :hg:`update` will also advance an active bookmark, if possible.
1178 1178 Updating away from a bookmark will cause it to be deactivated.
1179 1179
1180 1180 Bookmarks can be pushed and pulled between repositories (see
1181 1181 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1182 1182 diverged, a new 'divergent bookmark' of the form 'name@path' will
1183 1183 be created. Using :hg:`merge` will resolve the divergence.
1184 1184
1185 1185 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1186 1186 the active bookmark's name.
1187 1187
1188 1188 A bookmark named '@' has the special property that :hg:`clone` will
1189 1189 check it out by default if it exists.
1190 1190
1191 1191 .. container:: verbose
1192 1192
1193 1193 Template:
1194 1194
1195 1195 The following keywords are supported in addition to the common template
1196 1196 keywords and functions such as ``{bookmark}``. See also
1197 1197 :hg:`help templates`.
1198 1198
1199 1199 :active: Boolean. True if the bookmark is active.
1200 1200
1201 1201 Examples:
1202 1202
1203 1203 - create an active bookmark for a new line of development::
1204 1204
1205 1205 hg book new-feature
1206 1206
1207 1207 - create an inactive bookmark as a place marker::
1208 1208
1209 1209 hg book -i reviewed
1210 1210
1211 1211 - create an inactive bookmark on another changeset::
1212 1212
1213 1213 hg book -r .^ tested
1214 1214
1215 1215 - rename bookmark turkey to dinner::
1216 1216
1217 1217 hg book -m turkey dinner
1218 1218
1219 1219 - move the '@' bookmark from another branch::
1220 1220
1221 1221 hg book -f @
1222 1222
1223 1223 - print only the active bookmark name::
1224 1224
1225 1225 hg book -ql .
1226 1226 '''
1227 1227 opts = pycompat.byteskwargs(opts)
1228 1228 force = opts.get(b'force')
1229 1229 rev = opts.get(b'rev')
1230 1230 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1231 1231
1232 1232 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1233 1233 if action:
1234 1234 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1235 1235 elif names or rev:
1236 1236 action = b'add'
1237 1237 elif inactive:
1238 1238 action = b'inactive' # meaning deactivate
1239 1239 else:
1240 1240 action = b'list'
1241 1241
1242 1242 cmdutil.check_incompatible_arguments(
1243 1243 opts, b'inactive', [b'delete', b'list']
1244 1244 )
1245 1245 if not names and action in {b'add', b'delete'}:
1246 1246 raise error.Abort(_(b"bookmark name required"))
1247 1247
1248 1248 if action in {b'add', b'delete', b'rename', b'inactive'}:
1249 1249 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1250 1250 if action == b'delete':
1251 1251 names = pycompat.maplist(repo._bookmarks.expandname, names)
1252 1252 bookmarks.delete(repo, tr, names)
1253 1253 elif action == b'rename':
1254 1254 if not names:
1255 1255 raise error.Abort(_(b"new bookmark name required"))
1256 1256 elif len(names) > 1:
1257 1257 raise error.Abort(_(b"only one new bookmark name allowed"))
1258 1258 oldname = repo._bookmarks.expandname(opts[b'rename'])
1259 1259 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1260 1260 elif action == b'add':
1261 1261 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1262 1262 elif action == b'inactive':
1263 1263 if len(repo._bookmarks) == 0:
1264 1264 ui.status(_(b"no bookmarks set\n"))
1265 1265 elif not repo._activebookmark:
1266 1266 ui.status(_(b"no active bookmark\n"))
1267 1267 else:
1268 1268 bookmarks.deactivate(repo)
1269 1269 elif action == b'list':
1270 1270 names = pycompat.maplist(repo._bookmarks.expandname, names)
1271 1271 with ui.formatter(b'bookmarks', opts) as fm:
1272 1272 bookmarks.printbookmarks(ui, repo, fm, names)
1273 1273 else:
1274 1274 raise error.ProgrammingError(b'invalid action: %s' % action)
1275 1275
1276 1276
1277 1277 @command(
1278 1278 b'branch',
1279 1279 [
1280 1280 (
1281 1281 b'f',
1282 1282 b'force',
1283 1283 None,
1284 1284 _(b'set branch name even if it shadows an existing branch'),
1285 1285 ),
1286 1286 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1287 1287 (
1288 1288 b'r',
1289 1289 b'rev',
1290 1290 [],
1291 1291 _(b'change branches of the given revs (EXPERIMENTAL)'),
1292 1292 ),
1293 1293 ],
1294 1294 _(b'[-fC] [NAME]'),
1295 1295 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1296 1296 )
1297 1297 def branch(ui, repo, label=None, **opts):
1298 1298 """set or show the current branch name
1299 1299
1300 1300 .. note::
1301 1301
1302 1302 Branch names are permanent and global. Use :hg:`bookmark` to create a
1303 1303 light-weight bookmark instead. See :hg:`help glossary` for more
1304 1304 information about named branches and bookmarks.
1305 1305
1306 1306 With no argument, show the current branch name. With one argument,
1307 1307 set the working directory branch name (the branch will not exist
1308 1308 in the repository until the next commit). Standard practice
1309 1309 recommends that primary development take place on the 'default'
1310 1310 branch.
1311 1311
1312 1312 Unless -f/--force is specified, branch will not let you set a
1313 1313 branch name that already exists.
1314 1314
1315 1315 Use -C/--clean to reset the working directory branch to that of
1316 1316 the parent of the working directory, negating a previous branch
1317 1317 change.
1318 1318
1319 1319 Use the command :hg:`update` to switch to an existing branch. Use
1320 1320 :hg:`commit --close-branch` to mark this branch head as closed.
1321 1321 When all heads of a branch are closed, the branch will be
1322 1322 considered closed.
1323 1323
1324 1324 Returns 0 on success.
1325 1325 """
1326 1326 opts = pycompat.byteskwargs(opts)
1327 1327 revs = opts.get(b'rev')
1328 1328 if label:
1329 1329 label = label.strip()
1330 1330
1331 1331 if not opts.get(b'clean') and not label:
1332 1332 if revs:
1333 1333 raise error.Abort(_(b"no branch name specified for the revisions"))
1334 1334 ui.write(b"%s\n" % repo.dirstate.branch())
1335 1335 return
1336 1336
1337 1337 with repo.wlock():
1338 1338 if opts.get(b'clean'):
1339 1339 label = repo[b'.'].branch()
1340 1340 repo.dirstate.setbranch(label)
1341 1341 ui.status(_(b'reset working directory to branch %s\n') % label)
1342 1342 elif label:
1343 1343
1344 1344 scmutil.checknewlabel(repo, label, b'branch')
1345 1345 if revs:
1346 1346 return cmdutil.changebranch(ui, repo, revs, label, opts)
1347 1347
1348 1348 if not opts.get(b'force') and label in repo.branchmap():
1349 1349 if label not in [p.branch() for p in repo[None].parents()]:
1350 1350 raise error.Abort(
1351 1351 _(b'a branch of the same name already exists'),
1352 1352 # i18n: "it" refers to an existing branch
1353 1353 hint=_(b"use 'hg update' to switch to it"),
1354 1354 )
1355 1355
1356 1356 repo.dirstate.setbranch(label)
1357 1357 ui.status(_(b'marked working directory as branch %s\n') % label)
1358 1358
1359 1359 # find any open named branches aside from default
1360 1360 for n, h, t, c in repo.branchmap().iterbranches():
1361 1361 if n != b"default" and not c:
1362 1362 return 0
1363 1363 ui.status(
1364 1364 _(
1365 1365 b'(branches are permanent and global, '
1366 1366 b'did you want a bookmark?)\n'
1367 1367 )
1368 1368 )
1369 1369
1370 1370
1371 1371 @command(
1372 1372 b'branches',
1373 1373 [
1374 1374 (
1375 1375 b'a',
1376 1376 b'active',
1377 1377 False,
1378 1378 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1379 1379 ),
1380 1380 (b'c', b'closed', False, _(b'show normal and closed branches')),
1381 1381 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1382 1382 ]
1383 1383 + formatteropts,
1384 1384 _(b'[-c]'),
1385 1385 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1386 1386 intents={INTENT_READONLY},
1387 1387 )
1388 1388 def branches(ui, repo, active=False, closed=False, **opts):
1389 1389 """list repository named branches
1390 1390
1391 1391 List the repository's named branches, indicating which ones are
1392 1392 inactive. If -c/--closed is specified, also list branches which have
1393 1393 been marked closed (see :hg:`commit --close-branch`).
1394 1394
1395 1395 Use the command :hg:`update` to switch to an existing branch.
1396 1396
1397 1397 .. container:: verbose
1398 1398
1399 1399 Template:
1400 1400
1401 1401 The following keywords are supported in addition to the common template
1402 1402 keywords and functions such as ``{branch}``. See also
1403 1403 :hg:`help templates`.
1404 1404
1405 1405 :active: Boolean. True if the branch is active.
1406 1406 :closed: Boolean. True if the branch is closed.
1407 1407 :current: Boolean. True if it is the current branch.
1408 1408
1409 1409 Returns 0.
1410 1410 """
1411 1411
1412 1412 opts = pycompat.byteskwargs(opts)
1413 1413 revs = opts.get(b'rev')
1414 1414 selectedbranches = None
1415 1415 if revs:
1416 1416 revs = scmutil.revrange(repo, revs)
1417 1417 getbi = repo.revbranchcache().branchinfo
1418 1418 selectedbranches = {getbi(r)[0] for r in revs}
1419 1419
1420 1420 ui.pager(b'branches')
1421 1421 fm = ui.formatter(b'branches', opts)
1422 1422 hexfunc = fm.hexfunc
1423 1423
1424 1424 allheads = set(repo.heads())
1425 1425 branches = []
1426 1426 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1427 1427 if selectedbranches is not None and tag not in selectedbranches:
1428 1428 continue
1429 1429 isactive = False
1430 1430 if not isclosed:
1431 1431 openheads = set(repo.branchmap().iteropen(heads))
1432 1432 isactive = bool(openheads & allheads)
1433 1433 branches.append((tag, repo[tip], isactive, not isclosed))
1434 1434 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1435 1435
1436 1436 for tag, ctx, isactive, isopen in branches:
1437 1437 if active and not isactive:
1438 1438 continue
1439 1439 if isactive:
1440 1440 label = b'branches.active'
1441 1441 notice = b''
1442 1442 elif not isopen:
1443 1443 if not closed:
1444 1444 continue
1445 1445 label = b'branches.closed'
1446 1446 notice = _(b' (closed)')
1447 1447 else:
1448 1448 label = b'branches.inactive'
1449 1449 notice = _(b' (inactive)')
1450 1450 current = tag == repo.dirstate.branch()
1451 1451 if current:
1452 1452 label = b'branches.current'
1453 1453
1454 1454 fm.startitem()
1455 1455 fm.write(b'branch', b'%s', tag, label=label)
1456 1456 rev = ctx.rev()
1457 1457 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1458 1458 fmt = b' ' * padsize + b' %d:%s'
1459 1459 fm.condwrite(
1460 1460 not ui.quiet,
1461 1461 b'rev node',
1462 1462 fmt,
1463 1463 rev,
1464 1464 hexfunc(ctx.node()),
1465 1465 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1466 1466 )
1467 1467 fm.context(ctx=ctx)
1468 1468 fm.data(active=isactive, closed=not isopen, current=current)
1469 1469 if not ui.quiet:
1470 1470 fm.plain(notice)
1471 1471 fm.plain(b'\n')
1472 1472 fm.end()
1473 1473
1474 1474
1475 1475 @command(
1476 1476 b'bundle',
1477 1477 [
1478 1478 (
1479 1479 b'f',
1480 1480 b'force',
1481 1481 None,
1482 1482 _(b'run even when the destination is unrelated'),
1483 1483 ),
1484 1484 (
1485 1485 b'r',
1486 1486 b'rev',
1487 1487 [],
1488 1488 _(b'a changeset intended to be added to the destination'),
1489 1489 _(b'REV'),
1490 1490 ),
1491 1491 (
1492 1492 b'b',
1493 1493 b'branch',
1494 1494 [],
1495 1495 _(b'a specific branch you would like to bundle'),
1496 1496 _(b'BRANCH'),
1497 1497 ),
1498 1498 (
1499 1499 b'',
1500 1500 b'base',
1501 1501 [],
1502 1502 _(b'a base changeset assumed to be available at the destination'),
1503 1503 _(b'REV'),
1504 1504 ),
1505 1505 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1506 1506 (
1507 1507 b't',
1508 1508 b'type',
1509 1509 b'bzip2',
1510 1510 _(b'bundle compression type to use'),
1511 1511 _(b'TYPE'),
1512 1512 ),
1513 1513 ]
1514 1514 + remoteopts,
1515 1515 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1516 1516 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1517 1517 )
1518 1518 def bundle(ui, repo, fname, dest=None, **opts):
1519 1519 """create a bundle file
1520 1520
1521 1521 Generate a bundle file containing data to be transferred to another
1522 1522 repository.
1523 1523
1524 1524 To create a bundle containing all changesets, use -a/--all
1525 1525 (or --base null). Otherwise, hg assumes the destination will have
1526 1526 all the nodes you specify with --base parameters. Otherwise, hg
1527 1527 will assume the repository has all the nodes in destination, or
1528 1528 default-push/default if no destination is specified, where destination
1529 1529 is the repository you provide through DEST option.
1530 1530
1531 1531 You can change bundle format with the -t/--type option. See
1532 1532 :hg:`help bundlespec` for documentation on this format. By default,
1533 1533 the most appropriate format is used and compression defaults to
1534 1534 bzip2.
1535 1535
1536 1536 The bundle file can then be transferred using conventional means
1537 1537 and applied to another repository with the unbundle or pull
1538 1538 command. This is useful when direct push and pull are not
1539 1539 available or when exporting an entire repository is undesirable.
1540 1540
1541 1541 Applying bundles preserves all changeset contents including
1542 1542 permissions, copy/rename information, and revision history.
1543 1543
1544 1544 Returns 0 on success, 1 if no changes found.
1545 1545 """
1546 1546 opts = pycompat.byteskwargs(opts)
1547 1547 revs = None
1548 1548 if b'rev' in opts:
1549 1549 revstrings = opts[b'rev']
1550 1550 revs = scmutil.revrange(repo, revstrings)
1551 1551 if revstrings and not revs:
1552 1552 raise error.Abort(_(b'no commits to bundle'))
1553 1553
1554 1554 bundletype = opts.get(b'type', b'bzip2').lower()
1555 1555 try:
1556 1556 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1557 1557 except error.UnsupportedBundleSpecification as e:
1558 1558 raise error.Abort(
1559 1559 pycompat.bytestr(e),
1560 1560 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1561 1561 )
1562 1562 cgversion = bundlespec.contentopts[b"cg.version"]
1563 1563
1564 1564 # Packed bundles are a pseudo bundle format for now.
1565 1565 if cgversion == b's1':
1566 1566 raise error.Abort(
1567 1567 _(b'packed bundles cannot be produced by "hg bundle"'),
1568 1568 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1569 1569 )
1570 1570
1571 1571 if opts.get(b'all'):
1572 1572 if dest:
1573 1573 raise error.Abort(
1574 1574 _(b"--all is incompatible with specifying a destination")
1575 1575 )
1576 1576 if opts.get(b'base'):
1577 1577 ui.warn(_(b"ignoring --base because --all was specified\n"))
1578 1578 base = [nullrev]
1579 1579 else:
1580 1580 base = scmutil.revrange(repo, opts.get(b'base'))
1581 1581 if cgversion not in changegroup.supportedoutgoingversions(repo):
1582 1582 raise error.Abort(
1583 1583 _(b"repository does not support bundle version %s") % cgversion
1584 1584 )
1585 1585
1586 1586 if base:
1587 1587 if dest:
1588 1588 raise error.Abort(
1589 1589 _(b"--base is incompatible with specifying a destination")
1590 1590 )
1591 1591 common = [repo[rev].node() for rev in base]
1592 1592 heads = [repo[r].node() for r in revs] if revs else None
1593 1593 outgoing = discovery.outgoing(repo, common, heads)
1594 1594 else:
1595 1595 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1596 1596 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1597 1597 other = hg.peer(repo, opts, dest)
1598 1598 revs = [repo[r].hex() for r in revs]
1599 1599 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1600 1600 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1601 1601 outgoing = discovery.findcommonoutgoing(
1602 1602 repo,
1603 1603 other,
1604 1604 onlyheads=heads,
1605 1605 force=opts.get(b'force'),
1606 1606 portable=True,
1607 1607 )
1608 1608
1609 1609 if not outgoing.missing:
1610 1610 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1611 1611 return 1
1612 1612
1613 1613 if cgversion == b'01': # bundle1
1614 1614 bversion = b'HG10' + bundlespec.wirecompression
1615 1615 bcompression = None
1616 1616 elif cgversion in (b'02', b'03'):
1617 1617 bversion = b'HG20'
1618 1618 bcompression = bundlespec.wirecompression
1619 1619 else:
1620 1620 raise error.ProgrammingError(
1621 1621 b'bundle: unexpected changegroup version %s' % cgversion
1622 1622 )
1623 1623
1624 1624 # TODO compression options should be derived from bundlespec parsing.
1625 1625 # This is a temporary hack to allow adjusting bundle compression
1626 1626 # level without a) formalizing the bundlespec changes to declare it
1627 1627 # b) introducing a command flag.
1628 1628 compopts = {}
1629 1629 complevel = ui.configint(
1630 1630 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1631 1631 )
1632 1632 if complevel is None:
1633 1633 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1634 1634 if complevel is not None:
1635 1635 compopts[b'level'] = complevel
1636 1636
1637 1637 # Allow overriding the bundling of obsmarker in phases through
1638 1638 # configuration while we don't have a bundle version that include them
1639 1639 if repo.ui.configbool(b'experimental', b'evolution.bundle-obsmarker'):
1640 1640 bundlespec.contentopts[b'obsolescence'] = True
1641 1641 if repo.ui.configbool(b'experimental', b'bundle-phases'):
1642 1642 bundlespec.contentopts[b'phases'] = True
1643 1643
1644 1644 bundle2.writenewbundle(
1645 1645 ui,
1646 1646 repo,
1647 1647 b'bundle',
1648 1648 fname,
1649 1649 bversion,
1650 1650 outgoing,
1651 1651 bundlespec.contentopts,
1652 1652 compression=bcompression,
1653 1653 compopts=compopts,
1654 1654 )
1655 1655
1656 1656
1657 1657 @command(
1658 1658 b'cat',
1659 1659 [
1660 1660 (
1661 1661 b'o',
1662 1662 b'output',
1663 1663 b'',
1664 1664 _(b'print output to file with formatted name'),
1665 1665 _(b'FORMAT'),
1666 1666 ),
1667 1667 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1668 1668 (b'', b'decode', None, _(b'apply any matching decode filter')),
1669 1669 ]
1670 1670 + walkopts
1671 1671 + formatteropts,
1672 1672 _(b'[OPTION]... FILE...'),
1673 1673 helpcategory=command.CATEGORY_FILE_CONTENTS,
1674 1674 inferrepo=True,
1675 1675 intents={INTENT_READONLY},
1676 1676 )
1677 1677 def cat(ui, repo, file1, *pats, **opts):
1678 1678 """output the current or given revision of files
1679 1679
1680 1680 Print the specified files as they were at the given revision. If
1681 1681 no revision is given, the parent of the working directory is used.
1682 1682
1683 1683 Output may be to a file, in which case the name of the file is
1684 1684 given using a template string. See :hg:`help templates`. In addition
1685 1685 to the common template keywords, the following formatting rules are
1686 1686 supported:
1687 1687
1688 1688 :``%%``: literal "%" character
1689 1689 :``%s``: basename of file being printed
1690 1690 :``%d``: dirname of file being printed, or '.' if in repository root
1691 1691 :``%p``: root-relative path name of file being printed
1692 1692 :``%H``: changeset hash (40 hexadecimal digits)
1693 1693 :``%R``: changeset revision number
1694 1694 :``%h``: short-form changeset hash (12 hexadecimal digits)
1695 1695 :``%r``: zero-padded changeset revision number
1696 1696 :``%b``: basename of the exporting repository
1697 1697 :``\\``: literal "\\" character
1698 1698
1699 1699 .. container:: verbose
1700 1700
1701 1701 Template:
1702 1702
1703 1703 The following keywords are supported in addition to the common template
1704 1704 keywords and functions. See also :hg:`help templates`.
1705 1705
1706 1706 :data: String. File content.
1707 1707 :path: String. Repository-absolute path of the file.
1708 1708
1709 1709 Returns 0 on success.
1710 1710 """
1711 1711 opts = pycompat.byteskwargs(opts)
1712 1712 rev = opts.get(b'rev')
1713 1713 if rev:
1714 1714 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1715 1715 ctx = scmutil.revsingle(repo, rev)
1716 1716 m = scmutil.match(ctx, (file1,) + pats, opts)
1717 1717 fntemplate = opts.pop(b'output', b'')
1718 1718 if cmdutil.isstdiofilename(fntemplate):
1719 1719 fntemplate = b''
1720 1720
1721 1721 if fntemplate:
1722 1722 fm = formatter.nullformatter(ui, b'cat', opts)
1723 1723 else:
1724 1724 ui.pager(b'cat')
1725 1725 fm = ui.formatter(b'cat', opts)
1726 1726 with fm:
1727 1727 return cmdutil.cat(
1728 1728 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1729 1729 )
1730 1730
1731 1731
1732 1732 @command(
1733 1733 b'clone',
1734 1734 [
1735 1735 (
1736 1736 b'U',
1737 1737 b'noupdate',
1738 1738 None,
1739 1739 _(
1740 1740 b'the clone will include an empty working '
1741 1741 b'directory (only a repository)'
1742 1742 ),
1743 1743 ),
1744 1744 (
1745 1745 b'u',
1746 1746 b'updaterev',
1747 1747 b'',
1748 1748 _(b'revision, tag, or branch to check out'),
1749 1749 _(b'REV'),
1750 1750 ),
1751 1751 (
1752 1752 b'r',
1753 1753 b'rev',
1754 1754 [],
1755 1755 _(
1756 1756 b'do not clone everything, but include this changeset'
1757 1757 b' and its ancestors'
1758 1758 ),
1759 1759 _(b'REV'),
1760 1760 ),
1761 1761 (
1762 1762 b'b',
1763 1763 b'branch',
1764 1764 [],
1765 1765 _(
1766 1766 b'do not clone everything, but include this branch\'s'
1767 1767 b' changesets and their ancestors'
1768 1768 ),
1769 1769 _(b'BRANCH'),
1770 1770 ),
1771 1771 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1772 1772 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1773 1773 (b'', b'stream', None, _(b'clone with minimal data processing')),
1774 1774 ]
1775 1775 + remoteopts,
1776 1776 _(b'[OPTION]... SOURCE [DEST]'),
1777 1777 helpcategory=command.CATEGORY_REPO_CREATION,
1778 1778 helpbasic=True,
1779 1779 norepo=True,
1780 1780 )
1781 1781 def clone(ui, source, dest=None, **opts):
1782 1782 """make a copy of an existing repository
1783 1783
1784 1784 Create a copy of an existing repository in a new directory.
1785 1785
1786 1786 If no destination directory name is specified, it defaults to the
1787 1787 basename of the source.
1788 1788
1789 1789 The location of the source is added to the new repository's
1790 1790 ``.hg/hgrc`` file, as the default to be used for future pulls.
1791 1791
1792 1792 Only local paths and ``ssh://`` URLs are supported as
1793 1793 destinations. For ``ssh://`` destinations, no working directory or
1794 1794 ``.hg/hgrc`` will be created on the remote side.
1795 1795
1796 1796 If the source repository has a bookmark called '@' set, that
1797 1797 revision will be checked out in the new repository by default.
1798 1798
1799 1799 To check out a particular version, use -u/--update, or
1800 1800 -U/--noupdate to create a clone with no working directory.
1801 1801
1802 1802 To pull only a subset of changesets, specify one or more revisions
1803 1803 identifiers with -r/--rev or branches with -b/--branch. The
1804 1804 resulting clone will contain only the specified changesets and
1805 1805 their ancestors. These options (or 'clone src#rev dest') imply
1806 1806 --pull, even for local source repositories.
1807 1807
1808 1808 In normal clone mode, the remote normalizes repository data into a common
1809 1809 exchange format and the receiving end translates this data into its local
1810 1810 storage format. --stream activates a different clone mode that essentially
1811 1811 copies repository files from the remote with minimal data processing. This
1812 1812 significantly reduces the CPU cost of a clone both remotely and locally.
1813 1813 However, it often increases the transferred data size by 30-40%. This can
1814 1814 result in substantially faster clones where I/O throughput is plentiful,
1815 1815 especially for larger repositories. A side-effect of --stream clones is
1816 1816 that storage settings and requirements on the remote are applied locally:
1817 1817 a modern client may inherit legacy or inefficient storage used by the
1818 1818 remote or a legacy Mercurial client may not be able to clone from a
1819 1819 modern Mercurial remote.
1820 1820
1821 1821 .. note::
1822 1822
1823 1823 Specifying a tag will include the tagged changeset but not the
1824 1824 changeset containing the tag.
1825 1825
1826 1826 .. container:: verbose
1827 1827
1828 1828 For efficiency, hardlinks are used for cloning whenever the
1829 1829 source and destination are on the same filesystem (note this
1830 1830 applies only to the repository data, not to the working
1831 1831 directory). Some filesystems, such as AFS, implement hardlinking
1832 1832 incorrectly, but do not report errors. In these cases, use the
1833 1833 --pull option to avoid hardlinking.
1834 1834
1835 1835 Mercurial will update the working directory to the first applicable
1836 1836 revision from this list:
1837 1837
1838 1838 a) null if -U or the source repository has no changesets
1839 1839 b) if -u . and the source repository is local, the first parent of
1840 1840 the source repository's working directory
1841 1841 c) the changeset specified with -u (if a branch name, this means the
1842 1842 latest head of that branch)
1843 1843 d) the changeset specified with -r
1844 1844 e) the tipmost head specified with -b
1845 1845 f) the tipmost head specified with the url#branch source syntax
1846 1846 g) the revision marked with the '@' bookmark, if present
1847 1847 h) the tipmost head of the default branch
1848 1848 i) tip
1849 1849
1850 1850 When cloning from servers that support it, Mercurial may fetch
1851 1851 pre-generated data from a server-advertised URL or inline from the
1852 1852 same stream. When this is done, hooks operating on incoming changesets
1853 1853 and changegroups may fire more than once, once for each pre-generated
1854 1854 bundle and as well as for any additional remaining data. In addition,
1855 1855 if an error occurs, the repository may be rolled back to a partial
1856 1856 clone. This behavior may change in future releases.
1857 1857 See :hg:`help -e clonebundles` for more.
1858 1858
1859 1859 Examples:
1860 1860
1861 1861 - clone a remote repository to a new directory named hg/::
1862 1862
1863 1863 hg clone https://www.mercurial-scm.org/repo/hg/
1864 1864
1865 1865 - create a lightweight local clone::
1866 1866
1867 1867 hg clone project/ project-feature/
1868 1868
1869 1869 - clone from an absolute path on an ssh server (note double-slash)::
1870 1870
1871 1871 hg clone ssh://user@server//home/projects/alpha/
1872 1872
1873 1873 - do a streaming clone while checking out a specified version::
1874 1874
1875 1875 hg clone --stream http://server/repo -u 1.5
1876 1876
1877 1877 - create a repository without changesets after a particular revision::
1878 1878
1879 1879 hg clone -r 04e544 experimental/ good/
1880 1880
1881 1881 - clone (and track) a particular named branch::
1882 1882
1883 1883 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1884 1884
1885 1885 See :hg:`help urls` for details on specifying URLs.
1886 1886
1887 1887 Returns 0 on success.
1888 1888 """
1889 1889 opts = pycompat.byteskwargs(opts)
1890 1890 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1891 1891
1892 1892 # --include/--exclude can come from narrow or sparse.
1893 1893 includepats, excludepats = None, None
1894 1894
1895 1895 # hg.clone() differentiates between None and an empty set. So make sure
1896 1896 # patterns are sets if narrow is requested without patterns.
1897 1897 if opts.get(b'narrow'):
1898 1898 includepats = set()
1899 1899 excludepats = set()
1900 1900
1901 1901 if opts.get(b'include'):
1902 1902 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1903 1903 if opts.get(b'exclude'):
1904 1904 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1905 1905
1906 1906 r = hg.clone(
1907 1907 ui,
1908 1908 opts,
1909 1909 source,
1910 1910 dest,
1911 1911 pull=opts.get(b'pull'),
1912 1912 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1913 1913 revs=opts.get(b'rev'),
1914 1914 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1915 1915 branch=opts.get(b'branch'),
1916 1916 shareopts=opts.get(b'shareopts'),
1917 1917 storeincludepats=includepats,
1918 1918 storeexcludepats=excludepats,
1919 1919 depth=opts.get(b'depth') or None,
1920 1920 )
1921 1921
1922 1922 return r is None
1923 1923
1924 1924
1925 1925 @command(
1926 1926 b'commit|ci',
1927 1927 [
1928 1928 (
1929 1929 b'A',
1930 1930 b'addremove',
1931 1931 None,
1932 1932 _(b'mark new/missing files as added/removed before committing'),
1933 1933 ),
1934 1934 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1935 1935 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1936 1936 (b's', b'secret', None, _(b'use the secret phase for committing')),
1937 1937 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1938 1938 (
1939 1939 b'',
1940 1940 b'force-close-branch',
1941 1941 None,
1942 1942 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1943 1943 ),
1944 1944 (b'i', b'interactive', None, _(b'use interactive mode')),
1945 1945 ]
1946 1946 + walkopts
1947 1947 + commitopts
1948 1948 + commitopts2
1949 1949 + subrepoopts,
1950 1950 _(b'[OPTION]... [FILE]...'),
1951 1951 helpcategory=command.CATEGORY_COMMITTING,
1952 1952 helpbasic=True,
1953 1953 inferrepo=True,
1954 1954 )
1955 1955 def commit(ui, repo, *pats, **opts):
1956 1956 """commit the specified files or all outstanding changes
1957 1957
1958 1958 Commit changes to the given files into the repository. Unlike a
1959 1959 centralized SCM, this operation is a local operation. See
1960 1960 :hg:`push` for a way to actively distribute your changes.
1961 1961
1962 1962 If a list of files is omitted, all changes reported by :hg:`status`
1963 1963 will be committed.
1964 1964
1965 1965 If you are committing the result of a merge, do not provide any
1966 1966 filenames or -I/-X filters.
1967 1967
1968 1968 If no commit message is specified, Mercurial starts your
1969 1969 configured editor where you can enter a message. In case your
1970 1970 commit fails, you will find a backup of your message in
1971 1971 ``.hg/last-message.txt``.
1972 1972
1973 1973 The --close-branch flag can be used to mark the current branch
1974 1974 head closed. When all heads of a branch are closed, the branch
1975 1975 will be considered closed and no longer listed.
1976 1976
1977 1977 The --amend flag can be used to amend the parent of the
1978 1978 working directory with a new commit that contains the changes
1979 1979 in the parent in addition to those currently reported by :hg:`status`,
1980 1980 if there are any. The old commit is stored in a backup bundle in
1981 1981 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1982 1982 on how to restore it).
1983 1983
1984 1984 Message, user and date are taken from the amended commit unless
1985 1985 specified. When a message isn't specified on the command line,
1986 1986 the editor will open with the message of the amended commit.
1987 1987
1988 1988 It is not possible to amend public changesets (see :hg:`help phases`)
1989 1989 or changesets that have children.
1990 1990
1991 1991 See :hg:`help dates` for a list of formats valid for -d/--date.
1992 1992
1993 1993 Returns 0 on success, 1 if nothing changed.
1994 1994
1995 1995 .. container:: verbose
1996 1996
1997 1997 Examples:
1998 1998
1999 1999 - commit all files ending in .py::
2000 2000
2001 2001 hg commit --include "set:**.py"
2002 2002
2003 2003 - commit all non-binary files::
2004 2004
2005 2005 hg commit --exclude "set:binary()"
2006 2006
2007 2007 - amend the current commit and set the date to now::
2008 2008
2009 2009 hg commit --amend --date now
2010 2010 """
2011 2011 with repo.wlock(), repo.lock():
2012 2012 return _docommit(ui, repo, *pats, **opts)
2013 2013
2014 2014
2015 2015 def _docommit(ui, repo, *pats, **opts):
2016 2016 if opts.get('interactive'):
2017 2017 opts.pop('interactive')
2018 2018 ret = cmdutil.dorecord(
2019 2019 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2020 2020 )
2021 2021 # ret can be 0 (no changes to record) or the value returned by
2022 2022 # commit(), 1 if nothing changed or None on success.
2023 2023 return 1 if ret == 0 else ret
2024 2024
2025 2025 opts = pycompat.byteskwargs(opts)
2026 2026 if opts.get(b'subrepos'):
2027 2027 if opts.get(b'amend'):
2028 2028 raise error.Abort(_(b'cannot amend with --subrepos'))
2029 2029 # Let --subrepos on the command line override config setting.
2030 2030 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2031 2031
2032 2032 cmdutil.checkunfinished(repo, commit=True)
2033 2033
2034 2034 branch = repo[None].branch()
2035 2035 bheads = repo.branchheads(branch)
2036 2036
2037 2037 extra = {}
2038 2038 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2039 2039 extra[b'close'] = b'1'
2040 2040
2041 2041 if repo[b'.'].closesbranch():
2042 2042 raise error.Abort(
2043 2043 _(b'current revision is already a branch closing head')
2044 2044 )
2045 2045 elif not bheads:
2046 2046 raise error.Abort(_(b'branch "%s" has no heads to close') % branch)
2047 2047 elif (
2048 2048 branch == repo[b'.'].branch()
2049 2049 and repo[b'.'].node() not in bheads
2050 2050 and not opts.get(b'force_close_branch')
2051 2051 ):
2052 2052 hint = _(
2053 2053 b'use --force-close-branch to close branch from a non-head'
2054 2054 b' changeset'
2055 2055 )
2056 2056 raise error.Abort(_(b'can only close branch heads'), hint=hint)
2057 2057 elif opts.get(b'amend'):
2058 2058 if (
2059 2059 repo[b'.'].p1().branch() != branch
2060 2060 and repo[b'.'].p2().branch() != branch
2061 2061 ):
2062 2062 raise error.Abort(_(b'can only close branch heads'))
2063 2063
2064 2064 if opts.get(b'amend'):
2065 2065 if ui.configbool(b'ui', b'commitsubrepos'):
2066 2066 raise error.Abort(_(b'cannot amend with ui.commitsubrepos enabled'))
2067 2067
2068 2068 old = repo[b'.']
2069 2069 rewriteutil.precheck(repo, [old.rev()], b'amend')
2070 2070
2071 2071 # Currently histedit gets confused if an amend happens while histedit
2072 2072 # is in progress. Since we have a checkunfinished command, we are
2073 2073 # temporarily honoring it.
2074 2074 #
2075 2075 # Note: eventually this guard will be removed. Please do not expect
2076 2076 # this behavior to remain.
2077 2077 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2078 2078 cmdutil.checkunfinished(repo)
2079 2079
2080 2080 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2081 2081 if node == old.node():
2082 2082 ui.status(_(b"nothing changed\n"))
2083 2083 return 1
2084 2084 else:
2085 2085
2086 2086 def commitfunc(ui, repo, message, match, opts):
2087 2087 overrides = {}
2088 2088 if opts.get(b'secret'):
2089 2089 overrides[(b'phases', b'new-commit')] = b'secret'
2090 2090
2091 2091 baseui = repo.baseui
2092 2092 with baseui.configoverride(overrides, b'commit'):
2093 2093 with ui.configoverride(overrides, b'commit'):
2094 2094 editform = cmdutil.mergeeditform(
2095 2095 repo[None], b'commit.normal'
2096 2096 )
2097 2097 editor = cmdutil.getcommiteditor(
2098 2098 editform=editform, **pycompat.strkwargs(opts)
2099 2099 )
2100 2100 return repo.commit(
2101 2101 message,
2102 2102 opts.get(b'user'),
2103 2103 opts.get(b'date'),
2104 2104 match,
2105 2105 editor=editor,
2106 2106 extra=extra,
2107 2107 )
2108 2108
2109 2109 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2110 2110
2111 2111 if not node:
2112 2112 stat = cmdutil.postcommitstatus(repo, pats, opts)
2113 2113 if stat.deleted:
2114 2114 ui.status(
2115 2115 _(
2116 2116 b"nothing changed (%d missing files, see "
2117 2117 b"'hg status')\n"
2118 2118 )
2119 2119 % len(stat.deleted)
2120 2120 )
2121 2121 else:
2122 2122 ui.status(_(b"nothing changed\n"))
2123 2123 return 1
2124 2124
2125 2125 cmdutil.commitstatus(repo, node, branch, bheads, opts)
2126 2126
2127 2127 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2128 2128 status(
2129 2129 ui,
2130 2130 repo,
2131 2131 modified=True,
2132 2132 added=True,
2133 2133 removed=True,
2134 2134 deleted=True,
2135 2135 unknown=True,
2136 2136 subrepos=opts.get(b'subrepos'),
2137 2137 )
2138 2138
2139 2139
2140 2140 @command(
2141 2141 b'config|showconfig|debugconfig',
2142 2142 [
2143 2143 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2144 2144 (b'e', b'edit', None, _(b'edit user config')),
2145 2145 (b'l', b'local', None, _(b'edit repository config')),
2146 2146 (
2147 2147 b'',
2148 2148 b'shared',
2149 2149 None,
2150 2150 _(b'edit shared source repository config (EXPERIMENTAL)'),
2151 2151 ),
2152 2152 (b'g', b'global', None, _(b'edit global config')),
2153 2153 ]
2154 2154 + formatteropts,
2155 2155 _(b'[-u] [NAME]...'),
2156 2156 helpcategory=command.CATEGORY_HELP,
2157 2157 optionalrepo=True,
2158 2158 intents={INTENT_READONLY},
2159 2159 )
2160 2160 def config(ui, repo, *values, **opts):
2161 2161 """show combined config settings from all hgrc files
2162 2162
2163 2163 With no arguments, print names and values of all config items.
2164 2164
2165 2165 With one argument of the form section.name, print just the value
2166 2166 of that config item.
2167 2167
2168 2168 With multiple arguments, print names and values of all config
2169 2169 items with matching section names or section.names.
2170 2170
2171 2171 With --edit, start an editor on the user-level config file. With
2172 2172 --global, edit the system-wide config file. With --local, edit the
2173 2173 repository-level config file.
2174 2174
2175 2175 With --debug, the source (filename and line number) is printed
2176 2176 for each config item.
2177 2177
2178 2178 See :hg:`help config` for more information about config files.
2179 2179
2180 2180 .. container:: verbose
2181 2181
2182 2182 Template:
2183 2183
2184 2184 The following keywords are supported. See also :hg:`help templates`.
2185 2185
2186 2186 :name: String. Config name.
2187 2187 :source: String. Filename and line number where the item is defined.
2188 2188 :value: String. Config value.
2189 2189
2190 2190 The --shared flag can be used to edit the config file of shared source
2191 2191 repository. It only works when you have shared using the experimental
2192 2192 share safe feature.
2193 2193
2194 2194 Returns 0 on success, 1 if NAME does not exist.
2195 2195
2196 2196 """
2197 2197
2198 2198 opts = pycompat.byteskwargs(opts)
2199 2199 editopts = (b'edit', b'local', b'global', b'shared')
2200 2200 if any(opts.get(o) for o in editopts):
2201 2201 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2202 2202 if opts.get(b'local'):
2203 2203 if not repo:
2204 2204 raise error.Abort(_(b"can't use --local outside a repository"))
2205 2205 paths = [repo.vfs.join(b'hgrc')]
2206 2206 elif opts.get(b'global'):
2207 2207 paths = rcutil.systemrcpath()
2208 2208 elif opts.get(b'shared'):
2209 2209 if not repo.shared():
2210 2210 raise error.Abort(
2211 2211 _(b"repository is not shared; can't use --shared")
2212 2212 )
2213 2213 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2214 2214 raise error.Abort(
2215 2215 _(
2216 2216 b"share safe feature not unabled; "
2217 2217 b"unable to edit shared source repository config"
2218 2218 )
2219 2219 )
2220 2220 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2221 2221 else:
2222 2222 paths = rcutil.userrcpath()
2223 2223
2224 2224 for f in paths:
2225 2225 if os.path.exists(f):
2226 2226 break
2227 2227 else:
2228 2228 if opts.get(b'global'):
2229 2229 samplehgrc = uimod.samplehgrcs[b'global']
2230 2230 elif opts.get(b'local'):
2231 2231 samplehgrc = uimod.samplehgrcs[b'local']
2232 2232 else:
2233 2233 samplehgrc = uimod.samplehgrcs[b'user']
2234 2234
2235 2235 f = paths[0]
2236 2236 fp = open(f, b"wb")
2237 2237 fp.write(util.tonativeeol(samplehgrc))
2238 2238 fp.close()
2239 2239
2240 2240 editor = ui.geteditor()
2241 2241 ui.system(
2242 2242 b"%s \"%s\"" % (editor, f),
2243 2243 onerr=error.Abort,
2244 2244 errprefix=_(b"edit failed"),
2245 2245 blockedtag=b'config_edit',
2246 2246 )
2247 2247 return
2248 2248 ui.pager(b'config')
2249 2249 fm = ui.formatter(b'config', opts)
2250 2250 for t, f in rcutil.rccomponents():
2251 2251 if t == b'path':
2252 2252 ui.debug(b'read config from: %s\n' % f)
2253 2253 elif t == b'resource':
2254 2254 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2255 2255 elif t == b'items':
2256 2256 # Don't print anything for 'items'.
2257 2257 pass
2258 2258 else:
2259 2259 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2260 2260 untrusted = bool(opts.get(b'untrusted'))
2261 2261
2262 2262 selsections = selentries = []
2263 2263 if values:
2264 2264 selsections = [v for v in values if b'.' not in v]
2265 2265 selentries = [v for v in values if b'.' in v]
2266 2266 uniquesel = len(selentries) == 1 and not selsections
2267 2267 selsections = set(selsections)
2268 2268 selentries = set(selentries)
2269 2269
2270 2270 matched = False
2271 2271 for section, name, value in ui.walkconfig(untrusted=untrusted):
2272 2272 source = ui.configsource(section, name, untrusted)
2273 2273 value = pycompat.bytestr(value)
2274 2274 defaultvalue = ui.configdefault(section, name)
2275 2275 if fm.isplain():
2276 2276 source = source or b'none'
2277 2277 value = value.replace(b'\n', b'\\n')
2278 2278 entryname = section + b'.' + name
2279 2279 if values and not (section in selsections or entryname in selentries):
2280 2280 continue
2281 2281 fm.startitem()
2282 2282 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2283 2283 if uniquesel:
2284 2284 fm.data(name=entryname)
2285 2285 fm.write(b'value', b'%s\n', value)
2286 2286 else:
2287 2287 fm.write(b'name value', b'%s=%s\n', entryname, value)
2288 2288 if formatter.isprintable(defaultvalue):
2289 2289 fm.data(defaultvalue=defaultvalue)
2290 2290 elif isinstance(defaultvalue, list) and all(
2291 2291 formatter.isprintable(e) for e in defaultvalue
2292 2292 ):
2293 2293 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2294 2294 # TODO: no idea how to process unsupported defaultvalue types
2295 2295 matched = True
2296 2296 fm.end()
2297 2297 if matched:
2298 2298 return 0
2299 2299 return 1
2300 2300
2301 2301
2302 2302 @command(
2303 2303 b'continue',
2304 2304 dryrunopts,
2305 2305 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2306 2306 helpbasic=True,
2307 2307 )
2308 2308 def continuecmd(ui, repo, **opts):
2309 2309 """resumes an interrupted operation (EXPERIMENTAL)
2310 2310
2311 2311 Finishes a multistep operation like graft, histedit, rebase, merge,
2312 2312 and unshelve if they are in an interrupted state.
2313 2313
2314 2314 use --dry-run/-n to dry run the command.
2315 2315 """
2316 2316 dryrun = opts.get('dry_run')
2317 2317 contstate = cmdutil.getunfinishedstate(repo)
2318 2318 if not contstate:
2319 2319 raise error.Abort(_(b'no operation in progress'))
2320 2320 if not contstate.continuefunc:
2321 2321 raise error.Abort(
2322 2322 (
2323 2323 _(b"%s in progress but does not support 'hg continue'")
2324 2324 % (contstate._opname)
2325 2325 ),
2326 2326 hint=contstate.continuemsg(),
2327 2327 )
2328 2328 if dryrun:
2329 2329 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2330 2330 return
2331 2331 return contstate.continuefunc(ui, repo)
2332 2332
2333 2333
2334 2334 @command(
2335 2335 b'copy|cp',
2336 2336 [
2337 2337 (b'', b'forget', None, _(b'unmark a file as copied')),
2338 2338 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2339 2339 (
2340 2340 b'',
2341 2341 b'at-rev',
2342 2342 b'',
2343 2343 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2344 2344 _(b'REV'),
2345 2345 ),
2346 2346 (
2347 2347 b'f',
2348 2348 b'force',
2349 2349 None,
2350 2350 _(b'forcibly copy over an existing managed file'),
2351 2351 ),
2352 2352 ]
2353 2353 + walkopts
2354 2354 + dryrunopts,
2355 2355 _(b'[OPTION]... SOURCE... DEST'),
2356 2356 helpcategory=command.CATEGORY_FILE_CONTENTS,
2357 2357 )
2358 2358 def copy(ui, repo, *pats, **opts):
2359 2359 """mark files as copied for the next commit
2360 2360
2361 2361 Mark dest as having copies of source files. If dest is a
2362 2362 directory, copies are put in that directory. If dest is a file,
2363 2363 the source must be a single file.
2364 2364
2365 2365 By default, this command copies the contents of files as they
2366 2366 exist in the working directory. If invoked with -A/--after, the
2367 2367 operation is recorded, but no copying is performed.
2368 2368
2369 2369 To undo marking a file as copied, use --forget. With that option,
2370 2370 all given (positional) arguments are unmarked as copies. The destination
2371 2371 file(s) will be left in place (still tracked).
2372 2372
2373 2373 This command takes effect with the next commit by default.
2374 2374
2375 2375 Returns 0 on success, 1 if errors are encountered.
2376 2376 """
2377 2377 opts = pycompat.byteskwargs(opts)
2378 2378 with repo.wlock():
2379 2379 return cmdutil.copy(ui, repo, pats, opts)
2380 2380
2381 2381
2382 2382 @command(
2383 2383 b'debugcommands',
2384 2384 [],
2385 2385 _(b'[COMMAND]'),
2386 2386 helpcategory=command.CATEGORY_HELP,
2387 2387 norepo=True,
2388 2388 )
2389 2389 def debugcommands(ui, cmd=b'', *args):
2390 2390 """list all available commands and options"""
2391 2391 for cmd, vals in sorted(pycompat.iteritems(table)):
2392 2392 cmd = cmd.split(b'|')[0]
2393 2393 opts = b', '.join([i[1] for i in vals[1]])
2394 2394 ui.write(b'%s: %s\n' % (cmd, opts))
2395 2395
2396 2396
2397 2397 @command(
2398 2398 b'debugcomplete',
2399 2399 [(b'o', b'options', None, _(b'show the command options'))],
2400 2400 _(b'[-o] CMD'),
2401 2401 helpcategory=command.CATEGORY_HELP,
2402 2402 norepo=True,
2403 2403 )
2404 2404 def debugcomplete(ui, cmd=b'', **opts):
2405 2405 """returns the completion list associated with the given command"""
2406 2406
2407 2407 if opts.get('options'):
2408 2408 options = []
2409 2409 otables = [globalopts]
2410 2410 if cmd:
2411 2411 aliases, entry = cmdutil.findcmd(cmd, table, False)
2412 2412 otables.append(entry[1])
2413 2413 for t in otables:
2414 2414 for o in t:
2415 2415 if b"(DEPRECATED)" in o[3]:
2416 2416 continue
2417 2417 if o[0]:
2418 2418 options.append(b'-%s' % o[0])
2419 2419 options.append(b'--%s' % o[1])
2420 2420 ui.write(b"%s\n" % b"\n".join(options))
2421 2421 return
2422 2422
2423 2423 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2424 2424 if ui.verbose:
2425 2425 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2426 2426 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2427 2427
2428 2428
2429 2429 @command(
2430 2430 b'diff',
2431 2431 [
2432 2432 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
2433 2433 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2434 2434 ]
2435 2435 + diffopts
2436 2436 + diffopts2
2437 2437 + walkopts
2438 2438 + subrepoopts,
2439 2439 _(b'[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
2440 2440 helpcategory=command.CATEGORY_FILE_CONTENTS,
2441 2441 helpbasic=True,
2442 2442 inferrepo=True,
2443 2443 intents={INTENT_READONLY},
2444 2444 )
2445 2445 def diff(ui, repo, *pats, **opts):
2446 2446 """diff repository (or selected files)
2447 2447
2448 2448 Show differences between revisions for the specified files.
2449 2449
2450 2450 Differences between files are shown using the unified diff format.
2451 2451
2452 2452 .. note::
2453 2453
2454 2454 :hg:`diff` may generate unexpected results for merges, as it will
2455 2455 default to comparing against the working directory's first
2456 2456 parent changeset if no revisions are specified.
2457 2457
2458 2458 When two revision arguments are given, then changes are shown
2459 2459 between those revisions. If only one revision is specified then
2460 2460 that revision is compared to the working directory, and, when no
2461 2461 revisions are specified, the working directory files are compared
2462 2462 to its first parent.
2463 2463
2464 2464 Alternatively you can specify -c/--change with a revision to see
2465 2465 the changes in that changeset relative to its first parent.
2466 2466
2467 2467 Without the -a/--text option, diff will avoid generating diffs of
2468 2468 files it detects as binary. With -a, diff will generate a diff
2469 2469 anyway, probably with undesirable results.
2470 2470
2471 2471 Use the -g/--git option to generate diffs in the git extended diff
2472 2472 format. For more information, read :hg:`help diffs`.
2473 2473
2474 2474 .. container:: verbose
2475 2475
2476 2476 Examples:
2477 2477
2478 2478 - compare a file in the current working directory to its parent::
2479 2479
2480 2480 hg diff foo.c
2481 2481
2482 2482 - compare two historical versions of a directory, with rename info::
2483 2483
2484 2484 hg diff --git -r 1.0:1.2 lib/
2485 2485
2486 2486 - get change stats relative to the last change on some date::
2487 2487
2488 2488 hg diff --stat -r "date('may 2')"
2489 2489
2490 2490 - diff all newly-added files that contain a keyword::
2491 2491
2492 2492 hg diff "set:added() and grep(GNU)"
2493 2493
2494 2494 - compare a revision and its parents::
2495 2495
2496 2496 hg diff -c 9353 # compare against first parent
2497 2497 hg diff -r 9353^:9353 # same using revset syntax
2498 2498 hg diff -r 9353^2:9353 # compare against the second parent
2499 2499
2500 2500 Returns 0 on success.
2501 2501 """
2502 2502
2503 2503 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2504 2504 opts = pycompat.byteskwargs(opts)
2505 2505 revs = opts.get(b'rev')
2506 2506 change = opts.get(b'change')
2507 2507 stat = opts.get(b'stat')
2508 2508 reverse = opts.get(b'reverse')
2509 2509
2510 2510 if change:
2511 2511 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2512 2512 ctx2 = scmutil.revsingle(repo, change, None)
2513 2513 ctx1 = ctx2.p1()
2514 2514 else:
2515 2515 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2516 2516 ctx1, ctx2 = scmutil.revpair(repo, revs)
2517 2517
2518 2518 if reverse:
2519 2519 ctxleft = ctx2
2520 2520 ctxright = ctx1
2521 2521 else:
2522 2522 ctxleft = ctx1
2523 2523 ctxright = ctx2
2524 2524
2525 2525 diffopts = patch.diffallopts(ui, opts)
2526 2526 m = scmutil.match(ctx2, pats, opts)
2527 2527 m = repo.narrowmatch(m)
2528 2528 ui.pager(b'diff')
2529 2529 logcmdutil.diffordiffstat(
2530 2530 ui,
2531 2531 repo,
2532 2532 diffopts,
2533 2533 ctxleft,
2534 2534 ctxright,
2535 2535 m,
2536 2536 stat=stat,
2537 2537 listsubrepos=opts.get(b'subrepos'),
2538 2538 root=opts.get(b'root'),
2539 2539 )
2540 2540
2541 2541
2542 2542 @command(
2543 2543 b'export',
2544 2544 [
2545 2545 (
2546 2546 b'B',
2547 2547 b'bookmark',
2548 2548 b'',
2549 2549 _(b'export changes only reachable by given bookmark'),
2550 2550 _(b'BOOKMARK'),
2551 2551 ),
2552 2552 (
2553 2553 b'o',
2554 2554 b'output',
2555 2555 b'',
2556 2556 _(b'print output to file with formatted name'),
2557 2557 _(b'FORMAT'),
2558 2558 ),
2559 2559 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2560 2560 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2561 2561 ]
2562 2562 + diffopts
2563 2563 + formatteropts,
2564 2564 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2565 2565 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2566 2566 helpbasic=True,
2567 2567 intents={INTENT_READONLY},
2568 2568 )
2569 2569 def export(ui, repo, *changesets, **opts):
2570 2570 """dump the header and diffs for one or more changesets
2571 2571
2572 2572 Print the changeset header and diffs for one or more revisions.
2573 2573 If no revision is given, the parent of the working directory is used.
2574 2574
2575 2575 The information shown in the changeset header is: author, date,
2576 2576 branch name (if non-default), changeset hash, parent(s) and commit
2577 2577 comment.
2578 2578
2579 2579 .. note::
2580 2580
2581 2581 :hg:`export` may generate unexpected diff output for merge
2582 2582 changesets, as it will compare the merge changeset against its
2583 2583 first parent only.
2584 2584
2585 2585 Output may be to a file, in which case the name of the file is
2586 2586 given using a template string. See :hg:`help templates`. In addition
2587 2587 to the common template keywords, the following formatting rules are
2588 2588 supported:
2589 2589
2590 2590 :``%%``: literal "%" character
2591 2591 :``%H``: changeset hash (40 hexadecimal digits)
2592 2592 :``%N``: number of patches being generated
2593 2593 :``%R``: changeset revision number
2594 2594 :``%b``: basename of the exporting repository
2595 2595 :``%h``: short-form changeset hash (12 hexadecimal digits)
2596 2596 :``%m``: first line of the commit message (only alphanumeric characters)
2597 2597 :``%n``: zero-padded sequence number, starting at 1
2598 2598 :``%r``: zero-padded changeset revision number
2599 2599 :``\\``: literal "\\" character
2600 2600
2601 2601 Without the -a/--text option, export will avoid generating diffs
2602 2602 of files it detects as binary. With -a, export will generate a
2603 2603 diff anyway, probably with undesirable results.
2604 2604
2605 2605 With -B/--bookmark changesets reachable by the given bookmark are
2606 2606 selected.
2607 2607
2608 2608 Use the -g/--git option to generate diffs in the git extended diff
2609 2609 format. See :hg:`help diffs` for more information.
2610 2610
2611 2611 With the --switch-parent option, the diff will be against the
2612 2612 second parent. It can be useful to review a merge.
2613 2613
2614 2614 .. container:: verbose
2615 2615
2616 2616 Template:
2617 2617
2618 2618 The following keywords are supported in addition to the common template
2619 2619 keywords and functions. See also :hg:`help templates`.
2620 2620
2621 2621 :diff: String. Diff content.
2622 2622 :parents: List of strings. Parent nodes of the changeset.
2623 2623
2624 2624 Examples:
2625 2625
2626 2626 - use export and import to transplant a bugfix to the current
2627 2627 branch::
2628 2628
2629 2629 hg export -r 9353 | hg import -
2630 2630
2631 2631 - export all the changesets between two revisions to a file with
2632 2632 rename information::
2633 2633
2634 2634 hg export --git -r 123:150 > changes.txt
2635 2635
2636 2636 - split outgoing changes into a series of patches with
2637 2637 descriptive names::
2638 2638
2639 2639 hg export -r "outgoing()" -o "%n-%m.patch"
2640 2640
2641 2641 Returns 0 on success.
2642 2642 """
2643 2643 opts = pycompat.byteskwargs(opts)
2644 2644 bookmark = opts.get(b'bookmark')
2645 2645 changesets += tuple(opts.get(b'rev', []))
2646 2646
2647 2647 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2648 2648
2649 2649 if bookmark:
2650 2650 if bookmark not in repo._bookmarks:
2651 2651 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
2652 2652
2653 2653 revs = scmutil.bookmarkrevs(repo, bookmark)
2654 2654 else:
2655 2655 if not changesets:
2656 2656 changesets = [b'.']
2657 2657
2658 2658 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2659 2659 revs = scmutil.revrange(repo, changesets)
2660 2660
2661 2661 if not revs:
2662 2662 raise error.Abort(_(b"export requires at least one changeset"))
2663 2663 if len(revs) > 1:
2664 2664 ui.note(_(b'exporting patches:\n'))
2665 2665 else:
2666 2666 ui.note(_(b'exporting patch:\n'))
2667 2667
2668 2668 fntemplate = opts.get(b'output')
2669 2669 if cmdutil.isstdiofilename(fntemplate):
2670 2670 fntemplate = b''
2671 2671
2672 2672 if fntemplate:
2673 2673 fm = formatter.nullformatter(ui, b'export', opts)
2674 2674 else:
2675 2675 ui.pager(b'export')
2676 2676 fm = ui.formatter(b'export', opts)
2677 2677 with fm:
2678 2678 cmdutil.export(
2679 2679 repo,
2680 2680 revs,
2681 2681 fm,
2682 2682 fntemplate=fntemplate,
2683 2683 switch_parent=opts.get(b'switch_parent'),
2684 2684 opts=patch.diffallopts(ui, opts),
2685 2685 )
2686 2686
2687 2687
2688 2688 @command(
2689 2689 b'files',
2690 2690 [
2691 2691 (
2692 2692 b'r',
2693 2693 b'rev',
2694 2694 b'',
2695 2695 _(b'search the repository as it is in REV'),
2696 2696 _(b'REV'),
2697 2697 ),
2698 2698 (
2699 2699 b'0',
2700 2700 b'print0',
2701 2701 None,
2702 2702 _(b'end filenames with NUL, for use with xargs'),
2703 2703 ),
2704 2704 ]
2705 2705 + walkopts
2706 2706 + formatteropts
2707 2707 + subrepoopts,
2708 2708 _(b'[OPTION]... [FILE]...'),
2709 2709 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2710 2710 intents={INTENT_READONLY},
2711 2711 )
2712 2712 def files(ui, repo, *pats, **opts):
2713 2713 """list tracked files
2714 2714
2715 2715 Print files under Mercurial control in the working directory or
2716 2716 specified revision for given files (excluding removed files).
2717 2717 Files can be specified as filenames or filesets.
2718 2718
2719 2719 If no files are given to match, this command prints the names
2720 2720 of all files under Mercurial control.
2721 2721
2722 2722 .. container:: verbose
2723 2723
2724 2724 Template:
2725 2725
2726 2726 The following keywords are supported in addition to the common template
2727 2727 keywords and functions. See also :hg:`help templates`.
2728 2728
2729 2729 :flags: String. Character denoting file's symlink and executable bits.
2730 2730 :path: String. Repository-absolute path of the file.
2731 2731 :size: Integer. Size of the file in bytes.
2732 2732
2733 2733 Examples:
2734 2734
2735 2735 - list all files under the current directory::
2736 2736
2737 2737 hg files .
2738 2738
2739 2739 - shows sizes and flags for current revision::
2740 2740
2741 2741 hg files -vr .
2742 2742
2743 2743 - list all files named README::
2744 2744
2745 2745 hg files -I "**/README"
2746 2746
2747 2747 - list all binary files::
2748 2748
2749 2749 hg files "set:binary()"
2750 2750
2751 2751 - find files containing a regular expression::
2752 2752
2753 2753 hg files "set:grep('bob')"
2754 2754
2755 2755 - search tracked file contents with xargs and grep::
2756 2756
2757 2757 hg files -0 | xargs -0 grep foo
2758 2758
2759 2759 See :hg:`help patterns` and :hg:`help filesets` for more information
2760 2760 on specifying file patterns.
2761 2761
2762 2762 Returns 0 if a match is found, 1 otherwise.
2763 2763
2764 2764 """
2765 2765
2766 2766 opts = pycompat.byteskwargs(opts)
2767 2767 rev = opts.get(b'rev')
2768 2768 if rev:
2769 2769 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2770 2770 ctx = scmutil.revsingle(repo, rev, None)
2771 2771
2772 2772 end = b'\n'
2773 2773 if opts.get(b'print0'):
2774 2774 end = b'\0'
2775 2775 fmt = b'%s' + end
2776 2776
2777 2777 m = scmutil.match(ctx, pats, opts)
2778 2778 ui.pager(b'files')
2779 2779 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2780 2780 with ui.formatter(b'files', opts) as fm:
2781 2781 return cmdutil.files(
2782 2782 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2783 2783 )
2784 2784
2785 2785
2786 2786 @command(
2787 2787 b'forget',
2788 2788 [(b'i', b'interactive', None, _(b'use interactive mode')),]
2789 2789 + walkopts
2790 2790 + dryrunopts,
2791 2791 _(b'[OPTION]... FILE...'),
2792 2792 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2793 2793 helpbasic=True,
2794 2794 inferrepo=True,
2795 2795 )
2796 2796 def forget(ui, repo, *pats, **opts):
2797 2797 """forget the specified files on the next commit
2798 2798
2799 2799 Mark the specified files so they will no longer be tracked
2800 2800 after the next commit.
2801 2801
2802 2802 This only removes files from the current branch, not from the
2803 2803 entire project history, and it does not delete them from the
2804 2804 working directory.
2805 2805
2806 2806 To delete the file from the working directory, see :hg:`remove`.
2807 2807
2808 2808 To undo a forget before the next commit, see :hg:`add`.
2809 2809
2810 2810 .. container:: verbose
2811 2811
2812 2812 Examples:
2813 2813
2814 2814 - forget newly-added binary files::
2815 2815
2816 2816 hg forget "set:added() and binary()"
2817 2817
2818 2818 - forget files that would be excluded by .hgignore::
2819 2819
2820 2820 hg forget "set:hgignore()"
2821 2821
2822 2822 Returns 0 on success.
2823 2823 """
2824 2824
2825 2825 opts = pycompat.byteskwargs(opts)
2826 2826 if not pats:
2827 2827 raise error.Abort(_(b'no files specified'))
2828 2828
2829 2829 m = scmutil.match(repo[None], pats, opts)
2830 2830 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2831 2831 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2832 2832 rejected = cmdutil.forget(
2833 2833 ui,
2834 2834 repo,
2835 2835 m,
2836 2836 prefix=b"",
2837 2837 uipathfn=uipathfn,
2838 2838 explicitonly=False,
2839 2839 dryrun=dryrun,
2840 2840 interactive=interactive,
2841 2841 )[0]
2842 2842 return rejected and 1 or 0
2843 2843
2844 2844
2845 2845 @command(
2846 2846 b'graft',
2847 2847 [
2848 2848 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2849 2849 (
2850 2850 b'',
2851 2851 b'base',
2852 2852 b'',
2853 2853 _(b'base revision when doing the graft merge (ADVANCED)'),
2854 2854 _(b'REV'),
2855 2855 ),
2856 2856 (b'c', b'continue', False, _(b'resume interrupted graft')),
2857 2857 (b'', b'stop', False, _(b'stop interrupted graft')),
2858 2858 (b'', b'abort', False, _(b'abort interrupted graft')),
2859 2859 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2860 2860 (b'', b'log', None, _(b'append graft info to log message')),
2861 2861 (
2862 2862 b'',
2863 2863 b'no-commit',
2864 2864 None,
2865 2865 _(b"don't commit, just apply the changes in working directory"),
2866 2866 ),
2867 2867 (b'f', b'force', False, _(b'force graft')),
2868 2868 (
2869 2869 b'D',
2870 2870 b'currentdate',
2871 2871 False,
2872 2872 _(b'record the current date as commit date'),
2873 2873 ),
2874 2874 (
2875 2875 b'U',
2876 2876 b'currentuser',
2877 2877 False,
2878 2878 _(b'record the current user as committer'),
2879 2879 ),
2880 2880 ]
2881 2881 + commitopts2
2882 2882 + mergetoolopts
2883 2883 + dryrunopts,
2884 2884 _(b'[OPTION]... [-r REV]... REV...'),
2885 2885 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2886 2886 )
2887 2887 def graft(ui, repo, *revs, **opts):
2888 2888 '''copy changes from other branches onto the current branch
2889 2889
2890 2890 This command uses Mercurial's merge logic to copy individual
2891 2891 changes from other branches without merging branches in the
2892 2892 history graph. This is sometimes known as 'backporting' or
2893 2893 'cherry-picking'. By default, graft will copy user, date, and
2894 2894 description from the source changesets.
2895 2895
2896 2896 Changesets that are ancestors of the current revision, that have
2897 2897 already been grafted, or that are merges will be skipped.
2898 2898
2899 2899 If --log is specified, log messages will have a comment appended
2900 2900 of the form::
2901 2901
2902 2902 (grafted from CHANGESETHASH)
2903 2903
2904 2904 If --force is specified, revisions will be grafted even if they
2905 2905 are already ancestors of, or have been grafted to, the destination.
2906 2906 This is useful when the revisions have since been backed out.
2907 2907
2908 2908 If a graft merge results in conflicts, the graft process is
2909 2909 interrupted so that the current merge can be manually resolved.
2910 2910 Once all conflicts are addressed, the graft process can be
2911 2911 continued with the -c/--continue option.
2912 2912
2913 2913 The -c/--continue option reapplies all the earlier options.
2914 2914
2915 2915 .. container:: verbose
2916 2916
2917 2917 The --base option exposes more of how graft internally uses merge with a
2918 2918 custom base revision. --base can be used to specify another ancestor than
2919 2919 the first and only parent.
2920 2920
2921 2921 The command::
2922 2922
2923 2923 hg graft -r 345 --base 234
2924 2924
2925 2925 is thus pretty much the same as::
2926 2926
2927 2927 hg diff -r 234 -r 345 | hg import
2928 2928
2929 2929 but using merge to resolve conflicts and track moved files.
2930 2930
2931 2931 The result of a merge can thus be backported as a single commit by
2932 2932 specifying one of the merge parents as base, and thus effectively
2933 2933 grafting the changes from the other side.
2934 2934
2935 2935 It is also possible to collapse multiple changesets and clean up history
2936 2936 by specifying another ancestor as base, much like rebase --collapse
2937 2937 --keep.
2938 2938
2939 2939 The commit message can be tweaked after the fact using commit --amend .
2940 2940
2941 2941 For using non-ancestors as the base to backout changes, see the backout
2942 2942 command and the hidden --parent option.
2943 2943
2944 2944 .. container:: verbose
2945 2945
2946 2946 Examples:
2947 2947
2948 2948 - copy a single change to the stable branch and edit its description::
2949 2949
2950 2950 hg update stable
2951 2951 hg graft --edit 9393
2952 2952
2953 2953 - graft a range of changesets with one exception, updating dates::
2954 2954
2955 2955 hg graft -D "2085::2093 and not 2091"
2956 2956
2957 2957 - continue a graft after resolving conflicts::
2958 2958
2959 2959 hg graft -c
2960 2960
2961 2961 - show the source of a grafted changeset::
2962 2962
2963 2963 hg log --debug -r .
2964 2964
2965 2965 - show revisions sorted by date::
2966 2966
2967 2967 hg log -r "sort(all(), date)"
2968 2968
2969 2969 - backport the result of a merge as a single commit::
2970 2970
2971 2971 hg graft -r 123 --base 123^
2972 2972
2973 2973 - land a feature branch as one changeset::
2974 2974
2975 2975 hg up -cr default
2976 2976 hg graft -r featureX --base "ancestor('featureX', 'default')"
2977 2977
2978 2978 See :hg:`help revisions` for more about specifying revisions.
2979 2979
2980 2980 Returns 0 on successful completion, 1 if there are unresolved files.
2981 2981 '''
2982 2982 with repo.wlock():
2983 2983 return _dograft(ui, repo, *revs, **opts)
2984 2984
2985 2985
2986 2986 def _dograft(ui, repo, *revs, **opts):
2987 2987 opts = pycompat.byteskwargs(opts)
2988 2988 if revs and opts.get(b'rev'):
2989 2989 ui.warn(
2990 2990 _(
2991 2991 b'warning: inconsistent use of --rev might give unexpected '
2992 2992 b'revision ordering!\n'
2993 2993 )
2994 2994 )
2995 2995
2996 2996 revs = list(revs)
2997 2997 revs.extend(opts.get(b'rev'))
2998 2998 # a dict of data to be stored in state file
2999 2999 statedata = {}
3000 3000 # list of new nodes created by ongoing graft
3001 3001 statedata[b'newnodes'] = []
3002 3002
3003 3003 cmdutil.resolvecommitoptions(ui, opts)
3004 3004
3005 3005 editor = cmdutil.getcommiteditor(
3006 3006 editform=b'graft', **pycompat.strkwargs(opts)
3007 3007 )
3008 3008
3009 3009 cmdutil.check_at_most_one_arg(opts, b'abort', b'stop', b'continue')
3010 3010
3011 3011 cont = False
3012 3012 if opts.get(b'no_commit'):
3013 3013 cmdutil.check_incompatible_arguments(
3014 3014 opts,
3015 3015 b'no_commit',
3016 3016 [b'edit', b'currentuser', b'currentdate', b'log'],
3017 3017 )
3018 3018
3019 3019 graftstate = statemod.cmdstate(repo, b'graftstate')
3020 3020
3021 3021 if opts.get(b'stop'):
3022 3022 cmdutil.check_incompatible_arguments(
3023 3023 opts,
3024 3024 b'stop',
3025 3025 [
3026 3026 b'edit',
3027 3027 b'log',
3028 3028 b'user',
3029 3029 b'date',
3030 3030 b'currentdate',
3031 3031 b'currentuser',
3032 3032 b'rev',
3033 3033 ],
3034 3034 )
3035 3035 return _stopgraft(ui, repo, graftstate)
3036 3036 elif opts.get(b'abort'):
3037 3037 cmdutil.check_incompatible_arguments(
3038 3038 opts,
3039 3039 b'abort',
3040 3040 [
3041 3041 b'edit',
3042 3042 b'log',
3043 3043 b'user',
3044 3044 b'date',
3045 3045 b'currentdate',
3046 3046 b'currentuser',
3047 3047 b'rev',
3048 3048 ],
3049 3049 )
3050 3050 return cmdutil.abortgraft(ui, repo, graftstate)
3051 3051 elif opts.get(b'continue'):
3052 3052 cont = True
3053 3053 if revs:
3054 3054 raise error.Abort(_(b"can't specify --continue and revisions"))
3055 3055 # read in unfinished revisions
3056 3056 if graftstate.exists():
3057 3057 statedata = cmdutil.readgraftstate(repo, graftstate)
3058 3058 if statedata.get(b'date'):
3059 3059 opts[b'date'] = statedata[b'date']
3060 3060 if statedata.get(b'user'):
3061 3061 opts[b'user'] = statedata[b'user']
3062 3062 if statedata.get(b'log'):
3063 3063 opts[b'log'] = True
3064 3064 if statedata.get(b'no_commit'):
3065 3065 opts[b'no_commit'] = statedata.get(b'no_commit')
3066 3066 if statedata.get(b'base'):
3067 3067 opts[b'base'] = statedata.get(b'base')
3068 3068 nodes = statedata[b'nodes']
3069 3069 revs = [repo[node].rev() for node in nodes]
3070 3070 else:
3071 3071 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3072 3072 else:
3073 3073 if not revs:
3074 3074 raise error.Abort(_(b'no revisions specified'))
3075 3075 cmdutil.checkunfinished(repo)
3076 3076 cmdutil.bailifchanged(repo)
3077 3077 revs = scmutil.revrange(repo, revs)
3078 3078
3079 3079 skipped = set()
3080 3080 basectx = None
3081 3081 if opts.get(b'base'):
3082 3082 basectx = scmutil.revsingle(repo, opts[b'base'], None)
3083 3083 if basectx is None:
3084 3084 # check for merges
3085 3085 for rev in repo.revs(b'%ld and merge()', revs):
3086 3086 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3087 3087 skipped.add(rev)
3088 3088 revs = [r for r in revs if r not in skipped]
3089 3089 if not revs:
3090 3090 return -1
3091 3091 if basectx is not None and len(revs) != 1:
3092 3092 raise error.Abort(_(b'only one revision allowed with --base '))
3093 3093
3094 3094 # Don't check in the --continue case, in effect retaining --force across
3095 3095 # --continues. That's because without --force, any revisions we decided to
3096 3096 # skip would have been filtered out here, so they wouldn't have made their
3097 3097 # way to the graftstate. With --force, any revisions we would have otherwise
3098 3098 # skipped would not have been filtered out, and if they hadn't been applied
3099 3099 # already, they'd have been in the graftstate.
3100 3100 if not (cont or opts.get(b'force')) and basectx is None:
3101 3101 # check for ancestors of dest branch
3102 3102 ancestors = repo.revs(b'%ld & (::.)', revs)
3103 3103 for rev in ancestors:
3104 3104 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3105 3105
3106 3106 revs = [r for r in revs if r not in ancestors]
3107 3107
3108 3108 if not revs:
3109 3109 return -1
3110 3110
3111 3111 # analyze revs for earlier grafts
3112 3112 ids = {}
3113 3113 for ctx in repo.set(b"%ld", revs):
3114 3114 ids[ctx.hex()] = ctx.rev()
3115 3115 n = ctx.extra().get(b'source')
3116 3116 if n:
3117 3117 ids[n] = ctx.rev()
3118 3118
3119 3119 # check ancestors for earlier grafts
3120 3120 ui.debug(b'scanning for duplicate grafts\n')
3121 3121
3122 3122 # The only changesets we can be sure doesn't contain grafts of any
3123 3123 # revs, are the ones that are common ancestors of *all* revs:
3124 3124 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3125 3125 ctx = repo[rev]
3126 3126 n = ctx.extra().get(b'source')
3127 3127 if n in ids:
3128 3128 try:
3129 3129 r = repo[n].rev()
3130 3130 except error.RepoLookupError:
3131 3131 r = None
3132 3132 if r in revs:
3133 3133 ui.warn(
3134 3134 _(
3135 3135 b'skipping revision %d:%s '
3136 3136 b'(already grafted to %d:%s)\n'
3137 3137 )
3138 3138 % (r, repo[r], rev, ctx)
3139 3139 )
3140 3140 revs.remove(r)
3141 3141 elif ids[n] in revs:
3142 3142 if r is None:
3143 3143 ui.warn(
3144 3144 _(
3145 3145 b'skipping already grafted revision %d:%s '
3146 3146 b'(%d:%s also has unknown origin %s)\n'
3147 3147 )
3148 3148 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3149 3149 )
3150 3150 else:
3151 3151 ui.warn(
3152 3152 _(
3153 3153 b'skipping already grafted revision %d:%s '
3154 3154 b'(%d:%s also has origin %d:%s)\n'
3155 3155 )
3156 3156 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3157 3157 )
3158 3158 revs.remove(ids[n])
3159 3159 elif ctx.hex() in ids:
3160 3160 r = ids[ctx.hex()]
3161 3161 if r in revs:
3162 3162 ui.warn(
3163 3163 _(
3164 3164 b'skipping already grafted revision %d:%s '
3165 3165 b'(was grafted from %d:%s)\n'
3166 3166 )
3167 3167 % (r, repo[r], rev, ctx)
3168 3168 )
3169 3169 revs.remove(r)
3170 3170 if not revs:
3171 3171 return -1
3172 3172
3173 3173 if opts.get(b'no_commit'):
3174 3174 statedata[b'no_commit'] = True
3175 3175 if opts.get(b'base'):
3176 3176 statedata[b'base'] = opts[b'base']
3177 3177 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3178 3178 desc = b'%d:%s "%s"' % (
3179 3179 ctx.rev(),
3180 3180 ctx,
3181 3181 ctx.description().split(b'\n', 1)[0],
3182 3182 )
3183 3183 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3184 3184 if names:
3185 3185 desc += b' (%s)' % b' '.join(names)
3186 3186 ui.status(_(b'grafting %s\n') % desc)
3187 3187 if opts.get(b'dry_run'):
3188 3188 continue
3189 3189
3190 3190 source = ctx.extra().get(b'source')
3191 3191 extra = {}
3192 3192 if source:
3193 3193 extra[b'source'] = source
3194 3194 extra[b'intermediate-source'] = ctx.hex()
3195 3195 else:
3196 3196 extra[b'source'] = ctx.hex()
3197 3197 user = ctx.user()
3198 3198 if opts.get(b'user'):
3199 3199 user = opts[b'user']
3200 3200 statedata[b'user'] = user
3201 3201 date = ctx.date()
3202 3202 if opts.get(b'date'):
3203 3203 date = opts[b'date']
3204 3204 statedata[b'date'] = date
3205 3205 message = ctx.description()
3206 3206 if opts.get(b'log'):
3207 3207 message += b'\n(grafted from %s)' % ctx.hex()
3208 3208 statedata[b'log'] = True
3209 3209
3210 3210 # we don't merge the first commit when continuing
3211 3211 if not cont:
3212 3212 # perform the graft merge with p1(rev) as 'ancestor'
3213 3213 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3214 3214 base = ctx.p1() if basectx is None else basectx
3215 3215 with ui.configoverride(overrides, b'graft'):
3216 3216 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3217 3217 # report any conflicts
3218 3218 if stats.unresolvedcount > 0:
3219 3219 # write out state for --continue
3220 3220 nodes = [repo[rev].hex() for rev in revs[pos:]]
3221 3221 statedata[b'nodes'] = nodes
3222 3222 stateversion = 1
3223 3223 graftstate.save(stateversion, statedata)
3224 3224 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3225 3225 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3226 3226 return 1
3227 3227 else:
3228 3228 cont = False
3229 3229
3230 3230 # commit if --no-commit is false
3231 3231 if not opts.get(b'no_commit'):
3232 3232 node = repo.commit(
3233 3233 text=message, user=user, date=date, extra=extra, editor=editor
3234 3234 )
3235 3235 if node is None:
3236 3236 ui.warn(
3237 3237 _(b'note: graft of %d:%s created no changes to commit\n')
3238 3238 % (ctx.rev(), ctx)
3239 3239 )
3240 3240 # checking that newnodes exist because old state files won't have it
3241 3241 elif statedata.get(b'newnodes') is not None:
3242 3242 statedata[b'newnodes'].append(node)
3243 3243
3244 3244 # remove state when we complete successfully
3245 3245 if not opts.get(b'dry_run'):
3246 3246 graftstate.delete()
3247 3247
3248 3248 return 0
3249 3249
3250 3250
3251 3251 def _stopgraft(ui, repo, graftstate):
3252 3252 """stop the interrupted graft"""
3253 3253 if not graftstate.exists():
3254 3254 raise error.Abort(_(b"no interrupted graft found"))
3255 3255 pctx = repo[b'.']
3256 3256 hg.updaterepo(repo, pctx.node(), overwrite=True)
3257 3257 graftstate.delete()
3258 3258 ui.status(_(b"stopped the interrupted graft\n"))
3259 3259 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3260 3260 return 0
3261 3261
3262 3262
3263 3263 statemod.addunfinished(
3264 3264 b'graft',
3265 3265 fname=b'graftstate',
3266 3266 clearable=True,
3267 3267 stopflag=True,
3268 3268 continueflag=True,
3269 3269 abortfunc=cmdutil.hgabortgraft,
3270 3270 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3271 3271 )
3272 3272
3273 3273
3274 3274 @command(
3275 3275 b'grep',
3276 3276 [
3277 3277 (b'0', b'print0', None, _(b'end fields with NUL')),
3278 3278 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3279 3279 (
3280 3280 b'',
3281 3281 b'diff',
3282 3282 None,
3283 3283 _(
3284 3284 b'search revision differences for when the pattern was added '
3285 3285 b'or removed'
3286 3286 ),
3287 3287 ),
3288 3288 (b'a', b'text', None, _(b'treat all files as text')),
3289 3289 (
3290 3290 b'f',
3291 3291 b'follow',
3292 3292 None,
3293 3293 _(
3294 3294 b'follow changeset history,'
3295 3295 b' or file history across copies and renames'
3296 3296 ),
3297 3297 ),
3298 3298 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3299 3299 (
3300 3300 b'l',
3301 3301 b'files-with-matches',
3302 3302 None,
3303 3303 _(b'print only filenames and revisions that match'),
3304 3304 ),
3305 3305 (b'n', b'line-number', None, _(b'print matching line numbers')),
3306 3306 (
3307 3307 b'r',
3308 3308 b'rev',
3309 3309 [],
3310 3310 _(b'search files changed within revision range'),
3311 3311 _(b'REV'),
3312 3312 ),
3313 3313 (
3314 3314 b'',
3315 3315 b'all-files',
3316 3316 None,
3317 3317 _(
3318 3318 b'include all files in the changeset while grepping (DEPRECATED)'
3319 3319 ),
3320 3320 ),
3321 3321 (b'u', b'user', None, _(b'list the author (long with -v)')),
3322 3322 (b'd', b'date', None, _(b'list the date (short with -q)')),
3323 3323 ]
3324 3324 + formatteropts
3325 3325 + walkopts,
3326 3326 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3327 3327 helpcategory=command.CATEGORY_FILE_CONTENTS,
3328 3328 inferrepo=True,
3329 3329 intents={INTENT_READONLY},
3330 3330 )
3331 3331 def grep(ui, repo, pattern, *pats, **opts):
3332 3332 """search for a pattern in specified files
3333 3333
3334 3334 Search the working directory or revision history for a regular
3335 3335 expression in the specified files for the entire repository.
3336 3336
3337 3337 By default, grep searches the repository files in the working
3338 3338 directory and prints the files where it finds a match. To specify
3339 3339 historical revisions instead of the working directory, use the
3340 3340 --rev flag.
3341 3341
3342 3342 To search instead historical revision differences that contains a
3343 3343 change in match status ("-" for a match that becomes a non-match,
3344 3344 or "+" for a non-match that becomes a match), use the --diff flag.
3345 3345
3346 3346 PATTERN can be any Python (roughly Perl-compatible) regular
3347 3347 expression.
3348 3348
3349 3349 If no FILEs are specified and the --rev flag isn't supplied, all
3350 3350 files in the working directory are searched. When using the --rev
3351 3351 flag and specifying FILEs, use the --follow argument to also
3352 3352 follow the specified FILEs across renames and copies.
3353 3353
3354 3354 .. container:: verbose
3355 3355
3356 3356 Template:
3357 3357
3358 3358 The following keywords are supported in addition to the common template
3359 3359 keywords and functions. See also :hg:`help templates`.
3360 3360
3361 3361 :change: String. Character denoting insertion ``+`` or removal ``-``.
3362 3362 Available if ``--diff`` is specified.
3363 3363 :lineno: Integer. Line number of the match.
3364 3364 :path: String. Repository-absolute path of the file.
3365 3365 :texts: List of text chunks.
3366 3366
3367 3367 And each entry of ``{texts}`` provides the following sub-keywords.
3368 3368
3369 3369 :matched: Boolean. True if the chunk matches the specified pattern.
3370 3370 :text: String. Chunk content.
3371 3371
3372 3372 See :hg:`help templates.operators` for the list expansion syntax.
3373 3373
3374 3374 Returns 0 if a match is found, 1 otherwise.
3375 3375
3376 3376 """
3377 3377 opts = pycompat.byteskwargs(opts)
3378 3378 diff = opts.get(b'all') or opts.get(b'diff')
3379 3379 if diff and opts.get(b'all_files'):
3380 3380 raise error.Abort(_(b'--diff and --all-files are mutually exclusive'))
3381 3381 if opts.get(b'all_files') is None and not diff:
3382 3382 opts[b'all_files'] = True
3383 3383 plaingrep = (
3384 3384 opts.get(b'all_files')
3385 3385 and not opts.get(b'rev')
3386 3386 and not opts.get(b'follow')
3387 3387 )
3388 3388 all_files = opts.get(b'all_files')
3389 3389 if plaingrep:
3390 3390 opts[b'rev'] = [b'wdir()']
3391 3391
3392 3392 reflags = re.M
3393 3393 if opts.get(b'ignore_case'):
3394 3394 reflags |= re.I
3395 3395 try:
3396 3396 regexp = util.re.compile(pattern, reflags)
3397 3397 except re.error as inst:
3398 3398 ui.warn(
3399 3399 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3400 3400 )
3401 3401 return 1
3402 3402 sep, eol = b':', b'\n'
3403 3403 if opts.get(b'print0'):
3404 3404 sep = eol = b'\0'
3405 3405
3406 3406 getfile = util.lrucachefunc(repo.file)
3407 3407
3408 3408 def matchlines(body):
3409 3409 begin = 0
3410 3410 linenum = 0
3411 3411 while begin < len(body):
3412 3412 match = regexp.search(body, begin)
3413 3413 if not match:
3414 3414 break
3415 3415 mstart, mend = match.span()
3416 3416 linenum += body.count(b'\n', begin, mstart) + 1
3417 3417 lstart = body.rfind(b'\n', begin, mstart) + 1 or begin
3418 3418 begin = body.find(b'\n', mend) + 1 or len(body) + 1
3419 3419 lend = begin - 1
3420 3420 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3421 3421
3422 3422 class linestate(object):
3423 3423 def __init__(self, line, linenum, colstart, colend):
3424 3424 self.line = line
3425 3425 self.linenum = linenum
3426 3426 self.colstart = colstart
3427 3427 self.colend = colend
3428 3428
3429 3429 def __hash__(self):
3430 3430 return hash(self.line)
3431 3431
3432 3432 def __eq__(self, other):
3433 3433 return self.line == other.line
3434 3434
3435 3435 def findpos(self):
3436 3436 """Iterate all (start, end) indices of matches"""
3437 3437 yield self.colstart, self.colend
3438 3438 p = self.colend
3439 3439 while p < len(self.line):
3440 3440 m = regexp.search(self.line, p)
3441 3441 if not m:
3442 3442 break
3443 3443 if m.end() == p:
3444 3444 p += 1
3445 3445 else:
3446 3446 yield m.span()
3447 3447 p = m.end()
3448 3448
3449 3449 matches = {}
3450 3450 copies = {}
3451 3451
3452 3452 def grepbody(fn, rev, body):
3453 3453 matches[rev].setdefault(fn, [])
3454 3454 m = matches[rev][fn]
3455 3455 if body is None:
3456 3456 return
3457 3457
3458 3458 for lnum, cstart, cend, line in matchlines(body):
3459 3459 s = linestate(line, lnum, cstart, cend)
3460 3460 m.append(s)
3461 3461
3462 3462 def difflinestates(a, b):
3463 3463 sm = difflib.SequenceMatcher(None, a, b)
3464 3464 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3465 3465 if tag == 'insert':
3466 3466 for i in pycompat.xrange(blo, bhi):
3467 3467 yield (b'+', b[i])
3468 3468 elif tag == 'delete':
3469 3469 for i in pycompat.xrange(alo, ahi):
3470 3470 yield (b'-', a[i])
3471 3471 elif tag == 'replace':
3472 3472 for i in pycompat.xrange(alo, ahi):
3473 3473 yield (b'-', a[i])
3474 3474 for i in pycompat.xrange(blo, bhi):
3475 3475 yield (b'+', b[i])
3476 3476
3477 3477 uipathfn = scmutil.getuipathfn(repo)
3478 3478
3479 3479 def display(fm, fn, ctx, pstates, states):
3480 3480 rev = scmutil.intrev(ctx)
3481 3481 if fm.isplain():
3482 3482 formatuser = ui.shortuser
3483 3483 else:
3484 3484 formatuser = pycompat.bytestr
3485 3485 if ui.quiet:
3486 3486 datefmt = b'%Y-%m-%d'
3487 3487 else:
3488 3488 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3489 3489 found = False
3490 3490
3491 3491 @util.cachefunc
3492 3492 def binary():
3493 3493 flog = getfile(fn)
3494 3494 try:
3495 3495 return stringutil.binary(flog.read(ctx.filenode(fn)))
3496 3496 except error.WdirUnsupported:
3497 3497 return ctx[fn].isbinary()
3498 3498
3499 3499 fieldnamemap = {b'linenumber': b'lineno'}
3500 3500 if diff:
3501 3501 iter = difflinestates(pstates, states)
3502 3502 else:
3503 3503 iter = [(b'', l) for l in states]
3504 3504 for change, l in iter:
3505 3505 fm.startitem()
3506 3506 fm.context(ctx=ctx)
3507 3507 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3508 3508 fm.plain(uipathfn(fn), label=b'grep.filename')
3509 3509
3510 3510 cols = [
3511 3511 (b'rev', b'%d', rev, not plaingrep, b''),
3512 3512 (
3513 3513 b'linenumber',
3514 3514 b'%d',
3515 3515 l.linenum,
3516 3516 opts.get(b'line_number'),
3517 3517 b'',
3518 3518 ),
3519 3519 ]
3520 3520 if diff:
3521 3521 cols.append(
3522 3522 (
3523 3523 b'change',
3524 3524 b'%s',
3525 3525 change,
3526 3526 True,
3527 3527 b'grep.inserted '
3528 3528 if change == b'+'
3529 3529 else b'grep.deleted ',
3530 3530 )
3531 3531 )
3532 3532 cols.extend(
3533 3533 [
3534 3534 (
3535 3535 b'user',
3536 3536 b'%s',
3537 3537 formatuser(ctx.user()),
3538 3538 opts.get(b'user'),
3539 3539 b'',
3540 3540 ),
3541 3541 (
3542 3542 b'date',
3543 3543 b'%s',
3544 3544 fm.formatdate(ctx.date(), datefmt),
3545 3545 opts.get(b'date'),
3546 3546 b'',
3547 3547 ),
3548 3548 ]
3549 3549 )
3550 3550 for name, fmt, data, cond, extra_label in cols:
3551 3551 if cond:
3552 3552 fm.plain(sep, label=b'grep.sep')
3553 3553 field = fieldnamemap.get(name, name)
3554 3554 label = extra_label + (b'grep.%s' % name)
3555 3555 fm.condwrite(cond, field, fmt, data, label=label)
3556 3556 if not opts.get(b'files_with_matches'):
3557 3557 fm.plain(sep, label=b'grep.sep')
3558 3558 if not opts.get(b'text') and binary():
3559 3559 fm.plain(_(b" Binary file matches"))
3560 3560 else:
3561 3561 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3562 3562 fm.plain(eol)
3563 3563 found = True
3564 3564 if opts.get(b'files_with_matches'):
3565 3565 break
3566 3566 return found
3567 3567
3568 3568 def displaymatches(fm, l):
3569 3569 p = 0
3570 3570 for s, e in l.findpos():
3571 3571 if p < s:
3572 3572 fm.startitem()
3573 3573 fm.write(b'text', b'%s', l.line[p:s])
3574 3574 fm.data(matched=False)
3575 3575 fm.startitem()
3576 3576 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3577 3577 fm.data(matched=True)
3578 3578 p = e
3579 3579 if p < len(l.line):
3580 3580 fm.startitem()
3581 3581 fm.write(b'text', b'%s', l.line[p:])
3582 3582 fm.data(matched=False)
3583 3583 fm.end()
3584 3584
3585 3585 skip = set()
3586 3586 revfiles = {}
3587 3587 match = scmutil.match(repo[None], pats, opts)
3588 3588 found = False
3589 3589 follow = opts.get(b'follow')
3590 3590
3591 3591 getrenamed = scmutil.getrenamedfn(repo)
3592 3592
3593 3593 def readfile(ctx, fn):
3594 3594 rev = ctx.rev()
3595 3595 if rev is None:
3596 3596 fctx = ctx[fn]
3597 3597 try:
3598 3598 return fctx.data()
3599 3599 except IOError as e:
3600 3600 if e.errno != errno.ENOENT:
3601 3601 raise
3602 3602 else:
3603 3603 flog = getfile(fn)
3604 3604 fnode = ctx.filenode(fn)
3605 3605 try:
3606 3606 return flog.read(fnode)
3607 3607 except error.CensoredNodeError:
3608 3608 ui.warn(
3609 3609 _(
3610 3610 b'cannot search in censored file: %(filename)s:%(revnum)s\n'
3611 3611 )
3612 3612 % {b'filename': fn, b'revnum': pycompat.bytestr(rev),}
3613 3613 )
3614 3614
3615 3615 def prep(ctx, fns):
3616 3616 rev = ctx.rev()
3617 3617 pctx = ctx.p1()
3618 3618 matches.setdefault(rev, {})
3619 3619 if diff:
3620 3620 parent = pctx.rev()
3621 3621 matches.setdefault(parent, {})
3622 3622 files = revfiles.setdefault(rev, [])
3623 3623 if rev is None:
3624 3624 # in `hg grep pattern`, 2/3 of the time is spent is spent in
3625 3625 # pathauditor checks without this in mozilla-central
3626 3626 contextmanager = repo.wvfs.audit.cached
3627 3627 else:
3628 3628 contextmanager = util.nullcontextmanager
3629 3629 with contextmanager():
3630 3630 for fn in fns:
3631 3631 # fn might not exist in the revision (could be a file removed by
3632 3632 # the revision). We could check `fn not in ctx` even when rev is
3633 3633 # None, but it's less racy to protect againt that in readfile.
3634 3634 if rev is not None and fn not in ctx:
3635 3635 continue
3636 3636
3637 3637 copy = None
3638 3638 if follow:
3639 3639 copy = getrenamed(fn, rev)
3640 3640 if copy:
3641 3641 copies.setdefault(rev, {})[fn] = copy
3642 3642 if fn in skip:
3643 3643 skip.add(copy)
3644 3644 if fn in skip:
3645 3645 continue
3646 3646 files.append(fn)
3647 3647
3648 3648 if fn not in matches[rev]:
3649 3649 grepbody(fn, rev, readfile(ctx, fn))
3650 3650
3651 3651 if diff:
3652 3652 pfn = copy or fn
3653 3653 if pfn not in matches[parent] and pfn in pctx:
3654 3654 grepbody(pfn, parent, readfile(pctx, pfn))
3655 3655
3656 3656 ui.pager(b'grep')
3657 3657 fm = ui.formatter(b'grep', opts)
3658 3658 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
3659 3659 rev = ctx.rev()
3660 3660 parent = ctx.p1().rev()
3661 3661 for fn in sorted(revfiles.get(rev, [])):
3662 3662 states = matches[rev][fn]
3663 3663 copy = copies.get(rev, {}).get(fn)
3664 3664 if fn in skip:
3665 3665 if copy:
3666 3666 skip.add(copy)
3667 3667 continue
3668 3668 pstates = matches.get(parent, {}).get(copy or fn, [])
3669 3669 if pstates or states:
3670 3670 r = display(fm, fn, ctx, pstates, states)
3671 3671 found = found or r
3672 3672 if r and not diff and not all_files:
3673 3673 skip.add(fn)
3674 3674 if copy:
3675 3675 skip.add(copy)
3676 3676 del revfiles[rev]
3677 3677 # We will keep the matches dict for the duration of the window
3678 3678 # clear the matches dict once the window is over
3679 3679 if not revfiles:
3680 3680 matches.clear()
3681 3681 fm.end()
3682 3682
3683 3683 return not found
3684 3684
3685 3685
3686 3686 @command(
3687 3687 b'heads',
3688 3688 [
3689 3689 (
3690 3690 b'r',
3691 3691 b'rev',
3692 3692 b'',
3693 3693 _(b'show only heads which are descendants of STARTREV'),
3694 3694 _(b'STARTREV'),
3695 3695 ),
3696 3696 (b't', b'topo', False, _(b'show topological heads only')),
3697 3697 (
3698 3698 b'a',
3699 3699 b'active',
3700 3700 False,
3701 3701 _(b'show active branchheads only (DEPRECATED)'),
3702 3702 ),
3703 3703 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3704 3704 ]
3705 3705 + templateopts,
3706 3706 _(b'[-ct] [-r STARTREV] [REV]...'),
3707 3707 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3708 3708 intents={INTENT_READONLY},
3709 3709 )
3710 3710 def heads(ui, repo, *branchrevs, **opts):
3711 3711 """show branch heads
3712 3712
3713 3713 With no arguments, show all open branch heads in the repository.
3714 3714 Branch heads are changesets that have no descendants on the
3715 3715 same branch. They are where development generally takes place and
3716 3716 are the usual targets for update and merge operations.
3717 3717
3718 3718 If one or more REVs are given, only open branch heads on the
3719 3719 branches associated with the specified changesets are shown. This
3720 3720 means that you can use :hg:`heads .` to see the heads on the
3721 3721 currently checked-out branch.
3722 3722
3723 3723 If -c/--closed is specified, also show branch heads marked closed
3724 3724 (see :hg:`commit --close-branch`).
3725 3725
3726 3726 If STARTREV is specified, only those heads that are descendants of
3727 3727 STARTREV will be displayed.
3728 3728
3729 3729 If -t/--topo is specified, named branch mechanics will be ignored and only
3730 3730 topological heads (changesets with no children) will be shown.
3731 3731
3732 3732 Returns 0 if matching heads are found, 1 if not.
3733 3733 """
3734 3734
3735 3735 opts = pycompat.byteskwargs(opts)
3736 3736 start = None
3737 3737 rev = opts.get(b'rev')
3738 3738 if rev:
3739 3739 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3740 3740 start = scmutil.revsingle(repo, rev, None).node()
3741 3741
3742 3742 if opts.get(b'topo'):
3743 3743 heads = [repo[h] for h in repo.heads(start)]
3744 3744 else:
3745 3745 heads = []
3746 3746 for branch in repo.branchmap():
3747 3747 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3748 3748 heads = [repo[h] for h in heads]
3749 3749
3750 3750 if branchrevs:
3751 3751 branches = {
3752 3752 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3753 3753 }
3754 3754 heads = [h for h in heads if h.branch() in branches]
3755 3755
3756 3756 if opts.get(b'active') and branchrevs:
3757 3757 dagheads = repo.heads(start)
3758 3758 heads = [h for h in heads if h.node() in dagheads]
3759 3759
3760 3760 if branchrevs:
3761 3761 haveheads = {h.branch() for h in heads}
3762 3762 if branches - haveheads:
3763 3763 headless = b', '.join(b for b in branches - haveheads)
3764 3764 msg = _(b'no open branch heads found on branches %s')
3765 3765 if opts.get(b'rev'):
3766 3766 msg += _(b' (started at %s)') % opts[b'rev']
3767 3767 ui.warn((msg + b'\n') % headless)
3768 3768
3769 3769 if not heads:
3770 3770 return 1
3771 3771
3772 3772 ui.pager(b'heads')
3773 3773 heads = sorted(heads, key=lambda x: -(x.rev()))
3774 3774 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3775 3775 for ctx in heads:
3776 3776 displayer.show(ctx)
3777 3777 displayer.close()
3778 3778
3779 3779
3780 3780 @command(
3781 3781 b'help',
3782 3782 [
3783 3783 (b'e', b'extension', None, _(b'show only help for extensions')),
3784 3784 (b'c', b'command', None, _(b'show only help for commands')),
3785 3785 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3786 3786 (
3787 3787 b's',
3788 3788 b'system',
3789 3789 [],
3790 3790 _(b'show help for specific platform(s)'),
3791 3791 _(b'PLATFORM'),
3792 3792 ),
3793 3793 ],
3794 3794 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3795 3795 helpcategory=command.CATEGORY_HELP,
3796 3796 norepo=True,
3797 3797 intents={INTENT_READONLY},
3798 3798 )
3799 3799 def help_(ui, name=None, **opts):
3800 3800 """show help for a given topic or a help overview
3801 3801
3802 3802 With no arguments, print a list of commands with short help messages.
3803 3803
3804 3804 Given a topic, extension, or command name, print help for that
3805 3805 topic.
3806 3806
3807 3807 Returns 0 if successful.
3808 3808 """
3809 3809
3810 3810 keep = opts.get('system') or []
3811 3811 if len(keep) == 0:
3812 3812 if pycompat.sysplatform.startswith(b'win'):
3813 3813 keep.append(b'windows')
3814 3814 elif pycompat.sysplatform == b'OpenVMS':
3815 3815 keep.append(b'vms')
3816 3816 elif pycompat.sysplatform == b'plan9':
3817 3817 keep.append(b'plan9')
3818 3818 else:
3819 3819 keep.append(b'unix')
3820 3820 keep.append(pycompat.sysplatform.lower())
3821 3821 if ui.verbose:
3822 3822 keep.append(b'verbose')
3823 3823
3824 3824 commands = sys.modules[__name__]
3825 3825 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3826 3826 ui.pager(b'help')
3827 3827 ui.write(formatted)
3828 3828
3829 3829
3830 3830 @command(
3831 3831 b'identify|id',
3832 3832 [
3833 3833 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3834 3834 (b'n', b'num', None, _(b'show local revision number')),
3835 3835 (b'i', b'id', None, _(b'show global revision id')),
3836 3836 (b'b', b'branch', None, _(b'show branch')),
3837 3837 (b't', b'tags', None, _(b'show tags')),
3838 3838 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3839 3839 ]
3840 3840 + remoteopts
3841 3841 + formatteropts,
3842 3842 _(b'[-nibtB] [-r REV] [SOURCE]'),
3843 3843 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3844 3844 optionalrepo=True,
3845 3845 intents={INTENT_READONLY},
3846 3846 )
3847 3847 def identify(
3848 3848 ui,
3849 3849 repo,
3850 3850 source=None,
3851 3851 rev=None,
3852 3852 num=None,
3853 3853 id=None,
3854 3854 branch=None,
3855 3855 tags=None,
3856 3856 bookmarks=None,
3857 3857 **opts
3858 3858 ):
3859 3859 """identify the working directory or specified revision
3860 3860
3861 3861 Print a summary identifying the repository state at REV using one or
3862 3862 two parent hash identifiers, followed by a "+" if the working
3863 3863 directory has uncommitted changes, the branch name (if not default),
3864 3864 a list of tags, and a list of bookmarks.
3865 3865
3866 3866 When REV is not given, print a summary of the current state of the
3867 3867 repository including the working directory. Specify -r. to get information
3868 3868 of the working directory parent without scanning uncommitted changes.
3869 3869
3870 3870 Specifying a path to a repository root or Mercurial bundle will
3871 3871 cause lookup to operate on that repository/bundle.
3872 3872
3873 3873 .. container:: verbose
3874 3874
3875 3875 Template:
3876 3876
3877 3877 The following keywords are supported in addition to the common template
3878 3878 keywords and functions. See also :hg:`help templates`.
3879 3879
3880 3880 :dirty: String. Character ``+`` denoting if the working directory has
3881 3881 uncommitted changes.
3882 3882 :id: String. One or two nodes, optionally followed by ``+``.
3883 3883 :parents: List of strings. Parent nodes of the changeset.
3884 3884
3885 3885 Examples:
3886 3886
3887 3887 - generate a build identifier for the working directory::
3888 3888
3889 3889 hg id --id > build-id.dat
3890 3890
3891 3891 - find the revision corresponding to a tag::
3892 3892
3893 3893 hg id -n -r 1.3
3894 3894
3895 3895 - check the most recent revision of a remote repository::
3896 3896
3897 3897 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3898 3898
3899 3899 See :hg:`log` for generating more information about specific revisions,
3900 3900 including full hash identifiers.
3901 3901
3902 3902 Returns 0 if successful.
3903 3903 """
3904 3904
3905 3905 opts = pycompat.byteskwargs(opts)
3906 3906 if not repo and not source:
3907 3907 raise error.Abort(
3908 3908 _(b"there is no Mercurial repository here (.hg not found)")
3909 3909 )
3910 3910
3911 3911 default = not (num or id or branch or tags or bookmarks)
3912 3912 output = []
3913 3913 revs = []
3914 3914
3915 3915 if source:
3916 3916 source, branches = hg.parseurl(ui.expandpath(source))
3917 3917 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3918 3918 repo = peer.local()
3919 3919 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3920 3920
3921 3921 fm = ui.formatter(b'identify', opts)
3922 3922 fm.startitem()
3923 3923
3924 3924 if not repo:
3925 3925 if num or branch or tags:
3926 3926 raise error.Abort(
3927 3927 _(b"can't query remote revision number, branch, or tags")
3928 3928 )
3929 3929 if not rev and revs:
3930 3930 rev = revs[0]
3931 3931 if not rev:
3932 3932 rev = b"tip"
3933 3933
3934 3934 remoterev = peer.lookup(rev)
3935 3935 hexrev = fm.hexfunc(remoterev)
3936 3936 if default or id:
3937 3937 output = [hexrev]
3938 3938 fm.data(id=hexrev)
3939 3939
3940 3940 @util.cachefunc
3941 3941 def getbms():
3942 3942 bms = []
3943 3943
3944 3944 if b'bookmarks' in peer.listkeys(b'namespaces'):
3945 3945 hexremoterev = hex(remoterev)
3946 3946 bms = [
3947 3947 bm
3948 3948 for bm, bmr in pycompat.iteritems(
3949 3949 peer.listkeys(b'bookmarks')
3950 3950 )
3951 3951 if bmr == hexremoterev
3952 3952 ]
3953 3953
3954 3954 return sorted(bms)
3955 3955
3956 3956 if fm.isplain():
3957 3957 if bookmarks:
3958 3958 output.extend(getbms())
3959 3959 elif default and not ui.quiet:
3960 3960 # multiple bookmarks for a single parent separated by '/'
3961 3961 bm = b'/'.join(getbms())
3962 3962 if bm:
3963 3963 output.append(bm)
3964 3964 else:
3965 3965 fm.data(node=hex(remoterev))
3966 3966 if bookmarks or b'bookmarks' in fm.datahint():
3967 3967 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3968 3968 else:
3969 3969 if rev:
3970 3970 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3971 3971 ctx = scmutil.revsingle(repo, rev, None)
3972 3972
3973 3973 if ctx.rev() is None:
3974 3974 ctx = repo[None]
3975 3975 parents = ctx.parents()
3976 3976 taglist = []
3977 3977 for p in parents:
3978 3978 taglist.extend(p.tags())
3979 3979
3980 3980 dirty = b""
3981 3981 if ctx.dirty(missing=True, merge=False, branch=False):
3982 3982 dirty = b'+'
3983 3983 fm.data(dirty=dirty)
3984 3984
3985 3985 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3986 3986 if default or id:
3987 3987 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3988 3988 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3989 3989
3990 3990 if num:
3991 3991 numoutput = [b"%d" % p.rev() for p in parents]
3992 3992 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3993 3993
3994 3994 fm.data(
3995 3995 parents=fm.formatlist(
3996 3996 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3997 3997 )
3998 3998 )
3999 3999 else:
4000 4000 hexoutput = fm.hexfunc(ctx.node())
4001 4001 if default or id:
4002 4002 output = [hexoutput]
4003 4003 fm.data(id=hexoutput)
4004 4004
4005 4005 if num:
4006 4006 output.append(pycompat.bytestr(ctx.rev()))
4007 4007 taglist = ctx.tags()
4008 4008
4009 4009 if default and not ui.quiet:
4010 4010 b = ctx.branch()
4011 4011 if b != b'default':
4012 4012 output.append(b"(%s)" % b)
4013 4013
4014 4014 # multiple tags for a single parent separated by '/'
4015 4015 t = b'/'.join(taglist)
4016 4016 if t:
4017 4017 output.append(t)
4018 4018
4019 4019 # multiple bookmarks for a single parent separated by '/'
4020 4020 bm = b'/'.join(ctx.bookmarks())
4021 4021 if bm:
4022 4022 output.append(bm)
4023 4023 else:
4024 4024 if branch:
4025 4025 output.append(ctx.branch())
4026 4026
4027 4027 if tags:
4028 4028 output.extend(taglist)
4029 4029
4030 4030 if bookmarks:
4031 4031 output.extend(ctx.bookmarks())
4032 4032
4033 4033 fm.data(node=ctx.hex())
4034 4034 fm.data(branch=ctx.branch())
4035 4035 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4036 4036 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4037 4037 fm.context(ctx=ctx)
4038 4038
4039 4039 fm.plain(b"%s\n" % b' '.join(output))
4040 4040 fm.end()
4041 4041
4042 4042
4043 4043 @command(
4044 4044 b'import|patch',
4045 4045 [
4046 4046 (
4047 4047 b'p',
4048 4048 b'strip',
4049 4049 1,
4050 4050 _(
4051 4051 b'directory strip option for patch. This has the same '
4052 4052 b'meaning as the corresponding patch option'
4053 4053 ),
4054 4054 _(b'NUM'),
4055 4055 ),
4056 4056 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4057 4057 (b'', b'secret', None, _(b'use the secret phase for committing')),
4058 4058 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4059 4059 (
4060 4060 b'f',
4061 4061 b'force',
4062 4062 None,
4063 4063 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4064 4064 ),
4065 4065 (
4066 4066 b'',
4067 4067 b'no-commit',
4068 4068 None,
4069 4069 _(b"don't commit, just update the working directory"),
4070 4070 ),
4071 4071 (
4072 4072 b'',
4073 4073 b'bypass',
4074 4074 None,
4075 4075 _(b"apply patch without touching the working directory"),
4076 4076 ),
4077 4077 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4078 4078 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4079 4079 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4080 4080 (
4081 4081 b'',
4082 4082 b'import-branch',
4083 4083 None,
4084 4084 _(b'use any branch information in patch (implied by --exact)'),
4085 4085 ),
4086 4086 ]
4087 4087 + commitopts
4088 4088 + commitopts2
4089 4089 + similarityopts,
4090 4090 _(b'[OPTION]... PATCH...'),
4091 4091 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4092 4092 )
4093 4093 def import_(ui, repo, patch1=None, *patches, **opts):
4094 4094 """import an ordered set of patches
4095 4095
4096 4096 Import a list of patches and commit them individually (unless
4097 4097 --no-commit is specified).
4098 4098
4099 4099 To read a patch from standard input (stdin), use "-" as the patch
4100 4100 name. If a URL is specified, the patch will be downloaded from
4101 4101 there.
4102 4102
4103 4103 Import first applies changes to the working directory (unless
4104 4104 --bypass is specified), import will abort if there are outstanding
4105 4105 changes.
4106 4106
4107 4107 Use --bypass to apply and commit patches directly to the
4108 4108 repository, without affecting the working directory. Without
4109 4109 --exact, patches will be applied on top of the working directory
4110 4110 parent revision.
4111 4111
4112 4112 You can import a patch straight from a mail message. Even patches
4113 4113 as attachments work (to use the body part, it must have type
4114 4114 text/plain or text/x-patch). From and Subject headers of email
4115 4115 message are used as default committer and commit message. All
4116 4116 text/plain body parts before first diff are added to the commit
4117 4117 message.
4118 4118
4119 4119 If the imported patch was generated by :hg:`export`, user and
4120 4120 description from patch override values from message headers and
4121 4121 body. Values given on command line with -m/--message and -u/--user
4122 4122 override these.
4123 4123
4124 4124 If --exact is specified, import will set the working directory to
4125 4125 the parent of each patch before applying it, and will abort if the
4126 4126 resulting changeset has a different ID than the one recorded in
4127 4127 the patch. This will guard against various ways that portable
4128 4128 patch formats and mail systems might fail to transfer Mercurial
4129 4129 data or metadata. See :hg:`bundle` for lossless transmission.
4130 4130
4131 4131 Use --partial to ensure a changeset will be created from the patch
4132 4132 even if some hunks fail to apply. Hunks that fail to apply will be
4133 4133 written to a <target-file>.rej file. Conflicts can then be resolved
4134 4134 by hand before :hg:`commit --amend` is run to update the created
4135 4135 changeset. This flag exists to let people import patches that
4136 4136 partially apply without losing the associated metadata (author,
4137 4137 date, description, ...).
4138 4138
4139 4139 .. note::
4140 4140
4141 4141 When no hunks apply cleanly, :hg:`import --partial` will create
4142 4142 an empty changeset, importing only the patch metadata.
4143 4143
4144 4144 With -s/--similarity, hg will attempt to discover renames and
4145 4145 copies in the patch in the same way as :hg:`addremove`.
4146 4146
4147 4147 It is possible to use external patch programs to perform the patch
4148 4148 by setting the ``ui.patch`` configuration option. For the default
4149 4149 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4150 4150 See :hg:`help config` for more information about configuration
4151 4151 files and how to use these options.
4152 4152
4153 4153 See :hg:`help dates` for a list of formats valid for -d/--date.
4154 4154
4155 4155 .. container:: verbose
4156 4156
4157 4157 Examples:
4158 4158
4159 4159 - import a traditional patch from a website and detect renames::
4160 4160
4161 4161 hg import -s 80 http://example.com/bugfix.patch
4162 4162
4163 4163 - import a changeset from an hgweb server::
4164 4164
4165 4165 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4166 4166
4167 4167 - import all the patches in an Unix-style mbox::
4168 4168
4169 4169 hg import incoming-patches.mbox
4170 4170
4171 4171 - import patches from stdin::
4172 4172
4173 4173 hg import -
4174 4174
4175 4175 - attempt to exactly restore an exported changeset (not always
4176 4176 possible)::
4177 4177
4178 4178 hg import --exact proposed-fix.patch
4179 4179
4180 4180 - use an external tool to apply a patch which is too fuzzy for
4181 4181 the default internal tool.
4182 4182
4183 4183 hg import --config ui.patch="patch --merge" fuzzy.patch
4184 4184
4185 4185 - change the default fuzzing from 2 to a less strict 7
4186 4186
4187 4187 hg import --config ui.fuzz=7 fuzz.patch
4188 4188
4189 4189 Returns 0 on success, 1 on partial success (see --partial).
4190 4190 """
4191 4191
4192 4192 opts = pycompat.byteskwargs(opts)
4193 4193 if not patch1:
4194 4194 raise error.Abort(_(b'need at least one patch to import'))
4195 4195
4196 4196 patches = (patch1,) + patches
4197 4197
4198 4198 date = opts.get(b'date')
4199 4199 if date:
4200 4200 opts[b'date'] = dateutil.parsedate(date)
4201 4201
4202 4202 exact = opts.get(b'exact')
4203 4203 update = not opts.get(b'bypass')
4204 4204 if not update and opts.get(b'no_commit'):
4205 4205 raise error.Abort(_(b'cannot use --no-commit with --bypass'))
4206 4206 if opts.get(b'secret') and opts.get(b'no_commit'):
4207 4207 raise error.Abort(_(b'cannot use --no-commit with --secret'))
4208 4208 try:
4209 4209 sim = float(opts.get(b'similarity') or 0)
4210 4210 except ValueError:
4211 4211 raise error.Abort(_(b'similarity must be a number'))
4212 4212 if sim < 0 or sim > 100:
4213 4213 raise error.Abort(_(b'similarity must be between 0 and 100'))
4214 4214 if sim and not update:
4215 4215 raise error.Abort(_(b'cannot use --similarity with --bypass'))
4216 4216 if exact:
4217 4217 if opts.get(b'edit'):
4218 4218 raise error.Abort(_(b'cannot use --exact with --edit'))
4219 4219 if opts.get(b'prefix'):
4220 4220 raise error.Abort(_(b'cannot use --exact with --prefix'))
4221 4221
4222 4222 base = opts[b"base"]
4223 4223 msgs = []
4224 4224 ret = 0
4225 4225
4226 4226 with repo.wlock():
4227 4227 if update:
4228 4228 cmdutil.checkunfinished(repo)
4229 4229 if exact or not opts.get(b'force'):
4230 4230 cmdutil.bailifchanged(repo)
4231 4231
4232 4232 if not opts.get(b'no_commit'):
4233 4233 lock = repo.lock
4234 4234 tr = lambda: repo.transaction(b'import')
4235 4235 dsguard = util.nullcontextmanager
4236 4236 else:
4237 4237 lock = util.nullcontextmanager
4238 4238 tr = util.nullcontextmanager
4239 4239 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4240 4240 with lock(), tr(), dsguard():
4241 4241 parents = repo[None].parents()
4242 4242 for patchurl in patches:
4243 4243 if patchurl == b'-':
4244 4244 ui.status(_(b'applying patch from stdin\n'))
4245 4245 patchfile = ui.fin
4246 4246 patchurl = b'stdin' # for error message
4247 4247 else:
4248 4248 patchurl = os.path.join(base, patchurl)
4249 4249 ui.status(_(b'applying %s\n') % patchurl)
4250 4250 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4251 4251
4252 4252 haspatch = False
4253 4253 for hunk in patch.split(patchfile):
4254 4254 with patch.extract(ui, hunk) as patchdata:
4255 4255 msg, node, rej = cmdutil.tryimportone(
4256 4256 ui, repo, patchdata, parents, opts, msgs, hg.clean
4257 4257 )
4258 4258 if msg:
4259 4259 haspatch = True
4260 4260 ui.note(msg + b'\n')
4261 4261 if update or exact:
4262 4262 parents = repo[None].parents()
4263 4263 else:
4264 4264 parents = [repo[node]]
4265 4265 if rej:
4266 4266 ui.write_err(_(b"patch applied partially\n"))
4267 4267 ui.write_err(
4268 4268 _(
4269 4269 b"(fix the .rej files and run "
4270 4270 b"`hg commit --amend`)\n"
4271 4271 )
4272 4272 )
4273 4273 ret = 1
4274 4274 break
4275 4275
4276 4276 if not haspatch:
4277 4277 raise error.Abort(_(b'%s: no diffs found') % patchurl)
4278 4278
4279 4279 if msgs:
4280 4280 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4281 4281 return ret
4282 4282
4283 4283
4284 4284 @command(
4285 4285 b'incoming|in',
4286 4286 [
4287 4287 (
4288 4288 b'f',
4289 4289 b'force',
4290 4290 None,
4291 4291 _(b'run even if remote repository is unrelated'),
4292 4292 ),
4293 4293 (b'n', b'newest-first', None, _(b'show newest record first')),
4294 4294 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4295 4295 (
4296 4296 b'r',
4297 4297 b'rev',
4298 4298 [],
4299 4299 _(b'a remote changeset intended to be added'),
4300 4300 _(b'REV'),
4301 4301 ),
4302 4302 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4303 4303 (
4304 4304 b'b',
4305 4305 b'branch',
4306 4306 [],
4307 4307 _(b'a specific branch you would like to pull'),
4308 4308 _(b'BRANCH'),
4309 4309 ),
4310 4310 ]
4311 4311 + logopts
4312 4312 + remoteopts
4313 4313 + subrepoopts,
4314 4314 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4315 4315 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4316 4316 )
4317 4317 def incoming(ui, repo, source=b"default", **opts):
4318 4318 """show new changesets found in source
4319 4319
4320 4320 Show new changesets found in the specified path/URL or the default
4321 4321 pull location. These are the changesets that would have been pulled
4322 4322 by :hg:`pull` at the time you issued this command.
4323 4323
4324 4324 See pull for valid source format details.
4325 4325
4326 4326 .. container:: verbose
4327 4327
4328 4328 With -B/--bookmarks, the result of bookmark comparison between
4329 4329 local and remote repositories is displayed. With -v/--verbose,
4330 4330 status is also displayed for each bookmark like below::
4331 4331
4332 4332 BM1 01234567890a added
4333 4333 BM2 1234567890ab advanced
4334 4334 BM3 234567890abc diverged
4335 4335 BM4 34567890abcd changed
4336 4336
4337 4337 The action taken locally when pulling depends on the
4338 4338 status of each bookmark:
4339 4339
4340 4340 :``added``: pull will create it
4341 4341 :``advanced``: pull will update it
4342 4342 :``diverged``: pull will create a divergent bookmark
4343 4343 :``changed``: result depends on remote changesets
4344 4344
4345 4345 From the point of view of pulling behavior, bookmark
4346 4346 existing only in the remote repository are treated as ``added``,
4347 4347 even if it is in fact locally deleted.
4348 4348
4349 4349 .. container:: verbose
4350 4350
4351 4351 For remote repository, using --bundle avoids downloading the
4352 4352 changesets twice if the incoming is followed by a pull.
4353 4353
4354 4354 Examples:
4355 4355
4356 4356 - show incoming changes with patches and full description::
4357 4357
4358 4358 hg incoming -vp
4359 4359
4360 4360 - show incoming changes excluding merges, store a bundle::
4361 4361
4362 4362 hg in -vpM --bundle incoming.hg
4363 4363 hg pull incoming.hg
4364 4364
4365 4365 - briefly list changes inside a bundle::
4366 4366
4367 4367 hg in changes.hg -T "{desc|firstline}\\n"
4368 4368
4369 4369 Returns 0 if there are incoming changes, 1 otherwise.
4370 4370 """
4371 4371 opts = pycompat.byteskwargs(opts)
4372 4372 if opts.get(b'graph'):
4373 4373 logcmdutil.checkunsupportedgraphflags([], opts)
4374 4374
4375 4375 def display(other, chlist, displayer):
4376 4376 revdag = logcmdutil.graphrevs(other, chlist, opts)
4377 4377 logcmdutil.displaygraph(
4378 4378 ui, repo, revdag, displayer, graphmod.asciiedges
4379 4379 )
4380 4380
4381 4381 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4382 4382 return 0
4383 4383
4384 4384 if opts.get(b'bundle') and opts.get(b'subrepos'):
4385 4385 raise error.Abort(_(b'cannot combine --bundle and --subrepos'))
4386 4386
4387 4387 if opts.get(b'bookmarks'):
4388 4388 source, branches = hg.parseurl(
4389 4389 ui.expandpath(source), opts.get(b'branch')
4390 4390 )
4391 4391 other = hg.peer(repo, opts, source)
4392 4392 if b'bookmarks' not in other.listkeys(b'namespaces'):
4393 4393 ui.warn(_(b"remote doesn't support bookmarks\n"))
4394 4394 return 0
4395 4395 ui.pager(b'incoming')
4396 4396 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4397 4397 return bookmarks.incoming(ui, repo, other)
4398 4398
4399 4399 repo._subtoppath = ui.expandpath(source)
4400 4400 try:
4401 4401 return hg.incoming(ui, repo, source, opts)
4402 4402 finally:
4403 4403 del repo._subtoppath
4404 4404
4405 4405
4406 4406 @command(
4407 4407 b'init',
4408 4408 remoteopts,
4409 4409 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4410 4410 helpcategory=command.CATEGORY_REPO_CREATION,
4411 4411 helpbasic=True,
4412 4412 norepo=True,
4413 4413 )
4414 4414 def init(ui, dest=b".", **opts):
4415 4415 """create a new repository in the given directory
4416 4416
4417 4417 Initialize a new repository in the given directory. If the given
4418 4418 directory does not exist, it will be created.
4419 4419
4420 4420 If no directory is given, the current directory is used.
4421 4421
4422 4422 It is possible to specify an ``ssh://`` URL as the destination.
4423 4423 See :hg:`help urls` for more information.
4424 4424
4425 4425 Returns 0 on success.
4426 4426 """
4427 4427 opts = pycompat.byteskwargs(opts)
4428 4428 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4429 4429
4430 4430
4431 4431 @command(
4432 4432 b'locate',
4433 4433 [
4434 4434 (
4435 4435 b'r',
4436 4436 b'rev',
4437 4437 b'',
4438 4438 _(b'search the repository as it is in REV'),
4439 4439 _(b'REV'),
4440 4440 ),
4441 4441 (
4442 4442 b'0',
4443 4443 b'print0',
4444 4444 None,
4445 4445 _(b'end filenames with NUL, for use with xargs'),
4446 4446 ),
4447 4447 (
4448 4448 b'f',
4449 4449 b'fullpath',
4450 4450 None,
4451 4451 _(b'print complete paths from the filesystem root'),
4452 4452 ),
4453 4453 ]
4454 4454 + walkopts,
4455 4455 _(b'[OPTION]... [PATTERN]...'),
4456 4456 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4457 4457 )
4458 4458 def locate(ui, repo, *pats, **opts):
4459 4459 """locate files matching specific patterns (DEPRECATED)
4460 4460
4461 4461 Print files under Mercurial control in the working directory whose
4462 4462 names match the given patterns.
4463 4463
4464 4464 By default, this command searches all directories in the working
4465 4465 directory. To search just the current directory and its
4466 4466 subdirectories, use "--include .".
4467 4467
4468 4468 If no patterns are given to match, this command prints the names
4469 4469 of all files under Mercurial control in the working directory.
4470 4470
4471 4471 If you want to feed the output of this command into the "xargs"
4472 4472 command, use the -0 option to both this command and "xargs". This
4473 4473 will avoid the problem of "xargs" treating single filenames that
4474 4474 contain whitespace as multiple filenames.
4475 4475
4476 4476 See :hg:`help files` for a more versatile command.
4477 4477
4478 4478 Returns 0 if a match is found, 1 otherwise.
4479 4479 """
4480 4480 opts = pycompat.byteskwargs(opts)
4481 4481 if opts.get(b'print0'):
4482 4482 end = b'\0'
4483 4483 else:
4484 4484 end = b'\n'
4485 4485 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4486 4486
4487 4487 ret = 1
4488 4488 m = scmutil.match(
4489 4489 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4490 4490 )
4491 4491
4492 4492 ui.pager(b'locate')
4493 4493 if ctx.rev() is None:
4494 4494 # When run on the working copy, "locate" includes removed files, so
4495 4495 # we get the list of files from the dirstate.
4496 4496 filesgen = sorted(repo.dirstate.matches(m))
4497 4497 else:
4498 4498 filesgen = ctx.matches(m)
4499 4499 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4500 4500 for abs in filesgen:
4501 4501 if opts.get(b'fullpath'):
4502 4502 ui.write(repo.wjoin(abs), end)
4503 4503 else:
4504 4504 ui.write(uipathfn(abs), end)
4505 4505 ret = 0
4506 4506
4507 4507 return ret
4508 4508
4509 4509
4510 4510 @command(
4511 4511 b'log|history',
4512 4512 [
4513 4513 (
4514 4514 b'f',
4515 4515 b'follow',
4516 4516 None,
4517 4517 _(
4518 4518 b'follow changeset history, or file history across copies and renames'
4519 4519 ),
4520 4520 ),
4521 4521 (
4522 4522 b'',
4523 4523 b'follow-first',
4524 4524 None,
4525 4525 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4526 4526 ),
4527 4527 (
4528 4528 b'd',
4529 4529 b'date',
4530 4530 b'',
4531 4531 _(b'show revisions matching date spec'),
4532 4532 _(b'DATE'),
4533 4533 ),
4534 4534 (b'C', b'copies', None, _(b'show copied files')),
4535 4535 (
4536 4536 b'k',
4537 4537 b'keyword',
4538 4538 [],
4539 4539 _(b'do case-insensitive search for a given text'),
4540 4540 _(b'TEXT'),
4541 4541 ),
4542 4542 (
4543 4543 b'r',
4544 4544 b'rev',
4545 4545 [],
4546 4546 _(b'show the specified revision or revset'),
4547 4547 _(b'REV'),
4548 4548 ),
4549 4549 (
4550 4550 b'L',
4551 4551 b'line-range',
4552 4552 [],
4553 4553 _(b'follow line range of specified file (EXPERIMENTAL)'),
4554 4554 _(b'FILE,RANGE'),
4555 4555 ),
4556 4556 (
4557 4557 b'',
4558 4558 b'removed',
4559 4559 None,
4560 4560 _(b'include revisions where files were removed'),
4561 4561 ),
4562 4562 (
4563 4563 b'm',
4564 4564 b'only-merges',
4565 4565 None,
4566 4566 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4567 4567 ),
4568 4568 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4569 4569 (
4570 4570 b'',
4571 4571 b'only-branch',
4572 4572 [],
4573 4573 _(
4574 4574 b'show only changesets within the given named branch (DEPRECATED)'
4575 4575 ),
4576 4576 _(b'BRANCH'),
4577 4577 ),
4578 4578 (
4579 4579 b'b',
4580 4580 b'branch',
4581 4581 [],
4582 4582 _(b'show changesets within the given named branch'),
4583 4583 _(b'BRANCH'),
4584 4584 ),
4585 4585 (
4586 4586 b'P',
4587 4587 b'prune',
4588 4588 [],
4589 4589 _(b'do not display revision or any of its ancestors'),
4590 4590 _(b'REV'),
4591 4591 ),
4592 4592 ]
4593 4593 + logopts
4594 4594 + walkopts,
4595 4595 _(b'[OPTION]... [FILE]'),
4596 4596 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4597 4597 helpbasic=True,
4598 4598 inferrepo=True,
4599 4599 intents={INTENT_READONLY},
4600 4600 )
4601 4601 def log(ui, repo, *pats, **opts):
4602 4602 """show revision history of entire repository or files
4603 4603
4604 4604 Print the revision history of the specified files or the entire
4605 4605 project.
4606 4606
4607 4607 If no revision range is specified, the default is ``tip:0`` unless
4608 4608 --follow is set, in which case the working directory parent is
4609 4609 used as the starting revision.
4610 4610
4611 4611 File history is shown without following rename or copy history of
4612 4612 files. Use -f/--follow with a filename to follow history across
4613 4613 renames and copies. --follow without a filename will only show
4614 4614 ancestors of the starting revision.
4615 4615
4616 4616 By default this command prints revision number and changeset id,
4617 4617 tags, non-trivial parents, user, date and time, and a summary for
4618 4618 each commit. When the -v/--verbose switch is used, the list of
4619 4619 changed files and full commit message are shown.
4620 4620
4621 4621 With --graph the revisions are shown as an ASCII art DAG with the most
4622 4622 recent changeset at the top.
4623 4623 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4624 4624 involved in an unresolved merge conflict, '_' closes a branch,
4625 4625 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4626 4626 changeset from the lines below is a parent of the 'o' merge on the same
4627 4627 line.
4628 4628 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4629 4629 of a '|' indicates one or more revisions in a path are omitted.
4630 4630
4631 4631 .. container:: verbose
4632 4632
4633 4633 Use -L/--line-range FILE,M:N options to follow the history of lines
4634 4634 from M to N in FILE. With -p/--patch only diff hunks affecting
4635 4635 specified line range will be shown. This option requires --follow;
4636 4636 it can be specified multiple times. Currently, this option is not
4637 4637 compatible with --graph. This option is experimental.
4638 4638
4639 4639 .. note::
4640 4640
4641 4641 :hg:`log --patch` may generate unexpected diff output for merge
4642 4642 changesets, as it will only compare the merge changeset against
4643 4643 its first parent. Also, only files different from BOTH parents
4644 4644 will appear in files:.
4645 4645
4646 4646 .. note::
4647 4647
4648 4648 For performance reasons, :hg:`log FILE` may omit duplicate changes
4649 4649 made on branches and will not show removals or mode changes. To
4650 4650 see all such changes, use the --removed switch.
4651 4651
4652 4652 .. container:: verbose
4653 4653
4654 4654 .. note::
4655 4655
4656 4656 The history resulting from -L/--line-range options depends on diff
4657 4657 options; for instance if white-spaces are ignored, respective changes
4658 4658 with only white-spaces in specified line range will not be listed.
4659 4659
4660 4660 .. container:: verbose
4661 4661
4662 4662 Some examples:
4663 4663
4664 4664 - changesets with full descriptions and file lists::
4665 4665
4666 4666 hg log -v
4667 4667
4668 4668 - changesets ancestral to the working directory::
4669 4669
4670 4670 hg log -f
4671 4671
4672 4672 - last 10 commits on the current branch::
4673 4673
4674 4674 hg log -l 10 -b .
4675 4675
4676 4676 - changesets showing all modifications of a file, including removals::
4677 4677
4678 4678 hg log --removed file.c
4679 4679
4680 4680 - all changesets that touch a directory, with diffs, excluding merges::
4681 4681
4682 4682 hg log -Mp lib/
4683 4683
4684 4684 - all revision numbers that match a keyword::
4685 4685
4686 4686 hg log -k bug --template "{rev}\\n"
4687 4687
4688 4688 - the full hash identifier of the working directory parent::
4689 4689
4690 4690 hg log -r . --template "{node}\\n"
4691 4691
4692 4692 - list available log templates::
4693 4693
4694 4694 hg log -T list
4695 4695
4696 4696 - check if a given changeset is included in a tagged release::
4697 4697
4698 4698 hg log -r "a21ccf and ancestor(1.9)"
4699 4699
4700 4700 - find all changesets by some user in a date range::
4701 4701
4702 4702 hg log -k alice -d "may 2008 to jul 2008"
4703 4703
4704 4704 - summary of all changesets after the last tag::
4705 4705
4706 4706 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4707 4707
4708 4708 - changesets touching lines 13 to 23 for file.c::
4709 4709
4710 4710 hg log -L file.c,13:23
4711 4711
4712 4712 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4713 4713 main.c with patch::
4714 4714
4715 4715 hg log -L file.c,13:23 -L main.c,2:6 -p
4716 4716
4717 4717 See :hg:`help dates` for a list of formats valid for -d/--date.
4718 4718
4719 4719 See :hg:`help revisions` for more about specifying and ordering
4720 4720 revisions.
4721 4721
4722 4722 See :hg:`help templates` for more about pre-packaged styles and
4723 4723 specifying custom templates. The default template used by the log
4724 4724 command can be customized via the ``ui.logtemplate`` configuration
4725 4725 setting.
4726 4726
4727 4727 Returns 0 on success.
4728 4728
4729 4729 """
4730 4730 opts = pycompat.byteskwargs(opts)
4731 4731 linerange = opts.get(b'line_range')
4732 4732
4733 4733 if linerange and not opts.get(b'follow'):
4734 4734 raise error.Abort(_(b'--line-range requires --follow'))
4735 4735
4736 4736 if linerange and pats:
4737 4737 # TODO: take pats as patterns with no line-range filter
4738 4738 raise error.Abort(
4739 4739 _(b'FILE arguments are not compatible with --line-range option')
4740 4740 )
4741 4741
4742 4742 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4743 4743 revs, differ = logcmdutil.getrevs(repo, pats, opts)
4744 4744 if linerange:
4745 4745 # TODO: should follow file history from logcmdutil._initialrevs(),
4746 4746 # then filter the result by logcmdutil._makerevset() and --limit
4747 4747 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4748 4748
4749 4749 getcopies = None
4750 4750 if opts.get(b'copies'):
4751 4751 endrev = None
4752 4752 if revs:
4753 4753 endrev = revs.max() + 1
4754 4754 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4755 4755
4756 4756 ui.pager(b'log')
4757 4757 displayer = logcmdutil.changesetdisplayer(
4758 4758 ui, repo, opts, differ, buffered=True
4759 4759 )
4760 4760 if opts.get(b'graph'):
4761 4761 displayfn = logcmdutil.displaygraphrevs
4762 4762 else:
4763 4763 displayfn = logcmdutil.displayrevs
4764 4764 displayfn(ui, repo, revs, displayer, getcopies)
4765 4765
4766 4766
4767 4767 @command(
4768 4768 b'manifest',
4769 4769 [
4770 4770 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4771 4771 (b'', b'all', False, _(b"list files from all revisions")),
4772 4772 ]
4773 4773 + formatteropts,
4774 4774 _(b'[-r REV]'),
4775 4775 helpcategory=command.CATEGORY_MAINTENANCE,
4776 4776 intents={INTENT_READONLY},
4777 4777 )
4778 4778 def manifest(ui, repo, node=None, rev=None, **opts):
4779 4779 """output the current or given revision of the project manifest
4780 4780
4781 4781 Print a list of version controlled files for the given revision.
4782 4782 If no revision is given, the first parent of the working directory
4783 4783 is used, or the null revision if no revision is checked out.
4784 4784
4785 4785 With -v, print file permissions, symlink and executable bits.
4786 4786 With --debug, print file revision hashes.
4787 4787
4788 4788 If option --all is specified, the list of all files from all revisions
4789 4789 is printed. This includes deleted and renamed files.
4790 4790
4791 4791 Returns 0 on success.
4792 4792 """
4793 4793 opts = pycompat.byteskwargs(opts)
4794 4794 fm = ui.formatter(b'manifest', opts)
4795 4795
4796 4796 if opts.get(b'all'):
4797 4797 if rev or node:
4798 4798 raise error.Abort(_(b"can't specify a revision with --all"))
4799 4799
4800 4800 res = set()
4801 4801 for rev in repo:
4802 4802 ctx = repo[rev]
4803 4803 res |= set(ctx.files())
4804 4804
4805 4805 ui.pager(b'manifest')
4806 4806 for f in sorted(res):
4807 4807 fm.startitem()
4808 4808 fm.write(b"path", b'%s\n', f)
4809 4809 fm.end()
4810 4810 return
4811 4811
4812 4812 if rev and node:
4813 4813 raise error.Abort(_(b"please specify just one revision"))
4814 4814
4815 4815 if not node:
4816 4816 node = rev
4817 4817
4818 4818 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4819 4819 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4820 4820 if node:
4821 4821 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4822 4822 ctx = scmutil.revsingle(repo, node)
4823 4823 mf = ctx.manifest()
4824 4824 ui.pager(b'manifest')
4825 4825 for f in ctx:
4826 4826 fm.startitem()
4827 4827 fm.context(ctx=ctx)
4828 4828 fl = ctx[f].flags()
4829 4829 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4830 4830 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4831 4831 fm.write(b'path', b'%s\n', f)
4832 4832 fm.end()
4833 4833
4834 4834
4835 4835 @command(
4836 4836 b'merge',
4837 4837 [
4838 4838 (
4839 4839 b'f',
4840 4840 b'force',
4841 4841 None,
4842 4842 _(b'force a merge including outstanding changes (DEPRECATED)'),
4843 4843 ),
4844 4844 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4845 4845 (
4846 4846 b'P',
4847 4847 b'preview',
4848 4848 None,
4849 4849 _(b'review revisions to merge (no merge is performed)'),
4850 4850 ),
4851 4851 (b'', b'abort', None, _(b'abort the ongoing merge')),
4852 4852 ]
4853 4853 + mergetoolopts,
4854 4854 _(b'[-P] [[-r] REV]'),
4855 4855 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4856 4856 helpbasic=True,
4857 4857 )
4858 4858 def merge(ui, repo, node=None, **opts):
4859 4859 """merge another revision into working directory
4860 4860
4861 4861 The current working directory is updated with all changes made in
4862 4862 the requested revision since the last common predecessor revision.
4863 4863
4864 4864 Files that changed between either parent are marked as changed for
4865 4865 the next commit and a commit must be performed before any further
4866 4866 updates to the repository are allowed. The next commit will have
4867 4867 two parents.
4868 4868
4869 4869 ``--tool`` can be used to specify the merge tool used for file
4870 4870 merges. It overrides the HGMERGE environment variable and your
4871 4871 configuration files. See :hg:`help merge-tools` for options.
4872 4872
4873 4873 If no revision is specified, the working directory's parent is a
4874 4874 head revision, and the current branch contains exactly one other
4875 4875 head, the other head is merged with by default. Otherwise, an
4876 4876 explicit revision with which to merge must be provided.
4877 4877
4878 4878 See :hg:`help resolve` for information on handling file conflicts.
4879 4879
4880 4880 To undo an uncommitted merge, use :hg:`merge --abort` which
4881 4881 will check out a clean copy of the original merge parent, losing
4882 4882 all changes.
4883 4883
4884 4884 Returns 0 on success, 1 if there are unresolved files.
4885 4885 """
4886 4886
4887 4887 opts = pycompat.byteskwargs(opts)
4888 4888 abort = opts.get(b'abort')
4889 4889 if abort and repo.dirstate.p2() == nullid:
4890 4890 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4891 4891 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4892 4892 if abort:
4893 4893 state = cmdutil.getunfinishedstate(repo)
4894 4894 if state and state._opname != b'merge':
4895 4895 raise error.Abort(
4896 4896 _(b'cannot abort merge with %s in progress') % (state._opname),
4897 4897 hint=state.hint(),
4898 4898 )
4899 4899 if node:
4900 4900 raise error.Abort(_(b"cannot specify a node with --abort"))
4901 4901 return hg.abortmerge(repo.ui, repo)
4902 4902
4903 4903 if opts.get(b'rev') and node:
4904 4904 raise error.Abort(_(b"please specify just one revision"))
4905 4905 if not node:
4906 4906 node = opts.get(b'rev')
4907 4907
4908 4908 if node:
4909 4909 ctx = scmutil.revsingle(repo, node)
4910 4910 else:
4911 4911 if ui.configbool(b'commands', b'merge.require-rev'):
4912 4912 raise error.Abort(
4913 4913 _(
4914 4914 b'configuration requires specifying revision to merge '
4915 4915 b'with'
4916 4916 )
4917 4917 )
4918 4918 ctx = repo[destutil.destmerge(repo)]
4919 4919
4920 4920 if ctx.node() is None:
4921 4921 raise error.Abort(_(b'merging with the working copy has no effect'))
4922 4922
4923 4923 if opts.get(b'preview'):
4924 4924 # find nodes that are ancestors of p2 but not of p1
4925 4925 p1 = repo[b'.'].node()
4926 4926 p2 = ctx.node()
4927 4927 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4928 4928
4929 4929 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4930 4930 for node in nodes:
4931 4931 displayer.show(repo[node])
4932 4932 displayer.close()
4933 4933 return 0
4934 4934
4935 4935 # ui.forcemerge is an internal variable, do not document
4936 4936 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4937 4937 with ui.configoverride(overrides, b'merge'):
4938 4938 force = opts.get(b'force')
4939 4939 labels = [b'working copy', b'merge rev']
4940 4940 return hg.merge(ctx, force=force, labels=labels)
4941 4941
4942 4942
4943 4943 statemod.addunfinished(
4944 4944 b'merge',
4945 4945 fname=None,
4946 4946 clearable=True,
4947 4947 allowcommit=True,
4948 4948 cmdmsg=_(b'outstanding uncommitted merge'),
4949 4949 abortfunc=hg.abortmerge,
4950 4950 statushint=_(
4951 4951 b'To continue: hg commit\nTo abort: hg merge --abort'
4952 4952 ),
4953 4953 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4954 4954 )
4955 4955
4956 4956
4957 4957 @command(
4958 4958 b'outgoing|out',
4959 4959 [
4960 4960 (
4961 4961 b'f',
4962 4962 b'force',
4963 4963 None,
4964 4964 _(b'run even when the destination is unrelated'),
4965 4965 ),
4966 4966 (
4967 4967 b'r',
4968 4968 b'rev',
4969 4969 [],
4970 4970 _(b'a changeset intended to be included in the destination'),
4971 4971 _(b'REV'),
4972 4972 ),
4973 4973 (b'n', b'newest-first', None, _(b'show newest record first')),
4974 4974 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4975 4975 (
4976 4976 b'b',
4977 4977 b'branch',
4978 4978 [],
4979 4979 _(b'a specific branch you would like to push'),
4980 4980 _(b'BRANCH'),
4981 4981 ),
4982 4982 ]
4983 4983 + logopts
4984 4984 + remoteopts
4985 4985 + subrepoopts,
4986 4986 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4987 4987 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4988 4988 )
4989 4989 def outgoing(ui, repo, dest=None, **opts):
4990 4990 """show changesets not found in the destination
4991 4991
4992 4992 Show changesets not found in the specified destination repository
4993 4993 or the default push location. These are the changesets that would
4994 4994 be pushed if a push was requested.
4995 4995
4996 4996 See pull for details of valid destination formats.
4997 4997
4998 4998 .. container:: verbose
4999 4999
5000 5000 With -B/--bookmarks, the result of bookmark comparison between
5001 5001 local and remote repositories is displayed. With -v/--verbose,
5002 5002 status is also displayed for each bookmark like below::
5003 5003
5004 5004 BM1 01234567890a added
5005 5005 BM2 deleted
5006 5006 BM3 234567890abc advanced
5007 5007 BM4 34567890abcd diverged
5008 5008 BM5 4567890abcde changed
5009 5009
5010 5010 The action taken when pushing depends on the
5011 5011 status of each bookmark:
5012 5012
5013 5013 :``added``: push with ``-B`` will create it
5014 5014 :``deleted``: push with ``-B`` will delete it
5015 5015 :``advanced``: push will update it
5016 5016 :``diverged``: push with ``-B`` will update it
5017 5017 :``changed``: push with ``-B`` will update it
5018 5018
5019 5019 From the point of view of pushing behavior, bookmarks
5020 5020 existing only in the remote repository are treated as
5021 5021 ``deleted``, even if it is in fact added remotely.
5022 5022
5023 5023 Returns 0 if there are outgoing changes, 1 otherwise.
5024 5024 """
5025 5025 # hg._outgoing() needs to re-resolve the path in order to handle #branch
5026 5026 # style URLs, so don't overwrite dest.
5027 5027 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5028 5028 if not path:
5029 5029 raise error.Abort(
5030 5030 _(b'default repository not configured!'),
5031 5031 hint=_(b"see 'hg help config.paths'"),
5032 5032 )
5033 5033
5034 5034 opts = pycompat.byteskwargs(opts)
5035 5035 if opts.get(b'graph'):
5036 5036 logcmdutil.checkunsupportedgraphflags([], opts)
5037 5037 o, other = hg._outgoing(ui, repo, dest, opts)
5038 5038 if not o:
5039 5039 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5040 5040 return
5041 5041
5042 5042 revdag = logcmdutil.graphrevs(repo, o, opts)
5043 5043 ui.pager(b'outgoing')
5044 5044 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
5045 5045 logcmdutil.displaygraph(
5046 5046 ui, repo, revdag, displayer, graphmod.asciiedges
5047 5047 )
5048 5048 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5049 5049 return 0
5050 5050
5051 5051 if opts.get(b'bookmarks'):
5052 5052 dest = path.pushloc or path.loc
5053 5053 other = hg.peer(repo, opts, dest)
5054 5054 if b'bookmarks' not in other.listkeys(b'namespaces'):
5055 5055 ui.warn(_(b"remote doesn't support bookmarks\n"))
5056 5056 return 0
5057 5057 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
5058 5058 ui.pager(b'outgoing')
5059 5059 return bookmarks.outgoing(ui, repo, other)
5060 5060
5061 5061 repo._subtoppath = path.pushloc or path.loc
5062 5062 try:
5063 5063 return hg.outgoing(ui, repo, dest, opts)
5064 5064 finally:
5065 5065 del repo._subtoppath
5066 5066
5067 5067
5068 5068 @command(
5069 5069 b'parents',
5070 5070 [
5071 5071 (
5072 5072 b'r',
5073 5073 b'rev',
5074 5074 b'',
5075 5075 _(b'show parents of the specified revision'),
5076 5076 _(b'REV'),
5077 5077 ),
5078 5078 ]
5079 5079 + templateopts,
5080 5080 _(b'[-r REV] [FILE]'),
5081 5081 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5082 5082 inferrepo=True,
5083 5083 )
5084 5084 def parents(ui, repo, file_=None, **opts):
5085 5085 """show the parents of the working directory or revision (DEPRECATED)
5086 5086
5087 5087 Print the working directory's parent revisions. If a revision is
5088 5088 given via -r/--rev, the parent of that revision will be printed.
5089 5089 If a file argument is given, the revision in which the file was
5090 5090 last changed (before the working directory revision or the
5091 5091 argument to --rev if given) is printed.
5092 5092
5093 5093 This command is equivalent to::
5094 5094
5095 5095 hg log -r "p1()+p2()" or
5096 5096 hg log -r "p1(REV)+p2(REV)" or
5097 5097 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5098 5098 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5099 5099
5100 5100 See :hg:`summary` and :hg:`help revsets` for related information.
5101 5101
5102 5102 Returns 0 on success.
5103 5103 """
5104 5104
5105 5105 opts = pycompat.byteskwargs(opts)
5106 5106 rev = opts.get(b'rev')
5107 5107 if rev:
5108 5108 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5109 5109 ctx = scmutil.revsingle(repo, rev, None)
5110 5110
5111 5111 if file_:
5112 5112 m = scmutil.match(ctx, (file_,), opts)
5113 5113 if m.anypats() or len(m.files()) != 1:
5114 5114 raise error.Abort(_(b'can only specify an explicit filename'))
5115 5115 file_ = m.files()[0]
5116 5116 filenodes = []
5117 5117 for cp in ctx.parents():
5118 5118 if not cp:
5119 5119 continue
5120 5120 try:
5121 5121 filenodes.append(cp.filenode(file_))
5122 5122 except error.LookupError:
5123 5123 pass
5124 5124 if not filenodes:
5125 5125 raise error.Abort(_(b"'%s' not found in manifest!") % file_)
5126 5126 p = []
5127 5127 for fn in filenodes:
5128 5128 fctx = repo.filectx(file_, fileid=fn)
5129 5129 p.append(fctx.node())
5130 5130 else:
5131 5131 p = [cp.node() for cp in ctx.parents()]
5132 5132
5133 5133 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5134 5134 for n in p:
5135 5135 if n != nullid:
5136 5136 displayer.show(repo[n])
5137 5137 displayer.close()
5138 5138
5139 5139
5140 5140 @command(
5141 5141 b'paths',
5142 5142 formatteropts,
5143 5143 _(b'[NAME]'),
5144 5144 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5145 5145 optionalrepo=True,
5146 5146 intents={INTENT_READONLY},
5147 5147 )
5148 5148 def paths(ui, repo, search=None, **opts):
5149 5149 """show aliases for remote repositories
5150 5150
5151 5151 Show definition of symbolic path name NAME. If no name is given,
5152 5152 show definition of all available names.
5153 5153
5154 5154 Option -q/--quiet suppresses all output when searching for NAME
5155 5155 and shows only the path names when listing all definitions.
5156 5156
5157 5157 Path names are defined in the [paths] section of your
5158 5158 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5159 5159 repository, ``.hg/hgrc`` is used, too.
5160 5160
5161 5161 The path names ``default`` and ``default-push`` have a special
5162 5162 meaning. When performing a push or pull operation, they are used
5163 5163 as fallbacks if no location is specified on the command-line.
5164 5164 When ``default-push`` is set, it will be used for push and
5165 5165 ``default`` will be used for pull; otherwise ``default`` is used
5166 5166 as the fallback for both. When cloning a repository, the clone
5167 5167 source is written as ``default`` in ``.hg/hgrc``.
5168 5168
5169 5169 .. note::
5170 5170
5171 5171 ``default`` and ``default-push`` apply to all inbound (e.g.
5172 5172 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5173 5173 and :hg:`bundle`) operations.
5174 5174
5175 5175 See :hg:`help urls` for more information.
5176 5176
5177 5177 .. container:: verbose
5178 5178
5179 5179 Template:
5180 5180
5181 5181 The following keywords are supported. See also :hg:`help templates`.
5182 5182
5183 5183 :name: String. Symbolic name of the path alias.
5184 5184 :pushurl: String. URL for push operations.
5185 5185 :url: String. URL or directory path for the other operations.
5186 5186
5187 5187 Returns 0 on success.
5188 5188 """
5189 5189
5190 5190 opts = pycompat.byteskwargs(opts)
5191 5191 ui.pager(b'paths')
5192 5192 if search:
5193 5193 pathitems = [
5194 5194 (name, path)
5195 5195 for name, path in pycompat.iteritems(ui.paths)
5196 5196 if name == search
5197 5197 ]
5198 5198 else:
5199 5199 pathitems = sorted(pycompat.iteritems(ui.paths))
5200 5200
5201 5201 fm = ui.formatter(b'paths', opts)
5202 5202 if fm.isplain():
5203 5203 hidepassword = util.hidepassword
5204 5204 else:
5205 5205 hidepassword = bytes
5206 5206 if ui.quiet:
5207 5207 namefmt = b'%s\n'
5208 5208 else:
5209 5209 namefmt = b'%s = '
5210 5210 showsubopts = not search and not ui.quiet
5211 5211
5212 5212 for name, path in pathitems:
5213 5213 fm.startitem()
5214 5214 fm.condwrite(not search, b'name', namefmt, name)
5215 5215 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5216 5216 for subopt, value in sorted(path.suboptions.items()):
5217 5217 assert subopt not in (b'name', b'url')
5218 5218 if showsubopts:
5219 5219 fm.plain(b'%s:%s = ' % (name, subopt))
5220 5220 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5221 5221
5222 5222 fm.end()
5223 5223
5224 5224 if search and not pathitems:
5225 5225 if not ui.quiet:
5226 5226 ui.warn(_(b"not found!\n"))
5227 5227 return 1
5228 5228 else:
5229 5229 return 0
5230 5230
5231 5231
5232 5232 @command(
5233 5233 b'phase',
5234 5234 [
5235 5235 (b'p', b'public', False, _(b'set changeset phase to public')),
5236 5236 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5237 5237 (b's', b'secret', False, _(b'set changeset phase to secret')),
5238 5238 (b'f', b'force', False, _(b'allow to move boundary backward')),
5239 5239 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5240 5240 ],
5241 5241 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5242 5242 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5243 5243 )
5244 5244 def phase(ui, repo, *revs, **opts):
5245 5245 """set or show the current phase name
5246 5246
5247 5247 With no argument, show the phase name of the current revision(s).
5248 5248
5249 5249 With one of -p/--public, -d/--draft or -s/--secret, change the
5250 5250 phase value of the specified revisions.
5251 5251
5252 5252 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5253 5253 lower phase to a higher phase. Phases are ordered as follows::
5254 5254
5255 5255 public < draft < secret
5256 5256
5257 5257 Returns 0 on success, 1 if some phases could not be changed.
5258 5258
5259 5259 (For more information about the phases concept, see :hg:`help phases`.)
5260 5260 """
5261 5261 opts = pycompat.byteskwargs(opts)
5262 5262 # search for a unique phase argument
5263 5263 targetphase = None
5264 5264 for idx, name in enumerate(phases.cmdphasenames):
5265 5265 if opts[name]:
5266 5266 if targetphase is not None:
5267 5267 raise error.Abort(_(b'only one phase can be specified'))
5268 5268 targetphase = idx
5269 5269
5270 5270 # look for specified revision
5271 5271 revs = list(revs)
5272 5272 revs.extend(opts[b'rev'])
5273 5273 if not revs:
5274 5274 # display both parents as the second parent phase can influence
5275 5275 # the phase of a merge commit
5276 5276 revs = [c.rev() for c in repo[None].parents()]
5277 5277
5278 5278 revs = scmutil.revrange(repo, revs)
5279 5279
5280 5280 ret = 0
5281 5281 if targetphase is None:
5282 5282 # display
5283 5283 for r in revs:
5284 5284 ctx = repo[r]
5285 5285 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5286 5286 else:
5287 5287 with repo.lock(), repo.transaction(b"phase") as tr:
5288 5288 # set phase
5289 5289 if not revs:
5290 5290 raise error.Abort(_(b'empty revision set'))
5291 5291 nodes = [repo[r].node() for r in revs]
5292 5292 # moving revision from public to draft may hide them
5293 5293 # We have to check result on an unfiltered repository
5294 5294 unfi = repo.unfiltered()
5295 5295 getphase = unfi._phasecache.phase
5296 5296 olddata = [getphase(unfi, r) for r in unfi]
5297 5297 phases.advanceboundary(repo, tr, targetphase, nodes)
5298 5298 if opts[b'force']:
5299 5299 phases.retractboundary(repo, tr, targetphase, nodes)
5300 5300 getphase = unfi._phasecache.phase
5301 5301 newdata = [getphase(unfi, r) for r in unfi]
5302 5302 changes = sum(newdata[r] != olddata[r] for r in unfi)
5303 5303 cl = unfi.changelog
5304 5304 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5305 5305 if rejected:
5306 5306 ui.warn(
5307 5307 _(
5308 5308 b'cannot move %i changesets to a higher '
5309 5309 b'phase, use --force\n'
5310 5310 )
5311 5311 % len(rejected)
5312 5312 )
5313 5313 ret = 1
5314 5314 if changes:
5315 5315 msg = _(b'phase changed for %i changesets\n') % changes
5316 5316 if ret:
5317 5317 ui.status(msg)
5318 5318 else:
5319 5319 ui.note(msg)
5320 5320 else:
5321 5321 ui.warn(_(b'no phases changed\n'))
5322 5322 return ret
5323 5323
5324 5324
5325 5325 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5326 5326 """Run after a changegroup has been added via pull/unbundle
5327 5327
5328 5328 This takes arguments below:
5329 5329
5330 5330 :modheads: change of heads by pull/unbundle
5331 5331 :optupdate: updating working directory is needed or not
5332 5332 :checkout: update destination revision (or None to default destination)
5333 5333 :brev: a name, which might be a bookmark to be activated after updating
5334 5334 """
5335 5335 if modheads == 0:
5336 5336 return
5337 5337 if optupdate:
5338 5338 try:
5339 5339 return hg.updatetotally(ui, repo, checkout, brev)
5340 5340 except error.UpdateAbort as inst:
5341 5341 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5342 5342 hint = inst.hint
5343 5343 raise error.UpdateAbort(msg, hint=hint)
5344 5344 if modheads is not None and modheads > 1:
5345 5345 currentbranchheads = len(repo.branchheads())
5346 5346 if currentbranchheads == modheads:
5347 5347 ui.status(
5348 5348 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5349 5349 )
5350 5350 elif currentbranchheads > 1:
5351 5351 ui.status(
5352 5352 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5353 5353 )
5354 5354 else:
5355 5355 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5356 5356 elif not ui.configbool(b'commands', b'update.requiredest'):
5357 5357 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5358 5358
5359 5359
5360 5360 @command(
5361 5361 b'pull',
5362 5362 [
5363 5363 (
5364 5364 b'u',
5365 5365 b'update',
5366 5366 None,
5367 5367 _(b'update to new branch head if new descendants were pulled'),
5368 5368 ),
5369 5369 (
5370 5370 b'f',
5371 5371 b'force',
5372 5372 None,
5373 5373 _(b'run even when remote repository is unrelated'),
5374 5374 ),
5375 5375 (b'', b'confirm', None, _(b'confirm pull before applying changes'),),
5376 5376 (
5377 5377 b'r',
5378 5378 b'rev',
5379 5379 [],
5380 5380 _(b'a remote changeset intended to be added'),
5381 5381 _(b'REV'),
5382 5382 ),
5383 5383 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5384 5384 (
5385 5385 b'b',
5386 5386 b'branch',
5387 5387 [],
5388 5388 _(b'a specific branch you would like to pull'),
5389 5389 _(b'BRANCH'),
5390 5390 ),
5391 5391 ]
5392 5392 + remoteopts,
5393 5393 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5394 5394 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5395 5395 helpbasic=True,
5396 5396 )
5397 5397 def pull(ui, repo, source=b"default", **opts):
5398 5398 """pull changes from the specified source
5399 5399
5400 5400 Pull changes from a remote repository to a local one.
5401 5401
5402 5402 This finds all changes from the repository at the specified path
5403 5403 or URL and adds them to a local repository (the current one unless
5404 5404 -R is specified). By default, this does not update the copy of the
5405 5405 project in the working directory.
5406 5406
5407 5407 When cloning from servers that support it, Mercurial may fetch
5408 5408 pre-generated data. When this is done, hooks operating on incoming
5409 5409 changesets and changegroups may fire more than once, once for each
5410 5410 pre-generated bundle and as well as for any additional remaining
5411 5411 data. See :hg:`help -e clonebundles` for more.
5412 5412
5413 5413 Use :hg:`incoming` if you want to see what would have been added
5414 5414 by a pull at the time you issued this command. If you then decide
5415 5415 to add those changes to the repository, you should use :hg:`pull
5416 5416 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5417 5417
5418 5418 If SOURCE is omitted, the 'default' path will be used.
5419 5419 See :hg:`help urls` for more information.
5420 5420
5421 5421 Specifying bookmark as ``.`` is equivalent to specifying the active
5422 5422 bookmark's name.
5423 5423
5424 5424 Returns 0 on success, 1 if an update had unresolved files.
5425 5425 """
5426 5426
5427 5427 opts = pycompat.byteskwargs(opts)
5428 5428 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5429 5429 b'update'
5430 5430 ):
5431 5431 msg = _(b'update destination required by configuration')
5432 5432 hint = _(b'use hg pull followed by hg update DEST')
5433 5433 raise error.Abort(msg, hint=hint)
5434 5434
5435 5435 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5436 5436 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5437 5437 other = hg.peer(repo, opts, source)
5438 5438 try:
5439 5439 revs, checkout = hg.addbranchrevs(
5440 5440 repo, other, branches, opts.get(b'rev')
5441 5441 )
5442 5442
5443 5443 pullopargs = {}
5444 5444
5445 5445 nodes = None
5446 5446 if opts.get(b'bookmark') or revs:
5447 5447 # The list of bookmark used here is the same used to actually update
5448 5448 # the bookmark names, to avoid the race from issue 4689 and we do
5449 5449 # all lookup and bookmark queries in one go so they see the same
5450 5450 # version of the server state (issue 4700).
5451 5451 nodes = []
5452 5452 fnodes = []
5453 5453 revs = revs or []
5454 5454 if revs and not other.capable(b'lookup'):
5455 5455 err = _(
5456 5456 b"other repository doesn't support revision lookup, "
5457 5457 b"so a rev cannot be specified."
5458 5458 )
5459 5459 raise error.Abort(err)
5460 5460 with other.commandexecutor() as e:
5461 5461 fremotebookmarks = e.callcommand(
5462 5462 b'listkeys', {b'namespace': b'bookmarks'}
5463 5463 )
5464 5464 for r in revs:
5465 5465 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5466 5466 remotebookmarks = fremotebookmarks.result()
5467 5467 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5468 5468 pullopargs[b'remotebookmarks'] = remotebookmarks
5469 5469 for b in opts.get(b'bookmark', []):
5470 5470 b = repo._bookmarks.expandname(b)
5471 5471 if b not in remotebookmarks:
5472 5472 raise error.Abort(_(b'remote bookmark %s not found!') % b)
5473 5473 nodes.append(remotebookmarks[b])
5474 5474 for i, rev in enumerate(revs):
5475 5475 node = fnodes[i].result()
5476 5476 nodes.append(node)
5477 5477 if rev == checkout:
5478 5478 checkout = node
5479 5479
5480 5480 wlock = util.nullcontextmanager()
5481 5481 if opts.get(b'update'):
5482 5482 wlock = repo.wlock()
5483 5483 with wlock:
5484 5484 pullopargs.update(opts.get(b'opargs', {}))
5485 5485 modheads = exchange.pull(
5486 5486 repo,
5487 5487 other,
5488 5488 heads=nodes,
5489 5489 force=opts.get(b'force'),
5490 5490 bookmarks=opts.get(b'bookmark', ()),
5491 5491 opargs=pullopargs,
5492 5492 confirm=opts.get(b'confirm'),
5493 5493 ).cgresult
5494 5494
5495 5495 # brev is a name, which might be a bookmark to be activated at
5496 5496 # the end of the update. In other words, it is an explicit
5497 5497 # destination of the update
5498 5498 brev = None
5499 5499
5500 5500 if checkout:
5501 5501 checkout = repo.unfiltered().changelog.rev(checkout)
5502 5502
5503 5503 # order below depends on implementation of
5504 5504 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5505 5505 # because 'checkout' is determined without it.
5506 5506 if opts.get(b'rev'):
5507 5507 brev = opts[b'rev'][0]
5508 5508 elif opts.get(b'branch'):
5509 5509 brev = opts[b'branch'][0]
5510 5510 else:
5511 5511 brev = branches[0]
5512 5512 repo._subtoppath = source
5513 5513 try:
5514 5514 ret = postincoming(
5515 5515 ui, repo, modheads, opts.get(b'update'), checkout, brev
5516 5516 )
5517 5517 except error.FilteredRepoLookupError as exc:
5518 5518 msg = _(b'cannot update to target: %s') % exc.args[0]
5519 5519 exc.args = (msg,) + exc.args[1:]
5520 5520 raise
5521 5521 finally:
5522 5522 del repo._subtoppath
5523 5523
5524 5524 finally:
5525 5525 other.close()
5526 5526 return ret
5527 5527
5528 5528
5529 5529 @command(
5530 5530 b'push',
5531 5531 [
5532 5532 (b'f', b'force', None, _(b'force push')),
5533 5533 (
5534 5534 b'r',
5535 5535 b'rev',
5536 5536 [],
5537 5537 _(b'a changeset intended to be included in the destination'),
5538 5538 _(b'REV'),
5539 5539 ),
5540 5540 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5541 5541 (
5542 5542 b'b',
5543 5543 b'branch',
5544 5544 [],
5545 5545 _(b'a specific branch you would like to push'),
5546 5546 _(b'BRANCH'),
5547 5547 ),
5548 5548 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5549 5549 (
5550 5550 b'',
5551 5551 b'pushvars',
5552 5552 [],
5553 5553 _(b'variables that can be sent to server (ADVANCED)'),
5554 5554 ),
5555 5555 (
5556 5556 b'',
5557 5557 b'publish',
5558 5558 False,
5559 5559 _(b'push the changeset as public (EXPERIMENTAL)'),
5560 5560 ),
5561 5561 ]
5562 5562 + remoteopts,
5563 5563 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5564 5564 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5565 5565 helpbasic=True,
5566 5566 )
5567 5567 def push(ui, repo, dest=None, **opts):
5568 5568 """push changes to the specified destination
5569 5569
5570 5570 Push changesets from the local repository to the specified
5571 5571 destination.
5572 5572
5573 5573 This operation is symmetrical to pull: it is identical to a pull
5574 5574 in the destination repository from the current one.
5575 5575
5576 5576 By default, push will not allow creation of new heads at the
5577 5577 destination, since multiple heads would make it unclear which head
5578 5578 to use. In this situation, it is recommended to pull and merge
5579 5579 before pushing.
5580 5580
5581 5581 Use --new-branch if you want to allow push to create a new named
5582 5582 branch that is not present at the destination. This allows you to
5583 5583 only create a new branch without forcing other changes.
5584 5584
5585 5585 .. note::
5586 5586
5587 5587 Extra care should be taken with the -f/--force option,
5588 5588 which will push all new heads on all branches, an action which will
5589 5589 almost always cause confusion for collaborators.
5590 5590
5591 5591 If -r/--rev is used, the specified revision and all its ancestors
5592 5592 will be pushed to the remote repository.
5593 5593
5594 5594 If -B/--bookmark is used, the specified bookmarked revision, its
5595 5595 ancestors, and the bookmark will be pushed to the remote
5596 5596 repository. Specifying ``.`` is equivalent to specifying the active
5597 5597 bookmark's name.
5598 5598
5599 5599 Please see :hg:`help urls` for important details about ``ssh://``
5600 5600 URLs. If DESTINATION is omitted, a default path will be used.
5601 5601
5602 5602 .. container:: verbose
5603 5603
5604 5604 The --pushvars option sends strings to the server that become
5605 5605 environment variables prepended with ``HG_USERVAR_``. For example,
5606 5606 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5607 5607 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5608 5608
5609 5609 pushvars can provide for user-overridable hooks as well as set debug
5610 5610 levels. One example is having a hook that blocks commits containing
5611 5611 conflict markers, but enables the user to override the hook if the file
5612 5612 is using conflict markers for testing purposes or the file format has
5613 5613 strings that look like conflict markers.
5614 5614
5615 5615 By default, servers will ignore `--pushvars`. To enable it add the
5616 5616 following to your configuration file::
5617 5617
5618 5618 [push]
5619 5619 pushvars.server = true
5620 5620
5621 5621 Returns 0 if push was successful, 1 if nothing to push.
5622 5622 """
5623 5623
5624 5624 opts = pycompat.byteskwargs(opts)
5625 5625 if opts.get(b'bookmark'):
5626 5626 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5627 5627 for b in opts[b'bookmark']:
5628 5628 # translate -B options to -r so changesets get pushed
5629 5629 b = repo._bookmarks.expandname(b)
5630 5630 if b in repo._bookmarks:
5631 5631 opts.setdefault(b'rev', []).append(b)
5632 5632 else:
5633 5633 # if we try to push a deleted bookmark, translate it to null
5634 5634 # this lets simultaneous -r, -b options continue working
5635 5635 opts.setdefault(b'rev', []).append(b"null")
5636 5636
5637 5637 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5638 5638 if not path:
5639 5639 raise error.Abort(
5640 5640 _(b'default repository not configured!'),
5641 5641 hint=_(b"see 'hg help config.paths'"),
5642 5642 )
5643 5643 dest = path.pushloc or path.loc
5644 5644 branches = (path.branch, opts.get(b'branch') or [])
5645 5645 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5646 5646 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5647 5647 other = hg.peer(repo, opts, dest)
5648 5648
5649 5649 if revs:
5650 5650 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5651 5651 if not revs:
5652 5652 raise error.Abort(
5653 5653 _(b"specified revisions evaluate to an empty set"),
5654 5654 hint=_(b"use different revision arguments"),
5655 5655 )
5656 5656 elif path.pushrev:
5657 5657 # It doesn't make any sense to specify ancestor revisions. So limit
5658 5658 # to DAG heads to make discovery simpler.
5659 5659 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5660 5660 revs = scmutil.revrange(repo, [expr])
5661 5661 revs = [repo[rev].node() for rev in revs]
5662 5662 if not revs:
5663 5663 raise error.Abort(
5664 5664 _(b'default push revset for path evaluates to an empty set')
5665 5665 )
5666 5666 elif ui.configbool(b'commands', b'push.require-revs'):
5667 5667 raise error.Abort(
5668 5668 _(b'no revisions specified to push'),
5669 5669 hint=_(b'did you mean "hg push -r ."?'),
5670 5670 )
5671 5671
5672 5672 repo._subtoppath = dest
5673 5673 try:
5674 5674 # push subrepos depth-first for coherent ordering
5675 5675 c = repo[b'.']
5676 5676 subs = c.substate # only repos that are committed
5677 5677 for s in sorted(subs):
5678 5678 result = c.sub(s).push(opts)
5679 5679 if result == 0:
5680 5680 return not result
5681 5681 finally:
5682 5682 del repo._subtoppath
5683 5683
5684 5684 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5685 5685 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5686 5686
5687 5687 pushop = exchange.push(
5688 5688 repo,
5689 5689 other,
5690 5690 opts.get(b'force'),
5691 5691 revs=revs,
5692 5692 newbranch=opts.get(b'new_branch'),
5693 5693 bookmarks=opts.get(b'bookmark', ()),
5694 5694 publish=opts.get(b'publish'),
5695 5695 opargs=opargs,
5696 5696 )
5697 5697
5698 5698 result = not pushop.cgresult
5699 5699
5700 5700 if pushop.bkresult is not None:
5701 5701 if pushop.bkresult == 2:
5702 5702 result = 2
5703 5703 elif not result and pushop.bkresult:
5704 5704 result = 2
5705 5705
5706 5706 return result
5707 5707
5708 5708
5709 5709 @command(
5710 5710 b'recover',
5711 5711 [(b'', b'verify', False, b"run `hg verify` after successful recover"),],
5712 5712 helpcategory=command.CATEGORY_MAINTENANCE,
5713 5713 )
5714 5714 def recover(ui, repo, **opts):
5715 5715 """roll back an interrupted transaction
5716 5716
5717 5717 Recover from an interrupted commit or pull.
5718 5718
5719 5719 This command tries to fix the repository status after an
5720 5720 interrupted operation. It should only be necessary when Mercurial
5721 5721 suggests it.
5722 5722
5723 5723 Returns 0 if successful, 1 if nothing to recover or verify fails.
5724 5724 """
5725 5725 ret = repo.recover()
5726 5726 if ret:
5727 5727 if opts['verify']:
5728 5728 return hg.verify(repo)
5729 5729 else:
5730 5730 msg = _(
5731 5731 b"(verify step skipped, run `hg verify` to check your "
5732 5732 b"repository content)\n"
5733 5733 )
5734 5734 ui.warn(msg)
5735 5735 return 0
5736 5736 return 1
5737 5737
5738 5738
5739 5739 @command(
5740 5740 b'remove|rm',
5741 5741 [
5742 5742 (b'A', b'after', None, _(b'record delete for missing files')),
5743 5743 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5744 5744 ]
5745 5745 + subrepoopts
5746 5746 + walkopts
5747 5747 + dryrunopts,
5748 5748 _(b'[OPTION]... FILE...'),
5749 5749 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5750 5750 helpbasic=True,
5751 5751 inferrepo=True,
5752 5752 )
5753 5753 def remove(ui, repo, *pats, **opts):
5754 5754 """remove the specified files on the next commit
5755 5755
5756 5756 Schedule the indicated files for removal from the current branch.
5757 5757
5758 5758 This command schedules the files to be removed at the next commit.
5759 5759 To undo a remove before that, see :hg:`revert`. To undo added
5760 5760 files, see :hg:`forget`.
5761 5761
5762 5762 .. container:: verbose
5763 5763
5764 5764 -A/--after can be used to remove only files that have already
5765 5765 been deleted, -f/--force can be used to force deletion, and -Af
5766 5766 can be used to remove files from the next revision without
5767 5767 deleting them from the working directory.
5768 5768
5769 5769 The following table details the behavior of remove for different
5770 5770 file states (columns) and option combinations (rows). The file
5771 5771 states are Added [A], Clean [C], Modified [M] and Missing [!]
5772 5772 (as reported by :hg:`status`). The actions are Warn, Remove
5773 5773 (from branch) and Delete (from disk):
5774 5774
5775 5775 ========= == == == ==
5776 5776 opt/state A C M !
5777 5777 ========= == == == ==
5778 5778 none W RD W R
5779 5779 -f R RD RD R
5780 5780 -A W W W R
5781 5781 -Af R R R R
5782 5782 ========= == == == ==
5783 5783
5784 5784 .. note::
5785 5785
5786 5786 :hg:`remove` never deletes files in Added [A] state from the
5787 5787 working directory, not even if ``--force`` is specified.
5788 5788
5789 5789 Returns 0 on success, 1 if any warnings encountered.
5790 5790 """
5791 5791
5792 5792 opts = pycompat.byteskwargs(opts)
5793 5793 after, force = opts.get(b'after'), opts.get(b'force')
5794 5794 dryrun = opts.get(b'dry_run')
5795 5795 if not pats and not after:
5796 5796 raise error.Abort(_(b'no files specified'))
5797 5797
5798 5798 m = scmutil.match(repo[None], pats, opts)
5799 5799 subrepos = opts.get(b'subrepos')
5800 5800 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5801 5801 return cmdutil.remove(
5802 5802 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5803 5803 )
5804 5804
5805 5805
5806 5806 @command(
5807 5807 b'rename|move|mv',
5808 5808 [
5809 5809 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5810 5810 (
5811 5811 b'',
5812 5812 b'at-rev',
5813 5813 b'',
5814 5814 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5815 5815 _(b'REV'),
5816 5816 ),
5817 5817 (
5818 5818 b'f',
5819 5819 b'force',
5820 5820 None,
5821 5821 _(b'forcibly move over an existing managed file'),
5822 5822 ),
5823 5823 ]
5824 5824 + walkopts
5825 5825 + dryrunopts,
5826 5826 _(b'[OPTION]... SOURCE... DEST'),
5827 5827 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5828 5828 )
5829 5829 def rename(ui, repo, *pats, **opts):
5830 5830 """rename files; equivalent of copy + remove
5831 5831
5832 5832 Mark dest as copies of sources; mark sources for deletion. If dest
5833 5833 is a directory, copies are put in that directory. If dest is a
5834 5834 file, there can only be one source.
5835 5835
5836 5836 By default, this command copies the contents of files as they
5837 5837 exist in the working directory. If invoked with -A/--after, the
5838 5838 operation is recorded, but no copying is performed.
5839 5839
5840 5840 This command takes effect at the next commit. To undo a rename
5841 5841 before that, see :hg:`revert`.
5842 5842
5843 5843 Returns 0 on success, 1 if errors are encountered.
5844 5844 """
5845 5845 opts = pycompat.byteskwargs(opts)
5846 5846 with repo.wlock():
5847 5847 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5848 5848
5849 5849
5850 5850 @command(
5851 5851 b'resolve',
5852 5852 [
5853 5853 (b'a', b'all', None, _(b'select all unresolved files')),
5854 5854 (b'l', b'list', None, _(b'list state of files needing merge')),
5855 5855 (b'm', b'mark', None, _(b'mark files as resolved')),
5856 5856 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5857 5857 (b'n', b'no-status', None, _(b'hide status prefix')),
5858 5858 (b'', b're-merge', None, _(b're-merge files')),
5859 5859 ]
5860 5860 + mergetoolopts
5861 5861 + walkopts
5862 5862 + formatteropts,
5863 5863 _(b'[OPTION]... [FILE]...'),
5864 5864 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5865 5865 inferrepo=True,
5866 5866 )
5867 5867 def resolve(ui, repo, *pats, **opts):
5868 5868 """redo merges or set/view the merge status of files
5869 5869
5870 5870 Merges with unresolved conflicts are often the result of
5871 5871 non-interactive merging using the ``internal:merge`` configuration
5872 5872 setting, or a command-line merge tool like ``diff3``. The resolve
5873 5873 command is used to manage the files involved in a merge, after
5874 5874 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5875 5875 working directory must have two parents). See :hg:`help
5876 5876 merge-tools` for information on configuring merge tools.
5877 5877
5878 5878 The resolve command can be used in the following ways:
5879 5879
5880 5880 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5881 5881 the specified files, discarding any previous merge attempts. Re-merging
5882 5882 is not performed for files already marked as resolved. Use ``--all/-a``
5883 5883 to select all unresolved files. ``--tool`` can be used to specify
5884 5884 the merge tool used for the given files. It overrides the HGMERGE
5885 5885 environment variable and your configuration files. Previous file
5886 5886 contents are saved with a ``.orig`` suffix.
5887 5887
5888 5888 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5889 5889 (e.g. after having manually fixed-up the files). The default is
5890 5890 to mark all unresolved files.
5891 5891
5892 5892 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5893 5893 default is to mark all resolved files.
5894 5894
5895 5895 - :hg:`resolve -l`: list files which had or still have conflicts.
5896 5896 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5897 5897 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5898 5898 the list. See :hg:`help filesets` for details.
5899 5899
5900 5900 .. note::
5901 5901
5902 5902 Mercurial will not let you commit files with unresolved merge
5903 5903 conflicts. You must use :hg:`resolve -m ...` before you can
5904 5904 commit after a conflicting merge.
5905 5905
5906 5906 .. container:: verbose
5907 5907
5908 5908 Template:
5909 5909
5910 5910 The following keywords are supported in addition to the common template
5911 5911 keywords and functions. See also :hg:`help templates`.
5912 5912
5913 5913 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5914 5914 :path: String. Repository-absolute path of the file.
5915 5915
5916 5916 Returns 0 on success, 1 if any files fail a resolve attempt.
5917 5917 """
5918 5918
5919 5919 opts = pycompat.byteskwargs(opts)
5920 5920 confirm = ui.configbool(b'commands', b'resolve.confirm')
5921 5921 flaglist = b'all mark unmark list no_status re_merge'.split()
5922 5922 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5923 5923
5924 5924 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5925 5925 if actioncount > 1:
5926 5926 raise error.Abort(_(b"too many actions specified"))
5927 5927 elif actioncount == 0 and ui.configbool(
5928 5928 b'commands', b'resolve.explicit-re-merge'
5929 5929 ):
5930 5930 hint = _(b'use --mark, --unmark, --list or --re-merge')
5931 5931 raise error.Abort(_(b'no action specified'), hint=hint)
5932 5932 if pats and all:
5933 5933 raise error.Abort(_(b"can't specify --all and patterns"))
5934 5934 if not (all or pats or show or mark or unmark):
5935 5935 raise error.Abort(
5936 5936 _(b'no files or directories specified'),
5937 5937 hint=b'use --all to re-merge all unresolved files',
5938 5938 )
5939 5939
5940 5940 if confirm:
5941 5941 if all:
5942 5942 if ui.promptchoice(
5943 5943 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5944 5944 ):
5945 5945 raise error.Abort(_(b'user quit'))
5946 5946 if mark and not pats:
5947 5947 if ui.promptchoice(
5948 5948 _(
5949 5949 b'mark all unresolved files as resolved (yn)?'
5950 5950 b'$$ &Yes $$ &No'
5951 5951 )
5952 5952 ):
5953 5953 raise error.Abort(_(b'user quit'))
5954 5954 if unmark and not pats:
5955 5955 if ui.promptchoice(
5956 5956 _(
5957 5957 b'mark all resolved files as unresolved (yn)?'
5958 5958 b'$$ &Yes $$ &No'
5959 5959 )
5960 5960 ):
5961 5961 raise error.Abort(_(b'user quit'))
5962 5962
5963 5963 uipathfn = scmutil.getuipathfn(repo)
5964 5964
5965 5965 if show:
5966 5966 ui.pager(b'resolve')
5967 5967 fm = ui.formatter(b'resolve', opts)
5968 5968 ms = mergestatemod.mergestate.read(repo)
5969 5969 wctx = repo[None]
5970 5970 m = scmutil.match(wctx, pats, opts)
5971 5971
5972 5972 # Labels and keys based on merge state. Unresolved path conflicts show
5973 5973 # as 'P'. Resolved path conflicts show as 'R', the same as normal
5974 5974 # resolved conflicts.
5975 5975 mergestateinfo = {
5976 5976 mergestatemod.MERGE_RECORD_UNRESOLVED: (
5977 5977 b'resolve.unresolved',
5978 5978 b'U',
5979 5979 ),
5980 5980 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
5981 5981 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
5982 5982 b'resolve.unresolved',
5983 5983 b'P',
5984 5984 ),
5985 5985 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
5986 5986 b'resolve.resolved',
5987 5987 b'R',
5988 5988 ),
5989 mergestatemod.MERGE_RECORD_DRIVER_RESOLVED: (
5990 b'resolve.driverresolved',
5991 b'D',
5992 ),
5993 5989 }
5994 5990
5995 5991 for f in ms:
5996 5992 if not m(f):
5997 5993 continue
5998 5994
5999 5995 label, key = mergestateinfo[ms[f]]
6000 5996 fm.startitem()
6001 5997 fm.context(ctx=wctx)
6002 5998 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
6003 5999 fm.data(path=f)
6004 6000 fm.plain(b'%s\n' % uipathfn(f), label=label)
6005 6001 fm.end()
6006 6002 return 0
6007 6003
6008 6004 with repo.wlock():
6009 6005 ms = mergestatemod.mergestate.read(repo)
6010 6006
6011 6007 if not (ms.active() or repo.dirstate.p2() != nullid):
6012 6008 raise error.Abort(
6013 6009 _(b'resolve command not applicable when not merging')
6014 6010 )
6015 6011
6016 6012 wctx = repo[None]
6017
6018 if (
6019 ms.mergedriver
6020 and ms.mdstate() == mergestatemod.MERGE_DRIVER_STATE_UNMARKED
6021 ):
6022 proceed = mergemod.driverpreprocess(repo, ms, wctx)
6023 ms.commit()
6024 # allow mark and unmark to go through
6025 if not mark and not unmark and not proceed:
6026 return 1
6027
6028 6013 m = scmutil.match(wctx, pats, opts)
6029 6014 ret = 0
6030 6015 didwork = False
6031 runconclude = False
6032 6016
6033 6017 tocomplete = []
6034 6018 hasconflictmarkers = []
6035 6019 if mark:
6036 6020 markcheck = ui.config(b'commands', b'resolve.mark-check')
6037 6021 if markcheck not in [b'warn', b'abort']:
6038 6022 # Treat all invalid / unrecognized values as 'none'.
6039 6023 markcheck = False
6040 6024 for f in ms:
6041 6025 if not m(f):
6042 6026 continue
6043 6027
6044 6028 didwork = True
6045 6029
6046 # don't let driver-resolved files be marked, and run the conclude
6047 # step if asked to resolve
6048 if ms[f] == mergestatemod.MERGE_RECORD_DRIVER_RESOLVED:
6049 exact = m.exact(f)
6050 if mark:
6051 if exact:
6052 ui.warn(
6053 _(b'not marking %s as it is driver-resolved\n')
6054 % uipathfn(f)
6055 )
6056 elif unmark:
6057 if exact:
6058 ui.warn(
6059 _(b'not unmarking %s as it is driver-resolved\n')
6060 % uipathfn(f)
6061 )
6062 else:
6063 runconclude = True
6064 continue
6065
6066 6030 # path conflicts must be resolved manually
6067 6031 if ms[f] in (
6068 6032 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6069 6033 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6070 6034 ):
6071 6035 if mark:
6072 6036 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6073 6037 elif unmark:
6074 6038 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6075 6039 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6076 6040 ui.warn(
6077 6041 _(b'%s: path conflict must be resolved manually\n')
6078 6042 % uipathfn(f)
6079 6043 )
6080 6044 continue
6081 6045
6082 6046 if mark:
6083 6047 if markcheck:
6084 6048 fdata = repo.wvfs.tryread(f)
6085 6049 if (
6086 6050 filemerge.hasconflictmarkers(fdata)
6087 6051 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6088 6052 ):
6089 6053 hasconflictmarkers.append(f)
6090 6054 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6091 6055 elif unmark:
6092 6056 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6093 6057 else:
6094 6058 # backup pre-resolve (merge uses .orig for its own purposes)
6095 6059 a = repo.wjoin(f)
6096 6060 try:
6097 6061 util.copyfile(a, a + b".resolve")
6098 6062 except (IOError, OSError) as inst:
6099 6063 if inst.errno != errno.ENOENT:
6100 6064 raise
6101 6065
6102 6066 try:
6103 6067 # preresolve file
6104 6068 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6105 6069 with ui.configoverride(overrides, b'resolve'):
6106 6070 complete, r = ms.preresolve(f, wctx)
6107 6071 if not complete:
6108 6072 tocomplete.append(f)
6109 6073 elif r:
6110 6074 ret = 1
6111 6075 finally:
6112 6076 ms.commit()
6113 6077
6114 6078 # replace filemerge's .orig file with our resolve file, but only
6115 6079 # for merges that are complete
6116 6080 if complete:
6117 6081 try:
6118 6082 util.rename(
6119 6083 a + b".resolve", scmutil.backuppath(ui, repo, f)
6120 6084 )
6121 6085 except OSError as inst:
6122 6086 if inst.errno != errno.ENOENT:
6123 6087 raise
6124 6088
6125 6089 if hasconflictmarkers:
6126 6090 ui.warn(
6127 6091 _(
6128 6092 b'warning: the following files still have conflict '
6129 6093 b'markers:\n'
6130 6094 )
6131 6095 + b''.join(
6132 6096 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6133 6097 )
6134 6098 )
6135 6099 if markcheck == b'abort' and not all and not pats:
6136 6100 raise error.Abort(
6137 6101 _(b'conflict markers detected'),
6138 6102 hint=_(b'use --all to mark anyway'),
6139 6103 )
6140 6104
6141 6105 for f in tocomplete:
6142 6106 try:
6143 6107 # resolve file
6144 6108 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6145 6109 with ui.configoverride(overrides, b'resolve'):
6146 6110 r = ms.resolve(f, wctx)
6147 6111 if r:
6148 6112 ret = 1
6149 6113 finally:
6150 6114 ms.commit()
6151 6115
6152 6116 # replace filemerge's .orig file with our resolve file
6153 6117 a = repo.wjoin(f)
6154 6118 try:
6155 6119 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6156 6120 except OSError as inst:
6157 6121 if inst.errno != errno.ENOENT:
6158 6122 raise
6159 6123
6160 6124 ms.commit()
6161 6125 branchmerge = repo.dirstate.p2() != nullid
6162 6126 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6163 6127
6164 6128 if not didwork and pats:
6165 6129 hint = None
6166 6130 if not any([p for p in pats if p.find(b':') >= 0]):
6167 6131 pats = [b'path:%s' % p for p in pats]
6168 6132 m = scmutil.match(wctx, pats, opts)
6169 6133 for f in ms:
6170 6134 if not m(f):
6171 6135 continue
6172 6136
6173 6137 def flag(o):
6174 6138 if o == b're_merge':
6175 6139 return b'--re-merge '
6176 6140 return b'-%s ' % o[0:1]
6177 6141
6178 6142 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6179 6143 hint = _(b"(try: hg resolve %s%s)\n") % (
6180 6144 flags,
6181 6145 b' '.join(pats),
6182 6146 )
6183 6147 break
6184 6148 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6185 6149 if hint:
6186 6150 ui.warn(hint)
6187 elif ms.mergedriver and ms.mdstate() != b's':
6188 # run conclude step when either a driver-resolved file is requested
6189 # or there are no driver-resolved files
6190 # we can't use 'ret' to determine whether any files are unresolved
6191 # because we might not have tried to resolve some
6192 if (runconclude or not list(ms.driverresolved())) and not list(
6193 ms.unresolved()
6194 ):
6195 proceed = mergemod.driverconclude(repo, ms, wctx)
6196 ms.commit()
6197 if not proceed:
6198 return 1
6199
6200 # Nudge users into finishing an unfinished operation
6151
6201 6152 unresolvedf = list(ms.unresolved())
6202 driverresolvedf = list(ms.driverresolved())
6203 if not unresolvedf and not driverresolvedf:
6153 if not unresolvedf:
6204 6154 ui.status(_(b'(no more unresolved files)\n'))
6205 6155 cmdutil.checkafterresolved(repo)
6206 elif not unresolvedf:
6207 ui.status(
6208 _(
6209 b'(no more unresolved files -- '
6210 b'run "hg resolve --all" to conclude)\n'
6211 )
6212 )
6213 6156
6214 6157 return ret
6215 6158
6216 6159
6217 6160 @command(
6218 6161 b'revert',
6219 6162 [
6220 6163 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6221 6164 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6222 6165 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6223 6166 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6224 6167 (b'i', b'interactive', None, _(b'interactively select the changes')),
6225 6168 ]
6226 6169 + walkopts
6227 6170 + dryrunopts,
6228 6171 _(b'[OPTION]... [-r REV] [NAME]...'),
6229 6172 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6230 6173 )
6231 6174 def revert(ui, repo, *pats, **opts):
6232 6175 """restore files to their checkout state
6233 6176
6234 6177 .. note::
6235 6178
6236 6179 To check out earlier revisions, you should use :hg:`update REV`.
6237 6180 To cancel an uncommitted merge (and lose your changes),
6238 6181 use :hg:`merge --abort`.
6239 6182
6240 6183 With no revision specified, revert the specified files or directories
6241 6184 to the contents they had in the parent of the working directory.
6242 6185 This restores the contents of files to an unmodified
6243 6186 state and unschedules adds, removes, copies, and renames. If the
6244 6187 working directory has two parents, you must explicitly specify a
6245 6188 revision.
6246 6189
6247 6190 Using the -r/--rev or -d/--date options, revert the given files or
6248 6191 directories to their states as of a specific revision. Because
6249 6192 revert does not change the working directory parents, this will
6250 6193 cause these files to appear modified. This can be helpful to "back
6251 6194 out" some or all of an earlier change. See :hg:`backout` for a
6252 6195 related method.
6253 6196
6254 6197 Modified files are saved with a .orig suffix before reverting.
6255 6198 To disable these backups, use --no-backup. It is possible to store
6256 6199 the backup files in a custom directory relative to the root of the
6257 6200 repository by setting the ``ui.origbackuppath`` configuration
6258 6201 option.
6259 6202
6260 6203 See :hg:`help dates` for a list of formats valid for -d/--date.
6261 6204
6262 6205 See :hg:`help backout` for a way to reverse the effect of an
6263 6206 earlier changeset.
6264 6207
6265 6208 Returns 0 on success.
6266 6209 """
6267 6210
6268 6211 opts = pycompat.byteskwargs(opts)
6269 6212 if opts.get(b"date"):
6270 6213 if opts.get(b"rev"):
6271 6214 raise error.Abort(_(b"you can't specify a revision and a date"))
6272 6215 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6273 6216
6274 6217 parent, p2 = repo.dirstate.parents()
6275 6218 if not opts.get(b'rev') and p2 != nullid:
6276 6219 # revert after merge is a trap for new users (issue2915)
6277 6220 raise error.Abort(
6278 6221 _(b'uncommitted merge with no revision specified'),
6279 6222 hint=_(b"use 'hg update' or see 'hg help revert'"),
6280 6223 )
6281 6224
6282 6225 rev = opts.get(b'rev')
6283 6226 if rev:
6284 6227 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6285 6228 ctx = scmutil.revsingle(repo, rev)
6286 6229
6287 6230 if not (
6288 6231 pats
6289 6232 or opts.get(b'include')
6290 6233 or opts.get(b'exclude')
6291 6234 or opts.get(b'all')
6292 6235 or opts.get(b'interactive')
6293 6236 ):
6294 6237 msg = _(b"no files or directories specified")
6295 6238 if p2 != nullid:
6296 6239 hint = _(
6297 6240 b"uncommitted merge, use --all to discard all changes,"
6298 6241 b" or 'hg update -C .' to abort the merge"
6299 6242 )
6300 6243 raise error.Abort(msg, hint=hint)
6301 6244 dirty = any(repo.status())
6302 6245 node = ctx.node()
6303 6246 if node != parent:
6304 6247 if dirty:
6305 6248 hint = (
6306 6249 _(
6307 6250 b"uncommitted changes, use --all to discard all"
6308 6251 b" changes, or 'hg update %d' to update"
6309 6252 )
6310 6253 % ctx.rev()
6311 6254 )
6312 6255 else:
6313 6256 hint = (
6314 6257 _(
6315 6258 b"use --all to revert all files,"
6316 6259 b" or 'hg update %d' to update"
6317 6260 )
6318 6261 % ctx.rev()
6319 6262 )
6320 6263 elif dirty:
6321 6264 hint = _(b"uncommitted changes, use --all to discard all changes")
6322 6265 else:
6323 6266 hint = _(b"use --all to revert all files")
6324 6267 raise error.Abort(msg, hint=hint)
6325 6268
6326 6269 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6327 6270
6328 6271
6329 6272 @command(
6330 6273 b'rollback',
6331 6274 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6332 6275 helpcategory=command.CATEGORY_MAINTENANCE,
6333 6276 )
6334 6277 def rollback(ui, repo, **opts):
6335 6278 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6336 6279
6337 6280 Please use :hg:`commit --amend` instead of rollback to correct
6338 6281 mistakes in the last commit.
6339 6282
6340 6283 This command should be used with care. There is only one level of
6341 6284 rollback, and there is no way to undo a rollback. It will also
6342 6285 restore the dirstate at the time of the last transaction, losing
6343 6286 any dirstate changes since that time. This command does not alter
6344 6287 the working directory.
6345 6288
6346 6289 Transactions are used to encapsulate the effects of all commands
6347 6290 that create new changesets or propagate existing changesets into a
6348 6291 repository.
6349 6292
6350 6293 .. container:: verbose
6351 6294
6352 6295 For example, the following commands are transactional, and their
6353 6296 effects can be rolled back:
6354 6297
6355 6298 - commit
6356 6299 - import
6357 6300 - pull
6358 6301 - push (with this repository as the destination)
6359 6302 - unbundle
6360 6303
6361 6304 To avoid permanent data loss, rollback will refuse to rollback a
6362 6305 commit transaction if it isn't checked out. Use --force to
6363 6306 override this protection.
6364 6307
6365 6308 The rollback command can be entirely disabled by setting the
6366 6309 ``ui.rollback`` configuration setting to false. If you're here
6367 6310 because you want to use rollback and it's disabled, you can
6368 6311 re-enable the command by setting ``ui.rollback`` to true.
6369 6312
6370 6313 This command is not intended for use on public repositories. Once
6371 6314 changes are visible for pull by other users, rolling a transaction
6372 6315 back locally is ineffective (someone else may already have pulled
6373 6316 the changes). Furthermore, a race is possible with readers of the
6374 6317 repository; for example an in-progress pull from the repository
6375 6318 may fail if a rollback is performed.
6376 6319
6377 6320 Returns 0 on success, 1 if no rollback data is available.
6378 6321 """
6379 6322 if not ui.configbool(b'ui', b'rollback'):
6380 6323 raise error.Abort(
6381 6324 _(b'rollback is disabled because it is unsafe'),
6382 6325 hint=b'see `hg help -v rollback` for information',
6383 6326 )
6384 6327 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6385 6328
6386 6329
6387 6330 @command(
6388 6331 b'root',
6389 6332 [] + formatteropts,
6390 6333 intents={INTENT_READONLY},
6391 6334 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6392 6335 )
6393 6336 def root(ui, repo, **opts):
6394 6337 """print the root (top) of the current working directory
6395 6338
6396 6339 Print the root directory of the current repository.
6397 6340
6398 6341 .. container:: verbose
6399 6342
6400 6343 Template:
6401 6344
6402 6345 The following keywords are supported in addition to the common template
6403 6346 keywords and functions. See also :hg:`help templates`.
6404 6347
6405 6348 :hgpath: String. Path to the .hg directory.
6406 6349 :storepath: String. Path to the directory holding versioned data.
6407 6350
6408 6351 Returns 0 on success.
6409 6352 """
6410 6353 opts = pycompat.byteskwargs(opts)
6411 6354 with ui.formatter(b'root', opts) as fm:
6412 6355 fm.startitem()
6413 6356 fm.write(b'reporoot', b'%s\n', repo.root)
6414 6357 fm.data(hgpath=repo.path, storepath=repo.spath)
6415 6358
6416 6359
6417 6360 @command(
6418 6361 b'serve',
6419 6362 [
6420 6363 (
6421 6364 b'A',
6422 6365 b'accesslog',
6423 6366 b'',
6424 6367 _(b'name of access log file to write to'),
6425 6368 _(b'FILE'),
6426 6369 ),
6427 6370 (b'd', b'daemon', None, _(b'run server in background')),
6428 6371 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6429 6372 (
6430 6373 b'E',
6431 6374 b'errorlog',
6432 6375 b'',
6433 6376 _(b'name of error log file to write to'),
6434 6377 _(b'FILE'),
6435 6378 ),
6436 6379 # use string type, then we can check if something was passed
6437 6380 (
6438 6381 b'p',
6439 6382 b'port',
6440 6383 b'',
6441 6384 _(b'port to listen on (default: 8000)'),
6442 6385 _(b'PORT'),
6443 6386 ),
6444 6387 (
6445 6388 b'a',
6446 6389 b'address',
6447 6390 b'',
6448 6391 _(b'address to listen on (default: all interfaces)'),
6449 6392 _(b'ADDR'),
6450 6393 ),
6451 6394 (
6452 6395 b'',
6453 6396 b'prefix',
6454 6397 b'',
6455 6398 _(b'prefix path to serve from (default: server root)'),
6456 6399 _(b'PREFIX'),
6457 6400 ),
6458 6401 (
6459 6402 b'n',
6460 6403 b'name',
6461 6404 b'',
6462 6405 _(b'name to show in web pages (default: working directory)'),
6463 6406 _(b'NAME'),
6464 6407 ),
6465 6408 (
6466 6409 b'',
6467 6410 b'web-conf',
6468 6411 b'',
6469 6412 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6470 6413 _(b'FILE'),
6471 6414 ),
6472 6415 (
6473 6416 b'',
6474 6417 b'webdir-conf',
6475 6418 b'',
6476 6419 _(b'name of the hgweb config file (DEPRECATED)'),
6477 6420 _(b'FILE'),
6478 6421 ),
6479 6422 (
6480 6423 b'',
6481 6424 b'pid-file',
6482 6425 b'',
6483 6426 _(b'name of file to write process ID to'),
6484 6427 _(b'FILE'),
6485 6428 ),
6486 6429 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6487 6430 (
6488 6431 b'',
6489 6432 b'cmdserver',
6490 6433 b'',
6491 6434 _(b'for remote clients (ADVANCED)'),
6492 6435 _(b'MODE'),
6493 6436 ),
6494 6437 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6495 6438 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6496 6439 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6497 6440 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6498 6441 (b'', b'print-url', None, _(b'start and print only the URL')),
6499 6442 ]
6500 6443 + subrepoopts,
6501 6444 _(b'[OPTION]...'),
6502 6445 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6503 6446 helpbasic=True,
6504 6447 optionalrepo=True,
6505 6448 )
6506 6449 def serve(ui, repo, **opts):
6507 6450 """start stand-alone webserver
6508 6451
6509 6452 Start a local HTTP repository browser and pull server. You can use
6510 6453 this for ad-hoc sharing and browsing of repositories. It is
6511 6454 recommended to use a real web server to serve a repository for
6512 6455 longer periods of time.
6513 6456
6514 6457 Please note that the server does not implement access control.
6515 6458 This means that, by default, anybody can read from the server and
6516 6459 nobody can write to it by default. Set the ``web.allow-push``
6517 6460 option to ``*`` to allow everybody to push to the server. You
6518 6461 should use a real web server if you need to authenticate users.
6519 6462
6520 6463 By default, the server logs accesses to stdout and errors to
6521 6464 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6522 6465 files.
6523 6466
6524 6467 To have the server choose a free port number to listen on, specify
6525 6468 a port number of 0; in this case, the server will print the port
6526 6469 number it uses.
6527 6470
6528 6471 Returns 0 on success.
6529 6472 """
6530 6473
6531 6474 opts = pycompat.byteskwargs(opts)
6532 6475 if opts[b"stdio"] and opts[b"cmdserver"]:
6533 6476 raise error.Abort(_(b"cannot use --stdio with --cmdserver"))
6534 6477 if opts[b"print_url"] and ui.verbose:
6535 6478 raise error.Abort(_(b"cannot use --print-url with --verbose"))
6536 6479
6537 6480 if opts[b"stdio"]:
6538 6481 if repo is None:
6539 6482 raise error.RepoError(
6540 6483 _(b"there is no Mercurial repository here (.hg not found)")
6541 6484 )
6542 6485 s = wireprotoserver.sshserver(ui, repo)
6543 6486 s.serve_forever()
6544 6487
6545 6488 service = server.createservice(ui, repo, opts)
6546 6489 return server.runservice(opts, initfn=service.init, runfn=service.run)
6547 6490
6548 6491
6549 6492 @command(
6550 6493 b'shelve',
6551 6494 [
6552 6495 (
6553 6496 b'A',
6554 6497 b'addremove',
6555 6498 None,
6556 6499 _(b'mark new/missing files as added/removed before shelving'),
6557 6500 ),
6558 6501 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6559 6502 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6560 6503 (
6561 6504 b'',
6562 6505 b'date',
6563 6506 b'',
6564 6507 _(b'shelve with the specified commit date'),
6565 6508 _(b'DATE'),
6566 6509 ),
6567 6510 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6568 6511 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6569 6512 (
6570 6513 b'k',
6571 6514 b'keep',
6572 6515 False,
6573 6516 _(b'shelve, but keep changes in the working directory'),
6574 6517 ),
6575 6518 (b'l', b'list', None, _(b'list current shelves')),
6576 6519 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6577 6520 (
6578 6521 b'n',
6579 6522 b'name',
6580 6523 b'',
6581 6524 _(b'use the given name for the shelved commit'),
6582 6525 _(b'NAME'),
6583 6526 ),
6584 6527 (
6585 6528 b'p',
6586 6529 b'patch',
6587 6530 None,
6588 6531 _(
6589 6532 b'output patches for changes (provide the names of the shelved '
6590 6533 b'changes as positional arguments)'
6591 6534 ),
6592 6535 ),
6593 6536 (b'i', b'interactive', None, _(b'interactive mode')),
6594 6537 (
6595 6538 b'',
6596 6539 b'stat',
6597 6540 None,
6598 6541 _(
6599 6542 b'output diffstat-style summary of changes (provide the names of '
6600 6543 b'the shelved changes as positional arguments)'
6601 6544 ),
6602 6545 ),
6603 6546 ]
6604 6547 + cmdutil.walkopts,
6605 6548 _(b'hg shelve [OPTION]... [FILE]...'),
6606 6549 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6607 6550 )
6608 6551 def shelve(ui, repo, *pats, **opts):
6609 6552 '''save and set aside changes from the working directory
6610 6553
6611 6554 Shelving takes files that "hg status" reports as not clean, saves
6612 6555 the modifications to a bundle (a shelved change), and reverts the
6613 6556 files so that their state in the working directory becomes clean.
6614 6557
6615 6558 To restore these changes to the working directory, using "hg
6616 6559 unshelve"; this will work even if you switch to a different
6617 6560 commit.
6618 6561
6619 6562 When no files are specified, "hg shelve" saves all not-clean
6620 6563 files. If specific files or directories are named, only changes to
6621 6564 those files are shelved.
6622 6565
6623 6566 In bare shelve (when no files are specified, without interactive,
6624 6567 include and exclude option), shelving remembers information if the
6625 6568 working directory was on newly created branch, in other words working
6626 6569 directory was on different branch than its first parent. In this
6627 6570 situation unshelving restores branch information to the working directory.
6628 6571
6629 6572 Each shelved change has a name that makes it easier to find later.
6630 6573 The name of a shelved change defaults to being based on the active
6631 6574 bookmark, or if there is no active bookmark, the current named
6632 6575 branch. To specify a different name, use ``--name``.
6633 6576
6634 6577 To see a list of existing shelved changes, use the ``--list``
6635 6578 option. For each shelved change, this will print its name, age,
6636 6579 and description; use ``--patch`` or ``--stat`` for more details.
6637 6580
6638 6581 To delete specific shelved changes, use ``--delete``. To delete
6639 6582 all shelved changes, use ``--cleanup``.
6640 6583 '''
6641 6584 opts = pycompat.byteskwargs(opts)
6642 6585 allowables = [
6643 6586 (b'addremove', {b'create'}), # 'create' is pseudo action
6644 6587 (b'unknown', {b'create'}),
6645 6588 (b'cleanup', {b'cleanup'}),
6646 6589 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6647 6590 (b'delete', {b'delete'}),
6648 6591 (b'edit', {b'create'}),
6649 6592 (b'keep', {b'create'}),
6650 6593 (b'list', {b'list'}),
6651 6594 (b'message', {b'create'}),
6652 6595 (b'name', {b'create'}),
6653 6596 (b'patch', {b'patch', b'list'}),
6654 6597 (b'stat', {b'stat', b'list'}),
6655 6598 ]
6656 6599
6657 6600 def checkopt(opt):
6658 6601 if opts.get(opt):
6659 6602 for i, allowable in allowables:
6660 6603 if opts[i] and opt not in allowable:
6661 6604 raise error.Abort(
6662 6605 _(
6663 6606 b"options '--%s' and '--%s' may not be "
6664 6607 b"used together"
6665 6608 )
6666 6609 % (opt, i)
6667 6610 )
6668 6611 return True
6669 6612
6670 6613 if checkopt(b'cleanup'):
6671 6614 if pats:
6672 6615 raise error.Abort(_(b"cannot specify names when using '--cleanup'"))
6673 6616 return shelvemod.cleanupcmd(ui, repo)
6674 6617 elif checkopt(b'delete'):
6675 6618 return shelvemod.deletecmd(ui, repo, pats)
6676 6619 elif checkopt(b'list'):
6677 6620 return shelvemod.listcmd(ui, repo, pats, opts)
6678 6621 elif checkopt(b'patch') or checkopt(b'stat'):
6679 6622 return shelvemod.patchcmds(ui, repo, pats, opts)
6680 6623 else:
6681 6624 return shelvemod.createcmd(ui, repo, pats, opts)
6682 6625
6683 6626
6684 6627 _NOTTERSE = b'nothing'
6685 6628
6686 6629
6687 6630 @command(
6688 6631 b'status|st',
6689 6632 [
6690 6633 (b'A', b'all', None, _(b'show status of all files')),
6691 6634 (b'm', b'modified', None, _(b'show only modified files')),
6692 6635 (b'a', b'added', None, _(b'show only added files')),
6693 6636 (b'r', b'removed', None, _(b'show only removed files')),
6694 6637 (b'd', b'deleted', None, _(b'show only missing files')),
6695 6638 (b'c', b'clean', None, _(b'show only files without changes')),
6696 6639 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6697 6640 (b'i', b'ignored', None, _(b'show only ignored files')),
6698 6641 (b'n', b'no-status', None, _(b'hide status prefix')),
6699 6642 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6700 6643 (
6701 6644 b'C',
6702 6645 b'copies',
6703 6646 None,
6704 6647 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6705 6648 ),
6706 6649 (
6707 6650 b'0',
6708 6651 b'print0',
6709 6652 None,
6710 6653 _(b'end filenames with NUL, for use with xargs'),
6711 6654 ),
6712 6655 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6713 6656 (
6714 6657 b'',
6715 6658 b'change',
6716 6659 b'',
6717 6660 _(b'list the changed files of a revision'),
6718 6661 _(b'REV'),
6719 6662 ),
6720 6663 ]
6721 6664 + walkopts
6722 6665 + subrepoopts
6723 6666 + formatteropts,
6724 6667 _(b'[OPTION]... [FILE]...'),
6725 6668 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6726 6669 helpbasic=True,
6727 6670 inferrepo=True,
6728 6671 intents={INTENT_READONLY},
6729 6672 )
6730 6673 def status(ui, repo, *pats, **opts):
6731 6674 """show changed files in the working directory
6732 6675
6733 6676 Show status of files in the repository. If names are given, only
6734 6677 files that match are shown. Files that are clean or ignored or
6735 6678 the source of a copy/move operation, are not listed unless
6736 6679 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6737 6680 Unless options described with "show only ..." are given, the
6738 6681 options -mardu are used.
6739 6682
6740 6683 Option -q/--quiet hides untracked (unknown and ignored) files
6741 6684 unless explicitly requested with -u/--unknown or -i/--ignored.
6742 6685
6743 6686 .. note::
6744 6687
6745 6688 :hg:`status` may appear to disagree with diff if permissions have
6746 6689 changed or a merge has occurred. The standard diff format does
6747 6690 not report permission changes and diff only reports changes
6748 6691 relative to one merge parent.
6749 6692
6750 6693 If one revision is given, it is used as the base revision.
6751 6694 If two revisions are given, the differences between them are
6752 6695 shown. The --change option can also be used as a shortcut to list
6753 6696 the changed files of a revision from its first parent.
6754 6697
6755 6698 The codes used to show the status of files are::
6756 6699
6757 6700 M = modified
6758 6701 A = added
6759 6702 R = removed
6760 6703 C = clean
6761 6704 ! = missing (deleted by non-hg command, but still tracked)
6762 6705 ? = not tracked
6763 6706 I = ignored
6764 6707 = origin of the previous file (with --copies)
6765 6708
6766 6709 .. container:: verbose
6767 6710
6768 6711 The -t/--terse option abbreviates the output by showing only the directory
6769 6712 name if all the files in it share the same status. The option takes an
6770 6713 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6771 6714 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6772 6715 for 'ignored' and 'c' for clean.
6773 6716
6774 6717 It abbreviates only those statuses which are passed. Note that clean and
6775 6718 ignored files are not displayed with '--terse ic' unless the -c/--clean
6776 6719 and -i/--ignored options are also used.
6777 6720
6778 6721 The -v/--verbose option shows information when the repository is in an
6779 6722 unfinished merge, shelve, rebase state etc. You can have this behavior
6780 6723 turned on by default by enabling the ``commands.status.verbose`` option.
6781 6724
6782 6725 You can skip displaying some of these states by setting
6783 6726 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6784 6727 'histedit', 'merge', 'rebase', or 'unshelve'.
6785 6728
6786 6729 Template:
6787 6730
6788 6731 The following keywords are supported in addition to the common template
6789 6732 keywords and functions. See also :hg:`help templates`.
6790 6733
6791 6734 :path: String. Repository-absolute path of the file.
6792 6735 :source: String. Repository-absolute path of the file originated from.
6793 6736 Available if ``--copies`` is specified.
6794 6737 :status: String. Character denoting file's status.
6795 6738
6796 6739 Examples:
6797 6740
6798 6741 - show changes in the working directory relative to a
6799 6742 changeset::
6800 6743
6801 6744 hg status --rev 9353
6802 6745
6803 6746 - show changes in the working directory relative to the
6804 6747 current directory (see :hg:`help patterns` for more information)::
6805 6748
6806 6749 hg status re:
6807 6750
6808 6751 - show all changes including copies in an existing changeset::
6809 6752
6810 6753 hg status --copies --change 9353
6811 6754
6812 6755 - get a NUL separated list of added files, suitable for xargs::
6813 6756
6814 6757 hg status -an0
6815 6758
6816 6759 - show more information about the repository status, abbreviating
6817 6760 added, removed, modified, deleted, and untracked paths::
6818 6761
6819 6762 hg status -v -t mardu
6820 6763
6821 6764 Returns 0 on success.
6822 6765
6823 6766 """
6824 6767
6825 6768 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6826 6769 opts = pycompat.byteskwargs(opts)
6827 6770 revs = opts.get(b'rev')
6828 6771 change = opts.get(b'change')
6829 6772 terse = opts.get(b'terse')
6830 6773 if terse is _NOTTERSE:
6831 6774 if revs:
6832 6775 terse = b''
6833 6776 else:
6834 6777 terse = ui.config(b'commands', b'status.terse')
6835 6778
6836 6779 if revs and terse:
6837 6780 msg = _(b'cannot use --terse with --rev')
6838 6781 raise error.Abort(msg)
6839 6782 elif change:
6840 6783 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6841 6784 ctx2 = scmutil.revsingle(repo, change, None)
6842 6785 ctx1 = ctx2.p1()
6843 6786 else:
6844 6787 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6845 6788 ctx1, ctx2 = scmutil.revpair(repo, revs)
6846 6789
6847 6790 forcerelativevalue = None
6848 6791 if ui.hasconfig(b'commands', b'status.relative'):
6849 6792 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6850 6793 uipathfn = scmutil.getuipathfn(
6851 6794 repo,
6852 6795 legacyrelativevalue=bool(pats),
6853 6796 forcerelativevalue=forcerelativevalue,
6854 6797 )
6855 6798
6856 6799 if opts.get(b'print0'):
6857 6800 end = b'\0'
6858 6801 else:
6859 6802 end = b'\n'
6860 6803 states = b'modified added removed deleted unknown ignored clean'.split()
6861 6804 show = [k for k in states if opts.get(k)]
6862 6805 if opts.get(b'all'):
6863 6806 show += ui.quiet and (states[:4] + [b'clean']) or states
6864 6807
6865 6808 if not show:
6866 6809 if ui.quiet:
6867 6810 show = states[:4]
6868 6811 else:
6869 6812 show = states[:5]
6870 6813
6871 6814 m = scmutil.match(ctx2, pats, opts)
6872 6815 if terse:
6873 6816 # we need to compute clean and unknown to terse
6874 6817 stat = repo.status(
6875 6818 ctx1.node(),
6876 6819 ctx2.node(),
6877 6820 m,
6878 6821 b'ignored' in show or b'i' in terse,
6879 6822 clean=True,
6880 6823 unknown=True,
6881 6824 listsubrepos=opts.get(b'subrepos'),
6882 6825 )
6883 6826
6884 6827 stat = cmdutil.tersedir(stat, terse)
6885 6828 else:
6886 6829 stat = repo.status(
6887 6830 ctx1.node(),
6888 6831 ctx2.node(),
6889 6832 m,
6890 6833 b'ignored' in show,
6891 6834 b'clean' in show,
6892 6835 b'unknown' in show,
6893 6836 opts.get(b'subrepos'),
6894 6837 )
6895 6838
6896 6839 changestates = zip(
6897 6840 states,
6898 6841 pycompat.iterbytestr(b'MAR!?IC'),
6899 6842 [getattr(stat, s.decode('utf8')) for s in states],
6900 6843 )
6901 6844
6902 6845 copy = {}
6903 6846 if (
6904 6847 opts.get(b'all')
6905 6848 or opts.get(b'copies')
6906 6849 or ui.configbool(b'ui', b'statuscopies')
6907 6850 ) and not opts.get(b'no_status'):
6908 6851 copy = copies.pathcopies(ctx1, ctx2, m)
6909 6852
6910 6853 morestatus = None
6911 6854 if (
6912 6855 ui.verbose or ui.configbool(b'commands', b'status.verbose')
6913 6856 ) and not ui.plain():
6914 6857 morestatus = cmdutil.readmorestatus(repo)
6915 6858
6916 6859 ui.pager(b'status')
6917 6860 fm = ui.formatter(b'status', opts)
6918 6861 fmt = b'%s' + end
6919 6862 showchar = not opts.get(b'no_status')
6920 6863
6921 6864 for state, char, files in changestates:
6922 6865 if state in show:
6923 6866 label = b'status.' + state
6924 6867 for f in files:
6925 6868 fm.startitem()
6926 6869 fm.context(ctx=ctx2)
6927 6870 fm.data(itemtype=b'file', path=f)
6928 6871 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6929 6872 fm.plain(fmt % uipathfn(f), label=label)
6930 6873 if f in copy:
6931 6874 fm.data(source=copy[f])
6932 6875 fm.plain(
6933 6876 (b' %s' + end) % uipathfn(copy[f]),
6934 6877 label=b'status.copied',
6935 6878 )
6936 6879 if morestatus:
6937 6880 morestatus.formatfile(f, fm)
6938 6881
6939 6882 if morestatus:
6940 6883 morestatus.formatfooter(fm)
6941 6884 fm.end()
6942 6885
6943 6886
6944 6887 @command(
6945 6888 b'summary|sum',
6946 6889 [(b'', b'remote', None, _(b'check for push and pull'))],
6947 6890 b'[--remote]',
6948 6891 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6949 6892 helpbasic=True,
6950 6893 intents={INTENT_READONLY},
6951 6894 )
6952 6895 def summary(ui, repo, **opts):
6953 6896 """summarize working directory state
6954 6897
6955 6898 This generates a brief summary of the working directory state,
6956 6899 including parents, branch, commit status, phase and available updates.
6957 6900
6958 6901 With the --remote option, this will check the default paths for
6959 6902 incoming and outgoing changes. This can be time-consuming.
6960 6903
6961 6904 Returns 0 on success.
6962 6905 """
6963 6906
6964 6907 opts = pycompat.byteskwargs(opts)
6965 6908 ui.pager(b'summary')
6966 6909 ctx = repo[None]
6967 6910 parents = ctx.parents()
6968 6911 pnode = parents[0].node()
6969 6912 marks = []
6970 6913
6971 6914 try:
6972 6915 ms = mergestatemod.mergestate.read(repo)
6973 6916 except error.UnsupportedMergeRecords as e:
6974 6917 s = b' '.join(e.recordtypes)
6975 6918 ui.warn(
6976 6919 _(b'warning: merge state has unsupported record types: %s\n') % s
6977 6920 )
6978 6921 unresolved = []
6979 6922 else:
6980 6923 unresolved = list(ms.unresolved())
6981 6924
6982 6925 for p in parents:
6983 6926 # label with log.changeset (instead of log.parent) since this
6984 6927 # shows a working directory parent *changeset*:
6985 6928 # i18n: column positioning for "hg summary"
6986 6929 ui.write(
6987 6930 _(b'parent: %d:%s ') % (p.rev(), p),
6988 6931 label=logcmdutil.changesetlabels(p),
6989 6932 )
6990 6933 ui.write(b' '.join(p.tags()), label=b'log.tag')
6991 6934 if p.bookmarks():
6992 6935 marks.extend(p.bookmarks())
6993 6936 if p.rev() == -1:
6994 6937 if not len(repo):
6995 6938 ui.write(_(b' (empty repository)'))
6996 6939 else:
6997 6940 ui.write(_(b' (no revision checked out)'))
6998 6941 if p.obsolete():
6999 6942 ui.write(_(b' (obsolete)'))
7000 6943 if p.isunstable():
7001 6944 instabilities = (
7002 6945 ui.label(instability, b'trouble.%s' % instability)
7003 6946 for instability in p.instabilities()
7004 6947 )
7005 6948 ui.write(b' (' + b', '.join(instabilities) + b')')
7006 6949 ui.write(b'\n')
7007 6950 if p.description():
7008 6951 ui.status(
7009 6952 b' ' + p.description().splitlines()[0].strip() + b'\n',
7010 6953 label=b'log.summary',
7011 6954 )
7012 6955
7013 6956 branch = ctx.branch()
7014 6957 bheads = repo.branchheads(branch)
7015 6958 # i18n: column positioning for "hg summary"
7016 6959 m = _(b'branch: %s\n') % branch
7017 6960 if branch != b'default':
7018 6961 ui.write(m, label=b'log.branch')
7019 6962 else:
7020 6963 ui.status(m, label=b'log.branch')
7021 6964
7022 6965 if marks:
7023 6966 active = repo._activebookmark
7024 6967 # i18n: column positioning for "hg summary"
7025 6968 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
7026 6969 if active is not None:
7027 6970 if active in marks:
7028 6971 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
7029 6972 marks.remove(active)
7030 6973 else:
7031 6974 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
7032 6975 for m in marks:
7033 6976 ui.write(b' ' + m, label=b'log.bookmark')
7034 6977 ui.write(b'\n', label=b'log.bookmark')
7035 6978
7036 6979 status = repo.status(unknown=True)
7037 6980
7038 6981 c = repo.dirstate.copies()
7039 6982 copied, renamed = [], []
7040 6983 for d, s in pycompat.iteritems(c):
7041 6984 if s in status.removed:
7042 6985 status.removed.remove(s)
7043 6986 renamed.append(d)
7044 6987 else:
7045 6988 copied.append(d)
7046 6989 if d in status.added:
7047 6990 status.added.remove(d)
7048 6991
7049 6992 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7050 6993
7051 6994 labels = [
7052 6995 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7053 6996 (ui.label(_(b'%d added'), b'status.added'), status.added),
7054 6997 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7055 6998 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7056 6999 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7057 7000 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7058 7001 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7059 7002 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7060 7003 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7061 7004 ]
7062 7005 t = []
7063 7006 for l, s in labels:
7064 7007 if s:
7065 7008 t.append(l % len(s))
7066 7009
7067 7010 t = b', '.join(t)
7068 7011 cleanworkdir = False
7069 7012
7070 7013 if repo.vfs.exists(b'graftstate'):
7071 7014 t += _(b' (graft in progress)')
7072 7015 if repo.vfs.exists(b'updatestate'):
7073 7016 t += _(b' (interrupted update)')
7074 7017 elif len(parents) > 1:
7075 7018 t += _(b' (merge)')
7076 7019 elif branch != parents[0].branch():
7077 7020 t += _(b' (new branch)')
7078 7021 elif parents[0].closesbranch() and pnode in repo.branchheads(
7079 7022 branch, closed=True
7080 7023 ):
7081 7024 t += _(b' (head closed)')
7082 7025 elif not (
7083 7026 status.modified
7084 7027 or status.added
7085 7028 or status.removed
7086 7029 or renamed
7087 7030 or copied
7088 7031 or subs
7089 7032 ):
7090 7033 t += _(b' (clean)')
7091 7034 cleanworkdir = True
7092 7035 elif pnode not in bheads:
7093 7036 t += _(b' (new branch head)')
7094 7037
7095 7038 if parents:
7096 7039 pendingphase = max(p.phase() for p in parents)
7097 7040 else:
7098 7041 pendingphase = phases.public
7099 7042
7100 7043 if pendingphase > phases.newcommitphase(ui):
7101 7044 t += b' (%s)' % phases.phasenames[pendingphase]
7102 7045
7103 7046 if cleanworkdir:
7104 7047 # i18n: column positioning for "hg summary"
7105 7048 ui.status(_(b'commit: %s\n') % t.strip())
7106 7049 else:
7107 7050 # i18n: column positioning for "hg summary"
7108 7051 ui.write(_(b'commit: %s\n') % t.strip())
7109 7052
7110 7053 # all ancestors of branch heads - all ancestors of parent = new csets
7111 7054 new = len(
7112 7055 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7113 7056 )
7114 7057
7115 7058 if new == 0:
7116 7059 # i18n: column positioning for "hg summary"
7117 7060 ui.status(_(b'update: (current)\n'))
7118 7061 elif pnode not in bheads:
7119 7062 # i18n: column positioning for "hg summary"
7120 7063 ui.write(_(b'update: %d new changesets (update)\n') % new)
7121 7064 else:
7122 7065 # i18n: column positioning for "hg summary"
7123 7066 ui.write(
7124 7067 _(b'update: %d new changesets, %d branch heads (merge)\n')
7125 7068 % (new, len(bheads))
7126 7069 )
7127 7070
7128 7071 t = []
7129 7072 draft = len(repo.revs(b'draft()'))
7130 7073 if draft:
7131 7074 t.append(_(b'%d draft') % draft)
7132 7075 secret = len(repo.revs(b'secret()'))
7133 7076 if secret:
7134 7077 t.append(_(b'%d secret') % secret)
7135 7078
7136 7079 if draft or secret:
7137 7080 ui.status(_(b'phases: %s\n') % b', '.join(t))
7138 7081
7139 7082 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7140 7083 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7141 7084 numtrouble = len(repo.revs(trouble + b"()"))
7142 7085 # We write all the possibilities to ease translation
7143 7086 troublemsg = {
7144 7087 b"orphan": _(b"orphan: %d changesets"),
7145 7088 b"contentdivergent": _(b"content-divergent: %d changesets"),
7146 7089 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7147 7090 }
7148 7091 if numtrouble > 0:
7149 7092 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7150 7093
7151 7094 cmdutil.summaryhooks(ui, repo)
7152 7095
7153 7096 if opts.get(b'remote'):
7154 7097 needsincoming, needsoutgoing = True, True
7155 7098 else:
7156 7099 needsincoming, needsoutgoing = False, False
7157 7100 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7158 7101 if i:
7159 7102 needsincoming = True
7160 7103 if o:
7161 7104 needsoutgoing = True
7162 7105 if not needsincoming and not needsoutgoing:
7163 7106 return
7164 7107
7165 7108 def getincoming():
7166 7109 source, branches = hg.parseurl(ui.expandpath(b'default'))
7167 7110 sbranch = branches[0]
7168 7111 try:
7169 7112 other = hg.peer(repo, {}, source)
7170 7113 except error.RepoError:
7171 7114 if opts.get(b'remote'):
7172 7115 raise
7173 7116 return source, sbranch, None, None, None
7174 7117 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7175 7118 if revs:
7176 7119 revs = [other.lookup(rev) for rev in revs]
7177 7120 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7178 7121 repo.ui.pushbuffer()
7179 7122 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7180 7123 repo.ui.popbuffer()
7181 7124 return source, sbranch, other, commoninc, commoninc[1]
7182 7125
7183 7126 if needsincoming:
7184 7127 source, sbranch, sother, commoninc, incoming = getincoming()
7185 7128 else:
7186 7129 source = sbranch = sother = commoninc = incoming = None
7187 7130
7188 7131 def getoutgoing():
7189 7132 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7190 7133 dbranch = branches[0]
7191 7134 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7192 7135 if source != dest:
7193 7136 try:
7194 7137 dother = hg.peer(repo, {}, dest)
7195 7138 except error.RepoError:
7196 7139 if opts.get(b'remote'):
7197 7140 raise
7198 7141 return dest, dbranch, None, None
7199 7142 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7200 7143 elif sother is None:
7201 7144 # there is no explicit destination peer, but source one is invalid
7202 7145 return dest, dbranch, None, None
7203 7146 else:
7204 7147 dother = sother
7205 7148 if source != dest or (sbranch is not None and sbranch != dbranch):
7206 7149 common = None
7207 7150 else:
7208 7151 common = commoninc
7209 7152 if revs:
7210 7153 revs = [repo.lookup(rev) for rev in revs]
7211 7154 repo.ui.pushbuffer()
7212 7155 outgoing = discovery.findcommonoutgoing(
7213 7156 repo, dother, onlyheads=revs, commoninc=common
7214 7157 )
7215 7158 repo.ui.popbuffer()
7216 7159 return dest, dbranch, dother, outgoing
7217 7160
7218 7161 if needsoutgoing:
7219 7162 dest, dbranch, dother, outgoing = getoutgoing()
7220 7163 else:
7221 7164 dest = dbranch = dother = outgoing = None
7222 7165
7223 7166 if opts.get(b'remote'):
7224 7167 t = []
7225 7168 if incoming:
7226 7169 t.append(_(b'1 or more incoming'))
7227 7170 o = outgoing.missing
7228 7171 if o:
7229 7172 t.append(_(b'%d outgoing') % len(o))
7230 7173 other = dother or sother
7231 7174 if b'bookmarks' in other.listkeys(b'namespaces'):
7232 7175 counts = bookmarks.summary(repo, other)
7233 7176 if counts[0] > 0:
7234 7177 t.append(_(b'%d incoming bookmarks') % counts[0])
7235 7178 if counts[1] > 0:
7236 7179 t.append(_(b'%d outgoing bookmarks') % counts[1])
7237 7180
7238 7181 if t:
7239 7182 # i18n: column positioning for "hg summary"
7240 7183 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7241 7184 else:
7242 7185 # i18n: column positioning for "hg summary"
7243 7186 ui.status(_(b'remote: (synced)\n'))
7244 7187
7245 7188 cmdutil.summaryremotehooks(
7246 7189 ui,
7247 7190 repo,
7248 7191 opts,
7249 7192 (
7250 7193 (source, sbranch, sother, commoninc),
7251 7194 (dest, dbranch, dother, outgoing),
7252 7195 ),
7253 7196 )
7254 7197
7255 7198
7256 7199 @command(
7257 7200 b'tag',
7258 7201 [
7259 7202 (b'f', b'force', None, _(b'force tag')),
7260 7203 (b'l', b'local', None, _(b'make the tag local')),
7261 7204 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7262 7205 (b'', b'remove', None, _(b'remove a tag')),
7263 7206 # -l/--local is already there, commitopts cannot be used
7264 7207 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7265 7208 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7266 7209 ]
7267 7210 + commitopts2,
7268 7211 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7269 7212 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7270 7213 )
7271 7214 def tag(ui, repo, name1, *names, **opts):
7272 7215 """add one or more tags for the current or given revision
7273 7216
7274 7217 Name a particular revision using <name>.
7275 7218
7276 7219 Tags are used to name particular revisions of the repository and are
7277 7220 very useful to compare different revisions, to go back to significant
7278 7221 earlier versions or to mark branch points as releases, etc. Changing
7279 7222 an existing tag is normally disallowed; use -f/--force to override.
7280 7223
7281 7224 If no revision is given, the parent of the working directory is
7282 7225 used.
7283 7226
7284 7227 To facilitate version control, distribution, and merging of tags,
7285 7228 they are stored as a file named ".hgtags" which is managed similarly
7286 7229 to other project files and can be hand-edited if necessary. This
7287 7230 also means that tagging creates a new commit. The file
7288 7231 ".hg/localtags" is used for local tags (not shared among
7289 7232 repositories).
7290 7233
7291 7234 Tag commits are usually made at the head of a branch. If the parent
7292 7235 of the working directory is not a branch head, :hg:`tag` aborts; use
7293 7236 -f/--force to force the tag commit to be based on a non-head
7294 7237 changeset.
7295 7238
7296 7239 See :hg:`help dates` for a list of formats valid for -d/--date.
7297 7240
7298 7241 Since tag names have priority over branch names during revision
7299 7242 lookup, using an existing branch name as a tag name is discouraged.
7300 7243
7301 7244 Returns 0 on success.
7302 7245 """
7303 7246 opts = pycompat.byteskwargs(opts)
7304 7247 with repo.wlock(), repo.lock():
7305 7248 rev_ = b"."
7306 7249 names = [t.strip() for t in (name1,) + names]
7307 7250 if len(names) != len(set(names)):
7308 7251 raise error.Abort(_(b'tag names must be unique'))
7309 7252 for n in names:
7310 7253 scmutil.checknewlabel(repo, n, b'tag')
7311 7254 if not n:
7312 7255 raise error.Abort(
7313 7256 _(b'tag names cannot consist entirely of whitespace')
7314 7257 )
7315 7258 if opts.get(b'rev') and opts.get(b'remove'):
7316 7259 raise error.Abort(_(b"--rev and --remove are incompatible"))
7317 7260 if opts.get(b'rev'):
7318 7261 rev_ = opts[b'rev']
7319 7262 message = opts.get(b'message')
7320 7263 if opts.get(b'remove'):
7321 7264 if opts.get(b'local'):
7322 7265 expectedtype = b'local'
7323 7266 else:
7324 7267 expectedtype = b'global'
7325 7268
7326 7269 for n in names:
7327 7270 if repo.tagtype(n) == b'global':
7328 7271 alltags = tagsmod.findglobaltags(ui, repo)
7329 7272 if alltags[n][0] == nullid:
7330 7273 raise error.Abort(_(b"tag '%s' is already removed") % n)
7331 7274 if not repo.tagtype(n):
7332 7275 raise error.Abort(_(b"tag '%s' does not exist") % n)
7333 7276 if repo.tagtype(n) != expectedtype:
7334 7277 if expectedtype == b'global':
7335 7278 raise error.Abort(
7336 7279 _(b"tag '%s' is not a global tag") % n
7337 7280 )
7338 7281 else:
7339 7282 raise error.Abort(_(b"tag '%s' is not a local tag") % n)
7340 7283 rev_ = b'null'
7341 7284 if not message:
7342 7285 # we don't translate commit messages
7343 7286 message = b'Removed tag %s' % b', '.join(names)
7344 7287 elif not opts.get(b'force'):
7345 7288 for n in names:
7346 7289 if n in repo.tags():
7347 7290 raise error.Abort(
7348 7291 _(b"tag '%s' already exists (use -f to force)") % n
7349 7292 )
7350 7293 if not opts.get(b'local'):
7351 7294 p1, p2 = repo.dirstate.parents()
7352 7295 if p2 != nullid:
7353 7296 raise error.Abort(_(b'uncommitted merge'))
7354 7297 bheads = repo.branchheads()
7355 7298 if not opts.get(b'force') and bheads and p1 not in bheads:
7356 7299 raise error.Abort(
7357 7300 _(
7358 7301 b'working directory is not at a branch head '
7359 7302 b'(use -f to force)'
7360 7303 )
7361 7304 )
7362 7305 node = scmutil.revsingle(repo, rev_).node()
7363 7306
7364 7307 if not message:
7365 7308 # we don't translate commit messages
7366 7309 message = b'Added tag %s for changeset %s' % (
7367 7310 b', '.join(names),
7368 7311 short(node),
7369 7312 )
7370 7313
7371 7314 date = opts.get(b'date')
7372 7315 if date:
7373 7316 date = dateutil.parsedate(date)
7374 7317
7375 7318 if opts.get(b'remove'):
7376 7319 editform = b'tag.remove'
7377 7320 else:
7378 7321 editform = b'tag.add'
7379 7322 editor = cmdutil.getcommiteditor(
7380 7323 editform=editform, **pycompat.strkwargs(opts)
7381 7324 )
7382 7325
7383 7326 # don't allow tagging the null rev
7384 7327 if (
7385 7328 not opts.get(b'remove')
7386 7329 and scmutil.revsingle(repo, rev_).rev() == nullrev
7387 7330 ):
7388 7331 raise error.Abort(_(b"cannot tag null revision"))
7389 7332
7390 7333 tagsmod.tag(
7391 7334 repo,
7392 7335 names,
7393 7336 node,
7394 7337 message,
7395 7338 opts.get(b'local'),
7396 7339 opts.get(b'user'),
7397 7340 date,
7398 7341 editor=editor,
7399 7342 )
7400 7343
7401 7344
7402 7345 @command(
7403 7346 b'tags',
7404 7347 formatteropts,
7405 7348 b'',
7406 7349 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7407 7350 intents={INTENT_READONLY},
7408 7351 )
7409 7352 def tags(ui, repo, **opts):
7410 7353 """list repository tags
7411 7354
7412 7355 This lists both regular and local tags. When the -v/--verbose
7413 7356 switch is used, a third column "local" is printed for local tags.
7414 7357 When the -q/--quiet switch is used, only the tag name is printed.
7415 7358
7416 7359 .. container:: verbose
7417 7360
7418 7361 Template:
7419 7362
7420 7363 The following keywords are supported in addition to the common template
7421 7364 keywords and functions such as ``{tag}``. See also
7422 7365 :hg:`help templates`.
7423 7366
7424 7367 :type: String. ``local`` for local tags.
7425 7368
7426 7369 Returns 0 on success.
7427 7370 """
7428 7371
7429 7372 opts = pycompat.byteskwargs(opts)
7430 7373 ui.pager(b'tags')
7431 7374 fm = ui.formatter(b'tags', opts)
7432 7375 hexfunc = fm.hexfunc
7433 7376
7434 7377 for t, n in reversed(repo.tagslist()):
7435 7378 hn = hexfunc(n)
7436 7379 label = b'tags.normal'
7437 7380 tagtype = b''
7438 7381 if repo.tagtype(t) == b'local':
7439 7382 label = b'tags.local'
7440 7383 tagtype = b'local'
7441 7384
7442 7385 fm.startitem()
7443 7386 fm.context(repo=repo)
7444 7387 fm.write(b'tag', b'%s', t, label=label)
7445 7388 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7446 7389 fm.condwrite(
7447 7390 not ui.quiet,
7448 7391 b'rev node',
7449 7392 fmt,
7450 7393 repo.changelog.rev(n),
7451 7394 hn,
7452 7395 label=label,
7453 7396 )
7454 7397 fm.condwrite(
7455 7398 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7456 7399 )
7457 7400 fm.plain(b'\n')
7458 7401 fm.end()
7459 7402
7460 7403
7461 7404 @command(
7462 7405 b'tip',
7463 7406 [
7464 7407 (b'p', b'patch', None, _(b'show patch')),
7465 7408 (b'g', b'git', None, _(b'use git extended diff format')),
7466 7409 ]
7467 7410 + templateopts,
7468 7411 _(b'[-p] [-g]'),
7469 7412 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7470 7413 )
7471 7414 def tip(ui, repo, **opts):
7472 7415 """show the tip revision (DEPRECATED)
7473 7416
7474 7417 The tip revision (usually just called the tip) is the changeset
7475 7418 most recently added to the repository (and therefore the most
7476 7419 recently changed head).
7477 7420
7478 7421 If you have just made a commit, that commit will be the tip. If
7479 7422 you have just pulled changes from another repository, the tip of
7480 7423 that repository becomes the current tip. The "tip" tag is special
7481 7424 and cannot be renamed or assigned to a different changeset.
7482 7425
7483 7426 This command is deprecated, please use :hg:`heads` instead.
7484 7427
7485 7428 Returns 0 on success.
7486 7429 """
7487 7430 opts = pycompat.byteskwargs(opts)
7488 7431 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7489 7432 displayer.show(repo[b'tip'])
7490 7433 displayer.close()
7491 7434
7492 7435
7493 7436 @command(
7494 7437 b'unbundle',
7495 7438 [
7496 7439 (
7497 7440 b'u',
7498 7441 b'update',
7499 7442 None,
7500 7443 _(b'update to new branch head if changesets were unbundled'),
7501 7444 )
7502 7445 ],
7503 7446 _(b'[-u] FILE...'),
7504 7447 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7505 7448 )
7506 7449 def unbundle(ui, repo, fname1, *fnames, **opts):
7507 7450 """apply one or more bundle files
7508 7451
7509 7452 Apply one or more bundle files generated by :hg:`bundle`.
7510 7453
7511 7454 Returns 0 on success, 1 if an update has unresolved files.
7512 7455 """
7513 7456 fnames = (fname1,) + fnames
7514 7457
7515 7458 with repo.lock():
7516 7459 for fname in fnames:
7517 7460 f = hg.openpath(ui, fname)
7518 7461 gen = exchange.readbundle(ui, f, fname)
7519 7462 if isinstance(gen, streamclone.streamcloneapplier):
7520 7463 raise error.Abort(
7521 7464 _(
7522 7465 b'packed bundles cannot be applied with '
7523 7466 b'"hg unbundle"'
7524 7467 ),
7525 7468 hint=_(b'use "hg debugapplystreamclonebundle"'),
7526 7469 )
7527 7470 url = b'bundle:' + fname
7528 7471 try:
7529 7472 txnname = b'unbundle'
7530 7473 if not isinstance(gen, bundle2.unbundle20):
7531 7474 txnname = b'unbundle\n%s' % util.hidepassword(url)
7532 7475 with repo.transaction(txnname) as tr:
7533 7476 op = bundle2.applybundle(
7534 7477 repo, gen, tr, source=b'unbundle', url=url
7535 7478 )
7536 7479 except error.BundleUnknownFeatureError as exc:
7537 7480 raise error.Abort(
7538 7481 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7539 7482 hint=_(
7540 7483 b"see https://mercurial-scm.org/"
7541 7484 b"wiki/BundleFeature for more "
7542 7485 b"information"
7543 7486 ),
7544 7487 )
7545 7488 modheads = bundle2.combinechangegroupresults(op)
7546 7489
7547 7490 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7548 7491
7549 7492
7550 7493 @command(
7551 7494 b'unshelve',
7552 7495 [
7553 7496 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7554 7497 (
7555 7498 b'c',
7556 7499 b'continue',
7557 7500 None,
7558 7501 _(b'continue an incomplete unshelve operation'),
7559 7502 ),
7560 7503 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7561 7504 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7562 7505 (
7563 7506 b'n',
7564 7507 b'name',
7565 7508 b'',
7566 7509 _(b'restore shelved change with given name'),
7567 7510 _(b'NAME'),
7568 7511 ),
7569 7512 (b't', b'tool', b'', _(b'specify merge tool')),
7570 7513 (
7571 7514 b'',
7572 7515 b'date',
7573 7516 b'',
7574 7517 _(b'set date for temporary commits (DEPRECATED)'),
7575 7518 _(b'DATE'),
7576 7519 ),
7577 7520 ],
7578 7521 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7579 7522 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7580 7523 )
7581 7524 def unshelve(ui, repo, *shelved, **opts):
7582 7525 """restore a shelved change to the working directory
7583 7526
7584 7527 This command accepts an optional name of a shelved change to
7585 7528 restore. If none is given, the most recent shelved change is used.
7586 7529
7587 7530 If a shelved change is applied successfully, the bundle that
7588 7531 contains the shelved changes is moved to a backup location
7589 7532 (.hg/shelve-backup).
7590 7533
7591 7534 Since you can restore a shelved change on top of an arbitrary
7592 7535 commit, it is possible that unshelving will result in a conflict
7593 7536 between your changes and the commits you are unshelving onto. If
7594 7537 this occurs, you must resolve the conflict, then use
7595 7538 ``--continue`` to complete the unshelve operation. (The bundle
7596 7539 will not be moved until you successfully complete the unshelve.)
7597 7540
7598 7541 (Alternatively, you can use ``--abort`` to abandon an unshelve
7599 7542 that causes a conflict. This reverts the unshelved changes, and
7600 7543 leaves the bundle in place.)
7601 7544
7602 7545 If bare shelved change (without interactive, include and exclude
7603 7546 option) was done on newly created branch it would restore branch
7604 7547 information to the working directory.
7605 7548
7606 7549 After a successful unshelve, the shelved changes are stored in a
7607 7550 backup directory. Only the N most recent backups are kept. N
7608 7551 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7609 7552 configuration option.
7610 7553
7611 7554 .. container:: verbose
7612 7555
7613 7556 Timestamp in seconds is used to decide order of backups. More
7614 7557 than ``maxbackups`` backups are kept, if same timestamp
7615 7558 prevents from deciding exact order of them, for safety.
7616 7559
7617 7560 Selected changes can be unshelved with ``--interactive`` flag.
7618 7561 The working directory is updated with the selected changes, and
7619 7562 only the unselected changes remain shelved.
7620 7563 Note: The whole shelve is applied to working directory first before
7621 7564 running interactively. So, this will bring up all the conflicts between
7622 7565 working directory and the shelve, irrespective of which changes will be
7623 7566 unshelved.
7624 7567 """
7625 7568 with repo.wlock():
7626 7569 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7627 7570
7628 7571
7629 7572 statemod.addunfinished(
7630 7573 b'unshelve',
7631 7574 fname=b'shelvedstate',
7632 7575 continueflag=True,
7633 7576 abortfunc=shelvemod.hgabortunshelve,
7634 7577 continuefunc=shelvemod.hgcontinueunshelve,
7635 7578 cmdmsg=_(b'unshelve already in progress'),
7636 7579 )
7637 7580
7638 7581
7639 7582 @command(
7640 7583 b'update|up|checkout|co',
7641 7584 [
7642 7585 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7643 7586 (b'c', b'check', None, _(b'require clean working directory')),
7644 7587 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7645 7588 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7646 7589 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7647 7590 ]
7648 7591 + mergetoolopts,
7649 7592 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7650 7593 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7651 7594 helpbasic=True,
7652 7595 )
7653 7596 def update(ui, repo, node=None, **opts):
7654 7597 """update working directory (or switch revisions)
7655 7598
7656 7599 Update the repository's working directory to the specified
7657 7600 changeset. If no changeset is specified, update to the tip of the
7658 7601 current named branch and move the active bookmark (see :hg:`help
7659 7602 bookmarks`).
7660 7603
7661 7604 Update sets the working directory's parent revision to the specified
7662 7605 changeset (see :hg:`help parents`).
7663 7606
7664 7607 If the changeset is not a descendant or ancestor of the working
7665 7608 directory's parent and there are uncommitted changes, the update is
7666 7609 aborted. With the -c/--check option, the working directory is checked
7667 7610 for uncommitted changes; if none are found, the working directory is
7668 7611 updated to the specified changeset.
7669 7612
7670 7613 .. container:: verbose
7671 7614
7672 7615 The -C/--clean, -c/--check, and -m/--merge options control what
7673 7616 happens if the working directory contains uncommitted changes.
7674 7617 At most of one of them can be specified.
7675 7618
7676 7619 1. If no option is specified, and if
7677 7620 the requested changeset is an ancestor or descendant of
7678 7621 the working directory's parent, the uncommitted changes
7679 7622 are merged into the requested changeset and the merged
7680 7623 result is left uncommitted. If the requested changeset is
7681 7624 not an ancestor or descendant (that is, it is on another
7682 7625 branch), the update is aborted and the uncommitted changes
7683 7626 are preserved.
7684 7627
7685 7628 2. With the -m/--merge option, the update is allowed even if the
7686 7629 requested changeset is not an ancestor or descendant of
7687 7630 the working directory's parent.
7688 7631
7689 7632 3. With the -c/--check option, the update is aborted and the
7690 7633 uncommitted changes are preserved.
7691 7634
7692 7635 4. With the -C/--clean option, uncommitted changes are discarded and
7693 7636 the working directory is updated to the requested changeset.
7694 7637
7695 7638 To cancel an uncommitted merge (and lose your changes), use
7696 7639 :hg:`merge --abort`.
7697 7640
7698 7641 Use null as the changeset to remove the working directory (like
7699 7642 :hg:`clone -U`).
7700 7643
7701 7644 If you want to revert just one file to an older revision, use
7702 7645 :hg:`revert [-r REV] NAME`.
7703 7646
7704 7647 See :hg:`help dates` for a list of formats valid for -d/--date.
7705 7648
7706 7649 Returns 0 on success, 1 if there are unresolved files.
7707 7650 """
7708 7651 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7709 7652 rev = opts.get('rev')
7710 7653 date = opts.get('date')
7711 7654 clean = opts.get('clean')
7712 7655 check = opts.get('check')
7713 7656 merge = opts.get('merge')
7714 7657 if rev and node:
7715 7658 raise error.Abort(_(b"please specify just one revision"))
7716 7659
7717 7660 if ui.configbool(b'commands', b'update.requiredest'):
7718 7661 if not node and not rev and not date:
7719 7662 raise error.Abort(
7720 7663 _(b'you must specify a destination'),
7721 7664 hint=_(b'for example: hg update ".::"'),
7722 7665 )
7723 7666
7724 7667 if rev is None or rev == b'':
7725 7668 rev = node
7726 7669
7727 7670 if date and rev is not None:
7728 7671 raise error.Abort(_(b"you can't specify a revision and a date"))
7729 7672
7730 7673 updatecheck = None
7731 7674 if check:
7732 7675 updatecheck = b'abort'
7733 7676 elif merge:
7734 7677 updatecheck = b'none'
7735 7678
7736 7679 with repo.wlock():
7737 7680 cmdutil.clearunfinished(repo)
7738 7681 if date:
7739 7682 rev = cmdutil.finddate(ui, repo, date)
7740 7683
7741 7684 # if we defined a bookmark, we have to remember the original name
7742 7685 brev = rev
7743 7686 if rev:
7744 7687 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7745 7688 ctx = scmutil.revsingle(repo, rev, default=None)
7746 7689 rev = ctx.rev()
7747 7690 hidden = ctx.hidden()
7748 7691 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7749 7692 with ui.configoverride(overrides, b'update'):
7750 7693 ret = hg.updatetotally(
7751 7694 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7752 7695 )
7753 7696 if hidden:
7754 7697 ctxstr = ctx.hex()[:12]
7755 7698 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7756 7699
7757 7700 if ctx.obsolete():
7758 7701 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7759 7702 ui.warn(b"(%s)\n" % obsfatemsg)
7760 7703 return ret
7761 7704
7762 7705
7763 7706 @command(
7764 7707 b'verify',
7765 7708 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7766 7709 helpcategory=command.CATEGORY_MAINTENANCE,
7767 7710 )
7768 7711 def verify(ui, repo, **opts):
7769 7712 """verify the integrity of the repository
7770 7713
7771 7714 Verify the integrity of the current repository.
7772 7715
7773 7716 This will perform an extensive check of the repository's
7774 7717 integrity, validating the hashes and checksums of each entry in
7775 7718 the changelog, manifest, and tracked files, as well as the
7776 7719 integrity of their crosslinks and indices.
7777 7720
7778 7721 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7779 7722 for more information about recovery from corruption of the
7780 7723 repository.
7781 7724
7782 7725 Returns 0 on success, 1 if errors are encountered.
7783 7726 """
7784 7727 opts = pycompat.byteskwargs(opts)
7785 7728
7786 7729 level = None
7787 7730 if opts[b'full']:
7788 7731 level = verifymod.VERIFY_FULL
7789 7732 return hg.verify(repo, level)
7790 7733
7791 7734
7792 7735 @command(
7793 7736 b'version',
7794 7737 [] + formatteropts,
7795 7738 helpcategory=command.CATEGORY_HELP,
7796 7739 norepo=True,
7797 7740 intents={INTENT_READONLY},
7798 7741 )
7799 7742 def version_(ui, **opts):
7800 7743 """output version and copyright information
7801 7744
7802 7745 .. container:: verbose
7803 7746
7804 7747 Template:
7805 7748
7806 7749 The following keywords are supported. See also :hg:`help templates`.
7807 7750
7808 7751 :extensions: List of extensions.
7809 7752 :ver: String. Version number.
7810 7753
7811 7754 And each entry of ``{extensions}`` provides the following sub-keywords
7812 7755 in addition to ``{ver}``.
7813 7756
7814 7757 :bundled: Boolean. True if included in the release.
7815 7758 :name: String. Extension name.
7816 7759 """
7817 7760 opts = pycompat.byteskwargs(opts)
7818 7761 if ui.verbose:
7819 7762 ui.pager(b'version')
7820 7763 fm = ui.formatter(b"version", opts)
7821 7764 fm.startitem()
7822 7765 fm.write(
7823 7766 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7824 7767 )
7825 7768 license = _(
7826 7769 b"(see https://mercurial-scm.org for more information)\n"
7827 7770 b"\nCopyright (C) 2005-2020 Matt Mackall and others\n"
7828 7771 b"This is free software; see the source for copying conditions. "
7829 7772 b"There is NO\nwarranty; "
7830 7773 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7831 7774 )
7832 7775 if not ui.quiet:
7833 7776 fm.plain(license)
7834 7777
7835 7778 if ui.verbose:
7836 7779 fm.plain(_(b"\nEnabled extensions:\n\n"))
7837 7780 # format names and versions into columns
7838 7781 names = []
7839 7782 vers = []
7840 7783 isinternals = []
7841 7784 for name, module in sorted(extensions.extensions()):
7842 7785 names.append(name)
7843 7786 vers.append(extensions.moduleversion(module) or None)
7844 7787 isinternals.append(extensions.ismoduleinternal(module))
7845 7788 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7846 7789 if names:
7847 7790 namefmt = b" %%-%ds " % max(len(n) for n in names)
7848 7791 places = [_(b"external"), _(b"internal")]
7849 7792 for n, v, p in zip(names, vers, isinternals):
7850 7793 fn.startitem()
7851 7794 fn.condwrite(ui.verbose, b"name", namefmt, n)
7852 7795 if ui.verbose:
7853 7796 fn.plain(b"%s " % places[p])
7854 7797 fn.data(bundled=p)
7855 7798 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7856 7799 if ui.verbose:
7857 7800 fn.plain(b"\n")
7858 7801 fn.end()
7859 7802 fm.end()
7860 7803
7861 7804
7862 7805 def loadcmdtable(ui, name, cmdtable):
7863 7806 """Load command functions from specified cmdtable
7864 7807 """
7865 7808 overrides = [cmd for cmd in cmdtable if cmd in table]
7866 7809 if overrides:
7867 7810 ui.warn(
7868 7811 _(b"extension '%s' overrides commands: %s\n")
7869 7812 % (name, b" ".join(overrides))
7870 7813 )
7871 7814 table.update(cmdtable)
@@ -1,1591 +1,1588 b''
1 1 # configitems.py - centralized declaration of configuration option
2 2 #
3 3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
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 functools
11 11 import re
12 12
13 13 from . import (
14 14 encoding,
15 15 error,
16 16 )
17 17
18 18
19 19 def loadconfigtable(ui, extname, configtable):
20 20 """update config item known to the ui with the extension ones"""
21 21 for section, items in sorted(configtable.items()):
22 22 knownitems = ui._knownconfig.setdefault(section, itemregister())
23 23 knownkeys = set(knownitems)
24 24 newkeys = set(items)
25 25 for key in sorted(knownkeys & newkeys):
26 26 msg = b"extension '%s' overwrite config item '%s.%s'"
27 27 msg %= (extname, section, key)
28 28 ui.develwarn(msg, config=b'warn-config')
29 29
30 30 knownitems.update(items)
31 31
32 32
33 33 class configitem(object):
34 34 """represent a known config item
35 35
36 36 :section: the official config section where to find this item,
37 37 :name: the official name within the section,
38 38 :default: default value for this item,
39 39 :alias: optional list of tuples as alternatives,
40 40 :generic: this is a generic definition, match name using regular expression.
41 41 """
42 42
43 43 def __init__(
44 44 self,
45 45 section,
46 46 name,
47 47 default=None,
48 48 alias=(),
49 49 generic=False,
50 50 priority=0,
51 51 experimental=False,
52 52 ):
53 53 self.section = section
54 54 self.name = name
55 55 self.default = default
56 56 self.alias = list(alias)
57 57 self.generic = generic
58 58 self.priority = priority
59 59 self.experimental = experimental
60 60 self._re = None
61 61 if generic:
62 62 self._re = re.compile(self.name)
63 63
64 64
65 65 class itemregister(dict):
66 66 """A specialized dictionary that can handle wild-card selection"""
67 67
68 68 def __init__(self):
69 69 super(itemregister, self).__init__()
70 70 self._generics = set()
71 71
72 72 def update(self, other):
73 73 super(itemregister, self).update(other)
74 74 self._generics.update(other._generics)
75 75
76 76 def __setitem__(self, key, item):
77 77 super(itemregister, self).__setitem__(key, item)
78 78 if item.generic:
79 79 self._generics.add(item)
80 80
81 81 def get(self, key):
82 82 baseitem = super(itemregister, self).get(key)
83 83 if baseitem is not None and not baseitem.generic:
84 84 return baseitem
85 85
86 86 # search for a matching generic item
87 87 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
88 88 for item in generics:
89 89 # we use 'match' instead of 'search' to make the matching simpler
90 90 # for people unfamiliar with regular expression. Having the match
91 91 # rooted to the start of the string will produce less surprising
92 92 # result for user writing simple regex for sub-attribute.
93 93 #
94 94 # For example using "color\..*" match produces an unsurprising
95 95 # result, while using search could suddenly match apparently
96 96 # unrelated configuration that happens to contains "color."
97 97 # anywhere. This is a tradeoff where we favor requiring ".*" on
98 98 # some match to avoid the need to prefix most pattern with "^".
99 99 # The "^" seems more error prone.
100 100 if item._re.match(key):
101 101 return item
102 102
103 103 return None
104 104
105 105
106 106 coreitems = {}
107 107
108 108
109 109 def _register(configtable, *args, **kwargs):
110 110 item = configitem(*args, **kwargs)
111 111 section = configtable.setdefault(item.section, itemregister())
112 112 if item.name in section:
113 113 msg = b"duplicated config item registration for '%s.%s'"
114 114 raise error.ProgrammingError(msg % (item.section, item.name))
115 115 section[item.name] = item
116 116
117 117
118 118 # special value for case where the default is derived from other values
119 119 dynamicdefault = object()
120 120
121 121 # Registering actual config items
122 122
123 123
124 124 def getitemregister(configtable):
125 125 f = functools.partial(_register, configtable)
126 126 # export pseudo enum as configitem.*
127 127 f.dynamicdefault = dynamicdefault
128 128 return f
129 129
130 130
131 131 coreconfigitem = getitemregister(coreitems)
132 132
133 133
134 134 def _registerdiffopts(section, configprefix=b''):
135 135 coreconfigitem(
136 136 section, configprefix + b'nodates', default=False,
137 137 )
138 138 coreconfigitem(
139 139 section, configprefix + b'showfunc', default=False,
140 140 )
141 141 coreconfigitem(
142 142 section, configprefix + b'unified', default=None,
143 143 )
144 144 coreconfigitem(
145 145 section, configprefix + b'git', default=False,
146 146 )
147 147 coreconfigitem(
148 148 section, configprefix + b'ignorews', default=False,
149 149 )
150 150 coreconfigitem(
151 151 section, configprefix + b'ignorewsamount', default=False,
152 152 )
153 153 coreconfigitem(
154 154 section, configprefix + b'ignoreblanklines', default=False,
155 155 )
156 156 coreconfigitem(
157 157 section, configprefix + b'ignorewseol', default=False,
158 158 )
159 159 coreconfigitem(
160 160 section, configprefix + b'nobinary', default=False,
161 161 )
162 162 coreconfigitem(
163 163 section, configprefix + b'noprefix', default=False,
164 164 )
165 165 coreconfigitem(
166 166 section, configprefix + b'word-diff', default=False,
167 167 )
168 168
169 169
170 170 coreconfigitem(
171 171 b'alias', b'.*', default=dynamicdefault, generic=True,
172 172 )
173 173 coreconfigitem(
174 174 b'auth', b'cookiefile', default=None,
175 175 )
176 176 _registerdiffopts(section=b'annotate')
177 177 # bookmarks.pushing: internal hack for discovery
178 178 coreconfigitem(
179 179 b'bookmarks', b'pushing', default=list,
180 180 )
181 181 # bundle.mainreporoot: internal hack for bundlerepo
182 182 coreconfigitem(
183 183 b'bundle', b'mainreporoot', default=b'',
184 184 )
185 185 coreconfigitem(
186 186 b'censor', b'policy', default=b'abort', experimental=True,
187 187 )
188 188 coreconfigitem(
189 189 b'chgserver', b'idletimeout', default=3600,
190 190 )
191 191 coreconfigitem(
192 192 b'chgserver', b'skiphash', default=False,
193 193 )
194 194 coreconfigitem(
195 195 b'cmdserver', b'log', default=None,
196 196 )
197 197 coreconfigitem(
198 198 b'cmdserver', b'max-log-files', default=7,
199 199 )
200 200 coreconfigitem(
201 201 b'cmdserver', b'max-log-size', default=b'1 MB',
202 202 )
203 203 coreconfigitem(
204 204 b'cmdserver', b'max-repo-cache', default=0, experimental=True,
205 205 )
206 206 coreconfigitem(
207 207 b'cmdserver', b'message-encodings', default=list,
208 208 )
209 209 coreconfigitem(
210 210 b'cmdserver',
211 211 b'track-log',
212 212 default=lambda: [b'chgserver', b'cmdserver', b'repocache'],
213 213 )
214 214 coreconfigitem(
215 215 b'cmdserver', b'shutdown-on-interrupt', default=True,
216 216 )
217 217 coreconfigitem(
218 218 b'color', b'.*', default=None, generic=True,
219 219 )
220 220 coreconfigitem(
221 221 b'color', b'mode', default=b'auto',
222 222 )
223 223 coreconfigitem(
224 224 b'color', b'pagermode', default=dynamicdefault,
225 225 )
226 226 _registerdiffopts(section=b'commands', configprefix=b'commit.interactive.')
227 227 coreconfigitem(
228 228 b'commands', b'commit.post-status', default=False,
229 229 )
230 230 coreconfigitem(
231 231 b'commands', b'grep.all-files', default=False, experimental=True,
232 232 )
233 233 coreconfigitem(
234 234 b'commands', b'merge.require-rev', default=False,
235 235 )
236 236 coreconfigitem(
237 237 b'commands', b'push.require-revs', default=False,
238 238 )
239 239 coreconfigitem(
240 240 b'commands', b'resolve.confirm', default=False,
241 241 )
242 242 coreconfigitem(
243 243 b'commands', b'resolve.explicit-re-merge', default=False,
244 244 )
245 245 coreconfigitem(
246 246 b'commands', b'resolve.mark-check', default=b'none',
247 247 )
248 248 _registerdiffopts(section=b'commands', configprefix=b'revert.interactive.')
249 249 coreconfigitem(
250 250 b'commands', b'show.aliasprefix', default=list,
251 251 )
252 252 coreconfigitem(
253 253 b'commands', b'status.relative', default=False,
254 254 )
255 255 coreconfigitem(
256 256 b'commands', b'status.skipstates', default=[], experimental=True,
257 257 )
258 258 coreconfigitem(
259 259 b'commands', b'status.terse', default=b'',
260 260 )
261 261 coreconfigitem(
262 262 b'commands', b'status.verbose', default=False,
263 263 )
264 264 coreconfigitem(
265 265 b'commands', b'update.check', default=None,
266 266 )
267 267 coreconfigitem(
268 268 b'commands', b'update.requiredest', default=False,
269 269 )
270 270 coreconfigitem(
271 271 b'committemplate', b'.*', default=None, generic=True,
272 272 )
273 273 coreconfigitem(
274 274 b'convert', b'bzr.saverev', default=True,
275 275 )
276 276 coreconfigitem(
277 277 b'convert', b'cvsps.cache', default=True,
278 278 )
279 279 coreconfigitem(
280 280 b'convert', b'cvsps.fuzz', default=60,
281 281 )
282 282 coreconfigitem(
283 283 b'convert', b'cvsps.logencoding', default=None,
284 284 )
285 285 coreconfigitem(
286 286 b'convert', b'cvsps.mergefrom', default=None,
287 287 )
288 288 coreconfigitem(
289 289 b'convert', b'cvsps.mergeto', default=None,
290 290 )
291 291 coreconfigitem(
292 292 b'convert', b'git.committeractions', default=lambda: [b'messagedifferent'],
293 293 )
294 294 coreconfigitem(
295 295 b'convert', b'git.extrakeys', default=list,
296 296 )
297 297 coreconfigitem(
298 298 b'convert', b'git.findcopiesharder', default=False,
299 299 )
300 300 coreconfigitem(
301 301 b'convert', b'git.remoteprefix', default=b'remote',
302 302 )
303 303 coreconfigitem(
304 304 b'convert', b'git.renamelimit', default=400,
305 305 )
306 306 coreconfigitem(
307 307 b'convert', b'git.saverev', default=True,
308 308 )
309 309 coreconfigitem(
310 310 b'convert', b'git.similarity', default=50,
311 311 )
312 312 coreconfigitem(
313 313 b'convert', b'git.skipsubmodules', default=False,
314 314 )
315 315 coreconfigitem(
316 316 b'convert', b'hg.clonebranches', default=False,
317 317 )
318 318 coreconfigitem(
319 319 b'convert', b'hg.ignoreerrors', default=False,
320 320 )
321 321 coreconfigitem(
322 322 b'convert', b'hg.preserve-hash', default=False,
323 323 )
324 324 coreconfigitem(
325 325 b'convert', b'hg.revs', default=None,
326 326 )
327 327 coreconfigitem(
328 328 b'convert', b'hg.saverev', default=False,
329 329 )
330 330 coreconfigitem(
331 331 b'convert', b'hg.sourcename', default=None,
332 332 )
333 333 coreconfigitem(
334 334 b'convert', b'hg.startrev', default=None,
335 335 )
336 336 coreconfigitem(
337 337 b'convert', b'hg.tagsbranch', default=b'default',
338 338 )
339 339 coreconfigitem(
340 340 b'convert', b'hg.usebranchnames', default=True,
341 341 )
342 342 coreconfigitem(
343 343 b'convert', b'ignoreancestorcheck', default=False, experimental=True,
344 344 )
345 345 coreconfigitem(
346 346 b'convert', b'localtimezone', default=False,
347 347 )
348 348 coreconfigitem(
349 349 b'convert', b'p4.encoding', default=dynamicdefault,
350 350 )
351 351 coreconfigitem(
352 352 b'convert', b'p4.startrev', default=0,
353 353 )
354 354 coreconfigitem(
355 355 b'convert', b'skiptags', default=False,
356 356 )
357 357 coreconfigitem(
358 358 b'convert', b'svn.debugsvnlog', default=True,
359 359 )
360 360 coreconfigitem(
361 361 b'convert', b'svn.trunk', default=None,
362 362 )
363 363 coreconfigitem(
364 364 b'convert', b'svn.tags', default=None,
365 365 )
366 366 coreconfigitem(
367 367 b'convert', b'svn.branches', default=None,
368 368 )
369 369 coreconfigitem(
370 370 b'convert', b'svn.startrev', default=0,
371 371 )
372 372 coreconfigitem(
373 373 b'debug', b'dirstate.delaywrite', default=0,
374 374 )
375 375 coreconfigitem(
376 376 b'defaults', b'.*', default=None, generic=True,
377 377 )
378 378 coreconfigitem(
379 379 b'devel', b'all-warnings', default=False,
380 380 )
381 381 coreconfigitem(
382 382 b'devel', b'bundle2.debug', default=False,
383 383 )
384 384 coreconfigitem(
385 385 b'devel', b'bundle.delta', default=b'',
386 386 )
387 387 coreconfigitem(
388 388 b'devel', b'cache-vfs', default=None,
389 389 )
390 390 coreconfigitem(
391 391 b'devel', b'check-locks', default=False,
392 392 )
393 393 coreconfigitem(
394 394 b'devel', b'check-relroot', default=False,
395 395 )
396 396 coreconfigitem(
397 397 b'devel', b'default-date', default=None,
398 398 )
399 399 coreconfigitem(
400 400 b'devel', b'deprec-warn', default=False,
401 401 )
402 402 coreconfigitem(
403 403 b'devel', b'disableloaddefaultcerts', default=False,
404 404 )
405 405 coreconfigitem(
406 406 b'devel', b'warn-empty-changegroup', default=False,
407 407 )
408 408 coreconfigitem(
409 409 b'devel', b'legacy.exchange', default=list,
410 410 )
411 411 coreconfigitem(
412 412 b'devel', b'persistent-nodemap', default=False,
413 413 )
414 414 coreconfigitem(
415 415 b'devel', b'servercafile', default=b'',
416 416 )
417 417 coreconfigitem(
418 418 b'devel', b'serverexactprotocol', default=b'',
419 419 )
420 420 coreconfigitem(
421 421 b'devel', b'serverrequirecert', default=False,
422 422 )
423 423 coreconfigitem(
424 424 b'devel', b'strip-obsmarkers', default=True,
425 425 )
426 426 coreconfigitem(
427 427 b'devel', b'warn-config', default=None,
428 428 )
429 429 coreconfigitem(
430 430 b'devel', b'warn-config-default', default=None,
431 431 )
432 432 coreconfigitem(
433 433 b'devel', b'user.obsmarker', default=None,
434 434 )
435 435 coreconfigitem(
436 436 b'devel', b'warn-config-unknown', default=None,
437 437 )
438 438 coreconfigitem(
439 439 b'devel', b'debug.copies', default=False,
440 440 )
441 441 coreconfigitem(
442 442 b'devel', b'debug.extensions', default=False,
443 443 )
444 444 coreconfigitem(
445 445 b'devel', b'debug.repo-filters', default=False,
446 446 )
447 447 coreconfigitem(
448 448 b'devel', b'debug.peer-request', default=False,
449 449 )
450 450 coreconfigitem(
451 451 b'devel', b'discovery.randomize', default=True,
452 452 )
453 453 _registerdiffopts(section=b'diff')
454 454 coreconfigitem(
455 455 b'email', b'bcc', default=None,
456 456 )
457 457 coreconfigitem(
458 458 b'email', b'cc', default=None,
459 459 )
460 460 coreconfigitem(
461 461 b'email', b'charsets', default=list,
462 462 )
463 463 coreconfigitem(
464 464 b'email', b'from', default=None,
465 465 )
466 466 coreconfigitem(
467 467 b'email', b'method', default=b'smtp',
468 468 )
469 469 coreconfigitem(
470 470 b'email', b'reply-to', default=None,
471 471 )
472 472 coreconfigitem(
473 473 b'email', b'to', default=None,
474 474 )
475 475 coreconfigitem(
476 476 b'experimental', b'archivemetatemplate', default=dynamicdefault,
477 477 )
478 478 coreconfigitem(
479 479 b'experimental', b'auto-publish', default=b'publish',
480 480 )
481 481 coreconfigitem(
482 482 b'experimental', b'bundle-phases', default=False,
483 483 )
484 484 coreconfigitem(
485 485 b'experimental', b'bundle2-advertise', default=True,
486 486 )
487 487 coreconfigitem(
488 488 b'experimental', b'bundle2-output-capture', default=False,
489 489 )
490 490 coreconfigitem(
491 491 b'experimental', b'bundle2.pushback', default=False,
492 492 )
493 493 coreconfigitem(
494 494 b'experimental', b'bundle2lazylocking', default=False,
495 495 )
496 496 coreconfigitem(
497 497 b'experimental', b'bundlecomplevel', default=None,
498 498 )
499 499 coreconfigitem(
500 500 b'experimental', b'bundlecomplevel.bzip2', default=None,
501 501 )
502 502 coreconfigitem(
503 503 b'experimental', b'bundlecomplevel.gzip', default=None,
504 504 )
505 505 coreconfigitem(
506 506 b'experimental', b'bundlecomplevel.none', default=None,
507 507 )
508 508 coreconfigitem(
509 509 b'experimental', b'bundlecomplevel.zstd', default=None,
510 510 )
511 511 coreconfigitem(
512 512 b'experimental', b'changegroup3', default=False,
513 513 )
514 514 coreconfigitem(
515 515 b'experimental', b'cleanup-as-archived', default=False,
516 516 )
517 517 coreconfigitem(
518 518 b'experimental', b'clientcompressionengines', default=list,
519 519 )
520 520 coreconfigitem(
521 521 b'experimental', b'copytrace', default=b'on',
522 522 )
523 523 coreconfigitem(
524 524 b'experimental', b'copytrace.movecandidateslimit', default=100,
525 525 )
526 526 coreconfigitem(
527 527 b'experimental', b'copytrace.sourcecommitlimit', default=100,
528 528 )
529 529 coreconfigitem(
530 530 b'experimental', b'copies.read-from', default=b"filelog-only",
531 531 )
532 532 coreconfigitem(
533 533 b'experimental', b'copies.write-to', default=b'filelog-only',
534 534 )
535 535 coreconfigitem(
536 536 b'experimental', b'crecordtest', default=None,
537 537 )
538 538 coreconfigitem(
539 539 b'experimental', b'directaccess', default=False,
540 540 )
541 541 coreconfigitem(
542 542 b'experimental', b'directaccess.revnums', default=False,
543 543 )
544 544 coreconfigitem(
545 545 b'experimental', b'editortmpinhg', default=False,
546 546 )
547 547 coreconfigitem(
548 548 b'experimental', b'evolution', default=list,
549 549 )
550 550 coreconfigitem(
551 551 b'experimental',
552 552 b'evolution.allowdivergence',
553 553 default=False,
554 554 alias=[(b'experimental', b'allowdivergence')],
555 555 )
556 556 coreconfigitem(
557 557 b'experimental', b'evolution.allowunstable', default=None,
558 558 )
559 559 coreconfigitem(
560 560 b'experimental', b'evolution.createmarkers', default=None,
561 561 )
562 562 coreconfigitem(
563 563 b'experimental',
564 564 b'evolution.effect-flags',
565 565 default=True,
566 566 alias=[(b'experimental', b'effect-flags')],
567 567 )
568 568 coreconfigitem(
569 569 b'experimental', b'evolution.exchange', default=None,
570 570 )
571 571 coreconfigitem(
572 572 b'experimental', b'evolution.bundle-obsmarker', default=False,
573 573 )
574 574 coreconfigitem(
575 575 b'experimental', b'log.topo', default=False,
576 576 )
577 577 coreconfigitem(
578 578 b'experimental', b'evolution.report-instabilities', default=True,
579 579 )
580 580 coreconfigitem(
581 581 b'experimental', b'evolution.track-operation', default=True,
582 582 )
583 583 # repo-level config to exclude a revset visibility
584 584 #
585 585 # The target use case is to use `share` to expose different subset of the same
586 586 # repository, especially server side. See also `server.view`.
587 587 coreconfigitem(
588 588 b'experimental', b'extra-filter-revs', default=None,
589 589 )
590 590 coreconfigitem(
591 591 b'experimental', b'maxdeltachainspan', default=-1,
592 592 )
593 593 coreconfigitem(
594 594 b'experimental', b'mergetempdirprefix', default=None,
595 595 )
596 596 coreconfigitem(
597 597 b'experimental', b'mmapindexthreshold', default=None,
598 598 )
599 599 coreconfigitem(
600 600 b'experimental', b'narrow', default=False,
601 601 )
602 602 coreconfigitem(
603 603 b'experimental', b'nonnormalparanoidcheck', default=False,
604 604 )
605 605 coreconfigitem(
606 606 b'experimental', b'exportableenviron', default=list,
607 607 )
608 608 coreconfigitem(
609 609 b'experimental', b'extendedheader.index', default=None,
610 610 )
611 611 coreconfigitem(
612 612 b'experimental', b'extendedheader.similarity', default=False,
613 613 )
614 614 coreconfigitem(
615 615 b'experimental', b'graphshorten', default=False,
616 616 )
617 617 coreconfigitem(
618 618 b'experimental', b'graphstyle.parent', default=dynamicdefault,
619 619 )
620 620 coreconfigitem(
621 621 b'experimental', b'graphstyle.missing', default=dynamicdefault,
622 622 )
623 623 coreconfigitem(
624 624 b'experimental', b'graphstyle.grandparent', default=dynamicdefault,
625 625 )
626 626 coreconfigitem(
627 627 b'experimental', b'hook-track-tags', default=False,
628 628 )
629 629 coreconfigitem(
630 630 b'experimental', b'httppeer.advertise-v2', default=False,
631 631 )
632 632 coreconfigitem(
633 633 b'experimental', b'httppeer.v2-encoder-order', default=None,
634 634 )
635 635 coreconfigitem(
636 636 b'experimental', b'httppostargs', default=False,
637 637 )
638 coreconfigitem(
639 b'experimental', b'mergedriver', default=None,
640 )
641 638 coreconfigitem(b'experimental', b'nointerrupt', default=False)
642 639 coreconfigitem(b'experimental', b'nointerrupt-interactiveonly', default=True)
643 640
644 641 coreconfigitem(
645 642 b'experimental', b'obsmarkers-exchange-debug', default=False,
646 643 )
647 644 coreconfigitem(
648 645 b'experimental', b'remotenames', default=False,
649 646 )
650 647 coreconfigitem(
651 648 b'experimental', b'removeemptydirs', default=True,
652 649 )
653 650 coreconfigitem(
654 651 b'experimental', b'revert.interactive.select-to-keep', default=False,
655 652 )
656 653 coreconfigitem(
657 654 b'experimental', b'revisions.prefixhexnode', default=False,
658 655 )
659 656 coreconfigitem(
660 657 b'experimental', b'revlogv2', default=None,
661 658 )
662 659 coreconfigitem(
663 660 b'experimental', b'revisions.disambiguatewithin', default=None,
664 661 )
665 662 coreconfigitem(
666 663 b'experimental', b'rust.index', default=False,
667 664 )
668 665 coreconfigitem(
669 666 b'experimental', b'server.filesdata.recommended-batch-size', default=50000,
670 667 )
671 668 coreconfigitem(
672 669 b'experimental',
673 670 b'server.manifestdata.recommended-batch-size',
674 671 default=100000,
675 672 )
676 673 coreconfigitem(
677 674 b'experimental', b'server.stream-narrow-clones', default=False,
678 675 )
679 676 coreconfigitem(
680 677 b'experimental', b'single-head-per-branch', default=False,
681 678 )
682 679 coreconfigitem(
683 680 b'experimental',
684 681 b'single-head-per-branch:account-closed-heads',
685 682 default=False,
686 683 )
687 684 coreconfigitem(
688 685 b'experimental', b'sshserver.support-v2', default=False,
689 686 )
690 687 coreconfigitem(
691 688 b'experimental', b'sparse-read', default=False,
692 689 )
693 690 coreconfigitem(
694 691 b'experimental', b'sparse-read.density-threshold', default=0.50,
695 692 )
696 693 coreconfigitem(
697 694 b'experimental', b'sparse-read.min-gap-size', default=b'65K',
698 695 )
699 696 coreconfigitem(
700 697 b'experimental', b'treemanifest', default=False,
701 698 )
702 699 coreconfigitem(
703 700 b'experimental', b'update.atomic-file', default=False,
704 701 )
705 702 coreconfigitem(
706 703 b'experimental', b'sshpeer.advertise-v2', default=False,
707 704 )
708 705 coreconfigitem(
709 706 b'experimental', b'web.apiserver', default=False,
710 707 )
711 708 coreconfigitem(
712 709 b'experimental', b'web.api.http-v2', default=False,
713 710 )
714 711 coreconfigitem(
715 712 b'experimental', b'web.api.debugreflect', default=False,
716 713 )
717 714 coreconfigitem(
718 715 b'experimental', b'worker.wdir-get-thread-safe', default=False,
719 716 )
720 717 coreconfigitem(
721 718 b'experimental', b'worker.repository-upgrade', default=False,
722 719 )
723 720 coreconfigitem(
724 721 b'experimental', b'xdiff', default=False,
725 722 )
726 723 coreconfigitem(
727 724 b'extensions', b'.*', default=None, generic=True,
728 725 )
729 726 coreconfigitem(
730 727 b'extdata', b'.*', default=None, generic=True,
731 728 )
732 729 coreconfigitem(
733 730 b'format', b'bookmarks-in-store', default=False,
734 731 )
735 732 coreconfigitem(
736 733 b'format', b'chunkcachesize', default=None, experimental=True,
737 734 )
738 735 coreconfigitem(
739 736 b'format', b'dotencode', default=True,
740 737 )
741 738 coreconfigitem(
742 739 b'format', b'generaldelta', default=False, experimental=True,
743 740 )
744 741 coreconfigitem(
745 742 b'format', b'manifestcachesize', default=None, experimental=True,
746 743 )
747 744 coreconfigitem(
748 745 b'format', b'maxchainlen', default=dynamicdefault, experimental=True,
749 746 )
750 747 coreconfigitem(
751 748 b'format', b'obsstore-version', default=None,
752 749 )
753 750 coreconfigitem(
754 751 b'format', b'sparse-revlog', default=True,
755 752 )
756 753 coreconfigitem(
757 754 b'format',
758 755 b'revlog-compression',
759 756 default=lambda: [b'zlib'],
760 757 alias=[(b'experimental', b'format.compression')],
761 758 )
762 759 coreconfigitem(
763 760 b'format', b'usefncache', default=True,
764 761 )
765 762 coreconfigitem(
766 763 b'format', b'usegeneraldelta', default=True,
767 764 )
768 765 coreconfigitem(
769 766 b'format', b'usestore', default=True,
770 767 )
771 768 # Right now, the only efficient implement of the nodemap logic is in Rust, so
772 769 # the persistent nodemap feature needs to stay experimental as long as the Rust
773 770 # extensions are an experimental feature.
774 771 coreconfigitem(
775 772 b'format', b'use-persistent-nodemap', default=False, experimental=True
776 773 )
777 774 coreconfigitem(
778 775 b'format',
779 776 b'exp-use-copies-side-data-changeset',
780 777 default=False,
781 778 experimental=True,
782 779 )
783 780 coreconfigitem(
784 781 b'format', b'exp-use-side-data', default=False, experimental=True,
785 782 )
786 783 coreconfigitem(
787 784 b'format', b'exp-share-safe', default=False, experimental=True,
788 785 )
789 786 coreconfigitem(
790 787 b'format', b'internal-phase', default=False, experimental=True,
791 788 )
792 789 coreconfigitem(
793 790 b'fsmonitor', b'warn_when_unused', default=True,
794 791 )
795 792 coreconfigitem(
796 793 b'fsmonitor', b'warn_update_file_count', default=50000,
797 794 )
798 795 coreconfigitem(
799 796 b'fsmonitor', b'warn_update_file_count_rust', default=400000,
800 797 )
801 798 coreconfigitem(
802 799 b'help', br'hidden-command\..*', default=False, generic=True,
803 800 )
804 801 coreconfigitem(
805 802 b'help', br'hidden-topic\..*', default=False, generic=True,
806 803 )
807 804 coreconfigitem(
808 805 b'hooks', b'.*', default=dynamicdefault, generic=True,
809 806 )
810 807 coreconfigitem(
811 808 b'hgweb-paths', b'.*', default=list, generic=True,
812 809 )
813 810 coreconfigitem(
814 811 b'hostfingerprints', b'.*', default=list, generic=True,
815 812 )
816 813 coreconfigitem(
817 814 b'hostsecurity', b'ciphers', default=None,
818 815 )
819 816 coreconfigitem(
820 817 b'hostsecurity', b'minimumprotocol', default=dynamicdefault,
821 818 )
822 819 coreconfigitem(
823 820 b'hostsecurity',
824 821 b'.*:minimumprotocol$',
825 822 default=dynamicdefault,
826 823 generic=True,
827 824 )
828 825 coreconfigitem(
829 826 b'hostsecurity', b'.*:ciphers$', default=dynamicdefault, generic=True,
830 827 )
831 828 coreconfigitem(
832 829 b'hostsecurity', b'.*:fingerprints$', default=list, generic=True,
833 830 )
834 831 coreconfigitem(
835 832 b'hostsecurity', b'.*:verifycertsfile$', default=None, generic=True,
836 833 )
837 834
838 835 coreconfigitem(
839 836 b'http_proxy', b'always', default=False,
840 837 )
841 838 coreconfigitem(
842 839 b'http_proxy', b'host', default=None,
843 840 )
844 841 coreconfigitem(
845 842 b'http_proxy', b'no', default=list,
846 843 )
847 844 coreconfigitem(
848 845 b'http_proxy', b'passwd', default=None,
849 846 )
850 847 coreconfigitem(
851 848 b'http_proxy', b'user', default=None,
852 849 )
853 850
854 851 coreconfigitem(
855 852 b'http', b'timeout', default=None,
856 853 )
857 854
858 855 coreconfigitem(
859 856 b'logtoprocess', b'commandexception', default=None,
860 857 )
861 858 coreconfigitem(
862 859 b'logtoprocess', b'commandfinish', default=None,
863 860 )
864 861 coreconfigitem(
865 862 b'logtoprocess', b'command', default=None,
866 863 )
867 864 coreconfigitem(
868 865 b'logtoprocess', b'develwarn', default=None,
869 866 )
870 867 coreconfigitem(
871 868 b'logtoprocess', b'uiblocked', default=None,
872 869 )
873 870 coreconfigitem(
874 871 b'merge', b'checkunknown', default=b'abort',
875 872 )
876 873 coreconfigitem(
877 874 b'merge', b'checkignored', default=b'abort',
878 875 )
879 876 coreconfigitem(
880 877 b'experimental', b'merge.checkpathconflicts', default=False,
881 878 )
882 879 coreconfigitem(
883 880 b'merge', b'followcopies', default=True,
884 881 )
885 882 coreconfigitem(
886 883 b'merge', b'on-failure', default=b'continue',
887 884 )
888 885 coreconfigitem(
889 886 b'merge', b'preferancestor', default=lambda: [b'*'], experimental=True,
890 887 )
891 888 coreconfigitem(
892 889 b'merge', b'strict-capability-check', default=False,
893 890 )
894 891 coreconfigitem(
895 892 b'merge-tools', b'.*', default=None, generic=True,
896 893 )
897 894 coreconfigitem(
898 895 b'merge-tools',
899 896 br'.*\.args$',
900 897 default=b"$local $base $other",
901 898 generic=True,
902 899 priority=-1,
903 900 )
904 901 coreconfigitem(
905 902 b'merge-tools', br'.*\.binary$', default=False, generic=True, priority=-1,
906 903 )
907 904 coreconfigitem(
908 905 b'merge-tools', br'.*\.check$', default=list, generic=True, priority=-1,
909 906 )
910 907 coreconfigitem(
911 908 b'merge-tools',
912 909 br'.*\.checkchanged$',
913 910 default=False,
914 911 generic=True,
915 912 priority=-1,
916 913 )
917 914 coreconfigitem(
918 915 b'merge-tools',
919 916 br'.*\.executable$',
920 917 default=dynamicdefault,
921 918 generic=True,
922 919 priority=-1,
923 920 )
924 921 coreconfigitem(
925 922 b'merge-tools', br'.*\.fixeol$', default=False, generic=True, priority=-1,
926 923 )
927 924 coreconfigitem(
928 925 b'merge-tools', br'.*\.gui$', default=False, generic=True, priority=-1,
929 926 )
930 927 coreconfigitem(
931 928 b'merge-tools',
932 929 br'.*\.mergemarkers$',
933 930 default=b'basic',
934 931 generic=True,
935 932 priority=-1,
936 933 )
937 934 coreconfigitem(
938 935 b'merge-tools',
939 936 br'.*\.mergemarkertemplate$',
940 937 default=dynamicdefault, # take from ui.mergemarkertemplate
941 938 generic=True,
942 939 priority=-1,
943 940 )
944 941 coreconfigitem(
945 942 b'merge-tools', br'.*\.priority$', default=0, generic=True, priority=-1,
946 943 )
947 944 coreconfigitem(
948 945 b'merge-tools',
949 946 br'.*\.premerge$',
950 947 default=dynamicdefault,
951 948 generic=True,
952 949 priority=-1,
953 950 )
954 951 coreconfigitem(
955 952 b'merge-tools', br'.*\.symlink$', default=False, generic=True, priority=-1,
956 953 )
957 954 coreconfigitem(
958 955 b'pager', b'attend-.*', default=dynamicdefault, generic=True,
959 956 )
960 957 coreconfigitem(
961 958 b'pager', b'ignore', default=list,
962 959 )
963 960 coreconfigitem(
964 961 b'pager', b'pager', default=dynamicdefault,
965 962 )
966 963 coreconfigitem(
967 964 b'patch', b'eol', default=b'strict',
968 965 )
969 966 coreconfigitem(
970 967 b'patch', b'fuzz', default=2,
971 968 )
972 969 coreconfigitem(
973 970 b'paths', b'default', default=None,
974 971 )
975 972 coreconfigitem(
976 973 b'paths', b'default-push', default=None,
977 974 )
978 975 coreconfigitem(
979 976 b'paths', b'.*', default=None, generic=True,
980 977 )
981 978 coreconfigitem(
982 979 b'phases', b'checksubrepos', default=b'follow',
983 980 )
984 981 coreconfigitem(
985 982 b'phases', b'new-commit', default=b'draft',
986 983 )
987 984 coreconfigitem(
988 985 b'phases', b'publish', default=True,
989 986 )
990 987 coreconfigitem(
991 988 b'profiling', b'enabled', default=False,
992 989 )
993 990 coreconfigitem(
994 991 b'profiling', b'format', default=b'text',
995 992 )
996 993 coreconfigitem(
997 994 b'profiling', b'freq', default=1000,
998 995 )
999 996 coreconfigitem(
1000 997 b'profiling', b'limit', default=30,
1001 998 )
1002 999 coreconfigitem(
1003 1000 b'profiling', b'nested', default=0,
1004 1001 )
1005 1002 coreconfigitem(
1006 1003 b'profiling', b'output', default=None,
1007 1004 )
1008 1005 coreconfigitem(
1009 1006 b'profiling', b'showmax', default=0.999,
1010 1007 )
1011 1008 coreconfigitem(
1012 1009 b'profiling', b'showmin', default=dynamicdefault,
1013 1010 )
1014 1011 coreconfigitem(
1015 1012 b'profiling', b'showtime', default=True,
1016 1013 )
1017 1014 coreconfigitem(
1018 1015 b'profiling', b'sort', default=b'inlinetime',
1019 1016 )
1020 1017 coreconfigitem(
1021 1018 b'profiling', b'statformat', default=b'hotpath',
1022 1019 )
1023 1020 coreconfigitem(
1024 1021 b'profiling', b'time-track', default=dynamicdefault,
1025 1022 )
1026 1023 coreconfigitem(
1027 1024 b'profiling', b'type', default=b'stat',
1028 1025 )
1029 1026 coreconfigitem(
1030 1027 b'progress', b'assume-tty', default=False,
1031 1028 )
1032 1029 coreconfigitem(
1033 1030 b'progress', b'changedelay', default=1,
1034 1031 )
1035 1032 coreconfigitem(
1036 1033 b'progress', b'clear-complete', default=True,
1037 1034 )
1038 1035 coreconfigitem(
1039 1036 b'progress', b'debug', default=False,
1040 1037 )
1041 1038 coreconfigitem(
1042 1039 b'progress', b'delay', default=3,
1043 1040 )
1044 1041 coreconfigitem(
1045 1042 b'progress', b'disable', default=False,
1046 1043 )
1047 1044 coreconfigitem(
1048 1045 b'progress', b'estimateinterval', default=60.0,
1049 1046 )
1050 1047 coreconfigitem(
1051 1048 b'progress',
1052 1049 b'format',
1053 1050 default=lambda: [b'topic', b'bar', b'number', b'estimate'],
1054 1051 )
1055 1052 coreconfigitem(
1056 1053 b'progress', b'refresh', default=0.1,
1057 1054 )
1058 1055 coreconfigitem(
1059 1056 b'progress', b'width', default=dynamicdefault,
1060 1057 )
1061 1058 coreconfigitem(
1062 1059 b'pull', b'confirm', default=False,
1063 1060 )
1064 1061 coreconfigitem(
1065 1062 b'push', b'pushvars.server', default=False,
1066 1063 )
1067 1064 coreconfigitem(
1068 1065 b'rewrite',
1069 1066 b'backup-bundle',
1070 1067 default=True,
1071 1068 alias=[(b'ui', b'history-editing-backup')],
1072 1069 )
1073 1070 coreconfigitem(
1074 1071 b'rewrite', b'update-timestamp', default=False,
1075 1072 )
1076 1073 coreconfigitem(
1077 1074 b'rewrite', b'empty-successor', default=b'skip', experimental=True,
1078 1075 )
1079 1076 coreconfigitem(
1080 1077 b'storage', b'new-repo-backend', default=b'revlogv1', experimental=True,
1081 1078 )
1082 1079 coreconfigitem(
1083 1080 b'storage',
1084 1081 b'revlog.optimize-delta-parent-choice',
1085 1082 default=True,
1086 1083 alias=[(b'format', b'aggressivemergedeltas')],
1087 1084 )
1088 1085 # experimental as long as rust is experimental (or a C version is implemented)
1089 1086 coreconfigitem(
1090 1087 b'storage', b'revlog.nodemap.mmap', default=True, experimental=True
1091 1088 )
1092 1089 # experimental as long as format.use-persistent-nodemap is.
1093 1090 coreconfigitem(
1094 1091 b'storage', b'revlog.nodemap.mode', default=b'compat', experimental=True
1095 1092 )
1096 1093 coreconfigitem(
1097 1094 b'storage', b'revlog.reuse-external-delta', default=True,
1098 1095 )
1099 1096 coreconfigitem(
1100 1097 b'storage', b'revlog.reuse-external-delta-parent', default=None,
1101 1098 )
1102 1099 coreconfigitem(
1103 1100 b'storage', b'revlog.zlib.level', default=None,
1104 1101 )
1105 1102 coreconfigitem(
1106 1103 b'storage', b'revlog.zstd.level', default=None,
1107 1104 )
1108 1105 coreconfigitem(
1109 1106 b'server', b'bookmarks-pushkey-compat', default=True,
1110 1107 )
1111 1108 coreconfigitem(
1112 1109 b'server', b'bundle1', default=True,
1113 1110 )
1114 1111 coreconfigitem(
1115 1112 b'server', b'bundle1gd', default=None,
1116 1113 )
1117 1114 coreconfigitem(
1118 1115 b'server', b'bundle1.pull', default=None,
1119 1116 )
1120 1117 coreconfigitem(
1121 1118 b'server', b'bundle1gd.pull', default=None,
1122 1119 )
1123 1120 coreconfigitem(
1124 1121 b'server', b'bundle1.push', default=None,
1125 1122 )
1126 1123 coreconfigitem(
1127 1124 b'server', b'bundle1gd.push', default=None,
1128 1125 )
1129 1126 coreconfigitem(
1130 1127 b'server',
1131 1128 b'bundle2.stream',
1132 1129 default=True,
1133 1130 alias=[(b'experimental', b'bundle2.stream')],
1134 1131 )
1135 1132 coreconfigitem(
1136 1133 b'server', b'compressionengines', default=list,
1137 1134 )
1138 1135 coreconfigitem(
1139 1136 b'server', b'concurrent-push-mode', default=b'check-related',
1140 1137 )
1141 1138 coreconfigitem(
1142 1139 b'server', b'disablefullbundle', default=False,
1143 1140 )
1144 1141 coreconfigitem(
1145 1142 b'server', b'maxhttpheaderlen', default=1024,
1146 1143 )
1147 1144 coreconfigitem(
1148 1145 b'server', b'pullbundle', default=False,
1149 1146 )
1150 1147 coreconfigitem(
1151 1148 b'server', b'preferuncompressed', default=False,
1152 1149 )
1153 1150 coreconfigitem(
1154 1151 b'server', b'streamunbundle', default=False,
1155 1152 )
1156 1153 coreconfigitem(
1157 1154 b'server', b'uncompressed', default=True,
1158 1155 )
1159 1156 coreconfigitem(
1160 1157 b'server', b'uncompressedallowsecret', default=False,
1161 1158 )
1162 1159 coreconfigitem(
1163 1160 b'server', b'view', default=b'served',
1164 1161 )
1165 1162 coreconfigitem(
1166 1163 b'server', b'validate', default=False,
1167 1164 )
1168 1165 coreconfigitem(
1169 1166 b'server', b'zliblevel', default=-1,
1170 1167 )
1171 1168 coreconfigitem(
1172 1169 b'server', b'zstdlevel', default=3,
1173 1170 )
1174 1171 coreconfigitem(
1175 1172 b'share', b'pool', default=None,
1176 1173 )
1177 1174 coreconfigitem(
1178 1175 b'share', b'poolnaming', default=b'identity',
1179 1176 )
1180 1177 coreconfigitem(
1181 1178 b'shelve', b'maxbackups', default=10,
1182 1179 )
1183 1180 coreconfigitem(
1184 1181 b'smtp', b'host', default=None,
1185 1182 )
1186 1183 coreconfigitem(
1187 1184 b'smtp', b'local_hostname', default=None,
1188 1185 )
1189 1186 coreconfigitem(
1190 1187 b'smtp', b'password', default=None,
1191 1188 )
1192 1189 coreconfigitem(
1193 1190 b'smtp', b'port', default=dynamicdefault,
1194 1191 )
1195 1192 coreconfigitem(
1196 1193 b'smtp', b'tls', default=b'none',
1197 1194 )
1198 1195 coreconfigitem(
1199 1196 b'smtp', b'username', default=None,
1200 1197 )
1201 1198 coreconfigitem(
1202 1199 b'sparse', b'missingwarning', default=True, experimental=True,
1203 1200 )
1204 1201 coreconfigitem(
1205 1202 b'subrepos',
1206 1203 b'allowed',
1207 1204 default=dynamicdefault, # to make backporting simpler
1208 1205 )
1209 1206 coreconfigitem(
1210 1207 b'subrepos', b'hg:allowed', default=dynamicdefault,
1211 1208 )
1212 1209 coreconfigitem(
1213 1210 b'subrepos', b'git:allowed', default=dynamicdefault,
1214 1211 )
1215 1212 coreconfigitem(
1216 1213 b'subrepos', b'svn:allowed', default=dynamicdefault,
1217 1214 )
1218 1215 coreconfigitem(
1219 1216 b'templates', b'.*', default=None, generic=True,
1220 1217 )
1221 1218 coreconfigitem(
1222 1219 b'templateconfig', b'.*', default=dynamicdefault, generic=True,
1223 1220 )
1224 1221 coreconfigitem(
1225 1222 b'trusted', b'groups', default=list,
1226 1223 )
1227 1224 coreconfigitem(
1228 1225 b'trusted', b'users', default=list,
1229 1226 )
1230 1227 coreconfigitem(
1231 1228 b'ui', b'_usedassubrepo', default=False,
1232 1229 )
1233 1230 coreconfigitem(
1234 1231 b'ui', b'allowemptycommit', default=False,
1235 1232 )
1236 1233 coreconfigitem(
1237 1234 b'ui', b'archivemeta', default=True,
1238 1235 )
1239 1236 coreconfigitem(
1240 1237 b'ui', b'askusername', default=False,
1241 1238 )
1242 1239 coreconfigitem(
1243 1240 b'ui', b'available-memory', default=None,
1244 1241 )
1245 1242
1246 1243 coreconfigitem(
1247 1244 b'ui', b'clonebundlefallback', default=False,
1248 1245 )
1249 1246 coreconfigitem(
1250 1247 b'ui', b'clonebundleprefers', default=list,
1251 1248 )
1252 1249 coreconfigitem(
1253 1250 b'ui', b'clonebundles', default=True,
1254 1251 )
1255 1252 coreconfigitem(
1256 1253 b'ui', b'color', default=b'auto',
1257 1254 )
1258 1255 coreconfigitem(
1259 1256 b'ui', b'commitsubrepos', default=False,
1260 1257 )
1261 1258 coreconfigitem(
1262 1259 b'ui', b'debug', default=False,
1263 1260 )
1264 1261 coreconfigitem(
1265 1262 b'ui', b'debugger', default=None,
1266 1263 )
1267 1264 coreconfigitem(
1268 1265 b'ui', b'editor', default=dynamicdefault,
1269 1266 )
1270 1267 coreconfigitem(
1271 1268 b'ui', b'fallbackencoding', default=None,
1272 1269 )
1273 1270 coreconfigitem(
1274 1271 b'ui', b'forcecwd', default=None,
1275 1272 )
1276 1273 coreconfigitem(
1277 1274 b'ui', b'forcemerge', default=None,
1278 1275 )
1279 1276 coreconfigitem(
1280 1277 b'ui', b'formatdebug', default=False,
1281 1278 )
1282 1279 coreconfigitem(
1283 1280 b'ui', b'formatjson', default=False,
1284 1281 )
1285 1282 coreconfigitem(
1286 1283 b'ui', b'formatted', default=None,
1287 1284 )
1288 1285 coreconfigitem(
1289 1286 b'ui', b'graphnodetemplate', default=None,
1290 1287 )
1291 1288 coreconfigitem(
1292 1289 b'ui', b'interactive', default=None,
1293 1290 )
1294 1291 coreconfigitem(
1295 1292 b'ui', b'interface', default=None,
1296 1293 )
1297 1294 coreconfigitem(
1298 1295 b'ui', b'interface.chunkselector', default=None,
1299 1296 )
1300 1297 coreconfigitem(
1301 1298 b'ui', b'large-file-limit', default=10000000,
1302 1299 )
1303 1300 coreconfigitem(
1304 1301 b'ui', b'logblockedtimes', default=False,
1305 1302 )
1306 1303 coreconfigitem(
1307 1304 b'ui', b'logtemplate', default=None,
1308 1305 )
1309 1306 coreconfigitem(
1310 1307 b'ui', b'merge', default=None,
1311 1308 )
1312 1309 coreconfigitem(
1313 1310 b'ui', b'mergemarkers', default=b'basic',
1314 1311 )
1315 1312 coreconfigitem(
1316 1313 b'ui',
1317 1314 b'mergemarkertemplate',
1318 1315 default=(
1319 1316 b'{node|short} '
1320 1317 b'{ifeq(tags, "tip", "", '
1321 1318 b'ifeq(tags, "", "", "{tags} "))}'
1322 1319 b'{if(bookmarks, "{bookmarks} ")}'
1323 1320 b'{ifeq(branch, "default", "", "{branch} ")}'
1324 1321 b'- {author|user}: {desc|firstline}'
1325 1322 ),
1326 1323 )
1327 1324 coreconfigitem(
1328 1325 b'ui', b'message-output', default=b'stdio',
1329 1326 )
1330 1327 coreconfigitem(
1331 1328 b'ui', b'nontty', default=False,
1332 1329 )
1333 1330 coreconfigitem(
1334 1331 b'ui', b'origbackuppath', default=None,
1335 1332 )
1336 1333 coreconfigitem(
1337 1334 b'ui', b'paginate', default=True,
1338 1335 )
1339 1336 coreconfigitem(
1340 1337 b'ui', b'patch', default=None,
1341 1338 )
1342 1339 coreconfigitem(
1343 1340 b'ui', b'pre-merge-tool-output-template', default=None,
1344 1341 )
1345 1342 coreconfigitem(
1346 1343 b'ui', b'portablefilenames', default=b'warn',
1347 1344 )
1348 1345 coreconfigitem(
1349 1346 b'ui', b'promptecho', default=False,
1350 1347 )
1351 1348 coreconfigitem(
1352 1349 b'ui', b'quiet', default=False,
1353 1350 )
1354 1351 coreconfigitem(
1355 1352 b'ui', b'quietbookmarkmove', default=False,
1356 1353 )
1357 1354 coreconfigitem(
1358 1355 b'ui', b'relative-paths', default=b'legacy',
1359 1356 )
1360 1357 coreconfigitem(
1361 1358 b'ui', b'remotecmd', default=b'hg',
1362 1359 )
1363 1360 coreconfigitem(
1364 1361 b'ui', b'report_untrusted', default=True,
1365 1362 )
1366 1363 coreconfigitem(
1367 1364 b'ui', b'rollback', default=True,
1368 1365 )
1369 1366 coreconfigitem(
1370 1367 b'ui', b'signal-safe-lock', default=True,
1371 1368 )
1372 1369 coreconfigitem(
1373 1370 b'ui', b'slash', default=False,
1374 1371 )
1375 1372 coreconfigitem(
1376 1373 b'ui', b'ssh', default=b'ssh',
1377 1374 )
1378 1375 coreconfigitem(
1379 1376 b'ui', b'ssherrorhint', default=None,
1380 1377 )
1381 1378 coreconfigitem(
1382 1379 b'ui', b'statuscopies', default=False,
1383 1380 )
1384 1381 coreconfigitem(
1385 1382 b'ui', b'strict', default=False,
1386 1383 )
1387 1384 coreconfigitem(
1388 1385 b'ui', b'style', default=b'',
1389 1386 )
1390 1387 coreconfigitem(
1391 1388 b'ui', b'supportcontact', default=None,
1392 1389 )
1393 1390 coreconfigitem(
1394 1391 b'ui', b'textwidth', default=78,
1395 1392 )
1396 1393 coreconfigitem(
1397 1394 b'ui', b'timeout', default=b'600',
1398 1395 )
1399 1396 coreconfigitem(
1400 1397 b'ui', b'timeout.warn', default=0,
1401 1398 )
1402 1399 coreconfigitem(
1403 1400 b'ui', b'timestamp-output', default=False,
1404 1401 )
1405 1402 coreconfigitem(
1406 1403 b'ui', b'traceback', default=False,
1407 1404 )
1408 1405 coreconfigitem(
1409 1406 b'ui', b'tweakdefaults', default=False,
1410 1407 )
1411 1408 coreconfigitem(b'ui', b'username', alias=[(b'ui', b'user')])
1412 1409 coreconfigitem(
1413 1410 b'ui', b'verbose', default=False,
1414 1411 )
1415 1412 coreconfigitem(
1416 1413 b'verify', b'skipflags', default=None,
1417 1414 )
1418 1415 coreconfigitem(
1419 1416 b'web', b'allowbz2', default=False,
1420 1417 )
1421 1418 coreconfigitem(
1422 1419 b'web', b'allowgz', default=False,
1423 1420 )
1424 1421 coreconfigitem(
1425 1422 b'web', b'allow-pull', alias=[(b'web', b'allowpull')], default=True,
1426 1423 )
1427 1424 coreconfigitem(
1428 1425 b'web', b'allow-push', alias=[(b'web', b'allow_push')], default=list,
1429 1426 )
1430 1427 coreconfigitem(
1431 1428 b'web', b'allowzip', default=False,
1432 1429 )
1433 1430 coreconfigitem(
1434 1431 b'web', b'archivesubrepos', default=False,
1435 1432 )
1436 1433 coreconfigitem(
1437 1434 b'web', b'cache', default=True,
1438 1435 )
1439 1436 coreconfigitem(
1440 1437 b'web', b'comparisoncontext', default=5,
1441 1438 )
1442 1439 coreconfigitem(
1443 1440 b'web', b'contact', default=None,
1444 1441 )
1445 1442 coreconfigitem(
1446 1443 b'web', b'deny_push', default=list,
1447 1444 )
1448 1445 coreconfigitem(
1449 1446 b'web', b'guessmime', default=False,
1450 1447 )
1451 1448 coreconfigitem(
1452 1449 b'web', b'hidden', default=False,
1453 1450 )
1454 1451 coreconfigitem(
1455 1452 b'web', b'labels', default=list,
1456 1453 )
1457 1454 coreconfigitem(
1458 1455 b'web', b'logoimg', default=b'hglogo.png',
1459 1456 )
1460 1457 coreconfigitem(
1461 1458 b'web', b'logourl', default=b'https://mercurial-scm.org/',
1462 1459 )
1463 1460 coreconfigitem(
1464 1461 b'web', b'accesslog', default=b'-',
1465 1462 )
1466 1463 coreconfigitem(
1467 1464 b'web', b'address', default=b'',
1468 1465 )
1469 1466 coreconfigitem(
1470 1467 b'web', b'allow-archive', alias=[(b'web', b'allow_archive')], default=list,
1471 1468 )
1472 1469 coreconfigitem(
1473 1470 b'web', b'allow_read', default=list,
1474 1471 )
1475 1472 coreconfigitem(
1476 1473 b'web', b'baseurl', default=None,
1477 1474 )
1478 1475 coreconfigitem(
1479 1476 b'web', b'cacerts', default=None,
1480 1477 )
1481 1478 coreconfigitem(
1482 1479 b'web', b'certificate', default=None,
1483 1480 )
1484 1481 coreconfigitem(
1485 1482 b'web', b'collapse', default=False,
1486 1483 )
1487 1484 coreconfigitem(
1488 1485 b'web', b'csp', default=None,
1489 1486 )
1490 1487 coreconfigitem(
1491 1488 b'web', b'deny_read', default=list,
1492 1489 )
1493 1490 coreconfigitem(
1494 1491 b'web', b'descend', default=True,
1495 1492 )
1496 1493 coreconfigitem(
1497 1494 b'web', b'description', default=b"",
1498 1495 )
1499 1496 coreconfigitem(
1500 1497 b'web', b'encoding', default=lambda: encoding.encoding,
1501 1498 )
1502 1499 coreconfigitem(
1503 1500 b'web', b'errorlog', default=b'-',
1504 1501 )
1505 1502 coreconfigitem(
1506 1503 b'web', b'ipv6', default=False,
1507 1504 )
1508 1505 coreconfigitem(
1509 1506 b'web', b'maxchanges', default=10,
1510 1507 )
1511 1508 coreconfigitem(
1512 1509 b'web', b'maxfiles', default=10,
1513 1510 )
1514 1511 coreconfigitem(
1515 1512 b'web', b'maxshortchanges', default=60,
1516 1513 )
1517 1514 coreconfigitem(
1518 1515 b'web', b'motd', default=b'',
1519 1516 )
1520 1517 coreconfigitem(
1521 1518 b'web', b'name', default=dynamicdefault,
1522 1519 )
1523 1520 coreconfigitem(
1524 1521 b'web', b'port', default=8000,
1525 1522 )
1526 1523 coreconfigitem(
1527 1524 b'web', b'prefix', default=b'',
1528 1525 )
1529 1526 coreconfigitem(
1530 1527 b'web', b'push_ssl', default=True,
1531 1528 )
1532 1529 coreconfigitem(
1533 1530 b'web', b'refreshinterval', default=20,
1534 1531 )
1535 1532 coreconfigitem(
1536 1533 b'web', b'server-header', default=None,
1537 1534 )
1538 1535 coreconfigitem(
1539 1536 b'web', b'static', default=None,
1540 1537 )
1541 1538 coreconfigitem(
1542 1539 b'web', b'staticurl', default=None,
1543 1540 )
1544 1541 coreconfigitem(
1545 1542 b'web', b'stripes', default=1,
1546 1543 )
1547 1544 coreconfigitem(
1548 1545 b'web', b'style', default=b'paper',
1549 1546 )
1550 1547 coreconfigitem(
1551 1548 b'web', b'templates', default=None,
1552 1549 )
1553 1550 coreconfigitem(
1554 1551 b'web', b'view', default=b'served', experimental=True,
1555 1552 )
1556 1553 coreconfigitem(
1557 1554 b'worker', b'backgroundclose', default=dynamicdefault,
1558 1555 )
1559 1556 # Windows defaults to a limit of 512 open files. A buffer of 128
1560 1557 # should give us enough headway.
1561 1558 coreconfigitem(
1562 1559 b'worker', b'backgroundclosemaxqueue', default=384,
1563 1560 )
1564 1561 coreconfigitem(
1565 1562 b'worker', b'backgroundcloseminfilecount', default=2048,
1566 1563 )
1567 1564 coreconfigitem(
1568 1565 b'worker', b'backgroundclosethreadcount', default=4,
1569 1566 )
1570 1567 coreconfigitem(
1571 1568 b'worker', b'enabled', default=True,
1572 1569 )
1573 1570 coreconfigitem(
1574 1571 b'worker', b'numcpus', default=None,
1575 1572 )
1576 1573
1577 1574 # Rebase related configuration moved to core because other extension are doing
1578 1575 # strange things. For example, shelve import the extensions to reuse some bit
1579 1576 # without formally loading it.
1580 1577 coreconfigitem(
1581 1578 b'commands', b'rebase.requiredest', default=False,
1582 1579 )
1583 1580 coreconfigitem(
1584 1581 b'experimental', b'rebaseskipobsolete', default=True,
1585 1582 )
1586 1583 coreconfigitem(
1587 1584 b'rebase', b'singletransaction', default=False,
1588 1585 )
1589 1586 coreconfigitem(
1590 1587 b'rebase', b'experimental.inmemory', default=False,
1591 1588 )
@@ -1,80 +1,68 b''
1 1 The active mergestate is stored in ``.hg/merge`` when a merge is triggered
2 2 by commands like ``hg merge``, ``hg rebase``, etc. until the merge is
3 3 completed or aborted to track the 3-way merge state of individual files.
4 4
5 5 The contents of the directory are:
6 6
7 7 Conflicting files
8 8 -----------------
9 9
10 10 The local version of the conflicting files are stored with their
11 11 filenames as the hash of their paths.
12 12
13 13 state
14 14 -----
15 15
16 16 This mergestate file record is used by hg version prior to 2.9.1
17 17 and contains less data than ``state2``. If there is no contradiction
18 18 with ``state2``, we can assume that both are written at the same time.
19 19 In this case, data from ``state2`` is used. Otherwise, we use ``state``.
20 20 We read/write both ``state`` and ``state2`` records to ensure backward
21 21 compatibility.
22 22
23 23 state2
24 24 ------
25 25
26 26 This record stores a superset of data in ``state``, including new kinds
27 27 of records in the future.
28 28
29 29 Each record can contain arbitrary content and has an associated type. This
30 30 `type` should be a letter. If `type` is uppercase, the record is mandatory:
31 31 versions of Mercurial that don't support it should abort. If `type` is
32 32 lowercase, the record can be safely ignored.
33 33
34 34 Currently known records:
35 35
36 36 | * L: the node of the "local" part of the merge (hexified version)
37 37 | * O: the node of the "other" part of the merge (hexified version)
38 38 | * F: a file to be merged entry
39 39 | * C: a change/delete or delete/change conflict
40 | * D: a file that the external merge driver will merge internally
41 | (experimental)
42 40 | * P: a path conflict (file vs directory)
43 | * m: the external merge driver defined for this merge plus its run state
44 | (experimental)
45 41 | * f: a (filename, dictionary) tuple of optional values for a given file
46 42 | * X: unsupported mandatory record type (used in tests)
47 43 | * x: unsupported advisory record type (used in tests)
48 44 | * l: the labels for the parts of the merge.
49 45
50 Merge driver run states (experimental):
51
52 | * u: driver-resolved files unmarked -- needs to be run next time we're
53 | about to resolve or commit
54 | * m: driver-resolved files marked -- only needs to be run before commit
55 | * s: success/skipped -- does not need to be run any more
56
57 46 Merge record states (indexed by filename):
58 47
59 48 | * u: unresolved conflict
60 49 | * r: resolved conflict
61 50 | * pu: unresolved path conflict (file conflicts with directory)
62 51 | * pr: resolved path conflict
63 | * d: driver-resolved conflict
64 52
65 53 The resolve command transitions between 'u' and 'r' for conflicts and
66 54 'pu' and 'pr' for path conflicts.
67 55
68 56 This format is a list of arbitrary records of the form:
69 57
70 58 [type][length][content]
71 59
72 60 `type` is a single character, `length` is a 4 byte integer, and
73 61 `content` is an arbitrary byte sequence of length `length`.
74 62
75 63 Mercurial versions prior to 3.7 have a bug where if there are
76 64 unsupported mandatory merge records, attempting to clear out the merge
77 65 state with hg update --clean or similar aborts. The 't' record type
78 66 works around that by writing out what those versions treat as an
79 67 advisory record, but later versions interpret as special: the first
80 68 character is the 'real' record type and everything onwards is the data.
@@ -1,2313 +1,2242 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 def driverpreprocess(repo, ms, wctx, labels=None):
359 """run the preprocess step of the merge driver, if any
360
361 This is currently not implemented -- it's an extension point."""
362 return True
363
364
365 def driverconclude(repo, ms, wctx, labels=None):
366 """run the conclude step of the merge driver, if any
367
368 This is currently not implemented -- it's an extension point."""
369 return True
370
371
372 358 def _filesindirs(repo, manifest, dirs):
373 359 """
374 360 Generator that yields pairs of all the files in the manifest that are found
375 361 inside the directories listed in dirs, and which directory they are found
376 362 in.
377 363 """
378 364 for f in manifest:
379 365 for p in pathutil.finddirs(f):
380 366 if p in dirs:
381 367 yield f, p
382 368 break
383 369
384 370
385 371 def checkpathconflicts(repo, wctx, mctx, mresult):
386 372 """
387 373 Check if any actions introduce path conflicts in the repository, updating
388 374 actions to record or handle the path conflict accordingly.
389 375 """
390 376 mf = wctx.manifest()
391 377
392 378 # The set of local files that conflict with a remote directory.
393 379 localconflicts = set()
394 380
395 381 # The set of directories that conflict with a remote file, and so may cause
396 382 # conflicts if they still contain any files after the merge.
397 383 remoteconflicts = set()
398 384
399 385 # The set of directories that appear as both a file and a directory in the
400 386 # remote manifest. These indicate an invalid remote manifest, which
401 387 # can't be updated to cleanly.
402 388 invalidconflicts = set()
403 389
404 390 # The set of directories that contain files that are being created.
405 391 createdfiledirs = set()
406 392
407 393 # The set of files deleted by all the actions.
408 394 deletedfiles = set()
409 395
410 396 for f in mresult.files(
411 397 (
412 398 mergestatemod.ACTION_CREATED,
413 399 mergestatemod.ACTION_DELETED_CHANGED,
414 400 mergestatemod.ACTION_MERGE,
415 401 mergestatemod.ACTION_CREATED_MERGE,
416 402 )
417 403 ):
418 404 # This action may create a new local file.
419 405 createdfiledirs.update(pathutil.finddirs(f))
420 406 if mf.hasdir(f):
421 407 # The file aliases a local directory. This might be ok if all
422 408 # the files in the local directory are being deleted. This
423 409 # will be checked once we know what all the deleted files are.
424 410 remoteconflicts.add(f)
425 411 # Track the names of all deleted files.
426 412 for f in mresult.files((mergestatemod.ACTION_REMOVE,)):
427 413 deletedfiles.add(f)
428 414 for (f, args, msg) in mresult.getactions((mergestatemod.ACTION_MERGE,)):
429 415 f1, f2, fa, move, anc = args
430 416 if move:
431 417 deletedfiles.add(f1)
432 418 for (f, args, msg) in mresult.getactions(
433 419 (mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,)
434 420 ):
435 421 f2, flags = args
436 422 deletedfiles.add(f2)
437 423
438 424 # Check all directories that contain created files for path conflicts.
439 425 for p in createdfiledirs:
440 426 if p in mf:
441 427 if p in mctx:
442 428 # A file is in a directory which aliases both a local
443 429 # and a remote file. This is an internal inconsistency
444 430 # within the remote manifest.
445 431 invalidconflicts.add(p)
446 432 else:
447 433 # A file is in a directory which aliases a local file.
448 434 # We will need to rename the local file.
449 435 localconflicts.add(p)
450 436 pd = mresult.getfile(p)
451 437 if pd and pd[0] in (
452 438 mergestatemod.ACTION_CREATED,
453 439 mergestatemod.ACTION_DELETED_CHANGED,
454 440 mergestatemod.ACTION_MERGE,
455 441 mergestatemod.ACTION_CREATED_MERGE,
456 442 ):
457 443 # The file is in a directory which aliases a remote file.
458 444 # This is an internal inconsistency within the remote
459 445 # manifest.
460 446 invalidconflicts.add(p)
461 447
462 448 # Rename all local conflicting files that have not been deleted.
463 449 for p in localconflicts:
464 450 if p not in deletedfiles:
465 451 ctxname = bytes(wctx).rstrip(b'+')
466 452 pnew = util.safename(p, ctxname, wctx, set(mresult.files()))
467 453 porig = wctx[p].copysource() or p
468 454 mresult.addfile(
469 455 pnew,
470 456 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
471 457 (p, porig),
472 458 b'local path conflict',
473 459 )
474 460 mresult.addfile(
475 461 p,
476 462 mergestatemod.ACTION_PATH_CONFLICT,
477 463 (pnew, b'l'),
478 464 b'path conflict',
479 465 )
480 466
481 467 if remoteconflicts:
482 468 # Check if all files in the conflicting directories have been removed.
483 469 ctxname = bytes(mctx).rstrip(b'+')
484 470 for f, p in _filesindirs(repo, mf, remoteconflicts):
485 471 if f not in deletedfiles:
486 472 m, args, msg = mresult.getfile(p)
487 473 pnew = util.safename(p, ctxname, wctx, set(mresult.files()))
488 474 if m in (
489 475 mergestatemod.ACTION_DELETED_CHANGED,
490 476 mergestatemod.ACTION_MERGE,
491 477 ):
492 478 # Action was merge, just update target.
493 479 mresult.addfile(pnew, m, args, msg)
494 480 else:
495 481 # Action was create, change to renamed get action.
496 482 fl = args[0]
497 483 mresult.addfile(
498 484 pnew,
499 485 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
500 486 (p, fl),
501 487 b'remote path conflict',
502 488 )
503 489 mresult.addfile(
504 490 p,
505 491 mergestatemod.ACTION_PATH_CONFLICT,
506 492 (pnew, mergestatemod.ACTION_REMOVE),
507 493 b'path conflict',
508 494 )
509 495 remoteconflicts.remove(p)
510 496 break
511 497
512 498 if invalidconflicts:
513 499 for p in invalidconflicts:
514 500 repo.ui.warn(_(b"%s: is both a file and a directory\n") % p)
515 501 raise error.Abort(_(b"destination manifest contains path conflicts"))
516 502
517 503
518 504 def _filternarrowactions(narrowmatch, branchmerge, mresult):
519 505 """
520 506 Filters out actions that can ignored because the repo is narrowed.
521 507
522 508 Raise an exception if the merge cannot be completed because the repo is
523 509 narrowed.
524 510 """
525 511 # TODO: handle with nonconflicttypes
526 512 nonconflicttypes = {
527 513 mergestatemod.ACTION_ADD,
528 514 mergestatemod.ACTION_ADD_MODIFIED,
529 515 mergestatemod.ACTION_CREATED,
530 516 mergestatemod.ACTION_CREATED_MERGE,
531 517 mergestatemod.ACTION_FORGET,
532 518 mergestatemod.ACTION_GET,
533 519 mergestatemod.ACTION_REMOVE,
534 520 mergestatemod.ACTION_EXEC,
535 521 }
536 522 # We mutate the items in the dict during iteration, so iterate
537 523 # over a copy.
538 524 for f, action in mresult.filemap():
539 525 if narrowmatch(f):
540 526 pass
541 527 elif not branchmerge:
542 528 mresult.removefile(f) # just updating, ignore changes outside clone
543 529 elif action[0] in mergeresult.NO_OP_ACTIONS:
544 530 mresult.removefile(f) # merge does not affect file
545 531 elif action[0] in nonconflicttypes:
546 532 raise error.Abort(
547 533 _(
548 534 b'merge affects file \'%s\' outside narrow, '
549 535 b'which is not yet supported'
550 536 )
551 537 % f,
552 538 hint=_(b'merging in the other direction may work'),
553 539 )
554 540 else:
555 541 raise error.Abort(
556 542 _(b'conflict in file \'%s\' is outside narrow clone') % f
557 543 )
558 544
559 545
560 546 class mergeresult(object):
561 547 ''''An object representing result of merging manifests.
562 548
563 549 It has information about what actions need to be performed on dirstate
564 550 mapping of divergent renames and other such cases. '''
565 551
566 552 NO_OP_ACTIONS = (
567 553 mergestatemod.ACTION_KEEP,
568 554 mergestatemod.ACTION_KEEP_ABSENT,
569 555 )
570 556
571 557 def __init__(self):
572 558 """
573 559 filemapping: dict of filename as keys and action related info as values
574 560 diverge: mapping of source name -> list of dest name for
575 561 divergent renames
576 562 renamedelete: mapping of source name -> list of destinations for files
577 563 deleted on one side and renamed on other.
578 564 commitinfo: dict containing data which should be used on commit
579 565 contains a filename -> info mapping
580 566 actionmapping: dict of action names as keys and values are dict of
581 567 filename as key and related data as values
582 568 """
583 569 self._filemapping = {}
584 570 self._diverge = {}
585 571 self._renamedelete = {}
586 572 self._commitinfo = collections.defaultdict(dict)
587 573 self._actionmapping = collections.defaultdict(dict)
588 574
589 575 def updatevalues(self, diverge, renamedelete):
590 576 self._diverge = diverge
591 577 self._renamedelete = renamedelete
592 578
593 579 def addfile(self, filename, action, data, message):
594 580 """ adds a new file to the mergeresult object
595 581
596 582 filename: file which we are adding
597 583 action: one of mergestatemod.ACTION_*
598 584 data: a tuple of information like fctx and ctx related to this merge
599 585 message: a message about the merge
600 586 """
601 587 # if the file already existed, we need to delete it's old
602 588 # entry form _actionmapping too
603 589 if filename in self._filemapping:
604 590 a, d, m = self._filemapping[filename]
605 591 del self._actionmapping[a][filename]
606 592
607 593 self._filemapping[filename] = (action, data, message)
608 594 self._actionmapping[action][filename] = (data, message)
609 595
610 596 def getfile(self, filename, default_return=None):
611 597 """ returns (action, args, msg) about this file
612 598
613 599 returns default_return if the file is not present """
614 600 if filename in self._filemapping:
615 601 return self._filemapping[filename]
616 602 return default_return
617 603
618 604 def files(self, actions=None):
619 605 """ returns files on which provided action needs to perfromed
620 606
621 607 If actions is None, all files are returned
622 608 """
623 609 # TODO: think whether we should return renamedelete and
624 610 # diverge filenames also
625 611 if actions is None:
626 612 for f in self._filemapping:
627 613 yield f
628 614
629 615 else:
630 616 for a in actions:
631 617 for f in self._actionmapping[a]:
632 618 yield f
633 619
634 620 def removefile(self, filename):
635 621 """ removes a file from the mergeresult object as the file might
636 622 not merging anymore """
637 623 action, data, message = self._filemapping[filename]
638 624 del self._filemapping[filename]
639 625 del self._actionmapping[action][filename]
640 626
641 627 def getactions(self, actions, sort=False):
642 628 """ get list of files which are marked with these actions
643 629 if sort is true, files for each action is sorted and then added
644 630
645 631 Returns a list of tuple of form (filename, data, message)
646 632 """
647 633 for a in actions:
648 634 if sort:
649 635 for f in sorted(self._actionmapping[a]):
650 636 args, msg = self._actionmapping[a][f]
651 637 yield f, args, msg
652 638 else:
653 639 for f, (args, msg) in pycompat.iteritems(
654 640 self._actionmapping[a]
655 641 ):
656 642 yield f, args, msg
657 643
658 644 def len(self, actions=None):
659 645 """ returns number of files which needs actions
660 646
661 647 if actions is passed, total of number of files in that action
662 648 only is returned """
663 649
664 650 if actions is None:
665 651 return len(self._filemapping)
666 652
667 653 return sum(len(self._actionmapping[a]) for a in actions)
668 654
669 655 def filemap(self, sort=False):
670 656 if sorted:
671 657 for key, val in sorted(pycompat.iteritems(self._filemapping)):
672 658 yield key, val
673 659 else:
674 660 for key, val in pycompat.iteritems(self._filemapping):
675 661 yield key, val
676 662
677 663 def addcommitinfo(self, filename, key, value):
678 664 """ adds key-value information about filename which will be required
679 665 while committing this merge """
680 666 self._commitinfo[filename][key] = value
681 667
682 668 @property
683 669 def diverge(self):
684 670 return self._diverge
685 671
686 672 @property
687 673 def renamedelete(self):
688 674 return self._renamedelete
689 675
690 676 @property
691 677 def commitinfo(self):
692 678 return self._commitinfo
693 679
694 680 @property
695 681 def actionsdict(self):
696 682 """ returns a dictionary of actions to be perfomed with action as key
697 683 and a list of files and related arguments as values """
698 684 res = collections.defaultdict(list)
699 685 for a, d in pycompat.iteritems(self._actionmapping):
700 686 for f, (args, msg) in pycompat.iteritems(d):
701 687 res[a].append((f, args, msg))
702 688 return res
703 689
704 690 def setactions(self, actions):
705 691 self._filemapping = actions
706 692 self._actionmapping = collections.defaultdict(dict)
707 693 for f, (act, data, msg) in pycompat.iteritems(self._filemapping):
708 694 self._actionmapping[act][f] = data, msg
709 695
710 696 def hasconflicts(self):
711 697 """ tells whether this merge resulted in some actions which can
712 698 result in conflicts or not """
713 699 for a in self._actionmapping.keys():
714 700 if (
715 701 a
716 702 not in (
717 703 mergestatemod.ACTION_GET,
718 704 mergestatemod.ACTION_EXEC,
719 705 mergestatemod.ACTION_REMOVE,
720 706 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
721 707 )
722 708 and self._actionmapping[a]
723 709 and a not in self.NO_OP_ACTIONS
724 710 ):
725 711 return True
726 712
727 713 return False
728 714
729 715
730 716 def manifestmerge(
731 717 repo,
732 718 wctx,
733 719 p2,
734 720 pa,
735 721 branchmerge,
736 722 force,
737 723 matcher,
738 724 acceptremote,
739 725 followcopies,
740 726 forcefulldiff=False,
741 727 ):
742 728 """
743 729 Merge wctx and p2 with ancestor pa and generate merge action list
744 730
745 731 branchmerge and force are as passed in to update
746 732 matcher = matcher to filter file lists
747 733 acceptremote = accept the incoming changes without prompting
748 734
749 735 Returns an object of mergeresult class
750 736 """
751 737 mresult = mergeresult()
752 738 if matcher is not None and matcher.always():
753 739 matcher = None
754 740
755 741 # manifests fetched in order are going to be faster, so prime the caches
756 742 [
757 743 x.manifest()
758 744 for x in sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)
759 745 ]
760 746
761 747 branch_copies1 = copies.branch_copies()
762 748 branch_copies2 = copies.branch_copies()
763 749 diverge = {}
764 750 # information from merge which is needed at commit time
765 751 # for example choosing filelog of which parent to commit
766 752 # TODO: use specific constants in future for this mapping
767 753 if followcopies:
768 754 branch_copies1, branch_copies2, diverge = copies.mergecopies(
769 755 repo, wctx, p2, pa
770 756 )
771 757
772 758 boolbm = pycompat.bytestr(bool(branchmerge))
773 759 boolf = pycompat.bytestr(bool(force))
774 760 boolm = pycompat.bytestr(bool(matcher))
775 761 repo.ui.note(_(b"resolving manifests\n"))
776 762 repo.ui.debug(
777 763 b" branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm)
778 764 )
779 765 repo.ui.debug(b" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
780 766
781 767 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
782 768 copied1 = set(branch_copies1.copy.values())
783 769 copied1.update(branch_copies1.movewithdir.values())
784 770 copied2 = set(branch_copies2.copy.values())
785 771 copied2.update(branch_copies2.movewithdir.values())
786 772
787 773 if b'.hgsubstate' in m1 and wctx.rev() is None:
788 774 # Check whether sub state is modified, and overwrite the manifest
789 775 # to flag the change. If wctx is a committed revision, we shouldn't
790 776 # care for the dirty state of the working directory.
791 777 if any(wctx.sub(s).dirty() for s in wctx.substate):
792 778 m1[b'.hgsubstate'] = modifiednodeid
793 779
794 780 # Don't use m2-vs-ma optimization if:
795 781 # - ma is the same as m1 or m2, which we're just going to diff again later
796 782 # - The caller specifically asks for a full diff, which is useful during bid
797 783 # merge.
798 784 if pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff:
799 785 # Identify which files are relevant to the merge, so we can limit the
800 786 # total m1-vs-m2 diff to just those files. This has significant
801 787 # performance benefits in large repositories.
802 788 relevantfiles = set(ma.diff(m2).keys())
803 789
804 790 # For copied and moved files, we need to add the source file too.
805 791 for copykey, copyvalue in pycompat.iteritems(branch_copies1.copy):
806 792 if copyvalue in relevantfiles:
807 793 relevantfiles.add(copykey)
808 794 for movedirkey in branch_copies1.movewithdir:
809 795 relevantfiles.add(movedirkey)
810 796 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
811 797 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
812 798
813 799 diff = m1.diff(m2, match=matcher)
814 800
815 801 for f, ((n1, fl1), (n2, fl2)) in pycompat.iteritems(diff):
816 802 if n1 and n2: # file exists on both local and remote side
817 803 if f not in ma:
818 804 # TODO: what if they're renamed from different sources?
819 805 fa = branch_copies1.copy.get(
820 806 f, None
821 807 ) or branch_copies2.copy.get(f, None)
822 808 args, msg = None, None
823 809 if fa is not None:
824 810 args = (f, f, fa, False, pa.node())
825 811 msg = b'both renamed from %s' % fa
826 812 else:
827 813 args = (f, f, None, False, pa.node())
828 814 msg = b'both created'
829 815 mresult.addfile(f, mergestatemod.ACTION_MERGE, args, msg)
830 816 else:
831 817 a = ma[f]
832 818 fla = ma.flags(f)
833 819 nol = b'l' not in fl1 + fl2 + fla
834 820 if n2 == a and fl2 == fla:
835 821 mresult.addfile(
836 822 f, mergestatemod.ACTION_KEEP, (), b'remote unchanged',
837 823 )
838 824 elif n1 == a and fl1 == fla: # local unchanged - use remote
839 825 if n1 == n2: # optimization: keep local content
840 826 mresult.addfile(
841 827 f,
842 828 mergestatemod.ACTION_EXEC,
843 829 (fl2,),
844 830 b'update permissions',
845 831 )
846 832 else:
847 833 mresult.addfile(
848 834 f,
849 835 mergestatemod.ACTION_GET,
850 836 (fl2, False),
851 837 b'remote is newer',
852 838 )
853 839 if branchmerge:
854 840 mresult.addcommitinfo(
855 841 f, b'filenode-source', b'other'
856 842 )
857 843 elif nol and n2 == a: # remote only changed 'x'
858 844 mresult.addfile(
859 845 f,
860 846 mergestatemod.ACTION_EXEC,
861 847 (fl2,),
862 848 b'update permissions',
863 849 )
864 850 elif nol and n1 == a: # local only changed 'x'
865 851 mresult.addfile(
866 852 f,
867 853 mergestatemod.ACTION_GET,
868 854 (fl1, False),
869 855 b'remote is newer',
870 856 )
871 857 if branchmerge:
872 858 mresult.addcommitinfo(f, b'filenode-source', b'other')
873 859 else: # both changed something
874 860 mresult.addfile(
875 861 f,
876 862 mergestatemod.ACTION_MERGE,
877 863 (f, f, f, False, pa.node()),
878 864 b'versions differ',
879 865 )
880 866 elif n1: # file exists only on local side
881 867 if f in copied2:
882 868 pass # we'll deal with it on m2 side
883 869 elif (
884 870 f in branch_copies1.movewithdir
885 871 ): # directory rename, move local
886 872 f2 = branch_copies1.movewithdir[f]
887 873 if f2 in m2:
888 874 mresult.addfile(
889 875 f2,
890 876 mergestatemod.ACTION_MERGE,
891 877 (f, f2, None, True, pa.node()),
892 878 b'remote directory rename, both created',
893 879 )
894 880 else:
895 881 mresult.addfile(
896 882 f2,
897 883 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
898 884 (f, fl1),
899 885 b'remote directory rename - move from %s' % f,
900 886 )
901 887 elif f in branch_copies1.copy:
902 888 f2 = branch_copies1.copy[f]
903 889 mresult.addfile(
904 890 f,
905 891 mergestatemod.ACTION_MERGE,
906 892 (f, f2, f2, False, pa.node()),
907 893 b'local copied/moved from %s' % f2,
908 894 )
909 895 elif f in ma: # clean, a different, no remote
910 896 if n1 != ma[f]:
911 897 if acceptremote:
912 898 mresult.addfile(
913 899 f,
914 900 mergestatemod.ACTION_REMOVE,
915 901 None,
916 902 b'remote delete',
917 903 )
918 904 else:
919 905 mresult.addfile(
920 906 f,
921 907 mergestatemod.ACTION_CHANGED_DELETED,
922 908 (f, None, f, False, pa.node()),
923 909 b'prompt changed/deleted',
924 910 )
925 911 elif n1 == addednodeid:
926 912 # This file was locally added. We should forget it instead of
927 913 # deleting it.
928 914 mresult.addfile(
929 915 f, mergestatemod.ACTION_FORGET, None, b'remote deleted',
930 916 )
931 917 else:
932 918 mresult.addfile(
933 919 f, mergestatemod.ACTION_REMOVE, None, b'other deleted',
934 920 )
935 921 else: # file not in ancestor, not in remote
936 922 mresult.addfile(
937 923 f,
938 924 mergestatemod.ACTION_KEEP,
939 925 None,
940 926 b'ancestor missing, remote missing',
941 927 )
942 928
943 929 elif n2: # file exists only on remote side
944 930 if f in copied1:
945 931 pass # we'll deal with it on m1 side
946 932 elif f in branch_copies2.movewithdir:
947 933 f2 = branch_copies2.movewithdir[f]
948 934 if f2 in m1:
949 935 mresult.addfile(
950 936 f2,
951 937 mergestatemod.ACTION_MERGE,
952 938 (f2, f, None, False, pa.node()),
953 939 b'local directory rename, both created',
954 940 )
955 941 else:
956 942 mresult.addfile(
957 943 f2,
958 944 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
959 945 (f, fl2),
960 946 b'local directory rename - get from %s' % f,
961 947 )
962 948 elif f in branch_copies2.copy:
963 949 f2 = branch_copies2.copy[f]
964 950 msg, args = None, None
965 951 if f2 in m2:
966 952 args = (f2, f, f2, False, pa.node())
967 953 msg = b'remote copied from %s' % f2
968 954 else:
969 955 args = (f2, f, f2, True, pa.node())
970 956 msg = b'remote moved from %s' % f2
971 957 mresult.addfile(f, mergestatemod.ACTION_MERGE, args, msg)
972 958 elif f not in ma:
973 959 # local unknown, remote created: the logic is described by the
974 960 # following table:
975 961 #
976 962 # force branchmerge different | action
977 963 # n * * | create
978 964 # y n * | create
979 965 # y y n | create
980 966 # y y y | merge
981 967 #
982 968 # Checking whether the files are different is expensive, so we
983 969 # don't do that when we can avoid it.
984 970 if not force:
985 971 mresult.addfile(
986 972 f,
987 973 mergestatemod.ACTION_CREATED,
988 974 (fl2,),
989 975 b'remote created',
990 976 )
991 977 elif not branchmerge:
992 978 mresult.addfile(
993 979 f,
994 980 mergestatemod.ACTION_CREATED,
995 981 (fl2,),
996 982 b'remote created',
997 983 )
998 984 else:
999 985 mresult.addfile(
1000 986 f,
1001 987 mergestatemod.ACTION_CREATED_MERGE,
1002 988 (fl2, pa.node()),
1003 989 b'remote created, get or merge',
1004 990 )
1005 991 elif n2 != ma[f]:
1006 992 df = None
1007 993 for d in branch_copies1.dirmove:
1008 994 if f.startswith(d):
1009 995 # new file added in a directory that was moved
1010 996 df = branch_copies1.dirmove[d] + f[len(d) :]
1011 997 break
1012 998 if df is not None and df in m1:
1013 999 mresult.addfile(
1014 1000 df,
1015 1001 mergestatemod.ACTION_MERGE,
1016 1002 (df, f, f, False, pa.node()),
1017 1003 b'local directory rename - respect move '
1018 1004 b'from %s' % f,
1019 1005 )
1020 1006 elif acceptremote:
1021 1007 mresult.addfile(
1022 1008 f,
1023 1009 mergestatemod.ACTION_CREATED,
1024 1010 (fl2,),
1025 1011 b'remote recreating',
1026 1012 )
1027 1013 else:
1028 1014 mresult.addfile(
1029 1015 f,
1030 1016 mergestatemod.ACTION_DELETED_CHANGED,
1031 1017 (None, f, f, False, pa.node()),
1032 1018 b'prompt deleted/changed',
1033 1019 )
1034 1020 else:
1035 1021 mresult.addfile(
1036 1022 f,
1037 1023 mergestatemod.ACTION_KEEP_ABSENT,
1038 1024 None,
1039 1025 b'local not present, remote unchanged',
1040 1026 )
1041 1027
1042 1028 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
1043 1029 # If we are merging, look for path conflicts.
1044 1030 checkpathconflicts(repo, wctx, p2, mresult)
1045 1031
1046 1032 narrowmatch = repo.narrowmatch()
1047 1033 if not narrowmatch.always():
1048 1034 # Updates "actions" in place
1049 1035 _filternarrowactions(narrowmatch, branchmerge, mresult)
1050 1036
1051 1037 renamedelete = branch_copies1.renamedelete
1052 1038 renamedelete.update(branch_copies2.renamedelete)
1053 1039
1054 1040 mresult.updatevalues(diverge, renamedelete)
1055 1041 return mresult
1056 1042
1057 1043
1058 1044 def _resolvetrivial(repo, wctx, mctx, ancestor, mresult):
1059 1045 """Resolves false conflicts where the nodeid changed but the content
1060 1046 remained the same."""
1061 1047 # We force a copy of actions.items() because we're going to mutate
1062 1048 # actions as we resolve trivial conflicts.
1063 1049 for f in list(mresult.files((mergestatemod.ACTION_CHANGED_DELETED,))):
1064 1050 if f in ancestor and not wctx[f].cmp(ancestor[f]):
1065 1051 # local did change but ended up with same content
1066 1052 mresult.addfile(
1067 1053 f, mergestatemod.ACTION_REMOVE, None, b'prompt same'
1068 1054 )
1069 1055
1070 1056 for f in list(mresult.files((mergestatemod.ACTION_DELETED_CHANGED,))):
1071 1057 if f in ancestor and not mctx[f].cmp(ancestor[f]):
1072 1058 # remote did change but ended up with same content
1073 1059 mresult.removefile(f) # don't get = keep local deleted
1074 1060
1075 1061
1076 1062 def calculateupdates(
1077 1063 repo,
1078 1064 wctx,
1079 1065 mctx,
1080 1066 ancestors,
1081 1067 branchmerge,
1082 1068 force,
1083 1069 acceptremote,
1084 1070 followcopies,
1085 1071 matcher=None,
1086 1072 mergeforce=False,
1087 1073 ):
1088 1074 """
1089 1075 Calculate the actions needed to merge mctx into wctx using ancestors
1090 1076
1091 1077 Uses manifestmerge() to merge manifest and get list of actions required to
1092 1078 perform for merging two manifests. If there are multiple ancestors, uses bid
1093 1079 merge if enabled.
1094 1080
1095 1081 Also filters out actions which are unrequired if repository is sparse.
1096 1082
1097 1083 Returns mergeresult object same as manifestmerge().
1098 1084 """
1099 1085 # Avoid cycle.
1100 1086 from . import sparse
1101 1087
1102 1088 mresult = None
1103 1089 if len(ancestors) == 1: # default
1104 1090 mresult = manifestmerge(
1105 1091 repo,
1106 1092 wctx,
1107 1093 mctx,
1108 1094 ancestors[0],
1109 1095 branchmerge,
1110 1096 force,
1111 1097 matcher,
1112 1098 acceptremote,
1113 1099 followcopies,
1114 1100 )
1115 1101 _checkunknownfiles(repo, wctx, mctx, force, mresult, mergeforce)
1116 1102
1117 1103 else: # only when merge.preferancestor=* - the default
1118 1104 repo.ui.note(
1119 1105 _(b"note: merging %s and %s using bids from ancestors %s\n")
1120 1106 % (
1121 1107 wctx,
1122 1108 mctx,
1123 1109 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
1124 1110 )
1125 1111 )
1126 1112
1127 1113 # mapping filename to bids (action method to list af actions)
1128 1114 # {FILENAME1 : BID1, FILENAME2 : BID2}
1129 1115 # BID is another dictionary which contains
1130 1116 # mapping of following form:
1131 1117 # {ACTION_X : [info, ..], ACTION_Y : [info, ..]}
1132 1118 fbids = {}
1133 1119 mresult = mergeresult()
1134 1120 diverge, renamedelete = None, None
1135 1121 for ancestor in ancestors:
1136 1122 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
1137 1123 mresult1 = manifestmerge(
1138 1124 repo,
1139 1125 wctx,
1140 1126 mctx,
1141 1127 ancestor,
1142 1128 branchmerge,
1143 1129 force,
1144 1130 matcher,
1145 1131 acceptremote,
1146 1132 followcopies,
1147 1133 forcefulldiff=True,
1148 1134 )
1149 1135 _checkunknownfiles(repo, wctx, mctx, force, mresult1, mergeforce)
1150 1136
1151 1137 # Track the shortest set of warning on the theory that bid
1152 1138 # merge will correctly incorporate more information
1153 1139 if diverge is None or len(mresult1.diverge) < len(diverge):
1154 1140 diverge = mresult1.diverge
1155 1141 if renamedelete is None or len(renamedelete) < len(
1156 1142 mresult1.renamedelete
1157 1143 ):
1158 1144 renamedelete = mresult1.renamedelete
1159 1145
1160 1146 # blindly update final mergeresult commitinfo with what we get
1161 1147 # from mergeresult object for each ancestor
1162 1148 # TODO: some commitinfo depends on what bid merge choose and hence
1163 1149 # we will need to make commitinfo also depend on bid merge logic
1164 1150 mresult._commitinfo.update(mresult1._commitinfo)
1165 1151
1166 1152 for f, a in mresult1.filemap(sort=True):
1167 1153 m, args, msg = a
1168 1154 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
1169 1155 if f in fbids:
1170 1156 d = fbids[f]
1171 1157 if m in d:
1172 1158 d[m].append(a)
1173 1159 else:
1174 1160 d[m] = [a]
1175 1161 else:
1176 1162 fbids[f] = {m: [a]}
1177 1163
1178 1164 # Call for bids
1179 1165 # Pick the best bid for each file
1180 1166 repo.ui.note(
1181 1167 _(b'\nauction for merging merge bids (%d ancestors)\n')
1182 1168 % len(ancestors)
1183 1169 )
1184 1170 for f, bids in sorted(fbids.items()):
1185 1171 if repo.ui.debugflag:
1186 1172 repo.ui.debug(b" list of bids for %s:\n" % f)
1187 1173 for m, l in sorted(bids.items()):
1188 1174 for _f, args, msg in l:
1189 1175 repo.ui.debug(b' %s -> %s\n' % (msg, m))
1190 1176 # bids is a mapping from action method to list af actions
1191 1177 # Consensus?
1192 1178 if len(bids) == 1: # all bids are the same kind of method
1193 1179 m, l = list(bids.items())[0]
1194 1180 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1195 1181 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
1196 1182 mresult.addfile(f, *l[0])
1197 1183 continue
1198 1184 # If keep is an option, just do it.
1199 1185 if mergestatemod.ACTION_KEEP in bids:
1200 1186 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
1201 1187 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP][0])
1202 1188 continue
1203 1189 # If keep absent is an option, just do that
1204 1190 if mergestatemod.ACTION_KEEP_ABSENT in bids:
1205 1191 repo.ui.note(_(b" %s: picking 'keep absent' action\n") % f)
1206 1192 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP_ABSENT][0])
1207 1193 continue
1208 1194 # If there are gets and they all agree [how could they not?], do it.
1209 1195 if mergestatemod.ACTION_GET in bids:
1210 1196 ga0 = bids[mergestatemod.ACTION_GET][0]
1211 1197 if all(a == ga0 for a in bids[mergestatemod.ACTION_GET][1:]):
1212 1198 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
1213 1199 mresult.addfile(f, *ga0)
1214 1200 continue
1215 1201 # TODO: Consider other simple actions such as mode changes
1216 1202 # Handle inefficient democrazy.
1217 1203 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
1218 1204 for m, l in sorted(bids.items()):
1219 1205 for _f, args, msg in l:
1220 1206 repo.ui.note(b' %s -> %s\n' % (msg, m))
1221 1207 # Pick random action. TODO: Instead, prompt user when resolving
1222 1208 m, l = list(bids.items())[0]
1223 1209 repo.ui.warn(
1224 1210 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
1225 1211 )
1226 1212 mresult.addfile(f, *l[0])
1227 1213 continue
1228 1214 repo.ui.note(_(b'end of auction\n\n'))
1229 1215 mresult.updatevalues(diverge, renamedelete)
1230 1216
1231 1217 if wctx.rev() is None:
1232 1218 _forgetremoved(wctx, mctx, branchmerge, mresult)
1233 1219
1234 1220 sparse.filterupdatesactions(repo, wctx, mctx, branchmerge, mresult)
1235 1221 _resolvetrivial(repo, wctx, mctx, ancestors[0], mresult)
1236 1222
1237 1223 return mresult
1238 1224
1239 1225
1240 1226 def _getcwd():
1241 1227 try:
1242 1228 return encoding.getcwd()
1243 1229 except OSError as err:
1244 1230 if err.errno == errno.ENOENT:
1245 1231 return None
1246 1232 raise
1247 1233
1248 1234
1249 1235 def batchremove(repo, wctx, actions):
1250 1236 """apply removes to the working directory
1251 1237
1252 1238 yields tuples for progress updates
1253 1239 """
1254 1240 verbose = repo.ui.verbose
1255 1241 cwd = _getcwd()
1256 1242 i = 0
1257 1243 for f, args, msg in actions:
1258 1244 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1259 1245 if verbose:
1260 1246 repo.ui.note(_(b"removing %s\n") % f)
1261 1247 wctx[f].audit()
1262 1248 try:
1263 1249 wctx[f].remove(ignoremissing=True)
1264 1250 except OSError as inst:
1265 1251 repo.ui.warn(
1266 1252 _(b"update failed to remove %s: %s!\n") % (f, inst.strerror)
1267 1253 )
1268 1254 if i == 100:
1269 1255 yield i, f
1270 1256 i = 0
1271 1257 i += 1
1272 1258 if i > 0:
1273 1259 yield i, f
1274 1260
1275 1261 if cwd and not _getcwd():
1276 1262 # cwd was removed in the course of removing files; print a helpful
1277 1263 # warning.
1278 1264 repo.ui.warn(
1279 1265 _(
1280 1266 b"current directory was removed\n"
1281 1267 b"(consider changing to repo root: %s)\n"
1282 1268 )
1283 1269 % repo.root
1284 1270 )
1285 1271
1286 1272
1287 1273 def batchget(repo, mctx, wctx, wantfiledata, actions):
1288 1274 """apply gets to the working directory
1289 1275
1290 1276 mctx is the context to get from
1291 1277
1292 1278 Yields arbitrarily many (False, tuple) for progress updates, followed by
1293 1279 exactly one (True, filedata). When wantfiledata is false, filedata is an
1294 1280 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1295 1281 mtime) of the file f written for each action.
1296 1282 """
1297 1283 filedata = {}
1298 1284 verbose = repo.ui.verbose
1299 1285 fctx = mctx.filectx
1300 1286 ui = repo.ui
1301 1287 i = 0
1302 1288 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1303 1289 for f, (flags, backup), msg in actions:
1304 1290 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1305 1291 if verbose:
1306 1292 repo.ui.note(_(b"getting %s\n") % f)
1307 1293
1308 1294 if backup:
1309 1295 # If a file or directory exists with the same name, back that
1310 1296 # up. Otherwise, look to see if there is a file that conflicts
1311 1297 # with a directory this file is in, and if so, back that up.
1312 1298 conflicting = f
1313 1299 if not repo.wvfs.lexists(f):
1314 1300 for p in pathutil.finddirs(f):
1315 1301 if repo.wvfs.isfileorlink(p):
1316 1302 conflicting = p
1317 1303 break
1318 1304 if repo.wvfs.lexists(conflicting):
1319 1305 orig = scmutil.backuppath(ui, repo, conflicting)
1320 1306 util.rename(repo.wjoin(conflicting), orig)
1321 1307 wfctx = wctx[f]
1322 1308 wfctx.clearunknown()
1323 1309 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1324 1310 size = wfctx.write(
1325 1311 fctx(f).data(),
1326 1312 flags,
1327 1313 backgroundclose=True,
1328 1314 atomictemp=atomictemp,
1329 1315 )
1330 1316 if wantfiledata:
1331 1317 s = wfctx.lstat()
1332 1318 mode = s.st_mode
1333 1319 mtime = s[stat.ST_MTIME]
1334 1320 filedata[f] = (mode, size, mtime) # for dirstate.normal
1335 1321 if i == 100:
1336 1322 yield False, (i, f)
1337 1323 i = 0
1338 1324 i += 1
1339 1325 if i > 0:
1340 1326 yield False, (i, f)
1341 1327 yield True, filedata
1342 1328
1343 1329
1344 1330 def _prefetchfiles(repo, ctx, mresult):
1345 1331 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1346 1332 of merge actions. ``ctx`` is the context being merged in."""
1347 1333
1348 1334 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1349 1335 # don't touch the context to be merged in. 'cd' is skipped, because
1350 1336 # changed/deleted never resolves to something from the remote side.
1351 1337 files = mresult.files(
1352 1338 [
1353 1339 mergestatemod.ACTION_GET,
1354 1340 mergestatemod.ACTION_DELETED_CHANGED,
1355 1341 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1356 1342 mergestatemod.ACTION_MERGE,
1357 1343 ]
1358 1344 )
1359 1345
1360 1346 prefetch = scmutil.prefetchfiles
1361 1347 matchfiles = scmutil.matchfiles
1362 1348 prefetch(
1363 1349 repo, [(ctx.rev(), matchfiles(repo, files),)],
1364 1350 )
1365 1351
1366 1352
1367 1353 @attr.s(frozen=True)
1368 1354 class updateresult(object):
1369 1355 updatedcount = attr.ib()
1370 1356 mergedcount = attr.ib()
1371 1357 removedcount = attr.ib()
1372 1358 unresolvedcount = attr.ib()
1373 1359
1374 1360 def isempty(self):
1375 1361 return not (
1376 1362 self.updatedcount
1377 1363 or self.mergedcount
1378 1364 or self.removedcount
1379 1365 or self.unresolvedcount
1380 1366 )
1381 1367
1382 1368
1383 1369 def applyupdates(
1384 1370 repo, mresult, wctx, mctx, overwrite, wantfiledata, labels=None,
1385 1371 ):
1386 1372 """apply the merge action list to the working directory
1387 1373
1388 1374 mresult is a mergeresult object representing result of the merge
1389 1375 wctx is the working copy context
1390 1376 mctx is the context to be merged into the working copy
1391 1377
1392 1378 Return a tuple of (counts, filedata), where counts is a tuple
1393 1379 (updated, merged, removed, unresolved) that describes how many
1394 1380 files were affected by the update, and filedata is as described in
1395 1381 batchget.
1396 1382 """
1397 1383
1398 1384 _prefetchfiles(repo, mctx, mresult)
1399 1385
1400 1386 updated, merged, removed = 0, 0, 0
1401 1387 ms = wctx.mergestate(clean=True)
1402 1388 ms.start(wctx.p1().node(), mctx.node(), labels)
1403 1389
1404 1390 for f, op in pycompat.iteritems(mresult.commitinfo):
1405 1391 # the other side of filenode was choosen while merging, store this in
1406 1392 # mergestate so that it can be reused on commit
1407 1393 ms.addcommitinfo(f, op)
1408 1394
1409 1395 numupdates = mresult.len() - mresult.len(mergeresult.NO_OP_ACTIONS)
1410 1396 progress = repo.ui.makeprogress(
1411 1397 _(b'updating'), unit=_(b'files'), total=numupdates
1412 1398 )
1413 1399
1414 1400 if b'.hgsubstate' in mresult._actionmapping[mergestatemod.ACTION_REMOVE]:
1415 1401 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1416 1402
1417 1403 # record path conflicts
1418 1404 for f, args, msg in mresult.getactions(
1419 1405 [mergestatemod.ACTION_PATH_CONFLICT], sort=True
1420 1406 ):
1421 1407 f1, fo = args
1422 1408 s = repo.ui.status
1423 1409 s(
1424 1410 _(
1425 1411 b"%s: path conflict - a file or link has the same name as a "
1426 1412 b"directory\n"
1427 1413 )
1428 1414 % f
1429 1415 )
1430 1416 if fo == b'l':
1431 1417 s(_(b"the local file has been renamed to %s\n") % f1)
1432 1418 else:
1433 1419 s(_(b"the remote file has been renamed to %s\n") % f1)
1434 1420 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1435 1421 ms.addpathconflict(f, f1, fo)
1436 1422 progress.increment(item=f)
1437 1423
1438 1424 # When merging in-memory, we can't support worker processes, so set the
1439 1425 # per-item cost at 0 in that case.
1440 1426 cost = 0 if wctx.isinmemory() else 0.001
1441 1427
1442 1428 # remove in parallel (must come before resolving path conflicts and getting)
1443 1429 prog = worker.worker(
1444 1430 repo.ui,
1445 1431 cost,
1446 1432 batchremove,
1447 1433 (repo, wctx),
1448 1434 list(mresult.getactions([mergestatemod.ACTION_REMOVE], sort=True)),
1449 1435 )
1450 1436 for i, item in prog:
1451 1437 progress.increment(step=i, item=item)
1452 1438 removed = mresult.len((mergestatemod.ACTION_REMOVE,))
1453 1439
1454 1440 # resolve path conflicts (must come before getting)
1455 1441 for f, args, msg in mresult.getactions(
1456 1442 [mergestatemod.ACTION_PATH_CONFLICT_RESOLVE], sort=True
1457 1443 ):
1458 1444 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1459 1445 (f0, origf0) = args
1460 1446 if wctx[f0].lexists():
1461 1447 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1462 1448 wctx[f].audit()
1463 1449 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1464 1450 wctx[f0].remove()
1465 1451 progress.increment(item=f)
1466 1452
1467 1453 # get in parallel.
1468 1454 threadsafe = repo.ui.configbool(
1469 1455 b'experimental', b'worker.wdir-get-thread-safe'
1470 1456 )
1471 1457 prog = worker.worker(
1472 1458 repo.ui,
1473 1459 cost,
1474 1460 batchget,
1475 1461 (repo, mctx, wctx, wantfiledata),
1476 1462 list(mresult.getactions([mergestatemod.ACTION_GET], sort=True)),
1477 1463 threadsafe=threadsafe,
1478 1464 hasretval=True,
1479 1465 )
1480 1466 getfiledata = {}
1481 1467 for final, res in prog:
1482 1468 if final:
1483 1469 getfiledata = res
1484 1470 else:
1485 1471 i, item = res
1486 1472 progress.increment(step=i, item=item)
1487 1473
1488 1474 if b'.hgsubstate' in mresult._actionmapping[mergestatemod.ACTION_GET]:
1489 1475 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1490 1476
1491 1477 # forget (manifest only, just log it) (must come first)
1492 1478 for f, args, msg in mresult.getactions(
1493 1479 (mergestatemod.ACTION_FORGET,), sort=True
1494 1480 ):
1495 1481 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1496 1482 progress.increment(item=f)
1497 1483
1498 1484 # re-add (manifest only, just log it)
1499 1485 for f, args, msg in mresult.getactions(
1500 1486 (mergestatemod.ACTION_ADD,), sort=True
1501 1487 ):
1502 1488 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1503 1489 progress.increment(item=f)
1504 1490
1505 1491 # re-add/mark as modified (manifest only, just log it)
1506 1492 for f, args, msg in mresult.getactions(
1507 1493 (mergestatemod.ACTION_ADD_MODIFIED,), sort=True
1508 1494 ):
1509 1495 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1510 1496 progress.increment(item=f)
1511 1497
1512 1498 # keep (noop, just log it)
1513 1499 for f, args, msg in mresult.getactions(
1514 1500 (mergestatemod.ACTION_KEEP,), sort=True
1515 1501 ):
1516 1502 repo.ui.debug(b" %s: %s -> k\n" % (f, msg))
1517 1503 # no progress
1518 1504 for f, args, msg in mresult.getactions(
1519 1505 (mergestatemod.ACTION_KEEP_ABSENT,), sort=True
1520 1506 ):
1521 1507 repo.ui.debug(b" %s: %s -> ka\n" % (f, msg))
1522 1508 # no progress
1523 1509
1524 1510 # directory rename, move local
1525 1511 for f, args, msg in mresult.getactions(
1526 1512 (mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,), sort=True
1527 1513 ):
1528 1514 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1529 1515 progress.increment(item=f)
1530 1516 f0, flags = args
1531 1517 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1532 1518 wctx[f].audit()
1533 1519 wctx[f].write(wctx.filectx(f0).data(), flags)
1534 1520 wctx[f0].remove()
1535 1521
1536 1522 # local directory rename, get
1537 1523 for f, args, msg in mresult.getactions(
1538 1524 (mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,), sort=True
1539 1525 ):
1540 1526 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1541 1527 progress.increment(item=f)
1542 1528 f0, flags = args
1543 1529 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1544 1530 wctx[f].write(mctx.filectx(f0).data(), flags)
1545 1531
1546 1532 # exec
1547 1533 for f, args, msg in mresult.getactions(
1548 1534 (mergestatemod.ACTION_EXEC,), sort=True
1549 1535 ):
1550 1536 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1551 1537 progress.increment(item=f)
1552 1538 (flags,) = args
1553 1539 wctx[f].audit()
1554 1540 wctx[f].setflags(b'l' in flags, b'x' in flags)
1555 1541
1556 1542 moves = []
1557 1543
1558 1544 # 'cd' and 'dc' actions are treated like other merge conflicts
1559 1545 mergeactions = list(
1560 1546 mresult.getactions(
1561 1547 [
1562 1548 mergestatemod.ACTION_CHANGED_DELETED,
1563 1549 mergestatemod.ACTION_DELETED_CHANGED,
1564 1550 mergestatemod.ACTION_MERGE,
1565 1551 ],
1566 1552 sort=True,
1567 1553 )
1568 1554 )
1569 1555 for f, args, msg in mergeactions:
1570 1556 f1, f2, fa, move, anc = args
1571 1557 if f == b'.hgsubstate': # merged internally
1572 1558 continue
1573 1559 if f1 is None:
1574 1560 fcl = filemerge.absentfilectx(wctx, fa)
1575 1561 else:
1576 1562 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1577 1563 fcl = wctx[f1]
1578 1564 if f2 is None:
1579 1565 fco = filemerge.absentfilectx(mctx, fa)
1580 1566 else:
1581 1567 fco = mctx[f2]
1582 1568 actx = repo[anc]
1583 1569 if fa in actx:
1584 1570 fca = actx[fa]
1585 1571 else:
1586 1572 # TODO: move to absentfilectx
1587 1573 fca = repo.filectx(f1, fileid=nullrev)
1588 1574 ms.add(fcl, fco, fca, f)
1589 1575 if f1 != f and move:
1590 1576 moves.append(f1)
1591 1577
1592 1578 # remove renamed files after safely stored
1593 1579 for f in moves:
1594 1580 if wctx[f].lexists():
1595 1581 repo.ui.debug(b"removing %s\n" % f)
1596 1582 wctx[f].audit()
1597 1583 wctx[f].remove()
1598 1584
1599 1585 # these actions updates the file
1600 1586 updated = mresult.len(
1601 1587 (
1602 1588 mergestatemod.ACTION_GET,
1603 1589 mergestatemod.ACTION_EXEC,
1604 1590 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1605 1591 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
1606 1592 )
1607 1593 )
1608 # the ordering is important here -- ms.mergedriver will raise if the merge
1609 # driver has changed, and we want to be able to bypass it when overwrite is
1610 # True
1611 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1612
1613 if usemergedriver:
1614 ms.commit()
1615 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1616 # the driver might leave some files unresolved
1617 unresolvedf = set(ms.unresolved())
1618 if not proceed:
1619 # XXX setting unresolved to at least 1 is a hack to make sure we
1620 # error out
1621 return updateresult(
1622 updated, merged, removed, max(len(unresolvedf), 1)
1623 )
1624 newactions = []
1625 for f, args, msg in mergeactions:
1626 if f in unresolvedf:
1627 newactions.append((f, args, msg))
1628 mergeactions = newactions
1629 1594
1630 1595 try:
1631 1596 # premerge
1632 1597 tocomplete = []
1633 1598 for f, args, msg in mergeactions:
1634 1599 repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg))
1635 1600 progress.increment(item=f)
1636 1601 if f == b'.hgsubstate': # subrepo states need updating
1637 1602 subrepoutil.submerge(
1638 1603 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
1639 1604 )
1640 1605 continue
1641 1606 wctx[f].audit()
1642 1607 complete, r = ms.preresolve(f, wctx)
1643 1608 if not complete:
1644 1609 numupdates += 1
1645 1610 tocomplete.append((f, args, msg))
1646 1611
1647 1612 # merge
1648 1613 for f, args, msg in tocomplete:
1649 1614 repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg))
1650 1615 progress.increment(item=f, total=numupdates)
1651 1616 ms.resolve(f, wctx)
1652 1617
1653 1618 finally:
1654 1619 ms.commit()
1655 1620
1656 1621 unresolved = ms.unresolvedcount()
1657 1622
1658 if (
1659 usemergedriver
1660 and not unresolved
1661 and ms.mdstate() != mergestatemod.MERGE_DRIVER_STATE_SUCCESS
1662 ):
1663 if not driverconclude(repo, ms, wctx, labels=labels):
1664 # XXX setting unresolved to at least 1 is a hack to make sure we
1665 # error out
1666 unresolved = max(unresolved, 1)
1667
1668 ms.commit()
1669
1670 1623 msupdated, msmerged, msremoved = ms.counts()
1671 1624 updated += msupdated
1672 1625 merged += msmerged
1673 1626 removed += msremoved
1674 1627
1675 1628 extraactions = ms.actions()
1676 1629 if extraactions:
1677 mfiles = {
1678 a[0] for a in mresult.getactions((mergestatemod.ACTION_MERGE,))
1679 }
1680 1630 for k, acts in pycompat.iteritems(extraactions):
1681 1631 for a in acts:
1682 1632 mresult.addfile(a[0], k, *a[1:])
1683 1633 if k == mergestatemod.ACTION_GET and wantfiledata:
1684 1634 # no filedata until mergestate is updated to provide it
1685 1635 for a in acts:
1686 1636 getfiledata[a[0]] = None
1687 # Remove these files from actions[ACTION_MERGE] as well. This is
1688 # important because in recordupdates, files in actions[ACTION_MERGE]
1689 # are processed after files in other actions, and the merge driver
1690 # might add files to those actions via extraactions above. This can
1691 # lead to a file being recorded twice, with poor results. This is
1692 # especially problematic for actions[ACTION_REMOVE] (currently only
1693 # possible with the merge driver in the initial merge process;
1694 # interrupted merges don't go through this flow).
1695 #
1696 # The real fix here is to have indexes by both file and action so
1697 # that when the action for a file is changed it is automatically
1698 # reflected in the other action lists. But that involves a more
1699 # complex data structure, so this will do for now.
1700 #
1701 # We don't need to do the same operation for 'dc' and 'cd' because
1702 # those lists aren't consulted again.
1703 mfiles.difference_update(a[0] for a in acts)
1704
1705 for a in list(mresult.getactions((mergestatemod.ACTION_MERGE,))):
1706 if a[0] not in mfiles:
1707 mresult.removefile(a[0])
1708 1637
1709 1638 progress.complete()
1710 1639 assert len(getfiledata) == (
1711 1640 mresult.len((mergestatemod.ACTION_GET,)) if wantfiledata else 0
1712 1641 )
1713 1642 return updateresult(updated, merged, removed, unresolved), getfiledata
1714 1643
1715 1644
1716 1645 def _advertisefsmonitor(repo, num_gets, p1node):
1717 1646 # Advertise fsmonitor when its presence could be useful.
1718 1647 #
1719 1648 # We only advertise when performing an update from an empty working
1720 1649 # directory. This typically only occurs during initial clone.
1721 1650 #
1722 1651 # We give users a mechanism to disable the warning in case it is
1723 1652 # annoying.
1724 1653 #
1725 1654 # We only allow on Linux and MacOS because that's where fsmonitor is
1726 1655 # considered stable.
1727 1656 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
1728 1657 fsmonitorthreshold = repo.ui.configint(
1729 1658 b'fsmonitor', b'warn_update_file_count'
1730 1659 )
1731 1660 # avoid cycle dirstate -> sparse -> merge -> dirstate
1732 1661 from . import dirstate
1733 1662
1734 1663 if dirstate.rustmod is not None:
1735 1664 # When using rust status, fsmonitor becomes necessary at higher sizes
1736 1665 fsmonitorthreshold = repo.ui.configint(
1737 1666 b'fsmonitor', b'warn_update_file_count_rust',
1738 1667 )
1739 1668
1740 1669 try:
1741 1670 # avoid cycle: extensions -> cmdutil -> merge
1742 1671 from . import extensions
1743 1672
1744 1673 extensions.find(b'fsmonitor')
1745 1674 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
1746 1675 # We intentionally don't look at whether fsmonitor has disabled
1747 1676 # itself because a) fsmonitor may have already printed a warning
1748 1677 # b) we only care about the config state here.
1749 1678 except KeyError:
1750 1679 fsmonitorenabled = False
1751 1680
1752 1681 if (
1753 1682 fsmonitorwarning
1754 1683 and not fsmonitorenabled
1755 1684 and p1node == nullid
1756 1685 and num_gets >= fsmonitorthreshold
1757 1686 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
1758 1687 ):
1759 1688 repo.ui.warn(
1760 1689 _(
1761 1690 b'(warning: large working directory being used without '
1762 1691 b'fsmonitor enabled; enable fsmonitor to improve performance; '
1763 1692 b'see "hg help -e fsmonitor")\n'
1764 1693 )
1765 1694 )
1766 1695
1767 1696
1768 1697 UPDATECHECK_ABORT = b'abort' # handled at higher layers
1769 1698 UPDATECHECK_NONE = b'none'
1770 1699 UPDATECHECK_LINEAR = b'linear'
1771 1700 UPDATECHECK_NO_CONFLICT = b'noconflict'
1772 1701
1773 1702
1774 1703 def update(
1775 1704 repo,
1776 1705 node,
1777 1706 branchmerge,
1778 1707 force,
1779 1708 ancestor=None,
1780 1709 mergeancestor=False,
1781 1710 labels=None,
1782 1711 matcher=None,
1783 1712 mergeforce=False,
1784 1713 updatedirstate=True,
1785 1714 updatecheck=None,
1786 1715 wc=None,
1787 1716 ):
1788 1717 """
1789 1718 Perform a merge between the working directory and the given node
1790 1719
1791 1720 node = the node to update to
1792 1721 branchmerge = whether to merge between branches
1793 1722 force = whether to force branch merging or file overwriting
1794 1723 matcher = a matcher to filter file lists (dirstate not updated)
1795 1724 mergeancestor = whether it is merging with an ancestor. If true,
1796 1725 we should accept the incoming changes for any prompts that occur.
1797 1726 If false, merging with an ancestor (fast-forward) is only allowed
1798 1727 between different named branches. This flag is used by rebase extension
1799 1728 as a temporary fix and should be avoided in general.
1800 1729 labels = labels to use for base, local and other
1801 1730 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1802 1731 this is True, then 'force' should be True as well.
1803 1732
1804 1733 The table below shows all the behaviors of the update command given the
1805 1734 -c/--check and -C/--clean or no options, whether the working directory is
1806 1735 dirty, whether a revision is specified, and the relationship of the parent
1807 1736 rev to the target rev (linear or not). Match from top first. The -n
1808 1737 option doesn't exist on the command line, but represents the
1809 1738 experimental.updatecheck=noconflict option.
1810 1739
1811 1740 This logic is tested by test-update-branches.t.
1812 1741
1813 1742 -c -C -n -m dirty rev linear | result
1814 1743 y y * * * * * | (1)
1815 1744 y * y * * * * | (1)
1816 1745 y * * y * * * | (1)
1817 1746 * y y * * * * | (1)
1818 1747 * y * y * * * | (1)
1819 1748 * * y y * * * | (1)
1820 1749 * * * * * n n | x
1821 1750 * * * * n * * | ok
1822 1751 n n n n y * y | merge
1823 1752 n n n n y y n | (2)
1824 1753 n n n y y * * | merge
1825 1754 n n y n y * * | merge if no conflict
1826 1755 n y n n y * * | discard
1827 1756 y n n n y * * | (3)
1828 1757
1829 1758 x = can't happen
1830 1759 * = don't-care
1831 1760 1 = incompatible options (checked in commands.py)
1832 1761 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1833 1762 3 = abort: uncommitted changes (checked in commands.py)
1834 1763
1835 1764 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1836 1765 to repo[None] if None is passed.
1837 1766
1838 1767 Return the same tuple as applyupdates().
1839 1768 """
1840 1769 # Avoid cycle.
1841 1770 from . import sparse
1842 1771
1843 1772 # This function used to find the default destination if node was None, but
1844 1773 # that's now in destutil.py.
1845 1774 assert node is not None
1846 1775 if not branchmerge and not force:
1847 1776 # TODO: remove the default once all callers that pass branchmerge=False
1848 1777 # and force=False pass a value for updatecheck. We may want to allow
1849 1778 # updatecheck='abort' to better suppport some of these callers.
1850 1779 if updatecheck is None:
1851 1780 updatecheck = UPDATECHECK_LINEAR
1852 1781 if updatecheck not in (
1853 1782 UPDATECHECK_NONE,
1854 1783 UPDATECHECK_LINEAR,
1855 1784 UPDATECHECK_NO_CONFLICT,
1856 1785 ):
1857 1786 raise ValueError(
1858 1787 r'Invalid updatecheck %r (can accept %r)'
1859 1788 % (
1860 1789 updatecheck,
1861 1790 (
1862 1791 UPDATECHECK_NONE,
1863 1792 UPDATECHECK_LINEAR,
1864 1793 UPDATECHECK_NO_CONFLICT,
1865 1794 ),
1866 1795 )
1867 1796 )
1868 1797 if wc is not None and wc.isinmemory():
1869 1798 maybe_wlock = util.nullcontextmanager()
1870 1799 else:
1871 1800 maybe_wlock = repo.wlock()
1872 1801 with maybe_wlock:
1873 1802 if wc is None:
1874 1803 wc = repo[None]
1875 1804 pl = wc.parents()
1876 1805 p1 = pl[0]
1877 1806 p2 = repo[node]
1878 1807 if ancestor is not None:
1879 1808 pas = [repo[ancestor]]
1880 1809 else:
1881 1810 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
1882 1811 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1883 1812 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1884 1813 else:
1885 1814 pas = [p1.ancestor(p2, warn=branchmerge)]
1886 1815
1887 1816 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
1888 1817
1889 1818 overwrite = force and not branchmerge
1890 1819 ### check phase
1891 1820 if not overwrite:
1892 1821 if len(pl) > 1:
1893 1822 raise error.Abort(_(b"outstanding uncommitted merge"))
1894 1823 ms = wc.mergestate()
1895 1824 if list(ms.unresolved()):
1896 1825 raise error.Abort(
1897 1826 _(b"outstanding merge conflicts"),
1898 1827 hint=_(b"use 'hg resolve' to resolve"),
1899 1828 )
1900 1829 if branchmerge:
1901 1830 if pas == [p2]:
1902 1831 raise error.Abort(
1903 1832 _(
1904 1833 b"merging with a working directory ancestor"
1905 1834 b" has no effect"
1906 1835 )
1907 1836 )
1908 1837 elif pas == [p1]:
1909 1838 if not mergeancestor and wc.branch() == p2.branch():
1910 1839 raise error.Abort(
1911 1840 _(b"nothing to merge"),
1912 1841 hint=_(b"use 'hg update' or check 'hg heads'"),
1913 1842 )
1914 1843 if not force and (wc.files() or wc.deleted()):
1915 1844 raise error.Abort(
1916 1845 _(b"uncommitted changes"),
1917 1846 hint=_(b"use 'hg status' to list changes"),
1918 1847 )
1919 1848 if not wc.isinmemory():
1920 1849 for s in sorted(wc.substate):
1921 1850 wc.sub(s).bailifchanged()
1922 1851
1923 1852 elif not overwrite:
1924 1853 if p1 == p2: # no-op update
1925 1854 # call the hooks and exit early
1926 1855 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
1927 1856 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
1928 1857 return updateresult(0, 0, 0, 0)
1929 1858
1930 1859 if updatecheck == UPDATECHECK_LINEAR and pas not in (
1931 1860 [p1],
1932 1861 [p2],
1933 1862 ): # nonlinear
1934 1863 dirty = wc.dirty(missing=True)
1935 1864 if dirty:
1936 1865 # Branching is a bit strange to ensure we do the minimal
1937 1866 # amount of call to obsutil.foreground.
1938 1867 foreground = obsutil.foreground(repo, [p1.node()])
1939 1868 # note: the <node> variable contains a random identifier
1940 1869 if repo[node].node() in foreground:
1941 1870 pass # allow updating to successors
1942 1871 else:
1943 1872 msg = _(b"uncommitted changes")
1944 1873 hint = _(b"commit or update --clean to discard changes")
1945 1874 raise error.UpdateAbort(msg, hint=hint)
1946 1875 else:
1947 1876 # Allow jumping branches if clean and specific rev given
1948 1877 pass
1949 1878
1950 1879 if overwrite:
1951 1880 pas = [wc]
1952 1881 elif not branchmerge:
1953 1882 pas = [p1]
1954 1883
1955 1884 # deprecated config: merge.followcopies
1956 1885 followcopies = repo.ui.configbool(b'merge', b'followcopies')
1957 1886 if overwrite:
1958 1887 followcopies = False
1959 1888 elif not pas[0]:
1960 1889 followcopies = False
1961 1890 if not branchmerge and not wc.dirty(missing=True):
1962 1891 followcopies = False
1963 1892
1964 1893 ### calculate phase
1965 1894 mresult = calculateupdates(
1966 1895 repo,
1967 1896 wc,
1968 1897 p2,
1969 1898 pas,
1970 1899 branchmerge,
1971 1900 force,
1972 1901 mergeancestor,
1973 1902 followcopies,
1974 1903 matcher=matcher,
1975 1904 mergeforce=mergeforce,
1976 1905 )
1977 1906
1978 1907 if updatecheck == UPDATECHECK_NO_CONFLICT:
1979 1908 if mresult.hasconflicts():
1980 1909 msg = _(b"conflicting changes")
1981 1910 hint = _(b"commit or update --clean to discard changes")
1982 1911 raise error.Abort(msg, hint=hint)
1983 1912
1984 1913 # Prompt and create actions. Most of this is in the resolve phase
1985 1914 # already, but we can't handle .hgsubstate in filemerge or
1986 1915 # subrepoutil.submerge yet so we have to keep prompting for it.
1987 1916 vals = mresult.getfile(b'.hgsubstate')
1988 1917 if vals:
1989 1918 f = b'.hgsubstate'
1990 1919 m, args, msg = vals
1991 1920 prompts = filemerge.partextras(labels)
1992 1921 prompts[b'f'] = f
1993 1922 if m == mergestatemod.ACTION_CHANGED_DELETED:
1994 1923 if repo.ui.promptchoice(
1995 1924 _(
1996 1925 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
1997 1926 b"use (c)hanged version or (d)elete?"
1998 1927 b"$$ &Changed $$ &Delete"
1999 1928 )
2000 1929 % prompts,
2001 1930 0,
2002 1931 ):
2003 1932 mresult.addfile(
2004 1933 f, mergestatemod.ACTION_REMOVE, None, b'prompt delete',
2005 1934 )
2006 1935 elif f in p1:
2007 1936 mresult.addfile(
2008 1937 f,
2009 1938 mergestatemod.ACTION_ADD_MODIFIED,
2010 1939 None,
2011 1940 b'prompt keep',
2012 1941 )
2013 1942 else:
2014 1943 mresult.addfile(
2015 1944 f, mergestatemod.ACTION_ADD, None, b'prompt keep',
2016 1945 )
2017 1946 elif m == mergestatemod.ACTION_DELETED_CHANGED:
2018 1947 f1, f2, fa, move, anc = args
2019 1948 flags = p2[f2].flags()
2020 1949 if (
2021 1950 repo.ui.promptchoice(
2022 1951 _(
2023 1952 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
2024 1953 b"use (c)hanged version or leave (d)eleted?"
2025 1954 b"$$ &Changed $$ &Deleted"
2026 1955 )
2027 1956 % prompts,
2028 1957 0,
2029 1958 )
2030 1959 == 0
2031 1960 ):
2032 1961 mresult.addfile(
2033 1962 f,
2034 1963 mergestatemod.ACTION_GET,
2035 1964 (flags, False),
2036 1965 b'prompt recreating',
2037 1966 )
2038 1967 else:
2039 1968 mresult.removefile(f)
2040 1969
2041 1970 if not util.fscasesensitive(repo.path):
2042 1971 # check collision between files only in p2 for clean update
2043 1972 if not branchmerge and (
2044 1973 force or not wc.dirty(missing=True, branch=False)
2045 1974 ):
2046 1975 _checkcollision(repo, p2.manifest(), None)
2047 1976 else:
2048 1977 _checkcollision(repo, wc.manifest(), mresult)
2049 1978
2050 1979 # divergent renames
2051 1980 for f, fl in sorted(pycompat.iteritems(mresult.diverge)):
2052 1981 repo.ui.warn(
2053 1982 _(
2054 1983 b"note: possible conflict - %s was renamed "
2055 1984 b"multiple times to:\n"
2056 1985 )
2057 1986 % f
2058 1987 )
2059 1988 for nf in sorted(fl):
2060 1989 repo.ui.warn(b" %s\n" % nf)
2061 1990
2062 1991 # rename and delete
2063 1992 for f, fl in sorted(pycompat.iteritems(mresult.renamedelete)):
2064 1993 repo.ui.warn(
2065 1994 _(
2066 1995 b"note: possible conflict - %s was deleted "
2067 1996 b"and renamed to:\n"
2068 1997 )
2069 1998 % f
2070 1999 )
2071 2000 for nf in sorted(fl):
2072 2001 repo.ui.warn(b" %s\n" % nf)
2073 2002
2074 2003 ### apply phase
2075 2004 if not branchmerge: # just jump to the new rev
2076 2005 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, b''
2077 2006 # If we're doing a partial update, we need to skip updating
2078 2007 # the dirstate.
2079 2008 always = matcher is None or matcher.always()
2080 2009 updatedirstate = updatedirstate and always and not wc.isinmemory()
2081 2010 if updatedirstate:
2082 2011 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
2083 2012 # note that we're in the middle of an update
2084 2013 repo.vfs.write(b'updatestate', p2.hex())
2085 2014
2086 2015 _advertisefsmonitor(
2087 2016 repo, mresult.len((mergestatemod.ACTION_GET,)), p1.node()
2088 2017 )
2089 2018
2090 2019 wantfiledata = updatedirstate and not branchmerge
2091 2020 stats, getfiledata = applyupdates(
2092 2021 repo, mresult, wc, p2, overwrite, wantfiledata, labels=labels,
2093 2022 )
2094 2023
2095 2024 if updatedirstate:
2096 2025 with repo.dirstate.parentchange():
2097 2026 repo.setparents(fp1, fp2)
2098 2027 mergestatemod.recordupdates(
2099 2028 repo, mresult.actionsdict, branchmerge, getfiledata
2100 2029 )
2101 2030 # update completed, clear state
2102 2031 util.unlink(repo.vfs.join(b'updatestate'))
2103 2032
2104 2033 if not branchmerge:
2105 2034 repo.dirstate.setbranch(p2.branch())
2106 2035
2107 2036 # If we're updating to a location, clean up any stale temporary includes
2108 2037 # (ex: this happens during hg rebase --abort).
2109 2038 if not branchmerge:
2110 2039 sparse.prunetemporaryincludes(repo)
2111 2040
2112 2041 if updatedirstate:
2113 2042 repo.hook(
2114 2043 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
2115 2044 )
2116 2045 return stats
2117 2046
2118 2047
2119 2048 def merge(ctx, labels=None, force=False, wc=None):
2120 2049 """Merge another topological branch into the working copy.
2121 2050
2122 2051 force = whether the merge was run with 'merge --force' (deprecated)
2123 2052 """
2124 2053
2125 2054 return update(
2126 2055 ctx.repo(),
2127 2056 ctx.rev(),
2128 2057 labels=labels,
2129 2058 branchmerge=True,
2130 2059 force=force,
2131 2060 mergeforce=force,
2132 2061 wc=wc,
2133 2062 )
2134 2063
2135 2064
2136 2065 def clean_update(ctx, wc=None):
2137 2066 """Do a clean update to the given commit.
2138 2067
2139 2068 This involves updating to the commit and discarding any changes in the
2140 2069 working copy.
2141 2070 """
2142 2071 return update(ctx.repo(), ctx.rev(), branchmerge=False, force=True, wc=wc)
2143 2072
2144 2073
2145 2074 def revert_to(ctx, matcher=None, wc=None):
2146 2075 """Revert the working copy to the given commit.
2147 2076
2148 2077 The working copy will keep its current parent(s) but its content will
2149 2078 be the same as in the given commit.
2150 2079 """
2151 2080
2152 2081 return update(
2153 2082 ctx.repo(),
2154 2083 ctx.rev(),
2155 2084 branchmerge=False,
2156 2085 force=True,
2157 2086 updatedirstate=False,
2158 2087 matcher=matcher,
2159 2088 wc=wc,
2160 2089 )
2161 2090
2162 2091
2163 2092 def graft(
2164 2093 repo,
2165 2094 ctx,
2166 2095 base=None,
2167 2096 labels=None,
2168 2097 keepparent=False,
2169 2098 keepconflictparent=False,
2170 2099 wctx=None,
2171 2100 ):
2172 2101 """Do a graft-like merge.
2173 2102
2174 2103 This is a merge where the merge ancestor is chosen such that one
2175 2104 or more changesets are grafted onto the current changeset. In
2176 2105 addition to the merge, this fixes up the dirstate to include only
2177 2106 a single parent (if keepparent is False) and tries to duplicate any
2178 2107 renames/copies appropriately.
2179 2108
2180 2109 ctx - changeset to rebase
2181 2110 base - merge base, or ctx.p1() if not specified
2182 2111 labels - merge labels eg ['local', 'graft']
2183 2112 keepparent - keep second parent if any
2184 2113 keepconflictparent - if unresolved, keep parent used for the merge
2185 2114
2186 2115 """
2187 2116 # If we're grafting a descendant onto an ancestor, be sure to pass
2188 2117 # mergeancestor=True to update. This does two things: 1) allows the merge if
2189 2118 # the destination is the same as the parent of the ctx (so we can use graft
2190 2119 # to copy commits), and 2) informs update that the incoming changes are
2191 2120 # newer than the destination so it doesn't prompt about "remote changed foo
2192 2121 # which local deleted".
2193 2122 # We also pass mergeancestor=True when base is the same revision as p1. 2)
2194 2123 # doesn't matter as there can't possibly be conflicts, but 1) is necessary.
2195 2124 wctx = wctx or repo[None]
2196 2125 pctx = wctx.p1()
2197 2126 base = base or ctx.p1()
2198 2127 mergeancestor = (
2199 2128 repo.changelog.isancestor(pctx.node(), ctx.node())
2200 2129 or pctx.rev() == base.rev()
2201 2130 )
2202 2131
2203 2132 stats = update(
2204 2133 repo,
2205 2134 ctx.node(),
2206 2135 True,
2207 2136 True,
2208 2137 base.node(),
2209 2138 mergeancestor=mergeancestor,
2210 2139 labels=labels,
2211 2140 wc=wctx,
2212 2141 )
2213 2142
2214 2143 if keepconflictparent and stats.unresolvedcount:
2215 2144 pother = ctx.node()
2216 2145 else:
2217 2146 pother = nullid
2218 2147 parents = ctx.parents()
2219 2148 if keepparent and len(parents) == 2 and base in parents:
2220 2149 parents.remove(base)
2221 2150 pother = parents[0].node()
2222 2151 # Never set both parents equal to each other
2223 2152 if pother == pctx.node():
2224 2153 pother = nullid
2225 2154
2226 2155 if wctx.isinmemory():
2227 2156 wctx.setparents(pctx.node(), pother)
2228 2157 # fix up dirstate for copies and renames
2229 2158 copies.graftcopies(wctx, ctx, base)
2230 2159 else:
2231 2160 with repo.dirstate.parentchange():
2232 2161 repo.setparents(pctx.node(), pother)
2233 2162 repo.dirstate.write(repo.currenttransaction())
2234 2163 # fix up dirstate for copies and renames
2235 2164 copies.graftcopies(wctx, ctx, base)
2236 2165 return stats
2237 2166
2238 2167
2239 2168 def purge(
2240 2169 repo,
2241 2170 matcher,
2242 2171 unknown=True,
2243 2172 ignored=False,
2244 2173 removeemptydirs=True,
2245 2174 removefiles=True,
2246 2175 abortonerror=False,
2247 2176 noop=False,
2248 2177 ):
2249 2178 """Purge the working directory of untracked files.
2250 2179
2251 2180 ``matcher`` is a matcher configured to scan the working directory -
2252 2181 potentially a subset.
2253 2182
2254 2183 ``unknown`` controls whether unknown files should be purged.
2255 2184
2256 2185 ``ignored`` controls whether ignored files should be purged.
2257 2186
2258 2187 ``removeemptydirs`` controls whether empty directories should be removed.
2259 2188
2260 2189 ``removefiles`` controls whether files are removed.
2261 2190
2262 2191 ``abortonerror`` causes an exception to be raised if an error occurs
2263 2192 deleting a file or directory.
2264 2193
2265 2194 ``noop`` controls whether to actually remove files. If not defined, actions
2266 2195 will be taken.
2267 2196
2268 2197 Returns an iterable of relative paths in the working directory that were
2269 2198 or would be removed.
2270 2199 """
2271 2200
2272 2201 def remove(removefn, path):
2273 2202 try:
2274 2203 removefn(path)
2275 2204 except OSError:
2276 2205 m = _(b'%s cannot be removed') % path
2277 2206 if abortonerror:
2278 2207 raise error.Abort(m)
2279 2208 else:
2280 2209 repo.ui.warn(_(b'warning: %s\n') % m)
2281 2210
2282 2211 # There's no API to copy a matcher. So mutate the passed matcher and
2283 2212 # restore it when we're done.
2284 2213 oldtraversedir = matcher.traversedir
2285 2214
2286 2215 res = []
2287 2216
2288 2217 try:
2289 2218 if removeemptydirs:
2290 2219 directories = []
2291 2220 matcher.traversedir = directories.append
2292 2221
2293 2222 status = repo.status(match=matcher, ignored=ignored, unknown=unknown)
2294 2223
2295 2224 if removefiles:
2296 2225 for f in sorted(status.unknown + status.ignored):
2297 2226 if not noop:
2298 2227 repo.ui.note(_(b'removing file %s\n') % f)
2299 2228 remove(repo.wvfs.unlink, f)
2300 2229 res.append(f)
2301 2230
2302 2231 if removeemptydirs:
2303 2232 for f in sorted(directories, reverse=True):
2304 2233 if matcher(f) and not repo.wvfs.listdir(f):
2305 2234 if not noop:
2306 2235 repo.ui.note(_(b'removing directory %s\n') % f)
2307 2236 remove(repo.wvfs.rmdir, f)
2308 2237 res.append(f)
2309 2238
2310 2239 return res
2311 2240
2312 2241 finally:
2313 2242 matcher.traversedir = oldtraversedir
@@ -1,926 +1,821 b''
1 1 from __future__ import absolute_import
2 2
3 3 import collections
4 4 import errno
5 5 import shutil
6 6 import struct
7 7
8 8 from .i18n import _
9 9 from .node import (
10 10 bin,
11 11 hex,
12 12 nullhex,
13 13 nullid,
14 14 )
15 15 from . import (
16 16 error,
17 17 filemerge,
18 18 pycompat,
19 19 util,
20 20 )
21 21 from .utils import hashutil
22 22
23 23 _pack = struct.pack
24 24 _unpack = struct.unpack
25 25
26 26
27 27 def _droponode(data):
28 28 # used for compatibility for v1
29 29 bits = data.split(b'\0')
30 30 bits = bits[:-2] + bits[-1:]
31 31 return b'\0'.join(bits)
32 32
33 33
34 34 def _filectxorabsent(hexnode, ctx, f):
35 35 if hexnode == nullhex:
36 36 return filemerge.absentfilectx(ctx, f)
37 37 else:
38 38 return ctx[f]
39 39
40 40
41 41 # Merge state record types. See ``mergestate`` docs for more.
42 42
43 43 ####
44 44 # merge records which records metadata about a current merge
45 45 # exists only once in a mergestate
46 46 #####
47 47 RECORD_LOCAL = b'L'
48 48 RECORD_OTHER = b'O'
49 49 # record merge labels
50 50 RECORD_LABELS = b'l'
51 # store info about merge driver used and it's state
52 RECORD_MERGE_DRIVER_STATE = b'm'
53 51
54 52 #####
55 53 # record extra information about files, with one entry containing info about one
56 54 # file. Hence, multiple of them can exists
57 55 #####
58 56 RECORD_FILE_VALUES = b'f'
59 57
60 58 #####
61 59 # merge records which represents state of individual merges of files/folders
62 60 # These are top level records for each entry containing merge related info.
63 61 # Each record of these has info about one file. Hence multiple of them can
64 62 # exists
65 63 #####
66 64 RECORD_MERGED = b'F'
67 65 RECORD_CHANGEDELETE_CONFLICT = b'C'
68 RECORD_MERGE_DRIVER_MERGE = b'D'
69 66 # the path was dir on one side of merge and file on another
70 67 RECORD_PATH_CONFLICT = b'P'
71 68
72 69 #####
73 70 # possible state which a merge entry can have. These are stored inside top-level
74 71 # merge records mentioned just above.
75 72 #####
76 73 MERGE_RECORD_UNRESOLVED = b'u'
77 74 MERGE_RECORD_RESOLVED = b'r'
78 75 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
79 76 MERGE_RECORD_RESOLVED_PATH = b'pr'
80 MERGE_RECORD_DRIVER_RESOLVED = b'd'
81 77 # represents that the file was automatically merged in favor
82 78 # of other version. This info is used on commit.
83 79 # This is now deprecated and commit related information is now
84 80 # stored in RECORD_FILE_VALUES
85 81 MERGE_RECORD_MERGED_OTHER = b'o'
86 82
87 83 #####
88 84 # top level record which stores other unknown records. Multiple of these can
89 85 # exists
90 86 #####
91 87 RECORD_OVERRIDE = b't'
92 88
93 89 #####
94 # possible states which a merge driver can have. These are stored inside a
95 # RECORD_MERGE_DRIVER_STATE entry
96 #####
97 MERGE_DRIVER_STATE_UNMARKED = b'u'
98 MERGE_DRIVER_STATE_MARKED = b'm'
99 MERGE_DRIVER_STATE_SUCCESS = b's'
100
101 #####
102 90 # legacy records which are no longer used but kept to prevent breaking BC
103 91 #####
104 92 # This record was release in 5.4 and usage was removed in 5.5
105 93 LEGACY_RECORD_RESOLVED_OTHER = b'R'
94 # This record was release in 3.7 and usage was removed in 5.6
95 LEGACY_RECORD_DRIVER_RESOLVED = b'd'
96 # This record was release in 3.7 and usage was removed in 5.6
97 LEGACY_MERGE_DRIVER_STATE = b'm'
98 # This record was release in 3.7 and usage was removed in 5.6
99 LEGACY_MERGE_DRIVER_MERGE = b'D'
106 100
107 101
108 102 ACTION_FORGET = b'f'
109 103 ACTION_REMOVE = b'r'
110 104 ACTION_ADD = b'a'
111 105 ACTION_GET = b'g'
112 106 ACTION_PATH_CONFLICT = b'p'
113 107 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
114 108 ACTION_ADD_MODIFIED = b'am'
115 109 ACTION_CREATED = b'c'
116 110 ACTION_DELETED_CHANGED = b'dc'
117 111 ACTION_CHANGED_DELETED = b'cd'
118 112 ACTION_MERGE = b'm'
119 113 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
120 114 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
121 115 ACTION_KEEP = b'k'
122 116 # the file was absent on local side before merge and we should
123 117 # keep it absent (absent means file not present, it can be a result
124 118 # of file deletion, rename etc.)
125 119 ACTION_KEEP_ABSENT = b'ka'
126 120 ACTION_EXEC = b'e'
127 121 ACTION_CREATED_MERGE = b'cm'
128 122
129 123
130 124 class _mergestate_base(object):
131 125 '''track 3-way merge state of individual files
132 126
133 127 The merge state is stored on disk when needed. Two files are used: one with
134 128 an old format (version 1), and one with a new format (version 2). Version 2
135 129 stores a superset of the data in version 1, including new kinds of records
136 130 in the future. For more about the new format, see the documentation for
137 131 `_readrecordsv2`.
138 132
139 133 Each record can contain arbitrary content, and has an associated type. This
140 134 `type` should be a letter. If `type` is uppercase, the record is mandatory:
141 135 versions of Mercurial that don't support it should abort. If `type` is
142 136 lowercase, the record can be safely ignored.
143 137
144 138 Currently known records:
145 139
146 140 L: the node of the "local" part of the merge (hexified version)
147 141 O: the node of the "other" part of the merge (hexified version)
148 142 F: a file to be merged entry
149 143 C: a change/delete or delete/change conflict
150 D: a file that the external merge driver will merge internally
151 (experimental)
152 144 P: a path conflict (file vs directory)
153 m: the external merge driver defined for this merge plus its run state
154 (experimental)
155 145 f: a (filename, dictionary) tuple of optional values for a given file
156 146 l: the labels for the parts of the merge.
157 147
158 Merge driver run states (experimental):
159 u: driver-resolved files unmarked -- needs to be run next time we're about
160 to resolve or commit
161 m: driver-resolved files marked -- only needs to be run before commit
162 s: success/skipped -- does not need to be run any more
163
164 148 Merge record states (stored in self._state, indexed by filename):
165 149 u: unresolved conflict
166 150 r: resolved conflict
167 151 pu: unresolved path conflict (file conflicts with directory)
168 152 pr: resolved path conflict
169 d: driver-resolved conflict
170 153
171 154 The resolve command transitions between 'u' and 'r' for conflicts and
172 155 'pu' and 'pr' for path conflicts.
173 156 '''
174 157
175 158 def __init__(self, repo):
176 159 """Initialize the merge state.
177 160
178 161 Do not use this directly! Instead call read() or clean()."""
179 162 self._repo = repo
180 163 self._state = {}
181 164 self._stateextras = collections.defaultdict(dict)
182 165 self._local = None
183 166 self._other = None
184 167 self._labels = None
185 self._readmergedriver = None
186 self._mdstate = MERGE_DRIVER_STATE_UNMARKED
187 168 # contains a mapping of form:
188 169 # {filename : (merge_return_value, action_to_be_performed}
189 170 # these are results of re-running merge process
190 171 # this dict is used to perform actions on dirstate caused by re-running
191 172 # the merge
192 173 self._results = {}
193 174 self._dirty = False
194 175
195 176 def reset(self):
196 177 pass
197 178
198 179 def start(self, node, other, labels=None):
199 180 self._local = node
200 181 self._other = other
201 182 self._labels = labels
202 if self.mergedriver:
203 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
204
205 @util.propertycache
206 def mergedriver(self):
207 # protect against the following:
208 # - A configures a malicious merge driver in their hgrc, then
209 # pauses the merge
210 # - A edits their hgrc to remove references to the merge driver
211 # - A gives a copy of their entire repo, including .hg, to B
212 # - B inspects .hgrc and finds it to be clean
213 # - B then continues the merge and the malicious merge driver
214 # gets invoked
215 configmergedriver = self._repo.ui.config(
216 b'experimental', b'mergedriver'
217 )
218 if (
219 self._readmergedriver is not None
220 and self._readmergedriver != configmergedriver
221 ):
222 raise error.ConfigError(
223 _(b"merge driver changed since merge started"),
224 hint=_(b"revert merge driver change or abort merge"),
225 )
226
227 return configmergedriver
228 183
229 184 @util.propertycache
230 185 def local(self):
231 186 if self._local is None:
232 187 msg = b"local accessed but self._local isn't set"
233 188 raise error.ProgrammingError(msg)
234 189 return self._local
235 190
236 191 @util.propertycache
237 192 def localctx(self):
238 193 return self._repo[self.local]
239 194
240 195 @util.propertycache
241 196 def other(self):
242 197 if self._other is None:
243 198 msg = b"other accessed but self._other isn't set"
244 199 raise error.ProgrammingError(msg)
245 200 return self._other
246 201
247 202 @util.propertycache
248 203 def otherctx(self):
249 204 return self._repo[self.other]
250 205
251 206 def active(self):
252 207 """Whether mergestate is active.
253 208
254 209 Returns True if there appears to be mergestate. This is a rough proxy
255 210 for "is a merge in progress."
256 211 """
257 212 return bool(self._local) or bool(self._state)
258 213
259 214 def commit(self):
260 215 """Write current state on disk (if necessary)"""
261 216
262 217 @staticmethod
263 218 def getlocalkey(path):
264 219 """hash the path of a local file context for storage in the .hg/merge
265 220 directory."""
266 221
267 222 return hex(hashutil.sha1(path).digest())
268 223
269 224 def _make_backup(self, fctx, localkey):
270 225 raise NotImplementedError()
271 226
272 227 def _restore_backup(self, fctx, localkey, flags):
273 228 raise NotImplementedError()
274 229
275 230 def add(self, fcl, fco, fca, fd):
276 231 """add a new (potentially?) conflicting file the merge state
277 232 fcl: file context for local,
278 233 fco: file context for remote,
279 234 fca: file context for ancestors,
280 235 fd: file path of the resulting merge.
281 236
282 237 note: also write the local version to the `.hg/merge` directory.
283 238 """
284 239 if fcl.isabsent():
285 240 localkey = nullhex
286 241 else:
287 242 localkey = mergestate.getlocalkey(fcl.path())
288 243 self._make_backup(fcl, localkey)
289 244 self._state[fd] = [
290 245 MERGE_RECORD_UNRESOLVED,
291 246 localkey,
292 247 fcl.path(),
293 248 fca.path(),
294 249 hex(fca.filenode()),
295 250 fco.path(),
296 251 hex(fco.filenode()),
297 252 fcl.flags(),
298 253 ]
299 254 self._stateextras[fd] = {b'ancestorlinknode': hex(fca.node())}
300 255 self._dirty = True
301 256
302 257 def addpathconflict(self, path, frename, forigin):
303 258 """add a new conflicting path to the merge state
304 259 path: the path that conflicts
305 260 frename: the filename the conflicting file was renamed to
306 261 forigin: origin of the file ('l' or 'r' for local/remote)
307 262 """
308 263 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
309 264 self._dirty = True
310 265
311 266 def addcommitinfo(self, path, data):
312 267 """ stores information which is required at commit
313 268 into _stateextras """
314 269 self._stateextras[path].update(data)
315 270 self._dirty = True
316 271
317 272 def __contains__(self, dfile):
318 273 return dfile in self._state
319 274
320 275 def __getitem__(self, dfile):
321 276 return self._state[dfile][0]
322 277
323 278 def __iter__(self):
324 279 return iter(sorted(self._state))
325 280
326 281 def files(self):
327 282 return self._state.keys()
328 283
329 284 def mark(self, dfile, state):
330 285 self._state[dfile][0] = state
331 286 self._dirty = True
332 287
333 def mdstate(self):
334 return self._mdstate
335
336 288 def unresolved(self):
337 289 """Obtain the paths of unresolved files."""
338 290
339 291 for f, entry in pycompat.iteritems(self._state):
340 292 if entry[0] in (
341 293 MERGE_RECORD_UNRESOLVED,
342 294 MERGE_RECORD_UNRESOLVED_PATH,
343 295 ):
344 296 yield f
345 297
346 def driverresolved(self):
347 """Obtain the paths of driver-resolved files."""
348
349 for f, entry in self._state.items():
350 if entry[0] == MERGE_RECORD_DRIVER_RESOLVED:
351 yield f
352
353 298 def extras(self, filename):
354 299 return self._stateextras[filename]
355 300
356 301 def _resolve(self, preresolve, dfile, wctx):
357 302 """rerun merge process for file path `dfile`.
358 303 Returns whether the merge was completed and the return value of merge
359 304 obtained from filemerge._filemerge().
360 305 """
361 if self[dfile] in (MERGE_RECORD_RESOLVED, MERGE_RECORD_DRIVER_RESOLVED):
306 if self[dfile] in (
307 MERGE_RECORD_RESOLVED,
308 LEGACY_RECORD_DRIVER_RESOLVED,
309 ):
362 310 return True, 0
363 311 stateentry = self._state[dfile]
364 312 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
365 313 octx = self._repo[self._other]
366 314 extras = self.extras(dfile)
367 315 anccommitnode = extras.get(b'ancestorlinknode')
368 316 if anccommitnode:
369 317 actx = self._repo[anccommitnode]
370 318 else:
371 319 actx = None
372 320 fcd = _filectxorabsent(localkey, wctx, dfile)
373 321 fco = _filectxorabsent(onode, octx, ofile)
374 322 # TODO: move this to filectxorabsent
375 323 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
376 324 # "premerge" x flags
377 325 flo = fco.flags()
378 326 fla = fca.flags()
379 327 if b'x' in flags + flo + fla and b'l' not in flags + flo + fla:
380 328 if fca.node() == nullid and flags != flo:
381 329 if preresolve:
382 330 self._repo.ui.warn(
383 331 _(
384 332 b'warning: cannot merge flags for %s '
385 333 b'without common ancestor - keeping local flags\n'
386 334 )
387 335 % afile
388 336 )
389 337 elif flags == fla:
390 338 flags = flo
391 339 if preresolve:
392 340 # restore local
393 341 if localkey != nullhex:
394 342 self._restore_backup(wctx[dfile], localkey, flags)
395 343 else:
396 344 wctx[dfile].remove(ignoremissing=True)
397 345 complete, merge_ret, deleted = filemerge.premerge(
398 346 self._repo,
399 347 wctx,
400 348 self._local,
401 349 lfile,
402 350 fcd,
403 351 fco,
404 352 fca,
405 353 labels=self._labels,
406 354 )
407 355 else:
408 356 complete, merge_ret, deleted = filemerge.filemerge(
409 357 self._repo,
410 358 wctx,
411 359 self._local,
412 360 lfile,
413 361 fcd,
414 362 fco,
415 363 fca,
416 364 labels=self._labels,
417 365 )
418 366 if merge_ret is None:
419 367 # If return value of merge is None, then there are no real conflict
420 368 del self._state[dfile]
421 369 self._stateextras.pop(dfile, None)
422 370 self._dirty = True
423 371 elif not merge_ret:
424 372 self.mark(dfile, MERGE_RECORD_RESOLVED)
425 373
426 374 if complete:
427 375 action = None
428 376 if deleted:
429 377 if fcd.isabsent():
430 378 # dc: local picked. Need to drop if present, which may
431 379 # happen on re-resolves.
432 380 action = ACTION_FORGET
433 381 else:
434 382 # cd: remote picked (or otherwise deleted)
435 383 action = ACTION_REMOVE
436 384 else:
437 385 if fcd.isabsent(): # dc: remote picked
438 386 action = ACTION_GET
439 387 elif fco.isabsent(): # cd: local picked
440 388 if dfile in self.localctx:
441 389 action = ACTION_ADD_MODIFIED
442 390 else:
443 391 action = ACTION_ADD
444 392 # else: regular merges (no action necessary)
445 393 self._results[dfile] = merge_ret, action
446 394
447 395 return complete, merge_ret
448 396
449 397 def preresolve(self, dfile, wctx):
450 398 """run premerge process for dfile
451 399
452 400 Returns whether the merge is complete, and the exit code."""
453 401 return self._resolve(True, dfile, wctx)
454 402
455 403 def resolve(self, dfile, wctx):
456 404 """run merge process (assuming premerge was run) for dfile
457 405
458 406 Returns the exit code of the merge."""
459 407 return self._resolve(False, dfile, wctx)[1]
460 408
461 409 def counts(self):
462 410 """return counts for updated, merged and removed files in this
463 411 session"""
464 412 updated, merged, removed = 0, 0, 0
465 413 for r, action in pycompat.itervalues(self._results):
466 414 if r is None:
467 415 updated += 1
468 416 elif r == 0:
469 417 if action == ACTION_REMOVE:
470 418 removed += 1
471 419 else:
472 420 merged += 1
473 421 return updated, merged, removed
474 422
475 423 def unresolvedcount(self):
476 424 """get unresolved count for this merge (persistent)"""
477 425 return len(list(self.unresolved()))
478 426
479 427 def actions(self):
480 428 """return lists of actions to perform on the dirstate"""
481 429 actions = {
482 430 ACTION_REMOVE: [],
483 431 ACTION_FORGET: [],
484 432 ACTION_ADD: [],
485 433 ACTION_ADD_MODIFIED: [],
486 434 ACTION_GET: [],
487 435 }
488 436 for f, (r, action) in pycompat.iteritems(self._results):
489 437 if action is not None:
490 438 actions[action].append((f, None, b"merge result"))
491 439 return actions
492 440
493 def queueremove(self, f):
494 """queues a file to be removed from the dirstate
495
496 Meant for use by custom merge drivers."""
497 self._results[f] = 0, ACTION_REMOVE
498
499 def queueadd(self, f):
500 """queues a file to be added to the dirstate
501
502 Meant for use by custom merge drivers."""
503 self._results[f] = 0, ACTION_ADD
504
505 def queueget(self, f):
506 """queues a file to be marked modified in the dirstate
507
508 Meant for use by custom merge drivers."""
509 self._results[f] = 0, ACTION_GET
510
511 441
512 442 class mergestate(_mergestate_base):
513 443
514 444 statepathv1 = b'merge/state'
515 445 statepathv2 = b'merge/state2'
516 446
517 447 @staticmethod
518 448 def clean(repo):
519 449 """Initialize a brand new merge state, removing any existing state on
520 450 disk."""
521 451 ms = mergestate(repo)
522 452 ms.reset()
523 453 return ms
524 454
525 455 @staticmethod
526 456 def read(repo):
527 457 """Initialize the merge state, reading it from disk."""
528 458 ms = mergestate(repo)
529 459 ms._read()
530 460 return ms
531 461
532 462 def _read(self):
533 463 """Analyse each record content to restore a serialized state from disk
534 464
535 465 This function process "record" entry produced by the de-serialization
536 466 of on disk file.
537 467 """
538 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
539 468 unsupported = set()
540 469 records = self._readrecords()
541 470 for rtype, record in records:
542 471 if rtype == RECORD_LOCAL:
543 472 self._local = bin(record)
544 473 elif rtype == RECORD_OTHER:
545 474 self._other = bin(record)
546 elif rtype == RECORD_MERGE_DRIVER_STATE:
547 bits = record.split(b'\0', 1)
548 mdstate = bits[1]
549 if len(mdstate) != 1 or mdstate not in (
550 MERGE_DRIVER_STATE_UNMARKED,
551 MERGE_DRIVER_STATE_MARKED,
552 MERGE_DRIVER_STATE_SUCCESS,
553 ):
554 # the merge driver should be idempotent, so just rerun it
555 mdstate = MERGE_DRIVER_STATE_UNMARKED
556
557 self._readmergedriver = bits[0]
558 self._mdstate = mdstate
475 elif rtype == LEGACY_MERGE_DRIVER_STATE:
476 pass
559 477 elif rtype in (
560 478 RECORD_MERGED,
561 479 RECORD_CHANGEDELETE_CONFLICT,
562 480 RECORD_PATH_CONFLICT,
563 RECORD_MERGE_DRIVER_MERGE,
481 LEGACY_MERGE_DRIVER_MERGE,
564 482 LEGACY_RECORD_RESOLVED_OTHER,
565 483 ):
566 484 bits = record.split(b'\0')
567 485 # merge entry type MERGE_RECORD_MERGED_OTHER is deprecated
568 486 # and we now store related information in _stateextras, so
569 487 # lets write to _stateextras directly
570 488 if bits[1] == MERGE_RECORD_MERGED_OTHER:
571 489 self._stateextras[bits[0]][b'filenode-source'] = b'other'
572 490 else:
573 491 self._state[bits[0]] = bits[1:]
574 492 elif rtype == RECORD_FILE_VALUES:
575 493 filename, rawextras = record.split(b'\0', 1)
576 494 extraparts = rawextras.split(b'\0')
577 495 extras = {}
578 496 i = 0
579 497 while i < len(extraparts):
580 498 extras[extraparts[i]] = extraparts[i + 1]
581 499 i += 2
582 500
583 501 self._stateextras[filename] = extras
584 502 elif rtype == RECORD_LABELS:
585 503 labels = record.split(b'\0', 2)
586 504 self._labels = [l for l in labels if len(l) > 0]
587 505 elif not rtype.islower():
588 506 unsupported.add(rtype)
589 507
590 508 if unsupported:
591 509 raise error.UnsupportedMergeRecords(unsupported)
592 510
593 511 def _readrecords(self):
594 512 """Read merge state from disk and return a list of record (TYPE, data)
595 513
596 514 We read data from both v1 and v2 files and decide which one to use.
597 515
598 516 V1 has been used by version prior to 2.9.1 and contains less data than
599 517 v2. We read both versions and check if no data in v2 contradicts
600 518 v1. If there is not contradiction we can safely assume that both v1
601 519 and v2 were written at the same time and use the extract data in v2. If
602 520 there is contradiction we ignore v2 content as we assume an old version
603 521 of Mercurial has overwritten the mergestate file and left an old v2
604 522 file around.
605 523
606 524 returns list of record [(TYPE, data), ...]"""
607 525 v1records = self._readrecordsv1()
608 526 v2records = self._readrecordsv2()
609 527 if self._v1v2match(v1records, v2records):
610 528 return v2records
611 529 else:
612 530 # v1 file is newer than v2 file, use it
613 531 # we have to infer the "other" changeset of the merge
614 532 # we cannot do better than that with v1 of the format
615 533 mctx = self._repo[None].parents()[-1]
616 534 v1records.append((RECORD_OTHER, mctx.hex()))
617 535 # add place holder "other" file node information
618 536 # nobody is using it yet so we do no need to fetch the data
619 537 # if mctx was wrong `mctx[bits[-2]]` may fails.
620 538 for idx, r in enumerate(v1records):
621 539 if r[0] == RECORD_MERGED:
622 540 bits = r[1].split(b'\0')
623 541 bits.insert(-2, b'')
624 542 v1records[idx] = (r[0], b'\0'.join(bits))
625 543 return v1records
626 544
627 545 def _v1v2match(self, v1records, v2records):
628 546 oldv2 = set() # old format version of v2 record
629 547 for rec in v2records:
630 548 if rec[0] == RECORD_LOCAL:
631 549 oldv2.add(rec)
632 550 elif rec[0] == RECORD_MERGED:
633 551 # drop the onode data (not contained in v1)
634 552 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
635 553 for rec in v1records:
636 554 if rec not in oldv2:
637 555 return False
638 556 else:
639 557 return True
640 558
641 559 def _readrecordsv1(self):
642 560 """read on disk merge state for version 1 file
643 561
644 562 returns list of record [(TYPE, data), ...]
645 563
646 564 Note: the "F" data from this file are one entry short
647 565 (no "other file node" entry)
648 566 """
649 567 records = []
650 568 try:
651 569 f = self._repo.vfs(self.statepathv1)
652 570 for i, l in enumerate(f):
653 571 if i == 0:
654 572 records.append((RECORD_LOCAL, l[:-1]))
655 573 else:
656 574 records.append((RECORD_MERGED, l[:-1]))
657 575 f.close()
658 576 except IOError as err:
659 577 if err.errno != errno.ENOENT:
660 578 raise
661 579 return records
662 580
663 581 def _readrecordsv2(self):
664 582 """read on disk merge state for version 2 file
665 583
666 584 This format is a list of arbitrary records of the form:
667 585
668 586 [type][length][content]
669 587
670 588 `type` is a single character, `length` is a 4 byte integer, and
671 589 `content` is an arbitrary byte sequence of length `length`.
672 590
673 591 Mercurial versions prior to 3.7 have a bug where if there are
674 592 unsupported mandatory merge records, attempting to clear out the merge
675 593 state with hg update --clean or similar aborts. The 't' record type
676 594 works around that by writing out what those versions treat as an
677 595 advisory record, but later versions interpret as special: the first
678 596 character is the 'real' record type and everything onwards is the data.
679 597
680 598 Returns list of records [(TYPE, data), ...]."""
681 599 records = []
682 600 try:
683 601 f = self._repo.vfs(self.statepathv2)
684 602 data = f.read()
685 603 off = 0
686 604 end = len(data)
687 605 while off < end:
688 606 rtype = data[off : off + 1]
689 607 off += 1
690 608 length = _unpack(b'>I', data[off : (off + 4)])[0]
691 609 off += 4
692 610 record = data[off : (off + length)]
693 611 off += length
694 612 if rtype == RECORD_OVERRIDE:
695 613 rtype, record = record[0:1], record[1:]
696 614 records.append((rtype, record))
697 615 f.close()
698 616 except IOError as err:
699 617 if err.errno != errno.ENOENT:
700 618 raise
701 619 return records
702 620
703 621 def commit(self):
704 622 if self._dirty:
705 623 records = self._makerecords()
706 624 self._writerecords(records)
707 625 self._dirty = False
708 626
709 627 def _makerecords(self):
710 628 records = []
711 629 records.append((RECORD_LOCAL, hex(self._local)))
712 630 records.append((RECORD_OTHER, hex(self._other)))
713 if self.mergedriver:
714 records.append(
715 (
716 RECORD_MERGE_DRIVER_STATE,
717 b'\0'.join([self.mergedriver, self._mdstate]),
718 )
719 )
720 631 # Write out state items. In all cases, the value of the state map entry
721 632 # is written as the contents of the record. The record type depends on
722 633 # the type of state that is stored, and capital-letter records are used
723 634 # to prevent older versions of Mercurial that do not support the feature
724 635 # from loading them.
725 636 for filename, v in pycompat.iteritems(self._state):
726 if v[0] == MERGE_RECORD_DRIVER_RESOLVED:
727 # Driver-resolved merge. These are stored in 'D' records.
728 records.append(
729 (RECORD_MERGE_DRIVER_MERGE, b'\0'.join([filename] + v))
730 )
731 elif v[0] in (
637 if v[0] in (
732 638 MERGE_RECORD_UNRESOLVED_PATH,
733 639 MERGE_RECORD_RESOLVED_PATH,
734 640 ):
735 641 # Path conflicts. These are stored in 'P' records. The current
736 642 # resolution state ('pu' or 'pr') is stored within the record.
737 643 records.append(
738 644 (RECORD_PATH_CONFLICT, b'\0'.join([filename] + v))
739 645 )
740 646 elif v[1] == nullhex or v[6] == nullhex:
741 647 # Change/Delete or Delete/Change conflicts. These are stored in
742 648 # 'C' records. v[1] is the local file, and is nullhex when the
743 649 # file is deleted locally ('dc'). v[6] is the remote file, and
744 650 # is nullhex when the file is deleted remotely ('cd').
745 651 records.append(
746 652 (RECORD_CHANGEDELETE_CONFLICT, b'\0'.join([filename] + v))
747 653 )
748 654 else:
749 655 # Normal files. These are stored in 'F' records.
750 656 records.append((RECORD_MERGED, b'\0'.join([filename] + v)))
751 657 for filename, extras in sorted(pycompat.iteritems(self._stateextras)):
752 658 rawextras = b'\0'.join(
753 659 b'%s\0%s' % (k, v) for k, v in pycompat.iteritems(extras)
754 660 )
755 661 records.append(
756 662 (RECORD_FILE_VALUES, b'%s\0%s' % (filename, rawextras))
757 663 )
758 664 if self._labels is not None:
759 665 labels = b'\0'.join(self._labels)
760 666 records.append((RECORD_LABELS, labels))
761 667 return records
762 668
763 669 def _writerecords(self, records):
764 670 """Write current state on disk (both v1 and v2)"""
765 671 self._writerecordsv1(records)
766 672 self._writerecordsv2(records)
767 673
768 674 def _writerecordsv1(self, records):
769 675 """Write current state on disk in a version 1 file"""
770 676 f = self._repo.vfs(self.statepathv1, b'wb')
771 677 irecords = iter(records)
772 678 lrecords = next(irecords)
773 679 assert lrecords[0] == RECORD_LOCAL
774 680 f.write(hex(self._local) + b'\n')
775 681 for rtype, data in irecords:
776 682 if rtype == RECORD_MERGED:
777 683 f.write(b'%s\n' % _droponode(data))
778 684 f.close()
779 685
780 686 def _writerecordsv2(self, records):
781 687 """Write current state on disk in a version 2 file
782 688
783 689 See the docstring for _readrecordsv2 for why we use 't'."""
784 690 # these are the records that all version 2 clients can read
785 691 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED)
786 692 f = self._repo.vfs(self.statepathv2, b'wb')
787 693 for key, data in records:
788 694 assert len(key) == 1
789 695 if key not in allowlist:
790 696 key, data = RECORD_OVERRIDE, b'%s%s' % (key, data)
791 697 format = b'>sI%is' % len(data)
792 698 f.write(_pack(format, key, len(data), data))
793 699 f.close()
794 700
795 701 def _make_backup(self, fctx, localkey):
796 702 self._repo.vfs.write(b'merge/' + localkey, fctx.data())
797 703
798 704 def _restore_backup(self, fctx, localkey, flags):
799 705 with self._repo.vfs(b'merge/' + localkey) as f:
800 706 fctx.write(f.read(), flags)
801 707
802 708 def reset(self):
803 709 shutil.rmtree(self._repo.vfs.join(b'merge'), True)
804 710
805 711
806 712 class memmergestate(_mergestate_base):
807 713 def __init__(self, repo):
808 714 super(memmergestate, self).__init__(repo)
809 715 self._backups = {}
810 716
811 717 def _make_backup(self, fctx, localkey):
812 718 self._backups[localkey] = fctx.data()
813 719
814 720 def _restore_backup(self, fctx, localkey, flags):
815 721 fctx.write(self._backups[localkey], flags)
816 722
817 @util.propertycache
818 def mergedriver(self):
819 configmergedriver = self._repo.ui.config(
820 b'experimental', b'mergedriver'
821 )
822 if configmergedriver:
823 raise error.InMemoryMergeConflictsError(
824 b"in-memory merge does not support mergedriver"
825 )
826 return None
827
828 723
829 724 def recordupdates(repo, actions, branchmerge, getfiledata):
830 725 """record merge actions to the dirstate"""
831 726 # remove (must come first)
832 727 for f, args, msg in actions.get(ACTION_REMOVE, []):
833 728 if branchmerge:
834 729 repo.dirstate.remove(f)
835 730 else:
836 731 repo.dirstate.drop(f)
837 732
838 733 # forget (must come first)
839 734 for f, args, msg in actions.get(ACTION_FORGET, []):
840 735 repo.dirstate.drop(f)
841 736
842 737 # resolve path conflicts
843 738 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
844 739 (f0, origf0) = args
845 740 repo.dirstate.add(f)
846 741 repo.dirstate.copy(origf0, f)
847 742 if f0 == origf0:
848 743 repo.dirstate.remove(f0)
849 744 else:
850 745 repo.dirstate.drop(f0)
851 746
852 747 # re-add
853 748 for f, args, msg in actions.get(ACTION_ADD, []):
854 749 repo.dirstate.add(f)
855 750
856 751 # re-add/mark as modified
857 752 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
858 753 if branchmerge:
859 754 repo.dirstate.normallookup(f)
860 755 else:
861 756 repo.dirstate.add(f)
862 757
863 758 # exec change
864 759 for f, args, msg in actions.get(ACTION_EXEC, []):
865 760 repo.dirstate.normallookup(f)
866 761
867 762 # keep
868 763 for f, args, msg in actions.get(ACTION_KEEP, []):
869 764 pass
870 765
871 766 # keep deleted
872 767 for f, args, msg in actions.get(ACTION_KEEP_ABSENT, []):
873 768 pass
874 769
875 770 # get
876 771 for f, args, msg in actions.get(ACTION_GET, []):
877 772 if branchmerge:
878 773 repo.dirstate.otherparent(f)
879 774 else:
880 775 parentfiledata = getfiledata[f] if getfiledata else None
881 776 repo.dirstate.normal(f, parentfiledata=parentfiledata)
882 777
883 778 # merge
884 779 for f, args, msg in actions.get(ACTION_MERGE, []):
885 780 f1, f2, fa, move, anc = args
886 781 if branchmerge:
887 782 # We've done a branch merge, mark this file as merged
888 783 # so that we properly record the merger later
889 784 repo.dirstate.merge(f)
890 785 if f1 != f2: # copy/rename
891 786 if move:
892 787 repo.dirstate.remove(f1)
893 788 if f1 != f:
894 789 repo.dirstate.copy(f1, f)
895 790 else:
896 791 repo.dirstate.copy(f2, f)
897 792 else:
898 793 # We've update-merged a locally modified file, so
899 794 # we set the dirstate to emulate a normal checkout
900 795 # of that file some time in the past. Thus our
901 796 # merge will appear as a normal local file
902 797 # modification.
903 798 if f2 == f: # file not locally copied/moved
904 799 repo.dirstate.normallookup(f)
905 800 if move:
906 801 repo.dirstate.drop(f1)
907 802
908 803 # directory rename, move local
909 804 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
910 805 f0, flag = args
911 806 if branchmerge:
912 807 repo.dirstate.add(f)
913 808 repo.dirstate.remove(f0)
914 809 repo.dirstate.copy(f0, f)
915 810 else:
916 811 repo.dirstate.normal(f)
917 812 repo.dirstate.drop(f0)
918 813
919 814 # directory rename, get
920 815 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
921 816 f0, flag = args
922 817 if branchmerge:
923 818 repo.dirstate.add(f)
924 819 repo.dirstate.copy(f0, f)
925 820 else:
926 821 repo.dirstate.normal(f)
@@ -1,24 +1,19 b''
1 1 # mergeutil.py - help for merge processing in 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 from .i18n import _
11 11
12 12 from . import error
13 13
14 14
15 15 def checkunresolved(ms):
16 16 if list(ms.unresolved()):
17 17 raise error.Abort(
18 18 _(b"unresolved merge conflicts (see 'hg help resolve')")
19 19 )
20 if ms.mdstate() != b's' or list(ms.driverresolved()):
21 raise error.Abort(
22 _(b'driver-resolved merge conflicts'),
23 hint=_(b'run "hg resolve --all" to resolve'),
24 )
@@ -1,796 +1,738 b''
1 1 test that a commit clears the merge state.
2 2
3 3 $ hg init repo
4 4 $ cd repo
5 5
6 6 $ echo foo > file1
7 7 $ echo foo > file2
8 8 $ hg commit -Am 'add files'
9 9 adding file1
10 10 adding file2
11 11
12 12 $ echo bar >> file1
13 13 $ echo bar >> file2
14 14 $ hg commit -Am 'append bar to files'
15 15
16 16 create a second head with conflicting edits
17 17
18 18 $ hg up -C 0
19 19 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
20 20 $ echo baz >> file1
21 21 $ echo baz >> file2
22 22 $ hg commit -Am 'append baz to files'
23 23 created new head
24 24
25 25 create a third head with no conflicting edits
26 26 $ hg up -qC 0
27 27 $ echo foo > file3
28 28 $ hg commit -Am 'add non-conflicting file'
29 29 adding file3
30 30 created new head
31 31
32 32 failing merge
33 33
34 34 $ hg up -qC 2
35 35 $ hg merge --tool=internal:fail 1
36 36 0 files updated, 0 files merged, 0 files removed, 2 files unresolved
37 37 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
38 38 [1]
39 39
40 40 resolve -l should contain unresolved entries
41 41
42 42 $ hg resolve -l
43 43 U file1
44 44 U file2
45 45
46 46 $ hg resolve -l --no-status
47 47 file1
48 48 file2
49 49
50 50 resolving an unknown path should emit a warning, but not for -l
51 51
52 52 $ hg resolve -m does-not-exist
53 53 arguments do not match paths that need resolving
54 54 $ hg resolve -l does-not-exist
55 55
56 56 tell users how they could have used resolve
57 57
58 58 $ mkdir nested
59 59 $ cd nested
60 60 $ hg resolve -m file1
61 61 arguments do not match paths that need resolving
62 62 (try: hg resolve -m path:file1)
63 63 $ hg resolve -m file1 filez
64 64 arguments do not match paths that need resolving
65 65 (try: hg resolve -m path:file1 path:filez)
66 66 $ hg resolve -m path:file1 path:filez
67 67 $ hg resolve -l
68 68 R file1
69 69 U file2
70 70 $ hg resolve -l --config ui.relative-paths=yes
71 71 R ../file1
72 72 U ../file2
73 73 $ hg resolve --re-merge filez file2
74 74 arguments do not match paths that need resolving
75 75 (try: hg resolve --re-merge path:filez path:file2)
76 76 $ hg resolve -m filez file2
77 77 arguments do not match paths that need resolving
78 78 (try: hg resolve -m path:filez path:file2)
79 79 $ hg resolve -m path:filez path:file2
80 80 (no more unresolved files)
81 81 $ hg resolve -l
82 82 R file1
83 83 R file2
84 84
85 85 cleanup
86 86 $ hg resolve -u
87 87 $ cd ..
88 88 $ rmdir nested
89 89
90 don't allow marking or unmarking driver-resolved files
91
92 $ cat > $TESTTMP/markdriver.py << EOF
93 > '''mark and unmark files as driver-resolved'''
94 > from mercurial import (
95 > mergestate,
96 > pycompat,
97 > registrar,
98 > scmutil,
99 > )
100 > cmdtable = {}
101 > command = registrar.command(cmdtable)
102 > @command(b'markdriver',
103 > [(b'u', b'unmark', None, b'')],
104 > b'FILE...')
105 > def markdriver(ui, repo, *pats, **opts):
106 > wlock = repo.wlock()
107 > opts = pycompat.byteskwargs(opts)
108 > try:
109 > ms = mergestate.mergestate.read(repo)
110 > m = scmutil.match(repo[None], pats, opts)
111 > for f in ms:
112 > if not m(f):
113 > continue
114 > if not opts[b'unmark']:
115 > ms.mark(f, b'd')
116 > else:
117 > ms.mark(f, b'u')
118 > ms.commit()
119 > finally:
120 > wlock.release()
121 > EOF
122 $ hg --config extensions.markdriver=$TESTTMP/markdriver.py markdriver file1
123 $ hg resolve --list
124 D file1
125 U file2
126 $ hg resolve --mark file1
127 not marking file1 as it is driver-resolved
128 this should not print out file1
129 $ hg resolve --mark --all
130 (no more unresolved files -- run "hg resolve --all" to conclude)
131 $ hg resolve --mark 'glob:file*'
132 (no more unresolved files -- run "hg resolve --all" to conclude)
133 $ hg resolve --list
134 D file1
135 R file2
136 $ hg resolve --unmark file1
137 not unmarking file1 as it is driver-resolved
138 (no more unresolved files -- run "hg resolve --all" to conclude)
139 $ hg resolve --unmark --all
140 $ hg resolve --list
141 D file1
142 U file2
143 $ hg --config extensions.markdriver=$TESTTMP/markdriver.py markdriver --unmark file1
144 $ hg resolve --list
145 U file1
146 U file2
147
148 90 resolve the failure
149 91
150 92 $ echo resolved > file1
151 93 $ hg resolve -m file1
152 94
153 95 resolve -l should show resolved file as resolved
154 96
155 97 $ hg resolve -l
156 98 R file1
157 99 U file2
158 100
159 101 $ hg resolve -l -Tjson
160 102 [
161 103 {
162 104 "mergestatus": "R",
163 105 "path": "file1"
164 106 },
165 107 {
166 108 "mergestatus": "U",
167 109 "path": "file2"
168 110 }
169 111 ]
170 112
171 113 $ hg resolve -l -T '{path} {mergestatus} {status} {p1rev} {p2rev}\n'
172 114 file1 R M 2 1
173 115 file2 U M 2 1
174 116
175 117 resolve -m without paths should mark all resolved
176 118
177 119 $ hg resolve -m
178 120 (no more unresolved files)
179 121 $ hg commit -m 'resolved'
180 122
181 123 resolve -l should be empty after commit
182 124
183 125 $ hg resolve -l
184 126
185 127 $ hg resolve -l -Tjson
186 128 [
187 129 ]
188 130
189 131 resolve --all should abort when no merge in progress
190 132
191 133 $ hg resolve --all
192 134 abort: resolve command not applicable when not merging
193 135 [255]
194 136
195 137 resolve -m should abort when no merge in progress
196 138
197 139 $ hg resolve -m
198 140 abort: resolve command not applicable when not merging
199 141 [255]
200 142
201 143 can not update or merge when there are unresolved conflicts
202 144
203 145 $ hg up -qC 0
204 146 $ echo quux >> file1
205 147 $ hg up 1
206 148 merging file1
207 149 warning: conflicts while merging file1! (edit, then use 'hg resolve --mark')
208 150 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
209 151 use 'hg resolve' to retry unresolved file merges
210 152 [1]
211 153 $ hg up 0
212 154 abort: outstanding merge conflicts
213 155 (use 'hg resolve' to resolve)
214 156 [255]
215 157 $ hg merge 2
216 158 abort: outstanding merge conflicts
217 159 (use 'hg resolve' to resolve)
218 160 [255]
219 161 $ hg merge --force 2
220 162 abort: outstanding merge conflicts
221 163 (use 'hg resolve' to resolve)
222 164 [255]
223 165
224 166 set up conflict-free merge
225 167
226 168 $ hg up -qC 3
227 169 $ hg merge 1
228 170 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
229 171 (branch merge, don't forget to commit)
230 172
231 173 resolve --all should do nothing in merge without conflicts
232 174 $ hg resolve --all
233 175 (no more unresolved files)
234 176
235 177 resolve -m should do nothing in merge without conflicts
236 178
237 179 $ hg resolve -m
238 180 (no more unresolved files)
239 181
240 182 get back to conflicting state
241 183
242 184 $ hg up -qC 2
243 185 $ hg merge --tool=internal:fail 1
244 186 0 files updated, 0 files merged, 0 files removed, 2 files unresolved
245 187 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
246 188 [1]
247 189
248 190 resolve without arguments should suggest --all
249 191 $ hg resolve
250 192 abort: no files or directories specified
251 193 (use --all to re-merge all unresolved files)
252 194 [255]
253 195
254 196 resolve --all should re-merge all unresolved files
255 197 $ hg resolve --all
256 198 merging file1
257 199 merging file2
258 200 warning: conflicts while merging file1! (edit, then use 'hg resolve --mark')
259 201 warning: conflicts while merging file2! (edit, then use 'hg resolve --mark')
260 202 [1]
261 203 $ cat file1.orig
262 204 foo
263 205 baz
264 206 $ cat file2.orig
265 207 foo
266 208 baz
267 209
268 210 .orig files should exists where specified
269 211 $ hg resolve --all --verbose --config 'ui.origbackuppath=.hg/origbackups'
270 212 merging file1
271 213 creating directory: $TESTTMP/repo/.hg/origbackups
272 214 merging file2
273 215 warning: conflicts while merging file1! (edit, then use 'hg resolve --mark')
274 216 warning: conflicts while merging file2! (edit, then use 'hg resolve --mark')
275 217 [1]
276 218 $ ls .hg/origbackups
277 219 file1
278 220 file2
279 221 $ grep '<<<' file1 > /dev/null
280 222 $ grep '<<<' file2 > /dev/null
281 223
282 224 resolve <file> should re-merge file
283 225 $ echo resolved > file1
284 226 $ hg resolve -q file1
285 227 warning: conflicts while merging file1! (edit, then use 'hg resolve --mark')
286 228 [1]
287 229 $ grep '<<<' file1 > /dev/null
288 230
289 231 test .orig behavior with resolve
290 232
291 233 $ hg resolve -q file1 --tool "sh -c 'f --dump \"$TESTTMP/repo/file1.orig\"'"
292 234 $TESTTMP/repo/file1.orig:
293 235 >>>
294 236 foo
295 237 baz
296 238 <<<
297 239
298 240 resolve <file> should do nothing if 'file' was marked resolved
299 241 $ echo resolved > file1
300 242 $ hg resolve -m file1
301 243 $ hg resolve -q file1
302 244 $ cat file1
303 245 resolved
304 246
305 247 insert unsupported advisory merge record
306 248
307 249 $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -x
308 250 $ hg debugmergestate
309 251 local (working copy): 57653b9f834a4493f7240b0681efcb9ae7cab745
310 252 other (merge rev): dc77451844e37f03f5c559e3b8529b2b48d381d1
311 253 file: file1 (state "r")
312 254 local path: file1 (hash 60b27f004e454aca81b0480209cce5081ec52390, flags "")
313 255 ancestor path: file1 (node 2ed2a3912a0b24502043eae84ee4b279c18b90dd)
314 256 other path: file1 (node 6f4310b00b9a147241b071a60c28a650827fb03d)
315 257 extra: ancestorlinknode = 99726c03216e233810a2564cbc0adfe395007eac
316 258 file: file2 (state "u")
317 259 local path: file2 (hash cb99b709a1978bd205ab9dfd4c5aaa1fc91c7523, flags "")
318 260 ancestor path: file2 (node 2ed2a3912a0b24502043eae84ee4b279c18b90dd)
319 261 other path: file2 (node 6f4310b00b9a147241b071a60c28a650827fb03d)
320 262 extra: ancestorlinknode = 99726c03216e233810a2564cbc0adfe395007eac
321 263 $ hg resolve -l
322 264 R file1
323 265 U file2
324 266
325 267 test json output
326 268
327 269 $ hg debugmergestate -T json
328 270 [
329 271 {
330 272 "commits": [{"label": "working copy", "name": "local", "node": "57653b9f834a4493f7240b0681efcb9ae7cab745"}, {"label": "merge rev", "name": "other", "node": "dc77451844e37f03f5c559e3b8529b2b48d381d1"}],
331 273 "extras": [],
332 274 "files": [{"ancestor_node": "2ed2a3912a0b24502043eae84ee4b279c18b90dd", "ancestor_path": "file1", "extras": [{"key": "ancestorlinknode", "value": "99726c03216e233810a2564cbc0adfe395007eac"}], "local_flags": "", "local_key": "60b27f004e454aca81b0480209cce5081ec52390", "local_path": "file1", "other_node": "6f4310b00b9a147241b071a60c28a650827fb03d", "other_path": "file1", "path": "file1", "state": "r"}, {"ancestor_node": "2ed2a3912a0b24502043eae84ee4b279c18b90dd", "ancestor_path": "file2", "extras": [{"key": "ancestorlinknode", "value": "99726c03216e233810a2564cbc0adfe395007eac"}], "local_flags": "", "local_key": "cb99b709a1978bd205ab9dfd4c5aaa1fc91c7523", "local_path": "file2", "other_node": "6f4310b00b9a147241b071a60c28a650827fb03d", "other_path": "file2", "path": "file2", "state": "u"}]
333 275 }
334 276 ]
335 277
336 278
337 279 insert unsupported mandatory merge record
338 280
339 281 $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -X
340 282 $ hg debugmergestate
341 283 abort: unsupported merge state records: X
342 284 (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
343 285 [255]
344 286 $ hg resolve -l
345 287 abort: unsupported merge state records: X
346 288 (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
347 289 [255]
348 290 $ hg resolve -ma
349 291 abort: unsupported merge state records: X
350 292 (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
351 293 [255]
352 294 $ hg summary
353 295 warning: merge state has unsupported record types: X
354 296 parent: 2:57653b9f834a
355 297 append baz to files
356 298 parent: 1:dc77451844e3
357 299 append bar to files
358 300 branch: default
359 301 commit: 2 modified, 2 unknown (merge)
360 302 update: 2 new changesets (update)
361 303 phases: 5 draft
362 304
363 305 update --clean shouldn't abort on unsupported records
364 306
365 307 $ hg up -qC 1
366 308 $ hg debugmergestate
367 309 no merge state found
368 310
369 311 test crashed merge with empty mergestate
370 312
371 313 $ mkdir .hg/merge
372 314 $ touch .hg/merge/state
373 315
374 316 resolve -l should be empty
375 317
376 318 $ hg resolve -l
377 319
378 320 resolve -m can be configured to look for remaining conflict markers
379 321 $ hg up -qC 2
380 322 $ hg merge -q --tool=internal:merge 1
381 323 warning: conflicts while merging file1! (edit, then use 'hg resolve --mark')
382 324 warning: conflicts while merging file2! (edit, then use 'hg resolve --mark')
383 325 [1]
384 326 $ hg resolve -l
385 327 U file1
386 328 U file2
387 329 $ echo 'remove markers' > file1
388 330 $ hg --config commands.resolve.mark-check=abort resolve -m
389 331 warning: the following files still have conflict markers:
390 332 file2
391 333 abort: conflict markers detected
392 334 (use --all to mark anyway)
393 335 [255]
394 336 $ hg resolve -l
395 337 U file1
396 338 U file2
397 339 Try with --all from the hint
398 340 $ hg --config commands.resolve.mark-check=abort resolve -m --all
399 341 warning: the following files still have conflict markers:
400 342 file2
401 343 (no more unresolved files)
402 344 $ hg resolve -l
403 345 R file1
404 346 R file2
405 347 Test option value 'warn'
406 348 $ hg resolve --unmark
407 349 $ hg resolve -l
408 350 U file1
409 351 U file2
410 352 $ hg --config commands.resolve.mark-check=warn resolve -m
411 353 warning: the following files still have conflict markers:
412 354 file2
413 355 (no more unresolved files)
414 356 $ hg resolve -l
415 357 R file1
416 358 R file2
417 359 If the file is already marked as resolved, we don't warn about it
418 360 $ hg resolve --unmark file1
419 361 $ hg resolve -l
420 362 U file1
421 363 R file2
422 364 $ hg --config commands.resolve.mark-check=warn resolve -m
423 365 (no more unresolved files)
424 366 $ hg resolve -l
425 367 R file1
426 368 R file2
427 369 If the user passes an invalid value, we treat it as 'none'.
428 370 $ hg resolve --unmark
429 371 $ hg resolve -l
430 372 U file1
431 373 U file2
432 374 $ hg --config commands.resolve.mark-check=nope resolve -m
433 375 (no more unresolved files)
434 376 $ hg resolve -l
435 377 R file1
436 378 R file2
437 379 Test explicitly setting the option to 'none'
438 380 $ hg resolve --unmark
439 381 $ hg resolve -l
440 382 U file1
441 383 U file2
442 384 $ hg --config commands.resolve.mark-check=none resolve -m
443 385 (no more unresolved files)
444 386 $ hg resolve -l
445 387 R file1
446 388 R file2
447 389 Test with marking an explicit file as resolved, this should not abort (since
448 390 there's no --force flag, we have no way of combining --all with a filename)
449 391 $ hg resolve --unmark
450 392 $ hg resolve -l
451 393 U file1
452 394 U file2
453 395 (This downgrades to a warning since an explicit file was specified).
454 396 $ hg --config commands.resolve.mark-check=abort resolve -m file2
455 397 warning: the following files still have conflict markers:
456 398 file2
457 399 $ hg resolve -l
458 400 U file1
459 401 R file2
460 402 Testing the --re-merge flag
461 403 $ hg resolve --unmark file1
462 404 $ hg resolve -l
463 405 U file1
464 406 R file2
465 407 $ hg resolve --mark --re-merge
466 408 abort: too many actions specified
467 409 [255]
468 410 $ hg resolve --re-merge --all
469 411 merging file1
470 412 warning: conflicts while merging file1! (edit, then use 'hg resolve --mark')
471 413 [1]
472 414 Explicit re-merge
473 415 $ hg resolve --unmark file1
474 416 $ hg resolve --config commands.resolve.explicit-re-merge=1 --all
475 417 abort: no action specified
476 418 (use --mark, --unmark, --list or --re-merge)
477 419 [255]
478 420 $ hg resolve --config commands.resolve.explicit-re-merge=1 --re-merge --all
479 421 merging file1
480 422 warning: conflicts while merging file1! (edit, then use 'hg resolve --mark')
481 423 [1]
482 424
483 425 $ cd ..
484 426
485 427 ======================================================
486 428 Test 'hg resolve' confirm config option functionality |
487 429 ======================================================
488 430 $ cat >> $HGRCPATH << EOF
489 431 > [extensions]
490 432 > rebase=
491 433 > EOF
492 434
493 435 $ hg init repo2
494 436 $ cd repo2
495 437
496 438 $ echo boss > boss
497 439 $ hg ci -Am "add boss"
498 440 adding boss
499 441
500 442 $ for emp in emp1 emp2 emp3; do echo work > $emp; done;
501 443 $ hg ci -Aqm "added emp1 emp2 emp3"
502 444
503 445 $ hg up 0
504 446 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
505 447
506 448 $ for emp in emp1 emp2 emp3; do echo nowork > $emp; done;
507 449 $ hg ci -Aqm "added lazy emp1 emp2 emp3"
508 450
509 451 $ hg log -GT "{rev} {node|short} {firstline(desc)}\n"
510 452 @ 2 0acfd4a49af0 added lazy emp1 emp2 emp3
511 453 |
512 454 | o 1 f30f98a8181f added emp1 emp2 emp3
513 455 |/
514 456 o 0 88660038d466 add boss
515 457
516 458 $ hg rebase -s 1 -d 2
517 459 rebasing 1:f30f98a8181f "added emp1 emp2 emp3"
518 460 merging emp1
519 461 merging emp2
520 462 merging emp3
521 463 warning: conflicts while merging emp1! (edit, then use 'hg resolve --mark')
522 464 warning: conflicts while merging emp2! (edit, then use 'hg resolve --mark')
523 465 warning: conflicts while merging emp3! (edit, then use 'hg resolve --mark')
524 466 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
525 467 [1]
526 468
527 469 Test when commands.resolve.confirm config option is not set:
528 470 ===========================================================
529 471 $ hg resolve --all
530 472 merging emp1
531 473 merging emp2
532 474 merging emp3
533 475 warning: conflicts while merging emp1! (edit, then use 'hg resolve --mark')
534 476 warning: conflicts while merging emp2! (edit, then use 'hg resolve --mark')
535 477 warning: conflicts while merging emp3! (edit, then use 'hg resolve --mark')
536 478 [1]
537 479
538 480 Test when config option is set:
539 481 ==============================
540 482 $ cat >> .hg/hgrc << EOF
541 483 > [ui]
542 484 > interactive = True
543 485 > [commands]
544 486 > resolve.confirm = True
545 487 > EOF
546 488
547 489 $ hg resolve
548 490 abort: no files or directories specified
549 491 (use --all to re-merge all unresolved files)
550 492 [255]
551 493 $ hg resolve --all << EOF
552 494 > n
553 495 > EOF
554 496 re-merge all unresolved files (yn)? n
555 497 abort: user quit
556 498 [255]
557 499
558 500 $ hg resolve --all << EOF
559 501 > y
560 502 > EOF
561 503 re-merge all unresolved files (yn)? y
562 504 merging emp1
563 505 merging emp2
564 506 merging emp3
565 507 warning: conflicts while merging emp1! (edit, then use 'hg resolve --mark')
566 508 warning: conflicts while merging emp2! (edit, then use 'hg resolve --mark')
567 509 warning: conflicts while merging emp3! (edit, then use 'hg resolve --mark')
568 510 [1]
569 511
570 512 Test that commands.resolve.confirm respect --mark option (only when no patterns args are given):
571 513 ===============================================================================================
572 514
573 515 $ hg resolve -m emp1
574 516 $ hg resolve -l
575 517 R emp1
576 518 U emp2
577 519 U emp3
578 520
579 521 $ hg resolve -m << EOF
580 522 > n
581 523 > EOF
582 524 mark all unresolved files as resolved (yn)? n
583 525 abort: user quit
584 526 [255]
585 527
586 528 $ hg resolve -m << EOF
587 529 > y
588 530 > EOF
589 531 mark all unresolved files as resolved (yn)? y
590 532 (no more unresolved files)
591 533 continue: hg rebase --continue
592 534 $ hg resolve -l
593 535 R emp1
594 536 R emp2
595 537 R emp3
596 538
597 539 Test that commands.resolve.confirm respect --unmark option (only when no patterns args are given):
598 540 =================================================================================================
599 541
600 542 $ hg resolve -u emp1
601 543
602 544 $ hg resolve -l
603 545 U emp1
604 546 R emp2
605 547 R emp3
606 548
607 549 $ hg resolve -u << EOF
608 550 > n
609 551 > EOF
610 552 mark all resolved files as unresolved (yn)? n
611 553 abort: user quit
612 554 [255]
613 555
614 556 $ hg resolve -m << EOF
615 557 > y
616 558 > EOF
617 559 mark all unresolved files as resolved (yn)? y
618 560 (no more unresolved files)
619 561 continue: hg rebase --continue
620 562
621 563 $ hg resolve -l
622 564 R emp1
623 565 R emp2
624 566 R emp3
625 567
626 568 $ hg rebase --abort
627 569 rebase aborted
628 570
629 571 Done with commands.resolve.confirm tests:
630 572 $ cd ..
631 573
632 574 Test that commands.resolve.mark-check works even if there are deleted files:
633 575 $ hg init resolve-deleted
634 576 $ cd resolve-deleted
635 577 $ echo r0 > file1
636 578 $ hg ci -qAm r0
637 579 $ echo r1 > file1
638 580 $ hg ci -qm r1
639 581 $ hg co -qr 0
640 582 $ hg rm file1
641 583 $ hg ci -qm "r2 (delete file1)"
642 584
643 585 (At this point we have r0 creating file1, and sibling commits r1 and r2, which
644 586 modify and delete file1, respectively)
645 587
646 588 $ hg merge -r 1
647 589 file 'file1' was deleted in local [working copy] but was modified in other [merge rev].
648 590 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
649 591 What do you want to do? u
650 592 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
651 593 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
652 594 [1]
653 595 $ hg resolve --list
654 596 U file1
655 597 Because we left it as 'unresolved' the file should still exist.
656 598 $ [ -f file1 ] || echo "File does not exist?"
657 599 BC behavior: `hg resolve --mark` accepts that the file is still there, and
658 600 doesn't have a problem with this situation.
659 601 $ hg resolve --mark --config commands.resolve.mark-check=abort
660 602 (no more unresolved files)
661 603 $ hg resolve --list
662 604 R file1
663 605 The file is still there:
664 606 $ [ -f file1 ] || echo "File does not exist?"
665 607 Let's check mark-check=warn:
666 608 $ hg resolve --unmark file1
667 609 $ hg resolve --mark --config commands.resolve.mark-check=warn
668 610 (no more unresolved files)
669 611 $ hg resolve --list
670 612 R file1
671 613 The file is still there:
672 614 $ [ -f file1 ] || echo "File does not exist?"
673 615 Let's resolve the issue by deleting the file via `hg resolve`
674 616 $ hg resolve --unmark file1
675 617 $ echo 'd' | hg resolve file1 --config ui.interactive=1
676 618 file 'file1' was deleted in local [working copy] but was modified in other [merge rev].
677 619 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
678 620 What do you want to do? d
679 621 (no more unresolved files)
680 622 $ hg resolve --list
681 623 R file1
682 624 The file is deleted:
683 625 $ [ -f file1 ] && echo "File still exists?" || true
684 626 Doing `hg resolve --mark` doesn't break now that the file is missing:
685 627 $ hg resolve --mark --config commands.resolve.mark-check=abort
686 628 (no more unresolved files)
687 629 $ hg resolve --mark --config commands.resolve.mark-check=warn
688 630 (no more unresolved files)
689 631 Resurrect the file, and delete it outside of hg:
690 632 $ hg resolve --unmark file1
691 633 $ hg resolve file1
692 634 file 'file1' was deleted in local [working copy] but was modified in other [merge rev].
693 635 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
694 636 What do you want to do? u
695 637 [1]
696 638 $ [ -f file1 ] || echo "File does not exist?"
697 639 $ hg resolve --list
698 640 U file1
699 641 $ rm file1
700 642 $ hg resolve --mark --config commands.resolve.mark-check=abort
701 643 (no more unresolved files)
702 644 $ hg resolve --list
703 645 R file1
704 646 $ hg resolve --unmark file1
705 647 $ hg resolve file1
706 648 file 'file1' was deleted in local [working copy] but was modified in other [merge rev].
707 649 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
708 650 What do you want to do? u
709 651 [1]
710 652 $ [ -f file1 ] || echo "File does not exist?"
711 653 $ hg resolve --list
712 654 U file1
713 655 $ rm file1
714 656 $ hg resolve --mark --config commands.resolve.mark-check=warn
715 657 (no more unresolved files)
716 658 $ hg resolve --list
717 659 R file1
718 660
719 661
720 662 For completeness, let's try that in the opposite direction (merging r2 into r1,
721 663 instead of r1 into r2):
722 664 $ hg update -qCr 1
723 665 $ hg merge -r 2
724 666 file 'file1' was deleted in other [merge rev] but was modified in local [working copy].
725 667 You can use (c)hanged version, (d)elete, or leave (u)nresolved.
726 668 What do you want to do? u
727 669 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
728 670 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
729 671 [1]
730 672 $ hg resolve --list
731 673 U file1
732 674 Because we left it as 'unresolved' the file should still exist.
733 675 $ [ -f file1 ] || echo "File does not exist?"
734 676 BC behavior: `hg resolve --mark` accepts that the file is still there, and
735 677 doesn't have a problem with this situation.
736 678 $ hg resolve --mark --config commands.resolve.mark-check=abort
737 679 (no more unresolved files)
738 680 $ hg resolve --list
739 681 R file1
740 682 The file is still there:
741 683 $ [ -f file1 ] || echo "File does not exist?"
742 684 Let's check mark-check=warn:
743 685 $ hg resolve --unmark file1
744 686 $ hg resolve --mark --config commands.resolve.mark-check=warn
745 687 (no more unresolved files)
746 688 $ hg resolve --list
747 689 R file1
748 690 The file is still there:
749 691 $ [ -f file1 ] || echo "File does not exist?"
750 692 Let's resolve the issue by deleting the file via `hg resolve`
751 693 $ hg resolve --unmark file1
752 694 $ echo 'd' | hg resolve file1 --config ui.interactive=1
753 695 file 'file1' was deleted in other [merge rev] but was modified in local [working copy].
754 696 You can use (c)hanged version, (d)elete, or leave (u)nresolved.
755 697 What do you want to do? d
756 698 (no more unresolved files)
757 699 $ hg resolve --list
758 700 R file1
759 701 The file is deleted:
760 702 $ [ -f file1 ] && echo "File still exists?" || true
761 703 Doing `hg resolve --mark` doesn't break now that the file is missing:
762 704 $ hg resolve --mark --config commands.resolve.mark-check=abort
763 705 (no more unresolved files)
764 706 $ hg resolve --mark --config commands.resolve.mark-check=warn
765 707 (no more unresolved files)
766 708 Resurrect the file, and delete it outside of hg:
767 709 $ hg resolve --unmark file1
768 710 $ hg resolve file1
769 711 file 'file1' was deleted in other [merge rev] but was modified in local [working copy].
770 712 You can use (c)hanged version, (d)elete, or leave (u)nresolved.
771 713 What do you want to do? u
772 714 [1]
773 715 $ [ -f file1 ] || echo "File does not exist?"
774 716 $ hg resolve --list
775 717 U file1
776 718 $ rm file1
777 719 $ hg resolve --mark --config commands.resolve.mark-check=abort
778 720 (no more unresolved files)
779 721 $ hg resolve --list
780 722 R file1
781 723 $ hg resolve --unmark file1
782 724 $ hg resolve file1
783 725 file 'file1' was deleted in other [merge rev] but was modified in local [working copy].
784 726 You can use (c)hanged version, (d)elete, or leave (u)nresolved.
785 727 What do you want to do? u
786 728 [1]
787 729 $ [ -f file1 ] || echo "File does not exist?"
788 730 $ hg resolve --list
789 731 U file1
790 732 $ rm file1
791 733 $ hg resolve --mark --config commands.resolve.mark-check=warn
792 734 (no more unresolved files)
793 735 $ hg resolve --list
794 736 R file1
795 737
796 738 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now