##// END OF EJS Templates
config: add a .hg/hgrc-not-shared which won't be shared in share-safe mode...
Pulkit Goyal -
r46368:88a47cbf default
parent child Browse files
Show More
@@ -1,7657 +1,7663 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import os
12 12 import re
13 13 import sys
14 14
15 15 from .i18n import _
16 16 from .node import (
17 17 hex,
18 18 nullid,
19 19 nullrev,
20 20 short,
21 21 wdirhex,
22 22 wdirrev,
23 23 )
24 24 from .pycompat import open
25 25 from . import (
26 26 archival,
27 27 bookmarks,
28 28 bundle2,
29 29 changegroup,
30 30 cmdutil,
31 31 copies,
32 32 debugcommands as debugcommandsmod,
33 33 destutil,
34 34 dirstateguard,
35 35 discovery,
36 36 encoding,
37 37 error,
38 38 exchange,
39 39 extensions,
40 40 filemerge,
41 41 formatter,
42 42 graphmod,
43 43 grep as grepmod,
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 cmdutil.check_incompatible_arguments(opts, 'no_commit', ['commit', 'merge'])
773 773 opts = pycompat.byteskwargs(opts)
774 774
775 775 if rev and node:
776 776 raise error.Abort(_(b"please specify just one revision"))
777 777
778 778 if not rev:
779 779 rev = node
780 780
781 781 if not rev:
782 782 raise error.Abort(_(b"please specify a revision to backout"))
783 783
784 784 date = opts.get(b'date')
785 785 if date:
786 786 opts[b'date'] = dateutil.parsedate(date)
787 787
788 788 cmdutil.checkunfinished(repo)
789 789 cmdutil.bailifchanged(repo)
790 790 ctx = scmutil.revsingle(repo, rev)
791 791 node = ctx.node()
792 792
793 793 op1, op2 = repo.dirstate.parents()
794 794 if not repo.changelog.isancestor(node, op1):
795 795 raise error.Abort(_(b'cannot backout change that is not an ancestor'))
796 796
797 797 p1, p2 = repo.changelog.parents(node)
798 798 if p1 == nullid:
799 799 raise error.Abort(_(b'cannot backout a change with no parents'))
800 800 if p2 != nullid:
801 801 if not opts.get(b'parent'):
802 802 raise error.Abort(_(b'cannot backout a merge changeset'))
803 803 p = repo.lookup(opts[b'parent'])
804 804 if p not in (p1, p2):
805 805 raise error.Abort(
806 806 _(b'%s is not a parent of %s') % (short(p), short(node))
807 807 )
808 808 parent = p
809 809 else:
810 810 if opts.get(b'parent'):
811 811 raise error.Abort(_(b'cannot use --parent on non-merge changeset'))
812 812 parent = p1
813 813
814 814 # the backout should appear on the same branch
815 815 branch = repo.dirstate.branch()
816 816 bheads = repo.branchheads(branch)
817 817 rctx = scmutil.revsingle(repo, hex(parent))
818 818 if not opts.get(b'merge') and op1 != node:
819 819 with dirstateguard.dirstateguard(repo, b'backout'):
820 820 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
821 821 with ui.configoverride(overrides, b'backout'):
822 822 stats = mergemod.back_out(ctx, parent=repo[parent])
823 823 repo.setparents(op1, op2)
824 824 hg._showstats(repo, stats)
825 825 if stats.unresolvedcount:
826 826 repo.ui.status(
827 827 _(b"use 'hg resolve' to retry unresolved file merges\n")
828 828 )
829 829 return 1
830 830 else:
831 831 hg.clean(repo, node, show_stats=False)
832 832 repo.dirstate.setbranch(branch)
833 833 cmdutil.revert(ui, repo, rctx)
834 834
835 835 if opts.get(b'no_commit'):
836 836 msg = _(b"changeset %s backed out, don't forget to commit.\n")
837 837 ui.status(msg % short(node))
838 838 return 0
839 839
840 840 def commitfunc(ui, repo, message, match, opts):
841 841 editform = b'backout'
842 842 e = cmdutil.getcommiteditor(
843 843 editform=editform, **pycompat.strkwargs(opts)
844 844 )
845 845 if not message:
846 846 # we don't translate commit messages
847 847 message = b"Backed out changeset %s" % short(node)
848 848 e = cmdutil.getcommiteditor(edit=True, editform=editform)
849 849 return repo.commit(
850 850 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
851 851 )
852 852
853 853 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
854 854 if not newnode:
855 855 ui.status(_(b"nothing changed\n"))
856 856 return 1
857 857 cmdutil.commitstatus(repo, newnode, branch, bheads)
858 858
859 859 def nice(node):
860 860 return b'%d:%s' % (repo.changelog.rev(node), short(node))
861 861
862 862 ui.status(
863 863 _(b'changeset %s backs out changeset %s\n')
864 864 % (nice(repo.changelog.tip()), nice(node))
865 865 )
866 866 if opts.get(b'merge') and op1 != node:
867 867 hg.clean(repo, op1, show_stats=False)
868 868 ui.status(
869 869 _(b'merging with changeset %s\n') % nice(repo.changelog.tip())
870 870 )
871 871 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
872 872 with ui.configoverride(overrides, b'backout'):
873 873 return hg.merge(repo[b'tip'])
874 874 return 0
875 875
876 876
877 877 @command(
878 878 b'bisect',
879 879 [
880 880 (b'r', b'reset', False, _(b'reset bisect state')),
881 881 (b'g', b'good', False, _(b'mark changeset good')),
882 882 (b'b', b'bad', False, _(b'mark changeset bad')),
883 883 (b's', b'skip', False, _(b'skip testing changeset')),
884 884 (b'e', b'extend', False, _(b'extend the bisect range')),
885 885 (
886 886 b'c',
887 887 b'command',
888 888 b'',
889 889 _(b'use command to check changeset state'),
890 890 _(b'CMD'),
891 891 ),
892 892 (b'U', b'noupdate', False, _(b'do not update to target')),
893 893 ],
894 894 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
895 895 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
896 896 )
897 897 def bisect(
898 898 ui,
899 899 repo,
900 900 rev=None,
901 901 extra=None,
902 902 command=None,
903 903 reset=None,
904 904 good=None,
905 905 bad=None,
906 906 skip=None,
907 907 extend=None,
908 908 noupdate=None,
909 909 ):
910 910 """subdivision search of changesets
911 911
912 912 This command helps to find changesets which introduce problems. To
913 913 use, mark the earliest changeset you know exhibits the problem as
914 914 bad, then mark the latest changeset which is free from the problem
915 915 as good. Bisect will update your working directory to a revision
916 916 for testing (unless the -U/--noupdate option is specified). Once
917 917 you have performed tests, mark the working directory as good or
918 918 bad, and bisect will either update to another candidate changeset
919 919 or announce that it has found the bad revision.
920 920
921 921 As a shortcut, you can also use the revision argument to mark a
922 922 revision as good or bad without checking it out first.
923 923
924 924 If you supply a command, it will be used for automatic bisection.
925 925 The environment variable HG_NODE will contain the ID of the
926 926 changeset being tested. The exit status of the command will be
927 927 used to mark revisions as good or bad: status 0 means good, 125
928 928 means to skip the revision, 127 (command not found) will abort the
929 929 bisection, and any other non-zero exit status means the revision
930 930 is bad.
931 931
932 932 .. container:: verbose
933 933
934 934 Some examples:
935 935
936 936 - start a bisection with known bad revision 34, and good revision 12::
937 937
938 938 hg bisect --bad 34
939 939 hg bisect --good 12
940 940
941 941 - advance the current bisection by marking current revision as good or
942 942 bad::
943 943
944 944 hg bisect --good
945 945 hg bisect --bad
946 946
947 947 - mark the current revision, or a known revision, to be skipped (e.g. if
948 948 that revision is not usable because of another issue)::
949 949
950 950 hg bisect --skip
951 951 hg bisect --skip 23
952 952
953 953 - skip all revisions that do not touch directories ``foo`` or ``bar``::
954 954
955 955 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
956 956
957 957 - forget the current bisection::
958 958
959 959 hg bisect --reset
960 960
961 961 - use 'make && make tests' to automatically find the first broken
962 962 revision::
963 963
964 964 hg bisect --reset
965 965 hg bisect --bad 34
966 966 hg bisect --good 12
967 967 hg bisect --command "make && make tests"
968 968
969 969 - see all changesets whose states are already known in the current
970 970 bisection::
971 971
972 972 hg log -r "bisect(pruned)"
973 973
974 974 - see the changeset currently being bisected (especially useful
975 975 if running with -U/--noupdate)::
976 976
977 977 hg log -r "bisect(current)"
978 978
979 979 - see all changesets that took part in the current bisection::
980 980
981 981 hg log -r "bisect(range)"
982 982
983 983 - you can even get a nice graph::
984 984
985 985 hg log --graph -r "bisect(range)"
986 986
987 987 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
988 988
989 989 Returns 0 on success.
990 990 """
991 991 # backward compatibility
992 992 if rev in b"good bad reset init".split():
993 993 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
994 994 cmd, rev, extra = rev, extra, None
995 995 if cmd == b"good":
996 996 good = True
997 997 elif cmd == b"bad":
998 998 bad = True
999 999 else:
1000 1000 reset = True
1001 1001 elif extra:
1002 1002 raise error.Abort(_(b'incompatible arguments'))
1003 1003
1004 1004 incompatibles = {
1005 1005 b'--bad': bad,
1006 1006 b'--command': bool(command),
1007 1007 b'--extend': extend,
1008 1008 b'--good': good,
1009 1009 b'--reset': reset,
1010 1010 b'--skip': skip,
1011 1011 }
1012 1012
1013 1013 enabled = [x for x in incompatibles if incompatibles[x]]
1014 1014
1015 1015 if len(enabled) > 1:
1016 1016 raise error.Abort(
1017 1017 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1018 1018 )
1019 1019
1020 1020 if reset:
1021 1021 hbisect.resetstate(repo)
1022 1022 return
1023 1023
1024 1024 state = hbisect.load_state(repo)
1025 1025
1026 1026 # update state
1027 1027 if good or bad or skip:
1028 1028 if rev:
1029 1029 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
1030 1030 else:
1031 1031 nodes = [repo.lookup(b'.')]
1032 1032 if good:
1033 1033 state[b'good'] += nodes
1034 1034 elif bad:
1035 1035 state[b'bad'] += nodes
1036 1036 elif skip:
1037 1037 state[b'skip'] += nodes
1038 1038 hbisect.save_state(repo, state)
1039 1039 if not (state[b'good'] and state[b'bad']):
1040 1040 return
1041 1041
1042 1042 def mayupdate(repo, node, show_stats=True):
1043 1043 """common used update sequence"""
1044 1044 if noupdate:
1045 1045 return
1046 1046 cmdutil.checkunfinished(repo)
1047 1047 cmdutil.bailifchanged(repo)
1048 1048 return hg.clean(repo, node, show_stats=show_stats)
1049 1049
1050 1050 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1051 1051
1052 1052 if command:
1053 1053 changesets = 1
1054 1054 if noupdate:
1055 1055 try:
1056 1056 node = state[b'current'][0]
1057 1057 except LookupError:
1058 1058 raise error.Abort(
1059 1059 _(
1060 1060 b'current bisect revision is unknown - '
1061 1061 b'start a new bisect to fix'
1062 1062 )
1063 1063 )
1064 1064 else:
1065 1065 node, p2 = repo.dirstate.parents()
1066 1066 if p2 != nullid:
1067 1067 raise error.Abort(_(b'current bisect revision is a merge'))
1068 1068 if rev:
1069 1069 node = repo[scmutil.revsingle(repo, rev, node)].node()
1070 1070 with hbisect.restore_state(repo, state, node):
1071 1071 while changesets:
1072 1072 # update state
1073 1073 state[b'current'] = [node]
1074 1074 hbisect.save_state(repo, state)
1075 1075 status = ui.system(
1076 1076 command,
1077 1077 environ={b'HG_NODE': hex(node)},
1078 1078 blockedtag=b'bisect_check',
1079 1079 )
1080 1080 if status == 125:
1081 1081 transition = b"skip"
1082 1082 elif status == 0:
1083 1083 transition = b"good"
1084 1084 # status < 0 means process was killed
1085 1085 elif status == 127:
1086 1086 raise error.Abort(_(b"failed to execute %s") % command)
1087 1087 elif status < 0:
1088 1088 raise error.Abort(_(b"%s killed") % command)
1089 1089 else:
1090 1090 transition = b"bad"
1091 1091 state[transition].append(node)
1092 1092 ctx = repo[node]
1093 1093 ui.status(
1094 1094 _(b'changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition)
1095 1095 )
1096 1096 hbisect.checkstate(state)
1097 1097 # bisect
1098 1098 nodes, changesets, bgood = hbisect.bisect(repo, state)
1099 1099 # update to next check
1100 1100 node = nodes[0]
1101 1101 mayupdate(repo, node, show_stats=False)
1102 1102 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1103 1103 return
1104 1104
1105 1105 hbisect.checkstate(state)
1106 1106
1107 1107 # actually bisect
1108 1108 nodes, changesets, good = hbisect.bisect(repo, state)
1109 1109 if extend:
1110 1110 if not changesets:
1111 1111 extendnode = hbisect.extendrange(repo, state, nodes, good)
1112 1112 if extendnode is not None:
1113 1113 ui.write(
1114 1114 _(b"Extending search to changeset %d:%s\n")
1115 1115 % (extendnode.rev(), extendnode)
1116 1116 )
1117 1117 state[b'current'] = [extendnode.node()]
1118 1118 hbisect.save_state(repo, state)
1119 1119 return mayupdate(repo, extendnode.node())
1120 1120 raise error.Abort(_(b"nothing to extend"))
1121 1121
1122 1122 if changesets == 0:
1123 1123 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1124 1124 else:
1125 1125 assert len(nodes) == 1 # only a single node can be tested next
1126 1126 node = nodes[0]
1127 1127 # compute the approximate number of remaining tests
1128 1128 tests, size = 0, 2
1129 1129 while size <= changesets:
1130 1130 tests, size = tests + 1, size * 2
1131 1131 rev = repo.changelog.rev(node)
1132 1132 ui.write(
1133 1133 _(
1134 1134 b"Testing changeset %d:%s "
1135 1135 b"(%d changesets remaining, ~%d tests)\n"
1136 1136 )
1137 1137 % (rev, short(node), changesets, tests)
1138 1138 )
1139 1139 state[b'current'] = [node]
1140 1140 hbisect.save_state(repo, state)
1141 1141 return mayupdate(repo, node)
1142 1142
1143 1143
1144 1144 @command(
1145 1145 b'bookmarks|bookmark',
1146 1146 [
1147 1147 (b'f', b'force', False, _(b'force')),
1148 1148 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1149 1149 (b'd', b'delete', False, _(b'delete a given bookmark')),
1150 1150 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1151 1151 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1152 1152 (b'l', b'list', False, _(b'list existing bookmarks')),
1153 1153 ]
1154 1154 + formatteropts,
1155 1155 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1156 1156 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1157 1157 )
1158 1158 def bookmark(ui, repo, *names, **opts):
1159 1159 '''create a new bookmark or list existing bookmarks
1160 1160
1161 1161 Bookmarks are labels on changesets to help track lines of development.
1162 1162 Bookmarks are unversioned and can be moved, renamed and deleted.
1163 1163 Deleting or moving a bookmark has no effect on the associated changesets.
1164 1164
1165 1165 Creating or updating to a bookmark causes it to be marked as 'active'.
1166 1166 The active bookmark is indicated with a '*'.
1167 1167 When a commit is made, the active bookmark will advance to the new commit.
1168 1168 A plain :hg:`update` will also advance an active bookmark, if possible.
1169 1169 Updating away from a bookmark will cause it to be deactivated.
1170 1170
1171 1171 Bookmarks can be pushed and pulled between repositories (see
1172 1172 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1173 1173 diverged, a new 'divergent bookmark' of the form 'name@path' will
1174 1174 be created. Using :hg:`merge` will resolve the divergence.
1175 1175
1176 1176 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1177 1177 the active bookmark's name.
1178 1178
1179 1179 A bookmark named '@' has the special property that :hg:`clone` will
1180 1180 check it out by default if it exists.
1181 1181
1182 1182 .. container:: verbose
1183 1183
1184 1184 Template:
1185 1185
1186 1186 The following keywords are supported in addition to the common template
1187 1187 keywords and functions such as ``{bookmark}``. See also
1188 1188 :hg:`help templates`.
1189 1189
1190 1190 :active: Boolean. True if the bookmark is active.
1191 1191
1192 1192 Examples:
1193 1193
1194 1194 - create an active bookmark for a new line of development::
1195 1195
1196 1196 hg book new-feature
1197 1197
1198 1198 - create an inactive bookmark as a place marker::
1199 1199
1200 1200 hg book -i reviewed
1201 1201
1202 1202 - create an inactive bookmark on another changeset::
1203 1203
1204 1204 hg book -r .^ tested
1205 1205
1206 1206 - rename bookmark turkey to dinner::
1207 1207
1208 1208 hg book -m turkey dinner
1209 1209
1210 1210 - move the '@' bookmark from another branch::
1211 1211
1212 1212 hg book -f @
1213 1213
1214 1214 - print only the active bookmark name::
1215 1215
1216 1216 hg book -ql .
1217 1217 '''
1218 1218 opts = pycompat.byteskwargs(opts)
1219 1219 force = opts.get(b'force')
1220 1220 rev = opts.get(b'rev')
1221 1221 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1222 1222
1223 1223 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1224 1224 if action:
1225 1225 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1226 1226 elif names or rev:
1227 1227 action = b'add'
1228 1228 elif inactive:
1229 1229 action = b'inactive' # meaning deactivate
1230 1230 else:
1231 1231 action = b'list'
1232 1232
1233 1233 cmdutil.check_incompatible_arguments(
1234 1234 opts, b'inactive', [b'delete', b'list']
1235 1235 )
1236 1236 if not names and action in {b'add', b'delete'}:
1237 1237 raise error.Abort(_(b"bookmark name required"))
1238 1238
1239 1239 if action in {b'add', b'delete', b'rename', b'inactive'}:
1240 1240 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1241 1241 if action == b'delete':
1242 1242 names = pycompat.maplist(repo._bookmarks.expandname, names)
1243 1243 bookmarks.delete(repo, tr, names)
1244 1244 elif action == b'rename':
1245 1245 if not names:
1246 1246 raise error.Abort(_(b"new bookmark name required"))
1247 1247 elif len(names) > 1:
1248 1248 raise error.Abort(_(b"only one new bookmark name allowed"))
1249 1249 oldname = repo._bookmarks.expandname(opts[b'rename'])
1250 1250 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1251 1251 elif action == b'add':
1252 1252 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1253 1253 elif action == b'inactive':
1254 1254 if len(repo._bookmarks) == 0:
1255 1255 ui.status(_(b"no bookmarks set\n"))
1256 1256 elif not repo._activebookmark:
1257 1257 ui.status(_(b"no active bookmark\n"))
1258 1258 else:
1259 1259 bookmarks.deactivate(repo)
1260 1260 elif action == b'list':
1261 1261 names = pycompat.maplist(repo._bookmarks.expandname, names)
1262 1262 with ui.formatter(b'bookmarks', opts) as fm:
1263 1263 bookmarks.printbookmarks(ui, repo, fm, names)
1264 1264 else:
1265 1265 raise error.ProgrammingError(b'invalid action: %s' % action)
1266 1266
1267 1267
1268 1268 @command(
1269 1269 b'branch',
1270 1270 [
1271 1271 (
1272 1272 b'f',
1273 1273 b'force',
1274 1274 None,
1275 1275 _(b'set branch name even if it shadows an existing branch'),
1276 1276 ),
1277 1277 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1278 1278 (
1279 1279 b'r',
1280 1280 b'rev',
1281 1281 [],
1282 1282 _(b'change branches of the given revs (EXPERIMENTAL)'),
1283 1283 ),
1284 1284 ],
1285 1285 _(b'[-fC] [NAME]'),
1286 1286 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1287 1287 )
1288 1288 def branch(ui, repo, label=None, **opts):
1289 1289 """set or show the current branch name
1290 1290
1291 1291 .. note::
1292 1292
1293 1293 Branch names are permanent and global. Use :hg:`bookmark` to create a
1294 1294 light-weight bookmark instead. See :hg:`help glossary` for more
1295 1295 information about named branches and bookmarks.
1296 1296
1297 1297 With no argument, show the current branch name. With one argument,
1298 1298 set the working directory branch name (the branch will not exist
1299 1299 in the repository until the next commit). Standard practice
1300 1300 recommends that primary development take place on the 'default'
1301 1301 branch.
1302 1302
1303 1303 Unless -f/--force is specified, branch will not let you set a
1304 1304 branch name that already exists.
1305 1305
1306 1306 Use -C/--clean to reset the working directory branch to that of
1307 1307 the parent of the working directory, negating a previous branch
1308 1308 change.
1309 1309
1310 1310 Use the command :hg:`update` to switch to an existing branch. Use
1311 1311 :hg:`commit --close-branch` to mark this branch head as closed.
1312 1312 When all heads of a branch are closed, the branch will be
1313 1313 considered closed.
1314 1314
1315 1315 Returns 0 on success.
1316 1316 """
1317 1317 opts = pycompat.byteskwargs(opts)
1318 1318 revs = opts.get(b'rev')
1319 1319 if label:
1320 1320 label = label.strip()
1321 1321
1322 1322 if not opts.get(b'clean') and not label:
1323 1323 if revs:
1324 1324 raise error.Abort(_(b"no branch name specified for the revisions"))
1325 1325 ui.write(b"%s\n" % repo.dirstate.branch())
1326 1326 return
1327 1327
1328 1328 with repo.wlock():
1329 1329 if opts.get(b'clean'):
1330 1330 label = repo[b'.'].branch()
1331 1331 repo.dirstate.setbranch(label)
1332 1332 ui.status(_(b'reset working directory to branch %s\n') % label)
1333 1333 elif label:
1334 1334
1335 1335 scmutil.checknewlabel(repo, label, b'branch')
1336 1336 if revs:
1337 1337 return cmdutil.changebranch(ui, repo, revs, label, opts)
1338 1338
1339 1339 if not opts.get(b'force') and label in repo.branchmap():
1340 1340 if label not in [p.branch() for p in repo[None].parents()]:
1341 1341 raise error.Abort(
1342 1342 _(b'a branch of the same name already exists'),
1343 1343 # i18n: "it" refers to an existing branch
1344 1344 hint=_(b"use 'hg update' to switch to it"),
1345 1345 )
1346 1346
1347 1347 repo.dirstate.setbranch(label)
1348 1348 ui.status(_(b'marked working directory as branch %s\n') % label)
1349 1349
1350 1350 # find any open named branches aside from default
1351 1351 for n, h, t, c in repo.branchmap().iterbranches():
1352 1352 if n != b"default" and not c:
1353 1353 return 0
1354 1354 ui.status(
1355 1355 _(
1356 1356 b'(branches are permanent and global, '
1357 1357 b'did you want a bookmark?)\n'
1358 1358 )
1359 1359 )
1360 1360
1361 1361
1362 1362 @command(
1363 1363 b'branches',
1364 1364 [
1365 1365 (
1366 1366 b'a',
1367 1367 b'active',
1368 1368 False,
1369 1369 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1370 1370 ),
1371 1371 (b'c', b'closed', False, _(b'show normal and closed branches')),
1372 1372 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1373 1373 ]
1374 1374 + formatteropts,
1375 1375 _(b'[-c]'),
1376 1376 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1377 1377 intents={INTENT_READONLY},
1378 1378 )
1379 1379 def branches(ui, repo, active=False, closed=False, **opts):
1380 1380 """list repository named branches
1381 1381
1382 1382 List the repository's named branches, indicating which ones are
1383 1383 inactive. If -c/--closed is specified, also list branches which have
1384 1384 been marked closed (see :hg:`commit --close-branch`).
1385 1385
1386 1386 Use the command :hg:`update` to switch to an existing branch.
1387 1387
1388 1388 .. container:: verbose
1389 1389
1390 1390 Template:
1391 1391
1392 1392 The following keywords are supported in addition to the common template
1393 1393 keywords and functions such as ``{branch}``. See also
1394 1394 :hg:`help templates`.
1395 1395
1396 1396 :active: Boolean. True if the branch is active.
1397 1397 :closed: Boolean. True if the branch is closed.
1398 1398 :current: Boolean. True if it is the current branch.
1399 1399
1400 1400 Returns 0.
1401 1401 """
1402 1402
1403 1403 opts = pycompat.byteskwargs(opts)
1404 1404 revs = opts.get(b'rev')
1405 1405 selectedbranches = None
1406 1406 if revs:
1407 1407 revs = scmutil.revrange(repo, revs)
1408 1408 getbi = repo.revbranchcache().branchinfo
1409 1409 selectedbranches = {getbi(r)[0] for r in revs}
1410 1410
1411 1411 ui.pager(b'branches')
1412 1412 fm = ui.formatter(b'branches', opts)
1413 1413 hexfunc = fm.hexfunc
1414 1414
1415 1415 allheads = set(repo.heads())
1416 1416 branches = []
1417 1417 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1418 1418 if selectedbranches is not None and tag not in selectedbranches:
1419 1419 continue
1420 1420 isactive = False
1421 1421 if not isclosed:
1422 1422 openheads = set(repo.branchmap().iteropen(heads))
1423 1423 isactive = bool(openheads & allheads)
1424 1424 branches.append((tag, repo[tip], isactive, not isclosed))
1425 1425 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1426 1426
1427 1427 for tag, ctx, isactive, isopen in branches:
1428 1428 if active and not isactive:
1429 1429 continue
1430 1430 if isactive:
1431 1431 label = b'branches.active'
1432 1432 notice = b''
1433 1433 elif not isopen:
1434 1434 if not closed:
1435 1435 continue
1436 1436 label = b'branches.closed'
1437 1437 notice = _(b' (closed)')
1438 1438 else:
1439 1439 label = b'branches.inactive'
1440 1440 notice = _(b' (inactive)')
1441 1441 current = tag == repo.dirstate.branch()
1442 1442 if current:
1443 1443 label = b'branches.current'
1444 1444
1445 1445 fm.startitem()
1446 1446 fm.write(b'branch', b'%s', tag, label=label)
1447 1447 rev = ctx.rev()
1448 1448 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1449 1449 fmt = b' ' * padsize + b' %d:%s'
1450 1450 fm.condwrite(
1451 1451 not ui.quiet,
1452 1452 b'rev node',
1453 1453 fmt,
1454 1454 rev,
1455 1455 hexfunc(ctx.node()),
1456 1456 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1457 1457 )
1458 1458 fm.context(ctx=ctx)
1459 1459 fm.data(active=isactive, closed=not isopen, current=current)
1460 1460 if not ui.quiet:
1461 1461 fm.plain(notice)
1462 1462 fm.plain(b'\n')
1463 1463 fm.end()
1464 1464
1465 1465
1466 1466 @command(
1467 1467 b'bundle',
1468 1468 [
1469 1469 (
1470 1470 b'f',
1471 1471 b'force',
1472 1472 None,
1473 1473 _(b'run even when the destination is unrelated'),
1474 1474 ),
1475 1475 (
1476 1476 b'r',
1477 1477 b'rev',
1478 1478 [],
1479 1479 _(b'a changeset intended to be added to the destination'),
1480 1480 _(b'REV'),
1481 1481 ),
1482 1482 (
1483 1483 b'b',
1484 1484 b'branch',
1485 1485 [],
1486 1486 _(b'a specific branch you would like to bundle'),
1487 1487 _(b'BRANCH'),
1488 1488 ),
1489 1489 (
1490 1490 b'',
1491 1491 b'base',
1492 1492 [],
1493 1493 _(b'a base changeset assumed to be available at the destination'),
1494 1494 _(b'REV'),
1495 1495 ),
1496 1496 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1497 1497 (
1498 1498 b't',
1499 1499 b'type',
1500 1500 b'bzip2',
1501 1501 _(b'bundle compression type to use'),
1502 1502 _(b'TYPE'),
1503 1503 ),
1504 1504 ]
1505 1505 + remoteopts,
1506 1506 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1507 1507 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1508 1508 )
1509 1509 def bundle(ui, repo, fname, dest=None, **opts):
1510 1510 """create a bundle file
1511 1511
1512 1512 Generate a bundle file containing data to be transferred to another
1513 1513 repository.
1514 1514
1515 1515 To create a bundle containing all changesets, use -a/--all
1516 1516 (or --base null). Otherwise, hg assumes the destination will have
1517 1517 all the nodes you specify with --base parameters. Otherwise, hg
1518 1518 will assume the repository has all the nodes in destination, or
1519 1519 default-push/default if no destination is specified, where destination
1520 1520 is the repository you provide through DEST option.
1521 1521
1522 1522 You can change bundle format with the -t/--type option. See
1523 1523 :hg:`help bundlespec` for documentation on this format. By default,
1524 1524 the most appropriate format is used and compression defaults to
1525 1525 bzip2.
1526 1526
1527 1527 The bundle file can then be transferred using conventional means
1528 1528 and applied to another repository with the unbundle or pull
1529 1529 command. This is useful when direct push and pull are not
1530 1530 available or when exporting an entire repository is undesirable.
1531 1531
1532 1532 Applying bundles preserves all changeset contents including
1533 1533 permissions, copy/rename information, and revision history.
1534 1534
1535 1535 Returns 0 on success, 1 if no changes found.
1536 1536 """
1537 1537 opts = pycompat.byteskwargs(opts)
1538 1538 revs = None
1539 1539 if b'rev' in opts:
1540 1540 revstrings = opts[b'rev']
1541 1541 revs = scmutil.revrange(repo, revstrings)
1542 1542 if revstrings and not revs:
1543 1543 raise error.Abort(_(b'no commits to bundle'))
1544 1544
1545 1545 bundletype = opts.get(b'type', b'bzip2').lower()
1546 1546 try:
1547 1547 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1548 1548 except error.UnsupportedBundleSpecification as e:
1549 1549 raise error.Abort(
1550 1550 pycompat.bytestr(e),
1551 1551 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1552 1552 )
1553 1553 cgversion = bundlespec.contentopts[b"cg.version"]
1554 1554
1555 1555 # Packed bundles are a pseudo bundle format for now.
1556 1556 if cgversion == b's1':
1557 1557 raise error.Abort(
1558 1558 _(b'packed bundles cannot be produced by "hg bundle"'),
1559 1559 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1560 1560 )
1561 1561
1562 1562 if opts.get(b'all'):
1563 1563 if dest:
1564 1564 raise error.Abort(
1565 1565 _(b"--all is incompatible with specifying a destination")
1566 1566 )
1567 1567 if opts.get(b'base'):
1568 1568 ui.warn(_(b"ignoring --base because --all was specified\n"))
1569 1569 base = [nullrev]
1570 1570 else:
1571 1571 base = scmutil.revrange(repo, opts.get(b'base'))
1572 1572 if cgversion not in changegroup.supportedoutgoingversions(repo):
1573 1573 raise error.Abort(
1574 1574 _(b"repository does not support bundle version %s") % cgversion
1575 1575 )
1576 1576
1577 1577 if base:
1578 1578 if dest:
1579 1579 raise error.Abort(
1580 1580 _(b"--base is incompatible with specifying a destination")
1581 1581 )
1582 1582 common = [repo[rev].node() for rev in base]
1583 1583 heads = [repo[r].node() for r in revs] if revs else None
1584 1584 outgoing = discovery.outgoing(repo, common, heads)
1585 1585 else:
1586 1586 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1587 1587 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1588 1588 other = hg.peer(repo, opts, dest)
1589 1589 revs = [repo[r].hex() for r in revs]
1590 1590 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1591 1591 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1592 1592 outgoing = discovery.findcommonoutgoing(
1593 1593 repo,
1594 1594 other,
1595 1595 onlyheads=heads,
1596 1596 force=opts.get(b'force'),
1597 1597 portable=True,
1598 1598 )
1599 1599
1600 1600 if not outgoing.missing:
1601 1601 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1602 1602 return 1
1603 1603
1604 1604 if cgversion == b'01': # bundle1
1605 1605 bversion = b'HG10' + bundlespec.wirecompression
1606 1606 bcompression = None
1607 1607 elif cgversion in (b'02', b'03'):
1608 1608 bversion = b'HG20'
1609 1609 bcompression = bundlespec.wirecompression
1610 1610 else:
1611 1611 raise error.ProgrammingError(
1612 1612 b'bundle: unexpected changegroup version %s' % cgversion
1613 1613 )
1614 1614
1615 1615 # TODO compression options should be derived from bundlespec parsing.
1616 1616 # This is a temporary hack to allow adjusting bundle compression
1617 1617 # level without a) formalizing the bundlespec changes to declare it
1618 1618 # b) introducing a command flag.
1619 1619 compopts = {}
1620 1620 complevel = ui.configint(
1621 1621 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1622 1622 )
1623 1623 if complevel is None:
1624 1624 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1625 1625 if complevel is not None:
1626 1626 compopts[b'level'] = complevel
1627 1627
1628 1628 # Allow overriding the bundling of obsmarker in phases through
1629 1629 # configuration while we don't have a bundle version that include them
1630 1630 if repo.ui.configbool(b'experimental', b'evolution.bundle-obsmarker'):
1631 1631 bundlespec.contentopts[b'obsolescence'] = True
1632 1632 if repo.ui.configbool(b'experimental', b'bundle-phases'):
1633 1633 bundlespec.contentopts[b'phases'] = True
1634 1634
1635 1635 bundle2.writenewbundle(
1636 1636 ui,
1637 1637 repo,
1638 1638 b'bundle',
1639 1639 fname,
1640 1640 bversion,
1641 1641 outgoing,
1642 1642 bundlespec.contentopts,
1643 1643 compression=bcompression,
1644 1644 compopts=compopts,
1645 1645 )
1646 1646
1647 1647
1648 1648 @command(
1649 1649 b'cat',
1650 1650 [
1651 1651 (
1652 1652 b'o',
1653 1653 b'output',
1654 1654 b'',
1655 1655 _(b'print output to file with formatted name'),
1656 1656 _(b'FORMAT'),
1657 1657 ),
1658 1658 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1659 1659 (b'', b'decode', None, _(b'apply any matching decode filter')),
1660 1660 ]
1661 1661 + walkopts
1662 1662 + formatteropts,
1663 1663 _(b'[OPTION]... FILE...'),
1664 1664 helpcategory=command.CATEGORY_FILE_CONTENTS,
1665 1665 inferrepo=True,
1666 1666 intents={INTENT_READONLY},
1667 1667 )
1668 1668 def cat(ui, repo, file1, *pats, **opts):
1669 1669 """output the current or given revision of files
1670 1670
1671 1671 Print the specified files as they were at the given revision. If
1672 1672 no revision is given, the parent of the working directory is used.
1673 1673
1674 1674 Output may be to a file, in which case the name of the file is
1675 1675 given using a template string. See :hg:`help templates`. In addition
1676 1676 to the common template keywords, the following formatting rules are
1677 1677 supported:
1678 1678
1679 1679 :``%%``: literal "%" character
1680 1680 :``%s``: basename of file being printed
1681 1681 :``%d``: dirname of file being printed, or '.' if in repository root
1682 1682 :``%p``: root-relative path name of file being printed
1683 1683 :``%H``: changeset hash (40 hexadecimal digits)
1684 1684 :``%R``: changeset revision number
1685 1685 :``%h``: short-form changeset hash (12 hexadecimal digits)
1686 1686 :``%r``: zero-padded changeset revision number
1687 1687 :``%b``: basename of the exporting repository
1688 1688 :``\\``: literal "\\" character
1689 1689
1690 1690 .. container:: verbose
1691 1691
1692 1692 Template:
1693 1693
1694 1694 The following keywords are supported in addition to the common template
1695 1695 keywords and functions. See also :hg:`help templates`.
1696 1696
1697 1697 :data: String. File content.
1698 1698 :path: String. Repository-absolute path of the file.
1699 1699
1700 1700 Returns 0 on success.
1701 1701 """
1702 1702 opts = pycompat.byteskwargs(opts)
1703 1703 rev = opts.get(b'rev')
1704 1704 if rev:
1705 1705 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1706 1706 ctx = scmutil.revsingle(repo, rev)
1707 1707 m = scmutil.match(ctx, (file1,) + pats, opts)
1708 1708 fntemplate = opts.pop(b'output', b'')
1709 1709 if cmdutil.isstdiofilename(fntemplate):
1710 1710 fntemplate = b''
1711 1711
1712 1712 if fntemplate:
1713 1713 fm = formatter.nullformatter(ui, b'cat', opts)
1714 1714 else:
1715 1715 ui.pager(b'cat')
1716 1716 fm = ui.formatter(b'cat', opts)
1717 1717 with fm:
1718 1718 return cmdutil.cat(
1719 1719 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1720 1720 )
1721 1721
1722 1722
1723 1723 @command(
1724 1724 b'clone',
1725 1725 [
1726 1726 (
1727 1727 b'U',
1728 1728 b'noupdate',
1729 1729 None,
1730 1730 _(
1731 1731 b'the clone will include an empty working '
1732 1732 b'directory (only a repository)'
1733 1733 ),
1734 1734 ),
1735 1735 (
1736 1736 b'u',
1737 1737 b'updaterev',
1738 1738 b'',
1739 1739 _(b'revision, tag, or branch to check out'),
1740 1740 _(b'REV'),
1741 1741 ),
1742 1742 (
1743 1743 b'r',
1744 1744 b'rev',
1745 1745 [],
1746 1746 _(
1747 1747 b'do not clone everything, but include this changeset'
1748 1748 b' and its ancestors'
1749 1749 ),
1750 1750 _(b'REV'),
1751 1751 ),
1752 1752 (
1753 1753 b'b',
1754 1754 b'branch',
1755 1755 [],
1756 1756 _(
1757 1757 b'do not clone everything, but include this branch\'s'
1758 1758 b' changesets and their ancestors'
1759 1759 ),
1760 1760 _(b'BRANCH'),
1761 1761 ),
1762 1762 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1763 1763 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1764 1764 (b'', b'stream', None, _(b'clone with minimal data processing')),
1765 1765 ]
1766 1766 + remoteopts,
1767 1767 _(b'[OPTION]... SOURCE [DEST]'),
1768 1768 helpcategory=command.CATEGORY_REPO_CREATION,
1769 1769 helpbasic=True,
1770 1770 norepo=True,
1771 1771 )
1772 1772 def clone(ui, source, dest=None, **opts):
1773 1773 """make a copy of an existing repository
1774 1774
1775 1775 Create a copy of an existing repository in a new directory.
1776 1776
1777 1777 If no destination directory name is specified, it defaults to the
1778 1778 basename of the source.
1779 1779
1780 1780 The location of the source is added to the new repository's
1781 1781 ``.hg/hgrc`` file, as the default to be used for future pulls.
1782 1782
1783 1783 Only local paths and ``ssh://`` URLs are supported as
1784 1784 destinations. For ``ssh://`` destinations, no working directory or
1785 1785 ``.hg/hgrc`` will be created on the remote side.
1786 1786
1787 1787 If the source repository has a bookmark called '@' set, that
1788 1788 revision will be checked out in the new repository by default.
1789 1789
1790 1790 To check out a particular version, use -u/--update, or
1791 1791 -U/--noupdate to create a clone with no working directory.
1792 1792
1793 1793 To pull only a subset of changesets, specify one or more revisions
1794 1794 identifiers with -r/--rev or branches with -b/--branch. The
1795 1795 resulting clone will contain only the specified changesets and
1796 1796 their ancestors. These options (or 'clone src#rev dest') imply
1797 1797 --pull, even for local source repositories.
1798 1798
1799 1799 In normal clone mode, the remote normalizes repository data into a common
1800 1800 exchange format and the receiving end translates this data into its local
1801 1801 storage format. --stream activates a different clone mode that essentially
1802 1802 copies repository files from the remote with minimal data processing. This
1803 1803 significantly reduces the CPU cost of a clone both remotely and locally.
1804 1804 However, it often increases the transferred data size by 30-40%. This can
1805 1805 result in substantially faster clones where I/O throughput is plentiful,
1806 1806 especially for larger repositories. A side-effect of --stream clones is
1807 1807 that storage settings and requirements on the remote are applied locally:
1808 1808 a modern client may inherit legacy or inefficient storage used by the
1809 1809 remote or a legacy Mercurial client may not be able to clone from a
1810 1810 modern Mercurial remote.
1811 1811
1812 1812 .. note::
1813 1813
1814 1814 Specifying a tag will include the tagged changeset but not the
1815 1815 changeset containing the tag.
1816 1816
1817 1817 .. container:: verbose
1818 1818
1819 1819 For efficiency, hardlinks are used for cloning whenever the
1820 1820 source and destination are on the same filesystem (note this
1821 1821 applies only to the repository data, not to the working
1822 1822 directory). Some filesystems, such as AFS, implement hardlinking
1823 1823 incorrectly, but do not report errors. In these cases, use the
1824 1824 --pull option to avoid hardlinking.
1825 1825
1826 1826 Mercurial will update the working directory to the first applicable
1827 1827 revision from this list:
1828 1828
1829 1829 a) null if -U or the source repository has no changesets
1830 1830 b) if -u . and the source repository is local, the first parent of
1831 1831 the source repository's working directory
1832 1832 c) the changeset specified with -u (if a branch name, this means the
1833 1833 latest head of that branch)
1834 1834 d) the changeset specified with -r
1835 1835 e) the tipmost head specified with -b
1836 1836 f) the tipmost head specified with the url#branch source syntax
1837 1837 g) the revision marked with the '@' bookmark, if present
1838 1838 h) the tipmost head of the default branch
1839 1839 i) tip
1840 1840
1841 1841 When cloning from servers that support it, Mercurial may fetch
1842 1842 pre-generated data from a server-advertised URL or inline from the
1843 1843 same stream. When this is done, hooks operating on incoming changesets
1844 1844 and changegroups may fire more than once, once for each pre-generated
1845 1845 bundle and as well as for any additional remaining data. In addition,
1846 1846 if an error occurs, the repository may be rolled back to a partial
1847 1847 clone. This behavior may change in future releases.
1848 1848 See :hg:`help -e clonebundles` for more.
1849 1849
1850 1850 Examples:
1851 1851
1852 1852 - clone a remote repository to a new directory named hg/::
1853 1853
1854 1854 hg clone https://www.mercurial-scm.org/repo/hg/
1855 1855
1856 1856 - create a lightweight local clone::
1857 1857
1858 1858 hg clone project/ project-feature/
1859 1859
1860 1860 - clone from an absolute path on an ssh server (note double-slash)::
1861 1861
1862 1862 hg clone ssh://user@server//home/projects/alpha/
1863 1863
1864 1864 - do a streaming clone while checking out a specified version::
1865 1865
1866 1866 hg clone --stream http://server/repo -u 1.5
1867 1867
1868 1868 - create a repository without changesets after a particular revision::
1869 1869
1870 1870 hg clone -r 04e544 experimental/ good/
1871 1871
1872 1872 - clone (and track) a particular named branch::
1873 1873
1874 1874 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1875 1875
1876 1876 See :hg:`help urls` for details on specifying URLs.
1877 1877
1878 1878 Returns 0 on success.
1879 1879 """
1880 1880 opts = pycompat.byteskwargs(opts)
1881 1881 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1882 1882
1883 1883 # --include/--exclude can come from narrow or sparse.
1884 1884 includepats, excludepats = None, None
1885 1885
1886 1886 # hg.clone() differentiates between None and an empty set. So make sure
1887 1887 # patterns are sets if narrow is requested without patterns.
1888 1888 if opts.get(b'narrow'):
1889 1889 includepats = set()
1890 1890 excludepats = set()
1891 1891
1892 1892 if opts.get(b'include'):
1893 1893 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1894 1894 if opts.get(b'exclude'):
1895 1895 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1896 1896
1897 1897 r = hg.clone(
1898 1898 ui,
1899 1899 opts,
1900 1900 source,
1901 1901 dest,
1902 1902 pull=opts.get(b'pull'),
1903 1903 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1904 1904 revs=opts.get(b'rev'),
1905 1905 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1906 1906 branch=opts.get(b'branch'),
1907 1907 shareopts=opts.get(b'shareopts'),
1908 1908 storeincludepats=includepats,
1909 1909 storeexcludepats=excludepats,
1910 1910 depth=opts.get(b'depth') or None,
1911 1911 )
1912 1912
1913 1913 return r is None
1914 1914
1915 1915
1916 1916 @command(
1917 1917 b'commit|ci',
1918 1918 [
1919 1919 (
1920 1920 b'A',
1921 1921 b'addremove',
1922 1922 None,
1923 1923 _(b'mark new/missing files as added/removed before committing'),
1924 1924 ),
1925 1925 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1926 1926 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1927 1927 (b's', b'secret', None, _(b'use the secret phase for committing')),
1928 1928 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1929 1929 (
1930 1930 b'',
1931 1931 b'force-close-branch',
1932 1932 None,
1933 1933 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1934 1934 ),
1935 1935 (b'i', b'interactive', None, _(b'use interactive mode')),
1936 1936 ]
1937 1937 + walkopts
1938 1938 + commitopts
1939 1939 + commitopts2
1940 1940 + subrepoopts,
1941 1941 _(b'[OPTION]... [FILE]...'),
1942 1942 helpcategory=command.CATEGORY_COMMITTING,
1943 1943 helpbasic=True,
1944 1944 inferrepo=True,
1945 1945 )
1946 1946 def commit(ui, repo, *pats, **opts):
1947 1947 """commit the specified files or all outstanding changes
1948 1948
1949 1949 Commit changes to the given files into the repository. Unlike a
1950 1950 centralized SCM, this operation is a local operation. See
1951 1951 :hg:`push` for a way to actively distribute your changes.
1952 1952
1953 1953 If a list of files is omitted, all changes reported by :hg:`status`
1954 1954 will be committed.
1955 1955
1956 1956 If you are committing the result of a merge, do not provide any
1957 1957 filenames or -I/-X filters.
1958 1958
1959 1959 If no commit message is specified, Mercurial starts your
1960 1960 configured editor where you can enter a message. In case your
1961 1961 commit fails, you will find a backup of your message in
1962 1962 ``.hg/last-message.txt``.
1963 1963
1964 1964 The --close-branch flag can be used to mark the current branch
1965 1965 head closed. When all heads of a branch are closed, the branch
1966 1966 will be considered closed and no longer listed.
1967 1967
1968 1968 The --amend flag can be used to amend the parent of the
1969 1969 working directory with a new commit that contains the changes
1970 1970 in the parent in addition to those currently reported by :hg:`status`,
1971 1971 if there are any. The old commit is stored in a backup bundle in
1972 1972 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1973 1973 on how to restore it).
1974 1974
1975 1975 Message, user and date are taken from the amended commit unless
1976 1976 specified. When a message isn't specified on the command line,
1977 1977 the editor will open with the message of the amended commit.
1978 1978
1979 1979 It is not possible to amend public changesets (see :hg:`help phases`)
1980 1980 or changesets that have children.
1981 1981
1982 1982 See :hg:`help dates` for a list of formats valid for -d/--date.
1983 1983
1984 1984 Returns 0 on success, 1 if nothing changed.
1985 1985
1986 1986 .. container:: verbose
1987 1987
1988 1988 Examples:
1989 1989
1990 1990 - commit all files ending in .py::
1991 1991
1992 1992 hg commit --include "set:**.py"
1993 1993
1994 1994 - commit all non-binary files::
1995 1995
1996 1996 hg commit --exclude "set:binary()"
1997 1997
1998 1998 - amend the current commit and set the date to now::
1999 1999
2000 2000 hg commit --amend --date now
2001 2001 """
2002 2002 with repo.wlock(), repo.lock():
2003 2003 return _docommit(ui, repo, *pats, **opts)
2004 2004
2005 2005
2006 2006 def _docommit(ui, repo, *pats, **opts):
2007 2007 if opts.get('interactive'):
2008 2008 opts.pop('interactive')
2009 2009 ret = cmdutil.dorecord(
2010 2010 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2011 2011 )
2012 2012 # ret can be 0 (no changes to record) or the value returned by
2013 2013 # commit(), 1 if nothing changed or None on success.
2014 2014 return 1 if ret == 0 else ret
2015 2015
2016 2016 opts = pycompat.byteskwargs(opts)
2017 2017 if opts.get(b'subrepos'):
2018 2018 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'amend'])
2019 2019 # Let --subrepos on the command line override config setting.
2020 2020 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2021 2021
2022 2022 cmdutil.checkunfinished(repo, commit=True)
2023 2023
2024 2024 branch = repo[None].branch()
2025 2025 bheads = repo.branchheads(branch)
2026 2026
2027 2027 extra = {}
2028 2028 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2029 2029 extra[b'close'] = b'1'
2030 2030
2031 2031 if repo[b'.'].closesbranch():
2032 2032 raise error.Abort(
2033 2033 _(b'current revision is already a branch closing head')
2034 2034 )
2035 2035 elif not bheads:
2036 2036 raise error.Abort(_(b'branch "%s" has no heads to close') % branch)
2037 2037 elif (
2038 2038 branch == repo[b'.'].branch()
2039 2039 and repo[b'.'].node() not in bheads
2040 2040 and not opts.get(b'force_close_branch')
2041 2041 ):
2042 2042 hint = _(
2043 2043 b'use --force-close-branch to close branch from a non-head'
2044 2044 b' changeset'
2045 2045 )
2046 2046 raise error.Abort(_(b'can only close branch heads'), hint=hint)
2047 2047 elif opts.get(b'amend'):
2048 2048 if (
2049 2049 repo[b'.'].p1().branch() != branch
2050 2050 and repo[b'.'].p2().branch() != branch
2051 2051 ):
2052 2052 raise error.Abort(_(b'can only close branch heads'))
2053 2053
2054 2054 if opts.get(b'amend'):
2055 2055 if ui.configbool(b'ui', b'commitsubrepos'):
2056 2056 raise error.Abort(_(b'cannot amend with ui.commitsubrepos enabled'))
2057 2057
2058 2058 old = repo[b'.']
2059 2059 rewriteutil.precheck(repo, [old.rev()], b'amend')
2060 2060
2061 2061 # Currently histedit gets confused if an amend happens while histedit
2062 2062 # is in progress. Since we have a checkunfinished command, we are
2063 2063 # temporarily honoring it.
2064 2064 #
2065 2065 # Note: eventually this guard will be removed. Please do not expect
2066 2066 # this behavior to remain.
2067 2067 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2068 2068 cmdutil.checkunfinished(repo)
2069 2069
2070 2070 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2071 2071 if node == old.node():
2072 2072 ui.status(_(b"nothing changed\n"))
2073 2073 return 1
2074 2074 else:
2075 2075
2076 2076 def commitfunc(ui, repo, message, match, opts):
2077 2077 overrides = {}
2078 2078 if opts.get(b'secret'):
2079 2079 overrides[(b'phases', b'new-commit')] = b'secret'
2080 2080
2081 2081 baseui = repo.baseui
2082 2082 with baseui.configoverride(overrides, b'commit'):
2083 2083 with ui.configoverride(overrides, b'commit'):
2084 2084 editform = cmdutil.mergeeditform(
2085 2085 repo[None], b'commit.normal'
2086 2086 )
2087 2087 editor = cmdutil.getcommiteditor(
2088 2088 editform=editform, **pycompat.strkwargs(opts)
2089 2089 )
2090 2090 return repo.commit(
2091 2091 message,
2092 2092 opts.get(b'user'),
2093 2093 opts.get(b'date'),
2094 2094 match,
2095 2095 editor=editor,
2096 2096 extra=extra,
2097 2097 )
2098 2098
2099 2099 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2100 2100
2101 2101 if not node:
2102 2102 stat = cmdutil.postcommitstatus(repo, pats, opts)
2103 2103 if stat.deleted:
2104 2104 ui.status(
2105 2105 _(
2106 2106 b"nothing changed (%d missing files, see "
2107 2107 b"'hg status')\n"
2108 2108 )
2109 2109 % len(stat.deleted)
2110 2110 )
2111 2111 else:
2112 2112 ui.status(_(b"nothing changed\n"))
2113 2113 return 1
2114 2114
2115 2115 cmdutil.commitstatus(repo, node, branch, bheads, opts)
2116 2116
2117 2117 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2118 2118 status(
2119 2119 ui,
2120 2120 repo,
2121 2121 modified=True,
2122 2122 added=True,
2123 2123 removed=True,
2124 2124 deleted=True,
2125 2125 unknown=True,
2126 2126 subrepos=opts.get(b'subrepos'),
2127 2127 )
2128 2128
2129 2129
2130 2130 @command(
2131 2131 b'config|showconfig|debugconfig',
2132 2132 [
2133 2133 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2134 2134 (b'e', b'edit', None, _(b'edit user config')),
2135 2135 (b'l', b'local', None, _(b'edit repository config')),
2136 2136 (
2137 2137 b'',
2138 2138 b'shared',
2139 2139 None,
2140 2140 _(b'edit shared source repository config (EXPERIMENTAL)'),
2141 2141 ),
2142 (b'', b'non-shared', None, _(b'edit non shared config (EXPERIMENTAL)')),
2142 2143 (b'g', b'global', None, _(b'edit global config')),
2143 2144 ]
2144 2145 + formatteropts,
2145 2146 _(b'[-u] [NAME]...'),
2146 2147 helpcategory=command.CATEGORY_HELP,
2147 2148 optionalrepo=True,
2148 2149 intents={INTENT_READONLY},
2149 2150 )
2150 2151 def config(ui, repo, *values, **opts):
2151 2152 """show combined config settings from all hgrc files
2152 2153
2153 2154 With no arguments, print names and values of all config items.
2154 2155
2155 2156 With one argument of the form section.name, print just the value
2156 2157 of that config item.
2157 2158
2158 2159 With multiple arguments, print names and values of all config
2159 2160 items with matching section names or section.names.
2160 2161
2161 2162 With --edit, start an editor on the user-level config file. With
2162 2163 --global, edit the system-wide config file. With --local, edit the
2163 2164 repository-level config file.
2164 2165
2165 2166 With --debug, the source (filename and line number) is printed
2166 2167 for each config item.
2167 2168
2168 2169 See :hg:`help config` for more information about config files.
2169 2170
2170 2171 .. container:: verbose
2171 2172
2173 --non-shared flag is used to edit `.hg/hgrc-not-shared` config file.
2174 This file is not shared across shares when in share-safe mode.
2175
2172 2176 Template:
2173 2177
2174 2178 The following keywords are supported. See also :hg:`help templates`.
2175 2179
2176 2180 :name: String. Config name.
2177 2181 :source: String. Filename and line number where the item is defined.
2178 2182 :value: String. Config value.
2179 2183
2180 2184 The --shared flag can be used to edit the config file of shared source
2181 2185 repository. It only works when you have shared using the experimental
2182 2186 share safe feature.
2183 2187
2184 2188 Returns 0 on success, 1 if NAME does not exist.
2185 2189
2186 2190 """
2187 2191
2188 2192 opts = pycompat.byteskwargs(opts)
2189 editopts = (b'edit', b'local', b'global', b'shared')
2193 editopts = (b'edit', b'local', b'global', b'shared', b'non_shared')
2190 2194 if any(opts.get(o) for o in editopts):
2191 2195 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2192 2196 if opts.get(b'local'):
2193 2197 if not repo:
2194 2198 raise error.Abort(_(b"can't use --local outside a repository"))
2195 2199 paths = [repo.vfs.join(b'hgrc')]
2196 2200 elif opts.get(b'global'):
2197 2201 paths = rcutil.systemrcpath()
2198 2202 elif opts.get(b'shared'):
2199 2203 if not repo.shared():
2200 2204 raise error.Abort(
2201 2205 _(b"repository is not shared; can't use --shared")
2202 2206 )
2203 2207 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2204 2208 raise error.Abort(
2205 2209 _(
2206 2210 b"share safe feature not unabled; "
2207 2211 b"unable to edit shared source repository config"
2208 2212 )
2209 2213 )
2210 2214 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2215 elif opts.get(b'non_shared'):
2216 paths = [repo.vfs.join(b'hgrc-not-shared')]
2211 2217 else:
2212 2218 paths = rcutil.userrcpath()
2213 2219
2214 2220 for f in paths:
2215 2221 if os.path.exists(f):
2216 2222 break
2217 2223 else:
2218 2224 if opts.get(b'global'):
2219 2225 samplehgrc = uimod.samplehgrcs[b'global']
2220 2226 elif opts.get(b'local'):
2221 2227 samplehgrc = uimod.samplehgrcs[b'local']
2222 2228 else:
2223 2229 samplehgrc = uimod.samplehgrcs[b'user']
2224 2230
2225 2231 f = paths[0]
2226 2232 fp = open(f, b"wb")
2227 2233 fp.write(util.tonativeeol(samplehgrc))
2228 2234 fp.close()
2229 2235
2230 2236 editor = ui.geteditor()
2231 2237 ui.system(
2232 2238 b"%s \"%s\"" % (editor, f),
2233 2239 onerr=error.Abort,
2234 2240 errprefix=_(b"edit failed"),
2235 2241 blockedtag=b'config_edit',
2236 2242 )
2237 2243 return
2238 2244 ui.pager(b'config')
2239 2245 fm = ui.formatter(b'config', opts)
2240 2246 for t, f in rcutil.rccomponents():
2241 2247 if t == b'path':
2242 2248 ui.debug(b'read config from: %s\n' % f)
2243 2249 elif t == b'resource':
2244 2250 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2245 2251 elif t == b'items':
2246 2252 # Don't print anything for 'items'.
2247 2253 pass
2248 2254 else:
2249 2255 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2250 2256 untrusted = bool(opts.get(b'untrusted'))
2251 2257
2252 2258 selsections = selentries = []
2253 2259 if values:
2254 2260 selsections = [v for v in values if b'.' not in v]
2255 2261 selentries = [v for v in values if b'.' in v]
2256 2262 uniquesel = len(selentries) == 1 and not selsections
2257 2263 selsections = set(selsections)
2258 2264 selentries = set(selentries)
2259 2265
2260 2266 matched = False
2261 2267 for section, name, value in ui.walkconfig(untrusted=untrusted):
2262 2268 source = ui.configsource(section, name, untrusted)
2263 2269 value = pycompat.bytestr(value)
2264 2270 defaultvalue = ui.configdefault(section, name)
2265 2271 if fm.isplain():
2266 2272 source = source or b'none'
2267 2273 value = value.replace(b'\n', b'\\n')
2268 2274 entryname = section + b'.' + name
2269 2275 if values and not (section in selsections or entryname in selentries):
2270 2276 continue
2271 2277 fm.startitem()
2272 2278 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2273 2279 if uniquesel:
2274 2280 fm.data(name=entryname)
2275 2281 fm.write(b'value', b'%s\n', value)
2276 2282 else:
2277 2283 fm.write(b'name value', b'%s=%s\n', entryname, value)
2278 2284 if formatter.isprintable(defaultvalue):
2279 2285 fm.data(defaultvalue=defaultvalue)
2280 2286 elif isinstance(defaultvalue, list) and all(
2281 2287 formatter.isprintable(e) for e in defaultvalue
2282 2288 ):
2283 2289 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2284 2290 # TODO: no idea how to process unsupported defaultvalue types
2285 2291 matched = True
2286 2292 fm.end()
2287 2293 if matched:
2288 2294 return 0
2289 2295 return 1
2290 2296
2291 2297
2292 2298 @command(
2293 2299 b'continue',
2294 2300 dryrunopts,
2295 2301 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2296 2302 helpbasic=True,
2297 2303 )
2298 2304 def continuecmd(ui, repo, **opts):
2299 2305 """resumes an interrupted operation (EXPERIMENTAL)
2300 2306
2301 2307 Finishes a multistep operation like graft, histedit, rebase, merge,
2302 2308 and unshelve if they are in an interrupted state.
2303 2309
2304 2310 use --dry-run/-n to dry run the command.
2305 2311 """
2306 2312 dryrun = opts.get('dry_run')
2307 2313 contstate = cmdutil.getunfinishedstate(repo)
2308 2314 if not contstate:
2309 2315 raise error.Abort(_(b'no operation in progress'))
2310 2316 if not contstate.continuefunc:
2311 2317 raise error.Abort(
2312 2318 (
2313 2319 _(b"%s in progress but does not support 'hg continue'")
2314 2320 % (contstate._opname)
2315 2321 ),
2316 2322 hint=contstate.continuemsg(),
2317 2323 )
2318 2324 if dryrun:
2319 2325 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2320 2326 return
2321 2327 return contstate.continuefunc(ui, repo)
2322 2328
2323 2329
2324 2330 @command(
2325 2331 b'copy|cp',
2326 2332 [
2327 2333 (b'', b'forget', None, _(b'unmark a destination file as copied')),
2328 2334 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2329 2335 (
2330 2336 b'',
2331 2337 b'at-rev',
2332 2338 b'',
2333 2339 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2334 2340 _(b'REV'),
2335 2341 ),
2336 2342 (
2337 2343 b'f',
2338 2344 b'force',
2339 2345 None,
2340 2346 _(b'forcibly copy over an existing managed file'),
2341 2347 ),
2342 2348 ]
2343 2349 + walkopts
2344 2350 + dryrunopts,
2345 2351 _(b'[OPTION]... SOURCE... DEST'),
2346 2352 helpcategory=command.CATEGORY_FILE_CONTENTS,
2347 2353 )
2348 2354 def copy(ui, repo, *pats, **opts):
2349 2355 """mark files as copied for the next commit
2350 2356
2351 2357 Mark dest as having copies of source files. If dest is a
2352 2358 directory, copies are put in that directory. If dest is a file,
2353 2359 the source must be a single file.
2354 2360
2355 2361 By default, this command copies the contents of files as they
2356 2362 exist in the working directory. If invoked with -A/--after, the
2357 2363 operation is recorded, but no copying is performed.
2358 2364
2359 2365 To undo marking a destination file as copied, use --forget. With that
2360 2366 option, all given (positional) arguments are unmarked as copies. The
2361 2367 destination file(s) will be left in place (still tracked).
2362 2368
2363 2369 This command takes effect with the next commit by default.
2364 2370
2365 2371 Returns 0 on success, 1 if errors are encountered.
2366 2372 """
2367 2373 opts = pycompat.byteskwargs(opts)
2368 2374 with repo.wlock():
2369 2375 return cmdutil.copy(ui, repo, pats, opts)
2370 2376
2371 2377
2372 2378 @command(
2373 2379 b'debugcommands',
2374 2380 [],
2375 2381 _(b'[COMMAND]'),
2376 2382 helpcategory=command.CATEGORY_HELP,
2377 2383 norepo=True,
2378 2384 )
2379 2385 def debugcommands(ui, cmd=b'', *args):
2380 2386 """list all available commands and options"""
2381 2387 for cmd, vals in sorted(pycompat.iteritems(table)):
2382 2388 cmd = cmd.split(b'|')[0]
2383 2389 opts = b', '.join([i[1] for i in vals[1]])
2384 2390 ui.write(b'%s: %s\n' % (cmd, opts))
2385 2391
2386 2392
2387 2393 @command(
2388 2394 b'debugcomplete',
2389 2395 [(b'o', b'options', None, _(b'show the command options'))],
2390 2396 _(b'[-o] CMD'),
2391 2397 helpcategory=command.CATEGORY_HELP,
2392 2398 norepo=True,
2393 2399 )
2394 2400 def debugcomplete(ui, cmd=b'', **opts):
2395 2401 """returns the completion list associated with the given command"""
2396 2402
2397 2403 if opts.get('options'):
2398 2404 options = []
2399 2405 otables = [globalopts]
2400 2406 if cmd:
2401 2407 aliases, entry = cmdutil.findcmd(cmd, table, False)
2402 2408 otables.append(entry[1])
2403 2409 for t in otables:
2404 2410 for o in t:
2405 2411 if b"(DEPRECATED)" in o[3]:
2406 2412 continue
2407 2413 if o[0]:
2408 2414 options.append(b'-%s' % o[0])
2409 2415 options.append(b'--%s' % o[1])
2410 2416 ui.write(b"%s\n" % b"\n".join(options))
2411 2417 return
2412 2418
2413 2419 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2414 2420 if ui.verbose:
2415 2421 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2416 2422 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2417 2423
2418 2424
2419 2425 @command(
2420 2426 b'diff',
2421 2427 [
2422 2428 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
2423 2429 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2424 2430 ]
2425 2431 + diffopts
2426 2432 + diffopts2
2427 2433 + walkopts
2428 2434 + subrepoopts,
2429 2435 _(b'[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
2430 2436 helpcategory=command.CATEGORY_FILE_CONTENTS,
2431 2437 helpbasic=True,
2432 2438 inferrepo=True,
2433 2439 intents={INTENT_READONLY},
2434 2440 )
2435 2441 def diff(ui, repo, *pats, **opts):
2436 2442 """diff repository (or selected files)
2437 2443
2438 2444 Show differences between revisions for the specified files.
2439 2445
2440 2446 Differences between files are shown using the unified diff format.
2441 2447
2442 2448 .. note::
2443 2449
2444 2450 :hg:`diff` may generate unexpected results for merges, as it will
2445 2451 default to comparing against the working directory's first
2446 2452 parent changeset if no revisions are specified.
2447 2453
2448 2454 When two revision arguments are given, then changes are shown
2449 2455 between those revisions. If only one revision is specified then
2450 2456 that revision is compared to the working directory, and, when no
2451 2457 revisions are specified, the working directory files are compared
2452 2458 to its first parent.
2453 2459
2454 2460 Alternatively you can specify -c/--change with a revision to see
2455 2461 the changes in that changeset relative to its first parent.
2456 2462
2457 2463 Without the -a/--text option, diff will avoid generating diffs of
2458 2464 files it detects as binary. With -a, diff will generate a diff
2459 2465 anyway, probably with undesirable results.
2460 2466
2461 2467 Use the -g/--git option to generate diffs in the git extended diff
2462 2468 format. For more information, read :hg:`help diffs`.
2463 2469
2464 2470 .. container:: verbose
2465 2471
2466 2472 Examples:
2467 2473
2468 2474 - compare a file in the current working directory to its parent::
2469 2475
2470 2476 hg diff foo.c
2471 2477
2472 2478 - compare two historical versions of a directory, with rename info::
2473 2479
2474 2480 hg diff --git -r 1.0:1.2 lib/
2475 2481
2476 2482 - get change stats relative to the last change on some date::
2477 2483
2478 2484 hg diff --stat -r "date('may 2')"
2479 2485
2480 2486 - diff all newly-added files that contain a keyword::
2481 2487
2482 2488 hg diff "set:added() and grep(GNU)"
2483 2489
2484 2490 - compare a revision and its parents::
2485 2491
2486 2492 hg diff -c 9353 # compare against first parent
2487 2493 hg diff -r 9353^:9353 # same using revset syntax
2488 2494 hg diff -r 9353^2:9353 # compare against the second parent
2489 2495
2490 2496 Returns 0 on success.
2491 2497 """
2492 2498
2493 2499 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2494 2500 opts = pycompat.byteskwargs(opts)
2495 2501 revs = opts.get(b'rev')
2496 2502 change = opts.get(b'change')
2497 2503 stat = opts.get(b'stat')
2498 2504 reverse = opts.get(b'reverse')
2499 2505
2500 2506 if change:
2501 2507 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2502 2508 ctx2 = scmutil.revsingle(repo, change, None)
2503 2509 ctx1 = ctx2.p1()
2504 2510 else:
2505 2511 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2506 2512 ctx1, ctx2 = scmutil.revpair(repo, revs)
2507 2513
2508 2514 if reverse:
2509 2515 ctxleft = ctx2
2510 2516 ctxright = ctx1
2511 2517 else:
2512 2518 ctxleft = ctx1
2513 2519 ctxright = ctx2
2514 2520
2515 2521 diffopts = patch.diffallopts(ui, opts)
2516 2522 m = scmutil.match(ctx2, pats, opts)
2517 2523 m = repo.narrowmatch(m)
2518 2524 ui.pager(b'diff')
2519 2525 logcmdutil.diffordiffstat(
2520 2526 ui,
2521 2527 repo,
2522 2528 diffopts,
2523 2529 ctxleft,
2524 2530 ctxright,
2525 2531 m,
2526 2532 stat=stat,
2527 2533 listsubrepos=opts.get(b'subrepos'),
2528 2534 root=opts.get(b'root'),
2529 2535 )
2530 2536
2531 2537
2532 2538 @command(
2533 2539 b'export',
2534 2540 [
2535 2541 (
2536 2542 b'B',
2537 2543 b'bookmark',
2538 2544 b'',
2539 2545 _(b'export changes only reachable by given bookmark'),
2540 2546 _(b'BOOKMARK'),
2541 2547 ),
2542 2548 (
2543 2549 b'o',
2544 2550 b'output',
2545 2551 b'',
2546 2552 _(b'print output to file with formatted name'),
2547 2553 _(b'FORMAT'),
2548 2554 ),
2549 2555 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2550 2556 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2551 2557 ]
2552 2558 + diffopts
2553 2559 + formatteropts,
2554 2560 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2555 2561 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2556 2562 helpbasic=True,
2557 2563 intents={INTENT_READONLY},
2558 2564 )
2559 2565 def export(ui, repo, *changesets, **opts):
2560 2566 """dump the header and diffs for one or more changesets
2561 2567
2562 2568 Print the changeset header and diffs for one or more revisions.
2563 2569 If no revision is given, the parent of the working directory is used.
2564 2570
2565 2571 The information shown in the changeset header is: author, date,
2566 2572 branch name (if non-default), changeset hash, parent(s) and commit
2567 2573 comment.
2568 2574
2569 2575 .. note::
2570 2576
2571 2577 :hg:`export` may generate unexpected diff output for merge
2572 2578 changesets, as it will compare the merge changeset against its
2573 2579 first parent only.
2574 2580
2575 2581 Output may be to a file, in which case the name of the file is
2576 2582 given using a template string. See :hg:`help templates`. In addition
2577 2583 to the common template keywords, the following formatting rules are
2578 2584 supported:
2579 2585
2580 2586 :``%%``: literal "%" character
2581 2587 :``%H``: changeset hash (40 hexadecimal digits)
2582 2588 :``%N``: number of patches being generated
2583 2589 :``%R``: changeset revision number
2584 2590 :``%b``: basename of the exporting repository
2585 2591 :``%h``: short-form changeset hash (12 hexadecimal digits)
2586 2592 :``%m``: first line of the commit message (only alphanumeric characters)
2587 2593 :``%n``: zero-padded sequence number, starting at 1
2588 2594 :``%r``: zero-padded changeset revision number
2589 2595 :``\\``: literal "\\" character
2590 2596
2591 2597 Without the -a/--text option, export will avoid generating diffs
2592 2598 of files it detects as binary. With -a, export will generate a
2593 2599 diff anyway, probably with undesirable results.
2594 2600
2595 2601 With -B/--bookmark changesets reachable by the given bookmark are
2596 2602 selected.
2597 2603
2598 2604 Use the -g/--git option to generate diffs in the git extended diff
2599 2605 format. See :hg:`help diffs` for more information.
2600 2606
2601 2607 With the --switch-parent option, the diff will be against the
2602 2608 second parent. It can be useful to review a merge.
2603 2609
2604 2610 .. container:: verbose
2605 2611
2606 2612 Template:
2607 2613
2608 2614 The following keywords are supported in addition to the common template
2609 2615 keywords and functions. See also :hg:`help templates`.
2610 2616
2611 2617 :diff: String. Diff content.
2612 2618 :parents: List of strings. Parent nodes of the changeset.
2613 2619
2614 2620 Examples:
2615 2621
2616 2622 - use export and import to transplant a bugfix to the current
2617 2623 branch::
2618 2624
2619 2625 hg export -r 9353 | hg import -
2620 2626
2621 2627 - export all the changesets between two revisions to a file with
2622 2628 rename information::
2623 2629
2624 2630 hg export --git -r 123:150 > changes.txt
2625 2631
2626 2632 - split outgoing changes into a series of patches with
2627 2633 descriptive names::
2628 2634
2629 2635 hg export -r "outgoing()" -o "%n-%m.patch"
2630 2636
2631 2637 Returns 0 on success.
2632 2638 """
2633 2639 opts = pycompat.byteskwargs(opts)
2634 2640 bookmark = opts.get(b'bookmark')
2635 2641 changesets += tuple(opts.get(b'rev', []))
2636 2642
2637 2643 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2638 2644
2639 2645 if bookmark:
2640 2646 if bookmark not in repo._bookmarks:
2641 2647 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
2642 2648
2643 2649 revs = scmutil.bookmarkrevs(repo, bookmark)
2644 2650 else:
2645 2651 if not changesets:
2646 2652 changesets = [b'.']
2647 2653
2648 2654 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2649 2655 revs = scmutil.revrange(repo, changesets)
2650 2656
2651 2657 if not revs:
2652 2658 raise error.Abort(_(b"export requires at least one changeset"))
2653 2659 if len(revs) > 1:
2654 2660 ui.note(_(b'exporting patches:\n'))
2655 2661 else:
2656 2662 ui.note(_(b'exporting patch:\n'))
2657 2663
2658 2664 fntemplate = opts.get(b'output')
2659 2665 if cmdutil.isstdiofilename(fntemplate):
2660 2666 fntemplate = b''
2661 2667
2662 2668 if fntemplate:
2663 2669 fm = formatter.nullformatter(ui, b'export', opts)
2664 2670 else:
2665 2671 ui.pager(b'export')
2666 2672 fm = ui.formatter(b'export', opts)
2667 2673 with fm:
2668 2674 cmdutil.export(
2669 2675 repo,
2670 2676 revs,
2671 2677 fm,
2672 2678 fntemplate=fntemplate,
2673 2679 switch_parent=opts.get(b'switch_parent'),
2674 2680 opts=patch.diffallopts(ui, opts),
2675 2681 )
2676 2682
2677 2683
2678 2684 @command(
2679 2685 b'files',
2680 2686 [
2681 2687 (
2682 2688 b'r',
2683 2689 b'rev',
2684 2690 b'',
2685 2691 _(b'search the repository as it is in REV'),
2686 2692 _(b'REV'),
2687 2693 ),
2688 2694 (
2689 2695 b'0',
2690 2696 b'print0',
2691 2697 None,
2692 2698 _(b'end filenames with NUL, for use with xargs'),
2693 2699 ),
2694 2700 ]
2695 2701 + walkopts
2696 2702 + formatteropts
2697 2703 + subrepoopts,
2698 2704 _(b'[OPTION]... [FILE]...'),
2699 2705 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2700 2706 intents={INTENT_READONLY},
2701 2707 )
2702 2708 def files(ui, repo, *pats, **opts):
2703 2709 """list tracked files
2704 2710
2705 2711 Print files under Mercurial control in the working directory or
2706 2712 specified revision for given files (excluding removed files).
2707 2713 Files can be specified as filenames or filesets.
2708 2714
2709 2715 If no files are given to match, this command prints the names
2710 2716 of all files under Mercurial control.
2711 2717
2712 2718 .. container:: verbose
2713 2719
2714 2720 Template:
2715 2721
2716 2722 The following keywords are supported in addition to the common template
2717 2723 keywords and functions. See also :hg:`help templates`.
2718 2724
2719 2725 :flags: String. Character denoting file's symlink and executable bits.
2720 2726 :path: String. Repository-absolute path of the file.
2721 2727 :size: Integer. Size of the file in bytes.
2722 2728
2723 2729 Examples:
2724 2730
2725 2731 - list all files under the current directory::
2726 2732
2727 2733 hg files .
2728 2734
2729 2735 - shows sizes and flags for current revision::
2730 2736
2731 2737 hg files -vr .
2732 2738
2733 2739 - list all files named README::
2734 2740
2735 2741 hg files -I "**/README"
2736 2742
2737 2743 - list all binary files::
2738 2744
2739 2745 hg files "set:binary()"
2740 2746
2741 2747 - find files containing a regular expression::
2742 2748
2743 2749 hg files "set:grep('bob')"
2744 2750
2745 2751 - search tracked file contents with xargs and grep::
2746 2752
2747 2753 hg files -0 | xargs -0 grep foo
2748 2754
2749 2755 See :hg:`help patterns` and :hg:`help filesets` for more information
2750 2756 on specifying file patterns.
2751 2757
2752 2758 Returns 0 if a match is found, 1 otherwise.
2753 2759
2754 2760 """
2755 2761
2756 2762 opts = pycompat.byteskwargs(opts)
2757 2763 rev = opts.get(b'rev')
2758 2764 if rev:
2759 2765 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2760 2766 ctx = scmutil.revsingle(repo, rev, None)
2761 2767
2762 2768 end = b'\n'
2763 2769 if opts.get(b'print0'):
2764 2770 end = b'\0'
2765 2771 fmt = b'%s' + end
2766 2772
2767 2773 m = scmutil.match(ctx, pats, opts)
2768 2774 ui.pager(b'files')
2769 2775 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2770 2776 with ui.formatter(b'files', opts) as fm:
2771 2777 return cmdutil.files(
2772 2778 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2773 2779 )
2774 2780
2775 2781
2776 2782 @command(
2777 2783 b'forget',
2778 2784 [(b'i', b'interactive', None, _(b'use interactive mode')),]
2779 2785 + walkopts
2780 2786 + dryrunopts,
2781 2787 _(b'[OPTION]... FILE...'),
2782 2788 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2783 2789 helpbasic=True,
2784 2790 inferrepo=True,
2785 2791 )
2786 2792 def forget(ui, repo, *pats, **opts):
2787 2793 """forget the specified files on the next commit
2788 2794
2789 2795 Mark the specified files so they will no longer be tracked
2790 2796 after the next commit.
2791 2797
2792 2798 This only removes files from the current branch, not from the
2793 2799 entire project history, and it does not delete them from the
2794 2800 working directory.
2795 2801
2796 2802 To delete the file from the working directory, see :hg:`remove`.
2797 2803
2798 2804 To undo a forget before the next commit, see :hg:`add`.
2799 2805
2800 2806 .. container:: verbose
2801 2807
2802 2808 Examples:
2803 2809
2804 2810 - forget newly-added binary files::
2805 2811
2806 2812 hg forget "set:added() and binary()"
2807 2813
2808 2814 - forget files that would be excluded by .hgignore::
2809 2815
2810 2816 hg forget "set:hgignore()"
2811 2817
2812 2818 Returns 0 on success.
2813 2819 """
2814 2820
2815 2821 opts = pycompat.byteskwargs(opts)
2816 2822 if not pats:
2817 2823 raise error.Abort(_(b'no files specified'))
2818 2824
2819 2825 m = scmutil.match(repo[None], pats, opts)
2820 2826 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2821 2827 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2822 2828 rejected = cmdutil.forget(
2823 2829 ui,
2824 2830 repo,
2825 2831 m,
2826 2832 prefix=b"",
2827 2833 uipathfn=uipathfn,
2828 2834 explicitonly=False,
2829 2835 dryrun=dryrun,
2830 2836 interactive=interactive,
2831 2837 )[0]
2832 2838 return rejected and 1 or 0
2833 2839
2834 2840
2835 2841 @command(
2836 2842 b'graft',
2837 2843 [
2838 2844 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2839 2845 (
2840 2846 b'',
2841 2847 b'base',
2842 2848 b'',
2843 2849 _(b'base revision when doing the graft merge (ADVANCED)'),
2844 2850 _(b'REV'),
2845 2851 ),
2846 2852 (b'c', b'continue', False, _(b'resume interrupted graft')),
2847 2853 (b'', b'stop', False, _(b'stop interrupted graft')),
2848 2854 (b'', b'abort', False, _(b'abort interrupted graft')),
2849 2855 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2850 2856 (b'', b'log', None, _(b'append graft info to log message')),
2851 2857 (
2852 2858 b'',
2853 2859 b'no-commit',
2854 2860 None,
2855 2861 _(b"don't commit, just apply the changes in working directory"),
2856 2862 ),
2857 2863 (b'f', b'force', False, _(b'force graft')),
2858 2864 (
2859 2865 b'D',
2860 2866 b'currentdate',
2861 2867 False,
2862 2868 _(b'record the current date as commit date'),
2863 2869 ),
2864 2870 (
2865 2871 b'U',
2866 2872 b'currentuser',
2867 2873 False,
2868 2874 _(b'record the current user as committer'),
2869 2875 ),
2870 2876 ]
2871 2877 + commitopts2
2872 2878 + mergetoolopts
2873 2879 + dryrunopts,
2874 2880 _(b'[OPTION]... [-r REV]... REV...'),
2875 2881 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2876 2882 )
2877 2883 def graft(ui, repo, *revs, **opts):
2878 2884 '''copy changes from other branches onto the current branch
2879 2885
2880 2886 This command uses Mercurial's merge logic to copy individual
2881 2887 changes from other branches without merging branches in the
2882 2888 history graph. This is sometimes known as 'backporting' or
2883 2889 'cherry-picking'. By default, graft will copy user, date, and
2884 2890 description from the source changesets.
2885 2891
2886 2892 Changesets that are ancestors of the current revision, that have
2887 2893 already been grafted, or that are merges will be skipped.
2888 2894
2889 2895 If --log is specified, log messages will have a comment appended
2890 2896 of the form::
2891 2897
2892 2898 (grafted from CHANGESETHASH)
2893 2899
2894 2900 If --force is specified, revisions will be grafted even if they
2895 2901 are already ancestors of, or have been grafted to, the destination.
2896 2902 This is useful when the revisions have since been backed out.
2897 2903
2898 2904 If a graft merge results in conflicts, the graft process is
2899 2905 interrupted so that the current merge can be manually resolved.
2900 2906 Once all conflicts are addressed, the graft process can be
2901 2907 continued with the -c/--continue option.
2902 2908
2903 2909 The -c/--continue option reapplies all the earlier options.
2904 2910
2905 2911 .. container:: verbose
2906 2912
2907 2913 The --base option exposes more of how graft internally uses merge with a
2908 2914 custom base revision. --base can be used to specify another ancestor than
2909 2915 the first and only parent.
2910 2916
2911 2917 The command::
2912 2918
2913 2919 hg graft -r 345 --base 234
2914 2920
2915 2921 is thus pretty much the same as::
2916 2922
2917 2923 hg diff -r 234 -r 345 | hg import
2918 2924
2919 2925 but using merge to resolve conflicts and track moved files.
2920 2926
2921 2927 The result of a merge can thus be backported as a single commit by
2922 2928 specifying one of the merge parents as base, and thus effectively
2923 2929 grafting the changes from the other side.
2924 2930
2925 2931 It is also possible to collapse multiple changesets and clean up history
2926 2932 by specifying another ancestor as base, much like rebase --collapse
2927 2933 --keep.
2928 2934
2929 2935 The commit message can be tweaked after the fact using commit --amend .
2930 2936
2931 2937 For using non-ancestors as the base to backout changes, see the backout
2932 2938 command and the hidden --parent option.
2933 2939
2934 2940 .. container:: verbose
2935 2941
2936 2942 Examples:
2937 2943
2938 2944 - copy a single change to the stable branch and edit its description::
2939 2945
2940 2946 hg update stable
2941 2947 hg graft --edit 9393
2942 2948
2943 2949 - graft a range of changesets with one exception, updating dates::
2944 2950
2945 2951 hg graft -D "2085::2093 and not 2091"
2946 2952
2947 2953 - continue a graft after resolving conflicts::
2948 2954
2949 2955 hg graft -c
2950 2956
2951 2957 - show the source of a grafted changeset::
2952 2958
2953 2959 hg log --debug -r .
2954 2960
2955 2961 - show revisions sorted by date::
2956 2962
2957 2963 hg log -r "sort(all(), date)"
2958 2964
2959 2965 - backport the result of a merge as a single commit::
2960 2966
2961 2967 hg graft -r 123 --base 123^
2962 2968
2963 2969 - land a feature branch as one changeset::
2964 2970
2965 2971 hg up -cr default
2966 2972 hg graft -r featureX --base "ancestor('featureX', 'default')"
2967 2973
2968 2974 See :hg:`help revisions` for more about specifying revisions.
2969 2975
2970 2976 Returns 0 on successful completion, 1 if there are unresolved files.
2971 2977 '''
2972 2978 with repo.wlock():
2973 2979 return _dograft(ui, repo, *revs, **opts)
2974 2980
2975 2981
2976 2982 def _dograft(ui, repo, *revs, **opts):
2977 2983 opts = pycompat.byteskwargs(opts)
2978 2984 if revs and opts.get(b'rev'):
2979 2985 ui.warn(
2980 2986 _(
2981 2987 b'warning: inconsistent use of --rev might give unexpected '
2982 2988 b'revision ordering!\n'
2983 2989 )
2984 2990 )
2985 2991
2986 2992 revs = list(revs)
2987 2993 revs.extend(opts.get(b'rev'))
2988 2994 # a dict of data to be stored in state file
2989 2995 statedata = {}
2990 2996 # list of new nodes created by ongoing graft
2991 2997 statedata[b'newnodes'] = []
2992 2998
2993 2999 cmdutil.resolvecommitoptions(ui, opts)
2994 3000
2995 3001 editor = cmdutil.getcommiteditor(
2996 3002 editform=b'graft', **pycompat.strkwargs(opts)
2997 3003 )
2998 3004
2999 3005 cmdutil.check_at_most_one_arg(opts, b'abort', b'stop', b'continue')
3000 3006
3001 3007 cont = False
3002 3008 if opts.get(b'no_commit'):
3003 3009 cmdutil.check_incompatible_arguments(
3004 3010 opts,
3005 3011 b'no_commit',
3006 3012 [b'edit', b'currentuser', b'currentdate', b'log'],
3007 3013 )
3008 3014
3009 3015 graftstate = statemod.cmdstate(repo, b'graftstate')
3010 3016
3011 3017 if opts.get(b'stop'):
3012 3018 cmdutil.check_incompatible_arguments(
3013 3019 opts,
3014 3020 b'stop',
3015 3021 [
3016 3022 b'edit',
3017 3023 b'log',
3018 3024 b'user',
3019 3025 b'date',
3020 3026 b'currentdate',
3021 3027 b'currentuser',
3022 3028 b'rev',
3023 3029 ],
3024 3030 )
3025 3031 return _stopgraft(ui, repo, graftstate)
3026 3032 elif opts.get(b'abort'):
3027 3033 cmdutil.check_incompatible_arguments(
3028 3034 opts,
3029 3035 b'abort',
3030 3036 [
3031 3037 b'edit',
3032 3038 b'log',
3033 3039 b'user',
3034 3040 b'date',
3035 3041 b'currentdate',
3036 3042 b'currentuser',
3037 3043 b'rev',
3038 3044 ],
3039 3045 )
3040 3046 return cmdutil.abortgraft(ui, repo, graftstate)
3041 3047 elif opts.get(b'continue'):
3042 3048 cont = True
3043 3049 if revs:
3044 3050 raise error.Abort(_(b"can't specify --continue and revisions"))
3045 3051 # read in unfinished revisions
3046 3052 if graftstate.exists():
3047 3053 statedata = cmdutil.readgraftstate(repo, graftstate)
3048 3054 if statedata.get(b'date'):
3049 3055 opts[b'date'] = statedata[b'date']
3050 3056 if statedata.get(b'user'):
3051 3057 opts[b'user'] = statedata[b'user']
3052 3058 if statedata.get(b'log'):
3053 3059 opts[b'log'] = True
3054 3060 if statedata.get(b'no_commit'):
3055 3061 opts[b'no_commit'] = statedata.get(b'no_commit')
3056 3062 if statedata.get(b'base'):
3057 3063 opts[b'base'] = statedata.get(b'base')
3058 3064 nodes = statedata[b'nodes']
3059 3065 revs = [repo[node].rev() for node in nodes]
3060 3066 else:
3061 3067 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3062 3068 else:
3063 3069 if not revs:
3064 3070 raise error.Abort(_(b'no revisions specified'))
3065 3071 cmdutil.checkunfinished(repo)
3066 3072 cmdutil.bailifchanged(repo)
3067 3073 revs = scmutil.revrange(repo, revs)
3068 3074
3069 3075 skipped = set()
3070 3076 basectx = None
3071 3077 if opts.get(b'base'):
3072 3078 basectx = scmutil.revsingle(repo, opts[b'base'], None)
3073 3079 if basectx is None:
3074 3080 # check for merges
3075 3081 for rev in repo.revs(b'%ld and merge()', revs):
3076 3082 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3077 3083 skipped.add(rev)
3078 3084 revs = [r for r in revs if r not in skipped]
3079 3085 if not revs:
3080 3086 return -1
3081 3087 if basectx is not None and len(revs) != 1:
3082 3088 raise error.Abort(_(b'only one revision allowed with --base '))
3083 3089
3084 3090 # Don't check in the --continue case, in effect retaining --force across
3085 3091 # --continues. That's because without --force, any revisions we decided to
3086 3092 # skip would have been filtered out here, so they wouldn't have made their
3087 3093 # way to the graftstate. With --force, any revisions we would have otherwise
3088 3094 # skipped would not have been filtered out, and if they hadn't been applied
3089 3095 # already, they'd have been in the graftstate.
3090 3096 if not (cont or opts.get(b'force')) and basectx is None:
3091 3097 # check for ancestors of dest branch
3092 3098 ancestors = repo.revs(b'%ld & (::.)', revs)
3093 3099 for rev in ancestors:
3094 3100 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3095 3101
3096 3102 revs = [r for r in revs if r not in ancestors]
3097 3103
3098 3104 if not revs:
3099 3105 return -1
3100 3106
3101 3107 # analyze revs for earlier grafts
3102 3108 ids = {}
3103 3109 for ctx in repo.set(b"%ld", revs):
3104 3110 ids[ctx.hex()] = ctx.rev()
3105 3111 n = ctx.extra().get(b'source')
3106 3112 if n:
3107 3113 ids[n] = ctx.rev()
3108 3114
3109 3115 # check ancestors for earlier grafts
3110 3116 ui.debug(b'scanning for duplicate grafts\n')
3111 3117
3112 3118 # The only changesets we can be sure doesn't contain grafts of any
3113 3119 # revs, are the ones that are common ancestors of *all* revs:
3114 3120 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3115 3121 ctx = repo[rev]
3116 3122 n = ctx.extra().get(b'source')
3117 3123 if n in ids:
3118 3124 try:
3119 3125 r = repo[n].rev()
3120 3126 except error.RepoLookupError:
3121 3127 r = None
3122 3128 if r in revs:
3123 3129 ui.warn(
3124 3130 _(
3125 3131 b'skipping revision %d:%s '
3126 3132 b'(already grafted to %d:%s)\n'
3127 3133 )
3128 3134 % (r, repo[r], rev, ctx)
3129 3135 )
3130 3136 revs.remove(r)
3131 3137 elif ids[n] in revs:
3132 3138 if r is None:
3133 3139 ui.warn(
3134 3140 _(
3135 3141 b'skipping already grafted revision %d:%s '
3136 3142 b'(%d:%s also has unknown origin %s)\n'
3137 3143 )
3138 3144 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3139 3145 )
3140 3146 else:
3141 3147 ui.warn(
3142 3148 _(
3143 3149 b'skipping already grafted revision %d:%s '
3144 3150 b'(%d:%s also has origin %d:%s)\n'
3145 3151 )
3146 3152 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3147 3153 )
3148 3154 revs.remove(ids[n])
3149 3155 elif ctx.hex() in ids:
3150 3156 r = ids[ctx.hex()]
3151 3157 if r in revs:
3152 3158 ui.warn(
3153 3159 _(
3154 3160 b'skipping already grafted revision %d:%s '
3155 3161 b'(was grafted from %d:%s)\n'
3156 3162 )
3157 3163 % (r, repo[r], rev, ctx)
3158 3164 )
3159 3165 revs.remove(r)
3160 3166 if not revs:
3161 3167 return -1
3162 3168
3163 3169 if opts.get(b'no_commit'):
3164 3170 statedata[b'no_commit'] = True
3165 3171 if opts.get(b'base'):
3166 3172 statedata[b'base'] = opts[b'base']
3167 3173 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3168 3174 desc = b'%d:%s "%s"' % (
3169 3175 ctx.rev(),
3170 3176 ctx,
3171 3177 ctx.description().split(b'\n', 1)[0],
3172 3178 )
3173 3179 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3174 3180 if names:
3175 3181 desc += b' (%s)' % b' '.join(names)
3176 3182 ui.status(_(b'grafting %s\n') % desc)
3177 3183 if opts.get(b'dry_run'):
3178 3184 continue
3179 3185
3180 3186 source = ctx.extra().get(b'source')
3181 3187 extra = {}
3182 3188 if source:
3183 3189 extra[b'source'] = source
3184 3190 extra[b'intermediate-source'] = ctx.hex()
3185 3191 else:
3186 3192 extra[b'source'] = ctx.hex()
3187 3193 user = ctx.user()
3188 3194 if opts.get(b'user'):
3189 3195 user = opts[b'user']
3190 3196 statedata[b'user'] = user
3191 3197 date = ctx.date()
3192 3198 if opts.get(b'date'):
3193 3199 date = opts[b'date']
3194 3200 statedata[b'date'] = date
3195 3201 message = ctx.description()
3196 3202 if opts.get(b'log'):
3197 3203 message += b'\n(grafted from %s)' % ctx.hex()
3198 3204 statedata[b'log'] = True
3199 3205
3200 3206 # we don't merge the first commit when continuing
3201 3207 if not cont:
3202 3208 # perform the graft merge with p1(rev) as 'ancestor'
3203 3209 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3204 3210 base = ctx.p1() if basectx is None else basectx
3205 3211 with ui.configoverride(overrides, b'graft'):
3206 3212 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3207 3213 # report any conflicts
3208 3214 if stats.unresolvedcount > 0:
3209 3215 # write out state for --continue
3210 3216 nodes = [repo[rev].hex() for rev in revs[pos:]]
3211 3217 statedata[b'nodes'] = nodes
3212 3218 stateversion = 1
3213 3219 graftstate.save(stateversion, statedata)
3214 3220 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3215 3221 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3216 3222 return 1
3217 3223 else:
3218 3224 cont = False
3219 3225
3220 3226 # commit if --no-commit is false
3221 3227 if not opts.get(b'no_commit'):
3222 3228 node = repo.commit(
3223 3229 text=message, user=user, date=date, extra=extra, editor=editor
3224 3230 )
3225 3231 if node is None:
3226 3232 ui.warn(
3227 3233 _(b'note: graft of %d:%s created no changes to commit\n')
3228 3234 % (ctx.rev(), ctx)
3229 3235 )
3230 3236 # checking that newnodes exist because old state files won't have it
3231 3237 elif statedata.get(b'newnodes') is not None:
3232 3238 statedata[b'newnodes'].append(node)
3233 3239
3234 3240 # remove state when we complete successfully
3235 3241 if not opts.get(b'dry_run'):
3236 3242 graftstate.delete()
3237 3243
3238 3244 return 0
3239 3245
3240 3246
3241 3247 def _stopgraft(ui, repo, graftstate):
3242 3248 """stop the interrupted graft"""
3243 3249 if not graftstate.exists():
3244 3250 raise error.Abort(_(b"no interrupted graft found"))
3245 3251 pctx = repo[b'.']
3246 3252 mergemod.clean_update(pctx)
3247 3253 graftstate.delete()
3248 3254 ui.status(_(b"stopped the interrupted graft\n"))
3249 3255 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3250 3256 return 0
3251 3257
3252 3258
3253 3259 statemod.addunfinished(
3254 3260 b'graft',
3255 3261 fname=b'graftstate',
3256 3262 clearable=True,
3257 3263 stopflag=True,
3258 3264 continueflag=True,
3259 3265 abortfunc=cmdutil.hgabortgraft,
3260 3266 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3261 3267 )
3262 3268
3263 3269
3264 3270 @command(
3265 3271 b'grep',
3266 3272 [
3267 3273 (b'0', b'print0', None, _(b'end fields with NUL')),
3268 3274 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3269 3275 (
3270 3276 b'',
3271 3277 b'diff',
3272 3278 None,
3273 3279 _(
3274 3280 b'search revision differences for when the pattern was added '
3275 3281 b'or removed'
3276 3282 ),
3277 3283 ),
3278 3284 (b'a', b'text', None, _(b'treat all files as text')),
3279 3285 (
3280 3286 b'f',
3281 3287 b'follow',
3282 3288 None,
3283 3289 _(
3284 3290 b'follow changeset history,'
3285 3291 b' or file history across copies and renames'
3286 3292 ),
3287 3293 ),
3288 3294 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3289 3295 (
3290 3296 b'l',
3291 3297 b'files-with-matches',
3292 3298 None,
3293 3299 _(b'print only filenames and revisions that match'),
3294 3300 ),
3295 3301 (b'n', b'line-number', None, _(b'print matching line numbers')),
3296 3302 (
3297 3303 b'r',
3298 3304 b'rev',
3299 3305 [],
3300 3306 _(b'search files changed within revision range'),
3301 3307 _(b'REV'),
3302 3308 ),
3303 3309 (
3304 3310 b'',
3305 3311 b'all-files',
3306 3312 None,
3307 3313 _(
3308 3314 b'include all files in the changeset while grepping (DEPRECATED)'
3309 3315 ),
3310 3316 ),
3311 3317 (b'u', b'user', None, _(b'list the author (long with -v)')),
3312 3318 (b'd', b'date', None, _(b'list the date (short with -q)')),
3313 3319 ]
3314 3320 + formatteropts
3315 3321 + walkopts,
3316 3322 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3317 3323 helpcategory=command.CATEGORY_FILE_CONTENTS,
3318 3324 inferrepo=True,
3319 3325 intents={INTENT_READONLY},
3320 3326 )
3321 3327 def grep(ui, repo, pattern, *pats, **opts):
3322 3328 """search for a pattern in specified files
3323 3329
3324 3330 Search the working directory or revision history for a regular
3325 3331 expression in the specified files for the entire repository.
3326 3332
3327 3333 By default, grep searches the repository files in the working
3328 3334 directory and prints the files where it finds a match. To specify
3329 3335 historical revisions instead of the working directory, use the
3330 3336 --rev flag.
3331 3337
3332 3338 To search instead historical revision differences that contains a
3333 3339 change in match status ("-" for a match that becomes a non-match,
3334 3340 or "+" for a non-match that becomes a match), use the --diff flag.
3335 3341
3336 3342 PATTERN can be any Python (roughly Perl-compatible) regular
3337 3343 expression.
3338 3344
3339 3345 If no FILEs are specified and the --rev flag isn't supplied, all
3340 3346 files in the working directory are searched. When using the --rev
3341 3347 flag and specifying FILEs, use the --follow argument to also
3342 3348 follow the specified FILEs across renames and copies.
3343 3349
3344 3350 .. container:: verbose
3345 3351
3346 3352 Template:
3347 3353
3348 3354 The following keywords are supported in addition to the common template
3349 3355 keywords and functions. See also :hg:`help templates`.
3350 3356
3351 3357 :change: String. Character denoting insertion ``+`` or removal ``-``.
3352 3358 Available if ``--diff`` is specified.
3353 3359 :lineno: Integer. Line number of the match.
3354 3360 :path: String. Repository-absolute path of the file.
3355 3361 :texts: List of text chunks.
3356 3362
3357 3363 And each entry of ``{texts}`` provides the following sub-keywords.
3358 3364
3359 3365 :matched: Boolean. True if the chunk matches the specified pattern.
3360 3366 :text: String. Chunk content.
3361 3367
3362 3368 See :hg:`help templates.operators` for the list expansion syntax.
3363 3369
3364 3370 Returns 0 if a match is found, 1 otherwise.
3365 3371
3366 3372 """
3367 3373 cmdutil.check_incompatible_arguments(opts, 'all_files', ['all', 'diff'])
3368 3374 opts = pycompat.byteskwargs(opts)
3369 3375 diff = opts.get(b'all') or opts.get(b'diff')
3370 3376 follow = opts.get(b'follow')
3371 3377 if opts.get(b'all_files') is None and not diff:
3372 3378 opts[b'all_files'] = True
3373 3379 plaingrep = (
3374 3380 opts.get(b'all_files')
3375 3381 and not opts.get(b'rev')
3376 3382 and not opts.get(b'follow')
3377 3383 )
3378 3384 all_files = opts.get(b'all_files')
3379 3385 if plaingrep:
3380 3386 opts[b'rev'] = [b'wdir()']
3381 3387
3382 3388 reflags = re.M
3383 3389 if opts.get(b'ignore_case'):
3384 3390 reflags |= re.I
3385 3391 try:
3386 3392 regexp = util.re.compile(pattern, reflags)
3387 3393 except re.error as inst:
3388 3394 ui.warn(
3389 3395 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3390 3396 )
3391 3397 return 1
3392 3398 sep, eol = b':', b'\n'
3393 3399 if opts.get(b'print0'):
3394 3400 sep = eol = b'\0'
3395 3401
3396 3402 searcher = grepmod.grepsearcher(
3397 3403 ui, repo, regexp, all_files=all_files, diff=diff, follow=follow
3398 3404 )
3399 3405
3400 3406 getfile = searcher._getfile
3401 3407
3402 3408 uipathfn = scmutil.getuipathfn(repo)
3403 3409
3404 3410 def display(fm, fn, ctx, pstates, states):
3405 3411 rev = scmutil.intrev(ctx)
3406 3412 if fm.isplain():
3407 3413 formatuser = ui.shortuser
3408 3414 else:
3409 3415 formatuser = pycompat.bytestr
3410 3416 if ui.quiet:
3411 3417 datefmt = b'%Y-%m-%d'
3412 3418 else:
3413 3419 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3414 3420 found = False
3415 3421
3416 3422 @util.cachefunc
3417 3423 def binary():
3418 3424 flog = getfile(fn)
3419 3425 try:
3420 3426 return stringutil.binary(flog.read(ctx.filenode(fn)))
3421 3427 except error.WdirUnsupported:
3422 3428 return ctx[fn].isbinary()
3423 3429
3424 3430 fieldnamemap = {b'linenumber': b'lineno'}
3425 3431 if diff:
3426 3432 iter = grepmod.difflinestates(pstates, states)
3427 3433 else:
3428 3434 iter = [(b'', l) for l in states]
3429 3435 for change, l in iter:
3430 3436 fm.startitem()
3431 3437 fm.context(ctx=ctx)
3432 3438 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3433 3439 fm.plain(uipathfn(fn), label=b'grep.filename')
3434 3440
3435 3441 cols = [
3436 3442 (b'rev', b'%d', rev, not plaingrep, b''),
3437 3443 (
3438 3444 b'linenumber',
3439 3445 b'%d',
3440 3446 l.linenum,
3441 3447 opts.get(b'line_number'),
3442 3448 b'',
3443 3449 ),
3444 3450 ]
3445 3451 if diff:
3446 3452 cols.append(
3447 3453 (
3448 3454 b'change',
3449 3455 b'%s',
3450 3456 change,
3451 3457 True,
3452 3458 b'grep.inserted '
3453 3459 if change == b'+'
3454 3460 else b'grep.deleted ',
3455 3461 )
3456 3462 )
3457 3463 cols.extend(
3458 3464 [
3459 3465 (
3460 3466 b'user',
3461 3467 b'%s',
3462 3468 formatuser(ctx.user()),
3463 3469 opts.get(b'user'),
3464 3470 b'',
3465 3471 ),
3466 3472 (
3467 3473 b'date',
3468 3474 b'%s',
3469 3475 fm.formatdate(ctx.date(), datefmt),
3470 3476 opts.get(b'date'),
3471 3477 b'',
3472 3478 ),
3473 3479 ]
3474 3480 )
3475 3481 for name, fmt, data, cond, extra_label in cols:
3476 3482 if cond:
3477 3483 fm.plain(sep, label=b'grep.sep')
3478 3484 field = fieldnamemap.get(name, name)
3479 3485 label = extra_label + (b'grep.%s' % name)
3480 3486 fm.condwrite(cond, field, fmt, data, label=label)
3481 3487 if not opts.get(b'files_with_matches'):
3482 3488 fm.plain(sep, label=b'grep.sep')
3483 3489 if not opts.get(b'text') and binary():
3484 3490 fm.plain(_(b" Binary file matches"))
3485 3491 else:
3486 3492 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3487 3493 fm.plain(eol)
3488 3494 found = True
3489 3495 if opts.get(b'files_with_matches'):
3490 3496 break
3491 3497 return found
3492 3498
3493 3499 def displaymatches(fm, l):
3494 3500 p = 0
3495 3501 for s, e in l.findpos(regexp):
3496 3502 if p < s:
3497 3503 fm.startitem()
3498 3504 fm.write(b'text', b'%s', l.line[p:s])
3499 3505 fm.data(matched=False)
3500 3506 fm.startitem()
3501 3507 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3502 3508 fm.data(matched=True)
3503 3509 p = e
3504 3510 if p < len(l.line):
3505 3511 fm.startitem()
3506 3512 fm.write(b'text', b'%s', l.line[p:])
3507 3513 fm.data(matched=False)
3508 3514 fm.end()
3509 3515
3510 3516 found = False
3511 3517
3512 3518 wopts = logcmdutil.walkopts(
3513 3519 pats=pats,
3514 3520 opts=opts,
3515 3521 revspec=opts[b'rev'],
3516 3522 include_pats=opts[b'include'],
3517 3523 exclude_pats=opts[b'exclude'],
3518 3524 follow=follow,
3519 3525 force_changelog_traversal=all_files,
3520 3526 filter_revisions_by_pats=not all_files,
3521 3527 )
3522 3528 revs, makefilematcher = logcmdutil.makewalker(repo, wopts)
3523 3529
3524 3530 ui.pager(b'grep')
3525 3531 fm = ui.formatter(b'grep', opts)
3526 3532 for fn, ctx, pstates, states in searcher.searchfiles(revs, makefilematcher):
3527 3533 r = display(fm, fn, ctx, pstates, states)
3528 3534 found = found or r
3529 3535 if r and not diff and not all_files:
3530 3536 searcher.skipfile(fn, ctx.rev())
3531 3537 fm.end()
3532 3538
3533 3539 return not found
3534 3540
3535 3541
3536 3542 @command(
3537 3543 b'heads',
3538 3544 [
3539 3545 (
3540 3546 b'r',
3541 3547 b'rev',
3542 3548 b'',
3543 3549 _(b'show only heads which are descendants of STARTREV'),
3544 3550 _(b'STARTREV'),
3545 3551 ),
3546 3552 (b't', b'topo', False, _(b'show topological heads only')),
3547 3553 (
3548 3554 b'a',
3549 3555 b'active',
3550 3556 False,
3551 3557 _(b'show active branchheads only (DEPRECATED)'),
3552 3558 ),
3553 3559 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3554 3560 ]
3555 3561 + templateopts,
3556 3562 _(b'[-ct] [-r STARTREV] [REV]...'),
3557 3563 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3558 3564 intents={INTENT_READONLY},
3559 3565 )
3560 3566 def heads(ui, repo, *branchrevs, **opts):
3561 3567 """show branch heads
3562 3568
3563 3569 With no arguments, show all open branch heads in the repository.
3564 3570 Branch heads are changesets that have no descendants on the
3565 3571 same branch. They are where development generally takes place and
3566 3572 are the usual targets for update and merge operations.
3567 3573
3568 3574 If one or more REVs are given, only open branch heads on the
3569 3575 branches associated with the specified changesets are shown. This
3570 3576 means that you can use :hg:`heads .` to see the heads on the
3571 3577 currently checked-out branch.
3572 3578
3573 3579 If -c/--closed is specified, also show branch heads marked closed
3574 3580 (see :hg:`commit --close-branch`).
3575 3581
3576 3582 If STARTREV is specified, only those heads that are descendants of
3577 3583 STARTREV will be displayed.
3578 3584
3579 3585 If -t/--topo is specified, named branch mechanics will be ignored and only
3580 3586 topological heads (changesets with no children) will be shown.
3581 3587
3582 3588 Returns 0 if matching heads are found, 1 if not.
3583 3589 """
3584 3590
3585 3591 opts = pycompat.byteskwargs(opts)
3586 3592 start = None
3587 3593 rev = opts.get(b'rev')
3588 3594 if rev:
3589 3595 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3590 3596 start = scmutil.revsingle(repo, rev, None).node()
3591 3597
3592 3598 if opts.get(b'topo'):
3593 3599 heads = [repo[h] for h in repo.heads(start)]
3594 3600 else:
3595 3601 heads = []
3596 3602 for branch in repo.branchmap():
3597 3603 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3598 3604 heads = [repo[h] for h in heads]
3599 3605
3600 3606 if branchrevs:
3601 3607 branches = {
3602 3608 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3603 3609 }
3604 3610 heads = [h for h in heads if h.branch() in branches]
3605 3611
3606 3612 if opts.get(b'active') and branchrevs:
3607 3613 dagheads = repo.heads(start)
3608 3614 heads = [h for h in heads if h.node() in dagheads]
3609 3615
3610 3616 if branchrevs:
3611 3617 haveheads = {h.branch() for h in heads}
3612 3618 if branches - haveheads:
3613 3619 headless = b', '.join(b for b in branches - haveheads)
3614 3620 msg = _(b'no open branch heads found on branches %s')
3615 3621 if opts.get(b'rev'):
3616 3622 msg += _(b' (started at %s)') % opts[b'rev']
3617 3623 ui.warn((msg + b'\n') % headless)
3618 3624
3619 3625 if not heads:
3620 3626 return 1
3621 3627
3622 3628 ui.pager(b'heads')
3623 3629 heads = sorted(heads, key=lambda x: -(x.rev()))
3624 3630 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3625 3631 for ctx in heads:
3626 3632 displayer.show(ctx)
3627 3633 displayer.close()
3628 3634
3629 3635
3630 3636 @command(
3631 3637 b'help',
3632 3638 [
3633 3639 (b'e', b'extension', None, _(b'show only help for extensions')),
3634 3640 (b'c', b'command', None, _(b'show only help for commands')),
3635 3641 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3636 3642 (
3637 3643 b's',
3638 3644 b'system',
3639 3645 [],
3640 3646 _(b'show help for specific platform(s)'),
3641 3647 _(b'PLATFORM'),
3642 3648 ),
3643 3649 ],
3644 3650 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3645 3651 helpcategory=command.CATEGORY_HELP,
3646 3652 norepo=True,
3647 3653 intents={INTENT_READONLY},
3648 3654 )
3649 3655 def help_(ui, name=None, **opts):
3650 3656 """show help for a given topic or a help overview
3651 3657
3652 3658 With no arguments, print a list of commands with short help messages.
3653 3659
3654 3660 Given a topic, extension, or command name, print help for that
3655 3661 topic.
3656 3662
3657 3663 Returns 0 if successful.
3658 3664 """
3659 3665
3660 3666 keep = opts.get('system') or []
3661 3667 if len(keep) == 0:
3662 3668 if pycompat.sysplatform.startswith(b'win'):
3663 3669 keep.append(b'windows')
3664 3670 elif pycompat.sysplatform == b'OpenVMS':
3665 3671 keep.append(b'vms')
3666 3672 elif pycompat.sysplatform == b'plan9':
3667 3673 keep.append(b'plan9')
3668 3674 else:
3669 3675 keep.append(b'unix')
3670 3676 keep.append(pycompat.sysplatform.lower())
3671 3677 if ui.verbose:
3672 3678 keep.append(b'verbose')
3673 3679
3674 3680 commands = sys.modules[__name__]
3675 3681 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3676 3682 ui.pager(b'help')
3677 3683 ui.write(formatted)
3678 3684
3679 3685
3680 3686 @command(
3681 3687 b'identify|id',
3682 3688 [
3683 3689 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3684 3690 (b'n', b'num', None, _(b'show local revision number')),
3685 3691 (b'i', b'id', None, _(b'show global revision id')),
3686 3692 (b'b', b'branch', None, _(b'show branch')),
3687 3693 (b't', b'tags', None, _(b'show tags')),
3688 3694 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3689 3695 ]
3690 3696 + remoteopts
3691 3697 + formatteropts,
3692 3698 _(b'[-nibtB] [-r REV] [SOURCE]'),
3693 3699 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3694 3700 optionalrepo=True,
3695 3701 intents={INTENT_READONLY},
3696 3702 )
3697 3703 def identify(
3698 3704 ui,
3699 3705 repo,
3700 3706 source=None,
3701 3707 rev=None,
3702 3708 num=None,
3703 3709 id=None,
3704 3710 branch=None,
3705 3711 tags=None,
3706 3712 bookmarks=None,
3707 3713 **opts
3708 3714 ):
3709 3715 """identify the working directory or specified revision
3710 3716
3711 3717 Print a summary identifying the repository state at REV using one or
3712 3718 two parent hash identifiers, followed by a "+" if the working
3713 3719 directory has uncommitted changes, the branch name (if not default),
3714 3720 a list of tags, and a list of bookmarks.
3715 3721
3716 3722 When REV is not given, print a summary of the current state of the
3717 3723 repository including the working directory. Specify -r. to get information
3718 3724 of the working directory parent without scanning uncommitted changes.
3719 3725
3720 3726 Specifying a path to a repository root or Mercurial bundle will
3721 3727 cause lookup to operate on that repository/bundle.
3722 3728
3723 3729 .. container:: verbose
3724 3730
3725 3731 Template:
3726 3732
3727 3733 The following keywords are supported in addition to the common template
3728 3734 keywords and functions. See also :hg:`help templates`.
3729 3735
3730 3736 :dirty: String. Character ``+`` denoting if the working directory has
3731 3737 uncommitted changes.
3732 3738 :id: String. One or two nodes, optionally followed by ``+``.
3733 3739 :parents: List of strings. Parent nodes of the changeset.
3734 3740
3735 3741 Examples:
3736 3742
3737 3743 - generate a build identifier for the working directory::
3738 3744
3739 3745 hg id --id > build-id.dat
3740 3746
3741 3747 - find the revision corresponding to a tag::
3742 3748
3743 3749 hg id -n -r 1.3
3744 3750
3745 3751 - check the most recent revision of a remote repository::
3746 3752
3747 3753 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3748 3754
3749 3755 See :hg:`log` for generating more information about specific revisions,
3750 3756 including full hash identifiers.
3751 3757
3752 3758 Returns 0 if successful.
3753 3759 """
3754 3760
3755 3761 opts = pycompat.byteskwargs(opts)
3756 3762 if not repo and not source:
3757 3763 raise error.Abort(
3758 3764 _(b"there is no Mercurial repository here (.hg not found)")
3759 3765 )
3760 3766
3761 3767 default = not (num or id or branch or tags or bookmarks)
3762 3768 output = []
3763 3769 revs = []
3764 3770
3765 3771 if source:
3766 3772 source, branches = hg.parseurl(ui.expandpath(source))
3767 3773 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3768 3774 repo = peer.local()
3769 3775 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3770 3776
3771 3777 fm = ui.formatter(b'identify', opts)
3772 3778 fm.startitem()
3773 3779
3774 3780 if not repo:
3775 3781 if num or branch or tags:
3776 3782 raise error.Abort(
3777 3783 _(b"can't query remote revision number, branch, or tags")
3778 3784 )
3779 3785 if not rev and revs:
3780 3786 rev = revs[0]
3781 3787 if not rev:
3782 3788 rev = b"tip"
3783 3789
3784 3790 remoterev = peer.lookup(rev)
3785 3791 hexrev = fm.hexfunc(remoterev)
3786 3792 if default or id:
3787 3793 output = [hexrev]
3788 3794 fm.data(id=hexrev)
3789 3795
3790 3796 @util.cachefunc
3791 3797 def getbms():
3792 3798 bms = []
3793 3799
3794 3800 if b'bookmarks' in peer.listkeys(b'namespaces'):
3795 3801 hexremoterev = hex(remoterev)
3796 3802 bms = [
3797 3803 bm
3798 3804 for bm, bmr in pycompat.iteritems(
3799 3805 peer.listkeys(b'bookmarks')
3800 3806 )
3801 3807 if bmr == hexremoterev
3802 3808 ]
3803 3809
3804 3810 return sorted(bms)
3805 3811
3806 3812 if fm.isplain():
3807 3813 if bookmarks:
3808 3814 output.extend(getbms())
3809 3815 elif default and not ui.quiet:
3810 3816 # multiple bookmarks for a single parent separated by '/'
3811 3817 bm = b'/'.join(getbms())
3812 3818 if bm:
3813 3819 output.append(bm)
3814 3820 else:
3815 3821 fm.data(node=hex(remoterev))
3816 3822 if bookmarks or b'bookmarks' in fm.datahint():
3817 3823 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3818 3824 else:
3819 3825 if rev:
3820 3826 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3821 3827 ctx = scmutil.revsingle(repo, rev, None)
3822 3828
3823 3829 if ctx.rev() is None:
3824 3830 ctx = repo[None]
3825 3831 parents = ctx.parents()
3826 3832 taglist = []
3827 3833 for p in parents:
3828 3834 taglist.extend(p.tags())
3829 3835
3830 3836 dirty = b""
3831 3837 if ctx.dirty(missing=True, merge=False, branch=False):
3832 3838 dirty = b'+'
3833 3839 fm.data(dirty=dirty)
3834 3840
3835 3841 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3836 3842 if default or id:
3837 3843 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3838 3844 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3839 3845
3840 3846 if num:
3841 3847 numoutput = [b"%d" % p.rev() for p in parents]
3842 3848 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3843 3849
3844 3850 fm.data(
3845 3851 parents=fm.formatlist(
3846 3852 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3847 3853 )
3848 3854 )
3849 3855 else:
3850 3856 hexoutput = fm.hexfunc(ctx.node())
3851 3857 if default or id:
3852 3858 output = [hexoutput]
3853 3859 fm.data(id=hexoutput)
3854 3860
3855 3861 if num:
3856 3862 output.append(pycompat.bytestr(ctx.rev()))
3857 3863 taglist = ctx.tags()
3858 3864
3859 3865 if default and not ui.quiet:
3860 3866 b = ctx.branch()
3861 3867 if b != b'default':
3862 3868 output.append(b"(%s)" % b)
3863 3869
3864 3870 # multiple tags for a single parent separated by '/'
3865 3871 t = b'/'.join(taglist)
3866 3872 if t:
3867 3873 output.append(t)
3868 3874
3869 3875 # multiple bookmarks for a single parent separated by '/'
3870 3876 bm = b'/'.join(ctx.bookmarks())
3871 3877 if bm:
3872 3878 output.append(bm)
3873 3879 else:
3874 3880 if branch:
3875 3881 output.append(ctx.branch())
3876 3882
3877 3883 if tags:
3878 3884 output.extend(taglist)
3879 3885
3880 3886 if bookmarks:
3881 3887 output.extend(ctx.bookmarks())
3882 3888
3883 3889 fm.data(node=ctx.hex())
3884 3890 fm.data(branch=ctx.branch())
3885 3891 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
3886 3892 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
3887 3893 fm.context(ctx=ctx)
3888 3894
3889 3895 fm.plain(b"%s\n" % b' '.join(output))
3890 3896 fm.end()
3891 3897
3892 3898
3893 3899 @command(
3894 3900 b'import|patch',
3895 3901 [
3896 3902 (
3897 3903 b'p',
3898 3904 b'strip',
3899 3905 1,
3900 3906 _(
3901 3907 b'directory strip option for patch. This has the same '
3902 3908 b'meaning as the corresponding patch option'
3903 3909 ),
3904 3910 _(b'NUM'),
3905 3911 ),
3906 3912 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
3907 3913 (b'', b'secret', None, _(b'use the secret phase for committing')),
3908 3914 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
3909 3915 (
3910 3916 b'f',
3911 3917 b'force',
3912 3918 None,
3913 3919 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
3914 3920 ),
3915 3921 (
3916 3922 b'',
3917 3923 b'no-commit',
3918 3924 None,
3919 3925 _(b"don't commit, just update the working directory"),
3920 3926 ),
3921 3927 (
3922 3928 b'',
3923 3929 b'bypass',
3924 3930 None,
3925 3931 _(b"apply patch without touching the working directory"),
3926 3932 ),
3927 3933 (b'', b'partial', None, _(b'commit even if some hunks fail')),
3928 3934 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
3929 3935 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
3930 3936 (
3931 3937 b'',
3932 3938 b'import-branch',
3933 3939 None,
3934 3940 _(b'use any branch information in patch (implied by --exact)'),
3935 3941 ),
3936 3942 ]
3937 3943 + commitopts
3938 3944 + commitopts2
3939 3945 + similarityopts,
3940 3946 _(b'[OPTION]... PATCH...'),
3941 3947 helpcategory=command.CATEGORY_IMPORT_EXPORT,
3942 3948 )
3943 3949 def import_(ui, repo, patch1=None, *patches, **opts):
3944 3950 """import an ordered set of patches
3945 3951
3946 3952 Import a list of patches and commit them individually (unless
3947 3953 --no-commit is specified).
3948 3954
3949 3955 To read a patch from standard input (stdin), use "-" as the patch
3950 3956 name. If a URL is specified, the patch will be downloaded from
3951 3957 there.
3952 3958
3953 3959 Import first applies changes to the working directory (unless
3954 3960 --bypass is specified), import will abort if there are outstanding
3955 3961 changes.
3956 3962
3957 3963 Use --bypass to apply and commit patches directly to the
3958 3964 repository, without affecting the working directory. Without
3959 3965 --exact, patches will be applied on top of the working directory
3960 3966 parent revision.
3961 3967
3962 3968 You can import a patch straight from a mail message. Even patches
3963 3969 as attachments work (to use the body part, it must have type
3964 3970 text/plain or text/x-patch). From and Subject headers of email
3965 3971 message are used as default committer and commit message. All
3966 3972 text/plain body parts before first diff are added to the commit
3967 3973 message.
3968 3974
3969 3975 If the imported patch was generated by :hg:`export`, user and
3970 3976 description from patch override values from message headers and
3971 3977 body. Values given on command line with -m/--message and -u/--user
3972 3978 override these.
3973 3979
3974 3980 If --exact is specified, import will set the working directory to
3975 3981 the parent of each patch before applying it, and will abort if the
3976 3982 resulting changeset has a different ID than the one recorded in
3977 3983 the patch. This will guard against various ways that portable
3978 3984 patch formats and mail systems might fail to transfer Mercurial
3979 3985 data or metadata. See :hg:`bundle` for lossless transmission.
3980 3986
3981 3987 Use --partial to ensure a changeset will be created from the patch
3982 3988 even if some hunks fail to apply. Hunks that fail to apply will be
3983 3989 written to a <target-file>.rej file. Conflicts can then be resolved
3984 3990 by hand before :hg:`commit --amend` is run to update the created
3985 3991 changeset. This flag exists to let people import patches that
3986 3992 partially apply without losing the associated metadata (author,
3987 3993 date, description, ...).
3988 3994
3989 3995 .. note::
3990 3996
3991 3997 When no hunks apply cleanly, :hg:`import --partial` will create
3992 3998 an empty changeset, importing only the patch metadata.
3993 3999
3994 4000 With -s/--similarity, hg will attempt to discover renames and
3995 4001 copies in the patch in the same way as :hg:`addremove`.
3996 4002
3997 4003 It is possible to use external patch programs to perform the patch
3998 4004 by setting the ``ui.patch`` configuration option. For the default
3999 4005 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4000 4006 See :hg:`help config` for more information about configuration
4001 4007 files and how to use these options.
4002 4008
4003 4009 See :hg:`help dates` for a list of formats valid for -d/--date.
4004 4010
4005 4011 .. container:: verbose
4006 4012
4007 4013 Examples:
4008 4014
4009 4015 - import a traditional patch from a website and detect renames::
4010 4016
4011 4017 hg import -s 80 http://example.com/bugfix.patch
4012 4018
4013 4019 - import a changeset from an hgweb server::
4014 4020
4015 4021 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4016 4022
4017 4023 - import all the patches in an Unix-style mbox::
4018 4024
4019 4025 hg import incoming-patches.mbox
4020 4026
4021 4027 - import patches from stdin::
4022 4028
4023 4029 hg import -
4024 4030
4025 4031 - attempt to exactly restore an exported changeset (not always
4026 4032 possible)::
4027 4033
4028 4034 hg import --exact proposed-fix.patch
4029 4035
4030 4036 - use an external tool to apply a patch which is too fuzzy for
4031 4037 the default internal tool.
4032 4038
4033 4039 hg import --config ui.patch="patch --merge" fuzzy.patch
4034 4040
4035 4041 - change the default fuzzing from 2 to a less strict 7
4036 4042
4037 4043 hg import --config ui.fuzz=7 fuzz.patch
4038 4044
4039 4045 Returns 0 on success, 1 on partial success (see --partial).
4040 4046 """
4041 4047
4042 4048 cmdutil.check_incompatible_arguments(
4043 4049 opts, 'no_commit', ['bypass', 'secret']
4044 4050 )
4045 4051 cmdutil.check_incompatible_arguments(opts, 'exact', ['edit', 'prefix'])
4046 4052 opts = pycompat.byteskwargs(opts)
4047 4053 if not patch1:
4048 4054 raise error.Abort(_(b'need at least one patch to import'))
4049 4055
4050 4056 patches = (patch1,) + patches
4051 4057
4052 4058 date = opts.get(b'date')
4053 4059 if date:
4054 4060 opts[b'date'] = dateutil.parsedate(date)
4055 4061
4056 4062 exact = opts.get(b'exact')
4057 4063 update = not opts.get(b'bypass')
4058 4064 try:
4059 4065 sim = float(opts.get(b'similarity') or 0)
4060 4066 except ValueError:
4061 4067 raise error.Abort(_(b'similarity must be a number'))
4062 4068 if sim < 0 or sim > 100:
4063 4069 raise error.Abort(_(b'similarity must be between 0 and 100'))
4064 4070 if sim and not update:
4065 4071 raise error.Abort(_(b'cannot use --similarity with --bypass'))
4066 4072
4067 4073 base = opts[b"base"]
4068 4074 msgs = []
4069 4075 ret = 0
4070 4076
4071 4077 with repo.wlock():
4072 4078 if update:
4073 4079 cmdutil.checkunfinished(repo)
4074 4080 if exact or not opts.get(b'force'):
4075 4081 cmdutil.bailifchanged(repo)
4076 4082
4077 4083 if not opts.get(b'no_commit'):
4078 4084 lock = repo.lock
4079 4085 tr = lambda: repo.transaction(b'import')
4080 4086 dsguard = util.nullcontextmanager
4081 4087 else:
4082 4088 lock = util.nullcontextmanager
4083 4089 tr = util.nullcontextmanager
4084 4090 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4085 4091 with lock(), tr(), dsguard():
4086 4092 parents = repo[None].parents()
4087 4093 for patchurl in patches:
4088 4094 if patchurl == b'-':
4089 4095 ui.status(_(b'applying patch from stdin\n'))
4090 4096 patchfile = ui.fin
4091 4097 patchurl = b'stdin' # for error message
4092 4098 else:
4093 4099 patchurl = os.path.join(base, patchurl)
4094 4100 ui.status(_(b'applying %s\n') % patchurl)
4095 4101 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4096 4102
4097 4103 haspatch = False
4098 4104 for hunk in patch.split(patchfile):
4099 4105 with patch.extract(ui, hunk) as patchdata:
4100 4106 msg, node, rej = cmdutil.tryimportone(
4101 4107 ui, repo, patchdata, parents, opts, msgs, hg.clean
4102 4108 )
4103 4109 if msg:
4104 4110 haspatch = True
4105 4111 ui.note(msg + b'\n')
4106 4112 if update or exact:
4107 4113 parents = repo[None].parents()
4108 4114 else:
4109 4115 parents = [repo[node]]
4110 4116 if rej:
4111 4117 ui.write_err(_(b"patch applied partially\n"))
4112 4118 ui.write_err(
4113 4119 _(
4114 4120 b"(fix the .rej files and run "
4115 4121 b"`hg commit --amend`)\n"
4116 4122 )
4117 4123 )
4118 4124 ret = 1
4119 4125 break
4120 4126
4121 4127 if not haspatch:
4122 4128 raise error.Abort(_(b'%s: no diffs found') % patchurl)
4123 4129
4124 4130 if msgs:
4125 4131 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4126 4132 return ret
4127 4133
4128 4134
4129 4135 @command(
4130 4136 b'incoming|in',
4131 4137 [
4132 4138 (
4133 4139 b'f',
4134 4140 b'force',
4135 4141 None,
4136 4142 _(b'run even if remote repository is unrelated'),
4137 4143 ),
4138 4144 (b'n', b'newest-first', None, _(b'show newest record first')),
4139 4145 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4140 4146 (
4141 4147 b'r',
4142 4148 b'rev',
4143 4149 [],
4144 4150 _(b'a remote changeset intended to be added'),
4145 4151 _(b'REV'),
4146 4152 ),
4147 4153 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4148 4154 (
4149 4155 b'b',
4150 4156 b'branch',
4151 4157 [],
4152 4158 _(b'a specific branch you would like to pull'),
4153 4159 _(b'BRANCH'),
4154 4160 ),
4155 4161 ]
4156 4162 + logopts
4157 4163 + remoteopts
4158 4164 + subrepoopts,
4159 4165 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4160 4166 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4161 4167 )
4162 4168 def incoming(ui, repo, source=b"default", **opts):
4163 4169 """show new changesets found in source
4164 4170
4165 4171 Show new changesets found in the specified path/URL or the default
4166 4172 pull location. These are the changesets that would have been pulled
4167 4173 by :hg:`pull` at the time you issued this command.
4168 4174
4169 4175 See pull for valid source format details.
4170 4176
4171 4177 .. container:: verbose
4172 4178
4173 4179 With -B/--bookmarks, the result of bookmark comparison between
4174 4180 local and remote repositories is displayed. With -v/--verbose,
4175 4181 status is also displayed for each bookmark like below::
4176 4182
4177 4183 BM1 01234567890a added
4178 4184 BM2 1234567890ab advanced
4179 4185 BM3 234567890abc diverged
4180 4186 BM4 34567890abcd changed
4181 4187
4182 4188 The action taken locally when pulling depends on the
4183 4189 status of each bookmark:
4184 4190
4185 4191 :``added``: pull will create it
4186 4192 :``advanced``: pull will update it
4187 4193 :``diverged``: pull will create a divergent bookmark
4188 4194 :``changed``: result depends on remote changesets
4189 4195
4190 4196 From the point of view of pulling behavior, bookmark
4191 4197 existing only in the remote repository are treated as ``added``,
4192 4198 even if it is in fact locally deleted.
4193 4199
4194 4200 .. container:: verbose
4195 4201
4196 4202 For remote repository, using --bundle avoids downloading the
4197 4203 changesets twice if the incoming is followed by a pull.
4198 4204
4199 4205 Examples:
4200 4206
4201 4207 - show incoming changes with patches and full description::
4202 4208
4203 4209 hg incoming -vp
4204 4210
4205 4211 - show incoming changes excluding merges, store a bundle::
4206 4212
4207 4213 hg in -vpM --bundle incoming.hg
4208 4214 hg pull incoming.hg
4209 4215
4210 4216 - briefly list changes inside a bundle::
4211 4217
4212 4218 hg in changes.hg -T "{desc|firstline}\\n"
4213 4219
4214 4220 Returns 0 if there are incoming changes, 1 otherwise.
4215 4221 """
4216 4222 opts = pycompat.byteskwargs(opts)
4217 4223 if opts.get(b'graph'):
4218 4224 logcmdutil.checkunsupportedgraphflags([], opts)
4219 4225
4220 4226 def display(other, chlist, displayer):
4221 4227 revdag = logcmdutil.graphrevs(other, chlist, opts)
4222 4228 logcmdutil.displaygraph(
4223 4229 ui, repo, revdag, displayer, graphmod.asciiedges
4224 4230 )
4225 4231
4226 4232 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4227 4233 return 0
4228 4234
4229 4235 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'bundle'])
4230 4236
4231 4237 if opts.get(b'bookmarks'):
4232 4238 source, branches = hg.parseurl(
4233 4239 ui.expandpath(source), opts.get(b'branch')
4234 4240 )
4235 4241 other = hg.peer(repo, opts, source)
4236 4242 if b'bookmarks' not in other.listkeys(b'namespaces'):
4237 4243 ui.warn(_(b"remote doesn't support bookmarks\n"))
4238 4244 return 0
4239 4245 ui.pager(b'incoming')
4240 4246 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4241 4247 return bookmarks.incoming(ui, repo, other)
4242 4248
4243 4249 repo._subtoppath = ui.expandpath(source)
4244 4250 try:
4245 4251 return hg.incoming(ui, repo, source, opts)
4246 4252 finally:
4247 4253 del repo._subtoppath
4248 4254
4249 4255
4250 4256 @command(
4251 4257 b'init',
4252 4258 remoteopts,
4253 4259 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4254 4260 helpcategory=command.CATEGORY_REPO_CREATION,
4255 4261 helpbasic=True,
4256 4262 norepo=True,
4257 4263 )
4258 4264 def init(ui, dest=b".", **opts):
4259 4265 """create a new repository in the given directory
4260 4266
4261 4267 Initialize a new repository in the given directory. If the given
4262 4268 directory does not exist, it will be created.
4263 4269
4264 4270 If no directory is given, the current directory is used.
4265 4271
4266 4272 It is possible to specify an ``ssh://`` URL as the destination.
4267 4273 See :hg:`help urls` for more information.
4268 4274
4269 4275 Returns 0 on success.
4270 4276 """
4271 4277 opts = pycompat.byteskwargs(opts)
4272 4278 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4273 4279
4274 4280
4275 4281 @command(
4276 4282 b'locate',
4277 4283 [
4278 4284 (
4279 4285 b'r',
4280 4286 b'rev',
4281 4287 b'',
4282 4288 _(b'search the repository as it is in REV'),
4283 4289 _(b'REV'),
4284 4290 ),
4285 4291 (
4286 4292 b'0',
4287 4293 b'print0',
4288 4294 None,
4289 4295 _(b'end filenames with NUL, for use with xargs'),
4290 4296 ),
4291 4297 (
4292 4298 b'f',
4293 4299 b'fullpath',
4294 4300 None,
4295 4301 _(b'print complete paths from the filesystem root'),
4296 4302 ),
4297 4303 ]
4298 4304 + walkopts,
4299 4305 _(b'[OPTION]... [PATTERN]...'),
4300 4306 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4301 4307 )
4302 4308 def locate(ui, repo, *pats, **opts):
4303 4309 """locate files matching specific patterns (DEPRECATED)
4304 4310
4305 4311 Print files under Mercurial control in the working directory whose
4306 4312 names match the given patterns.
4307 4313
4308 4314 By default, this command searches all directories in the working
4309 4315 directory. To search just the current directory and its
4310 4316 subdirectories, use "--include .".
4311 4317
4312 4318 If no patterns are given to match, this command prints the names
4313 4319 of all files under Mercurial control in the working directory.
4314 4320
4315 4321 If you want to feed the output of this command into the "xargs"
4316 4322 command, use the -0 option to both this command and "xargs". This
4317 4323 will avoid the problem of "xargs" treating single filenames that
4318 4324 contain whitespace as multiple filenames.
4319 4325
4320 4326 See :hg:`help files` for a more versatile command.
4321 4327
4322 4328 Returns 0 if a match is found, 1 otherwise.
4323 4329 """
4324 4330 opts = pycompat.byteskwargs(opts)
4325 4331 if opts.get(b'print0'):
4326 4332 end = b'\0'
4327 4333 else:
4328 4334 end = b'\n'
4329 4335 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4330 4336
4331 4337 ret = 1
4332 4338 m = scmutil.match(
4333 4339 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4334 4340 )
4335 4341
4336 4342 ui.pager(b'locate')
4337 4343 if ctx.rev() is None:
4338 4344 # When run on the working copy, "locate" includes removed files, so
4339 4345 # we get the list of files from the dirstate.
4340 4346 filesgen = sorted(repo.dirstate.matches(m))
4341 4347 else:
4342 4348 filesgen = ctx.matches(m)
4343 4349 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4344 4350 for abs in filesgen:
4345 4351 if opts.get(b'fullpath'):
4346 4352 ui.write(repo.wjoin(abs), end)
4347 4353 else:
4348 4354 ui.write(uipathfn(abs), end)
4349 4355 ret = 0
4350 4356
4351 4357 return ret
4352 4358
4353 4359
4354 4360 @command(
4355 4361 b'log|history',
4356 4362 [
4357 4363 (
4358 4364 b'f',
4359 4365 b'follow',
4360 4366 None,
4361 4367 _(
4362 4368 b'follow changeset history, or file history across copies and renames'
4363 4369 ),
4364 4370 ),
4365 4371 (
4366 4372 b'',
4367 4373 b'follow-first',
4368 4374 None,
4369 4375 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4370 4376 ),
4371 4377 (
4372 4378 b'd',
4373 4379 b'date',
4374 4380 b'',
4375 4381 _(b'show revisions matching date spec'),
4376 4382 _(b'DATE'),
4377 4383 ),
4378 4384 (b'C', b'copies', None, _(b'show copied files')),
4379 4385 (
4380 4386 b'k',
4381 4387 b'keyword',
4382 4388 [],
4383 4389 _(b'do case-insensitive search for a given text'),
4384 4390 _(b'TEXT'),
4385 4391 ),
4386 4392 (
4387 4393 b'r',
4388 4394 b'rev',
4389 4395 [],
4390 4396 _(b'show the specified revision or revset'),
4391 4397 _(b'REV'),
4392 4398 ),
4393 4399 (
4394 4400 b'L',
4395 4401 b'line-range',
4396 4402 [],
4397 4403 _(b'follow line range of specified file (EXPERIMENTAL)'),
4398 4404 _(b'FILE,RANGE'),
4399 4405 ),
4400 4406 (
4401 4407 b'',
4402 4408 b'removed',
4403 4409 None,
4404 4410 _(b'include revisions where files were removed'),
4405 4411 ),
4406 4412 (
4407 4413 b'm',
4408 4414 b'only-merges',
4409 4415 None,
4410 4416 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4411 4417 ),
4412 4418 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4413 4419 (
4414 4420 b'',
4415 4421 b'only-branch',
4416 4422 [],
4417 4423 _(
4418 4424 b'show only changesets within the given named branch (DEPRECATED)'
4419 4425 ),
4420 4426 _(b'BRANCH'),
4421 4427 ),
4422 4428 (
4423 4429 b'b',
4424 4430 b'branch',
4425 4431 [],
4426 4432 _(b'show changesets within the given named branch'),
4427 4433 _(b'BRANCH'),
4428 4434 ),
4429 4435 (
4430 4436 b'P',
4431 4437 b'prune',
4432 4438 [],
4433 4439 _(b'do not display revision or any of its ancestors'),
4434 4440 _(b'REV'),
4435 4441 ),
4436 4442 ]
4437 4443 + logopts
4438 4444 + walkopts,
4439 4445 _(b'[OPTION]... [FILE]'),
4440 4446 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4441 4447 helpbasic=True,
4442 4448 inferrepo=True,
4443 4449 intents={INTENT_READONLY},
4444 4450 )
4445 4451 def log(ui, repo, *pats, **opts):
4446 4452 """show revision history of entire repository or files
4447 4453
4448 4454 Print the revision history of the specified files or the entire
4449 4455 project.
4450 4456
4451 4457 If no revision range is specified, the default is ``tip:0`` unless
4452 4458 --follow is set, in which case the working directory parent is
4453 4459 used as the starting revision.
4454 4460
4455 4461 File history is shown without following rename or copy history of
4456 4462 files. Use -f/--follow with a filename to follow history across
4457 4463 renames and copies. --follow without a filename will only show
4458 4464 ancestors of the starting revision.
4459 4465
4460 4466 By default this command prints revision number and changeset id,
4461 4467 tags, non-trivial parents, user, date and time, and a summary for
4462 4468 each commit. When the -v/--verbose switch is used, the list of
4463 4469 changed files and full commit message are shown.
4464 4470
4465 4471 With --graph the revisions are shown as an ASCII art DAG with the most
4466 4472 recent changeset at the top.
4467 4473 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4468 4474 involved in an unresolved merge conflict, '_' closes a branch,
4469 4475 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4470 4476 changeset from the lines below is a parent of the 'o' merge on the same
4471 4477 line.
4472 4478 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4473 4479 of a '|' indicates one or more revisions in a path are omitted.
4474 4480
4475 4481 .. container:: verbose
4476 4482
4477 4483 Use -L/--line-range FILE,M:N options to follow the history of lines
4478 4484 from M to N in FILE. With -p/--patch only diff hunks affecting
4479 4485 specified line range will be shown. This option requires --follow;
4480 4486 it can be specified multiple times. Currently, this option is not
4481 4487 compatible with --graph. This option is experimental.
4482 4488
4483 4489 .. note::
4484 4490
4485 4491 :hg:`log --patch` may generate unexpected diff output for merge
4486 4492 changesets, as it will only compare the merge changeset against
4487 4493 its first parent. Also, only files different from BOTH parents
4488 4494 will appear in files:.
4489 4495
4490 4496 .. note::
4491 4497
4492 4498 For performance reasons, :hg:`log FILE` may omit duplicate changes
4493 4499 made on branches and will not show removals or mode changes. To
4494 4500 see all such changes, use the --removed switch.
4495 4501
4496 4502 .. container:: verbose
4497 4503
4498 4504 .. note::
4499 4505
4500 4506 The history resulting from -L/--line-range options depends on diff
4501 4507 options; for instance if white-spaces are ignored, respective changes
4502 4508 with only white-spaces in specified line range will not be listed.
4503 4509
4504 4510 .. container:: verbose
4505 4511
4506 4512 Some examples:
4507 4513
4508 4514 - changesets with full descriptions and file lists::
4509 4515
4510 4516 hg log -v
4511 4517
4512 4518 - changesets ancestral to the working directory::
4513 4519
4514 4520 hg log -f
4515 4521
4516 4522 - last 10 commits on the current branch::
4517 4523
4518 4524 hg log -l 10 -b .
4519 4525
4520 4526 - changesets showing all modifications of a file, including removals::
4521 4527
4522 4528 hg log --removed file.c
4523 4529
4524 4530 - all changesets that touch a directory, with diffs, excluding merges::
4525 4531
4526 4532 hg log -Mp lib/
4527 4533
4528 4534 - all revision numbers that match a keyword::
4529 4535
4530 4536 hg log -k bug --template "{rev}\\n"
4531 4537
4532 4538 - the full hash identifier of the working directory parent::
4533 4539
4534 4540 hg log -r . --template "{node}\\n"
4535 4541
4536 4542 - list available log templates::
4537 4543
4538 4544 hg log -T list
4539 4545
4540 4546 - check if a given changeset is included in a tagged release::
4541 4547
4542 4548 hg log -r "a21ccf and ancestor(1.9)"
4543 4549
4544 4550 - find all changesets by some user in a date range::
4545 4551
4546 4552 hg log -k alice -d "may 2008 to jul 2008"
4547 4553
4548 4554 - summary of all changesets after the last tag::
4549 4555
4550 4556 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4551 4557
4552 4558 - changesets touching lines 13 to 23 for file.c::
4553 4559
4554 4560 hg log -L file.c,13:23
4555 4561
4556 4562 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4557 4563 main.c with patch::
4558 4564
4559 4565 hg log -L file.c,13:23 -L main.c,2:6 -p
4560 4566
4561 4567 See :hg:`help dates` for a list of formats valid for -d/--date.
4562 4568
4563 4569 See :hg:`help revisions` for more about specifying and ordering
4564 4570 revisions.
4565 4571
4566 4572 See :hg:`help templates` for more about pre-packaged styles and
4567 4573 specifying custom templates. The default template used by the log
4568 4574 command can be customized via the ``command-templates.log`` configuration
4569 4575 setting.
4570 4576
4571 4577 Returns 0 on success.
4572 4578
4573 4579 """
4574 4580 opts = pycompat.byteskwargs(opts)
4575 4581 linerange = opts.get(b'line_range')
4576 4582
4577 4583 if linerange and not opts.get(b'follow'):
4578 4584 raise error.Abort(_(b'--line-range requires --follow'))
4579 4585
4580 4586 if linerange and pats:
4581 4587 # TODO: take pats as patterns with no line-range filter
4582 4588 raise error.Abort(
4583 4589 _(b'FILE arguments are not compatible with --line-range option')
4584 4590 )
4585 4591
4586 4592 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4587 4593 revs, differ = logcmdutil.getrevs(
4588 4594 repo, logcmdutil.parseopts(ui, pats, opts)
4589 4595 )
4590 4596 if linerange:
4591 4597 # TODO: should follow file history from logcmdutil._initialrevs(),
4592 4598 # then filter the result by logcmdutil._makerevset() and --limit
4593 4599 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4594 4600
4595 4601 getcopies = None
4596 4602 if opts.get(b'copies'):
4597 4603 endrev = None
4598 4604 if revs:
4599 4605 endrev = revs.max() + 1
4600 4606 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4601 4607
4602 4608 ui.pager(b'log')
4603 4609 displayer = logcmdutil.changesetdisplayer(
4604 4610 ui, repo, opts, differ, buffered=True
4605 4611 )
4606 4612 if opts.get(b'graph'):
4607 4613 displayfn = logcmdutil.displaygraphrevs
4608 4614 else:
4609 4615 displayfn = logcmdutil.displayrevs
4610 4616 displayfn(ui, repo, revs, displayer, getcopies)
4611 4617
4612 4618
4613 4619 @command(
4614 4620 b'manifest',
4615 4621 [
4616 4622 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4617 4623 (b'', b'all', False, _(b"list files from all revisions")),
4618 4624 ]
4619 4625 + formatteropts,
4620 4626 _(b'[-r REV]'),
4621 4627 helpcategory=command.CATEGORY_MAINTENANCE,
4622 4628 intents={INTENT_READONLY},
4623 4629 )
4624 4630 def manifest(ui, repo, node=None, rev=None, **opts):
4625 4631 """output the current or given revision of the project manifest
4626 4632
4627 4633 Print a list of version controlled files for the given revision.
4628 4634 If no revision is given, the first parent of the working directory
4629 4635 is used, or the null revision if no revision is checked out.
4630 4636
4631 4637 With -v, print file permissions, symlink and executable bits.
4632 4638 With --debug, print file revision hashes.
4633 4639
4634 4640 If option --all is specified, the list of all files from all revisions
4635 4641 is printed. This includes deleted and renamed files.
4636 4642
4637 4643 Returns 0 on success.
4638 4644 """
4639 4645 opts = pycompat.byteskwargs(opts)
4640 4646 fm = ui.formatter(b'manifest', opts)
4641 4647
4642 4648 if opts.get(b'all'):
4643 4649 if rev or node:
4644 4650 raise error.Abort(_(b"can't specify a revision with --all"))
4645 4651
4646 4652 res = set()
4647 4653 for rev in repo:
4648 4654 ctx = repo[rev]
4649 4655 res |= set(ctx.files())
4650 4656
4651 4657 ui.pager(b'manifest')
4652 4658 for f in sorted(res):
4653 4659 fm.startitem()
4654 4660 fm.write(b"path", b'%s\n', f)
4655 4661 fm.end()
4656 4662 return
4657 4663
4658 4664 if rev and node:
4659 4665 raise error.Abort(_(b"please specify just one revision"))
4660 4666
4661 4667 if not node:
4662 4668 node = rev
4663 4669
4664 4670 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4665 4671 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4666 4672 if node:
4667 4673 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4668 4674 ctx = scmutil.revsingle(repo, node)
4669 4675 mf = ctx.manifest()
4670 4676 ui.pager(b'manifest')
4671 4677 for f in ctx:
4672 4678 fm.startitem()
4673 4679 fm.context(ctx=ctx)
4674 4680 fl = ctx[f].flags()
4675 4681 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4676 4682 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4677 4683 fm.write(b'path', b'%s\n', f)
4678 4684 fm.end()
4679 4685
4680 4686
4681 4687 @command(
4682 4688 b'merge',
4683 4689 [
4684 4690 (
4685 4691 b'f',
4686 4692 b'force',
4687 4693 None,
4688 4694 _(b'force a merge including outstanding changes (DEPRECATED)'),
4689 4695 ),
4690 4696 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4691 4697 (
4692 4698 b'P',
4693 4699 b'preview',
4694 4700 None,
4695 4701 _(b'review revisions to merge (no merge is performed)'),
4696 4702 ),
4697 4703 (b'', b'abort', None, _(b'abort the ongoing merge')),
4698 4704 ]
4699 4705 + mergetoolopts,
4700 4706 _(b'[-P] [[-r] REV]'),
4701 4707 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4702 4708 helpbasic=True,
4703 4709 )
4704 4710 def merge(ui, repo, node=None, **opts):
4705 4711 """merge another revision into working directory
4706 4712
4707 4713 The current working directory is updated with all changes made in
4708 4714 the requested revision since the last common predecessor revision.
4709 4715
4710 4716 Files that changed between either parent are marked as changed for
4711 4717 the next commit and a commit must be performed before any further
4712 4718 updates to the repository are allowed. The next commit will have
4713 4719 two parents.
4714 4720
4715 4721 ``--tool`` can be used to specify the merge tool used for file
4716 4722 merges. It overrides the HGMERGE environment variable and your
4717 4723 configuration files. See :hg:`help merge-tools` for options.
4718 4724
4719 4725 If no revision is specified, the working directory's parent is a
4720 4726 head revision, and the current branch contains exactly one other
4721 4727 head, the other head is merged with by default. Otherwise, an
4722 4728 explicit revision with which to merge must be provided.
4723 4729
4724 4730 See :hg:`help resolve` for information on handling file conflicts.
4725 4731
4726 4732 To undo an uncommitted merge, use :hg:`merge --abort` which
4727 4733 will check out a clean copy of the original merge parent, losing
4728 4734 all changes.
4729 4735
4730 4736 Returns 0 on success, 1 if there are unresolved files.
4731 4737 """
4732 4738
4733 4739 opts = pycompat.byteskwargs(opts)
4734 4740 abort = opts.get(b'abort')
4735 4741 if abort and repo.dirstate.p2() == nullid:
4736 4742 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4737 4743 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4738 4744 if abort:
4739 4745 state = cmdutil.getunfinishedstate(repo)
4740 4746 if state and state._opname != b'merge':
4741 4747 raise error.Abort(
4742 4748 _(b'cannot abort merge with %s in progress') % (state._opname),
4743 4749 hint=state.hint(),
4744 4750 )
4745 4751 if node:
4746 4752 raise error.Abort(_(b"cannot specify a node with --abort"))
4747 4753 return hg.abortmerge(repo.ui, repo)
4748 4754
4749 4755 if opts.get(b'rev') and node:
4750 4756 raise error.Abort(_(b"please specify just one revision"))
4751 4757 if not node:
4752 4758 node = opts.get(b'rev')
4753 4759
4754 4760 if node:
4755 4761 ctx = scmutil.revsingle(repo, node)
4756 4762 else:
4757 4763 if ui.configbool(b'commands', b'merge.require-rev'):
4758 4764 raise error.Abort(
4759 4765 _(
4760 4766 b'configuration requires specifying revision to merge '
4761 4767 b'with'
4762 4768 )
4763 4769 )
4764 4770 ctx = repo[destutil.destmerge(repo)]
4765 4771
4766 4772 if ctx.node() is None:
4767 4773 raise error.Abort(_(b'merging with the working copy has no effect'))
4768 4774
4769 4775 if opts.get(b'preview'):
4770 4776 # find nodes that are ancestors of p2 but not of p1
4771 4777 p1 = repo[b'.'].node()
4772 4778 p2 = ctx.node()
4773 4779 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4774 4780
4775 4781 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4776 4782 for node in nodes:
4777 4783 displayer.show(repo[node])
4778 4784 displayer.close()
4779 4785 return 0
4780 4786
4781 4787 # ui.forcemerge is an internal variable, do not document
4782 4788 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4783 4789 with ui.configoverride(overrides, b'merge'):
4784 4790 force = opts.get(b'force')
4785 4791 labels = [b'working copy', b'merge rev']
4786 4792 return hg.merge(ctx, force=force, labels=labels)
4787 4793
4788 4794
4789 4795 statemod.addunfinished(
4790 4796 b'merge',
4791 4797 fname=None,
4792 4798 clearable=True,
4793 4799 allowcommit=True,
4794 4800 cmdmsg=_(b'outstanding uncommitted merge'),
4795 4801 abortfunc=hg.abortmerge,
4796 4802 statushint=_(
4797 4803 b'To continue: hg commit\nTo abort: hg merge --abort'
4798 4804 ),
4799 4805 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4800 4806 )
4801 4807
4802 4808
4803 4809 @command(
4804 4810 b'outgoing|out',
4805 4811 [
4806 4812 (
4807 4813 b'f',
4808 4814 b'force',
4809 4815 None,
4810 4816 _(b'run even when the destination is unrelated'),
4811 4817 ),
4812 4818 (
4813 4819 b'r',
4814 4820 b'rev',
4815 4821 [],
4816 4822 _(b'a changeset intended to be included in the destination'),
4817 4823 _(b'REV'),
4818 4824 ),
4819 4825 (b'n', b'newest-first', None, _(b'show newest record first')),
4820 4826 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4821 4827 (
4822 4828 b'b',
4823 4829 b'branch',
4824 4830 [],
4825 4831 _(b'a specific branch you would like to push'),
4826 4832 _(b'BRANCH'),
4827 4833 ),
4828 4834 ]
4829 4835 + logopts
4830 4836 + remoteopts
4831 4837 + subrepoopts,
4832 4838 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4833 4839 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4834 4840 )
4835 4841 def outgoing(ui, repo, dest=None, **opts):
4836 4842 """show changesets not found in the destination
4837 4843
4838 4844 Show changesets not found in the specified destination repository
4839 4845 or the default push location. These are the changesets that would
4840 4846 be pushed if a push was requested.
4841 4847
4842 4848 See pull for details of valid destination formats.
4843 4849
4844 4850 .. container:: verbose
4845 4851
4846 4852 With -B/--bookmarks, the result of bookmark comparison between
4847 4853 local and remote repositories is displayed. With -v/--verbose,
4848 4854 status is also displayed for each bookmark like below::
4849 4855
4850 4856 BM1 01234567890a added
4851 4857 BM2 deleted
4852 4858 BM3 234567890abc advanced
4853 4859 BM4 34567890abcd diverged
4854 4860 BM5 4567890abcde changed
4855 4861
4856 4862 The action taken when pushing depends on the
4857 4863 status of each bookmark:
4858 4864
4859 4865 :``added``: push with ``-B`` will create it
4860 4866 :``deleted``: push with ``-B`` will delete it
4861 4867 :``advanced``: push will update it
4862 4868 :``diverged``: push with ``-B`` will update it
4863 4869 :``changed``: push with ``-B`` will update it
4864 4870
4865 4871 From the point of view of pushing behavior, bookmarks
4866 4872 existing only in the remote repository are treated as
4867 4873 ``deleted``, even if it is in fact added remotely.
4868 4874
4869 4875 Returns 0 if there are outgoing changes, 1 otherwise.
4870 4876 """
4871 4877 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4872 4878 # style URLs, so don't overwrite dest.
4873 4879 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
4874 4880 if not path:
4875 4881 raise error.Abort(
4876 4882 _(b'default repository not configured!'),
4877 4883 hint=_(b"see 'hg help config.paths'"),
4878 4884 )
4879 4885
4880 4886 opts = pycompat.byteskwargs(opts)
4881 4887 if opts.get(b'graph'):
4882 4888 logcmdutil.checkunsupportedgraphflags([], opts)
4883 4889 o, other = hg._outgoing(ui, repo, dest, opts)
4884 4890 if not o:
4885 4891 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4886 4892 return
4887 4893
4888 4894 revdag = logcmdutil.graphrevs(repo, o, opts)
4889 4895 ui.pager(b'outgoing')
4890 4896 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4891 4897 logcmdutil.displaygraph(
4892 4898 ui, repo, revdag, displayer, graphmod.asciiedges
4893 4899 )
4894 4900 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4895 4901 return 0
4896 4902
4897 4903 if opts.get(b'bookmarks'):
4898 4904 dest = path.pushloc or path.loc
4899 4905 other = hg.peer(repo, opts, dest)
4900 4906 if b'bookmarks' not in other.listkeys(b'namespaces'):
4901 4907 ui.warn(_(b"remote doesn't support bookmarks\n"))
4902 4908 return 0
4903 4909 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
4904 4910 ui.pager(b'outgoing')
4905 4911 return bookmarks.outgoing(ui, repo, other)
4906 4912
4907 4913 repo._subtoppath = path.pushloc or path.loc
4908 4914 try:
4909 4915 return hg.outgoing(ui, repo, dest, opts)
4910 4916 finally:
4911 4917 del repo._subtoppath
4912 4918
4913 4919
4914 4920 @command(
4915 4921 b'parents',
4916 4922 [
4917 4923 (
4918 4924 b'r',
4919 4925 b'rev',
4920 4926 b'',
4921 4927 _(b'show parents of the specified revision'),
4922 4928 _(b'REV'),
4923 4929 ),
4924 4930 ]
4925 4931 + templateopts,
4926 4932 _(b'[-r REV] [FILE]'),
4927 4933 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4928 4934 inferrepo=True,
4929 4935 )
4930 4936 def parents(ui, repo, file_=None, **opts):
4931 4937 """show the parents of the working directory or revision (DEPRECATED)
4932 4938
4933 4939 Print the working directory's parent revisions. If a revision is
4934 4940 given via -r/--rev, the parent of that revision will be printed.
4935 4941 If a file argument is given, the revision in which the file was
4936 4942 last changed (before the working directory revision or the
4937 4943 argument to --rev if given) is printed.
4938 4944
4939 4945 This command is equivalent to::
4940 4946
4941 4947 hg log -r "p1()+p2()" or
4942 4948 hg log -r "p1(REV)+p2(REV)" or
4943 4949 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
4944 4950 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
4945 4951
4946 4952 See :hg:`summary` and :hg:`help revsets` for related information.
4947 4953
4948 4954 Returns 0 on success.
4949 4955 """
4950 4956
4951 4957 opts = pycompat.byteskwargs(opts)
4952 4958 rev = opts.get(b'rev')
4953 4959 if rev:
4954 4960 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
4955 4961 ctx = scmutil.revsingle(repo, rev, None)
4956 4962
4957 4963 if file_:
4958 4964 m = scmutil.match(ctx, (file_,), opts)
4959 4965 if m.anypats() or len(m.files()) != 1:
4960 4966 raise error.Abort(_(b'can only specify an explicit filename'))
4961 4967 file_ = m.files()[0]
4962 4968 filenodes = []
4963 4969 for cp in ctx.parents():
4964 4970 if not cp:
4965 4971 continue
4966 4972 try:
4967 4973 filenodes.append(cp.filenode(file_))
4968 4974 except error.LookupError:
4969 4975 pass
4970 4976 if not filenodes:
4971 4977 raise error.Abort(_(b"'%s' not found in manifest!") % file_)
4972 4978 p = []
4973 4979 for fn in filenodes:
4974 4980 fctx = repo.filectx(file_, fileid=fn)
4975 4981 p.append(fctx.node())
4976 4982 else:
4977 4983 p = [cp.node() for cp in ctx.parents()]
4978 4984
4979 4985 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4980 4986 for n in p:
4981 4987 if n != nullid:
4982 4988 displayer.show(repo[n])
4983 4989 displayer.close()
4984 4990
4985 4991
4986 4992 @command(
4987 4993 b'paths',
4988 4994 formatteropts,
4989 4995 _(b'[NAME]'),
4990 4996 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4991 4997 optionalrepo=True,
4992 4998 intents={INTENT_READONLY},
4993 4999 )
4994 5000 def paths(ui, repo, search=None, **opts):
4995 5001 """show aliases for remote repositories
4996 5002
4997 5003 Show definition of symbolic path name NAME. If no name is given,
4998 5004 show definition of all available names.
4999 5005
5000 5006 Option -q/--quiet suppresses all output when searching for NAME
5001 5007 and shows only the path names when listing all definitions.
5002 5008
5003 5009 Path names are defined in the [paths] section of your
5004 5010 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5005 5011 repository, ``.hg/hgrc`` is used, too.
5006 5012
5007 5013 The path names ``default`` and ``default-push`` have a special
5008 5014 meaning. When performing a push or pull operation, they are used
5009 5015 as fallbacks if no location is specified on the command-line.
5010 5016 When ``default-push`` is set, it will be used for push and
5011 5017 ``default`` will be used for pull; otherwise ``default`` is used
5012 5018 as the fallback for both. When cloning a repository, the clone
5013 5019 source is written as ``default`` in ``.hg/hgrc``.
5014 5020
5015 5021 .. note::
5016 5022
5017 5023 ``default`` and ``default-push`` apply to all inbound (e.g.
5018 5024 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5019 5025 and :hg:`bundle`) operations.
5020 5026
5021 5027 See :hg:`help urls` for more information.
5022 5028
5023 5029 .. container:: verbose
5024 5030
5025 5031 Template:
5026 5032
5027 5033 The following keywords are supported. See also :hg:`help templates`.
5028 5034
5029 5035 :name: String. Symbolic name of the path alias.
5030 5036 :pushurl: String. URL for push operations.
5031 5037 :url: String. URL or directory path for the other operations.
5032 5038
5033 5039 Returns 0 on success.
5034 5040 """
5035 5041
5036 5042 opts = pycompat.byteskwargs(opts)
5037 5043 ui.pager(b'paths')
5038 5044 if search:
5039 5045 pathitems = [
5040 5046 (name, path)
5041 5047 for name, path in pycompat.iteritems(ui.paths)
5042 5048 if name == search
5043 5049 ]
5044 5050 else:
5045 5051 pathitems = sorted(pycompat.iteritems(ui.paths))
5046 5052
5047 5053 fm = ui.formatter(b'paths', opts)
5048 5054 if fm.isplain():
5049 5055 hidepassword = util.hidepassword
5050 5056 else:
5051 5057 hidepassword = bytes
5052 5058 if ui.quiet:
5053 5059 namefmt = b'%s\n'
5054 5060 else:
5055 5061 namefmt = b'%s = '
5056 5062 showsubopts = not search and not ui.quiet
5057 5063
5058 5064 for name, path in pathitems:
5059 5065 fm.startitem()
5060 5066 fm.condwrite(not search, b'name', namefmt, name)
5061 5067 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5062 5068 for subopt, value in sorted(path.suboptions.items()):
5063 5069 assert subopt not in (b'name', b'url')
5064 5070 if showsubopts:
5065 5071 fm.plain(b'%s:%s = ' % (name, subopt))
5066 5072 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5067 5073
5068 5074 fm.end()
5069 5075
5070 5076 if search and not pathitems:
5071 5077 if not ui.quiet:
5072 5078 ui.warn(_(b"not found!\n"))
5073 5079 return 1
5074 5080 else:
5075 5081 return 0
5076 5082
5077 5083
5078 5084 @command(
5079 5085 b'phase',
5080 5086 [
5081 5087 (b'p', b'public', False, _(b'set changeset phase to public')),
5082 5088 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5083 5089 (b's', b'secret', False, _(b'set changeset phase to secret')),
5084 5090 (b'f', b'force', False, _(b'allow to move boundary backward')),
5085 5091 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5086 5092 ],
5087 5093 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5088 5094 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5089 5095 )
5090 5096 def phase(ui, repo, *revs, **opts):
5091 5097 """set or show the current phase name
5092 5098
5093 5099 With no argument, show the phase name of the current revision(s).
5094 5100
5095 5101 With one of -p/--public, -d/--draft or -s/--secret, change the
5096 5102 phase value of the specified revisions.
5097 5103
5098 5104 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5099 5105 lower phase to a higher phase. Phases are ordered as follows::
5100 5106
5101 5107 public < draft < secret
5102 5108
5103 5109 Returns 0 on success, 1 if some phases could not be changed.
5104 5110
5105 5111 (For more information about the phases concept, see :hg:`help phases`.)
5106 5112 """
5107 5113 opts = pycompat.byteskwargs(opts)
5108 5114 # search for a unique phase argument
5109 5115 targetphase = None
5110 5116 for idx, name in enumerate(phases.cmdphasenames):
5111 5117 if opts[name]:
5112 5118 if targetphase is not None:
5113 5119 raise error.Abort(_(b'only one phase can be specified'))
5114 5120 targetphase = idx
5115 5121
5116 5122 # look for specified revision
5117 5123 revs = list(revs)
5118 5124 revs.extend(opts[b'rev'])
5119 5125 if not revs:
5120 5126 # display both parents as the second parent phase can influence
5121 5127 # the phase of a merge commit
5122 5128 revs = [c.rev() for c in repo[None].parents()]
5123 5129
5124 5130 revs = scmutil.revrange(repo, revs)
5125 5131
5126 5132 ret = 0
5127 5133 if targetphase is None:
5128 5134 # display
5129 5135 for r in revs:
5130 5136 ctx = repo[r]
5131 5137 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5132 5138 else:
5133 5139 with repo.lock(), repo.transaction(b"phase") as tr:
5134 5140 # set phase
5135 5141 if not revs:
5136 5142 raise error.Abort(_(b'empty revision set'))
5137 5143 nodes = [repo[r].node() for r in revs]
5138 5144 # moving revision from public to draft may hide them
5139 5145 # We have to check result on an unfiltered repository
5140 5146 unfi = repo.unfiltered()
5141 5147 getphase = unfi._phasecache.phase
5142 5148 olddata = [getphase(unfi, r) for r in unfi]
5143 5149 phases.advanceboundary(repo, tr, targetphase, nodes)
5144 5150 if opts[b'force']:
5145 5151 phases.retractboundary(repo, tr, targetphase, nodes)
5146 5152 getphase = unfi._phasecache.phase
5147 5153 newdata = [getphase(unfi, r) for r in unfi]
5148 5154 changes = sum(newdata[r] != olddata[r] for r in unfi)
5149 5155 cl = unfi.changelog
5150 5156 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5151 5157 if rejected:
5152 5158 ui.warn(
5153 5159 _(
5154 5160 b'cannot move %i changesets to a higher '
5155 5161 b'phase, use --force\n'
5156 5162 )
5157 5163 % len(rejected)
5158 5164 )
5159 5165 ret = 1
5160 5166 if changes:
5161 5167 msg = _(b'phase changed for %i changesets\n') % changes
5162 5168 if ret:
5163 5169 ui.status(msg)
5164 5170 else:
5165 5171 ui.note(msg)
5166 5172 else:
5167 5173 ui.warn(_(b'no phases changed\n'))
5168 5174 return ret
5169 5175
5170 5176
5171 5177 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5172 5178 """Run after a changegroup has been added via pull/unbundle
5173 5179
5174 5180 This takes arguments below:
5175 5181
5176 5182 :modheads: change of heads by pull/unbundle
5177 5183 :optupdate: updating working directory is needed or not
5178 5184 :checkout: update destination revision (or None to default destination)
5179 5185 :brev: a name, which might be a bookmark to be activated after updating
5180 5186 """
5181 5187 if modheads == 0:
5182 5188 return
5183 5189 if optupdate:
5184 5190 try:
5185 5191 return hg.updatetotally(ui, repo, checkout, brev)
5186 5192 except error.UpdateAbort as inst:
5187 5193 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5188 5194 hint = inst.hint
5189 5195 raise error.UpdateAbort(msg, hint=hint)
5190 5196 if modheads is not None and modheads > 1:
5191 5197 currentbranchheads = len(repo.branchheads())
5192 5198 if currentbranchheads == modheads:
5193 5199 ui.status(
5194 5200 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5195 5201 )
5196 5202 elif currentbranchheads > 1:
5197 5203 ui.status(
5198 5204 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5199 5205 )
5200 5206 else:
5201 5207 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5202 5208 elif not ui.configbool(b'commands', b'update.requiredest'):
5203 5209 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5204 5210
5205 5211
5206 5212 @command(
5207 5213 b'pull',
5208 5214 [
5209 5215 (
5210 5216 b'u',
5211 5217 b'update',
5212 5218 None,
5213 5219 _(b'update to new branch head if new descendants were pulled'),
5214 5220 ),
5215 5221 (
5216 5222 b'f',
5217 5223 b'force',
5218 5224 None,
5219 5225 _(b'run even when remote repository is unrelated'),
5220 5226 ),
5221 5227 (b'', b'confirm', None, _(b'confirm pull before applying changes'),),
5222 5228 (
5223 5229 b'r',
5224 5230 b'rev',
5225 5231 [],
5226 5232 _(b'a remote changeset intended to be added'),
5227 5233 _(b'REV'),
5228 5234 ),
5229 5235 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5230 5236 (
5231 5237 b'b',
5232 5238 b'branch',
5233 5239 [],
5234 5240 _(b'a specific branch you would like to pull'),
5235 5241 _(b'BRANCH'),
5236 5242 ),
5237 5243 ]
5238 5244 + remoteopts,
5239 5245 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5240 5246 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5241 5247 helpbasic=True,
5242 5248 )
5243 5249 def pull(ui, repo, source=b"default", **opts):
5244 5250 """pull changes from the specified source
5245 5251
5246 5252 Pull changes from a remote repository to a local one.
5247 5253
5248 5254 This finds all changes from the repository at the specified path
5249 5255 or URL and adds them to a local repository (the current one unless
5250 5256 -R is specified). By default, this does not update the copy of the
5251 5257 project in the working directory.
5252 5258
5253 5259 When cloning from servers that support it, Mercurial may fetch
5254 5260 pre-generated data. When this is done, hooks operating on incoming
5255 5261 changesets and changegroups may fire more than once, once for each
5256 5262 pre-generated bundle and as well as for any additional remaining
5257 5263 data. See :hg:`help -e clonebundles` for more.
5258 5264
5259 5265 Use :hg:`incoming` if you want to see what would have been added
5260 5266 by a pull at the time you issued this command. If you then decide
5261 5267 to add those changes to the repository, you should use :hg:`pull
5262 5268 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5263 5269
5264 5270 If SOURCE is omitted, the 'default' path will be used.
5265 5271 See :hg:`help urls` for more information.
5266 5272
5267 5273 Specifying bookmark as ``.`` is equivalent to specifying the active
5268 5274 bookmark's name.
5269 5275
5270 5276 Returns 0 on success, 1 if an update had unresolved files.
5271 5277 """
5272 5278
5273 5279 opts = pycompat.byteskwargs(opts)
5274 5280 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5275 5281 b'update'
5276 5282 ):
5277 5283 msg = _(b'update destination required by configuration')
5278 5284 hint = _(b'use hg pull followed by hg update DEST')
5279 5285 raise error.Abort(msg, hint=hint)
5280 5286
5281 5287 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5282 5288 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5283 5289 other = hg.peer(repo, opts, source)
5284 5290 try:
5285 5291 revs, checkout = hg.addbranchrevs(
5286 5292 repo, other, branches, opts.get(b'rev')
5287 5293 )
5288 5294
5289 5295 pullopargs = {}
5290 5296
5291 5297 nodes = None
5292 5298 if opts.get(b'bookmark') or revs:
5293 5299 # The list of bookmark used here is the same used to actually update
5294 5300 # the bookmark names, to avoid the race from issue 4689 and we do
5295 5301 # all lookup and bookmark queries in one go so they see the same
5296 5302 # version of the server state (issue 4700).
5297 5303 nodes = []
5298 5304 fnodes = []
5299 5305 revs = revs or []
5300 5306 if revs and not other.capable(b'lookup'):
5301 5307 err = _(
5302 5308 b"other repository doesn't support revision lookup, "
5303 5309 b"so a rev cannot be specified."
5304 5310 )
5305 5311 raise error.Abort(err)
5306 5312 with other.commandexecutor() as e:
5307 5313 fremotebookmarks = e.callcommand(
5308 5314 b'listkeys', {b'namespace': b'bookmarks'}
5309 5315 )
5310 5316 for r in revs:
5311 5317 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5312 5318 remotebookmarks = fremotebookmarks.result()
5313 5319 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5314 5320 pullopargs[b'remotebookmarks'] = remotebookmarks
5315 5321 for b in opts.get(b'bookmark', []):
5316 5322 b = repo._bookmarks.expandname(b)
5317 5323 if b not in remotebookmarks:
5318 5324 raise error.Abort(_(b'remote bookmark %s not found!') % b)
5319 5325 nodes.append(remotebookmarks[b])
5320 5326 for i, rev in enumerate(revs):
5321 5327 node = fnodes[i].result()
5322 5328 nodes.append(node)
5323 5329 if rev == checkout:
5324 5330 checkout = node
5325 5331
5326 5332 wlock = util.nullcontextmanager()
5327 5333 if opts.get(b'update'):
5328 5334 wlock = repo.wlock()
5329 5335 with wlock:
5330 5336 pullopargs.update(opts.get(b'opargs', {}))
5331 5337 modheads = exchange.pull(
5332 5338 repo,
5333 5339 other,
5334 5340 heads=nodes,
5335 5341 force=opts.get(b'force'),
5336 5342 bookmarks=opts.get(b'bookmark', ()),
5337 5343 opargs=pullopargs,
5338 5344 confirm=opts.get(b'confirm'),
5339 5345 ).cgresult
5340 5346
5341 5347 # brev is a name, which might be a bookmark to be activated at
5342 5348 # the end of the update. In other words, it is an explicit
5343 5349 # destination of the update
5344 5350 brev = None
5345 5351
5346 5352 if checkout:
5347 5353 checkout = repo.unfiltered().changelog.rev(checkout)
5348 5354
5349 5355 # order below depends on implementation of
5350 5356 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5351 5357 # because 'checkout' is determined without it.
5352 5358 if opts.get(b'rev'):
5353 5359 brev = opts[b'rev'][0]
5354 5360 elif opts.get(b'branch'):
5355 5361 brev = opts[b'branch'][0]
5356 5362 else:
5357 5363 brev = branches[0]
5358 5364 repo._subtoppath = source
5359 5365 try:
5360 5366 ret = postincoming(
5361 5367 ui, repo, modheads, opts.get(b'update'), checkout, brev
5362 5368 )
5363 5369 except error.FilteredRepoLookupError as exc:
5364 5370 msg = _(b'cannot update to target: %s') % exc.args[0]
5365 5371 exc.args = (msg,) + exc.args[1:]
5366 5372 raise
5367 5373 finally:
5368 5374 del repo._subtoppath
5369 5375
5370 5376 finally:
5371 5377 other.close()
5372 5378 return ret
5373 5379
5374 5380
5375 5381 @command(
5376 5382 b'push',
5377 5383 [
5378 5384 (b'f', b'force', None, _(b'force push')),
5379 5385 (
5380 5386 b'r',
5381 5387 b'rev',
5382 5388 [],
5383 5389 _(b'a changeset intended to be included in the destination'),
5384 5390 _(b'REV'),
5385 5391 ),
5386 5392 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5387 5393 (
5388 5394 b'b',
5389 5395 b'branch',
5390 5396 [],
5391 5397 _(b'a specific branch you would like to push'),
5392 5398 _(b'BRANCH'),
5393 5399 ),
5394 5400 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5395 5401 (
5396 5402 b'',
5397 5403 b'pushvars',
5398 5404 [],
5399 5405 _(b'variables that can be sent to server (ADVANCED)'),
5400 5406 ),
5401 5407 (
5402 5408 b'',
5403 5409 b'publish',
5404 5410 False,
5405 5411 _(b'push the changeset as public (EXPERIMENTAL)'),
5406 5412 ),
5407 5413 ]
5408 5414 + remoteopts,
5409 5415 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5410 5416 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5411 5417 helpbasic=True,
5412 5418 )
5413 5419 def push(ui, repo, dest=None, **opts):
5414 5420 """push changes to the specified destination
5415 5421
5416 5422 Push changesets from the local repository to the specified
5417 5423 destination.
5418 5424
5419 5425 This operation is symmetrical to pull: it is identical to a pull
5420 5426 in the destination repository from the current one.
5421 5427
5422 5428 By default, push will not allow creation of new heads at the
5423 5429 destination, since multiple heads would make it unclear which head
5424 5430 to use. In this situation, it is recommended to pull and merge
5425 5431 before pushing.
5426 5432
5427 5433 Use --new-branch if you want to allow push to create a new named
5428 5434 branch that is not present at the destination. This allows you to
5429 5435 only create a new branch without forcing other changes.
5430 5436
5431 5437 .. note::
5432 5438
5433 5439 Extra care should be taken with the -f/--force option,
5434 5440 which will push all new heads on all branches, an action which will
5435 5441 almost always cause confusion for collaborators.
5436 5442
5437 5443 If -r/--rev is used, the specified revision and all its ancestors
5438 5444 will be pushed to the remote repository.
5439 5445
5440 5446 If -B/--bookmark is used, the specified bookmarked revision, its
5441 5447 ancestors, and the bookmark will be pushed to the remote
5442 5448 repository. Specifying ``.`` is equivalent to specifying the active
5443 5449 bookmark's name.
5444 5450
5445 5451 Please see :hg:`help urls` for important details about ``ssh://``
5446 5452 URLs. If DESTINATION is omitted, a default path will be used.
5447 5453
5448 5454 .. container:: verbose
5449 5455
5450 5456 The --pushvars option sends strings to the server that become
5451 5457 environment variables prepended with ``HG_USERVAR_``. For example,
5452 5458 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5453 5459 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5454 5460
5455 5461 pushvars can provide for user-overridable hooks as well as set debug
5456 5462 levels. One example is having a hook that blocks commits containing
5457 5463 conflict markers, but enables the user to override the hook if the file
5458 5464 is using conflict markers for testing purposes or the file format has
5459 5465 strings that look like conflict markers.
5460 5466
5461 5467 By default, servers will ignore `--pushvars`. To enable it add the
5462 5468 following to your configuration file::
5463 5469
5464 5470 [push]
5465 5471 pushvars.server = true
5466 5472
5467 5473 Returns 0 if push was successful, 1 if nothing to push.
5468 5474 """
5469 5475
5470 5476 opts = pycompat.byteskwargs(opts)
5471 5477 if opts.get(b'bookmark'):
5472 5478 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5473 5479 for b in opts[b'bookmark']:
5474 5480 # translate -B options to -r so changesets get pushed
5475 5481 b = repo._bookmarks.expandname(b)
5476 5482 if b in repo._bookmarks:
5477 5483 opts.setdefault(b'rev', []).append(b)
5478 5484 else:
5479 5485 # if we try to push a deleted bookmark, translate it to null
5480 5486 # this lets simultaneous -r, -b options continue working
5481 5487 opts.setdefault(b'rev', []).append(b"null")
5482 5488
5483 5489 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5484 5490 if not path:
5485 5491 raise error.Abort(
5486 5492 _(b'default repository not configured!'),
5487 5493 hint=_(b"see 'hg help config.paths'"),
5488 5494 )
5489 5495 dest = path.pushloc or path.loc
5490 5496 branches = (path.branch, opts.get(b'branch') or [])
5491 5497 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5492 5498 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5493 5499 other = hg.peer(repo, opts, dest)
5494 5500
5495 5501 if revs:
5496 5502 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5497 5503 if not revs:
5498 5504 raise error.Abort(
5499 5505 _(b"specified revisions evaluate to an empty set"),
5500 5506 hint=_(b"use different revision arguments"),
5501 5507 )
5502 5508 elif path.pushrev:
5503 5509 # It doesn't make any sense to specify ancestor revisions. So limit
5504 5510 # to DAG heads to make discovery simpler.
5505 5511 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5506 5512 revs = scmutil.revrange(repo, [expr])
5507 5513 revs = [repo[rev].node() for rev in revs]
5508 5514 if not revs:
5509 5515 raise error.Abort(
5510 5516 _(b'default push revset for path evaluates to an empty set')
5511 5517 )
5512 5518 elif ui.configbool(b'commands', b'push.require-revs'):
5513 5519 raise error.Abort(
5514 5520 _(b'no revisions specified to push'),
5515 5521 hint=_(b'did you mean "hg push -r ."?'),
5516 5522 )
5517 5523
5518 5524 repo._subtoppath = dest
5519 5525 try:
5520 5526 # push subrepos depth-first for coherent ordering
5521 5527 c = repo[b'.']
5522 5528 subs = c.substate # only repos that are committed
5523 5529 for s in sorted(subs):
5524 5530 result = c.sub(s).push(opts)
5525 5531 if result == 0:
5526 5532 return not result
5527 5533 finally:
5528 5534 del repo._subtoppath
5529 5535
5530 5536 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5531 5537 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5532 5538
5533 5539 pushop = exchange.push(
5534 5540 repo,
5535 5541 other,
5536 5542 opts.get(b'force'),
5537 5543 revs=revs,
5538 5544 newbranch=opts.get(b'new_branch'),
5539 5545 bookmarks=opts.get(b'bookmark', ()),
5540 5546 publish=opts.get(b'publish'),
5541 5547 opargs=opargs,
5542 5548 )
5543 5549
5544 5550 result = not pushop.cgresult
5545 5551
5546 5552 if pushop.bkresult is not None:
5547 5553 if pushop.bkresult == 2:
5548 5554 result = 2
5549 5555 elif not result and pushop.bkresult:
5550 5556 result = 2
5551 5557
5552 5558 return result
5553 5559
5554 5560
5555 5561 @command(
5556 5562 b'recover',
5557 5563 [(b'', b'verify', False, b"run `hg verify` after successful recover"),],
5558 5564 helpcategory=command.CATEGORY_MAINTENANCE,
5559 5565 )
5560 5566 def recover(ui, repo, **opts):
5561 5567 """roll back an interrupted transaction
5562 5568
5563 5569 Recover from an interrupted commit or pull.
5564 5570
5565 5571 This command tries to fix the repository status after an
5566 5572 interrupted operation. It should only be necessary when Mercurial
5567 5573 suggests it.
5568 5574
5569 5575 Returns 0 if successful, 1 if nothing to recover or verify fails.
5570 5576 """
5571 5577 ret = repo.recover()
5572 5578 if ret:
5573 5579 if opts['verify']:
5574 5580 return hg.verify(repo)
5575 5581 else:
5576 5582 msg = _(
5577 5583 b"(verify step skipped, run `hg verify` to check your "
5578 5584 b"repository content)\n"
5579 5585 )
5580 5586 ui.warn(msg)
5581 5587 return 0
5582 5588 return 1
5583 5589
5584 5590
5585 5591 @command(
5586 5592 b'remove|rm',
5587 5593 [
5588 5594 (b'A', b'after', None, _(b'record delete for missing files')),
5589 5595 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5590 5596 ]
5591 5597 + subrepoopts
5592 5598 + walkopts
5593 5599 + dryrunopts,
5594 5600 _(b'[OPTION]... FILE...'),
5595 5601 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5596 5602 helpbasic=True,
5597 5603 inferrepo=True,
5598 5604 )
5599 5605 def remove(ui, repo, *pats, **opts):
5600 5606 """remove the specified files on the next commit
5601 5607
5602 5608 Schedule the indicated files for removal from the current branch.
5603 5609
5604 5610 This command schedules the files to be removed at the next commit.
5605 5611 To undo a remove before that, see :hg:`revert`. To undo added
5606 5612 files, see :hg:`forget`.
5607 5613
5608 5614 .. container:: verbose
5609 5615
5610 5616 -A/--after can be used to remove only files that have already
5611 5617 been deleted, -f/--force can be used to force deletion, and -Af
5612 5618 can be used to remove files from the next revision without
5613 5619 deleting them from the working directory.
5614 5620
5615 5621 The following table details the behavior of remove for different
5616 5622 file states (columns) and option combinations (rows). The file
5617 5623 states are Added [A], Clean [C], Modified [M] and Missing [!]
5618 5624 (as reported by :hg:`status`). The actions are Warn, Remove
5619 5625 (from branch) and Delete (from disk):
5620 5626
5621 5627 ========= == == == ==
5622 5628 opt/state A C M !
5623 5629 ========= == == == ==
5624 5630 none W RD W R
5625 5631 -f R RD RD R
5626 5632 -A W W W R
5627 5633 -Af R R R R
5628 5634 ========= == == == ==
5629 5635
5630 5636 .. note::
5631 5637
5632 5638 :hg:`remove` never deletes files in Added [A] state from the
5633 5639 working directory, not even if ``--force`` is specified.
5634 5640
5635 5641 Returns 0 on success, 1 if any warnings encountered.
5636 5642 """
5637 5643
5638 5644 opts = pycompat.byteskwargs(opts)
5639 5645 after, force = opts.get(b'after'), opts.get(b'force')
5640 5646 dryrun = opts.get(b'dry_run')
5641 5647 if not pats and not after:
5642 5648 raise error.Abort(_(b'no files specified'))
5643 5649
5644 5650 m = scmutil.match(repo[None], pats, opts)
5645 5651 subrepos = opts.get(b'subrepos')
5646 5652 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5647 5653 return cmdutil.remove(
5648 5654 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5649 5655 )
5650 5656
5651 5657
5652 5658 @command(
5653 5659 b'rename|move|mv',
5654 5660 [
5655 5661 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5656 5662 (
5657 5663 b'',
5658 5664 b'at-rev',
5659 5665 b'',
5660 5666 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5661 5667 _(b'REV'),
5662 5668 ),
5663 5669 (
5664 5670 b'f',
5665 5671 b'force',
5666 5672 None,
5667 5673 _(b'forcibly move over an existing managed file'),
5668 5674 ),
5669 5675 ]
5670 5676 + walkopts
5671 5677 + dryrunopts,
5672 5678 _(b'[OPTION]... SOURCE... DEST'),
5673 5679 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5674 5680 )
5675 5681 def rename(ui, repo, *pats, **opts):
5676 5682 """rename files; equivalent of copy + remove
5677 5683
5678 5684 Mark dest as copies of sources; mark sources for deletion. If dest
5679 5685 is a directory, copies are put in that directory. If dest is a
5680 5686 file, there can only be one source.
5681 5687
5682 5688 By default, this command copies the contents of files as they
5683 5689 exist in the working directory. If invoked with -A/--after, the
5684 5690 operation is recorded, but no copying is performed.
5685 5691
5686 5692 This command takes effect at the next commit. To undo a rename
5687 5693 before that, see :hg:`revert`.
5688 5694
5689 5695 Returns 0 on success, 1 if errors are encountered.
5690 5696 """
5691 5697 opts = pycompat.byteskwargs(opts)
5692 5698 with repo.wlock():
5693 5699 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5694 5700
5695 5701
5696 5702 @command(
5697 5703 b'resolve',
5698 5704 [
5699 5705 (b'a', b'all', None, _(b'select all unresolved files')),
5700 5706 (b'l', b'list', None, _(b'list state of files needing merge')),
5701 5707 (b'm', b'mark', None, _(b'mark files as resolved')),
5702 5708 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5703 5709 (b'n', b'no-status', None, _(b'hide status prefix')),
5704 5710 (b'', b're-merge', None, _(b're-merge files')),
5705 5711 ]
5706 5712 + mergetoolopts
5707 5713 + walkopts
5708 5714 + formatteropts,
5709 5715 _(b'[OPTION]... [FILE]...'),
5710 5716 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5711 5717 inferrepo=True,
5712 5718 )
5713 5719 def resolve(ui, repo, *pats, **opts):
5714 5720 """redo merges or set/view the merge status of files
5715 5721
5716 5722 Merges with unresolved conflicts are often the result of
5717 5723 non-interactive merging using the ``internal:merge`` configuration
5718 5724 setting, or a command-line merge tool like ``diff3``. The resolve
5719 5725 command is used to manage the files involved in a merge, after
5720 5726 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5721 5727 working directory must have two parents). See :hg:`help
5722 5728 merge-tools` for information on configuring merge tools.
5723 5729
5724 5730 The resolve command can be used in the following ways:
5725 5731
5726 5732 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5727 5733 the specified files, discarding any previous merge attempts. Re-merging
5728 5734 is not performed for files already marked as resolved. Use ``--all/-a``
5729 5735 to select all unresolved files. ``--tool`` can be used to specify
5730 5736 the merge tool used for the given files. It overrides the HGMERGE
5731 5737 environment variable and your configuration files. Previous file
5732 5738 contents are saved with a ``.orig`` suffix.
5733 5739
5734 5740 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5735 5741 (e.g. after having manually fixed-up the files). The default is
5736 5742 to mark all unresolved files.
5737 5743
5738 5744 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5739 5745 default is to mark all resolved files.
5740 5746
5741 5747 - :hg:`resolve -l`: list files which had or still have conflicts.
5742 5748 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5743 5749 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5744 5750 the list. See :hg:`help filesets` for details.
5745 5751
5746 5752 .. note::
5747 5753
5748 5754 Mercurial will not let you commit files with unresolved merge
5749 5755 conflicts. You must use :hg:`resolve -m ...` before you can
5750 5756 commit after a conflicting merge.
5751 5757
5752 5758 .. container:: verbose
5753 5759
5754 5760 Template:
5755 5761
5756 5762 The following keywords are supported in addition to the common template
5757 5763 keywords and functions. See also :hg:`help templates`.
5758 5764
5759 5765 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5760 5766 :path: String. Repository-absolute path of the file.
5761 5767
5762 5768 Returns 0 on success, 1 if any files fail a resolve attempt.
5763 5769 """
5764 5770
5765 5771 opts = pycompat.byteskwargs(opts)
5766 5772 confirm = ui.configbool(b'commands', b'resolve.confirm')
5767 5773 flaglist = b'all mark unmark list no_status re_merge'.split()
5768 5774 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5769 5775
5770 5776 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5771 5777 if actioncount > 1:
5772 5778 raise error.Abort(_(b"too many actions specified"))
5773 5779 elif actioncount == 0 and ui.configbool(
5774 5780 b'commands', b'resolve.explicit-re-merge'
5775 5781 ):
5776 5782 hint = _(b'use --mark, --unmark, --list or --re-merge')
5777 5783 raise error.Abort(_(b'no action specified'), hint=hint)
5778 5784 if pats and all:
5779 5785 raise error.Abort(_(b"can't specify --all and patterns"))
5780 5786 if not (all or pats or show or mark or unmark):
5781 5787 raise error.Abort(
5782 5788 _(b'no files or directories specified'),
5783 5789 hint=b'use --all to re-merge all unresolved files',
5784 5790 )
5785 5791
5786 5792 if confirm:
5787 5793 if all:
5788 5794 if ui.promptchoice(
5789 5795 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5790 5796 ):
5791 5797 raise error.Abort(_(b'user quit'))
5792 5798 if mark and not pats:
5793 5799 if ui.promptchoice(
5794 5800 _(
5795 5801 b'mark all unresolved files as resolved (yn)?'
5796 5802 b'$$ &Yes $$ &No'
5797 5803 )
5798 5804 ):
5799 5805 raise error.Abort(_(b'user quit'))
5800 5806 if unmark and not pats:
5801 5807 if ui.promptchoice(
5802 5808 _(
5803 5809 b'mark all resolved files as unresolved (yn)?'
5804 5810 b'$$ &Yes $$ &No'
5805 5811 )
5806 5812 ):
5807 5813 raise error.Abort(_(b'user quit'))
5808 5814
5809 5815 uipathfn = scmutil.getuipathfn(repo)
5810 5816
5811 5817 if show:
5812 5818 ui.pager(b'resolve')
5813 5819 fm = ui.formatter(b'resolve', opts)
5814 5820 ms = mergestatemod.mergestate.read(repo)
5815 5821 wctx = repo[None]
5816 5822 m = scmutil.match(wctx, pats, opts)
5817 5823
5818 5824 # Labels and keys based on merge state. Unresolved path conflicts show
5819 5825 # as 'P'. Resolved path conflicts show as 'R', the same as normal
5820 5826 # resolved conflicts.
5821 5827 mergestateinfo = {
5822 5828 mergestatemod.MERGE_RECORD_UNRESOLVED: (
5823 5829 b'resolve.unresolved',
5824 5830 b'U',
5825 5831 ),
5826 5832 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
5827 5833 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
5828 5834 b'resolve.unresolved',
5829 5835 b'P',
5830 5836 ),
5831 5837 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
5832 5838 b'resolve.resolved',
5833 5839 b'R',
5834 5840 ),
5835 5841 }
5836 5842
5837 5843 for f in ms:
5838 5844 if not m(f):
5839 5845 continue
5840 5846
5841 5847 label, key = mergestateinfo[ms[f]]
5842 5848 fm.startitem()
5843 5849 fm.context(ctx=wctx)
5844 5850 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
5845 5851 fm.data(path=f)
5846 5852 fm.plain(b'%s\n' % uipathfn(f), label=label)
5847 5853 fm.end()
5848 5854 return 0
5849 5855
5850 5856 with repo.wlock():
5851 5857 ms = mergestatemod.mergestate.read(repo)
5852 5858
5853 5859 if not (ms.active() or repo.dirstate.p2() != nullid):
5854 5860 raise error.Abort(
5855 5861 _(b'resolve command not applicable when not merging')
5856 5862 )
5857 5863
5858 5864 wctx = repo[None]
5859 5865 m = scmutil.match(wctx, pats, opts)
5860 5866 ret = 0
5861 5867 didwork = False
5862 5868
5863 5869 tocomplete = []
5864 5870 hasconflictmarkers = []
5865 5871 if mark:
5866 5872 markcheck = ui.config(b'commands', b'resolve.mark-check')
5867 5873 if markcheck not in [b'warn', b'abort']:
5868 5874 # Treat all invalid / unrecognized values as 'none'.
5869 5875 markcheck = False
5870 5876 for f in ms:
5871 5877 if not m(f):
5872 5878 continue
5873 5879
5874 5880 didwork = True
5875 5881
5876 5882 # path conflicts must be resolved manually
5877 5883 if ms[f] in (
5878 5884 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
5879 5885 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
5880 5886 ):
5881 5887 if mark:
5882 5888 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
5883 5889 elif unmark:
5884 5890 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
5885 5891 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
5886 5892 ui.warn(
5887 5893 _(b'%s: path conflict must be resolved manually\n')
5888 5894 % uipathfn(f)
5889 5895 )
5890 5896 continue
5891 5897
5892 5898 if mark:
5893 5899 if markcheck:
5894 5900 fdata = repo.wvfs.tryread(f)
5895 5901 if (
5896 5902 filemerge.hasconflictmarkers(fdata)
5897 5903 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
5898 5904 ):
5899 5905 hasconflictmarkers.append(f)
5900 5906 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
5901 5907 elif unmark:
5902 5908 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
5903 5909 else:
5904 5910 # backup pre-resolve (merge uses .orig for its own purposes)
5905 5911 a = repo.wjoin(f)
5906 5912 try:
5907 5913 util.copyfile(a, a + b".resolve")
5908 5914 except (IOError, OSError) as inst:
5909 5915 if inst.errno != errno.ENOENT:
5910 5916 raise
5911 5917
5912 5918 try:
5913 5919 # preresolve file
5914 5920 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
5915 5921 with ui.configoverride(overrides, b'resolve'):
5916 5922 complete, r = ms.preresolve(f, wctx)
5917 5923 if not complete:
5918 5924 tocomplete.append(f)
5919 5925 elif r:
5920 5926 ret = 1
5921 5927 finally:
5922 5928 ms.commit()
5923 5929
5924 5930 # replace filemerge's .orig file with our resolve file, but only
5925 5931 # for merges that are complete
5926 5932 if complete:
5927 5933 try:
5928 5934 util.rename(
5929 5935 a + b".resolve", scmutil.backuppath(ui, repo, f)
5930 5936 )
5931 5937 except OSError as inst:
5932 5938 if inst.errno != errno.ENOENT:
5933 5939 raise
5934 5940
5935 5941 if hasconflictmarkers:
5936 5942 ui.warn(
5937 5943 _(
5938 5944 b'warning: the following files still have conflict '
5939 5945 b'markers:\n'
5940 5946 )
5941 5947 + b''.join(
5942 5948 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
5943 5949 )
5944 5950 )
5945 5951 if markcheck == b'abort' and not all and not pats:
5946 5952 raise error.Abort(
5947 5953 _(b'conflict markers detected'),
5948 5954 hint=_(b'use --all to mark anyway'),
5949 5955 )
5950 5956
5951 5957 for f in tocomplete:
5952 5958 try:
5953 5959 # resolve file
5954 5960 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
5955 5961 with ui.configoverride(overrides, b'resolve'):
5956 5962 r = ms.resolve(f, wctx)
5957 5963 if r:
5958 5964 ret = 1
5959 5965 finally:
5960 5966 ms.commit()
5961 5967
5962 5968 # replace filemerge's .orig file with our resolve file
5963 5969 a = repo.wjoin(f)
5964 5970 try:
5965 5971 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
5966 5972 except OSError as inst:
5967 5973 if inst.errno != errno.ENOENT:
5968 5974 raise
5969 5975
5970 5976 ms.commit()
5971 5977 branchmerge = repo.dirstate.p2() != nullid
5972 5978 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
5973 5979
5974 5980 if not didwork and pats:
5975 5981 hint = None
5976 5982 if not any([p for p in pats if p.find(b':') >= 0]):
5977 5983 pats = [b'path:%s' % p for p in pats]
5978 5984 m = scmutil.match(wctx, pats, opts)
5979 5985 for f in ms:
5980 5986 if not m(f):
5981 5987 continue
5982 5988
5983 5989 def flag(o):
5984 5990 if o == b're_merge':
5985 5991 return b'--re-merge '
5986 5992 return b'-%s ' % o[0:1]
5987 5993
5988 5994 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
5989 5995 hint = _(b"(try: hg resolve %s%s)\n") % (
5990 5996 flags,
5991 5997 b' '.join(pats),
5992 5998 )
5993 5999 break
5994 6000 ui.warn(_(b"arguments do not match paths that need resolving\n"))
5995 6001 if hint:
5996 6002 ui.warn(hint)
5997 6003
5998 6004 unresolvedf = list(ms.unresolved())
5999 6005 if not unresolvedf:
6000 6006 ui.status(_(b'(no more unresolved files)\n'))
6001 6007 cmdutil.checkafterresolved(repo)
6002 6008
6003 6009 return ret
6004 6010
6005 6011
6006 6012 @command(
6007 6013 b'revert',
6008 6014 [
6009 6015 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6010 6016 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6011 6017 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6012 6018 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6013 6019 (b'i', b'interactive', None, _(b'interactively select the changes')),
6014 6020 ]
6015 6021 + walkopts
6016 6022 + dryrunopts,
6017 6023 _(b'[OPTION]... [-r REV] [NAME]...'),
6018 6024 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6019 6025 )
6020 6026 def revert(ui, repo, *pats, **opts):
6021 6027 """restore files to their checkout state
6022 6028
6023 6029 .. note::
6024 6030
6025 6031 To check out earlier revisions, you should use :hg:`update REV`.
6026 6032 To cancel an uncommitted merge (and lose your changes),
6027 6033 use :hg:`merge --abort`.
6028 6034
6029 6035 With no revision specified, revert the specified files or directories
6030 6036 to the contents they had in the parent of the working directory.
6031 6037 This restores the contents of files to an unmodified
6032 6038 state and unschedules adds, removes, copies, and renames. If the
6033 6039 working directory has two parents, you must explicitly specify a
6034 6040 revision.
6035 6041
6036 6042 Using the -r/--rev or -d/--date options, revert the given files or
6037 6043 directories to their states as of a specific revision. Because
6038 6044 revert does not change the working directory parents, this will
6039 6045 cause these files to appear modified. This can be helpful to "back
6040 6046 out" some or all of an earlier change. See :hg:`backout` for a
6041 6047 related method.
6042 6048
6043 6049 Modified files are saved with a .orig suffix before reverting.
6044 6050 To disable these backups, use --no-backup. It is possible to store
6045 6051 the backup files in a custom directory relative to the root of the
6046 6052 repository by setting the ``ui.origbackuppath`` configuration
6047 6053 option.
6048 6054
6049 6055 See :hg:`help dates` for a list of formats valid for -d/--date.
6050 6056
6051 6057 See :hg:`help backout` for a way to reverse the effect of an
6052 6058 earlier changeset.
6053 6059
6054 6060 Returns 0 on success.
6055 6061 """
6056 6062
6057 6063 opts = pycompat.byteskwargs(opts)
6058 6064 if opts.get(b"date"):
6059 6065 cmdutil.check_incompatible_arguments(opts, b'date', [b'rev'])
6060 6066 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6061 6067
6062 6068 parent, p2 = repo.dirstate.parents()
6063 6069 if not opts.get(b'rev') and p2 != nullid:
6064 6070 # revert after merge is a trap for new users (issue2915)
6065 6071 raise error.Abort(
6066 6072 _(b'uncommitted merge with no revision specified'),
6067 6073 hint=_(b"use 'hg update' or see 'hg help revert'"),
6068 6074 )
6069 6075
6070 6076 rev = opts.get(b'rev')
6071 6077 if rev:
6072 6078 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6073 6079 ctx = scmutil.revsingle(repo, rev)
6074 6080
6075 6081 if not (
6076 6082 pats
6077 6083 or opts.get(b'include')
6078 6084 or opts.get(b'exclude')
6079 6085 or opts.get(b'all')
6080 6086 or opts.get(b'interactive')
6081 6087 ):
6082 6088 msg = _(b"no files or directories specified")
6083 6089 if p2 != nullid:
6084 6090 hint = _(
6085 6091 b"uncommitted merge, use --all to discard all changes,"
6086 6092 b" or 'hg update -C .' to abort the merge"
6087 6093 )
6088 6094 raise error.Abort(msg, hint=hint)
6089 6095 dirty = any(repo.status())
6090 6096 node = ctx.node()
6091 6097 if node != parent:
6092 6098 if dirty:
6093 6099 hint = (
6094 6100 _(
6095 6101 b"uncommitted changes, use --all to discard all"
6096 6102 b" changes, or 'hg update %d' to update"
6097 6103 )
6098 6104 % ctx.rev()
6099 6105 )
6100 6106 else:
6101 6107 hint = (
6102 6108 _(
6103 6109 b"use --all to revert all files,"
6104 6110 b" or 'hg update %d' to update"
6105 6111 )
6106 6112 % ctx.rev()
6107 6113 )
6108 6114 elif dirty:
6109 6115 hint = _(b"uncommitted changes, use --all to discard all changes")
6110 6116 else:
6111 6117 hint = _(b"use --all to revert all files")
6112 6118 raise error.Abort(msg, hint=hint)
6113 6119
6114 6120 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6115 6121
6116 6122
6117 6123 @command(
6118 6124 b'rollback',
6119 6125 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6120 6126 helpcategory=command.CATEGORY_MAINTENANCE,
6121 6127 )
6122 6128 def rollback(ui, repo, **opts):
6123 6129 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6124 6130
6125 6131 Please use :hg:`commit --amend` instead of rollback to correct
6126 6132 mistakes in the last commit.
6127 6133
6128 6134 This command should be used with care. There is only one level of
6129 6135 rollback, and there is no way to undo a rollback. It will also
6130 6136 restore the dirstate at the time of the last transaction, losing
6131 6137 any dirstate changes since that time. This command does not alter
6132 6138 the working directory.
6133 6139
6134 6140 Transactions are used to encapsulate the effects of all commands
6135 6141 that create new changesets or propagate existing changesets into a
6136 6142 repository.
6137 6143
6138 6144 .. container:: verbose
6139 6145
6140 6146 For example, the following commands are transactional, and their
6141 6147 effects can be rolled back:
6142 6148
6143 6149 - commit
6144 6150 - import
6145 6151 - pull
6146 6152 - push (with this repository as the destination)
6147 6153 - unbundle
6148 6154
6149 6155 To avoid permanent data loss, rollback will refuse to rollback a
6150 6156 commit transaction if it isn't checked out. Use --force to
6151 6157 override this protection.
6152 6158
6153 6159 The rollback command can be entirely disabled by setting the
6154 6160 ``ui.rollback`` configuration setting to false. If you're here
6155 6161 because you want to use rollback and it's disabled, you can
6156 6162 re-enable the command by setting ``ui.rollback`` to true.
6157 6163
6158 6164 This command is not intended for use on public repositories. Once
6159 6165 changes are visible for pull by other users, rolling a transaction
6160 6166 back locally is ineffective (someone else may already have pulled
6161 6167 the changes). Furthermore, a race is possible with readers of the
6162 6168 repository; for example an in-progress pull from the repository
6163 6169 may fail if a rollback is performed.
6164 6170
6165 6171 Returns 0 on success, 1 if no rollback data is available.
6166 6172 """
6167 6173 if not ui.configbool(b'ui', b'rollback'):
6168 6174 raise error.Abort(
6169 6175 _(b'rollback is disabled because it is unsafe'),
6170 6176 hint=b'see `hg help -v rollback` for information',
6171 6177 )
6172 6178 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6173 6179
6174 6180
6175 6181 @command(
6176 6182 b'root',
6177 6183 [] + formatteropts,
6178 6184 intents={INTENT_READONLY},
6179 6185 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6180 6186 )
6181 6187 def root(ui, repo, **opts):
6182 6188 """print the root (top) of the current working directory
6183 6189
6184 6190 Print the root directory of the current repository.
6185 6191
6186 6192 .. container:: verbose
6187 6193
6188 6194 Template:
6189 6195
6190 6196 The following keywords are supported in addition to the common template
6191 6197 keywords and functions. See also :hg:`help templates`.
6192 6198
6193 6199 :hgpath: String. Path to the .hg directory.
6194 6200 :storepath: String. Path to the directory holding versioned data.
6195 6201
6196 6202 Returns 0 on success.
6197 6203 """
6198 6204 opts = pycompat.byteskwargs(opts)
6199 6205 with ui.formatter(b'root', opts) as fm:
6200 6206 fm.startitem()
6201 6207 fm.write(b'reporoot', b'%s\n', repo.root)
6202 6208 fm.data(hgpath=repo.path, storepath=repo.spath)
6203 6209
6204 6210
6205 6211 @command(
6206 6212 b'serve',
6207 6213 [
6208 6214 (
6209 6215 b'A',
6210 6216 b'accesslog',
6211 6217 b'',
6212 6218 _(b'name of access log file to write to'),
6213 6219 _(b'FILE'),
6214 6220 ),
6215 6221 (b'd', b'daemon', None, _(b'run server in background')),
6216 6222 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6217 6223 (
6218 6224 b'E',
6219 6225 b'errorlog',
6220 6226 b'',
6221 6227 _(b'name of error log file to write to'),
6222 6228 _(b'FILE'),
6223 6229 ),
6224 6230 # use string type, then we can check if something was passed
6225 6231 (
6226 6232 b'p',
6227 6233 b'port',
6228 6234 b'',
6229 6235 _(b'port to listen on (default: 8000)'),
6230 6236 _(b'PORT'),
6231 6237 ),
6232 6238 (
6233 6239 b'a',
6234 6240 b'address',
6235 6241 b'',
6236 6242 _(b'address to listen on (default: all interfaces)'),
6237 6243 _(b'ADDR'),
6238 6244 ),
6239 6245 (
6240 6246 b'',
6241 6247 b'prefix',
6242 6248 b'',
6243 6249 _(b'prefix path to serve from (default: server root)'),
6244 6250 _(b'PREFIX'),
6245 6251 ),
6246 6252 (
6247 6253 b'n',
6248 6254 b'name',
6249 6255 b'',
6250 6256 _(b'name to show in web pages (default: working directory)'),
6251 6257 _(b'NAME'),
6252 6258 ),
6253 6259 (
6254 6260 b'',
6255 6261 b'web-conf',
6256 6262 b'',
6257 6263 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6258 6264 _(b'FILE'),
6259 6265 ),
6260 6266 (
6261 6267 b'',
6262 6268 b'webdir-conf',
6263 6269 b'',
6264 6270 _(b'name of the hgweb config file (DEPRECATED)'),
6265 6271 _(b'FILE'),
6266 6272 ),
6267 6273 (
6268 6274 b'',
6269 6275 b'pid-file',
6270 6276 b'',
6271 6277 _(b'name of file to write process ID to'),
6272 6278 _(b'FILE'),
6273 6279 ),
6274 6280 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6275 6281 (
6276 6282 b'',
6277 6283 b'cmdserver',
6278 6284 b'',
6279 6285 _(b'for remote clients (ADVANCED)'),
6280 6286 _(b'MODE'),
6281 6287 ),
6282 6288 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6283 6289 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6284 6290 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6285 6291 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6286 6292 (b'', b'print-url', None, _(b'start and print only the URL')),
6287 6293 ]
6288 6294 + subrepoopts,
6289 6295 _(b'[OPTION]...'),
6290 6296 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6291 6297 helpbasic=True,
6292 6298 optionalrepo=True,
6293 6299 )
6294 6300 def serve(ui, repo, **opts):
6295 6301 """start stand-alone webserver
6296 6302
6297 6303 Start a local HTTP repository browser and pull server. You can use
6298 6304 this for ad-hoc sharing and browsing of repositories. It is
6299 6305 recommended to use a real web server to serve a repository for
6300 6306 longer periods of time.
6301 6307
6302 6308 Please note that the server does not implement access control.
6303 6309 This means that, by default, anybody can read from the server and
6304 6310 nobody can write to it by default. Set the ``web.allow-push``
6305 6311 option to ``*`` to allow everybody to push to the server. You
6306 6312 should use a real web server if you need to authenticate users.
6307 6313
6308 6314 By default, the server logs accesses to stdout and errors to
6309 6315 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6310 6316 files.
6311 6317
6312 6318 To have the server choose a free port number to listen on, specify
6313 6319 a port number of 0; in this case, the server will print the port
6314 6320 number it uses.
6315 6321
6316 6322 Returns 0 on success.
6317 6323 """
6318 6324
6319 6325 cmdutil.check_incompatible_arguments(opts, 'stdio', ['cmdserver'])
6320 6326 opts = pycompat.byteskwargs(opts)
6321 6327 if opts[b"print_url"] and ui.verbose:
6322 6328 raise error.Abort(_(b"cannot use --print-url with --verbose"))
6323 6329
6324 6330 if opts[b"stdio"]:
6325 6331 if repo is None:
6326 6332 raise error.RepoError(
6327 6333 _(b"there is no Mercurial repository here (.hg not found)")
6328 6334 )
6329 6335 s = wireprotoserver.sshserver(ui, repo)
6330 6336 s.serve_forever()
6331 6337
6332 6338 service = server.createservice(ui, repo, opts)
6333 6339 return server.runservice(opts, initfn=service.init, runfn=service.run)
6334 6340
6335 6341
6336 6342 @command(
6337 6343 b'shelve',
6338 6344 [
6339 6345 (
6340 6346 b'A',
6341 6347 b'addremove',
6342 6348 None,
6343 6349 _(b'mark new/missing files as added/removed before shelving'),
6344 6350 ),
6345 6351 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6346 6352 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6347 6353 (
6348 6354 b'',
6349 6355 b'date',
6350 6356 b'',
6351 6357 _(b'shelve with the specified commit date'),
6352 6358 _(b'DATE'),
6353 6359 ),
6354 6360 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6355 6361 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6356 6362 (
6357 6363 b'k',
6358 6364 b'keep',
6359 6365 False,
6360 6366 _(b'shelve, but keep changes in the working directory'),
6361 6367 ),
6362 6368 (b'l', b'list', None, _(b'list current shelves')),
6363 6369 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6364 6370 (
6365 6371 b'n',
6366 6372 b'name',
6367 6373 b'',
6368 6374 _(b'use the given name for the shelved commit'),
6369 6375 _(b'NAME'),
6370 6376 ),
6371 6377 (
6372 6378 b'p',
6373 6379 b'patch',
6374 6380 None,
6375 6381 _(
6376 6382 b'output patches for changes (provide the names of the shelved '
6377 6383 b'changes as positional arguments)'
6378 6384 ),
6379 6385 ),
6380 6386 (b'i', b'interactive', None, _(b'interactive mode')),
6381 6387 (
6382 6388 b'',
6383 6389 b'stat',
6384 6390 None,
6385 6391 _(
6386 6392 b'output diffstat-style summary of changes (provide the names of '
6387 6393 b'the shelved changes as positional arguments)'
6388 6394 ),
6389 6395 ),
6390 6396 ]
6391 6397 + cmdutil.walkopts,
6392 6398 _(b'hg shelve [OPTION]... [FILE]...'),
6393 6399 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6394 6400 )
6395 6401 def shelve(ui, repo, *pats, **opts):
6396 6402 '''save and set aside changes from the working directory
6397 6403
6398 6404 Shelving takes files that "hg status" reports as not clean, saves
6399 6405 the modifications to a bundle (a shelved change), and reverts the
6400 6406 files so that their state in the working directory becomes clean.
6401 6407
6402 6408 To restore these changes to the working directory, using "hg
6403 6409 unshelve"; this will work even if you switch to a different
6404 6410 commit.
6405 6411
6406 6412 When no files are specified, "hg shelve" saves all not-clean
6407 6413 files. If specific files or directories are named, only changes to
6408 6414 those files are shelved.
6409 6415
6410 6416 In bare shelve (when no files are specified, without interactive,
6411 6417 include and exclude option), shelving remembers information if the
6412 6418 working directory was on newly created branch, in other words working
6413 6419 directory was on different branch than its first parent. In this
6414 6420 situation unshelving restores branch information to the working directory.
6415 6421
6416 6422 Each shelved change has a name that makes it easier to find later.
6417 6423 The name of a shelved change defaults to being based on the active
6418 6424 bookmark, or if there is no active bookmark, the current named
6419 6425 branch. To specify a different name, use ``--name``.
6420 6426
6421 6427 To see a list of existing shelved changes, use the ``--list``
6422 6428 option. For each shelved change, this will print its name, age,
6423 6429 and description; use ``--patch`` or ``--stat`` for more details.
6424 6430
6425 6431 To delete specific shelved changes, use ``--delete``. To delete
6426 6432 all shelved changes, use ``--cleanup``.
6427 6433 '''
6428 6434 opts = pycompat.byteskwargs(opts)
6429 6435 allowables = [
6430 6436 (b'addremove', {b'create'}), # 'create' is pseudo action
6431 6437 (b'unknown', {b'create'}),
6432 6438 (b'cleanup', {b'cleanup'}),
6433 6439 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6434 6440 (b'delete', {b'delete'}),
6435 6441 (b'edit', {b'create'}),
6436 6442 (b'keep', {b'create'}),
6437 6443 (b'list', {b'list'}),
6438 6444 (b'message', {b'create'}),
6439 6445 (b'name', {b'create'}),
6440 6446 (b'patch', {b'patch', b'list'}),
6441 6447 (b'stat', {b'stat', b'list'}),
6442 6448 ]
6443 6449
6444 6450 def checkopt(opt):
6445 6451 if opts.get(opt):
6446 6452 for i, allowable in allowables:
6447 6453 if opts[i] and opt not in allowable:
6448 6454 raise error.Abort(
6449 6455 _(
6450 6456 b"options '--%s' and '--%s' may not be "
6451 6457 b"used together"
6452 6458 )
6453 6459 % (opt, i)
6454 6460 )
6455 6461 return True
6456 6462
6457 6463 if checkopt(b'cleanup'):
6458 6464 if pats:
6459 6465 raise error.Abort(_(b"cannot specify names when using '--cleanup'"))
6460 6466 return shelvemod.cleanupcmd(ui, repo)
6461 6467 elif checkopt(b'delete'):
6462 6468 return shelvemod.deletecmd(ui, repo, pats)
6463 6469 elif checkopt(b'list'):
6464 6470 return shelvemod.listcmd(ui, repo, pats, opts)
6465 6471 elif checkopt(b'patch') or checkopt(b'stat'):
6466 6472 return shelvemod.patchcmds(ui, repo, pats, opts)
6467 6473 else:
6468 6474 return shelvemod.createcmd(ui, repo, pats, opts)
6469 6475
6470 6476
6471 6477 _NOTTERSE = b'nothing'
6472 6478
6473 6479
6474 6480 @command(
6475 6481 b'status|st',
6476 6482 [
6477 6483 (b'A', b'all', None, _(b'show status of all files')),
6478 6484 (b'm', b'modified', None, _(b'show only modified files')),
6479 6485 (b'a', b'added', None, _(b'show only added files')),
6480 6486 (b'r', b'removed', None, _(b'show only removed files')),
6481 6487 (b'd', b'deleted', None, _(b'show only missing files')),
6482 6488 (b'c', b'clean', None, _(b'show only files without changes')),
6483 6489 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6484 6490 (b'i', b'ignored', None, _(b'show only ignored files')),
6485 6491 (b'n', b'no-status', None, _(b'hide status prefix')),
6486 6492 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6487 6493 (
6488 6494 b'C',
6489 6495 b'copies',
6490 6496 None,
6491 6497 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6492 6498 ),
6493 6499 (
6494 6500 b'0',
6495 6501 b'print0',
6496 6502 None,
6497 6503 _(b'end filenames with NUL, for use with xargs'),
6498 6504 ),
6499 6505 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6500 6506 (
6501 6507 b'',
6502 6508 b'change',
6503 6509 b'',
6504 6510 _(b'list the changed files of a revision'),
6505 6511 _(b'REV'),
6506 6512 ),
6507 6513 ]
6508 6514 + walkopts
6509 6515 + subrepoopts
6510 6516 + formatteropts,
6511 6517 _(b'[OPTION]... [FILE]...'),
6512 6518 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6513 6519 helpbasic=True,
6514 6520 inferrepo=True,
6515 6521 intents={INTENT_READONLY},
6516 6522 )
6517 6523 def status(ui, repo, *pats, **opts):
6518 6524 """show changed files in the working directory
6519 6525
6520 6526 Show status of files in the repository. If names are given, only
6521 6527 files that match are shown. Files that are clean or ignored or
6522 6528 the source of a copy/move operation, are not listed unless
6523 6529 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6524 6530 Unless options described with "show only ..." are given, the
6525 6531 options -mardu are used.
6526 6532
6527 6533 Option -q/--quiet hides untracked (unknown and ignored) files
6528 6534 unless explicitly requested with -u/--unknown or -i/--ignored.
6529 6535
6530 6536 .. note::
6531 6537
6532 6538 :hg:`status` may appear to disagree with diff if permissions have
6533 6539 changed or a merge has occurred. The standard diff format does
6534 6540 not report permission changes and diff only reports changes
6535 6541 relative to one merge parent.
6536 6542
6537 6543 If one revision is given, it is used as the base revision.
6538 6544 If two revisions are given, the differences between them are
6539 6545 shown. The --change option can also be used as a shortcut to list
6540 6546 the changed files of a revision from its first parent.
6541 6547
6542 6548 The codes used to show the status of files are::
6543 6549
6544 6550 M = modified
6545 6551 A = added
6546 6552 R = removed
6547 6553 C = clean
6548 6554 ! = missing (deleted by non-hg command, but still tracked)
6549 6555 ? = not tracked
6550 6556 I = ignored
6551 6557 = origin of the previous file (with --copies)
6552 6558
6553 6559 .. container:: verbose
6554 6560
6555 6561 The -t/--terse option abbreviates the output by showing only the directory
6556 6562 name if all the files in it share the same status. The option takes an
6557 6563 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6558 6564 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6559 6565 for 'ignored' and 'c' for clean.
6560 6566
6561 6567 It abbreviates only those statuses which are passed. Note that clean and
6562 6568 ignored files are not displayed with '--terse ic' unless the -c/--clean
6563 6569 and -i/--ignored options are also used.
6564 6570
6565 6571 The -v/--verbose option shows information when the repository is in an
6566 6572 unfinished merge, shelve, rebase state etc. You can have this behavior
6567 6573 turned on by default by enabling the ``commands.status.verbose`` option.
6568 6574
6569 6575 You can skip displaying some of these states by setting
6570 6576 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6571 6577 'histedit', 'merge', 'rebase', or 'unshelve'.
6572 6578
6573 6579 Template:
6574 6580
6575 6581 The following keywords are supported in addition to the common template
6576 6582 keywords and functions. See also :hg:`help templates`.
6577 6583
6578 6584 :path: String. Repository-absolute path of the file.
6579 6585 :source: String. Repository-absolute path of the file originated from.
6580 6586 Available if ``--copies`` is specified.
6581 6587 :status: String. Character denoting file's status.
6582 6588
6583 6589 Examples:
6584 6590
6585 6591 - show changes in the working directory relative to a
6586 6592 changeset::
6587 6593
6588 6594 hg status --rev 9353
6589 6595
6590 6596 - show changes in the working directory relative to the
6591 6597 current directory (see :hg:`help patterns` for more information)::
6592 6598
6593 6599 hg status re:
6594 6600
6595 6601 - show all changes including copies in an existing changeset::
6596 6602
6597 6603 hg status --copies --change 9353
6598 6604
6599 6605 - get a NUL separated list of added files, suitable for xargs::
6600 6606
6601 6607 hg status -an0
6602 6608
6603 6609 - show more information about the repository status, abbreviating
6604 6610 added, removed, modified, deleted, and untracked paths::
6605 6611
6606 6612 hg status -v -t mardu
6607 6613
6608 6614 Returns 0 on success.
6609 6615
6610 6616 """
6611 6617
6612 6618 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6613 6619 opts = pycompat.byteskwargs(opts)
6614 6620 revs = opts.get(b'rev')
6615 6621 change = opts.get(b'change')
6616 6622 terse = opts.get(b'terse')
6617 6623 if terse is _NOTTERSE:
6618 6624 if revs:
6619 6625 terse = b''
6620 6626 else:
6621 6627 terse = ui.config(b'commands', b'status.terse')
6622 6628
6623 6629 if revs and terse:
6624 6630 msg = _(b'cannot use --terse with --rev')
6625 6631 raise error.Abort(msg)
6626 6632 elif change:
6627 6633 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6628 6634 ctx2 = scmutil.revsingle(repo, change, None)
6629 6635 ctx1 = ctx2.p1()
6630 6636 else:
6631 6637 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6632 6638 ctx1, ctx2 = scmutil.revpair(repo, revs)
6633 6639
6634 6640 forcerelativevalue = None
6635 6641 if ui.hasconfig(b'commands', b'status.relative'):
6636 6642 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6637 6643 uipathfn = scmutil.getuipathfn(
6638 6644 repo,
6639 6645 legacyrelativevalue=bool(pats),
6640 6646 forcerelativevalue=forcerelativevalue,
6641 6647 )
6642 6648
6643 6649 if opts.get(b'print0'):
6644 6650 end = b'\0'
6645 6651 else:
6646 6652 end = b'\n'
6647 6653 states = b'modified added removed deleted unknown ignored clean'.split()
6648 6654 show = [k for k in states if opts.get(k)]
6649 6655 if opts.get(b'all'):
6650 6656 show += ui.quiet and (states[:4] + [b'clean']) or states
6651 6657
6652 6658 if not show:
6653 6659 if ui.quiet:
6654 6660 show = states[:4]
6655 6661 else:
6656 6662 show = states[:5]
6657 6663
6658 6664 m = scmutil.match(ctx2, pats, opts)
6659 6665 if terse:
6660 6666 # we need to compute clean and unknown to terse
6661 6667 stat = repo.status(
6662 6668 ctx1.node(),
6663 6669 ctx2.node(),
6664 6670 m,
6665 6671 b'ignored' in show or b'i' in terse,
6666 6672 clean=True,
6667 6673 unknown=True,
6668 6674 listsubrepos=opts.get(b'subrepos'),
6669 6675 )
6670 6676
6671 6677 stat = cmdutil.tersedir(stat, terse)
6672 6678 else:
6673 6679 stat = repo.status(
6674 6680 ctx1.node(),
6675 6681 ctx2.node(),
6676 6682 m,
6677 6683 b'ignored' in show,
6678 6684 b'clean' in show,
6679 6685 b'unknown' in show,
6680 6686 opts.get(b'subrepos'),
6681 6687 )
6682 6688
6683 6689 changestates = zip(
6684 6690 states,
6685 6691 pycompat.iterbytestr(b'MAR!?IC'),
6686 6692 [getattr(stat, s.decode('utf8')) for s in states],
6687 6693 )
6688 6694
6689 6695 copy = {}
6690 6696 if (
6691 6697 opts.get(b'all')
6692 6698 or opts.get(b'copies')
6693 6699 or ui.configbool(b'ui', b'statuscopies')
6694 6700 ) and not opts.get(b'no_status'):
6695 6701 copy = copies.pathcopies(ctx1, ctx2, m)
6696 6702
6697 6703 morestatus = None
6698 6704 if (
6699 6705 ui.verbose or ui.configbool(b'commands', b'status.verbose')
6700 6706 ) and not ui.plain():
6701 6707 morestatus = cmdutil.readmorestatus(repo)
6702 6708
6703 6709 ui.pager(b'status')
6704 6710 fm = ui.formatter(b'status', opts)
6705 6711 fmt = b'%s' + end
6706 6712 showchar = not opts.get(b'no_status')
6707 6713
6708 6714 for state, char, files in changestates:
6709 6715 if state in show:
6710 6716 label = b'status.' + state
6711 6717 for f in files:
6712 6718 fm.startitem()
6713 6719 fm.context(ctx=ctx2)
6714 6720 fm.data(itemtype=b'file', path=f)
6715 6721 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6716 6722 fm.plain(fmt % uipathfn(f), label=label)
6717 6723 if f in copy:
6718 6724 fm.data(source=copy[f])
6719 6725 fm.plain(
6720 6726 (b' %s' + end) % uipathfn(copy[f]),
6721 6727 label=b'status.copied',
6722 6728 )
6723 6729 if morestatus:
6724 6730 morestatus.formatfile(f, fm)
6725 6731
6726 6732 if morestatus:
6727 6733 morestatus.formatfooter(fm)
6728 6734 fm.end()
6729 6735
6730 6736
6731 6737 @command(
6732 6738 b'summary|sum',
6733 6739 [(b'', b'remote', None, _(b'check for push and pull'))],
6734 6740 b'[--remote]',
6735 6741 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6736 6742 helpbasic=True,
6737 6743 intents={INTENT_READONLY},
6738 6744 )
6739 6745 def summary(ui, repo, **opts):
6740 6746 """summarize working directory state
6741 6747
6742 6748 This generates a brief summary of the working directory state,
6743 6749 including parents, branch, commit status, phase and available updates.
6744 6750
6745 6751 With the --remote option, this will check the default paths for
6746 6752 incoming and outgoing changes. This can be time-consuming.
6747 6753
6748 6754 Returns 0 on success.
6749 6755 """
6750 6756
6751 6757 opts = pycompat.byteskwargs(opts)
6752 6758 ui.pager(b'summary')
6753 6759 ctx = repo[None]
6754 6760 parents = ctx.parents()
6755 6761 pnode = parents[0].node()
6756 6762 marks = []
6757 6763
6758 6764 try:
6759 6765 ms = mergestatemod.mergestate.read(repo)
6760 6766 except error.UnsupportedMergeRecords as e:
6761 6767 s = b' '.join(e.recordtypes)
6762 6768 ui.warn(
6763 6769 _(b'warning: merge state has unsupported record types: %s\n') % s
6764 6770 )
6765 6771 unresolved = []
6766 6772 else:
6767 6773 unresolved = list(ms.unresolved())
6768 6774
6769 6775 for p in parents:
6770 6776 # label with log.changeset (instead of log.parent) since this
6771 6777 # shows a working directory parent *changeset*:
6772 6778 # i18n: column positioning for "hg summary"
6773 6779 ui.write(
6774 6780 _(b'parent: %d:%s ') % (p.rev(), p),
6775 6781 label=logcmdutil.changesetlabels(p),
6776 6782 )
6777 6783 ui.write(b' '.join(p.tags()), label=b'log.tag')
6778 6784 if p.bookmarks():
6779 6785 marks.extend(p.bookmarks())
6780 6786 if p.rev() == -1:
6781 6787 if not len(repo):
6782 6788 ui.write(_(b' (empty repository)'))
6783 6789 else:
6784 6790 ui.write(_(b' (no revision checked out)'))
6785 6791 if p.obsolete():
6786 6792 ui.write(_(b' (obsolete)'))
6787 6793 if p.isunstable():
6788 6794 instabilities = (
6789 6795 ui.label(instability, b'trouble.%s' % instability)
6790 6796 for instability in p.instabilities()
6791 6797 )
6792 6798 ui.write(b' (' + b', '.join(instabilities) + b')')
6793 6799 ui.write(b'\n')
6794 6800 if p.description():
6795 6801 ui.status(
6796 6802 b' ' + p.description().splitlines()[0].strip() + b'\n',
6797 6803 label=b'log.summary',
6798 6804 )
6799 6805
6800 6806 branch = ctx.branch()
6801 6807 bheads = repo.branchheads(branch)
6802 6808 # i18n: column positioning for "hg summary"
6803 6809 m = _(b'branch: %s\n') % branch
6804 6810 if branch != b'default':
6805 6811 ui.write(m, label=b'log.branch')
6806 6812 else:
6807 6813 ui.status(m, label=b'log.branch')
6808 6814
6809 6815 if marks:
6810 6816 active = repo._activebookmark
6811 6817 # i18n: column positioning for "hg summary"
6812 6818 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
6813 6819 if active is not None:
6814 6820 if active in marks:
6815 6821 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
6816 6822 marks.remove(active)
6817 6823 else:
6818 6824 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
6819 6825 for m in marks:
6820 6826 ui.write(b' ' + m, label=b'log.bookmark')
6821 6827 ui.write(b'\n', label=b'log.bookmark')
6822 6828
6823 6829 status = repo.status(unknown=True)
6824 6830
6825 6831 c = repo.dirstate.copies()
6826 6832 copied, renamed = [], []
6827 6833 for d, s in pycompat.iteritems(c):
6828 6834 if s in status.removed:
6829 6835 status.removed.remove(s)
6830 6836 renamed.append(d)
6831 6837 else:
6832 6838 copied.append(d)
6833 6839 if d in status.added:
6834 6840 status.added.remove(d)
6835 6841
6836 6842 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
6837 6843
6838 6844 labels = [
6839 6845 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
6840 6846 (ui.label(_(b'%d added'), b'status.added'), status.added),
6841 6847 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
6842 6848 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
6843 6849 (ui.label(_(b'%d copied'), b'status.copied'), copied),
6844 6850 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
6845 6851 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
6846 6852 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
6847 6853 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
6848 6854 ]
6849 6855 t = []
6850 6856 for l, s in labels:
6851 6857 if s:
6852 6858 t.append(l % len(s))
6853 6859
6854 6860 t = b', '.join(t)
6855 6861 cleanworkdir = False
6856 6862
6857 6863 if repo.vfs.exists(b'graftstate'):
6858 6864 t += _(b' (graft in progress)')
6859 6865 if repo.vfs.exists(b'updatestate'):
6860 6866 t += _(b' (interrupted update)')
6861 6867 elif len(parents) > 1:
6862 6868 t += _(b' (merge)')
6863 6869 elif branch != parents[0].branch():
6864 6870 t += _(b' (new branch)')
6865 6871 elif parents[0].closesbranch() and pnode in repo.branchheads(
6866 6872 branch, closed=True
6867 6873 ):
6868 6874 t += _(b' (head closed)')
6869 6875 elif not (
6870 6876 status.modified
6871 6877 or status.added
6872 6878 or status.removed
6873 6879 or renamed
6874 6880 or copied
6875 6881 or subs
6876 6882 ):
6877 6883 t += _(b' (clean)')
6878 6884 cleanworkdir = True
6879 6885 elif pnode not in bheads:
6880 6886 t += _(b' (new branch head)')
6881 6887
6882 6888 if parents:
6883 6889 pendingphase = max(p.phase() for p in parents)
6884 6890 else:
6885 6891 pendingphase = phases.public
6886 6892
6887 6893 if pendingphase > phases.newcommitphase(ui):
6888 6894 t += b' (%s)' % phases.phasenames[pendingphase]
6889 6895
6890 6896 if cleanworkdir:
6891 6897 # i18n: column positioning for "hg summary"
6892 6898 ui.status(_(b'commit: %s\n') % t.strip())
6893 6899 else:
6894 6900 # i18n: column positioning for "hg summary"
6895 6901 ui.write(_(b'commit: %s\n') % t.strip())
6896 6902
6897 6903 # all ancestors of branch heads - all ancestors of parent = new csets
6898 6904 new = len(
6899 6905 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
6900 6906 )
6901 6907
6902 6908 if new == 0:
6903 6909 # i18n: column positioning for "hg summary"
6904 6910 ui.status(_(b'update: (current)\n'))
6905 6911 elif pnode not in bheads:
6906 6912 # i18n: column positioning for "hg summary"
6907 6913 ui.write(_(b'update: %d new changesets (update)\n') % new)
6908 6914 else:
6909 6915 # i18n: column positioning for "hg summary"
6910 6916 ui.write(
6911 6917 _(b'update: %d new changesets, %d branch heads (merge)\n')
6912 6918 % (new, len(bheads))
6913 6919 )
6914 6920
6915 6921 t = []
6916 6922 draft = len(repo.revs(b'draft()'))
6917 6923 if draft:
6918 6924 t.append(_(b'%d draft') % draft)
6919 6925 secret = len(repo.revs(b'secret()'))
6920 6926 if secret:
6921 6927 t.append(_(b'%d secret') % secret)
6922 6928
6923 6929 if draft or secret:
6924 6930 ui.status(_(b'phases: %s\n') % b', '.join(t))
6925 6931
6926 6932 if obsolete.isenabled(repo, obsolete.createmarkersopt):
6927 6933 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
6928 6934 numtrouble = len(repo.revs(trouble + b"()"))
6929 6935 # We write all the possibilities to ease translation
6930 6936 troublemsg = {
6931 6937 b"orphan": _(b"orphan: %d changesets"),
6932 6938 b"contentdivergent": _(b"content-divergent: %d changesets"),
6933 6939 b"phasedivergent": _(b"phase-divergent: %d changesets"),
6934 6940 }
6935 6941 if numtrouble > 0:
6936 6942 ui.status(troublemsg[trouble] % numtrouble + b"\n")
6937 6943
6938 6944 cmdutil.summaryhooks(ui, repo)
6939 6945
6940 6946 if opts.get(b'remote'):
6941 6947 needsincoming, needsoutgoing = True, True
6942 6948 else:
6943 6949 needsincoming, needsoutgoing = False, False
6944 6950 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
6945 6951 if i:
6946 6952 needsincoming = True
6947 6953 if o:
6948 6954 needsoutgoing = True
6949 6955 if not needsincoming and not needsoutgoing:
6950 6956 return
6951 6957
6952 6958 def getincoming():
6953 6959 source, branches = hg.parseurl(ui.expandpath(b'default'))
6954 6960 sbranch = branches[0]
6955 6961 try:
6956 6962 other = hg.peer(repo, {}, source)
6957 6963 except error.RepoError:
6958 6964 if opts.get(b'remote'):
6959 6965 raise
6960 6966 return source, sbranch, None, None, None
6961 6967 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
6962 6968 if revs:
6963 6969 revs = [other.lookup(rev) for rev in revs]
6964 6970 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
6965 6971 repo.ui.pushbuffer()
6966 6972 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
6967 6973 repo.ui.popbuffer()
6968 6974 return source, sbranch, other, commoninc, commoninc[1]
6969 6975
6970 6976 if needsincoming:
6971 6977 source, sbranch, sother, commoninc, incoming = getincoming()
6972 6978 else:
6973 6979 source = sbranch = sother = commoninc = incoming = None
6974 6980
6975 6981 def getoutgoing():
6976 6982 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
6977 6983 dbranch = branches[0]
6978 6984 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
6979 6985 if source != dest:
6980 6986 try:
6981 6987 dother = hg.peer(repo, {}, dest)
6982 6988 except error.RepoError:
6983 6989 if opts.get(b'remote'):
6984 6990 raise
6985 6991 return dest, dbranch, None, None
6986 6992 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
6987 6993 elif sother is None:
6988 6994 # there is no explicit destination peer, but source one is invalid
6989 6995 return dest, dbranch, None, None
6990 6996 else:
6991 6997 dother = sother
6992 6998 if source != dest or (sbranch is not None and sbranch != dbranch):
6993 6999 common = None
6994 7000 else:
6995 7001 common = commoninc
6996 7002 if revs:
6997 7003 revs = [repo.lookup(rev) for rev in revs]
6998 7004 repo.ui.pushbuffer()
6999 7005 outgoing = discovery.findcommonoutgoing(
7000 7006 repo, dother, onlyheads=revs, commoninc=common
7001 7007 )
7002 7008 repo.ui.popbuffer()
7003 7009 return dest, dbranch, dother, outgoing
7004 7010
7005 7011 if needsoutgoing:
7006 7012 dest, dbranch, dother, outgoing = getoutgoing()
7007 7013 else:
7008 7014 dest = dbranch = dother = outgoing = None
7009 7015
7010 7016 if opts.get(b'remote'):
7011 7017 t = []
7012 7018 if incoming:
7013 7019 t.append(_(b'1 or more incoming'))
7014 7020 o = outgoing.missing
7015 7021 if o:
7016 7022 t.append(_(b'%d outgoing') % len(o))
7017 7023 other = dother or sother
7018 7024 if b'bookmarks' in other.listkeys(b'namespaces'):
7019 7025 counts = bookmarks.summary(repo, other)
7020 7026 if counts[0] > 0:
7021 7027 t.append(_(b'%d incoming bookmarks') % counts[0])
7022 7028 if counts[1] > 0:
7023 7029 t.append(_(b'%d outgoing bookmarks') % counts[1])
7024 7030
7025 7031 if t:
7026 7032 # i18n: column positioning for "hg summary"
7027 7033 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7028 7034 else:
7029 7035 # i18n: column positioning for "hg summary"
7030 7036 ui.status(_(b'remote: (synced)\n'))
7031 7037
7032 7038 cmdutil.summaryremotehooks(
7033 7039 ui,
7034 7040 repo,
7035 7041 opts,
7036 7042 (
7037 7043 (source, sbranch, sother, commoninc),
7038 7044 (dest, dbranch, dother, outgoing),
7039 7045 ),
7040 7046 )
7041 7047
7042 7048
7043 7049 @command(
7044 7050 b'tag',
7045 7051 [
7046 7052 (b'f', b'force', None, _(b'force tag')),
7047 7053 (b'l', b'local', None, _(b'make the tag local')),
7048 7054 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7049 7055 (b'', b'remove', None, _(b'remove a tag')),
7050 7056 # -l/--local is already there, commitopts cannot be used
7051 7057 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7052 7058 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7053 7059 ]
7054 7060 + commitopts2,
7055 7061 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7056 7062 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7057 7063 )
7058 7064 def tag(ui, repo, name1, *names, **opts):
7059 7065 """add one or more tags for the current or given revision
7060 7066
7061 7067 Name a particular revision using <name>.
7062 7068
7063 7069 Tags are used to name particular revisions of the repository and are
7064 7070 very useful to compare different revisions, to go back to significant
7065 7071 earlier versions or to mark branch points as releases, etc. Changing
7066 7072 an existing tag is normally disallowed; use -f/--force to override.
7067 7073
7068 7074 If no revision is given, the parent of the working directory is
7069 7075 used.
7070 7076
7071 7077 To facilitate version control, distribution, and merging of tags,
7072 7078 they are stored as a file named ".hgtags" which is managed similarly
7073 7079 to other project files and can be hand-edited if necessary. This
7074 7080 also means that tagging creates a new commit. The file
7075 7081 ".hg/localtags" is used for local tags (not shared among
7076 7082 repositories).
7077 7083
7078 7084 Tag commits are usually made at the head of a branch. If the parent
7079 7085 of the working directory is not a branch head, :hg:`tag` aborts; use
7080 7086 -f/--force to force the tag commit to be based on a non-head
7081 7087 changeset.
7082 7088
7083 7089 See :hg:`help dates` for a list of formats valid for -d/--date.
7084 7090
7085 7091 Since tag names have priority over branch names during revision
7086 7092 lookup, using an existing branch name as a tag name is discouraged.
7087 7093
7088 7094 Returns 0 on success.
7089 7095 """
7090 7096 cmdutil.check_incompatible_arguments(opts, 'remove', ['rev'])
7091 7097 opts = pycompat.byteskwargs(opts)
7092 7098 with repo.wlock(), repo.lock():
7093 7099 rev_ = b"."
7094 7100 names = [t.strip() for t in (name1,) + names]
7095 7101 if len(names) != len(set(names)):
7096 7102 raise error.Abort(_(b'tag names must be unique'))
7097 7103 for n in names:
7098 7104 scmutil.checknewlabel(repo, n, b'tag')
7099 7105 if not n:
7100 7106 raise error.Abort(
7101 7107 _(b'tag names cannot consist entirely of whitespace')
7102 7108 )
7103 7109 if opts.get(b'rev'):
7104 7110 rev_ = opts[b'rev']
7105 7111 message = opts.get(b'message')
7106 7112 if opts.get(b'remove'):
7107 7113 if opts.get(b'local'):
7108 7114 expectedtype = b'local'
7109 7115 else:
7110 7116 expectedtype = b'global'
7111 7117
7112 7118 for n in names:
7113 7119 if repo.tagtype(n) == b'global':
7114 7120 alltags = tagsmod.findglobaltags(ui, repo)
7115 7121 if alltags[n][0] == nullid:
7116 7122 raise error.Abort(_(b"tag '%s' is already removed") % n)
7117 7123 if not repo.tagtype(n):
7118 7124 raise error.Abort(_(b"tag '%s' does not exist") % n)
7119 7125 if repo.tagtype(n) != expectedtype:
7120 7126 if expectedtype == b'global':
7121 7127 raise error.Abort(
7122 7128 _(b"tag '%s' is not a global tag") % n
7123 7129 )
7124 7130 else:
7125 7131 raise error.Abort(_(b"tag '%s' is not a local tag") % n)
7126 7132 rev_ = b'null'
7127 7133 if not message:
7128 7134 # we don't translate commit messages
7129 7135 message = b'Removed tag %s' % b', '.join(names)
7130 7136 elif not opts.get(b'force'):
7131 7137 for n in names:
7132 7138 if n in repo.tags():
7133 7139 raise error.Abort(
7134 7140 _(b"tag '%s' already exists (use -f to force)") % n
7135 7141 )
7136 7142 if not opts.get(b'local'):
7137 7143 p1, p2 = repo.dirstate.parents()
7138 7144 if p2 != nullid:
7139 7145 raise error.Abort(_(b'uncommitted merge'))
7140 7146 bheads = repo.branchheads()
7141 7147 if not opts.get(b'force') and bheads and p1 not in bheads:
7142 7148 raise error.Abort(
7143 7149 _(
7144 7150 b'working directory is not at a branch head '
7145 7151 b'(use -f to force)'
7146 7152 )
7147 7153 )
7148 7154 node = scmutil.revsingle(repo, rev_).node()
7149 7155
7150 7156 if not message:
7151 7157 # we don't translate commit messages
7152 7158 message = b'Added tag %s for changeset %s' % (
7153 7159 b', '.join(names),
7154 7160 short(node),
7155 7161 )
7156 7162
7157 7163 date = opts.get(b'date')
7158 7164 if date:
7159 7165 date = dateutil.parsedate(date)
7160 7166
7161 7167 if opts.get(b'remove'):
7162 7168 editform = b'tag.remove'
7163 7169 else:
7164 7170 editform = b'tag.add'
7165 7171 editor = cmdutil.getcommiteditor(
7166 7172 editform=editform, **pycompat.strkwargs(opts)
7167 7173 )
7168 7174
7169 7175 # don't allow tagging the null rev
7170 7176 if (
7171 7177 not opts.get(b'remove')
7172 7178 and scmutil.revsingle(repo, rev_).rev() == nullrev
7173 7179 ):
7174 7180 raise error.Abort(_(b"cannot tag null revision"))
7175 7181
7176 7182 tagsmod.tag(
7177 7183 repo,
7178 7184 names,
7179 7185 node,
7180 7186 message,
7181 7187 opts.get(b'local'),
7182 7188 opts.get(b'user'),
7183 7189 date,
7184 7190 editor=editor,
7185 7191 )
7186 7192
7187 7193
7188 7194 @command(
7189 7195 b'tags',
7190 7196 formatteropts,
7191 7197 b'',
7192 7198 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7193 7199 intents={INTENT_READONLY},
7194 7200 )
7195 7201 def tags(ui, repo, **opts):
7196 7202 """list repository tags
7197 7203
7198 7204 This lists both regular and local tags. When the -v/--verbose
7199 7205 switch is used, a third column "local" is printed for local tags.
7200 7206 When the -q/--quiet switch is used, only the tag name is printed.
7201 7207
7202 7208 .. container:: verbose
7203 7209
7204 7210 Template:
7205 7211
7206 7212 The following keywords are supported in addition to the common template
7207 7213 keywords and functions such as ``{tag}``. See also
7208 7214 :hg:`help templates`.
7209 7215
7210 7216 :type: String. ``local`` for local tags.
7211 7217
7212 7218 Returns 0 on success.
7213 7219 """
7214 7220
7215 7221 opts = pycompat.byteskwargs(opts)
7216 7222 ui.pager(b'tags')
7217 7223 fm = ui.formatter(b'tags', opts)
7218 7224 hexfunc = fm.hexfunc
7219 7225
7220 7226 for t, n in reversed(repo.tagslist()):
7221 7227 hn = hexfunc(n)
7222 7228 label = b'tags.normal'
7223 7229 tagtype = b''
7224 7230 if repo.tagtype(t) == b'local':
7225 7231 label = b'tags.local'
7226 7232 tagtype = b'local'
7227 7233
7228 7234 fm.startitem()
7229 7235 fm.context(repo=repo)
7230 7236 fm.write(b'tag', b'%s', t, label=label)
7231 7237 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7232 7238 fm.condwrite(
7233 7239 not ui.quiet,
7234 7240 b'rev node',
7235 7241 fmt,
7236 7242 repo.changelog.rev(n),
7237 7243 hn,
7238 7244 label=label,
7239 7245 )
7240 7246 fm.condwrite(
7241 7247 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7242 7248 )
7243 7249 fm.plain(b'\n')
7244 7250 fm.end()
7245 7251
7246 7252
7247 7253 @command(
7248 7254 b'tip',
7249 7255 [
7250 7256 (b'p', b'patch', None, _(b'show patch')),
7251 7257 (b'g', b'git', None, _(b'use git extended diff format')),
7252 7258 ]
7253 7259 + templateopts,
7254 7260 _(b'[-p] [-g]'),
7255 7261 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7256 7262 )
7257 7263 def tip(ui, repo, **opts):
7258 7264 """show the tip revision (DEPRECATED)
7259 7265
7260 7266 The tip revision (usually just called the tip) is the changeset
7261 7267 most recently added to the repository (and therefore the most
7262 7268 recently changed head).
7263 7269
7264 7270 If you have just made a commit, that commit will be the tip. If
7265 7271 you have just pulled changes from another repository, the tip of
7266 7272 that repository becomes the current tip. The "tip" tag is special
7267 7273 and cannot be renamed or assigned to a different changeset.
7268 7274
7269 7275 This command is deprecated, please use :hg:`heads` instead.
7270 7276
7271 7277 Returns 0 on success.
7272 7278 """
7273 7279 opts = pycompat.byteskwargs(opts)
7274 7280 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7275 7281 displayer.show(repo[b'tip'])
7276 7282 displayer.close()
7277 7283
7278 7284
7279 7285 @command(
7280 7286 b'unbundle',
7281 7287 [
7282 7288 (
7283 7289 b'u',
7284 7290 b'update',
7285 7291 None,
7286 7292 _(b'update to new branch head if changesets were unbundled'),
7287 7293 )
7288 7294 ],
7289 7295 _(b'[-u] FILE...'),
7290 7296 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7291 7297 )
7292 7298 def unbundle(ui, repo, fname1, *fnames, **opts):
7293 7299 """apply one or more bundle files
7294 7300
7295 7301 Apply one or more bundle files generated by :hg:`bundle`.
7296 7302
7297 7303 Returns 0 on success, 1 if an update has unresolved files.
7298 7304 """
7299 7305 fnames = (fname1,) + fnames
7300 7306
7301 7307 with repo.lock():
7302 7308 for fname in fnames:
7303 7309 f = hg.openpath(ui, fname)
7304 7310 gen = exchange.readbundle(ui, f, fname)
7305 7311 if isinstance(gen, streamclone.streamcloneapplier):
7306 7312 raise error.Abort(
7307 7313 _(
7308 7314 b'packed bundles cannot be applied with '
7309 7315 b'"hg unbundle"'
7310 7316 ),
7311 7317 hint=_(b'use "hg debugapplystreamclonebundle"'),
7312 7318 )
7313 7319 url = b'bundle:' + fname
7314 7320 try:
7315 7321 txnname = b'unbundle'
7316 7322 if not isinstance(gen, bundle2.unbundle20):
7317 7323 txnname = b'unbundle\n%s' % util.hidepassword(url)
7318 7324 with repo.transaction(txnname) as tr:
7319 7325 op = bundle2.applybundle(
7320 7326 repo, gen, tr, source=b'unbundle', url=url
7321 7327 )
7322 7328 except error.BundleUnknownFeatureError as exc:
7323 7329 raise error.Abort(
7324 7330 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7325 7331 hint=_(
7326 7332 b"see https://mercurial-scm.org/"
7327 7333 b"wiki/BundleFeature for more "
7328 7334 b"information"
7329 7335 ),
7330 7336 )
7331 7337 modheads = bundle2.combinechangegroupresults(op)
7332 7338
7333 7339 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7334 7340
7335 7341
7336 7342 @command(
7337 7343 b'unshelve',
7338 7344 [
7339 7345 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7340 7346 (
7341 7347 b'c',
7342 7348 b'continue',
7343 7349 None,
7344 7350 _(b'continue an incomplete unshelve operation'),
7345 7351 ),
7346 7352 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7347 7353 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7348 7354 (
7349 7355 b'n',
7350 7356 b'name',
7351 7357 b'',
7352 7358 _(b'restore shelved change with given name'),
7353 7359 _(b'NAME'),
7354 7360 ),
7355 7361 (b't', b'tool', b'', _(b'specify merge tool')),
7356 7362 (
7357 7363 b'',
7358 7364 b'date',
7359 7365 b'',
7360 7366 _(b'set date for temporary commits (DEPRECATED)'),
7361 7367 _(b'DATE'),
7362 7368 ),
7363 7369 ],
7364 7370 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7365 7371 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7366 7372 )
7367 7373 def unshelve(ui, repo, *shelved, **opts):
7368 7374 """restore a shelved change to the working directory
7369 7375
7370 7376 This command accepts an optional name of a shelved change to
7371 7377 restore. If none is given, the most recent shelved change is used.
7372 7378
7373 7379 If a shelved change is applied successfully, the bundle that
7374 7380 contains the shelved changes is moved to a backup location
7375 7381 (.hg/shelve-backup).
7376 7382
7377 7383 Since you can restore a shelved change on top of an arbitrary
7378 7384 commit, it is possible that unshelving will result in a conflict
7379 7385 between your changes and the commits you are unshelving onto. If
7380 7386 this occurs, you must resolve the conflict, then use
7381 7387 ``--continue`` to complete the unshelve operation. (The bundle
7382 7388 will not be moved until you successfully complete the unshelve.)
7383 7389
7384 7390 (Alternatively, you can use ``--abort`` to abandon an unshelve
7385 7391 that causes a conflict. This reverts the unshelved changes, and
7386 7392 leaves the bundle in place.)
7387 7393
7388 7394 If bare shelved change (without interactive, include and exclude
7389 7395 option) was done on newly created branch it would restore branch
7390 7396 information to the working directory.
7391 7397
7392 7398 After a successful unshelve, the shelved changes are stored in a
7393 7399 backup directory. Only the N most recent backups are kept. N
7394 7400 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7395 7401 configuration option.
7396 7402
7397 7403 .. container:: verbose
7398 7404
7399 7405 Timestamp in seconds is used to decide order of backups. More
7400 7406 than ``maxbackups`` backups are kept, if same timestamp
7401 7407 prevents from deciding exact order of them, for safety.
7402 7408
7403 7409 Selected changes can be unshelved with ``--interactive`` flag.
7404 7410 The working directory is updated with the selected changes, and
7405 7411 only the unselected changes remain shelved.
7406 7412 Note: The whole shelve is applied to working directory first before
7407 7413 running interactively. So, this will bring up all the conflicts between
7408 7414 working directory and the shelve, irrespective of which changes will be
7409 7415 unshelved.
7410 7416 """
7411 7417 with repo.wlock():
7412 7418 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7413 7419
7414 7420
7415 7421 statemod.addunfinished(
7416 7422 b'unshelve',
7417 7423 fname=b'shelvedstate',
7418 7424 continueflag=True,
7419 7425 abortfunc=shelvemod.hgabortunshelve,
7420 7426 continuefunc=shelvemod.hgcontinueunshelve,
7421 7427 cmdmsg=_(b'unshelve already in progress'),
7422 7428 )
7423 7429
7424 7430
7425 7431 @command(
7426 7432 b'update|up|checkout|co',
7427 7433 [
7428 7434 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7429 7435 (b'c', b'check', None, _(b'require clean working directory')),
7430 7436 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7431 7437 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7432 7438 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7433 7439 ]
7434 7440 + mergetoolopts,
7435 7441 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7436 7442 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7437 7443 helpbasic=True,
7438 7444 )
7439 7445 def update(ui, repo, node=None, **opts):
7440 7446 """update working directory (or switch revisions)
7441 7447
7442 7448 Update the repository's working directory to the specified
7443 7449 changeset. If no changeset is specified, update to the tip of the
7444 7450 current named branch and move the active bookmark (see :hg:`help
7445 7451 bookmarks`).
7446 7452
7447 7453 Update sets the working directory's parent revision to the specified
7448 7454 changeset (see :hg:`help parents`).
7449 7455
7450 7456 If the changeset is not a descendant or ancestor of the working
7451 7457 directory's parent and there are uncommitted changes, the update is
7452 7458 aborted. With the -c/--check option, the working directory is checked
7453 7459 for uncommitted changes; if none are found, the working directory is
7454 7460 updated to the specified changeset.
7455 7461
7456 7462 .. container:: verbose
7457 7463
7458 7464 The -C/--clean, -c/--check, and -m/--merge options control what
7459 7465 happens if the working directory contains uncommitted changes.
7460 7466 At most of one of them can be specified.
7461 7467
7462 7468 1. If no option is specified, and if
7463 7469 the requested changeset is an ancestor or descendant of
7464 7470 the working directory's parent, the uncommitted changes
7465 7471 are merged into the requested changeset and the merged
7466 7472 result is left uncommitted. If the requested changeset is
7467 7473 not an ancestor or descendant (that is, it is on another
7468 7474 branch), the update is aborted and the uncommitted changes
7469 7475 are preserved.
7470 7476
7471 7477 2. With the -m/--merge option, the update is allowed even if the
7472 7478 requested changeset is not an ancestor or descendant of
7473 7479 the working directory's parent.
7474 7480
7475 7481 3. With the -c/--check option, the update is aborted and the
7476 7482 uncommitted changes are preserved.
7477 7483
7478 7484 4. With the -C/--clean option, uncommitted changes are discarded and
7479 7485 the working directory is updated to the requested changeset.
7480 7486
7481 7487 To cancel an uncommitted merge (and lose your changes), use
7482 7488 :hg:`merge --abort`.
7483 7489
7484 7490 Use null as the changeset to remove the working directory (like
7485 7491 :hg:`clone -U`).
7486 7492
7487 7493 If you want to revert just one file to an older revision, use
7488 7494 :hg:`revert [-r REV] NAME`.
7489 7495
7490 7496 See :hg:`help dates` for a list of formats valid for -d/--date.
7491 7497
7492 7498 Returns 0 on success, 1 if there are unresolved files.
7493 7499 """
7494 7500 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7495 7501 rev = opts.get('rev')
7496 7502 date = opts.get('date')
7497 7503 clean = opts.get('clean')
7498 7504 check = opts.get('check')
7499 7505 merge = opts.get('merge')
7500 7506 if rev and node:
7501 7507 raise error.Abort(_(b"please specify just one revision"))
7502 7508
7503 7509 if ui.configbool(b'commands', b'update.requiredest'):
7504 7510 if not node and not rev and not date:
7505 7511 raise error.Abort(
7506 7512 _(b'you must specify a destination'),
7507 7513 hint=_(b'for example: hg update ".::"'),
7508 7514 )
7509 7515
7510 7516 if rev is None or rev == b'':
7511 7517 rev = node
7512 7518
7513 7519 if date and rev is not None:
7514 7520 raise error.Abort(_(b"you can't specify a revision and a date"))
7515 7521
7516 7522 updatecheck = None
7517 7523 if check:
7518 7524 updatecheck = b'abort'
7519 7525 elif merge:
7520 7526 updatecheck = b'none'
7521 7527
7522 7528 with repo.wlock():
7523 7529 cmdutil.clearunfinished(repo)
7524 7530 if date:
7525 7531 rev = cmdutil.finddate(ui, repo, date)
7526 7532
7527 7533 # if we defined a bookmark, we have to remember the original name
7528 7534 brev = rev
7529 7535 if rev:
7530 7536 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7531 7537 ctx = scmutil.revsingle(repo, rev, default=None)
7532 7538 rev = ctx.rev()
7533 7539 hidden = ctx.hidden()
7534 7540 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7535 7541 with ui.configoverride(overrides, b'update'):
7536 7542 ret = hg.updatetotally(
7537 7543 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7538 7544 )
7539 7545 if hidden:
7540 7546 ctxstr = ctx.hex()[:12]
7541 7547 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7542 7548
7543 7549 if ctx.obsolete():
7544 7550 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7545 7551 ui.warn(b"(%s)\n" % obsfatemsg)
7546 7552 return ret
7547 7553
7548 7554
7549 7555 @command(
7550 7556 b'verify',
7551 7557 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7552 7558 helpcategory=command.CATEGORY_MAINTENANCE,
7553 7559 )
7554 7560 def verify(ui, repo, **opts):
7555 7561 """verify the integrity of the repository
7556 7562
7557 7563 Verify the integrity of the current repository.
7558 7564
7559 7565 This will perform an extensive check of the repository's
7560 7566 integrity, validating the hashes and checksums of each entry in
7561 7567 the changelog, manifest, and tracked files, as well as the
7562 7568 integrity of their crosslinks and indices.
7563 7569
7564 7570 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7565 7571 for more information about recovery from corruption of the
7566 7572 repository.
7567 7573
7568 7574 Returns 0 on success, 1 if errors are encountered.
7569 7575 """
7570 7576 opts = pycompat.byteskwargs(opts)
7571 7577
7572 7578 level = None
7573 7579 if opts[b'full']:
7574 7580 level = verifymod.VERIFY_FULL
7575 7581 return hg.verify(repo, level)
7576 7582
7577 7583
7578 7584 @command(
7579 7585 b'version',
7580 7586 [] + formatteropts,
7581 7587 helpcategory=command.CATEGORY_HELP,
7582 7588 norepo=True,
7583 7589 intents={INTENT_READONLY},
7584 7590 )
7585 7591 def version_(ui, **opts):
7586 7592 """output version and copyright information
7587 7593
7588 7594 .. container:: verbose
7589 7595
7590 7596 Template:
7591 7597
7592 7598 The following keywords are supported. See also :hg:`help templates`.
7593 7599
7594 7600 :extensions: List of extensions.
7595 7601 :ver: String. Version number.
7596 7602
7597 7603 And each entry of ``{extensions}`` provides the following sub-keywords
7598 7604 in addition to ``{ver}``.
7599 7605
7600 7606 :bundled: Boolean. True if included in the release.
7601 7607 :name: String. Extension name.
7602 7608 """
7603 7609 opts = pycompat.byteskwargs(opts)
7604 7610 if ui.verbose:
7605 7611 ui.pager(b'version')
7606 7612 fm = ui.formatter(b"version", opts)
7607 7613 fm.startitem()
7608 7614 fm.write(
7609 7615 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7610 7616 )
7611 7617 license = _(
7612 7618 b"(see https://mercurial-scm.org for more information)\n"
7613 7619 b"\nCopyright (C) 2005-2020 Matt Mackall and others\n"
7614 7620 b"This is free software; see the source for copying conditions. "
7615 7621 b"There is NO\nwarranty; "
7616 7622 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7617 7623 )
7618 7624 if not ui.quiet:
7619 7625 fm.plain(license)
7620 7626
7621 7627 if ui.verbose:
7622 7628 fm.plain(_(b"\nEnabled extensions:\n\n"))
7623 7629 # format names and versions into columns
7624 7630 names = []
7625 7631 vers = []
7626 7632 isinternals = []
7627 7633 for name, module in sorted(extensions.extensions()):
7628 7634 names.append(name)
7629 7635 vers.append(extensions.moduleversion(module) or None)
7630 7636 isinternals.append(extensions.ismoduleinternal(module))
7631 7637 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7632 7638 if names:
7633 7639 namefmt = b" %%-%ds " % max(len(n) for n in names)
7634 7640 places = [_(b"external"), _(b"internal")]
7635 7641 for n, v, p in zip(names, vers, isinternals):
7636 7642 fn.startitem()
7637 7643 fn.condwrite(ui.verbose, b"name", namefmt, n)
7638 7644 if ui.verbose:
7639 7645 fn.plain(b"%s " % places[p])
7640 7646 fn.data(bundled=p)
7641 7647 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7642 7648 if ui.verbose:
7643 7649 fn.plain(b"\n")
7644 7650 fn.end()
7645 7651 fm.end()
7646 7652
7647 7653
7648 7654 def loadcmdtable(ui, name, cmdtable):
7649 7655 """Load command functions from specified cmdtable
7650 7656 """
7651 7657 overrides = [cmd for cmd in cmdtable if cmd in table]
7652 7658 if overrides:
7653 7659 ui.warn(
7654 7660 _(b"extension '%s' overrides commands: %s\n")
7655 7661 % (name, b" ".join(overrides))
7656 7662 )
7657 7663 table.update(cmdtable)
@@ -1,1363 +1,1365 b''
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import, print_function
9 9
10 10 import difflib
11 11 import errno
12 12 import getopt
13 13 import io
14 14 import os
15 15 import pdb
16 16 import re
17 17 import signal
18 18 import sys
19 19 import traceback
20 20
21 21
22 22 from .i18n import _
23 23 from .pycompat import getattr
24 24
25 25 from hgdemandimport import tracing
26 26
27 27 from . import (
28 28 cmdutil,
29 29 color,
30 30 commands,
31 31 demandimport,
32 32 encoding,
33 33 error,
34 34 extensions,
35 35 fancyopts,
36 36 help,
37 37 hg,
38 38 hook,
39 39 localrepo,
40 40 profiling,
41 41 pycompat,
42 42 rcutil,
43 43 registrar,
44 44 requirements as requirementsmod,
45 45 scmutil,
46 46 ui as uimod,
47 47 util,
48 48 vfs,
49 49 )
50 50
51 51 from .utils import (
52 52 procutil,
53 53 stringutil,
54 54 )
55 55
56 56
57 57 class request(object):
58 58 def __init__(
59 59 self,
60 60 args,
61 61 ui=None,
62 62 repo=None,
63 63 fin=None,
64 64 fout=None,
65 65 ferr=None,
66 66 fmsg=None,
67 67 prereposetups=None,
68 68 ):
69 69 self.args = args
70 70 self.ui = ui
71 71 self.repo = repo
72 72
73 73 # input/output/error streams
74 74 self.fin = fin
75 75 self.fout = fout
76 76 self.ferr = ferr
77 77 # separate stream for status/error messages
78 78 self.fmsg = fmsg
79 79
80 80 # remember options pre-parsed by _earlyparseopts()
81 81 self.earlyoptions = {}
82 82
83 83 # reposetups which run before extensions, useful for chg to pre-fill
84 84 # low-level repo state (for example, changelog) before extensions.
85 85 self.prereposetups = prereposetups or []
86 86
87 87 # store the parsed and canonical command
88 88 self.canonical_command = None
89 89
90 90 def _runexithandlers(self):
91 91 exc = None
92 92 handlers = self.ui._exithandlers
93 93 try:
94 94 while handlers:
95 95 func, args, kwargs = handlers.pop()
96 96 try:
97 97 func(*args, **kwargs)
98 98 except: # re-raises below
99 99 if exc is None:
100 100 exc = sys.exc_info()[1]
101 101 self.ui.warnnoi18n(b'error in exit handlers:\n')
102 102 self.ui.traceback(force=True)
103 103 finally:
104 104 if exc is not None:
105 105 raise exc
106 106
107 107
108 108 def run():
109 109 """run the command in sys.argv"""
110 110 try:
111 111 initstdio()
112 112 with tracing.log('parse args into request'):
113 113 req = request(pycompat.sysargv[1:])
114 114 err = None
115 115 try:
116 116 status = dispatch(req)
117 117 except error.StdioError as e:
118 118 err = e
119 119 status = -1
120 120
121 121 # In all cases we try to flush stdio streams.
122 122 if util.safehasattr(req.ui, b'fout'):
123 123 assert req.ui is not None # help pytype
124 124 assert req.ui.fout is not None # help pytype
125 125 try:
126 126 req.ui.fout.flush()
127 127 except IOError as e:
128 128 err = e
129 129 status = -1
130 130
131 131 if util.safehasattr(req.ui, b'ferr'):
132 132 assert req.ui is not None # help pytype
133 133 assert req.ui.ferr is not None # help pytype
134 134 try:
135 135 if err is not None and err.errno != errno.EPIPE:
136 136 req.ui.ferr.write(
137 137 b'abort: %s\n' % encoding.strtolocal(err.strerror)
138 138 )
139 139 req.ui.ferr.flush()
140 140 # There's not much we can do about an I/O error here. So (possibly)
141 141 # change the status code and move on.
142 142 except IOError:
143 143 status = -1
144 144
145 145 _silencestdio()
146 146 except KeyboardInterrupt:
147 147 # Catch early/late KeyboardInterrupt as last ditch. Here nothing will
148 148 # be printed to console to avoid another IOError/KeyboardInterrupt.
149 149 status = -1
150 150 sys.exit(status & 255)
151 151
152 152
153 153 if pycompat.ispy3:
154 154
155 155 def initstdio():
156 156 # stdio streams on Python 3 are io.TextIOWrapper instances proxying another
157 157 # buffer. These streams will normalize \n to \r\n by default. Mercurial's
158 158 # preferred mechanism for writing output (ui.write()) uses io.BufferedWriter
159 159 # instances, which write to the underlying stdio file descriptor in binary
160 160 # mode. ui.write() uses \n for line endings and no line ending normalization
161 161 # is attempted through this interface. This "just works," even if the system
162 162 # preferred line ending is not \n.
163 163 #
164 164 # But some parts of Mercurial (e.g. hooks) can still send data to sys.stdout
165 165 # and sys.stderr. They will inherit the line ending normalization settings,
166 166 # potentially causing e.g. \r\n to be emitted. Since emitting \n should
167 167 # "just work," here we change the sys.* streams to disable line ending
168 168 # normalization, ensuring compatibility with our ui type.
169 169
170 170 # write_through is new in Python 3.7.
171 171 kwargs = {
172 172 "newline": "\n",
173 173 "line_buffering": sys.stdout.line_buffering,
174 174 }
175 175 if util.safehasattr(sys.stdout, "write_through"):
176 176 kwargs["write_through"] = sys.stdout.write_through
177 177 sys.stdout = io.TextIOWrapper(
178 178 sys.stdout.buffer, sys.stdout.encoding, sys.stdout.errors, **kwargs
179 179 )
180 180
181 181 kwargs = {
182 182 "newline": "\n",
183 183 "line_buffering": sys.stderr.line_buffering,
184 184 }
185 185 if util.safehasattr(sys.stderr, "write_through"):
186 186 kwargs["write_through"] = sys.stderr.write_through
187 187 sys.stderr = io.TextIOWrapper(
188 188 sys.stderr.buffer, sys.stderr.encoding, sys.stderr.errors, **kwargs
189 189 )
190 190
191 191 # No write_through on read-only stream.
192 192 sys.stdin = io.TextIOWrapper(
193 193 sys.stdin.buffer,
194 194 sys.stdin.encoding,
195 195 sys.stdin.errors,
196 196 # None is universal newlines mode.
197 197 newline=None,
198 198 line_buffering=sys.stdin.line_buffering,
199 199 )
200 200
201 201 def _silencestdio():
202 202 for fp in (sys.stdout, sys.stderr):
203 203 # Check if the file is okay
204 204 try:
205 205 fp.flush()
206 206 continue
207 207 except IOError:
208 208 pass
209 209 # Otherwise mark it as closed to silence "Exception ignored in"
210 210 # message emitted by the interpreter finalizer. Be careful to
211 211 # not close procutil.stdout, which may be a fdopen-ed file object
212 212 # and its close() actually closes the underlying file descriptor.
213 213 try:
214 214 fp.close()
215 215 except IOError:
216 216 pass
217 217
218 218
219 219 else:
220 220
221 221 def initstdio():
222 222 for fp in (sys.stdin, sys.stdout, sys.stderr):
223 223 procutil.setbinary(fp)
224 224
225 225 def _silencestdio():
226 226 pass
227 227
228 228
229 229 def _getsimilar(symbols, value):
230 230 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
231 231 # The cutoff for similarity here is pretty arbitrary. It should
232 232 # probably be investigated and tweaked.
233 233 return [s for s in symbols if sim(s) > 0.6]
234 234
235 235
236 236 def _reportsimilar(write, similar):
237 237 if len(similar) == 1:
238 238 write(_(b"(did you mean %s?)\n") % similar[0])
239 239 elif similar:
240 240 ss = b", ".join(sorted(similar))
241 241 write(_(b"(did you mean one of %s?)\n") % ss)
242 242
243 243
244 244 def _formatparse(write, inst):
245 245 similar = []
246 246 if isinstance(inst, error.UnknownIdentifier):
247 247 # make sure to check fileset first, as revset can invoke fileset
248 248 similar = _getsimilar(inst.symbols, inst.function)
249 249 if inst.location is not None:
250 250 write(
251 251 _(b"hg: parse error at %s: %s\n")
252 252 % (pycompat.bytestr(inst.location), inst.message)
253 253 )
254 254 else:
255 255 write(_(b"hg: parse error: %s\n") % inst.message)
256 256 _reportsimilar(write, similar)
257 257 if inst.hint:
258 258 write(_(b"(%s)\n") % inst.hint)
259 259
260 260
261 261 def _formatargs(args):
262 262 return b' '.join(procutil.shellquote(a) for a in args)
263 263
264 264
265 265 def dispatch(req):
266 266 """run the command specified in req.args; returns an integer status code"""
267 267 with tracing.log('dispatch.dispatch'):
268 268 if req.ferr:
269 269 ferr = req.ferr
270 270 elif req.ui:
271 271 ferr = req.ui.ferr
272 272 else:
273 273 ferr = procutil.stderr
274 274
275 275 try:
276 276 if not req.ui:
277 277 req.ui = uimod.ui.load()
278 278 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
279 279 if req.earlyoptions[b'traceback']:
280 280 req.ui.setconfig(b'ui', b'traceback', b'on', b'--traceback')
281 281
282 282 # set ui streams from the request
283 283 if req.fin:
284 284 req.ui.fin = req.fin
285 285 if req.fout:
286 286 req.ui.fout = req.fout
287 287 if req.ferr:
288 288 req.ui.ferr = req.ferr
289 289 if req.fmsg:
290 290 req.ui.fmsg = req.fmsg
291 291 except error.Abort as inst:
292 292 ferr.write(_(b"abort: %s\n") % inst.message)
293 293 if inst.hint:
294 294 ferr.write(_(b"(%s)\n") % inst.hint)
295 295 return -1
296 296 except error.ParseError as inst:
297 297 _formatparse(ferr.write, inst)
298 298 return -1
299 299
300 300 msg = _formatargs(req.args)
301 301 starttime = util.timer()
302 302 ret = 1 # default of Python exit code on unhandled exception
303 303 try:
304 304 ret = _runcatch(req) or 0
305 305 except error.ProgrammingError as inst:
306 306 req.ui.error(_(b'** ProgrammingError: %s\n') % inst)
307 307 if inst.hint:
308 308 req.ui.error(_(b'** (%s)\n') % inst.hint)
309 309 raise
310 310 except KeyboardInterrupt as inst:
311 311 try:
312 312 if isinstance(inst, error.SignalInterrupt):
313 313 msg = _(b"killed!\n")
314 314 else:
315 315 msg = _(b"interrupted!\n")
316 316 req.ui.error(msg)
317 317 except error.SignalInterrupt:
318 318 # maybe pager would quit without consuming all the output, and
319 319 # SIGPIPE was raised. we cannot print anything in this case.
320 320 pass
321 321 except IOError as inst:
322 322 if inst.errno != errno.EPIPE:
323 323 raise
324 324 ret = -1
325 325 finally:
326 326 duration = util.timer() - starttime
327 327 req.ui.flush() # record blocked times
328 328 if req.ui.logblockedtimes:
329 329 req.ui._blockedtimes[b'command_duration'] = duration * 1000
330 330 req.ui.log(
331 331 b'uiblocked',
332 332 b'ui blocked ms\n',
333 333 **pycompat.strkwargs(req.ui._blockedtimes)
334 334 )
335 335 return_code = ret & 255
336 336 req.ui.log(
337 337 b"commandfinish",
338 338 b"%s exited %d after %0.2f seconds\n",
339 339 msg,
340 340 return_code,
341 341 duration,
342 342 return_code=return_code,
343 343 duration=duration,
344 344 canonical_command=req.canonical_command,
345 345 )
346 346 try:
347 347 req._runexithandlers()
348 348 except: # exiting, so no re-raises
349 349 ret = ret or -1
350 350 # do flush again since ui.log() and exit handlers may write to ui
351 351 req.ui.flush()
352 352 return ret
353 353
354 354
355 355 def _runcatch(req):
356 356 with tracing.log('dispatch._runcatch'):
357 357
358 358 def catchterm(*args):
359 359 raise error.SignalInterrupt
360 360
361 361 ui = req.ui
362 362 try:
363 363 for name in b'SIGBREAK', b'SIGHUP', b'SIGTERM':
364 364 num = getattr(signal, name, None)
365 365 if num:
366 366 signal.signal(num, catchterm)
367 367 except ValueError:
368 368 pass # happens if called in a thread
369 369
370 370 def _runcatchfunc():
371 371 realcmd = None
372 372 try:
373 373 cmdargs = fancyopts.fancyopts(
374 374 req.args[:], commands.globalopts, {}
375 375 )
376 376 cmd = cmdargs[0]
377 377 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
378 378 realcmd = aliases[0]
379 379 except (
380 380 error.UnknownCommand,
381 381 error.AmbiguousCommand,
382 382 IndexError,
383 383 getopt.GetoptError,
384 384 ):
385 385 # Don't handle this here. We know the command is
386 386 # invalid, but all we're worried about for now is that
387 387 # it's not a command that server operators expect to
388 388 # be safe to offer to users in a sandbox.
389 389 pass
390 390 if realcmd == b'serve' and b'--stdio' in cmdargs:
391 391 # We want to constrain 'hg serve --stdio' instances pretty
392 392 # closely, as many shared-ssh access tools want to grant
393 393 # access to run *only* 'hg -R $repo serve --stdio'. We
394 394 # restrict to exactly that set of arguments, and prohibit
395 395 # any repo name that starts with '--' to prevent
396 396 # shenanigans wherein a user does something like pass
397 397 # --debugger or --config=ui.debugger=1 as a repo
398 398 # name. This used to actually run the debugger.
399 399 if (
400 400 len(req.args) != 4
401 401 or req.args[0] != b'-R'
402 402 or req.args[1].startswith(b'--')
403 403 or req.args[2] != b'serve'
404 404 or req.args[3] != b'--stdio'
405 405 ):
406 406 raise error.Abort(
407 407 _(b'potentially unsafe serve --stdio invocation: %s')
408 408 % (stringutil.pprint(req.args),)
409 409 )
410 410
411 411 try:
412 412 debugger = b'pdb'
413 413 debugtrace = {b'pdb': pdb.set_trace}
414 414 debugmortem = {b'pdb': pdb.post_mortem}
415 415
416 416 # read --config before doing anything else
417 417 # (e.g. to change trust settings for reading .hg/hgrc)
418 418 cfgs = _parseconfig(req.ui, req.earlyoptions[b'config'])
419 419
420 420 if req.repo:
421 421 # copy configs that were passed on the cmdline (--config) to
422 422 # the repo ui
423 423 for sec, name, val in cfgs:
424 424 req.repo.ui.setconfig(
425 425 sec, name, val, source=b'--config'
426 426 )
427 427
428 428 # developer config: ui.debugger
429 429 debugger = ui.config(b"ui", b"debugger")
430 430 debugmod = pdb
431 431 if not debugger or ui.plain():
432 432 # if we are in HGPLAIN mode, then disable custom debugging
433 433 debugger = b'pdb'
434 434 elif req.earlyoptions[b'debugger']:
435 435 # This import can be slow for fancy debuggers, so only
436 436 # do it when absolutely necessary, i.e. when actual
437 437 # debugging has been requested
438 438 with demandimport.deactivated():
439 439 try:
440 440 debugmod = __import__(debugger)
441 441 except ImportError:
442 442 pass # Leave debugmod = pdb
443 443
444 444 debugtrace[debugger] = debugmod.set_trace
445 445 debugmortem[debugger] = debugmod.post_mortem
446 446
447 447 # enter the debugger before command execution
448 448 if req.earlyoptions[b'debugger']:
449 449 ui.warn(
450 450 _(
451 451 b"entering debugger - "
452 452 b"type c to continue starting hg or h for help\n"
453 453 )
454 454 )
455 455
456 456 if (
457 457 debugger != b'pdb'
458 458 and debugtrace[debugger] == debugtrace[b'pdb']
459 459 ):
460 460 ui.warn(
461 461 _(
462 462 b"%s debugger specified "
463 463 b"but its module was not found\n"
464 464 )
465 465 % debugger
466 466 )
467 467 with demandimport.deactivated():
468 468 debugtrace[debugger]()
469 469 try:
470 470 return _dispatch(req)
471 471 finally:
472 472 ui.flush()
473 473 except: # re-raises
474 474 # enter the debugger when we hit an exception
475 475 if req.earlyoptions[b'debugger']:
476 476 traceback.print_exc()
477 477 debugmortem[debugger](sys.exc_info()[2])
478 478 raise
479 479
480 480 return _callcatch(ui, _runcatchfunc)
481 481
482 482
483 483 def _callcatch(ui, func):
484 484 """like scmutil.callcatch but handles more high-level exceptions about
485 485 config parsing and commands. besides, use handlecommandexception to handle
486 486 uncaught exceptions.
487 487 """
488 488 try:
489 489 return scmutil.callcatch(ui, func)
490 490 except error.AmbiguousCommand as inst:
491 491 ui.warn(
492 492 _(b"hg: command '%s' is ambiguous:\n %s\n")
493 493 % (inst.prefix, b" ".join(inst.matches))
494 494 )
495 495 except error.CommandError as inst:
496 496 if inst.command:
497 497 ui.pager(b'help')
498 498 msgbytes = pycompat.bytestr(inst.message)
499 499 ui.warn(_(b"hg %s: %s\n") % (inst.command, msgbytes))
500 500 commands.help_(ui, inst.command, full=False, command=True)
501 501 else:
502 502 ui.warn(_(b"hg: %s\n") % inst.message)
503 503 ui.warn(_(b"(use 'hg help -v' for a list of global options)\n"))
504 504 except error.ParseError as inst:
505 505 _formatparse(ui.warn, inst)
506 506 return -1
507 507 except error.UnknownCommand as inst:
508 508 nocmdmsg = _(b"hg: unknown command '%s'\n") % inst.command
509 509 try:
510 510 # check if the command is in a disabled extension
511 511 # (but don't check for extensions themselves)
512 512 formatted = help.formattedhelp(
513 513 ui, commands, inst.command, unknowncmd=True
514 514 )
515 515 ui.warn(nocmdmsg)
516 516 ui.write(formatted)
517 517 except (error.UnknownCommand, error.Abort):
518 518 suggested = False
519 519 if inst.all_commands:
520 520 sim = _getsimilar(inst.all_commands, inst.command)
521 521 if sim:
522 522 ui.warn(nocmdmsg)
523 523 _reportsimilar(ui.warn, sim)
524 524 suggested = True
525 525 if not suggested:
526 526 ui.warn(nocmdmsg)
527 527 ui.warn(_(b"(use 'hg help' for a list of commands)\n"))
528 528 except IOError:
529 529 raise
530 530 except KeyboardInterrupt:
531 531 raise
532 532 except: # probably re-raises
533 533 if not handlecommandexception(ui):
534 534 raise
535 535
536 536 return -1
537 537
538 538
539 539 def aliasargs(fn, givenargs):
540 540 args = []
541 541 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
542 542 if not util.safehasattr(fn, b'_origfunc'):
543 543 args = getattr(fn, 'args', args)
544 544 if args:
545 545 cmd = b' '.join(map(procutil.shellquote, args))
546 546
547 547 nums = []
548 548
549 549 def replacer(m):
550 550 num = int(m.group(1)) - 1
551 551 nums.append(num)
552 552 if num < len(givenargs):
553 553 return givenargs[num]
554 554 raise error.Abort(_(b'too few arguments for command alias'))
555 555
556 556 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
557 557 givenargs = [x for i, x in enumerate(givenargs) if i not in nums]
558 558 args = pycompat.shlexsplit(cmd)
559 559 return args + givenargs
560 560
561 561
562 562 def aliasinterpolate(name, args, cmd):
563 563 '''interpolate args into cmd for shell aliases
564 564
565 565 This also handles $0, $@ and "$@".
566 566 '''
567 567 # util.interpolate can't deal with "$@" (with quotes) because it's only
568 568 # built to match prefix + patterns.
569 569 replacemap = {b'$%d' % (i + 1): arg for i, arg in enumerate(args)}
570 570 replacemap[b'$0'] = name
571 571 replacemap[b'$$'] = b'$'
572 572 replacemap[b'$@'] = b' '.join(args)
573 573 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
574 574 # parameters, separated out into words. Emulate the same behavior here by
575 575 # quoting the arguments individually. POSIX shells will then typically
576 576 # tokenize each argument into exactly one word.
577 577 replacemap[b'"$@"'] = b' '.join(procutil.shellquote(arg) for arg in args)
578 578 # escape '\$' for regex
579 579 regex = b'|'.join(replacemap.keys()).replace(b'$', br'\$')
580 580 r = re.compile(regex)
581 581 return r.sub(lambda x: replacemap[x.group()], cmd)
582 582
583 583
584 584 class cmdalias(object):
585 585 def __init__(self, ui, name, definition, cmdtable, source):
586 586 self.name = self.cmd = name
587 587 self.cmdname = b''
588 588 self.definition = definition
589 589 self.fn = None
590 590 self.givenargs = []
591 591 self.opts = []
592 592 self.help = b''
593 593 self.badalias = None
594 594 self.unknowncmd = False
595 595 self.source = source
596 596
597 597 try:
598 598 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
599 599 for alias, e in pycompat.iteritems(cmdtable):
600 600 if e is entry:
601 601 self.cmd = alias
602 602 break
603 603 self.shadows = True
604 604 except error.UnknownCommand:
605 605 self.shadows = False
606 606
607 607 if not self.definition:
608 608 self.badalias = _(b"no definition for alias '%s'") % self.name
609 609 return
610 610
611 611 if self.definition.startswith(b'!'):
612 612 shdef = self.definition[1:]
613 613 self.shell = True
614 614
615 615 def fn(ui, *args):
616 616 env = {b'HG_ARGS': b' '.join((self.name,) + args)}
617 617
618 618 def _checkvar(m):
619 619 if m.groups()[0] == b'$':
620 620 return m.group()
621 621 elif int(m.groups()[0]) <= len(args):
622 622 return m.group()
623 623 else:
624 624 ui.debug(
625 625 b"No argument found for substitution "
626 626 b"of %i variable in alias '%s' definition.\n"
627 627 % (int(m.groups()[0]), self.name)
628 628 )
629 629 return b''
630 630
631 631 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
632 632 cmd = aliasinterpolate(self.name, args, cmd)
633 633 return ui.system(
634 634 cmd, environ=env, blockedtag=b'alias_%s' % self.name
635 635 )
636 636
637 637 self.fn = fn
638 638 self.alias = True
639 639 self._populatehelp(ui, name, shdef, self.fn)
640 640 return
641 641
642 642 try:
643 643 args = pycompat.shlexsplit(self.definition)
644 644 except ValueError as inst:
645 645 self.badalias = _(b"error in definition for alias '%s': %s") % (
646 646 self.name,
647 647 stringutil.forcebytestr(inst),
648 648 )
649 649 return
650 650 earlyopts, args = _earlysplitopts(args)
651 651 if earlyopts:
652 652 self.badalias = _(
653 653 b"error in definition for alias '%s': %s may "
654 654 b"only be given on the command line"
655 655 ) % (self.name, b'/'.join(pycompat.ziplist(*earlyopts)[0]))
656 656 return
657 657 self.cmdname = cmd = args.pop(0)
658 658 self.givenargs = args
659 659
660 660 try:
661 661 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
662 662 if len(tableentry) > 2:
663 663 self.fn, self.opts, cmdhelp = tableentry
664 664 else:
665 665 self.fn, self.opts = tableentry
666 666 cmdhelp = None
667 667
668 668 self.alias = True
669 669 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
670 670
671 671 except error.UnknownCommand:
672 672 self.badalias = _(
673 673 b"alias '%s' resolves to unknown command '%s'"
674 674 ) % (self.name, cmd,)
675 675 self.unknowncmd = True
676 676 except error.AmbiguousCommand:
677 677 self.badalias = _(
678 678 b"alias '%s' resolves to ambiguous command '%s'"
679 679 ) % (self.name, cmd,)
680 680
681 681 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
682 682 # confine strings to be passed to i18n.gettext()
683 683 cfg = {}
684 684 for k in (b'doc', b'help', b'category'):
685 685 v = ui.config(b'alias', b'%s:%s' % (name, k), None)
686 686 if v is None:
687 687 continue
688 688 if not encoding.isasciistr(v):
689 689 self.badalias = _(
690 690 b"non-ASCII character in alias definition '%s:%s'"
691 691 ) % (name, k)
692 692 return
693 693 cfg[k] = v
694 694
695 695 self.help = cfg.get(b'help', defaulthelp or b'')
696 696 if self.help and self.help.startswith(b"hg " + cmd):
697 697 # drop prefix in old-style help lines so hg shows the alias
698 698 self.help = self.help[4 + len(cmd) :]
699 699
700 700 self.owndoc = b'doc' in cfg
701 701 doc = cfg.get(b'doc', pycompat.getdoc(fn))
702 702 if doc is not None:
703 703 doc = pycompat.sysstr(doc)
704 704 self.__doc__ = doc
705 705
706 706 self.helpcategory = cfg.get(
707 707 b'category', registrar.command.CATEGORY_NONE
708 708 )
709 709
710 710 @property
711 711 def args(self):
712 712 args = pycompat.maplist(util.expandpath, self.givenargs)
713 713 return aliasargs(self.fn, args)
714 714
715 715 def __getattr__(self, name):
716 716 adefaults = {
717 717 'norepo': True,
718 718 'intents': set(),
719 719 'optionalrepo': False,
720 720 'inferrepo': False,
721 721 }
722 722 if name not in adefaults:
723 723 raise AttributeError(name)
724 724 if self.badalias or util.safehasattr(self, b'shell'):
725 725 return adefaults[name]
726 726 return getattr(self.fn, name)
727 727
728 728 def __call__(self, ui, *args, **opts):
729 729 if self.badalias:
730 730 hint = None
731 731 if self.unknowncmd:
732 732 try:
733 733 # check if the command is in a disabled extension
734 734 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
735 735 hint = _(b"'%s' is provided by '%s' extension") % (cmd, ext)
736 736 except error.UnknownCommand:
737 737 pass
738 738 raise error.Abort(self.badalias, hint=hint)
739 739 if self.shadows:
740 740 ui.debug(
741 741 b"alias '%s' shadows command '%s'\n" % (self.name, self.cmdname)
742 742 )
743 743
744 744 ui.log(
745 745 b'commandalias',
746 746 b"alias '%s' expands to '%s'\n",
747 747 self.name,
748 748 self.definition,
749 749 )
750 750 if util.safehasattr(self, b'shell'):
751 751 return self.fn(ui, *args, **opts)
752 752 else:
753 753 try:
754 754 return util.checksignature(self.fn)(ui, *args, **opts)
755 755 except error.SignatureError:
756 756 args = b' '.join([self.cmdname] + self.args)
757 757 ui.debug(b"alias '%s' expands to '%s'\n" % (self.name, args))
758 758 raise
759 759
760 760
761 761 class lazyaliasentry(object):
762 762 """like a typical command entry (func, opts, help), but is lazy"""
763 763
764 764 def __init__(self, ui, name, definition, cmdtable, source):
765 765 self.ui = ui
766 766 self.name = name
767 767 self.definition = definition
768 768 self.cmdtable = cmdtable.copy()
769 769 self.source = source
770 770 self.alias = True
771 771
772 772 @util.propertycache
773 773 def _aliasdef(self):
774 774 return cmdalias(
775 775 self.ui, self.name, self.definition, self.cmdtable, self.source
776 776 )
777 777
778 778 def __getitem__(self, n):
779 779 aliasdef = self._aliasdef
780 780 if n == 0:
781 781 return aliasdef
782 782 elif n == 1:
783 783 return aliasdef.opts
784 784 elif n == 2:
785 785 return aliasdef.help
786 786 else:
787 787 raise IndexError
788 788
789 789 def __iter__(self):
790 790 for i in range(3):
791 791 yield self[i]
792 792
793 793 def __len__(self):
794 794 return 3
795 795
796 796
797 797 def addaliases(ui, cmdtable):
798 798 # aliases are processed after extensions have been loaded, so they
799 799 # may use extension commands. Aliases can also use other alias definitions,
800 800 # but only if they have been defined prior to the current definition.
801 801 for alias, definition in ui.configitems(b'alias', ignoresub=True):
802 802 try:
803 803 if cmdtable[alias].definition == definition:
804 804 continue
805 805 except (KeyError, AttributeError):
806 806 # definition might not exist or it might not be a cmdalias
807 807 pass
808 808
809 809 source = ui.configsource(b'alias', alias)
810 810 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
811 811 cmdtable[alias] = entry
812 812
813 813
814 814 def _parse(ui, args):
815 815 options = {}
816 816 cmdoptions = {}
817 817
818 818 try:
819 819 args = fancyopts.fancyopts(args, commands.globalopts, options)
820 820 except getopt.GetoptError as inst:
821 821 raise error.CommandError(None, stringutil.forcebytestr(inst))
822 822
823 823 if args:
824 824 cmd, args = args[0], args[1:]
825 825 aliases, entry = cmdutil.findcmd(
826 826 cmd, commands.table, ui.configbool(b"ui", b"strict")
827 827 )
828 828 cmd = aliases[0]
829 829 args = aliasargs(entry[0], args)
830 830 defaults = ui.config(b"defaults", cmd)
831 831 if defaults:
832 832 args = (
833 833 pycompat.maplist(util.expandpath, pycompat.shlexsplit(defaults))
834 834 + args
835 835 )
836 836 c = list(entry[1])
837 837 else:
838 838 cmd = None
839 839 c = []
840 840
841 841 # combine global options into local
842 842 for o in commands.globalopts:
843 843 c.append((o[0], o[1], options[o[1]], o[3]))
844 844
845 845 try:
846 846 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
847 847 except getopt.GetoptError as inst:
848 848 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
849 849
850 850 # separate global options back out
851 851 for o in commands.globalopts:
852 852 n = o[1]
853 853 options[n] = cmdoptions[n]
854 854 del cmdoptions[n]
855 855
856 856 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
857 857
858 858
859 859 def _parseconfig(ui, config):
860 860 """parse the --config options from the command line"""
861 861 configs = []
862 862
863 863 for cfg in config:
864 864 try:
865 865 name, value = [cfgelem.strip() for cfgelem in cfg.split(b'=', 1)]
866 866 section, name = name.split(b'.', 1)
867 867 if not section or not name:
868 868 raise IndexError
869 869 ui.setconfig(section, name, value, b'--config')
870 870 configs.append((section, name, value))
871 871 except (IndexError, ValueError):
872 872 raise error.Abort(
873 873 _(
874 874 b'malformed --config option: %r '
875 875 b'(use --config section.name=value)'
876 876 )
877 877 % pycompat.bytestr(cfg)
878 878 )
879 879
880 880 return configs
881 881
882 882
883 883 def _earlyparseopts(ui, args):
884 884 options = {}
885 885 fancyopts.fancyopts(
886 886 args,
887 887 commands.globalopts,
888 888 options,
889 889 gnu=not ui.plain(b'strictflags'),
890 890 early=True,
891 891 optaliases={b'repository': [b'repo']},
892 892 )
893 893 return options
894 894
895 895
896 896 def _earlysplitopts(args):
897 897 """Split args into a list of possible early options and remainder args"""
898 898 shortoptions = b'R:'
899 899 # TODO: perhaps 'debugger' should be included
900 900 longoptions = [b'cwd=', b'repository=', b'repo=', b'config=']
901 901 return fancyopts.earlygetopt(
902 902 args, shortoptions, longoptions, gnu=True, keepsep=True
903 903 )
904 904
905 905
906 906 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
907 907 # run pre-hook, and abort if it fails
908 908 hook.hook(
909 909 lui,
910 910 repo,
911 911 b"pre-%s" % cmd,
912 912 True,
913 913 args=b" ".join(fullargs),
914 914 pats=cmdpats,
915 915 opts=cmdoptions,
916 916 )
917 917 try:
918 918 ret = _runcommand(ui, options, cmd, d)
919 919 # run post-hook, passing command result
920 920 hook.hook(
921 921 lui,
922 922 repo,
923 923 b"post-%s" % cmd,
924 924 False,
925 925 args=b" ".join(fullargs),
926 926 result=ret,
927 927 pats=cmdpats,
928 928 opts=cmdoptions,
929 929 )
930 930 except Exception:
931 931 # run failure hook and re-raise
932 932 hook.hook(
933 933 lui,
934 934 repo,
935 935 b"fail-%s" % cmd,
936 936 False,
937 937 args=b" ".join(fullargs),
938 938 pats=cmdpats,
939 939 opts=cmdoptions,
940 940 )
941 941 raise
942 942 return ret
943 943
944 944
945 945 def _readsharedsourceconfig(ui, path):
946 946 """if the current repository is shared one, this tries to read
947 947 .hg/hgrc of shared source if we are in share-safe mode
948 948
949 949 Config read is loaded into the ui object passed
950 950
951 951 This should be called before reading .hg/hgrc or the main repo
952 952 as that overrides config set in shared source"""
953 953 try:
954 954 with open(os.path.join(path, b".hg", b"requires"), "rb") as fp:
955 955 requirements = set(fp.read().splitlines())
956 956 if not (
957 957 requirementsmod.SHARESAFE_REQUIREMENT in requirements
958 958 and requirementsmod.SHARED_REQUIREMENT in requirements
959 959 ):
960 960 return
961 961 hgvfs = vfs.vfs(os.path.join(path, b".hg"))
962 962 sharedvfs = localrepo._getsharedvfs(hgvfs, requirements)
963 963 ui.readconfig(sharedvfs.join(b"hgrc"), path)
964 964 except IOError:
965 965 pass
966 966
967 967
968 968 def _getlocal(ui, rpath, wd=None):
969 969 """Return (path, local ui object) for the given target path.
970 970
971 971 Takes paths in [cwd]/.hg/hgrc into account."
972 972 """
973 973 if wd is None:
974 974 try:
975 975 wd = encoding.getcwd()
976 976 except OSError as e:
977 977 raise error.Abort(
978 978 _(b"error getting current working directory: %s")
979 979 % encoding.strtolocal(e.strerror)
980 980 )
981 981
982 982 path = cmdutil.findrepo(wd) or b""
983 983 if not path:
984 984 lui = ui
985 985 else:
986 986 lui = ui.copy()
987 987 if rcutil.use_repo_hgrc():
988 988 _readsharedsourceconfig(lui, path)
989 989 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
990 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
990 991
991 992 if rpath:
992 993 path = lui.expandpath(rpath)
993 994 lui = ui.copy()
994 995 if rcutil.use_repo_hgrc():
995 996 _readsharedsourceconfig(lui, path)
996 997 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
998 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
997 999
998 1000 return path, lui
999 1001
1000 1002
1001 1003 def _checkshellalias(lui, ui, args):
1002 1004 """Return the function to run the shell alias, if it is required"""
1003 1005 options = {}
1004 1006
1005 1007 try:
1006 1008 args = fancyopts.fancyopts(args, commands.globalopts, options)
1007 1009 except getopt.GetoptError:
1008 1010 return
1009 1011
1010 1012 if not args:
1011 1013 return
1012 1014
1013 1015 cmdtable = commands.table
1014 1016
1015 1017 cmd = args[0]
1016 1018 try:
1017 1019 strict = ui.configbool(b"ui", b"strict")
1018 1020 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
1019 1021 except (error.AmbiguousCommand, error.UnknownCommand):
1020 1022 return
1021 1023
1022 1024 cmd = aliases[0]
1023 1025 fn = entry[0]
1024 1026
1025 1027 if cmd and util.safehasattr(fn, b'shell'):
1026 1028 # shell alias shouldn't receive early options which are consumed by hg
1027 1029 _earlyopts, args = _earlysplitopts(args)
1028 1030 d = lambda: fn(ui, *args[1:])
1029 1031 return lambda: runcommand(
1030 1032 lui, None, cmd, args[:1], ui, options, d, [], {}
1031 1033 )
1032 1034
1033 1035
1034 1036 def _dispatch(req):
1035 1037 args = req.args
1036 1038 ui = req.ui
1037 1039
1038 1040 # check for cwd
1039 1041 cwd = req.earlyoptions[b'cwd']
1040 1042 if cwd:
1041 1043 os.chdir(cwd)
1042 1044
1043 1045 rpath = req.earlyoptions[b'repository']
1044 1046 path, lui = _getlocal(ui, rpath)
1045 1047
1046 1048 uis = {ui, lui}
1047 1049
1048 1050 if req.repo:
1049 1051 uis.add(req.repo.ui)
1050 1052
1051 1053 if (
1052 1054 req.earlyoptions[b'verbose']
1053 1055 or req.earlyoptions[b'debug']
1054 1056 or req.earlyoptions[b'quiet']
1055 1057 ):
1056 1058 for opt in (b'verbose', b'debug', b'quiet'):
1057 1059 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
1058 1060 for ui_ in uis:
1059 1061 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1060 1062
1061 1063 if req.earlyoptions[b'profile']:
1062 1064 for ui_ in uis:
1063 1065 ui_.setconfig(b'profiling', b'enabled', b'true', b'--profile')
1064 1066
1065 1067 profile = lui.configbool(b'profiling', b'enabled')
1066 1068 with profiling.profile(lui, enabled=profile) as profiler:
1067 1069 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
1068 1070 # reposetup
1069 1071 extensions.loadall(lui)
1070 1072 # Propagate any changes to lui.__class__ by extensions
1071 1073 ui.__class__ = lui.__class__
1072 1074
1073 1075 # (uisetup and extsetup are handled in extensions.loadall)
1074 1076
1075 1077 # (reposetup is handled in hg.repository)
1076 1078
1077 1079 addaliases(lui, commands.table)
1078 1080
1079 1081 # All aliases and commands are completely defined, now.
1080 1082 # Check abbreviation/ambiguity of shell alias.
1081 1083 shellaliasfn = _checkshellalias(lui, ui, args)
1082 1084 if shellaliasfn:
1083 1085 # no additional configs will be set, set up the ui instances
1084 1086 for ui_ in uis:
1085 1087 extensions.populateui(ui_)
1086 1088 return shellaliasfn()
1087 1089
1088 1090 # check for fallback encoding
1089 1091 fallback = lui.config(b'ui', b'fallbackencoding')
1090 1092 if fallback:
1091 1093 encoding.fallbackencoding = fallback
1092 1094
1093 1095 fullargs = args
1094 1096 cmd, func, args, options, cmdoptions = _parse(lui, args)
1095 1097
1096 1098 # store the canonical command name in request object for later access
1097 1099 req.canonical_command = cmd
1098 1100
1099 1101 if options[b"config"] != req.earlyoptions[b"config"]:
1100 1102 raise error.Abort(_(b"option --config may not be abbreviated!"))
1101 1103 if options[b"cwd"] != req.earlyoptions[b"cwd"]:
1102 1104 raise error.Abort(_(b"option --cwd may not be abbreviated!"))
1103 1105 if options[b"repository"] != req.earlyoptions[b"repository"]:
1104 1106 raise error.Abort(
1105 1107 _(
1106 1108 b"option -R has to be separated from other options (e.g. not "
1107 1109 b"-qR) and --repository may only be abbreviated as --repo!"
1108 1110 )
1109 1111 )
1110 1112 if options[b"debugger"] != req.earlyoptions[b"debugger"]:
1111 1113 raise error.Abort(_(b"option --debugger may not be abbreviated!"))
1112 1114 # don't validate --profile/--traceback, which can be enabled from now
1113 1115
1114 1116 if options[b"encoding"]:
1115 1117 encoding.encoding = options[b"encoding"]
1116 1118 if options[b"encodingmode"]:
1117 1119 encoding.encodingmode = options[b"encodingmode"]
1118 1120 if options[b"time"]:
1119 1121
1120 1122 def get_times():
1121 1123 t = os.times()
1122 1124 if t[4] == 0.0:
1123 1125 # Windows leaves this as zero, so use time.perf_counter()
1124 1126 t = (t[0], t[1], t[2], t[3], util.timer())
1125 1127 return t
1126 1128
1127 1129 s = get_times()
1128 1130
1129 1131 def print_time():
1130 1132 t = get_times()
1131 1133 ui.warn(
1132 1134 _(b"time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n")
1133 1135 % (
1134 1136 t[4] - s[4],
1135 1137 t[0] - s[0],
1136 1138 t[2] - s[2],
1137 1139 t[1] - s[1],
1138 1140 t[3] - s[3],
1139 1141 )
1140 1142 )
1141 1143
1142 1144 ui.atexit(print_time)
1143 1145 if options[b"profile"]:
1144 1146 profiler.start()
1145 1147
1146 1148 # if abbreviated version of this were used, take them in account, now
1147 1149 if options[b'verbose'] or options[b'debug'] or options[b'quiet']:
1148 1150 for opt in (b'verbose', b'debug', b'quiet'):
1149 1151 if options[opt] == req.earlyoptions[opt]:
1150 1152 continue
1151 1153 val = pycompat.bytestr(bool(options[opt]))
1152 1154 for ui_ in uis:
1153 1155 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1154 1156
1155 1157 if options[b'traceback']:
1156 1158 for ui_ in uis:
1157 1159 ui_.setconfig(b'ui', b'traceback', b'on', b'--traceback')
1158 1160
1159 1161 if options[b'noninteractive']:
1160 1162 for ui_ in uis:
1161 1163 ui_.setconfig(b'ui', b'interactive', b'off', b'-y')
1162 1164
1163 1165 if cmdoptions.get(b'insecure', False):
1164 1166 for ui_ in uis:
1165 1167 ui_.insecureconnections = True
1166 1168
1167 1169 # setup color handling before pager, because setting up pager
1168 1170 # might cause incorrect console information
1169 1171 coloropt = options[b'color']
1170 1172 for ui_ in uis:
1171 1173 if coloropt:
1172 1174 ui_.setconfig(b'ui', b'color', coloropt, b'--color')
1173 1175 color.setup(ui_)
1174 1176
1175 1177 if stringutil.parsebool(options[b'pager']):
1176 1178 # ui.pager() expects 'internal-always-' prefix in this case
1177 1179 ui.pager(b'internal-always-' + cmd)
1178 1180 elif options[b'pager'] != b'auto':
1179 1181 for ui_ in uis:
1180 1182 ui_.disablepager()
1181 1183
1182 1184 # configs are fully loaded, set up the ui instances
1183 1185 for ui_ in uis:
1184 1186 extensions.populateui(ui_)
1185 1187
1186 1188 if options[b'version']:
1187 1189 return commands.version_(ui)
1188 1190 if options[b'help']:
1189 1191 return commands.help_(ui, cmd, command=cmd is not None)
1190 1192 elif not cmd:
1191 1193 return commands.help_(ui, b'shortlist')
1192 1194
1193 1195 repo = None
1194 1196 cmdpats = args[:]
1195 1197 assert func is not None # help out pytype
1196 1198 if not func.norepo:
1197 1199 # use the repo from the request only if we don't have -R
1198 1200 if not rpath and not cwd:
1199 1201 repo = req.repo
1200 1202
1201 1203 if repo:
1202 1204 # set the descriptors of the repo ui to those of ui
1203 1205 repo.ui.fin = ui.fin
1204 1206 repo.ui.fout = ui.fout
1205 1207 repo.ui.ferr = ui.ferr
1206 1208 repo.ui.fmsg = ui.fmsg
1207 1209 else:
1208 1210 try:
1209 1211 repo = hg.repository(
1210 1212 ui,
1211 1213 path=path,
1212 1214 presetupfuncs=req.prereposetups,
1213 1215 intents=func.intents,
1214 1216 )
1215 1217 if not repo.local():
1216 1218 raise error.Abort(
1217 1219 _(b"repository '%s' is not local") % path
1218 1220 )
1219 1221 repo.ui.setconfig(
1220 1222 b"bundle", b"mainreporoot", repo.root, b'repo'
1221 1223 )
1222 1224 except error.RequirementError:
1223 1225 raise
1224 1226 except error.RepoError:
1225 1227 if rpath: # invalid -R path
1226 1228 raise
1227 1229 if not func.optionalrepo:
1228 1230 if func.inferrepo and args and not path:
1229 1231 # try to infer -R from command args
1230 1232 repos = pycompat.maplist(cmdutil.findrepo, args)
1231 1233 guess = repos[0]
1232 1234 if guess and repos.count(guess) == len(repos):
1233 1235 req.args = [b'--repository', guess] + fullargs
1234 1236 req.earlyoptions[b'repository'] = guess
1235 1237 return _dispatch(req)
1236 1238 if not path:
1237 1239 raise error.RepoError(
1238 1240 _(
1239 1241 b"no repository found in"
1240 1242 b" '%s' (.hg not found)"
1241 1243 )
1242 1244 % encoding.getcwd()
1243 1245 )
1244 1246 raise
1245 1247 if repo:
1246 1248 ui = repo.ui
1247 1249 if options[b'hidden']:
1248 1250 repo = repo.unfiltered()
1249 1251 args.insert(0, repo)
1250 1252 elif rpath:
1251 1253 ui.warn(_(b"warning: --repository ignored\n"))
1252 1254
1253 1255 msg = _formatargs(fullargs)
1254 1256 ui.log(b"command", b'%s\n', msg)
1255 1257 strcmdopt = pycompat.strkwargs(cmdoptions)
1256 1258 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1257 1259 try:
1258 1260 return runcommand(
1259 1261 lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
1260 1262 )
1261 1263 finally:
1262 1264 if repo and repo != req.repo:
1263 1265 repo.close()
1264 1266
1265 1267
1266 1268 def _runcommand(ui, options, cmd, cmdfunc):
1267 1269 """Run a command function, possibly with profiling enabled."""
1268 1270 try:
1269 1271 with tracing.log("Running %s command" % cmd):
1270 1272 return cmdfunc()
1271 1273 except error.SignatureError:
1272 1274 raise error.CommandError(cmd, _(b'invalid arguments'))
1273 1275
1274 1276
1275 1277 def _exceptionwarning(ui):
1276 1278 """Produce a warning message for the current active exception"""
1277 1279
1278 1280 # For compatibility checking, we discard the portion of the hg
1279 1281 # version after the + on the assumption that if a "normal
1280 1282 # user" is running a build with a + in it the packager
1281 1283 # probably built from fairly close to a tag and anyone with a
1282 1284 # 'make local' copy of hg (where the version number can be out
1283 1285 # of date) will be clueful enough to notice the implausible
1284 1286 # version number and try updating.
1285 1287 ct = util.versiontuple(n=2)
1286 1288 worst = None, ct, b''
1287 1289 if ui.config(b'ui', b'supportcontact') is None:
1288 1290 for name, mod in extensions.extensions():
1289 1291 # 'testedwith' should be bytes, but not all extensions are ported
1290 1292 # to py3 and we don't want UnicodeException because of that.
1291 1293 testedwith = stringutil.forcebytestr(
1292 1294 getattr(mod, 'testedwith', b'')
1293 1295 )
1294 1296 report = getattr(mod, 'buglink', _(b'the extension author.'))
1295 1297 if not testedwith.strip():
1296 1298 # We found an untested extension. It's likely the culprit.
1297 1299 worst = name, b'unknown', report
1298 1300 break
1299 1301
1300 1302 # Never blame on extensions bundled with Mercurial.
1301 1303 if extensions.ismoduleinternal(mod):
1302 1304 continue
1303 1305
1304 1306 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1305 1307 if ct in tested:
1306 1308 continue
1307 1309
1308 1310 lower = [t for t in tested if t < ct]
1309 1311 nearest = max(lower or tested)
1310 1312 if worst[0] is None or nearest < worst[1]:
1311 1313 worst = name, nearest, report
1312 1314 if worst[0] is not None:
1313 1315 name, testedwith, report = worst
1314 1316 if not isinstance(testedwith, (bytes, str)):
1315 1317 testedwith = b'.'.join(
1316 1318 [stringutil.forcebytestr(c) for c in testedwith]
1317 1319 )
1318 1320 warning = _(
1319 1321 b'** Unknown exception encountered with '
1320 1322 b'possibly-broken third-party extension %s\n'
1321 1323 b'** which supports versions %s of Mercurial.\n'
1322 1324 b'** Please disable %s and try your action again.\n'
1323 1325 b'** If that fixes the bug please report it to %s\n'
1324 1326 ) % (name, testedwith, name, stringutil.forcebytestr(report))
1325 1327 else:
1326 1328 bugtracker = ui.config(b'ui', b'supportcontact')
1327 1329 if bugtracker is None:
1328 1330 bugtracker = _(b"https://mercurial-scm.org/wiki/BugTracker")
1329 1331 warning = (
1330 1332 _(
1331 1333 b"** unknown exception encountered, "
1332 1334 b"please report by visiting\n** "
1333 1335 )
1334 1336 + bugtracker
1335 1337 + b'\n'
1336 1338 )
1337 1339 sysversion = pycompat.sysbytes(sys.version).replace(b'\n', b'')
1338 1340 warning += (
1339 1341 (_(b"** Python %s\n") % sysversion)
1340 1342 + (_(b"** Mercurial Distributed SCM (version %s)\n") % util.version())
1341 1343 + (
1342 1344 _(b"** Extensions loaded: %s\n")
1343 1345 % b", ".join([x[0] for x in extensions.extensions()])
1344 1346 )
1345 1347 )
1346 1348 return warning
1347 1349
1348 1350
1349 1351 def handlecommandexception(ui):
1350 1352 """Produce a warning message for broken commands
1351 1353
1352 1354 Called when handling an exception; the exception is reraised if
1353 1355 this function returns False, ignored otherwise.
1354 1356 """
1355 1357 warning = _exceptionwarning(ui)
1356 1358 ui.log(
1357 1359 b"commandexception",
1358 1360 b"%s\n%s\n",
1359 1361 warning,
1360 1362 pycompat.sysbytes(traceback.format_exc()),
1361 1363 )
1362 1364 ui.warn(warning)
1363 1365 return False # re-raise the exception
@@ -1,3552 +1,3562 b''
1 1 # localrepo.py - read/write repository class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import functools
12 12 import os
13 13 import random
14 14 import sys
15 15 import time
16 16 import weakref
17 17
18 18 from .i18n import _
19 19 from .node import (
20 20 bin,
21 21 hex,
22 22 nullid,
23 23 nullrev,
24 24 short,
25 25 )
26 26 from .pycompat import (
27 27 delattr,
28 28 getattr,
29 29 )
30 30 from . import (
31 31 bookmarks,
32 32 branchmap,
33 33 bundle2,
34 34 changegroup,
35 35 color,
36 36 commit,
37 37 context,
38 38 dirstate,
39 39 dirstateguard,
40 40 discovery,
41 41 encoding,
42 42 error,
43 43 exchange,
44 44 extensions,
45 45 filelog,
46 46 hook,
47 47 lock as lockmod,
48 48 match as matchmod,
49 49 mergestate as mergestatemod,
50 50 mergeutil,
51 51 namespaces,
52 52 narrowspec,
53 53 obsolete,
54 54 pathutil,
55 55 phases,
56 56 pushkey,
57 57 pycompat,
58 58 rcutil,
59 59 repoview,
60 60 requirements as requirementsmod,
61 61 revset,
62 62 revsetlang,
63 63 scmutil,
64 64 sparse,
65 65 store as storemod,
66 66 subrepoutil,
67 67 tags as tagsmod,
68 68 transaction,
69 69 txnutil,
70 70 util,
71 71 vfs as vfsmod,
72 72 )
73 73
74 74 from .interfaces import (
75 75 repository,
76 76 util as interfaceutil,
77 77 )
78 78
79 79 from .utils import (
80 80 hashutil,
81 81 procutil,
82 82 stringutil,
83 83 )
84 84
85 85 from .revlogutils import constants as revlogconst
86 86
87 87 release = lockmod.release
88 88 urlerr = util.urlerr
89 89 urlreq = util.urlreq
90 90
91 91 # set of (path, vfs-location) tuples. vfs-location is:
92 92 # - 'plain for vfs relative paths
93 93 # - '' for svfs relative paths
94 94 _cachedfiles = set()
95 95
96 96
97 97 class _basefilecache(scmutil.filecache):
98 98 """All filecache usage on repo are done for logic that should be unfiltered
99 99 """
100 100
101 101 def __get__(self, repo, type=None):
102 102 if repo is None:
103 103 return self
104 104 # proxy to unfiltered __dict__ since filtered repo has no entry
105 105 unfi = repo.unfiltered()
106 106 try:
107 107 return unfi.__dict__[self.sname]
108 108 except KeyError:
109 109 pass
110 110 return super(_basefilecache, self).__get__(unfi, type)
111 111
112 112 def set(self, repo, value):
113 113 return super(_basefilecache, self).set(repo.unfiltered(), value)
114 114
115 115
116 116 class repofilecache(_basefilecache):
117 117 """filecache for files in .hg but outside of .hg/store"""
118 118
119 119 def __init__(self, *paths):
120 120 super(repofilecache, self).__init__(*paths)
121 121 for path in paths:
122 122 _cachedfiles.add((path, b'plain'))
123 123
124 124 def join(self, obj, fname):
125 125 return obj.vfs.join(fname)
126 126
127 127
128 128 class storecache(_basefilecache):
129 129 """filecache for files in the store"""
130 130
131 131 def __init__(self, *paths):
132 132 super(storecache, self).__init__(*paths)
133 133 for path in paths:
134 134 _cachedfiles.add((path, b''))
135 135
136 136 def join(self, obj, fname):
137 137 return obj.sjoin(fname)
138 138
139 139
140 140 class mixedrepostorecache(_basefilecache):
141 141 """filecache for a mix files in .hg/store and outside"""
142 142
143 143 def __init__(self, *pathsandlocations):
144 144 # scmutil.filecache only uses the path for passing back into our
145 145 # join(), so we can safely pass a list of paths and locations
146 146 super(mixedrepostorecache, self).__init__(*pathsandlocations)
147 147 _cachedfiles.update(pathsandlocations)
148 148
149 149 def join(self, obj, fnameandlocation):
150 150 fname, location = fnameandlocation
151 151 if location == b'plain':
152 152 return obj.vfs.join(fname)
153 153 else:
154 154 if location != b'':
155 155 raise error.ProgrammingError(
156 156 b'unexpected location: %s' % location
157 157 )
158 158 return obj.sjoin(fname)
159 159
160 160
161 161 def isfilecached(repo, name):
162 162 """check if a repo has already cached "name" filecache-ed property
163 163
164 164 This returns (cachedobj-or-None, iscached) tuple.
165 165 """
166 166 cacheentry = repo.unfiltered()._filecache.get(name, None)
167 167 if not cacheentry:
168 168 return None, False
169 169 return cacheentry.obj, True
170 170
171 171
172 172 class unfilteredpropertycache(util.propertycache):
173 173 """propertycache that apply to unfiltered repo only"""
174 174
175 175 def __get__(self, repo, type=None):
176 176 unfi = repo.unfiltered()
177 177 if unfi is repo:
178 178 return super(unfilteredpropertycache, self).__get__(unfi)
179 179 return getattr(unfi, self.name)
180 180
181 181
182 182 class filteredpropertycache(util.propertycache):
183 183 """propertycache that must take filtering in account"""
184 184
185 185 def cachevalue(self, obj, value):
186 186 object.__setattr__(obj, self.name, value)
187 187
188 188
189 189 def hasunfilteredcache(repo, name):
190 190 """check if a repo has an unfilteredpropertycache value for <name>"""
191 191 return name in vars(repo.unfiltered())
192 192
193 193
194 194 def unfilteredmethod(orig):
195 195 """decorate method that always need to be run on unfiltered version"""
196 196
197 197 @functools.wraps(orig)
198 198 def wrapper(repo, *args, **kwargs):
199 199 return orig(repo.unfiltered(), *args, **kwargs)
200 200
201 201 return wrapper
202 202
203 203
204 204 moderncaps = {
205 205 b'lookup',
206 206 b'branchmap',
207 207 b'pushkey',
208 208 b'known',
209 209 b'getbundle',
210 210 b'unbundle',
211 211 }
212 212 legacycaps = moderncaps.union({b'changegroupsubset'})
213 213
214 214
215 215 @interfaceutil.implementer(repository.ipeercommandexecutor)
216 216 class localcommandexecutor(object):
217 217 def __init__(self, peer):
218 218 self._peer = peer
219 219 self._sent = False
220 220 self._closed = False
221 221
222 222 def __enter__(self):
223 223 return self
224 224
225 225 def __exit__(self, exctype, excvalue, exctb):
226 226 self.close()
227 227
228 228 def callcommand(self, command, args):
229 229 if self._sent:
230 230 raise error.ProgrammingError(
231 231 b'callcommand() cannot be used after sendcommands()'
232 232 )
233 233
234 234 if self._closed:
235 235 raise error.ProgrammingError(
236 236 b'callcommand() cannot be used after close()'
237 237 )
238 238
239 239 # We don't need to support anything fancy. Just call the named
240 240 # method on the peer and return a resolved future.
241 241 fn = getattr(self._peer, pycompat.sysstr(command))
242 242
243 243 f = pycompat.futures.Future()
244 244
245 245 try:
246 246 result = fn(**pycompat.strkwargs(args))
247 247 except Exception:
248 248 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
249 249 else:
250 250 f.set_result(result)
251 251
252 252 return f
253 253
254 254 def sendcommands(self):
255 255 self._sent = True
256 256
257 257 def close(self):
258 258 self._closed = True
259 259
260 260
261 261 @interfaceutil.implementer(repository.ipeercommands)
262 262 class localpeer(repository.peer):
263 263 '''peer for a local repo; reflects only the most recent API'''
264 264
265 265 def __init__(self, repo, caps=None):
266 266 super(localpeer, self).__init__()
267 267
268 268 if caps is None:
269 269 caps = moderncaps.copy()
270 270 self._repo = repo.filtered(b'served')
271 271 self.ui = repo.ui
272 272 self._caps = repo._restrictcapabilities(caps)
273 273
274 274 # Begin of _basepeer interface.
275 275
276 276 def url(self):
277 277 return self._repo.url()
278 278
279 279 def local(self):
280 280 return self._repo
281 281
282 282 def peer(self):
283 283 return self
284 284
285 285 def canpush(self):
286 286 return True
287 287
288 288 def close(self):
289 289 self._repo.close()
290 290
291 291 # End of _basepeer interface.
292 292
293 293 # Begin of _basewirecommands interface.
294 294
295 295 def branchmap(self):
296 296 return self._repo.branchmap()
297 297
298 298 def capabilities(self):
299 299 return self._caps
300 300
301 301 def clonebundles(self):
302 302 return self._repo.tryread(b'clonebundles.manifest')
303 303
304 304 def debugwireargs(self, one, two, three=None, four=None, five=None):
305 305 """Used to test argument passing over the wire"""
306 306 return b"%s %s %s %s %s" % (
307 307 one,
308 308 two,
309 309 pycompat.bytestr(three),
310 310 pycompat.bytestr(four),
311 311 pycompat.bytestr(five),
312 312 )
313 313
314 314 def getbundle(
315 315 self, source, heads=None, common=None, bundlecaps=None, **kwargs
316 316 ):
317 317 chunks = exchange.getbundlechunks(
318 318 self._repo,
319 319 source,
320 320 heads=heads,
321 321 common=common,
322 322 bundlecaps=bundlecaps,
323 323 **kwargs
324 324 )[1]
325 325 cb = util.chunkbuffer(chunks)
326 326
327 327 if exchange.bundle2requested(bundlecaps):
328 328 # When requesting a bundle2, getbundle returns a stream to make the
329 329 # wire level function happier. We need to build a proper object
330 330 # from it in local peer.
331 331 return bundle2.getunbundler(self.ui, cb)
332 332 else:
333 333 return changegroup.getunbundler(b'01', cb, None)
334 334
335 335 def heads(self):
336 336 return self._repo.heads()
337 337
338 338 def known(self, nodes):
339 339 return self._repo.known(nodes)
340 340
341 341 def listkeys(self, namespace):
342 342 return self._repo.listkeys(namespace)
343 343
344 344 def lookup(self, key):
345 345 return self._repo.lookup(key)
346 346
347 347 def pushkey(self, namespace, key, old, new):
348 348 return self._repo.pushkey(namespace, key, old, new)
349 349
350 350 def stream_out(self):
351 351 raise error.Abort(_(b'cannot perform stream clone against local peer'))
352 352
353 353 def unbundle(self, bundle, heads, url):
354 354 """apply a bundle on a repo
355 355
356 356 This function handles the repo locking itself."""
357 357 try:
358 358 try:
359 359 bundle = exchange.readbundle(self.ui, bundle, None)
360 360 ret = exchange.unbundle(self._repo, bundle, heads, b'push', url)
361 361 if util.safehasattr(ret, b'getchunks'):
362 362 # This is a bundle20 object, turn it into an unbundler.
363 363 # This little dance should be dropped eventually when the
364 364 # API is finally improved.
365 365 stream = util.chunkbuffer(ret.getchunks())
366 366 ret = bundle2.getunbundler(self.ui, stream)
367 367 return ret
368 368 except Exception as exc:
369 369 # If the exception contains output salvaged from a bundle2
370 370 # reply, we need to make sure it is printed before continuing
371 371 # to fail. So we build a bundle2 with such output and consume
372 372 # it directly.
373 373 #
374 374 # This is not very elegant but allows a "simple" solution for
375 375 # issue4594
376 376 output = getattr(exc, '_bundle2salvagedoutput', ())
377 377 if output:
378 378 bundler = bundle2.bundle20(self._repo.ui)
379 379 for out in output:
380 380 bundler.addpart(out)
381 381 stream = util.chunkbuffer(bundler.getchunks())
382 382 b = bundle2.getunbundler(self.ui, stream)
383 383 bundle2.processbundle(self._repo, b)
384 384 raise
385 385 except error.PushRaced as exc:
386 386 raise error.ResponseError(
387 387 _(b'push failed:'), stringutil.forcebytestr(exc)
388 388 )
389 389
390 390 # End of _basewirecommands interface.
391 391
392 392 # Begin of peer interface.
393 393
394 394 def commandexecutor(self):
395 395 return localcommandexecutor(self)
396 396
397 397 # End of peer interface.
398 398
399 399
400 400 @interfaceutil.implementer(repository.ipeerlegacycommands)
401 401 class locallegacypeer(localpeer):
402 402 '''peer extension which implements legacy methods too; used for tests with
403 403 restricted capabilities'''
404 404
405 405 def __init__(self, repo):
406 406 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
407 407
408 408 # Begin of baselegacywirecommands interface.
409 409
410 410 def between(self, pairs):
411 411 return self._repo.between(pairs)
412 412
413 413 def branches(self, nodes):
414 414 return self._repo.branches(nodes)
415 415
416 416 def changegroup(self, nodes, source):
417 417 outgoing = discovery.outgoing(
418 418 self._repo, missingroots=nodes, ancestorsof=self._repo.heads()
419 419 )
420 420 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
421 421
422 422 def changegroupsubset(self, bases, heads, source):
423 423 outgoing = discovery.outgoing(
424 424 self._repo, missingroots=bases, ancestorsof=heads
425 425 )
426 426 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
427 427
428 428 # End of baselegacywirecommands interface.
429 429
430 430
431 431 # Functions receiving (ui, features) that extensions can register to impact
432 432 # the ability to load repositories with custom requirements. Only
433 433 # functions defined in loaded extensions are called.
434 434 #
435 435 # The function receives a set of requirement strings that the repository
436 436 # is capable of opening. Functions will typically add elements to the
437 437 # set to reflect that the extension knows how to handle that requirements.
438 438 featuresetupfuncs = set()
439 439
440 440
441 441 def _getsharedvfs(hgvfs, requirements):
442 442 """ returns the vfs object pointing to root of shared source
443 443 repo for a shared repository
444 444
445 445 hgvfs is vfs pointing at .hg/ of current repo (shared one)
446 446 requirements is a set of requirements of current repo (shared one)
447 447 """
448 448 # The ``shared`` or ``relshared`` requirements indicate the
449 449 # store lives in the path contained in the ``.hg/sharedpath`` file.
450 450 # This is an absolute path for ``shared`` and relative to
451 451 # ``.hg/`` for ``relshared``.
452 452 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
453 453 if requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements:
454 454 sharedpath = hgvfs.join(sharedpath)
455 455
456 456 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
457 457
458 458 if not sharedvfs.exists():
459 459 raise error.RepoError(
460 460 _(b'.hg/sharedpath points to nonexistent directory %s')
461 461 % sharedvfs.base
462 462 )
463 463 return sharedvfs
464 464
465 465
466 466 def _readrequires(vfs, allowmissing):
467 467 """ reads the require file present at root of this vfs
468 468 and return a set of requirements
469 469
470 470 If allowmissing is True, we suppress ENOENT if raised"""
471 471 # requires file contains a newline-delimited list of
472 472 # features/capabilities the opener (us) must have in order to use
473 473 # the repository. This file was introduced in Mercurial 0.9.2,
474 474 # which means very old repositories may not have one. We assume
475 475 # a missing file translates to no requirements.
476 476 try:
477 477 requirements = set(vfs.read(b'requires').splitlines())
478 478 except IOError as e:
479 479 if not (allowmissing and e.errno == errno.ENOENT):
480 480 raise
481 481 requirements = set()
482 482 return requirements
483 483
484 484
485 485 def makelocalrepository(baseui, path, intents=None):
486 486 """Create a local repository object.
487 487
488 488 Given arguments needed to construct a local repository, this function
489 489 performs various early repository loading functionality (such as
490 490 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
491 491 the repository can be opened, derives a type suitable for representing
492 492 that repository, and returns an instance of it.
493 493
494 494 The returned object conforms to the ``repository.completelocalrepository``
495 495 interface.
496 496
497 497 The repository type is derived by calling a series of factory functions
498 498 for each aspect/interface of the final repository. These are defined by
499 499 ``REPO_INTERFACES``.
500 500
501 501 Each factory function is called to produce a type implementing a specific
502 502 interface. The cumulative list of returned types will be combined into a
503 503 new type and that type will be instantiated to represent the local
504 504 repository.
505 505
506 506 The factory functions each receive various state that may be consulted
507 507 as part of deriving a type.
508 508
509 509 Extensions should wrap these factory functions to customize repository type
510 510 creation. Note that an extension's wrapped function may be called even if
511 511 that extension is not loaded for the repo being constructed. Extensions
512 512 should check if their ``__name__`` appears in the
513 513 ``extensionmodulenames`` set passed to the factory function and no-op if
514 514 not.
515 515 """
516 516 ui = baseui.copy()
517 517 # Prevent copying repo configuration.
518 518 ui.copy = baseui.copy
519 519
520 520 # Working directory VFS rooted at repository root.
521 521 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
522 522
523 523 # Main VFS for .hg/ directory.
524 524 hgpath = wdirvfs.join(b'.hg')
525 525 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
526 526 # Whether this repository is shared one or not
527 527 shared = False
528 528 # If this repository is shared, vfs pointing to shared repo
529 529 sharedvfs = None
530 530
531 531 # The .hg/ path should exist and should be a directory. All other
532 532 # cases are errors.
533 533 if not hgvfs.isdir():
534 534 try:
535 535 hgvfs.stat()
536 536 except OSError as e:
537 537 if e.errno != errno.ENOENT:
538 538 raise
539 539 except ValueError as e:
540 540 # Can be raised on Python 3.8 when path is invalid.
541 541 raise error.Abort(
542 542 _(b'invalid path %s: %s') % (path, pycompat.bytestr(e))
543 543 )
544 544
545 545 raise error.RepoError(_(b'repository %s not found') % path)
546 546
547 547 requirements = _readrequires(hgvfs, True)
548 548 shared = (
549 549 requirementsmod.SHARED_REQUIREMENT in requirements
550 550 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
551 551 )
552 552 if shared:
553 553 sharedvfs = _getsharedvfs(hgvfs, requirements)
554 554
555 555 # if .hg/requires contains the sharesafe requirement, it means
556 556 # there exists a `.hg/store/requires` too and we should read it
557 557 # NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
558 558 # is present. We never write SHARESAFE_REQUIREMENT for a repo if store
559 559 # is not present, refer checkrequirementscompat() for that
560 560 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
561 561 if shared:
562 562 # This is a shared repo
563 563 storevfs = vfsmod.vfs(sharedvfs.join(b'store'))
564 564 else:
565 565 storevfs = vfsmod.vfs(hgvfs.join(b'store'))
566 566
567 567 requirements |= _readrequires(storevfs, False)
568 568
569 569 # The .hg/hgrc file may load extensions or contain config options
570 570 # that influence repository construction. Attempt to load it and
571 571 # process any new extensions that it may have pulled in.
572 572 if loadhgrc(ui, wdirvfs, hgvfs, requirements, sharedvfs):
573 573 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
574 574 extensions.loadall(ui)
575 575 extensions.populateui(ui)
576 576
577 577 # Set of module names of extensions loaded for this repository.
578 578 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
579 579
580 580 supportedrequirements = gathersupportedrequirements(ui)
581 581
582 582 # We first validate the requirements are known.
583 583 ensurerequirementsrecognized(requirements, supportedrequirements)
584 584
585 585 # Then we validate that the known set is reasonable to use together.
586 586 ensurerequirementscompatible(ui, requirements)
587 587
588 588 # TODO there are unhandled edge cases related to opening repositories with
589 589 # shared storage. If storage is shared, we should also test for requirements
590 590 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
591 591 # that repo, as that repo may load extensions needed to open it. This is a
592 592 # bit complicated because we don't want the other hgrc to overwrite settings
593 593 # in this hgrc.
594 594 #
595 595 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
596 596 # file when sharing repos. But if a requirement is added after the share is
597 597 # performed, thereby introducing a new requirement for the opener, we may
598 598 # will not see that and could encounter a run-time error interacting with
599 599 # that shared store since it has an unknown-to-us requirement.
600 600
601 601 # At this point, we know we should be capable of opening the repository.
602 602 # Now get on with doing that.
603 603
604 604 features = set()
605 605
606 606 # The "store" part of the repository holds versioned data. How it is
607 607 # accessed is determined by various requirements. If `shared` or
608 608 # `relshared` requirements are present, this indicates current repository
609 609 # is a share and store exists in path mentioned in `.hg/sharedpath`
610 610 if shared:
611 611 storebasepath = sharedvfs.base
612 612 cachepath = sharedvfs.join(b'cache')
613 613 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
614 614 else:
615 615 storebasepath = hgvfs.base
616 616 cachepath = hgvfs.join(b'cache')
617 617 wcachepath = hgvfs.join(b'wcache')
618 618
619 619 # The store has changed over time and the exact layout is dictated by
620 620 # requirements. The store interface abstracts differences across all
621 621 # of them.
622 622 store = makestore(
623 623 requirements,
624 624 storebasepath,
625 625 lambda base: vfsmod.vfs(base, cacheaudited=True),
626 626 )
627 627 hgvfs.createmode = store.createmode
628 628
629 629 storevfs = store.vfs
630 630 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
631 631
632 632 # The cache vfs is used to manage cache files.
633 633 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
634 634 cachevfs.createmode = store.createmode
635 635 # The cache vfs is used to manage cache files related to the working copy
636 636 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
637 637 wcachevfs.createmode = store.createmode
638 638
639 639 # Now resolve the type for the repository object. We do this by repeatedly
640 640 # calling a factory function to produces types for specific aspects of the
641 641 # repo's operation. The aggregate returned types are used as base classes
642 642 # for a dynamically-derived type, which will represent our new repository.
643 643
644 644 bases = []
645 645 extrastate = {}
646 646
647 647 for iface, fn in REPO_INTERFACES:
648 648 # We pass all potentially useful state to give extensions tons of
649 649 # flexibility.
650 650 typ = fn()(
651 651 ui=ui,
652 652 intents=intents,
653 653 requirements=requirements,
654 654 features=features,
655 655 wdirvfs=wdirvfs,
656 656 hgvfs=hgvfs,
657 657 store=store,
658 658 storevfs=storevfs,
659 659 storeoptions=storevfs.options,
660 660 cachevfs=cachevfs,
661 661 wcachevfs=wcachevfs,
662 662 extensionmodulenames=extensionmodulenames,
663 663 extrastate=extrastate,
664 664 baseclasses=bases,
665 665 )
666 666
667 667 if not isinstance(typ, type):
668 668 raise error.ProgrammingError(
669 669 b'unable to construct type for %s' % iface
670 670 )
671 671
672 672 bases.append(typ)
673 673
674 674 # type() allows you to use characters in type names that wouldn't be
675 675 # recognized as Python symbols in source code. We abuse that to add
676 676 # rich information about our constructed repo.
677 677 name = pycompat.sysstr(
678 678 b'derivedrepo:%s<%s>' % (wdirvfs.base, b','.join(sorted(requirements)))
679 679 )
680 680
681 681 cls = type(name, tuple(bases), {})
682 682
683 683 return cls(
684 684 baseui=baseui,
685 685 ui=ui,
686 686 origroot=path,
687 687 wdirvfs=wdirvfs,
688 688 hgvfs=hgvfs,
689 689 requirements=requirements,
690 690 supportedrequirements=supportedrequirements,
691 691 sharedpath=storebasepath,
692 692 store=store,
693 693 cachevfs=cachevfs,
694 694 wcachevfs=wcachevfs,
695 695 features=features,
696 696 intents=intents,
697 697 )
698 698
699 699
700 700 def loadhgrc(ui, wdirvfs, hgvfs, requirements, sharedvfs=None):
701 701 """Load hgrc files/content into a ui instance.
702 702
703 703 This is called during repository opening to load any additional
704 704 config files or settings relevant to the current repository.
705 705
706 706 Returns a bool indicating whether any additional configs were loaded.
707 707
708 708 Extensions should monkeypatch this function to modify how per-repo
709 709 configs are loaded. For example, an extension may wish to pull in
710 710 configs from alternate files or sources.
711 711
712 712 sharedvfs is vfs object pointing to source repo if the current one is a
713 713 shared one
714 714 """
715 715 if not rcutil.use_repo_hgrc():
716 716 return False
717 717
718 ret = False
718 719 # first load config from shared source if we has to
719 720 if requirementsmod.SHARESAFE_REQUIREMENT in requirements and sharedvfs:
720 721 try:
721 722 ui.readconfig(sharedvfs.join(b'hgrc'), root=sharedvfs.base)
723 ret = True
722 724 except IOError:
723 725 pass
724 726
725 727 try:
726 728 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
727 return True
729 ret = True
728 730 except IOError:
729 return False
731 pass
732
733 try:
734 ui.readconfig(hgvfs.join(b'hgrc-not-shared'), root=wdirvfs.base)
735 ret = True
736 except IOError:
737 pass
738
739 return ret
730 740
731 741
732 742 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
733 743 """Perform additional actions after .hg/hgrc is loaded.
734 744
735 745 This function is called during repository loading immediately after
736 746 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
737 747
738 748 The function can be used to validate configs, automatically add
739 749 options (including extensions) based on requirements, etc.
740 750 """
741 751
742 752 # Map of requirements to list of extensions to load automatically when
743 753 # requirement is present.
744 754 autoextensions = {
745 755 b'git': [b'git'],
746 756 b'largefiles': [b'largefiles'],
747 757 b'lfs': [b'lfs'],
748 758 }
749 759
750 760 for requirement, names in sorted(autoextensions.items()):
751 761 if requirement not in requirements:
752 762 continue
753 763
754 764 for name in names:
755 765 if not ui.hasconfig(b'extensions', name):
756 766 ui.setconfig(b'extensions', name, b'', source=b'autoload')
757 767
758 768
759 769 def gathersupportedrequirements(ui):
760 770 """Determine the complete set of recognized requirements."""
761 771 # Start with all requirements supported by this file.
762 772 supported = set(localrepository._basesupported)
763 773
764 774 # Execute ``featuresetupfuncs`` entries if they belong to an extension
765 775 # relevant to this ui instance.
766 776 modules = {m.__name__ for n, m in extensions.extensions(ui)}
767 777
768 778 for fn in featuresetupfuncs:
769 779 if fn.__module__ in modules:
770 780 fn(ui, supported)
771 781
772 782 # Add derived requirements from registered compression engines.
773 783 for name in util.compengines:
774 784 engine = util.compengines[name]
775 785 if engine.available() and engine.revlogheader():
776 786 supported.add(b'exp-compression-%s' % name)
777 787 if engine.name() == b'zstd':
778 788 supported.add(b'revlog-compression-zstd')
779 789
780 790 return supported
781 791
782 792
783 793 def ensurerequirementsrecognized(requirements, supported):
784 794 """Validate that a set of local requirements is recognized.
785 795
786 796 Receives a set of requirements. Raises an ``error.RepoError`` if there
787 797 exists any requirement in that set that currently loaded code doesn't
788 798 recognize.
789 799
790 800 Returns a set of supported requirements.
791 801 """
792 802 missing = set()
793 803
794 804 for requirement in requirements:
795 805 if requirement in supported:
796 806 continue
797 807
798 808 if not requirement or not requirement[0:1].isalnum():
799 809 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
800 810
801 811 missing.add(requirement)
802 812
803 813 if missing:
804 814 raise error.RequirementError(
805 815 _(b'repository requires features unknown to this Mercurial: %s')
806 816 % b' '.join(sorted(missing)),
807 817 hint=_(
808 818 b'see https://mercurial-scm.org/wiki/MissingRequirement '
809 819 b'for more information'
810 820 ),
811 821 )
812 822
813 823
814 824 def ensurerequirementscompatible(ui, requirements):
815 825 """Validates that a set of recognized requirements is mutually compatible.
816 826
817 827 Some requirements may not be compatible with others or require
818 828 config options that aren't enabled. This function is called during
819 829 repository opening to ensure that the set of requirements needed
820 830 to open a repository is sane and compatible with config options.
821 831
822 832 Extensions can monkeypatch this function to perform additional
823 833 checking.
824 834
825 835 ``error.RepoError`` should be raised on failure.
826 836 """
827 837 if (
828 838 requirementsmod.SPARSE_REQUIREMENT in requirements
829 839 and not sparse.enabled
830 840 ):
831 841 raise error.RepoError(
832 842 _(
833 843 b'repository is using sparse feature but '
834 844 b'sparse is not enabled; enable the '
835 845 b'"sparse" extensions to access'
836 846 )
837 847 )
838 848
839 849
840 850 def makestore(requirements, path, vfstype):
841 851 """Construct a storage object for a repository."""
842 852 if b'store' in requirements:
843 853 if b'fncache' in requirements:
844 854 return storemod.fncachestore(
845 855 path, vfstype, b'dotencode' in requirements
846 856 )
847 857
848 858 return storemod.encodedstore(path, vfstype)
849 859
850 860 return storemod.basicstore(path, vfstype)
851 861
852 862
853 863 def resolvestorevfsoptions(ui, requirements, features):
854 864 """Resolve the options to pass to the store vfs opener.
855 865
856 866 The returned dict is used to influence behavior of the storage layer.
857 867 """
858 868 options = {}
859 869
860 870 if requirementsmod.TREEMANIFEST_REQUIREMENT in requirements:
861 871 options[b'treemanifest'] = True
862 872
863 873 # experimental config: format.manifestcachesize
864 874 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
865 875 if manifestcachesize is not None:
866 876 options[b'manifestcachesize'] = manifestcachesize
867 877
868 878 # In the absence of another requirement superseding a revlog-related
869 879 # requirement, we have to assume the repo is using revlog version 0.
870 880 # This revlog format is super old and we don't bother trying to parse
871 881 # opener options for it because those options wouldn't do anything
872 882 # meaningful on such old repos.
873 883 if (
874 884 b'revlogv1' in requirements
875 885 or requirementsmod.REVLOGV2_REQUIREMENT in requirements
876 886 ):
877 887 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
878 888 else: # explicitly mark repo as using revlogv0
879 889 options[b'revlogv0'] = True
880 890
881 891 if requirementsmod.COPIESSDC_REQUIREMENT in requirements:
882 892 options[b'copies-storage'] = b'changeset-sidedata'
883 893 else:
884 894 writecopiesto = ui.config(b'experimental', b'copies.write-to')
885 895 copiesextramode = (b'changeset-only', b'compatibility')
886 896 if writecopiesto in copiesextramode:
887 897 options[b'copies-storage'] = b'extra'
888 898
889 899 return options
890 900
891 901
892 902 def resolverevlogstorevfsoptions(ui, requirements, features):
893 903 """Resolve opener options specific to revlogs."""
894 904
895 905 options = {}
896 906 options[b'flagprocessors'] = {}
897 907
898 908 if b'revlogv1' in requirements:
899 909 options[b'revlogv1'] = True
900 910 if requirementsmod.REVLOGV2_REQUIREMENT in requirements:
901 911 options[b'revlogv2'] = True
902 912
903 913 if b'generaldelta' in requirements:
904 914 options[b'generaldelta'] = True
905 915
906 916 # experimental config: format.chunkcachesize
907 917 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
908 918 if chunkcachesize is not None:
909 919 options[b'chunkcachesize'] = chunkcachesize
910 920
911 921 deltabothparents = ui.configbool(
912 922 b'storage', b'revlog.optimize-delta-parent-choice'
913 923 )
914 924 options[b'deltabothparents'] = deltabothparents
915 925
916 926 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
917 927 lazydeltabase = False
918 928 if lazydelta:
919 929 lazydeltabase = ui.configbool(
920 930 b'storage', b'revlog.reuse-external-delta-parent'
921 931 )
922 932 if lazydeltabase is None:
923 933 lazydeltabase = not scmutil.gddeltaconfig(ui)
924 934 options[b'lazydelta'] = lazydelta
925 935 options[b'lazydeltabase'] = lazydeltabase
926 936
927 937 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
928 938 if 0 <= chainspan:
929 939 options[b'maxdeltachainspan'] = chainspan
930 940
931 941 mmapindexthreshold = ui.configbytes(b'experimental', b'mmapindexthreshold')
932 942 if mmapindexthreshold is not None:
933 943 options[b'mmapindexthreshold'] = mmapindexthreshold
934 944
935 945 withsparseread = ui.configbool(b'experimental', b'sparse-read')
936 946 srdensitythres = float(
937 947 ui.config(b'experimental', b'sparse-read.density-threshold')
938 948 )
939 949 srmingapsize = ui.configbytes(b'experimental', b'sparse-read.min-gap-size')
940 950 options[b'with-sparse-read'] = withsparseread
941 951 options[b'sparse-read-density-threshold'] = srdensitythres
942 952 options[b'sparse-read-min-gap-size'] = srmingapsize
943 953
944 954 sparserevlog = requirementsmod.SPARSEREVLOG_REQUIREMENT in requirements
945 955 options[b'sparse-revlog'] = sparserevlog
946 956 if sparserevlog:
947 957 options[b'generaldelta'] = True
948 958
949 959 sidedata = requirementsmod.SIDEDATA_REQUIREMENT in requirements
950 960 options[b'side-data'] = sidedata
951 961
952 962 maxchainlen = None
953 963 if sparserevlog:
954 964 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
955 965 # experimental config: format.maxchainlen
956 966 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
957 967 if maxchainlen is not None:
958 968 options[b'maxchainlen'] = maxchainlen
959 969
960 970 for r in requirements:
961 971 # we allow multiple compression engine requirement to co-exist because
962 972 # strickly speaking, revlog seems to support mixed compression style.
963 973 #
964 974 # The compression used for new entries will be "the last one"
965 975 prefix = r.startswith
966 976 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
967 977 options[b'compengine'] = r.split(b'-', 2)[2]
968 978
969 979 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
970 980 if options[b'zlib.level'] is not None:
971 981 if not (0 <= options[b'zlib.level'] <= 9):
972 982 msg = _(b'invalid value for `storage.revlog.zlib.level` config: %d')
973 983 raise error.Abort(msg % options[b'zlib.level'])
974 984 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
975 985 if options[b'zstd.level'] is not None:
976 986 if not (0 <= options[b'zstd.level'] <= 22):
977 987 msg = _(b'invalid value for `storage.revlog.zstd.level` config: %d')
978 988 raise error.Abort(msg % options[b'zstd.level'])
979 989
980 990 if requirementsmod.NARROW_REQUIREMENT in requirements:
981 991 options[b'enableellipsis'] = True
982 992
983 993 if ui.configbool(b'experimental', b'rust.index'):
984 994 options[b'rust.index'] = True
985 995 if requirementsmod.NODEMAP_REQUIREMENT in requirements:
986 996 options[b'persistent-nodemap'] = True
987 997 if ui.configbool(b'storage', b'revlog.nodemap.mmap'):
988 998 options[b'persistent-nodemap.mmap'] = True
989 999 epnm = ui.config(b'storage', b'revlog.nodemap.mode')
990 1000 options[b'persistent-nodemap.mode'] = epnm
991 1001 if ui.configbool(b'devel', b'persistent-nodemap'):
992 1002 options[b'devel-force-nodemap'] = True
993 1003
994 1004 return options
995 1005
996 1006
997 1007 def makemain(**kwargs):
998 1008 """Produce a type conforming to ``ilocalrepositorymain``."""
999 1009 return localrepository
1000 1010
1001 1011
1002 1012 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1003 1013 class revlogfilestorage(object):
1004 1014 """File storage when using revlogs."""
1005 1015
1006 1016 def file(self, path):
1007 1017 if path[0] == b'/':
1008 1018 path = path[1:]
1009 1019
1010 1020 return filelog.filelog(self.svfs, path)
1011 1021
1012 1022
1013 1023 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1014 1024 class revlognarrowfilestorage(object):
1015 1025 """File storage when using revlogs and narrow files."""
1016 1026
1017 1027 def file(self, path):
1018 1028 if path[0] == b'/':
1019 1029 path = path[1:]
1020 1030
1021 1031 return filelog.narrowfilelog(self.svfs, path, self._storenarrowmatch)
1022 1032
1023 1033
1024 1034 def makefilestorage(requirements, features, **kwargs):
1025 1035 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
1026 1036 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
1027 1037 features.add(repository.REPO_FEATURE_STREAM_CLONE)
1028 1038
1029 1039 if requirementsmod.NARROW_REQUIREMENT in requirements:
1030 1040 return revlognarrowfilestorage
1031 1041 else:
1032 1042 return revlogfilestorage
1033 1043
1034 1044
1035 1045 # List of repository interfaces and factory functions for them. Each
1036 1046 # will be called in order during ``makelocalrepository()`` to iteratively
1037 1047 # derive the final type for a local repository instance. We capture the
1038 1048 # function as a lambda so we don't hold a reference and the module-level
1039 1049 # functions can be wrapped.
1040 1050 REPO_INTERFACES = [
1041 1051 (repository.ilocalrepositorymain, lambda: makemain),
1042 1052 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
1043 1053 ]
1044 1054
1045 1055
1046 1056 @interfaceutil.implementer(repository.ilocalrepositorymain)
1047 1057 class localrepository(object):
1048 1058 """Main class for representing local repositories.
1049 1059
1050 1060 All local repositories are instances of this class.
1051 1061
1052 1062 Constructed on its own, instances of this class are not usable as
1053 1063 repository objects. To obtain a usable repository object, call
1054 1064 ``hg.repository()``, ``localrepo.instance()``, or
1055 1065 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
1056 1066 ``instance()`` adds support for creating new repositories.
1057 1067 ``hg.repository()`` adds more extension integration, including calling
1058 1068 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
1059 1069 used.
1060 1070 """
1061 1071
1062 1072 # obsolete experimental requirements:
1063 1073 # - manifestv2: An experimental new manifest format that allowed
1064 1074 # for stem compression of long paths. Experiment ended up not
1065 1075 # being successful (repository sizes went up due to worse delta
1066 1076 # chains), and the code was deleted in 4.6.
1067 1077 supportedformats = {
1068 1078 b'revlogv1',
1069 1079 b'generaldelta',
1070 1080 requirementsmod.TREEMANIFEST_REQUIREMENT,
1071 1081 requirementsmod.COPIESSDC_REQUIREMENT,
1072 1082 requirementsmod.REVLOGV2_REQUIREMENT,
1073 1083 requirementsmod.SIDEDATA_REQUIREMENT,
1074 1084 requirementsmod.SPARSEREVLOG_REQUIREMENT,
1075 1085 requirementsmod.NODEMAP_REQUIREMENT,
1076 1086 bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
1077 1087 requirementsmod.SHARESAFE_REQUIREMENT,
1078 1088 }
1079 1089 _basesupported = supportedformats | {
1080 1090 b'store',
1081 1091 b'fncache',
1082 1092 requirementsmod.SHARED_REQUIREMENT,
1083 1093 requirementsmod.RELATIVE_SHARED_REQUIREMENT,
1084 1094 b'dotencode',
1085 1095 requirementsmod.SPARSE_REQUIREMENT,
1086 1096 requirementsmod.INTERNAL_PHASE_REQUIREMENT,
1087 1097 }
1088 1098
1089 1099 # list of prefix for file which can be written without 'wlock'
1090 1100 # Extensions should extend this list when needed
1091 1101 _wlockfreeprefix = {
1092 1102 # We migh consider requiring 'wlock' for the next
1093 1103 # two, but pretty much all the existing code assume
1094 1104 # wlock is not needed so we keep them excluded for
1095 1105 # now.
1096 1106 b'hgrc',
1097 1107 b'requires',
1098 1108 # XXX cache is a complicatged business someone
1099 1109 # should investigate this in depth at some point
1100 1110 b'cache/',
1101 1111 # XXX shouldn't be dirstate covered by the wlock?
1102 1112 b'dirstate',
1103 1113 # XXX bisect was still a bit too messy at the time
1104 1114 # this changeset was introduced. Someone should fix
1105 1115 # the remainig bit and drop this line
1106 1116 b'bisect.state',
1107 1117 }
1108 1118
1109 1119 def __init__(
1110 1120 self,
1111 1121 baseui,
1112 1122 ui,
1113 1123 origroot,
1114 1124 wdirvfs,
1115 1125 hgvfs,
1116 1126 requirements,
1117 1127 supportedrequirements,
1118 1128 sharedpath,
1119 1129 store,
1120 1130 cachevfs,
1121 1131 wcachevfs,
1122 1132 features,
1123 1133 intents=None,
1124 1134 ):
1125 1135 """Create a new local repository instance.
1126 1136
1127 1137 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
1128 1138 or ``localrepo.makelocalrepository()`` for obtaining a new repository
1129 1139 object.
1130 1140
1131 1141 Arguments:
1132 1142
1133 1143 baseui
1134 1144 ``ui.ui`` instance that ``ui`` argument was based off of.
1135 1145
1136 1146 ui
1137 1147 ``ui.ui`` instance for use by the repository.
1138 1148
1139 1149 origroot
1140 1150 ``bytes`` path to working directory root of this repository.
1141 1151
1142 1152 wdirvfs
1143 1153 ``vfs.vfs`` rooted at the working directory.
1144 1154
1145 1155 hgvfs
1146 1156 ``vfs.vfs`` rooted at .hg/
1147 1157
1148 1158 requirements
1149 1159 ``set`` of bytestrings representing repository opening requirements.
1150 1160
1151 1161 supportedrequirements
1152 1162 ``set`` of bytestrings representing repository requirements that we
1153 1163 know how to open. May be a supetset of ``requirements``.
1154 1164
1155 1165 sharedpath
1156 1166 ``bytes`` Defining path to storage base directory. Points to a
1157 1167 ``.hg/`` directory somewhere.
1158 1168
1159 1169 store
1160 1170 ``store.basicstore`` (or derived) instance providing access to
1161 1171 versioned storage.
1162 1172
1163 1173 cachevfs
1164 1174 ``vfs.vfs`` used for cache files.
1165 1175
1166 1176 wcachevfs
1167 1177 ``vfs.vfs`` used for cache files related to the working copy.
1168 1178
1169 1179 features
1170 1180 ``set`` of bytestrings defining features/capabilities of this
1171 1181 instance.
1172 1182
1173 1183 intents
1174 1184 ``set`` of system strings indicating what this repo will be used
1175 1185 for.
1176 1186 """
1177 1187 self.baseui = baseui
1178 1188 self.ui = ui
1179 1189 self.origroot = origroot
1180 1190 # vfs rooted at working directory.
1181 1191 self.wvfs = wdirvfs
1182 1192 self.root = wdirvfs.base
1183 1193 # vfs rooted at .hg/. Used to access most non-store paths.
1184 1194 self.vfs = hgvfs
1185 1195 self.path = hgvfs.base
1186 1196 self.requirements = requirements
1187 1197 self.supported = supportedrequirements
1188 1198 self.sharedpath = sharedpath
1189 1199 self.store = store
1190 1200 self.cachevfs = cachevfs
1191 1201 self.wcachevfs = wcachevfs
1192 1202 self.features = features
1193 1203
1194 1204 self.filtername = None
1195 1205
1196 1206 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1197 1207 b'devel', b'check-locks'
1198 1208 ):
1199 1209 self.vfs.audit = self._getvfsward(self.vfs.audit)
1200 1210 # A list of callback to shape the phase if no data were found.
1201 1211 # Callback are in the form: func(repo, roots) --> processed root.
1202 1212 # This list it to be filled by extension during repo setup
1203 1213 self._phasedefaults = []
1204 1214
1205 1215 color.setup(self.ui)
1206 1216
1207 1217 self.spath = self.store.path
1208 1218 self.svfs = self.store.vfs
1209 1219 self.sjoin = self.store.join
1210 1220 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1211 1221 b'devel', b'check-locks'
1212 1222 ):
1213 1223 if util.safehasattr(self.svfs, b'vfs'): # this is filtervfs
1214 1224 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1215 1225 else: # standard vfs
1216 1226 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1217 1227
1218 1228 self._dirstatevalidatewarned = False
1219 1229
1220 1230 self._branchcaches = branchmap.BranchMapCache()
1221 1231 self._revbranchcache = None
1222 1232 self._filterpats = {}
1223 1233 self._datafilters = {}
1224 1234 self._transref = self._lockref = self._wlockref = None
1225 1235
1226 1236 # A cache for various files under .hg/ that tracks file changes,
1227 1237 # (used by the filecache decorator)
1228 1238 #
1229 1239 # Maps a property name to its util.filecacheentry
1230 1240 self._filecache = {}
1231 1241
1232 1242 # hold sets of revision to be filtered
1233 1243 # should be cleared when something might have changed the filter value:
1234 1244 # - new changesets,
1235 1245 # - phase change,
1236 1246 # - new obsolescence marker,
1237 1247 # - working directory parent change,
1238 1248 # - bookmark changes
1239 1249 self.filteredrevcache = {}
1240 1250
1241 1251 # post-dirstate-status hooks
1242 1252 self._postdsstatus = []
1243 1253
1244 1254 # generic mapping between names and nodes
1245 1255 self.names = namespaces.namespaces()
1246 1256
1247 1257 # Key to signature value.
1248 1258 self._sparsesignaturecache = {}
1249 1259 # Signature to cached matcher instance.
1250 1260 self._sparsematchercache = {}
1251 1261
1252 1262 self._extrafilterid = repoview.extrafilter(ui)
1253 1263
1254 1264 self.filecopiesmode = None
1255 1265 if requirementsmod.COPIESSDC_REQUIREMENT in self.requirements:
1256 1266 self.filecopiesmode = b'changeset-sidedata'
1257 1267
1258 1268 def _getvfsward(self, origfunc):
1259 1269 """build a ward for self.vfs"""
1260 1270 rref = weakref.ref(self)
1261 1271
1262 1272 def checkvfs(path, mode=None):
1263 1273 ret = origfunc(path, mode=mode)
1264 1274 repo = rref()
1265 1275 if (
1266 1276 repo is None
1267 1277 or not util.safehasattr(repo, b'_wlockref')
1268 1278 or not util.safehasattr(repo, b'_lockref')
1269 1279 ):
1270 1280 return
1271 1281 if mode in (None, b'r', b'rb'):
1272 1282 return
1273 1283 if path.startswith(repo.path):
1274 1284 # truncate name relative to the repository (.hg)
1275 1285 path = path[len(repo.path) + 1 :]
1276 1286 if path.startswith(b'cache/'):
1277 1287 msg = b'accessing cache with vfs instead of cachevfs: "%s"'
1278 1288 repo.ui.develwarn(msg % path, stacklevel=3, config=b"cache-vfs")
1279 1289 # path prefixes covered by 'lock'
1280 1290 vfs_path_prefixes = (
1281 1291 b'journal.',
1282 1292 b'undo.',
1283 1293 b'strip-backup/',
1284 1294 b'cache/',
1285 1295 )
1286 1296 if any(path.startswith(prefix) for prefix in vfs_path_prefixes):
1287 1297 if repo._currentlock(repo._lockref) is None:
1288 1298 repo.ui.develwarn(
1289 1299 b'write with no lock: "%s"' % path,
1290 1300 stacklevel=3,
1291 1301 config=b'check-locks',
1292 1302 )
1293 1303 elif repo._currentlock(repo._wlockref) is None:
1294 1304 # rest of vfs files are covered by 'wlock'
1295 1305 #
1296 1306 # exclude special files
1297 1307 for prefix in self._wlockfreeprefix:
1298 1308 if path.startswith(prefix):
1299 1309 return
1300 1310 repo.ui.develwarn(
1301 1311 b'write with no wlock: "%s"' % path,
1302 1312 stacklevel=3,
1303 1313 config=b'check-locks',
1304 1314 )
1305 1315 return ret
1306 1316
1307 1317 return checkvfs
1308 1318
1309 1319 def _getsvfsward(self, origfunc):
1310 1320 """build a ward for self.svfs"""
1311 1321 rref = weakref.ref(self)
1312 1322
1313 1323 def checksvfs(path, mode=None):
1314 1324 ret = origfunc(path, mode=mode)
1315 1325 repo = rref()
1316 1326 if repo is None or not util.safehasattr(repo, b'_lockref'):
1317 1327 return
1318 1328 if mode in (None, b'r', b'rb'):
1319 1329 return
1320 1330 if path.startswith(repo.sharedpath):
1321 1331 # truncate name relative to the repository (.hg)
1322 1332 path = path[len(repo.sharedpath) + 1 :]
1323 1333 if repo._currentlock(repo._lockref) is None:
1324 1334 repo.ui.develwarn(
1325 1335 b'write with no lock: "%s"' % path, stacklevel=4
1326 1336 )
1327 1337 return ret
1328 1338
1329 1339 return checksvfs
1330 1340
1331 1341 def close(self):
1332 1342 self._writecaches()
1333 1343
1334 1344 def _writecaches(self):
1335 1345 if self._revbranchcache:
1336 1346 self._revbranchcache.write()
1337 1347
1338 1348 def _restrictcapabilities(self, caps):
1339 1349 if self.ui.configbool(b'experimental', b'bundle2-advertise'):
1340 1350 caps = set(caps)
1341 1351 capsblob = bundle2.encodecaps(
1342 1352 bundle2.getrepocaps(self, role=b'client')
1343 1353 )
1344 1354 caps.add(b'bundle2=' + urlreq.quote(capsblob))
1345 1355 return caps
1346 1356
1347 1357 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1348 1358 # self -> auditor -> self._checknested -> self
1349 1359
1350 1360 @property
1351 1361 def auditor(self):
1352 1362 # This is only used by context.workingctx.match in order to
1353 1363 # detect files in subrepos.
1354 1364 return pathutil.pathauditor(self.root, callback=self._checknested)
1355 1365
1356 1366 @property
1357 1367 def nofsauditor(self):
1358 1368 # This is only used by context.basectx.match in order to detect
1359 1369 # files in subrepos.
1360 1370 return pathutil.pathauditor(
1361 1371 self.root, callback=self._checknested, realfs=False, cached=True
1362 1372 )
1363 1373
1364 1374 def _checknested(self, path):
1365 1375 """Determine if path is a legal nested repository."""
1366 1376 if not path.startswith(self.root):
1367 1377 return False
1368 1378 subpath = path[len(self.root) + 1 :]
1369 1379 normsubpath = util.pconvert(subpath)
1370 1380
1371 1381 # XXX: Checking against the current working copy is wrong in
1372 1382 # the sense that it can reject things like
1373 1383 #
1374 1384 # $ hg cat -r 10 sub/x.txt
1375 1385 #
1376 1386 # if sub/ is no longer a subrepository in the working copy
1377 1387 # parent revision.
1378 1388 #
1379 1389 # However, it can of course also allow things that would have
1380 1390 # been rejected before, such as the above cat command if sub/
1381 1391 # is a subrepository now, but was a normal directory before.
1382 1392 # The old path auditor would have rejected by mistake since it
1383 1393 # panics when it sees sub/.hg/.
1384 1394 #
1385 1395 # All in all, checking against the working copy seems sensible
1386 1396 # since we want to prevent access to nested repositories on
1387 1397 # the filesystem *now*.
1388 1398 ctx = self[None]
1389 1399 parts = util.splitpath(subpath)
1390 1400 while parts:
1391 1401 prefix = b'/'.join(parts)
1392 1402 if prefix in ctx.substate:
1393 1403 if prefix == normsubpath:
1394 1404 return True
1395 1405 else:
1396 1406 sub = ctx.sub(prefix)
1397 1407 return sub.checknested(subpath[len(prefix) + 1 :])
1398 1408 else:
1399 1409 parts.pop()
1400 1410 return False
1401 1411
1402 1412 def peer(self):
1403 1413 return localpeer(self) # not cached to avoid reference cycle
1404 1414
1405 1415 def unfiltered(self):
1406 1416 """Return unfiltered version of the repository
1407 1417
1408 1418 Intended to be overwritten by filtered repo."""
1409 1419 return self
1410 1420
1411 1421 def filtered(self, name, visibilityexceptions=None):
1412 1422 """Return a filtered version of a repository
1413 1423
1414 1424 The `name` parameter is the identifier of the requested view. This
1415 1425 will return a repoview object set "exactly" to the specified view.
1416 1426
1417 1427 This function does not apply recursive filtering to a repository. For
1418 1428 example calling `repo.filtered("served")` will return a repoview using
1419 1429 the "served" view, regardless of the initial view used by `repo`.
1420 1430
1421 1431 In other word, there is always only one level of `repoview` "filtering".
1422 1432 """
1423 1433 if self._extrafilterid is not None and b'%' not in name:
1424 1434 name = name + b'%' + self._extrafilterid
1425 1435
1426 1436 cls = repoview.newtype(self.unfiltered().__class__)
1427 1437 return cls(self, name, visibilityexceptions)
1428 1438
1429 1439 @mixedrepostorecache(
1430 1440 (b'bookmarks', b'plain'),
1431 1441 (b'bookmarks.current', b'plain'),
1432 1442 (b'bookmarks', b''),
1433 1443 (b'00changelog.i', b''),
1434 1444 )
1435 1445 def _bookmarks(self):
1436 1446 # Since the multiple files involved in the transaction cannot be
1437 1447 # written atomically (with current repository format), there is a race
1438 1448 # condition here.
1439 1449 #
1440 1450 # 1) changelog content A is read
1441 1451 # 2) outside transaction update changelog to content B
1442 1452 # 3) outside transaction update bookmark file referring to content B
1443 1453 # 4) bookmarks file content is read and filtered against changelog-A
1444 1454 #
1445 1455 # When this happens, bookmarks against nodes missing from A are dropped.
1446 1456 #
1447 1457 # Having this happening during read is not great, but it become worse
1448 1458 # when this happen during write because the bookmarks to the "unknown"
1449 1459 # nodes will be dropped for good. However, writes happen within locks.
1450 1460 # This locking makes it possible to have a race free consistent read.
1451 1461 # For this purpose data read from disc before locking are
1452 1462 # "invalidated" right after the locks are taken. This invalidations are
1453 1463 # "light", the `filecache` mechanism keep the data in memory and will
1454 1464 # reuse them if the underlying files did not changed. Not parsing the
1455 1465 # same data multiple times helps performances.
1456 1466 #
1457 1467 # Unfortunately in the case describe above, the files tracked by the
1458 1468 # bookmarks file cache might not have changed, but the in-memory
1459 1469 # content is still "wrong" because we used an older changelog content
1460 1470 # to process the on-disk data. So after locking, the changelog would be
1461 1471 # refreshed but `_bookmarks` would be preserved.
1462 1472 # Adding `00changelog.i` to the list of tracked file is not
1463 1473 # enough, because at the time we build the content for `_bookmarks` in
1464 1474 # (4), the changelog file has already diverged from the content used
1465 1475 # for loading `changelog` in (1)
1466 1476 #
1467 1477 # To prevent the issue, we force the changelog to be explicitly
1468 1478 # reloaded while computing `_bookmarks`. The data race can still happen
1469 1479 # without the lock (with a narrower window), but it would no longer go
1470 1480 # undetected during the lock time refresh.
1471 1481 #
1472 1482 # The new schedule is as follow
1473 1483 #
1474 1484 # 1) filecache logic detect that `_bookmarks` needs to be computed
1475 1485 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1476 1486 # 3) We force `changelog` filecache to be tested
1477 1487 # 4) cachestat for `changelog` are captured (for changelog)
1478 1488 # 5) `_bookmarks` is computed and cached
1479 1489 #
1480 1490 # The step in (3) ensure we have a changelog at least as recent as the
1481 1491 # cache stat computed in (1). As a result at locking time:
1482 1492 # * if the changelog did not changed since (1) -> we can reuse the data
1483 1493 # * otherwise -> the bookmarks get refreshed.
1484 1494 self._refreshchangelog()
1485 1495 return bookmarks.bmstore(self)
1486 1496
1487 1497 def _refreshchangelog(self):
1488 1498 """make sure the in memory changelog match the on-disk one"""
1489 1499 if 'changelog' in vars(self) and self.currenttransaction() is None:
1490 1500 del self.changelog
1491 1501
1492 1502 @property
1493 1503 def _activebookmark(self):
1494 1504 return self._bookmarks.active
1495 1505
1496 1506 # _phasesets depend on changelog. what we need is to call
1497 1507 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1498 1508 # can't be easily expressed in filecache mechanism.
1499 1509 @storecache(b'phaseroots', b'00changelog.i')
1500 1510 def _phasecache(self):
1501 1511 return phases.phasecache(self, self._phasedefaults)
1502 1512
1503 1513 @storecache(b'obsstore')
1504 1514 def obsstore(self):
1505 1515 return obsolete.makestore(self.ui, self)
1506 1516
1507 1517 @storecache(b'00changelog.i')
1508 1518 def changelog(self):
1509 1519 # load dirstate before changelog to avoid race see issue6303
1510 1520 self.dirstate.prefetch_parents()
1511 1521 return self.store.changelog(txnutil.mayhavepending(self.root))
1512 1522
1513 1523 @storecache(b'00manifest.i')
1514 1524 def manifestlog(self):
1515 1525 return self.store.manifestlog(self, self._storenarrowmatch)
1516 1526
1517 1527 @repofilecache(b'dirstate')
1518 1528 def dirstate(self):
1519 1529 return self._makedirstate()
1520 1530
1521 1531 def _makedirstate(self):
1522 1532 """Extension point for wrapping the dirstate per-repo."""
1523 1533 sparsematchfn = lambda: sparse.matcher(self)
1524 1534
1525 1535 return dirstate.dirstate(
1526 1536 self.vfs, self.ui, self.root, self._dirstatevalidate, sparsematchfn
1527 1537 )
1528 1538
1529 1539 def _dirstatevalidate(self, node):
1530 1540 try:
1531 1541 self.changelog.rev(node)
1532 1542 return node
1533 1543 except error.LookupError:
1534 1544 if not self._dirstatevalidatewarned:
1535 1545 self._dirstatevalidatewarned = True
1536 1546 self.ui.warn(
1537 1547 _(b"warning: ignoring unknown working parent %s!\n")
1538 1548 % short(node)
1539 1549 )
1540 1550 return nullid
1541 1551
1542 1552 @storecache(narrowspec.FILENAME)
1543 1553 def narrowpats(self):
1544 1554 """matcher patterns for this repository's narrowspec
1545 1555
1546 1556 A tuple of (includes, excludes).
1547 1557 """
1548 1558 return narrowspec.load(self)
1549 1559
1550 1560 @storecache(narrowspec.FILENAME)
1551 1561 def _storenarrowmatch(self):
1552 1562 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1553 1563 return matchmod.always()
1554 1564 include, exclude = self.narrowpats
1555 1565 return narrowspec.match(self.root, include=include, exclude=exclude)
1556 1566
1557 1567 @storecache(narrowspec.FILENAME)
1558 1568 def _narrowmatch(self):
1559 1569 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1560 1570 return matchmod.always()
1561 1571 narrowspec.checkworkingcopynarrowspec(self)
1562 1572 include, exclude = self.narrowpats
1563 1573 return narrowspec.match(self.root, include=include, exclude=exclude)
1564 1574
1565 1575 def narrowmatch(self, match=None, includeexact=False):
1566 1576 """matcher corresponding the the repo's narrowspec
1567 1577
1568 1578 If `match` is given, then that will be intersected with the narrow
1569 1579 matcher.
1570 1580
1571 1581 If `includeexact` is True, then any exact matches from `match` will
1572 1582 be included even if they're outside the narrowspec.
1573 1583 """
1574 1584 if match:
1575 1585 if includeexact and not self._narrowmatch.always():
1576 1586 # do not exclude explicitly-specified paths so that they can
1577 1587 # be warned later on
1578 1588 em = matchmod.exact(match.files())
1579 1589 nm = matchmod.unionmatcher([self._narrowmatch, em])
1580 1590 return matchmod.intersectmatchers(match, nm)
1581 1591 return matchmod.intersectmatchers(match, self._narrowmatch)
1582 1592 return self._narrowmatch
1583 1593
1584 1594 def setnarrowpats(self, newincludes, newexcludes):
1585 1595 narrowspec.save(self, newincludes, newexcludes)
1586 1596 self.invalidate(clearfilecache=True)
1587 1597
1588 1598 @unfilteredpropertycache
1589 1599 def _quick_access_changeid_null(self):
1590 1600 return {
1591 1601 b'null': (nullrev, nullid),
1592 1602 nullrev: (nullrev, nullid),
1593 1603 nullid: (nullrev, nullid),
1594 1604 }
1595 1605
1596 1606 @unfilteredpropertycache
1597 1607 def _quick_access_changeid_wc(self):
1598 1608 # also fast path access to the working copy parents
1599 1609 # however, only do it for filter that ensure wc is visible.
1600 1610 quick = self._quick_access_changeid_null.copy()
1601 1611 cl = self.unfiltered().changelog
1602 1612 for node in self.dirstate.parents():
1603 1613 if node == nullid:
1604 1614 continue
1605 1615 rev = cl.index.get_rev(node)
1606 1616 if rev is None:
1607 1617 # unknown working copy parent case:
1608 1618 #
1609 1619 # skip the fast path and let higher code deal with it
1610 1620 continue
1611 1621 pair = (rev, node)
1612 1622 quick[rev] = pair
1613 1623 quick[node] = pair
1614 1624 # also add the parents of the parents
1615 1625 for r in cl.parentrevs(rev):
1616 1626 if r == nullrev:
1617 1627 continue
1618 1628 n = cl.node(r)
1619 1629 pair = (r, n)
1620 1630 quick[r] = pair
1621 1631 quick[n] = pair
1622 1632 p1node = self.dirstate.p1()
1623 1633 if p1node != nullid:
1624 1634 quick[b'.'] = quick[p1node]
1625 1635 return quick
1626 1636
1627 1637 @unfilteredmethod
1628 1638 def _quick_access_changeid_invalidate(self):
1629 1639 if '_quick_access_changeid_wc' in vars(self):
1630 1640 del self.__dict__['_quick_access_changeid_wc']
1631 1641
1632 1642 @property
1633 1643 def _quick_access_changeid(self):
1634 1644 """an helper dictionnary for __getitem__ calls
1635 1645
1636 1646 This contains a list of symbol we can recognise right away without
1637 1647 further processing.
1638 1648 """
1639 1649 if self.filtername in repoview.filter_has_wc:
1640 1650 return self._quick_access_changeid_wc
1641 1651 return self._quick_access_changeid_null
1642 1652
1643 1653 def __getitem__(self, changeid):
1644 1654 # dealing with special cases
1645 1655 if changeid is None:
1646 1656 return context.workingctx(self)
1647 1657 if isinstance(changeid, context.basectx):
1648 1658 return changeid
1649 1659
1650 1660 # dealing with multiple revisions
1651 1661 if isinstance(changeid, slice):
1652 1662 # wdirrev isn't contiguous so the slice shouldn't include it
1653 1663 return [
1654 1664 self[i]
1655 1665 for i in pycompat.xrange(*changeid.indices(len(self)))
1656 1666 if i not in self.changelog.filteredrevs
1657 1667 ]
1658 1668
1659 1669 # dealing with some special values
1660 1670 quick_access = self._quick_access_changeid.get(changeid)
1661 1671 if quick_access is not None:
1662 1672 rev, node = quick_access
1663 1673 return context.changectx(self, rev, node, maybe_filtered=False)
1664 1674 if changeid == b'tip':
1665 1675 node = self.changelog.tip()
1666 1676 rev = self.changelog.rev(node)
1667 1677 return context.changectx(self, rev, node)
1668 1678
1669 1679 # dealing with arbitrary values
1670 1680 try:
1671 1681 if isinstance(changeid, int):
1672 1682 node = self.changelog.node(changeid)
1673 1683 rev = changeid
1674 1684 elif changeid == b'.':
1675 1685 # this is a hack to delay/avoid loading obsmarkers
1676 1686 # when we know that '.' won't be hidden
1677 1687 node = self.dirstate.p1()
1678 1688 rev = self.unfiltered().changelog.rev(node)
1679 1689 elif len(changeid) == 20:
1680 1690 try:
1681 1691 node = changeid
1682 1692 rev = self.changelog.rev(changeid)
1683 1693 except error.FilteredLookupError:
1684 1694 changeid = hex(changeid) # for the error message
1685 1695 raise
1686 1696 except LookupError:
1687 1697 # check if it might have come from damaged dirstate
1688 1698 #
1689 1699 # XXX we could avoid the unfiltered if we had a recognizable
1690 1700 # exception for filtered changeset access
1691 1701 if (
1692 1702 self.local()
1693 1703 and changeid in self.unfiltered().dirstate.parents()
1694 1704 ):
1695 1705 msg = _(b"working directory has unknown parent '%s'!")
1696 1706 raise error.Abort(msg % short(changeid))
1697 1707 changeid = hex(changeid) # for the error message
1698 1708 raise
1699 1709
1700 1710 elif len(changeid) == 40:
1701 1711 node = bin(changeid)
1702 1712 rev = self.changelog.rev(node)
1703 1713 else:
1704 1714 raise error.ProgrammingError(
1705 1715 b"unsupported changeid '%s' of type %s"
1706 1716 % (changeid, pycompat.bytestr(type(changeid)))
1707 1717 )
1708 1718
1709 1719 return context.changectx(self, rev, node)
1710 1720
1711 1721 except (error.FilteredIndexError, error.FilteredLookupError):
1712 1722 raise error.FilteredRepoLookupError(
1713 1723 _(b"filtered revision '%s'") % pycompat.bytestr(changeid)
1714 1724 )
1715 1725 except (IndexError, LookupError):
1716 1726 raise error.RepoLookupError(
1717 1727 _(b"unknown revision '%s'") % pycompat.bytestr(changeid)
1718 1728 )
1719 1729 except error.WdirUnsupported:
1720 1730 return context.workingctx(self)
1721 1731
1722 1732 def __contains__(self, changeid):
1723 1733 """True if the given changeid exists
1724 1734
1725 1735 error.AmbiguousPrefixLookupError is raised if an ambiguous node
1726 1736 specified.
1727 1737 """
1728 1738 try:
1729 1739 self[changeid]
1730 1740 return True
1731 1741 except error.RepoLookupError:
1732 1742 return False
1733 1743
1734 1744 def __nonzero__(self):
1735 1745 return True
1736 1746
1737 1747 __bool__ = __nonzero__
1738 1748
1739 1749 def __len__(self):
1740 1750 # no need to pay the cost of repoview.changelog
1741 1751 unfi = self.unfiltered()
1742 1752 return len(unfi.changelog)
1743 1753
1744 1754 def __iter__(self):
1745 1755 return iter(self.changelog)
1746 1756
1747 1757 def revs(self, expr, *args):
1748 1758 '''Find revisions matching a revset.
1749 1759
1750 1760 The revset is specified as a string ``expr`` that may contain
1751 1761 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1752 1762
1753 1763 Revset aliases from the configuration are not expanded. To expand
1754 1764 user aliases, consider calling ``scmutil.revrange()`` or
1755 1765 ``repo.anyrevs([expr], user=True)``.
1756 1766
1757 1767 Returns a smartset.abstractsmartset, which is a list-like interface
1758 1768 that contains integer revisions.
1759 1769 '''
1760 1770 tree = revsetlang.spectree(expr, *args)
1761 1771 return revset.makematcher(tree)(self)
1762 1772
1763 1773 def set(self, expr, *args):
1764 1774 '''Find revisions matching a revset and emit changectx instances.
1765 1775
1766 1776 This is a convenience wrapper around ``revs()`` that iterates the
1767 1777 result and is a generator of changectx instances.
1768 1778
1769 1779 Revset aliases from the configuration are not expanded. To expand
1770 1780 user aliases, consider calling ``scmutil.revrange()``.
1771 1781 '''
1772 1782 for r in self.revs(expr, *args):
1773 1783 yield self[r]
1774 1784
1775 1785 def anyrevs(self, specs, user=False, localalias=None):
1776 1786 '''Find revisions matching one of the given revsets.
1777 1787
1778 1788 Revset aliases from the configuration are not expanded by default. To
1779 1789 expand user aliases, specify ``user=True``. To provide some local
1780 1790 definitions overriding user aliases, set ``localalias`` to
1781 1791 ``{name: definitionstring}``.
1782 1792 '''
1783 1793 if specs == [b'null']:
1784 1794 return revset.baseset([nullrev])
1785 1795 if specs == [b'.']:
1786 1796 quick_data = self._quick_access_changeid.get(b'.')
1787 1797 if quick_data is not None:
1788 1798 return revset.baseset([quick_data[0]])
1789 1799 if user:
1790 1800 m = revset.matchany(
1791 1801 self.ui,
1792 1802 specs,
1793 1803 lookup=revset.lookupfn(self),
1794 1804 localalias=localalias,
1795 1805 )
1796 1806 else:
1797 1807 m = revset.matchany(None, specs, localalias=localalias)
1798 1808 return m(self)
1799 1809
1800 1810 def url(self):
1801 1811 return b'file:' + self.root
1802 1812
1803 1813 def hook(self, name, throw=False, **args):
1804 1814 """Call a hook, passing this repo instance.
1805 1815
1806 1816 This a convenience method to aid invoking hooks. Extensions likely
1807 1817 won't call this unless they have registered a custom hook or are
1808 1818 replacing code that is expected to call a hook.
1809 1819 """
1810 1820 return hook.hook(self.ui, self, name, throw, **args)
1811 1821
1812 1822 @filteredpropertycache
1813 1823 def _tagscache(self):
1814 1824 '''Returns a tagscache object that contains various tags related
1815 1825 caches.'''
1816 1826
1817 1827 # This simplifies its cache management by having one decorated
1818 1828 # function (this one) and the rest simply fetch things from it.
1819 1829 class tagscache(object):
1820 1830 def __init__(self):
1821 1831 # These two define the set of tags for this repository. tags
1822 1832 # maps tag name to node; tagtypes maps tag name to 'global' or
1823 1833 # 'local'. (Global tags are defined by .hgtags across all
1824 1834 # heads, and local tags are defined in .hg/localtags.)
1825 1835 # They constitute the in-memory cache of tags.
1826 1836 self.tags = self.tagtypes = None
1827 1837
1828 1838 self.nodetagscache = self.tagslist = None
1829 1839
1830 1840 cache = tagscache()
1831 1841 cache.tags, cache.tagtypes = self._findtags()
1832 1842
1833 1843 return cache
1834 1844
1835 1845 def tags(self):
1836 1846 '''return a mapping of tag to node'''
1837 1847 t = {}
1838 1848 if self.changelog.filteredrevs:
1839 1849 tags, tt = self._findtags()
1840 1850 else:
1841 1851 tags = self._tagscache.tags
1842 1852 rev = self.changelog.rev
1843 1853 for k, v in pycompat.iteritems(tags):
1844 1854 try:
1845 1855 # ignore tags to unknown nodes
1846 1856 rev(v)
1847 1857 t[k] = v
1848 1858 except (error.LookupError, ValueError):
1849 1859 pass
1850 1860 return t
1851 1861
1852 1862 def _findtags(self):
1853 1863 '''Do the hard work of finding tags. Return a pair of dicts
1854 1864 (tags, tagtypes) where tags maps tag name to node, and tagtypes
1855 1865 maps tag name to a string like \'global\' or \'local\'.
1856 1866 Subclasses or extensions are free to add their own tags, but
1857 1867 should be aware that the returned dicts will be retained for the
1858 1868 duration of the localrepo object.'''
1859 1869
1860 1870 # XXX what tagtype should subclasses/extensions use? Currently
1861 1871 # mq and bookmarks add tags, but do not set the tagtype at all.
1862 1872 # Should each extension invent its own tag type? Should there
1863 1873 # be one tagtype for all such "virtual" tags? Or is the status
1864 1874 # quo fine?
1865 1875
1866 1876 # map tag name to (node, hist)
1867 1877 alltags = tagsmod.findglobaltags(self.ui, self)
1868 1878 # map tag name to tag type
1869 1879 tagtypes = {tag: b'global' for tag in alltags}
1870 1880
1871 1881 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1872 1882
1873 1883 # Build the return dicts. Have to re-encode tag names because
1874 1884 # the tags module always uses UTF-8 (in order not to lose info
1875 1885 # writing to the cache), but the rest of Mercurial wants them in
1876 1886 # local encoding.
1877 1887 tags = {}
1878 1888 for (name, (node, hist)) in pycompat.iteritems(alltags):
1879 1889 if node != nullid:
1880 1890 tags[encoding.tolocal(name)] = node
1881 1891 tags[b'tip'] = self.changelog.tip()
1882 1892 tagtypes = {
1883 1893 encoding.tolocal(name): value
1884 1894 for (name, value) in pycompat.iteritems(tagtypes)
1885 1895 }
1886 1896 return (tags, tagtypes)
1887 1897
1888 1898 def tagtype(self, tagname):
1889 1899 '''
1890 1900 return the type of the given tag. result can be:
1891 1901
1892 1902 'local' : a local tag
1893 1903 'global' : a global tag
1894 1904 None : tag does not exist
1895 1905 '''
1896 1906
1897 1907 return self._tagscache.tagtypes.get(tagname)
1898 1908
1899 1909 def tagslist(self):
1900 1910 '''return a list of tags ordered by revision'''
1901 1911 if not self._tagscache.tagslist:
1902 1912 l = []
1903 1913 for t, n in pycompat.iteritems(self.tags()):
1904 1914 l.append((self.changelog.rev(n), t, n))
1905 1915 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1906 1916
1907 1917 return self._tagscache.tagslist
1908 1918
1909 1919 def nodetags(self, node):
1910 1920 '''return the tags associated with a node'''
1911 1921 if not self._tagscache.nodetagscache:
1912 1922 nodetagscache = {}
1913 1923 for t, n in pycompat.iteritems(self._tagscache.tags):
1914 1924 nodetagscache.setdefault(n, []).append(t)
1915 1925 for tags in pycompat.itervalues(nodetagscache):
1916 1926 tags.sort()
1917 1927 self._tagscache.nodetagscache = nodetagscache
1918 1928 return self._tagscache.nodetagscache.get(node, [])
1919 1929
1920 1930 def nodebookmarks(self, node):
1921 1931 """return the list of bookmarks pointing to the specified node"""
1922 1932 return self._bookmarks.names(node)
1923 1933
1924 1934 def branchmap(self):
1925 1935 '''returns a dictionary {branch: [branchheads]} with branchheads
1926 1936 ordered by increasing revision number'''
1927 1937 return self._branchcaches[self]
1928 1938
1929 1939 @unfilteredmethod
1930 1940 def revbranchcache(self):
1931 1941 if not self._revbranchcache:
1932 1942 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1933 1943 return self._revbranchcache
1934 1944
1935 1945 def branchtip(self, branch, ignoremissing=False):
1936 1946 '''return the tip node for a given branch
1937 1947
1938 1948 If ignoremissing is True, then this method will not raise an error.
1939 1949 This is helpful for callers that only expect None for a missing branch
1940 1950 (e.g. namespace).
1941 1951
1942 1952 '''
1943 1953 try:
1944 1954 return self.branchmap().branchtip(branch)
1945 1955 except KeyError:
1946 1956 if not ignoremissing:
1947 1957 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
1948 1958 else:
1949 1959 pass
1950 1960
1951 1961 def lookup(self, key):
1952 1962 node = scmutil.revsymbol(self, key).node()
1953 1963 if node is None:
1954 1964 raise error.RepoLookupError(_(b"unknown revision '%s'") % key)
1955 1965 return node
1956 1966
1957 1967 def lookupbranch(self, key):
1958 1968 if self.branchmap().hasbranch(key):
1959 1969 return key
1960 1970
1961 1971 return scmutil.revsymbol(self, key).branch()
1962 1972
1963 1973 def known(self, nodes):
1964 1974 cl = self.changelog
1965 1975 get_rev = cl.index.get_rev
1966 1976 filtered = cl.filteredrevs
1967 1977 result = []
1968 1978 for n in nodes:
1969 1979 r = get_rev(n)
1970 1980 resp = not (r is None or r in filtered)
1971 1981 result.append(resp)
1972 1982 return result
1973 1983
1974 1984 def local(self):
1975 1985 return self
1976 1986
1977 1987 def publishing(self):
1978 1988 # it's safe (and desirable) to trust the publish flag unconditionally
1979 1989 # so that we don't finalize changes shared between users via ssh or nfs
1980 1990 return self.ui.configbool(b'phases', b'publish', untrusted=True)
1981 1991
1982 1992 def cancopy(self):
1983 1993 # so statichttprepo's override of local() works
1984 1994 if not self.local():
1985 1995 return False
1986 1996 if not self.publishing():
1987 1997 return True
1988 1998 # if publishing we can't copy if there is filtered content
1989 1999 return not self.filtered(b'visible').changelog.filteredrevs
1990 2000
1991 2001 def shared(self):
1992 2002 '''the type of shared repository (None if not shared)'''
1993 2003 if self.sharedpath != self.path:
1994 2004 return b'store'
1995 2005 return None
1996 2006
1997 2007 def wjoin(self, f, *insidef):
1998 2008 return self.vfs.reljoin(self.root, f, *insidef)
1999 2009
2000 2010 def setparents(self, p1, p2=nullid):
2001 2011 self[None].setparents(p1, p2)
2002 2012 self._quick_access_changeid_invalidate()
2003 2013
2004 2014 def filectx(self, path, changeid=None, fileid=None, changectx=None):
2005 2015 """changeid must be a changeset revision, if specified.
2006 2016 fileid can be a file revision or node."""
2007 2017 return context.filectx(
2008 2018 self, path, changeid, fileid, changectx=changectx
2009 2019 )
2010 2020
2011 2021 def getcwd(self):
2012 2022 return self.dirstate.getcwd()
2013 2023
2014 2024 def pathto(self, f, cwd=None):
2015 2025 return self.dirstate.pathto(f, cwd)
2016 2026
2017 2027 def _loadfilter(self, filter):
2018 2028 if filter not in self._filterpats:
2019 2029 l = []
2020 2030 for pat, cmd in self.ui.configitems(filter):
2021 2031 if cmd == b'!':
2022 2032 continue
2023 2033 mf = matchmod.match(self.root, b'', [pat])
2024 2034 fn = None
2025 2035 params = cmd
2026 2036 for name, filterfn in pycompat.iteritems(self._datafilters):
2027 2037 if cmd.startswith(name):
2028 2038 fn = filterfn
2029 2039 params = cmd[len(name) :].lstrip()
2030 2040 break
2031 2041 if not fn:
2032 2042 fn = lambda s, c, **kwargs: procutil.filter(s, c)
2033 2043 fn.__name__ = 'commandfilter'
2034 2044 # Wrap old filters not supporting keyword arguments
2035 2045 if not pycompat.getargspec(fn)[2]:
2036 2046 oldfn = fn
2037 2047 fn = lambda s, c, oldfn=oldfn, **kwargs: oldfn(s, c)
2038 2048 fn.__name__ = 'compat-' + oldfn.__name__
2039 2049 l.append((mf, fn, params))
2040 2050 self._filterpats[filter] = l
2041 2051 return self._filterpats[filter]
2042 2052
2043 2053 def _filter(self, filterpats, filename, data):
2044 2054 for mf, fn, cmd in filterpats:
2045 2055 if mf(filename):
2046 2056 self.ui.debug(
2047 2057 b"filtering %s through %s\n"
2048 2058 % (filename, cmd or pycompat.sysbytes(fn.__name__))
2049 2059 )
2050 2060 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
2051 2061 break
2052 2062
2053 2063 return data
2054 2064
2055 2065 @unfilteredpropertycache
2056 2066 def _encodefilterpats(self):
2057 2067 return self._loadfilter(b'encode')
2058 2068
2059 2069 @unfilteredpropertycache
2060 2070 def _decodefilterpats(self):
2061 2071 return self._loadfilter(b'decode')
2062 2072
2063 2073 def adddatafilter(self, name, filter):
2064 2074 self._datafilters[name] = filter
2065 2075
2066 2076 def wread(self, filename):
2067 2077 if self.wvfs.islink(filename):
2068 2078 data = self.wvfs.readlink(filename)
2069 2079 else:
2070 2080 data = self.wvfs.read(filename)
2071 2081 return self._filter(self._encodefilterpats, filename, data)
2072 2082
2073 2083 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
2074 2084 """write ``data`` into ``filename`` in the working directory
2075 2085
2076 2086 This returns length of written (maybe decoded) data.
2077 2087 """
2078 2088 data = self._filter(self._decodefilterpats, filename, data)
2079 2089 if b'l' in flags:
2080 2090 self.wvfs.symlink(data, filename)
2081 2091 else:
2082 2092 self.wvfs.write(
2083 2093 filename, data, backgroundclose=backgroundclose, **kwargs
2084 2094 )
2085 2095 if b'x' in flags:
2086 2096 self.wvfs.setflags(filename, False, True)
2087 2097 else:
2088 2098 self.wvfs.setflags(filename, False, False)
2089 2099 return len(data)
2090 2100
2091 2101 def wwritedata(self, filename, data):
2092 2102 return self._filter(self._decodefilterpats, filename, data)
2093 2103
2094 2104 def currenttransaction(self):
2095 2105 """return the current transaction or None if non exists"""
2096 2106 if self._transref:
2097 2107 tr = self._transref()
2098 2108 else:
2099 2109 tr = None
2100 2110
2101 2111 if tr and tr.running():
2102 2112 return tr
2103 2113 return None
2104 2114
2105 2115 def transaction(self, desc, report=None):
2106 2116 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
2107 2117 b'devel', b'check-locks'
2108 2118 ):
2109 2119 if self._currentlock(self._lockref) is None:
2110 2120 raise error.ProgrammingError(b'transaction requires locking')
2111 2121 tr = self.currenttransaction()
2112 2122 if tr is not None:
2113 2123 return tr.nest(name=desc)
2114 2124
2115 2125 # abort here if the journal already exists
2116 2126 if self.svfs.exists(b"journal"):
2117 2127 raise error.RepoError(
2118 2128 _(b"abandoned transaction found"),
2119 2129 hint=_(b"run 'hg recover' to clean up transaction"),
2120 2130 )
2121 2131
2122 2132 idbase = b"%.40f#%f" % (random.random(), time.time())
2123 2133 ha = hex(hashutil.sha1(idbase).digest())
2124 2134 txnid = b'TXN:' + ha
2125 2135 self.hook(b'pretxnopen', throw=True, txnname=desc, txnid=txnid)
2126 2136
2127 2137 self._writejournal(desc)
2128 2138 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
2129 2139 if report:
2130 2140 rp = report
2131 2141 else:
2132 2142 rp = self.ui.warn
2133 2143 vfsmap = {b'plain': self.vfs, b'store': self.svfs} # root of .hg/
2134 2144 # we must avoid cyclic reference between repo and transaction.
2135 2145 reporef = weakref.ref(self)
2136 2146 # Code to track tag movement
2137 2147 #
2138 2148 # Since tags are all handled as file content, it is actually quite hard
2139 2149 # to track these movement from a code perspective. So we fallback to a
2140 2150 # tracking at the repository level. One could envision to track changes
2141 2151 # to the '.hgtags' file through changegroup apply but that fails to
2142 2152 # cope with case where transaction expose new heads without changegroup
2143 2153 # being involved (eg: phase movement).
2144 2154 #
2145 2155 # For now, We gate the feature behind a flag since this likely comes
2146 2156 # with performance impacts. The current code run more often than needed
2147 2157 # and do not use caches as much as it could. The current focus is on
2148 2158 # the behavior of the feature so we disable it by default. The flag
2149 2159 # will be removed when we are happy with the performance impact.
2150 2160 #
2151 2161 # Once this feature is no longer experimental move the following
2152 2162 # documentation to the appropriate help section:
2153 2163 #
2154 2164 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
2155 2165 # tags (new or changed or deleted tags). In addition the details of
2156 2166 # these changes are made available in a file at:
2157 2167 # ``REPOROOT/.hg/changes/tags.changes``.
2158 2168 # Make sure you check for HG_TAG_MOVED before reading that file as it
2159 2169 # might exist from a previous transaction even if no tag were touched
2160 2170 # in this one. Changes are recorded in a line base format::
2161 2171 #
2162 2172 # <action> <hex-node> <tag-name>\n
2163 2173 #
2164 2174 # Actions are defined as follow:
2165 2175 # "-R": tag is removed,
2166 2176 # "+A": tag is added,
2167 2177 # "-M": tag is moved (old value),
2168 2178 # "+M": tag is moved (new value),
2169 2179 tracktags = lambda x: None
2170 2180 # experimental config: experimental.hook-track-tags
2171 2181 shouldtracktags = self.ui.configbool(
2172 2182 b'experimental', b'hook-track-tags'
2173 2183 )
2174 2184 if desc != b'strip' and shouldtracktags:
2175 2185 oldheads = self.changelog.headrevs()
2176 2186
2177 2187 def tracktags(tr2):
2178 2188 repo = reporef()
2179 2189 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
2180 2190 newheads = repo.changelog.headrevs()
2181 2191 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
2182 2192 # notes: we compare lists here.
2183 2193 # As we do it only once buiding set would not be cheaper
2184 2194 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
2185 2195 if changes:
2186 2196 tr2.hookargs[b'tag_moved'] = b'1'
2187 2197 with repo.vfs(
2188 2198 b'changes/tags.changes', b'w', atomictemp=True
2189 2199 ) as changesfile:
2190 2200 # note: we do not register the file to the transaction
2191 2201 # because we needs it to still exist on the transaction
2192 2202 # is close (for txnclose hooks)
2193 2203 tagsmod.writediff(changesfile, changes)
2194 2204
2195 2205 def validate(tr2):
2196 2206 """will run pre-closing hooks"""
2197 2207 # XXX the transaction API is a bit lacking here so we take a hacky
2198 2208 # path for now
2199 2209 #
2200 2210 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
2201 2211 # dict is copied before these run. In addition we needs the data
2202 2212 # available to in memory hooks too.
2203 2213 #
2204 2214 # Moreover, we also need to make sure this runs before txnclose
2205 2215 # hooks and there is no "pending" mechanism that would execute
2206 2216 # logic only if hooks are about to run.
2207 2217 #
2208 2218 # Fixing this limitation of the transaction is also needed to track
2209 2219 # other families of changes (bookmarks, phases, obsolescence).
2210 2220 #
2211 2221 # This will have to be fixed before we remove the experimental
2212 2222 # gating.
2213 2223 tracktags(tr2)
2214 2224 repo = reporef()
2215 2225
2216 2226 singleheadopt = (b'experimental', b'single-head-per-branch')
2217 2227 singlehead = repo.ui.configbool(*singleheadopt)
2218 2228 if singlehead:
2219 2229 singleheadsub = repo.ui.configsuboptions(*singleheadopt)[1]
2220 2230 accountclosed = singleheadsub.get(
2221 2231 b"account-closed-heads", False
2222 2232 )
2223 2233 scmutil.enforcesinglehead(repo, tr2, desc, accountclosed)
2224 2234 if hook.hashook(repo.ui, b'pretxnclose-bookmark'):
2225 2235 for name, (old, new) in sorted(
2226 2236 tr.changes[b'bookmarks'].items()
2227 2237 ):
2228 2238 args = tr.hookargs.copy()
2229 2239 args.update(bookmarks.preparehookargs(name, old, new))
2230 2240 repo.hook(
2231 2241 b'pretxnclose-bookmark',
2232 2242 throw=True,
2233 2243 **pycompat.strkwargs(args)
2234 2244 )
2235 2245 if hook.hashook(repo.ui, b'pretxnclose-phase'):
2236 2246 cl = repo.unfiltered().changelog
2237 2247 for revs, (old, new) in tr.changes[b'phases']:
2238 2248 for rev in revs:
2239 2249 args = tr.hookargs.copy()
2240 2250 node = hex(cl.node(rev))
2241 2251 args.update(phases.preparehookargs(node, old, new))
2242 2252 repo.hook(
2243 2253 b'pretxnclose-phase',
2244 2254 throw=True,
2245 2255 **pycompat.strkwargs(args)
2246 2256 )
2247 2257
2248 2258 repo.hook(
2249 2259 b'pretxnclose', throw=True, **pycompat.strkwargs(tr.hookargs)
2250 2260 )
2251 2261
2252 2262 def releasefn(tr, success):
2253 2263 repo = reporef()
2254 2264 if repo is None:
2255 2265 # If the repo has been GC'd (and this release function is being
2256 2266 # called from transaction.__del__), there's not much we can do,
2257 2267 # so just leave the unfinished transaction there and let the
2258 2268 # user run `hg recover`.
2259 2269 return
2260 2270 if success:
2261 2271 # this should be explicitly invoked here, because
2262 2272 # in-memory changes aren't written out at closing
2263 2273 # transaction, if tr.addfilegenerator (via
2264 2274 # dirstate.write or so) isn't invoked while
2265 2275 # transaction running
2266 2276 repo.dirstate.write(None)
2267 2277 else:
2268 2278 # discard all changes (including ones already written
2269 2279 # out) in this transaction
2270 2280 narrowspec.restorebackup(self, b'journal.narrowspec')
2271 2281 narrowspec.restorewcbackup(self, b'journal.narrowspec.dirstate')
2272 2282 repo.dirstate.restorebackup(None, b'journal.dirstate')
2273 2283
2274 2284 repo.invalidate(clearfilecache=True)
2275 2285
2276 2286 tr = transaction.transaction(
2277 2287 rp,
2278 2288 self.svfs,
2279 2289 vfsmap,
2280 2290 b"journal",
2281 2291 b"undo",
2282 2292 aftertrans(renames),
2283 2293 self.store.createmode,
2284 2294 validator=validate,
2285 2295 releasefn=releasefn,
2286 2296 checkambigfiles=_cachedfiles,
2287 2297 name=desc,
2288 2298 )
2289 2299 tr.changes[b'origrepolen'] = len(self)
2290 2300 tr.changes[b'obsmarkers'] = set()
2291 2301 tr.changes[b'phases'] = []
2292 2302 tr.changes[b'bookmarks'] = {}
2293 2303
2294 2304 tr.hookargs[b'txnid'] = txnid
2295 2305 tr.hookargs[b'txnname'] = desc
2296 2306 tr.hookargs[b'changes'] = tr.changes
2297 2307 # note: writing the fncache only during finalize mean that the file is
2298 2308 # outdated when running hooks. As fncache is used for streaming clone,
2299 2309 # this is not expected to break anything that happen during the hooks.
2300 2310 tr.addfinalize(b'flush-fncache', self.store.write)
2301 2311
2302 2312 def txnclosehook(tr2):
2303 2313 """To be run if transaction is successful, will schedule a hook run
2304 2314 """
2305 2315 # Don't reference tr2 in hook() so we don't hold a reference.
2306 2316 # This reduces memory consumption when there are multiple
2307 2317 # transactions per lock. This can likely go away if issue5045
2308 2318 # fixes the function accumulation.
2309 2319 hookargs = tr2.hookargs
2310 2320
2311 2321 def hookfunc(unused_success):
2312 2322 repo = reporef()
2313 2323 if hook.hashook(repo.ui, b'txnclose-bookmark'):
2314 2324 bmchanges = sorted(tr.changes[b'bookmarks'].items())
2315 2325 for name, (old, new) in bmchanges:
2316 2326 args = tr.hookargs.copy()
2317 2327 args.update(bookmarks.preparehookargs(name, old, new))
2318 2328 repo.hook(
2319 2329 b'txnclose-bookmark',
2320 2330 throw=False,
2321 2331 **pycompat.strkwargs(args)
2322 2332 )
2323 2333
2324 2334 if hook.hashook(repo.ui, b'txnclose-phase'):
2325 2335 cl = repo.unfiltered().changelog
2326 2336 phasemv = sorted(
2327 2337 tr.changes[b'phases'], key=lambda r: r[0][0]
2328 2338 )
2329 2339 for revs, (old, new) in phasemv:
2330 2340 for rev in revs:
2331 2341 args = tr.hookargs.copy()
2332 2342 node = hex(cl.node(rev))
2333 2343 args.update(phases.preparehookargs(node, old, new))
2334 2344 repo.hook(
2335 2345 b'txnclose-phase',
2336 2346 throw=False,
2337 2347 **pycompat.strkwargs(args)
2338 2348 )
2339 2349
2340 2350 repo.hook(
2341 2351 b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
2342 2352 )
2343 2353
2344 2354 reporef()._afterlock(hookfunc)
2345 2355
2346 2356 tr.addfinalize(b'txnclose-hook', txnclosehook)
2347 2357 # Include a leading "-" to make it happen before the transaction summary
2348 2358 # reports registered via scmutil.registersummarycallback() whose names
2349 2359 # are 00-txnreport etc. That way, the caches will be warm when the
2350 2360 # callbacks run.
2351 2361 tr.addpostclose(b'-warm-cache', self._buildcacheupdater(tr))
2352 2362
2353 2363 def txnaborthook(tr2):
2354 2364 """To be run if transaction is aborted
2355 2365 """
2356 2366 reporef().hook(
2357 2367 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
2358 2368 )
2359 2369
2360 2370 tr.addabort(b'txnabort-hook', txnaborthook)
2361 2371 # avoid eager cache invalidation. in-memory data should be identical
2362 2372 # to stored data if transaction has no error.
2363 2373 tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
2364 2374 self._transref = weakref.ref(tr)
2365 2375 scmutil.registersummarycallback(self, tr, desc)
2366 2376 return tr
2367 2377
2368 2378 def _journalfiles(self):
2369 2379 return (
2370 2380 (self.svfs, b'journal'),
2371 2381 (self.svfs, b'journal.narrowspec'),
2372 2382 (self.vfs, b'journal.narrowspec.dirstate'),
2373 2383 (self.vfs, b'journal.dirstate'),
2374 2384 (self.vfs, b'journal.branch'),
2375 2385 (self.vfs, b'journal.desc'),
2376 2386 (bookmarks.bookmarksvfs(self), b'journal.bookmarks'),
2377 2387 (self.svfs, b'journal.phaseroots'),
2378 2388 )
2379 2389
2380 2390 def undofiles(self):
2381 2391 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2382 2392
2383 2393 @unfilteredmethod
2384 2394 def _writejournal(self, desc):
2385 2395 self.dirstate.savebackup(None, b'journal.dirstate')
2386 2396 narrowspec.savewcbackup(self, b'journal.narrowspec.dirstate')
2387 2397 narrowspec.savebackup(self, b'journal.narrowspec')
2388 2398 self.vfs.write(
2389 2399 b"journal.branch", encoding.fromlocal(self.dirstate.branch())
2390 2400 )
2391 2401 self.vfs.write(b"journal.desc", b"%d\n%s\n" % (len(self), desc))
2392 2402 bookmarksvfs = bookmarks.bookmarksvfs(self)
2393 2403 bookmarksvfs.write(
2394 2404 b"journal.bookmarks", bookmarksvfs.tryread(b"bookmarks")
2395 2405 )
2396 2406 self.svfs.write(b"journal.phaseroots", self.svfs.tryread(b"phaseroots"))
2397 2407
2398 2408 def recover(self):
2399 2409 with self.lock():
2400 2410 if self.svfs.exists(b"journal"):
2401 2411 self.ui.status(_(b"rolling back interrupted transaction\n"))
2402 2412 vfsmap = {
2403 2413 b'': self.svfs,
2404 2414 b'plain': self.vfs,
2405 2415 }
2406 2416 transaction.rollback(
2407 2417 self.svfs,
2408 2418 vfsmap,
2409 2419 b"journal",
2410 2420 self.ui.warn,
2411 2421 checkambigfiles=_cachedfiles,
2412 2422 )
2413 2423 self.invalidate()
2414 2424 return True
2415 2425 else:
2416 2426 self.ui.warn(_(b"no interrupted transaction available\n"))
2417 2427 return False
2418 2428
2419 2429 def rollback(self, dryrun=False, force=False):
2420 2430 wlock = lock = dsguard = None
2421 2431 try:
2422 2432 wlock = self.wlock()
2423 2433 lock = self.lock()
2424 2434 if self.svfs.exists(b"undo"):
2425 2435 dsguard = dirstateguard.dirstateguard(self, b'rollback')
2426 2436
2427 2437 return self._rollback(dryrun, force, dsguard)
2428 2438 else:
2429 2439 self.ui.warn(_(b"no rollback information available\n"))
2430 2440 return 1
2431 2441 finally:
2432 2442 release(dsguard, lock, wlock)
2433 2443
2434 2444 @unfilteredmethod # Until we get smarter cache management
2435 2445 def _rollback(self, dryrun, force, dsguard):
2436 2446 ui = self.ui
2437 2447 try:
2438 2448 args = self.vfs.read(b'undo.desc').splitlines()
2439 2449 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2440 2450 if len(args) >= 3:
2441 2451 detail = args[2]
2442 2452 oldtip = oldlen - 1
2443 2453
2444 2454 if detail and ui.verbose:
2445 2455 msg = _(
2446 2456 b'repository tip rolled back to revision %d'
2447 2457 b' (undo %s: %s)\n'
2448 2458 ) % (oldtip, desc, detail)
2449 2459 else:
2450 2460 msg = _(
2451 2461 b'repository tip rolled back to revision %d (undo %s)\n'
2452 2462 ) % (oldtip, desc)
2453 2463 except IOError:
2454 2464 msg = _(b'rolling back unknown transaction\n')
2455 2465 desc = None
2456 2466
2457 2467 if not force and self[b'.'] != self[b'tip'] and desc == b'commit':
2458 2468 raise error.Abort(
2459 2469 _(
2460 2470 b'rollback of last commit while not checked out '
2461 2471 b'may lose data'
2462 2472 ),
2463 2473 hint=_(b'use -f to force'),
2464 2474 )
2465 2475
2466 2476 ui.status(msg)
2467 2477 if dryrun:
2468 2478 return 0
2469 2479
2470 2480 parents = self.dirstate.parents()
2471 2481 self.destroying()
2472 2482 vfsmap = {b'plain': self.vfs, b'': self.svfs}
2473 2483 transaction.rollback(
2474 2484 self.svfs, vfsmap, b'undo', ui.warn, checkambigfiles=_cachedfiles
2475 2485 )
2476 2486 bookmarksvfs = bookmarks.bookmarksvfs(self)
2477 2487 if bookmarksvfs.exists(b'undo.bookmarks'):
2478 2488 bookmarksvfs.rename(
2479 2489 b'undo.bookmarks', b'bookmarks', checkambig=True
2480 2490 )
2481 2491 if self.svfs.exists(b'undo.phaseroots'):
2482 2492 self.svfs.rename(b'undo.phaseroots', b'phaseroots', checkambig=True)
2483 2493 self.invalidate()
2484 2494
2485 2495 has_node = self.changelog.index.has_node
2486 2496 parentgone = any(not has_node(p) for p in parents)
2487 2497 if parentgone:
2488 2498 # prevent dirstateguard from overwriting already restored one
2489 2499 dsguard.close()
2490 2500
2491 2501 narrowspec.restorebackup(self, b'undo.narrowspec')
2492 2502 narrowspec.restorewcbackup(self, b'undo.narrowspec.dirstate')
2493 2503 self.dirstate.restorebackup(None, b'undo.dirstate')
2494 2504 try:
2495 2505 branch = self.vfs.read(b'undo.branch')
2496 2506 self.dirstate.setbranch(encoding.tolocal(branch))
2497 2507 except IOError:
2498 2508 ui.warn(
2499 2509 _(
2500 2510 b'named branch could not be reset: '
2501 2511 b'current branch is still \'%s\'\n'
2502 2512 )
2503 2513 % self.dirstate.branch()
2504 2514 )
2505 2515
2506 2516 parents = tuple([p.rev() for p in self[None].parents()])
2507 2517 if len(parents) > 1:
2508 2518 ui.status(
2509 2519 _(
2510 2520 b'working directory now based on '
2511 2521 b'revisions %d and %d\n'
2512 2522 )
2513 2523 % parents
2514 2524 )
2515 2525 else:
2516 2526 ui.status(
2517 2527 _(b'working directory now based on revision %d\n') % parents
2518 2528 )
2519 2529 mergestatemod.mergestate.clean(self)
2520 2530
2521 2531 # TODO: if we know which new heads may result from this rollback, pass
2522 2532 # them to destroy(), which will prevent the branchhead cache from being
2523 2533 # invalidated.
2524 2534 self.destroyed()
2525 2535 return 0
2526 2536
2527 2537 def _buildcacheupdater(self, newtransaction):
2528 2538 """called during transaction to build the callback updating cache
2529 2539
2530 2540 Lives on the repository to help extension who might want to augment
2531 2541 this logic. For this purpose, the created transaction is passed to the
2532 2542 method.
2533 2543 """
2534 2544 # we must avoid cyclic reference between repo and transaction.
2535 2545 reporef = weakref.ref(self)
2536 2546
2537 2547 def updater(tr):
2538 2548 repo = reporef()
2539 2549 repo.updatecaches(tr)
2540 2550
2541 2551 return updater
2542 2552
2543 2553 @unfilteredmethod
2544 2554 def updatecaches(self, tr=None, full=False):
2545 2555 """warm appropriate caches
2546 2556
2547 2557 If this function is called after a transaction closed. The transaction
2548 2558 will be available in the 'tr' argument. This can be used to selectively
2549 2559 update caches relevant to the changes in that transaction.
2550 2560
2551 2561 If 'full' is set, make sure all caches the function knows about have
2552 2562 up-to-date data. Even the ones usually loaded more lazily.
2553 2563 """
2554 2564 if tr is not None and tr.hookargs.get(b'source') == b'strip':
2555 2565 # During strip, many caches are invalid but
2556 2566 # later call to `destroyed` will refresh them.
2557 2567 return
2558 2568
2559 2569 if tr is None or tr.changes[b'origrepolen'] < len(self):
2560 2570 # accessing the 'ser ved' branchmap should refresh all the others,
2561 2571 self.ui.debug(b'updating the branch cache\n')
2562 2572 self.filtered(b'served').branchmap()
2563 2573 self.filtered(b'served.hidden').branchmap()
2564 2574
2565 2575 if full:
2566 2576 unfi = self.unfiltered()
2567 2577
2568 2578 self.changelog.update_caches(transaction=tr)
2569 2579 self.manifestlog.update_caches(transaction=tr)
2570 2580
2571 2581 rbc = unfi.revbranchcache()
2572 2582 for r in unfi.changelog:
2573 2583 rbc.branchinfo(r)
2574 2584 rbc.write()
2575 2585
2576 2586 # ensure the working copy parents are in the manifestfulltextcache
2577 2587 for ctx in self[b'.'].parents():
2578 2588 ctx.manifest() # accessing the manifest is enough
2579 2589
2580 2590 # accessing fnode cache warms the cache
2581 2591 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2582 2592 # accessing tags warm the cache
2583 2593 self.tags()
2584 2594 self.filtered(b'served').tags()
2585 2595
2586 2596 # The `full` arg is documented as updating even the lazily-loaded
2587 2597 # caches immediately, so we're forcing a write to cause these caches
2588 2598 # to be warmed up even if they haven't explicitly been requested
2589 2599 # yet (if they've never been used by hg, they won't ever have been
2590 2600 # written, even if they're a subset of another kind of cache that
2591 2601 # *has* been used).
2592 2602 for filt in repoview.filtertable.keys():
2593 2603 filtered = self.filtered(filt)
2594 2604 filtered.branchmap().write(filtered)
2595 2605
2596 2606 def invalidatecaches(self):
2597 2607
2598 2608 if '_tagscache' in vars(self):
2599 2609 # can't use delattr on proxy
2600 2610 del self.__dict__['_tagscache']
2601 2611
2602 2612 self._branchcaches.clear()
2603 2613 self.invalidatevolatilesets()
2604 2614 self._sparsesignaturecache.clear()
2605 2615
2606 2616 def invalidatevolatilesets(self):
2607 2617 self.filteredrevcache.clear()
2608 2618 obsolete.clearobscaches(self)
2609 2619 self._quick_access_changeid_invalidate()
2610 2620
2611 2621 def invalidatedirstate(self):
2612 2622 '''Invalidates the dirstate, causing the next call to dirstate
2613 2623 to check if it was modified since the last time it was read,
2614 2624 rereading it if it has.
2615 2625
2616 2626 This is different to dirstate.invalidate() that it doesn't always
2617 2627 rereads the dirstate. Use dirstate.invalidate() if you want to
2618 2628 explicitly read the dirstate again (i.e. restoring it to a previous
2619 2629 known good state).'''
2620 2630 if hasunfilteredcache(self, 'dirstate'):
2621 2631 for k in self.dirstate._filecache:
2622 2632 try:
2623 2633 delattr(self.dirstate, k)
2624 2634 except AttributeError:
2625 2635 pass
2626 2636 delattr(self.unfiltered(), 'dirstate')
2627 2637
2628 2638 def invalidate(self, clearfilecache=False):
2629 2639 '''Invalidates both store and non-store parts other than dirstate
2630 2640
2631 2641 If a transaction is running, invalidation of store is omitted,
2632 2642 because discarding in-memory changes might cause inconsistency
2633 2643 (e.g. incomplete fncache causes unintentional failure, but
2634 2644 redundant one doesn't).
2635 2645 '''
2636 2646 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2637 2647 for k in list(self._filecache.keys()):
2638 2648 # dirstate is invalidated separately in invalidatedirstate()
2639 2649 if k == b'dirstate':
2640 2650 continue
2641 2651 if (
2642 2652 k == b'changelog'
2643 2653 and self.currenttransaction()
2644 2654 and self.changelog._delayed
2645 2655 ):
2646 2656 # The changelog object may store unwritten revisions. We don't
2647 2657 # want to lose them.
2648 2658 # TODO: Solve the problem instead of working around it.
2649 2659 continue
2650 2660
2651 2661 if clearfilecache:
2652 2662 del self._filecache[k]
2653 2663 try:
2654 2664 delattr(unfiltered, k)
2655 2665 except AttributeError:
2656 2666 pass
2657 2667 self.invalidatecaches()
2658 2668 if not self.currenttransaction():
2659 2669 # TODO: Changing contents of store outside transaction
2660 2670 # causes inconsistency. We should make in-memory store
2661 2671 # changes detectable, and abort if changed.
2662 2672 self.store.invalidatecaches()
2663 2673
2664 2674 def invalidateall(self):
2665 2675 '''Fully invalidates both store and non-store parts, causing the
2666 2676 subsequent operation to reread any outside changes.'''
2667 2677 # extension should hook this to invalidate its caches
2668 2678 self.invalidate()
2669 2679 self.invalidatedirstate()
2670 2680
2671 2681 @unfilteredmethod
2672 2682 def _refreshfilecachestats(self, tr):
2673 2683 """Reload stats of cached files so that they are flagged as valid"""
2674 2684 for k, ce in self._filecache.items():
2675 2685 k = pycompat.sysstr(k)
2676 2686 if k == 'dirstate' or k not in self.__dict__:
2677 2687 continue
2678 2688 ce.refresh()
2679 2689
2680 2690 def _lock(
2681 2691 self, vfs, lockname, wait, releasefn, acquirefn, desc,
2682 2692 ):
2683 2693 timeout = 0
2684 2694 warntimeout = 0
2685 2695 if wait:
2686 2696 timeout = self.ui.configint(b"ui", b"timeout")
2687 2697 warntimeout = self.ui.configint(b"ui", b"timeout.warn")
2688 2698 # internal config: ui.signal-safe-lock
2689 2699 signalsafe = self.ui.configbool(b'ui', b'signal-safe-lock')
2690 2700
2691 2701 l = lockmod.trylock(
2692 2702 self.ui,
2693 2703 vfs,
2694 2704 lockname,
2695 2705 timeout,
2696 2706 warntimeout,
2697 2707 releasefn=releasefn,
2698 2708 acquirefn=acquirefn,
2699 2709 desc=desc,
2700 2710 signalsafe=signalsafe,
2701 2711 )
2702 2712 return l
2703 2713
2704 2714 def _afterlock(self, callback):
2705 2715 """add a callback to be run when the repository is fully unlocked
2706 2716
2707 2717 The callback will be executed when the outermost lock is released
2708 2718 (with wlock being higher level than 'lock')."""
2709 2719 for ref in (self._wlockref, self._lockref):
2710 2720 l = ref and ref()
2711 2721 if l and l.held:
2712 2722 l.postrelease.append(callback)
2713 2723 break
2714 2724 else: # no lock have been found.
2715 2725 callback(True)
2716 2726
2717 2727 def lock(self, wait=True):
2718 2728 '''Lock the repository store (.hg/store) and return a weak reference
2719 2729 to the lock. Use this before modifying the store (e.g. committing or
2720 2730 stripping). If you are opening a transaction, get a lock as well.)
2721 2731
2722 2732 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2723 2733 'wlock' first to avoid a dead-lock hazard.'''
2724 2734 l = self._currentlock(self._lockref)
2725 2735 if l is not None:
2726 2736 l.lock()
2727 2737 return l
2728 2738
2729 2739 l = self._lock(
2730 2740 vfs=self.svfs,
2731 2741 lockname=b"lock",
2732 2742 wait=wait,
2733 2743 releasefn=None,
2734 2744 acquirefn=self.invalidate,
2735 2745 desc=_(b'repository %s') % self.origroot,
2736 2746 )
2737 2747 self._lockref = weakref.ref(l)
2738 2748 return l
2739 2749
2740 2750 def wlock(self, wait=True):
2741 2751 '''Lock the non-store parts of the repository (everything under
2742 2752 .hg except .hg/store) and return a weak reference to the lock.
2743 2753
2744 2754 Use this before modifying files in .hg.
2745 2755
2746 2756 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2747 2757 'wlock' first to avoid a dead-lock hazard.'''
2748 2758 l = self._wlockref and self._wlockref()
2749 2759 if l is not None and l.held:
2750 2760 l.lock()
2751 2761 return l
2752 2762
2753 2763 # We do not need to check for non-waiting lock acquisition. Such
2754 2764 # acquisition would not cause dead-lock as they would just fail.
2755 2765 if wait and (
2756 2766 self.ui.configbool(b'devel', b'all-warnings')
2757 2767 or self.ui.configbool(b'devel', b'check-locks')
2758 2768 ):
2759 2769 if self._currentlock(self._lockref) is not None:
2760 2770 self.ui.develwarn(b'"wlock" acquired after "lock"')
2761 2771
2762 2772 def unlock():
2763 2773 if self.dirstate.pendingparentchange():
2764 2774 self.dirstate.invalidate()
2765 2775 else:
2766 2776 self.dirstate.write(None)
2767 2777
2768 2778 self._filecache[b'dirstate'].refresh()
2769 2779
2770 2780 l = self._lock(
2771 2781 self.vfs,
2772 2782 b"wlock",
2773 2783 wait,
2774 2784 unlock,
2775 2785 self.invalidatedirstate,
2776 2786 _(b'working directory of %s') % self.origroot,
2777 2787 )
2778 2788 self._wlockref = weakref.ref(l)
2779 2789 return l
2780 2790
2781 2791 def _currentlock(self, lockref):
2782 2792 """Returns the lock if it's held, or None if it's not."""
2783 2793 if lockref is None:
2784 2794 return None
2785 2795 l = lockref()
2786 2796 if l is None or not l.held:
2787 2797 return None
2788 2798 return l
2789 2799
2790 2800 def currentwlock(self):
2791 2801 """Returns the wlock if it's held, or None if it's not."""
2792 2802 return self._currentlock(self._wlockref)
2793 2803
2794 2804 def checkcommitpatterns(self, wctx, match, status, fail):
2795 2805 """check for commit arguments that aren't committable"""
2796 2806 if match.isexact() or match.prefix():
2797 2807 matched = set(status.modified + status.added + status.removed)
2798 2808
2799 2809 for f in match.files():
2800 2810 f = self.dirstate.normalize(f)
2801 2811 if f == b'.' or f in matched or f in wctx.substate:
2802 2812 continue
2803 2813 if f in status.deleted:
2804 2814 fail(f, _(b'file not found!'))
2805 2815 # Is it a directory that exists or used to exist?
2806 2816 if self.wvfs.isdir(f) or wctx.p1().hasdir(f):
2807 2817 d = f + b'/'
2808 2818 for mf in matched:
2809 2819 if mf.startswith(d):
2810 2820 break
2811 2821 else:
2812 2822 fail(f, _(b"no match under directory!"))
2813 2823 elif f not in self.dirstate:
2814 2824 fail(f, _(b"file not tracked!"))
2815 2825
2816 2826 @unfilteredmethod
2817 2827 def commit(
2818 2828 self,
2819 2829 text=b"",
2820 2830 user=None,
2821 2831 date=None,
2822 2832 match=None,
2823 2833 force=False,
2824 2834 editor=None,
2825 2835 extra=None,
2826 2836 ):
2827 2837 """Add a new revision to current repository.
2828 2838
2829 2839 Revision information is gathered from the working directory,
2830 2840 match can be used to filter the committed files. If editor is
2831 2841 supplied, it is called to get a commit message.
2832 2842 """
2833 2843 if extra is None:
2834 2844 extra = {}
2835 2845
2836 2846 def fail(f, msg):
2837 2847 raise error.Abort(b'%s: %s' % (f, msg))
2838 2848
2839 2849 if not match:
2840 2850 match = matchmod.always()
2841 2851
2842 2852 if not force:
2843 2853 match.bad = fail
2844 2854
2845 2855 # lock() for recent changelog (see issue4368)
2846 2856 with self.wlock(), self.lock():
2847 2857 wctx = self[None]
2848 2858 merge = len(wctx.parents()) > 1
2849 2859
2850 2860 if not force and merge and not match.always():
2851 2861 raise error.Abort(
2852 2862 _(
2853 2863 b'cannot partially commit a merge '
2854 2864 b'(do not specify files or patterns)'
2855 2865 )
2856 2866 )
2857 2867
2858 2868 status = self.status(match=match, clean=force)
2859 2869 if force:
2860 2870 status.modified.extend(
2861 2871 status.clean
2862 2872 ) # mq may commit clean files
2863 2873
2864 2874 # check subrepos
2865 2875 subs, commitsubs, newstate = subrepoutil.precommit(
2866 2876 self.ui, wctx, status, match, force=force
2867 2877 )
2868 2878
2869 2879 # make sure all explicit patterns are matched
2870 2880 if not force:
2871 2881 self.checkcommitpatterns(wctx, match, status, fail)
2872 2882
2873 2883 cctx = context.workingcommitctx(
2874 2884 self, status, text, user, date, extra
2875 2885 )
2876 2886
2877 2887 ms = mergestatemod.mergestate.read(self)
2878 2888 mergeutil.checkunresolved(ms)
2879 2889
2880 2890 # internal config: ui.allowemptycommit
2881 2891 if cctx.isempty() and not self.ui.configbool(
2882 2892 b'ui', b'allowemptycommit'
2883 2893 ):
2884 2894 self.ui.debug(b'nothing to commit, clearing merge state\n')
2885 2895 ms.reset()
2886 2896 return None
2887 2897
2888 2898 if merge and cctx.deleted():
2889 2899 raise error.Abort(_(b"cannot commit merge with missing files"))
2890 2900
2891 2901 if editor:
2892 2902 cctx._text = editor(self, cctx, subs)
2893 2903 edited = text != cctx._text
2894 2904
2895 2905 # Save commit message in case this transaction gets rolled back
2896 2906 # (e.g. by a pretxncommit hook). Leave the content alone on
2897 2907 # the assumption that the user will use the same editor again.
2898 2908 msgfn = self.savecommitmessage(cctx._text)
2899 2909
2900 2910 # commit subs and write new state
2901 2911 if subs:
2902 2912 uipathfn = scmutil.getuipathfn(self)
2903 2913 for s in sorted(commitsubs):
2904 2914 sub = wctx.sub(s)
2905 2915 self.ui.status(
2906 2916 _(b'committing subrepository %s\n')
2907 2917 % uipathfn(subrepoutil.subrelpath(sub))
2908 2918 )
2909 2919 sr = sub.commit(cctx._text, user, date)
2910 2920 newstate[s] = (newstate[s][0], sr)
2911 2921 subrepoutil.writestate(self, newstate)
2912 2922
2913 2923 p1, p2 = self.dirstate.parents()
2914 2924 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or b'')
2915 2925 try:
2916 2926 self.hook(
2917 2927 b"precommit", throw=True, parent1=hookp1, parent2=hookp2
2918 2928 )
2919 2929 with self.transaction(b'commit'):
2920 2930 ret = self.commitctx(cctx, True)
2921 2931 # update bookmarks, dirstate and mergestate
2922 2932 bookmarks.update(self, [p1, p2], ret)
2923 2933 cctx.markcommitted(ret)
2924 2934 ms.reset()
2925 2935 except: # re-raises
2926 2936 if edited:
2927 2937 self.ui.write(
2928 2938 _(b'note: commit message saved in %s\n') % msgfn
2929 2939 )
2930 2940 self.ui.write(
2931 2941 _(
2932 2942 b"note: use 'hg commit --logfile "
2933 2943 b".hg/last-message.txt --edit' to reuse it\n"
2934 2944 )
2935 2945 )
2936 2946 raise
2937 2947
2938 2948 def commithook(unused_success):
2939 2949 # hack for command that use a temporary commit (eg: histedit)
2940 2950 # temporary commit got stripped before hook release
2941 2951 if self.changelog.hasnode(ret):
2942 2952 self.hook(
2943 2953 b"commit", node=hex(ret), parent1=hookp1, parent2=hookp2
2944 2954 )
2945 2955
2946 2956 self._afterlock(commithook)
2947 2957 return ret
2948 2958
2949 2959 @unfilteredmethod
2950 2960 def commitctx(self, ctx, error=False, origctx=None):
2951 2961 return commit.commitctx(self, ctx, error=error, origctx=origctx)
2952 2962
2953 2963 @unfilteredmethod
2954 2964 def destroying(self):
2955 2965 '''Inform the repository that nodes are about to be destroyed.
2956 2966 Intended for use by strip and rollback, so there's a common
2957 2967 place for anything that has to be done before destroying history.
2958 2968
2959 2969 This is mostly useful for saving state that is in memory and waiting
2960 2970 to be flushed when the current lock is released. Because a call to
2961 2971 destroyed is imminent, the repo will be invalidated causing those
2962 2972 changes to stay in memory (waiting for the next unlock), or vanish
2963 2973 completely.
2964 2974 '''
2965 2975 # When using the same lock to commit and strip, the phasecache is left
2966 2976 # dirty after committing. Then when we strip, the repo is invalidated,
2967 2977 # causing those changes to disappear.
2968 2978 if '_phasecache' in vars(self):
2969 2979 self._phasecache.write()
2970 2980
2971 2981 @unfilteredmethod
2972 2982 def destroyed(self):
2973 2983 '''Inform the repository that nodes have been destroyed.
2974 2984 Intended for use by strip and rollback, so there's a common
2975 2985 place for anything that has to be done after destroying history.
2976 2986 '''
2977 2987 # When one tries to:
2978 2988 # 1) destroy nodes thus calling this method (e.g. strip)
2979 2989 # 2) use phasecache somewhere (e.g. commit)
2980 2990 #
2981 2991 # then 2) will fail because the phasecache contains nodes that were
2982 2992 # removed. We can either remove phasecache from the filecache,
2983 2993 # causing it to reload next time it is accessed, or simply filter
2984 2994 # the removed nodes now and write the updated cache.
2985 2995 self._phasecache.filterunknown(self)
2986 2996 self._phasecache.write()
2987 2997
2988 2998 # refresh all repository caches
2989 2999 self.updatecaches()
2990 3000
2991 3001 # Ensure the persistent tag cache is updated. Doing it now
2992 3002 # means that the tag cache only has to worry about destroyed
2993 3003 # heads immediately after a strip/rollback. That in turn
2994 3004 # guarantees that "cachetip == currenttip" (comparing both rev
2995 3005 # and node) always means no nodes have been added or destroyed.
2996 3006
2997 3007 # XXX this is suboptimal when qrefresh'ing: we strip the current
2998 3008 # head, refresh the tag cache, then immediately add a new head.
2999 3009 # But I think doing it this way is necessary for the "instant
3000 3010 # tag cache retrieval" case to work.
3001 3011 self.invalidate()
3002 3012
3003 3013 def status(
3004 3014 self,
3005 3015 node1=b'.',
3006 3016 node2=None,
3007 3017 match=None,
3008 3018 ignored=False,
3009 3019 clean=False,
3010 3020 unknown=False,
3011 3021 listsubrepos=False,
3012 3022 ):
3013 3023 '''a convenience method that calls node1.status(node2)'''
3014 3024 return self[node1].status(
3015 3025 node2, match, ignored, clean, unknown, listsubrepos
3016 3026 )
3017 3027
3018 3028 def addpostdsstatus(self, ps):
3019 3029 """Add a callback to run within the wlock, at the point at which status
3020 3030 fixups happen.
3021 3031
3022 3032 On status completion, callback(wctx, status) will be called with the
3023 3033 wlock held, unless the dirstate has changed from underneath or the wlock
3024 3034 couldn't be grabbed.
3025 3035
3026 3036 Callbacks should not capture and use a cached copy of the dirstate --
3027 3037 it might change in the meanwhile. Instead, they should access the
3028 3038 dirstate via wctx.repo().dirstate.
3029 3039
3030 3040 This list is emptied out after each status run -- extensions should
3031 3041 make sure it adds to this list each time dirstate.status is called.
3032 3042 Extensions should also make sure they don't call this for statuses
3033 3043 that don't involve the dirstate.
3034 3044 """
3035 3045
3036 3046 # The list is located here for uniqueness reasons -- it is actually
3037 3047 # managed by the workingctx, but that isn't unique per-repo.
3038 3048 self._postdsstatus.append(ps)
3039 3049
3040 3050 def postdsstatus(self):
3041 3051 """Used by workingctx to get the list of post-dirstate-status hooks."""
3042 3052 return self._postdsstatus
3043 3053
3044 3054 def clearpostdsstatus(self):
3045 3055 """Used by workingctx to clear post-dirstate-status hooks."""
3046 3056 del self._postdsstatus[:]
3047 3057
3048 3058 def heads(self, start=None):
3049 3059 if start is None:
3050 3060 cl = self.changelog
3051 3061 headrevs = reversed(cl.headrevs())
3052 3062 return [cl.node(rev) for rev in headrevs]
3053 3063
3054 3064 heads = self.changelog.heads(start)
3055 3065 # sort the output in rev descending order
3056 3066 return sorted(heads, key=self.changelog.rev, reverse=True)
3057 3067
3058 3068 def branchheads(self, branch=None, start=None, closed=False):
3059 3069 '''return a (possibly filtered) list of heads for the given branch
3060 3070
3061 3071 Heads are returned in topological order, from newest to oldest.
3062 3072 If branch is None, use the dirstate branch.
3063 3073 If start is not None, return only heads reachable from start.
3064 3074 If closed is True, return heads that are marked as closed as well.
3065 3075 '''
3066 3076 if branch is None:
3067 3077 branch = self[None].branch()
3068 3078 branches = self.branchmap()
3069 3079 if not branches.hasbranch(branch):
3070 3080 return []
3071 3081 # the cache returns heads ordered lowest to highest
3072 3082 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
3073 3083 if start is not None:
3074 3084 # filter out the heads that cannot be reached from startrev
3075 3085 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
3076 3086 bheads = [h for h in bheads if h in fbheads]
3077 3087 return bheads
3078 3088
3079 3089 def branches(self, nodes):
3080 3090 if not nodes:
3081 3091 nodes = [self.changelog.tip()]
3082 3092 b = []
3083 3093 for n in nodes:
3084 3094 t = n
3085 3095 while True:
3086 3096 p = self.changelog.parents(n)
3087 3097 if p[1] != nullid or p[0] == nullid:
3088 3098 b.append((t, n, p[0], p[1]))
3089 3099 break
3090 3100 n = p[0]
3091 3101 return b
3092 3102
3093 3103 def between(self, pairs):
3094 3104 r = []
3095 3105
3096 3106 for top, bottom in pairs:
3097 3107 n, l, i = top, [], 0
3098 3108 f = 1
3099 3109
3100 3110 while n != bottom and n != nullid:
3101 3111 p = self.changelog.parents(n)[0]
3102 3112 if i == f:
3103 3113 l.append(n)
3104 3114 f = f * 2
3105 3115 n = p
3106 3116 i += 1
3107 3117
3108 3118 r.append(l)
3109 3119
3110 3120 return r
3111 3121
3112 3122 def checkpush(self, pushop):
3113 3123 """Extensions can override this function if additional checks have
3114 3124 to be performed before pushing, or call it if they override push
3115 3125 command.
3116 3126 """
3117 3127
3118 3128 @unfilteredpropertycache
3119 3129 def prepushoutgoinghooks(self):
3120 3130 """Return util.hooks consists of a pushop with repo, remote, outgoing
3121 3131 methods, which are called before pushing changesets.
3122 3132 """
3123 3133 return util.hooks()
3124 3134
3125 3135 def pushkey(self, namespace, key, old, new):
3126 3136 try:
3127 3137 tr = self.currenttransaction()
3128 3138 hookargs = {}
3129 3139 if tr is not None:
3130 3140 hookargs.update(tr.hookargs)
3131 3141 hookargs = pycompat.strkwargs(hookargs)
3132 3142 hookargs['namespace'] = namespace
3133 3143 hookargs['key'] = key
3134 3144 hookargs['old'] = old
3135 3145 hookargs['new'] = new
3136 3146 self.hook(b'prepushkey', throw=True, **hookargs)
3137 3147 except error.HookAbort as exc:
3138 3148 self.ui.write_err(_(b"pushkey-abort: %s\n") % exc)
3139 3149 if exc.hint:
3140 3150 self.ui.write_err(_(b"(%s)\n") % exc.hint)
3141 3151 return False
3142 3152 self.ui.debug(b'pushing key for "%s:%s"\n' % (namespace, key))
3143 3153 ret = pushkey.push(self, namespace, key, old, new)
3144 3154
3145 3155 def runhook(unused_success):
3146 3156 self.hook(
3147 3157 b'pushkey',
3148 3158 namespace=namespace,
3149 3159 key=key,
3150 3160 old=old,
3151 3161 new=new,
3152 3162 ret=ret,
3153 3163 )
3154 3164
3155 3165 self._afterlock(runhook)
3156 3166 return ret
3157 3167
3158 3168 def listkeys(self, namespace):
3159 3169 self.hook(b'prelistkeys', throw=True, namespace=namespace)
3160 3170 self.ui.debug(b'listing keys for "%s"\n' % namespace)
3161 3171 values = pushkey.list(self, namespace)
3162 3172 self.hook(b'listkeys', namespace=namespace, values=values)
3163 3173 return values
3164 3174
3165 3175 def debugwireargs(self, one, two, three=None, four=None, five=None):
3166 3176 '''used to test argument passing over the wire'''
3167 3177 return b"%s %s %s %s %s" % (
3168 3178 one,
3169 3179 two,
3170 3180 pycompat.bytestr(three),
3171 3181 pycompat.bytestr(four),
3172 3182 pycompat.bytestr(five),
3173 3183 )
3174 3184
3175 3185 def savecommitmessage(self, text):
3176 3186 fp = self.vfs(b'last-message.txt', b'wb')
3177 3187 try:
3178 3188 fp.write(text)
3179 3189 finally:
3180 3190 fp.close()
3181 3191 return self.pathto(fp.name[len(self.root) + 1 :])
3182 3192
3183 3193
3184 3194 # used to avoid circular references so destructors work
3185 3195 def aftertrans(files):
3186 3196 renamefiles = [tuple(t) for t in files]
3187 3197
3188 3198 def a():
3189 3199 for vfs, src, dest in renamefiles:
3190 3200 # if src and dest refer to a same file, vfs.rename is a no-op,
3191 3201 # leaving both src and dest on disk. delete dest to make sure
3192 3202 # the rename couldn't be such a no-op.
3193 3203 vfs.tryunlink(dest)
3194 3204 try:
3195 3205 vfs.rename(src, dest)
3196 3206 except OSError: # journal file does not yet exist
3197 3207 pass
3198 3208
3199 3209 return a
3200 3210
3201 3211
3202 3212 def undoname(fn):
3203 3213 base, name = os.path.split(fn)
3204 3214 assert name.startswith(b'journal')
3205 3215 return os.path.join(base, name.replace(b'journal', b'undo', 1))
3206 3216
3207 3217
3208 3218 def instance(ui, path, create, intents=None, createopts=None):
3209 3219 localpath = util.urllocalpath(path)
3210 3220 if create:
3211 3221 createrepository(ui, localpath, createopts=createopts)
3212 3222
3213 3223 return makelocalrepository(ui, localpath, intents=intents)
3214 3224
3215 3225
3216 3226 def islocal(path):
3217 3227 return True
3218 3228
3219 3229
3220 3230 def defaultcreateopts(ui, createopts=None):
3221 3231 """Populate the default creation options for a repository.
3222 3232
3223 3233 A dictionary of explicitly requested creation options can be passed
3224 3234 in. Missing keys will be populated.
3225 3235 """
3226 3236 createopts = dict(createopts or {})
3227 3237
3228 3238 if b'backend' not in createopts:
3229 3239 # experimental config: storage.new-repo-backend
3230 3240 createopts[b'backend'] = ui.config(b'storage', b'new-repo-backend')
3231 3241
3232 3242 return createopts
3233 3243
3234 3244
3235 3245 def newreporequirements(ui, createopts):
3236 3246 """Determine the set of requirements for a new local repository.
3237 3247
3238 3248 Extensions can wrap this function to specify custom requirements for
3239 3249 new repositories.
3240 3250 """
3241 3251 # If the repo is being created from a shared repository, we copy
3242 3252 # its requirements.
3243 3253 if b'sharedrepo' in createopts:
3244 3254 requirements = set(createopts[b'sharedrepo'].requirements)
3245 3255 if createopts.get(b'sharedrelative'):
3246 3256 requirements.add(requirementsmod.RELATIVE_SHARED_REQUIREMENT)
3247 3257 else:
3248 3258 requirements.add(requirementsmod.SHARED_REQUIREMENT)
3249 3259
3250 3260 return requirements
3251 3261
3252 3262 if b'backend' not in createopts:
3253 3263 raise error.ProgrammingError(
3254 3264 b'backend key not present in createopts; '
3255 3265 b'was defaultcreateopts() called?'
3256 3266 )
3257 3267
3258 3268 if createopts[b'backend'] != b'revlogv1':
3259 3269 raise error.Abort(
3260 3270 _(
3261 3271 b'unable to determine repository requirements for '
3262 3272 b'storage backend: %s'
3263 3273 )
3264 3274 % createopts[b'backend']
3265 3275 )
3266 3276
3267 3277 requirements = {b'revlogv1'}
3268 3278 if ui.configbool(b'format', b'usestore'):
3269 3279 requirements.add(b'store')
3270 3280 if ui.configbool(b'format', b'usefncache'):
3271 3281 requirements.add(b'fncache')
3272 3282 if ui.configbool(b'format', b'dotencode'):
3273 3283 requirements.add(b'dotencode')
3274 3284
3275 3285 compengines = ui.configlist(b'format', b'revlog-compression')
3276 3286 for compengine in compengines:
3277 3287 if compengine in util.compengines:
3278 3288 break
3279 3289 else:
3280 3290 raise error.Abort(
3281 3291 _(
3282 3292 b'compression engines %s defined by '
3283 3293 b'format.revlog-compression not available'
3284 3294 )
3285 3295 % b', '.join(b'"%s"' % e for e in compengines),
3286 3296 hint=_(
3287 3297 b'run "hg debuginstall" to list available '
3288 3298 b'compression engines'
3289 3299 ),
3290 3300 )
3291 3301
3292 3302 # zlib is the historical default and doesn't need an explicit requirement.
3293 3303 if compengine == b'zstd':
3294 3304 requirements.add(b'revlog-compression-zstd')
3295 3305 elif compengine != b'zlib':
3296 3306 requirements.add(b'exp-compression-%s' % compengine)
3297 3307
3298 3308 if scmutil.gdinitconfig(ui):
3299 3309 requirements.add(b'generaldelta')
3300 3310 if ui.configbool(b'format', b'sparse-revlog'):
3301 3311 requirements.add(requirementsmod.SPARSEREVLOG_REQUIREMENT)
3302 3312
3303 3313 # experimental config: format.exp-use-side-data
3304 3314 if ui.configbool(b'format', b'exp-use-side-data'):
3305 3315 requirements.add(requirementsmod.SIDEDATA_REQUIREMENT)
3306 3316 # experimental config: format.exp-use-copies-side-data-changeset
3307 3317 if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'):
3308 3318 requirements.add(requirementsmod.SIDEDATA_REQUIREMENT)
3309 3319 requirements.add(requirementsmod.COPIESSDC_REQUIREMENT)
3310 3320 if ui.configbool(b'experimental', b'treemanifest'):
3311 3321 requirements.add(requirementsmod.TREEMANIFEST_REQUIREMENT)
3312 3322
3313 3323 revlogv2 = ui.config(b'experimental', b'revlogv2')
3314 3324 if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
3315 3325 requirements.remove(b'revlogv1')
3316 3326 # generaldelta is implied by revlogv2.
3317 3327 requirements.discard(b'generaldelta')
3318 3328 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3319 3329 # experimental config: format.internal-phase
3320 3330 if ui.configbool(b'format', b'internal-phase'):
3321 3331 requirements.add(requirementsmod.INTERNAL_PHASE_REQUIREMENT)
3322 3332
3323 3333 if createopts.get(b'narrowfiles'):
3324 3334 requirements.add(requirementsmod.NARROW_REQUIREMENT)
3325 3335
3326 3336 if createopts.get(b'lfs'):
3327 3337 requirements.add(b'lfs')
3328 3338
3329 3339 if ui.configbool(b'format', b'bookmarks-in-store'):
3330 3340 requirements.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3331 3341
3332 3342 if ui.configbool(b'format', b'use-persistent-nodemap'):
3333 3343 requirements.add(requirementsmod.NODEMAP_REQUIREMENT)
3334 3344
3335 3345 # if share-safe is enabled, let's create the new repository with the new
3336 3346 # requirement
3337 3347 if ui.configbool(b'format', b'exp-share-safe'):
3338 3348 requirements.add(requirementsmod.SHARESAFE_REQUIREMENT)
3339 3349
3340 3350 return requirements
3341 3351
3342 3352
3343 3353 def checkrequirementscompat(ui, requirements):
3344 3354 """ Checks compatibility of repository requirements enabled and disabled.
3345 3355
3346 3356 Returns a set of requirements which needs to be dropped because dependend
3347 3357 requirements are not enabled. Also warns users about it """
3348 3358
3349 3359 dropped = set()
3350 3360
3351 3361 if b'store' not in requirements:
3352 3362 if bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT in requirements:
3353 3363 ui.warn(
3354 3364 _(
3355 3365 b'ignoring enabled \'format.bookmarks-in-store\' config '
3356 3366 b'beacuse it is incompatible with disabled '
3357 3367 b'\'format.usestore\' config\n'
3358 3368 )
3359 3369 )
3360 3370 dropped.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3361 3371
3362 3372 if (
3363 3373 requirementsmod.SHARED_REQUIREMENT in requirements
3364 3374 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
3365 3375 ):
3366 3376 raise error.Abort(
3367 3377 _(
3368 3378 b"cannot create shared repository as source was created"
3369 3379 b" with 'format.usestore' config disabled"
3370 3380 )
3371 3381 )
3372 3382
3373 3383 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
3374 3384 ui.warn(
3375 3385 _(
3376 3386 b"ignoring enabled 'format.exp-share-safe' config because "
3377 3387 b"it is incompatible with disabled 'format.usestore'"
3378 3388 b" config\n"
3379 3389 )
3380 3390 )
3381 3391 dropped.add(requirementsmod.SHARESAFE_REQUIREMENT)
3382 3392
3383 3393 return dropped
3384 3394
3385 3395
3386 3396 def filterknowncreateopts(ui, createopts):
3387 3397 """Filters a dict of repo creation options against options that are known.
3388 3398
3389 3399 Receives a dict of repo creation options and returns a dict of those
3390 3400 options that we don't know how to handle.
3391 3401
3392 3402 This function is called as part of repository creation. If the
3393 3403 returned dict contains any items, repository creation will not
3394 3404 be allowed, as it means there was a request to create a repository
3395 3405 with options not recognized by loaded code.
3396 3406
3397 3407 Extensions can wrap this function to filter out creation options
3398 3408 they know how to handle.
3399 3409 """
3400 3410 known = {
3401 3411 b'backend',
3402 3412 b'lfs',
3403 3413 b'narrowfiles',
3404 3414 b'sharedrepo',
3405 3415 b'sharedrelative',
3406 3416 b'shareditems',
3407 3417 b'shallowfilestore',
3408 3418 }
3409 3419
3410 3420 return {k: v for k, v in createopts.items() if k not in known}
3411 3421
3412 3422
3413 3423 def createrepository(ui, path, createopts=None):
3414 3424 """Create a new repository in a vfs.
3415 3425
3416 3426 ``path`` path to the new repo's working directory.
3417 3427 ``createopts`` options for the new repository.
3418 3428
3419 3429 The following keys for ``createopts`` are recognized:
3420 3430
3421 3431 backend
3422 3432 The storage backend to use.
3423 3433 lfs
3424 3434 Repository will be created with ``lfs`` requirement. The lfs extension
3425 3435 will automatically be loaded when the repository is accessed.
3426 3436 narrowfiles
3427 3437 Set up repository to support narrow file storage.
3428 3438 sharedrepo
3429 3439 Repository object from which storage should be shared.
3430 3440 sharedrelative
3431 3441 Boolean indicating if the path to the shared repo should be
3432 3442 stored as relative. By default, the pointer to the "parent" repo
3433 3443 is stored as an absolute path.
3434 3444 shareditems
3435 3445 Set of items to share to the new repository (in addition to storage).
3436 3446 shallowfilestore
3437 3447 Indicates that storage for files should be shallow (not all ancestor
3438 3448 revisions are known).
3439 3449 """
3440 3450 createopts = defaultcreateopts(ui, createopts=createopts)
3441 3451
3442 3452 unknownopts = filterknowncreateopts(ui, createopts)
3443 3453
3444 3454 if not isinstance(unknownopts, dict):
3445 3455 raise error.ProgrammingError(
3446 3456 b'filterknowncreateopts() did not return a dict'
3447 3457 )
3448 3458
3449 3459 if unknownopts:
3450 3460 raise error.Abort(
3451 3461 _(
3452 3462 b'unable to create repository because of unknown '
3453 3463 b'creation option: %s'
3454 3464 )
3455 3465 % b', '.join(sorted(unknownopts)),
3456 3466 hint=_(b'is a required extension not loaded?'),
3457 3467 )
3458 3468
3459 3469 requirements = newreporequirements(ui, createopts=createopts)
3460 3470 requirements -= checkrequirementscompat(ui, requirements)
3461 3471
3462 3472 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3463 3473
3464 3474 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3465 3475 if hgvfs.exists():
3466 3476 raise error.RepoError(_(b'repository %s already exists') % path)
3467 3477
3468 3478 if b'sharedrepo' in createopts:
3469 3479 sharedpath = createopts[b'sharedrepo'].sharedpath
3470 3480
3471 3481 if createopts.get(b'sharedrelative'):
3472 3482 try:
3473 3483 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3474 3484 except (IOError, ValueError) as e:
3475 3485 # ValueError is raised on Windows if the drive letters differ
3476 3486 # on each path.
3477 3487 raise error.Abort(
3478 3488 _(b'cannot calculate relative path'),
3479 3489 hint=stringutil.forcebytestr(e),
3480 3490 )
3481 3491
3482 3492 if not wdirvfs.exists():
3483 3493 wdirvfs.makedirs()
3484 3494
3485 3495 hgvfs.makedir(notindexed=True)
3486 3496 if b'sharedrepo' not in createopts:
3487 3497 hgvfs.mkdir(b'cache')
3488 3498 hgvfs.mkdir(b'wcache')
3489 3499
3490 3500 if b'store' in requirements and b'sharedrepo' not in createopts:
3491 3501 hgvfs.mkdir(b'store')
3492 3502
3493 3503 # We create an invalid changelog outside the store so very old
3494 3504 # Mercurial versions (which didn't know about the requirements
3495 3505 # file) encounter an error on reading the changelog. This
3496 3506 # effectively locks out old clients and prevents them from
3497 3507 # mucking with a repo in an unknown format.
3498 3508 #
3499 3509 # The revlog header has version 2, which won't be recognized by
3500 3510 # such old clients.
3501 3511 hgvfs.append(
3502 3512 b'00changelog.i',
3503 3513 b'\0\0\0\2 dummy changelog to prevent using the old repo '
3504 3514 b'layout',
3505 3515 )
3506 3516
3507 3517 # Filter the requirements into working copy and store ones
3508 3518 wcreq, storereq = scmutil.filterrequirements(requirements)
3509 3519 # write working copy ones
3510 3520 scmutil.writerequires(hgvfs, wcreq)
3511 3521 # If there are store requirements and the current repository
3512 3522 # is not a shared one, write stored requirements
3513 3523 # For new shared repository, we don't need to write the store
3514 3524 # requirements as they are already present in store requires
3515 3525 if storereq and b'sharedrepo' not in createopts:
3516 3526 storevfs = vfsmod.vfs(hgvfs.join(b'store'), cacheaudited=True)
3517 3527 scmutil.writerequires(storevfs, storereq)
3518 3528
3519 3529 # Write out file telling readers where to find the shared store.
3520 3530 if b'sharedrepo' in createopts:
3521 3531 hgvfs.write(b'sharedpath', sharedpath)
3522 3532
3523 3533 if createopts.get(b'shareditems'):
3524 3534 shared = b'\n'.join(sorted(createopts[b'shareditems'])) + b'\n'
3525 3535 hgvfs.write(b'shared', shared)
3526 3536
3527 3537
3528 3538 def poisonrepository(repo):
3529 3539 """Poison a repository instance so it can no longer be used."""
3530 3540 # Perform any cleanup on the instance.
3531 3541 repo.close()
3532 3542
3533 3543 # Our strategy is to replace the type of the object with one that
3534 3544 # has all attribute lookups result in error.
3535 3545 #
3536 3546 # But we have to allow the close() method because some constructors
3537 3547 # of repos call close() on repo references.
3538 3548 class poisonedrepository(object):
3539 3549 def __getattribute__(self, item):
3540 3550 if item == 'close':
3541 3551 return object.__getattribute__(self, item)
3542 3552
3543 3553 raise error.ProgrammingError(
3544 3554 b'repo instances should not be used after unshare'
3545 3555 )
3546 3556
3547 3557 def close(self):
3548 3558 pass
3549 3559
3550 3560 # We may have a repoview, which intercepts __setattr__. So be sure
3551 3561 # we operate at the lowest level possible.
3552 3562 object.__setattr__(repo, '__class__', poisonedrepository)
@@ -1,437 +1,437 b''
1 1 Show all commands except debug commands
2 2 $ hg debugcomplete
3 3 abort
4 4 add
5 5 addremove
6 6 annotate
7 7 archive
8 8 backout
9 9 bisect
10 10 bookmarks
11 11 branch
12 12 branches
13 13 bundle
14 14 cat
15 15 clone
16 16 commit
17 17 config
18 18 continue
19 19 copy
20 20 diff
21 21 export
22 22 files
23 23 forget
24 24 graft
25 25 grep
26 26 heads
27 27 help
28 28 identify
29 29 import
30 30 incoming
31 31 init
32 32 locate
33 33 log
34 34 manifest
35 35 merge
36 36 outgoing
37 37 parents
38 38 paths
39 39 phase
40 40 pull
41 41 push
42 42 recover
43 43 remove
44 44 rename
45 45 resolve
46 46 revert
47 47 rollback
48 48 root
49 49 serve
50 50 shelve
51 51 status
52 52 summary
53 53 tag
54 54 tags
55 55 tip
56 56 unbundle
57 57 unshelve
58 58 update
59 59 verify
60 60 version
61 61
62 62 Show all commands that start with "a"
63 63 $ hg debugcomplete a
64 64 abort
65 65 add
66 66 addremove
67 67 annotate
68 68 archive
69 69
70 70 Do not show debug commands if there are other candidates
71 71 $ hg debugcomplete d
72 72 diff
73 73
74 74 Show debug commands if there are no other candidates
75 75 $ hg debugcomplete debug
76 76 debugancestor
77 77 debugantivirusrunning
78 78 debugapplystreamclonebundle
79 79 debugbackupbundle
80 80 debugbuilddag
81 81 debugbundle
82 82 debugcapabilities
83 83 debugchangedfiles
84 84 debugcheckstate
85 85 debugcolor
86 86 debugcommands
87 87 debugcomplete
88 88 debugconfig
89 89 debugcreatestreamclonebundle
90 90 debugdag
91 91 debugdata
92 92 debugdate
93 93 debugdeltachain
94 94 debugdirstate
95 95 debugdiscovery
96 96 debugdownload
97 97 debugextensions
98 98 debugfileset
99 99 debugformat
100 100 debugfsinfo
101 101 debuggetbundle
102 102 debugignore
103 103 debugindex
104 104 debugindexdot
105 105 debugindexstats
106 106 debuginstall
107 107 debugknown
108 108 debuglabelcomplete
109 109 debuglocks
110 110 debugmanifestfulltextcache
111 111 debugmergestate
112 112 debugnamecomplete
113 113 debugnodemap
114 114 debugobsolete
115 115 debugp1copies
116 116 debugp2copies
117 117 debugpathcomplete
118 118 debugpathcopies
119 119 debugpeer
120 120 debugpickmergetool
121 121 debugpushkey
122 122 debugpvec
123 123 debugrebuilddirstate
124 124 debugrebuildfncache
125 125 debugrename
126 126 debugrequires
127 127 debugrevlog
128 128 debugrevlogindex
129 129 debugrevspec
130 130 debugserve
131 131 debugsetparents
132 132 debugsidedata
133 133 debugssl
134 134 debugsub
135 135 debugsuccessorssets
136 136 debugtagscache
137 137 debugtemplate
138 138 debuguigetpass
139 139 debuguiprompt
140 140 debugupdatecaches
141 141 debugupgraderepo
142 142 debugwalk
143 143 debugwhyunstable
144 144 debugwireargs
145 145 debugwireproto
146 146
147 147 Do not show the alias of a debug command if there are other candidates
148 148 (this should hide rawcommit)
149 149 $ hg debugcomplete r
150 150 recover
151 151 remove
152 152 rename
153 153 resolve
154 154 revert
155 155 rollback
156 156 root
157 157 Show the alias of a debug command if there are no other candidates
158 158 $ hg debugcomplete rawc
159 159
160 160
161 161 Show the global options
162 162 $ hg debugcomplete --options | sort
163 163 --color
164 164 --config
165 165 --cwd
166 166 --debug
167 167 --debugger
168 168 --encoding
169 169 --encodingmode
170 170 --help
171 171 --hidden
172 172 --noninteractive
173 173 --pager
174 174 --profile
175 175 --quiet
176 176 --repository
177 177 --time
178 178 --traceback
179 179 --verbose
180 180 --version
181 181 -R
182 182 -h
183 183 -q
184 184 -v
185 185 -y
186 186
187 187 Show the options for the "serve" command
188 188 $ hg debugcomplete --options serve | sort
189 189 --accesslog
190 190 --address
191 191 --certificate
192 192 --cmdserver
193 193 --color
194 194 --config
195 195 --cwd
196 196 --daemon
197 197 --daemon-postexec
198 198 --debug
199 199 --debugger
200 200 --encoding
201 201 --encodingmode
202 202 --errorlog
203 203 --help
204 204 --hidden
205 205 --ipv6
206 206 --name
207 207 --noninteractive
208 208 --pager
209 209 --pid-file
210 210 --port
211 211 --prefix
212 212 --print-url
213 213 --profile
214 214 --quiet
215 215 --repository
216 216 --stdio
217 217 --style
218 218 --subrepos
219 219 --templates
220 220 --time
221 221 --traceback
222 222 --verbose
223 223 --version
224 224 --web-conf
225 225 -6
226 226 -A
227 227 -E
228 228 -R
229 229 -S
230 230 -a
231 231 -d
232 232 -h
233 233 -n
234 234 -p
235 235 -q
236 236 -t
237 237 -v
238 238 -y
239 239
240 240 Show an error if we use --options with an ambiguous abbreviation
241 241 $ hg debugcomplete --options s
242 242 hg: command 's' is ambiguous:
243 243 serve shelve showconfig status summary
244 244 [255]
245 245
246 246 Show all commands + options
247 247 $ hg debugcommands
248 248 abort: dry-run
249 249 add: include, exclude, subrepos, dry-run
250 250 addremove: similarity, subrepos, include, exclude, dry-run
251 251 annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, skip, ignore-all-space, ignore-space-change, ignore-blank-lines, ignore-space-at-eol, include, exclude, template
252 252 archive: no-decode, prefix, rev, type, subrepos, include, exclude
253 253 backout: merge, commit, no-commit, parent, rev, edit, tool, include, exclude, message, logfile, date, user
254 254 bisect: reset, good, bad, skip, extend, command, noupdate
255 255 bookmarks: force, rev, delete, rename, inactive, list, template
256 256 branch: force, clean, rev
257 257 branches: active, closed, rev, template
258 258 bundle: force, rev, branch, base, all, type, ssh, remotecmd, insecure
259 259 cat: output, rev, decode, include, exclude, template
260 260 clone: noupdate, updaterev, rev, branch, pull, uncompressed, stream, ssh, remotecmd, insecure
261 261 commit: addremove, close-branch, amend, secret, edit, force-close-branch, interactive, include, exclude, message, logfile, date, user, subrepos
262 config: untrusted, edit, local, shared, global, template
262 config: untrusted, edit, local, shared, non-shared, global, template
263 263 continue: dry-run
264 264 copy: forget, after, at-rev, force, include, exclude, dry-run
265 265 debugancestor:
266 266 debugantivirusrunning:
267 267 debugapplystreamclonebundle:
268 268 debugbackupbundle: recover, patch, git, limit, no-merges, stat, graph, style, template
269 269 debugbuilddag: mergeable-file, overwritten-file, new-file
270 270 debugbundle: all, part-type, spec
271 271 debugcapabilities:
272 272 debugchangedfiles:
273 273 debugcheckstate:
274 274 debugcolor: style
275 275 debugcommands:
276 276 debugcomplete: options
277 277 debugcreatestreamclonebundle:
278 278 debugdag: tags, branches, dots, spaces
279 279 debugdata: changelog, manifest, dir
280 280 debugdate: extended
281 281 debugdeltachain: changelog, manifest, dir, template
282 282 debugdirstate: nodates, dates, datesort
283 283 debugdiscovery: old, nonheads, rev, seed, ssh, remotecmd, insecure
284 284 debugdownload: output
285 285 debugextensions: template
286 286 debugfileset: rev, all-files, show-matcher, show-stage
287 287 debugformat: template
288 288 debugfsinfo:
289 289 debuggetbundle: head, common, type
290 290 debugignore:
291 291 debugindex: changelog, manifest, dir, template
292 292 debugindexdot: changelog, manifest, dir
293 293 debugindexstats:
294 294 debuginstall: template
295 295 debugknown:
296 296 debuglabelcomplete:
297 297 debuglocks: force-lock, force-wlock, set-lock, set-wlock
298 298 debugmanifestfulltextcache: clear, add
299 299 debugmergestate: style, template
300 300 debugnamecomplete:
301 301 debugnodemap: dump-new, dump-disk, check, metadata
302 302 debugobsolete: flags, record-parents, rev, exclusive, index, delete, date, user, template
303 303 debugp1copies: rev
304 304 debugp2copies: rev
305 305 debugpathcomplete: full, normal, added, removed
306 306 debugpathcopies: include, exclude
307 307 debugpeer:
308 308 debugpickmergetool: rev, changedelete, include, exclude, tool
309 309 debugpushkey:
310 310 debugpvec:
311 311 debugrebuilddirstate: rev, minimal
312 312 debugrebuildfncache:
313 313 debugrename: rev
314 314 debugrequires:
315 315 debugrevlog: changelog, manifest, dir, dump
316 316 debugrevlogindex: changelog, manifest, dir, format
317 317 debugrevspec: optimize, show-revs, show-set, show-stage, no-optimized, verify-optimized
318 318 debugserve: sshstdio, logiofd, logiofile
319 319 debugsetparents:
320 320 debugsidedata: changelog, manifest, dir
321 321 debugssl:
322 322 debugsub: rev
323 323 debugsuccessorssets: closest
324 324 debugtagscache:
325 325 debugtemplate: rev, define
326 326 debuguigetpass: prompt
327 327 debuguiprompt: prompt
328 328 debugupdatecaches:
329 329 debugupgraderepo: optimize, run, backup, changelog, manifest
330 330 debugwalk: include, exclude
331 331 debugwhyunstable:
332 332 debugwireargs: three, four, five, ssh, remotecmd, insecure
333 333 debugwireproto: localssh, peer, noreadstderr, nologhandshake, ssh, remotecmd, insecure
334 334 diff: rev, change, text, git, binary, nodates, noprefix, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, ignore-space-at-eol, unified, stat, root, include, exclude, subrepos
335 335 export: bookmark, output, switch-parent, rev, text, git, binary, nodates, template
336 336 files: rev, print0, include, exclude, template, subrepos
337 337 forget: interactive, include, exclude, dry-run
338 338 graft: rev, base, continue, stop, abort, edit, log, no-commit, force, currentdate, currentuser, date, user, tool, dry-run
339 339 grep: print0, all, diff, text, follow, ignore-case, files-with-matches, line-number, rev, all-files, user, date, template, include, exclude
340 340 heads: rev, topo, active, closed, style, template
341 341 help: extension, command, keyword, system
342 342 identify: rev, num, id, branch, tags, bookmarks, ssh, remotecmd, insecure, template
343 343 import: strip, base, secret, edit, force, no-commit, bypass, partial, exact, prefix, import-branch, message, logfile, date, user, similarity
344 344 incoming: force, newest-first, bundle, rev, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
345 345 init: ssh, remotecmd, insecure
346 346 locate: rev, print0, fullpath, include, exclude
347 347 log: follow, follow-first, date, copies, keyword, rev, line-range, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
348 348 manifest: rev, all, template
349 349 merge: force, rev, preview, abort, tool
350 350 outgoing: force, rev, newest-first, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
351 351 parents: rev, style, template
352 352 paths: template
353 353 phase: public, draft, secret, force, rev
354 354 pull: update, force, confirm, rev, bookmark, branch, ssh, remotecmd, insecure
355 355 push: force, rev, bookmark, branch, new-branch, pushvars, publish, ssh, remotecmd, insecure
356 356 recover: verify
357 357 remove: after, force, subrepos, include, exclude, dry-run
358 358 rename: after, at-rev, force, include, exclude, dry-run
359 359 resolve: all, list, mark, unmark, no-status, re-merge, tool, include, exclude, template
360 360 revert: all, date, rev, no-backup, interactive, include, exclude, dry-run
361 361 rollback: dry-run, force
362 362 root: template
363 363 serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate, print-url, subrepos
364 364 shelve: addremove, unknown, cleanup, date, delete, edit, keep, list, message, name, patch, interactive, stat, include, exclude
365 365 status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, terse, copies, print0, rev, change, include, exclude, subrepos, template
366 366 summary: remote
367 367 tag: force, local, rev, remove, edit, message, date, user
368 368 tags: template
369 369 tip: patch, git, style, template
370 370 unbundle: update
371 371 unshelve: abort, continue, interactive, keep, name, tool, date
372 372 update: clean, check, merge, date, rev, tool
373 373 verify: full
374 374 version: template
375 375
376 376 $ hg init a
377 377 $ cd a
378 378 $ echo fee > fee
379 379 $ hg ci -q -Amfee
380 380 $ hg tag fee
381 381 $ mkdir fie
382 382 $ echo dead > fie/dead
383 383 $ echo live > fie/live
384 384 $ hg bookmark fo
385 385 $ hg branch -q fie
386 386 $ hg ci -q -Amfie
387 387 $ echo fo > fo
388 388 $ hg branch -qf default
389 389 $ hg ci -q -Amfo
390 390 $ echo Fum > Fum
391 391 $ hg ci -q -AmFum
392 392 $ hg bookmark Fum
393 393
394 394 Test debugpathcomplete
395 395
396 396 $ hg debugpathcomplete f
397 397 fee
398 398 fie
399 399 fo
400 400 $ hg debugpathcomplete -f f
401 401 fee
402 402 fie/dead
403 403 fie/live
404 404 fo
405 405
406 406 $ hg rm Fum
407 407 $ hg debugpathcomplete -r F
408 408 Fum
409 409
410 410 Test debugnamecomplete
411 411
412 412 $ hg debugnamecomplete
413 413 Fum
414 414 default
415 415 fee
416 416 fie
417 417 fo
418 418 tip
419 419 $ hg debugnamecomplete f
420 420 fee
421 421 fie
422 422 fo
423 423
424 424 Test debuglabelcomplete, a deprecated name for debugnamecomplete that is still
425 425 used for completions in some shells.
426 426
427 427 $ hg debuglabelcomplete
428 428 Fum
429 429 default
430 430 fee
431 431 fie
432 432 fo
433 433 tip
434 434 $ hg debuglabelcomplete f
435 435 fee
436 436 fie
437 437 fo
@@ -1,275 +1,294 b''
1 1 setup
2 2
3 3 $ cat >> $HGRCPATH <<EOF
4 4 > [extensions]
5 5 > share =
6 6 > [format]
7 7 > exp-share-safe = True
8 8 > EOF
9 9
10 10 prepare source repo
11 11
12 12 $ hg init source
13 13 $ cd source
14 14 $ cat .hg/requires
15 15 exp-sharesafe
16 16 $ cat .hg/store/requires
17 17 dotencode
18 18 fncache
19 19 generaldelta
20 20 revlogv1
21 21 sparserevlog
22 22 store
23 23 $ hg debugrequirements
24 24 dotencode
25 25 exp-sharesafe
26 26 fncache
27 27 generaldelta
28 28 revlogv1
29 29 sparserevlog
30 30 store
31 31
32 32 $ echo a > a
33 33 $ hg ci -Aqm "added a"
34 34 $ echo b > b
35 35 $ hg ci -Aqm "added b"
36 36
37 37 $ HGEDITOR=cat hg config --shared
38 38 abort: repository is not shared; can't use --shared
39 39 [255]
40 40 $ cd ..
41 41
42 42 Create a shared repo and check the requirements are shared and read correctly
43 43 $ hg share source shared1
44 44 updating working directory
45 45 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
46 46 $ cd shared1
47 47 $ cat .hg/requires
48 48 exp-sharesafe
49 49 shared
50 50
51 51 $ hg debugrequirements -R ../source
52 52 dotencode
53 53 exp-sharesafe
54 54 fncache
55 55 generaldelta
56 56 revlogv1
57 57 sparserevlog
58 58 store
59 59
60 60 $ hg debugrequirements
61 61 dotencode
62 62 exp-sharesafe
63 63 fncache
64 64 generaldelta
65 65 revlogv1
66 66 shared
67 67 sparserevlog
68 68 store
69 69
70 70 $ echo c > c
71 71 $ hg ci -Aqm "added c"
72 72
73 73 Check that config of the source repository is also loaded
74 74
75 75 $ hg showconfig ui.curses
76 76 [1]
77 77
78 78 $ echo "[ui]" >> ../source/.hg/hgrc
79 79 $ echo "curses=true" >> ../source/.hg/hgrc
80 80
81 81 $ hg showconfig ui.curses
82 82 true
83 83
84 84 Test that extensions of source repository are also loaded
85 85
86 86 $ hg debugextensions
87 87 share
88 88 $ hg extdiff -p echo
89 89 hg: unknown command 'extdiff'
90 90 'extdiff' is provided by the following extension:
91 91
92 92 extdiff command to allow external programs to compare revisions
93 93
94 94 (use 'hg help extensions' for information on enabling extensions)
95 95 [255]
96 96
97 97 $ echo "[extensions]" >> ../source/.hg/hgrc
98 98 $ echo "extdiff=" >> ../source/.hg/hgrc
99 99
100 100 $ hg debugextensions -R ../source
101 101 extdiff
102 102 share
103 103 $ hg extdiff -R ../source -p echo
104 104
105 105 BROKEN: the command below will not work if config of shared source is not loaded
106 106 on dispatch but debugextensions says that extension
107 107 is loaded
108 108 $ hg debugextensions
109 109 extdiff
110 110 share
111 111
112 112 $ hg extdiff -p echo
113 113
114 114 However, local .hg/hgrc should override the config set by share source
115 115
116 116 $ echo "[ui]" >> .hg/hgrc
117 117 $ echo "curses=false" >> .hg/hgrc
118 118
119 119 $ hg showconfig ui.curses
120 120 false
121 121
122 122 $ HGEDITOR=cat hg config --shared
123 123 [ui]
124 124 curses=true
125 125 [extensions]
126 126 extdiff=
127 127
128 128 $ HGEDITOR=cat hg config --local
129 129 [ui]
130 130 curses=false
131 131
132 132 Testing that hooks set in source repository also runs in shared repo
133 133
134 134 $ cd ../source
135 135 $ cat <<EOF >> .hg/hgrc
136 136 > [extensions]
137 137 > hooklib=
138 138 > [hooks]
139 139 > pretxnchangegroup.reject_merge_commits = \
140 140 > python:hgext.hooklib.reject_merge_commits.hook
141 141 > EOF
142 142
143 143 $ cd ..
144 144 $ hg clone source cloned
145 145 updating to branch default
146 146 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
147 147 $ cd cloned
148 148 $ hg up 0
149 149 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
150 150 $ echo bar > bar
151 151 $ hg ci -Aqm "added bar"
152 152 $ hg merge
153 153 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
154 154 (branch merge, don't forget to commit)
155 155 $ hg ci -m "merge commit"
156 156
157 157 $ hg push ../source
158 158 pushing to ../source
159 159 searching for changes
160 160 adding changesets
161 161 adding manifests
162 162 adding file changes
163 163 error: pretxnchangegroup.reject_merge_commits hook failed: bcde3522682d rejected as merge on the same branch. Please consider rebase.
164 164 transaction abort!
165 165 rollback completed
166 166 abort: bcde3522682d rejected as merge on the same branch. Please consider rebase.
167 167 [255]
168 168
169 169 $ hg push ../shared1
170 170 pushing to ../shared1
171 171 searching for changes
172 172 adding changesets
173 173 adding manifests
174 174 adding file changes
175 175 error: pretxnchangegroup.reject_merge_commits hook failed: bcde3522682d rejected as merge on the same branch. Please consider rebase.
176 176 transaction abort!
177 177 rollback completed
178 178 abort: bcde3522682d rejected as merge on the same branch. Please consider rebase.
179 179 [255]
180 180
181 181 Test that if share source config is untrusted, we dont read it
182 182
183 183 $ cd ../shared1
184 184
185 185 $ cat << EOF > $TESTTMP/untrusted.py
186 186 > from mercurial import scmutil, util
187 187 > def uisetup(ui):
188 188 > class untrustedui(ui.__class__):
189 189 > def _trusted(self, fp, f):
190 190 > if util.normpath(fp.name).endswith(b'source/.hg/hgrc'):
191 191 > return False
192 192 > return super(untrustedui, self)._trusted(fp, f)
193 193 > ui.__class__ = untrustedui
194 194 > EOF
195 195
196 196 $ hg showconfig hooks
197 197 hooks.pretxnchangegroup.reject_merge_commits=python:hgext.hooklib.reject_merge_commits.hook
198 198
199 199 $ hg showconfig hooks --config extensions.untrusted=$TESTTMP/untrusted.py
200 200 [1]
201 201
202 202 Update the source repository format and check that shared repo works
203 203
204 204 $ cd ../source
205 205
206 206 Disable zstd related tests because its not present on pure version
207 207 #if zstd
208 208 $ echo "[format]" >> .hg/hgrc
209 209 $ echo "revlog-compression=zstd" >> .hg/hgrc
210 210
211 211 $ hg debugupgraderepo --run -q
212 212 upgrade will perform the following actions:
213 213
214 214 requirements
215 215 preserved: dotencode, exp-sharesafe, fncache, generaldelta, revlogv1, sparserevlog, store
216 216 added: revlog-compression-zstd
217 217
218 218 $ hg log -r .
219 219 changeset: 1:5f6d8a4bf34a
220 220 user: test
221 221 date: Thu Jan 01 00:00:00 1970 +0000
222 222 summary: added b
223 223
224 224 #endif
225 225 $ echo "[format]" >> .hg/hgrc
226 226 $ echo "use-persistent-nodemap=True" >> .hg/hgrc
227 227
228 228 $ hg debugupgraderepo --run -q -R ../shared1
229 229 abort: cannot upgrade repository; unsupported source requirement: shared
230 230 [255]
231 231
232 232 $ hg debugupgraderepo --run -q
233 233 upgrade will perform the following actions:
234 234
235 235 requirements
236 236 preserved: dotencode, exp-sharesafe, fncache, generaldelta, revlogv1, sparserevlog, store (no-zstd !)
237 237 preserved: dotencode, exp-sharesafe, fncache, generaldelta, revlog-compression-zstd, revlogv1, sparserevlog, store (zstd !)
238 238 added: persistent-nodemap
239 239
240 240 $ hg log -r .
241 241 changeset: 1:5f6d8a4bf34a
242 242 user: test
243 243 date: Thu Jan 01 00:00:00 1970 +0000
244 244 summary: added b
245 245
246 246
247 247 Shared one should work
248 248 $ cd ../shared1
249 249 $ hg log -r .
250 250 changeset: 2:155349b645be
251 251 tag: tip
252 252 user: test
253 253 date: Thu Jan 01 00:00:00 1970 +0000
254 254 summary: added c
255 255
256
257 Testing that nonsharedrc is loaded for source and not shared
258
259 $ cd ../source
260 $ touch .hg/hgrc-not-shared
261 $ echo "[ui]" >> .hg/hgrc-not-shared
262 $ echo "traceback=true" >> .hg/hgrc-not-shared
263
264 $ hg showconfig ui.traceback
265 true
266
267 $ HGEDITOR=cat hg config --non-shared
268 [ui]
269 traceback=true
270
271 $ cd ../shared1
272 $ hg showconfig ui.traceback
273 [1]
274
256 275 Unsharing works
257 276
258 277 $ hg unshare
259 278
260 279 Test that source config is added to the shared one after unshare, and the config
261 280 of current repo is still respected over the config which came from source config
262 281 $ cd ../cloned
263 282 $ hg push ../shared1
264 283 pushing to ../shared1
265 284 searching for changes
266 285 adding changesets
267 286 adding manifests
268 287 adding file changes
269 288 error: pretxnchangegroup.reject_merge_commits hook failed: bcde3522682d rejected as merge on the same branch. Please consider rebase.
270 289 transaction abort!
271 290 rollback completed
272 291 abort: bcde3522682d rejected as merge on the same branch. Please consider rebase.
273 292 [255]
274 293 $ hg showconfig ui.curses -R ../shared1
275 294 false
General Comments 0
You need to be logged in to leave comments. Login now