##// END OF EJS Templates
branch: migrate `opts` to native kwargs
Matt Harbison -
r51721:13ad1b2a default
parent child Browse files
Show More
@@ -1,4126 +1,4126 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 Olivia Mackall <olivia@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
9 9 import copy as copymod
10 10 import errno
11 11 import functools
12 12 import os
13 13 import re
14 14
15 15 from typing import (
16 16 Any,
17 17 AnyStr,
18 18 Dict,
19 19 Iterable,
20 20 Optional,
21 21 cast,
22 22 )
23 23
24 24 from .i18n import _
25 25 from .node import (
26 26 hex,
27 27 nullrev,
28 28 short,
29 29 )
30 30 from .pycompat import (
31 31 getattr,
32 32 open,
33 33 setattr,
34 34 )
35 35 from .thirdparty import attr
36 36
37 37 from . import (
38 38 bookmarks,
39 39 changelog,
40 40 copies,
41 41 crecord as crecordmod,
42 42 encoding,
43 43 error,
44 44 formatter,
45 45 logcmdutil,
46 46 match as matchmod,
47 47 merge as mergemod,
48 48 mergestate as mergestatemod,
49 49 mergeutil,
50 50 obsolete,
51 51 patch,
52 52 pathutil,
53 53 phases,
54 54 pycompat,
55 55 repair,
56 56 revlog,
57 57 rewriteutil,
58 58 scmutil,
59 59 state as statemod,
60 60 subrepoutil,
61 61 templatekw,
62 62 templater,
63 63 util,
64 64 vfs as vfsmod,
65 65 )
66 66
67 67 from .utils import (
68 68 dateutil,
69 69 stringutil,
70 70 )
71 71
72 72 from .revlogutils import (
73 73 constants as revlog_constants,
74 74 )
75 75
76 76 if pycompat.TYPE_CHECKING:
77 77 from . import (
78 78 ui as uimod,
79 79 )
80 80
81 81 stringio = util.stringio
82 82
83 83 # templates of common command options
84 84
85 85 dryrunopts = [
86 86 (b'n', b'dry-run', None, _(b'do not perform actions, just print output')),
87 87 ]
88 88
89 89 confirmopts = [
90 90 (b'', b'confirm', None, _(b'ask before applying actions')),
91 91 ]
92 92
93 93 remoteopts = [
94 94 (b'e', b'ssh', b'', _(b'specify ssh command to use'), _(b'CMD')),
95 95 (
96 96 b'',
97 97 b'remotecmd',
98 98 b'',
99 99 _(b'specify hg command to run on the remote side'),
100 100 _(b'CMD'),
101 101 ),
102 102 (
103 103 b'',
104 104 b'insecure',
105 105 None,
106 106 _(b'do not verify server certificate (ignoring web.cacerts config)'),
107 107 ),
108 108 ]
109 109
110 110 walkopts = [
111 111 (
112 112 b'I',
113 113 b'include',
114 114 [],
115 115 _(b'include names matching the given patterns'),
116 116 _(b'PATTERN'),
117 117 ),
118 118 (
119 119 b'X',
120 120 b'exclude',
121 121 [],
122 122 _(b'exclude names matching the given patterns'),
123 123 _(b'PATTERN'),
124 124 ),
125 125 ]
126 126
127 127 commitopts = [
128 128 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
129 129 (b'l', b'logfile', b'', _(b'read commit message from file'), _(b'FILE')),
130 130 ]
131 131
132 132 commitopts2 = [
133 133 (
134 134 b'd',
135 135 b'date',
136 136 b'',
137 137 _(b'record the specified date as commit date'),
138 138 _(b'DATE'),
139 139 ),
140 140 (
141 141 b'u',
142 142 b'user',
143 143 b'',
144 144 _(b'record the specified user as committer'),
145 145 _(b'USER'),
146 146 ),
147 147 ]
148 148
149 149 commitopts3 = [
150 150 (b'D', b'currentdate', None, _(b'record the current date as commit date')),
151 151 (b'U', b'currentuser', None, _(b'record the current user as committer')),
152 152 ]
153 153
154 154 formatteropts = [
155 155 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
156 156 ]
157 157
158 158 templateopts = [
159 159 (
160 160 b'',
161 161 b'style',
162 162 b'',
163 163 _(b'display using template map file (DEPRECATED)'),
164 164 _(b'STYLE'),
165 165 ),
166 166 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
167 167 ]
168 168
169 169 logopts = [
170 170 (b'p', b'patch', None, _(b'show patch')),
171 171 (b'g', b'git', None, _(b'use git extended diff format')),
172 172 (b'l', b'limit', b'', _(b'limit number of changes displayed'), _(b'NUM')),
173 173 (b'M', b'no-merges', None, _(b'do not show merges')),
174 174 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
175 175 (b'G', b'graph', None, _(b"show the revision DAG")),
176 176 ] + templateopts
177 177
178 178 diffopts = [
179 179 (b'a', b'text', None, _(b'treat all files as text')),
180 180 (
181 181 b'g',
182 182 b'git',
183 183 None,
184 184 _(b'use git extended diff format (DEFAULT: diff.git)'),
185 185 ),
186 186 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
187 187 (b'', b'nodates', None, _(b'omit dates from diff headers')),
188 188 ]
189 189
190 190 diffwsopts = [
191 191 (
192 192 b'w',
193 193 b'ignore-all-space',
194 194 None,
195 195 _(b'ignore white space when comparing lines'),
196 196 ),
197 197 (
198 198 b'b',
199 199 b'ignore-space-change',
200 200 None,
201 201 _(b'ignore changes in the amount of white space'),
202 202 ),
203 203 (
204 204 b'B',
205 205 b'ignore-blank-lines',
206 206 None,
207 207 _(b'ignore changes whose lines are all blank'),
208 208 ),
209 209 (
210 210 b'Z',
211 211 b'ignore-space-at-eol',
212 212 None,
213 213 _(b'ignore changes in whitespace at EOL'),
214 214 ),
215 215 ]
216 216
217 217 diffopts2 = (
218 218 [
219 219 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
220 220 (
221 221 b'p',
222 222 b'show-function',
223 223 None,
224 224 _(
225 225 b'show which function each change is in (DEFAULT: diff.showfunc)'
226 226 ),
227 227 ),
228 228 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
229 229 ]
230 230 + diffwsopts
231 231 + [
232 232 (
233 233 b'U',
234 234 b'unified',
235 235 b'',
236 236 _(b'number of lines of context to show'),
237 237 _(b'NUM'),
238 238 ),
239 239 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
240 240 (
241 241 b'',
242 242 b'root',
243 243 b'',
244 244 _(b'produce diffs relative to subdirectory'),
245 245 _(b'DIR'),
246 246 ),
247 247 ]
248 248 )
249 249
250 250 mergetoolopts = [
251 251 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
252 252 ]
253 253
254 254 similarityopts = [
255 255 (
256 256 b's',
257 257 b'similarity',
258 258 b'',
259 259 _(b'guess renamed files by similarity (0<=s<=100)'),
260 260 _(b'SIMILARITY'),
261 261 )
262 262 ]
263 263
264 264 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
265 265
266 266 debugrevlogopts = [
267 267 (b'c', b'changelog', False, _(b'open changelog')),
268 268 (b'm', b'manifest', False, _(b'open manifest')),
269 269 (b'', b'dir', b'', _(b'open directory manifest')),
270 270 ]
271 271
272 272 # special string such that everything below this line will be ingored in the
273 273 # editor text
274 274 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
275 275
276 276
277 277 def check_at_most_one_arg(
278 278 opts: Dict[AnyStr, Any],
279 279 *args: AnyStr,
280 280 ) -> Optional[AnyStr]:
281 281 """abort if more than one of the arguments are in opts
282 282
283 283 Returns the unique argument or None if none of them were specified.
284 284 """
285 285
286 286 def to_display(name: AnyStr) -> bytes:
287 287 return pycompat.sysbytes(name).replace(b'_', b'-')
288 288
289 289 previous = None
290 290 for x in args:
291 291 if opts.get(x):
292 292 if previous:
293 293 raise error.InputError(
294 294 _(b'cannot specify both --%s and --%s')
295 295 % (to_display(previous), to_display(x))
296 296 )
297 297 previous = x
298 298 return previous
299 299
300 300
301 301 def check_incompatible_arguments(
302 302 opts: Dict[AnyStr, Any],
303 303 first: AnyStr,
304 304 others: Iterable[AnyStr],
305 305 ) -> None:
306 306 """abort if the first argument is given along with any of the others
307 307
308 308 Unlike check_at_most_one_arg(), `others` are not mutually exclusive
309 309 among themselves, and they're passed as a single collection.
310 310 """
311 311 for other in others:
312 312 check_at_most_one_arg(opts, first, other)
313 313
314 314
315 315 def resolve_commit_options(ui: "uimod.ui", opts: Dict[str, Any]) -> bool:
316 316 """modify commit options dict to handle related options
317 317
318 318 The return value indicates that ``rewrite.update-timestamp`` is the reason
319 319 the ``date`` option is set.
320 320 """
321 321 check_at_most_one_arg(opts, 'date', 'currentdate')
322 322 check_at_most_one_arg(opts, 'user', 'currentuser')
323 323
324 324 datemaydiffer = False # date-only change should be ignored?
325 325
326 326 if opts.get('currentdate'):
327 327 opts['date'] = b'%d %d' % dateutil.makedate()
328 328 elif (
329 329 not opts.get('date')
330 330 and ui.configbool(b'rewrite', b'update-timestamp')
331 331 and opts.get('currentdate') is None
332 332 ):
333 333 opts['date'] = b'%d %d' % dateutil.makedate()
334 334 datemaydiffer = True
335 335
336 336 if opts.get('currentuser'):
337 337 opts['user'] = ui.username()
338 338
339 339 return datemaydiffer
340 340
341 341
342 342 def check_note_size(opts: Dict[str, Any]) -> None:
343 343 """make sure note is of valid format"""
344 344
345 345 note = opts.get('note')
346 346 if not note:
347 347 return
348 348
349 349 if len(note) > 255:
350 350 raise error.InputError(_(b"cannot store a note of more than 255 bytes"))
351 351 if b'\n' in note:
352 352 raise error.InputError(_(b"note cannot contain a newline"))
353 353
354 354
355 355 def ishunk(x):
356 356 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
357 357 return isinstance(x, hunkclasses)
358 358
359 359
360 360 def isheader(x):
361 361 headerclasses = (crecordmod.uiheader, patch.header)
362 362 return isinstance(x, headerclasses)
363 363
364 364
365 365 def newandmodified(chunks):
366 366 newlyaddedandmodifiedfiles = set()
367 367 alsorestore = set()
368 368 for chunk in chunks:
369 369 if isheader(chunk) and chunk.isnewfile():
370 370 newlyaddedandmodifiedfiles.add(chunk.filename())
371 371 alsorestore.update(set(chunk.files()) - {chunk.filename()})
372 372 return newlyaddedandmodifiedfiles, alsorestore
373 373
374 374
375 375 def parsealiases(cmd):
376 376 base_aliases = cmd.split(b"|")
377 377 all_aliases = set(base_aliases)
378 378 extra_aliases = []
379 379 for alias in base_aliases:
380 380 if b'-' in alias:
381 381 folded_alias = alias.replace(b'-', b'')
382 382 if folded_alias not in all_aliases:
383 383 all_aliases.add(folded_alias)
384 384 extra_aliases.append(folded_alias)
385 385 base_aliases.extend(extra_aliases)
386 386 return base_aliases
387 387
388 388
389 389 def setupwrapcolorwrite(ui):
390 390 # wrap ui.write so diff output can be labeled/colorized
391 391 def wrapwrite(orig, *args, **kw):
392 392 label = kw.pop('label', b'')
393 393 for chunk, l in patch.difflabel(lambda: args):
394 394 orig(chunk, label=label + l)
395 395
396 396 oldwrite = ui.write
397 397
398 398 def wrap(*args, **kwargs):
399 399 return wrapwrite(oldwrite, *args, **kwargs)
400 400
401 401 setattr(ui, 'write', wrap)
402 402 return oldwrite
403 403
404 404
405 405 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
406 406 try:
407 407 if usecurses:
408 408 if testfile:
409 409 recordfn = crecordmod.testdecorator(
410 410 testfile, crecordmod.testchunkselector
411 411 )
412 412 else:
413 413 recordfn = crecordmod.chunkselector
414 414
415 415 return crecordmod.filterpatch(
416 416 ui, originalhunks, recordfn, operation
417 417 )
418 418 except crecordmod.fallbackerror as e:
419 419 ui.warn(b'%s\n' % e)
420 420 ui.warn(_(b'falling back to text mode\n'))
421 421
422 422 return patch.filterpatch(ui, originalhunks, match, operation)
423 423
424 424
425 425 def recordfilter(ui, originalhunks, match, operation=None):
426 426 """Prompts the user to filter the originalhunks and return a list of
427 427 selected hunks.
428 428 *operation* is used for to build ui messages to indicate the user what
429 429 kind of filtering they are doing: reverting, committing, shelving, etc.
430 430 (see patch.filterpatch).
431 431 """
432 432 usecurses = crecordmod.checkcurses(ui)
433 433 testfile = ui.config(b'experimental', b'crecordtest')
434 434 oldwrite = setupwrapcolorwrite(ui)
435 435 try:
436 436 newchunks, newopts = filterchunks(
437 437 ui, originalhunks, usecurses, testfile, match, operation
438 438 )
439 439 finally:
440 440 ui.write = oldwrite
441 441 return newchunks, newopts
442 442
443 443
444 444 def _record(
445 445 ui,
446 446 repo,
447 447 message,
448 448 match,
449 449 opts,
450 450 commitfunc,
451 451 backupall,
452 452 filterfn,
453 453 pats,
454 454 ):
455 455 """This is generic record driver.
456 456
457 457 Its job is to interactively filter local changes, and
458 458 accordingly prepare working directory into a state in which the
459 459 job can be delegated to a non-interactive commit command such as
460 460 'commit' or 'qrefresh'.
461 461
462 462 After the actual job is done by non-interactive command, the
463 463 working directory is restored to its original state.
464 464
465 465 In the end we'll record interesting changes, and everything else
466 466 will be left in place, so the user can continue working.
467 467 """
468 468 assert repo.currentwlock() is not None
469 469 if not opts.get(b'interactive-unshelve'):
470 470 checkunfinished(repo, commit=True)
471 471 wctx = repo[None]
472 472 merge = len(wctx.parents()) > 1
473 473 if merge:
474 474 raise error.InputError(
475 475 _(b'cannot partially commit a merge ' b'(use "hg commit" instead)')
476 476 )
477 477
478 478 def fail(f, msg):
479 479 raise error.InputError(b'%s: %s' % (f, msg))
480 480
481 481 force = opts.get(b'force')
482 482 if not force:
483 483 match = matchmod.badmatch(match, fail)
484 484
485 485 status = repo.status(match=match)
486 486
487 487 overrides = {(b'ui', b'commitsubrepos'): True}
488 488
489 489 with repo.ui.configoverride(overrides, b'record'):
490 490 # subrepoutil.precommit() modifies the status
491 491 tmpstatus = scmutil.status(
492 492 copymod.copy(status.modified),
493 493 copymod.copy(status.added),
494 494 copymod.copy(status.removed),
495 495 copymod.copy(status.deleted),
496 496 copymod.copy(status.unknown),
497 497 copymod.copy(status.ignored),
498 498 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
499 499 )
500 500
501 501 # Force allows -X subrepo to skip the subrepo.
502 502 subs, commitsubs, newstate = subrepoutil.precommit(
503 503 repo.ui, wctx, tmpstatus, match, force=True
504 504 )
505 505 for s in subs:
506 506 if s in commitsubs:
507 507 dirtyreason = wctx.sub(s).dirtyreason(True)
508 508 raise error.Abort(dirtyreason)
509 509
510 510 if not force:
511 511 repo.checkcommitpatterns(wctx, match, status, fail)
512 512 diffopts = patch.difffeatureopts(
513 513 ui,
514 514 opts=opts,
515 515 whitespace=True,
516 516 section=b'commands',
517 517 configprefix=b'commit.interactive.',
518 518 )
519 519 diffopts.nodates = True
520 520 diffopts.git = True
521 521 diffopts.showfunc = True
522 522 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
523 523 original_headers = patch.parsepatch(originaldiff)
524 524 match = scmutil.match(repo[None], pats)
525 525
526 526 # 1. filter patch, since we are intending to apply subset of it
527 527 try:
528 528 chunks, newopts = filterfn(ui, original_headers, match)
529 529 except error.PatchParseError as err:
530 530 raise error.InputError(_(b'error parsing patch: %s') % err)
531 531 except error.PatchApplicationError as err:
532 532 raise error.StateError(_(b'error applying patch: %s') % err)
533 533 opts.update(newopts)
534 534
535 535 # We need to keep a backup of files that have been newly added and
536 536 # modified during the recording process because there is a previous
537 537 # version without the edit in the workdir. We also will need to restore
538 538 # files that were the sources of renames so that the patch application
539 539 # works.
540 540 newlyaddedandmodifiedfiles, alsorestore = newandmodified(chunks)
541 541 contenders = set()
542 542 for h in chunks:
543 543 if isheader(h):
544 544 contenders.update(set(h.files()))
545 545
546 546 changed = status.modified + status.added + status.removed
547 547 newfiles = [f for f in changed if f in contenders]
548 548 if not newfiles:
549 549 ui.status(_(b'no changes to record\n'))
550 550 return 0
551 551
552 552 modified = set(status.modified)
553 553
554 554 # 2. backup changed files, so we can restore them in the end
555 555
556 556 if backupall:
557 557 tobackup = changed
558 558 else:
559 559 tobackup = [
560 560 f
561 561 for f in newfiles
562 562 if f in modified or f in newlyaddedandmodifiedfiles
563 563 ]
564 564 backups = {}
565 565 if tobackup:
566 566 backupdir = repo.vfs.join(b'record-backups')
567 567 try:
568 568 os.mkdir(backupdir)
569 569 except FileExistsError:
570 570 pass
571 571 try:
572 572 # backup continues
573 573 for f in tobackup:
574 574 fd, tmpname = pycompat.mkstemp(
575 575 prefix=os.path.basename(f) + b'.', dir=backupdir
576 576 )
577 577 os.close(fd)
578 578 ui.debug(b'backup %r as %r\n' % (f, tmpname))
579 579 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
580 580 backups[f] = tmpname
581 581
582 582 fp = stringio()
583 583 for c in chunks:
584 584 fname = c.filename()
585 585 if fname in backups:
586 586 c.write(fp)
587 587 dopatch = fp.tell()
588 588 fp.seek(0)
589 589
590 590 # 2.5 optionally review / modify patch in text editor
591 591 if opts.get(b'review', False):
592 592 patchtext = (
593 593 crecordmod.diffhelptext + crecordmod.patchhelptext + fp.read()
594 594 )
595 595 reviewedpatch = ui.edit(
596 596 patchtext, b"", action=b"diff", repopath=repo.path
597 597 )
598 598 fp.truncate(0)
599 599 fp.write(reviewedpatch)
600 600 fp.seek(0)
601 601
602 602 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
603 603 # 3a. apply filtered patch to clean repo (clean)
604 604 if backups:
605 605 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
606 606 mergemod.revert_to(repo[b'.'], matcher=m)
607 607
608 608 # 3b. (apply)
609 609 if dopatch:
610 610 try:
611 611 ui.debug(b'applying patch\n')
612 612 ui.debug(fp.getvalue())
613 613 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
614 614 except error.PatchParseError as err:
615 615 raise error.InputError(pycompat.bytestr(err))
616 616 except error.PatchApplicationError as err:
617 617 raise error.StateError(pycompat.bytestr(err))
618 618 del fp
619 619
620 620 # 4. We prepared working directory according to filtered
621 621 # patch. Now is the time to delegate the job to
622 622 # commit/qrefresh or the like!
623 623
624 624 # Make all of the pathnames absolute.
625 625 newfiles = [repo.wjoin(nf) for nf in newfiles]
626 626 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
627 627 finally:
628 628 # 5. finally restore backed-up files
629 629 try:
630 630 dirstate = repo.dirstate
631 631 for realname, tmpname in backups.items():
632 632 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
633 633
634 634 if dirstate.get_entry(realname).maybe_clean:
635 635 # without normallookup, restoring timestamp
636 636 # may cause partially committed files
637 637 # to be treated as unmodified
638 638
639 639 # XXX-PENDINGCHANGE: We should clarify the context in
640 640 # which this function is called to make sure it
641 641 # already called within a `pendingchange`, However we
642 642 # are taking a shortcut here in order to be able to
643 643 # quickly deprecated the older API.
644 644 with dirstate.changing_parents(repo):
645 645 dirstate.update_file(
646 646 realname,
647 647 p1_tracked=True,
648 648 wc_tracked=True,
649 649 possibly_dirty=True,
650 650 )
651 651
652 652 # copystat=True here and above are a hack to trick any
653 653 # editors that have f open that we haven't modified them.
654 654 #
655 655 # Also note that this racy as an editor could notice the
656 656 # file's mtime before we've finished writing it.
657 657 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
658 658 os.unlink(tmpname)
659 659 if tobackup:
660 660 os.rmdir(backupdir)
661 661 except OSError:
662 662 pass
663 663
664 664
665 665 def dorecord(
666 666 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
667 667 ):
668 668 opts = pycompat.byteskwargs(opts)
669 669 if not ui.interactive():
670 670 if cmdsuggest:
671 671 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
672 672 else:
673 673 msg = _(b'running non-interactively')
674 674 raise error.InputError(msg)
675 675
676 676 # make sure username is set before going interactive
677 677 if not opts.get(b'user'):
678 678 ui.username() # raise exception, username not provided
679 679
680 680 func = functools.partial(
681 681 _record,
682 682 commitfunc=commitfunc,
683 683 backupall=backupall,
684 684 filterfn=filterfn,
685 685 pats=pats,
686 686 )
687 687
688 688 return commit(ui, repo, func, pats, opts)
689 689
690 690
691 691 class dirnode:
692 692 """
693 693 Represent a directory in user working copy with information required for
694 694 the purpose of tersing its status.
695 695
696 696 path is the path to the directory, without a trailing '/'
697 697
698 698 statuses is a set of statuses of all files in this directory (this includes
699 699 all the files in all the subdirectories too)
700 700
701 701 files is a list of files which are direct child of this directory
702 702
703 703 subdirs is a dictionary of sub-directory name as the key and it's own
704 704 dirnode object as the value
705 705 """
706 706
707 707 def __init__(self, dirpath):
708 708 self.path = dirpath
709 709 self.statuses = set()
710 710 self.files = []
711 711 self.subdirs = {}
712 712
713 713 def _addfileindir(self, filename, status):
714 714 """Add a file in this directory as a direct child."""
715 715 self.files.append((filename, status))
716 716
717 717 def addfile(self, filename, status):
718 718 """
719 719 Add a file to this directory or to its direct parent directory.
720 720
721 721 If the file is not direct child of this directory, we traverse to the
722 722 directory of which this file is a direct child of and add the file
723 723 there.
724 724 """
725 725
726 726 # the filename contains a path separator, it means it's not the direct
727 727 # child of this directory
728 728 if b'/' in filename:
729 729 subdir, filep = filename.split(b'/', 1)
730 730
731 731 # does the dirnode object for subdir exists
732 732 if subdir not in self.subdirs:
733 733 subdirpath = pathutil.join(self.path, subdir)
734 734 self.subdirs[subdir] = dirnode(subdirpath)
735 735
736 736 # try adding the file in subdir
737 737 self.subdirs[subdir].addfile(filep, status)
738 738
739 739 else:
740 740 self._addfileindir(filename, status)
741 741
742 742 if status not in self.statuses:
743 743 self.statuses.add(status)
744 744
745 745 def iterfilepaths(self):
746 746 """Yield (status, path) for files directly under this directory."""
747 747 for f, st in self.files:
748 748 yield st, pathutil.join(self.path, f)
749 749
750 750 def tersewalk(self, terseargs):
751 751 """
752 752 Yield (status, path) obtained by processing the status of this
753 753 dirnode.
754 754
755 755 terseargs is the string of arguments passed by the user with `--terse`
756 756 flag.
757 757
758 758 Following are the cases which can happen:
759 759
760 760 1) All the files in the directory (including all the files in its
761 761 subdirectories) share the same status and the user has asked us to terse
762 762 that status. -> yield (status, dirpath). dirpath will end in '/'.
763 763
764 764 2) Otherwise, we do following:
765 765
766 766 a) Yield (status, filepath) for all the files which are in this
767 767 directory (only the ones in this directory, not the subdirs)
768 768
769 769 b) Recurse the function on all the subdirectories of this
770 770 directory
771 771 """
772 772
773 773 if len(self.statuses) == 1:
774 774 onlyst = self.statuses.pop()
775 775
776 776 # Making sure we terse only when the status abbreviation is
777 777 # passed as terse argument
778 778 if onlyst in terseargs:
779 779 yield onlyst, self.path + b'/'
780 780 return
781 781
782 782 # add the files to status list
783 783 for st, fpath in self.iterfilepaths():
784 784 yield st, fpath
785 785
786 786 # recurse on the subdirs
787 787 for dirobj in self.subdirs.values():
788 788 for st, fpath in dirobj.tersewalk(terseargs):
789 789 yield st, fpath
790 790
791 791
792 792 def tersedir(statuslist, terseargs):
793 793 """
794 794 Terse the status if all the files in a directory shares the same status.
795 795
796 796 statuslist is scmutil.status() object which contains a list of files for
797 797 each status.
798 798 terseargs is string which is passed by the user as the argument to `--terse`
799 799 flag.
800 800
801 801 The function makes a tree of objects of dirnode class, and at each node it
802 802 stores the information required to know whether we can terse a certain
803 803 directory or not.
804 804 """
805 805 # the order matters here as that is used to produce final list
806 806 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
807 807
808 808 # checking the argument validity
809 809 for s in pycompat.bytestr(terseargs):
810 810 if s not in allst:
811 811 raise error.InputError(_(b"'%s' not recognized") % s)
812 812
813 813 # creating a dirnode object for the root of the repo
814 814 rootobj = dirnode(b'')
815 815 pstatus = (
816 816 b'modified',
817 817 b'added',
818 818 b'deleted',
819 819 b'clean',
820 820 b'unknown',
821 821 b'ignored',
822 822 b'removed',
823 823 )
824 824
825 825 tersedict = {}
826 826 for attrname in pstatus:
827 827 statuschar = attrname[0:1]
828 828 for f in getattr(statuslist, attrname):
829 829 rootobj.addfile(f, statuschar)
830 830 tersedict[statuschar] = []
831 831
832 832 # we won't be tersing the root dir, so add files in it
833 833 for st, fpath in rootobj.iterfilepaths():
834 834 tersedict[st].append(fpath)
835 835
836 836 # process each sub-directory and build tersedict
837 837 for subdir in rootobj.subdirs.values():
838 838 for st, f in subdir.tersewalk(terseargs):
839 839 tersedict[st].append(f)
840 840
841 841 tersedlist = []
842 842 for st in allst:
843 843 tersedict[st].sort()
844 844 tersedlist.append(tersedict[st])
845 845
846 846 return scmutil.status(*tersedlist)
847 847
848 848
849 849 def _commentlines(raw):
850 850 '''Surround lineswith a comment char and a new line'''
851 851 lines = raw.splitlines()
852 852 commentedlines = [b'# %s' % line for line in lines]
853 853 return b'\n'.join(commentedlines) + b'\n'
854 854
855 855
856 856 @attr.s(frozen=True)
857 857 class morestatus:
858 858 repo = attr.ib()
859 859 unfinishedop = attr.ib()
860 860 unfinishedmsg = attr.ib()
861 861 activemerge = attr.ib()
862 862 unresolvedpaths = attr.ib()
863 863 _formattedpaths = attr.ib(init=False, default=set())
864 864 _label = b'status.morestatus'
865 865
866 866 def formatfile(self, path, fm):
867 867 self._formattedpaths.add(path)
868 868 if self.activemerge and path in self.unresolvedpaths:
869 869 fm.data(unresolved=True)
870 870
871 871 def formatfooter(self, fm):
872 872 if self.unfinishedop or self.unfinishedmsg:
873 873 fm.startitem()
874 874 fm.data(itemtype=b'morestatus')
875 875
876 876 if self.unfinishedop:
877 877 fm.data(unfinished=self.unfinishedop)
878 878 statemsg = (
879 879 _(b'The repository is in an unfinished *%s* state.')
880 880 % self.unfinishedop
881 881 )
882 882 fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label)
883 883 if self.unfinishedmsg:
884 884 fm.data(unfinishedmsg=self.unfinishedmsg)
885 885
886 886 # May also start new data items.
887 887 self._formatconflicts(fm)
888 888
889 889 if self.unfinishedmsg:
890 890 fm.plain(
891 891 b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label
892 892 )
893 893
894 894 def _formatconflicts(self, fm):
895 895 if not self.activemerge:
896 896 return
897 897
898 898 if self.unresolvedpaths:
899 899 mergeliststr = b'\n'.join(
900 900 [
901 901 b' %s'
902 902 % util.pathto(self.repo.root, encoding.getcwd(), path)
903 903 for path in self.unresolvedpaths
904 904 ]
905 905 )
906 906 msg = (
907 907 _(
908 908 b'''Unresolved merge conflicts:
909 909
910 910 %s
911 911
912 912 To mark files as resolved: hg resolve --mark FILE'''
913 913 )
914 914 % mergeliststr
915 915 )
916 916
917 917 # If any paths with unresolved conflicts were not previously
918 918 # formatted, output them now.
919 919 for f in self.unresolvedpaths:
920 920 if f in self._formattedpaths:
921 921 # Already output.
922 922 continue
923 923 fm.startitem()
924 924 fm.context(repo=self.repo)
925 925 # We can't claim to know the status of the file - it may just
926 926 # have been in one of the states that were not requested for
927 927 # display, so it could be anything.
928 928 fm.data(itemtype=b'file', path=f, unresolved=True)
929 929
930 930 else:
931 931 msg = _(b'No unresolved merge conflicts.')
932 932
933 933 fm.plain(b'%s\n' % _commentlines(msg), label=self._label)
934 934
935 935
936 936 def readmorestatus(repo):
937 937 """Returns a morestatus object if the repo has unfinished state."""
938 938 statetuple = statemod.getrepostate(repo)
939 939 mergestate = mergestatemod.mergestate.read(repo)
940 940 activemerge = mergestate.active()
941 941 if not statetuple and not activemerge:
942 942 return None
943 943
944 944 unfinishedop = unfinishedmsg = unresolved = None
945 945 if statetuple:
946 946 unfinishedop, unfinishedmsg = statetuple
947 947 if activemerge:
948 948 unresolved = sorted(mergestate.unresolved())
949 949 return morestatus(
950 950 repo, unfinishedop, unfinishedmsg, activemerge, unresolved
951 951 )
952 952
953 953
954 954 def findpossible(cmd, table, strict=False):
955 955 """
956 956 Return cmd -> (aliases, command table entry)
957 957 for each matching command.
958 958 Return debug commands (or their aliases) only if no normal command matches.
959 959 """
960 960 choice = {}
961 961 debugchoice = {}
962 962
963 963 if cmd in table:
964 964 # short-circuit exact matches, "log" alias beats "log|history"
965 965 keys = [cmd]
966 966 else:
967 967 keys = table.keys()
968 968
969 969 allcmds = []
970 970 for e in keys:
971 971 aliases = parsealiases(e)
972 972 allcmds.extend(aliases)
973 973 found = None
974 974 if cmd in aliases:
975 975 found = cmd
976 976 elif not strict:
977 977 for a in aliases:
978 978 if a.startswith(cmd):
979 979 found = a
980 980 break
981 981 if found is not None:
982 982 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
983 983 debugchoice[found] = (aliases, table[e])
984 984 else:
985 985 choice[found] = (aliases, table[e])
986 986
987 987 if not choice and debugchoice:
988 988 choice = debugchoice
989 989
990 990 return choice, allcmds
991 991
992 992
993 993 def findcmd(cmd, table, strict=True):
994 994 """Return (aliases, command table entry) for command string."""
995 995 choice, allcmds = findpossible(cmd, table, strict)
996 996
997 997 if cmd in choice:
998 998 return choice[cmd]
999 999
1000 1000 if len(choice) > 1:
1001 1001 clist = sorted(choice)
1002 1002 raise error.AmbiguousCommand(cmd, clist)
1003 1003
1004 1004 if choice:
1005 1005 return list(choice.values())[0]
1006 1006
1007 1007 raise error.UnknownCommand(cmd, allcmds)
1008 1008
1009 1009
1010 def changebranch(ui, repo, revs, label, opts):
1010 def changebranch(ui, repo, revs, label, **opts):
1011 1011 """Change the branch name of given revs to label"""
1012 1012
1013 1013 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
1014 1014 # abort in case of uncommitted merge or dirty wdir
1015 1015 bailifchanged(repo)
1016 1016 revs = logcmdutil.revrange(repo, revs)
1017 1017 if not revs:
1018 1018 raise error.InputError(b"empty revision set")
1019 1019 roots = repo.revs(b'roots(%ld)', revs)
1020 1020 if len(roots) > 1:
1021 1021 raise error.InputError(
1022 1022 _(b"cannot change branch of non-linear revisions")
1023 1023 )
1024 1024 rewriteutil.precheck(repo, revs, b'change branch of')
1025 1025
1026 1026 root = repo[roots.first()]
1027 1027 rpb = {parent.branch() for parent in root.parents()}
1028 1028 if (
1029 not opts.get(b'force')
1029 not opts.get('force')
1030 1030 and label not in rpb
1031 1031 and label in repo.branchmap()
1032 1032 ):
1033 1033 raise error.InputError(
1034 1034 _(b"a branch of the same name already exists")
1035 1035 )
1036 1036
1037 1037 # make sure only topological heads
1038 1038 if repo.revs(b'heads(%ld) - head()', revs):
1039 1039 raise error.InputError(
1040 1040 _(b"cannot change branch in middle of a stack")
1041 1041 )
1042 1042
1043 1043 replacements = {}
1044 1044 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
1045 1045 # mercurial.subrepo -> mercurial.cmdutil
1046 1046 from . import context
1047 1047
1048 1048 for rev in revs:
1049 1049 ctx = repo[rev]
1050 1050 oldbranch = ctx.branch()
1051 1051 # check if ctx has same branch
1052 1052 if oldbranch == label:
1053 1053 continue
1054 1054
1055 1055 def filectxfn(repo, newctx, path):
1056 1056 try:
1057 1057 return ctx[path]
1058 1058 except error.ManifestLookupError:
1059 1059 return None
1060 1060
1061 1061 ui.debug(
1062 1062 b"changing branch of '%s' from '%s' to '%s'\n"
1063 1063 % (hex(ctx.node()), oldbranch, label)
1064 1064 )
1065 1065 extra = ctx.extra()
1066 1066 extra[b'branch_change'] = hex(ctx.node())
1067 1067 # While changing branch of set of linear commits, make sure that
1068 1068 # we base our commits on new parent rather than old parent which
1069 1069 # was obsoleted while changing the branch
1070 1070 p1 = ctx.p1().node()
1071 1071 p2 = ctx.p2().node()
1072 1072 if p1 in replacements:
1073 1073 p1 = replacements[p1][0]
1074 1074 if p2 in replacements:
1075 1075 p2 = replacements[p2][0]
1076 1076
1077 1077 mc = context.memctx(
1078 1078 repo,
1079 1079 (p1, p2),
1080 1080 ctx.description(),
1081 1081 ctx.files(),
1082 1082 filectxfn,
1083 1083 user=ctx.user(),
1084 1084 date=ctx.date(),
1085 1085 extra=extra,
1086 1086 branch=label,
1087 1087 )
1088 1088
1089 1089 newnode = repo.commitctx(mc)
1090 1090 replacements[ctx.node()] = (newnode,)
1091 1091 ui.debug(b'new node id is %s\n' % hex(newnode))
1092 1092
1093 1093 # create obsmarkers and move bookmarks
1094 1094 scmutil.cleanupnodes(
1095 1095 repo, replacements, b'branch-change', fixphase=True
1096 1096 )
1097 1097
1098 1098 # move the working copy too
1099 1099 wctx = repo[None]
1100 1100 # in-progress merge is a bit too complex for now.
1101 1101 if len(wctx.parents()) == 1:
1102 1102 newid = replacements.get(wctx.p1().node())
1103 1103 if newid is not None:
1104 1104 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
1105 1105 # mercurial.cmdutil
1106 1106 from . import hg
1107 1107
1108 1108 hg.update(repo, newid[0], quietempty=True)
1109 1109
1110 1110 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
1111 1111
1112 1112
1113 1113 def findrepo(p):
1114 1114 while not os.path.isdir(os.path.join(p, b".hg")):
1115 1115 oldp, p = p, os.path.dirname(p)
1116 1116 if p == oldp:
1117 1117 return None
1118 1118
1119 1119 return p
1120 1120
1121 1121
1122 1122 def bailifchanged(repo, merge=True, hint=None):
1123 1123 """enforce the precondition that working directory must be clean.
1124 1124
1125 1125 'merge' can be set to false if a pending uncommitted merge should be
1126 1126 ignored (such as when 'update --check' runs).
1127 1127
1128 1128 'hint' is the usual hint given to Abort exception.
1129 1129 """
1130 1130
1131 1131 if merge and repo.dirstate.p2() != repo.nullid:
1132 1132 raise error.StateError(_(b'outstanding uncommitted merge'), hint=hint)
1133 1133 st = repo.status()
1134 1134 if st.modified or st.added or st.removed or st.deleted:
1135 1135 raise error.StateError(_(b'uncommitted changes'), hint=hint)
1136 1136 ctx = repo[None]
1137 1137 for s in sorted(ctx.substate):
1138 1138 ctx.sub(s).bailifchanged(hint=hint)
1139 1139
1140 1140
1141 1141 def logmessage(ui: "uimod.ui", opts: Dict[bytes, Any]) -> Optional[bytes]:
1142 1142 """get the log message according to -m and -l option"""
1143 1143
1144 1144 check_at_most_one_arg(opts, b'message', b'logfile')
1145 1145
1146 1146 message = cast(Optional[bytes], opts.get(b'message'))
1147 1147 logfile = opts.get(b'logfile')
1148 1148
1149 1149 if not message and logfile:
1150 1150 try:
1151 1151 if isstdiofilename(logfile):
1152 1152 message = ui.fin.read()
1153 1153 else:
1154 1154 message = b'\n'.join(util.readfile(logfile).splitlines())
1155 1155 except IOError as inst:
1156 1156 raise error.Abort(
1157 1157 _(b"can't read commit message '%s': %s")
1158 1158 % (logfile, encoding.strtolocal(inst.strerror))
1159 1159 )
1160 1160 return message
1161 1161
1162 1162
1163 1163 def mergeeditform(ctxorbool, baseformname):
1164 1164 """return appropriate editform name (referencing a committemplate)
1165 1165
1166 1166 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1167 1167 merging is committed.
1168 1168
1169 1169 This returns baseformname with '.merge' appended if it is a merge,
1170 1170 otherwise '.normal' is appended.
1171 1171 """
1172 1172 if isinstance(ctxorbool, bool):
1173 1173 if ctxorbool:
1174 1174 return baseformname + b".merge"
1175 1175 elif len(ctxorbool.parents()) > 1:
1176 1176 return baseformname + b".merge"
1177 1177
1178 1178 return baseformname + b".normal"
1179 1179
1180 1180
1181 1181 def getcommiteditor(
1182 1182 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1183 1183 ):
1184 1184 """get appropriate commit message editor according to '--edit' option
1185 1185
1186 1186 'finishdesc' is a function to be called with edited commit message
1187 1187 (= 'description' of the new changeset) just after editing, but
1188 1188 before checking empty-ness. It should return actual text to be
1189 1189 stored into history. This allows to change description before
1190 1190 storing.
1191 1191
1192 1192 'extramsg' is a extra message to be shown in the editor instead of
1193 1193 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1194 1194 is automatically added.
1195 1195
1196 1196 'editform' is a dot-separated list of names, to distinguish
1197 1197 the purpose of commit text editing.
1198 1198
1199 1199 'getcommiteditor' returns 'commitforceeditor' regardless of
1200 1200 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1201 1201 they are specific for usage in MQ.
1202 1202 """
1203 1203 if edit or finishdesc or extramsg:
1204 1204 return lambda r, c, s: commitforceeditor(
1205 1205 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1206 1206 )
1207 1207 elif editform:
1208 1208 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1209 1209 else:
1210 1210 return commiteditor
1211 1211
1212 1212
1213 1213 def _escapecommandtemplate(tmpl):
1214 1214 parts = []
1215 1215 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1216 1216 if typ == b'string':
1217 1217 parts.append(stringutil.escapestr(tmpl[start:end]))
1218 1218 else:
1219 1219 parts.append(tmpl[start:end])
1220 1220 return b''.join(parts)
1221 1221
1222 1222
1223 1223 def rendercommandtemplate(ui, tmpl, props):
1224 1224 r"""Expand a literal template 'tmpl' in a way suitable for command line
1225 1225
1226 1226 '\' in outermost string is not taken as an escape character because it
1227 1227 is a directory separator on Windows.
1228 1228
1229 1229 >>> from . import ui as uimod
1230 1230 >>> ui = uimod.ui()
1231 1231 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1232 1232 'c:\\foo'
1233 1233 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1234 1234 'c:{path}'
1235 1235 """
1236 1236 if not tmpl:
1237 1237 return tmpl
1238 1238 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1239 1239 return t.renderdefault(props)
1240 1240
1241 1241
1242 1242 def rendertemplate(ctx, tmpl, props=None):
1243 1243 """Expand a literal template 'tmpl' byte-string against one changeset
1244 1244
1245 1245 Each props item must be a stringify-able value or a callable returning
1246 1246 such value, i.e. no bare list nor dict should be passed.
1247 1247 """
1248 1248 repo = ctx.repo()
1249 1249 tres = formatter.templateresources(repo.ui, repo)
1250 1250 t = formatter.maketemplater(
1251 1251 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1252 1252 )
1253 1253 mapping = {b'ctx': ctx}
1254 1254 if props:
1255 1255 mapping.update(props)
1256 1256 return t.renderdefault(mapping)
1257 1257
1258 1258
1259 1259 def format_changeset_summary(ui, ctx, command=None, default_spec=None):
1260 1260 """Format a changeset summary (one line)."""
1261 1261 spec = None
1262 1262 if command:
1263 1263 spec = ui.config(
1264 1264 b'command-templates', b'oneline-summary.%s' % command, None
1265 1265 )
1266 1266 if not spec:
1267 1267 spec = ui.config(b'command-templates', b'oneline-summary')
1268 1268 if not spec:
1269 1269 spec = default_spec
1270 1270 if not spec:
1271 1271 spec = (
1272 1272 b'{separate(" ", '
1273 1273 b'label("oneline-summary.changeset", "{rev}:{node|short}")'
1274 1274 b', '
1275 1275 b'join(filter(namespaces % "{ifeq(namespace, "branches", "", join(names % "{label("oneline-summary.{namespace}", name)}", " "))}"), " ")'
1276 1276 b')} '
1277 1277 b'"{label("oneline-summary.desc", desc|firstline)}"'
1278 1278 )
1279 1279 text = rendertemplate(ctx, spec)
1280 1280 return text.split(b'\n')[0]
1281 1281
1282 1282
1283 1283 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1284 1284 r"""Convert old-style filename format string to template string
1285 1285
1286 1286 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1287 1287 'foo-{reporoot|basename}-{seqno}.patch'
1288 1288 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1289 1289 '{rev}{tags % "{tag}"}{node}'
1290 1290
1291 1291 '\' in outermost strings has to be escaped because it is a directory
1292 1292 separator on Windows:
1293 1293
1294 1294 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1295 1295 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1296 1296 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1297 1297 '\\\\\\\\foo\\\\bar.patch'
1298 1298 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1299 1299 '\\\\{tags % "{tag}"}'
1300 1300
1301 1301 but inner strings follow the template rules (i.e. '\' is taken as an
1302 1302 escape character):
1303 1303
1304 1304 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1305 1305 '{"c:\\tmp"}'
1306 1306 """
1307 1307 expander = {
1308 1308 b'H': b'{node}',
1309 1309 b'R': b'{rev}',
1310 1310 b'h': b'{node|short}',
1311 1311 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1312 1312 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1313 1313 b'%': b'%',
1314 1314 b'b': b'{reporoot|basename}',
1315 1315 }
1316 1316 if total is not None:
1317 1317 expander[b'N'] = b'{total}'
1318 1318 if seqno is not None:
1319 1319 expander[b'n'] = b'{seqno}'
1320 1320 if total is not None and seqno is not None:
1321 1321 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1322 1322 if pathname is not None:
1323 1323 expander[b's'] = b'{pathname|basename}'
1324 1324 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1325 1325 expander[b'p'] = b'{pathname}'
1326 1326
1327 1327 newname = []
1328 1328 for typ, start, end in templater.scantemplate(pat, raw=True):
1329 1329 if typ != b'string':
1330 1330 newname.append(pat[start:end])
1331 1331 continue
1332 1332 i = start
1333 1333 while i < end:
1334 1334 n = pat.find(b'%', i, end)
1335 1335 if n < 0:
1336 1336 newname.append(stringutil.escapestr(pat[i:end]))
1337 1337 break
1338 1338 newname.append(stringutil.escapestr(pat[i:n]))
1339 1339 if n + 2 > end:
1340 1340 raise error.Abort(
1341 1341 _(b"incomplete format spec in output filename")
1342 1342 )
1343 1343 c = pat[n + 1 : n + 2]
1344 1344 i = n + 2
1345 1345 try:
1346 1346 newname.append(expander[c])
1347 1347 except KeyError:
1348 1348 raise error.Abort(
1349 1349 _(b"invalid format spec '%%%s' in output filename") % c
1350 1350 )
1351 1351 return b''.join(newname)
1352 1352
1353 1353
1354 1354 def makefilename(ctx, pat, **props):
1355 1355 if not pat:
1356 1356 return pat
1357 1357 tmpl = _buildfntemplate(pat, **props)
1358 1358 # BUG: alias expansion shouldn't be made against template fragments
1359 1359 # rewritten from %-format strings, but we have no easy way to partially
1360 1360 # disable the expansion.
1361 1361 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1362 1362
1363 1363
1364 1364 def isstdiofilename(pat):
1365 1365 """True if the given pat looks like a filename denoting stdin/stdout"""
1366 1366 return not pat or pat == b'-'
1367 1367
1368 1368
1369 1369 class _unclosablefile:
1370 1370 def __init__(self, fp):
1371 1371 self._fp = fp
1372 1372
1373 1373 def close(self):
1374 1374 pass
1375 1375
1376 1376 def __iter__(self):
1377 1377 return iter(self._fp)
1378 1378
1379 1379 def __getattr__(self, attr):
1380 1380 return getattr(self._fp, attr)
1381 1381
1382 1382 def __enter__(self):
1383 1383 return self
1384 1384
1385 1385 def __exit__(self, exc_type, exc_value, exc_tb):
1386 1386 pass
1387 1387
1388 1388
1389 1389 def makefileobj(ctx, pat, mode=b'wb', **props):
1390 1390 writable = mode not in (b'r', b'rb')
1391 1391
1392 1392 if isstdiofilename(pat):
1393 1393 repo = ctx.repo()
1394 1394 if writable:
1395 1395 fp = repo.ui.fout
1396 1396 else:
1397 1397 fp = repo.ui.fin
1398 1398 return _unclosablefile(fp)
1399 1399 fn = makefilename(ctx, pat, **props)
1400 1400 return open(fn, mode)
1401 1401
1402 1402
1403 1403 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1404 1404 """opens the changelog, manifest, a filelog or a given revlog"""
1405 1405 cl = opts[b'changelog']
1406 1406 mf = opts[b'manifest']
1407 1407 dir = opts[b'dir']
1408 1408 msg = None
1409 1409 if cl and mf:
1410 1410 msg = _(b'cannot specify --changelog and --manifest at the same time')
1411 1411 elif cl and dir:
1412 1412 msg = _(b'cannot specify --changelog and --dir at the same time')
1413 1413 elif cl or mf or dir:
1414 1414 if file_:
1415 1415 msg = _(b'cannot specify filename with --changelog or --manifest')
1416 1416 elif not repo:
1417 1417 msg = _(
1418 1418 b'cannot specify --changelog or --manifest or --dir '
1419 1419 b'without a repository'
1420 1420 )
1421 1421 if msg:
1422 1422 raise error.InputError(msg)
1423 1423
1424 1424 r = None
1425 1425 if repo:
1426 1426 if cl:
1427 1427 r = repo.unfiltered().changelog
1428 1428 elif dir:
1429 1429 if not scmutil.istreemanifest(repo):
1430 1430 raise error.InputError(
1431 1431 _(
1432 1432 b"--dir can only be used on repos with "
1433 1433 b"treemanifest enabled"
1434 1434 )
1435 1435 )
1436 1436 if not dir.endswith(b'/'):
1437 1437 dir = dir + b'/'
1438 1438 dirlog = repo.manifestlog.getstorage(dir)
1439 1439 if len(dirlog):
1440 1440 r = dirlog
1441 1441 elif mf:
1442 1442 r = repo.manifestlog.getstorage(b'')
1443 1443 elif file_:
1444 1444 filelog = repo.file(file_)
1445 1445 if len(filelog):
1446 1446 r = filelog
1447 1447
1448 1448 # Not all storage may be revlogs. If requested, try to return an actual
1449 1449 # revlog instance.
1450 1450 if returnrevlog:
1451 1451 if isinstance(r, revlog.revlog):
1452 1452 pass
1453 1453 elif util.safehasattr(r, '_revlog'):
1454 1454 r = r._revlog # pytype: disable=attribute-error
1455 1455 elif r is not None:
1456 1456 raise error.InputError(
1457 1457 _(b'%r does not appear to be a revlog') % r
1458 1458 )
1459 1459
1460 1460 if not r:
1461 1461 if not returnrevlog:
1462 1462 raise error.InputError(_(b'cannot give path to non-revlog'))
1463 1463
1464 1464 if not file_:
1465 1465 raise error.CommandError(cmd, _(b'invalid arguments'))
1466 1466 if not os.path.isfile(file_):
1467 1467 raise error.InputError(_(b"revlog '%s' not found") % file_)
1468 1468
1469 1469 target = (revlog_constants.KIND_OTHER, b'free-form:%s' % file_)
1470 1470 r = revlog.revlog(
1471 1471 vfsmod.vfs(encoding.getcwd(), audit=False),
1472 1472 target=target,
1473 1473 radix=file_[:-2],
1474 1474 )
1475 1475 return r
1476 1476
1477 1477
1478 1478 def openrevlog(repo, cmd, file_, opts):
1479 1479 """Obtain a revlog backing storage of an item.
1480 1480
1481 1481 This is similar to ``openstorage()`` except it always returns a revlog.
1482 1482
1483 1483 In most cases, a caller cares about the main storage object - not the
1484 1484 revlog backing it. Therefore, this function should only be used by code
1485 1485 that needs to examine low-level revlog implementation details. e.g. debug
1486 1486 commands.
1487 1487 """
1488 1488 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1489 1489
1490 1490
1491 1491 def copy(ui, repo, pats, opts: Dict[bytes, Any], rename=False):
1492 1492 check_incompatible_arguments(opts, b'forget', [b'dry_run'])
1493 1493
1494 1494 # called with the repo lock held
1495 1495 #
1496 1496 # hgsep => pathname that uses "/" to separate directories
1497 1497 # ossep => pathname that uses os.sep to separate directories
1498 1498 cwd = repo.getcwd()
1499 1499 targets = {}
1500 1500 forget = opts.get(b"forget")
1501 1501 after = opts.get(b"after")
1502 1502 dryrun = opts.get(b"dry_run")
1503 1503 rev = opts.get(b'at_rev')
1504 1504 if rev:
1505 1505 if not forget and not after:
1506 1506 # TODO: Remove this restriction and make it also create the copy
1507 1507 # targets (and remove the rename source if rename==True).
1508 1508 raise error.InputError(_(b'--at-rev requires --after'))
1509 1509 ctx = logcmdutil.revsingle(repo, rev)
1510 1510 if len(ctx.parents()) > 1:
1511 1511 raise error.InputError(
1512 1512 _(b'cannot mark/unmark copy in merge commit')
1513 1513 )
1514 1514 else:
1515 1515 ctx = repo[None]
1516 1516
1517 1517 pctx = ctx.p1()
1518 1518
1519 1519 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1520 1520
1521 1521 if forget:
1522 1522 if ctx.rev() is None:
1523 1523 new_ctx = ctx
1524 1524 else:
1525 1525 if len(ctx.parents()) > 1:
1526 1526 raise error.InputError(_(b'cannot unmark copy in merge commit'))
1527 1527 # avoid cycle context -> subrepo -> cmdutil
1528 1528 from . import context
1529 1529
1530 1530 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1531 1531 new_ctx = context.overlayworkingctx(repo)
1532 1532 new_ctx.setbase(ctx.p1())
1533 1533 mergemod.graft(repo, ctx, wctx=new_ctx)
1534 1534
1535 1535 match = scmutil.match(ctx, pats, opts)
1536 1536
1537 1537 current_copies = ctx.p1copies()
1538 1538 current_copies.update(ctx.p2copies())
1539 1539
1540 1540 uipathfn = scmutil.getuipathfn(repo)
1541 1541 for f in ctx.walk(match):
1542 1542 if f in current_copies:
1543 1543 new_ctx[f].markcopied(None)
1544 1544 elif match.exact(f):
1545 1545 ui.warn(
1546 1546 _(
1547 1547 b'%s: not unmarking as copy - file is not marked as copied\n'
1548 1548 )
1549 1549 % uipathfn(f)
1550 1550 )
1551 1551
1552 1552 if ctx.rev() is not None:
1553 1553 with repo.lock():
1554 1554 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1555 1555 new_node = mem_ctx.commit()
1556 1556
1557 1557 if repo.dirstate.p1() == ctx.node():
1558 1558 with repo.dirstate.changing_parents(repo):
1559 1559 scmutil.movedirstate(repo, repo[new_node])
1560 1560 replacements = {ctx.node(): [new_node]}
1561 1561 scmutil.cleanupnodes(
1562 1562 repo, replacements, b'uncopy', fixphase=True
1563 1563 )
1564 1564
1565 1565 return
1566 1566
1567 1567 pats = scmutil.expandpats(pats)
1568 1568 if not pats:
1569 1569 raise error.InputError(_(b'no source or destination specified'))
1570 1570 if len(pats) == 1:
1571 1571 raise error.InputError(_(b'no destination specified'))
1572 1572 dest = pats.pop()
1573 1573
1574 1574 def walkpat(pat):
1575 1575 srcs = []
1576 1576 # TODO: Inline and simplify the non-working-copy version of this code
1577 1577 # since it shares very little with the working-copy version of it.
1578 1578 ctx_to_walk = ctx if ctx.rev() is None else pctx
1579 1579 m = scmutil.match(ctx_to_walk, [pat], opts, globbed=True)
1580 1580 for abs in ctx_to_walk.walk(m):
1581 1581 rel = uipathfn(abs)
1582 1582 exact = m.exact(abs)
1583 1583 if abs not in ctx:
1584 1584 if abs in pctx:
1585 1585 if not after:
1586 1586 if exact:
1587 1587 ui.warn(
1588 1588 _(
1589 1589 b'%s: not copying - file has been marked '
1590 1590 b'for remove\n'
1591 1591 )
1592 1592 % rel
1593 1593 )
1594 1594 continue
1595 1595 else:
1596 1596 if exact:
1597 1597 ui.warn(
1598 1598 _(b'%s: not copying - file is not managed\n') % rel
1599 1599 )
1600 1600 continue
1601 1601
1602 1602 # abs: hgsep
1603 1603 # rel: ossep
1604 1604 srcs.append((abs, rel, exact))
1605 1605 return srcs
1606 1606
1607 1607 if ctx.rev() is not None:
1608 1608 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1609 1609 absdest = pathutil.canonpath(repo.root, cwd, dest)
1610 1610 if ctx.hasdir(absdest):
1611 1611 raise error.InputError(
1612 1612 _(b'%s: --at-rev does not support a directory as destination')
1613 1613 % uipathfn(absdest)
1614 1614 )
1615 1615 if absdest not in ctx:
1616 1616 raise error.InputError(
1617 1617 _(b'%s: copy destination does not exist in %s')
1618 1618 % (uipathfn(absdest), ctx)
1619 1619 )
1620 1620
1621 1621 # avoid cycle context -> subrepo -> cmdutil
1622 1622 from . import context
1623 1623
1624 1624 copylist = []
1625 1625 for pat in pats:
1626 1626 srcs = walkpat(pat)
1627 1627 if not srcs:
1628 1628 continue
1629 1629 for abs, rel, exact in srcs:
1630 1630 copylist.append(abs)
1631 1631
1632 1632 if not copylist:
1633 1633 raise error.InputError(_(b'no files to copy'))
1634 1634 # TODO: Add support for `hg cp --at-rev . foo bar dir` and
1635 1635 # `hg cp --at-rev . dir1 dir2`, preferably unifying the code with the
1636 1636 # existing functions below.
1637 1637 if len(copylist) != 1:
1638 1638 raise error.InputError(_(b'--at-rev requires a single source'))
1639 1639
1640 1640 new_ctx = context.overlayworkingctx(repo)
1641 1641 new_ctx.setbase(ctx.p1())
1642 1642 mergemod.graft(repo, ctx, wctx=new_ctx)
1643 1643
1644 1644 new_ctx.markcopied(absdest, copylist[0])
1645 1645
1646 1646 with repo.lock():
1647 1647 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1648 1648 new_node = mem_ctx.commit()
1649 1649
1650 1650 if repo.dirstate.p1() == ctx.node():
1651 1651 with repo.dirstate.changing_parents(repo):
1652 1652 scmutil.movedirstate(repo, repo[new_node])
1653 1653 replacements = {ctx.node(): [new_node]}
1654 1654 scmutil.cleanupnodes(repo, replacements, b'copy', fixphase=True)
1655 1655
1656 1656 return
1657 1657
1658 1658 # abssrc: hgsep
1659 1659 # relsrc: ossep
1660 1660 # otarget: ossep
1661 1661 def copyfile(abssrc, relsrc, otarget, exact):
1662 1662 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1663 1663 if b'/' in abstarget:
1664 1664 # We cannot normalize abstarget itself, this would prevent
1665 1665 # case only renames, like a => A.
1666 1666 abspath, absname = abstarget.rsplit(b'/', 1)
1667 1667 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1668 1668 reltarget = repo.pathto(abstarget, cwd)
1669 1669 target = repo.wjoin(abstarget)
1670 1670 src = repo.wjoin(abssrc)
1671 1671 entry = repo.dirstate.get_entry(abstarget)
1672 1672
1673 1673 already_commited = entry.tracked and not entry.added
1674 1674
1675 1675 scmutil.checkportable(ui, abstarget)
1676 1676
1677 1677 # check for collisions
1678 1678 prevsrc = targets.get(abstarget)
1679 1679 if prevsrc is not None:
1680 1680 ui.warn(
1681 1681 _(b'%s: not overwriting - %s collides with %s\n')
1682 1682 % (
1683 1683 reltarget,
1684 1684 repo.pathto(abssrc, cwd),
1685 1685 repo.pathto(prevsrc, cwd),
1686 1686 )
1687 1687 )
1688 1688 return True # report a failure
1689 1689
1690 1690 # check for overwrites
1691 1691 exists = os.path.lexists(target)
1692 1692 samefile = False
1693 1693 if exists and abssrc != abstarget:
1694 1694 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1695 1695 abstarget
1696 1696 ):
1697 1697 if not rename:
1698 1698 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1699 1699 return True # report a failure
1700 1700 exists = False
1701 1701 samefile = True
1702 1702
1703 1703 if not after and exists or after and already_commited:
1704 1704 if not opts[b'force']:
1705 1705 if already_commited:
1706 1706 msg = _(b'%s: not overwriting - file already committed\n')
1707 1707 # Check if if the target was added in the parent and the
1708 1708 # source already existed in the grandparent.
1709 1709 looks_like_copy_in_pctx = abstarget in pctx and any(
1710 1710 abssrc in gpctx and abstarget not in gpctx
1711 1711 for gpctx in pctx.parents()
1712 1712 )
1713 1713 if looks_like_copy_in_pctx:
1714 1714 if rename:
1715 1715 hint = _(
1716 1716 b"('hg rename --at-rev .' to record the rename "
1717 1717 b"in the parent of the working copy)\n"
1718 1718 )
1719 1719 else:
1720 1720 hint = _(
1721 1721 b"('hg copy --at-rev .' to record the copy in "
1722 1722 b"the parent of the working copy)\n"
1723 1723 )
1724 1724 else:
1725 1725 if after:
1726 1726 flags = b'--after --force'
1727 1727 else:
1728 1728 flags = b'--force'
1729 1729 if rename:
1730 1730 hint = (
1731 1731 _(
1732 1732 b"('hg rename %s' to replace the file by "
1733 1733 b'recording a rename)\n'
1734 1734 )
1735 1735 % flags
1736 1736 )
1737 1737 else:
1738 1738 hint = (
1739 1739 _(
1740 1740 b"('hg copy %s' to replace the file by "
1741 1741 b'recording a copy)\n'
1742 1742 )
1743 1743 % flags
1744 1744 )
1745 1745 else:
1746 1746 msg = _(b'%s: not overwriting - file exists\n')
1747 1747 if rename:
1748 1748 hint = _(
1749 1749 b"('hg rename --after' to record the rename)\n"
1750 1750 )
1751 1751 else:
1752 1752 hint = _(b"('hg copy --after' to record the copy)\n")
1753 1753 ui.warn(msg % reltarget)
1754 1754 ui.warn(hint)
1755 1755 return True # report a failure
1756 1756
1757 1757 if after:
1758 1758 if not exists:
1759 1759 if rename:
1760 1760 ui.warn(
1761 1761 _(b'%s: not recording move - %s does not exist\n')
1762 1762 % (relsrc, reltarget)
1763 1763 )
1764 1764 else:
1765 1765 ui.warn(
1766 1766 _(b'%s: not recording copy - %s does not exist\n')
1767 1767 % (relsrc, reltarget)
1768 1768 )
1769 1769 return True # report a failure
1770 1770 elif not dryrun:
1771 1771 try:
1772 1772 if exists:
1773 1773 os.unlink(target)
1774 1774 targetdir = os.path.dirname(target) or b'.'
1775 1775 if not os.path.isdir(targetdir):
1776 1776 os.makedirs(targetdir)
1777 1777 if samefile:
1778 1778 tmp = target + b"~hgrename"
1779 1779 os.rename(src, tmp)
1780 1780 os.rename(tmp, target)
1781 1781 else:
1782 1782 # Preserve stat info on renames, not on copies; this matches
1783 1783 # Linux CLI behavior.
1784 1784 util.copyfile(src, target, copystat=rename)
1785 1785 srcexists = True
1786 1786 except IOError as inst:
1787 1787 if inst.errno == errno.ENOENT:
1788 1788 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1789 1789 srcexists = False
1790 1790 else:
1791 1791 ui.warn(
1792 1792 _(b'%s: cannot copy - %s\n')
1793 1793 % (relsrc, encoding.strtolocal(inst.strerror))
1794 1794 )
1795 1795 return True # report a failure
1796 1796
1797 1797 if ui.verbose or not exact:
1798 1798 if rename:
1799 1799 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1800 1800 else:
1801 1801 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1802 1802
1803 1803 targets[abstarget] = abssrc
1804 1804
1805 1805 # fix up dirstate
1806 1806 scmutil.dirstatecopy(
1807 1807 ui, repo, ctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1808 1808 )
1809 1809 if rename and not dryrun:
1810 1810 if not after and srcexists and not samefile:
1811 1811 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1812 1812 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1813 1813 ctx.forget([abssrc])
1814 1814
1815 1815 # pat: ossep
1816 1816 # dest ossep
1817 1817 # srcs: list of (hgsep, hgsep, ossep, bool)
1818 1818 # return: function that takes hgsep and returns ossep
1819 1819 def targetpathfn(pat, dest, srcs):
1820 1820 if os.path.isdir(pat):
1821 1821 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1822 1822 abspfx = util.localpath(abspfx)
1823 1823 if destdirexists:
1824 1824 striplen = len(os.path.split(abspfx)[0])
1825 1825 else:
1826 1826 striplen = len(abspfx)
1827 1827 if striplen:
1828 1828 striplen += len(pycompat.ossep)
1829 1829 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1830 1830 elif destdirexists:
1831 1831 res = lambda p: os.path.join(
1832 1832 dest, os.path.basename(util.localpath(p))
1833 1833 )
1834 1834 else:
1835 1835 res = lambda p: dest
1836 1836 return res
1837 1837
1838 1838 # pat: ossep
1839 1839 # dest ossep
1840 1840 # srcs: list of (hgsep, hgsep, ossep, bool)
1841 1841 # return: function that takes hgsep and returns ossep
1842 1842 def targetpathafterfn(pat, dest, srcs):
1843 1843 if matchmod.patkind(pat):
1844 1844 # a mercurial pattern
1845 1845 res = lambda p: os.path.join(
1846 1846 dest, os.path.basename(util.localpath(p))
1847 1847 )
1848 1848 else:
1849 1849 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1850 1850 if len(abspfx) < len(srcs[0][0]):
1851 1851 # A directory. Either the target path contains the last
1852 1852 # component of the source path or it does not.
1853 1853 def evalpath(striplen):
1854 1854 score = 0
1855 1855 for s in srcs:
1856 1856 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1857 1857 if os.path.lexists(t):
1858 1858 score += 1
1859 1859 return score
1860 1860
1861 1861 abspfx = util.localpath(abspfx)
1862 1862 striplen = len(abspfx)
1863 1863 if striplen:
1864 1864 striplen += len(pycompat.ossep)
1865 1865 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1866 1866 score = evalpath(striplen)
1867 1867 striplen1 = len(os.path.split(abspfx)[0])
1868 1868 if striplen1:
1869 1869 striplen1 += len(pycompat.ossep)
1870 1870 if evalpath(striplen1) > score:
1871 1871 striplen = striplen1
1872 1872 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1873 1873 else:
1874 1874 # a file
1875 1875 if destdirexists:
1876 1876 res = lambda p: os.path.join(
1877 1877 dest, os.path.basename(util.localpath(p))
1878 1878 )
1879 1879 else:
1880 1880 res = lambda p: dest
1881 1881 return res
1882 1882
1883 1883 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1884 1884 if not destdirexists:
1885 1885 if len(pats) > 1 or matchmod.patkind(pats[0]):
1886 1886 raise error.InputError(
1887 1887 _(
1888 1888 b'with multiple sources, destination must be an '
1889 1889 b'existing directory'
1890 1890 )
1891 1891 )
1892 1892 if util.endswithsep(dest):
1893 1893 raise error.InputError(
1894 1894 _(b'destination %s is not a directory') % dest
1895 1895 )
1896 1896
1897 1897 tfn = targetpathfn
1898 1898 if after:
1899 1899 tfn = targetpathafterfn
1900 1900 copylist = []
1901 1901 for pat in pats:
1902 1902 srcs = walkpat(pat)
1903 1903 if not srcs:
1904 1904 continue
1905 1905 copylist.append((tfn(pat, dest, srcs), srcs))
1906 1906 if not copylist:
1907 1907 hint = None
1908 1908 if rename:
1909 1909 hint = _(b'maybe you meant to use --after --at-rev=.')
1910 1910 raise error.InputError(_(b'no files to copy'), hint=hint)
1911 1911
1912 1912 errors = 0
1913 1913 for targetpath, srcs in copylist:
1914 1914 for abssrc, relsrc, exact in srcs:
1915 1915 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1916 1916 errors += 1
1917 1917
1918 1918 return errors != 0
1919 1919
1920 1920
1921 1921 ## facility to let extension process additional data into an import patch
1922 1922 # list of identifier to be executed in order
1923 1923 extrapreimport = [] # run before commit
1924 1924 extrapostimport = [] # run after commit
1925 1925 # mapping from identifier to actual import function
1926 1926 #
1927 1927 # 'preimport' are run before the commit is made and are provided the following
1928 1928 # arguments:
1929 1929 # - repo: the localrepository instance,
1930 1930 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1931 1931 # - extra: the future extra dictionary of the changeset, please mutate it,
1932 1932 # - opts: the import options.
1933 1933 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1934 1934 # mutation of in memory commit and more. Feel free to rework the code to get
1935 1935 # there.
1936 1936 extrapreimportmap = {}
1937 1937 # 'postimport' are run after the commit is made and are provided the following
1938 1938 # argument:
1939 1939 # - ctx: the changectx created by import.
1940 1940 extrapostimportmap = {}
1941 1941
1942 1942
1943 1943 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1944 1944 """Utility function used by commands.import to import a single patch
1945 1945
1946 1946 This function is explicitly defined here to help the evolve extension to
1947 1947 wrap this part of the import logic.
1948 1948
1949 1949 The API is currently a bit ugly because it a simple code translation from
1950 1950 the import command. Feel free to make it better.
1951 1951
1952 1952 :patchdata: a dictionary containing parsed patch data (such as from
1953 1953 ``patch.extract()``)
1954 1954 :parents: nodes that will be parent of the created commit
1955 1955 :opts: the full dict of option passed to the import command
1956 1956 :msgs: list to save commit message to.
1957 1957 (used in case we need to save it when failing)
1958 1958 :updatefunc: a function that update a repo to a given node
1959 1959 updatefunc(<repo>, <node>)
1960 1960 """
1961 1961 # avoid cycle context -> subrepo -> cmdutil
1962 1962 from . import context
1963 1963
1964 1964 tmpname = patchdata.get(b'filename')
1965 1965 message = patchdata.get(b'message')
1966 1966 user = opts.get(b'user') or patchdata.get(b'user')
1967 1967 date = opts.get(b'date') or patchdata.get(b'date')
1968 1968 branch = patchdata.get(b'branch')
1969 1969 nodeid = patchdata.get(b'nodeid')
1970 1970 p1 = patchdata.get(b'p1')
1971 1971 p2 = patchdata.get(b'p2')
1972 1972
1973 1973 nocommit = opts.get(b'no_commit')
1974 1974 importbranch = opts.get(b'import_branch')
1975 1975 update = not opts.get(b'bypass')
1976 1976 strip = opts[b"strip"]
1977 1977 prefix = opts[b"prefix"]
1978 1978 sim = float(opts.get(b'similarity') or 0)
1979 1979
1980 1980 if not tmpname:
1981 1981 return None, None, False
1982 1982
1983 1983 rejects = False
1984 1984
1985 1985 cmdline_message = logmessage(ui, opts)
1986 1986 if cmdline_message:
1987 1987 # pickup the cmdline msg
1988 1988 message = cmdline_message
1989 1989 elif message:
1990 1990 # pickup the patch msg
1991 1991 message = message.strip()
1992 1992 else:
1993 1993 # launch the editor
1994 1994 message = None
1995 1995 ui.debug(b'message:\n%s\n' % (message or b''))
1996 1996
1997 1997 if len(parents) == 1:
1998 1998 parents.append(repo[nullrev])
1999 1999 if opts.get(b'exact'):
2000 2000 if not nodeid or not p1:
2001 2001 raise error.InputError(_(b'not a Mercurial patch'))
2002 2002 p1 = repo[p1]
2003 2003 p2 = repo[p2 or nullrev]
2004 2004 elif p2:
2005 2005 try:
2006 2006 p1 = repo[p1]
2007 2007 p2 = repo[p2]
2008 2008 # Without any options, consider p2 only if the
2009 2009 # patch is being applied on top of the recorded
2010 2010 # first parent.
2011 2011 if p1 != parents[0]:
2012 2012 p1 = parents[0]
2013 2013 p2 = repo[nullrev]
2014 2014 except error.RepoError:
2015 2015 p1, p2 = parents
2016 2016 if p2.rev() == nullrev:
2017 2017 ui.warn(
2018 2018 _(
2019 2019 b"warning: import the patch as a normal revision\n"
2020 2020 b"(use --exact to import the patch as a merge)\n"
2021 2021 )
2022 2022 )
2023 2023 else:
2024 2024 p1, p2 = parents
2025 2025
2026 2026 n = None
2027 2027 if update:
2028 2028 if p1 != parents[0]:
2029 2029 updatefunc(repo, p1.node())
2030 2030 if p2 != parents[1]:
2031 2031 repo.setparents(p1.node(), p2.node())
2032 2032
2033 2033 if opts.get(b'exact') or importbranch:
2034 2034 repo.dirstate.setbranch(
2035 2035 branch or b'default', repo.currenttransaction()
2036 2036 )
2037 2037
2038 2038 partial = opts.get(b'partial', False)
2039 2039 files = set()
2040 2040 try:
2041 2041 patch.patch(
2042 2042 ui,
2043 2043 repo,
2044 2044 tmpname,
2045 2045 strip=strip,
2046 2046 prefix=prefix,
2047 2047 files=files,
2048 2048 eolmode=None,
2049 2049 similarity=sim / 100.0,
2050 2050 )
2051 2051 except error.PatchParseError as e:
2052 2052 raise error.InputError(
2053 2053 pycompat.bytestr(e),
2054 2054 hint=_(
2055 2055 b'check that whitespace in the patch has not been mangled'
2056 2056 ),
2057 2057 )
2058 2058 except error.PatchApplicationError as e:
2059 2059 if not partial:
2060 2060 raise error.StateError(pycompat.bytestr(e))
2061 2061 if partial:
2062 2062 rejects = True
2063 2063
2064 2064 files = list(files)
2065 2065 if nocommit:
2066 2066 if message:
2067 2067 msgs.append(message)
2068 2068 else:
2069 2069 if opts.get(b'exact') or p2:
2070 2070 # If you got here, you either use --force and know what
2071 2071 # you are doing or used --exact or a merge patch while
2072 2072 # being updated to its first parent.
2073 2073 m = None
2074 2074 else:
2075 2075 m = scmutil.matchfiles(repo, files or [])
2076 2076 editform = mergeeditform(repo[None], b'import.normal')
2077 2077 if opts.get(b'exact'):
2078 2078 editor = None
2079 2079 else:
2080 2080 editor = getcommiteditor(
2081 2081 editform=editform, **pycompat.strkwargs(opts)
2082 2082 )
2083 2083 extra = {}
2084 2084 for idfunc in extrapreimport:
2085 2085 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
2086 2086 overrides = {}
2087 2087 if partial:
2088 2088 overrides[(b'ui', b'allowemptycommit')] = True
2089 2089 if opts.get(b'secret'):
2090 2090 overrides[(b'phases', b'new-commit')] = b'secret'
2091 2091 with repo.ui.configoverride(overrides, b'import'):
2092 2092 n = repo.commit(
2093 2093 message, user, date, match=m, editor=editor, extra=extra
2094 2094 )
2095 2095 for idfunc in extrapostimport:
2096 2096 extrapostimportmap[idfunc](repo[n])
2097 2097 else:
2098 2098 if opts.get(b'exact') or importbranch:
2099 2099 branch = branch or b'default'
2100 2100 else:
2101 2101 branch = p1.branch()
2102 2102 store = patch.filestore()
2103 2103 try:
2104 2104 files = set()
2105 2105 try:
2106 2106 patch.patchrepo(
2107 2107 ui,
2108 2108 repo,
2109 2109 p1,
2110 2110 store,
2111 2111 tmpname,
2112 2112 strip,
2113 2113 prefix,
2114 2114 files,
2115 2115 eolmode=None,
2116 2116 )
2117 2117 except error.PatchParseError as e:
2118 2118 raise error.InputError(
2119 2119 stringutil.forcebytestr(e),
2120 2120 hint=_(
2121 2121 b'check that whitespace in the patch has not been mangled'
2122 2122 ),
2123 2123 )
2124 2124 except error.PatchApplicationError as e:
2125 2125 raise error.StateError(stringutil.forcebytestr(e))
2126 2126 if opts.get(b'exact'):
2127 2127 editor = None
2128 2128 else:
2129 2129 editor = getcommiteditor(editform=b'import.bypass')
2130 2130 memctx = context.memctx(
2131 2131 repo,
2132 2132 (p1.node(), p2.node()),
2133 2133 message,
2134 2134 files=files,
2135 2135 filectxfn=store,
2136 2136 user=user,
2137 2137 date=date,
2138 2138 branch=branch,
2139 2139 editor=editor,
2140 2140 )
2141 2141
2142 2142 overrides = {}
2143 2143 if opts.get(b'secret'):
2144 2144 overrides[(b'phases', b'new-commit')] = b'secret'
2145 2145 with repo.ui.configoverride(overrides, b'import'):
2146 2146 n = memctx.commit()
2147 2147 finally:
2148 2148 store.close()
2149 2149 if opts.get(b'exact') and nocommit:
2150 2150 # --exact with --no-commit is still useful in that it does merge
2151 2151 # and branch bits
2152 2152 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
2153 2153 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
2154 2154 raise error.Abort(_(b'patch is damaged or loses information'))
2155 2155 msg = _(b'applied to working directory')
2156 2156 if n:
2157 2157 # i18n: refers to a short changeset id
2158 2158 msg = _(b'created %s') % short(n)
2159 2159 return msg, n, rejects
2160 2160
2161 2161
2162 2162 # facility to let extensions include additional data in an exported patch
2163 2163 # list of identifiers to be executed in order
2164 2164 extraexport = []
2165 2165 # mapping from identifier to actual export function
2166 2166 # function as to return a string to be added to the header or None
2167 2167 # it is given two arguments (sequencenumber, changectx)
2168 2168 extraexportmap = {}
2169 2169
2170 2170
2171 2171 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
2172 2172 node = scmutil.binnode(ctx)
2173 2173 parents = [p.node() for p in ctx.parents() if p]
2174 2174 branch = ctx.branch()
2175 2175 if switch_parent:
2176 2176 parents.reverse()
2177 2177
2178 2178 if parents:
2179 2179 prev = parents[0]
2180 2180 else:
2181 2181 prev = repo.nullid
2182 2182
2183 2183 fm.context(ctx=ctx)
2184 2184 fm.plain(b'# HG changeset patch\n')
2185 2185 fm.write(b'user', b'# User %s\n', ctx.user())
2186 2186 fm.plain(b'# Date %d %d\n' % ctx.date())
2187 2187 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
2188 2188 fm.condwrite(
2189 2189 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
2190 2190 )
2191 2191 fm.write(b'node', b'# Node ID %s\n', hex(node))
2192 2192 fm.plain(b'# Parent %s\n' % hex(prev))
2193 2193 if len(parents) > 1:
2194 2194 fm.plain(b'# Parent %s\n' % hex(parents[1]))
2195 2195 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
2196 2196
2197 2197 # TODO: redesign extraexportmap function to support formatter
2198 2198 for headerid in extraexport:
2199 2199 header = extraexportmap[headerid](seqno, ctx)
2200 2200 if header is not None:
2201 2201 fm.plain(b'# %s\n' % header)
2202 2202
2203 2203 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
2204 2204 fm.plain(b'\n')
2205 2205
2206 2206 if fm.isplain():
2207 2207 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
2208 2208 for chunk, label in chunkiter:
2209 2209 fm.plain(chunk, label=label)
2210 2210 else:
2211 2211 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
2212 2212 # TODO: make it structured?
2213 2213 fm.data(diff=b''.join(chunkiter))
2214 2214
2215 2215
2216 2216 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
2217 2217 """Export changesets to stdout or a single file"""
2218 2218 for seqno, rev in enumerate(revs, 1):
2219 2219 ctx = repo[rev]
2220 2220 if not dest.startswith(b'<'):
2221 2221 repo.ui.note(b"%s\n" % dest)
2222 2222 fm.startitem()
2223 2223 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
2224 2224
2225 2225
2226 2226 def _exportfntemplate(
2227 2227 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
2228 2228 ):
2229 2229 """Export changesets to possibly multiple files"""
2230 2230 total = len(revs)
2231 2231 revwidth = max(len(str(rev)) for rev in revs)
2232 2232 filemap = util.sortdict() # filename: [(seqno, rev), ...]
2233 2233
2234 2234 for seqno, rev in enumerate(revs, 1):
2235 2235 ctx = repo[rev]
2236 2236 dest = makefilename(
2237 2237 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
2238 2238 )
2239 2239 filemap.setdefault(dest, []).append((seqno, rev))
2240 2240
2241 2241 for dest in filemap:
2242 2242 with formatter.maybereopen(basefm, dest) as fm:
2243 2243 repo.ui.note(b"%s\n" % dest)
2244 2244 for seqno, rev in filemap[dest]:
2245 2245 fm.startitem()
2246 2246 ctx = repo[rev]
2247 2247 _exportsingle(
2248 2248 repo, ctx, fm, match, switch_parent, seqno, diffopts
2249 2249 )
2250 2250
2251 2251
2252 2252 def _prefetchchangedfiles(repo, revs, match):
2253 2253 allfiles = set()
2254 2254 for rev in revs:
2255 2255 for file in repo[rev].files():
2256 2256 if not match or match(file):
2257 2257 allfiles.add(file)
2258 2258 match = scmutil.matchfiles(repo, allfiles)
2259 2259 revmatches = [(rev, match) for rev in revs]
2260 2260 scmutil.prefetchfiles(repo, revmatches)
2261 2261
2262 2262
2263 2263 def export(
2264 2264 repo,
2265 2265 revs,
2266 2266 basefm,
2267 2267 fntemplate=b'hg-%h.patch',
2268 2268 switch_parent=False,
2269 2269 opts=None,
2270 2270 match=None,
2271 2271 ):
2272 2272 """export changesets as hg patches
2273 2273
2274 2274 Args:
2275 2275 repo: The repository from which we're exporting revisions.
2276 2276 revs: A list of revisions to export as revision numbers.
2277 2277 basefm: A formatter to which patches should be written.
2278 2278 fntemplate: An optional string to use for generating patch file names.
2279 2279 switch_parent: If True, show diffs against second parent when not nullid.
2280 2280 Default is false, which always shows diff against p1.
2281 2281 opts: diff options to use for generating the patch.
2282 2282 match: If specified, only export changes to files matching this matcher.
2283 2283
2284 2284 Returns:
2285 2285 Nothing.
2286 2286
2287 2287 Side Effect:
2288 2288 "HG Changeset Patch" data is emitted to one of the following
2289 2289 destinations:
2290 2290 fntemplate specified: Each rev is written to a unique file named using
2291 2291 the given template.
2292 2292 Otherwise: All revs will be written to basefm.
2293 2293 """
2294 2294 _prefetchchangedfiles(repo, revs, match)
2295 2295
2296 2296 if not fntemplate:
2297 2297 _exportfile(
2298 2298 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
2299 2299 )
2300 2300 else:
2301 2301 _exportfntemplate(
2302 2302 repo, revs, basefm, fntemplate, switch_parent, opts, match
2303 2303 )
2304 2304
2305 2305
2306 2306 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
2307 2307 """Export changesets to the given file stream"""
2308 2308 _prefetchchangedfiles(repo, revs, match)
2309 2309
2310 2310 dest = getattr(fp, 'name', b'<unnamed>')
2311 2311 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
2312 2312 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
2313 2313
2314 2314
2315 2315 def showmarker(fm, marker, index=None):
2316 2316 """utility function to display obsolescence marker in a readable way
2317 2317
2318 2318 To be used by debug function."""
2319 2319 if index is not None:
2320 2320 fm.write(b'index', b'%i ', index)
2321 2321 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
2322 2322 succs = marker.succnodes()
2323 2323 fm.condwrite(
2324 2324 succs,
2325 2325 b'succnodes',
2326 2326 b'%s ',
2327 2327 fm.formatlist(map(hex, succs), name=b'node'),
2328 2328 )
2329 2329 fm.write(b'flag', b'%X ', marker.flags())
2330 2330 parents = marker.parentnodes()
2331 2331 if parents is not None:
2332 2332 fm.write(
2333 2333 b'parentnodes',
2334 2334 b'{%s} ',
2335 2335 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2336 2336 )
2337 2337 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2338 2338 meta = marker.metadata().copy()
2339 2339 meta.pop(b'date', None)
2340 2340 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2341 2341 fm.write(
2342 2342 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2343 2343 )
2344 2344 fm.plain(b'\n')
2345 2345
2346 2346
2347 2347 def finddate(ui, repo, date):
2348 2348 """Find the tipmost changeset that matches the given date spec"""
2349 2349 mrevs = repo.revs(b'date(%s)', date)
2350 2350 try:
2351 2351 rev = mrevs.max()
2352 2352 except ValueError:
2353 2353 raise error.InputError(_(b"revision matching date not found"))
2354 2354
2355 2355 ui.status(
2356 2356 _(b"found revision %d from %s\n")
2357 2357 % (rev, dateutil.datestr(repo[rev].date()))
2358 2358 )
2359 2359 return b'%d' % rev
2360 2360
2361 2361
2362 2362 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2363 2363 bad = []
2364 2364
2365 2365 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2366 2366 names = []
2367 2367 wctx = repo[None]
2368 2368 cca = None
2369 2369 abort, warn = scmutil.checkportabilityalert(ui)
2370 2370 if abort or warn:
2371 2371 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2372 2372
2373 2373 match = repo.narrowmatch(match, includeexact=True)
2374 2374 badmatch = matchmod.badmatch(match, badfn)
2375 2375 dirstate = repo.dirstate
2376 2376 # We don't want to just call wctx.walk here, since it would return a lot of
2377 2377 # clean files, which we aren't interested in and takes time.
2378 2378 for f in sorted(
2379 2379 dirstate.walk(
2380 2380 badmatch,
2381 2381 subrepos=sorted(wctx.substate),
2382 2382 unknown=True,
2383 2383 ignored=False,
2384 2384 full=False,
2385 2385 )
2386 2386 ):
2387 2387 exact = match.exact(f)
2388 2388 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2389 2389 if cca:
2390 2390 cca(f)
2391 2391 names.append(f)
2392 2392 if ui.verbose or not exact:
2393 2393 ui.status(
2394 2394 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2395 2395 )
2396 2396
2397 2397 for subpath in sorted(wctx.substate):
2398 2398 sub = wctx.sub(subpath)
2399 2399 try:
2400 2400 submatch = matchmod.subdirmatcher(subpath, match)
2401 2401 subprefix = repo.wvfs.reljoin(prefix, subpath)
2402 2402 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2403 2403 if opts.get('subrepos'):
2404 2404 bad.extend(
2405 2405 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2406 2406 )
2407 2407 else:
2408 2408 bad.extend(
2409 2409 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2410 2410 )
2411 2411 except error.LookupError:
2412 2412 ui.status(
2413 2413 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2414 2414 )
2415 2415
2416 2416 if not opts.get('dry_run'):
2417 2417 rejected = wctx.add(names, prefix)
2418 2418 bad.extend(f for f in rejected if f in match.files())
2419 2419 return bad
2420 2420
2421 2421
2422 2422 def addwebdirpath(repo, serverpath, webconf):
2423 2423 webconf[serverpath] = repo.root
2424 2424 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2425 2425
2426 2426 for r in repo.revs(b'filelog("path:.hgsub")'):
2427 2427 ctx = repo[r]
2428 2428 for subpath in ctx.substate:
2429 2429 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2430 2430
2431 2431
2432 2432 def forget(
2433 2433 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2434 2434 ):
2435 2435 if dryrun and interactive:
2436 2436 raise error.InputError(
2437 2437 _(b"cannot specify both --dry-run and --interactive")
2438 2438 )
2439 2439 bad = []
2440 2440 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2441 2441 wctx = repo[None]
2442 2442 forgot = []
2443 2443
2444 2444 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2445 2445 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2446 2446 if explicitonly:
2447 2447 forget = [f for f in forget if match.exact(f)]
2448 2448
2449 2449 for subpath in sorted(wctx.substate):
2450 2450 sub = wctx.sub(subpath)
2451 2451 submatch = matchmod.subdirmatcher(subpath, match)
2452 2452 subprefix = repo.wvfs.reljoin(prefix, subpath)
2453 2453 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2454 2454 try:
2455 2455 subbad, subforgot = sub.forget(
2456 2456 submatch,
2457 2457 subprefix,
2458 2458 subuipathfn,
2459 2459 dryrun=dryrun,
2460 2460 interactive=interactive,
2461 2461 )
2462 2462 bad.extend([subpath + b'/' + f for f in subbad])
2463 2463 forgot.extend([subpath + b'/' + f for f in subforgot])
2464 2464 except error.LookupError:
2465 2465 ui.status(
2466 2466 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2467 2467 )
2468 2468
2469 2469 if not explicitonly:
2470 2470 for f in match.files():
2471 2471 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2472 2472 if f not in forgot:
2473 2473 if repo.wvfs.exists(f):
2474 2474 # Don't complain if the exact case match wasn't given.
2475 2475 # But don't do this until after checking 'forgot', so
2476 2476 # that subrepo files aren't normalized, and this op is
2477 2477 # purely from data cached by the status walk above.
2478 2478 if repo.dirstate.normalize(f) in repo.dirstate:
2479 2479 continue
2480 2480 ui.warn(
2481 2481 _(
2482 2482 b'not removing %s: '
2483 2483 b'file is already untracked\n'
2484 2484 )
2485 2485 % uipathfn(f)
2486 2486 )
2487 2487 bad.append(f)
2488 2488
2489 2489 if interactive:
2490 2490 responses = _(
2491 2491 b'[Ynsa?]'
2492 2492 b'$$ &Yes, forget this file'
2493 2493 b'$$ &No, skip this file'
2494 2494 b'$$ &Skip remaining files'
2495 2495 b'$$ Include &all remaining files'
2496 2496 b'$$ &? (display help)'
2497 2497 )
2498 2498 for filename in forget[:]:
2499 2499 r = ui.promptchoice(
2500 2500 _(b'forget %s %s') % (uipathfn(filename), responses)
2501 2501 )
2502 2502 if r == 4: # ?
2503 2503 while r == 4:
2504 2504 for c, t in ui.extractchoices(responses)[1]:
2505 2505 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2506 2506 r = ui.promptchoice(
2507 2507 _(b'forget %s %s') % (uipathfn(filename), responses)
2508 2508 )
2509 2509 if r == 0: # yes
2510 2510 continue
2511 2511 elif r == 1: # no
2512 2512 forget.remove(filename)
2513 2513 elif r == 2: # Skip
2514 2514 fnindex = forget.index(filename)
2515 2515 del forget[fnindex:]
2516 2516 break
2517 2517 elif r == 3: # All
2518 2518 break
2519 2519
2520 2520 for f in forget:
2521 2521 if ui.verbose or not match.exact(f) or interactive:
2522 2522 ui.status(
2523 2523 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2524 2524 )
2525 2525
2526 2526 if not dryrun:
2527 2527 rejected = wctx.forget(forget, prefix)
2528 2528 bad.extend(f for f in rejected if f in match.files())
2529 2529 forgot.extend(f for f in forget if f not in rejected)
2530 2530 return bad, forgot
2531 2531
2532 2532
2533 2533 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2534 2534 ret = 1
2535 2535
2536 2536 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2537 2537 if fm.isplain() and not needsfctx:
2538 2538 # Fast path. The speed-up comes from skipping the formatter, and batching
2539 2539 # calls to ui.write.
2540 2540 buf = []
2541 2541 for f in ctx.matches(m):
2542 2542 buf.append(fmt % uipathfn(f))
2543 2543 if len(buf) > 100:
2544 2544 ui.write(b''.join(buf))
2545 2545 del buf[:]
2546 2546 ret = 0
2547 2547 if buf:
2548 2548 ui.write(b''.join(buf))
2549 2549 else:
2550 2550 for f in ctx.matches(m):
2551 2551 fm.startitem()
2552 2552 fm.context(ctx=ctx)
2553 2553 if needsfctx:
2554 2554 fc = ctx[f]
2555 2555 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2556 2556 fm.data(path=f)
2557 2557 fm.plain(fmt % uipathfn(f))
2558 2558 ret = 0
2559 2559
2560 2560 for subpath in sorted(ctx.substate):
2561 2561 submatch = matchmod.subdirmatcher(subpath, m)
2562 2562 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2563 2563 if subrepos or m.exact(subpath) or any(submatch.files()):
2564 2564 sub = ctx.sub(subpath)
2565 2565 try:
2566 2566 recurse = m.exact(subpath) or subrepos
2567 2567 if (
2568 2568 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2569 2569 == 0
2570 2570 ):
2571 2571 ret = 0
2572 2572 except error.LookupError:
2573 2573 ui.status(
2574 2574 _(b"skipping missing subrepository: %s\n")
2575 2575 % uipathfn(subpath)
2576 2576 )
2577 2577
2578 2578 return ret
2579 2579
2580 2580
2581 2581 def remove(
2582 2582 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2583 2583 ):
2584 2584 ret = 0
2585 2585 s = repo.status(match=m, clean=True)
2586 2586 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2587 2587
2588 2588 wctx = repo[None]
2589 2589
2590 2590 if warnings is None:
2591 2591 warnings = []
2592 2592 warn = True
2593 2593 else:
2594 2594 warn = False
2595 2595
2596 2596 subs = sorted(wctx.substate)
2597 2597 progress = ui.makeprogress(
2598 2598 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2599 2599 )
2600 2600 for subpath in subs:
2601 2601 submatch = matchmod.subdirmatcher(subpath, m)
2602 2602 subprefix = repo.wvfs.reljoin(prefix, subpath)
2603 2603 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2604 2604 if subrepos or m.exact(subpath) or any(submatch.files()):
2605 2605 progress.increment()
2606 2606 sub = wctx.sub(subpath)
2607 2607 try:
2608 2608 if sub.removefiles(
2609 2609 submatch,
2610 2610 subprefix,
2611 2611 subuipathfn,
2612 2612 after,
2613 2613 force,
2614 2614 subrepos,
2615 2615 dryrun,
2616 2616 warnings,
2617 2617 ):
2618 2618 ret = 1
2619 2619 except error.LookupError:
2620 2620 warnings.append(
2621 2621 _(b"skipping missing subrepository: %s\n")
2622 2622 % uipathfn(subpath)
2623 2623 )
2624 2624 progress.complete()
2625 2625
2626 2626 # warn about failure to delete explicit files/dirs
2627 2627 deleteddirs = pathutil.dirs(deleted)
2628 2628 files = m.files()
2629 2629 progress = ui.makeprogress(
2630 2630 _(b'deleting'), total=len(files), unit=_(b'files')
2631 2631 )
2632 2632 for f in files:
2633 2633
2634 2634 def insubrepo():
2635 2635 for subpath in wctx.substate:
2636 2636 if f.startswith(subpath + b'/'):
2637 2637 return True
2638 2638 return False
2639 2639
2640 2640 progress.increment()
2641 2641 isdir = f in deleteddirs or wctx.hasdir(f)
2642 2642 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2643 2643 continue
2644 2644
2645 2645 if repo.wvfs.exists(f):
2646 2646 if repo.wvfs.isdir(f):
2647 2647 warnings.append(
2648 2648 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2649 2649 )
2650 2650 else:
2651 2651 warnings.append(
2652 2652 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2653 2653 )
2654 2654 # missing files will generate a warning elsewhere
2655 2655 ret = 1
2656 2656 progress.complete()
2657 2657
2658 2658 if force:
2659 2659 list = modified + deleted + clean + added
2660 2660 elif after:
2661 2661 list = deleted
2662 2662 remaining = modified + added + clean
2663 2663 progress = ui.makeprogress(
2664 2664 _(b'skipping'), total=len(remaining), unit=_(b'files')
2665 2665 )
2666 2666 for f in remaining:
2667 2667 progress.increment()
2668 2668 if ui.verbose or (f in files):
2669 2669 warnings.append(
2670 2670 _(b'not removing %s: file still exists\n') % uipathfn(f)
2671 2671 )
2672 2672 ret = 1
2673 2673 progress.complete()
2674 2674 else:
2675 2675 list = deleted + clean
2676 2676 progress = ui.makeprogress(
2677 2677 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2678 2678 )
2679 2679 for f in modified:
2680 2680 progress.increment()
2681 2681 warnings.append(
2682 2682 _(
2683 2683 b'not removing %s: file is modified (use -f'
2684 2684 b' to force removal)\n'
2685 2685 )
2686 2686 % uipathfn(f)
2687 2687 )
2688 2688 ret = 1
2689 2689 for f in added:
2690 2690 progress.increment()
2691 2691 warnings.append(
2692 2692 _(
2693 2693 b"not removing %s: file has been marked for add"
2694 2694 b" (use 'hg forget' to undo add)\n"
2695 2695 )
2696 2696 % uipathfn(f)
2697 2697 )
2698 2698 ret = 1
2699 2699 progress.complete()
2700 2700
2701 2701 list = sorted(list)
2702 2702 progress = ui.makeprogress(
2703 2703 _(b'deleting'), total=len(list), unit=_(b'files')
2704 2704 )
2705 2705 for f in list:
2706 2706 if ui.verbose or not m.exact(f):
2707 2707 progress.increment()
2708 2708 ui.status(
2709 2709 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2710 2710 )
2711 2711 progress.complete()
2712 2712
2713 2713 if not dryrun:
2714 2714 with repo.wlock():
2715 2715 if not after:
2716 2716 for f in list:
2717 2717 if f in added:
2718 2718 continue # we never unlink added files on remove
2719 2719 rmdir = repo.ui.configbool(
2720 2720 b'experimental', b'removeemptydirs'
2721 2721 )
2722 2722 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2723 2723 repo[None].forget(list)
2724 2724
2725 2725 if warn:
2726 2726 for warning in warnings:
2727 2727 ui.warn(warning)
2728 2728
2729 2729 return ret
2730 2730
2731 2731
2732 2732 def _catfmtneedsdata(fm):
2733 2733 return not fm.datahint() or b'data' in fm.datahint()
2734 2734
2735 2735
2736 2736 def _updatecatformatter(fm, ctx, matcher, path, decode):
2737 2737 """Hook for adding data to the formatter used by ``hg cat``.
2738 2738
2739 2739 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2740 2740 this method first."""
2741 2741
2742 2742 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2743 2743 # wasn't requested.
2744 2744 data = b''
2745 2745 if _catfmtneedsdata(fm):
2746 2746 data = ctx[path].data()
2747 2747 if decode:
2748 2748 data = ctx.repo().wwritedata(path, data)
2749 2749 fm.startitem()
2750 2750 fm.context(ctx=ctx)
2751 2751 fm.write(b'data', b'%s', data)
2752 2752 fm.data(path=path)
2753 2753
2754 2754
2755 2755 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2756 2756 err = 1
2757 2757
2758 2758 def write(path):
2759 2759 filename = None
2760 2760 if fntemplate:
2761 2761 filename = makefilename(
2762 2762 ctx, fntemplate, pathname=os.path.join(prefix, path)
2763 2763 )
2764 2764 # attempt to create the directory if it does not already exist
2765 2765 try:
2766 2766 os.makedirs(os.path.dirname(filename))
2767 2767 except OSError:
2768 2768 pass
2769 2769 with formatter.maybereopen(basefm, filename) as fm:
2770 2770 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2771 2771
2772 2772 # Automation often uses hg cat on single files, so special case it
2773 2773 # for performance to avoid the cost of parsing the manifest.
2774 2774 if len(matcher.files()) == 1 and not matcher.anypats():
2775 2775 file = matcher.files()[0]
2776 2776 mfl = repo.manifestlog
2777 2777 mfnode = ctx.manifestnode()
2778 2778 try:
2779 2779 if mfnode and mfl[mfnode].find(file)[0]:
2780 2780 if _catfmtneedsdata(basefm):
2781 2781 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
2782 2782 write(file)
2783 2783 return 0
2784 2784 except KeyError:
2785 2785 pass
2786 2786
2787 2787 if _catfmtneedsdata(basefm):
2788 2788 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
2789 2789
2790 2790 for abs in ctx.walk(matcher):
2791 2791 write(abs)
2792 2792 err = 0
2793 2793
2794 2794 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2795 2795 for subpath in sorted(ctx.substate):
2796 2796 sub = ctx.sub(subpath)
2797 2797 try:
2798 2798 submatch = matchmod.subdirmatcher(subpath, matcher)
2799 2799 subprefix = os.path.join(prefix, subpath)
2800 2800 if not sub.cat(
2801 2801 submatch,
2802 2802 basefm,
2803 2803 fntemplate,
2804 2804 subprefix,
2805 2805 **opts,
2806 2806 ):
2807 2807 err = 0
2808 2808 except error.RepoLookupError:
2809 2809 ui.status(
2810 2810 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2811 2811 )
2812 2812
2813 2813 return err
2814 2814
2815 2815
2816 2816 class _AddRemoveContext:
2817 2817 """a small (hacky) context to deal with lazy opening of context
2818 2818
2819 2819 This is to be used in the `commit` function right below. This deals with
2820 2820 lazily open a `changing_files` context inside a `transaction` that span the
2821 2821 full commit operation.
2822 2822
2823 2823 We need :
2824 2824 - a `changing_files` context to wrap the dirstate change within the
2825 2825 "addremove" operation,
2826 2826 - a transaction to make sure these change are not written right after the
2827 2827 addremove, but when the commit operation succeed.
2828 2828
2829 2829 However it get complicated because:
2830 2830 - opening a transaction "this early" shuffle hooks order, especially the
2831 2831 `precommit` one happening after the `pretxtopen` one which I am not too
2832 2832 enthusiastic about.
2833 2833 - the `mq` extensions + the `record` extension stacks many layers of call
2834 2834 to implement `qrefresh --interactive` and this result with `mq` calling a
2835 2835 `strip` in the middle of this function. Which prevent the existence of
2836 2836 transaction wrapping all of its function code. (however, `qrefresh` never
2837 2837 call the `addremove` bits.
2838 2838 - the largefile extensions (and maybe other extensions?) wraps `addremove`
2839 2839 so slicing `addremove` in smaller bits is a complex endeavour.
2840 2840
2841 2841 So I eventually took a this shortcut that open the transaction if we
2842 2842 actually needs it, not disturbing much of the rest of the code.
2843 2843
2844 2844 It will result in some hooks order change for `hg commit --addremove`,
2845 2845 however it seems a corner case enough to ignore that for now (hopefully).
2846 2846
2847 2847 Notes that None of the above problems seems insurmountable, however I have
2848 2848 been fighting with this specific piece of code for a couple of day already
2849 2849 and I need a solution to keep moving forward on the bigger work around
2850 2850 `changing_files` context that is being introduced at the same time as this
2851 2851 hack.
2852 2852
2853 2853 Each problem seems to have a solution:
2854 2854 - the hook order issue could be solved by refactoring the many-layer stack
2855 2855 that currently composes a commit and calling them earlier,
2856 2856 - the mq issue could be solved by refactoring `mq` so that the final strip
2857 2857 is done after transaction closure. Be warned that the mq code is quite
2858 2858 antic however.
2859 2859 - large-file could be reworked in parallel of the `addremove` to be
2860 2860 friendlier to this.
2861 2861
2862 2862 However each of these tasks are too much a diversion right now. In addition
2863 2863 they will be much easier to undertake when the `changing_files` dust has
2864 2864 settled."""
2865 2865
2866 2866 def __init__(self, repo):
2867 2867 self._repo = repo
2868 2868 self._transaction = None
2869 2869 self._dirstate_context = None
2870 2870 self._state = None
2871 2871
2872 2872 def __enter__(self):
2873 2873 assert self._state is None
2874 2874 self._state = True
2875 2875 return self
2876 2876
2877 2877 def open_transaction(self):
2878 2878 """open a `transaction` and `changing_files` context
2879 2879
2880 2880 Call this when you know that change to the dirstate will be needed and
2881 2881 we need to open the transaction early
2882 2882
2883 2883 This will also open the dirstate `changing_files` context, so you should
2884 2884 call `close_dirstate_context` when the distate changes are done.
2885 2885 """
2886 2886 assert self._state is not None
2887 2887 if self._transaction is None:
2888 2888 self._transaction = self._repo.transaction(b'commit')
2889 2889 self._transaction.__enter__()
2890 2890 if self._dirstate_context is None:
2891 2891 self._dirstate_context = self._repo.dirstate.changing_files(
2892 2892 self._repo
2893 2893 )
2894 2894 self._dirstate_context.__enter__()
2895 2895
2896 2896 def close_dirstate_context(self):
2897 2897 """close the change_files if any
2898 2898
2899 2899 Call this after the (potential) `open_transaction` call to close the
2900 2900 (potential) changing_files context.
2901 2901 """
2902 2902 if self._dirstate_context is not None:
2903 2903 self._dirstate_context.__exit__(None, None, None)
2904 2904 self._dirstate_context = None
2905 2905
2906 2906 def __exit__(self, *args):
2907 2907 if self._dirstate_context is not None:
2908 2908 self._dirstate_context.__exit__(*args)
2909 2909 if self._transaction is not None:
2910 2910 self._transaction.__exit__(*args)
2911 2911
2912 2912
2913 2913 def commit(ui, repo, commitfunc, pats, opts):
2914 2914 '''commit the specified files or all outstanding changes'''
2915 2915 date = opts.get(b'date')
2916 2916 if date:
2917 2917 opts[b'date'] = dateutil.parsedate(date)
2918 2918
2919 2919 with repo.wlock(), repo.lock():
2920 2920 message = logmessage(ui, opts)
2921 2921 matcher = scmutil.match(repo[None], pats, opts)
2922 2922
2923 2923 with _AddRemoveContext(repo) as c:
2924 2924 # extract addremove carefully -- this function can be called from a
2925 2925 # command that doesn't support addremove
2926 2926 if opts.get(b'addremove'):
2927 2927 relative = scmutil.anypats(pats, opts)
2928 2928 uipathfn = scmutil.getuipathfn(
2929 2929 repo,
2930 2930 legacyrelativevalue=relative,
2931 2931 )
2932 2932 r = scmutil.addremove(
2933 2933 repo,
2934 2934 matcher,
2935 2935 b"",
2936 2936 uipathfn,
2937 2937 opts,
2938 2938 open_tr=c.open_transaction,
2939 2939 )
2940 2940 m = _(b"failed to mark all new/missing files as added/removed")
2941 2941 if r != 0:
2942 2942 raise error.Abort(m)
2943 2943 c.close_dirstate_context()
2944 2944 return commitfunc(ui, repo, message, matcher, opts)
2945 2945
2946 2946
2947 2947 def samefile(f, ctx1, ctx2):
2948 2948 if f in ctx1.manifest():
2949 2949 a = ctx1.filectx(f)
2950 2950 if f in ctx2.manifest():
2951 2951 b = ctx2.filectx(f)
2952 2952 return not a.cmp(b) and a.flags() == b.flags()
2953 2953 else:
2954 2954 return False
2955 2955 else:
2956 2956 return f not in ctx2.manifest()
2957 2957
2958 2958
2959 2959 def amend(ui, repo, old, extra, pats, opts: Dict[str, Any]):
2960 2960 # avoid cycle context -> subrepo -> cmdutil
2961 2961 from . import context
2962 2962
2963 2963 # amend will reuse the existing user if not specified, but the obsolete
2964 2964 # marker creation requires that the current user's name is specified.
2965 2965 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2966 2966 ui.username() # raise exception if username not set
2967 2967
2968 2968 ui.note(_(b'amending changeset %s\n') % old)
2969 2969 base = old.p1()
2970 2970
2971 2971 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
2972 2972 # Participating changesets:
2973 2973 #
2974 2974 # wctx o - workingctx that contains changes from working copy
2975 2975 # | to go into amending commit
2976 2976 # |
2977 2977 # old o - changeset to amend
2978 2978 # |
2979 2979 # base o - first parent of the changeset to amend
2980 2980 wctx = repo[None]
2981 2981
2982 2982 # Copy to avoid mutating input
2983 2983 extra = extra.copy()
2984 2984 # Update extra dict from amended commit (e.g. to preserve graft
2985 2985 # source)
2986 2986 extra.update(old.extra())
2987 2987
2988 2988 # Also update it from the from the wctx
2989 2989 extra.update(wctx.extra())
2990 2990
2991 2991 # date-only change should be ignored?
2992 2992 datemaydiffer = resolve_commit_options(ui, opts)
2993 2993 opts = pycompat.byteskwargs(opts)
2994 2994
2995 2995 date = old.date()
2996 2996 if opts.get(b'date'):
2997 2997 date = dateutil.parsedate(opts.get(b'date'))
2998 2998 user = opts.get(b'user') or old.user()
2999 2999
3000 3000 if len(old.parents()) > 1:
3001 3001 # ctx.files() isn't reliable for merges, so fall back to the
3002 3002 # slower repo.status() method
3003 3003 st = base.status(old)
3004 3004 files = set(st.modified) | set(st.added) | set(st.removed)
3005 3005 else:
3006 3006 files = set(old.files())
3007 3007
3008 3008 # add/remove the files to the working copy if the "addremove" option
3009 3009 # was specified.
3010 3010 matcher = scmutil.match(wctx, pats, opts)
3011 3011 relative = scmutil.anypats(pats, opts)
3012 3012 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
3013 3013 if opts.get(b'addremove'):
3014 3014 with repo.dirstate.changing_files(repo):
3015 3015 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
3016 3016 m = _(
3017 3017 b"failed to mark all new/missing files as added/removed"
3018 3018 )
3019 3019 raise error.Abort(m)
3020 3020
3021 3021 # Check subrepos. This depends on in-place wctx._status update in
3022 3022 # subrepo.precommit(). To minimize the risk of this hack, we do
3023 3023 # nothing if .hgsub does not exist.
3024 3024 if b'.hgsub' in wctx or b'.hgsub' in old:
3025 3025 subs, commitsubs, newsubstate = subrepoutil.precommit(
3026 3026 ui, wctx, wctx._status, matcher
3027 3027 )
3028 3028 # amend should abort if commitsubrepos is enabled
3029 3029 assert not commitsubs
3030 3030 if subs:
3031 3031 subrepoutil.writestate(repo, newsubstate)
3032 3032
3033 3033 ms = mergestatemod.mergestate.read(repo)
3034 3034 mergeutil.checkunresolved(ms)
3035 3035
3036 3036 filestoamend = {f for f in wctx.files() if matcher(f)}
3037 3037
3038 3038 changes = len(filestoamend) > 0
3039 3039 changeset_copies = (
3040 3040 repo.ui.config(b'experimental', b'copies.read-from')
3041 3041 != b'filelog-only'
3042 3042 )
3043 3043 # If there are changes to amend or if copy information needs to be read
3044 3044 # from the changeset extras, we cannot take the fast path of using
3045 3045 # filectxs from the old commit.
3046 3046 if changes or changeset_copies:
3047 3047 # Recompute copies (avoid recording a -> b -> a)
3048 3048 copied = copies.pathcopies(base, wctx)
3049 3049 if old.p2():
3050 3050 copied.update(copies.pathcopies(old.p2(), wctx))
3051 3051
3052 3052 # Prune files which were reverted by the updates: if old
3053 3053 # introduced file X and the file was renamed in the working
3054 3054 # copy, then those two files are the same and
3055 3055 # we can discard X from our list of files. Likewise if X
3056 3056 # was removed, it's no longer relevant. If X is missing (aka
3057 3057 # deleted), old X must be preserved.
3058 3058 files.update(filestoamend)
3059 3059 files = [
3060 3060 f
3061 3061 for f in files
3062 3062 if (f not in filestoamend or not samefile(f, wctx, base))
3063 3063 ]
3064 3064
3065 3065 def filectxfn(repo, ctx_, path):
3066 3066 try:
3067 3067 # If the file being considered is not amongst the files
3068 3068 # to be amended, we should use the file context from the
3069 3069 # old changeset. This avoids issues when only some files in
3070 3070 # the working copy are being amended but there are also
3071 3071 # changes to other files from the old changeset.
3072 3072 if path in filestoamend:
3073 3073 # Return None for removed files.
3074 3074 if path in wctx.removed():
3075 3075 return None
3076 3076 fctx = wctx[path]
3077 3077 else:
3078 3078 fctx = old.filectx(path)
3079 3079 flags = fctx.flags()
3080 3080 mctx = context.memfilectx(
3081 3081 repo,
3082 3082 ctx_,
3083 3083 fctx.path(),
3084 3084 fctx.data(),
3085 3085 islink=b'l' in flags,
3086 3086 isexec=b'x' in flags,
3087 3087 copysource=copied.get(path),
3088 3088 )
3089 3089 return mctx
3090 3090 except KeyError:
3091 3091 return None
3092 3092
3093 3093 else:
3094 3094 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
3095 3095
3096 3096 # Use version of files as in the old cset
3097 3097 def filectxfn(repo, ctx_, path):
3098 3098 try:
3099 3099 return old.filectx(path)
3100 3100 except KeyError:
3101 3101 return None
3102 3102
3103 3103 # See if we got a message from -m or -l, if not, open the editor with
3104 3104 # the message of the changeset to amend.
3105 3105 message = logmessage(ui, opts)
3106 3106
3107 3107 editform = mergeeditform(old, b'commit.amend')
3108 3108
3109 3109 if not message:
3110 3110 message = old.description()
3111 3111 # Default if message isn't provided and --edit is not passed is to
3112 3112 # invoke editor, but allow --no-edit. If somehow we don't have any
3113 3113 # description, let's always start the editor.
3114 3114 doedit = not message or opts.get(b'edit') in [True, None]
3115 3115 else:
3116 3116 # Default if message is provided is to not invoke editor, but allow
3117 3117 # --edit.
3118 3118 doedit = opts.get(b'edit') is True
3119 3119 editor = getcommiteditor(edit=doedit, editform=editform)
3120 3120
3121 3121 pureextra = extra.copy()
3122 3122 extra[b'amend_source'] = old.hex()
3123 3123
3124 3124 new = context.memctx(
3125 3125 repo,
3126 3126 parents=[base.node(), old.p2().node()],
3127 3127 text=message,
3128 3128 files=files,
3129 3129 filectxfn=filectxfn,
3130 3130 user=user,
3131 3131 date=date,
3132 3132 extra=extra,
3133 3133 editor=editor,
3134 3134 )
3135 3135
3136 3136 newdesc = changelog.stripdesc(new.description())
3137 3137 if (
3138 3138 (not changes)
3139 3139 and newdesc == old.description()
3140 3140 and user == old.user()
3141 3141 and (date == old.date() or datemaydiffer)
3142 3142 and pureextra == old.extra()
3143 3143 ):
3144 3144 # nothing changed. continuing here would create a new node
3145 3145 # anyway because of the amend_source noise.
3146 3146 #
3147 3147 # This not what we expect from amend.
3148 3148 return old.node()
3149 3149
3150 3150 commitphase = None
3151 3151 if opts.get(b'secret'):
3152 3152 commitphase = phases.secret
3153 3153 elif opts.get(b'draft'):
3154 3154 commitphase = phases.draft
3155 3155 newid = repo.commitctx(new)
3156 3156 ms.reset()
3157 3157
3158 3158 with repo.dirstate.changing_parents(repo):
3159 3159 # Reroute the working copy parent to the new changeset
3160 3160 repo.setparents(newid, repo.nullid)
3161 3161
3162 3162 # Fixing the dirstate because localrepo.commitctx does not update
3163 3163 # it. This is rather convenient because we did not need to update
3164 3164 # the dirstate for all the files in the new commit which commitctx
3165 3165 # could have done if it updated the dirstate. Now, we can
3166 3166 # selectively update the dirstate only for the amended files.
3167 3167 dirstate = repo.dirstate
3168 3168
3169 3169 # Update the state of the files which were added and modified in the
3170 3170 # amend to "normal" in the dirstate. We need to use "normallookup" since
3171 3171 # the files may have changed since the command started; using "normal"
3172 3172 # would mark them as clean but with uncommitted contents.
3173 3173 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
3174 3174 for f in normalfiles:
3175 3175 dirstate.update_file(
3176 3176 f, p1_tracked=True, wc_tracked=True, possibly_dirty=True
3177 3177 )
3178 3178
3179 3179 # Update the state of files which were removed in the amend
3180 3180 # to "removed" in the dirstate.
3181 3181 removedfiles = set(wctx.removed()) & filestoamend
3182 3182 for f in removedfiles:
3183 3183 dirstate.update_file(f, p1_tracked=False, wc_tracked=False)
3184 3184
3185 3185 mapping = {old.node(): (newid,)}
3186 3186 obsmetadata = None
3187 3187 if opts.get(b'note'):
3188 3188 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
3189 3189 backup = ui.configbool(b'rewrite', b'backup-bundle')
3190 3190 scmutil.cleanupnodes(
3191 3191 repo,
3192 3192 mapping,
3193 3193 b'amend',
3194 3194 metadata=obsmetadata,
3195 3195 fixphase=True,
3196 3196 targetphase=commitphase,
3197 3197 backup=backup,
3198 3198 )
3199 3199
3200 3200 return newid
3201 3201
3202 3202
3203 3203 def commiteditor(repo, ctx, subs, editform=b''):
3204 3204 if ctx.description():
3205 3205 return ctx.description()
3206 3206 return commitforceeditor(
3207 3207 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
3208 3208 )
3209 3209
3210 3210
3211 3211 def commitforceeditor(
3212 3212 repo,
3213 3213 ctx,
3214 3214 subs,
3215 3215 finishdesc=None,
3216 3216 extramsg=None,
3217 3217 editform=b'',
3218 3218 unchangedmessagedetection=False,
3219 3219 ):
3220 3220 if not extramsg:
3221 3221 extramsg = _(b"Leave message empty to abort commit.")
3222 3222
3223 3223 forms = [e for e in editform.split(b'.') if e]
3224 3224 forms.insert(0, b'changeset')
3225 3225 templatetext = None
3226 3226 while forms:
3227 3227 ref = b'.'.join(forms)
3228 3228 if repo.ui.config(b'committemplate', ref):
3229 3229 templatetext = committext = buildcommittemplate(
3230 3230 repo, ctx, subs, extramsg, ref
3231 3231 )
3232 3232 break
3233 3233 forms.pop()
3234 3234 else:
3235 3235 committext = buildcommittext(repo, ctx, subs, extramsg)
3236 3236
3237 3237 # run editor in the repository root
3238 3238 olddir = encoding.getcwd()
3239 3239 os.chdir(repo.root)
3240 3240
3241 3241 # make in-memory changes visible to external process
3242 3242 tr = repo.currenttransaction()
3243 3243 repo.dirstate.write(tr)
3244 3244 pending = tr and tr.writepending() and repo.root
3245 3245
3246 3246 editortext = repo.ui.edit(
3247 3247 committext,
3248 3248 ctx.user(),
3249 3249 ctx.extra(),
3250 3250 editform=editform,
3251 3251 pending=pending,
3252 3252 repopath=repo.path,
3253 3253 action=b'commit',
3254 3254 )
3255 3255 text = editortext
3256 3256
3257 3257 # strip away anything below this special string (used for editors that want
3258 3258 # to display the diff)
3259 3259 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3260 3260 if stripbelow:
3261 3261 text = text[: stripbelow.start()]
3262 3262
3263 3263 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3264 3264 os.chdir(olddir)
3265 3265
3266 3266 if finishdesc:
3267 3267 text = finishdesc(text)
3268 3268 if not text.strip():
3269 3269 raise error.InputError(_(b"empty commit message"))
3270 3270 if unchangedmessagedetection and editortext == templatetext:
3271 3271 raise error.InputError(_(b"commit message unchanged"))
3272 3272
3273 3273 return text
3274 3274
3275 3275
3276 3276 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3277 3277 ui = repo.ui
3278 3278 spec = formatter.reference_templatespec(ref)
3279 3279 t = logcmdutil.changesettemplater(ui, repo, spec)
3280 3280 t.t.cache.update(
3281 3281 (k, templater.unquotestring(v))
3282 3282 for k, v in repo.ui.configitems(b'committemplate')
3283 3283 )
3284 3284
3285 3285 if not extramsg:
3286 3286 extramsg = b'' # ensure that extramsg is string
3287 3287
3288 3288 ui.pushbuffer()
3289 3289 t.show(ctx, extramsg=extramsg)
3290 3290 return ui.popbuffer()
3291 3291
3292 3292
3293 3293 def hgprefix(msg):
3294 3294 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3295 3295
3296 3296
3297 3297 def buildcommittext(repo, ctx, subs, extramsg):
3298 3298 edittext = []
3299 3299 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3300 3300 if ctx.description():
3301 3301 edittext.append(ctx.description())
3302 3302 edittext.append(b"")
3303 3303 edittext.append(b"") # Empty line between message and comments.
3304 3304 edittext.append(
3305 3305 hgprefix(
3306 3306 _(
3307 3307 b"Enter commit message."
3308 3308 b" Lines beginning with 'HG:' are removed."
3309 3309 )
3310 3310 )
3311 3311 )
3312 3312 edittext.append(hgprefix(extramsg))
3313 3313 edittext.append(b"HG: --")
3314 3314 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3315 3315 if ctx.p2():
3316 3316 edittext.append(hgprefix(_(b"branch merge")))
3317 3317 if ctx.branch():
3318 3318 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3319 3319 if bookmarks.isactivewdirparent(repo):
3320 3320 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3321 3321 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3322 3322 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3323 3323 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3324 3324 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3325 3325 if not added and not modified and not removed:
3326 3326 edittext.append(hgprefix(_(b"no files changed")))
3327 3327 edittext.append(b"")
3328 3328
3329 3329 return b"\n".join(edittext)
3330 3330
3331 3331
3332 3332 def commitstatus(repo, node, branch, bheads=None, tip=None, **opts):
3333 3333 ctx = repo[node]
3334 3334 parents = ctx.parents()
3335 3335
3336 3336 if tip is not None and repo.changelog.tip() == tip:
3337 3337 # avoid reporting something like "committed new head" when
3338 3338 # recommitting old changesets, and issue a helpful warning
3339 3339 # for most instances
3340 3340 repo.ui.warn(_(b"warning: commit already existed in the repository!\n"))
3341 3341 elif (
3342 3342 not opts.get('amend')
3343 3343 and bheads
3344 3344 and node not in bheads
3345 3345 and not any(
3346 3346 p.node() in bheads and p.branch() == branch for p in parents
3347 3347 )
3348 3348 ):
3349 3349 repo.ui.status(_(b'created new head\n'))
3350 3350 # The message is not printed for initial roots. For the other
3351 3351 # changesets, it is printed in the following situations:
3352 3352 #
3353 3353 # Par column: for the 2 parents with ...
3354 3354 # N: null or no parent
3355 3355 # B: parent is on another named branch
3356 3356 # C: parent is a regular non head changeset
3357 3357 # H: parent was a branch head of the current branch
3358 3358 # Msg column: whether we print "created new head" message
3359 3359 # In the following, it is assumed that there already exists some
3360 3360 # initial branch heads of the current branch, otherwise nothing is
3361 3361 # printed anyway.
3362 3362 #
3363 3363 # Par Msg Comment
3364 3364 # N N y additional topo root
3365 3365 #
3366 3366 # B N y additional branch root
3367 3367 # C N y additional topo head
3368 3368 # H N n usual case
3369 3369 #
3370 3370 # B B y weird additional branch root
3371 3371 # C B y branch merge
3372 3372 # H B n merge with named branch
3373 3373 #
3374 3374 # C C y additional head from merge
3375 3375 # C H n merge with a head
3376 3376 #
3377 3377 # H H n head merge: head count decreases
3378 3378
3379 3379 if not opts.get('close_branch'):
3380 3380 for r in parents:
3381 3381 if r.closesbranch() and r.branch() == branch:
3382 3382 repo.ui.status(
3383 3383 _(b'reopening closed branch head %d\n') % r.rev()
3384 3384 )
3385 3385
3386 3386 if repo.ui.debugflag:
3387 3387 repo.ui.write(
3388 3388 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3389 3389 )
3390 3390 elif repo.ui.verbose:
3391 3391 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3392 3392
3393 3393
3394 3394 def postcommitstatus(repo, pats, opts):
3395 3395 return repo.status(match=scmutil.match(repo[None], pats, opts))
3396 3396
3397 3397
3398 3398 def revert(ui, repo, ctx, *pats, **opts):
3399 3399 opts = pycompat.byteskwargs(opts)
3400 3400 parent, p2 = repo.dirstate.parents()
3401 3401 node = ctx.node()
3402 3402
3403 3403 mf = ctx.manifest()
3404 3404 if node == p2:
3405 3405 parent = p2
3406 3406
3407 3407 # need all matching names in dirstate and manifest of target rev,
3408 3408 # so have to walk both. do not print errors if files exist in one
3409 3409 # but not other. in both cases, filesets should be evaluated against
3410 3410 # workingctx to get consistent result (issue4497). this means 'set:**'
3411 3411 # cannot be used to select missing files from target rev.
3412 3412
3413 3413 # `names` is a mapping for all elements in working copy and target revision
3414 3414 # The mapping is in the form:
3415 3415 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3416 3416 names = {}
3417 3417 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3418 3418
3419 3419 with repo.wlock(), repo.dirstate.changing_files(repo):
3420 3420 ## filling of the `names` mapping
3421 3421 # walk dirstate to fill `names`
3422 3422
3423 3423 interactive = opts.get(b'interactive', False)
3424 3424 wctx = repo[None]
3425 3425 m = scmutil.match(wctx, pats, opts)
3426 3426
3427 3427 # we'll need this later
3428 3428 targetsubs = sorted(s for s in wctx.substate if m(s))
3429 3429
3430 3430 if not m.always():
3431 3431 matcher = matchmod.badmatch(m, lambda x, y: False)
3432 3432 for abs in wctx.walk(matcher):
3433 3433 names[abs] = m.exact(abs)
3434 3434
3435 3435 # walk target manifest to fill `names`
3436 3436
3437 3437 def badfn(path, msg):
3438 3438 if path in names:
3439 3439 return
3440 3440 if path in ctx.substate:
3441 3441 return
3442 3442 path_ = path + b'/'
3443 3443 for f in names:
3444 3444 if f.startswith(path_):
3445 3445 return
3446 3446 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3447 3447
3448 3448 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3449 3449 if abs not in names:
3450 3450 names[abs] = m.exact(abs)
3451 3451
3452 3452 # Find status of all file in `names`.
3453 3453 m = scmutil.matchfiles(repo, names)
3454 3454
3455 3455 changes = repo.status(
3456 3456 node1=node, match=m, unknown=True, ignored=True, clean=True
3457 3457 )
3458 3458 else:
3459 3459 changes = repo.status(node1=node, match=m)
3460 3460 for kind in changes:
3461 3461 for abs in kind:
3462 3462 names[abs] = m.exact(abs)
3463 3463
3464 3464 m = scmutil.matchfiles(repo, names)
3465 3465
3466 3466 modified = set(changes.modified)
3467 3467 added = set(changes.added)
3468 3468 removed = set(changes.removed)
3469 3469 _deleted = set(changes.deleted)
3470 3470 unknown = set(changes.unknown)
3471 3471 unknown.update(changes.ignored)
3472 3472 clean = set(changes.clean)
3473 3473 modadded = set()
3474 3474
3475 3475 # We need to account for the state of the file in the dirstate,
3476 3476 # even when we revert against something else than parent. This will
3477 3477 # slightly alter the behavior of revert (doing back up or not, delete
3478 3478 # or just forget etc).
3479 3479 if parent == node:
3480 3480 dsmodified = modified
3481 3481 dsadded = added
3482 3482 dsremoved = removed
3483 3483 # store all local modifications, useful later for rename detection
3484 3484 localchanges = dsmodified | dsadded
3485 3485 modified, added, removed = set(), set(), set()
3486 3486 else:
3487 3487 changes = repo.status(node1=parent, match=m)
3488 3488 dsmodified = set(changes.modified)
3489 3489 dsadded = set(changes.added)
3490 3490 dsremoved = set(changes.removed)
3491 3491 # store all local modifications, useful later for rename detection
3492 3492 localchanges = dsmodified | dsadded
3493 3493
3494 3494 # only take into account for removes between wc and target
3495 3495 clean |= dsremoved - removed
3496 3496 dsremoved &= removed
3497 3497 # distinct between dirstate remove and other
3498 3498 removed -= dsremoved
3499 3499
3500 3500 modadded = added & dsmodified
3501 3501 added -= modadded
3502 3502
3503 3503 # tell newly modified apart.
3504 3504 dsmodified &= modified
3505 3505 dsmodified |= modified & dsadded # dirstate added may need backup
3506 3506 modified -= dsmodified
3507 3507
3508 3508 # We need to wait for some post-processing to update this set
3509 3509 # before making the distinction. The dirstate will be used for
3510 3510 # that purpose.
3511 3511 dsadded = added
3512 3512
3513 3513 # in case of merge, files that are actually added can be reported as
3514 3514 # modified, we need to post process the result
3515 3515 if p2 != repo.nullid:
3516 3516 mergeadd = set(dsmodified)
3517 3517 for path in dsmodified:
3518 3518 if path in mf:
3519 3519 mergeadd.remove(path)
3520 3520 dsadded |= mergeadd
3521 3521 dsmodified -= mergeadd
3522 3522
3523 3523 # if f is a rename, update `names` to also revert the source
3524 3524 for f in localchanges:
3525 3525 src = repo.dirstate.copied(f)
3526 3526 # XXX should we check for rename down to target node?
3527 3527 if (
3528 3528 src
3529 3529 and src not in names
3530 3530 and repo.dirstate.get_entry(src).removed
3531 3531 ):
3532 3532 dsremoved.add(src)
3533 3533 names[src] = True
3534 3534
3535 3535 # determine the exact nature of the deleted changesets
3536 3536 deladded = set(_deleted)
3537 3537 for path in _deleted:
3538 3538 if path in mf:
3539 3539 deladded.remove(path)
3540 3540 deleted = _deleted - deladded
3541 3541
3542 3542 # distinguish between file to forget and the other
3543 3543 added = set()
3544 3544 for abs in dsadded:
3545 3545 if not repo.dirstate.get_entry(abs).added:
3546 3546 added.add(abs)
3547 3547 dsadded -= added
3548 3548
3549 3549 for abs in deladded:
3550 3550 if repo.dirstate.get_entry(abs).added:
3551 3551 dsadded.add(abs)
3552 3552 deladded -= dsadded
3553 3553
3554 3554 # For files marked as removed, we check if an unknown file is present at
3555 3555 # the same path. If a such file exists it may need to be backed up.
3556 3556 # Making the distinction at this stage helps have simpler backup
3557 3557 # logic.
3558 3558 removunk = set()
3559 3559 for abs in removed:
3560 3560 target = repo.wjoin(abs)
3561 3561 if os.path.lexists(target):
3562 3562 removunk.add(abs)
3563 3563 removed -= removunk
3564 3564
3565 3565 dsremovunk = set()
3566 3566 for abs in dsremoved:
3567 3567 target = repo.wjoin(abs)
3568 3568 if os.path.lexists(target):
3569 3569 dsremovunk.add(abs)
3570 3570 dsremoved -= dsremovunk
3571 3571
3572 3572 # action to be actually performed by revert
3573 3573 # (<list of file>, message>) tuple
3574 3574 actions = {
3575 3575 b'revert': ([], _(b'reverting %s\n')),
3576 3576 b'add': ([], _(b'adding %s\n')),
3577 3577 b'remove': ([], _(b'removing %s\n')),
3578 3578 b'drop': ([], _(b'removing %s\n')),
3579 3579 b'forget': ([], _(b'forgetting %s\n')),
3580 3580 b'undelete': ([], _(b'undeleting %s\n')),
3581 3581 b'noop': (None, _(b'no changes needed to %s\n')),
3582 3582 b'unknown': (None, _(b'file not managed: %s\n')),
3583 3583 }
3584 3584
3585 3585 # "constant" that convey the backup strategy.
3586 3586 # All set to `discard` if `no-backup` is set do avoid checking
3587 3587 # no_backup lower in the code.
3588 3588 # These values are ordered for comparison purposes
3589 3589 backupinteractive = 3 # do backup if interactively modified
3590 3590 backup = 2 # unconditionally do backup
3591 3591 check = 1 # check if the existing file differs from target
3592 3592 discard = 0 # never do backup
3593 3593 if opts.get(b'no_backup'):
3594 3594 backupinteractive = backup = check = discard
3595 3595 if interactive:
3596 3596 dsmodifiedbackup = backupinteractive
3597 3597 else:
3598 3598 dsmodifiedbackup = backup
3599 3599 tobackup = set()
3600 3600
3601 3601 backupanddel = actions[b'remove']
3602 3602 if not opts.get(b'no_backup'):
3603 3603 backupanddel = actions[b'drop']
3604 3604
3605 3605 disptable = (
3606 3606 # dispatch table:
3607 3607 # file state
3608 3608 # action
3609 3609 # make backup
3610 3610 ## Sets that results that will change file on disk
3611 3611 # Modified compared to target, no local change
3612 3612 (modified, actions[b'revert'], discard),
3613 3613 # Modified compared to target, but local file is deleted
3614 3614 (deleted, actions[b'revert'], discard),
3615 3615 # Modified compared to target, local change
3616 3616 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3617 3617 # Added since target
3618 3618 (added, actions[b'remove'], discard),
3619 3619 # Added in working directory
3620 3620 (dsadded, actions[b'forget'], discard),
3621 3621 # Added since target, have local modification
3622 3622 (modadded, backupanddel, backup),
3623 3623 # Added since target but file is missing in working directory
3624 3624 (deladded, actions[b'drop'], discard),
3625 3625 # Removed since target, before working copy parent
3626 3626 (removed, actions[b'add'], discard),
3627 3627 # Same as `removed` but an unknown file exists at the same path
3628 3628 (removunk, actions[b'add'], check),
3629 3629 # Removed since targe, marked as such in working copy parent
3630 3630 (dsremoved, actions[b'undelete'], discard),
3631 3631 # Same as `dsremoved` but an unknown file exists at the same path
3632 3632 (dsremovunk, actions[b'undelete'], check),
3633 3633 ## the following sets does not result in any file changes
3634 3634 # File with no modification
3635 3635 (clean, actions[b'noop'], discard),
3636 3636 # Existing file, not tracked anywhere
3637 3637 (unknown, actions[b'unknown'], discard),
3638 3638 )
3639 3639
3640 3640 for abs, exact in sorted(names.items()):
3641 3641 # target file to be touch on disk (relative to cwd)
3642 3642 target = repo.wjoin(abs)
3643 3643 # search the entry in the dispatch table.
3644 3644 # if the file is in any of these sets, it was touched in the working
3645 3645 # directory parent and we are sure it needs to be reverted.
3646 3646 for table, (xlist, msg), dobackup in disptable:
3647 3647 if abs not in table:
3648 3648 continue
3649 3649 if xlist is not None:
3650 3650 xlist.append(abs)
3651 3651 if dobackup:
3652 3652 # If in interactive mode, don't automatically create
3653 3653 # .orig files (issue4793)
3654 3654 if dobackup == backupinteractive:
3655 3655 tobackup.add(abs)
3656 3656 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3657 3657 absbakname = scmutil.backuppath(ui, repo, abs)
3658 3658 bakname = os.path.relpath(
3659 3659 absbakname, start=repo.root
3660 3660 )
3661 3661 ui.note(
3662 3662 _(b'saving current version of %s as %s\n')
3663 3663 % (uipathfn(abs), uipathfn(bakname))
3664 3664 )
3665 3665 if not opts.get(b'dry_run'):
3666 3666 if interactive:
3667 3667 util.copyfile(target, absbakname)
3668 3668 else:
3669 3669 util.rename(target, absbakname)
3670 3670 if opts.get(b'dry_run'):
3671 3671 if ui.verbose or not exact:
3672 3672 ui.status(msg % uipathfn(abs))
3673 3673 elif exact:
3674 3674 ui.warn(msg % uipathfn(abs))
3675 3675 break
3676 3676
3677 3677 if not opts.get(b'dry_run'):
3678 3678 needdata = (b'revert', b'add', b'undelete')
3679 3679 oplist = [actions[name][0] for name in needdata]
3680 3680 prefetch = scmutil.prefetchfiles
3681 3681 matchfiles = scmutil.matchfiles(
3682 3682 repo, [f for sublist in oplist for f in sublist]
3683 3683 )
3684 3684 prefetch(
3685 3685 repo,
3686 3686 [(ctx.rev(), matchfiles)],
3687 3687 )
3688 3688 match = scmutil.match(repo[None], pats)
3689 3689 _performrevert(
3690 3690 repo,
3691 3691 ctx,
3692 3692 names,
3693 3693 uipathfn,
3694 3694 actions,
3695 3695 match,
3696 3696 interactive,
3697 3697 tobackup,
3698 3698 )
3699 3699
3700 3700 if targetsubs:
3701 3701 # Revert the subrepos on the revert list
3702 3702 for sub in targetsubs:
3703 3703 try:
3704 3704 wctx.sub(sub).revert(
3705 3705 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3706 3706 )
3707 3707 except KeyError:
3708 3708 raise error.Abort(
3709 3709 b"subrepository '%s' does not exist in %s!"
3710 3710 % (sub, short(ctx.node()))
3711 3711 )
3712 3712
3713 3713
3714 3714 def _performrevert(
3715 3715 repo,
3716 3716 ctx,
3717 3717 names,
3718 3718 uipathfn,
3719 3719 actions,
3720 3720 match,
3721 3721 interactive=False,
3722 3722 tobackup=None,
3723 3723 ):
3724 3724 """function that actually perform all the actions computed for revert
3725 3725
3726 3726 This is an independent function to let extension to plug in and react to
3727 3727 the imminent revert.
3728 3728
3729 3729 Make sure you have the working directory locked when calling this function.
3730 3730 """
3731 3731 parent, p2 = repo.dirstate.parents()
3732 3732 node = ctx.node()
3733 3733 excluded_files = []
3734 3734
3735 3735 def checkout(f):
3736 3736 fc = ctx[f]
3737 3737 repo.wwrite(f, fc.data(), fc.flags())
3738 3738
3739 3739 def doremove(f):
3740 3740 try:
3741 3741 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3742 3742 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3743 3743 except OSError:
3744 3744 pass
3745 3745 repo.dirstate.set_untracked(f)
3746 3746
3747 3747 def prntstatusmsg(action, f):
3748 3748 exact = names[f]
3749 3749 if repo.ui.verbose or not exact:
3750 3750 repo.ui.status(actions[action][1] % uipathfn(f))
3751 3751
3752 3752 audit_path = pathutil.pathauditor(repo.root, cached=True)
3753 3753 for f in actions[b'forget'][0]:
3754 3754 if interactive:
3755 3755 choice = repo.ui.promptchoice(
3756 3756 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3757 3757 )
3758 3758 if choice == 0:
3759 3759 prntstatusmsg(b'forget', f)
3760 3760 repo.dirstate.set_untracked(f)
3761 3761 else:
3762 3762 excluded_files.append(f)
3763 3763 else:
3764 3764 prntstatusmsg(b'forget', f)
3765 3765 repo.dirstate.set_untracked(f)
3766 3766 for f in actions[b'remove'][0]:
3767 3767 audit_path(f)
3768 3768 if interactive:
3769 3769 choice = repo.ui.promptchoice(
3770 3770 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3771 3771 )
3772 3772 if choice == 0:
3773 3773 prntstatusmsg(b'remove', f)
3774 3774 doremove(f)
3775 3775 else:
3776 3776 excluded_files.append(f)
3777 3777 else:
3778 3778 prntstatusmsg(b'remove', f)
3779 3779 doremove(f)
3780 3780 for f in actions[b'drop'][0]:
3781 3781 audit_path(f)
3782 3782 prntstatusmsg(b'drop', f)
3783 3783 repo.dirstate.set_untracked(f)
3784 3784
3785 3785 # We are reverting to our parent. If possible, we had like `hg status`
3786 3786 # to report the file as clean. We have to be less agressive for
3787 3787 # merges to avoid losing information about copy introduced by the merge.
3788 3788 # This might comes with bugs ?
3789 3789 reset_copy = p2 == repo.nullid
3790 3790
3791 3791 def normal(filename):
3792 3792 return repo.dirstate.set_tracked(filename, reset_copy=reset_copy)
3793 3793
3794 3794 newlyaddedandmodifiedfiles = set()
3795 3795 if interactive:
3796 3796 # Prompt the user for changes to revert
3797 3797 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3798 3798 m = scmutil.matchfiles(repo, torevert)
3799 3799 diffopts = patch.difffeatureopts(
3800 3800 repo.ui,
3801 3801 whitespace=True,
3802 3802 section=b'commands',
3803 3803 configprefix=b'revert.interactive.',
3804 3804 )
3805 3805 diffopts.nodates = True
3806 3806 diffopts.git = True
3807 3807 operation = b'apply'
3808 3808 if node == parent:
3809 3809 if repo.ui.configbool(
3810 3810 b'experimental', b'revert.interactive.select-to-keep'
3811 3811 ):
3812 3812 operation = b'keep'
3813 3813 else:
3814 3814 operation = b'discard'
3815 3815
3816 3816 if operation == b'apply':
3817 3817 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3818 3818 else:
3819 3819 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3820 3820 original_headers = patch.parsepatch(diff)
3821 3821
3822 3822 try:
3823 3823
3824 3824 chunks, opts = recordfilter(
3825 3825 repo.ui, original_headers, match, operation=operation
3826 3826 )
3827 3827 if operation == b'discard':
3828 3828 chunks = patch.reversehunks(chunks)
3829 3829
3830 3830 except error.PatchParseError as err:
3831 3831 raise error.InputError(_(b'error parsing patch: %s') % err)
3832 3832 except error.PatchApplicationError as err:
3833 3833 raise error.StateError(_(b'error applying patch: %s') % err)
3834 3834
3835 3835 # FIXME: when doing an interactive revert of a copy, there's no way of
3836 3836 # performing a partial revert of the added file, the only option is
3837 3837 # "remove added file <name> (Yn)?", so we don't need to worry about the
3838 3838 # alsorestore value. Ideally we'd be able to partially revert
3839 3839 # copied/renamed files.
3840 3840 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(chunks)
3841 3841 if tobackup is None:
3842 3842 tobackup = set()
3843 3843 # Apply changes
3844 3844 fp = stringio()
3845 3845 # chunks are serialized per file, but files aren't sorted
3846 3846 for f in sorted({c.header.filename() for c in chunks if ishunk(c)}):
3847 3847 prntstatusmsg(b'revert', f)
3848 3848 files = set()
3849 3849 for c in chunks:
3850 3850 if ishunk(c):
3851 3851 abs = c.header.filename()
3852 3852 # Create a backup file only if this hunk should be backed up
3853 3853 if c.header.filename() in tobackup:
3854 3854 target = repo.wjoin(abs)
3855 3855 bakname = scmutil.backuppath(repo.ui, repo, abs)
3856 3856 util.copyfile(target, bakname)
3857 3857 tobackup.remove(abs)
3858 3858 if abs not in files:
3859 3859 files.add(abs)
3860 3860 if operation == b'keep':
3861 3861 checkout(abs)
3862 3862 c.write(fp)
3863 3863 dopatch = fp.tell()
3864 3864 fp.seek(0)
3865 3865 if dopatch:
3866 3866 try:
3867 3867 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3868 3868 except error.PatchParseError as err:
3869 3869 raise error.InputError(pycompat.bytestr(err))
3870 3870 except error.PatchApplicationError as err:
3871 3871 raise error.StateError(pycompat.bytestr(err))
3872 3872 del fp
3873 3873 else:
3874 3874 for f in actions[b'revert'][0]:
3875 3875 prntstatusmsg(b'revert', f)
3876 3876 checkout(f)
3877 3877 if normal:
3878 3878 normal(f)
3879 3879
3880 3880 for f in actions[b'add'][0]:
3881 3881 # Don't checkout modified files, they are already created by the diff
3882 3882 if f in newlyaddedandmodifiedfiles:
3883 3883 continue
3884 3884
3885 3885 if interactive:
3886 3886 choice = repo.ui.promptchoice(
3887 3887 _(b"add new file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3888 3888 )
3889 3889 if choice != 0:
3890 3890 continue
3891 3891 prntstatusmsg(b'add', f)
3892 3892 checkout(f)
3893 3893 repo.dirstate.set_tracked(f)
3894 3894
3895 3895 for f in actions[b'undelete'][0]:
3896 3896 if interactive:
3897 3897 choice = repo.ui.promptchoice(
3898 3898 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3899 3899 )
3900 3900 if choice == 0:
3901 3901 prntstatusmsg(b'undelete', f)
3902 3902 checkout(f)
3903 3903 normal(f)
3904 3904 else:
3905 3905 excluded_files.append(f)
3906 3906 else:
3907 3907 prntstatusmsg(b'undelete', f)
3908 3908 checkout(f)
3909 3909 normal(f)
3910 3910
3911 3911 copied = copies.pathcopies(repo[parent], ctx)
3912 3912
3913 3913 for f in (
3914 3914 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
3915 3915 ):
3916 3916 if f in copied:
3917 3917 repo.dirstate.copy(copied[f], f)
3918 3918
3919 3919
3920 3920 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3921 3921 # commands.outgoing. "missing" is "missing" of the result of
3922 3922 # "findcommonoutgoing()"
3923 3923 outgoinghooks = util.hooks()
3924 3924
3925 3925 # a list of (ui, repo) functions called by commands.summary
3926 3926 summaryhooks = util.hooks()
3927 3927
3928 3928 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3929 3929 #
3930 3930 # functions should return tuple of booleans below, if 'changes' is None:
3931 3931 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3932 3932 #
3933 3933 # otherwise, 'changes' is a tuple of tuples below:
3934 3934 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3935 3935 # - (desturl, destbranch, destpeer, outgoing)
3936 3936 summaryremotehooks = util.hooks()
3937 3937
3938 3938
3939 3939 def checkunfinished(repo, commit=False, skipmerge=False):
3940 3940 """Look for an unfinished multistep operation, like graft, and abort
3941 3941 if found. It's probably good to check this right before
3942 3942 bailifchanged().
3943 3943 """
3944 3944 # Check for non-clearable states first, so things like rebase will take
3945 3945 # precedence over update.
3946 3946 for state in statemod._unfinishedstates:
3947 3947 if (
3948 3948 state._clearable
3949 3949 or (commit and state._allowcommit)
3950 3950 or state._reportonly
3951 3951 ):
3952 3952 continue
3953 3953 if state.isunfinished(repo):
3954 3954 raise error.StateError(state.msg(), hint=state.hint())
3955 3955
3956 3956 for s in statemod._unfinishedstates:
3957 3957 if (
3958 3958 not s._clearable
3959 3959 or (commit and s._allowcommit)
3960 3960 or (s._opname == b'merge' and skipmerge)
3961 3961 or s._reportonly
3962 3962 ):
3963 3963 continue
3964 3964 if s.isunfinished(repo):
3965 3965 raise error.StateError(s.msg(), hint=s.hint())
3966 3966
3967 3967
3968 3968 def clearunfinished(repo):
3969 3969 """Check for unfinished operations (as above), and clear the ones
3970 3970 that are clearable.
3971 3971 """
3972 3972 for state in statemod._unfinishedstates:
3973 3973 if state._reportonly:
3974 3974 continue
3975 3975 if not state._clearable and state.isunfinished(repo):
3976 3976 raise error.StateError(state.msg(), hint=state.hint())
3977 3977
3978 3978 for s in statemod._unfinishedstates:
3979 3979 if s._opname == b'merge' or s._reportonly:
3980 3980 continue
3981 3981 if s._clearable and s.isunfinished(repo):
3982 3982 util.unlink(repo.vfs.join(s._fname))
3983 3983
3984 3984
3985 3985 def getunfinishedstate(repo):
3986 3986 """Checks for unfinished operations and returns statecheck object
3987 3987 for it"""
3988 3988 for state in statemod._unfinishedstates:
3989 3989 if state.isunfinished(repo):
3990 3990 return state
3991 3991 return None
3992 3992
3993 3993
3994 3994 def howtocontinue(repo):
3995 3995 """Check for an unfinished operation and return the command to finish
3996 3996 it.
3997 3997
3998 3998 statemod._unfinishedstates list is checked for an unfinished operation
3999 3999 and the corresponding message to finish it is generated if a method to
4000 4000 continue is supported by the operation.
4001 4001
4002 4002 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
4003 4003 a boolean.
4004 4004 """
4005 4005 contmsg = _(b"continue: %s")
4006 4006 for state in statemod._unfinishedstates:
4007 4007 if not state._continueflag:
4008 4008 continue
4009 4009 if state.isunfinished(repo):
4010 4010 return contmsg % state.continuemsg(), True
4011 4011 if repo[None].dirty(missing=True, merge=False, branch=False):
4012 4012 return contmsg % _(b"hg commit"), False
4013 4013 return None, None
4014 4014
4015 4015
4016 4016 def checkafterresolved(repo):
4017 4017 """Inform the user about the next action after completing hg resolve
4018 4018
4019 4019 If there's a an unfinished operation that supports continue flag,
4020 4020 howtocontinue will yield repo.ui.warn as the reporter.
4021 4021
4022 4022 Otherwise, it will yield repo.ui.note.
4023 4023 """
4024 4024 msg, warning = howtocontinue(repo)
4025 4025 if msg is not None:
4026 4026 if warning:
4027 4027 repo.ui.warn(b"%s\n" % msg)
4028 4028 else:
4029 4029 repo.ui.note(b"%s\n" % msg)
4030 4030
4031 4031
4032 4032 def wrongtooltocontinue(repo, task):
4033 4033 """Raise an abort suggesting how to properly continue if there is an
4034 4034 active task.
4035 4035
4036 4036 Uses howtocontinue() to find the active task.
4037 4037
4038 4038 If there's no task (repo.ui.note for 'hg commit'), it does not offer
4039 4039 a hint.
4040 4040 """
4041 4041 after = howtocontinue(repo)
4042 4042 hint = None
4043 4043 if after[1]:
4044 4044 hint = after[0]
4045 4045 raise error.StateError(_(b'no %s in progress') % task, hint=hint)
4046 4046
4047 4047
4048 4048 def abortgraft(ui, repo, graftstate):
4049 4049 """abort the interrupted graft and rollbacks to the state before interrupted
4050 4050 graft"""
4051 4051 if not graftstate.exists():
4052 4052 raise error.StateError(_(b"no interrupted graft to abort"))
4053 4053 statedata = readgraftstate(repo, graftstate)
4054 4054 newnodes = statedata.get(b'newnodes')
4055 4055 if newnodes is None:
4056 4056 # and old graft state which does not have all the data required to abort
4057 4057 # the graft
4058 4058 raise error.Abort(_(b"cannot abort using an old graftstate"))
4059 4059
4060 4060 # changeset from which graft operation was started
4061 4061 if len(newnodes) > 0:
4062 4062 startctx = repo[newnodes[0]].p1()
4063 4063 else:
4064 4064 startctx = repo[b'.']
4065 4065 # whether to strip or not
4066 4066 cleanup = False
4067 4067
4068 4068 if newnodes:
4069 4069 newnodes = [repo[r].rev() for r in newnodes]
4070 4070 cleanup = True
4071 4071 # checking that none of the newnodes turned public or is public
4072 4072 immutable = [c for c in newnodes if not repo[c].mutable()]
4073 4073 if immutable:
4074 4074 repo.ui.warn(
4075 4075 _(b"cannot clean up public changesets %s\n")
4076 4076 % b', '.join(bytes(repo[r]) for r in immutable),
4077 4077 hint=_(b"see 'hg help phases' for details"),
4078 4078 )
4079 4079 cleanup = False
4080 4080
4081 4081 # checking that no new nodes are created on top of grafted revs
4082 4082 desc = set(repo.changelog.descendants(newnodes))
4083 4083 if desc - set(newnodes):
4084 4084 repo.ui.warn(
4085 4085 _(
4086 4086 b"new changesets detected on destination "
4087 4087 b"branch, can't strip\n"
4088 4088 )
4089 4089 )
4090 4090 cleanup = False
4091 4091
4092 4092 if cleanup:
4093 4093 with repo.wlock(), repo.lock():
4094 4094 mergemod.clean_update(startctx)
4095 4095 # stripping the new nodes created
4096 4096 strippoints = [
4097 4097 c.node() for c in repo.set(b"roots(%ld)", newnodes)
4098 4098 ]
4099 4099 repair.strip(repo.ui, repo, strippoints, backup=False)
4100 4100
4101 4101 if not cleanup:
4102 4102 # we don't update to the startnode if we can't strip
4103 4103 startctx = repo[b'.']
4104 4104 mergemod.clean_update(startctx)
4105 4105
4106 4106 ui.status(_(b"graft aborted\n"))
4107 4107 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
4108 4108 graftstate.delete()
4109 4109 return 0
4110 4110
4111 4111
4112 4112 def readgraftstate(repo, graftstate):
4113 4113 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
4114 4114 """read the graft state file and return a dict of the data stored in it"""
4115 4115 try:
4116 4116 return graftstate.read()
4117 4117 except error.CorruptedState:
4118 4118 nodes = repo.vfs.read(b'graftstate').splitlines()
4119 4119 return {b'nodes': nodes}
4120 4120
4121 4121
4122 4122 def hgabortgraft(ui, repo):
4123 4123 """abort logic for aborting graft using 'hg abort'"""
4124 4124 with repo.wlock():
4125 4125 graftstate = statemod.cmdstate(repo, b'graftstate')
4126 4126 return abortgraft(ui, repo, graftstate)
@@ -1,8052 +1,8051 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Olivia Mackall <olivia@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
9 9 import os
10 10 import re
11 11 import sys
12 12
13 13 from .i18n import _
14 14 from .node import (
15 15 hex,
16 16 nullid,
17 17 nullrev,
18 18 short,
19 19 wdirrev,
20 20 )
21 21 from .pycompat import open
22 22 from . import (
23 23 archival,
24 24 bookmarks,
25 25 bundle2,
26 26 bundlecaches,
27 27 changegroup,
28 28 cmdutil,
29 29 copies,
30 30 debugcommands as debugcommandsmod,
31 31 destutil,
32 32 discovery,
33 33 encoding,
34 34 error,
35 35 exchange,
36 36 extensions,
37 37 filemerge,
38 38 formatter,
39 39 graphmod,
40 40 grep as grepmod,
41 41 hbisect,
42 42 help,
43 43 hg,
44 44 logcmdutil,
45 45 merge as mergemod,
46 46 mergestate as mergestatemod,
47 47 narrowspec,
48 48 obsolete,
49 49 obsutil,
50 50 patch,
51 51 phases,
52 52 pycompat,
53 53 rcutil,
54 54 registrar,
55 55 requirements,
56 56 revsetlang,
57 57 rewriteutil,
58 58 scmutil,
59 59 server,
60 60 shelve as shelvemod,
61 61 state as statemod,
62 62 streamclone,
63 63 tags as tagsmod,
64 64 ui as uimod,
65 65 util,
66 66 verify as verifymod,
67 67 vfs as vfsmod,
68 68 wireprotoserver,
69 69 )
70 70 from .utils import (
71 71 dateutil,
72 72 procutil,
73 73 stringutil,
74 74 urlutil,
75 75 )
76 76
77 77 table = {}
78 78 table.update(debugcommandsmod.command._table)
79 79
80 80 command = registrar.command(table)
81 81 INTENT_READONLY = registrar.INTENT_READONLY
82 82
83 83 # common command options
84 84
85 85 globalopts = [
86 86 (
87 87 b'R',
88 88 b'repository',
89 89 b'',
90 90 _(b'repository root directory or name of overlay bundle file'),
91 91 _(b'REPO'),
92 92 ),
93 93 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
94 94 (
95 95 b'y',
96 96 b'noninteractive',
97 97 None,
98 98 _(
99 99 b'do not prompt, automatically pick the first choice for all prompts'
100 100 ),
101 101 ),
102 102 (b'q', b'quiet', None, _(b'suppress output')),
103 103 (b'v', b'verbose', None, _(b'enable additional output')),
104 104 (
105 105 b'',
106 106 b'color',
107 107 b'',
108 108 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
109 109 # and should not be translated
110 110 _(b"when to colorize (boolean, always, auto, never, or debug)"),
111 111 _(b'TYPE'),
112 112 ),
113 113 (
114 114 b'',
115 115 b'config',
116 116 [],
117 117 _(b'set/override config option (use \'section.name=value\')'),
118 118 _(b'CONFIG'),
119 119 ),
120 120 (b'', b'debug', None, _(b'enable debugging output')),
121 121 (b'', b'debugger', None, _(b'start debugger')),
122 122 (
123 123 b'',
124 124 b'encoding',
125 125 encoding.encoding,
126 126 _(b'set the charset encoding'),
127 127 _(b'ENCODE'),
128 128 ),
129 129 (
130 130 b'',
131 131 b'encodingmode',
132 132 encoding.encodingmode,
133 133 _(b'set the charset encoding mode'),
134 134 _(b'MODE'),
135 135 ),
136 136 (b'', b'traceback', None, _(b'always print a traceback on exception')),
137 137 (b'', b'time', None, _(b'time how long the command takes')),
138 138 (b'', b'profile', None, _(b'print command execution profile')),
139 139 (b'', b'version', None, _(b'output version information and exit')),
140 140 (b'h', b'help', None, _(b'display help and exit')),
141 141 (b'', b'hidden', False, _(b'consider hidden changesets')),
142 142 (
143 143 b'',
144 144 b'pager',
145 145 b'auto',
146 146 _(b"when to paginate (boolean, always, auto, or never)"),
147 147 _(b'TYPE'),
148 148 ),
149 149 ]
150 150
151 151 dryrunopts = cmdutil.dryrunopts
152 152 remoteopts = cmdutil.remoteopts
153 153 walkopts = cmdutil.walkopts
154 154 commitopts = cmdutil.commitopts
155 155 commitopts2 = cmdutil.commitopts2
156 156 commitopts3 = cmdutil.commitopts3
157 157 formatteropts = cmdutil.formatteropts
158 158 templateopts = cmdutil.templateopts
159 159 logopts = cmdutil.logopts
160 160 diffopts = cmdutil.diffopts
161 161 diffwsopts = cmdutil.diffwsopts
162 162 diffopts2 = cmdutil.diffopts2
163 163 mergetoolopts = cmdutil.mergetoolopts
164 164 similarityopts = cmdutil.similarityopts
165 165 subrepoopts = cmdutil.subrepoopts
166 166 debugrevlogopts = cmdutil.debugrevlogopts
167 167
168 168 # Commands start here, listed alphabetically
169 169
170 170
171 171 @command(
172 172 b'abort',
173 173 dryrunopts,
174 174 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
175 175 helpbasic=True,
176 176 )
177 177 def abort(ui, repo, **opts):
178 178 """abort an unfinished operation (EXPERIMENTAL)
179 179
180 180 Aborts a multistep operation like graft, histedit, rebase, merge,
181 181 and unshelve if they are in an unfinished state.
182 182
183 183 use --dry-run/-n to dry run the command.
184 184 """
185 185 dryrun = opts.get('dry_run')
186 186 abortstate = cmdutil.getunfinishedstate(repo)
187 187 if not abortstate:
188 188 raise error.StateError(_(b'no operation in progress'))
189 189 if not abortstate.abortfunc:
190 190 raise error.InputError(
191 191 (
192 192 _(b"%s in progress but does not support 'hg abort'")
193 193 % (abortstate._opname)
194 194 ),
195 195 hint=abortstate.hint(),
196 196 )
197 197 if dryrun:
198 198 ui.status(
199 199 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
200 200 )
201 201 return
202 202 return abortstate.abortfunc(ui, repo)
203 203
204 204
205 205 @command(
206 206 b'add',
207 207 walkopts + subrepoopts + dryrunopts,
208 208 _(b'[OPTION]... [FILE]...'),
209 209 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
210 210 helpbasic=True,
211 211 inferrepo=True,
212 212 )
213 213 def add(ui, repo, *pats, **opts):
214 214 """add the specified files on the next commit
215 215
216 216 Schedule files to be version controlled and added to the
217 217 repository.
218 218
219 219 The files will be added to the repository at the next commit. To
220 220 undo an add before that, see :hg:`forget`.
221 221
222 222 If no names are given, add all files to the repository (except
223 223 files matching ``.hgignore``).
224 224
225 225 .. container:: verbose
226 226
227 227 Examples:
228 228
229 229 - New (unknown) files are added
230 230 automatically by :hg:`add`::
231 231
232 232 $ ls
233 233 foo.c
234 234 $ hg status
235 235 ? foo.c
236 236 $ hg add
237 237 adding foo.c
238 238 $ hg status
239 239 A foo.c
240 240
241 241 - Specific files to be added can be specified::
242 242
243 243 $ ls
244 244 bar.c foo.c
245 245 $ hg status
246 246 ? bar.c
247 247 ? foo.c
248 248 $ hg add bar.c
249 249 $ hg status
250 250 A bar.c
251 251 ? foo.c
252 252
253 253 Returns 0 if all files are successfully added.
254 254 """
255 255
256 256 with repo.wlock(), repo.dirstate.changing_files(repo):
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 with repo.wlock(), repo.dirstate.changing_files(repo):
336 336 matcher = scmutil.match(repo[None], pats, opts)
337 337 relative = scmutil.anypats(pats, opts)
338 338 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
339 339 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
340 340
341 341
342 342 @command(
343 343 b'annotate|blame',
344 344 [
345 345 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
346 346 (
347 347 b'',
348 348 b'follow',
349 349 None,
350 350 _(b'follow copies/renames and list the filename (DEPRECATED)'),
351 351 ),
352 352 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
353 353 (b'a', b'text', None, _(b'treat all files as text')),
354 354 (b'u', b'user', None, _(b'list the author (long with -v)')),
355 355 (b'f', b'file', None, _(b'list the filename')),
356 356 (b'd', b'date', None, _(b'list the date (short with -q)')),
357 357 (b'n', b'number', None, _(b'list the revision number (default)')),
358 358 (b'c', b'changeset', None, _(b'list the changeset')),
359 359 (
360 360 b'l',
361 361 b'line-number',
362 362 None,
363 363 _(b'show line number at the first appearance'),
364 364 ),
365 365 (
366 366 b'',
367 367 b'skip',
368 368 [],
369 369 _(b'revset to not display (EXPERIMENTAL)'),
370 370 _(b'REV'),
371 371 ),
372 372 ]
373 373 + diffwsopts
374 374 + walkopts
375 375 + formatteropts,
376 376 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
377 377 helpcategory=command.CATEGORY_FILE_CONTENTS,
378 378 helpbasic=True,
379 379 inferrepo=True,
380 380 )
381 381 def annotate(ui, repo, *pats, **opts):
382 382 """show changeset information by line for each file
383 383
384 384 List changes in files, showing the revision id responsible for
385 385 each line.
386 386
387 387 This command is useful for discovering when a change was made and
388 388 by whom.
389 389
390 390 If you include --file, --user, or --date, the revision number is
391 391 suppressed unless you also include --number.
392 392
393 393 Without the -a/--text option, annotate will avoid processing files
394 394 it detects as binary. With -a, annotate will annotate the file
395 395 anyway, although the results will probably be neither useful
396 396 nor desirable.
397 397
398 398 .. container:: verbose
399 399
400 400 Template:
401 401
402 402 The following keywords are supported in addition to the common template
403 403 keywords and functions. See also :hg:`help templates`.
404 404
405 405 :lines: List of lines with annotation data.
406 406 :path: String. Repository-absolute path of the specified file.
407 407
408 408 And each entry of ``{lines}`` provides the following sub-keywords in
409 409 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
410 410
411 411 :line: String. Line content.
412 412 :lineno: Integer. Line number at that revision.
413 413 :path: String. Repository-absolute path of the file at that revision.
414 414
415 415 See :hg:`help templates.operators` for the list expansion syntax.
416 416
417 417 Returns 0 on success.
418 418 """
419 419 opts = pycompat.byteskwargs(opts)
420 420 if not pats:
421 421 raise error.InputError(
422 422 _(b'at least one filename or pattern is required')
423 423 )
424 424
425 425 if opts.get(b'follow'):
426 426 # --follow is deprecated and now just an alias for -f/--file
427 427 # to mimic the behavior of Mercurial before version 1.5
428 428 opts[b'file'] = True
429 429
430 430 if (
431 431 not opts.get(b'user')
432 432 and not opts.get(b'changeset')
433 433 and not opts.get(b'date')
434 434 and not opts.get(b'file')
435 435 ):
436 436 opts[b'number'] = True
437 437
438 438 linenumber = opts.get(b'line_number') is not None
439 439 if (
440 440 linenumber
441 441 and (not opts.get(b'changeset'))
442 442 and (not opts.get(b'number'))
443 443 ):
444 444 raise error.InputError(_(b'at least one of -n/-c is required for -l'))
445 445
446 446 rev = opts.get(b'rev')
447 447 if rev:
448 448 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
449 449 ctx = logcmdutil.revsingle(repo, rev)
450 450
451 451 ui.pager(b'annotate')
452 452 rootfm = ui.formatter(b'annotate', opts)
453 453 if ui.debugflag:
454 454 shorthex = pycompat.identity
455 455 else:
456 456
457 457 def shorthex(h):
458 458 return h[:12]
459 459
460 460 if ui.quiet:
461 461 datefunc = dateutil.shortdate
462 462 else:
463 463 datefunc = dateutil.datestr
464 464 if ctx.rev() is None:
465 465 if opts.get(b'changeset'):
466 466 # omit "+" suffix which is appended to node hex
467 467 def formatrev(rev):
468 468 if rev == wdirrev:
469 469 return b'%d' % ctx.p1().rev()
470 470 else:
471 471 return b'%d' % rev
472 472
473 473 else:
474 474
475 475 def formatrev(rev):
476 476 if rev == wdirrev:
477 477 return b'%d+' % ctx.p1().rev()
478 478 else:
479 479 return b'%d ' % rev
480 480
481 481 def formathex(h):
482 482 if h == repo.nodeconstants.wdirhex:
483 483 return b'%s+' % shorthex(hex(ctx.p1().node()))
484 484 else:
485 485 return b'%s ' % shorthex(h)
486 486
487 487 else:
488 488 formatrev = b'%d'.__mod__
489 489 formathex = shorthex
490 490
491 491 opmap = [
492 492 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
493 493 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
494 494 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
495 495 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
496 496 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
497 497 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
498 498 ]
499 499 opnamemap = {
500 500 b'rev': b'number',
501 501 b'node': b'changeset',
502 502 b'path': b'file',
503 503 b'lineno': b'line_number',
504 504 }
505 505
506 506 if rootfm.isplain():
507 507
508 508 def makefunc(get, fmt):
509 509 return lambda x: fmt(get(x))
510 510
511 511 else:
512 512
513 513 def makefunc(get, fmt):
514 514 return get
515 515
516 516 datahint = rootfm.datahint()
517 517 funcmap = [
518 518 (makefunc(get, fmt), sep)
519 519 for fn, sep, get, fmt in opmap
520 520 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
521 521 ]
522 522 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
523 523 fields = b' '.join(
524 524 fn
525 525 for fn, sep, get, fmt in opmap
526 526 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
527 527 )
528 528
529 529 def bad(x, y):
530 530 raise error.InputError(b"%s: %s" % (x, y))
531 531
532 532 m = scmutil.match(ctx, pats, opts, badfn=bad)
533 533
534 534 follow = not opts.get(b'no_follow')
535 535 diffopts = patch.difffeatureopts(
536 536 ui, opts, section=b'annotate', whitespace=True
537 537 )
538 538 skiprevs = opts.get(b'skip')
539 539 if skiprevs:
540 540 skiprevs = logcmdutil.revrange(repo, skiprevs)
541 541
542 542 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
543 543 for abs in ctx.walk(m):
544 544 fctx = ctx[abs]
545 545 rootfm.startitem()
546 546 rootfm.data(path=abs)
547 547 if not opts.get(b'text') and fctx.isbinary():
548 548 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
549 549 continue
550 550
551 551 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
552 552 lines = fctx.annotate(
553 553 follow=follow, skiprevs=skiprevs, diffopts=diffopts
554 554 )
555 555 if not lines:
556 556 fm.end()
557 557 continue
558 558 formats = []
559 559 pieces = []
560 560
561 561 for f, sep in funcmap:
562 562 l = [f(n) for n in lines]
563 563 if fm.isplain():
564 564 sizes = [encoding.colwidth(x) for x in l]
565 565 ml = max(sizes)
566 566 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
567 567 else:
568 568 formats.append([b'%s'] * len(l))
569 569 pieces.append(l)
570 570
571 571 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
572 572 fm.startitem()
573 573 fm.context(fctx=n.fctx)
574 574 fm.write(fields, b"".join(f), *p)
575 575 if n.skip:
576 576 fmt = b"* %s"
577 577 else:
578 578 fmt = b": %s"
579 579 fm.write(b'line', fmt, n.text)
580 580
581 581 if not lines[-1].text.endswith(b'\n'):
582 582 fm.plain(b'\n')
583 583 fm.end()
584 584
585 585 rootfm.end()
586 586
587 587
588 588 @command(
589 589 b'archive',
590 590 [
591 591 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
592 592 (
593 593 b'p',
594 594 b'prefix',
595 595 b'',
596 596 _(b'directory prefix for files in archive'),
597 597 _(b'PREFIX'),
598 598 ),
599 599 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
600 600 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
601 601 ]
602 602 + subrepoopts
603 603 + walkopts,
604 604 _(b'[OPTION]... DEST'),
605 605 helpcategory=command.CATEGORY_IMPORT_EXPORT,
606 606 )
607 607 def archive(ui, repo, dest, **opts):
608 608 """create an unversioned archive of a repository revision
609 609
610 610 By default, the revision used is the parent of the working
611 611 directory; use -r/--rev to specify a different revision.
612 612
613 613 The archive type is automatically detected based on file
614 614 extension (to override, use -t/--type).
615 615
616 616 .. container:: verbose
617 617
618 618 Examples:
619 619
620 620 - create a zip file containing the 1.0 release::
621 621
622 622 hg archive -r 1.0 project-1.0.zip
623 623
624 624 - create a tarball excluding .hg files::
625 625
626 626 hg archive project.tar.gz -X ".hg*"
627 627
628 628 Valid types are:
629 629
630 630 :``files``: a directory full of files (default)
631 631 :``tar``: tar archive, uncompressed
632 632 :``tbz2``: tar archive, compressed using bzip2
633 633 :``tgz``: tar archive, compressed using gzip
634 634 :``txz``: tar archive, compressed using lzma (only in Python 3)
635 635 :``uzip``: zip archive, uncompressed
636 636 :``zip``: zip archive, compressed using deflate
637 637
638 638 The exact name of the destination archive or directory is given
639 639 using a format string; see :hg:`help export` for details.
640 640
641 641 Each member added to an archive file has a directory prefix
642 642 prepended. Use -p/--prefix to specify a format string for the
643 643 prefix. The default is the basename of the archive, with suffixes
644 644 removed.
645 645
646 646 Returns 0 on success.
647 647 """
648 648
649 649 rev = opts.get('rev')
650 650 if rev:
651 651 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
652 652 ctx = logcmdutil.revsingle(repo, rev)
653 653 if not ctx:
654 654 raise error.InputError(
655 655 _(b'no working directory: please specify a revision')
656 656 )
657 657 node = ctx.node()
658 658 dest = cmdutil.makefilename(ctx, dest)
659 659 if os.path.realpath(dest) == repo.root:
660 660 raise error.InputError(_(b'repository root cannot be destination'))
661 661
662 662 kind = opts.get('type') or archival.guesskind(dest) or b'files'
663 663 prefix = opts.get('prefix')
664 664
665 665 if dest == b'-':
666 666 if kind == b'files':
667 667 raise error.InputError(_(b'cannot archive plain files to stdout'))
668 668 dest = cmdutil.makefileobj(ctx, dest)
669 669 if not prefix:
670 670 prefix = os.path.basename(repo.root) + b'-%h'
671 671
672 672 prefix = cmdutil.makefilename(ctx, prefix)
673 673 match = scmutil.match(ctx, [], pycompat.byteskwargs(opts))
674 674 archival.archive(
675 675 repo,
676 676 dest,
677 677 node,
678 678 kind,
679 679 not opts.get('no_decode'),
680 680 match,
681 681 prefix,
682 682 subrepos=opts.get('subrepos'),
683 683 )
684 684
685 685
686 686 @command(
687 687 b'backout',
688 688 [
689 689 (
690 690 b'',
691 691 b'merge',
692 692 None,
693 693 _(b'merge with old dirstate parent after backout'),
694 694 ),
695 695 (
696 696 b'',
697 697 b'commit',
698 698 None,
699 699 _(b'commit if no conflicts were encountered (DEPRECATED)'),
700 700 ),
701 701 (b'', b'no-commit', None, _(b'do not commit')),
702 702 (
703 703 b'',
704 704 b'parent',
705 705 b'',
706 706 _(b'parent to choose when backing out merge (DEPRECATED)'),
707 707 _(b'REV'),
708 708 ),
709 709 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
710 710 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
711 711 ]
712 712 + mergetoolopts
713 713 + walkopts
714 714 + commitopts
715 715 + commitopts2,
716 716 _(b'[OPTION]... [-r] REV'),
717 717 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
718 718 )
719 719 def backout(ui, repo, node=None, rev=None, **opts):
720 720 """reverse effect of earlier changeset
721 721
722 722 Prepare a new changeset with the effect of REV undone in the
723 723 current working directory. If no conflicts were encountered,
724 724 it will be committed immediately.
725 725
726 726 If REV is the parent of the working directory, then this new changeset
727 727 is committed automatically (unless --no-commit is specified).
728 728
729 729 .. note::
730 730
731 731 :hg:`backout` cannot be used to fix either an unwanted or
732 732 incorrect merge.
733 733
734 734 .. container:: verbose
735 735
736 736 Examples:
737 737
738 738 - Reverse the effect of the parent of the working directory.
739 739 This backout will be committed immediately::
740 740
741 741 hg backout -r .
742 742
743 743 - Reverse the effect of previous bad revision 23::
744 744
745 745 hg backout -r 23
746 746
747 747 - Reverse the effect of previous bad revision 23 and
748 748 leave changes uncommitted::
749 749
750 750 hg backout -r 23 --no-commit
751 751 hg commit -m "Backout revision 23"
752 752
753 753 By default, the pending changeset will have one parent,
754 754 maintaining a linear history. With --merge, the pending
755 755 changeset will instead have two parents: the old parent of the
756 756 working directory and a new child of REV that simply undoes REV.
757 757
758 758 Before version 1.7, the behavior without --merge was equivalent
759 759 to specifying --merge followed by :hg:`update --clean .` to
760 760 cancel the merge and leave the child of REV as a head to be
761 761 merged separately.
762 762
763 763 See :hg:`help dates` for a list of formats valid for -d/--date.
764 764
765 765 See :hg:`help revert` for a way to restore files to the state
766 766 of another revision.
767 767
768 768 Returns 0 on success, 1 if nothing to backout or there are unresolved
769 769 files.
770 770 """
771 771 with repo.wlock(), repo.lock():
772 772 return _dobackout(ui, repo, node, rev, **opts)
773 773
774 774
775 775 def _dobackout(ui, repo, node=None, rev=None, **opts):
776 776 cmdutil.check_incompatible_arguments(opts, 'no_commit', ['commit', 'merge'])
777 777
778 778 if rev and node:
779 779 raise error.InputError(_(b"please specify just one revision"))
780 780
781 781 if not rev:
782 782 rev = node
783 783
784 784 if not rev:
785 785 raise error.InputError(_(b"please specify a revision to backout"))
786 786
787 787 date = opts.get('date')
788 788 if date:
789 789 opts['date'] = dateutil.parsedate(date)
790 790
791 791 cmdutil.checkunfinished(repo)
792 792 cmdutil.bailifchanged(repo)
793 793 ctx = logcmdutil.revsingle(repo, rev)
794 794 node = ctx.node()
795 795
796 796 op1, op2 = repo.dirstate.parents()
797 797 if not repo.changelog.isancestor(node, op1):
798 798 raise error.InputError(
799 799 _(b'cannot backout change that is not an ancestor')
800 800 )
801 801
802 802 p1, p2 = repo.changelog.parents(node)
803 803 if p1 == repo.nullid:
804 804 raise error.InputError(_(b'cannot backout a change with no parents'))
805 805 if p2 != repo.nullid:
806 806 if not opts.get('parent'):
807 807 raise error.InputError(_(b'cannot backout a merge changeset'))
808 808 p = repo.lookup(opts['parent'])
809 809 if p not in (p1, p2):
810 810 raise error.InputError(
811 811 _(b'%s is not a parent of %s') % (short(p), short(node))
812 812 )
813 813 parent = p
814 814 else:
815 815 if opts.get('parent'):
816 816 raise error.InputError(
817 817 _(b'cannot use --parent on non-merge changeset')
818 818 )
819 819 parent = p1
820 820
821 821 # the backout should appear on the same branch
822 822 branch = repo.dirstate.branch()
823 823 bheads = repo.branchheads(branch)
824 824 rctx = scmutil.revsingle(repo, hex(parent))
825 825 if not opts.get('merge') and op1 != node:
826 826 with repo.transaction(b"backout"):
827 827 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
828 828 with ui.configoverride(overrides, b'backout'):
829 829 stats = mergemod.back_out(ctx, parent=repo[parent])
830 830 repo.setparents(op1, op2)
831 831 hg._showstats(repo, stats)
832 832 if stats.unresolvedcount:
833 833 repo.ui.status(
834 834 _(b"use 'hg resolve' to retry unresolved file merges\n")
835 835 )
836 836 return 1
837 837 else:
838 838 hg.clean(repo, node, show_stats=False)
839 839 repo.dirstate.setbranch(branch, repo.currenttransaction())
840 840 cmdutil.revert(ui, repo, rctx)
841 841
842 842 if opts.get('no_commit'):
843 843 msg = _(b"changeset %s backed out, don't forget to commit.\n")
844 844 ui.status(msg % short(node))
845 845 return 0
846 846
847 847 def commitfunc(ui, repo, message, match, opts):
848 848 editform = b'backout'
849 849 e = cmdutil.getcommiteditor(
850 850 editform=editform, **pycompat.strkwargs(opts)
851 851 )
852 852 if not message:
853 853 # we don't translate commit messages
854 854 message = b"Backed out changeset %s" % short(node)
855 855 e = cmdutil.getcommiteditor(edit=True, editform=editform)
856 856 return repo.commit(
857 857 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
858 858 )
859 859
860 860 # save to detect changes
861 861 tip = repo.changelog.tip()
862 862
863 863 newnode = cmdutil.commit(
864 864 ui, repo, commitfunc, [], pycompat.byteskwargs(opts)
865 865 )
866 866 if not newnode:
867 867 ui.status(_(b"nothing changed\n"))
868 868 return 1
869 869 cmdutil.commitstatus(repo, newnode, branch, bheads, tip)
870 870
871 871 def nice(node):
872 872 return b'%d:%s' % (repo.changelog.rev(node), short(node))
873 873
874 874 ui.status(
875 875 _(b'changeset %s backs out changeset %s\n')
876 876 % (nice(newnode), nice(node))
877 877 )
878 878 if opts.get('merge') and op1 != node:
879 879 hg.clean(repo, op1, show_stats=False)
880 880 ui.status(_(b'merging with changeset %s\n') % nice(newnode))
881 881 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
882 882 with ui.configoverride(overrides, b'backout'):
883 883 return hg.merge(repo[b'tip'])
884 884 return 0
885 885
886 886
887 887 @command(
888 888 b'bisect',
889 889 [
890 890 (b'r', b'reset', False, _(b'reset bisect state')),
891 891 (b'g', b'good', False, _(b'mark changeset good')),
892 892 (b'b', b'bad', False, _(b'mark changeset bad')),
893 893 (b's', b'skip', False, _(b'skip testing changeset')),
894 894 (b'e', b'extend', False, _(b'extend the bisect range')),
895 895 (
896 896 b'c',
897 897 b'command',
898 898 b'',
899 899 _(b'use command to check changeset state'),
900 900 _(b'CMD'),
901 901 ),
902 902 (b'U', b'noupdate', False, _(b'do not update to target')),
903 903 ],
904 904 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
905 905 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
906 906 )
907 907 def bisect(
908 908 ui,
909 909 repo,
910 910 positional_1=None,
911 911 positional_2=None,
912 912 command=None,
913 913 reset=None,
914 914 good=None,
915 915 bad=None,
916 916 skip=None,
917 917 extend=None,
918 918 noupdate=None,
919 919 ):
920 920 """subdivision search of changesets
921 921
922 922 This command helps to find changesets which introduce problems. To
923 923 use, mark the earliest changeset you know exhibits the problem as
924 924 bad, then mark the latest changeset which is free from the problem
925 925 as good. Bisect will update your working directory to a revision
926 926 for testing (unless the -U/--noupdate option is specified). Once
927 927 you have performed tests, mark the working directory as good or
928 928 bad, and bisect will either update to another candidate changeset
929 929 or announce that it has found the bad revision.
930 930
931 931 As a shortcut, you can also use the revision argument to mark a
932 932 revision as good or bad without checking it out first.
933 933
934 934 If you supply a command, it will be used for automatic bisection.
935 935 The environment variable HG_NODE will contain the ID of the
936 936 changeset being tested. The exit status of the command will be
937 937 used to mark revisions as good or bad: status 0 means good, 125
938 938 means to skip the revision, 127 (command not found) will abort the
939 939 bisection, and any other non-zero exit status means the revision
940 940 is bad.
941 941
942 942 .. container:: verbose
943 943
944 944 Some examples:
945 945
946 946 - start a bisection with known bad revision 34, and good revision 12::
947 947
948 948 hg bisect --bad 34
949 949 hg bisect --good 12
950 950
951 951 - advance the current bisection by marking current revision as good or
952 952 bad::
953 953
954 954 hg bisect --good
955 955 hg bisect --bad
956 956
957 957 - mark the current revision, or a known revision, to be skipped (e.g. if
958 958 that revision is not usable because of another issue)::
959 959
960 960 hg bisect --skip
961 961 hg bisect --skip 23
962 962
963 963 - skip all revisions that do not touch directories ``foo`` or ``bar``::
964 964
965 965 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
966 966
967 967 - forget the current bisection::
968 968
969 969 hg bisect --reset
970 970
971 971 - use 'make && make tests' to automatically find the first broken
972 972 revision::
973 973
974 974 hg bisect --reset
975 975 hg bisect --bad 34
976 976 hg bisect --good 12
977 977 hg bisect --command "make && make tests"
978 978
979 979 - see all changesets whose states are already known in the current
980 980 bisection::
981 981
982 982 hg log -r "bisect(pruned)"
983 983
984 984 - see the changeset currently being bisected (especially useful
985 985 if running with -U/--noupdate)::
986 986
987 987 hg log -r "bisect(current)"
988 988
989 989 - see all changesets that took part in the current bisection::
990 990
991 991 hg log -r "bisect(range)"
992 992
993 993 - you can even get a nice graph::
994 994
995 995 hg log --graph -r "bisect(range)"
996 996
997 997 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
998 998
999 999 Returns 0 on success.
1000 1000 """
1001 1001 rev = []
1002 1002 # backward compatibility
1003 1003 if positional_1 in (b"good", b"bad", b"reset", b"init"):
1004 1004 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1005 1005 cmd = positional_1
1006 1006 rev.append(positional_2)
1007 1007 if cmd == b"good":
1008 1008 good = True
1009 1009 elif cmd == b"bad":
1010 1010 bad = True
1011 1011 else:
1012 1012 reset = True
1013 1013 elif positional_2:
1014 1014 raise error.InputError(_(b'incompatible arguments'))
1015 1015 elif positional_1 is not None:
1016 1016 rev.append(positional_1)
1017 1017
1018 1018 incompatibles = {
1019 1019 b'--bad': bad,
1020 1020 b'--command': bool(command),
1021 1021 b'--extend': extend,
1022 1022 b'--good': good,
1023 1023 b'--reset': reset,
1024 1024 b'--skip': skip,
1025 1025 }
1026 1026
1027 1027 enabled = [x for x in incompatibles if incompatibles[x]]
1028 1028
1029 1029 if len(enabled) > 1:
1030 1030 raise error.InputError(
1031 1031 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1032 1032 )
1033 1033
1034 1034 if reset:
1035 1035 hbisect.resetstate(repo)
1036 1036 return
1037 1037
1038 1038 state = hbisect.load_state(repo)
1039 1039
1040 1040 if rev:
1041 1041 revs = logcmdutil.revrange(repo, rev)
1042 1042 goodnodes = state[b'good']
1043 1043 badnodes = state[b'bad']
1044 1044 if goodnodes and badnodes:
1045 1045 candidates = repo.revs(b'(%ln)::(%ln)', goodnodes, badnodes)
1046 1046 candidates += repo.revs(b'(%ln)::(%ln)', badnodes, goodnodes)
1047 1047 revs = candidates & revs
1048 1048 nodes = [repo.changelog.node(i) for i in revs]
1049 1049 else:
1050 1050 nodes = [repo.lookup(b'.')]
1051 1051
1052 1052 # update state
1053 1053 if good or bad or skip:
1054 1054 if good:
1055 1055 state[b'good'] += nodes
1056 1056 elif bad:
1057 1057 state[b'bad'] += nodes
1058 1058 elif skip:
1059 1059 state[b'skip'] += nodes
1060 1060 hbisect.save_state(repo, state)
1061 1061 if not (state[b'good'] and state[b'bad']):
1062 1062 return
1063 1063
1064 1064 def mayupdate(repo, node, show_stats=True):
1065 1065 """common used update sequence"""
1066 1066 if noupdate:
1067 1067 return
1068 1068 cmdutil.checkunfinished(repo)
1069 1069 cmdutil.bailifchanged(repo)
1070 1070 return hg.clean(repo, node, show_stats=show_stats)
1071 1071
1072 1072 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1073 1073
1074 1074 if command:
1075 1075 changesets = 1
1076 1076 if noupdate:
1077 1077 try:
1078 1078 node = state[b'current'][0]
1079 1079 except LookupError:
1080 1080 raise error.StateError(
1081 1081 _(
1082 1082 b'current bisect revision is unknown - '
1083 1083 b'start a new bisect to fix'
1084 1084 )
1085 1085 )
1086 1086 else:
1087 1087 node, p2 = repo.dirstate.parents()
1088 1088 if p2 != repo.nullid:
1089 1089 raise error.StateError(_(b'current bisect revision is a merge'))
1090 1090 if rev:
1091 1091 if not nodes:
1092 1092 raise error.InputError(_(b'empty revision set'))
1093 1093 node = repo[nodes[-1]].node()
1094 1094 with hbisect.restore_state(repo, state, node):
1095 1095 while changesets:
1096 1096 # update state
1097 1097 state[b'current'] = [node]
1098 1098 hbisect.save_state(repo, state)
1099 1099 status = ui.system(
1100 1100 command,
1101 1101 environ={b'HG_NODE': hex(node)},
1102 1102 blockedtag=b'bisect_check',
1103 1103 )
1104 1104 if status == 125:
1105 1105 transition = b"skip"
1106 1106 elif status == 0:
1107 1107 transition = b"good"
1108 1108 # status < 0 means process was killed
1109 1109 elif status == 127:
1110 1110 raise error.Abort(_(b"failed to execute %s") % command)
1111 1111 elif status < 0:
1112 1112 raise error.Abort(_(b"%s killed") % command)
1113 1113 else:
1114 1114 transition = b"bad"
1115 1115 state[transition].append(node)
1116 1116 ctx = repo[node]
1117 1117 summary = cmdutil.format_changeset_summary(ui, ctx, b'bisect')
1118 1118 ui.status(_(b'changeset %s: %s\n') % (summary, transition))
1119 1119 hbisect.checkstate(state)
1120 1120 # bisect
1121 1121 nodes, changesets, bgood = hbisect.bisect(repo, state)
1122 1122 # update to next check
1123 1123 node = nodes[0]
1124 1124 mayupdate(repo, node, show_stats=False)
1125 1125 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1126 1126 return
1127 1127
1128 1128 hbisect.checkstate(state)
1129 1129
1130 1130 # actually bisect
1131 1131 nodes, changesets, good = hbisect.bisect(repo, state)
1132 1132 if extend:
1133 1133 if not changesets:
1134 1134 extendctx = hbisect.extendrange(repo, state, nodes, good)
1135 1135 if extendctx is not None:
1136 1136 ui.write(
1137 1137 _(b"Extending search to changeset %s\n")
1138 1138 % cmdutil.format_changeset_summary(ui, extendctx, b'bisect')
1139 1139 )
1140 1140 state[b'current'] = [extendctx.node()]
1141 1141 hbisect.save_state(repo, state)
1142 1142 return mayupdate(repo, extendctx.node())
1143 1143 raise error.StateError(_(b"nothing to extend"))
1144 1144
1145 1145 if changesets == 0:
1146 1146 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1147 1147 else:
1148 1148 assert len(nodes) == 1 # only a single node can be tested next
1149 1149 node = nodes[0]
1150 1150 # compute the approximate number of remaining tests
1151 1151 tests, size = 0, 2
1152 1152 while size <= changesets:
1153 1153 tests, size = tests + 1, size * 2
1154 1154 rev = repo.changelog.rev(node)
1155 1155 summary = cmdutil.format_changeset_summary(ui, repo[rev], b'bisect')
1156 1156 ui.write(
1157 1157 _(
1158 1158 b"Testing changeset %s "
1159 1159 b"(%d changesets remaining, ~%d tests)\n"
1160 1160 )
1161 1161 % (summary, changesets, tests)
1162 1162 )
1163 1163 state[b'current'] = [node]
1164 1164 hbisect.save_state(repo, state)
1165 1165 return mayupdate(repo, node)
1166 1166
1167 1167
1168 1168 @command(
1169 1169 b'bookmarks|bookmark',
1170 1170 [
1171 1171 (b'f', b'force', False, _(b'force')),
1172 1172 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1173 1173 (b'd', b'delete', False, _(b'delete a given bookmark')),
1174 1174 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1175 1175 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1176 1176 (b'l', b'list', False, _(b'list existing bookmarks')),
1177 1177 ]
1178 1178 + formatteropts,
1179 1179 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1180 1180 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1181 1181 )
1182 1182 def bookmark(ui, repo, *names, **opts):
1183 1183 """create a new bookmark or list existing bookmarks
1184 1184
1185 1185 Bookmarks are labels on changesets to help track lines of development.
1186 1186 Bookmarks are unversioned and can be moved, renamed and deleted.
1187 1187 Deleting or moving a bookmark has no effect on the associated changesets.
1188 1188
1189 1189 Creating or updating to a bookmark causes it to be marked as 'active'.
1190 1190 The active bookmark is indicated with a '*'.
1191 1191 When a commit is made, the active bookmark will advance to the new commit.
1192 1192 A plain :hg:`update` will also advance an active bookmark, if possible.
1193 1193 Updating away from a bookmark will cause it to be deactivated.
1194 1194
1195 1195 Bookmarks can be pushed and pulled between repositories (see
1196 1196 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1197 1197 diverged, a new 'divergent bookmark' of the form 'name@path' will
1198 1198 be created. Using :hg:`merge` will resolve the divergence.
1199 1199
1200 1200 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1201 1201 the active bookmark's name.
1202 1202
1203 1203 A bookmark named '@' has the special property that :hg:`clone` will
1204 1204 check it out by default if it exists.
1205 1205
1206 1206 .. container:: verbose
1207 1207
1208 1208 Template:
1209 1209
1210 1210 The following keywords are supported in addition to the common template
1211 1211 keywords and functions such as ``{bookmark}``. See also
1212 1212 :hg:`help templates`.
1213 1213
1214 1214 :active: Boolean. True if the bookmark is active.
1215 1215
1216 1216 Examples:
1217 1217
1218 1218 - create an active bookmark for a new line of development::
1219 1219
1220 1220 hg book new-feature
1221 1221
1222 1222 - create an inactive bookmark as a place marker::
1223 1223
1224 1224 hg book -i reviewed
1225 1225
1226 1226 - create an inactive bookmark on another changeset::
1227 1227
1228 1228 hg book -r .^ tested
1229 1229
1230 1230 - rename bookmark turkey to dinner::
1231 1231
1232 1232 hg book -m turkey dinner
1233 1233
1234 1234 - move the '@' bookmark from another branch::
1235 1235
1236 1236 hg book -f @
1237 1237
1238 1238 - print only the active bookmark name::
1239 1239
1240 1240 hg book -ql .
1241 1241 """
1242 1242 force = opts.get('force')
1243 1243 rev = opts.get('rev')
1244 1244 inactive = opts.get('inactive') # meaning add/rename to inactive bookmark
1245 1245
1246 1246 action = cmdutil.check_at_most_one_arg(opts, 'delete', 'rename', 'list')
1247 1247 if action:
1248 1248 cmdutil.check_incompatible_arguments(opts, action, ['rev'])
1249 1249 elif names or rev:
1250 1250 action = 'add'
1251 1251 elif inactive:
1252 1252 action = 'inactive' # meaning deactivate
1253 1253 else:
1254 1254 action = 'list'
1255 1255
1256 1256 cmdutil.check_incompatible_arguments(opts, 'inactive', ['delete', 'list'])
1257 1257 if not names and action in {'add', 'delete'}:
1258 1258 raise error.InputError(_(b"bookmark name required"))
1259 1259
1260 1260 if action in {'add', 'delete', 'rename', 'inactive'}:
1261 1261 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1262 1262 if action == 'delete':
1263 1263 names = pycompat.maplist(repo._bookmarks.expandname, names)
1264 1264 bookmarks.delete(repo, tr, names)
1265 1265 elif action == 'rename':
1266 1266 if not names:
1267 1267 raise error.InputError(_(b"new bookmark name required"))
1268 1268 elif len(names) > 1:
1269 1269 raise error.InputError(
1270 1270 _(b"only one new bookmark name allowed")
1271 1271 )
1272 1272 oldname = repo._bookmarks.expandname(opts['rename'])
1273 1273 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1274 1274 elif action == 'add':
1275 1275 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1276 1276 elif action == 'inactive':
1277 1277 if len(repo._bookmarks) == 0:
1278 1278 ui.status(_(b"no bookmarks set\n"))
1279 1279 elif not repo._activebookmark:
1280 1280 ui.status(_(b"no active bookmark\n"))
1281 1281 else:
1282 1282 bookmarks.deactivate(repo)
1283 1283 elif action == 'list':
1284 1284 names = pycompat.maplist(repo._bookmarks.expandname, names)
1285 1285 with ui.formatter(b'bookmarks', pycompat.byteskwargs(opts)) as fm:
1286 1286 bookmarks.printbookmarks(ui, repo, fm, names)
1287 1287 else:
1288 1288 raise error.ProgrammingError(
1289 1289 b'invalid action: %s' % pycompat.sysbytes(action)
1290 1290 )
1291 1291
1292 1292
1293 1293 @command(
1294 1294 b'branch',
1295 1295 [
1296 1296 (
1297 1297 b'f',
1298 1298 b'force',
1299 1299 None,
1300 1300 _(b'set branch name even if it shadows an existing branch'),
1301 1301 ),
1302 1302 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1303 1303 (
1304 1304 b'r',
1305 1305 b'rev',
1306 1306 [],
1307 1307 _(b'change branches of the given revs (EXPERIMENTAL)'),
1308 1308 ),
1309 1309 ],
1310 1310 _(b'[-fC] [NAME]'),
1311 1311 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1312 1312 )
1313 1313 def branch(ui, repo, label=None, **opts):
1314 1314 """set or show the current branch name
1315 1315
1316 1316 .. note::
1317 1317
1318 1318 Branch names are permanent and global. Use :hg:`bookmark` to create a
1319 1319 light-weight bookmark instead. See :hg:`help glossary` for more
1320 1320 information about named branches and bookmarks.
1321 1321
1322 1322 With no argument, show the current branch name. With one argument,
1323 1323 set the working directory branch name (the branch will not exist
1324 1324 in the repository until the next commit). Standard practice
1325 1325 recommends that primary development take place on the 'default'
1326 1326 branch.
1327 1327
1328 1328 Unless -f/--force is specified, branch will not let you set a
1329 1329 branch name that already exists.
1330 1330
1331 1331 Use -C/--clean to reset the working directory branch to that of
1332 1332 the parent of the working directory, negating a previous branch
1333 1333 change.
1334 1334
1335 1335 Use the command :hg:`update` to switch to an existing branch. Use
1336 1336 :hg:`commit --close-branch` to mark this branch head as closed.
1337 1337 When all heads of a branch are closed, the branch will be
1338 1338 considered closed.
1339 1339
1340 1340 Returns 0 on success.
1341 1341 """
1342 opts = pycompat.byteskwargs(opts)
1343 revs = opts.get(b'rev')
1342 revs = opts.get('rev')
1344 1343 if label:
1345 1344 label = label.strip()
1346 1345
1347 if not opts.get(b'clean') and not label:
1346 if not opts.get('clean') and not label:
1348 1347 if revs:
1349 1348 raise error.InputError(
1350 1349 _(b"no branch name specified for the revisions")
1351 1350 )
1352 1351 ui.write(b"%s\n" % repo.dirstate.branch())
1353 1352 return
1354 1353
1355 1354 with repo.wlock():
1356 if opts.get(b'clean'):
1355 if opts.get('clean'):
1357 1356 label = repo[b'.'].branch()
1358 1357 repo.dirstate.setbranch(label, repo.currenttransaction())
1359 1358 ui.status(_(b'reset working directory to branch %s\n') % label)
1360 1359 elif label:
1361 1360
1362 1361 scmutil.checknewlabel(repo, label, b'branch')
1363 1362 if revs:
1364 return cmdutil.changebranch(ui, repo, revs, label, opts)
1365
1366 if not opts.get(b'force') and label in repo.branchmap():
1363 return cmdutil.changebranch(ui, repo, revs, label, **opts)
1364
1365 if not opts.get('force') and label in repo.branchmap():
1367 1366 if label not in [p.branch() for p in repo[None].parents()]:
1368 1367 raise error.InputError(
1369 1368 _(b'a branch of the same name already exists'),
1370 1369 # i18n: "it" refers to an existing branch
1371 1370 hint=_(b"use 'hg update' to switch to it"),
1372 1371 )
1373 1372
1374 1373 repo.dirstate.setbranch(label, repo.currenttransaction())
1375 1374 ui.status(_(b'marked working directory as branch %s\n') % label)
1376 1375
1377 1376 # find any open named branches aside from default
1378 1377 for n, h, t, c in repo.branchmap().iterbranches():
1379 1378 if n != b"default" and not c:
1380 1379 return 0
1381 1380 ui.status(
1382 1381 _(
1383 1382 b'(branches are permanent and global, '
1384 1383 b'did you want a bookmark?)\n'
1385 1384 )
1386 1385 )
1387 1386
1388 1387
1389 1388 @command(
1390 1389 b'branches',
1391 1390 [
1392 1391 (
1393 1392 b'a',
1394 1393 b'active',
1395 1394 False,
1396 1395 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1397 1396 ),
1398 1397 (b'c', b'closed', False, _(b'show normal and closed branches')),
1399 1398 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1400 1399 ]
1401 1400 + formatteropts,
1402 1401 _(b'[-c]'),
1403 1402 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1404 1403 intents={INTENT_READONLY},
1405 1404 )
1406 1405 def branches(ui, repo, active=False, closed=False, **opts):
1407 1406 """list repository named branches
1408 1407
1409 1408 List the repository's named branches, indicating which ones are
1410 1409 inactive. If -c/--closed is specified, also list branches which have
1411 1410 been marked closed (see :hg:`commit --close-branch`).
1412 1411
1413 1412 Use the command :hg:`update` to switch to an existing branch.
1414 1413
1415 1414 .. container:: verbose
1416 1415
1417 1416 Template:
1418 1417
1419 1418 The following keywords are supported in addition to the common template
1420 1419 keywords and functions such as ``{branch}``. See also
1421 1420 :hg:`help templates`.
1422 1421
1423 1422 :active: Boolean. True if the branch is active.
1424 1423 :closed: Boolean. True if the branch is closed.
1425 1424 :current: Boolean. True if it is the current branch.
1426 1425
1427 1426 Returns 0.
1428 1427 """
1429 1428
1430 1429 opts = pycompat.byteskwargs(opts)
1431 1430 revs = opts.get(b'rev')
1432 1431 selectedbranches = None
1433 1432 if revs:
1434 1433 revs = logcmdutil.revrange(repo, revs)
1435 1434 getbi = repo.revbranchcache().branchinfo
1436 1435 selectedbranches = {getbi(r)[0] for r in revs}
1437 1436
1438 1437 ui.pager(b'branches')
1439 1438 fm = ui.formatter(b'branches', opts)
1440 1439 hexfunc = fm.hexfunc
1441 1440
1442 1441 allheads = set(repo.heads())
1443 1442 branches = []
1444 1443 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1445 1444 if selectedbranches is not None and tag not in selectedbranches:
1446 1445 continue
1447 1446 isactive = False
1448 1447 if not isclosed:
1449 1448 openheads = set(repo.branchmap().iteropen(heads))
1450 1449 isactive = bool(openheads & allheads)
1451 1450 branches.append((tag, repo[tip], isactive, not isclosed))
1452 1451 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1453 1452
1454 1453 for tag, ctx, isactive, isopen in branches:
1455 1454 if active and not isactive:
1456 1455 continue
1457 1456 if isactive:
1458 1457 label = b'branches.active'
1459 1458 notice = b''
1460 1459 elif not isopen:
1461 1460 if not closed:
1462 1461 continue
1463 1462 label = b'branches.closed'
1464 1463 notice = _(b' (closed)')
1465 1464 else:
1466 1465 label = b'branches.inactive'
1467 1466 notice = _(b' (inactive)')
1468 1467 current = tag == repo.dirstate.branch()
1469 1468 if current:
1470 1469 label = b'branches.current'
1471 1470
1472 1471 fm.startitem()
1473 1472 fm.write(b'branch', b'%s', tag, label=label)
1474 1473 rev = ctx.rev()
1475 1474 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1476 1475 fmt = b' ' * padsize + b' %d:%s'
1477 1476 fm.condwrite(
1478 1477 not ui.quiet,
1479 1478 b'rev node',
1480 1479 fmt,
1481 1480 rev,
1482 1481 hexfunc(ctx.node()),
1483 1482 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1484 1483 )
1485 1484 fm.context(ctx=ctx)
1486 1485 fm.data(active=isactive, closed=not isopen, current=current)
1487 1486 if not ui.quiet:
1488 1487 fm.plain(notice)
1489 1488 fm.plain(b'\n')
1490 1489 fm.end()
1491 1490
1492 1491
1493 1492 @command(
1494 1493 b'bundle',
1495 1494 [
1496 1495 (
1497 1496 b'',
1498 1497 b'exact',
1499 1498 None,
1500 1499 _(b'compute the base from the revision specified'),
1501 1500 ),
1502 1501 (
1503 1502 b'f',
1504 1503 b'force',
1505 1504 None,
1506 1505 _(b'run even when the destination is unrelated'),
1507 1506 ),
1508 1507 (
1509 1508 b'r',
1510 1509 b'rev',
1511 1510 [],
1512 1511 _(b'a changeset intended to be added to the destination'),
1513 1512 _(b'REV'),
1514 1513 ),
1515 1514 (
1516 1515 b'b',
1517 1516 b'branch',
1518 1517 [],
1519 1518 _(b'a specific branch you would like to bundle'),
1520 1519 _(b'BRANCH'),
1521 1520 ),
1522 1521 (
1523 1522 b'',
1524 1523 b'base',
1525 1524 [],
1526 1525 _(b'a base changeset assumed to be available at the destination'),
1527 1526 _(b'REV'),
1528 1527 ),
1529 1528 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1530 1529 (
1531 1530 b't',
1532 1531 b'type',
1533 1532 b'bzip2',
1534 1533 _(b'bundle compression type to use'),
1535 1534 _(b'TYPE'),
1536 1535 ),
1537 1536 ]
1538 1537 + remoteopts,
1539 1538 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]...'),
1540 1539 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1541 1540 )
1542 1541 def bundle(ui, repo, fname, *dests, **opts):
1543 1542 """create a bundle file
1544 1543
1545 1544 Generate a bundle file containing data to be transferred to another
1546 1545 repository.
1547 1546
1548 1547 To create a bundle containing all changesets, use -a/--all
1549 1548 (or --base null). Otherwise, hg assumes the destination will have
1550 1549 all the nodes you specify with --base parameters. Otherwise, hg
1551 1550 will assume the repository has all the nodes in destination, or
1552 1551 default-push/default if no destination is specified, where destination
1553 1552 is the repositories you provide through DEST option.
1554 1553
1555 1554 You can change bundle format with the -t/--type option. See
1556 1555 :hg:`help bundlespec` for documentation on this format. By default,
1557 1556 the most appropriate format is used and compression defaults to
1558 1557 bzip2.
1559 1558
1560 1559 The bundle file can then be transferred using conventional means
1561 1560 and applied to another repository with the unbundle or pull
1562 1561 command. This is useful when direct push and pull are not
1563 1562 available or when exporting an entire repository is undesirable.
1564 1563
1565 1564 Applying bundles preserves all changeset contents including
1566 1565 permissions, copy/rename information, and revision history.
1567 1566
1568 1567 Returns 0 on success, 1 if no changes found.
1569 1568 """
1570 1569 opts = pycompat.byteskwargs(opts)
1571 1570
1572 1571 revs = None
1573 1572 if b'rev' in opts:
1574 1573 revstrings = opts[b'rev']
1575 1574 revs = logcmdutil.revrange(repo, revstrings)
1576 1575 if revstrings and not revs:
1577 1576 raise error.InputError(_(b'no commits to bundle'))
1578 1577
1579 1578 bundletype = opts.get(b'type', b'bzip2').lower()
1580 1579 try:
1581 1580 bundlespec = bundlecaches.parsebundlespec(
1582 1581 repo, bundletype, strict=False
1583 1582 )
1584 1583 except error.UnsupportedBundleSpecification as e:
1585 1584 raise error.InputError(
1586 1585 pycompat.bytestr(e),
1587 1586 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1588 1587 )
1589 1588 cgversion = bundlespec.params[b"cg.version"]
1590 1589
1591 1590 # Packed bundles are a pseudo bundle format for now.
1592 1591 if cgversion == b's1':
1593 1592 raise error.InputError(
1594 1593 _(b'packed bundles cannot be produced by "hg bundle"'),
1595 1594 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1596 1595 )
1597 1596
1598 1597 if opts.get(b'all'):
1599 1598 if dests:
1600 1599 raise error.InputError(
1601 1600 _(b"--all is incompatible with specifying destinations")
1602 1601 )
1603 1602 if opts.get(b'base'):
1604 1603 ui.warn(_(b"ignoring --base because --all was specified\n"))
1605 1604 if opts.get(b'exact'):
1606 1605 ui.warn(_(b"ignoring --exact because --all was specified\n"))
1607 1606 base = [nullrev]
1608 1607 elif opts.get(b'exact'):
1609 1608 if dests:
1610 1609 raise error.InputError(
1611 1610 _(b"--exact is incompatible with specifying destinations")
1612 1611 )
1613 1612 if opts.get(b'base'):
1614 1613 ui.warn(_(b"ignoring --base because --exact was specified\n"))
1615 1614 base = repo.revs(b'parents(%ld) - %ld', revs, revs)
1616 1615 if not base:
1617 1616 base = [nullrev]
1618 1617 else:
1619 1618 base = logcmdutil.revrange(repo, opts.get(b'base'))
1620 1619 if cgversion not in changegroup.supportedoutgoingversions(repo):
1621 1620 raise error.Abort(
1622 1621 _(b"repository does not support bundle version %s") % cgversion
1623 1622 )
1624 1623
1625 1624 if base:
1626 1625 if dests:
1627 1626 raise error.InputError(
1628 1627 _(b"--base is incompatible with specifying destinations")
1629 1628 )
1630 1629 cl = repo.changelog
1631 1630 common = [cl.node(rev) for rev in base]
1632 1631 heads = [cl.node(r) for r in revs] if revs else None
1633 1632 outgoing = discovery.outgoing(repo, common, heads)
1634 1633 missing = outgoing.missing
1635 1634 excluded = outgoing.excluded
1636 1635 else:
1637 1636 missing = set()
1638 1637 excluded = set()
1639 1638 for path in urlutil.get_push_paths(repo, ui, dests):
1640 1639 other = hg.peer(repo, opts, path)
1641 1640 if revs is not None:
1642 1641 hex_revs = [repo[r].hex() for r in revs]
1643 1642 else:
1644 1643 hex_revs = None
1645 1644 branches = (path.branch, [])
1646 1645 head_revs, checkout = hg.addbranchrevs(
1647 1646 repo, repo, branches, hex_revs
1648 1647 )
1649 1648 heads = (
1650 1649 head_revs
1651 1650 and pycompat.maplist(repo.lookup, head_revs)
1652 1651 or head_revs
1653 1652 )
1654 1653 outgoing = discovery.findcommonoutgoing(
1655 1654 repo,
1656 1655 other,
1657 1656 onlyheads=heads,
1658 1657 force=opts.get(b'force'),
1659 1658 portable=True,
1660 1659 )
1661 1660 missing.update(outgoing.missing)
1662 1661 excluded.update(outgoing.excluded)
1663 1662
1664 1663 if not missing:
1665 1664 scmutil.nochangesfound(ui, repo, not base and excluded)
1666 1665 return 1
1667 1666
1668 1667 # internal changeset are internal implementation details that should not
1669 1668 # leave the repository. Bundling with `hg bundle` create such risk.
1670 1669 bundled_internal = repo.revs(b"%ln and _internal()", missing)
1671 1670 if bundled_internal:
1672 1671 msg = _(b"cannot bundle internal changesets")
1673 1672 hint = _(b"%d internal changesets selected") % len(bundled_internal)
1674 1673 raise error.Abort(msg, hint=hint)
1675 1674
1676 1675 if heads:
1677 1676 outgoing = discovery.outgoing(
1678 1677 repo, missingroots=missing, ancestorsof=heads
1679 1678 )
1680 1679 else:
1681 1680 outgoing = discovery.outgoing(repo, missingroots=missing)
1682 1681 outgoing.excluded = sorted(excluded)
1683 1682
1684 1683 if cgversion == b'01': # bundle1
1685 1684 bversion = b'HG10' + bundlespec.wirecompression
1686 1685 bcompression = None
1687 1686 elif cgversion in (b'02', b'03'):
1688 1687 bversion = b'HG20'
1689 1688 bcompression = bundlespec.wirecompression
1690 1689 else:
1691 1690 raise error.ProgrammingError(
1692 1691 b'bundle: unexpected changegroup version %s' % cgversion
1693 1692 )
1694 1693
1695 1694 # TODO compression options should be derived from bundlespec parsing.
1696 1695 # This is a temporary hack to allow adjusting bundle compression
1697 1696 # level without a) formalizing the bundlespec changes to declare it
1698 1697 # b) introducing a command flag.
1699 1698 compopts = {}
1700 1699 complevel = ui.configint(
1701 1700 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1702 1701 )
1703 1702 if complevel is None:
1704 1703 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1705 1704 if complevel is not None:
1706 1705 compopts[b'level'] = complevel
1707 1706
1708 1707 compthreads = ui.configint(
1709 1708 b'experimental', b'bundlecompthreads.' + bundlespec.compression
1710 1709 )
1711 1710 if compthreads is None:
1712 1711 compthreads = ui.configint(b'experimental', b'bundlecompthreads')
1713 1712 if compthreads is not None:
1714 1713 compopts[b'threads'] = compthreads
1715 1714
1716 1715 # Bundling of obsmarker and phases is optional as not all clients
1717 1716 # support the necessary features.
1718 1717 cfg = ui.configbool
1719 1718 obsolescence_cfg = cfg(b'experimental', b'evolution.bundle-obsmarker')
1720 1719 bundlespec.set_param(b'obsolescence', obsolescence_cfg, overwrite=False)
1721 1720 obs_mand_cfg = cfg(b'experimental', b'evolution.bundle-obsmarker:mandatory')
1722 1721 bundlespec.set_param(
1723 1722 b'obsolescence-mandatory', obs_mand_cfg, overwrite=False
1724 1723 )
1725 1724 if not bundlespec.params.get(b'phases', False):
1726 1725 phases_cfg = cfg(b'experimental', b'bundle-phases')
1727 1726 bundlespec.set_param(b'phases', phases_cfg, overwrite=False)
1728 1727
1729 1728 bundle2.writenewbundle(
1730 1729 ui,
1731 1730 repo,
1732 1731 b'bundle',
1733 1732 fname,
1734 1733 bversion,
1735 1734 outgoing,
1736 1735 bundlespec.params,
1737 1736 compression=bcompression,
1738 1737 compopts=compopts,
1739 1738 )
1740 1739
1741 1740
1742 1741 @command(
1743 1742 b'cat',
1744 1743 [
1745 1744 (
1746 1745 b'o',
1747 1746 b'output',
1748 1747 b'',
1749 1748 _(b'print output to file with formatted name'),
1750 1749 _(b'FORMAT'),
1751 1750 ),
1752 1751 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1753 1752 (b'', b'decode', None, _(b'apply any matching decode filter')),
1754 1753 ]
1755 1754 + walkopts
1756 1755 + formatteropts,
1757 1756 _(b'[OPTION]... FILE...'),
1758 1757 helpcategory=command.CATEGORY_FILE_CONTENTS,
1759 1758 inferrepo=True,
1760 1759 intents={INTENT_READONLY},
1761 1760 )
1762 1761 def cat(ui, repo, file1, *pats, **opts):
1763 1762 """output the current or given revision of files
1764 1763
1765 1764 Print the specified files as they were at the given revision. If
1766 1765 no revision is given, the parent of the working directory is used.
1767 1766
1768 1767 Output may be to a file, in which case the name of the file is
1769 1768 given using a template string. See :hg:`help templates`. In addition
1770 1769 to the common template keywords, the following formatting rules are
1771 1770 supported:
1772 1771
1773 1772 :``%%``: literal "%" character
1774 1773 :``%s``: basename of file being printed
1775 1774 :``%d``: dirname of file being printed, or '.' if in repository root
1776 1775 :``%p``: root-relative path name of file being printed
1777 1776 :``%H``: changeset hash (40 hexadecimal digits)
1778 1777 :``%R``: changeset revision number
1779 1778 :``%h``: short-form changeset hash (12 hexadecimal digits)
1780 1779 :``%r``: zero-padded changeset revision number
1781 1780 :``%b``: basename of the exporting repository
1782 1781 :``\\``: literal "\\" character
1783 1782
1784 1783 .. container:: verbose
1785 1784
1786 1785 Template:
1787 1786
1788 1787 The following keywords are supported in addition to the common template
1789 1788 keywords and functions. See also :hg:`help templates`.
1790 1789
1791 1790 :data: String. File content.
1792 1791 :path: String. Repository-absolute path of the file.
1793 1792
1794 1793 Returns 0 on success.
1795 1794 """
1796 1795 rev = opts.get('rev')
1797 1796 if rev:
1798 1797 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1799 1798 ctx = logcmdutil.revsingle(repo, rev)
1800 1799 m = scmutil.match(ctx, (file1,) + pats, pycompat.byteskwargs(opts))
1801 1800 fntemplate = opts.pop('output', b'')
1802 1801 if cmdutil.isstdiofilename(fntemplate):
1803 1802 fntemplate = b''
1804 1803
1805 1804 if fntemplate:
1806 1805 fm = formatter.nullformatter(ui, b'cat', pycompat.byteskwargs(opts))
1807 1806 else:
1808 1807 ui.pager(b'cat')
1809 1808 fm = ui.formatter(b'cat', pycompat.byteskwargs(opts))
1810 1809 with fm:
1811 1810 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, b'', **opts)
1812 1811
1813 1812
1814 1813 @command(
1815 1814 b'clone',
1816 1815 [
1817 1816 (
1818 1817 b'U',
1819 1818 b'noupdate',
1820 1819 None,
1821 1820 _(
1822 1821 b'the clone will include an empty working '
1823 1822 b'directory (only a repository)'
1824 1823 ),
1825 1824 ),
1826 1825 (
1827 1826 b'u',
1828 1827 b'updaterev',
1829 1828 b'',
1830 1829 _(b'revision, tag, or branch to check out'),
1831 1830 _(b'REV'),
1832 1831 ),
1833 1832 (
1834 1833 b'r',
1835 1834 b'rev',
1836 1835 [],
1837 1836 _(
1838 1837 b'do not clone everything, but include this changeset'
1839 1838 b' and its ancestors'
1840 1839 ),
1841 1840 _(b'REV'),
1842 1841 ),
1843 1842 (
1844 1843 b'b',
1845 1844 b'branch',
1846 1845 [],
1847 1846 _(
1848 1847 b'do not clone everything, but include this branch\'s'
1849 1848 b' changesets and their ancestors'
1850 1849 ),
1851 1850 _(b'BRANCH'),
1852 1851 ),
1853 1852 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1854 1853 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1855 1854 (b'', b'stream', None, _(b'clone with minimal data processing')),
1856 1855 ]
1857 1856 + remoteopts,
1858 1857 _(b'[OPTION]... SOURCE [DEST]'),
1859 1858 helpcategory=command.CATEGORY_REPO_CREATION,
1860 1859 helpbasic=True,
1861 1860 norepo=True,
1862 1861 )
1863 1862 def clone(ui, source, dest=None, **opts):
1864 1863 """make a copy of an existing repository
1865 1864
1866 1865 Create a copy of an existing repository in a new directory.
1867 1866
1868 1867 If no destination directory name is specified, it defaults to the
1869 1868 basename of the source.
1870 1869
1871 1870 The location of the source is added to the new repository's
1872 1871 ``.hg/hgrc`` file, as the default to be used for future pulls.
1873 1872
1874 1873 Only local paths and ``ssh://`` URLs are supported as
1875 1874 destinations. For ``ssh://`` destinations, no working directory or
1876 1875 ``.hg/hgrc`` will be created on the remote side.
1877 1876
1878 1877 If the source repository has a bookmark called '@' set, that
1879 1878 revision will be checked out in the new repository by default.
1880 1879
1881 1880 To check out a particular version, use -u/--update, or
1882 1881 -U/--noupdate to create a clone with no working directory.
1883 1882
1884 1883 To pull only a subset of changesets, specify one or more revisions
1885 1884 identifiers with -r/--rev or branches with -b/--branch. The
1886 1885 resulting clone will contain only the specified changesets and
1887 1886 their ancestors. These options (or 'clone src#rev dest') imply
1888 1887 --pull, even for local source repositories.
1889 1888
1890 1889 In normal clone mode, the remote normalizes repository data into a common
1891 1890 exchange format and the receiving end translates this data into its local
1892 1891 storage format. --stream activates a different clone mode that essentially
1893 1892 copies repository files from the remote with minimal data processing. This
1894 1893 significantly reduces the CPU cost of a clone both remotely and locally.
1895 1894 However, it often increases the transferred data size by 30-40%. This can
1896 1895 result in substantially faster clones where I/O throughput is plentiful,
1897 1896 especially for larger repositories. A side-effect of --stream clones is
1898 1897 that storage settings and requirements on the remote are applied locally:
1899 1898 a modern client may inherit legacy or inefficient storage used by the
1900 1899 remote or a legacy Mercurial client may not be able to clone from a
1901 1900 modern Mercurial remote.
1902 1901
1903 1902 .. note::
1904 1903
1905 1904 Specifying a tag will include the tagged changeset but not the
1906 1905 changeset containing the tag.
1907 1906
1908 1907 .. container:: verbose
1909 1908
1910 1909 For efficiency, hardlinks are used for cloning whenever the
1911 1910 source and destination are on the same filesystem (note this
1912 1911 applies only to the repository data, not to the working
1913 1912 directory). Some filesystems, such as AFS, implement hardlinking
1914 1913 incorrectly, but do not report errors. In these cases, use the
1915 1914 --pull option to avoid hardlinking.
1916 1915
1917 1916 Mercurial will update the working directory to the first applicable
1918 1917 revision from this list:
1919 1918
1920 1919 a) null if -U or the source repository has no changesets
1921 1920 b) if -u . and the source repository is local, the first parent of
1922 1921 the source repository's working directory
1923 1922 c) the changeset specified with -u (if a branch name, this means the
1924 1923 latest head of that branch)
1925 1924 d) the changeset specified with -r
1926 1925 e) the tipmost head specified with -b
1927 1926 f) the tipmost head specified with the url#branch source syntax
1928 1927 g) the revision marked with the '@' bookmark, if present
1929 1928 h) the tipmost head of the default branch
1930 1929 i) tip
1931 1930
1932 1931 When cloning from servers that support it, Mercurial may fetch
1933 1932 pre-generated data from a server-advertised URL or inline from the
1934 1933 same stream. When this is done, hooks operating on incoming changesets
1935 1934 and changegroups may fire more than once, once for each pre-generated
1936 1935 bundle and as well as for any additional remaining data. In addition,
1937 1936 if an error occurs, the repository may be rolled back to a partial
1938 1937 clone. This behavior may change in future releases.
1939 1938 See :hg:`help -e clonebundles` for more.
1940 1939
1941 1940 Examples:
1942 1941
1943 1942 - clone a remote repository to a new directory named hg/::
1944 1943
1945 1944 hg clone https://www.mercurial-scm.org/repo/hg/
1946 1945
1947 1946 - create a lightweight local clone::
1948 1947
1949 1948 hg clone project/ project-feature/
1950 1949
1951 1950 - clone from an absolute path on an ssh server (note double-slash)::
1952 1951
1953 1952 hg clone ssh://user@server//home/projects/alpha/
1954 1953
1955 1954 - do a streaming clone while checking out a specified version::
1956 1955
1957 1956 hg clone --stream http://server/repo -u 1.5
1958 1957
1959 1958 - create a repository without changesets after a particular revision::
1960 1959
1961 1960 hg clone -r 04e544 experimental/ good/
1962 1961
1963 1962 - clone (and track) a particular named branch::
1964 1963
1965 1964 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1966 1965
1967 1966 See :hg:`help urls` for details on specifying URLs.
1968 1967
1969 1968 Returns 0 on success.
1970 1969 """
1971 1970 opts = pycompat.byteskwargs(opts)
1972 1971 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1973 1972
1974 1973 # --include/--exclude can come from narrow or sparse.
1975 1974 includepats, excludepats = None, None
1976 1975
1977 1976 # hg.clone() differentiates between None and an empty set. So make sure
1978 1977 # patterns are sets if narrow is requested without patterns.
1979 1978 if opts.get(b'narrow'):
1980 1979 includepats = set()
1981 1980 excludepats = set()
1982 1981
1983 1982 if opts.get(b'include'):
1984 1983 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1985 1984 if opts.get(b'exclude'):
1986 1985 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1987 1986
1988 1987 r = hg.clone(
1989 1988 ui,
1990 1989 opts,
1991 1990 source,
1992 1991 dest,
1993 1992 pull=opts.get(b'pull'),
1994 1993 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1995 1994 revs=opts.get(b'rev'),
1996 1995 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1997 1996 branch=opts.get(b'branch'),
1998 1997 shareopts=opts.get(b'shareopts'),
1999 1998 storeincludepats=includepats,
2000 1999 storeexcludepats=excludepats,
2001 2000 depth=opts.get(b'depth') or None,
2002 2001 )
2003 2002
2004 2003 return r is None
2005 2004
2006 2005
2007 2006 @command(
2008 2007 b'commit|ci',
2009 2008 [
2010 2009 (
2011 2010 b'A',
2012 2011 b'addremove',
2013 2012 None,
2014 2013 _(b'mark new/missing files as added/removed before committing'),
2015 2014 ),
2016 2015 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
2017 2016 (b'', b'amend', None, _(b'amend the parent of the working directory')),
2018 2017 (b's', b'secret', None, _(b'use the secret phase for committing')),
2019 2018 (b'', b'draft', None, _(b'use the draft phase for committing')),
2020 2019 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
2021 2020 (
2022 2021 b'',
2023 2022 b'force-close-branch',
2024 2023 None,
2025 2024 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
2026 2025 ),
2027 2026 (b'i', b'interactive', None, _(b'use interactive mode')),
2028 2027 ]
2029 2028 + walkopts
2030 2029 + commitopts
2031 2030 + commitopts2
2032 2031 + subrepoopts,
2033 2032 _(b'[OPTION]... [FILE]...'),
2034 2033 helpcategory=command.CATEGORY_COMMITTING,
2035 2034 helpbasic=True,
2036 2035 inferrepo=True,
2037 2036 )
2038 2037 def commit(ui, repo, *pats, **opts):
2039 2038 """commit the specified files or all outstanding changes
2040 2039
2041 2040 Commit changes to the given files into the repository. Unlike a
2042 2041 centralized SCM, this operation is a local operation. See
2043 2042 :hg:`push` for a way to actively distribute your changes.
2044 2043
2045 2044 If a list of files is omitted, all changes reported by :hg:`status`
2046 2045 will be committed.
2047 2046
2048 2047 If you are committing the result of a merge, do not provide any
2049 2048 filenames or -I/-X filters.
2050 2049
2051 2050 If no commit message is specified, Mercurial starts your
2052 2051 configured editor where you can enter a message. In case your
2053 2052 commit fails, you will find a backup of your message in
2054 2053 ``.hg/last-message.txt``.
2055 2054
2056 2055 The --close-branch flag can be used to mark the current branch
2057 2056 head closed. When all heads of a branch are closed, the branch
2058 2057 will be considered closed and no longer listed.
2059 2058
2060 2059 The --amend flag can be used to amend the parent of the
2061 2060 working directory with a new commit that contains the changes
2062 2061 in the parent in addition to those currently reported by :hg:`status`,
2063 2062 if there are any. The old commit is stored in a backup bundle in
2064 2063 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
2065 2064 on how to restore it).
2066 2065
2067 2066 Message, user and date are taken from the amended commit unless
2068 2067 specified. When a message isn't specified on the command line,
2069 2068 the editor will open with the message of the amended commit.
2070 2069
2071 2070 It is not possible to amend public changesets (see :hg:`help phases`)
2072 2071 or changesets that have children.
2073 2072
2074 2073 See :hg:`help dates` for a list of formats valid for -d/--date.
2075 2074
2076 2075 Returns 0 on success, 1 if nothing changed.
2077 2076
2078 2077 .. container:: verbose
2079 2078
2080 2079 Examples:
2081 2080
2082 2081 - commit all files ending in .py::
2083 2082
2084 2083 hg commit --include "set:**.py"
2085 2084
2086 2085 - commit all non-binary files::
2087 2086
2088 2087 hg commit --exclude "set:binary()"
2089 2088
2090 2089 - amend the current commit and set the date to now::
2091 2090
2092 2091 hg commit --amend --date now
2093 2092 """
2094 2093 cmdutil.check_at_most_one_arg(opts, 'draft', 'secret')
2095 2094 cmdutil.check_incompatible_arguments(opts, 'subrepos', ['amend'])
2096 2095 with repo.wlock(), repo.lock():
2097 2096 return _docommit(ui, repo, *pats, **opts)
2098 2097
2099 2098
2100 2099 def _docommit(ui, repo, *pats, **opts):
2101 2100 if opts.get('interactive'):
2102 2101 opts.pop('interactive')
2103 2102 ret = cmdutil.dorecord(
2104 2103 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2105 2104 )
2106 2105 # ret can be 0 (no changes to record) or the value returned by
2107 2106 # commit(), 1 if nothing changed or None on success.
2108 2107 return 1 if ret == 0 else ret
2109 2108
2110 2109 if opts.get('subrepos'):
2111 2110 # Let --subrepos on the command line override config setting.
2112 2111 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2113 2112
2114 2113 cmdutil.checkunfinished(repo, commit=True)
2115 2114
2116 2115 branch = repo[None].branch()
2117 2116 bheads = repo.branchheads(branch)
2118 2117 tip = repo.changelog.tip()
2119 2118
2120 2119 extra = {}
2121 2120 if opts.get('close_branch') or opts.get('force_close_branch'):
2122 2121 extra[b'close'] = b'1'
2123 2122
2124 2123 if repo[b'.'].closesbranch():
2125 2124 # Not ideal, but let us do an extra status early to prevent early
2126 2125 # bail out.
2127 2126 matcher = scmutil.match(
2128 2127 repo[None], pats, pycompat.byteskwargs(opts)
2129 2128 )
2130 2129 s = repo.status(match=matcher)
2131 2130 if s.modified or s.added or s.removed:
2132 2131 bheads = repo.branchheads(branch, closed=True)
2133 2132 else:
2134 2133 msg = _(b'current revision is already a branch closing head')
2135 2134 raise error.InputError(msg)
2136 2135
2137 2136 if not bheads:
2138 2137 raise error.InputError(
2139 2138 _(b'branch "%s" has no heads to close') % branch
2140 2139 )
2141 2140 elif (
2142 2141 branch == repo[b'.'].branch()
2143 2142 and repo[b'.'].node() not in bheads
2144 2143 and not opts.get('force_close_branch')
2145 2144 ):
2146 2145 hint = _(
2147 2146 b'use --force-close-branch to close branch from a non-head'
2148 2147 b' changeset'
2149 2148 )
2150 2149 raise error.InputError(_(b'can only close branch heads'), hint=hint)
2151 2150 elif opts.get('amend'):
2152 2151 if (
2153 2152 repo[b'.'].p1().branch() != branch
2154 2153 and repo[b'.'].p2().branch() != branch
2155 2154 ):
2156 2155 raise error.InputError(_(b'can only close branch heads'))
2157 2156
2158 2157 if opts.get('amend'):
2159 2158 if ui.configbool(b'ui', b'commitsubrepos'):
2160 2159 raise error.InputError(
2161 2160 _(b'cannot amend with ui.commitsubrepos enabled')
2162 2161 )
2163 2162
2164 2163 old = repo[b'.']
2165 2164 rewriteutil.precheck(repo, [old.rev()], b'amend')
2166 2165
2167 2166 # Currently histedit gets confused if an amend happens while histedit
2168 2167 # is in progress. Since we have a checkunfinished command, we are
2169 2168 # temporarily honoring it.
2170 2169 #
2171 2170 # Note: eventually this guard will be removed. Please do not expect
2172 2171 # this behavior to remain.
2173 2172 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2174 2173 cmdutil.checkunfinished(repo)
2175 2174
2176 2175 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2177 2176 if node == old.node():
2178 2177 ui.status(_(b"nothing changed\n"))
2179 2178 return 1
2180 2179 else:
2181 2180
2182 2181 def commitfunc(ui, repo, message, match, opts):
2183 2182 overrides = {}
2184 2183 if opts.get(b'secret'):
2185 2184 overrides[(b'phases', b'new-commit')] = b'secret'
2186 2185 elif opts.get(b'draft'):
2187 2186 overrides[(b'phases', b'new-commit')] = b'draft'
2188 2187
2189 2188 baseui = repo.baseui
2190 2189 with baseui.configoverride(overrides, b'commit'):
2191 2190 with ui.configoverride(overrides, b'commit'):
2192 2191 editform = cmdutil.mergeeditform(
2193 2192 repo[None], b'commit.normal'
2194 2193 )
2195 2194 editor = cmdutil.getcommiteditor(
2196 2195 editform=editform, **pycompat.strkwargs(opts)
2197 2196 )
2198 2197 return repo.commit(
2199 2198 message,
2200 2199 opts.get(b'user'),
2201 2200 opts.get(b'date'),
2202 2201 match,
2203 2202 editor=editor,
2204 2203 extra=extra,
2205 2204 )
2206 2205
2207 2206 node = cmdutil.commit(
2208 2207 ui, repo, commitfunc, pats, pycompat.byteskwargs(opts)
2209 2208 )
2210 2209
2211 2210 if not node:
2212 2211 stat = cmdutil.postcommitstatus(
2213 2212 repo, pats, pycompat.byteskwargs(opts)
2214 2213 )
2215 2214 if stat.deleted:
2216 2215 ui.status(
2217 2216 _(
2218 2217 b"nothing changed (%d missing files, see "
2219 2218 b"'hg status')\n"
2220 2219 )
2221 2220 % len(stat.deleted)
2222 2221 )
2223 2222 else:
2224 2223 ui.status(_(b"nothing changed\n"))
2225 2224 return 1
2226 2225
2227 2226 cmdutil.commitstatus(repo, node, branch, bheads, tip, **opts)
2228 2227
2229 2228 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2230 2229 status(
2231 2230 ui,
2232 2231 repo,
2233 2232 modified=True,
2234 2233 added=True,
2235 2234 removed=True,
2236 2235 deleted=True,
2237 2236 unknown=True,
2238 2237 subrepos=opts.get('subrepos'),
2239 2238 )
2240 2239
2241 2240
2242 2241 @command(
2243 2242 b'config|showconfig|debugconfig',
2244 2243 [
2245 2244 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2246 2245 # This is experimental because we need
2247 2246 # * reasonable behavior around aliases,
2248 2247 # * decide if we display [debug] [experimental] and [devel] section par
2249 2248 # default
2250 2249 # * some way to display "generic" config entry (the one matching
2251 2250 # regexp,
2252 2251 # * proper display of the different value type
2253 2252 # * a better way to handle <DYNAMIC> values (and variable types),
2254 2253 # * maybe some type information ?
2255 2254 (
2256 2255 b'',
2257 2256 b'exp-all-known',
2258 2257 None,
2259 2258 _(b'show all known config option (EXPERIMENTAL)'),
2260 2259 ),
2261 2260 (b'e', b'edit', None, _(b'edit user config')),
2262 2261 (b'l', b'local', None, _(b'edit repository config')),
2263 2262 (b'', b'source', None, _(b'show source of configuration value')),
2264 2263 (
2265 2264 b'',
2266 2265 b'shared',
2267 2266 None,
2268 2267 _(b'edit shared source repository config (EXPERIMENTAL)'),
2269 2268 ),
2270 2269 (b'', b'non-shared', None, _(b'edit non shared config (EXPERIMENTAL)')),
2271 2270 (b'g', b'global', None, _(b'edit global config')),
2272 2271 ]
2273 2272 + formatteropts,
2274 2273 _(b'[-u] [NAME]...'),
2275 2274 helpcategory=command.CATEGORY_HELP,
2276 2275 optionalrepo=True,
2277 2276 intents={INTENT_READONLY},
2278 2277 )
2279 2278 def config(ui, repo, *values, **opts):
2280 2279 """show combined config settings from all hgrc files
2281 2280
2282 2281 With no arguments, print names and values of all config items.
2283 2282
2284 2283 With one argument of the form section.name, print just the value
2285 2284 of that config item.
2286 2285
2287 2286 With multiple arguments, print names and values of all config
2288 2287 items with matching section names or section.names.
2289 2288
2290 2289 With --edit, start an editor on the user-level config file. With
2291 2290 --global, edit the system-wide config file. With --local, edit the
2292 2291 repository-level config file.
2293 2292
2294 2293 With --source, the source (filename and line number) is printed
2295 2294 for each config item.
2296 2295
2297 2296 See :hg:`help config` for more information about config files.
2298 2297
2299 2298 .. container:: verbose
2300 2299
2301 2300 --non-shared flag is used to edit `.hg/hgrc-not-shared` config file.
2302 2301 This file is not shared across shares when in share-safe mode.
2303 2302
2304 2303 Template:
2305 2304
2306 2305 The following keywords are supported. See also :hg:`help templates`.
2307 2306
2308 2307 :name: String. Config name.
2309 2308 :source: String. Filename and line number where the item is defined.
2310 2309 :value: String. Config value.
2311 2310
2312 2311 The --shared flag can be used to edit the config file of shared source
2313 2312 repository. It only works when you have shared using the experimental
2314 2313 share safe feature.
2315 2314
2316 2315 Returns 0 on success, 1 if NAME does not exist.
2317 2316
2318 2317 """
2319 2318
2320 2319 opts = pycompat.byteskwargs(opts)
2321 2320 editopts = (b'edit', b'local', b'global', b'shared', b'non_shared')
2322 2321 if any(opts.get(o) for o in editopts):
2323 2322 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2324 2323 if opts.get(b'local'):
2325 2324 if not repo:
2326 2325 raise error.InputError(
2327 2326 _(b"can't use --local outside a repository")
2328 2327 )
2329 2328 paths = [repo.vfs.join(b'hgrc')]
2330 2329 elif opts.get(b'global'):
2331 2330 paths = rcutil.systemrcpath()
2332 2331 elif opts.get(b'shared'):
2333 2332 if not repo.shared():
2334 2333 raise error.InputError(
2335 2334 _(b"repository is not shared; can't use --shared")
2336 2335 )
2337 2336 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2338 2337 raise error.InputError(
2339 2338 _(
2340 2339 b"share safe feature not enabled; "
2341 2340 b"unable to edit shared source repository config"
2342 2341 )
2343 2342 )
2344 2343 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2345 2344 elif opts.get(b'non_shared'):
2346 2345 paths = [repo.vfs.join(b'hgrc-not-shared')]
2347 2346 else:
2348 2347 paths = rcutil.userrcpath()
2349 2348
2350 2349 for f in paths:
2351 2350 if os.path.exists(f):
2352 2351 break
2353 2352 else:
2354 2353 if opts.get(b'global'):
2355 2354 samplehgrc = uimod.samplehgrcs[b'global']
2356 2355 elif opts.get(b'local'):
2357 2356 samplehgrc = uimod.samplehgrcs[b'local']
2358 2357 else:
2359 2358 samplehgrc = uimod.samplehgrcs[b'user']
2360 2359
2361 2360 f = paths[0]
2362 2361 fp = open(f, b"wb")
2363 2362 fp.write(util.tonativeeol(samplehgrc))
2364 2363 fp.close()
2365 2364
2366 2365 editor = ui.geteditor()
2367 2366 ui.system(
2368 2367 b"%s \"%s\"" % (editor, f),
2369 2368 onerr=error.InputError,
2370 2369 errprefix=_(b"edit failed"),
2371 2370 blockedtag=b'config_edit',
2372 2371 )
2373 2372 return
2374 2373 ui.pager(b'config')
2375 2374 fm = ui.formatter(b'config', opts)
2376 2375 for t, f in rcutil.rccomponents():
2377 2376 if t == b'path':
2378 2377 ui.debug(b'read config from: %s\n' % f)
2379 2378 elif t == b'resource':
2380 2379 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2381 2380 elif t == b'items':
2382 2381 # Don't print anything for 'items'.
2383 2382 pass
2384 2383 else:
2385 2384 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2386 2385 untrusted = bool(opts.get(b'untrusted'))
2387 2386
2388 2387 selsections = selentries = []
2389 2388 if values:
2390 2389 selsections = [v for v in values if b'.' not in v]
2391 2390 selentries = [v for v in values if b'.' in v]
2392 2391 uniquesel = len(selentries) == 1 and not selsections
2393 2392 selsections = set(selsections)
2394 2393 selentries = set(selentries)
2395 2394
2396 2395 matched = False
2397 2396 all_known = opts[b'exp_all_known']
2398 2397 show_source = ui.debugflag or opts.get(b'source')
2399 2398 entries = ui.walkconfig(untrusted=untrusted, all_known=all_known)
2400 2399 for section, name, value in entries:
2401 2400 source = ui.configsource(section, name, untrusted)
2402 2401 value = pycompat.bytestr(value)
2403 2402 defaultvalue = ui.configdefault(section, name)
2404 2403 if fm.isplain():
2405 2404 source = source or b'none'
2406 2405 value = value.replace(b'\n', b'\\n')
2407 2406 entryname = section + b'.' + name
2408 2407 if values and not (section in selsections or entryname in selentries):
2409 2408 continue
2410 2409 fm.startitem()
2411 2410 fm.condwrite(show_source, b'source', b'%s: ', source)
2412 2411 if uniquesel:
2413 2412 fm.data(name=entryname)
2414 2413 fm.write(b'value', b'%s\n', value)
2415 2414 else:
2416 2415 fm.write(b'name value', b'%s=%s\n', entryname, value)
2417 2416 if formatter.isprintable(defaultvalue):
2418 2417 fm.data(defaultvalue=defaultvalue)
2419 2418 elif isinstance(defaultvalue, list) and all(
2420 2419 formatter.isprintable(e) for e in defaultvalue
2421 2420 ):
2422 2421 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2423 2422 # TODO: no idea how to process unsupported defaultvalue types
2424 2423 matched = True
2425 2424 fm.end()
2426 2425 if matched:
2427 2426 return 0
2428 2427 return 1
2429 2428
2430 2429
2431 2430 @command(
2432 2431 b'continue',
2433 2432 dryrunopts,
2434 2433 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2435 2434 helpbasic=True,
2436 2435 )
2437 2436 def continuecmd(ui, repo, **opts):
2438 2437 """resumes an interrupted operation (EXPERIMENTAL)
2439 2438
2440 2439 Finishes a multistep operation like graft, histedit, rebase, merge,
2441 2440 and unshelve if they are in an interrupted state.
2442 2441
2443 2442 use --dry-run/-n to dry run the command.
2444 2443 """
2445 2444 dryrun = opts.get('dry_run')
2446 2445 contstate = cmdutil.getunfinishedstate(repo)
2447 2446 if not contstate:
2448 2447 raise error.StateError(_(b'no operation in progress'))
2449 2448 if not contstate.continuefunc:
2450 2449 raise error.StateError(
2451 2450 (
2452 2451 _(b"%s in progress but does not support 'hg continue'")
2453 2452 % (contstate._opname)
2454 2453 ),
2455 2454 hint=contstate.continuemsg(),
2456 2455 )
2457 2456 if dryrun:
2458 2457 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2459 2458 return
2460 2459 return contstate.continuefunc(ui, repo)
2461 2460
2462 2461
2463 2462 @command(
2464 2463 b'copy|cp',
2465 2464 [
2466 2465 (b'', b'forget', None, _(b'unmark a destination file as copied')),
2467 2466 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2468 2467 (
2469 2468 b'',
2470 2469 b'at-rev',
2471 2470 b'',
2472 2471 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2473 2472 _(b'REV'),
2474 2473 ),
2475 2474 (
2476 2475 b'f',
2477 2476 b'force',
2478 2477 None,
2479 2478 _(b'forcibly copy over an existing managed file'),
2480 2479 ),
2481 2480 ]
2482 2481 + walkopts
2483 2482 + dryrunopts,
2484 2483 _(b'[OPTION]... (SOURCE... DEST | --forget DEST...)'),
2485 2484 helpcategory=command.CATEGORY_FILE_CONTENTS,
2486 2485 )
2487 2486 def copy(ui, repo, *pats, **opts):
2488 2487 """mark files as copied for the next commit
2489 2488
2490 2489 Mark dest as having copies of source files. If dest is a
2491 2490 directory, copies are put in that directory. If dest is a file,
2492 2491 the source must be a single file.
2493 2492
2494 2493 By default, this command copies the contents of files as they
2495 2494 exist in the working directory. If invoked with -A/--after, the
2496 2495 operation is recorded, but no copying is performed.
2497 2496
2498 2497 To undo marking a destination file as copied, use --forget. With that
2499 2498 option, all given (positional) arguments are unmarked as copies. The
2500 2499 destination file(s) will be left in place (still tracked). Note that
2501 2500 :hg:`copy --forget` behaves the same way as :hg:`rename --forget`.
2502 2501
2503 2502 This command takes effect with the next commit by default.
2504 2503
2505 2504 Returns 0 on success, 1 if errors are encountered.
2506 2505 """
2507 2506 opts = pycompat.byteskwargs(opts)
2508 2507
2509 2508 context = lambda repo: repo.dirstate.changing_files(repo)
2510 2509 rev = opts.get(b'at_rev')
2511 2510 ctx = None
2512 2511 if rev:
2513 2512 ctx = logcmdutil.revsingle(repo, rev)
2514 2513 if ctx.rev() is not None:
2515 2514
2516 2515 def context(repo):
2517 2516 return util.nullcontextmanager()
2518 2517
2519 2518 opts[b'at_rev'] = ctx.rev()
2520 2519 with repo.wlock(), context(repo):
2521 2520 return cmdutil.copy(ui, repo, pats, opts)
2522 2521
2523 2522
2524 2523 @command(
2525 2524 b'debugcommands',
2526 2525 [],
2527 2526 _(b'[COMMAND]'),
2528 2527 helpcategory=command.CATEGORY_HELP,
2529 2528 norepo=True,
2530 2529 )
2531 2530 def debugcommands(ui, cmd=b'', *args):
2532 2531 """list all available commands and options"""
2533 2532 for cmd, vals in sorted(table.items()):
2534 2533 cmd = cmd.split(b'|')[0]
2535 2534 opts = b', '.join([i[1] for i in vals[1]])
2536 2535 ui.write(b'%s: %s\n' % (cmd, opts))
2537 2536
2538 2537
2539 2538 @command(
2540 2539 b'debugcomplete',
2541 2540 [(b'o', b'options', None, _(b'show the command options'))],
2542 2541 _(b'[-o] CMD'),
2543 2542 helpcategory=command.CATEGORY_HELP,
2544 2543 norepo=True,
2545 2544 )
2546 2545 def debugcomplete(ui, cmd=b'', **opts):
2547 2546 """returns the completion list associated with the given command"""
2548 2547
2549 2548 if opts.get('options'):
2550 2549 options = []
2551 2550 otables = [globalopts]
2552 2551 if cmd:
2553 2552 aliases, entry = cmdutil.findcmd(cmd, table, False)
2554 2553 otables.append(entry[1])
2555 2554 for t in otables:
2556 2555 for o in t:
2557 2556 if b"(DEPRECATED)" in o[3]:
2558 2557 continue
2559 2558 if o[0]:
2560 2559 options.append(b'-%s' % o[0])
2561 2560 options.append(b'--%s' % o[1])
2562 2561 ui.write(b"%s\n" % b"\n".join(options))
2563 2562 return
2564 2563
2565 2564 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2566 2565 if ui.verbose:
2567 2566 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2568 2567 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2569 2568
2570 2569
2571 2570 @command(
2572 2571 b'diff',
2573 2572 [
2574 2573 (b'r', b'rev', [], _(b'revision (DEPRECATED)'), _(b'REV')),
2575 2574 (b'', b'from', b'', _(b'revision to diff from'), _(b'REV1')),
2576 2575 (b'', b'to', b'', _(b'revision to diff to'), _(b'REV2')),
2577 2576 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2578 2577 ]
2579 2578 + diffopts
2580 2579 + diffopts2
2581 2580 + walkopts
2582 2581 + subrepoopts,
2583 2582 _(b'[OPTION]... ([-c REV] | [--from REV1] [--to REV2]) [FILE]...'),
2584 2583 helpcategory=command.CATEGORY_FILE_CONTENTS,
2585 2584 helpbasic=True,
2586 2585 inferrepo=True,
2587 2586 intents={INTENT_READONLY},
2588 2587 )
2589 2588 def diff(ui, repo, *pats, **opts):
2590 2589 """diff repository (or selected files)
2591 2590
2592 2591 Show differences between revisions for the specified files.
2593 2592
2594 2593 Differences between files are shown using the unified diff format.
2595 2594
2596 2595 .. note::
2597 2596
2598 2597 :hg:`diff` may generate unexpected results for merges, as it will
2599 2598 default to comparing against the working directory's first
2600 2599 parent changeset if no revisions are specified. To diff against the
2601 2600 conflict regions, you can use `--config diff.merge=yes`.
2602 2601
2603 2602 By default, the working directory files are compared to its first parent. To
2604 2603 see the differences from another revision, use --from. To see the difference
2605 2604 to another revision, use --to. For example, :hg:`diff --from .^` will show
2606 2605 the differences from the working copy's grandparent to the working copy,
2607 2606 :hg:`diff --to .` will show the diff from the working copy to its parent
2608 2607 (i.e. the reverse of the default), and :hg:`diff --from 1.0 --to 1.2` will
2609 2608 show the diff between those two revisions.
2610 2609
2611 2610 Alternatively you can specify -c/--change with a revision to see the changes
2612 2611 in that changeset relative to its first parent (i.e. :hg:`diff -c 42` is
2613 2612 equivalent to :hg:`diff --from 42^ --to 42`)
2614 2613
2615 2614 Without the -a/--text option, diff will avoid generating diffs of
2616 2615 files it detects as binary. With -a, diff will generate a diff
2617 2616 anyway, probably with undesirable results.
2618 2617
2619 2618 Use the -g/--git option to generate diffs in the git extended diff
2620 2619 format. For more information, read :hg:`help diffs`.
2621 2620
2622 2621 .. container:: verbose
2623 2622
2624 2623 Examples:
2625 2624
2626 2625 - compare a file in the current working directory to its parent::
2627 2626
2628 2627 hg diff foo.c
2629 2628
2630 2629 - compare two historical versions of a directory, with rename info::
2631 2630
2632 2631 hg diff --git --from 1.0 --to 1.2 lib/
2633 2632
2634 2633 - get change stats relative to the last change on some date::
2635 2634
2636 2635 hg diff --stat --from "date('may 2')"
2637 2636
2638 2637 - diff all newly-added files that contain a keyword::
2639 2638
2640 2639 hg diff "set:added() and grep(GNU)"
2641 2640
2642 2641 - compare a revision and its parents::
2643 2642
2644 2643 hg diff -c 9353 # compare against first parent
2645 2644 hg diff --from 9353^ --to 9353 # same using revset syntax
2646 2645 hg diff --from 9353^2 --to 9353 # compare against the second parent
2647 2646
2648 2647 Returns 0 on success.
2649 2648 """
2650 2649
2651 2650 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2652 2651 opts = pycompat.byteskwargs(opts)
2653 2652 revs = opts.get(b'rev')
2654 2653 change = opts.get(b'change')
2655 2654 from_rev = opts.get(b'from')
2656 2655 to_rev = opts.get(b'to')
2657 2656 stat = opts.get(b'stat')
2658 2657 reverse = opts.get(b'reverse')
2659 2658
2660 2659 cmdutil.check_incompatible_arguments(opts, b'from', [b'rev', b'change'])
2661 2660 cmdutil.check_incompatible_arguments(opts, b'to', [b'rev', b'change'])
2662 2661 if change:
2663 2662 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2664 2663 ctx2 = logcmdutil.revsingle(repo, change, None)
2665 2664 ctx1 = logcmdutil.diff_parent(ctx2)
2666 2665 elif from_rev or to_rev:
2667 2666 repo = scmutil.unhidehashlikerevs(
2668 2667 repo, [from_rev] + [to_rev], b'nowarn'
2669 2668 )
2670 2669 ctx1 = logcmdutil.revsingle(repo, from_rev, None)
2671 2670 ctx2 = logcmdutil.revsingle(repo, to_rev, None)
2672 2671 else:
2673 2672 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2674 2673 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
2675 2674
2676 2675 if reverse:
2677 2676 ctxleft = ctx2
2678 2677 ctxright = ctx1
2679 2678 else:
2680 2679 ctxleft = ctx1
2681 2680 ctxright = ctx2
2682 2681
2683 2682 diffopts = patch.diffallopts(ui, opts)
2684 2683 m = scmutil.match(ctx2, pats, opts)
2685 2684 m = repo.narrowmatch(m)
2686 2685 ui.pager(b'diff')
2687 2686 logcmdutil.diffordiffstat(
2688 2687 ui,
2689 2688 repo,
2690 2689 diffopts,
2691 2690 ctxleft,
2692 2691 ctxright,
2693 2692 m,
2694 2693 stat=stat,
2695 2694 listsubrepos=opts.get(b'subrepos'),
2696 2695 root=opts.get(b'root'),
2697 2696 )
2698 2697
2699 2698
2700 2699 @command(
2701 2700 b'export',
2702 2701 [
2703 2702 (
2704 2703 b'B',
2705 2704 b'bookmark',
2706 2705 b'',
2707 2706 _(b'export changes only reachable by given bookmark'),
2708 2707 _(b'BOOKMARK'),
2709 2708 ),
2710 2709 (
2711 2710 b'o',
2712 2711 b'output',
2713 2712 b'',
2714 2713 _(b'print output to file with formatted name'),
2715 2714 _(b'FORMAT'),
2716 2715 ),
2717 2716 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2718 2717 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2719 2718 ]
2720 2719 + diffopts
2721 2720 + formatteropts,
2722 2721 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2723 2722 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2724 2723 helpbasic=True,
2725 2724 intents={INTENT_READONLY},
2726 2725 )
2727 2726 def export(ui, repo, *changesets, **opts):
2728 2727 """dump the header and diffs for one or more changesets
2729 2728
2730 2729 Print the changeset header and diffs for one or more revisions.
2731 2730 If no revision is given, the parent of the working directory is used.
2732 2731
2733 2732 The information shown in the changeset header is: author, date,
2734 2733 branch name (if non-default), changeset hash, parent(s) and commit
2735 2734 comment.
2736 2735
2737 2736 .. note::
2738 2737
2739 2738 :hg:`export` may generate unexpected diff output for merge
2740 2739 changesets, as it will compare the merge changeset against its
2741 2740 first parent only.
2742 2741
2743 2742 Output may be to a file, in which case the name of the file is
2744 2743 given using a template string. See :hg:`help templates`. In addition
2745 2744 to the common template keywords, the following formatting rules are
2746 2745 supported:
2747 2746
2748 2747 :``%%``: literal "%" character
2749 2748 :``%H``: changeset hash (40 hexadecimal digits)
2750 2749 :``%N``: number of patches being generated
2751 2750 :``%R``: changeset revision number
2752 2751 :``%b``: basename of the exporting repository
2753 2752 :``%h``: short-form changeset hash (12 hexadecimal digits)
2754 2753 :``%m``: first line of the commit message (only alphanumeric characters)
2755 2754 :``%n``: zero-padded sequence number, starting at 1
2756 2755 :``%r``: zero-padded changeset revision number
2757 2756 :``\\``: literal "\\" character
2758 2757
2759 2758 Without the -a/--text option, export will avoid generating diffs
2760 2759 of files it detects as binary. With -a, export will generate a
2761 2760 diff anyway, probably with undesirable results.
2762 2761
2763 2762 With -B/--bookmark changesets reachable by the given bookmark are
2764 2763 selected.
2765 2764
2766 2765 Use the -g/--git option to generate diffs in the git extended diff
2767 2766 format. See :hg:`help diffs` for more information.
2768 2767
2769 2768 With the --switch-parent option, the diff will be against the
2770 2769 second parent. It can be useful to review a merge.
2771 2770
2772 2771 .. container:: verbose
2773 2772
2774 2773 Template:
2775 2774
2776 2775 The following keywords are supported in addition to the common template
2777 2776 keywords and functions. See also :hg:`help templates`.
2778 2777
2779 2778 :diff: String. Diff content.
2780 2779 :parents: List of strings. Parent nodes of the changeset.
2781 2780
2782 2781 Examples:
2783 2782
2784 2783 - use export and import to transplant a bugfix to the current
2785 2784 branch::
2786 2785
2787 2786 hg export -r 9353 | hg import -
2788 2787
2789 2788 - export all the changesets between two revisions to a file with
2790 2789 rename information::
2791 2790
2792 2791 hg export --git -r 123:150 > changes.txt
2793 2792
2794 2793 - split outgoing changes into a series of patches with
2795 2794 descriptive names::
2796 2795
2797 2796 hg export -r "outgoing()" -o "%n-%m.patch"
2798 2797
2799 2798 Returns 0 on success.
2800 2799 """
2801 2800 opts = pycompat.byteskwargs(opts)
2802 2801 bookmark = opts.get(b'bookmark')
2803 2802 changesets += tuple(opts.get(b'rev', []))
2804 2803
2805 2804 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2806 2805
2807 2806 if bookmark:
2808 2807 if bookmark not in repo._bookmarks:
2809 2808 raise error.InputError(_(b"bookmark '%s' not found") % bookmark)
2810 2809
2811 2810 revs = scmutil.bookmarkrevs(repo, bookmark)
2812 2811 else:
2813 2812 if not changesets:
2814 2813 changesets = [b'.']
2815 2814
2816 2815 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2817 2816 revs = logcmdutil.revrange(repo, changesets)
2818 2817
2819 2818 if not revs:
2820 2819 raise error.InputError(_(b"export requires at least one changeset"))
2821 2820 if len(revs) > 1:
2822 2821 ui.note(_(b'exporting patches:\n'))
2823 2822 else:
2824 2823 ui.note(_(b'exporting patch:\n'))
2825 2824
2826 2825 fntemplate = opts.get(b'output')
2827 2826 if cmdutil.isstdiofilename(fntemplate):
2828 2827 fntemplate = b''
2829 2828
2830 2829 if fntemplate:
2831 2830 fm = formatter.nullformatter(ui, b'export', opts)
2832 2831 else:
2833 2832 ui.pager(b'export')
2834 2833 fm = ui.formatter(b'export', opts)
2835 2834 with fm:
2836 2835 cmdutil.export(
2837 2836 repo,
2838 2837 revs,
2839 2838 fm,
2840 2839 fntemplate=fntemplate,
2841 2840 switch_parent=opts.get(b'switch_parent'),
2842 2841 opts=patch.diffallopts(ui, opts),
2843 2842 )
2844 2843
2845 2844
2846 2845 @command(
2847 2846 b'files',
2848 2847 [
2849 2848 (
2850 2849 b'r',
2851 2850 b'rev',
2852 2851 b'',
2853 2852 _(b'search the repository as it is in REV'),
2854 2853 _(b'REV'),
2855 2854 ),
2856 2855 (
2857 2856 b'0',
2858 2857 b'print0',
2859 2858 None,
2860 2859 _(b'end filenames with NUL, for use with xargs'),
2861 2860 ),
2862 2861 ]
2863 2862 + walkopts
2864 2863 + formatteropts
2865 2864 + subrepoopts,
2866 2865 _(b'[OPTION]... [FILE]...'),
2867 2866 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2868 2867 intents={INTENT_READONLY},
2869 2868 )
2870 2869 def files(ui, repo, *pats, **opts):
2871 2870 """list tracked files
2872 2871
2873 2872 Print files under Mercurial control in the working directory or
2874 2873 specified revision for given files (excluding removed files).
2875 2874 Files can be specified as filenames or filesets.
2876 2875
2877 2876 If no files are given to match, this command prints the names
2878 2877 of all files under Mercurial control.
2879 2878
2880 2879 .. container:: verbose
2881 2880
2882 2881 Template:
2883 2882
2884 2883 The following keywords are supported in addition to the common template
2885 2884 keywords and functions. See also :hg:`help templates`.
2886 2885
2887 2886 :flags: String. Character denoting file's symlink and executable bits.
2888 2887 :path: String. Repository-absolute path of the file.
2889 2888 :size: Integer. Size of the file in bytes.
2890 2889
2891 2890 Examples:
2892 2891
2893 2892 - list all files under the current directory::
2894 2893
2895 2894 hg files .
2896 2895
2897 2896 - shows sizes and flags for current revision::
2898 2897
2899 2898 hg files -vr .
2900 2899
2901 2900 - list all files named README::
2902 2901
2903 2902 hg files -I "**/README"
2904 2903
2905 2904 - list all binary files::
2906 2905
2907 2906 hg files "set:binary()"
2908 2907
2909 2908 - find files containing a regular expression::
2910 2909
2911 2910 hg files "set:grep('bob')"
2912 2911
2913 2912 - search tracked file contents with xargs and grep::
2914 2913
2915 2914 hg files -0 | xargs -0 grep foo
2916 2915
2917 2916 See :hg:`help patterns` and :hg:`help filesets` for more information
2918 2917 on specifying file patterns.
2919 2918
2920 2919 Returns 0 if a match is found, 1 otherwise.
2921 2920
2922 2921 """
2923 2922
2924 2923 opts = pycompat.byteskwargs(opts)
2925 2924 rev = opts.get(b'rev')
2926 2925 if rev:
2927 2926 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2928 2927 ctx = logcmdutil.revsingle(repo, rev, None)
2929 2928
2930 2929 end = b'\n'
2931 2930 if opts.get(b'print0'):
2932 2931 end = b'\0'
2933 2932 fmt = b'%s' + end
2934 2933
2935 2934 m = scmutil.match(ctx, pats, opts)
2936 2935 ui.pager(b'files')
2937 2936 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2938 2937 with ui.formatter(b'files', opts) as fm:
2939 2938 return cmdutil.files(
2940 2939 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2941 2940 )
2942 2941
2943 2942
2944 2943 @command(
2945 2944 b'forget',
2946 2945 [
2947 2946 (b'i', b'interactive', None, _(b'use interactive mode')),
2948 2947 ]
2949 2948 + walkopts
2950 2949 + dryrunopts,
2951 2950 _(b'[OPTION]... FILE...'),
2952 2951 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2953 2952 helpbasic=True,
2954 2953 inferrepo=True,
2955 2954 )
2956 2955 def forget(ui, repo, *pats, **opts):
2957 2956 """forget the specified files on the next commit
2958 2957
2959 2958 Mark the specified files so they will no longer be tracked
2960 2959 after the next commit.
2961 2960
2962 2961 This only removes files from the current branch, not from the
2963 2962 entire project history, and it does not delete them from the
2964 2963 working directory.
2965 2964
2966 2965 To delete the file from the working directory, see :hg:`remove`.
2967 2966
2968 2967 To undo a forget before the next commit, see :hg:`add`.
2969 2968
2970 2969 .. container:: verbose
2971 2970
2972 2971 Examples:
2973 2972
2974 2973 - forget newly-added binary files::
2975 2974
2976 2975 hg forget "set:added() and binary()"
2977 2976
2978 2977 - forget files that would be excluded by .hgignore::
2979 2978
2980 2979 hg forget "set:hgignore()"
2981 2980
2982 2981 Returns 0 on success.
2983 2982 """
2984 2983
2985 2984 opts = pycompat.byteskwargs(opts)
2986 2985 if not pats:
2987 2986 raise error.InputError(_(b'no files specified'))
2988 2987
2989 2988 with repo.wlock(), repo.dirstate.changing_files(repo):
2990 2989 m = scmutil.match(repo[None], pats, opts)
2991 2990 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2992 2991 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2993 2992 rejected = cmdutil.forget(
2994 2993 ui,
2995 2994 repo,
2996 2995 m,
2997 2996 prefix=b"",
2998 2997 uipathfn=uipathfn,
2999 2998 explicitonly=False,
3000 2999 dryrun=dryrun,
3001 3000 interactive=interactive,
3002 3001 )[0]
3003 3002 return rejected and 1 or 0
3004 3003
3005 3004
3006 3005 @command(
3007 3006 b'graft',
3008 3007 [
3009 3008 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
3010 3009 (
3011 3010 b'',
3012 3011 b'base',
3013 3012 b'',
3014 3013 _(b'base revision when doing the graft merge (ADVANCED)'),
3015 3014 _(b'REV'),
3016 3015 ),
3017 3016 (b'c', b'continue', False, _(b'resume interrupted graft')),
3018 3017 (b'', b'stop', False, _(b'stop interrupted graft')),
3019 3018 (b'', b'abort', False, _(b'abort interrupted graft')),
3020 3019 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
3021 3020 (b'', b'log', None, _(b'append graft info to log message')),
3022 3021 (
3023 3022 b'',
3024 3023 b'no-commit',
3025 3024 None,
3026 3025 _(b"don't commit, just apply the changes in working directory"),
3027 3026 ),
3028 3027 (b'f', b'force', False, _(b'force graft')),
3029 3028 (
3030 3029 b'D',
3031 3030 b'currentdate',
3032 3031 False,
3033 3032 _(b'record the current date as commit date'),
3034 3033 ),
3035 3034 (
3036 3035 b'U',
3037 3036 b'currentuser',
3038 3037 False,
3039 3038 _(b'record the current user as committer'),
3040 3039 ),
3041 3040 ]
3042 3041 + commitopts2
3043 3042 + mergetoolopts
3044 3043 + dryrunopts,
3045 3044 _(b'[OPTION]... [-r REV]... REV...'),
3046 3045 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
3047 3046 )
3048 3047 def graft(ui, repo, *revs, **opts):
3049 3048 """copy changes from other branches onto the current branch
3050 3049
3051 3050 This command uses Mercurial's merge logic to copy individual
3052 3051 changes from other branches without merging branches in the
3053 3052 history graph. This is sometimes known as 'backporting' or
3054 3053 'cherry-picking'. By default, graft will copy user, date, and
3055 3054 description from the source changesets.
3056 3055
3057 3056 Changesets that are ancestors of the current revision, that have
3058 3057 already been grafted, or that are merges will be skipped.
3059 3058
3060 3059 If --log is specified, log messages will have a comment appended
3061 3060 of the form::
3062 3061
3063 3062 (grafted from CHANGESETHASH)
3064 3063
3065 3064 If --force is specified, revisions will be grafted even if they
3066 3065 are already ancestors of, or have been grafted to, the destination.
3067 3066 This is useful when the revisions have since been backed out.
3068 3067
3069 3068 If a graft merge results in conflicts, the graft process is
3070 3069 interrupted so that the current merge can be manually resolved.
3071 3070 Once all conflicts are addressed, the graft process can be
3072 3071 continued with the -c/--continue option.
3073 3072
3074 3073 The -c/--continue option reapplies all the earlier options.
3075 3074
3076 3075 .. container:: verbose
3077 3076
3078 3077 The --base option exposes more of how graft internally uses merge with a
3079 3078 custom base revision. --base can be used to specify another ancestor than
3080 3079 the first and only parent.
3081 3080
3082 3081 The command::
3083 3082
3084 3083 hg graft -r 345 --base 234
3085 3084
3086 3085 is thus pretty much the same as::
3087 3086
3088 3087 hg diff --from 234 --to 345 | hg import
3089 3088
3090 3089 but using merge to resolve conflicts and track moved files.
3091 3090
3092 3091 The result of a merge can thus be backported as a single commit by
3093 3092 specifying one of the merge parents as base, and thus effectively
3094 3093 grafting the changes from the other side.
3095 3094
3096 3095 It is also possible to collapse multiple changesets and clean up history
3097 3096 by specifying another ancestor as base, much like rebase --collapse
3098 3097 --keep.
3099 3098
3100 3099 The commit message can be tweaked after the fact using commit --amend .
3101 3100
3102 3101 For using non-ancestors as the base to backout changes, see the backout
3103 3102 command and the hidden --parent option.
3104 3103
3105 3104 .. container:: verbose
3106 3105
3107 3106 Examples:
3108 3107
3109 3108 - copy a single change to the stable branch and edit its description::
3110 3109
3111 3110 hg update stable
3112 3111 hg graft --edit 9393
3113 3112
3114 3113 - graft a range of changesets with one exception, updating dates::
3115 3114
3116 3115 hg graft -D "2085::2093 and not 2091"
3117 3116
3118 3117 - continue a graft after resolving conflicts::
3119 3118
3120 3119 hg graft -c
3121 3120
3122 3121 - show the source of a grafted changeset::
3123 3122
3124 3123 hg log --debug -r .
3125 3124
3126 3125 - show revisions sorted by date::
3127 3126
3128 3127 hg log -r "sort(all(), date)"
3129 3128
3130 3129 - backport the result of a merge as a single commit::
3131 3130
3132 3131 hg graft -r 123 --base 123^
3133 3132
3134 3133 - land a feature branch as one changeset::
3135 3134
3136 3135 hg up -cr default
3137 3136 hg graft -r featureX --base "ancestor('featureX', 'default')"
3138 3137
3139 3138 See :hg:`help revisions` for more about specifying revisions.
3140 3139
3141 3140 Returns 0 on successful completion, 1 if there are unresolved files.
3142 3141 """
3143 3142 with repo.wlock():
3144 3143 return _dograft(ui, repo, *revs, **opts)
3145 3144
3146 3145
3147 3146 def _dograft(ui, repo, *revs, **opts):
3148 3147 if revs and opts.get('rev'):
3149 3148 ui.warn(
3150 3149 _(
3151 3150 b'warning: inconsistent use of --rev might give unexpected '
3152 3151 b'revision ordering!\n'
3153 3152 )
3154 3153 )
3155 3154
3156 3155 revs = list(revs)
3157 3156 revs.extend(opts.get('rev'))
3158 3157 # a dict of data to be stored in state file
3159 3158 statedata = {}
3160 3159 # list of new nodes created by ongoing graft
3161 3160 statedata[b'newnodes'] = []
3162 3161
3163 3162 cmdutil.resolve_commit_options(ui, opts)
3164 3163
3165 3164 editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
3166 3165
3167 3166 cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
3168 3167
3169 3168 cont = False
3170 3169 if opts.get('no_commit'):
3171 3170 cmdutil.check_incompatible_arguments(
3172 3171 opts,
3173 3172 'no_commit',
3174 3173 ['edit', 'currentuser', 'currentdate', 'log'],
3175 3174 )
3176 3175
3177 3176 graftstate = statemod.cmdstate(repo, b'graftstate')
3178 3177
3179 3178 if opts.get('stop'):
3180 3179 cmdutil.check_incompatible_arguments(
3181 3180 opts,
3182 3181 'stop',
3183 3182 [
3184 3183 'edit',
3185 3184 'log',
3186 3185 'user',
3187 3186 'date',
3188 3187 'currentdate',
3189 3188 'currentuser',
3190 3189 'rev',
3191 3190 ],
3192 3191 )
3193 3192 return _stopgraft(ui, repo, graftstate)
3194 3193 elif opts.get('abort'):
3195 3194 cmdutil.check_incompatible_arguments(
3196 3195 opts,
3197 3196 'abort',
3198 3197 [
3199 3198 'edit',
3200 3199 'log',
3201 3200 'user',
3202 3201 'date',
3203 3202 'currentdate',
3204 3203 'currentuser',
3205 3204 'rev',
3206 3205 ],
3207 3206 )
3208 3207 return cmdutil.abortgraft(ui, repo, graftstate)
3209 3208 elif opts.get('continue'):
3210 3209 cont = True
3211 3210 if revs:
3212 3211 raise error.InputError(_(b"can't specify --continue and revisions"))
3213 3212 # read in unfinished revisions
3214 3213 if graftstate.exists():
3215 3214 statedata = cmdutil.readgraftstate(repo, graftstate)
3216 3215 if statedata.get(b'date'):
3217 3216 opts['date'] = statedata[b'date']
3218 3217 if statedata.get(b'user'):
3219 3218 opts['user'] = statedata[b'user']
3220 3219 if statedata.get(b'log'):
3221 3220 opts['log'] = True
3222 3221 if statedata.get(b'no_commit'):
3223 3222 opts['no_commit'] = statedata.get(b'no_commit')
3224 3223 if statedata.get(b'base'):
3225 3224 opts['base'] = statedata.get(b'base')
3226 3225 nodes = statedata[b'nodes']
3227 3226 revs = [repo[node].rev() for node in nodes]
3228 3227 else:
3229 3228 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3230 3229 else:
3231 3230 if not revs:
3232 3231 raise error.InputError(_(b'no revisions specified'))
3233 3232 cmdutil.checkunfinished(repo)
3234 3233 cmdutil.bailifchanged(repo)
3235 3234 revs = logcmdutil.revrange(repo, revs)
3236 3235
3237 3236 skipped = set()
3238 3237 basectx = None
3239 3238 if opts.get('base'):
3240 3239 basectx = logcmdutil.revsingle(repo, opts['base'], None)
3241 3240 if basectx is None:
3242 3241 # check for merges
3243 3242 for rev in repo.revs(b'%ld and merge()', revs):
3244 3243 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3245 3244 skipped.add(rev)
3246 3245 revs = [r for r in revs if r not in skipped]
3247 3246 if not revs:
3248 3247 return -1
3249 3248 if basectx is not None and len(revs) != 1:
3250 3249 raise error.InputError(_(b'only one revision allowed with --base '))
3251 3250
3252 3251 # Don't check in the --continue case, in effect retaining --force across
3253 3252 # --continues. That's because without --force, any revisions we decided to
3254 3253 # skip would have been filtered out here, so they wouldn't have made their
3255 3254 # way to the graftstate. With --force, any revisions we would have otherwise
3256 3255 # skipped would not have been filtered out, and if they hadn't been applied
3257 3256 # already, they'd have been in the graftstate.
3258 3257 if not (cont or opts.get('force')) and basectx is None:
3259 3258 # check for ancestors of dest branch
3260 3259 ancestors = repo.revs(b'%ld & (::.)', revs)
3261 3260 for rev in ancestors:
3262 3261 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3263 3262
3264 3263 revs = [r for r in revs if r not in ancestors]
3265 3264
3266 3265 if not revs:
3267 3266 return -1
3268 3267
3269 3268 # analyze revs for earlier grafts
3270 3269 ids = {}
3271 3270 for ctx in repo.set(b"%ld", revs):
3272 3271 ids[ctx.hex()] = ctx.rev()
3273 3272 n = ctx.extra().get(b'source')
3274 3273 if n:
3275 3274 ids[n] = ctx.rev()
3276 3275
3277 3276 # check ancestors for earlier grafts
3278 3277 ui.debug(b'scanning for duplicate grafts\n')
3279 3278
3280 3279 # The only changesets we can be sure doesn't contain grafts of any
3281 3280 # revs, are the ones that are common ancestors of *all* revs:
3282 3281 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3283 3282 ctx = repo[rev]
3284 3283 n = ctx.extra().get(b'source')
3285 3284 if n in ids:
3286 3285 try:
3287 3286 r = repo[n].rev()
3288 3287 except error.RepoLookupError:
3289 3288 r = None
3290 3289 if r in revs:
3291 3290 ui.warn(
3292 3291 _(
3293 3292 b'skipping revision %d:%s '
3294 3293 b'(already grafted to %d:%s)\n'
3295 3294 )
3296 3295 % (r, repo[r], rev, ctx)
3297 3296 )
3298 3297 revs.remove(r)
3299 3298 elif ids[n] in revs:
3300 3299 if r is None:
3301 3300 ui.warn(
3302 3301 _(
3303 3302 b'skipping already grafted revision %d:%s '
3304 3303 b'(%d:%s also has unknown origin %s)\n'
3305 3304 )
3306 3305 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3307 3306 )
3308 3307 else:
3309 3308 ui.warn(
3310 3309 _(
3311 3310 b'skipping already grafted revision %d:%s '
3312 3311 b'(%d:%s also has origin %d:%s)\n'
3313 3312 )
3314 3313 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3315 3314 )
3316 3315 revs.remove(ids[n])
3317 3316 elif ctx.hex() in ids:
3318 3317 r = ids[ctx.hex()]
3319 3318 if r in revs:
3320 3319 ui.warn(
3321 3320 _(
3322 3321 b'skipping already grafted revision %d:%s '
3323 3322 b'(was grafted from %d:%s)\n'
3324 3323 )
3325 3324 % (r, repo[r], rev, ctx)
3326 3325 )
3327 3326 revs.remove(r)
3328 3327 if not revs:
3329 3328 return -1
3330 3329
3331 3330 if opts.get('no_commit'):
3332 3331 statedata[b'no_commit'] = True
3333 3332 if opts.get('base'):
3334 3333 statedata[b'base'] = opts['base']
3335 3334 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3336 3335 desc = b'%d:%s "%s"' % (
3337 3336 ctx.rev(),
3338 3337 ctx,
3339 3338 ctx.description().split(b'\n', 1)[0],
3340 3339 )
3341 3340 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3342 3341 if names:
3343 3342 desc += b' (%s)' % b' '.join(names)
3344 3343 ui.status(_(b'grafting %s\n') % desc)
3345 3344 if opts.get('dry_run'):
3346 3345 continue
3347 3346
3348 3347 source = ctx.extra().get(b'source')
3349 3348 extra = {}
3350 3349 if source:
3351 3350 extra[b'source'] = source
3352 3351 extra[b'intermediate-source'] = ctx.hex()
3353 3352 else:
3354 3353 extra[b'source'] = ctx.hex()
3355 3354 user = ctx.user()
3356 3355 if opts.get('user'):
3357 3356 user = opts['user']
3358 3357 statedata[b'user'] = user
3359 3358 date = ctx.date()
3360 3359 if opts.get('date'):
3361 3360 date = opts['date']
3362 3361 statedata[b'date'] = date
3363 3362 message = ctx.description()
3364 3363 if opts.get('log'):
3365 3364 message += b'\n(grafted from %s)' % ctx.hex()
3366 3365 statedata[b'log'] = True
3367 3366
3368 3367 # we don't merge the first commit when continuing
3369 3368 if not cont:
3370 3369 # perform the graft merge with p1(rev) as 'ancestor'
3371 3370 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
3372 3371 base = ctx.p1() if basectx is None else basectx
3373 3372 with ui.configoverride(overrides, b'graft'):
3374 3373 stats = mergemod.graft(
3375 3374 repo, ctx, base, [b'local', b'graft', b'parent of graft']
3376 3375 )
3377 3376 # report any conflicts
3378 3377 if stats.unresolvedcount > 0:
3379 3378 # write out state for --continue
3380 3379 nodes = [repo[rev].hex() for rev in revs[pos:]]
3381 3380 statedata[b'nodes'] = nodes
3382 3381 stateversion = 1
3383 3382 graftstate.save(stateversion, statedata)
3384 3383 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3385 3384 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3386 3385 return 1
3387 3386 else:
3388 3387 cont = False
3389 3388
3390 3389 # commit if --no-commit is false
3391 3390 if not opts.get('no_commit'):
3392 3391 node = repo.commit(
3393 3392 text=message, user=user, date=date, extra=extra, editor=editor
3394 3393 )
3395 3394 if node is None:
3396 3395 ui.warn(
3397 3396 _(b'note: graft of %d:%s created no changes to commit\n')
3398 3397 % (ctx.rev(), ctx)
3399 3398 )
3400 3399 # checking that newnodes exist because old state files won't have it
3401 3400 elif statedata.get(b'newnodes') is not None:
3402 3401 nn = statedata[b'newnodes']
3403 3402 assert isinstance(nn, list) # list of bytes
3404 3403 nn.append(node)
3405 3404
3406 3405 # remove state when we complete successfully
3407 3406 if not opts.get('dry_run'):
3408 3407 graftstate.delete()
3409 3408
3410 3409 return 0
3411 3410
3412 3411
3413 3412 def _stopgraft(ui, repo, graftstate):
3414 3413 """stop the interrupted graft"""
3415 3414 if not graftstate.exists():
3416 3415 raise error.StateError(_(b"no interrupted graft found"))
3417 3416 pctx = repo[b'.']
3418 3417 mergemod.clean_update(pctx)
3419 3418 graftstate.delete()
3420 3419 ui.status(_(b"stopped the interrupted graft\n"))
3421 3420 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3422 3421 return 0
3423 3422
3424 3423
3425 3424 statemod.addunfinished(
3426 3425 b'graft',
3427 3426 fname=b'graftstate',
3428 3427 clearable=True,
3429 3428 stopflag=True,
3430 3429 continueflag=True,
3431 3430 abortfunc=cmdutil.hgabortgraft,
3432 3431 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3433 3432 )
3434 3433
3435 3434
3436 3435 @command(
3437 3436 b'grep',
3438 3437 [
3439 3438 (b'0', b'print0', None, _(b'end fields with NUL')),
3440 3439 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3441 3440 (
3442 3441 b'',
3443 3442 b'diff',
3444 3443 None,
3445 3444 _(
3446 3445 b'search revision differences for when the pattern was added '
3447 3446 b'or removed'
3448 3447 ),
3449 3448 ),
3450 3449 (b'a', b'text', None, _(b'treat all files as text')),
3451 3450 (
3452 3451 b'f',
3453 3452 b'follow',
3454 3453 None,
3455 3454 _(
3456 3455 b'follow changeset history,'
3457 3456 b' or file history across copies and renames'
3458 3457 ),
3459 3458 ),
3460 3459 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3461 3460 (
3462 3461 b'l',
3463 3462 b'files-with-matches',
3464 3463 None,
3465 3464 _(b'print only filenames and revisions that match'),
3466 3465 ),
3467 3466 (b'n', b'line-number', None, _(b'print matching line numbers')),
3468 3467 (
3469 3468 b'r',
3470 3469 b'rev',
3471 3470 [],
3472 3471 _(b'search files changed within revision range'),
3473 3472 _(b'REV'),
3474 3473 ),
3475 3474 (
3476 3475 b'',
3477 3476 b'all-files',
3478 3477 None,
3479 3478 _(
3480 3479 b'include all files in the changeset while grepping (DEPRECATED)'
3481 3480 ),
3482 3481 ),
3483 3482 (b'u', b'user', None, _(b'list the author (long with -v)')),
3484 3483 (b'd', b'date', None, _(b'list the date (short with -q)')),
3485 3484 ]
3486 3485 + formatteropts
3487 3486 + walkopts,
3488 3487 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3489 3488 helpcategory=command.CATEGORY_FILE_CONTENTS,
3490 3489 inferrepo=True,
3491 3490 intents={INTENT_READONLY},
3492 3491 )
3493 3492 def grep(ui, repo, pattern, *pats, **opts):
3494 3493 """search for a pattern in specified files
3495 3494
3496 3495 Search the working directory or revision history for a regular
3497 3496 expression in the specified files for the entire repository.
3498 3497
3499 3498 By default, grep searches the repository files in the working
3500 3499 directory and prints the files where it finds a match. To specify
3501 3500 historical revisions instead of the working directory, use the
3502 3501 --rev flag.
3503 3502
3504 3503 To search instead historical revision differences that contains a
3505 3504 change in match status ("-" for a match that becomes a non-match,
3506 3505 or "+" for a non-match that becomes a match), use the --diff flag.
3507 3506
3508 3507 PATTERN can be any Python (roughly Perl-compatible) regular
3509 3508 expression.
3510 3509
3511 3510 If no FILEs are specified and the --rev flag isn't supplied, all
3512 3511 files in the working directory are searched. When using the --rev
3513 3512 flag and specifying FILEs, use the --follow argument to also
3514 3513 follow the specified FILEs across renames and copies.
3515 3514
3516 3515 .. container:: verbose
3517 3516
3518 3517 Template:
3519 3518
3520 3519 The following keywords are supported in addition to the common template
3521 3520 keywords and functions. See also :hg:`help templates`.
3522 3521
3523 3522 :change: String. Character denoting insertion ``+`` or removal ``-``.
3524 3523 Available if ``--diff`` is specified.
3525 3524 :lineno: Integer. Line number of the match.
3526 3525 :path: String. Repository-absolute path of the file.
3527 3526 :texts: List of text chunks.
3528 3527
3529 3528 And each entry of ``{texts}`` provides the following sub-keywords.
3530 3529
3531 3530 :matched: Boolean. True if the chunk matches the specified pattern.
3532 3531 :text: String. Chunk content.
3533 3532
3534 3533 See :hg:`help templates.operators` for the list expansion syntax.
3535 3534
3536 3535 Returns 0 if a match is found, 1 otherwise.
3537 3536
3538 3537 """
3539 3538 cmdutil.check_incompatible_arguments(opts, 'all_files', ['all', 'diff'])
3540 3539
3541 3540 diff = opts.get('all') or opts.get('diff')
3542 3541 follow = opts.get('follow')
3543 3542 if opts.get('all_files') is None and not diff:
3544 3543 opts['all_files'] = True
3545 3544 plaingrep = (
3546 3545 opts.get('all_files') and not opts.get('rev') and not opts.get('follow')
3547 3546 )
3548 3547 all_files = opts.get('all_files')
3549 3548 if plaingrep:
3550 3549 opts['rev'] = [b'wdir()']
3551 3550
3552 3551 reflags = re.M
3553 3552 if opts.get('ignore_case'):
3554 3553 reflags |= re.I
3555 3554 try:
3556 3555 regexp = util.re.compile(pattern, reflags)
3557 3556 except re.error as inst:
3558 3557 ui.warn(
3559 3558 _(b"grep: invalid match pattern: %s\n")
3560 3559 % stringutil.forcebytestr(inst)
3561 3560 )
3562 3561 return 1
3563 3562 sep, eol = b':', b'\n'
3564 3563 if opts.get('print0'):
3565 3564 sep = eol = b'\0'
3566 3565
3567 3566 searcher = grepmod.grepsearcher(
3568 3567 ui, repo, regexp, all_files=all_files, diff=diff, follow=follow
3569 3568 )
3570 3569
3571 3570 getfile = searcher._getfile
3572 3571
3573 3572 uipathfn = scmutil.getuipathfn(repo)
3574 3573
3575 3574 def display(fm, fn, ctx, pstates, states):
3576 3575 rev = scmutil.intrev(ctx)
3577 3576 if fm.isplain():
3578 3577 formatuser = ui.shortuser
3579 3578 else:
3580 3579 formatuser = pycompat.bytestr
3581 3580 if ui.quiet:
3582 3581 datefmt = b'%Y-%m-%d'
3583 3582 else:
3584 3583 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3585 3584 found = False
3586 3585
3587 3586 @util.cachefunc
3588 3587 def binary():
3589 3588 flog = getfile(fn)
3590 3589 try:
3591 3590 return stringutil.binary(flog.read(ctx.filenode(fn)))
3592 3591 except error.WdirUnsupported:
3593 3592 return ctx[fn].isbinary()
3594 3593
3595 3594 fieldnamemap = {b'linenumber': b'lineno'}
3596 3595 if diff:
3597 3596 iter = grepmod.difflinestates(pstates, states)
3598 3597 else:
3599 3598 iter = [(b'', l) for l in states]
3600 3599 for change, l in iter:
3601 3600 fm.startitem()
3602 3601 fm.context(ctx=ctx)
3603 3602 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3604 3603 fm.plain(uipathfn(fn), label=b'grep.filename')
3605 3604
3606 3605 cols = [
3607 3606 (b'rev', b'%d', rev, not plaingrep, b''),
3608 3607 (
3609 3608 b'linenumber',
3610 3609 b'%d',
3611 3610 l.linenum,
3612 3611 opts.get('line_number'),
3613 3612 b'',
3614 3613 ),
3615 3614 ]
3616 3615 if diff:
3617 3616 cols.append(
3618 3617 (
3619 3618 b'change',
3620 3619 b'%s',
3621 3620 change,
3622 3621 True,
3623 3622 b'grep.inserted '
3624 3623 if change == b'+'
3625 3624 else b'grep.deleted ',
3626 3625 )
3627 3626 )
3628 3627 cols.extend(
3629 3628 [
3630 3629 (
3631 3630 b'user',
3632 3631 b'%s',
3633 3632 formatuser(ctx.user()),
3634 3633 opts.get('user'),
3635 3634 b'',
3636 3635 ),
3637 3636 (
3638 3637 b'date',
3639 3638 b'%s',
3640 3639 fm.formatdate(ctx.date(), datefmt),
3641 3640 opts.get('date'),
3642 3641 b'',
3643 3642 ),
3644 3643 ]
3645 3644 )
3646 3645 for name, fmt, data, cond, extra_label in cols:
3647 3646 if cond:
3648 3647 fm.plain(sep, label=b'grep.sep')
3649 3648 field = fieldnamemap.get(name, name)
3650 3649 label = extra_label + (b'grep.%s' % name)
3651 3650 fm.condwrite(cond, field, fmt, data, label=label)
3652 3651 if not opts.get('files_with_matches'):
3653 3652 fm.plain(sep, label=b'grep.sep')
3654 3653 if not opts.get('text') and binary():
3655 3654 fm.plain(_(b" Binary file matches"))
3656 3655 else:
3657 3656 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3658 3657 fm.plain(eol)
3659 3658 found = True
3660 3659 if opts.get('files_with_matches'):
3661 3660 break
3662 3661 return found
3663 3662
3664 3663 def displaymatches(fm, l):
3665 3664 p = 0
3666 3665 for s, e in l.findpos(regexp):
3667 3666 if p < s:
3668 3667 fm.startitem()
3669 3668 fm.write(b'text', b'%s', l.line[p:s])
3670 3669 fm.data(matched=False)
3671 3670 fm.startitem()
3672 3671 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3673 3672 fm.data(matched=True)
3674 3673 p = e
3675 3674 if p < len(l.line):
3676 3675 fm.startitem()
3677 3676 fm.write(b'text', b'%s', l.line[p:])
3678 3677 fm.data(matched=False)
3679 3678 fm.end()
3680 3679
3681 3680 found = False
3682 3681
3683 3682 wopts = logcmdutil.walkopts(
3684 3683 pats=pats,
3685 3684 opts=opts,
3686 3685 revspec=opts['rev'],
3687 3686 include_pats=opts['include'],
3688 3687 exclude_pats=opts['exclude'],
3689 3688 follow=follow,
3690 3689 force_changelog_traversal=all_files,
3691 3690 filter_revisions_by_pats=not all_files,
3692 3691 )
3693 3692 revs, makefilematcher = logcmdutil.makewalker(repo, wopts)
3694 3693
3695 3694 ui.pager(b'grep')
3696 3695 fm = ui.formatter(b'grep', pycompat.byteskwargs(opts))
3697 3696 for fn, ctx, pstates, states in searcher.searchfiles(revs, makefilematcher):
3698 3697 r = display(fm, fn, ctx, pstates, states)
3699 3698 found = found or r
3700 3699 if r and not diff and not all_files:
3701 3700 searcher.skipfile(fn, ctx.rev())
3702 3701 fm.end()
3703 3702
3704 3703 return not found
3705 3704
3706 3705
3707 3706 @command(
3708 3707 b'heads',
3709 3708 [
3710 3709 (
3711 3710 b'r',
3712 3711 b'rev',
3713 3712 b'',
3714 3713 _(b'show only heads which are descendants of STARTREV'),
3715 3714 _(b'STARTREV'),
3716 3715 ),
3717 3716 (b't', b'topo', False, _(b'show topological heads only')),
3718 3717 (
3719 3718 b'a',
3720 3719 b'active',
3721 3720 False,
3722 3721 _(b'show active branchheads only (DEPRECATED)'),
3723 3722 ),
3724 3723 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3725 3724 ]
3726 3725 + templateopts,
3727 3726 _(b'[-ct] [-r STARTREV] [REV]...'),
3728 3727 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3729 3728 intents={INTENT_READONLY},
3730 3729 )
3731 3730 def heads(ui, repo, *branchrevs, **opts):
3732 3731 """show branch heads
3733 3732
3734 3733 With no arguments, show all open branch heads in the repository.
3735 3734 Branch heads are changesets that have no descendants on the
3736 3735 same branch. They are where development generally takes place and
3737 3736 are the usual targets for update and merge operations.
3738 3737
3739 3738 If one or more REVs are given, only open branch heads on the
3740 3739 branches associated with the specified changesets are shown. This
3741 3740 means that you can use :hg:`heads .` to see the heads on the
3742 3741 currently checked-out branch.
3743 3742
3744 3743 If -c/--closed is specified, also show branch heads marked closed
3745 3744 (see :hg:`commit --close-branch`).
3746 3745
3747 3746 If STARTREV is specified, only those heads that are descendants of
3748 3747 STARTREV will be displayed.
3749 3748
3750 3749 If -t/--topo is specified, named branch mechanics will be ignored and only
3751 3750 topological heads (changesets with no children) will be shown.
3752 3751
3753 3752 Returns 0 if matching heads are found, 1 if not.
3754 3753 """
3755 3754
3756 3755 opts = pycompat.byteskwargs(opts)
3757 3756 start = None
3758 3757 rev = opts.get(b'rev')
3759 3758 if rev:
3760 3759 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3761 3760 start = logcmdutil.revsingle(repo, rev, None).node()
3762 3761
3763 3762 if opts.get(b'topo'):
3764 3763 heads = [repo[h] for h in repo.heads(start)]
3765 3764 else:
3766 3765 heads = []
3767 3766 for branch in repo.branchmap():
3768 3767 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3769 3768 heads = [repo[h] for h in heads]
3770 3769
3771 3770 if branchrevs:
3772 3771 branches = {
3773 3772 repo[r].branch() for r in logcmdutil.revrange(repo, branchrevs)
3774 3773 }
3775 3774 heads = [h for h in heads if h.branch() in branches]
3776 3775
3777 3776 if opts.get(b'active') and branchrevs:
3778 3777 dagheads = repo.heads(start)
3779 3778 heads = [h for h in heads if h.node() in dagheads]
3780 3779
3781 3780 if branchrevs:
3782 3781 haveheads = {h.branch() for h in heads}
3783 3782 if branches - haveheads:
3784 3783 headless = b', '.join(b for b in branches - haveheads)
3785 3784 msg = _(b'no open branch heads found on branches %s')
3786 3785 if opts.get(b'rev'):
3787 3786 msg += _(b' (started at %s)') % opts[b'rev']
3788 3787 ui.warn((msg + b'\n') % headless)
3789 3788
3790 3789 if not heads:
3791 3790 return 1
3792 3791
3793 3792 ui.pager(b'heads')
3794 3793 heads = sorted(heads, key=lambda x: -(x.rev()))
3795 3794 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3796 3795 for ctx in heads:
3797 3796 displayer.show(ctx)
3798 3797 displayer.close()
3799 3798
3800 3799
3801 3800 @command(
3802 3801 b'help',
3803 3802 [
3804 3803 (b'e', b'extension', None, _(b'show only help for extensions')),
3805 3804 (b'c', b'command', None, _(b'show only help for commands')),
3806 3805 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3807 3806 (
3808 3807 b's',
3809 3808 b'system',
3810 3809 [],
3811 3810 _(b'show help for specific platform(s)'),
3812 3811 _(b'PLATFORM'),
3813 3812 ),
3814 3813 ],
3815 3814 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3816 3815 helpcategory=command.CATEGORY_HELP,
3817 3816 norepo=True,
3818 3817 intents={INTENT_READONLY},
3819 3818 )
3820 3819 def help_(ui, name=None, **opts):
3821 3820 """show help for a given topic or a help overview
3822 3821
3823 3822 With no arguments, print a list of commands with short help messages.
3824 3823
3825 3824 Given a topic, extension, or command name, print help for that
3826 3825 topic.
3827 3826
3828 3827 Returns 0 if successful.
3829 3828 """
3830 3829
3831 3830 keep = opts.get('system') or []
3832 3831 if len(keep) == 0:
3833 3832 if pycompat.sysplatform.startswith(b'win'):
3834 3833 keep.append(b'windows')
3835 3834 elif pycompat.sysplatform == b'OpenVMS':
3836 3835 keep.append(b'vms')
3837 3836 elif pycompat.sysplatform == b'plan9':
3838 3837 keep.append(b'plan9')
3839 3838 else:
3840 3839 keep.append(b'unix')
3841 3840 keep.append(pycompat.sysplatform.lower())
3842 3841 if ui.verbose:
3843 3842 keep.append(b'verbose')
3844 3843
3845 3844 commands = sys.modules[__name__]
3846 3845 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3847 3846 ui.pager(b'help')
3848 3847 ui.write(formatted)
3849 3848
3850 3849
3851 3850 @command(
3852 3851 b'identify|id',
3853 3852 [
3854 3853 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3855 3854 (b'n', b'num', None, _(b'show local revision number')),
3856 3855 (b'i', b'id', None, _(b'show global revision id')),
3857 3856 (b'b', b'branch', None, _(b'show branch')),
3858 3857 (b't', b'tags', None, _(b'show tags')),
3859 3858 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3860 3859 ]
3861 3860 + remoteopts
3862 3861 + formatteropts,
3863 3862 _(b'[-nibtB] [-r REV] [SOURCE]'),
3864 3863 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3865 3864 optionalrepo=True,
3866 3865 intents={INTENT_READONLY},
3867 3866 )
3868 3867 def identify(
3869 3868 ui,
3870 3869 repo,
3871 3870 source=None,
3872 3871 rev=None,
3873 3872 num=None,
3874 3873 id=None,
3875 3874 branch=None,
3876 3875 tags=None,
3877 3876 bookmarks=None,
3878 3877 **opts
3879 3878 ):
3880 3879 """identify the working directory or specified revision
3881 3880
3882 3881 Print a summary identifying the repository state at REV using one or
3883 3882 two parent hash identifiers, followed by a "+" if the working
3884 3883 directory has uncommitted changes, the branch name (if not default),
3885 3884 a list of tags, and a list of bookmarks.
3886 3885
3887 3886 When REV is not given, print a summary of the current state of the
3888 3887 repository including the working directory. Specify -r. to get information
3889 3888 of the working directory parent without scanning uncommitted changes.
3890 3889
3891 3890 Specifying a path to a repository root or Mercurial bundle will
3892 3891 cause lookup to operate on that repository/bundle.
3893 3892
3894 3893 .. container:: verbose
3895 3894
3896 3895 Template:
3897 3896
3898 3897 The following keywords are supported in addition to the common template
3899 3898 keywords and functions. See also :hg:`help templates`.
3900 3899
3901 3900 :dirty: String. Character ``+`` denoting if the working directory has
3902 3901 uncommitted changes.
3903 3902 :id: String. One or two nodes, optionally followed by ``+``.
3904 3903 :parents: List of strings. Parent nodes of the changeset.
3905 3904
3906 3905 Examples:
3907 3906
3908 3907 - generate a build identifier for the working directory::
3909 3908
3910 3909 hg id --id > build-id.dat
3911 3910
3912 3911 - find the revision corresponding to a tag::
3913 3912
3914 3913 hg id -n -r 1.3
3915 3914
3916 3915 - check the most recent revision of a remote repository::
3917 3916
3918 3917 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3919 3918
3920 3919 See :hg:`log` for generating more information about specific revisions,
3921 3920 including full hash identifiers.
3922 3921
3923 3922 Returns 0 if successful.
3924 3923 """
3925 3924
3926 3925 opts = pycompat.byteskwargs(opts)
3927 3926 if not repo and not source:
3928 3927 raise error.InputError(
3929 3928 _(b"there is no Mercurial repository here (.hg not found)")
3930 3929 )
3931 3930
3932 3931 default = not (num or id or branch or tags or bookmarks)
3933 3932 output = []
3934 3933 revs = []
3935 3934
3936 3935 peer = None
3937 3936 try:
3938 3937 if source:
3939 3938 path = urlutil.get_unique_pull_path_obj(b'identify', ui, source)
3940 3939 # only pass ui when no repo
3941 3940 peer = hg.peer(repo or ui, opts, path)
3942 3941 repo = peer.local()
3943 3942 branches = (path.branch, [])
3944 3943 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3945 3944
3946 3945 fm = ui.formatter(b'identify', opts)
3947 3946 fm.startitem()
3948 3947
3949 3948 if not repo:
3950 3949 if num or branch or tags:
3951 3950 raise error.InputError(
3952 3951 _(b"can't query remote revision number, branch, or tags")
3953 3952 )
3954 3953 if not rev and revs:
3955 3954 rev = revs[0]
3956 3955 if not rev:
3957 3956 rev = b"tip"
3958 3957
3959 3958 remoterev = peer.lookup(rev)
3960 3959 hexrev = fm.hexfunc(remoterev)
3961 3960 if default or id:
3962 3961 output = [hexrev]
3963 3962 fm.data(id=hexrev)
3964 3963
3965 3964 @util.cachefunc
3966 3965 def getbms():
3967 3966 bms = []
3968 3967
3969 3968 if b'bookmarks' in peer.listkeys(b'namespaces'):
3970 3969 hexremoterev = hex(remoterev)
3971 3970 bms = [
3972 3971 bm
3973 3972 for bm, bmr in peer.listkeys(b'bookmarks').items()
3974 3973 if bmr == hexremoterev
3975 3974 ]
3976 3975
3977 3976 return sorted(bms)
3978 3977
3979 3978 if fm.isplain():
3980 3979 if bookmarks:
3981 3980 output.extend(getbms())
3982 3981 elif default and not ui.quiet:
3983 3982 # multiple bookmarks for a single parent separated by '/'
3984 3983 bm = b'/'.join(getbms())
3985 3984 if bm:
3986 3985 output.append(bm)
3987 3986 else:
3988 3987 fm.data(node=hex(remoterev))
3989 3988 if bookmarks or b'bookmarks' in fm.datahint():
3990 3989 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3991 3990 else:
3992 3991 if rev:
3993 3992 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3994 3993 ctx = logcmdutil.revsingle(repo, rev, None)
3995 3994
3996 3995 if ctx.rev() is None:
3997 3996 ctx = repo[None]
3998 3997 parents = ctx.parents()
3999 3998 taglist = []
4000 3999 for p in parents:
4001 4000 taglist.extend(p.tags())
4002 4001
4003 4002 dirty = b""
4004 4003 if ctx.dirty(missing=True, merge=False, branch=False):
4005 4004 dirty = b'+'
4006 4005 fm.data(dirty=dirty)
4007 4006
4008 4007 hexoutput = [fm.hexfunc(p.node()) for p in parents]
4009 4008 if default or id:
4010 4009 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
4011 4010 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
4012 4011
4013 4012 if num:
4014 4013 numoutput = [b"%d" % p.rev() for p in parents]
4015 4014 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
4016 4015
4017 4016 fm.data(
4018 4017 parents=fm.formatlist(
4019 4018 [fm.hexfunc(p.node()) for p in parents], name=b'node'
4020 4019 )
4021 4020 )
4022 4021 else:
4023 4022 hexoutput = fm.hexfunc(ctx.node())
4024 4023 if default or id:
4025 4024 output = [hexoutput]
4026 4025 fm.data(id=hexoutput)
4027 4026
4028 4027 if num:
4029 4028 output.append(pycompat.bytestr(ctx.rev()))
4030 4029 taglist = ctx.tags()
4031 4030
4032 4031 if default and not ui.quiet:
4033 4032 b = ctx.branch()
4034 4033 if b != b'default':
4035 4034 output.append(b"(%s)" % b)
4036 4035
4037 4036 # multiple tags for a single parent separated by '/'
4038 4037 t = b'/'.join(taglist)
4039 4038 if t:
4040 4039 output.append(t)
4041 4040
4042 4041 # multiple bookmarks for a single parent separated by '/'
4043 4042 bm = b'/'.join(ctx.bookmarks())
4044 4043 if bm:
4045 4044 output.append(bm)
4046 4045 else:
4047 4046 if branch:
4048 4047 output.append(ctx.branch())
4049 4048
4050 4049 if tags:
4051 4050 output.extend(taglist)
4052 4051
4053 4052 if bookmarks:
4054 4053 output.extend(ctx.bookmarks())
4055 4054
4056 4055 fm.data(node=ctx.hex())
4057 4056 fm.data(branch=ctx.branch())
4058 4057 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4059 4058 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4060 4059 fm.context(ctx=ctx)
4061 4060
4062 4061 fm.plain(b"%s\n" % b' '.join(output))
4063 4062 fm.end()
4064 4063 finally:
4065 4064 if peer:
4066 4065 peer.close()
4067 4066
4068 4067
4069 4068 @command(
4070 4069 b'import|patch',
4071 4070 [
4072 4071 (
4073 4072 b'p',
4074 4073 b'strip',
4075 4074 1,
4076 4075 _(
4077 4076 b'directory strip option for patch. This has the same '
4078 4077 b'meaning as the corresponding patch option'
4079 4078 ),
4080 4079 _(b'NUM'),
4081 4080 ),
4082 4081 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4083 4082 (b'', b'secret', None, _(b'use the secret phase for committing')),
4084 4083 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4085 4084 (
4086 4085 b'f',
4087 4086 b'force',
4088 4087 None,
4089 4088 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4090 4089 ),
4091 4090 (
4092 4091 b'',
4093 4092 b'no-commit',
4094 4093 None,
4095 4094 _(b"don't commit, just update the working directory"),
4096 4095 ),
4097 4096 (
4098 4097 b'',
4099 4098 b'bypass',
4100 4099 None,
4101 4100 _(b"apply patch without touching the working directory"),
4102 4101 ),
4103 4102 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4104 4103 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4105 4104 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4106 4105 (
4107 4106 b'',
4108 4107 b'import-branch',
4109 4108 None,
4110 4109 _(b'use any branch information in patch (implied by --exact)'),
4111 4110 ),
4112 4111 ]
4113 4112 + commitopts
4114 4113 + commitopts2
4115 4114 + similarityopts,
4116 4115 _(b'[OPTION]... PATCH...'),
4117 4116 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4118 4117 )
4119 4118 def import_(ui, repo, patch1=None, *patches, **opts):
4120 4119 """import an ordered set of patches
4121 4120
4122 4121 Import a list of patches and commit them individually (unless
4123 4122 --no-commit is specified).
4124 4123
4125 4124 To read a patch from standard input (stdin), use "-" as the patch
4126 4125 name. If a URL is specified, the patch will be downloaded from
4127 4126 there.
4128 4127
4129 4128 Import first applies changes to the working directory (unless
4130 4129 --bypass is specified), import will abort if there are outstanding
4131 4130 changes.
4132 4131
4133 4132 Use --bypass to apply and commit patches directly to the
4134 4133 repository, without affecting the working directory. Without
4135 4134 --exact, patches will be applied on top of the working directory
4136 4135 parent revision.
4137 4136
4138 4137 You can import a patch straight from a mail message. Even patches
4139 4138 as attachments work (to use the body part, it must have type
4140 4139 text/plain or text/x-patch). From and Subject headers of email
4141 4140 message are used as default committer and commit message. All
4142 4141 text/plain body parts before first diff are added to the commit
4143 4142 message.
4144 4143
4145 4144 If the imported patch was generated by :hg:`export`, user and
4146 4145 description from patch override values from message headers and
4147 4146 body. Values given on command line with -m/--message and -u/--user
4148 4147 override these.
4149 4148
4150 4149 If --exact is specified, import will set the working directory to
4151 4150 the parent of each patch before applying it, and will abort if the
4152 4151 resulting changeset has a different ID than the one recorded in
4153 4152 the patch. This will guard against various ways that portable
4154 4153 patch formats and mail systems might fail to transfer Mercurial
4155 4154 data or metadata. See :hg:`bundle` for lossless transmission.
4156 4155
4157 4156 Use --partial to ensure a changeset will be created from the patch
4158 4157 even if some hunks fail to apply. Hunks that fail to apply will be
4159 4158 written to a <target-file>.rej file. Conflicts can then be resolved
4160 4159 by hand before :hg:`commit --amend` is run to update the created
4161 4160 changeset. This flag exists to let people import patches that
4162 4161 partially apply without losing the associated metadata (author,
4163 4162 date, description, ...).
4164 4163
4165 4164 .. note::
4166 4165
4167 4166 When no hunks apply cleanly, :hg:`import --partial` will create
4168 4167 an empty changeset, importing only the patch metadata.
4169 4168
4170 4169 With -s/--similarity, hg will attempt to discover renames and
4171 4170 copies in the patch in the same way as :hg:`addremove`.
4172 4171
4173 4172 It is possible to use external patch programs to perform the patch
4174 4173 by setting the ``ui.patch`` configuration option. For the default
4175 4174 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4176 4175 See :hg:`help config` for more information about configuration
4177 4176 files and how to use these options.
4178 4177
4179 4178 See :hg:`help dates` for a list of formats valid for -d/--date.
4180 4179
4181 4180 .. container:: verbose
4182 4181
4183 4182 Examples:
4184 4183
4185 4184 - import a traditional patch from a website and detect renames::
4186 4185
4187 4186 hg import -s 80 http://example.com/bugfix.patch
4188 4187
4189 4188 - import a changeset from an hgweb server::
4190 4189
4191 4190 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4192 4191
4193 4192 - import all the patches in an Unix-style mbox::
4194 4193
4195 4194 hg import incoming-patches.mbox
4196 4195
4197 4196 - import patches from stdin::
4198 4197
4199 4198 hg import -
4200 4199
4201 4200 - attempt to exactly restore an exported changeset (not always
4202 4201 possible)::
4203 4202
4204 4203 hg import --exact proposed-fix.patch
4205 4204
4206 4205 - use an external tool to apply a patch which is too fuzzy for
4207 4206 the default internal tool.
4208 4207
4209 4208 hg import --config ui.patch="patch --merge" fuzzy.patch
4210 4209
4211 4210 - change the default fuzzing from 2 to a less strict 7
4212 4211
4213 4212 hg import --config ui.fuzz=7 fuzz.patch
4214 4213
4215 4214 Returns 0 on success, 1 on partial success (see --partial).
4216 4215 """
4217 4216
4218 4217 cmdutil.check_incompatible_arguments(
4219 4218 opts, 'no_commit', ['bypass', 'secret']
4220 4219 )
4221 4220 cmdutil.check_incompatible_arguments(opts, 'exact', ['edit', 'prefix'])
4222 4221 opts = pycompat.byteskwargs(opts)
4223 4222 if not patch1:
4224 4223 raise error.InputError(_(b'need at least one patch to import'))
4225 4224
4226 4225 patches = (patch1,) + patches
4227 4226
4228 4227 date = opts.get(b'date')
4229 4228 if date:
4230 4229 opts[b'date'] = dateutil.parsedate(date)
4231 4230
4232 4231 exact = opts.get(b'exact')
4233 4232 update = not opts.get(b'bypass')
4234 4233 try:
4235 4234 sim = float(opts.get(b'similarity') or 0)
4236 4235 except ValueError:
4237 4236 raise error.InputError(_(b'similarity must be a number'))
4238 4237 if sim < 0 or sim > 100:
4239 4238 raise error.InputError(_(b'similarity must be between 0 and 100'))
4240 4239 if sim and not update:
4241 4240 raise error.InputError(_(b'cannot use --similarity with --bypass'))
4242 4241
4243 4242 base = opts[b"base"]
4244 4243 msgs = []
4245 4244 ret = 0
4246 4245
4247 4246 with repo.wlock():
4248 4247 if update:
4249 4248 cmdutil.checkunfinished(repo)
4250 4249 if exact or not opts.get(b'force'):
4251 4250 cmdutil.bailifchanged(repo)
4252 4251
4253 4252 if not opts.get(b'no_commit'):
4254 4253 lock = repo.lock
4255 4254 tr = lambda: repo.transaction(b'import')
4256 4255 else:
4257 4256 lock = util.nullcontextmanager
4258 4257 tr = util.nullcontextmanager
4259 4258 with lock(), tr():
4260 4259 parents = repo[None].parents()
4261 4260 for patchurl in patches:
4262 4261 if patchurl == b'-':
4263 4262 ui.status(_(b'applying patch from stdin\n'))
4264 4263 patchfile = ui.fin
4265 4264 patchurl = b'stdin' # for error message
4266 4265 else:
4267 4266 patchurl = os.path.join(base, patchurl)
4268 4267 ui.status(_(b'applying %s\n') % patchurl)
4269 4268 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4270 4269
4271 4270 haspatch = False
4272 4271 for hunk in patch.split(patchfile):
4273 4272 with patch.extract(ui, hunk) as patchdata:
4274 4273 msg, node, rej = cmdutil.tryimportone(
4275 4274 ui, repo, patchdata, parents, opts, msgs, hg.clean
4276 4275 )
4277 4276 if msg:
4278 4277 haspatch = True
4279 4278 ui.note(msg + b'\n')
4280 4279 if update or exact:
4281 4280 parents = repo[None].parents()
4282 4281 else:
4283 4282 parents = [repo[node]]
4284 4283 if rej:
4285 4284 ui.write_err(_(b"patch applied partially\n"))
4286 4285 ui.write_err(
4287 4286 _(
4288 4287 b"(fix the .rej files and run "
4289 4288 b"`hg commit --amend`)\n"
4290 4289 )
4291 4290 )
4292 4291 ret = 1
4293 4292 break
4294 4293
4295 4294 if not haspatch:
4296 4295 raise error.InputError(_(b'%s: no diffs found') % patchurl)
4297 4296
4298 4297 if msgs:
4299 4298 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4300 4299 return ret
4301 4300
4302 4301
4303 4302 @command(
4304 4303 b'incoming|in',
4305 4304 [
4306 4305 (
4307 4306 b'f',
4308 4307 b'force',
4309 4308 None,
4310 4309 _(b'run even if remote repository is unrelated'),
4311 4310 ),
4312 4311 (b'n', b'newest-first', None, _(b'show newest record first')),
4313 4312 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4314 4313 (
4315 4314 b'r',
4316 4315 b'rev',
4317 4316 [],
4318 4317 _(b'a remote changeset intended to be added'),
4319 4318 _(b'REV'),
4320 4319 ),
4321 4320 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4322 4321 (
4323 4322 b'b',
4324 4323 b'branch',
4325 4324 [],
4326 4325 _(b'a specific branch you would like to pull'),
4327 4326 _(b'BRANCH'),
4328 4327 ),
4329 4328 ]
4330 4329 + logopts
4331 4330 + remoteopts
4332 4331 + subrepoopts,
4333 4332 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4334 4333 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4335 4334 )
4336 4335 def incoming(ui, repo, source=b"default", **opts):
4337 4336 """show new changesets found in source
4338 4337
4339 4338 Show new changesets found in the specified path/URL or the default
4340 4339 pull location. These are the changesets that would have been pulled
4341 4340 by :hg:`pull` at the time you issued this command.
4342 4341
4343 4342 See pull for valid source format details.
4344 4343
4345 4344 .. container:: verbose
4346 4345
4347 4346 With -B/--bookmarks, the result of bookmark comparison between
4348 4347 local and remote repositories is displayed. With -v/--verbose,
4349 4348 status is also displayed for each bookmark like below::
4350 4349
4351 4350 BM1 01234567890a added
4352 4351 BM2 1234567890ab advanced
4353 4352 BM3 234567890abc diverged
4354 4353 BM4 34567890abcd changed
4355 4354
4356 4355 The action taken locally when pulling depends on the
4357 4356 status of each bookmark:
4358 4357
4359 4358 :``added``: pull will create it
4360 4359 :``advanced``: pull will update it
4361 4360 :``diverged``: pull will create a divergent bookmark
4362 4361 :``changed``: result depends on remote changesets
4363 4362
4364 4363 From the point of view of pulling behavior, bookmark
4365 4364 existing only in the remote repository are treated as ``added``,
4366 4365 even if it is in fact locally deleted.
4367 4366
4368 4367 .. container:: verbose
4369 4368
4370 4369 For remote repository, using --bundle avoids downloading the
4371 4370 changesets twice if the incoming is followed by a pull.
4372 4371
4373 4372 Examples:
4374 4373
4375 4374 - show incoming changes with patches and full description::
4376 4375
4377 4376 hg incoming -vp
4378 4377
4379 4378 - show incoming changes excluding merges, store a bundle::
4380 4379
4381 4380 hg in -vpM --bundle incoming.hg
4382 4381 hg pull incoming.hg
4383 4382
4384 4383 - briefly list changes inside a bundle::
4385 4384
4386 4385 hg in changes.hg -T "{desc|firstline}\\n"
4387 4386
4388 4387 Returns 0 if there are incoming changes, 1 otherwise.
4389 4388 """
4390 4389 opts = pycompat.byteskwargs(opts)
4391 4390 if opts.get(b'graph'):
4392 4391 logcmdutil.checkunsupportedgraphflags([], opts)
4393 4392
4394 4393 def display(other, chlist, displayer):
4395 4394 revdag = logcmdutil.graphrevs(other, chlist, opts)
4396 4395 logcmdutil.displaygraph(
4397 4396 ui, repo, revdag, displayer, graphmod.asciiedges
4398 4397 )
4399 4398
4400 4399 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4401 4400 return 0
4402 4401
4403 4402 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'bundle'])
4404 4403
4405 4404 if opts.get(b'bookmarks'):
4406 4405 srcs = urlutil.get_pull_paths(repo, ui, [source])
4407 4406 for path in srcs:
4408 4407 # XXX the "branches" options are not used. Should it be used?
4409 4408 other = hg.peer(repo, opts, path)
4410 4409 try:
4411 4410 if b'bookmarks' not in other.listkeys(b'namespaces'):
4412 4411 ui.warn(_(b"remote doesn't support bookmarks\n"))
4413 4412 return 0
4414 4413 ui.pager(b'incoming')
4415 4414 ui.status(
4416 4415 _(b'comparing with %s\n') % urlutil.hidepassword(path.loc)
4417 4416 )
4418 4417 return bookmarks.incoming(
4419 4418 ui, repo, other, mode=path.bookmarks_mode
4420 4419 )
4421 4420 finally:
4422 4421 other.close()
4423 4422
4424 4423 return hg.incoming(ui, repo, source, opts)
4425 4424
4426 4425
4427 4426 @command(
4428 4427 b'init',
4429 4428 remoteopts,
4430 4429 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4431 4430 helpcategory=command.CATEGORY_REPO_CREATION,
4432 4431 helpbasic=True,
4433 4432 norepo=True,
4434 4433 )
4435 4434 def init(ui, dest=b".", **opts):
4436 4435 """create a new repository in the given directory
4437 4436
4438 4437 Initialize a new repository in the given directory. If the given
4439 4438 directory does not exist, it will be created.
4440 4439
4441 4440 If no directory is given, the current directory is used.
4442 4441
4443 4442 It is possible to specify an ``ssh://`` URL as the destination.
4444 4443 See :hg:`help urls` for more information.
4445 4444
4446 4445 Returns 0 on success.
4447 4446 """
4448 4447 opts = pycompat.byteskwargs(opts)
4449 4448 path = urlutil.get_clone_path_obj(ui, dest)
4450 4449 peer = hg.peer(ui, opts, path, create=True)
4451 4450 peer.close()
4452 4451
4453 4452
4454 4453 @command(
4455 4454 b'locate',
4456 4455 [
4457 4456 (
4458 4457 b'r',
4459 4458 b'rev',
4460 4459 b'',
4461 4460 _(b'search the repository as it is in REV'),
4462 4461 _(b'REV'),
4463 4462 ),
4464 4463 (
4465 4464 b'0',
4466 4465 b'print0',
4467 4466 None,
4468 4467 _(b'end filenames with NUL, for use with xargs'),
4469 4468 ),
4470 4469 (
4471 4470 b'f',
4472 4471 b'fullpath',
4473 4472 None,
4474 4473 _(b'print complete paths from the filesystem root'),
4475 4474 ),
4476 4475 ]
4477 4476 + walkopts,
4478 4477 _(b'[OPTION]... [PATTERN]...'),
4479 4478 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4480 4479 )
4481 4480 def locate(ui, repo, *pats, **opts):
4482 4481 """locate files matching specific patterns (DEPRECATED)
4483 4482
4484 4483 Print files under Mercurial control in the working directory whose
4485 4484 names match the given patterns.
4486 4485
4487 4486 By default, this command searches all directories in the working
4488 4487 directory. To search just the current directory and its
4489 4488 subdirectories, use "--include .".
4490 4489
4491 4490 If no patterns are given to match, this command prints the names
4492 4491 of all files under Mercurial control in the working directory.
4493 4492
4494 4493 If you want to feed the output of this command into the "xargs"
4495 4494 command, use the -0 option to both this command and "xargs". This
4496 4495 will avoid the problem of "xargs" treating single filenames that
4497 4496 contain whitespace as multiple filenames.
4498 4497
4499 4498 See :hg:`help files` for a more versatile command.
4500 4499
4501 4500 Returns 0 if a match is found, 1 otherwise.
4502 4501 """
4503 4502 opts = pycompat.byteskwargs(opts)
4504 4503 if opts.get(b'print0'):
4505 4504 end = b'\0'
4506 4505 else:
4507 4506 end = b'\n'
4508 4507 ctx = logcmdutil.revsingle(repo, opts.get(b'rev'), None)
4509 4508
4510 4509 ret = 1
4511 4510 m = scmutil.match(
4512 4511 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4513 4512 )
4514 4513
4515 4514 ui.pager(b'locate')
4516 4515 if ctx.rev() is None:
4517 4516 # When run on the working copy, "locate" includes removed files, so
4518 4517 # we get the list of files from the dirstate.
4519 4518 filesgen = sorted(repo.dirstate.matches(m))
4520 4519 else:
4521 4520 filesgen = ctx.matches(m)
4522 4521 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4523 4522 for abs in filesgen:
4524 4523 if opts.get(b'fullpath'):
4525 4524 ui.write(repo.wjoin(abs), end)
4526 4525 else:
4527 4526 ui.write(uipathfn(abs), end)
4528 4527 ret = 0
4529 4528
4530 4529 return ret
4531 4530
4532 4531
4533 4532 @command(
4534 4533 b'log|history',
4535 4534 [
4536 4535 (
4537 4536 b'f',
4538 4537 b'follow',
4539 4538 None,
4540 4539 _(
4541 4540 b'follow changeset history, or file history across copies and renames'
4542 4541 ),
4543 4542 ),
4544 4543 (
4545 4544 b'',
4546 4545 b'follow-first',
4547 4546 None,
4548 4547 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4549 4548 ),
4550 4549 (
4551 4550 b'd',
4552 4551 b'date',
4553 4552 b'',
4554 4553 _(b'show revisions matching date spec'),
4555 4554 _(b'DATE'),
4556 4555 ),
4557 4556 (b'C', b'copies', None, _(b'show copied files')),
4558 4557 (
4559 4558 b'k',
4560 4559 b'keyword',
4561 4560 [],
4562 4561 _(b'do case-insensitive search for a given text'),
4563 4562 _(b'TEXT'),
4564 4563 ),
4565 4564 (
4566 4565 b'r',
4567 4566 b'rev',
4568 4567 [],
4569 4568 _(b'revisions to select or follow from'),
4570 4569 _(b'REV'),
4571 4570 ),
4572 4571 (
4573 4572 b'L',
4574 4573 b'line-range',
4575 4574 [],
4576 4575 _(b'follow line range of specified file (EXPERIMENTAL)'),
4577 4576 _(b'FILE,RANGE'),
4578 4577 ),
4579 4578 (
4580 4579 b'',
4581 4580 b'removed',
4582 4581 None,
4583 4582 _(b'include revisions where files were removed'),
4584 4583 ),
4585 4584 (
4586 4585 b'm',
4587 4586 b'only-merges',
4588 4587 None,
4589 4588 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4590 4589 ),
4591 4590 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4592 4591 (
4593 4592 b'',
4594 4593 b'only-branch',
4595 4594 [],
4596 4595 _(
4597 4596 b'show only changesets within the given named branch (DEPRECATED)'
4598 4597 ),
4599 4598 _(b'BRANCH'),
4600 4599 ),
4601 4600 (
4602 4601 b'b',
4603 4602 b'branch',
4604 4603 [],
4605 4604 _(b'show changesets within the given named branch'),
4606 4605 _(b'BRANCH'),
4607 4606 ),
4608 4607 (
4609 4608 b'B',
4610 4609 b'bookmark',
4611 4610 [],
4612 4611 _(b"show changesets within the given bookmark"),
4613 4612 _(b'BOOKMARK'),
4614 4613 ),
4615 4614 (
4616 4615 b'P',
4617 4616 b'prune',
4618 4617 [],
4619 4618 _(b'do not display revision or any of its ancestors'),
4620 4619 _(b'REV'),
4621 4620 ),
4622 4621 ]
4623 4622 + logopts
4624 4623 + walkopts,
4625 4624 _(b'[OPTION]... [FILE]'),
4626 4625 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4627 4626 helpbasic=True,
4628 4627 inferrepo=True,
4629 4628 intents={INTENT_READONLY},
4630 4629 )
4631 4630 def log(ui, repo, *pats, **opts):
4632 4631 """show revision history of entire repository or files
4633 4632
4634 4633 Print the revision history of the specified files or the entire
4635 4634 project.
4636 4635
4637 4636 If no revision range is specified, the default is ``tip:0`` unless
4638 4637 --follow is set.
4639 4638
4640 4639 File history is shown without following rename or copy history of
4641 4640 files. Use -f/--follow with a filename to follow history across
4642 4641 renames and copies. --follow without a filename will only show
4643 4642 ancestors of the starting revisions. The starting revisions can be
4644 4643 specified by -r/--rev, which default to the working directory parent.
4645 4644
4646 4645 By default this command prints revision number and changeset id,
4647 4646 tags, non-trivial parents, user, date and time, and a summary for
4648 4647 each commit. When the -v/--verbose switch is used, the list of
4649 4648 changed files and full commit message are shown.
4650 4649
4651 4650 With --graph the revisions are shown as an ASCII art DAG with the most
4652 4651 recent changeset at the top.
4653 4652 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4654 4653 involved in an unresolved merge conflict, '_' closes a branch,
4655 4654 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4656 4655 changeset from the lines below is a parent of the 'o' merge on the same
4657 4656 line.
4658 4657 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4659 4658 of a '|' indicates one or more revisions in a path are omitted.
4660 4659
4661 4660 .. container:: verbose
4662 4661
4663 4662 Use -L/--line-range FILE,M:N options to follow the history of lines
4664 4663 from M to N in FILE. With -p/--patch only diff hunks affecting
4665 4664 specified line range will be shown. This option requires --follow;
4666 4665 it can be specified multiple times. Currently, this option is not
4667 4666 compatible with --graph. This option is experimental.
4668 4667
4669 4668 .. note::
4670 4669
4671 4670 :hg:`log --patch` may generate unexpected diff output for merge
4672 4671 changesets, as it will only compare the merge changeset against
4673 4672 its first parent. Also, only files different from BOTH parents
4674 4673 will appear in files:.
4675 4674
4676 4675 .. note::
4677 4676
4678 4677 For performance reasons, :hg:`log FILE` may omit duplicate changes
4679 4678 made on branches and will not show removals or mode changes. To
4680 4679 see all such changes, use the --removed switch.
4681 4680
4682 4681 .. container:: verbose
4683 4682
4684 4683 .. note::
4685 4684
4686 4685 The history resulting from -L/--line-range options depends on diff
4687 4686 options; for instance if white-spaces are ignored, respective changes
4688 4687 with only white-spaces in specified line range will not be listed.
4689 4688
4690 4689 .. container:: verbose
4691 4690
4692 4691 Some examples:
4693 4692
4694 4693 - changesets with full descriptions and file lists::
4695 4694
4696 4695 hg log -v
4697 4696
4698 4697 - changesets ancestral to the working directory::
4699 4698
4700 4699 hg log -f
4701 4700
4702 4701 - last 10 commits on the current branch::
4703 4702
4704 4703 hg log -l 10 -b .
4705 4704
4706 4705 - changesets showing all modifications of a file, including removals::
4707 4706
4708 4707 hg log --removed file.c
4709 4708
4710 4709 - all changesets that touch a directory, with diffs, excluding merges::
4711 4710
4712 4711 hg log -Mp lib/
4713 4712
4714 4713 - all revision numbers that match a keyword::
4715 4714
4716 4715 hg log -k bug --template "{rev}\\n"
4717 4716
4718 4717 - the full hash identifier of the working directory parent::
4719 4718
4720 4719 hg log -r . --template "{node}\\n"
4721 4720
4722 4721 - list available log templates::
4723 4722
4724 4723 hg log -T list
4725 4724
4726 4725 - check if a given changeset is included in a tagged release::
4727 4726
4728 4727 hg log -r "a21ccf and ancestor(1.9)"
4729 4728
4730 4729 - find all changesets by some user in a date range::
4731 4730
4732 4731 hg log -k alice -d "may 2008 to jul 2008"
4733 4732
4734 4733 - summary of all changesets after the last tag::
4735 4734
4736 4735 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4737 4736
4738 4737 - changesets touching lines 13 to 23 for file.c::
4739 4738
4740 4739 hg log -L file.c,13:23
4741 4740
4742 4741 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4743 4742 main.c with patch::
4744 4743
4745 4744 hg log -L file.c,13:23 -L main.c,2:6 -p
4746 4745
4747 4746 See :hg:`help dates` for a list of formats valid for -d/--date.
4748 4747
4749 4748 See :hg:`help revisions` for more about specifying and ordering
4750 4749 revisions.
4751 4750
4752 4751 See :hg:`help templates` for more about pre-packaged styles and
4753 4752 specifying custom templates. The default template used by the log
4754 4753 command can be customized via the ``command-templates.log`` configuration
4755 4754 setting.
4756 4755
4757 4756 Returns 0 on success.
4758 4757
4759 4758 """
4760 4759 opts = pycompat.byteskwargs(opts)
4761 4760 linerange = opts.get(b'line_range')
4762 4761
4763 4762 if linerange and not opts.get(b'follow'):
4764 4763 raise error.InputError(_(b'--line-range requires --follow'))
4765 4764
4766 4765 if linerange and pats:
4767 4766 # TODO: take pats as patterns with no line-range filter
4768 4767 raise error.InputError(
4769 4768 _(b'FILE arguments are not compatible with --line-range option')
4770 4769 )
4771 4770
4772 4771 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4773 4772 walk_opts = logcmdutil.parseopts(ui, pats, opts)
4774 4773 revs, differ = logcmdutil.getrevs(repo, walk_opts)
4775 4774 if linerange:
4776 4775 # TODO: should follow file history from logcmdutil._initialrevs(),
4777 4776 # then filter the result by logcmdutil._makerevset() and --limit
4778 4777 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4779 4778
4780 4779 getcopies = None
4781 4780 if opts.get(b'copies'):
4782 4781 endrev = None
4783 4782 if revs:
4784 4783 endrev = revs.max() + 1
4785 4784 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4786 4785
4787 4786 ui.pager(b'log')
4788 4787 displayer = logcmdutil.changesetdisplayer(
4789 4788 ui, repo, opts, differ, buffered=True
4790 4789 )
4791 4790 if opts.get(b'graph'):
4792 4791 displayfn = logcmdutil.displaygraphrevs
4793 4792 else:
4794 4793 displayfn = logcmdutil.displayrevs
4795 4794 displayfn(ui, repo, revs, displayer, getcopies)
4796 4795
4797 4796
4798 4797 @command(
4799 4798 b'manifest',
4800 4799 [
4801 4800 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4802 4801 (b'', b'all', False, _(b"list files from all revisions")),
4803 4802 ]
4804 4803 + formatteropts,
4805 4804 _(b'[-r REV]'),
4806 4805 helpcategory=command.CATEGORY_MAINTENANCE,
4807 4806 intents={INTENT_READONLY},
4808 4807 )
4809 4808 def manifest(ui, repo, node=None, rev=None, **opts):
4810 4809 """output the current or given revision of the project manifest
4811 4810
4812 4811 Print a list of version controlled files for the given revision.
4813 4812 If no revision is given, the first parent of the working directory
4814 4813 is used, or the null revision if no revision is checked out.
4815 4814
4816 4815 With -v, print file permissions, symlink and executable bits.
4817 4816 With --debug, print file revision hashes.
4818 4817
4819 4818 If option --all is specified, the list of all files from all revisions
4820 4819 is printed. This includes deleted and renamed files.
4821 4820
4822 4821 Returns 0 on success.
4823 4822 """
4824 4823 opts = pycompat.byteskwargs(opts)
4825 4824 fm = ui.formatter(b'manifest', opts)
4826 4825
4827 4826 if opts.get(b'all'):
4828 4827 if rev or node:
4829 4828 raise error.InputError(_(b"can't specify a revision with --all"))
4830 4829
4831 4830 res = set()
4832 4831 for rev in repo:
4833 4832 ctx = repo[rev]
4834 4833 res |= set(ctx.files())
4835 4834
4836 4835 ui.pager(b'manifest')
4837 4836 for f in sorted(res):
4838 4837 fm.startitem()
4839 4838 fm.write(b"path", b'%s\n', f)
4840 4839 fm.end()
4841 4840 return
4842 4841
4843 4842 if rev and node:
4844 4843 raise error.InputError(_(b"please specify just one revision"))
4845 4844
4846 4845 if not node:
4847 4846 node = rev
4848 4847
4849 4848 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4850 4849 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4851 4850 if node:
4852 4851 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4853 4852 ctx = logcmdutil.revsingle(repo, node)
4854 4853 mf = ctx.manifest()
4855 4854 ui.pager(b'manifest')
4856 4855 for f in ctx:
4857 4856 fm.startitem()
4858 4857 fm.context(ctx=ctx)
4859 4858 fl = ctx[f].flags()
4860 4859 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4861 4860 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4862 4861 fm.write(b'path', b'%s\n', f)
4863 4862 fm.end()
4864 4863
4865 4864
4866 4865 @command(
4867 4866 b'merge',
4868 4867 [
4869 4868 (
4870 4869 b'f',
4871 4870 b'force',
4872 4871 None,
4873 4872 _(b'force a merge including outstanding changes (DEPRECATED)'),
4874 4873 ),
4875 4874 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4876 4875 (
4877 4876 b'P',
4878 4877 b'preview',
4879 4878 None,
4880 4879 _(b'review revisions to merge (no merge is performed)'),
4881 4880 ),
4882 4881 (b'', b'abort', None, _(b'abort the ongoing merge')),
4883 4882 ]
4884 4883 + mergetoolopts,
4885 4884 _(b'[-P] [[-r] REV]'),
4886 4885 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4887 4886 helpbasic=True,
4888 4887 )
4889 4888 def merge(ui, repo, node=None, **opts):
4890 4889 """merge another revision into working directory
4891 4890
4892 4891 The current working directory is updated with all changes made in
4893 4892 the requested revision since the last common predecessor revision.
4894 4893
4895 4894 Files that changed between either parent are marked as changed for
4896 4895 the next commit and a commit must be performed before any further
4897 4896 updates to the repository are allowed. The next commit will have
4898 4897 two parents.
4899 4898
4900 4899 ``--tool`` can be used to specify the merge tool used for file
4901 4900 merges. It overrides the HGMERGE environment variable and your
4902 4901 configuration files. See :hg:`help merge-tools` for options.
4903 4902
4904 4903 If no revision is specified, the working directory's parent is a
4905 4904 head revision, and the current branch contains exactly one other
4906 4905 head, the other head is merged with by default. Otherwise, an
4907 4906 explicit revision with which to merge must be provided.
4908 4907
4909 4908 See :hg:`help resolve` for information on handling file conflicts.
4910 4909
4911 4910 To undo an uncommitted merge, use :hg:`merge --abort` which
4912 4911 will check out a clean copy of the original merge parent, losing
4913 4912 all changes.
4914 4913
4915 4914 Returns 0 on success, 1 if there are unresolved files.
4916 4915 """
4917 4916
4918 4917 opts = pycompat.byteskwargs(opts)
4919 4918 abort = opts.get(b'abort')
4920 4919 if abort and repo.dirstate.p2() == repo.nullid:
4921 4920 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4922 4921 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4923 4922 if abort:
4924 4923 state = cmdutil.getunfinishedstate(repo)
4925 4924 if state and state._opname != b'merge':
4926 4925 raise error.StateError(
4927 4926 _(b'cannot abort merge with %s in progress') % (state._opname),
4928 4927 hint=state.hint(),
4929 4928 )
4930 4929 if node:
4931 4930 raise error.InputError(_(b"cannot specify a node with --abort"))
4932 4931 return hg.abortmerge(repo.ui, repo)
4933 4932
4934 4933 if opts.get(b'rev') and node:
4935 4934 raise error.InputError(_(b"please specify just one revision"))
4936 4935 if not node:
4937 4936 node = opts.get(b'rev')
4938 4937
4939 4938 if node:
4940 4939 ctx = logcmdutil.revsingle(repo, node)
4941 4940 else:
4942 4941 if ui.configbool(b'commands', b'merge.require-rev'):
4943 4942 raise error.InputError(
4944 4943 _(
4945 4944 b'configuration requires specifying revision to merge '
4946 4945 b'with'
4947 4946 )
4948 4947 )
4949 4948 ctx = repo[destutil.destmerge(repo)]
4950 4949
4951 4950 if ctx.node() is None:
4952 4951 raise error.InputError(
4953 4952 _(b'merging with the working copy has no effect')
4954 4953 )
4955 4954
4956 4955 if opts.get(b'preview'):
4957 4956 # find nodes that are ancestors of p2 but not of p1
4958 4957 p1 = repo[b'.'].node()
4959 4958 p2 = ctx.node()
4960 4959 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4961 4960
4962 4961 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4963 4962 for node in nodes:
4964 4963 displayer.show(repo[node])
4965 4964 displayer.close()
4966 4965 return 0
4967 4966
4968 4967 # ui.forcemerge is an internal variable, do not document
4969 4968 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4970 4969 with ui.configoverride(overrides, b'merge'):
4971 4970 force = opts.get(b'force')
4972 4971 labels = [b'working copy', b'merge rev', b'common ancestor']
4973 4972 return hg.merge(ctx, force=force, labels=labels)
4974 4973
4975 4974
4976 4975 statemod.addunfinished(
4977 4976 b'merge',
4978 4977 fname=None,
4979 4978 clearable=True,
4980 4979 allowcommit=True,
4981 4980 cmdmsg=_(b'outstanding uncommitted merge'),
4982 4981 abortfunc=hg.abortmerge,
4983 4982 statushint=_(
4984 4983 b'To continue: hg commit\nTo abort: hg merge --abort'
4985 4984 ),
4986 4985 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4987 4986 )
4988 4987
4989 4988
4990 4989 @command(
4991 4990 b'outgoing|out',
4992 4991 [
4993 4992 (
4994 4993 b'f',
4995 4994 b'force',
4996 4995 None,
4997 4996 _(b'run even when the destination is unrelated'),
4998 4997 ),
4999 4998 (
5000 4999 b'r',
5001 5000 b'rev',
5002 5001 [],
5003 5002 _(b'a changeset intended to be included in the destination'),
5004 5003 _(b'REV'),
5005 5004 ),
5006 5005 (b'n', b'newest-first', None, _(b'show newest record first')),
5007 5006 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
5008 5007 (
5009 5008 b'b',
5010 5009 b'branch',
5011 5010 [],
5012 5011 _(b'a specific branch you would like to push'),
5013 5012 _(b'BRANCH'),
5014 5013 ),
5015 5014 ]
5016 5015 + logopts
5017 5016 + remoteopts
5018 5017 + subrepoopts,
5019 5018 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]...'),
5020 5019 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5021 5020 )
5022 5021 def outgoing(ui, repo, *dests, **opts):
5023 5022 """show changesets not found in the destination
5024 5023
5025 5024 Show changesets not found in the specified destination repository
5026 5025 or the default push location. These are the changesets that would
5027 5026 be pushed if a push was requested.
5028 5027
5029 5028 See pull for details of valid destination formats.
5030 5029
5031 5030 .. container:: verbose
5032 5031
5033 5032 With -B/--bookmarks, the result of bookmark comparison between
5034 5033 local and remote repositories is displayed. With -v/--verbose,
5035 5034 status is also displayed for each bookmark like below::
5036 5035
5037 5036 BM1 01234567890a added
5038 5037 BM2 deleted
5039 5038 BM3 234567890abc advanced
5040 5039 BM4 34567890abcd diverged
5041 5040 BM5 4567890abcde changed
5042 5041
5043 5042 The action taken when pushing depends on the
5044 5043 status of each bookmark:
5045 5044
5046 5045 :``added``: push with ``-B`` will create it
5047 5046 :``deleted``: push with ``-B`` will delete it
5048 5047 :``advanced``: push will update it
5049 5048 :``diverged``: push with ``-B`` will update it
5050 5049 :``changed``: push with ``-B`` will update it
5051 5050
5052 5051 From the point of view of pushing behavior, bookmarks
5053 5052 existing only in the remote repository are treated as
5054 5053 ``deleted``, even if it is in fact added remotely.
5055 5054
5056 5055 Returns 0 if there are outgoing changes, 1 otherwise.
5057 5056 """
5058 5057 opts = pycompat.byteskwargs(opts)
5059 5058 if opts.get(b'bookmarks'):
5060 5059 for path in urlutil.get_push_paths(repo, ui, dests):
5061 5060 other = hg.peer(repo, opts, path)
5062 5061 try:
5063 5062 if b'bookmarks' not in other.listkeys(b'namespaces'):
5064 5063 ui.warn(_(b"remote doesn't support bookmarks\n"))
5065 5064 return 0
5066 5065 ui.status(
5067 5066 _(b'comparing with %s\n') % urlutil.hidepassword(path.loc)
5068 5067 )
5069 5068 ui.pager(b'outgoing')
5070 5069 return bookmarks.outgoing(ui, repo, other)
5071 5070 finally:
5072 5071 other.close()
5073 5072
5074 5073 return hg.outgoing(ui, repo, dests, opts)
5075 5074
5076 5075
5077 5076 @command(
5078 5077 b'parents',
5079 5078 [
5080 5079 (
5081 5080 b'r',
5082 5081 b'rev',
5083 5082 b'',
5084 5083 _(b'show parents of the specified revision'),
5085 5084 _(b'REV'),
5086 5085 ),
5087 5086 ]
5088 5087 + templateopts,
5089 5088 _(b'[-r REV] [FILE]'),
5090 5089 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5091 5090 inferrepo=True,
5092 5091 )
5093 5092 def parents(ui, repo, file_=None, **opts):
5094 5093 """show the parents of the working directory or revision (DEPRECATED)
5095 5094
5096 5095 Print the working directory's parent revisions. If a revision is
5097 5096 given via -r/--rev, the parent of that revision will be printed.
5098 5097 If a file argument is given, the revision in which the file was
5099 5098 last changed (before the working directory revision or the
5100 5099 argument to --rev if given) is printed.
5101 5100
5102 5101 This command is equivalent to::
5103 5102
5104 5103 hg log -r "p1()+p2()" or
5105 5104 hg log -r "p1(REV)+p2(REV)" or
5106 5105 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5107 5106 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5108 5107
5109 5108 See :hg:`summary` and :hg:`help revsets` for related information.
5110 5109
5111 5110 Returns 0 on success.
5112 5111 """
5113 5112
5114 5113 opts = pycompat.byteskwargs(opts)
5115 5114 rev = opts.get(b'rev')
5116 5115 if rev:
5117 5116 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5118 5117 ctx = logcmdutil.revsingle(repo, rev, None)
5119 5118
5120 5119 if file_:
5121 5120 m = scmutil.match(ctx, (file_,), opts)
5122 5121 if m.anypats() or len(m.files()) != 1:
5123 5122 raise error.InputError(_(b'can only specify an explicit filename'))
5124 5123 file_ = m.files()[0]
5125 5124 filenodes = []
5126 5125 for cp in ctx.parents():
5127 5126 if not cp:
5128 5127 continue
5129 5128 try:
5130 5129 filenodes.append(cp.filenode(file_))
5131 5130 except error.LookupError:
5132 5131 pass
5133 5132 if not filenodes:
5134 5133 raise error.InputError(_(b"'%s' not found in manifest") % file_)
5135 5134 p = []
5136 5135 for fn in filenodes:
5137 5136 fctx = repo.filectx(file_, fileid=fn)
5138 5137 p.append(fctx.node())
5139 5138 else:
5140 5139 p = [cp.node() for cp in ctx.parents()]
5141 5140
5142 5141 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5143 5142 for n in p:
5144 5143 if n != repo.nullid:
5145 5144 displayer.show(repo[n])
5146 5145 displayer.close()
5147 5146
5148 5147
5149 5148 @command(
5150 5149 b'paths',
5151 5150 formatteropts,
5152 5151 _(b'[NAME]'),
5153 5152 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5154 5153 optionalrepo=True,
5155 5154 intents={INTENT_READONLY},
5156 5155 )
5157 5156 def paths(ui, repo, search=None, **opts):
5158 5157 """show aliases for remote repositories
5159 5158
5160 5159 Show definition of symbolic path name NAME. If no name is given,
5161 5160 show definition of all available names.
5162 5161
5163 5162 Option -q/--quiet suppresses all output when searching for NAME
5164 5163 and shows only the path names when listing all definitions.
5165 5164
5166 5165 Path names are defined in the [paths] section of your
5167 5166 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5168 5167 repository, ``.hg/hgrc`` is used, too.
5169 5168
5170 5169 The path names ``default`` and ``default-push`` have a special
5171 5170 meaning. When performing a push or pull operation, they are used
5172 5171 as fallbacks if no location is specified on the command-line.
5173 5172 When ``default-push`` is set, it will be used for push and
5174 5173 ``default`` will be used for pull; otherwise ``default`` is used
5175 5174 as the fallback for both. When cloning a repository, the clone
5176 5175 source is written as ``default`` in ``.hg/hgrc``.
5177 5176
5178 5177 .. note::
5179 5178
5180 5179 ``default`` and ``default-push`` apply to all inbound (e.g.
5181 5180 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5182 5181 and :hg:`bundle`) operations.
5183 5182
5184 5183 See :hg:`help urls` for more information.
5185 5184
5186 5185 .. container:: verbose
5187 5186
5188 5187 Template:
5189 5188
5190 5189 The following keywords are supported. See also :hg:`help templates`.
5191 5190
5192 5191 :name: String. Symbolic name of the path alias.
5193 5192 :pushurl: String. URL for push operations.
5194 5193 :url: String. URL or directory path for the other operations.
5195 5194
5196 5195 Returns 0 on success.
5197 5196 """
5198 5197
5199 5198 opts = pycompat.byteskwargs(opts)
5200 5199
5201 5200 pathitems = urlutil.list_paths(ui, search)
5202 5201 ui.pager(b'paths')
5203 5202
5204 5203 fm = ui.formatter(b'paths', opts)
5205 5204 if fm.isplain():
5206 5205 hidepassword = urlutil.hidepassword
5207 5206 else:
5208 5207 hidepassword = bytes
5209 5208 if ui.quiet:
5210 5209 namefmt = b'%s\n'
5211 5210 else:
5212 5211 namefmt = b'%s = '
5213 5212 showsubopts = not search and not ui.quiet
5214 5213
5215 5214 for name, path in pathitems:
5216 5215 fm.startitem()
5217 5216 fm.condwrite(not search, b'name', namefmt, name)
5218 5217 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5219 5218 for subopt, value in sorted(path.suboptions.items()):
5220 5219 assert subopt not in (b'name', b'url')
5221 5220 if showsubopts:
5222 5221 fm.plain(b'%s:%s = ' % (name, subopt))
5223 5222 display = urlutil.path_suboptions_display[subopt]
5224 5223 value = display(value)
5225 5224 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5226 5225
5227 5226 fm.end()
5228 5227
5229 5228 if search and not pathitems:
5230 5229 if not ui.quiet:
5231 5230 ui.warn(_(b"not found!\n"))
5232 5231 return 1
5233 5232 else:
5234 5233 return 0
5235 5234
5236 5235
5237 5236 @command(
5238 5237 b'phase',
5239 5238 [
5240 5239 (b'p', b'public', False, _(b'set changeset phase to public')),
5241 5240 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5242 5241 (b's', b'secret', False, _(b'set changeset phase to secret')),
5243 5242 (b'f', b'force', False, _(b'allow to move boundary backward')),
5244 5243 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5245 5244 ],
5246 5245 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5247 5246 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5248 5247 )
5249 5248 def phase(ui, repo, *revs, **opts):
5250 5249 """set or show the current phase name
5251 5250
5252 5251 With no argument, show the phase name of the current revision(s).
5253 5252
5254 5253 With one of -p/--public, -d/--draft or -s/--secret, change the
5255 5254 phase value of the specified revisions.
5256 5255
5257 5256 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5258 5257 lower phase to a higher phase. Phases are ordered as follows::
5259 5258
5260 5259 public < draft < secret
5261 5260
5262 5261 Returns 0 on success, 1 if some phases could not be changed.
5263 5262
5264 5263 (For more information about the phases concept, see :hg:`help phases`.)
5265 5264 """
5266 5265 opts = pycompat.byteskwargs(opts)
5267 5266 # search for a unique phase argument
5268 5267 targetphase = None
5269 5268 for idx, name in enumerate(phases.cmdphasenames):
5270 5269 if opts[name]:
5271 5270 if targetphase is not None:
5272 5271 raise error.InputError(_(b'only one phase can be specified'))
5273 5272 targetphase = idx
5274 5273
5275 5274 # look for specified revision
5276 5275 revs = list(revs)
5277 5276 revs.extend(opts[b'rev'])
5278 5277 if revs:
5279 5278 revs = logcmdutil.revrange(repo, revs)
5280 5279 else:
5281 5280 # display both parents as the second parent phase can influence
5282 5281 # the phase of a merge commit
5283 5282 revs = [c.rev() for c in repo[None].parents()]
5284 5283
5285 5284 ret = 0
5286 5285 if targetphase is None:
5287 5286 # display
5288 5287 for r in revs:
5289 5288 ctx = repo[r]
5290 5289 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5291 5290 else:
5292 5291 with repo.lock(), repo.transaction(b"phase") as tr:
5293 5292 # set phase
5294 5293 if not revs:
5295 5294 raise error.InputError(_(b'empty revision set'))
5296 5295 nodes = [repo[r].node() for r in revs]
5297 5296 # moving revision from public to draft may hide them
5298 5297 # We have to check result on an unfiltered repository
5299 5298 unfi = repo.unfiltered()
5300 5299 getphase = unfi._phasecache.phase
5301 5300 olddata = [getphase(unfi, r) for r in unfi]
5302 5301 phases.advanceboundary(repo, tr, targetphase, nodes)
5303 5302 if opts[b'force']:
5304 5303 phases.retractboundary(repo, tr, targetphase, nodes)
5305 5304 getphase = unfi._phasecache.phase
5306 5305 newdata = [getphase(unfi, r) for r in unfi]
5307 5306 changes = sum(newdata[r] != olddata[r] for r in unfi)
5308 5307 cl = unfi.changelog
5309 5308 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5310 5309 if rejected:
5311 5310 ui.warn(
5312 5311 _(
5313 5312 b'cannot move %i changesets to a higher '
5314 5313 b'phase, use --force\n'
5315 5314 )
5316 5315 % len(rejected)
5317 5316 )
5318 5317 ret = 1
5319 5318 if changes:
5320 5319 msg = _(b'phase changed for %i changesets\n') % changes
5321 5320 if ret:
5322 5321 ui.status(msg)
5323 5322 else:
5324 5323 ui.note(msg)
5325 5324 else:
5326 5325 ui.warn(_(b'no phases changed\n'))
5327 5326 return ret
5328 5327
5329 5328
5330 5329 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5331 5330 """Run after a changegroup has been added via pull/unbundle
5332 5331
5333 5332 This takes arguments below:
5334 5333
5335 5334 :modheads: change of heads by pull/unbundle
5336 5335 :optupdate: updating working directory is needed or not
5337 5336 :checkout: update destination revision (or None to default destination)
5338 5337 :brev: a name, which might be a bookmark to be activated after updating
5339 5338
5340 5339 return True if update raise any conflict, False otherwise.
5341 5340 """
5342 5341 if modheads == 0:
5343 5342 return False
5344 5343 if optupdate:
5345 5344 try:
5346 5345 return hg.updatetotally(ui, repo, checkout, brev)
5347 5346 except error.UpdateAbort as inst:
5348 5347 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5349 5348 hint = inst.hint
5350 5349 raise error.UpdateAbort(msg, hint=hint)
5351 5350 if modheads is not None and modheads > 1:
5352 5351 currentbranchheads = len(repo.branchheads())
5353 5352 if currentbranchheads == modheads:
5354 5353 ui.status(
5355 5354 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5356 5355 )
5357 5356 elif currentbranchheads > 1:
5358 5357 ui.status(
5359 5358 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5360 5359 )
5361 5360 else:
5362 5361 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5363 5362 elif not ui.configbool(b'commands', b'update.requiredest'):
5364 5363 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5365 5364 return False
5366 5365
5367 5366
5368 5367 @command(
5369 5368 b'pull',
5370 5369 [
5371 5370 (
5372 5371 b'u',
5373 5372 b'update',
5374 5373 None,
5375 5374 _(b'update to new branch head if new descendants were pulled'),
5376 5375 ),
5377 5376 (
5378 5377 b'f',
5379 5378 b'force',
5380 5379 None,
5381 5380 _(b'run even when remote repository is unrelated'),
5382 5381 ),
5383 5382 (
5384 5383 b'',
5385 5384 b'confirm',
5386 5385 None,
5387 5386 _(b'confirm pull before applying changes'),
5388 5387 ),
5389 5388 (
5390 5389 b'r',
5391 5390 b'rev',
5392 5391 [],
5393 5392 _(b'a remote changeset intended to be added'),
5394 5393 _(b'REV'),
5395 5394 ),
5396 5395 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5397 5396 (
5398 5397 b'b',
5399 5398 b'branch',
5400 5399 [],
5401 5400 _(b'a specific branch you would like to pull'),
5402 5401 _(b'BRANCH'),
5403 5402 ),
5404 5403 (
5405 5404 b'',
5406 5405 b'remote-hidden',
5407 5406 False,
5408 5407 _(b"include changesets hidden on the remote (EXPERIMENTAL)"),
5409 5408 ),
5410 5409 ]
5411 5410 + remoteopts,
5412 5411 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]...'),
5413 5412 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5414 5413 helpbasic=True,
5415 5414 )
5416 5415 def pull(ui, repo, *sources, **opts):
5417 5416 """pull changes from the specified source
5418 5417
5419 5418 Pull changes from a remote repository to a local one.
5420 5419
5421 5420 This finds all changes from the repository at the specified path
5422 5421 or URL and adds them to a local repository (the current one unless
5423 5422 -R is specified). By default, this does not update the copy of the
5424 5423 project in the working directory.
5425 5424
5426 5425 When cloning from servers that support it, Mercurial may fetch
5427 5426 pre-generated data. When this is done, hooks operating on incoming
5428 5427 changesets and changegroups may fire more than once, once for each
5429 5428 pre-generated bundle and as well as for any additional remaining
5430 5429 data. See :hg:`help -e clonebundles` for more.
5431 5430
5432 5431 Use :hg:`incoming` if you want to see what would have been added
5433 5432 by a pull at the time you issued this command. If you then decide
5434 5433 to add those changes to the repository, you should use :hg:`pull
5435 5434 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5436 5435
5437 5436 If SOURCE is omitted, the 'default' path will be used.
5438 5437 See :hg:`help urls` for more information.
5439 5438
5440 5439 If multiple sources are specified, they will be pulled sequentially as if
5441 5440 the command was run multiple time. If --update is specify and the command
5442 5441 will stop at the first failed --update.
5443 5442
5444 5443 Specifying bookmark as ``.`` is equivalent to specifying the active
5445 5444 bookmark's name.
5446 5445
5447 5446 .. container:: verbose
5448 5447
5449 5448 One can use the `--remote-hidden` flag to pull changesets
5450 5449 hidden on the remote. This flag is "best effort", and will only
5451 5450 work if the server supports the feature and is configured to
5452 5451 allow the user to access hidden changesets. This option is
5453 5452 experimental and backwards compatibility is not garanteed.
5454 5453
5455 5454 Returns 0 on success, 1 if an update had unresolved files.
5456 5455 """
5457 5456
5458 5457 opts = pycompat.byteskwargs(opts)
5459 5458 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5460 5459 b'update'
5461 5460 ):
5462 5461 msg = _(b'update destination required by configuration')
5463 5462 hint = _(b'use hg pull followed by hg update DEST')
5464 5463 raise error.InputError(msg, hint=hint)
5465 5464
5466 5465 for path in urlutil.get_pull_paths(repo, ui, sources):
5467 5466 ui.status(_(b'pulling from %s\n') % urlutil.hidepassword(path.loc))
5468 5467 ui.flush()
5469 5468 other = hg.peer(repo, opts, path, remotehidden=opts[b'remote_hidden'])
5470 5469 update_conflict = None
5471 5470 try:
5472 5471 branches = (path.branch, opts.get(b'branch', []))
5473 5472 revs, checkout = hg.addbranchrevs(
5474 5473 repo,
5475 5474 other,
5476 5475 branches,
5477 5476 opts.get(b'rev'),
5478 5477 remotehidden=opts[b'remote_hidden'],
5479 5478 )
5480 5479
5481 5480 pullopargs = {}
5482 5481
5483 5482 nodes = None
5484 5483 if opts.get(b'bookmark') or revs:
5485 5484 # The list of bookmark used here is the same used to actually update
5486 5485 # the bookmark names, to avoid the race from issue 4689 and we do
5487 5486 # all lookup and bookmark queries in one go so they see the same
5488 5487 # version of the server state (issue 4700).
5489 5488 nodes = []
5490 5489 fnodes = []
5491 5490 revs = revs or []
5492 5491 if revs and not other.capable(b'lookup'):
5493 5492 err = _(
5494 5493 b"other repository doesn't support revision lookup, "
5495 5494 b"so a rev cannot be specified."
5496 5495 )
5497 5496 raise error.Abort(err)
5498 5497 with other.commandexecutor() as e:
5499 5498 fremotebookmarks = e.callcommand(
5500 5499 b'listkeys', {b'namespace': b'bookmarks'}
5501 5500 )
5502 5501 for r in revs:
5503 5502 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5504 5503 remotebookmarks = fremotebookmarks.result()
5505 5504 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5506 5505 pullopargs[b'remotebookmarks'] = remotebookmarks
5507 5506 for b in opts.get(b'bookmark', []):
5508 5507 b = repo._bookmarks.expandname(b)
5509 5508 if b not in remotebookmarks:
5510 5509 raise error.InputError(
5511 5510 _(b'remote bookmark %s not found!') % b
5512 5511 )
5513 5512 nodes.append(remotebookmarks[b])
5514 5513 for i, rev in enumerate(revs):
5515 5514 node = fnodes[i].result()
5516 5515 nodes.append(node)
5517 5516 if rev == checkout:
5518 5517 checkout = node
5519 5518
5520 5519 wlock = util.nullcontextmanager()
5521 5520 if opts.get(b'update'):
5522 5521 wlock = repo.wlock()
5523 5522 with wlock:
5524 5523 pullopargs.update(opts.get(b'opargs', {}))
5525 5524 modheads = exchange.pull(
5526 5525 repo,
5527 5526 other,
5528 5527 path=path,
5529 5528 heads=nodes,
5530 5529 force=opts.get(b'force'),
5531 5530 bookmarks=opts.get(b'bookmark', ()),
5532 5531 opargs=pullopargs,
5533 5532 confirm=opts.get(b'confirm'),
5534 5533 ).cgresult
5535 5534
5536 5535 # brev is a name, which might be a bookmark to be activated at
5537 5536 # the end of the update. In other words, it is an explicit
5538 5537 # destination of the update
5539 5538 brev = None
5540 5539
5541 5540 if checkout:
5542 5541 checkout = repo.unfiltered().changelog.rev(checkout)
5543 5542
5544 5543 # order below depends on implementation of
5545 5544 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5546 5545 # because 'checkout' is determined without it.
5547 5546 if opts.get(b'rev'):
5548 5547 brev = opts[b'rev'][0]
5549 5548 elif opts.get(b'branch'):
5550 5549 brev = opts[b'branch'][0]
5551 5550 else:
5552 5551 brev = path.branch
5553 5552
5554 5553 # XXX path: we are losing the `path` object here. Keeping it
5555 5554 # would be valuable. For example as a "variant" as we do
5556 5555 # for pushes.
5557 5556 repo._subtoppath = path.loc
5558 5557 try:
5559 5558 update_conflict = postincoming(
5560 5559 ui, repo, modheads, opts.get(b'update'), checkout, brev
5561 5560 )
5562 5561 except error.FilteredRepoLookupError as exc:
5563 5562 msg = _(b'cannot update to target: %s') % exc.args[0]
5564 5563 exc.args = (msg,) + exc.args[1:]
5565 5564 raise
5566 5565 finally:
5567 5566 del repo._subtoppath
5568 5567
5569 5568 finally:
5570 5569 other.close()
5571 5570 # skip the remaining pull source if they are some conflict.
5572 5571 if update_conflict:
5573 5572 break
5574 5573 if update_conflict:
5575 5574 return 1
5576 5575 else:
5577 5576 return 0
5578 5577
5579 5578
5580 5579 @command(
5581 5580 b'purge|clean',
5582 5581 [
5583 5582 (b'a', b'abort-on-err', None, _(b'abort if an error occurs')),
5584 5583 (b'', b'all', None, _(b'purge ignored files too')),
5585 5584 (b'i', b'ignored', None, _(b'purge only ignored files')),
5586 5585 (b'', b'dirs', None, _(b'purge empty directories')),
5587 5586 (b'', b'files', None, _(b'purge files')),
5588 5587 (b'p', b'print', None, _(b'print filenames instead of deleting them')),
5589 5588 (
5590 5589 b'0',
5591 5590 b'print0',
5592 5591 None,
5593 5592 _(
5594 5593 b'end filenames with NUL, for use with xargs'
5595 5594 b' (implies -p/--print)'
5596 5595 ),
5597 5596 ),
5598 5597 (b'', b'confirm', None, _(b'ask before permanently deleting files')),
5599 5598 ]
5600 5599 + cmdutil.walkopts,
5601 5600 _(b'hg purge [OPTION]... [DIR]...'),
5602 5601 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5603 5602 )
5604 5603 def purge(ui, repo, *dirs, **opts):
5605 5604 """removes files not tracked by Mercurial
5606 5605
5607 5606 Delete files not known to Mercurial. This is useful to test local
5608 5607 and uncommitted changes in an otherwise-clean source tree.
5609 5608
5610 5609 This means that purge will delete the following by default:
5611 5610
5612 5611 - Unknown files: files marked with "?" by :hg:`status`
5613 5612 - Empty directories: in fact Mercurial ignores directories unless
5614 5613 they contain files under source control management
5615 5614
5616 5615 But it will leave untouched:
5617 5616
5618 5617 - Modified and unmodified tracked files
5619 5618 - Ignored files (unless -i or --all is specified)
5620 5619 - New files added to the repository (with :hg:`add`)
5621 5620
5622 5621 The --files and --dirs options can be used to direct purge to delete
5623 5622 only files, only directories, or both. If neither option is given,
5624 5623 both will be deleted.
5625 5624
5626 5625 If directories are given on the command line, only files in these
5627 5626 directories are considered.
5628 5627
5629 5628 Be careful with purge, as you could irreversibly delete some files
5630 5629 you forgot to add to the repository. If you only want to print the
5631 5630 list of files that this program would delete, use the --print
5632 5631 option.
5633 5632 """
5634 5633 opts = pycompat.byteskwargs(opts)
5635 5634 cmdutil.check_at_most_one_arg(opts, b'all', b'ignored')
5636 5635
5637 5636 act = not opts.get(b'print')
5638 5637 eol = b'\n'
5639 5638 if opts.get(b'print0'):
5640 5639 eol = b'\0'
5641 5640 act = False # --print0 implies --print
5642 5641 if opts.get(b'all', False):
5643 5642 ignored = True
5644 5643 unknown = True
5645 5644 else:
5646 5645 ignored = opts.get(b'ignored', False)
5647 5646 unknown = not ignored
5648 5647
5649 5648 removefiles = opts.get(b'files')
5650 5649 removedirs = opts.get(b'dirs')
5651 5650 confirm = opts.get(b'confirm')
5652 5651 if confirm is None:
5653 5652 try:
5654 5653 extensions.find(b'purge')
5655 5654 confirm = False
5656 5655 except KeyError:
5657 5656 confirm = True
5658 5657
5659 5658 if not removefiles and not removedirs:
5660 5659 removefiles = True
5661 5660 removedirs = True
5662 5661
5663 5662 match = scmutil.match(repo[None], dirs, opts)
5664 5663
5665 5664 paths = mergemod.purge(
5666 5665 repo,
5667 5666 match,
5668 5667 unknown=unknown,
5669 5668 ignored=ignored,
5670 5669 removeemptydirs=removedirs,
5671 5670 removefiles=removefiles,
5672 5671 abortonerror=opts.get(b'abort_on_err'),
5673 5672 noop=not act,
5674 5673 confirm=confirm,
5675 5674 )
5676 5675
5677 5676 for path in paths:
5678 5677 if not act:
5679 5678 ui.write(b'%s%s' % (path, eol))
5680 5679
5681 5680
5682 5681 @command(
5683 5682 b'push',
5684 5683 [
5685 5684 (b'f', b'force', None, _(b'force push')),
5686 5685 (
5687 5686 b'r',
5688 5687 b'rev',
5689 5688 [],
5690 5689 _(b'a changeset intended to be included in the destination'),
5691 5690 _(b'REV'),
5692 5691 ),
5693 5692 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5694 5693 (b'', b'all-bookmarks', None, _(b"push all bookmarks (EXPERIMENTAL)")),
5695 5694 (
5696 5695 b'b',
5697 5696 b'branch',
5698 5697 [],
5699 5698 _(b'a specific branch you would like to push'),
5700 5699 _(b'BRANCH'),
5701 5700 ),
5702 5701 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5703 5702 (
5704 5703 b'',
5705 5704 b'pushvars',
5706 5705 [],
5707 5706 _(b'variables that can be sent to server (ADVANCED)'),
5708 5707 ),
5709 5708 (
5710 5709 b'',
5711 5710 b'publish',
5712 5711 False,
5713 5712 _(b'push the changeset as public (EXPERIMENTAL)'),
5714 5713 ),
5715 5714 ]
5716 5715 + remoteopts,
5717 5716 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]...'),
5718 5717 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5719 5718 helpbasic=True,
5720 5719 )
5721 5720 def push(ui, repo, *dests, **opts):
5722 5721 """push changes to the specified destination
5723 5722
5724 5723 Push changesets from the local repository to the specified
5725 5724 destination.
5726 5725
5727 5726 This operation is symmetrical to pull: it is identical to a pull
5728 5727 in the destination repository from the current one.
5729 5728
5730 5729 By default, push will not allow creation of new heads at the
5731 5730 destination, since multiple heads would make it unclear which head
5732 5731 to use. In this situation, it is recommended to pull and merge
5733 5732 before pushing.
5734 5733
5735 5734 Use --new-branch if you want to allow push to create a new named
5736 5735 branch that is not present at the destination. This allows you to
5737 5736 only create a new branch without forcing other changes.
5738 5737
5739 5738 .. note::
5740 5739
5741 5740 Extra care should be taken with the -f/--force option,
5742 5741 which will push all new heads on all branches, an action which will
5743 5742 almost always cause confusion for collaborators.
5744 5743
5745 5744 If -r/--rev is used, the specified revision and all its ancestors
5746 5745 will be pushed to the remote repository.
5747 5746
5748 5747 If -B/--bookmark is used, the specified bookmarked revision, its
5749 5748 ancestors, and the bookmark will be pushed to the remote
5750 5749 repository. Specifying ``.`` is equivalent to specifying the active
5751 5750 bookmark's name. Use the --all-bookmarks option for pushing all
5752 5751 current bookmarks.
5753 5752
5754 5753 Please see :hg:`help urls` for important details about ``ssh://``
5755 5754 URLs. If DESTINATION is omitted, a default path will be used.
5756 5755
5757 5756 When passed multiple destinations, push will process them one after the
5758 5757 other, but stop should an error occur.
5759 5758
5760 5759 .. container:: verbose
5761 5760
5762 5761 The --pushvars option sends strings to the server that become
5763 5762 environment variables prepended with ``HG_USERVAR_``. For example,
5764 5763 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5765 5764 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5766 5765
5767 5766 pushvars can provide for user-overridable hooks as well as set debug
5768 5767 levels. One example is having a hook that blocks commits containing
5769 5768 conflict markers, but enables the user to override the hook if the file
5770 5769 is using conflict markers for testing purposes or the file format has
5771 5770 strings that look like conflict markers.
5772 5771
5773 5772 By default, servers will ignore `--pushvars`. To enable it add the
5774 5773 following to your configuration file::
5775 5774
5776 5775 [push]
5777 5776 pushvars.server = true
5778 5777
5779 5778 Returns 0 if push was successful, 1 if nothing to push.
5780 5779 """
5781 5780
5782 5781 opts = pycompat.byteskwargs(opts)
5783 5782
5784 5783 if opts.get(b'all_bookmarks'):
5785 5784 cmdutil.check_incompatible_arguments(
5786 5785 opts,
5787 5786 b'all_bookmarks',
5788 5787 [b'bookmark', b'rev'],
5789 5788 )
5790 5789 opts[b'bookmark'] = list(repo._bookmarks)
5791 5790
5792 5791 if opts.get(b'bookmark'):
5793 5792 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5794 5793 for b in opts[b'bookmark']:
5795 5794 # translate -B options to -r so changesets get pushed
5796 5795 b = repo._bookmarks.expandname(b)
5797 5796 if b in repo._bookmarks:
5798 5797 opts.setdefault(b'rev', []).append(b)
5799 5798 else:
5800 5799 # if we try to push a deleted bookmark, translate it to null
5801 5800 # this lets simultaneous -r, -b options continue working
5802 5801 opts.setdefault(b'rev', []).append(b"null")
5803 5802
5804 5803 some_pushed = False
5805 5804 result = 0
5806 5805 for path in urlutil.get_push_paths(repo, ui, dests):
5807 5806 dest = path.loc
5808 5807 branches = (path.branch, opts.get(b'branch') or [])
5809 5808 ui.status(_(b'pushing to %s\n') % urlutil.hidepassword(dest))
5810 5809 revs, checkout = hg.addbranchrevs(
5811 5810 repo, repo, branches, opts.get(b'rev')
5812 5811 )
5813 5812 other = hg.peer(repo, opts, dest)
5814 5813
5815 5814 try:
5816 5815 if revs:
5817 5816 revs = [repo[r].node() for r in logcmdutil.revrange(repo, revs)]
5818 5817 if not revs:
5819 5818 raise error.InputError(
5820 5819 _(b"specified revisions evaluate to an empty set"),
5821 5820 hint=_(b"use different revision arguments"),
5822 5821 )
5823 5822 elif path.pushrev:
5824 5823 # It doesn't make any sense to specify ancestor revisions. So limit
5825 5824 # to DAG heads to make discovery simpler.
5826 5825 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5827 5826 revs = scmutil.revrange(repo, [expr])
5828 5827 revs = [repo[rev].node() for rev in revs]
5829 5828 if not revs:
5830 5829 raise error.InputError(
5831 5830 _(
5832 5831 b'default push revset for path evaluates to an empty set'
5833 5832 )
5834 5833 )
5835 5834 elif ui.configbool(b'commands', b'push.require-revs'):
5836 5835 raise error.InputError(
5837 5836 _(b'no revisions specified to push'),
5838 5837 hint=_(b'did you mean "hg push -r ."?'),
5839 5838 )
5840 5839
5841 5840 repo._subtoppath = dest
5842 5841 try:
5843 5842 # push subrepos depth-first for coherent ordering
5844 5843 c = repo[b'.']
5845 5844 subs = c.substate # only repos that are committed
5846 5845 for s in sorted(subs):
5847 5846 sub_result = c.sub(s).push(opts)
5848 5847 if sub_result == 0:
5849 5848 return 1
5850 5849 finally:
5851 5850 del repo._subtoppath
5852 5851
5853 5852 opargs = dict(
5854 5853 opts.get(b'opargs', {})
5855 5854 ) # copy opargs since we may mutate it
5856 5855 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5857 5856
5858 5857 pushop = exchange.push(
5859 5858 repo,
5860 5859 other,
5861 5860 opts.get(b'force'),
5862 5861 revs=revs,
5863 5862 newbranch=opts.get(b'new_branch'),
5864 5863 bookmarks=opts.get(b'bookmark', ()),
5865 5864 publish=opts.get(b'publish'),
5866 5865 opargs=opargs,
5867 5866 )
5868 5867
5869 5868 if pushop.cgresult == 0:
5870 5869 result = 1
5871 5870 elif pushop.cgresult is not None:
5872 5871 some_pushed = True
5873 5872
5874 5873 if pushop.bkresult is not None:
5875 5874 if pushop.bkresult == 2:
5876 5875 result = 2
5877 5876 elif not result and pushop.bkresult:
5878 5877 result = 2
5879 5878
5880 5879 if result:
5881 5880 break
5882 5881
5883 5882 finally:
5884 5883 other.close()
5885 5884 if result == 0 and not some_pushed:
5886 5885 result = 1
5887 5886 return result
5888 5887
5889 5888
5890 5889 @command(
5891 5890 b'recover',
5892 5891 [
5893 5892 (b'', b'verify', False, b"run `hg verify` after successful recover"),
5894 5893 ],
5895 5894 helpcategory=command.CATEGORY_MAINTENANCE,
5896 5895 )
5897 5896 def recover(ui, repo, **opts):
5898 5897 """roll back an interrupted transaction
5899 5898
5900 5899 Recover from an interrupted commit or pull.
5901 5900
5902 5901 This command tries to fix the repository status after an
5903 5902 interrupted operation. It should only be necessary when Mercurial
5904 5903 suggests it.
5905 5904
5906 5905 Returns 0 if successful, 1 if nothing to recover or verify fails.
5907 5906 """
5908 5907 ret = repo.recover()
5909 5908 if ret:
5910 5909 if opts['verify']:
5911 5910 return hg.verify(repo)
5912 5911 else:
5913 5912 msg = _(
5914 5913 b"(verify step skipped, run `hg verify` to check your "
5915 5914 b"repository content)\n"
5916 5915 )
5917 5916 ui.warn(msg)
5918 5917 return 0
5919 5918 return 1
5920 5919
5921 5920
5922 5921 @command(
5923 5922 b'remove|rm',
5924 5923 [
5925 5924 (b'A', b'after', None, _(b'record delete for missing files')),
5926 5925 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5927 5926 ]
5928 5927 + subrepoopts
5929 5928 + walkopts
5930 5929 + dryrunopts,
5931 5930 _(b'[OPTION]... FILE...'),
5932 5931 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5933 5932 helpbasic=True,
5934 5933 inferrepo=True,
5935 5934 )
5936 5935 def remove(ui, repo, *pats, **opts):
5937 5936 """remove the specified files on the next commit
5938 5937
5939 5938 Schedule the indicated files for removal from the current branch.
5940 5939
5941 5940 This command schedules the files to be removed at the next commit.
5942 5941 To undo a remove before that, see :hg:`revert`. To undo added
5943 5942 files, see :hg:`forget`.
5944 5943
5945 5944 .. container:: verbose
5946 5945
5947 5946 -A/--after can be used to remove only files that have already
5948 5947 been deleted, -f/--force can be used to force deletion, and -Af
5949 5948 can be used to remove files from the next revision without
5950 5949 deleting them from the working directory.
5951 5950
5952 5951 The following table details the behavior of remove for different
5953 5952 file states (columns) and option combinations (rows). The file
5954 5953 states are Added [A], Clean [C], Modified [M] and Missing [!]
5955 5954 (as reported by :hg:`status`). The actions are Warn, Remove
5956 5955 (from branch) and Delete (from disk):
5957 5956
5958 5957 ========= == == == ==
5959 5958 opt/state A C M !
5960 5959 ========= == == == ==
5961 5960 none W RD W R
5962 5961 -f R RD RD R
5963 5962 -A W W W R
5964 5963 -Af R R R R
5965 5964 ========= == == == ==
5966 5965
5967 5966 .. note::
5968 5967
5969 5968 :hg:`remove` never deletes files in Added [A] state from the
5970 5969 working directory, not even if ``--force`` is specified.
5971 5970
5972 5971 Returns 0 on success, 1 if any warnings encountered.
5973 5972 """
5974 5973
5975 5974 opts = pycompat.byteskwargs(opts)
5976 5975 after, force = opts.get(b'after'), opts.get(b'force')
5977 5976 dryrun = opts.get(b'dry_run')
5978 5977 if not pats and not after:
5979 5978 raise error.InputError(_(b'no files specified'))
5980 5979
5981 5980 with repo.wlock(), repo.dirstate.changing_files(repo):
5982 5981 m = scmutil.match(repo[None], pats, opts)
5983 5982 subrepos = opts.get(b'subrepos')
5984 5983 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5985 5984 return cmdutil.remove(
5986 5985 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5987 5986 )
5988 5987
5989 5988
5990 5989 @command(
5991 5990 b'rename|move|mv',
5992 5991 [
5993 5992 (b'', b'forget', None, _(b'unmark a destination file as renamed')),
5994 5993 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5995 5994 (
5996 5995 b'',
5997 5996 b'at-rev',
5998 5997 b'',
5999 5998 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
6000 5999 _(b'REV'),
6001 6000 ),
6002 6001 (
6003 6002 b'f',
6004 6003 b'force',
6005 6004 None,
6006 6005 _(b'forcibly move over an existing managed file'),
6007 6006 ),
6008 6007 ]
6009 6008 + walkopts
6010 6009 + dryrunopts,
6011 6010 _(b'[OPTION]... SOURCE... DEST'),
6012 6011 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6013 6012 )
6014 6013 def rename(ui, repo, *pats, **opts):
6015 6014 """rename files; equivalent of copy + remove
6016 6015
6017 6016 Mark dest as copies of sources; mark sources for deletion. If dest
6018 6017 is a directory, copies are put in that directory. If dest is a
6019 6018 file, there can only be one source.
6020 6019
6021 6020 By default, this command copies the contents of files as they
6022 6021 exist in the working directory. If invoked with -A/--after, the
6023 6022 operation is recorded, but no copying is performed.
6024 6023
6025 6024 To undo marking a destination file as renamed, use --forget. With that
6026 6025 option, all given (positional) arguments are unmarked as renames. The
6027 6026 destination file(s) will be left in place (still tracked). The source
6028 6027 file(s) will not be restored. Note that :hg:`rename --forget` behaves
6029 6028 the same way as :hg:`copy --forget`.
6030 6029
6031 6030 This command takes effect with the next commit by default.
6032 6031
6033 6032 Returns 0 on success, 1 if errors are encountered.
6034 6033 """
6035 6034 opts = pycompat.byteskwargs(opts)
6036 6035 context = lambda repo: repo.dirstate.changing_files(repo)
6037 6036 rev = opts.get(b'at_rev')
6038 6037 ctx = None
6039 6038 if rev:
6040 6039 ctx = logcmdutil.revsingle(repo, rev)
6041 6040 if ctx.rev() is not None:
6042 6041
6043 6042 def context(repo):
6044 6043 return util.nullcontextmanager()
6045 6044
6046 6045 opts[b'at_rev'] = ctx.rev()
6047 6046 with repo.wlock(), context(repo):
6048 6047 return cmdutil.copy(ui, repo, pats, opts, rename=True)
6049 6048
6050 6049
6051 6050 @command(
6052 6051 b'resolve',
6053 6052 [
6054 6053 (b'a', b'all', None, _(b'select all unresolved files')),
6055 6054 (b'l', b'list', None, _(b'list state of files needing merge')),
6056 6055 (b'm', b'mark', None, _(b'mark files as resolved')),
6057 6056 (b'u', b'unmark', None, _(b'mark files as unresolved')),
6058 6057 (b'n', b'no-status', None, _(b'hide status prefix')),
6059 6058 (b'', b're-merge', None, _(b're-merge files')),
6060 6059 ]
6061 6060 + mergetoolopts
6062 6061 + walkopts
6063 6062 + formatteropts,
6064 6063 _(b'[OPTION]... [FILE]...'),
6065 6064 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6066 6065 inferrepo=True,
6067 6066 )
6068 6067 def resolve(ui, repo, *pats, **opts):
6069 6068 """redo merges or set/view the merge status of files
6070 6069
6071 6070 Merges with unresolved conflicts are often the result of
6072 6071 non-interactive merging using the ``internal:merge`` configuration
6073 6072 setting, or a command-line merge tool like ``diff3``. The resolve
6074 6073 command is used to manage the files involved in a merge, after
6075 6074 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
6076 6075 working directory must have two parents). See :hg:`help
6077 6076 merge-tools` for information on configuring merge tools.
6078 6077
6079 6078 The resolve command can be used in the following ways:
6080 6079
6081 6080 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
6082 6081 the specified files, discarding any previous merge attempts. Re-merging
6083 6082 is not performed for files already marked as resolved. Use ``--all/-a``
6084 6083 to select all unresolved files. ``--tool`` can be used to specify
6085 6084 the merge tool used for the given files. It overrides the HGMERGE
6086 6085 environment variable and your configuration files. Previous file
6087 6086 contents are saved with a ``.orig`` suffix.
6088 6087
6089 6088 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
6090 6089 (e.g. after having manually fixed-up the files). The default is
6091 6090 to mark all unresolved files.
6092 6091
6093 6092 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
6094 6093 default is to mark all resolved files.
6095 6094
6096 6095 - :hg:`resolve -l`: list files which had or still have conflicts.
6097 6096 In the printed list, ``U`` = unresolved and ``R`` = resolved.
6098 6097 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
6099 6098 the list. See :hg:`help filesets` for details.
6100 6099
6101 6100 .. note::
6102 6101
6103 6102 Mercurial will not let you commit files with unresolved merge
6104 6103 conflicts. You must use :hg:`resolve -m ...` before you can
6105 6104 commit after a conflicting merge.
6106 6105
6107 6106 .. container:: verbose
6108 6107
6109 6108 Template:
6110 6109
6111 6110 The following keywords are supported in addition to the common template
6112 6111 keywords and functions. See also :hg:`help templates`.
6113 6112
6114 6113 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
6115 6114 :path: String. Repository-absolute path of the file.
6116 6115
6117 6116 Returns 0 on success, 1 if any files fail a resolve attempt.
6118 6117 """
6119 6118
6120 6119 opts = pycompat.byteskwargs(opts)
6121 6120 confirm = ui.configbool(b'commands', b'resolve.confirm')
6122 6121 flaglist = b'all mark unmark list no_status re_merge'.split()
6123 6122 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
6124 6123
6125 6124 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
6126 6125 if actioncount > 1:
6127 6126 raise error.InputError(_(b"too many actions specified"))
6128 6127 elif actioncount == 0 and ui.configbool(
6129 6128 b'commands', b'resolve.explicit-re-merge'
6130 6129 ):
6131 6130 hint = _(b'use --mark, --unmark, --list or --re-merge')
6132 6131 raise error.InputError(_(b'no action specified'), hint=hint)
6133 6132 if pats and all:
6134 6133 raise error.InputError(_(b"can't specify --all and patterns"))
6135 6134 if not (all or pats or show or mark or unmark):
6136 6135 raise error.InputError(
6137 6136 _(b'no files or directories specified'),
6138 6137 hint=b'use --all to re-merge all unresolved files',
6139 6138 )
6140 6139
6141 6140 if confirm:
6142 6141 if all:
6143 6142 if ui.promptchoice(
6144 6143 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
6145 6144 ):
6146 6145 raise error.CanceledError(_(b'user quit'))
6147 6146 if mark and not pats:
6148 6147 if ui.promptchoice(
6149 6148 _(
6150 6149 b'mark all unresolved files as resolved (yn)?'
6151 6150 b'$$ &Yes $$ &No'
6152 6151 )
6153 6152 ):
6154 6153 raise error.CanceledError(_(b'user quit'))
6155 6154 if unmark and not pats:
6156 6155 if ui.promptchoice(
6157 6156 _(
6158 6157 b'mark all resolved files as unresolved (yn)?'
6159 6158 b'$$ &Yes $$ &No'
6160 6159 )
6161 6160 ):
6162 6161 raise error.CanceledError(_(b'user quit'))
6163 6162
6164 6163 uipathfn = scmutil.getuipathfn(repo)
6165 6164
6166 6165 if show:
6167 6166 ui.pager(b'resolve')
6168 6167 fm = ui.formatter(b'resolve', opts)
6169 6168 ms = mergestatemod.mergestate.read(repo)
6170 6169 wctx = repo[None]
6171 6170 m = scmutil.match(wctx, pats, opts)
6172 6171
6173 6172 # Labels and keys based on merge state. Unresolved path conflicts show
6174 6173 # as 'P'. Resolved path conflicts show as 'R', the same as normal
6175 6174 # resolved conflicts.
6176 6175 mergestateinfo = {
6177 6176 mergestatemod.MERGE_RECORD_UNRESOLVED: (
6178 6177 b'resolve.unresolved',
6179 6178 b'U',
6180 6179 ),
6181 6180 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
6182 6181 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
6183 6182 b'resolve.unresolved',
6184 6183 b'P',
6185 6184 ),
6186 6185 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
6187 6186 b'resolve.resolved',
6188 6187 b'R',
6189 6188 ),
6190 6189 }
6191 6190
6192 6191 for f in ms:
6193 6192 if not m(f):
6194 6193 continue
6195 6194
6196 6195 label, key = mergestateinfo[ms[f]]
6197 6196 fm.startitem()
6198 6197 fm.context(ctx=wctx)
6199 6198 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
6200 6199 fm.data(path=f)
6201 6200 fm.plain(b'%s\n' % uipathfn(f), label=label)
6202 6201 fm.end()
6203 6202 return 0
6204 6203
6205 6204 with repo.wlock():
6206 6205 ms = mergestatemod.mergestate.read(repo)
6207 6206
6208 6207 if not (ms.active() or repo.dirstate.p2() != repo.nullid):
6209 6208 raise error.StateError(
6210 6209 _(b'resolve command not applicable when not merging')
6211 6210 )
6212 6211
6213 6212 wctx = repo[None]
6214 6213 m = scmutil.match(wctx, pats, opts)
6215 6214 ret = 0
6216 6215 didwork = False
6217 6216
6218 6217 hasconflictmarkers = []
6219 6218 if mark:
6220 6219 markcheck = ui.config(b'commands', b'resolve.mark-check')
6221 6220 if markcheck not in [b'warn', b'abort']:
6222 6221 # Treat all invalid / unrecognized values as 'none'.
6223 6222 markcheck = False
6224 6223 for f in ms:
6225 6224 if not m(f):
6226 6225 continue
6227 6226
6228 6227 didwork = True
6229 6228
6230 6229 # path conflicts must be resolved manually
6231 6230 if ms[f] in (
6232 6231 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6233 6232 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6234 6233 ):
6235 6234 if mark:
6236 6235 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6237 6236 elif unmark:
6238 6237 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6239 6238 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6240 6239 ui.warn(
6241 6240 _(b'%s: path conflict must be resolved manually\n')
6242 6241 % uipathfn(f)
6243 6242 )
6244 6243 continue
6245 6244
6246 6245 if mark:
6247 6246 if markcheck:
6248 6247 fdata = repo.wvfs.tryread(f)
6249 6248 if (
6250 6249 filemerge.hasconflictmarkers(fdata)
6251 6250 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6252 6251 ):
6253 6252 hasconflictmarkers.append(f)
6254 6253 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6255 6254 elif unmark:
6256 6255 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6257 6256 else:
6258 6257 # backup pre-resolve (merge uses .orig for its own purposes)
6259 6258 a = repo.wjoin(f)
6260 6259 try:
6261 6260 util.copyfile(a, a + b".resolve")
6262 6261 except FileNotFoundError:
6263 6262 pass
6264 6263
6265 6264 try:
6266 6265 # preresolve file
6267 6266 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6268 6267 with ui.configoverride(overrides, b'resolve'):
6269 6268 r = ms.resolve(f, wctx)
6270 6269 if r:
6271 6270 ret = 1
6272 6271 finally:
6273 6272 ms.commit()
6274 6273
6275 6274 # replace filemerge's .orig file with our resolve file
6276 6275 try:
6277 6276 util.rename(
6278 6277 a + b".resolve", scmutil.backuppath(ui, repo, f)
6279 6278 )
6280 6279 except FileNotFoundError:
6281 6280 pass
6282 6281
6283 6282 if hasconflictmarkers:
6284 6283 ui.warn(
6285 6284 _(
6286 6285 b'warning: the following files still have conflict '
6287 6286 b'markers:\n'
6288 6287 )
6289 6288 + b''.join(
6290 6289 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6291 6290 )
6292 6291 )
6293 6292 if markcheck == b'abort' and not all and not pats:
6294 6293 raise error.StateError(
6295 6294 _(b'conflict markers detected'),
6296 6295 hint=_(b'use --all to mark anyway'),
6297 6296 )
6298 6297
6299 6298 ms.commit()
6300 6299 branchmerge = repo.dirstate.p2() != repo.nullid
6301 6300 # resolve is not doing a parent change here, however, `record updates`
6302 6301 # will call some dirstate API that at intended for parent changes call.
6303 6302 # Ideally we would not need this and could implement a lighter version
6304 6303 # of the recordupdateslogic that will not have to deal with the part
6305 6304 # related to parent changes. However this would requires that:
6306 6305 # - we are sure we passed around enough information at update/merge
6307 6306 # time to no longer needs it at `hg resolve time`
6308 6307 # - we are sure we store that information well enough to be able to reuse it
6309 6308 # - we are the necessary logic to reuse it right.
6310 6309 #
6311 6310 # All this should eventually happens, but in the mean time, we use this
6312 6311 # context manager slightly out of the context it should be.
6313 6312 with repo.dirstate.changing_parents(repo):
6314 6313 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6315 6314
6316 6315 if not didwork and pats:
6317 6316 hint = None
6318 6317 if not any([p for p in pats if p.find(b':') >= 0]):
6319 6318 pats = [b'path:%s' % p for p in pats]
6320 6319 m = scmutil.match(wctx, pats, opts)
6321 6320 for f in ms:
6322 6321 if not m(f):
6323 6322 continue
6324 6323
6325 6324 def flag(o):
6326 6325 if o == b're_merge':
6327 6326 return b'--re-merge '
6328 6327 return b'-%s ' % o[0:1]
6329 6328
6330 6329 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6331 6330 hint = _(b"(try: hg resolve %s%s)\n") % (
6332 6331 flags,
6333 6332 b' '.join(pats),
6334 6333 )
6335 6334 break
6336 6335 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6337 6336 if hint:
6338 6337 ui.warn(hint)
6339 6338
6340 6339 unresolvedf = ms.unresolvedcount()
6341 6340 if not unresolvedf:
6342 6341 ui.status(_(b'(no more unresolved files)\n'))
6343 6342 cmdutil.checkafterresolved(repo)
6344 6343
6345 6344 return ret
6346 6345
6347 6346
6348 6347 @command(
6349 6348 b'revert',
6350 6349 [
6351 6350 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6352 6351 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6353 6352 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6354 6353 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6355 6354 (b'i', b'interactive', None, _(b'interactively select the changes')),
6356 6355 ]
6357 6356 + walkopts
6358 6357 + dryrunopts,
6359 6358 _(b'[OPTION]... [-r REV] [NAME]...'),
6360 6359 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6361 6360 )
6362 6361 def revert(ui, repo, *pats, **opts):
6363 6362 """restore files to their checkout state
6364 6363
6365 6364 .. note::
6366 6365
6367 6366 To check out earlier revisions, you should use :hg:`update REV`.
6368 6367 To cancel an uncommitted merge (and lose your changes),
6369 6368 use :hg:`merge --abort`.
6370 6369
6371 6370 With no revision specified, revert the specified files or directories
6372 6371 to the contents they had in the parent of the working directory.
6373 6372 This restores the contents of files to an unmodified
6374 6373 state and unschedules adds, removes, copies, and renames. If the
6375 6374 working directory has two parents, you must explicitly specify a
6376 6375 revision.
6377 6376
6378 6377 Using the -r/--rev or -d/--date options, revert the given files or
6379 6378 directories to their states as of a specific revision. Because
6380 6379 revert does not change the working directory parents, this will
6381 6380 cause these files to appear modified. This can be helpful to "back
6382 6381 out" some or all of an earlier change. See :hg:`backout` for a
6383 6382 related method.
6384 6383
6385 6384 Modified files are saved with a .orig suffix before reverting.
6386 6385 To disable these backups, use --no-backup. It is possible to store
6387 6386 the backup files in a custom directory relative to the root of the
6388 6387 repository by setting the ``ui.origbackuppath`` configuration
6389 6388 option.
6390 6389
6391 6390 See :hg:`help dates` for a list of formats valid for -d/--date.
6392 6391
6393 6392 See :hg:`help backout` for a way to reverse the effect of an
6394 6393 earlier changeset.
6395 6394
6396 6395 Returns 0 on success.
6397 6396 """
6398 6397
6399 6398 opts = pycompat.byteskwargs(opts)
6400 6399 if opts.get(b"date"):
6401 6400 cmdutil.check_incompatible_arguments(opts, b'date', [b'rev'])
6402 6401 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6403 6402
6404 6403 parent, p2 = repo.dirstate.parents()
6405 6404 if not opts.get(b'rev') and p2 != repo.nullid:
6406 6405 # revert after merge is a trap for new users (issue2915)
6407 6406 raise error.InputError(
6408 6407 _(b'uncommitted merge with no revision specified'),
6409 6408 hint=_(b"use 'hg update' or see 'hg help revert'"),
6410 6409 )
6411 6410
6412 6411 rev = opts.get(b'rev')
6413 6412 if rev:
6414 6413 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6415 6414 ctx = logcmdutil.revsingle(repo, rev)
6416 6415
6417 6416 if not (
6418 6417 pats
6419 6418 or opts.get(b'include')
6420 6419 or opts.get(b'exclude')
6421 6420 or opts.get(b'all')
6422 6421 or opts.get(b'interactive')
6423 6422 ):
6424 6423 msg = _(b"no files or directories specified")
6425 6424 if p2 != repo.nullid:
6426 6425 hint = _(
6427 6426 b"uncommitted merge, use --all to discard all changes,"
6428 6427 b" or 'hg update -C .' to abort the merge"
6429 6428 )
6430 6429 raise error.InputError(msg, hint=hint)
6431 6430 dirty = any(repo.status())
6432 6431 node = ctx.node()
6433 6432 if node != parent:
6434 6433 if dirty:
6435 6434 hint = (
6436 6435 _(
6437 6436 b"uncommitted changes, use --all to discard all"
6438 6437 b" changes, or 'hg update %d' to update"
6439 6438 )
6440 6439 % ctx.rev()
6441 6440 )
6442 6441 else:
6443 6442 hint = (
6444 6443 _(
6445 6444 b"use --all to revert all files,"
6446 6445 b" or 'hg update %d' to update"
6447 6446 )
6448 6447 % ctx.rev()
6449 6448 )
6450 6449 elif dirty:
6451 6450 hint = _(b"uncommitted changes, use --all to discard all changes")
6452 6451 else:
6453 6452 hint = _(b"use --all to revert all files")
6454 6453 raise error.InputError(msg, hint=hint)
6455 6454
6456 6455 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6457 6456
6458 6457
6459 6458 @command(
6460 6459 b'rollback',
6461 6460 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6462 6461 helpcategory=command.CATEGORY_MAINTENANCE,
6463 6462 )
6464 6463 def rollback(ui, repo, **opts):
6465 6464 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6466 6465
6467 6466 Please use :hg:`commit --amend` instead of rollback to correct
6468 6467 mistakes in the last commit.
6469 6468
6470 6469 This command should be used with care. There is only one level of
6471 6470 rollback, and there is no way to undo a rollback. It will also
6472 6471 restore the dirstate at the time of the last transaction, losing
6473 6472 any dirstate changes since that time. This command does not alter
6474 6473 the working directory.
6475 6474
6476 6475 Transactions are used to encapsulate the effects of all commands
6477 6476 that create new changesets or propagate existing changesets into a
6478 6477 repository.
6479 6478
6480 6479 .. container:: verbose
6481 6480
6482 6481 For example, the following commands are transactional, and their
6483 6482 effects can be rolled back:
6484 6483
6485 6484 - commit
6486 6485 - import
6487 6486 - pull
6488 6487 - push (with this repository as the destination)
6489 6488 - unbundle
6490 6489
6491 6490 To avoid permanent data loss, rollback will refuse to rollback a
6492 6491 commit transaction if it isn't checked out. Use --force to
6493 6492 override this protection.
6494 6493
6495 6494 The rollback command can be entirely disabled by setting the
6496 6495 ``ui.rollback`` configuration setting to false. If you're here
6497 6496 because you want to use rollback and it's disabled, you can
6498 6497 re-enable the command by setting ``ui.rollback`` to true.
6499 6498
6500 6499 This command is not intended for use on public repositories. Once
6501 6500 changes are visible for pull by other users, rolling a transaction
6502 6501 back locally is ineffective (someone else may already have pulled
6503 6502 the changes). Furthermore, a race is possible with readers of the
6504 6503 repository; for example an in-progress pull from the repository
6505 6504 may fail if a rollback is performed.
6506 6505
6507 6506 Returns 0 on success, 1 if no rollback data is available.
6508 6507 """
6509 6508 if not ui.configbool(b'ui', b'rollback'):
6510 6509 raise error.Abort(
6511 6510 _(b'rollback is disabled because it is unsafe'),
6512 6511 hint=b'see `hg help -v rollback` for information',
6513 6512 )
6514 6513 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6515 6514
6516 6515
6517 6516 @command(
6518 6517 b'root',
6519 6518 [] + formatteropts,
6520 6519 intents={INTENT_READONLY},
6521 6520 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6522 6521 )
6523 6522 def root(ui, repo, **opts):
6524 6523 """print the root (top) of the current working directory
6525 6524
6526 6525 Print the root directory of the current repository.
6527 6526
6528 6527 .. container:: verbose
6529 6528
6530 6529 Template:
6531 6530
6532 6531 The following keywords are supported in addition to the common template
6533 6532 keywords and functions. See also :hg:`help templates`.
6534 6533
6535 6534 :hgpath: String. Path to the .hg directory.
6536 6535 :storepath: String. Path to the directory holding versioned data.
6537 6536
6538 6537 Returns 0 on success.
6539 6538 """
6540 6539 opts = pycompat.byteskwargs(opts)
6541 6540 with ui.formatter(b'root', opts) as fm:
6542 6541 fm.startitem()
6543 6542 fm.write(b'reporoot', b'%s\n', repo.root)
6544 6543 fm.data(hgpath=repo.path, storepath=repo.spath)
6545 6544
6546 6545
6547 6546 @command(
6548 6547 b'serve',
6549 6548 [
6550 6549 (
6551 6550 b'A',
6552 6551 b'accesslog',
6553 6552 b'',
6554 6553 _(b'name of access log file to write to'),
6555 6554 _(b'FILE'),
6556 6555 ),
6557 6556 (b'd', b'daemon', None, _(b'run server in background')),
6558 6557 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6559 6558 (
6560 6559 b'E',
6561 6560 b'errorlog',
6562 6561 b'',
6563 6562 _(b'name of error log file to write to'),
6564 6563 _(b'FILE'),
6565 6564 ),
6566 6565 # use string type, then we can check if something was passed
6567 6566 (
6568 6567 b'p',
6569 6568 b'port',
6570 6569 b'',
6571 6570 _(b'port to listen on (default: 8000)'),
6572 6571 _(b'PORT'),
6573 6572 ),
6574 6573 (
6575 6574 b'a',
6576 6575 b'address',
6577 6576 b'',
6578 6577 _(b'address to listen on (default: all interfaces)'),
6579 6578 _(b'ADDR'),
6580 6579 ),
6581 6580 (
6582 6581 b'',
6583 6582 b'prefix',
6584 6583 b'',
6585 6584 _(b'prefix path to serve from (default: server root)'),
6586 6585 _(b'PREFIX'),
6587 6586 ),
6588 6587 (
6589 6588 b'n',
6590 6589 b'name',
6591 6590 b'',
6592 6591 _(b'name to show in web pages (default: working directory)'),
6593 6592 _(b'NAME'),
6594 6593 ),
6595 6594 (
6596 6595 b'',
6597 6596 b'web-conf',
6598 6597 b'',
6599 6598 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6600 6599 _(b'FILE'),
6601 6600 ),
6602 6601 (
6603 6602 b'',
6604 6603 b'webdir-conf',
6605 6604 b'',
6606 6605 _(b'name of the hgweb config file (DEPRECATED)'),
6607 6606 _(b'FILE'),
6608 6607 ),
6609 6608 (
6610 6609 b'',
6611 6610 b'pid-file',
6612 6611 b'',
6613 6612 _(b'name of file to write process ID to'),
6614 6613 _(b'FILE'),
6615 6614 ),
6616 6615 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6617 6616 (
6618 6617 b'',
6619 6618 b'cmdserver',
6620 6619 b'',
6621 6620 _(b'for remote clients (ADVANCED)'),
6622 6621 _(b'MODE'),
6623 6622 ),
6624 6623 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6625 6624 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6626 6625 (b'6', b'ipv6', None, _(b'use IPv6 instead of IPv4')),
6627 6626 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6628 6627 (b'', b'print-url', None, _(b'start and print only the URL')),
6629 6628 ]
6630 6629 + subrepoopts,
6631 6630 _(b'[OPTION]...'),
6632 6631 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6633 6632 helpbasic=True,
6634 6633 optionalrepo=True,
6635 6634 )
6636 6635 def serve(ui, repo, **opts):
6637 6636 """start stand-alone webserver
6638 6637
6639 6638 Start a local HTTP repository browser and pull server. You can use
6640 6639 this for ad-hoc sharing and browsing of repositories. It is
6641 6640 recommended to use a real web server to serve a repository for
6642 6641 longer periods of time.
6643 6642
6644 6643 Please note that the server does not implement access control.
6645 6644 This means that, by default, anybody can read from the server and
6646 6645 nobody can write to it by default. Set the ``web.allow-push``
6647 6646 option to ``*`` to allow everybody to push to the server. You
6648 6647 should use a real web server if you need to authenticate users.
6649 6648
6650 6649 By default, the server logs accesses to stdout and errors to
6651 6650 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6652 6651 files.
6653 6652
6654 6653 To have the server choose a free port number to listen on, specify
6655 6654 a port number of 0; in this case, the server will print the port
6656 6655 number it uses.
6657 6656
6658 6657 Returns 0 on success.
6659 6658 """
6660 6659
6661 6660 cmdutil.check_incompatible_arguments(opts, 'stdio', ['cmdserver'])
6662 6661 opts = pycompat.byteskwargs(opts)
6663 6662 if opts[b"print_url"] and ui.verbose:
6664 6663 raise error.InputError(_(b"cannot use --print-url with --verbose"))
6665 6664
6666 6665 if opts[b"stdio"]:
6667 6666 if repo is None:
6668 6667 raise error.RepoError(
6669 6668 _(b"there is no Mercurial repository here (.hg not found)")
6670 6669 )
6671 6670 accesshidden = False
6672 6671 if repo.filtername is None:
6673 6672 allow = ui.configlist(
6674 6673 b'experimental', b'server.allow-hidden-access'
6675 6674 )
6676 6675 user = procutil.getuser()
6677 6676 if allow and scmutil.ismember(ui, user, allow):
6678 6677 accesshidden = True
6679 6678 else:
6680 6679 msg = (
6681 6680 _(
6682 6681 b'ignoring request to access hidden changeset by '
6683 6682 b'unauthorized user: %s\n'
6684 6683 )
6685 6684 % user
6686 6685 )
6687 6686 ui.warn(msg)
6688 6687
6689 6688 s = wireprotoserver.sshserver(ui, repo, accesshidden=accesshidden)
6690 6689 s.serve_forever()
6691 6690 return
6692 6691
6693 6692 service = server.createservice(ui, repo, opts)
6694 6693 return server.runservice(opts, initfn=service.init, runfn=service.run)
6695 6694
6696 6695
6697 6696 @command(
6698 6697 b'shelve',
6699 6698 [
6700 6699 (
6701 6700 b'A',
6702 6701 b'addremove',
6703 6702 None,
6704 6703 _(b'mark new/missing files as added/removed before shelving'),
6705 6704 ),
6706 6705 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6707 6706 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6708 6707 (
6709 6708 b'',
6710 6709 b'date',
6711 6710 b'',
6712 6711 _(b'shelve with the specified commit date'),
6713 6712 _(b'DATE'),
6714 6713 ),
6715 6714 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6716 6715 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6717 6716 (
6718 6717 b'k',
6719 6718 b'keep',
6720 6719 False,
6721 6720 _(b'shelve, but keep changes in the working directory'),
6722 6721 ),
6723 6722 (b'l', b'list', None, _(b'list current shelves')),
6724 6723 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6725 6724 (
6726 6725 b'n',
6727 6726 b'name',
6728 6727 b'',
6729 6728 _(b'use the given name for the shelved commit'),
6730 6729 _(b'NAME'),
6731 6730 ),
6732 6731 (
6733 6732 b'p',
6734 6733 b'patch',
6735 6734 None,
6736 6735 _(
6737 6736 b'output patches for changes (provide the names of the shelved '
6738 6737 b'changes as positional arguments)'
6739 6738 ),
6740 6739 ),
6741 6740 (b'i', b'interactive', None, _(b'interactive mode')),
6742 6741 (
6743 6742 b'',
6744 6743 b'stat',
6745 6744 None,
6746 6745 _(
6747 6746 b'output diffstat-style summary of changes (provide the names of '
6748 6747 b'the shelved changes as positional arguments)'
6749 6748 ),
6750 6749 ),
6751 6750 ]
6752 6751 + cmdutil.walkopts,
6753 6752 _(b'hg shelve [OPTION]... [FILE]...'),
6754 6753 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6755 6754 )
6756 6755 def shelve(ui, repo, *pats, **opts):
6757 6756 """save and set aside changes from the working directory
6758 6757
6759 6758 Shelving takes files that "hg status" reports as not clean, saves
6760 6759 the modifications to a bundle (a shelved change), and reverts the
6761 6760 files so that their state in the working directory becomes clean.
6762 6761
6763 6762 To restore these changes to the working directory, using "hg
6764 6763 unshelve"; this will work even if you switch to a different
6765 6764 commit.
6766 6765
6767 6766 When no files are specified, "hg shelve" saves all not-clean
6768 6767 files. If specific files or directories are named, only changes to
6769 6768 those files are shelved.
6770 6769
6771 6770 In bare shelve (when no files are specified, without interactive,
6772 6771 include and exclude option), shelving remembers information if the
6773 6772 working directory was on newly created branch, in other words working
6774 6773 directory was on different branch than its first parent. In this
6775 6774 situation unshelving restores branch information to the working directory.
6776 6775
6777 6776 Each shelved change has a name that makes it easier to find later.
6778 6777 The name of a shelved change defaults to being based on the active
6779 6778 bookmark, or if there is no active bookmark, the current named
6780 6779 branch. To specify a different name, use ``--name``.
6781 6780
6782 6781 To see a list of existing shelved changes, use the ``--list``
6783 6782 option. For each shelved change, this will print its name, age,
6784 6783 and description; use ``--patch`` or ``--stat`` for more details.
6785 6784
6786 6785 To delete specific shelved changes, use ``--delete``. To delete
6787 6786 all shelved changes, use ``--cleanup``.
6788 6787 """
6789 6788 opts = pycompat.byteskwargs(opts)
6790 6789 allowables = [
6791 6790 (b'addremove', {b'create'}), # 'create' is pseudo action
6792 6791 (b'unknown', {b'create'}),
6793 6792 (b'cleanup', {b'cleanup'}),
6794 6793 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6795 6794 (b'delete', {b'delete'}),
6796 6795 (b'edit', {b'create'}),
6797 6796 (b'keep', {b'create'}),
6798 6797 (b'list', {b'list'}),
6799 6798 (b'message', {b'create'}),
6800 6799 (b'name', {b'create'}),
6801 6800 (b'patch', {b'patch', b'list'}),
6802 6801 (b'stat', {b'stat', b'list'}),
6803 6802 ]
6804 6803
6805 6804 def checkopt(opt):
6806 6805 if opts.get(opt):
6807 6806 for i, allowable in allowables:
6808 6807 if opts[i] and opt not in allowable:
6809 6808 raise error.InputError(
6810 6809 _(
6811 6810 b"options '--%s' and '--%s' may not be "
6812 6811 b"used together"
6813 6812 )
6814 6813 % (opt, i)
6815 6814 )
6816 6815 return True
6817 6816
6818 6817 if checkopt(b'cleanup'):
6819 6818 if pats:
6820 6819 raise error.InputError(
6821 6820 _(b"cannot specify names when using '--cleanup'")
6822 6821 )
6823 6822 return shelvemod.cleanupcmd(ui, repo)
6824 6823 elif checkopt(b'delete'):
6825 6824 return shelvemod.deletecmd(ui, repo, pats)
6826 6825 elif checkopt(b'list'):
6827 6826 return shelvemod.listcmd(ui, repo, pats, opts)
6828 6827 elif checkopt(b'patch') or checkopt(b'stat'):
6829 6828 return shelvemod.patchcmds(ui, repo, pats, opts)
6830 6829 else:
6831 6830 return shelvemod.createcmd(ui, repo, pats, opts)
6832 6831
6833 6832
6834 6833 _NOTTERSE = b'nothing'
6835 6834
6836 6835
6837 6836 @command(
6838 6837 b'status|st',
6839 6838 [
6840 6839 (b'A', b'all', None, _(b'show status of all files')),
6841 6840 (b'm', b'modified', None, _(b'show only modified files')),
6842 6841 (b'a', b'added', None, _(b'show only added files')),
6843 6842 (b'r', b'removed', None, _(b'show only removed files')),
6844 6843 (b'd', b'deleted', None, _(b'show only missing files')),
6845 6844 (b'c', b'clean', None, _(b'show only files without changes')),
6846 6845 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6847 6846 (b'i', b'ignored', None, _(b'show only ignored files')),
6848 6847 (b'n', b'no-status', None, _(b'hide status prefix')),
6849 6848 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6850 6849 (
6851 6850 b'C',
6852 6851 b'copies',
6853 6852 None,
6854 6853 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6855 6854 ),
6856 6855 (
6857 6856 b'0',
6858 6857 b'print0',
6859 6858 None,
6860 6859 _(b'end filenames with NUL, for use with xargs'),
6861 6860 ),
6862 6861 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6863 6862 (
6864 6863 b'',
6865 6864 b'change',
6866 6865 b'',
6867 6866 _(b'list the changed files of a revision'),
6868 6867 _(b'REV'),
6869 6868 ),
6870 6869 ]
6871 6870 + walkopts
6872 6871 + subrepoopts
6873 6872 + formatteropts,
6874 6873 _(b'[OPTION]... [FILE]...'),
6875 6874 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6876 6875 helpbasic=True,
6877 6876 inferrepo=True,
6878 6877 intents={INTENT_READONLY},
6879 6878 )
6880 6879 def status(ui, repo, *pats, **opts):
6881 6880 """show changed files in the working directory
6882 6881
6883 6882 Show status of files in the repository. If names are given, only
6884 6883 files that match are shown. Files that are clean or ignored or
6885 6884 the source of a copy/move operation, are not listed unless
6886 6885 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6887 6886 Unless options described with "show only ..." are given, the
6888 6887 options -mardu are used.
6889 6888
6890 6889 Option -q/--quiet hides untracked (unknown and ignored) files
6891 6890 unless explicitly requested with -u/--unknown or -i/--ignored.
6892 6891
6893 6892 .. note::
6894 6893
6895 6894 :hg:`status` may appear to disagree with diff if permissions have
6896 6895 changed or a merge has occurred. The standard diff format does
6897 6896 not report permission changes and diff only reports changes
6898 6897 relative to one merge parent.
6899 6898
6900 6899 If one revision is given, it is used as the base revision.
6901 6900 If two revisions are given, the differences between them are
6902 6901 shown. The --change option can also be used as a shortcut to list
6903 6902 the changed files of a revision from its first parent.
6904 6903
6905 6904 The codes used to show the status of files are::
6906 6905
6907 6906 M = modified
6908 6907 A = added
6909 6908 R = removed
6910 6909 C = clean
6911 6910 ! = missing (deleted by non-hg command, but still tracked)
6912 6911 ? = not tracked
6913 6912 I = ignored
6914 6913 = origin of the previous file (with --copies)
6915 6914
6916 6915 .. container:: verbose
6917 6916
6918 6917 The -t/--terse option abbreviates the output by showing only the directory
6919 6918 name if all the files in it share the same status. The option takes an
6920 6919 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6921 6920 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6922 6921 for 'ignored' and 'c' for clean.
6923 6922
6924 6923 It abbreviates only those statuses which are passed. Note that clean and
6925 6924 ignored files are not displayed with '--terse ic' unless the -c/--clean
6926 6925 and -i/--ignored options are also used.
6927 6926
6928 6927 The -v/--verbose option shows information when the repository is in an
6929 6928 unfinished merge, shelve, rebase state etc. You can have this behavior
6930 6929 turned on by default by enabling the ``commands.status.verbose`` option.
6931 6930
6932 6931 You can skip displaying some of these states by setting
6933 6932 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6934 6933 'histedit', 'merge', 'rebase', or 'unshelve'.
6935 6934
6936 6935 Template:
6937 6936
6938 6937 The following keywords are supported in addition to the common template
6939 6938 keywords and functions. See also :hg:`help templates`.
6940 6939
6941 6940 :path: String. Repository-absolute path of the file.
6942 6941 :source: String. Repository-absolute path of the file originated from.
6943 6942 Available if ``--copies`` is specified.
6944 6943 :status: String. Character denoting file's status.
6945 6944
6946 6945 Examples:
6947 6946
6948 6947 - show changes in the working directory relative to a
6949 6948 changeset::
6950 6949
6951 6950 hg status --rev 9353
6952 6951
6953 6952 - show changes in the working directory relative to the
6954 6953 current directory (see :hg:`help patterns` for more information)::
6955 6954
6956 6955 hg status re:
6957 6956
6958 6957 - show all changes including copies in an existing changeset::
6959 6958
6960 6959 hg status --copies --change 9353
6961 6960
6962 6961 - get a NUL separated list of added files, suitable for xargs::
6963 6962
6964 6963 hg status -an0
6965 6964
6966 6965 - show more information about the repository status, abbreviating
6967 6966 added, removed, modified, deleted, and untracked paths::
6968 6967
6969 6968 hg status -v -t mardu
6970 6969
6971 6970 Returns 0 on success.
6972 6971
6973 6972 """
6974 6973
6975 6974 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6976 6975 opts = pycompat.byteskwargs(opts)
6977 6976 revs = opts.get(b'rev', [])
6978 6977 change = opts.get(b'change', b'')
6979 6978 terse = opts.get(b'terse', _NOTTERSE)
6980 6979 if terse is _NOTTERSE:
6981 6980 if revs:
6982 6981 terse = b''
6983 6982 else:
6984 6983 terse = ui.config(b'commands', b'status.terse')
6985 6984
6986 6985 if revs and terse:
6987 6986 msg = _(b'cannot use --terse with --rev')
6988 6987 raise error.InputError(msg)
6989 6988 elif change:
6990 6989 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6991 6990 ctx2 = logcmdutil.revsingle(repo, change, None)
6992 6991 ctx1 = ctx2.p1()
6993 6992 else:
6994 6993 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6995 6994 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
6996 6995
6997 6996 forcerelativevalue = None
6998 6997 if ui.hasconfig(b'commands', b'status.relative'):
6999 6998 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
7000 6999 uipathfn = scmutil.getuipathfn(
7001 7000 repo,
7002 7001 legacyrelativevalue=bool(pats),
7003 7002 forcerelativevalue=forcerelativevalue,
7004 7003 )
7005 7004
7006 7005 if opts.get(b'print0'):
7007 7006 end = b'\0'
7008 7007 else:
7009 7008 end = b'\n'
7010 7009 states = b'modified added removed deleted unknown ignored clean'.split()
7011 7010 show = [k for k in states if opts.get(k)]
7012 7011 if opts.get(b'all'):
7013 7012 show += ui.quiet and (states[:4] + [b'clean']) or states
7014 7013
7015 7014 if not show:
7016 7015 if ui.quiet:
7017 7016 show = states[:4]
7018 7017 else:
7019 7018 show = states[:5]
7020 7019
7021 7020 m = scmutil.match(ctx2, pats, opts)
7022 7021 if terse:
7023 7022 # we need to compute clean and unknown to terse
7024 7023 stat = repo.status(
7025 7024 ctx1.node(),
7026 7025 ctx2.node(),
7027 7026 m,
7028 7027 b'ignored' in show or b'i' in terse,
7029 7028 clean=True,
7030 7029 unknown=True,
7031 7030 listsubrepos=opts.get(b'subrepos'),
7032 7031 )
7033 7032
7034 7033 stat = cmdutil.tersedir(stat, terse)
7035 7034 else:
7036 7035 stat = repo.status(
7037 7036 ctx1.node(),
7038 7037 ctx2.node(),
7039 7038 m,
7040 7039 b'ignored' in show,
7041 7040 b'clean' in show,
7042 7041 b'unknown' in show,
7043 7042 opts.get(b'subrepos'),
7044 7043 )
7045 7044
7046 7045 changestates = zip(
7047 7046 states,
7048 7047 pycompat.iterbytestr(b'MAR!?IC'),
7049 7048 [getattr(stat, s.decode('utf8')) for s in states],
7050 7049 )
7051 7050
7052 7051 copy = {}
7053 7052 show_copies = ui.configbool(b'ui', b'statuscopies')
7054 7053 if opts.get(b'copies') is not None:
7055 7054 show_copies = opts.get(b'copies')
7056 7055 show_copies = (show_copies or opts.get(b'all')) and not opts.get(
7057 7056 b'no_status'
7058 7057 )
7059 7058 if show_copies:
7060 7059 copy = copies.pathcopies(ctx1, ctx2, m)
7061 7060
7062 7061 morestatus = None
7063 7062 if (
7064 7063 (ui.verbose or ui.configbool(b'commands', b'status.verbose'))
7065 7064 and not ui.plain()
7066 7065 and not opts.get(b'print0')
7067 7066 ):
7068 7067 morestatus = cmdutil.readmorestatus(repo)
7069 7068
7070 7069 ui.pager(b'status')
7071 7070 fm = ui.formatter(b'status', opts)
7072 7071 fmt = b'%s' + end
7073 7072 showchar = not opts.get(b'no_status')
7074 7073
7075 7074 for state, char, files in changestates:
7076 7075 if state in show:
7077 7076 label = b'status.' + state
7078 7077 for f in files:
7079 7078 fm.startitem()
7080 7079 fm.context(ctx=ctx2)
7081 7080 fm.data(itemtype=b'file', path=f)
7082 7081 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
7083 7082 fm.plain(fmt % uipathfn(f), label=label)
7084 7083 if f in copy:
7085 7084 fm.data(source=copy[f])
7086 7085 fm.plain(
7087 7086 (b' %s' + end) % uipathfn(copy[f]),
7088 7087 label=b'status.copied',
7089 7088 )
7090 7089 if morestatus:
7091 7090 morestatus.formatfile(f, fm)
7092 7091
7093 7092 if morestatus:
7094 7093 morestatus.formatfooter(fm)
7095 7094 fm.end()
7096 7095
7097 7096
7098 7097 @command(
7099 7098 b'summary|sum',
7100 7099 [(b'', b'remote', None, _(b'check for push and pull'))],
7101 7100 b'[--remote]',
7102 7101 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7103 7102 helpbasic=True,
7104 7103 intents={INTENT_READONLY},
7105 7104 )
7106 7105 def summary(ui, repo, **opts):
7107 7106 """summarize working directory state
7108 7107
7109 7108 This generates a brief summary of the working directory state,
7110 7109 including parents, branch, commit status, phase and available updates.
7111 7110
7112 7111 With the --remote option, this will check the default paths for
7113 7112 incoming and outgoing changes. This can be time-consuming.
7114 7113
7115 7114 Returns 0 on success.
7116 7115 """
7117 7116
7118 7117 opts = pycompat.byteskwargs(opts)
7119 7118 ui.pager(b'summary')
7120 7119 ctx = repo[None]
7121 7120 parents = ctx.parents()
7122 7121 pnode = parents[0].node()
7123 7122 marks = []
7124 7123
7125 7124 try:
7126 7125 ms = mergestatemod.mergestate.read(repo)
7127 7126 except error.UnsupportedMergeRecords as e:
7128 7127 s = b' '.join(e.recordtypes)
7129 7128 ui.warn(
7130 7129 _(b'warning: merge state has unsupported record types: %s\n') % s
7131 7130 )
7132 7131 unresolved = []
7133 7132 else:
7134 7133 unresolved = list(ms.unresolved())
7135 7134
7136 7135 for p in parents:
7137 7136 # label with log.changeset (instead of log.parent) since this
7138 7137 # shows a working directory parent *changeset*:
7139 7138 # i18n: column positioning for "hg summary"
7140 7139 ui.write(
7141 7140 _(b'parent: %d:%s ') % (p.rev(), p),
7142 7141 label=logcmdutil.changesetlabels(p),
7143 7142 )
7144 7143 ui.write(b' '.join(p.tags()), label=b'log.tag')
7145 7144 if p.bookmarks():
7146 7145 marks.extend(p.bookmarks())
7147 7146 if p.rev() == -1:
7148 7147 if not len(repo):
7149 7148 ui.write(_(b' (empty repository)'))
7150 7149 else:
7151 7150 ui.write(_(b' (no revision checked out)'))
7152 7151 if p.obsolete():
7153 7152 ui.write(_(b' (obsolete)'))
7154 7153 if p.isunstable():
7155 7154 instabilities = (
7156 7155 ui.label(instability, b'trouble.%s' % instability)
7157 7156 for instability in p.instabilities()
7158 7157 )
7159 7158 ui.write(b' (' + b', '.join(instabilities) + b')')
7160 7159 ui.write(b'\n')
7161 7160 if p.description():
7162 7161 ui.status(
7163 7162 b' ' + p.description().splitlines()[0].strip() + b'\n',
7164 7163 label=b'log.summary',
7165 7164 )
7166 7165
7167 7166 branch = ctx.branch()
7168 7167 bheads = repo.branchheads(branch)
7169 7168 # i18n: column positioning for "hg summary"
7170 7169 m = _(b'branch: %s\n') % branch
7171 7170 if branch != b'default':
7172 7171 ui.write(m, label=b'log.branch')
7173 7172 else:
7174 7173 ui.status(m, label=b'log.branch')
7175 7174
7176 7175 if marks:
7177 7176 active = repo._activebookmark
7178 7177 # i18n: column positioning for "hg summary"
7179 7178 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
7180 7179 if active is not None:
7181 7180 if active in marks:
7182 7181 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
7183 7182 marks.remove(active)
7184 7183 else:
7185 7184 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
7186 7185 for m in marks:
7187 7186 ui.write(b' ' + m, label=b'log.bookmark')
7188 7187 ui.write(b'\n', label=b'log.bookmark')
7189 7188
7190 7189 status = repo.status(unknown=True)
7191 7190
7192 7191 c = repo.dirstate.copies()
7193 7192 copied, renamed = [], []
7194 7193 for d, s in c.items():
7195 7194 if s in status.removed:
7196 7195 status.removed.remove(s)
7197 7196 renamed.append(d)
7198 7197 else:
7199 7198 copied.append(d)
7200 7199 if d in status.added:
7201 7200 status.added.remove(d)
7202 7201
7203 7202 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7204 7203
7205 7204 labels = [
7206 7205 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7207 7206 (ui.label(_(b'%d added'), b'status.added'), status.added),
7208 7207 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7209 7208 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7210 7209 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7211 7210 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7212 7211 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7213 7212 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7214 7213 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7215 7214 ]
7216 7215 t = []
7217 7216 for l, s in labels:
7218 7217 if s:
7219 7218 t.append(l % len(s))
7220 7219
7221 7220 t = b', '.join(t)
7222 7221 cleanworkdir = False
7223 7222
7224 7223 if repo.vfs.exists(b'graftstate'):
7225 7224 t += _(b' (graft in progress)')
7226 7225 if repo.vfs.exists(b'updatestate'):
7227 7226 t += _(b' (interrupted update)')
7228 7227 elif len(parents) > 1:
7229 7228 t += _(b' (merge)')
7230 7229 elif branch != parents[0].branch():
7231 7230 t += _(b' (new branch)')
7232 7231 elif parents[0].closesbranch() and pnode in repo.branchheads(
7233 7232 branch, closed=True
7234 7233 ):
7235 7234 t += _(b' (head closed)')
7236 7235 elif not (
7237 7236 status.modified
7238 7237 or status.added
7239 7238 or status.removed
7240 7239 or renamed
7241 7240 or copied
7242 7241 or subs
7243 7242 ):
7244 7243 t += _(b' (clean)')
7245 7244 cleanworkdir = True
7246 7245 elif pnode not in bheads:
7247 7246 t += _(b' (new branch head)')
7248 7247
7249 7248 if parents:
7250 7249 pendingphase = max(p.phase() for p in parents)
7251 7250 else:
7252 7251 pendingphase = phases.public
7253 7252
7254 7253 if pendingphase > phases.newcommitphase(ui):
7255 7254 t += b' (%s)' % phases.phasenames[pendingphase]
7256 7255
7257 7256 if cleanworkdir:
7258 7257 # i18n: column positioning for "hg summary"
7259 7258 ui.status(_(b'commit: %s\n') % t.strip())
7260 7259 else:
7261 7260 # i18n: column positioning for "hg summary"
7262 7261 ui.write(_(b'commit: %s\n') % t.strip())
7263 7262
7264 7263 # all ancestors of branch heads - all ancestors of parent = new csets
7265 7264 new = len(
7266 7265 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7267 7266 )
7268 7267
7269 7268 if new == 0:
7270 7269 # i18n: column positioning for "hg summary"
7271 7270 ui.status(_(b'update: (current)\n'))
7272 7271 elif pnode not in bheads:
7273 7272 # i18n: column positioning for "hg summary"
7274 7273 ui.write(_(b'update: %d new changesets (update)\n') % new)
7275 7274 else:
7276 7275 # i18n: column positioning for "hg summary"
7277 7276 ui.write(
7278 7277 _(b'update: %d new changesets, %d branch heads (merge)\n')
7279 7278 % (new, len(bheads))
7280 7279 )
7281 7280
7282 7281 t = []
7283 7282 draft = len(repo.revs(b'draft()'))
7284 7283 if draft:
7285 7284 t.append(_(b'%d draft') % draft)
7286 7285 secret = len(repo.revs(b'secret()'))
7287 7286 if secret:
7288 7287 t.append(_(b'%d secret') % secret)
7289 7288
7290 7289 if draft or secret:
7291 7290 ui.status(_(b'phases: %s\n') % b', '.join(t))
7292 7291
7293 7292 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7294 7293 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7295 7294 numtrouble = len(repo.revs(trouble + b"()"))
7296 7295 # We write all the possibilities to ease translation
7297 7296 troublemsg = {
7298 7297 b"orphan": _(b"orphan: %d changesets"),
7299 7298 b"contentdivergent": _(b"content-divergent: %d changesets"),
7300 7299 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7301 7300 }
7302 7301 if numtrouble > 0:
7303 7302 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7304 7303
7305 7304 cmdutil.summaryhooks(ui, repo)
7306 7305
7307 7306 if opts.get(b'remote'):
7308 7307 needsincoming, needsoutgoing = True, True
7309 7308 else:
7310 7309 needsincoming, needsoutgoing = False, False
7311 7310 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7312 7311 if i:
7313 7312 needsincoming = True
7314 7313 if o:
7315 7314 needsoutgoing = True
7316 7315 if not needsincoming and not needsoutgoing:
7317 7316 return
7318 7317
7319 7318 def getincoming():
7320 7319 # XXX We should actually skip this if no default is specified, instead
7321 7320 # of passing "default" which will resolve as "./default/" if no default
7322 7321 # path is defined.
7323 7322 path = urlutil.get_unique_pull_path_obj(b'summary', ui, b'default')
7324 7323 sbranch = path.branch
7325 7324 try:
7326 7325 other = hg.peer(repo, {}, path)
7327 7326 except error.RepoError:
7328 7327 if opts.get(b'remote'):
7329 7328 raise
7330 7329 return path.loc, sbranch, None, None, None
7331 7330 branches = (path.branch, [])
7332 7331 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7333 7332 if revs:
7334 7333 revs = [other.lookup(rev) for rev in revs]
7335 7334 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(path.loc))
7336 7335 with repo.ui.silent():
7337 7336 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7338 7337 return path.loc, sbranch, other, commoninc, commoninc[1]
7339 7338
7340 7339 if needsincoming:
7341 7340 source, sbranch, sother, commoninc, incoming = getincoming()
7342 7341 else:
7343 7342 source = sbranch = sother = commoninc = incoming = None
7344 7343
7345 7344 def getoutgoing():
7346 7345 # XXX We should actually skip this if no default is specified, instead
7347 7346 # of passing "default" which will resolve as "./default/" if no default
7348 7347 # path is defined.
7349 7348 d = None
7350 7349 if b'default-push' in ui.paths:
7351 7350 d = b'default-push'
7352 7351 elif b'default' in ui.paths:
7353 7352 d = b'default'
7354 7353 path = None
7355 7354 if d is not None:
7356 7355 path = urlutil.get_unique_push_path(b'summary', repo, ui, d)
7357 7356 dest = path.loc
7358 7357 dbranch = path.branch
7359 7358 else:
7360 7359 dest = b'default'
7361 7360 dbranch = None
7362 7361 revs, checkout = hg.addbranchrevs(repo, repo, (dbranch, []), None)
7363 7362 if source != dest:
7364 7363 try:
7365 7364 dother = hg.peer(repo, {}, path if path is not None else dest)
7366 7365 except error.RepoError:
7367 7366 if opts.get(b'remote'):
7368 7367 raise
7369 7368 return dest, dbranch, None, None
7370 7369 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(dest))
7371 7370 elif sother is None:
7372 7371 # there is no explicit destination peer, but source one is invalid
7373 7372 return dest, dbranch, None, None
7374 7373 else:
7375 7374 dother = sother
7376 7375 if source != dest or (sbranch is not None and sbranch != dbranch):
7377 7376 common = None
7378 7377 else:
7379 7378 common = commoninc
7380 7379 if revs:
7381 7380 revs = [repo.lookup(rev) for rev in revs]
7382 7381 with repo.ui.silent():
7383 7382 outgoing = discovery.findcommonoutgoing(
7384 7383 repo, dother, onlyheads=revs, commoninc=common
7385 7384 )
7386 7385 return dest, dbranch, dother, outgoing
7387 7386
7388 7387 if needsoutgoing:
7389 7388 dest, dbranch, dother, outgoing = getoutgoing()
7390 7389 else:
7391 7390 dest = dbranch = dother = outgoing = None
7392 7391
7393 7392 if opts.get(b'remote'):
7394 7393 # Help pytype. --remote sets both `needsincoming` and `needsoutgoing`.
7395 7394 # The former always sets `sother` (or raises an exception if it can't);
7396 7395 # the latter always sets `outgoing`.
7397 7396 assert sother is not None
7398 7397 assert outgoing is not None
7399 7398
7400 7399 t = []
7401 7400 if incoming:
7402 7401 t.append(_(b'1 or more incoming'))
7403 7402 o = outgoing.missing
7404 7403 if o:
7405 7404 t.append(_(b'%d outgoing') % len(o))
7406 7405 other = dother or sother
7407 7406 if b'bookmarks' in other.listkeys(b'namespaces'):
7408 7407 counts = bookmarks.summary(repo, other)
7409 7408 if counts[0] > 0:
7410 7409 t.append(_(b'%d incoming bookmarks') % counts[0])
7411 7410 if counts[1] > 0:
7412 7411 t.append(_(b'%d outgoing bookmarks') % counts[1])
7413 7412
7414 7413 if t:
7415 7414 # i18n: column positioning for "hg summary"
7416 7415 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7417 7416 else:
7418 7417 # i18n: column positioning for "hg summary"
7419 7418 ui.status(_(b'remote: (synced)\n'))
7420 7419
7421 7420 cmdutil.summaryremotehooks(
7422 7421 ui,
7423 7422 repo,
7424 7423 opts,
7425 7424 (
7426 7425 (source, sbranch, sother, commoninc),
7427 7426 (dest, dbranch, dother, outgoing),
7428 7427 ),
7429 7428 )
7430 7429
7431 7430
7432 7431 @command(
7433 7432 b'tag',
7434 7433 [
7435 7434 (b'f', b'force', None, _(b'force tag')),
7436 7435 (b'l', b'local', None, _(b'make the tag local')),
7437 7436 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7438 7437 (b'', b'remove', None, _(b'remove a tag')),
7439 7438 # -l/--local is already there, commitopts cannot be used
7440 7439 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7441 7440 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7442 7441 ]
7443 7442 + commitopts2,
7444 7443 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7445 7444 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7446 7445 )
7447 7446 def tag(ui, repo, name1, *names, **opts):
7448 7447 """add one or more tags for the current or given revision
7449 7448
7450 7449 Name a particular revision using <name>.
7451 7450
7452 7451 Tags are used to name particular revisions of the repository and are
7453 7452 very useful to compare different revisions, to go back to significant
7454 7453 earlier versions or to mark branch points as releases, etc. Changing
7455 7454 an existing tag is normally disallowed; use -f/--force to override.
7456 7455
7457 7456 If no revision is given, the parent of the working directory is
7458 7457 used.
7459 7458
7460 7459 To facilitate version control, distribution, and merging of tags,
7461 7460 they are stored as a file named ".hgtags" which is managed similarly
7462 7461 to other project files and can be hand-edited if necessary. This
7463 7462 also means that tagging creates a new commit. The file
7464 7463 ".hg/localtags" is used for local tags (not shared among
7465 7464 repositories).
7466 7465
7467 7466 Tag commits are usually made at the head of a branch. If the parent
7468 7467 of the working directory is not a branch head, :hg:`tag` aborts; use
7469 7468 -f/--force to force the tag commit to be based on a non-head
7470 7469 changeset.
7471 7470
7472 7471 See :hg:`help dates` for a list of formats valid for -d/--date.
7473 7472
7474 7473 Since tag names have priority over branch names during revision
7475 7474 lookup, using an existing branch name as a tag name is discouraged.
7476 7475
7477 7476 Returns 0 on success.
7478 7477 """
7479 7478 cmdutil.check_incompatible_arguments(opts, 'remove', ['rev'])
7480 7479 opts = pycompat.byteskwargs(opts)
7481 7480 with repo.wlock(), repo.lock():
7482 7481 rev_ = b"."
7483 7482 names = [t.strip() for t in (name1,) + names]
7484 7483 if len(names) != len(set(names)):
7485 7484 raise error.InputError(_(b'tag names must be unique'))
7486 7485 for n in names:
7487 7486 scmutil.checknewlabel(repo, n, b'tag')
7488 7487 if not n:
7489 7488 raise error.InputError(
7490 7489 _(b'tag names cannot consist entirely of whitespace')
7491 7490 )
7492 7491 if opts.get(b'rev'):
7493 7492 rev_ = opts[b'rev']
7494 7493 message = opts.get(b'message')
7495 7494 if opts.get(b'remove'):
7496 7495 if opts.get(b'local'):
7497 7496 expectedtype = b'local'
7498 7497 else:
7499 7498 expectedtype = b'global'
7500 7499
7501 7500 for n in names:
7502 7501 if repo.tagtype(n) == b'global':
7503 7502 alltags = tagsmod.findglobaltags(ui, repo)
7504 7503 if alltags[n][0] == repo.nullid:
7505 7504 raise error.InputError(
7506 7505 _(b"tag '%s' is already removed") % n
7507 7506 )
7508 7507 if not repo.tagtype(n):
7509 7508 raise error.InputError(_(b"tag '%s' does not exist") % n)
7510 7509 if repo.tagtype(n) != expectedtype:
7511 7510 if expectedtype == b'global':
7512 7511 raise error.InputError(
7513 7512 _(b"tag '%s' is not a global tag") % n
7514 7513 )
7515 7514 else:
7516 7515 raise error.InputError(
7517 7516 _(b"tag '%s' is not a local tag") % n
7518 7517 )
7519 7518 rev_ = b'null'
7520 7519 if not message:
7521 7520 # we don't translate commit messages
7522 7521 message = b'Removed tag %s' % b', '.join(names)
7523 7522 elif not opts.get(b'force'):
7524 7523 for n in names:
7525 7524 if n in repo.tags():
7526 7525 raise error.InputError(
7527 7526 _(b"tag '%s' already exists (use -f to force)") % n
7528 7527 )
7529 7528 if not opts.get(b'local'):
7530 7529 p1, p2 = repo.dirstate.parents()
7531 7530 if p2 != repo.nullid:
7532 7531 raise error.StateError(_(b'uncommitted merge'))
7533 7532 bheads = repo.branchheads()
7534 7533 if not opts.get(b'force') and bheads and p1 not in bheads:
7535 7534 raise error.InputError(
7536 7535 _(
7537 7536 b'working directory is not at a branch head '
7538 7537 b'(use -f to force)'
7539 7538 )
7540 7539 )
7541 7540 node = logcmdutil.revsingle(repo, rev_).node()
7542 7541
7543 7542 # don't allow tagging the null rev or the working directory
7544 7543 if node is None:
7545 7544 raise error.InputError(_(b"cannot tag working directory"))
7546 7545 elif not opts.get(b'remove') and node == nullid:
7547 7546 raise error.InputError(_(b"cannot tag null revision"))
7548 7547
7549 7548 if not message:
7550 7549 # we don't translate commit messages
7551 7550 message = b'Added tag %s for changeset %s' % (
7552 7551 b', '.join(names),
7553 7552 short(node),
7554 7553 )
7555 7554
7556 7555 date = opts.get(b'date')
7557 7556 if date:
7558 7557 date = dateutil.parsedate(date)
7559 7558
7560 7559 if opts.get(b'remove'):
7561 7560 editform = b'tag.remove'
7562 7561 else:
7563 7562 editform = b'tag.add'
7564 7563 editor = cmdutil.getcommiteditor(
7565 7564 editform=editform, **pycompat.strkwargs(opts)
7566 7565 )
7567 7566
7568 7567 tagsmod.tag(
7569 7568 repo,
7570 7569 names,
7571 7570 node,
7572 7571 message,
7573 7572 opts.get(b'local'),
7574 7573 opts.get(b'user'),
7575 7574 date,
7576 7575 editor=editor,
7577 7576 )
7578 7577
7579 7578
7580 7579 @command(
7581 7580 b'tags',
7582 7581 formatteropts,
7583 7582 b'',
7584 7583 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7585 7584 intents={INTENT_READONLY},
7586 7585 )
7587 7586 def tags(ui, repo, **opts):
7588 7587 """list repository tags
7589 7588
7590 7589 This lists both regular and local tags. When the -v/--verbose
7591 7590 switch is used, a third column "local" is printed for local tags.
7592 7591 When the -q/--quiet switch is used, only the tag name is printed.
7593 7592
7594 7593 .. container:: verbose
7595 7594
7596 7595 Template:
7597 7596
7598 7597 The following keywords are supported in addition to the common template
7599 7598 keywords and functions such as ``{tag}``. See also
7600 7599 :hg:`help templates`.
7601 7600
7602 7601 :type: String. ``local`` for local tags.
7603 7602
7604 7603 Returns 0 on success.
7605 7604 """
7606 7605
7607 7606 opts = pycompat.byteskwargs(opts)
7608 7607 ui.pager(b'tags')
7609 7608 fm = ui.formatter(b'tags', opts)
7610 7609 hexfunc = fm.hexfunc
7611 7610
7612 7611 for t, n in reversed(repo.tagslist()):
7613 7612 hn = hexfunc(n)
7614 7613 label = b'tags.normal'
7615 7614 tagtype = repo.tagtype(t)
7616 7615 if not tagtype or tagtype == b'global':
7617 7616 tagtype = b''
7618 7617 else:
7619 7618 label = b'tags.' + tagtype
7620 7619
7621 7620 fm.startitem()
7622 7621 fm.context(repo=repo)
7623 7622 fm.write(b'tag', b'%s', t, label=label)
7624 7623 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7625 7624 fm.condwrite(
7626 7625 not ui.quiet,
7627 7626 b'rev node',
7628 7627 fmt,
7629 7628 repo.changelog.rev(n),
7630 7629 hn,
7631 7630 label=label,
7632 7631 )
7633 7632 fm.condwrite(
7634 7633 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7635 7634 )
7636 7635 fm.plain(b'\n')
7637 7636 fm.end()
7638 7637
7639 7638
7640 7639 @command(
7641 7640 b'tip',
7642 7641 [
7643 7642 (b'p', b'patch', None, _(b'show patch')),
7644 7643 (b'g', b'git', None, _(b'use git extended diff format')),
7645 7644 ]
7646 7645 + templateopts,
7647 7646 _(b'[-p] [-g]'),
7648 7647 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7649 7648 )
7650 7649 def tip(ui, repo, **opts):
7651 7650 """show the tip revision (DEPRECATED)
7652 7651
7653 7652 The tip revision (usually just called the tip) is the changeset
7654 7653 most recently added to the repository (and therefore the most
7655 7654 recently changed head).
7656 7655
7657 7656 If you have just made a commit, that commit will be the tip. If
7658 7657 you have just pulled changes from another repository, the tip of
7659 7658 that repository becomes the current tip. The "tip" tag is special
7660 7659 and cannot be renamed or assigned to a different changeset.
7661 7660
7662 7661 This command is deprecated, please use :hg:`heads` instead.
7663 7662
7664 7663 Returns 0 on success.
7665 7664 """
7666 7665 opts = pycompat.byteskwargs(opts)
7667 7666 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7668 7667 displayer.show(repo[b'tip'])
7669 7668 displayer.close()
7670 7669
7671 7670
7672 7671 @command(
7673 7672 b'unbundle',
7674 7673 [
7675 7674 (
7676 7675 b'u',
7677 7676 b'update',
7678 7677 None,
7679 7678 _(b'update to new branch head if changesets were unbundled'),
7680 7679 )
7681 7680 ],
7682 7681 _(b'[-u] FILE...'),
7683 7682 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7684 7683 )
7685 7684 def unbundle(ui, repo, fname1, *fnames, **opts):
7686 7685 """apply one or more bundle files
7687 7686
7688 7687 Apply one or more bundle files generated by :hg:`bundle`.
7689 7688
7690 7689 Returns 0 on success, 1 if an update has unresolved files.
7691 7690 """
7692 7691 fnames = (fname1,) + fnames
7693 7692
7694 7693 with repo.lock():
7695 7694 for fname in fnames:
7696 7695 f = hg.openpath(ui, fname)
7697 7696 gen = exchange.readbundle(ui, f, fname)
7698 7697 if isinstance(gen, streamclone.streamcloneapplier):
7699 7698 raise error.InputError(
7700 7699 _(
7701 7700 b'packed bundles cannot be applied with '
7702 7701 b'"hg unbundle"'
7703 7702 ),
7704 7703 hint=_(b'use "hg debugapplystreamclonebundle"'),
7705 7704 )
7706 7705 url = b'bundle:' + fname
7707 7706 try:
7708 7707 txnname = b'unbundle'
7709 7708 if not isinstance(gen, bundle2.unbundle20):
7710 7709 txnname = b'unbundle\n%s' % urlutil.hidepassword(url)
7711 7710 with repo.transaction(txnname) as tr:
7712 7711 op = bundle2.applybundle(
7713 7712 repo, gen, tr, source=b'unbundle', url=url
7714 7713 )
7715 7714 except error.BundleUnknownFeatureError as exc:
7716 7715 raise error.Abort(
7717 7716 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7718 7717 hint=_(
7719 7718 b"see https://mercurial-scm.org/"
7720 7719 b"wiki/BundleFeature for more "
7721 7720 b"information"
7722 7721 ),
7723 7722 )
7724 7723 modheads = bundle2.combinechangegroupresults(op)
7725 7724
7726 7725 if postincoming(ui, repo, modheads, opts.get('update'), None, None):
7727 7726 return 1
7728 7727 else:
7729 7728 return 0
7730 7729
7731 7730
7732 7731 @command(
7733 7732 b'unshelve',
7734 7733 [
7735 7734 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7736 7735 (
7737 7736 b'c',
7738 7737 b'continue',
7739 7738 None,
7740 7739 _(b'continue an incomplete unshelve operation'),
7741 7740 ),
7742 7741 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7743 7742 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7744 7743 (
7745 7744 b'n',
7746 7745 b'name',
7747 7746 b'',
7748 7747 _(b'restore shelved change with given name'),
7749 7748 _(b'NAME'),
7750 7749 ),
7751 7750 (b't', b'tool', b'', _(b'specify merge tool')),
7752 7751 (
7753 7752 b'',
7754 7753 b'date',
7755 7754 b'',
7756 7755 _(b'set date for temporary commits (DEPRECATED)'),
7757 7756 _(b'DATE'),
7758 7757 ),
7759 7758 ],
7760 7759 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7761 7760 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7762 7761 )
7763 7762 def unshelve(ui, repo, *shelved, **opts):
7764 7763 """restore a shelved change to the working directory
7765 7764
7766 7765 This command accepts an optional name of a shelved change to
7767 7766 restore. If none is given, the most recent shelved change is used.
7768 7767
7769 7768 If a shelved change is applied successfully, the bundle that
7770 7769 contains the shelved changes is moved to a backup location
7771 7770 (.hg/shelve-backup).
7772 7771
7773 7772 Since you can restore a shelved change on top of an arbitrary
7774 7773 commit, it is possible that unshelving will result in a conflict
7775 7774 between your changes and the commits you are unshelving onto. If
7776 7775 this occurs, you must resolve the conflict, then use
7777 7776 ``--continue`` to complete the unshelve operation. (The bundle
7778 7777 will not be moved until you successfully complete the unshelve.)
7779 7778
7780 7779 (Alternatively, you can use ``--abort`` to abandon an unshelve
7781 7780 that causes a conflict. This reverts the unshelved changes, and
7782 7781 leaves the bundle in place.)
7783 7782
7784 7783 If bare shelved change (without interactive, include and exclude
7785 7784 option) was done on newly created branch it would restore branch
7786 7785 information to the working directory.
7787 7786
7788 7787 After a successful unshelve, the shelved changes are stored in a
7789 7788 backup directory. Only the N most recent backups are kept. N
7790 7789 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7791 7790 configuration option.
7792 7791
7793 7792 .. container:: verbose
7794 7793
7795 7794 Timestamp in seconds is used to decide order of backups. More
7796 7795 than ``maxbackups`` backups are kept, if same timestamp
7797 7796 prevents from deciding exact order of them, for safety.
7798 7797
7799 7798 Selected changes can be unshelved with ``--interactive`` flag.
7800 7799 The working directory is updated with the selected changes, and
7801 7800 only the unselected changes remain shelved.
7802 7801 Note: The whole shelve is applied to working directory first before
7803 7802 running interactively. So, this will bring up all the conflicts between
7804 7803 working directory and the shelve, irrespective of which changes will be
7805 7804 unshelved.
7806 7805 """
7807 7806 with repo.wlock():
7808 7807 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7809 7808
7810 7809
7811 7810 statemod.addunfinished(
7812 7811 b'unshelve',
7813 7812 fname=b'shelvedstate',
7814 7813 continueflag=True,
7815 7814 abortfunc=shelvemod.hgabortunshelve,
7816 7815 continuefunc=shelvemod.hgcontinueunshelve,
7817 7816 cmdmsg=_(b'unshelve already in progress'),
7818 7817 )
7819 7818
7820 7819
7821 7820 @command(
7822 7821 b'update|up|checkout|co',
7823 7822 [
7824 7823 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7825 7824 (b'c', b'check', None, _(b'require clean working directory')),
7826 7825 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7827 7826 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7828 7827 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7829 7828 ]
7830 7829 + mergetoolopts,
7831 7830 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7832 7831 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7833 7832 helpbasic=True,
7834 7833 )
7835 7834 def update(ui, repo, node=None, **opts):
7836 7835 """update working directory (or switch revisions)
7837 7836
7838 7837 Update the repository's working directory to the specified
7839 7838 changeset. If no changeset is specified, update to the tip of the
7840 7839 current named branch and move the active bookmark (see :hg:`help
7841 7840 bookmarks`).
7842 7841
7843 7842 Update sets the working directory's parent revision to the specified
7844 7843 changeset (see :hg:`help parents`).
7845 7844
7846 7845 If the changeset is not a descendant or ancestor of the working
7847 7846 directory's parent and there are uncommitted changes, the update is
7848 7847 aborted. With the -c/--check option, the working directory is checked
7849 7848 for uncommitted changes; if none are found, the working directory is
7850 7849 updated to the specified changeset.
7851 7850
7852 7851 .. container:: verbose
7853 7852
7854 7853 The -C/--clean, -c/--check, and -m/--merge options control what
7855 7854 happens if the working directory contains uncommitted changes.
7856 7855 At most of one of them can be specified.
7857 7856
7858 7857 1. If no option is specified, and if
7859 7858 the requested changeset is an ancestor or descendant of
7860 7859 the working directory's parent, the uncommitted changes
7861 7860 are merged into the requested changeset and the merged
7862 7861 result is left uncommitted. If the requested changeset is
7863 7862 not an ancestor or descendant (that is, it is on another
7864 7863 branch), the update is aborted and the uncommitted changes
7865 7864 are preserved.
7866 7865
7867 7866 2. With the -m/--merge option, the update is allowed even if the
7868 7867 requested changeset is not an ancestor or descendant of
7869 7868 the working directory's parent.
7870 7869
7871 7870 3. With the -c/--check option, the update is aborted and the
7872 7871 uncommitted changes are preserved.
7873 7872
7874 7873 4. With the -C/--clean option, uncommitted changes are discarded and
7875 7874 the working directory is updated to the requested changeset.
7876 7875
7877 7876 To cancel an uncommitted merge (and lose your changes), use
7878 7877 :hg:`merge --abort`.
7879 7878
7880 7879 Use null as the changeset to remove the working directory (like
7881 7880 :hg:`clone -U`).
7882 7881
7883 7882 If you want to revert just one file to an older revision, use
7884 7883 :hg:`revert [-r REV] NAME`.
7885 7884
7886 7885 See :hg:`help dates` for a list of formats valid for -d/--date.
7887 7886
7888 7887 Returns 0 on success, 1 if there are unresolved files.
7889 7888 """
7890 7889 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7891 7890 rev = opts.get('rev')
7892 7891 date = opts.get('date')
7893 7892 clean = opts.get('clean')
7894 7893 check = opts.get('check')
7895 7894 merge = opts.get('merge')
7896 7895 if rev and node:
7897 7896 raise error.InputError(_(b"please specify just one revision"))
7898 7897
7899 7898 if ui.configbool(b'commands', b'update.requiredest'):
7900 7899 if not node and not rev and not date:
7901 7900 raise error.InputError(
7902 7901 _(b'you must specify a destination'),
7903 7902 hint=_(b'for example: hg update ".::"'),
7904 7903 )
7905 7904
7906 7905 if rev is None or rev == b'':
7907 7906 rev = node
7908 7907
7909 7908 if date and rev is not None:
7910 7909 raise error.InputError(_(b"you can't specify a revision and a date"))
7911 7910
7912 7911 updatecheck = None
7913 7912 if check or merge is not None and not merge:
7914 7913 updatecheck = b'abort'
7915 7914 elif merge or check is not None and not check:
7916 7915 updatecheck = b'none'
7917 7916
7918 7917 with repo.wlock():
7919 7918 cmdutil.clearunfinished(repo)
7920 7919 if date:
7921 7920 rev = cmdutil.finddate(ui, repo, date)
7922 7921
7923 7922 # if we defined a bookmark, we have to remember the original name
7924 7923 brev = rev
7925 7924 if rev:
7926 7925 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7927 7926 ctx = logcmdutil.revsingle(repo, rev, default=None)
7928 7927 rev = ctx.rev()
7929 7928 hidden = ctx.hidden()
7930 7929 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7931 7930 with ui.configoverride(overrides, b'update'):
7932 7931 ret = hg.updatetotally(
7933 7932 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7934 7933 )
7935 7934 if hidden:
7936 7935 ctxstr = ctx.hex()[:12]
7937 7936 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7938 7937
7939 7938 if ctx.obsolete():
7940 7939 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7941 7940 ui.warn(b"(%s)\n" % obsfatemsg)
7942 7941 return ret
7943 7942
7944 7943
7945 7944 @command(
7946 7945 b'verify',
7947 7946 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7948 7947 helpcategory=command.CATEGORY_MAINTENANCE,
7949 7948 )
7950 7949 def verify(ui, repo, **opts):
7951 7950 """verify the integrity of the repository
7952 7951
7953 7952 Verify the integrity of the current repository.
7954 7953
7955 7954 This will perform an extensive check of the repository's
7956 7955 integrity, validating the hashes and checksums of each entry in
7957 7956 the changelog, manifest, and tracked files, as well as the
7958 7957 integrity of their crosslinks and indices.
7959 7958
7960 7959 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7961 7960 for more information about recovery from corruption of the
7962 7961 repository.
7963 7962
7964 7963 Returns 0 on success, 1 if errors are encountered.
7965 7964 """
7966 7965 opts = pycompat.byteskwargs(opts)
7967 7966
7968 7967 level = None
7969 7968 if opts[b'full']:
7970 7969 level = verifymod.VERIFY_FULL
7971 7970 return hg.verify(repo, level)
7972 7971
7973 7972
7974 7973 @command(
7975 7974 b'version',
7976 7975 [] + formatteropts,
7977 7976 helpcategory=command.CATEGORY_HELP,
7978 7977 norepo=True,
7979 7978 intents={INTENT_READONLY},
7980 7979 )
7981 7980 def version_(ui, **opts):
7982 7981 """output version and copyright information
7983 7982
7984 7983 .. container:: verbose
7985 7984
7986 7985 Template:
7987 7986
7988 7987 The following keywords are supported. See also :hg:`help templates`.
7989 7988
7990 7989 :extensions: List of extensions.
7991 7990 :ver: String. Version number.
7992 7991
7993 7992 And each entry of ``{extensions}`` provides the following sub-keywords
7994 7993 in addition to ``{ver}``.
7995 7994
7996 7995 :bundled: Boolean. True if included in the release.
7997 7996 :name: String. Extension name.
7998 7997 """
7999 7998 opts = pycompat.byteskwargs(opts)
8000 7999 if ui.verbose:
8001 8000 ui.pager(b'version')
8002 8001 fm = ui.formatter(b"version", opts)
8003 8002 fm.startitem()
8004 8003 fm.write(
8005 8004 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
8006 8005 )
8007 8006 license = _(
8008 8007 b"(see https://mercurial-scm.org for more information)\n"
8009 8008 b"\nCopyright (C) 2005-2023 Olivia Mackall and others\n"
8010 8009 b"This is free software; see the source for copying conditions. "
8011 8010 b"There is NO\nwarranty; "
8012 8011 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
8013 8012 )
8014 8013 if not ui.quiet:
8015 8014 fm.plain(license)
8016 8015
8017 8016 if ui.verbose:
8018 8017 fm.plain(_(b"\nEnabled extensions:\n\n"))
8019 8018 # format names and versions into columns
8020 8019 names = []
8021 8020 vers = []
8022 8021 isinternals = []
8023 8022 for name, module in sorted(extensions.extensions()):
8024 8023 names.append(name)
8025 8024 vers.append(extensions.moduleversion(module) or None)
8026 8025 isinternals.append(extensions.ismoduleinternal(module))
8027 8026 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
8028 8027 if names:
8029 8028 namefmt = b" %%-%ds " % max(len(n) for n in names)
8030 8029 places = [_(b"external"), _(b"internal")]
8031 8030 for n, v, p in zip(names, vers, isinternals):
8032 8031 fn.startitem()
8033 8032 fn.condwrite(ui.verbose, b"name", namefmt, n)
8034 8033 if ui.verbose:
8035 8034 fn.plain(b"%s " % places[p])
8036 8035 fn.data(bundled=p)
8037 8036 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
8038 8037 if ui.verbose:
8039 8038 fn.plain(b"\n")
8040 8039 fn.end()
8041 8040 fm.end()
8042 8041
8043 8042
8044 8043 def loadcmdtable(ui, name, cmdtable):
8045 8044 """Load command functions from specified cmdtable"""
8046 8045 overrides = [cmd for cmd in cmdtable if cmd in table]
8047 8046 if overrides:
8048 8047 ui.warn(
8049 8048 _(b"extension '%s' overrides commands: %s\n")
8050 8049 % (name, b" ".join(overrides))
8051 8050 )
8052 8051 table.update(cmdtable)
General Comments 0
You need to be logged in to leave comments. Login now