##// END OF EJS Templates
perf-bundle: accept --type argument
marmoute -
r50308:b380583a default
parent child Browse files
Show More
@@ -1,4044 +1,4071 b''
1 1 # perf.py - performance test routines
2 2 '''helper extension to measure performance
3 3
4 4 Configurations
5 5 ==============
6 6
7 7 ``perf``
8 8 --------
9 9
10 10 ``all-timing``
11 11 When set, additional statistics will be reported for each benchmark: best,
12 12 worst, median average. If not set only the best timing is reported
13 13 (default: off).
14 14
15 15 ``presleep``
16 16 number of second to wait before any group of runs (default: 1)
17 17
18 18 ``pre-run``
19 19 number of run to perform before starting measurement.
20 20
21 21 ``profile-benchmark``
22 22 Enable profiling for the benchmarked section.
23 23 (The first iteration is benchmarked)
24 24
25 25 ``run-limits``
26 26 Control the number of runs each benchmark will perform. The option value
27 27 should be a list of `<time>-<numberofrun>` pairs. After each run the
28 28 conditions are considered in order with the following logic:
29 29
30 30 If benchmark has been running for <time> seconds, and we have performed
31 31 <numberofrun> iterations, stop the benchmark,
32 32
33 33 The default value is: `3.0-100, 10.0-3`
34 34
35 35 ``stub``
36 36 When set, benchmarks will only be run once, useful for testing
37 37 (default: off)
38 38 '''
39 39
40 40 # "historical portability" policy of perf.py:
41 41 #
42 42 # We have to do:
43 43 # - make perf.py "loadable" with as wide Mercurial version as possible
44 44 # This doesn't mean that perf commands work correctly with that Mercurial.
45 45 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
46 46 # - make historical perf command work correctly with as wide Mercurial
47 47 # version as possible
48 48 #
49 49 # We have to do, if possible with reasonable cost:
50 50 # - make recent perf command for historical feature work correctly
51 51 # with early Mercurial
52 52 #
53 53 # We don't have to do:
54 54 # - make perf command for recent feature work correctly with early
55 55 # Mercurial
56 56
57 57 import contextlib
58 58 import functools
59 59 import gc
60 60 import os
61 61 import random
62 62 import shutil
63 63 import struct
64 64 import sys
65 65 import tempfile
66 66 import threading
67 67 import time
68 68
69 69 import mercurial.revlog
70 70 from mercurial import (
71 71 changegroup,
72 72 cmdutil,
73 73 commands,
74 74 copies,
75 75 error,
76 76 extensions,
77 77 hg,
78 78 mdiff,
79 79 merge,
80 80 util,
81 81 )
82 82
83 83 # for "historical portability":
84 84 # try to import modules separately (in dict order), and ignore
85 85 # failure, because these aren't available with early Mercurial
86 86 try:
87 87 from mercurial import branchmap # since 2.5 (or bcee63733aad)
88 88 except ImportError:
89 89 pass
90 90 try:
91 91 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
92 92 except ImportError:
93 93 pass
94 94 try:
95 95 from mercurial import registrar # since 3.7 (or 37d50250b696)
96 96
97 97 dir(registrar) # forcibly load it
98 98 except ImportError:
99 99 registrar = None
100 100 try:
101 101 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
102 102 except ImportError:
103 103 pass
104 104 try:
105 105 from mercurial.utils import repoviewutil # since 5.0
106 106 except ImportError:
107 107 repoviewutil = None
108 108 try:
109 109 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
110 110 except ImportError:
111 111 pass
112 112 try:
113 113 from mercurial import setdiscovery # since 1.9 (or cb98fed52495)
114 114 except ImportError:
115 115 pass
116 116
117 117 try:
118 118 from mercurial import profiling
119 119 except ImportError:
120 120 profiling = None
121 121
122 122 try:
123 123 from mercurial.revlogutils import constants as revlog_constants
124 124
125 125 perf_rl_kind = (revlog_constants.KIND_OTHER, b'created-by-perf')
126 126
127 127 def revlog(opener, *args, **kwargs):
128 128 return mercurial.revlog.revlog(opener, perf_rl_kind, *args, **kwargs)
129 129
130 130
131 131 except (ImportError, AttributeError):
132 132 perf_rl_kind = None
133 133
134 134 def revlog(opener, *args, **kwargs):
135 135 return mercurial.revlog.revlog(opener, *args, **kwargs)
136 136
137 137
138 138 def identity(a):
139 139 return a
140 140
141 141
142 142 try:
143 143 from mercurial import pycompat
144 144
145 145 getargspec = pycompat.getargspec # added to module after 4.5
146 146 _byteskwargs = pycompat.byteskwargs # since 4.1 (or fbc3f73dc802)
147 147 _sysstr = pycompat.sysstr # since 4.0 (or 2219f4f82ede)
148 148 _bytestr = pycompat.bytestr # since 4.2 (or b70407bd84d5)
149 149 _xrange = pycompat.xrange # since 4.8 (or 7eba8f83129b)
150 150 fsencode = pycompat.fsencode # since 3.9 (or f4a5e0e86a7e)
151 151 if pycompat.ispy3:
152 152 _maxint = sys.maxsize # per py3 docs for replacing maxint
153 153 else:
154 154 _maxint = sys.maxint
155 155 except (NameError, ImportError, AttributeError):
156 156 import inspect
157 157
158 158 getargspec = inspect.getargspec
159 159 _byteskwargs = identity
160 160 _bytestr = str
161 161 fsencode = identity # no py3 support
162 162 _maxint = sys.maxint # no py3 support
163 163 _sysstr = lambda x: x # no py3 support
164 164 _xrange = xrange
165 165
166 166 try:
167 167 # 4.7+
168 168 queue = pycompat.queue.Queue
169 169 except (NameError, AttributeError, ImportError):
170 170 # <4.7.
171 171 try:
172 172 queue = pycompat.queue
173 173 except (NameError, AttributeError, ImportError):
174 174 import Queue as queue
175 175
176 176 try:
177 177 from mercurial import logcmdutil
178 178
179 179 makelogtemplater = logcmdutil.maketemplater
180 180 except (AttributeError, ImportError):
181 181 try:
182 182 makelogtemplater = cmdutil.makelogtemplater
183 183 except (AttributeError, ImportError):
184 184 makelogtemplater = None
185 185
186 186 # for "historical portability":
187 187 # define util.safehasattr forcibly, because util.safehasattr has been
188 188 # available since 1.9.3 (or 94b200a11cf7)
189 189 _undefined = object()
190 190
191 191
192 192 def safehasattr(thing, attr):
193 193 return getattr(thing, _sysstr(attr), _undefined) is not _undefined
194 194
195 195
196 196 setattr(util, 'safehasattr', safehasattr)
197 197
198 198 # for "historical portability":
199 199 # define util.timer forcibly, because util.timer has been available
200 200 # since ae5d60bb70c9
201 201 if safehasattr(time, 'perf_counter'):
202 202 util.timer = time.perf_counter
203 203 elif os.name == b'nt':
204 204 util.timer = time.clock
205 205 else:
206 206 util.timer = time.time
207 207
208 208 # for "historical portability":
209 209 # use locally defined empty option list, if formatteropts isn't
210 210 # available, because commands.formatteropts has been available since
211 211 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
212 212 # available since 2.2 (or ae5f92e154d3)
213 213 formatteropts = getattr(
214 214 cmdutil, "formatteropts", getattr(commands, "formatteropts", [])
215 215 )
216 216
217 217 # for "historical portability":
218 218 # use locally defined option list, if debugrevlogopts isn't available,
219 219 # because commands.debugrevlogopts has been available since 3.7 (or
220 220 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
221 221 # since 1.9 (or a79fea6b3e77).
222 222 revlogopts = getattr(
223 223 cmdutil,
224 224 "debugrevlogopts",
225 225 getattr(
226 226 commands,
227 227 "debugrevlogopts",
228 228 [
229 229 (b'c', b'changelog', False, b'open changelog'),
230 230 (b'm', b'manifest', False, b'open manifest'),
231 231 (b'', b'dir', False, b'open directory manifest'),
232 232 ],
233 233 ),
234 234 )
235 235
236 236 cmdtable = {}
237 237
238 238 # for "historical portability":
239 239 # define parsealiases locally, because cmdutil.parsealiases has been
240 240 # available since 1.5 (or 6252852b4332)
241 241 def parsealiases(cmd):
242 242 return cmd.split(b"|")
243 243
244 244
245 245 if safehasattr(registrar, 'command'):
246 246 command = registrar.command(cmdtable)
247 247 elif safehasattr(cmdutil, 'command'):
248 248 command = cmdutil.command(cmdtable)
249 249 if 'norepo' not in getargspec(command).args:
250 250 # for "historical portability":
251 251 # wrap original cmdutil.command, because "norepo" option has
252 252 # been available since 3.1 (or 75a96326cecb)
253 253 _command = command
254 254
255 255 def command(name, options=(), synopsis=None, norepo=False):
256 256 if norepo:
257 257 commands.norepo += b' %s' % b' '.join(parsealiases(name))
258 258 return _command(name, list(options), synopsis)
259 259
260 260
261 261 else:
262 262 # for "historical portability":
263 263 # define "@command" annotation locally, because cmdutil.command
264 264 # has been available since 1.9 (or 2daa5179e73f)
265 265 def command(name, options=(), synopsis=None, norepo=False):
266 266 def decorator(func):
267 267 if synopsis:
268 268 cmdtable[name] = func, list(options), synopsis
269 269 else:
270 270 cmdtable[name] = func, list(options)
271 271 if norepo:
272 272 commands.norepo += b' %s' % b' '.join(parsealiases(name))
273 273 return func
274 274
275 275 return decorator
276 276
277 277
278 278 try:
279 279 import mercurial.registrar
280 280 import mercurial.configitems
281 281
282 282 configtable = {}
283 283 configitem = mercurial.registrar.configitem(configtable)
284 284 configitem(
285 285 b'perf',
286 286 b'presleep',
287 287 default=mercurial.configitems.dynamicdefault,
288 288 experimental=True,
289 289 )
290 290 configitem(
291 291 b'perf',
292 292 b'stub',
293 293 default=mercurial.configitems.dynamicdefault,
294 294 experimental=True,
295 295 )
296 296 configitem(
297 297 b'perf',
298 298 b'parentscount',
299 299 default=mercurial.configitems.dynamicdefault,
300 300 experimental=True,
301 301 )
302 302 configitem(
303 303 b'perf',
304 304 b'all-timing',
305 305 default=mercurial.configitems.dynamicdefault,
306 306 experimental=True,
307 307 )
308 308 configitem(
309 309 b'perf',
310 310 b'pre-run',
311 311 default=mercurial.configitems.dynamicdefault,
312 312 )
313 313 configitem(
314 314 b'perf',
315 315 b'profile-benchmark',
316 316 default=mercurial.configitems.dynamicdefault,
317 317 )
318 318 configitem(
319 319 b'perf',
320 320 b'run-limits',
321 321 default=mercurial.configitems.dynamicdefault,
322 322 experimental=True,
323 323 )
324 324 except (ImportError, AttributeError):
325 325 pass
326 326 except TypeError:
327 327 # compatibility fix for a11fd395e83f
328 328 # hg version: 5.2
329 329 configitem(
330 330 b'perf',
331 331 b'presleep',
332 332 default=mercurial.configitems.dynamicdefault,
333 333 )
334 334 configitem(
335 335 b'perf',
336 336 b'stub',
337 337 default=mercurial.configitems.dynamicdefault,
338 338 )
339 339 configitem(
340 340 b'perf',
341 341 b'parentscount',
342 342 default=mercurial.configitems.dynamicdefault,
343 343 )
344 344 configitem(
345 345 b'perf',
346 346 b'all-timing',
347 347 default=mercurial.configitems.dynamicdefault,
348 348 )
349 349 configitem(
350 350 b'perf',
351 351 b'pre-run',
352 352 default=mercurial.configitems.dynamicdefault,
353 353 )
354 354 configitem(
355 355 b'perf',
356 356 b'profile-benchmark',
357 357 default=mercurial.configitems.dynamicdefault,
358 358 )
359 359 configitem(
360 360 b'perf',
361 361 b'run-limits',
362 362 default=mercurial.configitems.dynamicdefault,
363 363 )
364 364
365 365
366 366 def getlen(ui):
367 367 if ui.configbool(b"perf", b"stub", False):
368 368 return lambda x: 1
369 369 return len
370 370
371 371
372 372 class noop:
373 373 """dummy context manager"""
374 374
375 375 def __enter__(self):
376 376 pass
377 377
378 378 def __exit__(self, *args):
379 379 pass
380 380
381 381
382 382 NOOPCTX = noop()
383 383
384 384
385 385 def gettimer(ui, opts=None):
386 386 """return a timer function and formatter: (timer, formatter)
387 387
388 388 This function exists to gather the creation of formatter in a single
389 389 place instead of duplicating it in all performance commands."""
390 390
391 391 # enforce an idle period before execution to counteract power management
392 392 # experimental config: perf.presleep
393 393 time.sleep(getint(ui, b"perf", b"presleep", 1))
394 394
395 395 if opts is None:
396 396 opts = {}
397 397 # redirect all to stderr unless buffer api is in use
398 398 if not ui._buffers:
399 399 ui = ui.copy()
400 400 uifout = safeattrsetter(ui, b'fout', ignoremissing=True)
401 401 if uifout:
402 402 # for "historical portability":
403 403 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
404 404 uifout.set(ui.ferr)
405 405
406 406 # get a formatter
407 407 uiformatter = getattr(ui, 'formatter', None)
408 408 if uiformatter:
409 409 fm = uiformatter(b'perf', opts)
410 410 else:
411 411 # for "historical portability":
412 412 # define formatter locally, because ui.formatter has been
413 413 # available since 2.2 (or ae5f92e154d3)
414 414 from mercurial import node
415 415
416 416 class defaultformatter:
417 417 """Minimized composition of baseformatter and plainformatter"""
418 418
419 419 def __init__(self, ui, topic, opts):
420 420 self._ui = ui
421 421 if ui.debugflag:
422 422 self.hexfunc = node.hex
423 423 else:
424 424 self.hexfunc = node.short
425 425
426 426 def __nonzero__(self):
427 427 return False
428 428
429 429 __bool__ = __nonzero__
430 430
431 431 def startitem(self):
432 432 pass
433 433
434 434 def data(self, **data):
435 435 pass
436 436
437 437 def write(self, fields, deftext, *fielddata, **opts):
438 438 self._ui.write(deftext % fielddata, **opts)
439 439
440 440 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
441 441 if cond:
442 442 self._ui.write(deftext % fielddata, **opts)
443 443
444 444 def plain(self, text, **opts):
445 445 self._ui.write(text, **opts)
446 446
447 447 def end(self):
448 448 pass
449 449
450 450 fm = defaultformatter(ui, b'perf', opts)
451 451
452 452 # stub function, runs code only once instead of in a loop
453 453 # experimental config: perf.stub
454 454 if ui.configbool(b"perf", b"stub", False):
455 455 return functools.partial(stub_timer, fm), fm
456 456
457 457 # experimental config: perf.all-timing
458 458 displayall = ui.configbool(b"perf", b"all-timing", False)
459 459
460 460 # experimental config: perf.run-limits
461 461 limitspec = ui.configlist(b"perf", b"run-limits", [])
462 462 limits = []
463 463 for item in limitspec:
464 464 parts = item.split(b'-', 1)
465 465 if len(parts) < 2:
466 466 ui.warn((b'malformatted run limit entry, missing "-": %s\n' % item))
467 467 continue
468 468 try:
469 469 time_limit = float(_sysstr(parts[0]))
470 470 except ValueError as e:
471 471 ui.warn(
472 472 (
473 473 b'malformatted run limit entry, %s: %s\n'
474 474 % (_bytestr(e), item)
475 475 )
476 476 )
477 477 continue
478 478 try:
479 479 run_limit = int(_sysstr(parts[1]))
480 480 except ValueError as e:
481 481 ui.warn(
482 482 (
483 483 b'malformatted run limit entry, %s: %s\n'
484 484 % (_bytestr(e), item)
485 485 )
486 486 )
487 487 continue
488 488 limits.append((time_limit, run_limit))
489 489 if not limits:
490 490 limits = DEFAULTLIMITS
491 491
492 492 profiler = None
493 493 if profiling is not None:
494 494 if ui.configbool(b"perf", b"profile-benchmark", False):
495 495 profiler = profiling.profile(ui)
496 496
497 497 prerun = getint(ui, b"perf", b"pre-run", 0)
498 498 t = functools.partial(
499 499 _timer,
500 500 fm,
501 501 displayall=displayall,
502 502 limits=limits,
503 503 prerun=prerun,
504 504 profiler=profiler,
505 505 )
506 506 return t, fm
507 507
508 508
509 509 def stub_timer(fm, func, setup=None, title=None):
510 510 if setup is not None:
511 511 setup()
512 512 func()
513 513
514 514
515 515 @contextlib.contextmanager
516 516 def timeone():
517 517 r = []
518 518 ostart = os.times()
519 519 cstart = util.timer()
520 520 yield r
521 521 cstop = util.timer()
522 522 ostop = os.times()
523 523 a, b = ostart, ostop
524 524 r.append((cstop - cstart, b[0] - a[0], b[1] - a[1]))
525 525
526 526
527 527 # list of stop condition (elapsed time, minimal run count)
528 528 DEFAULTLIMITS = (
529 529 (3.0, 100),
530 530 (10.0, 3),
531 531 )
532 532
533 533
534 534 def _timer(
535 535 fm,
536 536 func,
537 537 setup=None,
538 538 title=None,
539 539 displayall=False,
540 540 limits=DEFAULTLIMITS,
541 541 prerun=0,
542 542 profiler=None,
543 543 ):
544 544 gc.collect()
545 545 results = []
546 546 begin = util.timer()
547 547 count = 0
548 548 if profiler is None:
549 549 profiler = NOOPCTX
550 550 for i in range(prerun):
551 551 if setup is not None:
552 552 setup()
553 553 func()
554 554 keepgoing = True
555 555 while keepgoing:
556 556 if setup is not None:
557 557 setup()
558 558 with profiler:
559 559 with timeone() as item:
560 560 r = func()
561 561 profiler = NOOPCTX
562 562 count += 1
563 563 results.append(item[0])
564 564 cstop = util.timer()
565 565 # Look for a stop condition.
566 566 elapsed = cstop - begin
567 567 for t, mincount in limits:
568 568 if elapsed >= t and count >= mincount:
569 569 keepgoing = False
570 570 break
571 571
572 572 formatone(fm, results, title=title, result=r, displayall=displayall)
573 573
574 574
575 575 def formatone(fm, timings, title=None, result=None, displayall=False):
576 576
577 577 count = len(timings)
578 578
579 579 fm.startitem()
580 580
581 581 if title:
582 582 fm.write(b'title', b'! %s\n', title)
583 583 if result:
584 584 fm.write(b'result', b'! result: %s\n', result)
585 585
586 586 def display(role, entry):
587 587 prefix = b''
588 588 if role != b'best':
589 589 prefix = b'%s.' % role
590 590 fm.plain(b'!')
591 591 fm.write(prefix + b'wall', b' wall %f', entry[0])
592 592 fm.write(prefix + b'comb', b' comb %f', entry[1] + entry[2])
593 593 fm.write(prefix + b'user', b' user %f', entry[1])
594 594 fm.write(prefix + b'sys', b' sys %f', entry[2])
595 595 fm.write(prefix + b'count', b' (%s of %%d)' % role, count)
596 596 fm.plain(b'\n')
597 597
598 598 timings.sort()
599 599 min_val = timings[0]
600 600 display(b'best', min_val)
601 601 if displayall:
602 602 max_val = timings[-1]
603 603 display(b'max', max_val)
604 604 avg = tuple([sum(x) / count for x in zip(*timings)])
605 605 display(b'avg', avg)
606 606 median = timings[len(timings) // 2]
607 607 display(b'median', median)
608 608
609 609
610 610 # utilities for historical portability
611 611
612 612
613 613 def getint(ui, section, name, default):
614 614 # for "historical portability":
615 615 # ui.configint has been available since 1.9 (or fa2b596db182)
616 616 v = ui.config(section, name, None)
617 617 if v is None:
618 618 return default
619 619 try:
620 620 return int(v)
621 621 except ValueError:
622 622 raise error.ConfigError(
623 623 b"%s.%s is not an integer ('%s')" % (section, name, v)
624 624 )
625 625
626 626
627 627 def safeattrsetter(obj, name, ignoremissing=False):
628 628 """Ensure that 'obj' has 'name' attribute before subsequent setattr
629 629
630 630 This function is aborted, if 'obj' doesn't have 'name' attribute
631 631 at runtime. This avoids overlooking removal of an attribute, which
632 632 breaks assumption of performance measurement, in the future.
633 633
634 634 This function returns the object to (1) assign a new value, and
635 635 (2) restore an original value to the attribute.
636 636
637 637 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
638 638 abortion, and this function returns None. This is useful to
639 639 examine an attribute, which isn't ensured in all Mercurial
640 640 versions.
641 641 """
642 642 if not util.safehasattr(obj, name):
643 643 if ignoremissing:
644 644 return None
645 645 raise error.Abort(
646 646 (
647 647 b"missing attribute %s of %s might break assumption"
648 648 b" of performance measurement"
649 649 )
650 650 % (name, obj)
651 651 )
652 652
653 653 origvalue = getattr(obj, _sysstr(name))
654 654
655 655 class attrutil:
656 656 def set(self, newvalue):
657 657 setattr(obj, _sysstr(name), newvalue)
658 658
659 659 def restore(self):
660 660 setattr(obj, _sysstr(name), origvalue)
661 661
662 662 return attrutil()
663 663
664 664
665 665 # utilities to examine each internal API changes
666 666
667 667
668 668 def getbranchmapsubsettable():
669 669 # for "historical portability":
670 670 # subsettable is defined in:
671 671 # - branchmap since 2.9 (or 175c6fd8cacc)
672 672 # - repoview since 2.5 (or 59a9f18d4587)
673 673 # - repoviewutil since 5.0
674 674 for mod in (branchmap, repoview, repoviewutil):
675 675 subsettable = getattr(mod, 'subsettable', None)
676 676 if subsettable:
677 677 return subsettable
678 678
679 679 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
680 680 # branchmap and repoview modules exist, but subsettable attribute
681 681 # doesn't)
682 682 raise error.Abort(
683 683 b"perfbranchmap not available with this Mercurial",
684 684 hint=b"use 2.5 or later",
685 685 )
686 686
687 687
688 688 def getsvfs(repo):
689 689 """Return appropriate object to access files under .hg/store"""
690 690 # for "historical portability":
691 691 # repo.svfs has been available since 2.3 (or 7034365089bf)
692 692 svfs = getattr(repo, 'svfs', None)
693 693 if svfs:
694 694 return svfs
695 695 else:
696 696 return getattr(repo, 'sopener')
697 697
698 698
699 699 def getvfs(repo):
700 700 """Return appropriate object to access files under .hg"""
701 701 # for "historical portability":
702 702 # repo.vfs has been available since 2.3 (or 7034365089bf)
703 703 vfs = getattr(repo, 'vfs', None)
704 704 if vfs:
705 705 return vfs
706 706 else:
707 707 return getattr(repo, 'opener')
708 708
709 709
710 710 def repocleartagscachefunc(repo):
711 711 """Return the function to clear tags cache according to repo internal API"""
712 712 if util.safehasattr(repo, b'_tagscache'): # since 2.0 (or 9dca7653b525)
713 713 # in this case, setattr(repo, '_tagscache', None) or so isn't
714 714 # correct way to clear tags cache, because existing code paths
715 715 # expect _tagscache to be a structured object.
716 716 def clearcache():
717 717 # _tagscache has been filteredpropertycache since 2.5 (or
718 718 # 98c867ac1330), and delattr() can't work in such case
719 719 if '_tagscache' in vars(repo):
720 720 del repo.__dict__['_tagscache']
721 721
722 722 return clearcache
723 723
724 724 repotags = safeattrsetter(repo, b'_tags', ignoremissing=True)
725 725 if repotags: # since 1.4 (or 5614a628d173)
726 726 return lambda: repotags.set(None)
727 727
728 728 repotagscache = safeattrsetter(repo, b'tagscache', ignoremissing=True)
729 729 if repotagscache: # since 0.6 (or d7df759d0e97)
730 730 return lambda: repotagscache.set(None)
731 731
732 732 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
733 733 # this point, but it isn't so problematic, because:
734 734 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
735 735 # in perftags() causes failure soon
736 736 # - perf.py itself has been available since 1.1 (or eb240755386d)
737 737 raise error.Abort(b"tags API of this hg command is unknown")
738 738
739 739
740 740 # utilities to clear cache
741 741
742 742
743 743 def clearfilecache(obj, attrname):
744 744 unfiltered = getattr(obj, 'unfiltered', None)
745 745 if unfiltered is not None:
746 746 obj = obj.unfiltered()
747 747 if attrname in vars(obj):
748 748 delattr(obj, attrname)
749 749 obj._filecache.pop(attrname, None)
750 750
751 751
752 752 def clearchangelog(repo):
753 753 if repo is not repo.unfiltered():
754 754 object.__setattr__(repo, '_clcachekey', None)
755 755 object.__setattr__(repo, '_clcache', None)
756 756 clearfilecache(repo.unfiltered(), 'changelog')
757 757
758 758
759 759 # perf commands
760 760
761 761
762 762 @command(b'perf::walk|perfwalk', formatteropts)
763 763 def perfwalk(ui, repo, *pats, **opts):
764 764 opts = _byteskwargs(opts)
765 765 timer, fm = gettimer(ui, opts)
766 766 m = scmutil.match(repo[None], pats, {})
767 767 timer(
768 768 lambda: len(
769 769 list(
770 770 repo.dirstate.walk(m, subrepos=[], unknown=True, ignored=False)
771 771 )
772 772 )
773 773 )
774 774 fm.end()
775 775
776 776
777 777 @command(b'perf::annotate|perfannotate', formatteropts)
778 778 def perfannotate(ui, repo, f, **opts):
779 779 opts = _byteskwargs(opts)
780 780 timer, fm = gettimer(ui, opts)
781 781 fc = repo[b'.'][f]
782 782 timer(lambda: len(fc.annotate(True)))
783 783 fm.end()
784 784
785 785
786 786 @command(
787 787 b'perf::status|perfstatus',
788 788 [
789 789 (b'u', b'unknown', False, b'ask status to look for unknown files'),
790 790 (b'', b'dirstate', False, b'benchmark the internal dirstate call'),
791 791 ]
792 792 + formatteropts,
793 793 )
794 794 def perfstatus(ui, repo, **opts):
795 795 """benchmark the performance of a single status call
796 796
797 797 The repository data are preserved between each call.
798 798
799 799 By default, only the status of the tracked file are requested. If
800 800 `--unknown` is passed, the "unknown" files are also tracked.
801 801 """
802 802 opts = _byteskwargs(opts)
803 803 # m = match.always(repo.root, repo.getcwd())
804 804 # timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
805 805 # False))))
806 806 timer, fm = gettimer(ui, opts)
807 807 if opts[b'dirstate']:
808 808 dirstate = repo.dirstate
809 809 m = scmutil.matchall(repo)
810 810 unknown = opts[b'unknown']
811 811
812 812 def status_dirstate():
813 813 s = dirstate.status(
814 814 m, subrepos=[], ignored=False, clean=False, unknown=unknown
815 815 )
816 816 sum(map(bool, s))
817 817
818 818 timer(status_dirstate)
819 819 else:
820 820 timer(lambda: sum(map(len, repo.status(unknown=opts[b'unknown']))))
821 821 fm.end()
822 822
823 823
824 824 @command(b'perf::addremove|perfaddremove', formatteropts)
825 825 def perfaddremove(ui, repo, **opts):
826 826 opts = _byteskwargs(opts)
827 827 timer, fm = gettimer(ui, opts)
828 828 try:
829 829 oldquiet = repo.ui.quiet
830 830 repo.ui.quiet = True
831 831 matcher = scmutil.match(repo[None])
832 832 opts[b'dry_run'] = True
833 833 if 'uipathfn' in getargspec(scmutil.addremove).args:
834 834 uipathfn = scmutil.getuipathfn(repo)
835 835 timer(lambda: scmutil.addremove(repo, matcher, b"", uipathfn, opts))
836 836 else:
837 837 timer(lambda: scmutil.addremove(repo, matcher, b"", opts))
838 838 finally:
839 839 repo.ui.quiet = oldquiet
840 840 fm.end()
841 841
842 842
843 843 def clearcaches(cl):
844 844 # behave somewhat consistently across internal API changes
845 845 if util.safehasattr(cl, b'clearcaches'):
846 846 cl.clearcaches()
847 847 elif util.safehasattr(cl, b'_nodecache'):
848 848 # <= hg-5.2
849 849 from mercurial.node import nullid, nullrev
850 850
851 851 cl._nodecache = {nullid: nullrev}
852 852 cl._nodepos = None
853 853
854 854
855 855 @command(b'perf::heads|perfheads', formatteropts)
856 856 def perfheads(ui, repo, **opts):
857 857 """benchmark the computation of a changelog heads"""
858 858 opts = _byteskwargs(opts)
859 859 timer, fm = gettimer(ui, opts)
860 860 cl = repo.changelog
861 861
862 862 def s():
863 863 clearcaches(cl)
864 864
865 865 def d():
866 866 len(cl.headrevs())
867 867
868 868 timer(d, setup=s)
869 869 fm.end()
870 870
871 871
872 872 @command(
873 873 b'perf::tags|perftags',
874 874 formatteropts
875 875 + [
876 876 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
877 877 ],
878 878 )
879 879 def perftags(ui, repo, **opts):
880 880 opts = _byteskwargs(opts)
881 881 timer, fm = gettimer(ui, opts)
882 882 repocleartagscache = repocleartagscachefunc(repo)
883 883 clearrevlogs = opts[b'clear_revlogs']
884 884
885 885 def s():
886 886 if clearrevlogs:
887 887 clearchangelog(repo)
888 888 clearfilecache(repo.unfiltered(), 'manifest')
889 889 repocleartagscache()
890 890
891 891 def t():
892 892 return len(repo.tags())
893 893
894 894 timer(t, setup=s)
895 895 fm.end()
896 896
897 897
898 898 @command(b'perf::ancestors|perfancestors', formatteropts)
899 899 def perfancestors(ui, repo, **opts):
900 900 opts = _byteskwargs(opts)
901 901 timer, fm = gettimer(ui, opts)
902 902 heads = repo.changelog.headrevs()
903 903
904 904 def d():
905 905 for a in repo.changelog.ancestors(heads):
906 906 pass
907 907
908 908 timer(d)
909 909 fm.end()
910 910
911 911
912 912 @command(b'perf::ancestorset|perfancestorset', formatteropts)
913 913 def perfancestorset(ui, repo, revset, **opts):
914 914 opts = _byteskwargs(opts)
915 915 timer, fm = gettimer(ui, opts)
916 916 revs = repo.revs(revset)
917 917 heads = repo.changelog.headrevs()
918 918
919 919 def d():
920 920 s = repo.changelog.ancestors(heads)
921 921 for rev in revs:
922 922 rev in s
923 923
924 924 timer(d)
925 925 fm.end()
926 926
927 927
928 928 @command(b'perf::discovery|perfdiscovery', formatteropts, b'PATH')
929 929 def perfdiscovery(ui, repo, path, **opts):
930 930 """benchmark discovery between local repo and the peer at given path"""
931 931 repos = [repo, None]
932 932 timer, fm = gettimer(ui, opts)
933 933
934 934 try:
935 935 from mercurial.utils.urlutil import get_unique_pull_path
936 936
937 937 path = get_unique_pull_path(b'perfdiscovery', repo, ui, path)[0]
938 938 except ImportError:
939 939 path = ui.expandpath(path)
940 940
941 941 def s():
942 942 repos[1] = hg.peer(ui, opts, path)
943 943
944 944 def d():
945 945 setdiscovery.findcommonheads(ui, *repos)
946 946
947 947 timer(d, setup=s)
948 948 fm.end()
949 949
950 950
951 951 @command(
952 952 b'perf::bookmarks|perfbookmarks',
953 953 formatteropts
954 954 + [
955 955 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
956 956 ],
957 957 )
958 958 def perfbookmarks(ui, repo, **opts):
959 959 """benchmark parsing bookmarks from disk to memory"""
960 960 opts = _byteskwargs(opts)
961 961 timer, fm = gettimer(ui, opts)
962 962
963 963 clearrevlogs = opts[b'clear_revlogs']
964 964
965 965 def s():
966 966 if clearrevlogs:
967 967 clearchangelog(repo)
968 968 clearfilecache(repo, b'_bookmarks')
969 969
970 970 def d():
971 971 repo._bookmarks
972 972
973 973 timer(d, setup=s)
974 974 fm.end()
975 975
976 976
977 977 @command(
978 978 b'perf::bundle',
979 979 [
980 980 (
981 981 b'r',
982 982 b'rev',
983 983 [],
984 984 b'changesets to bundle',
985 985 b'REV',
986 986 ),
987 (
988 b't',
989 b'type',
990 b'none',
991 b'bundlespec to use (see `hg help bundlespec`)',
992 b'TYPE',
993 ),
987 994 ]
988 995 + formatteropts,
989 996 b'REVS',
990 997 )
991 998 def perfbundle(ui, repo, *revs, **opts):
992 999 """benchmark the creation of a bundle from a repository
993 1000
994 For now, this create a `none-v1` bundle.
1001 For now, this only supports "none" compression.
995 1002 """
996 1003 from mercurial import bundlecaches
997 1004 from mercurial import discovery
998 1005 from mercurial import bundle2
999 1006
1000 1007 opts = _byteskwargs(opts)
1001 1008 timer, fm = gettimer(ui, opts)
1002 1009
1003 1010 cl = repo.changelog
1004 1011 revs = list(revs)
1005 1012 revs.extend(opts.get(b'rev', ()))
1006 1013 revs = scmutil.revrange(repo, revs)
1007 1014 if not revs:
1008 1015 raise error.Abort(b"not revision specified")
1009 1016 # make it a consistent set (ie: without topological gaps)
1010 1017 old_len = len(revs)
1011 1018 revs = list(repo.revs(b"%ld::%ld", revs, revs))
1012 1019 if old_len != len(revs):
1013 1020 new_count = len(revs) - old_len
1014 1021 msg = b"add %d new revisions to make it a consistent set\n"
1015 1022 ui.write_err(msg % new_count)
1016 1023
1017 1024 targets = [cl.node(r) for r in repo.revs(b"heads(::%ld)", revs)]
1018 1025 bases = [cl.node(r) for r in repo.revs(b"heads(::%ld - %ld)", revs, revs)]
1019 1026 outgoing = discovery.outgoing(repo, bases, targets)
1020 1027
1021 bundlespec = bundlecaches.parsebundlespec(
1022 repo, b"none", strict=False
1023 )
1024
1025 bversion = b'HG10' + bundlespec.wirecompression
1028 bundle_spec = opts.get(b'type')
1029
1030 bundle_spec = bundlecaches.parsebundlespec(repo, bundle_spec, strict=False)
1031
1032 cgversion = bundle_spec.params[b"cg.version"]
1033 if cgversion not in changegroup.supportedoutgoingversions(repo):
1034 err = b"repository does not support bundle version %s"
1035 raise error.Abort(err % cgversion)
1036
1037 if cgversion == b'01': # bundle1
1038 bversion = b'HG10' + bundle_spec.wirecompression
1039 bcompression = None
1040 elif cgversion in (b'02', b'03'):
1041 bversion = b'HG20'
1042 bcompression = bundle_spec.wirecompression
1043 else:
1044 err = b'perf::bundle: unexpected changegroup version %s'
1045 raise error.ProgrammingError(err % cgversion)
1046
1047 if bcompression is None:
1048 bcompression = b'UN'
1049
1050 if bcompression != b'UN':
1051 err = b'perf::bundle: compression currently unsupported: %s'
1052 raise error.ProgrammingError(err % bcompression)
1026 1053
1027 1054 def do_bundle():
1028 1055 bundle2.writenewbundle(
1029 1056 ui,
1030 1057 repo,
1031 1058 b'perf::bundle',
1032 1059 os.devnull,
1033 1060 bversion,
1034 1061 outgoing,
1035 {},
1062 bundle_spec.params,
1036 1063 )
1037 1064
1038 1065 timer(do_bundle)
1039 1066 fm.end()
1040 1067
1041 1068
1042 1069 @command(b'perf::bundleread|perfbundleread', formatteropts, b'BUNDLE')
1043 1070 def perfbundleread(ui, repo, bundlepath, **opts):
1044 1071 """Benchmark reading of bundle files.
1045 1072
1046 1073 This command is meant to isolate the I/O part of bundle reading as
1047 1074 much as possible.
1048 1075 """
1049 1076 from mercurial import (
1050 1077 bundle2,
1051 1078 exchange,
1052 1079 streamclone,
1053 1080 )
1054 1081
1055 1082 opts = _byteskwargs(opts)
1056 1083
1057 1084 def makebench(fn):
1058 1085 def run():
1059 1086 with open(bundlepath, b'rb') as fh:
1060 1087 bundle = exchange.readbundle(ui, fh, bundlepath)
1061 1088 fn(bundle)
1062 1089
1063 1090 return run
1064 1091
1065 1092 def makereadnbytes(size):
1066 1093 def run():
1067 1094 with open(bundlepath, b'rb') as fh:
1068 1095 bundle = exchange.readbundle(ui, fh, bundlepath)
1069 1096 while bundle.read(size):
1070 1097 pass
1071 1098
1072 1099 return run
1073 1100
1074 1101 def makestdioread(size):
1075 1102 def run():
1076 1103 with open(bundlepath, b'rb') as fh:
1077 1104 while fh.read(size):
1078 1105 pass
1079 1106
1080 1107 return run
1081 1108
1082 1109 # bundle1
1083 1110
1084 1111 def deltaiter(bundle):
1085 1112 for delta in bundle.deltaiter():
1086 1113 pass
1087 1114
1088 1115 def iterchunks(bundle):
1089 1116 for chunk in bundle.getchunks():
1090 1117 pass
1091 1118
1092 1119 # bundle2
1093 1120
1094 1121 def forwardchunks(bundle):
1095 1122 for chunk in bundle._forwardchunks():
1096 1123 pass
1097 1124
1098 1125 def iterparts(bundle):
1099 1126 for part in bundle.iterparts():
1100 1127 pass
1101 1128
1102 1129 def iterpartsseekable(bundle):
1103 1130 for part in bundle.iterparts(seekable=True):
1104 1131 pass
1105 1132
1106 1133 def seek(bundle):
1107 1134 for part in bundle.iterparts(seekable=True):
1108 1135 part.seek(0, os.SEEK_END)
1109 1136
1110 1137 def makepartreadnbytes(size):
1111 1138 def run():
1112 1139 with open(bundlepath, b'rb') as fh:
1113 1140 bundle = exchange.readbundle(ui, fh, bundlepath)
1114 1141 for part in bundle.iterparts():
1115 1142 while part.read(size):
1116 1143 pass
1117 1144
1118 1145 return run
1119 1146
1120 1147 benches = [
1121 1148 (makestdioread(8192), b'read(8k)'),
1122 1149 (makestdioread(16384), b'read(16k)'),
1123 1150 (makestdioread(32768), b'read(32k)'),
1124 1151 (makestdioread(131072), b'read(128k)'),
1125 1152 ]
1126 1153
1127 1154 with open(bundlepath, b'rb') as fh:
1128 1155 bundle = exchange.readbundle(ui, fh, bundlepath)
1129 1156
1130 1157 if isinstance(bundle, changegroup.cg1unpacker):
1131 1158 benches.extend(
1132 1159 [
1133 1160 (makebench(deltaiter), b'cg1 deltaiter()'),
1134 1161 (makebench(iterchunks), b'cg1 getchunks()'),
1135 1162 (makereadnbytes(8192), b'cg1 read(8k)'),
1136 1163 (makereadnbytes(16384), b'cg1 read(16k)'),
1137 1164 (makereadnbytes(32768), b'cg1 read(32k)'),
1138 1165 (makereadnbytes(131072), b'cg1 read(128k)'),
1139 1166 ]
1140 1167 )
1141 1168 elif isinstance(bundle, bundle2.unbundle20):
1142 1169 benches.extend(
1143 1170 [
1144 1171 (makebench(forwardchunks), b'bundle2 forwardchunks()'),
1145 1172 (makebench(iterparts), b'bundle2 iterparts()'),
1146 1173 (
1147 1174 makebench(iterpartsseekable),
1148 1175 b'bundle2 iterparts() seekable',
1149 1176 ),
1150 1177 (makebench(seek), b'bundle2 part seek()'),
1151 1178 (makepartreadnbytes(8192), b'bundle2 part read(8k)'),
1152 1179 (makepartreadnbytes(16384), b'bundle2 part read(16k)'),
1153 1180 (makepartreadnbytes(32768), b'bundle2 part read(32k)'),
1154 1181 (makepartreadnbytes(131072), b'bundle2 part read(128k)'),
1155 1182 ]
1156 1183 )
1157 1184 elif isinstance(bundle, streamclone.streamcloneapplier):
1158 1185 raise error.Abort(b'stream clone bundles not supported')
1159 1186 else:
1160 1187 raise error.Abort(b'unhandled bundle type: %s' % type(bundle))
1161 1188
1162 1189 for fn, title in benches:
1163 1190 timer, fm = gettimer(ui, opts)
1164 1191 timer(fn, title=title)
1165 1192 fm.end()
1166 1193
1167 1194
1168 1195 @command(
1169 1196 b'perf::changegroupchangelog|perfchangegroupchangelog',
1170 1197 formatteropts
1171 1198 + [
1172 1199 (b'', b'cgversion', b'02', b'changegroup version'),
1173 1200 (b'r', b'rev', b'', b'revisions to add to changegroup'),
1174 1201 ],
1175 1202 )
1176 1203 def perfchangegroupchangelog(ui, repo, cgversion=b'02', rev=None, **opts):
1177 1204 """Benchmark producing a changelog group for a changegroup.
1178 1205
1179 1206 This measures the time spent processing the changelog during a
1180 1207 bundle operation. This occurs during `hg bundle` and on a server
1181 1208 processing a `getbundle` wire protocol request (handles clones
1182 1209 and pull requests).
1183 1210
1184 1211 By default, all revisions are added to the changegroup.
1185 1212 """
1186 1213 opts = _byteskwargs(opts)
1187 1214 cl = repo.changelog
1188 1215 nodes = [cl.lookup(r) for r in repo.revs(rev or b'all()')]
1189 1216 bundler = changegroup.getbundler(cgversion, repo)
1190 1217
1191 1218 def d():
1192 1219 state, chunks = bundler._generatechangelog(cl, nodes)
1193 1220 for chunk in chunks:
1194 1221 pass
1195 1222
1196 1223 timer, fm = gettimer(ui, opts)
1197 1224
1198 1225 # Terminal printing can interfere with timing. So disable it.
1199 1226 with ui.configoverride({(b'progress', b'disable'): True}):
1200 1227 timer(d)
1201 1228
1202 1229 fm.end()
1203 1230
1204 1231
1205 1232 @command(b'perf::dirs|perfdirs', formatteropts)
1206 1233 def perfdirs(ui, repo, **opts):
1207 1234 opts = _byteskwargs(opts)
1208 1235 timer, fm = gettimer(ui, opts)
1209 1236 dirstate = repo.dirstate
1210 1237 b'a' in dirstate
1211 1238
1212 1239 def d():
1213 1240 dirstate.hasdir(b'a')
1214 1241 try:
1215 1242 del dirstate._map._dirs
1216 1243 except AttributeError:
1217 1244 pass
1218 1245
1219 1246 timer(d)
1220 1247 fm.end()
1221 1248
1222 1249
1223 1250 @command(
1224 1251 b'perf::dirstate|perfdirstate',
1225 1252 [
1226 1253 (
1227 1254 b'',
1228 1255 b'iteration',
1229 1256 None,
1230 1257 b'benchmark a full iteration for the dirstate',
1231 1258 ),
1232 1259 (
1233 1260 b'',
1234 1261 b'contains',
1235 1262 None,
1236 1263 b'benchmark a large amount of `nf in dirstate` calls',
1237 1264 ),
1238 1265 ]
1239 1266 + formatteropts,
1240 1267 )
1241 1268 def perfdirstate(ui, repo, **opts):
1242 1269 """benchmap the time of various distate operations
1243 1270
1244 1271 By default benchmark the time necessary to load a dirstate from scratch.
1245 1272 The dirstate is loaded to the point were a "contains" request can be
1246 1273 answered.
1247 1274 """
1248 1275 opts = _byteskwargs(opts)
1249 1276 timer, fm = gettimer(ui, opts)
1250 1277 b"a" in repo.dirstate
1251 1278
1252 1279 if opts[b'iteration'] and opts[b'contains']:
1253 1280 msg = b'only specify one of --iteration or --contains'
1254 1281 raise error.Abort(msg)
1255 1282
1256 1283 if opts[b'iteration']:
1257 1284 setup = None
1258 1285 dirstate = repo.dirstate
1259 1286
1260 1287 def d():
1261 1288 for f in dirstate:
1262 1289 pass
1263 1290
1264 1291 elif opts[b'contains']:
1265 1292 setup = None
1266 1293 dirstate = repo.dirstate
1267 1294 allfiles = list(dirstate)
1268 1295 # also add file path that will be "missing" from the dirstate
1269 1296 allfiles.extend([f[::-1] for f in allfiles])
1270 1297
1271 1298 def d():
1272 1299 for f in allfiles:
1273 1300 f in dirstate
1274 1301
1275 1302 else:
1276 1303
1277 1304 def setup():
1278 1305 repo.dirstate.invalidate()
1279 1306
1280 1307 def d():
1281 1308 b"a" in repo.dirstate
1282 1309
1283 1310 timer(d, setup=setup)
1284 1311 fm.end()
1285 1312
1286 1313
1287 1314 @command(b'perf::dirstatedirs|perfdirstatedirs', formatteropts)
1288 1315 def perfdirstatedirs(ui, repo, **opts):
1289 1316 """benchmap a 'dirstate.hasdir' call from an empty `dirs` cache"""
1290 1317 opts = _byteskwargs(opts)
1291 1318 timer, fm = gettimer(ui, opts)
1292 1319 repo.dirstate.hasdir(b"a")
1293 1320
1294 1321 def setup():
1295 1322 try:
1296 1323 del repo.dirstate._map._dirs
1297 1324 except AttributeError:
1298 1325 pass
1299 1326
1300 1327 def d():
1301 1328 repo.dirstate.hasdir(b"a")
1302 1329
1303 1330 timer(d, setup=setup)
1304 1331 fm.end()
1305 1332
1306 1333
1307 1334 @command(b'perf::dirstatefoldmap|perfdirstatefoldmap', formatteropts)
1308 1335 def perfdirstatefoldmap(ui, repo, **opts):
1309 1336 """benchmap a `dirstate._map.filefoldmap.get()` request
1310 1337
1311 1338 The dirstate filefoldmap cache is dropped between every request.
1312 1339 """
1313 1340 opts = _byteskwargs(opts)
1314 1341 timer, fm = gettimer(ui, opts)
1315 1342 dirstate = repo.dirstate
1316 1343 dirstate._map.filefoldmap.get(b'a')
1317 1344
1318 1345 def setup():
1319 1346 del dirstate._map.filefoldmap
1320 1347
1321 1348 def d():
1322 1349 dirstate._map.filefoldmap.get(b'a')
1323 1350
1324 1351 timer(d, setup=setup)
1325 1352 fm.end()
1326 1353
1327 1354
1328 1355 @command(b'perf::dirfoldmap|perfdirfoldmap', formatteropts)
1329 1356 def perfdirfoldmap(ui, repo, **opts):
1330 1357 """benchmap a `dirstate._map.dirfoldmap.get()` request
1331 1358
1332 1359 The dirstate dirfoldmap cache is dropped between every request.
1333 1360 """
1334 1361 opts = _byteskwargs(opts)
1335 1362 timer, fm = gettimer(ui, opts)
1336 1363 dirstate = repo.dirstate
1337 1364 dirstate._map.dirfoldmap.get(b'a')
1338 1365
1339 1366 def setup():
1340 1367 del dirstate._map.dirfoldmap
1341 1368 try:
1342 1369 del dirstate._map._dirs
1343 1370 except AttributeError:
1344 1371 pass
1345 1372
1346 1373 def d():
1347 1374 dirstate._map.dirfoldmap.get(b'a')
1348 1375
1349 1376 timer(d, setup=setup)
1350 1377 fm.end()
1351 1378
1352 1379
1353 1380 @command(b'perf::dirstatewrite|perfdirstatewrite', formatteropts)
1354 1381 def perfdirstatewrite(ui, repo, **opts):
1355 1382 """benchmap the time it take to write a dirstate on disk"""
1356 1383 opts = _byteskwargs(opts)
1357 1384 timer, fm = gettimer(ui, opts)
1358 1385 ds = repo.dirstate
1359 1386 b"a" in ds
1360 1387
1361 1388 def setup():
1362 1389 ds._dirty = True
1363 1390
1364 1391 def d():
1365 1392 ds.write(repo.currenttransaction())
1366 1393
1367 1394 timer(d, setup=setup)
1368 1395 fm.end()
1369 1396
1370 1397
1371 1398 def _getmergerevs(repo, opts):
1372 1399 """parse command argument to return rev involved in merge
1373 1400
1374 1401 input: options dictionnary with `rev`, `from` and `bse`
1375 1402 output: (localctx, otherctx, basectx)
1376 1403 """
1377 1404 if opts[b'from']:
1378 1405 fromrev = scmutil.revsingle(repo, opts[b'from'])
1379 1406 wctx = repo[fromrev]
1380 1407 else:
1381 1408 wctx = repo[None]
1382 1409 # we don't want working dir files to be stat'd in the benchmark, so
1383 1410 # prime that cache
1384 1411 wctx.dirty()
1385 1412 rctx = scmutil.revsingle(repo, opts[b'rev'], opts[b'rev'])
1386 1413 if opts[b'base']:
1387 1414 fromrev = scmutil.revsingle(repo, opts[b'base'])
1388 1415 ancestor = repo[fromrev]
1389 1416 else:
1390 1417 ancestor = wctx.ancestor(rctx)
1391 1418 return (wctx, rctx, ancestor)
1392 1419
1393 1420
1394 1421 @command(
1395 1422 b'perf::mergecalculate|perfmergecalculate',
1396 1423 [
1397 1424 (b'r', b'rev', b'.', b'rev to merge against'),
1398 1425 (b'', b'from', b'', b'rev to merge from'),
1399 1426 (b'', b'base', b'', b'the revision to use as base'),
1400 1427 ]
1401 1428 + formatteropts,
1402 1429 )
1403 1430 def perfmergecalculate(ui, repo, **opts):
1404 1431 opts = _byteskwargs(opts)
1405 1432 timer, fm = gettimer(ui, opts)
1406 1433
1407 1434 wctx, rctx, ancestor = _getmergerevs(repo, opts)
1408 1435
1409 1436 def d():
1410 1437 # acceptremote is True because we don't want prompts in the middle of
1411 1438 # our benchmark
1412 1439 merge.calculateupdates(
1413 1440 repo,
1414 1441 wctx,
1415 1442 rctx,
1416 1443 [ancestor],
1417 1444 branchmerge=False,
1418 1445 force=False,
1419 1446 acceptremote=True,
1420 1447 followcopies=True,
1421 1448 )
1422 1449
1423 1450 timer(d)
1424 1451 fm.end()
1425 1452
1426 1453
1427 1454 @command(
1428 1455 b'perf::mergecopies|perfmergecopies',
1429 1456 [
1430 1457 (b'r', b'rev', b'.', b'rev to merge against'),
1431 1458 (b'', b'from', b'', b'rev to merge from'),
1432 1459 (b'', b'base', b'', b'the revision to use as base'),
1433 1460 ]
1434 1461 + formatteropts,
1435 1462 )
1436 1463 def perfmergecopies(ui, repo, **opts):
1437 1464 """measure runtime of `copies.mergecopies`"""
1438 1465 opts = _byteskwargs(opts)
1439 1466 timer, fm = gettimer(ui, opts)
1440 1467 wctx, rctx, ancestor = _getmergerevs(repo, opts)
1441 1468
1442 1469 def d():
1443 1470 # acceptremote is True because we don't want prompts in the middle of
1444 1471 # our benchmark
1445 1472 copies.mergecopies(repo, wctx, rctx, ancestor)
1446 1473
1447 1474 timer(d)
1448 1475 fm.end()
1449 1476
1450 1477
1451 1478 @command(b'perf::pathcopies|perfpathcopies', [], b"REV REV")
1452 1479 def perfpathcopies(ui, repo, rev1, rev2, **opts):
1453 1480 """benchmark the copy tracing logic"""
1454 1481 opts = _byteskwargs(opts)
1455 1482 timer, fm = gettimer(ui, opts)
1456 1483 ctx1 = scmutil.revsingle(repo, rev1, rev1)
1457 1484 ctx2 = scmutil.revsingle(repo, rev2, rev2)
1458 1485
1459 1486 def d():
1460 1487 copies.pathcopies(ctx1, ctx2)
1461 1488
1462 1489 timer(d)
1463 1490 fm.end()
1464 1491
1465 1492
1466 1493 @command(
1467 1494 b'perf::phases|perfphases',
1468 1495 [
1469 1496 (b'', b'full', False, b'include file reading time too'),
1470 1497 ],
1471 1498 b"",
1472 1499 )
1473 1500 def perfphases(ui, repo, **opts):
1474 1501 """benchmark phasesets computation"""
1475 1502 opts = _byteskwargs(opts)
1476 1503 timer, fm = gettimer(ui, opts)
1477 1504 _phases = repo._phasecache
1478 1505 full = opts.get(b'full')
1479 1506
1480 1507 def d():
1481 1508 phases = _phases
1482 1509 if full:
1483 1510 clearfilecache(repo, b'_phasecache')
1484 1511 phases = repo._phasecache
1485 1512 phases.invalidate()
1486 1513 phases.loadphaserevs(repo)
1487 1514
1488 1515 timer(d)
1489 1516 fm.end()
1490 1517
1491 1518
1492 1519 @command(b'perf::phasesremote|perfphasesremote', [], b"[DEST]")
1493 1520 def perfphasesremote(ui, repo, dest=None, **opts):
1494 1521 """benchmark time needed to analyse phases of the remote server"""
1495 1522 from mercurial.node import bin
1496 1523 from mercurial import (
1497 1524 exchange,
1498 1525 hg,
1499 1526 phases,
1500 1527 )
1501 1528
1502 1529 opts = _byteskwargs(opts)
1503 1530 timer, fm = gettimer(ui, opts)
1504 1531
1505 1532 path = ui.getpath(dest, default=(b'default-push', b'default'))
1506 1533 if not path:
1507 1534 raise error.Abort(
1508 1535 b'default repository not configured!',
1509 1536 hint=b"see 'hg help config.paths'",
1510 1537 )
1511 1538 dest = path.pushloc or path.loc
1512 1539 ui.statusnoi18n(b'analysing phase of %s\n' % util.hidepassword(dest))
1513 1540 other = hg.peer(repo, opts, dest)
1514 1541
1515 1542 # easier to perform discovery through the operation
1516 1543 op = exchange.pushoperation(repo, other)
1517 1544 exchange._pushdiscoverychangeset(op)
1518 1545
1519 1546 remotesubset = op.fallbackheads
1520 1547
1521 1548 with other.commandexecutor() as e:
1522 1549 remotephases = e.callcommand(
1523 1550 b'listkeys', {b'namespace': b'phases'}
1524 1551 ).result()
1525 1552 del other
1526 1553 publishing = remotephases.get(b'publishing', False)
1527 1554 if publishing:
1528 1555 ui.statusnoi18n(b'publishing: yes\n')
1529 1556 else:
1530 1557 ui.statusnoi18n(b'publishing: no\n')
1531 1558
1532 1559 has_node = getattr(repo.changelog.index, 'has_node', None)
1533 1560 if has_node is None:
1534 1561 has_node = repo.changelog.nodemap.__contains__
1535 1562 nonpublishroots = 0
1536 1563 for nhex, phase in remotephases.iteritems():
1537 1564 if nhex == b'publishing': # ignore data related to publish option
1538 1565 continue
1539 1566 node = bin(nhex)
1540 1567 if has_node(node) and int(phase):
1541 1568 nonpublishroots += 1
1542 1569 ui.statusnoi18n(b'number of roots: %d\n' % len(remotephases))
1543 1570 ui.statusnoi18n(b'number of known non public roots: %d\n' % nonpublishroots)
1544 1571
1545 1572 def d():
1546 1573 phases.remotephasessummary(repo, remotesubset, remotephases)
1547 1574
1548 1575 timer(d)
1549 1576 fm.end()
1550 1577
1551 1578
1552 1579 @command(
1553 1580 b'perf::manifest|perfmanifest',
1554 1581 [
1555 1582 (b'm', b'manifest-rev', False, b'Look up a manifest node revision'),
1556 1583 (b'', b'clear-disk', False, b'clear on-disk caches too'),
1557 1584 ]
1558 1585 + formatteropts,
1559 1586 b'REV|NODE',
1560 1587 )
1561 1588 def perfmanifest(ui, repo, rev, manifest_rev=False, clear_disk=False, **opts):
1562 1589 """benchmark the time to read a manifest from disk and return a usable
1563 1590 dict-like object
1564 1591
1565 1592 Manifest caches are cleared before retrieval."""
1566 1593 opts = _byteskwargs(opts)
1567 1594 timer, fm = gettimer(ui, opts)
1568 1595 if not manifest_rev:
1569 1596 ctx = scmutil.revsingle(repo, rev, rev)
1570 1597 t = ctx.manifestnode()
1571 1598 else:
1572 1599 from mercurial.node import bin
1573 1600
1574 1601 if len(rev) == 40:
1575 1602 t = bin(rev)
1576 1603 else:
1577 1604 try:
1578 1605 rev = int(rev)
1579 1606
1580 1607 if util.safehasattr(repo.manifestlog, b'getstorage'):
1581 1608 t = repo.manifestlog.getstorage(b'').node(rev)
1582 1609 else:
1583 1610 t = repo.manifestlog._revlog.lookup(rev)
1584 1611 except ValueError:
1585 1612 raise error.Abort(
1586 1613 b'manifest revision must be integer or full node'
1587 1614 )
1588 1615
1589 1616 def d():
1590 1617 repo.manifestlog.clearcaches(clear_persisted_data=clear_disk)
1591 1618 repo.manifestlog[t].read()
1592 1619
1593 1620 timer(d)
1594 1621 fm.end()
1595 1622
1596 1623
1597 1624 @command(b'perf::changeset|perfchangeset', formatteropts)
1598 1625 def perfchangeset(ui, repo, rev, **opts):
1599 1626 opts = _byteskwargs(opts)
1600 1627 timer, fm = gettimer(ui, opts)
1601 1628 n = scmutil.revsingle(repo, rev).node()
1602 1629
1603 1630 def d():
1604 1631 repo.changelog.read(n)
1605 1632 # repo.changelog._cache = None
1606 1633
1607 1634 timer(d)
1608 1635 fm.end()
1609 1636
1610 1637
1611 1638 @command(b'perf::ignore|perfignore', formatteropts)
1612 1639 def perfignore(ui, repo, **opts):
1613 1640 """benchmark operation related to computing ignore"""
1614 1641 opts = _byteskwargs(opts)
1615 1642 timer, fm = gettimer(ui, opts)
1616 1643 dirstate = repo.dirstate
1617 1644
1618 1645 def setupone():
1619 1646 dirstate.invalidate()
1620 1647 clearfilecache(dirstate, b'_ignore')
1621 1648
1622 1649 def runone():
1623 1650 dirstate._ignore
1624 1651
1625 1652 timer(runone, setup=setupone, title=b"load")
1626 1653 fm.end()
1627 1654
1628 1655
1629 1656 @command(
1630 1657 b'perf::index|perfindex',
1631 1658 [
1632 1659 (b'', b'rev', [], b'revision to be looked up (default tip)'),
1633 1660 (b'', b'no-lookup', None, b'do not revision lookup post creation'),
1634 1661 ]
1635 1662 + formatteropts,
1636 1663 )
1637 1664 def perfindex(ui, repo, **opts):
1638 1665 """benchmark index creation time followed by a lookup
1639 1666
1640 1667 The default is to look `tip` up. Depending on the index implementation,
1641 1668 the revision looked up can matters. For example, an implementation
1642 1669 scanning the index will have a faster lookup time for `--rev tip` than for
1643 1670 `--rev 0`. The number of looked up revisions and their order can also
1644 1671 matters.
1645 1672
1646 1673 Example of useful set to test:
1647 1674
1648 1675 * tip
1649 1676 * 0
1650 1677 * -10:
1651 1678 * :10
1652 1679 * -10: + :10
1653 1680 * :10: + -10:
1654 1681 * -10000:
1655 1682 * -10000: + 0
1656 1683
1657 1684 It is not currently possible to check for lookup of a missing node. For
1658 1685 deeper lookup benchmarking, checkout the `perfnodemap` command."""
1659 1686 import mercurial.revlog
1660 1687
1661 1688 opts = _byteskwargs(opts)
1662 1689 timer, fm = gettimer(ui, opts)
1663 1690 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1664 1691 if opts[b'no_lookup']:
1665 1692 if opts['rev']:
1666 1693 raise error.Abort('--no-lookup and --rev are mutually exclusive')
1667 1694 nodes = []
1668 1695 elif not opts[b'rev']:
1669 1696 nodes = [repo[b"tip"].node()]
1670 1697 else:
1671 1698 revs = scmutil.revrange(repo, opts[b'rev'])
1672 1699 cl = repo.changelog
1673 1700 nodes = [cl.node(r) for r in revs]
1674 1701
1675 1702 unfi = repo.unfiltered()
1676 1703 # find the filecache func directly
1677 1704 # This avoid polluting the benchmark with the filecache logic
1678 1705 makecl = unfi.__class__.changelog.func
1679 1706
1680 1707 def setup():
1681 1708 # probably not necessary, but for good measure
1682 1709 clearchangelog(unfi)
1683 1710
1684 1711 def d():
1685 1712 cl = makecl(unfi)
1686 1713 for n in nodes:
1687 1714 cl.rev(n)
1688 1715
1689 1716 timer(d, setup=setup)
1690 1717 fm.end()
1691 1718
1692 1719
1693 1720 @command(
1694 1721 b'perf::nodemap|perfnodemap',
1695 1722 [
1696 1723 (b'', b'rev', [], b'revision to be looked up (default tip)'),
1697 1724 (b'', b'clear-caches', True, b'clear revlog cache between calls'),
1698 1725 ]
1699 1726 + formatteropts,
1700 1727 )
1701 1728 def perfnodemap(ui, repo, **opts):
1702 1729 """benchmark the time necessary to look up revision from a cold nodemap
1703 1730
1704 1731 Depending on the implementation, the amount and order of revision we look
1705 1732 up can varies. Example of useful set to test:
1706 1733 * tip
1707 1734 * 0
1708 1735 * -10:
1709 1736 * :10
1710 1737 * -10: + :10
1711 1738 * :10: + -10:
1712 1739 * -10000:
1713 1740 * -10000: + 0
1714 1741
1715 1742 The command currently focus on valid binary lookup. Benchmarking for
1716 1743 hexlookup, prefix lookup and missing lookup would also be valuable.
1717 1744 """
1718 1745 import mercurial.revlog
1719 1746
1720 1747 opts = _byteskwargs(opts)
1721 1748 timer, fm = gettimer(ui, opts)
1722 1749 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1723 1750
1724 1751 unfi = repo.unfiltered()
1725 1752 clearcaches = opts[b'clear_caches']
1726 1753 # find the filecache func directly
1727 1754 # This avoid polluting the benchmark with the filecache logic
1728 1755 makecl = unfi.__class__.changelog.func
1729 1756 if not opts[b'rev']:
1730 1757 raise error.Abort(b'use --rev to specify revisions to look up')
1731 1758 revs = scmutil.revrange(repo, opts[b'rev'])
1732 1759 cl = repo.changelog
1733 1760 nodes = [cl.node(r) for r in revs]
1734 1761
1735 1762 # use a list to pass reference to a nodemap from one closure to the next
1736 1763 nodeget = [None]
1737 1764
1738 1765 def setnodeget():
1739 1766 # probably not necessary, but for good measure
1740 1767 clearchangelog(unfi)
1741 1768 cl = makecl(unfi)
1742 1769 if util.safehasattr(cl.index, 'get_rev'):
1743 1770 nodeget[0] = cl.index.get_rev
1744 1771 else:
1745 1772 nodeget[0] = cl.nodemap.get
1746 1773
1747 1774 def d():
1748 1775 get = nodeget[0]
1749 1776 for n in nodes:
1750 1777 get(n)
1751 1778
1752 1779 setup = None
1753 1780 if clearcaches:
1754 1781
1755 1782 def setup():
1756 1783 setnodeget()
1757 1784
1758 1785 else:
1759 1786 setnodeget()
1760 1787 d() # prewarm the data structure
1761 1788 timer(d, setup=setup)
1762 1789 fm.end()
1763 1790
1764 1791
1765 1792 @command(b'perf::startup|perfstartup', formatteropts)
1766 1793 def perfstartup(ui, repo, **opts):
1767 1794 opts = _byteskwargs(opts)
1768 1795 timer, fm = gettimer(ui, opts)
1769 1796
1770 1797 def d():
1771 1798 if os.name != 'nt':
1772 1799 os.system(
1773 1800 b"HGRCPATH= %s version -q > /dev/null" % fsencode(sys.argv[0])
1774 1801 )
1775 1802 else:
1776 1803 os.environ['HGRCPATH'] = r' '
1777 1804 os.system("%s version -q > NUL" % sys.argv[0])
1778 1805
1779 1806 timer(d)
1780 1807 fm.end()
1781 1808
1782 1809
1783 1810 @command(b'perf::parents|perfparents', formatteropts)
1784 1811 def perfparents(ui, repo, **opts):
1785 1812 """benchmark the time necessary to fetch one changeset's parents.
1786 1813
1787 1814 The fetch is done using the `node identifier`, traversing all object layers
1788 1815 from the repository object. The first N revisions will be used for this
1789 1816 benchmark. N is controlled by the ``perf.parentscount`` config option
1790 1817 (default: 1000).
1791 1818 """
1792 1819 opts = _byteskwargs(opts)
1793 1820 timer, fm = gettimer(ui, opts)
1794 1821 # control the number of commits perfparents iterates over
1795 1822 # experimental config: perf.parentscount
1796 1823 count = getint(ui, b"perf", b"parentscount", 1000)
1797 1824 if len(repo.changelog) < count:
1798 1825 raise error.Abort(b"repo needs %d commits for this test" % count)
1799 1826 repo = repo.unfiltered()
1800 1827 nl = [repo.changelog.node(i) for i in _xrange(count)]
1801 1828
1802 1829 def d():
1803 1830 for n in nl:
1804 1831 repo.changelog.parents(n)
1805 1832
1806 1833 timer(d)
1807 1834 fm.end()
1808 1835
1809 1836
1810 1837 @command(b'perf::ctxfiles|perfctxfiles', formatteropts)
1811 1838 def perfctxfiles(ui, repo, x, **opts):
1812 1839 opts = _byteskwargs(opts)
1813 1840 x = int(x)
1814 1841 timer, fm = gettimer(ui, opts)
1815 1842
1816 1843 def d():
1817 1844 len(repo[x].files())
1818 1845
1819 1846 timer(d)
1820 1847 fm.end()
1821 1848
1822 1849
1823 1850 @command(b'perf::rawfiles|perfrawfiles', formatteropts)
1824 1851 def perfrawfiles(ui, repo, x, **opts):
1825 1852 opts = _byteskwargs(opts)
1826 1853 x = int(x)
1827 1854 timer, fm = gettimer(ui, opts)
1828 1855 cl = repo.changelog
1829 1856
1830 1857 def d():
1831 1858 len(cl.read(x)[3])
1832 1859
1833 1860 timer(d)
1834 1861 fm.end()
1835 1862
1836 1863
1837 1864 @command(b'perf::lookup|perflookup', formatteropts)
1838 1865 def perflookup(ui, repo, rev, **opts):
1839 1866 opts = _byteskwargs(opts)
1840 1867 timer, fm = gettimer(ui, opts)
1841 1868 timer(lambda: len(repo.lookup(rev)))
1842 1869 fm.end()
1843 1870
1844 1871
1845 1872 @command(
1846 1873 b'perf::linelogedits|perflinelogedits',
1847 1874 [
1848 1875 (b'n', b'edits', 10000, b'number of edits'),
1849 1876 (b'', b'max-hunk-lines', 10, b'max lines in a hunk'),
1850 1877 ],
1851 1878 norepo=True,
1852 1879 )
1853 1880 def perflinelogedits(ui, **opts):
1854 1881 from mercurial import linelog
1855 1882
1856 1883 opts = _byteskwargs(opts)
1857 1884
1858 1885 edits = opts[b'edits']
1859 1886 maxhunklines = opts[b'max_hunk_lines']
1860 1887
1861 1888 maxb1 = 100000
1862 1889 random.seed(0)
1863 1890 randint = random.randint
1864 1891 currentlines = 0
1865 1892 arglist = []
1866 1893 for rev in _xrange(edits):
1867 1894 a1 = randint(0, currentlines)
1868 1895 a2 = randint(a1, min(currentlines, a1 + maxhunklines))
1869 1896 b1 = randint(0, maxb1)
1870 1897 b2 = randint(b1, b1 + maxhunklines)
1871 1898 currentlines += (b2 - b1) - (a2 - a1)
1872 1899 arglist.append((rev, a1, a2, b1, b2))
1873 1900
1874 1901 def d():
1875 1902 ll = linelog.linelog()
1876 1903 for args in arglist:
1877 1904 ll.replacelines(*args)
1878 1905
1879 1906 timer, fm = gettimer(ui, opts)
1880 1907 timer(d)
1881 1908 fm.end()
1882 1909
1883 1910
1884 1911 @command(b'perf::revrange|perfrevrange', formatteropts)
1885 1912 def perfrevrange(ui, repo, *specs, **opts):
1886 1913 opts = _byteskwargs(opts)
1887 1914 timer, fm = gettimer(ui, opts)
1888 1915 revrange = scmutil.revrange
1889 1916 timer(lambda: len(revrange(repo, specs)))
1890 1917 fm.end()
1891 1918
1892 1919
1893 1920 @command(b'perf::nodelookup|perfnodelookup', formatteropts)
1894 1921 def perfnodelookup(ui, repo, rev, **opts):
1895 1922 opts = _byteskwargs(opts)
1896 1923 timer, fm = gettimer(ui, opts)
1897 1924 import mercurial.revlog
1898 1925
1899 1926 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1900 1927 n = scmutil.revsingle(repo, rev).node()
1901 1928
1902 1929 try:
1903 1930 cl = revlog(getsvfs(repo), radix=b"00changelog")
1904 1931 except TypeError:
1905 1932 cl = revlog(getsvfs(repo), indexfile=b"00changelog.i")
1906 1933
1907 1934 def d():
1908 1935 cl.rev(n)
1909 1936 clearcaches(cl)
1910 1937
1911 1938 timer(d)
1912 1939 fm.end()
1913 1940
1914 1941
1915 1942 @command(
1916 1943 b'perf::log|perflog',
1917 1944 [(b'', b'rename', False, b'ask log to follow renames')] + formatteropts,
1918 1945 )
1919 1946 def perflog(ui, repo, rev=None, **opts):
1920 1947 opts = _byteskwargs(opts)
1921 1948 if rev is None:
1922 1949 rev = []
1923 1950 timer, fm = gettimer(ui, opts)
1924 1951 ui.pushbuffer()
1925 1952 timer(
1926 1953 lambda: commands.log(
1927 1954 ui, repo, rev=rev, date=b'', user=b'', copies=opts.get(b'rename')
1928 1955 )
1929 1956 )
1930 1957 ui.popbuffer()
1931 1958 fm.end()
1932 1959
1933 1960
1934 1961 @command(b'perf::moonwalk|perfmoonwalk', formatteropts)
1935 1962 def perfmoonwalk(ui, repo, **opts):
1936 1963 """benchmark walking the changelog backwards
1937 1964
1938 1965 This also loads the changelog data for each revision in the changelog.
1939 1966 """
1940 1967 opts = _byteskwargs(opts)
1941 1968 timer, fm = gettimer(ui, opts)
1942 1969
1943 1970 def moonwalk():
1944 1971 for i in repo.changelog.revs(start=(len(repo) - 1), stop=-1):
1945 1972 ctx = repo[i]
1946 1973 ctx.branch() # read changelog data (in addition to the index)
1947 1974
1948 1975 timer(moonwalk)
1949 1976 fm.end()
1950 1977
1951 1978
1952 1979 @command(
1953 1980 b'perf::templating|perftemplating',
1954 1981 [
1955 1982 (b'r', b'rev', [], b'revisions to run the template on'),
1956 1983 ]
1957 1984 + formatteropts,
1958 1985 )
1959 1986 def perftemplating(ui, repo, testedtemplate=None, **opts):
1960 1987 """test the rendering time of a given template"""
1961 1988 if makelogtemplater is None:
1962 1989 raise error.Abort(
1963 1990 b"perftemplating not available with this Mercurial",
1964 1991 hint=b"use 4.3 or later",
1965 1992 )
1966 1993
1967 1994 opts = _byteskwargs(opts)
1968 1995
1969 1996 nullui = ui.copy()
1970 1997 nullui.fout = open(os.devnull, 'wb')
1971 1998 nullui.disablepager()
1972 1999 revs = opts.get(b'rev')
1973 2000 if not revs:
1974 2001 revs = [b'all()']
1975 2002 revs = list(scmutil.revrange(repo, revs))
1976 2003
1977 2004 defaulttemplate = (
1978 2005 b'{date|shortdate} [{rev}:{node|short}]'
1979 2006 b' {author|person}: {desc|firstline}\n'
1980 2007 )
1981 2008 if testedtemplate is None:
1982 2009 testedtemplate = defaulttemplate
1983 2010 displayer = makelogtemplater(nullui, repo, testedtemplate)
1984 2011
1985 2012 def format():
1986 2013 for r in revs:
1987 2014 ctx = repo[r]
1988 2015 displayer.show(ctx)
1989 2016 displayer.flush(ctx)
1990 2017
1991 2018 timer, fm = gettimer(ui, opts)
1992 2019 timer(format)
1993 2020 fm.end()
1994 2021
1995 2022
1996 2023 def _displaystats(ui, opts, entries, data):
1997 2024 # use a second formatter because the data are quite different, not sure
1998 2025 # how it flies with the templater.
1999 2026 fm = ui.formatter(b'perf-stats', opts)
2000 2027 for key, title in entries:
2001 2028 values = data[key]
2002 2029 nbvalues = len(data)
2003 2030 values.sort()
2004 2031 stats = {
2005 2032 'key': key,
2006 2033 'title': title,
2007 2034 'nbitems': len(values),
2008 2035 'min': values[0][0],
2009 2036 '10%': values[(nbvalues * 10) // 100][0],
2010 2037 '25%': values[(nbvalues * 25) // 100][0],
2011 2038 '50%': values[(nbvalues * 50) // 100][0],
2012 2039 '75%': values[(nbvalues * 75) // 100][0],
2013 2040 '80%': values[(nbvalues * 80) // 100][0],
2014 2041 '85%': values[(nbvalues * 85) // 100][0],
2015 2042 '90%': values[(nbvalues * 90) // 100][0],
2016 2043 '95%': values[(nbvalues * 95) // 100][0],
2017 2044 '99%': values[(nbvalues * 99) // 100][0],
2018 2045 'max': values[-1][0],
2019 2046 }
2020 2047 fm.startitem()
2021 2048 fm.data(**stats)
2022 2049 # make node pretty for the human output
2023 2050 fm.plain('### %s (%d items)\n' % (title, len(values)))
2024 2051 lines = [
2025 2052 'min',
2026 2053 '10%',
2027 2054 '25%',
2028 2055 '50%',
2029 2056 '75%',
2030 2057 '80%',
2031 2058 '85%',
2032 2059 '90%',
2033 2060 '95%',
2034 2061 '99%',
2035 2062 'max',
2036 2063 ]
2037 2064 for l in lines:
2038 2065 fm.plain('%s: %s\n' % (l, stats[l]))
2039 2066 fm.end()
2040 2067
2041 2068
2042 2069 @command(
2043 2070 b'perf::helper-mergecopies|perfhelper-mergecopies',
2044 2071 formatteropts
2045 2072 + [
2046 2073 (b'r', b'revs', [], b'restrict search to these revisions'),
2047 2074 (b'', b'timing', False, b'provides extra data (costly)'),
2048 2075 (b'', b'stats', False, b'provides statistic about the measured data'),
2049 2076 ],
2050 2077 )
2051 2078 def perfhelpermergecopies(ui, repo, revs=[], **opts):
2052 2079 """find statistics about potential parameters for `perfmergecopies`
2053 2080
2054 2081 This command find (base, p1, p2) triplet relevant for copytracing
2055 2082 benchmarking in the context of a merge. It reports values for some of the
2056 2083 parameters that impact merge copy tracing time during merge.
2057 2084
2058 2085 If `--timing` is set, rename detection is run and the associated timing
2059 2086 will be reported. The extra details come at the cost of slower command
2060 2087 execution.
2061 2088
2062 2089 Since rename detection is only run once, other factors might easily
2063 2090 affect the precision of the timing. However it should give a good
2064 2091 approximation of which revision triplets are very costly.
2065 2092 """
2066 2093 opts = _byteskwargs(opts)
2067 2094 fm = ui.formatter(b'perf', opts)
2068 2095 dotiming = opts[b'timing']
2069 2096 dostats = opts[b'stats']
2070 2097
2071 2098 output_template = [
2072 2099 ("base", "%(base)12s"),
2073 2100 ("p1", "%(p1.node)12s"),
2074 2101 ("p2", "%(p2.node)12s"),
2075 2102 ("p1.nb-revs", "%(p1.nbrevs)12d"),
2076 2103 ("p1.nb-files", "%(p1.nbmissingfiles)12d"),
2077 2104 ("p1.renames", "%(p1.renamedfiles)12d"),
2078 2105 ("p1.time", "%(p1.time)12.3f"),
2079 2106 ("p2.nb-revs", "%(p2.nbrevs)12d"),
2080 2107 ("p2.nb-files", "%(p2.nbmissingfiles)12d"),
2081 2108 ("p2.renames", "%(p2.renamedfiles)12d"),
2082 2109 ("p2.time", "%(p2.time)12.3f"),
2083 2110 ("renames", "%(nbrenamedfiles)12d"),
2084 2111 ("total.time", "%(time)12.3f"),
2085 2112 ]
2086 2113 if not dotiming:
2087 2114 output_template = [
2088 2115 i
2089 2116 for i in output_template
2090 2117 if not ('time' in i[0] or 'renames' in i[0])
2091 2118 ]
2092 2119 header_names = [h for (h, v) in output_template]
2093 2120 output = ' '.join([v for (h, v) in output_template]) + '\n'
2094 2121 header = ' '.join(['%12s'] * len(header_names)) + '\n'
2095 2122 fm.plain(header % tuple(header_names))
2096 2123
2097 2124 if not revs:
2098 2125 revs = ['all()']
2099 2126 revs = scmutil.revrange(repo, revs)
2100 2127
2101 2128 if dostats:
2102 2129 alldata = {
2103 2130 'nbrevs': [],
2104 2131 'nbmissingfiles': [],
2105 2132 }
2106 2133 if dotiming:
2107 2134 alldata['parentnbrenames'] = []
2108 2135 alldata['totalnbrenames'] = []
2109 2136 alldata['parenttime'] = []
2110 2137 alldata['totaltime'] = []
2111 2138
2112 2139 roi = repo.revs('merge() and %ld', revs)
2113 2140 for r in roi:
2114 2141 ctx = repo[r]
2115 2142 p1 = ctx.p1()
2116 2143 p2 = ctx.p2()
2117 2144 bases = repo.changelog._commonancestorsheads(p1.rev(), p2.rev())
2118 2145 for b in bases:
2119 2146 b = repo[b]
2120 2147 p1missing = copies._computeforwardmissing(b, p1)
2121 2148 p2missing = copies._computeforwardmissing(b, p2)
2122 2149 data = {
2123 2150 b'base': b.hex(),
2124 2151 b'p1.node': p1.hex(),
2125 2152 b'p1.nbrevs': len(repo.revs('only(%d, %d)', p1.rev(), b.rev())),
2126 2153 b'p1.nbmissingfiles': len(p1missing),
2127 2154 b'p2.node': p2.hex(),
2128 2155 b'p2.nbrevs': len(repo.revs('only(%d, %d)', p2.rev(), b.rev())),
2129 2156 b'p2.nbmissingfiles': len(p2missing),
2130 2157 }
2131 2158 if dostats:
2132 2159 if p1missing:
2133 2160 alldata['nbrevs'].append(
2134 2161 (data['p1.nbrevs'], b.hex(), p1.hex())
2135 2162 )
2136 2163 alldata['nbmissingfiles'].append(
2137 2164 (data['p1.nbmissingfiles'], b.hex(), p1.hex())
2138 2165 )
2139 2166 if p2missing:
2140 2167 alldata['nbrevs'].append(
2141 2168 (data['p2.nbrevs'], b.hex(), p2.hex())
2142 2169 )
2143 2170 alldata['nbmissingfiles'].append(
2144 2171 (data['p2.nbmissingfiles'], b.hex(), p2.hex())
2145 2172 )
2146 2173 if dotiming:
2147 2174 begin = util.timer()
2148 2175 mergedata = copies.mergecopies(repo, p1, p2, b)
2149 2176 end = util.timer()
2150 2177 # not very stable timing since we did only one run
2151 2178 data['time'] = end - begin
2152 2179 # mergedata contains five dicts: "copy", "movewithdir",
2153 2180 # "diverge", "renamedelete" and "dirmove".
2154 2181 # The first 4 are about renamed file so lets count that.
2155 2182 renames = len(mergedata[0])
2156 2183 renames += len(mergedata[1])
2157 2184 renames += len(mergedata[2])
2158 2185 renames += len(mergedata[3])
2159 2186 data['nbrenamedfiles'] = renames
2160 2187 begin = util.timer()
2161 2188 p1renames = copies.pathcopies(b, p1)
2162 2189 end = util.timer()
2163 2190 data['p1.time'] = end - begin
2164 2191 begin = util.timer()
2165 2192 p2renames = copies.pathcopies(b, p2)
2166 2193 end = util.timer()
2167 2194 data['p2.time'] = end - begin
2168 2195 data['p1.renamedfiles'] = len(p1renames)
2169 2196 data['p2.renamedfiles'] = len(p2renames)
2170 2197
2171 2198 if dostats:
2172 2199 if p1missing:
2173 2200 alldata['parentnbrenames'].append(
2174 2201 (data['p1.renamedfiles'], b.hex(), p1.hex())
2175 2202 )
2176 2203 alldata['parenttime'].append(
2177 2204 (data['p1.time'], b.hex(), p1.hex())
2178 2205 )
2179 2206 if p2missing:
2180 2207 alldata['parentnbrenames'].append(
2181 2208 (data['p2.renamedfiles'], b.hex(), p2.hex())
2182 2209 )
2183 2210 alldata['parenttime'].append(
2184 2211 (data['p2.time'], b.hex(), p2.hex())
2185 2212 )
2186 2213 if p1missing or p2missing:
2187 2214 alldata['totalnbrenames'].append(
2188 2215 (
2189 2216 data['nbrenamedfiles'],
2190 2217 b.hex(),
2191 2218 p1.hex(),
2192 2219 p2.hex(),
2193 2220 )
2194 2221 )
2195 2222 alldata['totaltime'].append(
2196 2223 (data['time'], b.hex(), p1.hex(), p2.hex())
2197 2224 )
2198 2225 fm.startitem()
2199 2226 fm.data(**data)
2200 2227 # make node pretty for the human output
2201 2228 out = data.copy()
2202 2229 out['base'] = fm.hexfunc(b.node())
2203 2230 out['p1.node'] = fm.hexfunc(p1.node())
2204 2231 out['p2.node'] = fm.hexfunc(p2.node())
2205 2232 fm.plain(output % out)
2206 2233
2207 2234 fm.end()
2208 2235 if dostats:
2209 2236 # use a second formatter because the data are quite different, not sure
2210 2237 # how it flies with the templater.
2211 2238 entries = [
2212 2239 ('nbrevs', 'number of revision covered'),
2213 2240 ('nbmissingfiles', 'number of missing files at head'),
2214 2241 ]
2215 2242 if dotiming:
2216 2243 entries.append(
2217 2244 ('parentnbrenames', 'rename from one parent to base')
2218 2245 )
2219 2246 entries.append(('totalnbrenames', 'total number of renames'))
2220 2247 entries.append(('parenttime', 'time for one parent'))
2221 2248 entries.append(('totaltime', 'time for both parents'))
2222 2249 _displaystats(ui, opts, entries, alldata)
2223 2250
2224 2251
2225 2252 @command(
2226 2253 b'perf::helper-pathcopies|perfhelper-pathcopies',
2227 2254 formatteropts
2228 2255 + [
2229 2256 (b'r', b'revs', [], b'restrict search to these revisions'),
2230 2257 (b'', b'timing', False, b'provides extra data (costly)'),
2231 2258 (b'', b'stats', False, b'provides statistic about the measured data'),
2232 2259 ],
2233 2260 )
2234 2261 def perfhelperpathcopies(ui, repo, revs=[], **opts):
2235 2262 """find statistic about potential parameters for the `perftracecopies`
2236 2263
2237 2264 This command find source-destination pair relevant for copytracing testing.
2238 2265 It report value for some of the parameters that impact copy tracing time.
2239 2266
2240 2267 If `--timing` is set, rename detection is run and the associated timing
2241 2268 will be reported. The extra details comes at the cost of a slower command
2242 2269 execution.
2243 2270
2244 2271 Since the rename detection is only run once, other factors might easily
2245 2272 affect the precision of the timing. However it should give a good
2246 2273 approximation of which revision pairs are very costly.
2247 2274 """
2248 2275 opts = _byteskwargs(opts)
2249 2276 fm = ui.formatter(b'perf', opts)
2250 2277 dotiming = opts[b'timing']
2251 2278 dostats = opts[b'stats']
2252 2279
2253 2280 if dotiming:
2254 2281 header = '%12s %12s %12s %12s %12s %12s\n'
2255 2282 output = (
2256 2283 "%(source)12s %(destination)12s "
2257 2284 "%(nbrevs)12d %(nbmissingfiles)12d "
2258 2285 "%(nbrenamedfiles)12d %(time)18.5f\n"
2259 2286 )
2260 2287 header_names = (
2261 2288 "source",
2262 2289 "destination",
2263 2290 "nb-revs",
2264 2291 "nb-files",
2265 2292 "nb-renames",
2266 2293 "time",
2267 2294 )
2268 2295 fm.plain(header % header_names)
2269 2296 else:
2270 2297 header = '%12s %12s %12s %12s\n'
2271 2298 output = (
2272 2299 "%(source)12s %(destination)12s "
2273 2300 "%(nbrevs)12d %(nbmissingfiles)12d\n"
2274 2301 )
2275 2302 fm.plain(header % ("source", "destination", "nb-revs", "nb-files"))
2276 2303
2277 2304 if not revs:
2278 2305 revs = ['all()']
2279 2306 revs = scmutil.revrange(repo, revs)
2280 2307
2281 2308 if dostats:
2282 2309 alldata = {
2283 2310 'nbrevs': [],
2284 2311 'nbmissingfiles': [],
2285 2312 }
2286 2313 if dotiming:
2287 2314 alldata['nbrenames'] = []
2288 2315 alldata['time'] = []
2289 2316
2290 2317 roi = repo.revs('merge() and %ld', revs)
2291 2318 for r in roi:
2292 2319 ctx = repo[r]
2293 2320 p1 = ctx.p1().rev()
2294 2321 p2 = ctx.p2().rev()
2295 2322 bases = repo.changelog._commonancestorsheads(p1, p2)
2296 2323 for p in (p1, p2):
2297 2324 for b in bases:
2298 2325 base = repo[b]
2299 2326 parent = repo[p]
2300 2327 missing = copies._computeforwardmissing(base, parent)
2301 2328 if not missing:
2302 2329 continue
2303 2330 data = {
2304 2331 b'source': base.hex(),
2305 2332 b'destination': parent.hex(),
2306 2333 b'nbrevs': len(repo.revs('only(%d, %d)', p, b)),
2307 2334 b'nbmissingfiles': len(missing),
2308 2335 }
2309 2336 if dostats:
2310 2337 alldata['nbrevs'].append(
2311 2338 (
2312 2339 data['nbrevs'],
2313 2340 base.hex(),
2314 2341 parent.hex(),
2315 2342 )
2316 2343 )
2317 2344 alldata['nbmissingfiles'].append(
2318 2345 (
2319 2346 data['nbmissingfiles'],
2320 2347 base.hex(),
2321 2348 parent.hex(),
2322 2349 )
2323 2350 )
2324 2351 if dotiming:
2325 2352 begin = util.timer()
2326 2353 renames = copies.pathcopies(base, parent)
2327 2354 end = util.timer()
2328 2355 # not very stable timing since we did only one run
2329 2356 data['time'] = end - begin
2330 2357 data['nbrenamedfiles'] = len(renames)
2331 2358 if dostats:
2332 2359 alldata['time'].append(
2333 2360 (
2334 2361 data['time'],
2335 2362 base.hex(),
2336 2363 parent.hex(),
2337 2364 )
2338 2365 )
2339 2366 alldata['nbrenames'].append(
2340 2367 (
2341 2368 data['nbrenamedfiles'],
2342 2369 base.hex(),
2343 2370 parent.hex(),
2344 2371 )
2345 2372 )
2346 2373 fm.startitem()
2347 2374 fm.data(**data)
2348 2375 out = data.copy()
2349 2376 out['source'] = fm.hexfunc(base.node())
2350 2377 out['destination'] = fm.hexfunc(parent.node())
2351 2378 fm.plain(output % out)
2352 2379
2353 2380 fm.end()
2354 2381 if dostats:
2355 2382 entries = [
2356 2383 ('nbrevs', 'number of revision covered'),
2357 2384 ('nbmissingfiles', 'number of missing files at head'),
2358 2385 ]
2359 2386 if dotiming:
2360 2387 entries.append(('nbrenames', 'renamed files'))
2361 2388 entries.append(('time', 'time'))
2362 2389 _displaystats(ui, opts, entries, alldata)
2363 2390
2364 2391
2365 2392 @command(b'perf::cca|perfcca', formatteropts)
2366 2393 def perfcca(ui, repo, **opts):
2367 2394 opts = _byteskwargs(opts)
2368 2395 timer, fm = gettimer(ui, opts)
2369 2396 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
2370 2397 fm.end()
2371 2398
2372 2399
2373 2400 @command(b'perf::fncacheload|perffncacheload', formatteropts)
2374 2401 def perffncacheload(ui, repo, **opts):
2375 2402 opts = _byteskwargs(opts)
2376 2403 timer, fm = gettimer(ui, opts)
2377 2404 s = repo.store
2378 2405
2379 2406 def d():
2380 2407 s.fncache._load()
2381 2408
2382 2409 timer(d)
2383 2410 fm.end()
2384 2411
2385 2412
2386 2413 @command(b'perf::fncachewrite|perffncachewrite', formatteropts)
2387 2414 def perffncachewrite(ui, repo, **opts):
2388 2415 opts = _byteskwargs(opts)
2389 2416 timer, fm = gettimer(ui, opts)
2390 2417 s = repo.store
2391 2418 lock = repo.lock()
2392 2419 s.fncache._load()
2393 2420 tr = repo.transaction(b'perffncachewrite')
2394 2421 tr.addbackup(b'fncache')
2395 2422
2396 2423 def d():
2397 2424 s.fncache._dirty = True
2398 2425 s.fncache.write(tr)
2399 2426
2400 2427 timer(d)
2401 2428 tr.close()
2402 2429 lock.release()
2403 2430 fm.end()
2404 2431
2405 2432
2406 2433 @command(b'perf::fncacheencode|perffncacheencode', formatteropts)
2407 2434 def perffncacheencode(ui, repo, **opts):
2408 2435 opts = _byteskwargs(opts)
2409 2436 timer, fm = gettimer(ui, opts)
2410 2437 s = repo.store
2411 2438 s.fncache._load()
2412 2439
2413 2440 def d():
2414 2441 for p in s.fncache.entries:
2415 2442 s.encode(p)
2416 2443
2417 2444 timer(d)
2418 2445 fm.end()
2419 2446
2420 2447
2421 2448 def _bdiffworker(q, blocks, xdiff, ready, done):
2422 2449 while not done.is_set():
2423 2450 pair = q.get()
2424 2451 while pair is not None:
2425 2452 if xdiff:
2426 2453 mdiff.bdiff.xdiffblocks(*pair)
2427 2454 elif blocks:
2428 2455 mdiff.bdiff.blocks(*pair)
2429 2456 else:
2430 2457 mdiff.textdiff(*pair)
2431 2458 q.task_done()
2432 2459 pair = q.get()
2433 2460 q.task_done() # for the None one
2434 2461 with ready:
2435 2462 ready.wait()
2436 2463
2437 2464
2438 2465 def _manifestrevision(repo, mnode):
2439 2466 ml = repo.manifestlog
2440 2467
2441 2468 if util.safehasattr(ml, b'getstorage'):
2442 2469 store = ml.getstorage(b'')
2443 2470 else:
2444 2471 store = ml._revlog
2445 2472
2446 2473 return store.revision(mnode)
2447 2474
2448 2475
2449 2476 @command(
2450 2477 b'perf::bdiff|perfbdiff',
2451 2478 revlogopts
2452 2479 + formatteropts
2453 2480 + [
2454 2481 (
2455 2482 b'',
2456 2483 b'count',
2457 2484 1,
2458 2485 b'number of revisions to test (when using --startrev)',
2459 2486 ),
2460 2487 (b'', b'alldata', False, b'test bdiffs for all associated revisions'),
2461 2488 (b'', b'threads', 0, b'number of thread to use (disable with 0)'),
2462 2489 (b'', b'blocks', False, b'test computing diffs into blocks'),
2463 2490 (b'', b'xdiff', False, b'use xdiff algorithm'),
2464 2491 ],
2465 2492 b'-c|-m|FILE REV',
2466 2493 )
2467 2494 def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts):
2468 2495 """benchmark a bdiff between revisions
2469 2496
2470 2497 By default, benchmark a bdiff between its delta parent and itself.
2471 2498
2472 2499 With ``--count``, benchmark bdiffs between delta parents and self for N
2473 2500 revisions starting at the specified revision.
2474 2501
2475 2502 With ``--alldata``, assume the requested revision is a changeset and
2476 2503 measure bdiffs for all changes related to that changeset (manifest
2477 2504 and filelogs).
2478 2505 """
2479 2506 opts = _byteskwargs(opts)
2480 2507
2481 2508 if opts[b'xdiff'] and not opts[b'blocks']:
2482 2509 raise error.CommandError(b'perfbdiff', b'--xdiff requires --blocks')
2483 2510
2484 2511 if opts[b'alldata']:
2485 2512 opts[b'changelog'] = True
2486 2513
2487 2514 if opts.get(b'changelog') or opts.get(b'manifest'):
2488 2515 file_, rev = None, file_
2489 2516 elif rev is None:
2490 2517 raise error.CommandError(b'perfbdiff', b'invalid arguments')
2491 2518
2492 2519 blocks = opts[b'blocks']
2493 2520 xdiff = opts[b'xdiff']
2494 2521 textpairs = []
2495 2522
2496 2523 r = cmdutil.openrevlog(repo, b'perfbdiff', file_, opts)
2497 2524
2498 2525 startrev = r.rev(r.lookup(rev))
2499 2526 for rev in range(startrev, min(startrev + count, len(r) - 1)):
2500 2527 if opts[b'alldata']:
2501 2528 # Load revisions associated with changeset.
2502 2529 ctx = repo[rev]
2503 2530 mtext = _manifestrevision(repo, ctx.manifestnode())
2504 2531 for pctx in ctx.parents():
2505 2532 pman = _manifestrevision(repo, pctx.manifestnode())
2506 2533 textpairs.append((pman, mtext))
2507 2534
2508 2535 # Load filelog revisions by iterating manifest delta.
2509 2536 man = ctx.manifest()
2510 2537 pman = ctx.p1().manifest()
2511 2538 for filename, change in pman.diff(man).items():
2512 2539 fctx = repo.file(filename)
2513 2540 f1 = fctx.revision(change[0][0] or -1)
2514 2541 f2 = fctx.revision(change[1][0] or -1)
2515 2542 textpairs.append((f1, f2))
2516 2543 else:
2517 2544 dp = r.deltaparent(rev)
2518 2545 textpairs.append((r.revision(dp), r.revision(rev)))
2519 2546
2520 2547 withthreads = threads > 0
2521 2548 if not withthreads:
2522 2549
2523 2550 def d():
2524 2551 for pair in textpairs:
2525 2552 if xdiff:
2526 2553 mdiff.bdiff.xdiffblocks(*pair)
2527 2554 elif blocks:
2528 2555 mdiff.bdiff.blocks(*pair)
2529 2556 else:
2530 2557 mdiff.textdiff(*pair)
2531 2558
2532 2559 else:
2533 2560 q = queue()
2534 2561 for i in _xrange(threads):
2535 2562 q.put(None)
2536 2563 ready = threading.Condition()
2537 2564 done = threading.Event()
2538 2565 for i in _xrange(threads):
2539 2566 threading.Thread(
2540 2567 target=_bdiffworker, args=(q, blocks, xdiff, ready, done)
2541 2568 ).start()
2542 2569 q.join()
2543 2570
2544 2571 def d():
2545 2572 for pair in textpairs:
2546 2573 q.put(pair)
2547 2574 for i in _xrange(threads):
2548 2575 q.put(None)
2549 2576 with ready:
2550 2577 ready.notify_all()
2551 2578 q.join()
2552 2579
2553 2580 timer, fm = gettimer(ui, opts)
2554 2581 timer(d)
2555 2582 fm.end()
2556 2583
2557 2584 if withthreads:
2558 2585 done.set()
2559 2586 for i in _xrange(threads):
2560 2587 q.put(None)
2561 2588 with ready:
2562 2589 ready.notify_all()
2563 2590
2564 2591
2565 2592 @command(
2566 2593 b'perf::unidiff|perfunidiff',
2567 2594 revlogopts
2568 2595 + formatteropts
2569 2596 + [
2570 2597 (
2571 2598 b'',
2572 2599 b'count',
2573 2600 1,
2574 2601 b'number of revisions to test (when using --startrev)',
2575 2602 ),
2576 2603 (b'', b'alldata', False, b'test unidiffs for all associated revisions'),
2577 2604 ],
2578 2605 b'-c|-m|FILE REV',
2579 2606 )
2580 2607 def perfunidiff(ui, repo, file_, rev=None, count=None, **opts):
2581 2608 """benchmark a unified diff between revisions
2582 2609
2583 2610 This doesn't include any copy tracing - it's just a unified diff
2584 2611 of the texts.
2585 2612
2586 2613 By default, benchmark a diff between its delta parent and itself.
2587 2614
2588 2615 With ``--count``, benchmark diffs between delta parents and self for N
2589 2616 revisions starting at the specified revision.
2590 2617
2591 2618 With ``--alldata``, assume the requested revision is a changeset and
2592 2619 measure diffs for all changes related to that changeset (manifest
2593 2620 and filelogs).
2594 2621 """
2595 2622 opts = _byteskwargs(opts)
2596 2623 if opts[b'alldata']:
2597 2624 opts[b'changelog'] = True
2598 2625
2599 2626 if opts.get(b'changelog') or opts.get(b'manifest'):
2600 2627 file_, rev = None, file_
2601 2628 elif rev is None:
2602 2629 raise error.CommandError(b'perfunidiff', b'invalid arguments')
2603 2630
2604 2631 textpairs = []
2605 2632
2606 2633 r = cmdutil.openrevlog(repo, b'perfunidiff', file_, opts)
2607 2634
2608 2635 startrev = r.rev(r.lookup(rev))
2609 2636 for rev in range(startrev, min(startrev + count, len(r) - 1)):
2610 2637 if opts[b'alldata']:
2611 2638 # Load revisions associated with changeset.
2612 2639 ctx = repo[rev]
2613 2640 mtext = _manifestrevision(repo, ctx.manifestnode())
2614 2641 for pctx in ctx.parents():
2615 2642 pman = _manifestrevision(repo, pctx.manifestnode())
2616 2643 textpairs.append((pman, mtext))
2617 2644
2618 2645 # Load filelog revisions by iterating manifest delta.
2619 2646 man = ctx.manifest()
2620 2647 pman = ctx.p1().manifest()
2621 2648 for filename, change in pman.diff(man).items():
2622 2649 fctx = repo.file(filename)
2623 2650 f1 = fctx.revision(change[0][0] or -1)
2624 2651 f2 = fctx.revision(change[1][0] or -1)
2625 2652 textpairs.append((f1, f2))
2626 2653 else:
2627 2654 dp = r.deltaparent(rev)
2628 2655 textpairs.append((r.revision(dp), r.revision(rev)))
2629 2656
2630 2657 def d():
2631 2658 for left, right in textpairs:
2632 2659 # The date strings don't matter, so we pass empty strings.
2633 2660 headerlines, hunks = mdiff.unidiff(
2634 2661 left, b'', right, b'', b'left', b'right', binary=False
2635 2662 )
2636 2663 # consume iterators in roughly the way patch.py does
2637 2664 b'\n'.join(headerlines)
2638 2665 b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
2639 2666
2640 2667 timer, fm = gettimer(ui, opts)
2641 2668 timer(d)
2642 2669 fm.end()
2643 2670
2644 2671
2645 2672 @command(b'perf::diffwd|perfdiffwd', formatteropts)
2646 2673 def perfdiffwd(ui, repo, **opts):
2647 2674 """Profile diff of working directory changes"""
2648 2675 opts = _byteskwargs(opts)
2649 2676 timer, fm = gettimer(ui, opts)
2650 2677 options = {
2651 2678 'w': 'ignore_all_space',
2652 2679 'b': 'ignore_space_change',
2653 2680 'B': 'ignore_blank_lines',
2654 2681 }
2655 2682
2656 2683 for diffopt in ('', 'w', 'b', 'B', 'wB'):
2657 2684 opts = {options[c]: b'1' for c in diffopt}
2658 2685
2659 2686 def d():
2660 2687 ui.pushbuffer()
2661 2688 commands.diff(ui, repo, **opts)
2662 2689 ui.popbuffer()
2663 2690
2664 2691 diffopt = diffopt.encode('ascii')
2665 2692 title = b'diffopts: %s' % (diffopt and (b'-' + diffopt) or b'none')
2666 2693 timer(d, title=title)
2667 2694 fm.end()
2668 2695
2669 2696
2670 2697 @command(
2671 2698 b'perf::revlogindex|perfrevlogindex',
2672 2699 revlogopts + formatteropts,
2673 2700 b'-c|-m|FILE',
2674 2701 )
2675 2702 def perfrevlogindex(ui, repo, file_=None, **opts):
2676 2703 """Benchmark operations against a revlog index.
2677 2704
2678 2705 This tests constructing a revlog instance, reading index data,
2679 2706 parsing index data, and performing various operations related to
2680 2707 index data.
2681 2708 """
2682 2709
2683 2710 opts = _byteskwargs(opts)
2684 2711
2685 2712 rl = cmdutil.openrevlog(repo, b'perfrevlogindex', file_, opts)
2686 2713
2687 2714 opener = getattr(rl, 'opener') # trick linter
2688 2715 # compat with hg <= 5.8
2689 2716 radix = getattr(rl, 'radix', None)
2690 2717 indexfile = getattr(rl, '_indexfile', None)
2691 2718 if indexfile is None:
2692 2719 # compatibility with <= hg-5.8
2693 2720 indexfile = getattr(rl, 'indexfile')
2694 2721 data = opener.read(indexfile)
2695 2722
2696 2723 header = struct.unpack(b'>I', data[0:4])[0]
2697 2724 version = header & 0xFFFF
2698 2725 if version == 1:
2699 2726 inline = header & (1 << 16)
2700 2727 else:
2701 2728 raise error.Abort(b'unsupported revlog version: %d' % version)
2702 2729
2703 2730 parse_index_v1 = getattr(mercurial.revlog, 'parse_index_v1', None)
2704 2731 if parse_index_v1 is None:
2705 2732 parse_index_v1 = mercurial.revlog.revlogio().parseindex
2706 2733
2707 2734 rllen = len(rl)
2708 2735
2709 2736 node0 = rl.node(0)
2710 2737 node25 = rl.node(rllen // 4)
2711 2738 node50 = rl.node(rllen // 2)
2712 2739 node75 = rl.node(rllen // 4 * 3)
2713 2740 node100 = rl.node(rllen - 1)
2714 2741
2715 2742 allrevs = range(rllen)
2716 2743 allrevsrev = list(reversed(allrevs))
2717 2744 allnodes = [rl.node(rev) for rev in range(rllen)]
2718 2745 allnodesrev = list(reversed(allnodes))
2719 2746
2720 2747 def constructor():
2721 2748 if radix is not None:
2722 2749 revlog(opener, radix=radix)
2723 2750 else:
2724 2751 # hg <= 5.8
2725 2752 revlog(opener, indexfile=indexfile)
2726 2753
2727 2754 def read():
2728 2755 with opener(indexfile) as fh:
2729 2756 fh.read()
2730 2757
2731 2758 def parseindex():
2732 2759 parse_index_v1(data, inline)
2733 2760
2734 2761 def getentry(revornode):
2735 2762 index = parse_index_v1(data, inline)[0]
2736 2763 index[revornode]
2737 2764
2738 2765 def getentries(revs, count=1):
2739 2766 index = parse_index_v1(data, inline)[0]
2740 2767
2741 2768 for i in range(count):
2742 2769 for rev in revs:
2743 2770 index[rev]
2744 2771
2745 2772 def resolvenode(node):
2746 2773 index = parse_index_v1(data, inline)[0]
2747 2774 rev = getattr(index, 'rev', None)
2748 2775 if rev is None:
2749 2776 nodemap = getattr(parse_index_v1(data, inline)[0], 'nodemap', None)
2750 2777 # This only works for the C code.
2751 2778 if nodemap is None:
2752 2779 return
2753 2780 rev = nodemap.__getitem__
2754 2781
2755 2782 try:
2756 2783 rev(node)
2757 2784 except error.RevlogError:
2758 2785 pass
2759 2786
2760 2787 def resolvenodes(nodes, count=1):
2761 2788 index = parse_index_v1(data, inline)[0]
2762 2789 rev = getattr(index, 'rev', None)
2763 2790 if rev is None:
2764 2791 nodemap = getattr(parse_index_v1(data, inline)[0], 'nodemap', None)
2765 2792 # This only works for the C code.
2766 2793 if nodemap is None:
2767 2794 return
2768 2795 rev = nodemap.__getitem__
2769 2796
2770 2797 for i in range(count):
2771 2798 for node in nodes:
2772 2799 try:
2773 2800 rev(node)
2774 2801 except error.RevlogError:
2775 2802 pass
2776 2803
2777 2804 benches = [
2778 2805 (constructor, b'revlog constructor'),
2779 2806 (read, b'read'),
2780 2807 (parseindex, b'create index object'),
2781 2808 (lambda: getentry(0), b'retrieve index entry for rev 0'),
2782 2809 (lambda: resolvenode(b'a' * 20), b'look up missing node'),
2783 2810 (lambda: resolvenode(node0), b'look up node at rev 0'),
2784 2811 (lambda: resolvenode(node25), b'look up node at 1/4 len'),
2785 2812 (lambda: resolvenode(node50), b'look up node at 1/2 len'),
2786 2813 (lambda: resolvenode(node75), b'look up node at 3/4 len'),
2787 2814 (lambda: resolvenode(node100), b'look up node at tip'),
2788 2815 # 2x variation is to measure caching impact.
2789 2816 (lambda: resolvenodes(allnodes), b'look up all nodes (forward)'),
2790 2817 (lambda: resolvenodes(allnodes, 2), b'look up all nodes 2x (forward)'),
2791 2818 (lambda: resolvenodes(allnodesrev), b'look up all nodes (reverse)'),
2792 2819 (
2793 2820 lambda: resolvenodes(allnodesrev, 2),
2794 2821 b'look up all nodes 2x (reverse)',
2795 2822 ),
2796 2823 (lambda: getentries(allrevs), b'retrieve all index entries (forward)'),
2797 2824 (
2798 2825 lambda: getentries(allrevs, 2),
2799 2826 b'retrieve all index entries 2x (forward)',
2800 2827 ),
2801 2828 (
2802 2829 lambda: getentries(allrevsrev),
2803 2830 b'retrieve all index entries (reverse)',
2804 2831 ),
2805 2832 (
2806 2833 lambda: getentries(allrevsrev, 2),
2807 2834 b'retrieve all index entries 2x (reverse)',
2808 2835 ),
2809 2836 ]
2810 2837
2811 2838 for fn, title in benches:
2812 2839 timer, fm = gettimer(ui, opts)
2813 2840 timer(fn, title=title)
2814 2841 fm.end()
2815 2842
2816 2843
2817 2844 @command(
2818 2845 b'perf::revlogrevisions|perfrevlogrevisions',
2819 2846 revlogopts
2820 2847 + formatteropts
2821 2848 + [
2822 2849 (b'd', b'dist', 100, b'distance between the revisions'),
2823 2850 (b's', b'startrev', 0, b'revision to start reading at'),
2824 2851 (b'', b'reverse', False, b'read in reverse'),
2825 2852 ],
2826 2853 b'-c|-m|FILE',
2827 2854 )
2828 2855 def perfrevlogrevisions(
2829 2856 ui, repo, file_=None, startrev=0, reverse=False, **opts
2830 2857 ):
2831 2858 """Benchmark reading a series of revisions from a revlog.
2832 2859
2833 2860 By default, we read every ``-d/--dist`` revision from 0 to tip of
2834 2861 the specified revlog.
2835 2862
2836 2863 The start revision can be defined via ``-s/--startrev``.
2837 2864 """
2838 2865 opts = _byteskwargs(opts)
2839 2866
2840 2867 rl = cmdutil.openrevlog(repo, b'perfrevlogrevisions', file_, opts)
2841 2868 rllen = getlen(ui)(rl)
2842 2869
2843 2870 if startrev < 0:
2844 2871 startrev = rllen + startrev
2845 2872
2846 2873 def d():
2847 2874 rl.clearcaches()
2848 2875
2849 2876 beginrev = startrev
2850 2877 endrev = rllen
2851 2878 dist = opts[b'dist']
2852 2879
2853 2880 if reverse:
2854 2881 beginrev, endrev = endrev - 1, beginrev - 1
2855 2882 dist = -1 * dist
2856 2883
2857 2884 for x in _xrange(beginrev, endrev, dist):
2858 2885 # Old revisions don't support passing int.
2859 2886 n = rl.node(x)
2860 2887 rl.revision(n)
2861 2888
2862 2889 timer, fm = gettimer(ui, opts)
2863 2890 timer(d)
2864 2891 fm.end()
2865 2892
2866 2893
2867 2894 @command(
2868 2895 b'perf::revlogwrite|perfrevlogwrite',
2869 2896 revlogopts
2870 2897 + formatteropts
2871 2898 + [
2872 2899 (b's', b'startrev', 1000, b'revision to start writing at'),
2873 2900 (b'', b'stoprev', -1, b'last revision to write'),
2874 2901 (b'', b'count', 3, b'number of passes to perform'),
2875 2902 (b'', b'details', False, b'print timing for every revisions tested'),
2876 2903 (b'', b'source', b'full', b'the kind of data feed in the revlog'),
2877 2904 (b'', b'lazydeltabase', True, b'try the provided delta first'),
2878 2905 (b'', b'clear-caches', True, b'clear revlog cache between calls'),
2879 2906 ],
2880 2907 b'-c|-m|FILE',
2881 2908 )
2882 2909 def perfrevlogwrite(ui, repo, file_=None, startrev=1000, stoprev=-1, **opts):
2883 2910 """Benchmark writing a series of revisions to a revlog.
2884 2911
2885 2912 Possible source values are:
2886 2913 * `full`: add from a full text (default).
2887 2914 * `parent-1`: add from a delta to the first parent
2888 2915 * `parent-2`: add from a delta to the second parent if it exists
2889 2916 (use a delta from the first parent otherwise)
2890 2917 * `parent-smallest`: add from the smallest delta (either p1 or p2)
2891 2918 * `storage`: add from the existing precomputed deltas
2892 2919
2893 2920 Note: This performance command measures performance in a custom way. As a
2894 2921 result some of the global configuration of the 'perf' command does not
2895 2922 apply to it:
2896 2923
2897 2924 * ``pre-run``: disabled
2898 2925
2899 2926 * ``profile-benchmark``: disabled
2900 2927
2901 2928 * ``run-limits``: disabled use --count instead
2902 2929 """
2903 2930 opts = _byteskwargs(opts)
2904 2931
2905 2932 rl = cmdutil.openrevlog(repo, b'perfrevlogwrite', file_, opts)
2906 2933 rllen = getlen(ui)(rl)
2907 2934 if startrev < 0:
2908 2935 startrev = rllen + startrev
2909 2936 if stoprev < 0:
2910 2937 stoprev = rllen + stoprev
2911 2938
2912 2939 lazydeltabase = opts['lazydeltabase']
2913 2940 source = opts['source']
2914 2941 clearcaches = opts['clear_caches']
2915 2942 validsource = (
2916 2943 b'full',
2917 2944 b'parent-1',
2918 2945 b'parent-2',
2919 2946 b'parent-smallest',
2920 2947 b'storage',
2921 2948 )
2922 2949 if source not in validsource:
2923 2950 raise error.Abort('invalid source type: %s' % source)
2924 2951
2925 2952 ### actually gather results
2926 2953 count = opts['count']
2927 2954 if count <= 0:
2928 2955 raise error.Abort('invalide run count: %d' % count)
2929 2956 allresults = []
2930 2957 for c in range(count):
2931 2958 timing = _timeonewrite(
2932 2959 ui,
2933 2960 rl,
2934 2961 source,
2935 2962 startrev,
2936 2963 stoprev,
2937 2964 c + 1,
2938 2965 lazydeltabase=lazydeltabase,
2939 2966 clearcaches=clearcaches,
2940 2967 )
2941 2968 allresults.append(timing)
2942 2969
2943 2970 ### consolidate the results in a single list
2944 2971 results = []
2945 2972 for idx, (rev, t) in enumerate(allresults[0]):
2946 2973 ts = [t]
2947 2974 for other in allresults[1:]:
2948 2975 orev, ot = other[idx]
2949 2976 assert orev == rev
2950 2977 ts.append(ot)
2951 2978 results.append((rev, ts))
2952 2979 resultcount = len(results)
2953 2980
2954 2981 ### Compute and display relevant statistics
2955 2982
2956 2983 # get a formatter
2957 2984 fm = ui.formatter(b'perf', opts)
2958 2985 displayall = ui.configbool(b"perf", b"all-timing", False)
2959 2986
2960 2987 # print individual details if requested
2961 2988 if opts['details']:
2962 2989 for idx, item in enumerate(results, 1):
2963 2990 rev, data = item
2964 2991 title = 'revisions #%d of %d, rev %d' % (idx, resultcount, rev)
2965 2992 formatone(fm, data, title=title, displayall=displayall)
2966 2993
2967 2994 # sorts results by median time
2968 2995 results.sort(key=lambda x: sorted(x[1])[len(x[1]) // 2])
2969 2996 # list of (name, index) to display)
2970 2997 relevants = [
2971 2998 ("min", 0),
2972 2999 ("10%", resultcount * 10 // 100),
2973 3000 ("25%", resultcount * 25 // 100),
2974 3001 ("50%", resultcount * 70 // 100),
2975 3002 ("75%", resultcount * 75 // 100),
2976 3003 ("90%", resultcount * 90 // 100),
2977 3004 ("95%", resultcount * 95 // 100),
2978 3005 ("99%", resultcount * 99 // 100),
2979 3006 ("99.9%", resultcount * 999 // 1000),
2980 3007 ("99.99%", resultcount * 9999 // 10000),
2981 3008 ("99.999%", resultcount * 99999 // 100000),
2982 3009 ("max", -1),
2983 3010 ]
2984 3011 if not ui.quiet:
2985 3012 for name, idx in relevants:
2986 3013 data = results[idx]
2987 3014 title = '%s of %d, rev %d' % (name, resultcount, data[0])
2988 3015 formatone(fm, data[1], title=title, displayall=displayall)
2989 3016
2990 3017 # XXX summing that many float will not be very precise, we ignore this fact
2991 3018 # for now
2992 3019 totaltime = []
2993 3020 for item in allresults:
2994 3021 totaltime.append(
2995 3022 (
2996 3023 sum(x[1][0] for x in item),
2997 3024 sum(x[1][1] for x in item),
2998 3025 sum(x[1][2] for x in item),
2999 3026 )
3000 3027 )
3001 3028 formatone(
3002 3029 fm,
3003 3030 totaltime,
3004 3031 title="total time (%d revs)" % resultcount,
3005 3032 displayall=displayall,
3006 3033 )
3007 3034 fm.end()
3008 3035
3009 3036
3010 3037 class _faketr:
3011 3038 def add(s, x, y, z=None):
3012 3039 return None
3013 3040
3014 3041
3015 3042 def _timeonewrite(
3016 3043 ui,
3017 3044 orig,
3018 3045 source,
3019 3046 startrev,
3020 3047 stoprev,
3021 3048 runidx=None,
3022 3049 lazydeltabase=True,
3023 3050 clearcaches=True,
3024 3051 ):
3025 3052 timings = []
3026 3053 tr = _faketr()
3027 3054 with _temprevlog(ui, orig, startrev) as dest:
3028 3055 dest._lazydeltabase = lazydeltabase
3029 3056 revs = list(orig.revs(startrev, stoprev))
3030 3057 total = len(revs)
3031 3058 topic = 'adding'
3032 3059 if runidx is not None:
3033 3060 topic += ' (run #%d)' % runidx
3034 3061 # Support both old and new progress API
3035 3062 if util.safehasattr(ui, 'makeprogress'):
3036 3063 progress = ui.makeprogress(topic, unit='revs', total=total)
3037 3064
3038 3065 def updateprogress(pos):
3039 3066 progress.update(pos)
3040 3067
3041 3068 def completeprogress():
3042 3069 progress.complete()
3043 3070
3044 3071 else:
3045 3072
3046 3073 def updateprogress(pos):
3047 3074 ui.progress(topic, pos, unit='revs', total=total)
3048 3075
3049 3076 def completeprogress():
3050 3077 ui.progress(topic, None, unit='revs', total=total)
3051 3078
3052 3079 for idx, rev in enumerate(revs):
3053 3080 updateprogress(idx)
3054 3081 addargs, addkwargs = _getrevisionseed(orig, rev, tr, source)
3055 3082 if clearcaches:
3056 3083 dest.index.clearcaches()
3057 3084 dest.clearcaches()
3058 3085 with timeone() as r:
3059 3086 dest.addrawrevision(*addargs, **addkwargs)
3060 3087 timings.append((rev, r[0]))
3061 3088 updateprogress(total)
3062 3089 completeprogress()
3063 3090 return timings
3064 3091
3065 3092
3066 3093 def _getrevisionseed(orig, rev, tr, source):
3067 3094 from mercurial.node import nullid
3068 3095
3069 3096 linkrev = orig.linkrev(rev)
3070 3097 node = orig.node(rev)
3071 3098 p1, p2 = orig.parents(node)
3072 3099 flags = orig.flags(rev)
3073 3100 cachedelta = None
3074 3101 text = None
3075 3102
3076 3103 if source == b'full':
3077 3104 text = orig.revision(rev)
3078 3105 elif source == b'parent-1':
3079 3106 baserev = orig.rev(p1)
3080 3107 cachedelta = (baserev, orig.revdiff(p1, rev))
3081 3108 elif source == b'parent-2':
3082 3109 parent = p2
3083 3110 if p2 == nullid:
3084 3111 parent = p1
3085 3112 baserev = orig.rev(parent)
3086 3113 cachedelta = (baserev, orig.revdiff(parent, rev))
3087 3114 elif source == b'parent-smallest':
3088 3115 p1diff = orig.revdiff(p1, rev)
3089 3116 parent = p1
3090 3117 diff = p1diff
3091 3118 if p2 != nullid:
3092 3119 p2diff = orig.revdiff(p2, rev)
3093 3120 if len(p1diff) > len(p2diff):
3094 3121 parent = p2
3095 3122 diff = p2diff
3096 3123 baserev = orig.rev(parent)
3097 3124 cachedelta = (baserev, diff)
3098 3125 elif source == b'storage':
3099 3126 baserev = orig.deltaparent(rev)
3100 3127 cachedelta = (baserev, orig.revdiff(orig.node(baserev), rev))
3101 3128
3102 3129 return (
3103 3130 (text, tr, linkrev, p1, p2),
3104 3131 {'node': node, 'flags': flags, 'cachedelta': cachedelta},
3105 3132 )
3106 3133
3107 3134
3108 3135 @contextlib.contextmanager
3109 3136 def _temprevlog(ui, orig, truncaterev):
3110 3137 from mercurial import vfs as vfsmod
3111 3138
3112 3139 if orig._inline:
3113 3140 raise error.Abort('not supporting inline revlog (yet)')
3114 3141 revlogkwargs = {}
3115 3142 k = 'upperboundcomp'
3116 3143 if util.safehasattr(orig, k):
3117 3144 revlogkwargs[k] = getattr(orig, k)
3118 3145
3119 3146 indexfile = getattr(orig, '_indexfile', None)
3120 3147 if indexfile is None:
3121 3148 # compatibility with <= hg-5.8
3122 3149 indexfile = getattr(orig, 'indexfile')
3123 3150 origindexpath = orig.opener.join(indexfile)
3124 3151
3125 3152 datafile = getattr(orig, '_datafile', getattr(orig, 'datafile'))
3126 3153 origdatapath = orig.opener.join(datafile)
3127 3154 radix = b'revlog'
3128 3155 indexname = b'revlog.i'
3129 3156 dataname = b'revlog.d'
3130 3157
3131 3158 tmpdir = tempfile.mkdtemp(prefix='tmp-hgperf-')
3132 3159 try:
3133 3160 # copy the data file in a temporary directory
3134 3161 ui.debug('copying data in %s\n' % tmpdir)
3135 3162 destindexpath = os.path.join(tmpdir, 'revlog.i')
3136 3163 destdatapath = os.path.join(tmpdir, 'revlog.d')
3137 3164 shutil.copyfile(origindexpath, destindexpath)
3138 3165 shutil.copyfile(origdatapath, destdatapath)
3139 3166
3140 3167 # remove the data we want to add again
3141 3168 ui.debug('truncating data to be rewritten\n')
3142 3169 with open(destindexpath, 'ab') as index:
3143 3170 index.seek(0)
3144 3171 index.truncate(truncaterev * orig._io.size)
3145 3172 with open(destdatapath, 'ab') as data:
3146 3173 data.seek(0)
3147 3174 data.truncate(orig.start(truncaterev))
3148 3175
3149 3176 # instantiate a new revlog from the temporary copy
3150 3177 ui.debug('truncating adding to be rewritten\n')
3151 3178 vfs = vfsmod.vfs(tmpdir)
3152 3179 vfs.options = getattr(orig.opener, 'options', None)
3153 3180
3154 3181 try:
3155 3182 dest = revlog(vfs, radix=radix, **revlogkwargs)
3156 3183 except TypeError:
3157 3184 dest = revlog(
3158 3185 vfs, indexfile=indexname, datafile=dataname, **revlogkwargs
3159 3186 )
3160 3187 if dest._inline:
3161 3188 raise error.Abort('not supporting inline revlog (yet)')
3162 3189 # make sure internals are initialized
3163 3190 dest.revision(len(dest) - 1)
3164 3191 yield dest
3165 3192 del dest, vfs
3166 3193 finally:
3167 3194 shutil.rmtree(tmpdir, True)
3168 3195
3169 3196
3170 3197 @command(
3171 3198 b'perf::revlogchunks|perfrevlogchunks',
3172 3199 revlogopts
3173 3200 + formatteropts
3174 3201 + [
3175 3202 (b'e', b'engines', b'', b'compression engines to use'),
3176 3203 (b's', b'startrev', 0, b'revision to start at'),
3177 3204 ],
3178 3205 b'-c|-m|FILE',
3179 3206 )
3180 3207 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
3181 3208 """Benchmark operations on revlog chunks.
3182 3209
3183 3210 Logically, each revlog is a collection of fulltext revisions. However,
3184 3211 stored within each revlog are "chunks" of possibly compressed data. This
3185 3212 data needs to be read and decompressed or compressed and written.
3186 3213
3187 3214 This command measures the time it takes to read+decompress and recompress
3188 3215 chunks in a revlog. It effectively isolates I/O and compression performance.
3189 3216 For measurements of higher-level operations like resolving revisions,
3190 3217 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
3191 3218 """
3192 3219 opts = _byteskwargs(opts)
3193 3220
3194 3221 rl = cmdutil.openrevlog(repo, b'perfrevlogchunks', file_, opts)
3195 3222
3196 3223 # _chunkraw was renamed to _getsegmentforrevs.
3197 3224 try:
3198 3225 segmentforrevs = rl._getsegmentforrevs
3199 3226 except AttributeError:
3200 3227 segmentforrevs = rl._chunkraw
3201 3228
3202 3229 # Verify engines argument.
3203 3230 if engines:
3204 3231 engines = {e.strip() for e in engines.split(b',')}
3205 3232 for engine in engines:
3206 3233 try:
3207 3234 util.compressionengines[engine]
3208 3235 except KeyError:
3209 3236 raise error.Abort(b'unknown compression engine: %s' % engine)
3210 3237 else:
3211 3238 engines = []
3212 3239 for e in util.compengines:
3213 3240 engine = util.compengines[e]
3214 3241 try:
3215 3242 if engine.available():
3216 3243 engine.revlogcompressor().compress(b'dummy')
3217 3244 engines.append(e)
3218 3245 except NotImplementedError:
3219 3246 pass
3220 3247
3221 3248 revs = list(rl.revs(startrev, len(rl) - 1))
3222 3249
3223 3250 def rlfh(rl):
3224 3251 if rl._inline:
3225 3252 indexfile = getattr(rl, '_indexfile', None)
3226 3253 if indexfile is None:
3227 3254 # compatibility with <= hg-5.8
3228 3255 indexfile = getattr(rl, 'indexfile')
3229 3256 return getsvfs(repo)(indexfile)
3230 3257 else:
3231 3258 datafile = getattr(rl, 'datafile', getattr(rl, 'datafile'))
3232 3259 return getsvfs(repo)(datafile)
3233 3260
3234 3261 def doread():
3235 3262 rl.clearcaches()
3236 3263 for rev in revs:
3237 3264 segmentforrevs(rev, rev)
3238 3265
3239 3266 def doreadcachedfh():
3240 3267 rl.clearcaches()
3241 3268 fh = rlfh(rl)
3242 3269 for rev in revs:
3243 3270 segmentforrevs(rev, rev, df=fh)
3244 3271
3245 3272 def doreadbatch():
3246 3273 rl.clearcaches()
3247 3274 segmentforrevs(revs[0], revs[-1])
3248 3275
3249 3276 def doreadbatchcachedfh():
3250 3277 rl.clearcaches()
3251 3278 fh = rlfh(rl)
3252 3279 segmentforrevs(revs[0], revs[-1], df=fh)
3253 3280
3254 3281 def dochunk():
3255 3282 rl.clearcaches()
3256 3283 fh = rlfh(rl)
3257 3284 for rev in revs:
3258 3285 rl._chunk(rev, df=fh)
3259 3286
3260 3287 chunks = [None]
3261 3288
3262 3289 def dochunkbatch():
3263 3290 rl.clearcaches()
3264 3291 fh = rlfh(rl)
3265 3292 # Save chunks as a side-effect.
3266 3293 chunks[0] = rl._chunks(revs, df=fh)
3267 3294
3268 3295 def docompress(compressor):
3269 3296 rl.clearcaches()
3270 3297
3271 3298 try:
3272 3299 # Swap in the requested compression engine.
3273 3300 oldcompressor = rl._compressor
3274 3301 rl._compressor = compressor
3275 3302 for chunk in chunks[0]:
3276 3303 rl.compress(chunk)
3277 3304 finally:
3278 3305 rl._compressor = oldcompressor
3279 3306
3280 3307 benches = [
3281 3308 (lambda: doread(), b'read'),
3282 3309 (lambda: doreadcachedfh(), b'read w/ reused fd'),
3283 3310 (lambda: doreadbatch(), b'read batch'),
3284 3311 (lambda: doreadbatchcachedfh(), b'read batch w/ reused fd'),
3285 3312 (lambda: dochunk(), b'chunk'),
3286 3313 (lambda: dochunkbatch(), b'chunk batch'),
3287 3314 ]
3288 3315
3289 3316 for engine in sorted(engines):
3290 3317 compressor = util.compengines[engine].revlogcompressor()
3291 3318 benches.append(
3292 3319 (
3293 3320 functools.partial(docompress, compressor),
3294 3321 b'compress w/ %s' % engine,
3295 3322 )
3296 3323 )
3297 3324
3298 3325 for fn, title in benches:
3299 3326 timer, fm = gettimer(ui, opts)
3300 3327 timer(fn, title=title)
3301 3328 fm.end()
3302 3329
3303 3330
3304 3331 @command(
3305 3332 b'perf::revlogrevision|perfrevlogrevision',
3306 3333 revlogopts
3307 3334 + formatteropts
3308 3335 + [(b'', b'cache', False, b'use caches instead of clearing')],
3309 3336 b'-c|-m|FILE REV',
3310 3337 )
3311 3338 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
3312 3339 """Benchmark obtaining a revlog revision.
3313 3340
3314 3341 Obtaining a revlog revision consists of roughly the following steps:
3315 3342
3316 3343 1. Compute the delta chain
3317 3344 2. Slice the delta chain if applicable
3318 3345 3. Obtain the raw chunks for that delta chain
3319 3346 4. Decompress each raw chunk
3320 3347 5. Apply binary patches to obtain fulltext
3321 3348 6. Verify hash of fulltext
3322 3349
3323 3350 This command measures the time spent in each of these phases.
3324 3351 """
3325 3352 opts = _byteskwargs(opts)
3326 3353
3327 3354 if opts.get(b'changelog') or opts.get(b'manifest'):
3328 3355 file_, rev = None, file_
3329 3356 elif rev is None:
3330 3357 raise error.CommandError(b'perfrevlogrevision', b'invalid arguments')
3331 3358
3332 3359 r = cmdutil.openrevlog(repo, b'perfrevlogrevision', file_, opts)
3333 3360
3334 3361 # _chunkraw was renamed to _getsegmentforrevs.
3335 3362 try:
3336 3363 segmentforrevs = r._getsegmentforrevs
3337 3364 except AttributeError:
3338 3365 segmentforrevs = r._chunkraw
3339 3366
3340 3367 node = r.lookup(rev)
3341 3368 rev = r.rev(node)
3342 3369
3343 3370 def getrawchunks(data, chain):
3344 3371 start = r.start
3345 3372 length = r.length
3346 3373 inline = r._inline
3347 3374 try:
3348 3375 iosize = r.index.entry_size
3349 3376 except AttributeError:
3350 3377 iosize = r._io.size
3351 3378 buffer = util.buffer
3352 3379
3353 3380 chunks = []
3354 3381 ladd = chunks.append
3355 3382 for idx, item in enumerate(chain):
3356 3383 offset = start(item[0])
3357 3384 bits = data[idx]
3358 3385 for rev in item:
3359 3386 chunkstart = start(rev)
3360 3387 if inline:
3361 3388 chunkstart += (rev + 1) * iosize
3362 3389 chunklength = length(rev)
3363 3390 ladd(buffer(bits, chunkstart - offset, chunklength))
3364 3391
3365 3392 return chunks
3366 3393
3367 3394 def dodeltachain(rev):
3368 3395 if not cache:
3369 3396 r.clearcaches()
3370 3397 r._deltachain(rev)
3371 3398
3372 3399 def doread(chain):
3373 3400 if not cache:
3374 3401 r.clearcaches()
3375 3402 for item in slicedchain:
3376 3403 segmentforrevs(item[0], item[-1])
3377 3404
3378 3405 def doslice(r, chain, size):
3379 3406 for s in slicechunk(r, chain, targetsize=size):
3380 3407 pass
3381 3408
3382 3409 def dorawchunks(data, chain):
3383 3410 if not cache:
3384 3411 r.clearcaches()
3385 3412 getrawchunks(data, chain)
3386 3413
3387 3414 def dodecompress(chunks):
3388 3415 decomp = r.decompress
3389 3416 for chunk in chunks:
3390 3417 decomp(chunk)
3391 3418
3392 3419 def dopatch(text, bins):
3393 3420 if not cache:
3394 3421 r.clearcaches()
3395 3422 mdiff.patches(text, bins)
3396 3423
3397 3424 def dohash(text):
3398 3425 if not cache:
3399 3426 r.clearcaches()
3400 3427 r.checkhash(text, node, rev=rev)
3401 3428
3402 3429 def dorevision():
3403 3430 if not cache:
3404 3431 r.clearcaches()
3405 3432 r.revision(node)
3406 3433
3407 3434 try:
3408 3435 from mercurial.revlogutils.deltas import slicechunk
3409 3436 except ImportError:
3410 3437 slicechunk = getattr(revlog, '_slicechunk', None)
3411 3438
3412 3439 size = r.length(rev)
3413 3440 chain = r._deltachain(rev)[0]
3414 3441 if not getattr(r, '_withsparseread', False):
3415 3442 slicedchain = (chain,)
3416 3443 else:
3417 3444 slicedchain = tuple(slicechunk(r, chain, targetsize=size))
3418 3445 data = [segmentforrevs(seg[0], seg[-1])[1] for seg in slicedchain]
3419 3446 rawchunks = getrawchunks(data, slicedchain)
3420 3447 bins = r._chunks(chain)
3421 3448 text = bytes(bins[0])
3422 3449 bins = bins[1:]
3423 3450 text = mdiff.patches(text, bins)
3424 3451
3425 3452 benches = [
3426 3453 (lambda: dorevision(), b'full'),
3427 3454 (lambda: dodeltachain(rev), b'deltachain'),
3428 3455 (lambda: doread(chain), b'read'),
3429 3456 ]
3430 3457
3431 3458 if getattr(r, '_withsparseread', False):
3432 3459 slicing = (lambda: doslice(r, chain, size), b'slice-sparse-chain')
3433 3460 benches.append(slicing)
3434 3461
3435 3462 benches.extend(
3436 3463 [
3437 3464 (lambda: dorawchunks(data, slicedchain), b'rawchunks'),
3438 3465 (lambda: dodecompress(rawchunks), b'decompress'),
3439 3466 (lambda: dopatch(text, bins), b'patch'),
3440 3467 (lambda: dohash(text), b'hash'),
3441 3468 ]
3442 3469 )
3443 3470
3444 3471 timer, fm = gettimer(ui, opts)
3445 3472 for fn, title in benches:
3446 3473 timer(fn, title=title)
3447 3474 fm.end()
3448 3475
3449 3476
3450 3477 @command(
3451 3478 b'perf::revset|perfrevset',
3452 3479 [
3453 3480 (b'C', b'clear', False, b'clear volatile cache between each call.'),
3454 3481 (b'', b'contexts', False, b'obtain changectx for each revision'),
3455 3482 ]
3456 3483 + formatteropts,
3457 3484 b"REVSET",
3458 3485 )
3459 3486 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
3460 3487 """benchmark the execution time of a revset
3461 3488
3462 3489 Use the --clean option if need to evaluate the impact of build volatile
3463 3490 revisions set cache on the revset execution. Volatile cache hold filtered
3464 3491 and obsolete related cache."""
3465 3492 opts = _byteskwargs(opts)
3466 3493
3467 3494 timer, fm = gettimer(ui, opts)
3468 3495
3469 3496 def d():
3470 3497 if clear:
3471 3498 repo.invalidatevolatilesets()
3472 3499 if contexts:
3473 3500 for ctx in repo.set(expr):
3474 3501 pass
3475 3502 else:
3476 3503 for r in repo.revs(expr):
3477 3504 pass
3478 3505
3479 3506 timer(d)
3480 3507 fm.end()
3481 3508
3482 3509
3483 3510 @command(
3484 3511 b'perf::volatilesets|perfvolatilesets',
3485 3512 [
3486 3513 (b'', b'clear-obsstore', False, b'drop obsstore between each call.'),
3487 3514 ]
3488 3515 + formatteropts,
3489 3516 )
3490 3517 def perfvolatilesets(ui, repo, *names, **opts):
3491 3518 """benchmark the computation of various volatile set
3492 3519
3493 3520 Volatile set computes element related to filtering and obsolescence."""
3494 3521 opts = _byteskwargs(opts)
3495 3522 timer, fm = gettimer(ui, opts)
3496 3523 repo = repo.unfiltered()
3497 3524
3498 3525 def getobs(name):
3499 3526 def d():
3500 3527 repo.invalidatevolatilesets()
3501 3528 if opts[b'clear_obsstore']:
3502 3529 clearfilecache(repo, b'obsstore')
3503 3530 obsolete.getrevs(repo, name)
3504 3531
3505 3532 return d
3506 3533
3507 3534 allobs = sorted(obsolete.cachefuncs)
3508 3535 if names:
3509 3536 allobs = [n for n in allobs if n in names]
3510 3537
3511 3538 for name in allobs:
3512 3539 timer(getobs(name), title=name)
3513 3540
3514 3541 def getfiltered(name):
3515 3542 def d():
3516 3543 repo.invalidatevolatilesets()
3517 3544 if opts[b'clear_obsstore']:
3518 3545 clearfilecache(repo, b'obsstore')
3519 3546 repoview.filterrevs(repo, name)
3520 3547
3521 3548 return d
3522 3549
3523 3550 allfilter = sorted(repoview.filtertable)
3524 3551 if names:
3525 3552 allfilter = [n for n in allfilter if n in names]
3526 3553
3527 3554 for name in allfilter:
3528 3555 timer(getfiltered(name), title=name)
3529 3556 fm.end()
3530 3557
3531 3558
3532 3559 @command(
3533 3560 b'perf::branchmap|perfbranchmap',
3534 3561 [
3535 3562 (b'f', b'full', False, b'Includes build time of subset'),
3536 3563 (
3537 3564 b'',
3538 3565 b'clear-revbranch',
3539 3566 False,
3540 3567 b'purge the revbranch cache between computation',
3541 3568 ),
3542 3569 ]
3543 3570 + formatteropts,
3544 3571 )
3545 3572 def perfbranchmap(ui, repo, *filternames, **opts):
3546 3573 """benchmark the update of a branchmap
3547 3574
3548 3575 This benchmarks the full repo.branchmap() call with read and write disabled
3549 3576 """
3550 3577 opts = _byteskwargs(opts)
3551 3578 full = opts.get(b"full", False)
3552 3579 clear_revbranch = opts.get(b"clear_revbranch", False)
3553 3580 timer, fm = gettimer(ui, opts)
3554 3581
3555 3582 def getbranchmap(filtername):
3556 3583 """generate a benchmark function for the filtername"""
3557 3584 if filtername is None:
3558 3585 view = repo
3559 3586 else:
3560 3587 view = repo.filtered(filtername)
3561 3588 if util.safehasattr(view._branchcaches, '_per_filter'):
3562 3589 filtered = view._branchcaches._per_filter
3563 3590 else:
3564 3591 # older versions
3565 3592 filtered = view._branchcaches
3566 3593
3567 3594 def d():
3568 3595 if clear_revbranch:
3569 3596 repo.revbranchcache()._clear()
3570 3597 if full:
3571 3598 view._branchcaches.clear()
3572 3599 else:
3573 3600 filtered.pop(filtername, None)
3574 3601 view.branchmap()
3575 3602
3576 3603 return d
3577 3604
3578 3605 # add filter in smaller subset to bigger subset
3579 3606 possiblefilters = set(repoview.filtertable)
3580 3607 if filternames:
3581 3608 possiblefilters &= set(filternames)
3582 3609 subsettable = getbranchmapsubsettable()
3583 3610 allfilters = []
3584 3611 while possiblefilters:
3585 3612 for name in possiblefilters:
3586 3613 subset = subsettable.get(name)
3587 3614 if subset not in possiblefilters:
3588 3615 break
3589 3616 else:
3590 3617 assert False, b'subset cycle %s!' % possiblefilters
3591 3618 allfilters.append(name)
3592 3619 possiblefilters.remove(name)
3593 3620
3594 3621 # warm the cache
3595 3622 if not full:
3596 3623 for name in allfilters:
3597 3624 repo.filtered(name).branchmap()
3598 3625 if not filternames or b'unfiltered' in filternames:
3599 3626 # add unfiltered
3600 3627 allfilters.append(None)
3601 3628
3602 3629 if util.safehasattr(branchmap.branchcache, 'fromfile'):
3603 3630 branchcacheread = safeattrsetter(branchmap.branchcache, b'fromfile')
3604 3631 branchcacheread.set(classmethod(lambda *args: None))
3605 3632 else:
3606 3633 # older versions
3607 3634 branchcacheread = safeattrsetter(branchmap, b'read')
3608 3635 branchcacheread.set(lambda *args: None)
3609 3636 branchcachewrite = safeattrsetter(branchmap.branchcache, b'write')
3610 3637 branchcachewrite.set(lambda *args: None)
3611 3638 try:
3612 3639 for name in allfilters:
3613 3640 printname = name
3614 3641 if name is None:
3615 3642 printname = b'unfiltered'
3616 3643 timer(getbranchmap(name), title=printname)
3617 3644 finally:
3618 3645 branchcacheread.restore()
3619 3646 branchcachewrite.restore()
3620 3647 fm.end()
3621 3648
3622 3649
3623 3650 @command(
3624 3651 b'perf::branchmapupdate|perfbranchmapupdate',
3625 3652 [
3626 3653 (b'', b'base', [], b'subset of revision to start from'),
3627 3654 (b'', b'target', [], b'subset of revision to end with'),
3628 3655 (b'', b'clear-caches', False, b'clear cache between each runs'),
3629 3656 ]
3630 3657 + formatteropts,
3631 3658 )
3632 3659 def perfbranchmapupdate(ui, repo, base=(), target=(), **opts):
3633 3660 """benchmark branchmap update from for <base> revs to <target> revs
3634 3661
3635 3662 If `--clear-caches` is passed, the following items will be reset before
3636 3663 each update:
3637 3664 * the changelog instance and associated indexes
3638 3665 * the rev-branch-cache instance
3639 3666
3640 3667 Examples:
3641 3668
3642 3669 # update for the one last revision
3643 3670 $ hg perfbranchmapupdate --base 'not tip' --target 'tip'
3644 3671
3645 3672 $ update for change coming with a new branch
3646 3673 $ hg perfbranchmapupdate --base 'stable' --target 'default'
3647 3674 """
3648 3675 from mercurial import branchmap
3649 3676 from mercurial import repoview
3650 3677
3651 3678 opts = _byteskwargs(opts)
3652 3679 timer, fm = gettimer(ui, opts)
3653 3680 clearcaches = opts[b'clear_caches']
3654 3681 unfi = repo.unfiltered()
3655 3682 x = [None] # used to pass data between closure
3656 3683
3657 3684 # we use a `list` here to avoid possible side effect from smartset
3658 3685 baserevs = list(scmutil.revrange(repo, base))
3659 3686 targetrevs = list(scmutil.revrange(repo, target))
3660 3687 if not baserevs:
3661 3688 raise error.Abort(b'no revisions selected for --base')
3662 3689 if not targetrevs:
3663 3690 raise error.Abort(b'no revisions selected for --target')
3664 3691
3665 3692 # make sure the target branchmap also contains the one in the base
3666 3693 targetrevs = list(set(baserevs) | set(targetrevs))
3667 3694 targetrevs.sort()
3668 3695
3669 3696 cl = repo.changelog
3670 3697 allbaserevs = list(cl.ancestors(baserevs, inclusive=True))
3671 3698 allbaserevs.sort()
3672 3699 alltargetrevs = frozenset(cl.ancestors(targetrevs, inclusive=True))
3673 3700
3674 3701 newrevs = list(alltargetrevs.difference(allbaserevs))
3675 3702 newrevs.sort()
3676 3703
3677 3704 allrevs = frozenset(unfi.changelog.revs())
3678 3705 basefilterrevs = frozenset(allrevs.difference(allbaserevs))
3679 3706 targetfilterrevs = frozenset(allrevs.difference(alltargetrevs))
3680 3707
3681 3708 def basefilter(repo, visibilityexceptions=None):
3682 3709 return basefilterrevs
3683 3710
3684 3711 def targetfilter(repo, visibilityexceptions=None):
3685 3712 return targetfilterrevs
3686 3713
3687 3714 msg = b'benchmark of branchmap with %d revisions with %d new ones\n'
3688 3715 ui.status(msg % (len(allbaserevs), len(newrevs)))
3689 3716 if targetfilterrevs:
3690 3717 msg = b'(%d revisions still filtered)\n'
3691 3718 ui.status(msg % len(targetfilterrevs))
3692 3719
3693 3720 try:
3694 3721 repoview.filtertable[b'__perf_branchmap_update_base'] = basefilter
3695 3722 repoview.filtertable[b'__perf_branchmap_update_target'] = targetfilter
3696 3723
3697 3724 baserepo = repo.filtered(b'__perf_branchmap_update_base')
3698 3725 targetrepo = repo.filtered(b'__perf_branchmap_update_target')
3699 3726
3700 3727 # try to find an existing branchmap to reuse
3701 3728 subsettable = getbranchmapsubsettable()
3702 3729 candidatefilter = subsettable.get(None)
3703 3730 while candidatefilter is not None:
3704 3731 candidatebm = repo.filtered(candidatefilter).branchmap()
3705 3732 if candidatebm.validfor(baserepo):
3706 3733 filtered = repoview.filterrevs(repo, candidatefilter)
3707 3734 missing = [r for r in allbaserevs if r in filtered]
3708 3735 base = candidatebm.copy()
3709 3736 base.update(baserepo, missing)
3710 3737 break
3711 3738 candidatefilter = subsettable.get(candidatefilter)
3712 3739 else:
3713 3740 # no suitable subset where found
3714 3741 base = branchmap.branchcache()
3715 3742 base.update(baserepo, allbaserevs)
3716 3743
3717 3744 def setup():
3718 3745 x[0] = base.copy()
3719 3746 if clearcaches:
3720 3747 unfi._revbranchcache = None
3721 3748 clearchangelog(repo)
3722 3749
3723 3750 def bench():
3724 3751 x[0].update(targetrepo, newrevs)
3725 3752
3726 3753 timer(bench, setup=setup)
3727 3754 fm.end()
3728 3755 finally:
3729 3756 repoview.filtertable.pop(b'__perf_branchmap_update_base', None)
3730 3757 repoview.filtertable.pop(b'__perf_branchmap_update_target', None)
3731 3758
3732 3759
3733 3760 @command(
3734 3761 b'perf::branchmapload|perfbranchmapload',
3735 3762 [
3736 3763 (b'f', b'filter', b'', b'Specify repoview filter'),
3737 3764 (b'', b'list', False, b'List brachmap filter caches'),
3738 3765 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
3739 3766 ]
3740 3767 + formatteropts,
3741 3768 )
3742 3769 def perfbranchmapload(ui, repo, filter=b'', list=False, **opts):
3743 3770 """benchmark reading the branchmap"""
3744 3771 opts = _byteskwargs(opts)
3745 3772 clearrevlogs = opts[b'clear_revlogs']
3746 3773
3747 3774 if list:
3748 3775 for name, kind, st in repo.cachevfs.readdir(stat=True):
3749 3776 if name.startswith(b'branch2'):
3750 3777 filtername = name.partition(b'-')[2] or b'unfiltered'
3751 3778 ui.status(
3752 3779 b'%s - %s\n' % (filtername, util.bytecount(st.st_size))
3753 3780 )
3754 3781 return
3755 3782 if not filter:
3756 3783 filter = None
3757 3784 subsettable = getbranchmapsubsettable()
3758 3785 if filter is None:
3759 3786 repo = repo.unfiltered()
3760 3787 else:
3761 3788 repo = repoview.repoview(repo, filter)
3762 3789
3763 3790 repo.branchmap() # make sure we have a relevant, up to date branchmap
3764 3791
3765 3792 try:
3766 3793 fromfile = branchmap.branchcache.fromfile
3767 3794 except AttributeError:
3768 3795 # older versions
3769 3796 fromfile = branchmap.read
3770 3797
3771 3798 currentfilter = filter
3772 3799 # try once without timer, the filter may not be cached
3773 3800 while fromfile(repo) is None:
3774 3801 currentfilter = subsettable.get(currentfilter)
3775 3802 if currentfilter is None:
3776 3803 raise error.Abort(
3777 3804 b'No branchmap cached for %s repo' % (filter or b'unfiltered')
3778 3805 )
3779 3806 repo = repo.filtered(currentfilter)
3780 3807 timer, fm = gettimer(ui, opts)
3781 3808
3782 3809 def setup():
3783 3810 if clearrevlogs:
3784 3811 clearchangelog(repo)
3785 3812
3786 3813 def bench():
3787 3814 fromfile(repo)
3788 3815
3789 3816 timer(bench, setup=setup)
3790 3817 fm.end()
3791 3818
3792 3819
3793 3820 @command(b'perf::loadmarkers|perfloadmarkers')
3794 3821 def perfloadmarkers(ui, repo):
3795 3822 """benchmark the time to parse the on-disk markers for a repo
3796 3823
3797 3824 Result is the number of markers in the repo."""
3798 3825 timer, fm = gettimer(ui)
3799 3826 svfs = getsvfs(repo)
3800 3827 timer(lambda: len(obsolete.obsstore(repo, svfs)))
3801 3828 fm.end()
3802 3829
3803 3830
3804 3831 @command(
3805 3832 b'perf::lrucachedict|perflrucachedict',
3806 3833 formatteropts
3807 3834 + [
3808 3835 (b'', b'costlimit', 0, b'maximum total cost of items in cache'),
3809 3836 (b'', b'mincost', 0, b'smallest cost of items in cache'),
3810 3837 (b'', b'maxcost', 100, b'maximum cost of items in cache'),
3811 3838 (b'', b'size', 4, b'size of cache'),
3812 3839 (b'', b'gets', 10000, b'number of key lookups'),
3813 3840 (b'', b'sets', 10000, b'number of key sets'),
3814 3841 (b'', b'mixed', 10000, b'number of mixed mode operations'),
3815 3842 (
3816 3843 b'',
3817 3844 b'mixedgetfreq',
3818 3845 50,
3819 3846 b'frequency of get vs set ops in mixed mode',
3820 3847 ),
3821 3848 ],
3822 3849 norepo=True,
3823 3850 )
3824 3851 def perflrucache(
3825 3852 ui,
3826 3853 mincost=0,
3827 3854 maxcost=100,
3828 3855 costlimit=0,
3829 3856 size=4,
3830 3857 gets=10000,
3831 3858 sets=10000,
3832 3859 mixed=10000,
3833 3860 mixedgetfreq=50,
3834 3861 **opts
3835 3862 ):
3836 3863 opts = _byteskwargs(opts)
3837 3864
3838 3865 def doinit():
3839 3866 for i in _xrange(10000):
3840 3867 util.lrucachedict(size)
3841 3868
3842 3869 costrange = list(range(mincost, maxcost + 1))
3843 3870
3844 3871 values = []
3845 3872 for i in _xrange(size):
3846 3873 values.append(random.randint(0, _maxint))
3847 3874
3848 3875 # Get mode fills the cache and tests raw lookup performance with no
3849 3876 # eviction.
3850 3877 getseq = []
3851 3878 for i in _xrange(gets):
3852 3879 getseq.append(random.choice(values))
3853 3880
3854 3881 def dogets():
3855 3882 d = util.lrucachedict(size)
3856 3883 for v in values:
3857 3884 d[v] = v
3858 3885 for key in getseq:
3859 3886 value = d[key]
3860 3887 value # silence pyflakes warning
3861 3888
3862 3889 def dogetscost():
3863 3890 d = util.lrucachedict(size, maxcost=costlimit)
3864 3891 for i, v in enumerate(values):
3865 3892 d.insert(v, v, cost=costs[i])
3866 3893 for key in getseq:
3867 3894 try:
3868 3895 value = d[key]
3869 3896 value # silence pyflakes warning
3870 3897 except KeyError:
3871 3898 pass
3872 3899
3873 3900 # Set mode tests insertion speed with cache eviction.
3874 3901 setseq = []
3875 3902 costs = []
3876 3903 for i in _xrange(sets):
3877 3904 setseq.append(random.randint(0, _maxint))
3878 3905 costs.append(random.choice(costrange))
3879 3906
3880 3907 def doinserts():
3881 3908 d = util.lrucachedict(size)
3882 3909 for v in setseq:
3883 3910 d.insert(v, v)
3884 3911
3885 3912 def doinsertscost():
3886 3913 d = util.lrucachedict(size, maxcost=costlimit)
3887 3914 for i, v in enumerate(setseq):
3888 3915 d.insert(v, v, cost=costs[i])
3889 3916
3890 3917 def dosets():
3891 3918 d = util.lrucachedict(size)
3892 3919 for v in setseq:
3893 3920 d[v] = v
3894 3921
3895 3922 # Mixed mode randomly performs gets and sets with eviction.
3896 3923 mixedops = []
3897 3924 for i in _xrange(mixed):
3898 3925 r = random.randint(0, 100)
3899 3926 if r < mixedgetfreq:
3900 3927 op = 0
3901 3928 else:
3902 3929 op = 1
3903 3930
3904 3931 mixedops.append(
3905 3932 (op, random.randint(0, size * 2), random.choice(costrange))
3906 3933 )
3907 3934
3908 3935 def domixed():
3909 3936 d = util.lrucachedict(size)
3910 3937
3911 3938 for op, v, cost in mixedops:
3912 3939 if op == 0:
3913 3940 try:
3914 3941 d[v]
3915 3942 except KeyError:
3916 3943 pass
3917 3944 else:
3918 3945 d[v] = v
3919 3946
3920 3947 def domixedcost():
3921 3948 d = util.lrucachedict(size, maxcost=costlimit)
3922 3949
3923 3950 for op, v, cost in mixedops:
3924 3951 if op == 0:
3925 3952 try:
3926 3953 d[v]
3927 3954 except KeyError:
3928 3955 pass
3929 3956 else:
3930 3957 d.insert(v, v, cost=cost)
3931 3958
3932 3959 benches = [
3933 3960 (doinit, b'init'),
3934 3961 ]
3935 3962
3936 3963 if costlimit:
3937 3964 benches.extend(
3938 3965 [
3939 3966 (dogetscost, b'gets w/ cost limit'),
3940 3967 (doinsertscost, b'inserts w/ cost limit'),
3941 3968 (domixedcost, b'mixed w/ cost limit'),
3942 3969 ]
3943 3970 )
3944 3971 else:
3945 3972 benches.extend(
3946 3973 [
3947 3974 (dogets, b'gets'),
3948 3975 (doinserts, b'inserts'),
3949 3976 (dosets, b'sets'),
3950 3977 (domixed, b'mixed'),
3951 3978 ]
3952 3979 )
3953 3980
3954 3981 for fn, title in benches:
3955 3982 timer, fm = gettimer(ui, opts)
3956 3983 timer(fn, title=title)
3957 3984 fm.end()
3958 3985
3959 3986
3960 3987 @command(
3961 3988 b'perf::write|perfwrite',
3962 3989 formatteropts
3963 3990 + [
3964 3991 (b'', b'write-method', b'write', b'ui write method'),
3965 3992 (b'', b'nlines', 100, b'number of lines'),
3966 3993 (b'', b'nitems', 100, b'number of items (per line)'),
3967 3994 (b'', b'item', b'x', b'item that is written'),
3968 3995 (b'', b'batch-line', None, b'pass whole line to write method at once'),
3969 3996 (b'', b'flush-line', None, b'flush after each line'),
3970 3997 ],
3971 3998 )
3972 3999 def perfwrite(ui, repo, **opts):
3973 4000 """microbenchmark ui.write (and others)"""
3974 4001 opts = _byteskwargs(opts)
3975 4002
3976 4003 write = getattr(ui, _sysstr(opts[b'write_method']))
3977 4004 nlines = int(opts[b'nlines'])
3978 4005 nitems = int(opts[b'nitems'])
3979 4006 item = opts[b'item']
3980 4007 batch_line = opts.get(b'batch_line')
3981 4008 flush_line = opts.get(b'flush_line')
3982 4009
3983 4010 if batch_line:
3984 4011 line = item * nitems + b'\n'
3985 4012
3986 4013 def benchmark():
3987 4014 for i in pycompat.xrange(nlines):
3988 4015 if batch_line:
3989 4016 write(line)
3990 4017 else:
3991 4018 for i in pycompat.xrange(nitems):
3992 4019 write(item)
3993 4020 write(b'\n')
3994 4021 if flush_line:
3995 4022 ui.flush()
3996 4023 ui.flush()
3997 4024
3998 4025 timer, fm = gettimer(ui, opts)
3999 4026 timer(benchmark)
4000 4027 fm.end()
4001 4028
4002 4029
4003 4030 def uisetup(ui):
4004 4031 if util.safehasattr(cmdutil, b'openrevlog') and not util.safehasattr(
4005 4032 commands, b'debugrevlogopts'
4006 4033 ):
4007 4034 # for "historical portability":
4008 4035 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
4009 4036 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
4010 4037 # openrevlog() should cause failure, because it has been
4011 4038 # available since 3.5 (or 49c583ca48c4).
4012 4039 def openrevlog(orig, repo, cmd, file_, opts):
4013 4040 if opts.get(b'dir') and not util.safehasattr(repo, b'dirlog'):
4014 4041 raise error.Abort(
4015 4042 b"This version doesn't support --dir option",
4016 4043 hint=b"use 3.5 or later",
4017 4044 )
4018 4045 return orig(repo, cmd, file_, opts)
4019 4046
4020 4047 extensions.wrapfunction(cmdutil, b'openrevlog', openrevlog)
4021 4048
4022 4049
4023 4050 @command(
4024 4051 b'perf::progress|perfprogress',
4025 4052 formatteropts
4026 4053 + [
4027 4054 (b'', b'topic', b'topic', b'topic for progress messages'),
4028 4055 (b'c', b'total', 1000000, b'total value we are progressing to'),
4029 4056 ],
4030 4057 norepo=True,
4031 4058 )
4032 4059 def perfprogress(ui, topic=None, total=None, **opts):
4033 4060 """printing of progress bars"""
4034 4061 opts = _byteskwargs(opts)
4035 4062
4036 4063 timer, fm = gettimer(ui, opts)
4037 4064
4038 4065 def doprogress():
4039 4066 with ui.makeprogress(topic, total=total) as progress:
4040 4067 for i in _xrange(total):
4041 4068 progress.increment()
4042 4069
4043 4070 timer(doprogress)
4044 4071 fm.end()
@@ -1,7975 +1,7974 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 nullrev,
17 17 short,
18 18 wdirrev,
19 19 )
20 20 from .pycompat import open
21 21 from . import (
22 22 archival,
23 23 bookmarks,
24 24 bundle2,
25 25 bundlecaches,
26 26 changegroup,
27 27 cmdutil,
28 28 copies,
29 29 debugcommands as debugcommandsmod,
30 30 destutil,
31 31 dirstateguard,
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 stringutil,
73 73 urlutil,
74 74 )
75 75
76 76 table = {}
77 77 table.update(debugcommandsmod.command._table)
78 78
79 79 command = registrar.command(table)
80 80 INTENT_READONLY = registrar.INTENT_READONLY
81 81
82 82 # common command options
83 83
84 84 globalopts = [
85 85 (
86 86 b'R',
87 87 b'repository',
88 88 b'',
89 89 _(b'repository root directory or name of overlay bundle file'),
90 90 _(b'REPO'),
91 91 ),
92 92 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
93 93 (
94 94 b'y',
95 95 b'noninteractive',
96 96 None,
97 97 _(
98 98 b'do not prompt, automatically pick the first choice for all prompts'
99 99 ),
100 100 ),
101 101 (b'q', b'quiet', None, _(b'suppress output')),
102 102 (b'v', b'verbose', None, _(b'enable additional output')),
103 103 (
104 104 b'',
105 105 b'color',
106 106 b'',
107 107 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
108 108 # and should not be translated
109 109 _(b"when to colorize (boolean, always, auto, never, or debug)"),
110 110 _(b'TYPE'),
111 111 ),
112 112 (
113 113 b'',
114 114 b'config',
115 115 [],
116 116 _(b'set/override config option (use \'section.name=value\')'),
117 117 _(b'CONFIG'),
118 118 ),
119 119 (b'', b'debug', None, _(b'enable debugging output')),
120 120 (b'', b'debugger', None, _(b'start debugger')),
121 121 (
122 122 b'',
123 123 b'encoding',
124 124 encoding.encoding,
125 125 _(b'set the charset encoding'),
126 126 _(b'ENCODE'),
127 127 ),
128 128 (
129 129 b'',
130 130 b'encodingmode',
131 131 encoding.encodingmode,
132 132 _(b'set the charset encoding mode'),
133 133 _(b'MODE'),
134 134 ),
135 135 (b'', b'traceback', None, _(b'always print a traceback on exception')),
136 136 (b'', b'time', None, _(b'time how long the command takes')),
137 137 (b'', b'profile', None, _(b'print command execution profile')),
138 138 (b'', b'version', None, _(b'output version information and exit')),
139 139 (b'h', b'help', None, _(b'display help and exit')),
140 140 (b'', b'hidden', False, _(b'consider hidden changesets')),
141 141 (
142 142 b'',
143 143 b'pager',
144 144 b'auto',
145 145 _(b"when to paginate (boolean, always, auto, or never)"),
146 146 _(b'TYPE'),
147 147 ),
148 148 ]
149 149
150 150 dryrunopts = cmdutil.dryrunopts
151 151 remoteopts = cmdutil.remoteopts
152 152 walkopts = cmdutil.walkopts
153 153 commitopts = cmdutil.commitopts
154 154 commitopts2 = cmdutil.commitopts2
155 155 commitopts3 = cmdutil.commitopts3
156 156 formatteropts = cmdutil.formatteropts
157 157 templateopts = cmdutil.templateopts
158 158 logopts = cmdutil.logopts
159 159 diffopts = cmdutil.diffopts
160 160 diffwsopts = cmdutil.diffwsopts
161 161 diffopts2 = cmdutil.diffopts2
162 162 mergetoolopts = cmdutil.mergetoolopts
163 163 similarityopts = cmdutil.similarityopts
164 164 subrepoopts = cmdutil.subrepoopts
165 165 debugrevlogopts = cmdutil.debugrevlogopts
166 166
167 167 # Commands start here, listed alphabetically
168 168
169 169
170 170 @command(
171 171 b'abort',
172 172 dryrunopts,
173 173 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
174 174 helpbasic=True,
175 175 )
176 176 def abort(ui, repo, **opts):
177 177 """abort an unfinished operation (EXPERIMENTAL)
178 178
179 179 Aborts a multistep operation like graft, histedit, rebase, merge,
180 180 and unshelve if they are in an unfinished state.
181 181
182 182 use --dry-run/-n to dry run the command.
183 183 """
184 184 dryrun = opts.get('dry_run')
185 185 abortstate = cmdutil.getunfinishedstate(repo)
186 186 if not abortstate:
187 187 raise error.StateError(_(b'no operation in progress'))
188 188 if not abortstate.abortfunc:
189 189 raise error.InputError(
190 190 (
191 191 _(b"%s in progress but does not support 'hg abort'")
192 192 % (abortstate._opname)
193 193 ),
194 194 hint=abortstate.hint(),
195 195 )
196 196 if dryrun:
197 197 ui.status(
198 198 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
199 199 )
200 200 return
201 201 return abortstate.abortfunc(ui, repo)
202 202
203 203
204 204 @command(
205 205 b'add',
206 206 walkopts + subrepoopts + dryrunopts,
207 207 _(b'[OPTION]... [FILE]...'),
208 208 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
209 209 helpbasic=True,
210 210 inferrepo=True,
211 211 )
212 212 def add(ui, repo, *pats, **opts):
213 213 """add the specified files on the next commit
214 214
215 215 Schedule files to be version controlled and added to the
216 216 repository.
217 217
218 218 The files will be added to the repository at the next commit. To
219 219 undo an add before that, see :hg:`forget`.
220 220
221 221 If no names are given, add all files to the repository (except
222 222 files matching ``.hgignore``).
223 223
224 224 .. container:: verbose
225 225
226 226 Examples:
227 227
228 228 - New (unknown) files are added
229 229 automatically by :hg:`add`::
230 230
231 231 $ ls
232 232 foo.c
233 233 $ hg status
234 234 ? foo.c
235 235 $ hg add
236 236 adding foo.c
237 237 $ hg status
238 238 A foo.c
239 239
240 240 - Specific files to be added can be specified::
241 241
242 242 $ ls
243 243 bar.c foo.c
244 244 $ hg status
245 245 ? bar.c
246 246 ? foo.c
247 247 $ hg add bar.c
248 248 $ hg status
249 249 A bar.c
250 250 ? foo.c
251 251
252 252 Returns 0 if all files are successfully added.
253 253 """
254 254
255 255 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
256 256 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
257 257 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
258 258 return rejected and 1 or 0
259 259
260 260
261 261 @command(
262 262 b'addremove',
263 263 similarityopts + subrepoopts + walkopts + dryrunopts,
264 264 _(b'[OPTION]... [FILE]...'),
265 265 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
266 266 inferrepo=True,
267 267 )
268 268 def addremove(ui, repo, *pats, **opts):
269 269 """add all new files, delete all missing files
270 270
271 271 Add all new files and remove all missing files from the
272 272 repository.
273 273
274 274 Unless names are given, new files are ignored if they match any of
275 275 the patterns in ``.hgignore``. As with add, these changes take
276 276 effect at the next commit.
277 277
278 278 Use the -s/--similarity option to detect renamed files. This
279 279 option takes a percentage between 0 (disabled) and 100 (files must
280 280 be identical) as its parameter. With a parameter greater than 0,
281 281 this compares every removed file with every added file and records
282 282 those similar enough as renames. Detecting renamed files this way
283 283 can be expensive. After using this option, :hg:`status -C` can be
284 284 used to check which files were identified as moved or renamed. If
285 285 not specified, -s/--similarity defaults to 100 and only renames of
286 286 identical files are detected.
287 287
288 288 .. container:: verbose
289 289
290 290 Examples:
291 291
292 292 - A number of files (bar.c and foo.c) are new,
293 293 while foobar.c has been removed (without using :hg:`remove`)
294 294 from the repository::
295 295
296 296 $ ls
297 297 bar.c foo.c
298 298 $ hg status
299 299 ! foobar.c
300 300 ? bar.c
301 301 ? foo.c
302 302 $ hg addremove
303 303 adding bar.c
304 304 adding foo.c
305 305 removing foobar.c
306 306 $ hg status
307 307 A bar.c
308 308 A foo.c
309 309 R foobar.c
310 310
311 311 - A file foobar.c was moved to foo.c without using :hg:`rename`.
312 312 Afterwards, it was edited slightly::
313 313
314 314 $ ls
315 315 foo.c
316 316 $ hg status
317 317 ! foobar.c
318 318 ? foo.c
319 319 $ hg addremove --similarity 90
320 320 removing foobar.c
321 321 adding foo.c
322 322 recording removal of foobar.c as rename to foo.c (94% similar)
323 323 $ hg status -C
324 324 A foo.c
325 325 foobar.c
326 326 R foobar.c
327 327
328 328 Returns 0 if all files are successfully added.
329 329 """
330 330 opts = pycompat.byteskwargs(opts)
331 331 if not opts.get(b'similarity'):
332 332 opts[b'similarity'] = b'100'
333 333 matcher = scmutil.match(repo[None], pats, opts)
334 334 relative = scmutil.anypats(pats, opts)
335 335 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
336 336 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
337 337
338 338
339 339 @command(
340 340 b'annotate|blame',
341 341 [
342 342 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
343 343 (
344 344 b'',
345 345 b'follow',
346 346 None,
347 347 _(b'follow copies/renames and list the filename (DEPRECATED)'),
348 348 ),
349 349 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
350 350 (b'a', b'text', None, _(b'treat all files as text')),
351 351 (b'u', b'user', None, _(b'list the author (long with -v)')),
352 352 (b'f', b'file', None, _(b'list the filename')),
353 353 (b'd', b'date', None, _(b'list the date (short with -q)')),
354 354 (b'n', b'number', None, _(b'list the revision number (default)')),
355 355 (b'c', b'changeset', None, _(b'list the changeset')),
356 356 (
357 357 b'l',
358 358 b'line-number',
359 359 None,
360 360 _(b'show line number at the first appearance'),
361 361 ),
362 362 (
363 363 b'',
364 364 b'skip',
365 365 [],
366 366 _(b'revset to not display (EXPERIMENTAL)'),
367 367 _(b'REV'),
368 368 ),
369 369 ]
370 370 + diffwsopts
371 371 + walkopts
372 372 + formatteropts,
373 373 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
374 374 helpcategory=command.CATEGORY_FILE_CONTENTS,
375 375 helpbasic=True,
376 376 inferrepo=True,
377 377 )
378 378 def annotate(ui, repo, *pats, **opts):
379 379 """show changeset information by line for each file
380 380
381 381 List changes in files, showing the revision id responsible for
382 382 each line.
383 383
384 384 This command is useful for discovering when a change was made and
385 385 by whom.
386 386
387 387 If you include --file, --user, or --date, the revision number is
388 388 suppressed unless you also include --number.
389 389
390 390 Without the -a/--text option, annotate will avoid processing files
391 391 it detects as binary. With -a, annotate will annotate the file
392 392 anyway, although the results will probably be neither useful
393 393 nor desirable.
394 394
395 395 .. container:: verbose
396 396
397 397 Template:
398 398
399 399 The following keywords are supported in addition to the common template
400 400 keywords and functions. See also :hg:`help templates`.
401 401
402 402 :lines: List of lines with annotation data.
403 403 :path: String. Repository-absolute path of the specified file.
404 404
405 405 And each entry of ``{lines}`` provides the following sub-keywords in
406 406 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
407 407
408 408 :line: String. Line content.
409 409 :lineno: Integer. Line number at that revision.
410 410 :path: String. Repository-absolute path of the file at that revision.
411 411
412 412 See :hg:`help templates.operators` for the list expansion syntax.
413 413
414 414 Returns 0 on success.
415 415 """
416 416 opts = pycompat.byteskwargs(opts)
417 417 if not pats:
418 418 raise error.InputError(
419 419 _(b'at least one filename or pattern is required')
420 420 )
421 421
422 422 if opts.get(b'follow'):
423 423 # --follow is deprecated and now just an alias for -f/--file
424 424 # to mimic the behavior of Mercurial before version 1.5
425 425 opts[b'file'] = True
426 426
427 427 if (
428 428 not opts.get(b'user')
429 429 and not opts.get(b'changeset')
430 430 and not opts.get(b'date')
431 431 and not opts.get(b'file')
432 432 ):
433 433 opts[b'number'] = True
434 434
435 435 linenumber = opts.get(b'line_number') is not None
436 436 if (
437 437 linenumber
438 438 and (not opts.get(b'changeset'))
439 439 and (not opts.get(b'number'))
440 440 ):
441 441 raise error.InputError(_(b'at least one of -n/-c is required for -l'))
442 442
443 443 rev = opts.get(b'rev')
444 444 if rev:
445 445 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
446 446 ctx = logcmdutil.revsingle(repo, rev)
447 447
448 448 ui.pager(b'annotate')
449 449 rootfm = ui.formatter(b'annotate', opts)
450 450 if ui.debugflag:
451 451 shorthex = pycompat.identity
452 452 else:
453 453
454 454 def shorthex(h):
455 455 return h[:12]
456 456
457 457 if ui.quiet:
458 458 datefunc = dateutil.shortdate
459 459 else:
460 460 datefunc = dateutil.datestr
461 461 if ctx.rev() is None:
462 462 if opts.get(b'changeset'):
463 463 # omit "+" suffix which is appended to node hex
464 464 def formatrev(rev):
465 465 if rev == wdirrev:
466 466 return b'%d' % ctx.p1().rev()
467 467 else:
468 468 return b'%d' % rev
469 469
470 470 else:
471 471
472 472 def formatrev(rev):
473 473 if rev == wdirrev:
474 474 return b'%d+' % ctx.p1().rev()
475 475 else:
476 476 return b'%d ' % rev
477 477
478 478 def formathex(h):
479 479 if h == repo.nodeconstants.wdirhex:
480 480 return b'%s+' % shorthex(hex(ctx.p1().node()))
481 481 else:
482 482 return b'%s ' % shorthex(h)
483 483
484 484 else:
485 485 formatrev = b'%d'.__mod__
486 486 formathex = shorthex
487 487
488 488 opmap = [
489 489 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
490 490 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
491 491 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
492 492 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
493 493 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
494 494 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
495 495 ]
496 496 opnamemap = {
497 497 b'rev': b'number',
498 498 b'node': b'changeset',
499 499 b'path': b'file',
500 500 b'lineno': b'line_number',
501 501 }
502 502
503 503 if rootfm.isplain():
504 504
505 505 def makefunc(get, fmt):
506 506 return lambda x: fmt(get(x))
507 507
508 508 else:
509 509
510 510 def makefunc(get, fmt):
511 511 return get
512 512
513 513 datahint = rootfm.datahint()
514 514 funcmap = [
515 515 (makefunc(get, fmt), sep)
516 516 for fn, sep, get, fmt in opmap
517 517 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
518 518 ]
519 519 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
520 520 fields = b' '.join(
521 521 fn
522 522 for fn, sep, get, fmt in opmap
523 523 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
524 524 )
525 525
526 526 def bad(x, y):
527 527 raise error.InputError(b"%s: %s" % (x, y))
528 528
529 529 m = scmutil.match(ctx, pats, opts, badfn=bad)
530 530
531 531 follow = not opts.get(b'no_follow')
532 532 diffopts = patch.difffeatureopts(
533 533 ui, opts, section=b'annotate', whitespace=True
534 534 )
535 535 skiprevs = opts.get(b'skip')
536 536 if skiprevs:
537 537 skiprevs = logcmdutil.revrange(repo, skiprevs)
538 538
539 539 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
540 540 for abs in ctx.walk(m):
541 541 fctx = ctx[abs]
542 542 rootfm.startitem()
543 543 rootfm.data(path=abs)
544 544 if not opts.get(b'text') and fctx.isbinary():
545 545 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
546 546 continue
547 547
548 548 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
549 549 lines = fctx.annotate(
550 550 follow=follow, skiprevs=skiprevs, diffopts=diffopts
551 551 )
552 552 if not lines:
553 553 fm.end()
554 554 continue
555 555 formats = []
556 556 pieces = []
557 557
558 558 for f, sep in funcmap:
559 559 l = [f(n) for n in lines]
560 560 if fm.isplain():
561 561 sizes = [encoding.colwidth(x) for x in l]
562 562 ml = max(sizes)
563 563 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
564 564 else:
565 565 formats.append([b'%s'] * len(l))
566 566 pieces.append(l)
567 567
568 568 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
569 569 fm.startitem()
570 570 fm.context(fctx=n.fctx)
571 571 fm.write(fields, b"".join(f), *p)
572 572 if n.skip:
573 573 fmt = b"* %s"
574 574 else:
575 575 fmt = b": %s"
576 576 fm.write(b'line', fmt, n.text)
577 577
578 578 if not lines[-1].text.endswith(b'\n'):
579 579 fm.plain(b'\n')
580 580 fm.end()
581 581
582 582 rootfm.end()
583 583
584 584
585 585 @command(
586 586 b'archive',
587 587 [
588 588 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
589 589 (
590 590 b'p',
591 591 b'prefix',
592 592 b'',
593 593 _(b'directory prefix for files in archive'),
594 594 _(b'PREFIX'),
595 595 ),
596 596 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
597 597 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
598 598 ]
599 599 + subrepoopts
600 600 + walkopts,
601 601 _(b'[OPTION]... DEST'),
602 602 helpcategory=command.CATEGORY_IMPORT_EXPORT,
603 603 )
604 604 def archive(ui, repo, dest, **opts):
605 605 """create an unversioned archive of a repository revision
606 606
607 607 By default, the revision used is the parent of the working
608 608 directory; use -r/--rev to specify a different revision.
609 609
610 610 The archive type is automatically detected based on file
611 611 extension (to override, use -t/--type).
612 612
613 613 .. container:: verbose
614 614
615 615 Examples:
616 616
617 617 - create a zip file containing the 1.0 release::
618 618
619 619 hg archive -r 1.0 project-1.0.zip
620 620
621 621 - create a tarball excluding .hg files::
622 622
623 623 hg archive project.tar.gz -X ".hg*"
624 624
625 625 Valid types are:
626 626
627 627 :``files``: a directory full of files (default)
628 628 :``tar``: tar archive, uncompressed
629 629 :``tbz2``: tar archive, compressed using bzip2
630 630 :``tgz``: tar archive, compressed using gzip
631 631 :``txz``: tar archive, compressed using lzma (only in Python 3)
632 632 :``uzip``: zip archive, uncompressed
633 633 :``zip``: zip archive, compressed using deflate
634 634
635 635 The exact name of the destination archive or directory is given
636 636 using a format string; see :hg:`help export` for details.
637 637
638 638 Each member added to an archive file has a directory prefix
639 639 prepended. Use -p/--prefix to specify a format string for the
640 640 prefix. The default is the basename of the archive, with suffixes
641 641 removed.
642 642
643 643 Returns 0 on success.
644 644 """
645 645
646 646 opts = pycompat.byteskwargs(opts)
647 647 rev = opts.get(b'rev')
648 648 if rev:
649 649 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
650 650 ctx = logcmdutil.revsingle(repo, rev)
651 651 if not ctx:
652 652 raise error.InputError(
653 653 _(b'no working directory: please specify a revision')
654 654 )
655 655 node = ctx.node()
656 656 dest = cmdutil.makefilename(ctx, dest)
657 657 if os.path.realpath(dest) == repo.root:
658 658 raise error.InputError(_(b'repository root cannot be destination'))
659 659
660 660 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
661 661 prefix = opts.get(b'prefix')
662 662
663 663 if dest == b'-':
664 664 if kind == b'files':
665 665 raise error.InputError(_(b'cannot archive plain files to stdout'))
666 666 dest = cmdutil.makefileobj(ctx, dest)
667 667 if not prefix:
668 668 prefix = os.path.basename(repo.root) + b'-%h'
669 669
670 670 prefix = cmdutil.makefilename(ctx, prefix)
671 671 match = scmutil.match(ctx, [], opts)
672 672 archival.archive(
673 673 repo,
674 674 dest,
675 675 node,
676 676 kind,
677 677 not opts.get(b'no_decode'),
678 678 match,
679 679 prefix,
680 680 subrepos=opts.get(b'subrepos'),
681 681 )
682 682
683 683
684 684 @command(
685 685 b'backout',
686 686 [
687 687 (
688 688 b'',
689 689 b'merge',
690 690 None,
691 691 _(b'merge with old dirstate parent after backout'),
692 692 ),
693 693 (
694 694 b'',
695 695 b'commit',
696 696 None,
697 697 _(b'commit if no conflicts were encountered (DEPRECATED)'),
698 698 ),
699 699 (b'', b'no-commit', None, _(b'do not commit')),
700 700 (
701 701 b'',
702 702 b'parent',
703 703 b'',
704 704 _(b'parent to choose when backing out merge (DEPRECATED)'),
705 705 _(b'REV'),
706 706 ),
707 707 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
708 708 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
709 709 ]
710 710 + mergetoolopts
711 711 + walkopts
712 712 + commitopts
713 713 + commitopts2,
714 714 _(b'[OPTION]... [-r] REV'),
715 715 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
716 716 )
717 717 def backout(ui, repo, node=None, rev=None, **opts):
718 718 """reverse effect of earlier changeset
719 719
720 720 Prepare a new changeset with the effect of REV undone in the
721 721 current working directory. If no conflicts were encountered,
722 722 it will be committed immediately.
723 723
724 724 If REV is the parent of the working directory, then this new changeset
725 725 is committed automatically (unless --no-commit is specified).
726 726
727 727 .. note::
728 728
729 729 :hg:`backout` cannot be used to fix either an unwanted or
730 730 incorrect merge.
731 731
732 732 .. container:: verbose
733 733
734 734 Examples:
735 735
736 736 - Reverse the effect of the parent of the working directory.
737 737 This backout will be committed immediately::
738 738
739 739 hg backout -r .
740 740
741 741 - Reverse the effect of previous bad revision 23::
742 742
743 743 hg backout -r 23
744 744
745 745 - Reverse the effect of previous bad revision 23 and
746 746 leave changes uncommitted::
747 747
748 748 hg backout -r 23 --no-commit
749 749 hg commit -m "Backout revision 23"
750 750
751 751 By default, the pending changeset will have one parent,
752 752 maintaining a linear history. With --merge, the pending
753 753 changeset will instead have two parents: the old parent of the
754 754 working directory and a new child of REV that simply undoes REV.
755 755
756 756 Before version 1.7, the behavior without --merge was equivalent
757 757 to specifying --merge followed by :hg:`update --clean .` to
758 758 cancel the merge and leave the child of REV as a head to be
759 759 merged separately.
760 760
761 761 See :hg:`help dates` for a list of formats valid for -d/--date.
762 762
763 763 See :hg:`help revert` for a way to restore files to the state
764 764 of another revision.
765 765
766 766 Returns 0 on success, 1 if nothing to backout or there are unresolved
767 767 files.
768 768 """
769 769 with repo.wlock(), repo.lock():
770 770 return _dobackout(ui, repo, node, rev, **opts)
771 771
772 772
773 773 def _dobackout(ui, repo, node=None, rev=None, **opts):
774 774 cmdutil.check_incompatible_arguments(opts, 'no_commit', ['commit', 'merge'])
775 775 opts = pycompat.byteskwargs(opts)
776 776
777 777 if rev and node:
778 778 raise error.InputError(_(b"please specify just one revision"))
779 779
780 780 if not rev:
781 781 rev = node
782 782
783 783 if not rev:
784 784 raise error.InputError(_(b"please specify a revision to backout"))
785 785
786 786 date = opts.get(b'date')
787 787 if date:
788 788 opts[b'date'] = dateutil.parsedate(date)
789 789
790 790 cmdutil.checkunfinished(repo)
791 791 cmdutil.bailifchanged(repo)
792 792 ctx = logcmdutil.revsingle(repo, rev)
793 793 node = ctx.node()
794 794
795 795 op1, op2 = repo.dirstate.parents()
796 796 if not repo.changelog.isancestor(node, op1):
797 797 raise error.InputError(
798 798 _(b'cannot backout change that is not an ancestor')
799 799 )
800 800
801 801 p1, p2 = repo.changelog.parents(node)
802 802 if p1 == repo.nullid:
803 803 raise error.InputError(_(b'cannot backout a change with no parents'))
804 804 if p2 != repo.nullid:
805 805 if not opts.get(b'parent'):
806 806 raise error.InputError(_(b'cannot backout a merge changeset'))
807 807 p = repo.lookup(opts[b'parent'])
808 808 if p not in (p1, p2):
809 809 raise error.InputError(
810 810 _(b'%s is not a parent of %s') % (short(p), short(node))
811 811 )
812 812 parent = p
813 813 else:
814 814 if opts.get(b'parent'):
815 815 raise error.InputError(
816 816 _(b'cannot use --parent on non-merge changeset')
817 817 )
818 818 parent = p1
819 819
820 820 # the backout should appear on the same branch
821 821 branch = repo.dirstate.branch()
822 822 bheads = repo.branchheads(branch)
823 823 rctx = scmutil.revsingle(repo, hex(parent))
824 824 if not opts.get(b'merge') and op1 != node:
825 825 with dirstateguard.dirstateguard(repo, b'backout'):
826 826 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
827 827 with ui.configoverride(overrides, b'backout'):
828 828 stats = mergemod.back_out(ctx, parent=repo[parent])
829 829 repo.setparents(op1, op2)
830 830 hg._showstats(repo, stats)
831 831 if stats.unresolvedcount:
832 832 repo.ui.status(
833 833 _(b"use 'hg resolve' to retry unresolved file merges\n")
834 834 )
835 835 return 1
836 836 else:
837 837 hg.clean(repo, node, show_stats=False)
838 838 repo.dirstate.setbranch(branch)
839 839 cmdutil.revert(ui, repo, rctx)
840 840
841 841 if opts.get(b'no_commit'):
842 842 msg = _(b"changeset %s backed out, don't forget to commit.\n")
843 843 ui.status(msg % short(node))
844 844 return 0
845 845
846 846 def commitfunc(ui, repo, message, match, opts):
847 847 editform = b'backout'
848 848 e = cmdutil.getcommiteditor(
849 849 editform=editform, **pycompat.strkwargs(opts)
850 850 )
851 851 if not message:
852 852 # we don't translate commit messages
853 853 message = b"Backed out changeset %s" % short(node)
854 854 e = cmdutil.getcommiteditor(edit=True, editform=editform)
855 855 return repo.commit(
856 856 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
857 857 )
858 858
859 859 # save to detect changes
860 860 tip = repo.changelog.tip()
861 861
862 862 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
863 863 if not newnode:
864 864 ui.status(_(b"nothing changed\n"))
865 865 return 1
866 866 cmdutil.commitstatus(repo, newnode, branch, bheads, tip)
867 867
868 868 def nice(node):
869 869 return b'%d:%s' % (repo.changelog.rev(node), short(node))
870 870
871 871 ui.status(
872 872 _(b'changeset %s backs out changeset %s\n')
873 873 % (nice(newnode), nice(node))
874 874 )
875 875 if opts.get(b'merge') and op1 != node:
876 876 hg.clean(repo, op1, show_stats=False)
877 877 ui.status(_(b'merging with changeset %s\n') % nice(newnode))
878 878 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
879 879 with ui.configoverride(overrides, b'backout'):
880 880 return hg.merge(repo[b'tip'])
881 881 return 0
882 882
883 883
884 884 @command(
885 885 b'bisect',
886 886 [
887 887 (b'r', b'reset', False, _(b'reset bisect state')),
888 888 (b'g', b'good', False, _(b'mark changeset good')),
889 889 (b'b', b'bad', False, _(b'mark changeset bad')),
890 890 (b's', b'skip', False, _(b'skip testing changeset')),
891 891 (b'e', b'extend', False, _(b'extend the bisect range')),
892 892 (
893 893 b'c',
894 894 b'command',
895 895 b'',
896 896 _(b'use command to check changeset state'),
897 897 _(b'CMD'),
898 898 ),
899 899 (b'U', b'noupdate', False, _(b'do not update to target')),
900 900 ],
901 901 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
902 902 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
903 903 )
904 904 def bisect(
905 905 ui,
906 906 repo,
907 907 positional_1=None,
908 908 positional_2=None,
909 909 command=None,
910 910 reset=None,
911 911 good=None,
912 912 bad=None,
913 913 skip=None,
914 914 extend=None,
915 915 noupdate=None,
916 916 ):
917 917 """subdivision search of changesets
918 918
919 919 This command helps to find changesets which introduce problems. To
920 920 use, mark the earliest changeset you know exhibits the problem as
921 921 bad, then mark the latest changeset which is free from the problem
922 922 as good. Bisect will update your working directory to a revision
923 923 for testing (unless the -U/--noupdate option is specified). Once
924 924 you have performed tests, mark the working directory as good or
925 925 bad, and bisect will either update to another candidate changeset
926 926 or announce that it has found the bad revision.
927 927
928 928 As a shortcut, you can also use the revision argument to mark a
929 929 revision as good or bad without checking it out first.
930 930
931 931 If you supply a command, it will be used for automatic bisection.
932 932 The environment variable HG_NODE will contain the ID of the
933 933 changeset being tested. The exit status of the command will be
934 934 used to mark revisions as good or bad: status 0 means good, 125
935 935 means to skip the revision, 127 (command not found) will abort the
936 936 bisection, and any other non-zero exit status means the revision
937 937 is bad.
938 938
939 939 .. container:: verbose
940 940
941 941 Some examples:
942 942
943 943 - start a bisection with known bad revision 34, and good revision 12::
944 944
945 945 hg bisect --bad 34
946 946 hg bisect --good 12
947 947
948 948 - advance the current bisection by marking current revision as good or
949 949 bad::
950 950
951 951 hg bisect --good
952 952 hg bisect --bad
953 953
954 954 - mark the current revision, or a known revision, to be skipped (e.g. if
955 955 that revision is not usable because of another issue)::
956 956
957 957 hg bisect --skip
958 958 hg bisect --skip 23
959 959
960 960 - skip all revisions that do not touch directories ``foo`` or ``bar``::
961 961
962 962 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
963 963
964 964 - forget the current bisection::
965 965
966 966 hg bisect --reset
967 967
968 968 - use 'make && make tests' to automatically find the first broken
969 969 revision::
970 970
971 971 hg bisect --reset
972 972 hg bisect --bad 34
973 973 hg bisect --good 12
974 974 hg bisect --command "make && make tests"
975 975
976 976 - see all changesets whose states are already known in the current
977 977 bisection::
978 978
979 979 hg log -r "bisect(pruned)"
980 980
981 981 - see the changeset currently being bisected (especially useful
982 982 if running with -U/--noupdate)::
983 983
984 984 hg log -r "bisect(current)"
985 985
986 986 - see all changesets that took part in the current bisection::
987 987
988 988 hg log -r "bisect(range)"
989 989
990 990 - you can even get a nice graph::
991 991
992 992 hg log --graph -r "bisect(range)"
993 993
994 994 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
995 995
996 996 Returns 0 on success.
997 997 """
998 998 rev = []
999 999 # backward compatibility
1000 1000 if positional_1 in (b"good", b"bad", b"reset", b"init"):
1001 1001 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1002 1002 cmd = positional_1
1003 1003 rev.append(positional_2)
1004 1004 if cmd == b"good":
1005 1005 good = True
1006 1006 elif cmd == b"bad":
1007 1007 bad = True
1008 1008 else:
1009 1009 reset = True
1010 1010 elif positional_2:
1011 1011 raise error.InputError(_(b'incompatible arguments'))
1012 1012 elif positional_1 is not None:
1013 1013 rev.append(positional_1)
1014 1014
1015 1015 incompatibles = {
1016 1016 b'--bad': bad,
1017 1017 b'--command': bool(command),
1018 1018 b'--extend': extend,
1019 1019 b'--good': good,
1020 1020 b'--reset': reset,
1021 1021 b'--skip': skip,
1022 1022 }
1023 1023
1024 1024 enabled = [x for x in incompatibles if incompatibles[x]]
1025 1025
1026 1026 if len(enabled) > 1:
1027 1027 raise error.InputError(
1028 1028 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1029 1029 )
1030 1030
1031 1031 if reset:
1032 1032 hbisect.resetstate(repo)
1033 1033 return
1034 1034
1035 1035 state = hbisect.load_state(repo)
1036 1036
1037 1037 if rev:
1038 1038 nodes = [repo[i].node() for i in logcmdutil.revrange(repo, rev)]
1039 1039 else:
1040 1040 nodes = [repo.lookup(b'.')]
1041 1041
1042 1042 # update state
1043 1043 if good or bad or skip:
1044 1044 if good:
1045 1045 state[b'good'] += nodes
1046 1046 elif bad:
1047 1047 state[b'bad'] += nodes
1048 1048 elif skip:
1049 1049 state[b'skip'] += nodes
1050 1050 hbisect.save_state(repo, state)
1051 1051 if not (state[b'good'] and state[b'bad']):
1052 1052 return
1053 1053
1054 1054 def mayupdate(repo, node, show_stats=True):
1055 1055 """common used update sequence"""
1056 1056 if noupdate:
1057 1057 return
1058 1058 cmdutil.checkunfinished(repo)
1059 1059 cmdutil.bailifchanged(repo)
1060 1060 return hg.clean(repo, node, show_stats=show_stats)
1061 1061
1062 1062 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1063 1063
1064 1064 if command:
1065 1065 changesets = 1
1066 1066 if noupdate:
1067 1067 try:
1068 1068 node = state[b'current'][0]
1069 1069 except LookupError:
1070 1070 raise error.StateError(
1071 1071 _(
1072 1072 b'current bisect revision is unknown - '
1073 1073 b'start a new bisect to fix'
1074 1074 )
1075 1075 )
1076 1076 else:
1077 1077 node, p2 = repo.dirstate.parents()
1078 1078 if p2 != repo.nullid:
1079 1079 raise error.StateError(_(b'current bisect revision is a merge'))
1080 1080 if rev:
1081 1081 if not nodes:
1082 1082 raise error.InputError(_(b'empty revision set'))
1083 1083 node = repo[nodes[-1]].node()
1084 1084 with hbisect.restore_state(repo, state, node):
1085 1085 while changesets:
1086 1086 # update state
1087 1087 state[b'current'] = [node]
1088 1088 hbisect.save_state(repo, state)
1089 1089 status = ui.system(
1090 1090 command,
1091 1091 environ={b'HG_NODE': hex(node)},
1092 1092 blockedtag=b'bisect_check',
1093 1093 )
1094 1094 if status == 125:
1095 1095 transition = b"skip"
1096 1096 elif status == 0:
1097 1097 transition = b"good"
1098 1098 # status < 0 means process was killed
1099 1099 elif status == 127:
1100 1100 raise error.Abort(_(b"failed to execute %s") % command)
1101 1101 elif status < 0:
1102 1102 raise error.Abort(_(b"%s killed") % command)
1103 1103 else:
1104 1104 transition = b"bad"
1105 1105 state[transition].append(node)
1106 1106 ctx = repo[node]
1107 1107 summary = cmdutil.format_changeset_summary(ui, ctx, b'bisect')
1108 1108 ui.status(_(b'changeset %s: %s\n') % (summary, transition))
1109 1109 hbisect.checkstate(state)
1110 1110 # bisect
1111 1111 nodes, changesets, bgood = hbisect.bisect(repo, state)
1112 1112 # update to next check
1113 1113 node = nodes[0]
1114 1114 mayupdate(repo, node, show_stats=False)
1115 1115 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1116 1116 return
1117 1117
1118 1118 hbisect.checkstate(state)
1119 1119
1120 1120 # actually bisect
1121 1121 nodes, changesets, good = hbisect.bisect(repo, state)
1122 1122 if extend:
1123 1123 if not changesets:
1124 1124 extendctx = hbisect.extendrange(repo, state, nodes, good)
1125 1125 if extendctx is not None:
1126 1126 ui.write(
1127 1127 _(b"Extending search to changeset %s\n")
1128 1128 % cmdutil.format_changeset_summary(ui, extendctx, b'bisect')
1129 1129 )
1130 1130 state[b'current'] = [extendctx.node()]
1131 1131 hbisect.save_state(repo, state)
1132 1132 return mayupdate(repo, extendctx.node())
1133 1133 raise error.StateError(_(b"nothing to extend"))
1134 1134
1135 1135 if changesets == 0:
1136 1136 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1137 1137 else:
1138 1138 assert len(nodes) == 1 # only a single node can be tested next
1139 1139 node = nodes[0]
1140 1140 # compute the approximate number of remaining tests
1141 1141 tests, size = 0, 2
1142 1142 while size <= changesets:
1143 1143 tests, size = tests + 1, size * 2
1144 1144 rev = repo.changelog.rev(node)
1145 1145 summary = cmdutil.format_changeset_summary(ui, repo[rev], b'bisect')
1146 1146 ui.write(
1147 1147 _(
1148 1148 b"Testing changeset %s "
1149 1149 b"(%d changesets remaining, ~%d tests)\n"
1150 1150 )
1151 1151 % (summary, changesets, tests)
1152 1152 )
1153 1153 state[b'current'] = [node]
1154 1154 hbisect.save_state(repo, state)
1155 1155 return mayupdate(repo, node)
1156 1156
1157 1157
1158 1158 @command(
1159 1159 b'bookmarks|bookmark',
1160 1160 [
1161 1161 (b'f', b'force', False, _(b'force')),
1162 1162 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1163 1163 (b'd', b'delete', False, _(b'delete a given bookmark')),
1164 1164 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1165 1165 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1166 1166 (b'l', b'list', False, _(b'list existing bookmarks')),
1167 1167 ]
1168 1168 + formatteropts,
1169 1169 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1170 1170 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1171 1171 )
1172 1172 def bookmark(ui, repo, *names, **opts):
1173 1173 """create a new bookmark or list existing bookmarks
1174 1174
1175 1175 Bookmarks are labels on changesets to help track lines of development.
1176 1176 Bookmarks are unversioned and can be moved, renamed and deleted.
1177 1177 Deleting or moving a bookmark has no effect on the associated changesets.
1178 1178
1179 1179 Creating or updating to a bookmark causes it to be marked as 'active'.
1180 1180 The active bookmark is indicated with a '*'.
1181 1181 When a commit is made, the active bookmark will advance to the new commit.
1182 1182 A plain :hg:`update` will also advance an active bookmark, if possible.
1183 1183 Updating away from a bookmark will cause it to be deactivated.
1184 1184
1185 1185 Bookmarks can be pushed and pulled between repositories (see
1186 1186 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1187 1187 diverged, a new 'divergent bookmark' of the form 'name@path' will
1188 1188 be created. Using :hg:`merge` will resolve the divergence.
1189 1189
1190 1190 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1191 1191 the active bookmark's name.
1192 1192
1193 1193 A bookmark named '@' has the special property that :hg:`clone` will
1194 1194 check it out by default if it exists.
1195 1195
1196 1196 .. container:: verbose
1197 1197
1198 1198 Template:
1199 1199
1200 1200 The following keywords are supported in addition to the common template
1201 1201 keywords and functions such as ``{bookmark}``. See also
1202 1202 :hg:`help templates`.
1203 1203
1204 1204 :active: Boolean. True if the bookmark is active.
1205 1205
1206 1206 Examples:
1207 1207
1208 1208 - create an active bookmark for a new line of development::
1209 1209
1210 1210 hg book new-feature
1211 1211
1212 1212 - create an inactive bookmark as a place marker::
1213 1213
1214 1214 hg book -i reviewed
1215 1215
1216 1216 - create an inactive bookmark on another changeset::
1217 1217
1218 1218 hg book -r .^ tested
1219 1219
1220 1220 - rename bookmark turkey to dinner::
1221 1221
1222 1222 hg book -m turkey dinner
1223 1223
1224 1224 - move the '@' bookmark from another branch::
1225 1225
1226 1226 hg book -f @
1227 1227
1228 1228 - print only the active bookmark name::
1229 1229
1230 1230 hg book -ql .
1231 1231 """
1232 1232 opts = pycompat.byteskwargs(opts)
1233 1233 force = opts.get(b'force')
1234 1234 rev = opts.get(b'rev')
1235 1235 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1236 1236
1237 1237 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1238 1238 if action:
1239 1239 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1240 1240 elif names or rev:
1241 1241 action = b'add'
1242 1242 elif inactive:
1243 1243 action = b'inactive' # meaning deactivate
1244 1244 else:
1245 1245 action = b'list'
1246 1246
1247 1247 cmdutil.check_incompatible_arguments(
1248 1248 opts, b'inactive', [b'delete', b'list']
1249 1249 )
1250 1250 if not names and action in {b'add', b'delete'}:
1251 1251 raise error.InputError(_(b"bookmark name required"))
1252 1252
1253 1253 if action in {b'add', b'delete', b'rename', b'inactive'}:
1254 1254 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1255 1255 if action == b'delete':
1256 1256 names = pycompat.maplist(repo._bookmarks.expandname, names)
1257 1257 bookmarks.delete(repo, tr, names)
1258 1258 elif action == b'rename':
1259 1259 if not names:
1260 1260 raise error.InputError(_(b"new bookmark name required"))
1261 1261 elif len(names) > 1:
1262 1262 raise error.InputError(
1263 1263 _(b"only one new bookmark name allowed")
1264 1264 )
1265 1265 oldname = repo._bookmarks.expandname(opts[b'rename'])
1266 1266 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1267 1267 elif action == b'add':
1268 1268 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1269 1269 elif action == b'inactive':
1270 1270 if len(repo._bookmarks) == 0:
1271 1271 ui.status(_(b"no bookmarks set\n"))
1272 1272 elif not repo._activebookmark:
1273 1273 ui.status(_(b"no active bookmark\n"))
1274 1274 else:
1275 1275 bookmarks.deactivate(repo)
1276 1276 elif action == b'list':
1277 1277 names = pycompat.maplist(repo._bookmarks.expandname, names)
1278 1278 with ui.formatter(b'bookmarks', opts) as fm:
1279 1279 bookmarks.printbookmarks(ui, repo, fm, names)
1280 1280 else:
1281 1281 raise error.ProgrammingError(b'invalid action: %s' % action)
1282 1282
1283 1283
1284 1284 @command(
1285 1285 b'branch',
1286 1286 [
1287 1287 (
1288 1288 b'f',
1289 1289 b'force',
1290 1290 None,
1291 1291 _(b'set branch name even if it shadows an existing branch'),
1292 1292 ),
1293 1293 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1294 1294 (
1295 1295 b'r',
1296 1296 b'rev',
1297 1297 [],
1298 1298 _(b'change branches of the given revs (EXPERIMENTAL)'),
1299 1299 ),
1300 1300 ],
1301 1301 _(b'[-fC] [NAME]'),
1302 1302 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1303 1303 )
1304 1304 def branch(ui, repo, label=None, **opts):
1305 1305 """set or show the current branch name
1306 1306
1307 1307 .. note::
1308 1308
1309 1309 Branch names are permanent and global. Use :hg:`bookmark` to create a
1310 1310 light-weight bookmark instead. See :hg:`help glossary` for more
1311 1311 information about named branches and bookmarks.
1312 1312
1313 1313 With no argument, show the current branch name. With one argument,
1314 1314 set the working directory branch name (the branch will not exist
1315 1315 in the repository until the next commit). Standard practice
1316 1316 recommends that primary development take place on the 'default'
1317 1317 branch.
1318 1318
1319 1319 Unless -f/--force is specified, branch will not let you set a
1320 1320 branch name that already exists.
1321 1321
1322 1322 Use -C/--clean to reset the working directory branch to that of
1323 1323 the parent of the working directory, negating a previous branch
1324 1324 change.
1325 1325
1326 1326 Use the command :hg:`update` to switch to an existing branch. Use
1327 1327 :hg:`commit --close-branch` to mark this branch head as closed.
1328 1328 When all heads of a branch are closed, the branch will be
1329 1329 considered closed.
1330 1330
1331 1331 Returns 0 on success.
1332 1332 """
1333 1333 opts = pycompat.byteskwargs(opts)
1334 1334 revs = opts.get(b'rev')
1335 1335 if label:
1336 1336 label = label.strip()
1337 1337
1338 1338 if not opts.get(b'clean') and not label:
1339 1339 if revs:
1340 1340 raise error.InputError(
1341 1341 _(b"no branch name specified for the revisions")
1342 1342 )
1343 1343 ui.write(b"%s\n" % repo.dirstate.branch())
1344 1344 return
1345 1345
1346 1346 with repo.wlock():
1347 1347 if opts.get(b'clean'):
1348 1348 label = repo[b'.'].branch()
1349 1349 repo.dirstate.setbranch(label)
1350 1350 ui.status(_(b'reset working directory to branch %s\n') % label)
1351 1351 elif label:
1352 1352
1353 1353 scmutil.checknewlabel(repo, label, b'branch')
1354 1354 if revs:
1355 1355 return cmdutil.changebranch(ui, repo, revs, label, opts)
1356 1356
1357 1357 if not opts.get(b'force') and label in repo.branchmap():
1358 1358 if label not in [p.branch() for p in repo[None].parents()]:
1359 1359 raise error.InputError(
1360 1360 _(b'a branch of the same name already exists'),
1361 1361 # i18n: "it" refers to an existing branch
1362 1362 hint=_(b"use 'hg update' to switch to it"),
1363 1363 )
1364 1364
1365 1365 repo.dirstate.setbranch(label)
1366 1366 ui.status(_(b'marked working directory as branch %s\n') % label)
1367 1367
1368 1368 # find any open named branches aside from default
1369 1369 for n, h, t, c in repo.branchmap().iterbranches():
1370 1370 if n != b"default" and not c:
1371 1371 return 0
1372 1372 ui.status(
1373 1373 _(
1374 1374 b'(branches are permanent and global, '
1375 1375 b'did you want a bookmark?)\n'
1376 1376 )
1377 1377 )
1378 1378
1379 1379
1380 1380 @command(
1381 1381 b'branches',
1382 1382 [
1383 1383 (
1384 1384 b'a',
1385 1385 b'active',
1386 1386 False,
1387 1387 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1388 1388 ),
1389 1389 (b'c', b'closed', False, _(b'show normal and closed branches')),
1390 1390 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1391 1391 ]
1392 1392 + formatteropts,
1393 1393 _(b'[-c]'),
1394 1394 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1395 1395 intents={INTENT_READONLY},
1396 1396 )
1397 1397 def branches(ui, repo, active=False, closed=False, **opts):
1398 1398 """list repository named branches
1399 1399
1400 1400 List the repository's named branches, indicating which ones are
1401 1401 inactive. If -c/--closed is specified, also list branches which have
1402 1402 been marked closed (see :hg:`commit --close-branch`).
1403 1403
1404 1404 Use the command :hg:`update` to switch to an existing branch.
1405 1405
1406 1406 .. container:: verbose
1407 1407
1408 1408 Template:
1409 1409
1410 1410 The following keywords are supported in addition to the common template
1411 1411 keywords and functions such as ``{branch}``. See also
1412 1412 :hg:`help templates`.
1413 1413
1414 1414 :active: Boolean. True if the branch is active.
1415 1415 :closed: Boolean. True if the branch is closed.
1416 1416 :current: Boolean. True if it is the current branch.
1417 1417
1418 1418 Returns 0.
1419 1419 """
1420 1420
1421 1421 opts = pycompat.byteskwargs(opts)
1422 1422 revs = opts.get(b'rev')
1423 1423 selectedbranches = None
1424 1424 if revs:
1425 1425 revs = logcmdutil.revrange(repo, revs)
1426 1426 getbi = repo.revbranchcache().branchinfo
1427 1427 selectedbranches = {getbi(r)[0] for r in revs}
1428 1428
1429 1429 ui.pager(b'branches')
1430 1430 fm = ui.formatter(b'branches', opts)
1431 1431 hexfunc = fm.hexfunc
1432 1432
1433 1433 allheads = set(repo.heads())
1434 1434 branches = []
1435 1435 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1436 1436 if selectedbranches is not None and tag not in selectedbranches:
1437 1437 continue
1438 1438 isactive = False
1439 1439 if not isclosed:
1440 1440 openheads = set(repo.branchmap().iteropen(heads))
1441 1441 isactive = bool(openheads & allheads)
1442 1442 branches.append((tag, repo[tip], isactive, not isclosed))
1443 1443 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1444 1444
1445 1445 for tag, ctx, isactive, isopen in branches:
1446 1446 if active and not isactive:
1447 1447 continue
1448 1448 if isactive:
1449 1449 label = b'branches.active'
1450 1450 notice = b''
1451 1451 elif not isopen:
1452 1452 if not closed:
1453 1453 continue
1454 1454 label = b'branches.closed'
1455 1455 notice = _(b' (closed)')
1456 1456 else:
1457 1457 label = b'branches.inactive'
1458 1458 notice = _(b' (inactive)')
1459 1459 current = tag == repo.dirstate.branch()
1460 1460 if current:
1461 1461 label = b'branches.current'
1462 1462
1463 1463 fm.startitem()
1464 1464 fm.write(b'branch', b'%s', tag, label=label)
1465 1465 rev = ctx.rev()
1466 1466 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1467 1467 fmt = b' ' * padsize + b' %d:%s'
1468 1468 fm.condwrite(
1469 1469 not ui.quiet,
1470 1470 b'rev node',
1471 1471 fmt,
1472 1472 rev,
1473 1473 hexfunc(ctx.node()),
1474 1474 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1475 1475 )
1476 1476 fm.context(ctx=ctx)
1477 1477 fm.data(active=isactive, closed=not isopen, current=current)
1478 1478 if not ui.quiet:
1479 1479 fm.plain(notice)
1480 1480 fm.plain(b'\n')
1481 1481 fm.end()
1482 1482
1483 1483
1484 1484 @command(
1485 1485 b'bundle',
1486 1486 [
1487
1488 1487 (
1489 1488 b'',
1490 1489 b'exact',
1491 1490 None,
1492 1491 _(b'compute the base from the revision specified'),
1493 1492 ),
1494 1493 (
1495 1494 b'f',
1496 1495 b'force',
1497 1496 None,
1498 1497 _(b'run even when the destination is unrelated'),
1499 1498 ),
1500 1499 (
1501 1500 b'r',
1502 1501 b'rev',
1503 1502 [],
1504 1503 _(b'a changeset intended to be added to the destination'),
1505 1504 _(b'REV'),
1506 1505 ),
1507 1506 (
1508 1507 b'b',
1509 1508 b'branch',
1510 1509 [],
1511 1510 _(b'a specific branch you would like to bundle'),
1512 1511 _(b'BRANCH'),
1513 1512 ),
1514 1513 (
1515 1514 b'',
1516 1515 b'base',
1517 1516 [],
1518 1517 _(b'a base changeset assumed to be available at the destination'),
1519 1518 _(b'REV'),
1520 1519 ),
1521 1520 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1522 1521 (
1523 1522 b't',
1524 1523 b'type',
1525 1524 b'bzip2',
1526 1525 _(b'bundle compression type to use'),
1527 1526 _(b'TYPE'),
1528 1527 ),
1529 1528 ]
1530 1529 + remoteopts,
1531 1530 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]...'),
1532 1531 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1533 1532 )
1534 1533 def bundle(ui, repo, fname, *dests, **opts):
1535 1534 """create a bundle file
1536 1535
1537 1536 Generate a bundle file containing data to be transferred to another
1538 1537 repository.
1539 1538
1540 1539 To create a bundle containing all changesets, use -a/--all
1541 1540 (or --base null). Otherwise, hg assumes the destination will have
1542 1541 all the nodes you specify with --base parameters. Otherwise, hg
1543 1542 will assume the repository has all the nodes in destination, or
1544 1543 default-push/default if no destination is specified, where destination
1545 1544 is the repositories you provide through DEST option.
1546 1545
1547 1546 You can change bundle format with the -t/--type option. See
1548 1547 :hg:`help bundlespec` for documentation on this format. By default,
1549 1548 the most appropriate format is used and compression defaults to
1550 1549 bzip2.
1551 1550
1552 1551 The bundle file can then be transferred using conventional means
1553 1552 and applied to another repository with the unbundle or pull
1554 1553 command. This is useful when direct push and pull are not
1555 1554 available or when exporting an entire repository is undesirable.
1556 1555
1557 1556 Applying bundles preserves all changeset contents including
1558 1557 permissions, copy/rename information, and revision history.
1559 1558
1560 1559 Returns 0 on success, 1 if no changes found.
1561 1560 """
1562 1561 opts = pycompat.byteskwargs(opts)
1563 1562
1564 1563 revs = None
1565 1564 if b'rev' in opts:
1566 1565 revstrings = opts[b'rev']
1567 1566 revs = logcmdutil.revrange(repo, revstrings)
1568 1567 if revstrings and not revs:
1569 1568 raise error.InputError(_(b'no commits to bundle'))
1570 1569
1571 1570 bundletype = opts.get(b'type', b'bzip2').lower()
1572 1571 try:
1573 1572 bundlespec = bundlecaches.parsebundlespec(
1574 1573 repo, bundletype, strict=False
1575 1574 )
1576 1575 except error.UnsupportedBundleSpecification as e:
1577 1576 raise error.InputError(
1578 1577 pycompat.bytestr(e),
1579 1578 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1580 1579 )
1581 1580 cgversion = bundlespec.params[b"cg.version"]
1582 1581
1583 1582 # Packed bundles are a pseudo bundle format for now.
1584 1583 if cgversion == b's1':
1585 1584 raise error.InputError(
1586 1585 _(b'packed bundles cannot be produced by "hg bundle"'),
1587 1586 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1588 1587 )
1589 1588
1590 1589 if opts.get(b'all'):
1591 1590 if dests:
1592 1591 raise error.InputError(
1593 1592 _(b"--all is incompatible with specifying destinations")
1594 1593 )
1595 1594 if opts.get(b'base'):
1596 1595 ui.warn(_(b"ignoring --base because --all was specified\n"))
1597 1596 if opts.get(b'exact'):
1598 1597 ui.warn(_(b"ignoring --exact because --all was specified\n"))
1599 1598 base = [nullrev]
1600 1599 elif opts.get(b'exact'):
1601 1600 if dests:
1602 1601 raise error.InputError(
1603 1602 _(b"--exact is incompatible with specifying destinations")
1604 1603 )
1605 1604 if opts.get(b'base'):
1606 1605 ui.warn(_(b"ignoring --base because --exact was specified\n"))
1607 1606 base = repo.revs(b'parents(%ld) - %ld', revs, revs)
1608 1607 if not base:
1609 1608 base = [nullrev]
1610 1609 else:
1611 1610 base = logcmdutil.revrange(repo, opts.get(b'base'))
1612 1611 if cgversion not in changegroup.supportedoutgoingversions(repo):
1613 1612 raise error.Abort(
1614 1613 _(b"repository does not support bundle version %s") % cgversion
1615 1614 )
1616 1615
1617 1616 if base:
1618 1617 if dests:
1619 1618 raise error.InputError(
1620 1619 _(b"--base is incompatible with specifying destinations")
1621 1620 )
1622 1621 cl = repo.changelog
1623 1622 common = [cl.node(rev) for rev in base]
1624 1623 heads = [cl.node(r) for r in revs] if revs else None
1625 1624 outgoing = discovery.outgoing(repo, common, heads)
1626 1625 missing = outgoing.missing
1627 1626 excluded = outgoing.excluded
1628 1627 else:
1629 1628 missing = set()
1630 1629 excluded = set()
1631 1630 for path in urlutil.get_push_paths(repo, ui, dests):
1632 1631 other = hg.peer(repo, opts, path.rawloc)
1633 1632 if revs is not None:
1634 1633 hex_revs = [repo[r].hex() for r in revs]
1635 1634 else:
1636 1635 hex_revs = None
1637 1636 branches = (path.branch, [])
1638 1637 head_revs, checkout = hg.addbranchrevs(
1639 1638 repo, repo, branches, hex_revs
1640 1639 )
1641 1640 heads = (
1642 1641 head_revs
1643 1642 and pycompat.maplist(repo.lookup, head_revs)
1644 1643 or head_revs
1645 1644 )
1646 1645 outgoing = discovery.findcommonoutgoing(
1647 1646 repo,
1648 1647 other,
1649 1648 onlyheads=heads,
1650 1649 force=opts.get(b'force'),
1651 1650 portable=True,
1652 1651 )
1653 1652 missing.update(outgoing.missing)
1654 1653 excluded.update(outgoing.excluded)
1655 1654
1656 1655 if not missing:
1657 1656 scmutil.nochangesfound(ui, repo, not base and excluded)
1658 1657 return 1
1659 1658
1660 1659 if heads:
1661 1660 outgoing = discovery.outgoing(
1662 1661 repo, missingroots=missing, ancestorsof=heads
1663 1662 )
1664 1663 else:
1665 1664 outgoing = discovery.outgoing(repo, missingroots=missing)
1666 1665 outgoing.excluded = sorted(excluded)
1667 1666
1668 1667 if cgversion == b'01': # bundle1
1669 1668 bversion = b'HG10' + bundlespec.wirecompression
1670 1669 bcompression = None
1671 1670 elif cgversion in (b'02', b'03'):
1672 1671 bversion = b'HG20'
1673 1672 bcompression = bundlespec.wirecompression
1674 1673 else:
1675 1674 raise error.ProgrammingError(
1676 1675 b'bundle: unexpected changegroup version %s' % cgversion
1677 1676 )
1678 1677
1679 1678 # TODO compression options should be derived from bundlespec parsing.
1680 1679 # This is a temporary hack to allow adjusting bundle compression
1681 1680 # level without a) formalizing the bundlespec changes to declare it
1682 1681 # b) introducing a command flag.
1683 1682 compopts = {}
1684 1683 complevel = ui.configint(
1685 1684 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1686 1685 )
1687 1686 if complevel is None:
1688 1687 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1689 1688 if complevel is not None:
1690 1689 compopts[b'level'] = complevel
1691 1690
1692 1691 compthreads = ui.configint(
1693 1692 b'experimental', b'bundlecompthreads.' + bundlespec.compression
1694 1693 )
1695 1694 if compthreads is None:
1696 1695 compthreads = ui.configint(b'experimental', b'bundlecompthreads')
1697 1696 if compthreads is not None:
1698 1697 compopts[b'threads'] = compthreads
1699 1698
1700 1699 # Bundling of obsmarker and phases is optional as not all clients
1701 1700 # support the necessary features.
1702 1701 cfg = ui.configbool
1703 1702 obsolescence_cfg = cfg(b'experimental', b'evolution.bundle-obsmarker')
1704 1703 bundlespec.set_param(b'obsolescence', obsolescence_cfg, overwrite=False)
1705 1704 obs_mand_cfg = cfg(b'experimental', b'evolution.bundle-obsmarker:mandatory')
1706 1705 bundlespec.set_param(
1707 1706 b'obsolescence-mandatory', obs_mand_cfg, overwrite=False
1708 1707 )
1709 1708 phases_cfg = cfg(b'experimental', b'bundle-phases')
1710 1709 bundlespec.set_param(b'phases', phases_cfg, overwrite=False)
1711 1710
1712 1711 bundle2.writenewbundle(
1713 1712 ui,
1714 1713 repo,
1715 1714 b'bundle',
1716 1715 fname,
1717 1716 bversion,
1718 1717 outgoing,
1719 1718 bundlespec.params,
1720 1719 compression=bcompression,
1721 1720 compopts=compopts,
1722 1721 )
1723 1722
1724 1723
1725 1724 @command(
1726 1725 b'cat',
1727 1726 [
1728 1727 (
1729 1728 b'o',
1730 1729 b'output',
1731 1730 b'',
1732 1731 _(b'print output to file with formatted name'),
1733 1732 _(b'FORMAT'),
1734 1733 ),
1735 1734 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1736 1735 (b'', b'decode', None, _(b'apply any matching decode filter')),
1737 1736 ]
1738 1737 + walkopts
1739 1738 + formatteropts,
1740 1739 _(b'[OPTION]... FILE...'),
1741 1740 helpcategory=command.CATEGORY_FILE_CONTENTS,
1742 1741 inferrepo=True,
1743 1742 intents={INTENT_READONLY},
1744 1743 )
1745 1744 def cat(ui, repo, file1, *pats, **opts):
1746 1745 """output the current or given revision of files
1747 1746
1748 1747 Print the specified files as they were at the given revision. If
1749 1748 no revision is given, the parent of the working directory is used.
1750 1749
1751 1750 Output may be to a file, in which case the name of the file is
1752 1751 given using a template string. See :hg:`help templates`. In addition
1753 1752 to the common template keywords, the following formatting rules are
1754 1753 supported:
1755 1754
1756 1755 :``%%``: literal "%" character
1757 1756 :``%s``: basename of file being printed
1758 1757 :``%d``: dirname of file being printed, or '.' if in repository root
1759 1758 :``%p``: root-relative path name of file being printed
1760 1759 :``%H``: changeset hash (40 hexadecimal digits)
1761 1760 :``%R``: changeset revision number
1762 1761 :``%h``: short-form changeset hash (12 hexadecimal digits)
1763 1762 :``%r``: zero-padded changeset revision number
1764 1763 :``%b``: basename of the exporting repository
1765 1764 :``\\``: literal "\\" character
1766 1765
1767 1766 .. container:: verbose
1768 1767
1769 1768 Template:
1770 1769
1771 1770 The following keywords are supported in addition to the common template
1772 1771 keywords and functions. See also :hg:`help templates`.
1773 1772
1774 1773 :data: String. File content.
1775 1774 :path: String. Repository-absolute path of the file.
1776 1775
1777 1776 Returns 0 on success.
1778 1777 """
1779 1778 opts = pycompat.byteskwargs(opts)
1780 1779 rev = opts.get(b'rev')
1781 1780 if rev:
1782 1781 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1783 1782 ctx = logcmdutil.revsingle(repo, rev)
1784 1783 m = scmutil.match(ctx, (file1,) + pats, opts)
1785 1784 fntemplate = opts.pop(b'output', b'')
1786 1785 if cmdutil.isstdiofilename(fntemplate):
1787 1786 fntemplate = b''
1788 1787
1789 1788 if fntemplate:
1790 1789 fm = formatter.nullformatter(ui, b'cat', opts)
1791 1790 else:
1792 1791 ui.pager(b'cat')
1793 1792 fm = ui.formatter(b'cat', opts)
1794 1793 with fm:
1795 1794 return cmdutil.cat(
1796 1795 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1797 1796 )
1798 1797
1799 1798
1800 1799 @command(
1801 1800 b'clone',
1802 1801 [
1803 1802 (
1804 1803 b'U',
1805 1804 b'noupdate',
1806 1805 None,
1807 1806 _(
1808 1807 b'the clone will include an empty working '
1809 1808 b'directory (only a repository)'
1810 1809 ),
1811 1810 ),
1812 1811 (
1813 1812 b'u',
1814 1813 b'updaterev',
1815 1814 b'',
1816 1815 _(b'revision, tag, or branch to check out'),
1817 1816 _(b'REV'),
1818 1817 ),
1819 1818 (
1820 1819 b'r',
1821 1820 b'rev',
1822 1821 [],
1823 1822 _(
1824 1823 b'do not clone everything, but include this changeset'
1825 1824 b' and its ancestors'
1826 1825 ),
1827 1826 _(b'REV'),
1828 1827 ),
1829 1828 (
1830 1829 b'b',
1831 1830 b'branch',
1832 1831 [],
1833 1832 _(
1834 1833 b'do not clone everything, but include this branch\'s'
1835 1834 b' changesets and their ancestors'
1836 1835 ),
1837 1836 _(b'BRANCH'),
1838 1837 ),
1839 1838 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1840 1839 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1841 1840 (b'', b'stream', None, _(b'clone with minimal data processing')),
1842 1841 ]
1843 1842 + remoteopts,
1844 1843 _(b'[OPTION]... SOURCE [DEST]'),
1845 1844 helpcategory=command.CATEGORY_REPO_CREATION,
1846 1845 helpbasic=True,
1847 1846 norepo=True,
1848 1847 )
1849 1848 def clone(ui, source, dest=None, **opts):
1850 1849 """make a copy of an existing repository
1851 1850
1852 1851 Create a copy of an existing repository in a new directory.
1853 1852
1854 1853 If no destination directory name is specified, it defaults to the
1855 1854 basename of the source.
1856 1855
1857 1856 The location of the source is added to the new repository's
1858 1857 ``.hg/hgrc`` file, as the default to be used for future pulls.
1859 1858
1860 1859 Only local paths and ``ssh://`` URLs are supported as
1861 1860 destinations. For ``ssh://`` destinations, no working directory or
1862 1861 ``.hg/hgrc`` will be created on the remote side.
1863 1862
1864 1863 If the source repository has a bookmark called '@' set, that
1865 1864 revision will be checked out in the new repository by default.
1866 1865
1867 1866 To check out a particular version, use -u/--update, or
1868 1867 -U/--noupdate to create a clone with no working directory.
1869 1868
1870 1869 To pull only a subset of changesets, specify one or more revisions
1871 1870 identifiers with -r/--rev or branches with -b/--branch. The
1872 1871 resulting clone will contain only the specified changesets and
1873 1872 their ancestors. These options (or 'clone src#rev dest') imply
1874 1873 --pull, even for local source repositories.
1875 1874
1876 1875 In normal clone mode, the remote normalizes repository data into a common
1877 1876 exchange format and the receiving end translates this data into its local
1878 1877 storage format. --stream activates a different clone mode that essentially
1879 1878 copies repository files from the remote with minimal data processing. This
1880 1879 significantly reduces the CPU cost of a clone both remotely and locally.
1881 1880 However, it often increases the transferred data size by 30-40%. This can
1882 1881 result in substantially faster clones where I/O throughput is plentiful,
1883 1882 especially for larger repositories. A side-effect of --stream clones is
1884 1883 that storage settings and requirements on the remote are applied locally:
1885 1884 a modern client may inherit legacy or inefficient storage used by the
1886 1885 remote or a legacy Mercurial client may not be able to clone from a
1887 1886 modern Mercurial remote.
1888 1887
1889 1888 .. note::
1890 1889
1891 1890 Specifying a tag will include the tagged changeset but not the
1892 1891 changeset containing the tag.
1893 1892
1894 1893 .. container:: verbose
1895 1894
1896 1895 For efficiency, hardlinks are used for cloning whenever the
1897 1896 source and destination are on the same filesystem (note this
1898 1897 applies only to the repository data, not to the working
1899 1898 directory). Some filesystems, such as AFS, implement hardlinking
1900 1899 incorrectly, but do not report errors. In these cases, use the
1901 1900 --pull option to avoid hardlinking.
1902 1901
1903 1902 Mercurial will update the working directory to the first applicable
1904 1903 revision from this list:
1905 1904
1906 1905 a) null if -U or the source repository has no changesets
1907 1906 b) if -u . and the source repository is local, the first parent of
1908 1907 the source repository's working directory
1909 1908 c) the changeset specified with -u (if a branch name, this means the
1910 1909 latest head of that branch)
1911 1910 d) the changeset specified with -r
1912 1911 e) the tipmost head specified with -b
1913 1912 f) the tipmost head specified with the url#branch source syntax
1914 1913 g) the revision marked with the '@' bookmark, if present
1915 1914 h) the tipmost head of the default branch
1916 1915 i) tip
1917 1916
1918 1917 When cloning from servers that support it, Mercurial may fetch
1919 1918 pre-generated data from a server-advertised URL or inline from the
1920 1919 same stream. When this is done, hooks operating on incoming changesets
1921 1920 and changegroups may fire more than once, once for each pre-generated
1922 1921 bundle and as well as for any additional remaining data. In addition,
1923 1922 if an error occurs, the repository may be rolled back to a partial
1924 1923 clone. This behavior may change in future releases.
1925 1924 See :hg:`help -e clonebundles` for more.
1926 1925
1927 1926 Examples:
1928 1927
1929 1928 - clone a remote repository to a new directory named hg/::
1930 1929
1931 1930 hg clone https://www.mercurial-scm.org/repo/hg/
1932 1931
1933 1932 - create a lightweight local clone::
1934 1933
1935 1934 hg clone project/ project-feature/
1936 1935
1937 1936 - clone from an absolute path on an ssh server (note double-slash)::
1938 1937
1939 1938 hg clone ssh://user@server//home/projects/alpha/
1940 1939
1941 1940 - do a streaming clone while checking out a specified version::
1942 1941
1943 1942 hg clone --stream http://server/repo -u 1.5
1944 1943
1945 1944 - create a repository without changesets after a particular revision::
1946 1945
1947 1946 hg clone -r 04e544 experimental/ good/
1948 1947
1949 1948 - clone (and track) a particular named branch::
1950 1949
1951 1950 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1952 1951
1953 1952 See :hg:`help urls` for details on specifying URLs.
1954 1953
1955 1954 Returns 0 on success.
1956 1955 """
1957 1956 opts = pycompat.byteskwargs(opts)
1958 1957 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1959 1958
1960 1959 # --include/--exclude can come from narrow or sparse.
1961 1960 includepats, excludepats = None, None
1962 1961
1963 1962 # hg.clone() differentiates between None and an empty set. So make sure
1964 1963 # patterns are sets if narrow is requested without patterns.
1965 1964 if opts.get(b'narrow'):
1966 1965 includepats = set()
1967 1966 excludepats = set()
1968 1967
1969 1968 if opts.get(b'include'):
1970 1969 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1971 1970 if opts.get(b'exclude'):
1972 1971 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1973 1972
1974 1973 r = hg.clone(
1975 1974 ui,
1976 1975 opts,
1977 1976 source,
1978 1977 dest,
1979 1978 pull=opts.get(b'pull'),
1980 1979 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1981 1980 revs=opts.get(b'rev'),
1982 1981 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1983 1982 branch=opts.get(b'branch'),
1984 1983 shareopts=opts.get(b'shareopts'),
1985 1984 storeincludepats=includepats,
1986 1985 storeexcludepats=excludepats,
1987 1986 depth=opts.get(b'depth') or None,
1988 1987 )
1989 1988
1990 1989 return r is None
1991 1990
1992 1991
1993 1992 @command(
1994 1993 b'commit|ci',
1995 1994 [
1996 1995 (
1997 1996 b'A',
1998 1997 b'addremove',
1999 1998 None,
2000 1999 _(b'mark new/missing files as added/removed before committing'),
2001 2000 ),
2002 2001 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
2003 2002 (b'', b'amend', None, _(b'amend the parent of the working directory')),
2004 2003 (b's', b'secret', None, _(b'use the secret phase for committing')),
2005 2004 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
2006 2005 (
2007 2006 b'',
2008 2007 b'force-close-branch',
2009 2008 None,
2010 2009 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
2011 2010 ),
2012 2011 (b'i', b'interactive', None, _(b'use interactive mode')),
2013 2012 ]
2014 2013 + walkopts
2015 2014 + commitopts
2016 2015 + commitopts2
2017 2016 + subrepoopts,
2018 2017 _(b'[OPTION]... [FILE]...'),
2019 2018 helpcategory=command.CATEGORY_COMMITTING,
2020 2019 helpbasic=True,
2021 2020 inferrepo=True,
2022 2021 )
2023 2022 def commit(ui, repo, *pats, **opts):
2024 2023 """commit the specified files or all outstanding changes
2025 2024
2026 2025 Commit changes to the given files into the repository. Unlike a
2027 2026 centralized SCM, this operation is a local operation. See
2028 2027 :hg:`push` for a way to actively distribute your changes.
2029 2028
2030 2029 If a list of files is omitted, all changes reported by :hg:`status`
2031 2030 will be committed.
2032 2031
2033 2032 If you are committing the result of a merge, do not provide any
2034 2033 filenames or -I/-X filters.
2035 2034
2036 2035 If no commit message is specified, Mercurial starts your
2037 2036 configured editor where you can enter a message. In case your
2038 2037 commit fails, you will find a backup of your message in
2039 2038 ``.hg/last-message.txt``.
2040 2039
2041 2040 The --close-branch flag can be used to mark the current branch
2042 2041 head closed. When all heads of a branch are closed, the branch
2043 2042 will be considered closed and no longer listed.
2044 2043
2045 2044 The --amend flag can be used to amend the parent of the
2046 2045 working directory with a new commit that contains the changes
2047 2046 in the parent in addition to those currently reported by :hg:`status`,
2048 2047 if there are any. The old commit is stored in a backup bundle in
2049 2048 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
2050 2049 on how to restore it).
2051 2050
2052 2051 Message, user and date are taken from the amended commit unless
2053 2052 specified. When a message isn't specified on the command line,
2054 2053 the editor will open with the message of the amended commit.
2055 2054
2056 2055 It is not possible to amend public changesets (see :hg:`help phases`)
2057 2056 or changesets that have children.
2058 2057
2059 2058 See :hg:`help dates` for a list of formats valid for -d/--date.
2060 2059
2061 2060 Returns 0 on success, 1 if nothing changed.
2062 2061
2063 2062 .. container:: verbose
2064 2063
2065 2064 Examples:
2066 2065
2067 2066 - commit all files ending in .py::
2068 2067
2069 2068 hg commit --include "set:**.py"
2070 2069
2071 2070 - commit all non-binary files::
2072 2071
2073 2072 hg commit --exclude "set:binary()"
2074 2073
2075 2074 - amend the current commit and set the date to now::
2076 2075
2077 2076 hg commit --amend --date now
2078 2077 """
2079 2078 with repo.wlock(), repo.lock():
2080 2079 return _docommit(ui, repo, *pats, **opts)
2081 2080
2082 2081
2083 2082 def _docommit(ui, repo, *pats, **opts):
2084 2083 if opts.get('interactive'):
2085 2084 opts.pop('interactive')
2086 2085 ret = cmdutil.dorecord(
2087 2086 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2088 2087 )
2089 2088 # ret can be 0 (no changes to record) or the value returned by
2090 2089 # commit(), 1 if nothing changed or None on success.
2091 2090 return 1 if ret == 0 else ret
2092 2091
2093 2092 if opts.get('subrepos'):
2094 2093 cmdutil.check_incompatible_arguments(opts, 'subrepos', ['amend'])
2095 2094 # Let --subrepos on the command line override config setting.
2096 2095 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2097 2096
2098 2097 cmdutil.checkunfinished(repo, commit=True)
2099 2098
2100 2099 branch = repo[None].branch()
2101 2100 bheads = repo.branchheads(branch)
2102 2101 tip = repo.changelog.tip()
2103 2102
2104 2103 extra = {}
2105 2104 if opts.get('close_branch') or opts.get('force_close_branch'):
2106 2105 extra[b'close'] = b'1'
2107 2106
2108 2107 if repo[b'.'].closesbranch():
2109 2108 # Not ideal, but let us do an extra status early to prevent early
2110 2109 # bail out.
2111 2110 matcher = scmutil.match(repo[None], pats, opts)
2112 2111 s = repo.status(match=matcher)
2113 2112 if s.modified or s.added or s.removed:
2114 2113 bheads = repo.branchheads(branch, closed=True)
2115 2114 else:
2116 2115 msg = _(b'current revision is already a branch closing head')
2117 2116 raise error.InputError(msg)
2118 2117
2119 2118 if not bheads:
2120 2119 raise error.InputError(
2121 2120 _(b'branch "%s" has no heads to close') % branch
2122 2121 )
2123 2122 elif (
2124 2123 branch == repo[b'.'].branch()
2125 2124 and repo[b'.'].node() not in bheads
2126 2125 and not opts.get('force_close_branch')
2127 2126 ):
2128 2127 hint = _(
2129 2128 b'use --force-close-branch to close branch from a non-head'
2130 2129 b' changeset'
2131 2130 )
2132 2131 raise error.InputError(_(b'can only close branch heads'), hint=hint)
2133 2132 elif opts.get('amend'):
2134 2133 if (
2135 2134 repo[b'.'].p1().branch() != branch
2136 2135 and repo[b'.'].p2().branch() != branch
2137 2136 ):
2138 2137 raise error.InputError(_(b'can only close branch heads'))
2139 2138
2140 2139 if opts.get('amend'):
2141 2140 if ui.configbool(b'ui', b'commitsubrepos'):
2142 2141 raise error.InputError(
2143 2142 _(b'cannot amend with ui.commitsubrepos enabled')
2144 2143 )
2145 2144
2146 2145 old = repo[b'.']
2147 2146 rewriteutil.precheck(repo, [old.rev()], b'amend')
2148 2147
2149 2148 # Currently histedit gets confused if an amend happens while histedit
2150 2149 # is in progress. Since we have a checkunfinished command, we are
2151 2150 # temporarily honoring it.
2152 2151 #
2153 2152 # Note: eventually this guard will be removed. Please do not expect
2154 2153 # this behavior to remain.
2155 2154 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2156 2155 cmdutil.checkunfinished(repo)
2157 2156
2158 2157 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2159 2158 opts = pycompat.byteskwargs(opts)
2160 2159 if node == old.node():
2161 2160 ui.status(_(b"nothing changed\n"))
2162 2161 return 1
2163 2162 else:
2164 2163
2165 2164 def commitfunc(ui, repo, message, match, opts):
2166 2165 overrides = {}
2167 2166 if opts.get(b'secret'):
2168 2167 overrides[(b'phases', b'new-commit')] = b'secret'
2169 2168
2170 2169 baseui = repo.baseui
2171 2170 with baseui.configoverride(overrides, b'commit'):
2172 2171 with ui.configoverride(overrides, b'commit'):
2173 2172 editform = cmdutil.mergeeditform(
2174 2173 repo[None], b'commit.normal'
2175 2174 )
2176 2175 editor = cmdutil.getcommiteditor(
2177 2176 editform=editform, **pycompat.strkwargs(opts)
2178 2177 )
2179 2178 return repo.commit(
2180 2179 message,
2181 2180 opts.get(b'user'),
2182 2181 opts.get(b'date'),
2183 2182 match,
2184 2183 editor=editor,
2185 2184 extra=extra,
2186 2185 )
2187 2186
2188 2187 opts = pycompat.byteskwargs(opts)
2189 2188 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2190 2189
2191 2190 if not node:
2192 2191 stat = cmdutil.postcommitstatus(repo, pats, opts)
2193 2192 if stat.deleted:
2194 2193 ui.status(
2195 2194 _(
2196 2195 b"nothing changed (%d missing files, see "
2197 2196 b"'hg status')\n"
2198 2197 )
2199 2198 % len(stat.deleted)
2200 2199 )
2201 2200 else:
2202 2201 ui.status(_(b"nothing changed\n"))
2203 2202 return 1
2204 2203
2205 2204 cmdutil.commitstatus(repo, node, branch, bheads, tip, opts)
2206 2205
2207 2206 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2208 2207 status(
2209 2208 ui,
2210 2209 repo,
2211 2210 modified=True,
2212 2211 added=True,
2213 2212 removed=True,
2214 2213 deleted=True,
2215 2214 unknown=True,
2216 2215 subrepos=opts.get(b'subrepos'),
2217 2216 )
2218 2217
2219 2218
2220 2219 @command(
2221 2220 b'config|showconfig|debugconfig',
2222 2221 [
2223 2222 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2224 2223 # This is experimental because we need
2225 2224 # * reasonable behavior around aliases,
2226 2225 # * decide if we display [debug] [experimental] and [devel] section par
2227 2226 # default
2228 2227 # * some way to display "generic" config entry (the one matching
2229 2228 # regexp,
2230 2229 # * proper display of the different value type
2231 2230 # * a better way to handle <DYNAMIC> values (and variable types),
2232 2231 # * maybe some type information ?
2233 2232 (
2234 2233 b'',
2235 2234 b'exp-all-known',
2236 2235 None,
2237 2236 _(b'show all known config option (EXPERIMENTAL)'),
2238 2237 ),
2239 2238 (b'e', b'edit', None, _(b'edit user config')),
2240 2239 (b'l', b'local', None, _(b'edit repository config')),
2241 2240 (b'', b'source', None, _(b'show source of configuration value')),
2242 2241 (
2243 2242 b'',
2244 2243 b'shared',
2245 2244 None,
2246 2245 _(b'edit shared source repository config (EXPERIMENTAL)'),
2247 2246 ),
2248 2247 (b'', b'non-shared', None, _(b'edit non shared config (EXPERIMENTAL)')),
2249 2248 (b'g', b'global', None, _(b'edit global config')),
2250 2249 ]
2251 2250 + formatteropts,
2252 2251 _(b'[-u] [NAME]...'),
2253 2252 helpcategory=command.CATEGORY_HELP,
2254 2253 optionalrepo=True,
2255 2254 intents={INTENT_READONLY},
2256 2255 )
2257 2256 def config(ui, repo, *values, **opts):
2258 2257 """show combined config settings from all hgrc files
2259 2258
2260 2259 With no arguments, print names and values of all config items.
2261 2260
2262 2261 With one argument of the form section.name, print just the value
2263 2262 of that config item.
2264 2263
2265 2264 With multiple arguments, print names and values of all config
2266 2265 items with matching section names or section.names.
2267 2266
2268 2267 With --edit, start an editor on the user-level config file. With
2269 2268 --global, edit the system-wide config file. With --local, edit the
2270 2269 repository-level config file.
2271 2270
2272 2271 With --source, the source (filename and line number) is printed
2273 2272 for each config item.
2274 2273
2275 2274 See :hg:`help config` for more information about config files.
2276 2275
2277 2276 .. container:: verbose
2278 2277
2279 2278 --non-shared flag is used to edit `.hg/hgrc-not-shared` config file.
2280 2279 This file is not shared across shares when in share-safe mode.
2281 2280
2282 2281 Template:
2283 2282
2284 2283 The following keywords are supported. See also :hg:`help templates`.
2285 2284
2286 2285 :name: String. Config name.
2287 2286 :source: String. Filename and line number where the item is defined.
2288 2287 :value: String. Config value.
2289 2288
2290 2289 The --shared flag can be used to edit the config file of shared source
2291 2290 repository. It only works when you have shared using the experimental
2292 2291 share safe feature.
2293 2292
2294 2293 Returns 0 on success, 1 if NAME does not exist.
2295 2294
2296 2295 """
2297 2296
2298 2297 opts = pycompat.byteskwargs(opts)
2299 2298 editopts = (b'edit', b'local', b'global', b'shared', b'non_shared')
2300 2299 if any(opts.get(o) for o in editopts):
2301 2300 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2302 2301 if opts.get(b'local'):
2303 2302 if not repo:
2304 2303 raise error.InputError(
2305 2304 _(b"can't use --local outside a repository")
2306 2305 )
2307 2306 paths = [repo.vfs.join(b'hgrc')]
2308 2307 elif opts.get(b'global'):
2309 2308 paths = rcutil.systemrcpath()
2310 2309 elif opts.get(b'shared'):
2311 2310 if not repo.shared():
2312 2311 raise error.InputError(
2313 2312 _(b"repository is not shared; can't use --shared")
2314 2313 )
2315 2314 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2316 2315 raise error.InputError(
2317 2316 _(
2318 2317 b"share safe feature not enabled; "
2319 2318 b"unable to edit shared source repository config"
2320 2319 )
2321 2320 )
2322 2321 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2323 2322 elif opts.get(b'non_shared'):
2324 2323 paths = [repo.vfs.join(b'hgrc-not-shared')]
2325 2324 else:
2326 2325 paths = rcutil.userrcpath()
2327 2326
2328 2327 for f in paths:
2329 2328 if os.path.exists(f):
2330 2329 break
2331 2330 else:
2332 2331 if opts.get(b'global'):
2333 2332 samplehgrc = uimod.samplehgrcs[b'global']
2334 2333 elif opts.get(b'local'):
2335 2334 samplehgrc = uimod.samplehgrcs[b'local']
2336 2335 else:
2337 2336 samplehgrc = uimod.samplehgrcs[b'user']
2338 2337
2339 2338 f = paths[0]
2340 2339 fp = open(f, b"wb")
2341 2340 fp.write(util.tonativeeol(samplehgrc))
2342 2341 fp.close()
2343 2342
2344 2343 editor = ui.geteditor()
2345 2344 ui.system(
2346 2345 b"%s \"%s\"" % (editor, f),
2347 2346 onerr=error.InputError,
2348 2347 errprefix=_(b"edit failed"),
2349 2348 blockedtag=b'config_edit',
2350 2349 )
2351 2350 return
2352 2351 ui.pager(b'config')
2353 2352 fm = ui.formatter(b'config', opts)
2354 2353 for t, f in rcutil.rccomponents():
2355 2354 if t == b'path':
2356 2355 ui.debug(b'read config from: %s\n' % f)
2357 2356 elif t == b'resource':
2358 2357 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2359 2358 elif t == b'items':
2360 2359 # Don't print anything for 'items'.
2361 2360 pass
2362 2361 else:
2363 2362 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2364 2363 untrusted = bool(opts.get(b'untrusted'))
2365 2364
2366 2365 selsections = selentries = []
2367 2366 if values:
2368 2367 selsections = [v for v in values if b'.' not in v]
2369 2368 selentries = [v for v in values if b'.' in v]
2370 2369 uniquesel = len(selentries) == 1 and not selsections
2371 2370 selsections = set(selsections)
2372 2371 selentries = set(selentries)
2373 2372
2374 2373 matched = False
2375 2374 all_known = opts[b'exp_all_known']
2376 2375 show_source = ui.debugflag or opts.get(b'source')
2377 2376 entries = ui.walkconfig(untrusted=untrusted, all_known=all_known)
2378 2377 for section, name, value in entries:
2379 2378 source = ui.configsource(section, name, untrusted)
2380 2379 value = pycompat.bytestr(value)
2381 2380 defaultvalue = ui.configdefault(section, name)
2382 2381 if fm.isplain():
2383 2382 source = source or b'none'
2384 2383 value = value.replace(b'\n', b'\\n')
2385 2384 entryname = section + b'.' + name
2386 2385 if values and not (section in selsections or entryname in selentries):
2387 2386 continue
2388 2387 fm.startitem()
2389 2388 fm.condwrite(show_source, b'source', b'%s: ', source)
2390 2389 if uniquesel:
2391 2390 fm.data(name=entryname)
2392 2391 fm.write(b'value', b'%s\n', value)
2393 2392 else:
2394 2393 fm.write(b'name value', b'%s=%s\n', entryname, value)
2395 2394 if formatter.isprintable(defaultvalue):
2396 2395 fm.data(defaultvalue=defaultvalue)
2397 2396 elif isinstance(defaultvalue, list) and all(
2398 2397 formatter.isprintable(e) for e in defaultvalue
2399 2398 ):
2400 2399 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2401 2400 # TODO: no idea how to process unsupported defaultvalue types
2402 2401 matched = True
2403 2402 fm.end()
2404 2403 if matched:
2405 2404 return 0
2406 2405 return 1
2407 2406
2408 2407
2409 2408 @command(
2410 2409 b'continue',
2411 2410 dryrunopts,
2412 2411 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2413 2412 helpbasic=True,
2414 2413 )
2415 2414 def continuecmd(ui, repo, **opts):
2416 2415 """resumes an interrupted operation (EXPERIMENTAL)
2417 2416
2418 2417 Finishes a multistep operation like graft, histedit, rebase, merge,
2419 2418 and unshelve if they are in an interrupted state.
2420 2419
2421 2420 use --dry-run/-n to dry run the command.
2422 2421 """
2423 2422 dryrun = opts.get('dry_run')
2424 2423 contstate = cmdutil.getunfinishedstate(repo)
2425 2424 if not contstate:
2426 2425 raise error.StateError(_(b'no operation in progress'))
2427 2426 if not contstate.continuefunc:
2428 2427 raise error.StateError(
2429 2428 (
2430 2429 _(b"%s in progress but does not support 'hg continue'")
2431 2430 % (contstate._opname)
2432 2431 ),
2433 2432 hint=contstate.continuemsg(),
2434 2433 )
2435 2434 if dryrun:
2436 2435 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2437 2436 return
2438 2437 return contstate.continuefunc(ui, repo)
2439 2438
2440 2439
2441 2440 @command(
2442 2441 b'copy|cp',
2443 2442 [
2444 2443 (b'', b'forget', None, _(b'unmark a destination file as copied')),
2445 2444 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2446 2445 (
2447 2446 b'',
2448 2447 b'at-rev',
2449 2448 b'',
2450 2449 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2451 2450 _(b'REV'),
2452 2451 ),
2453 2452 (
2454 2453 b'f',
2455 2454 b'force',
2456 2455 None,
2457 2456 _(b'forcibly copy over an existing managed file'),
2458 2457 ),
2459 2458 ]
2460 2459 + walkopts
2461 2460 + dryrunopts,
2462 2461 _(b'[OPTION]... (SOURCE... DEST | --forget DEST...)'),
2463 2462 helpcategory=command.CATEGORY_FILE_CONTENTS,
2464 2463 )
2465 2464 def copy(ui, repo, *pats, **opts):
2466 2465 """mark files as copied for the next commit
2467 2466
2468 2467 Mark dest as having copies of source files. If dest is a
2469 2468 directory, copies are put in that directory. If dest is a file,
2470 2469 the source must be a single file.
2471 2470
2472 2471 By default, this command copies the contents of files as they
2473 2472 exist in the working directory. If invoked with -A/--after, the
2474 2473 operation is recorded, but no copying is performed.
2475 2474
2476 2475 To undo marking a destination file as copied, use --forget. With that
2477 2476 option, all given (positional) arguments are unmarked as copies. The
2478 2477 destination file(s) will be left in place (still tracked). Note that
2479 2478 :hg:`copy --forget` behaves the same way as :hg:`rename --forget`.
2480 2479
2481 2480 This command takes effect with the next commit by default.
2482 2481
2483 2482 Returns 0 on success, 1 if errors are encountered.
2484 2483 """
2485 2484 opts = pycompat.byteskwargs(opts)
2486 2485 with repo.wlock():
2487 2486 return cmdutil.copy(ui, repo, pats, opts)
2488 2487
2489 2488
2490 2489 @command(
2491 2490 b'debugcommands',
2492 2491 [],
2493 2492 _(b'[COMMAND]'),
2494 2493 helpcategory=command.CATEGORY_HELP,
2495 2494 norepo=True,
2496 2495 )
2497 2496 def debugcommands(ui, cmd=b'', *args):
2498 2497 """list all available commands and options"""
2499 2498 for cmd, vals in sorted(table.items()):
2500 2499 cmd = cmd.split(b'|')[0]
2501 2500 opts = b', '.join([i[1] for i in vals[1]])
2502 2501 ui.write(b'%s: %s\n' % (cmd, opts))
2503 2502
2504 2503
2505 2504 @command(
2506 2505 b'debugcomplete',
2507 2506 [(b'o', b'options', None, _(b'show the command options'))],
2508 2507 _(b'[-o] CMD'),
2509 2508 helpcategory=command.CATEGORY_HELP,
2510 2509 norepo=True,
2511 2510 )
2512 2511 def debugcomplete(ui, cmd=b'', **opts):
2513 2512 """returns the completion list associated with the given command"""
2514 2513
2515 2514 if opts.get('options'):
2516 2515 options = []
2517 2516 otables = [globalopts]
2518 2517 if cmd:
2519 2518 aliases, entry = cmdutil.findcmd(cmd, table, False)
2520 2519 otables.append(entry[1])
2521 2520 for t in otables:
2522 2521 for o in t:
2523 2522 if b"(DEPRECATED)" in o[3]:
2524 2523 continue
2525 2524 if o[0]:
2526 2525 options.append(b'-%s' % o[0])
2527 2526 options.append(b'--%s' % o[1])
2528 2527 ui.write(b"%s\n" % b"\n".join(options))
2529 2528 return
2530 2529
2531 2530 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2532 2531 if ui.verbose:
2533 2532 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2534 2533 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2535 2534
2536 2535
2537 2536 @command(
2538 2537 b'diff',
2539 2538 [
2540 2539 (b'r', b'rev', [], _(b'revision (DEPRECATED)'), _(b'REV')),
2541 2540 (b'', b'from', b'', _(b'revision to diff from'), _(b'REV1')),
2542 2541 (b'', b'to', b'', _(b'revision to diff to'), _(b'REV2')),
2543 2542 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2544 2543 ]
2545 2544 + diffopts
2546 2545 + diffopts2
2547 2546 + walkopts
2548 2547 + subrepoopts,
2549 2548 _(b'[OPTION]... ([-c REV] | [--from REV1] [--to REV2]) [FILE]...'),
2550 2549 helpcategory=command.CATEGORY_FILE_CONTENTS,
2551 2550 helpbasic=True,
2552 2551 inferrepo=True,
2553 2552 intents={INTENT_READONLY},
2554 2553 )
2555 2554 def diff(ui, repo, *pats, **opts):
2556 2555 """diff repository (or selected files)
2557 2556
2558 2557 Show differences between revisions for the specified files.
2559 2558
2560 2559 Differences between files are shown using the unified diff format.
2561 2560
2562 2561 .. note::
2563 2562
2564 2563 :hg:`diff` may generate unexpected results for merges, as it will
2565 2564 default to comparing against the working directory's first
2566 2565 parent changeset if no revisions are specified. To diff against the
2567 2566 conflict regions, you can use `--config diff.merge=yes`.
2568 2567
2569 2568 By default, the working directory files are compared to its first parent. To
2570 2569 see the differences from another revision, use --from. To see the difference
2571 2570 to another revision, use --to. For example, :hg:`diff --from .^` will show
2572 2571 the differences from the working copy's grandparent to the working copy,
2573 2572 :hg:`diff --to .` will show the diff from the working copy to its parent
2574 2573 (i.e. the reverse of the default), and :hg:`diff --from 1.0 --to 1.2` will
2575 2574 show the diff between those two revisions.
2576 2575
2577 2576 Alternatively you can specify -c/--change with a revision to see the changes
2578 2577 in that changeset relative to its first parent (i.e. :hg:`diff -c 42` is
2579 2578 equivalent to :hg:`diff --from 42^ --to 42`)
2580 2579
2581 2580 Without the -a/--text option, diff will avoid generating diffs of
2582 2581 files it detects as binary. With -a, diff will generate a diff
2583 2582 anyway, probably with undesirable results.
2584 2583
2585 2584 Use the -g/--git option to generate diffs in the git extended diff
2586 2585 format. For more information, read :hg:`help diffs`.
2587 2586
2588 2587 .. container:: verbose
2589 2588
2590 2589 Examples:
2591 2590
2592 2591 - compare a file in the current working directory to its parent::
2593 2592
2594 2593 hg diff foo.c
2595 2594
2596 2595 - compare two historical versions of a directory, with rename info::
2597 2596
2598 2597 hg diff --git --from 1.0 --to 1.2 lib/
2599 2598
2600 2599 - get change stats relative to the last change on some date::
2601 2600
2602 2601 hg diff --stat --from "date('may 2')"
2603 2602
2604 2603 - diff all newly-added files that contain a keyword::
2605 2604
2606 2605 hg diff "set:added() and grep(GNU)"
2607 2606
2608 2607 - compare a revision and its parents::
2609 2608
2610 2609 hg diff -c 9353 # compare against first parent
2611 2610 hg diff --from 9353^ --to 9353 # same using revset syntax
2612 2611 hg diff --from 9353^2 --to 9353 # compare against the second parent
2613 2612
2614 2613 Returns 0 on success.
2615 2614 """
2616 2615
2617 2616 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2618 2617 opts = pycompat.byteskwargs(opts)
2619 2618 revs = opts.get(b'rev')
2620 2619 change = opts.get(b'change')
2621 2620 from_rev = opts.get(b'from')
2622 2621 to_rev = opts.get(b'to')
2623 2622 stat = opts.get(b'stat')
2624 2623 reverse = opts.get(b'reverse')
2625 2624
2626 2625 cmdutil.check_incompatible_arguments(opts, b'from', [b'rev', b'change'])
2627 2626 cmdutil.check_incompatible_arguments(opts, b'to', [b'rev', b'change'])
2628 2627 if change:
2629 2628 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2630 2629 ctx2 = logcmdutil.revsingle(repo, change, None)
2631 2630 ctx1 = logcmdutil.diff_parent(ctx2)
2632 2631 elif from_rev or to_rev:
2633 2632 repo = scmutil.unhidehashlikerevs(
2634 2633 repo, [from_rev] + [to_rev], b'nowarn'
2635 2634 )
2636 2635 ctx1 = logcmdutil.revsingle(repo, from_rev, None)
2637 2636 ctx2 = logcmdutil.revsingle(repo, to_rev, None)
2638 2637 else:
2639 2638 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2640 2639 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
2641 2640
2642 2641 if reverse:
2643 2642 ctxleft = ctx2
2644 2643 ctxright = ctx1
2645 2644 else:
2646 2645 ctxleft = ctx1
2647 2646 ctxright = ctx2
2648 2647
2649 2648 diffopts = patch.diffallopts(ui, opts)
2650 2649 m = scmutil.match(ctx2, pats, opts)
2651 2650 m = repo.narrowmatch(m)
2652 2651 ui.pager(b'diff')
2653 2652 logcmdutil.diffordiffstat(
2654 2653 ui,
2655 2654 repo,
2656 2655 diffopts,
2657 2656 ctxleft,
2658 2657 ctxright,
2659 2658 m,
2660 2659 stat=stat,
2661 2660 listsubrepos=opts.get(b'subrepos'),
2662 2661 root=opts.get(b'root'),
2663 2662 )
2664 2663
2665 2664
2666 2665 @command(
2667 2666 b'export',
2668 2667 [
2669 2668 (
2670 2669 b'B',
2671 2670 b'bookmark',
2672 2671 b'',
2673 2672 _(b'export changes only reachable by given bookmark'),
2674 2673 _(b'BOOKMARK'),
2675 2674 ),
2676 2675 (
2677 2676 b'o',
2678 2677 b'output',
2679 2678 b'',
2680 2679 _(b'print output to file with formatted name'),
2681 2680 _(b'FORMAT'),
2682 2681 ),
2683 2682 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2684 2683 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2685 2684 ]
2686 2685 + diffopts
2687 2686 + formatteropts,
2688 2687 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2689 2688 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2690 2689 helpbasic=True,
2691 2690 intents={INTENT_READONLY},
2692 2691 )
2693 2692 def export(ui, repo, *changesets, **opts):
2694 2693 """dump the header and diffs for one or more changesets
2695 2694
2696 2695 Print the changeset header and diffs for one or more revisions.
2697 2696 If no revision is given, the parent of the working directory is used.
2698 2697
2699 2698 The information shown in the changeset header is: author, date,
2700 2699 branch name (if non-default), changeset hash, parent(s) and commit
2701 2700 comment.
2702 2701
2703 2702 .. note::
2704 2703
2705 2704 :hg:`export` may generate unexpected diff output for merge
2706 2705 changesets, as it will compare the merge changeset against its
2707 2706 first parent only.
2708 2707
2709 2708 Output may be to a file, in which case the name of the file is
2710 2709 given using a template string. See :hg:`help templates`. In addition
2711 2710 to the common template keywords, the following formatting rules are
2712 2711 supported:
2713 2712
2714 2713 :``%%``: literal "%" character
2715 2714 :``%H``: changeset hash (40 hexadecimal digits)
2716 2715 :``%N``: number of patches being generated
2717 2716 :``%R``: changeset revision number
2718 2717 :``%b``: basename of the exporting repository
2719 2718 :``%h``: short-form changeset hash (12 hexadecimal digits)
2720 2719 :``%m``: first line of the commit message (only alphanumeric characters)
2721 2720 :``%n``: zero-padded sequence number, starting at 1
2722 2721 :``%r``: zero-padded changeset revision number
2723 2722 :``\\``: literal "\\" character
2724 2723
2725 2724 Without the -a/--text option, export will avoid generating diffs
2726 2725 of files it detects as binary. With -a, export will generate a
2727 2726 diff anyway, probably with undesirable results.
2728 2727
2729 2728 With -B/--bookmark changesets reachable by the given bookmark are
2730 2729 selected.
2731 2730
2732 2731 Use the -g/--git option to generate diffs in the git extended diff
2733 2732 format. See :hg:`help diffs` for more information.
2734 2733
2735 2734 With the --switch-parent option, the diff will be against the
2736 2735 second parent. It can be useful to review a merge.
2737 2736
2738 2737 .. container:: verbose
2739 2738
2740 2739 Template:
2741 2740
2742 2741 The following keywords are supported in addition to the common template
2743 2742 keywords and functions. See also :hg:`help templates`.
2744 2743
2745 2744 :diff: String. Diff content.
2746 2745 :parents: List of strings. Parent nodes of the changeset.
2747 2746
2748 2747 Examples:
2749 2748
2750 2749 - use export and import to transplant a bugfix to the current
2751 2750 branch::
2752 2751
2753 2752 hg export -r 9353 | hg import -
2754 2753
2755 2754 - export all the changesets between two revisions to a file with
2756 2755 rename information::
2757 2756
2758 2757 hg export --git -r 123:150 > changes.txt
2759 2758
2760 2759 - split outgoing changes into a series of patches with
2761 2760 descriptive names::
2762 2761
2763 2762 hg export -r "outgoing()" -o "%n-%m.patch"
2764 2763
2765 2764 Returns 0 on success.
2766 2765 """
2767 2766 opts = pycompat.byteskwargs(opts)
2768 2767 bookmark = opts.get(b'bookmark')
2769 2768 changesets += tuple(opts.get(b'rev', []))
2770 2769
2771 2770 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2772 2771
2773 2772 if bookmark:
2774 2773 if bookmark not in repo._bookmarks:
2775 2774 raise error.InputError(_(b"bookmark '%s' not found") % bookmark)
2776 2775
2777 2776 revs = scmutil.bookmarkrevs(repo, bookmark)
2778 2777 else:
2779 2778 if not changesets:
2780 2779 changesets = [b'.']
2781 2780
2782 2781 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2783 2782 revs = logcmdutil.revrange(repo, changesets)
2784 2783
2785 2784 if not revs:
2786 2785 raise error.InputError(_(b"export requires at least one changeset"))
2787 2786 if len(revs) > 1:
2788 2787 ui.note(_(b'exporting patches:\n'))
2789 2788 else:
2790 2789 ui.note(_(b'exporting patch:\n'))
2791 2790
2792 2791 fntemplate = opts.get(b'output')
2793 2792 if cmdutil.isstdiofilename(fntemplate):
2794 2793 fntemplate = b''
2795 2794
2796 2795 if fntemplate:
2797 2796 fm = formatter.nullformatter(ui, b'export', opts)
2798 2797 else:
2799 2798 ui.pager(b'export')
2800 2799 fm = ui.formatter(b'export', opts)
2801 2800 with fm:
2802 2801 cmdutil.export(
2803 2802 repo,
2804 2803 revs,
2805 2804 fm,
2806 2805 fntemplate=fntemplate,
2807 2806 switch_parent=opts.get(b'switch_parent'),
2808 2807 opts=patch.diffallopts(ui, opts),
2809 2808 )
2810 2809
2811 2810
2812 2811 @command(
2813 2812 b'files',
2814 2813 [
2815 2814 (
2816 2815 b'r',
2817 2816 b'rev',
2818 2817 b'',
2819 2818 _(b'search the repository as it is in REV'),
2820 2819 _(b'REV'),
2821 2820 ),
2822 2821 (
2823 2822 b'0',
2824 2823 b'print0',
2825 2824 None,
2826 2825 _(b'end filenames with NUL, for use with xargs'),
2827 2826 ),
2828 2827 ]
2829 2828 + walkopts
2830 2829 + formatteropts
2831 2830 + subrepoopts,
2832 2831 _(b'[OPTION]... [FILE]...'),
2833 2832 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2834 2833 intents={INTENT_READONLY},
2835 2834 )
2836 2835 def files(ui, repo, *pats, **opts):
2837 2836 """list tracked files
2838 2837
2839 2838 Print files under Mercurial control in the working directory or
2840 2839 specified revision for given files (excluding removed files).
2841 2840 Files can be specified as filenames or filesets.
2842 2841
2843 2842 If no files are given to match, this command prints the names
2844 2843 of all files under Mercurial control.
2845 2844
2846 2845 .. container:: verbose
2847 2846
2848 2847 Template:
2849 2848
2850 2849 The following keywords are supported in addition to the common template
2851 2850 keywords and functions. See also :hg:`help templates`.
2852 2851
2853 2852 :flags: String. Character denoting file's symlink and executable bits.
2854 2853 :path: String. Repository-absolute path of the file.
2855 2854 :size: Integer. Size of the file in bytes.
2856 2855
2857 2856 Examples:
2858 2857
2859 2858 - list all files under the current directory::
2860 2859
2861 2860 hg files .
2862 2861
2863 2862 - shows sizes and flags for current revision::
2864 2863
2865 2864 hg files -vr .
2866 2865
2867 2866 - list all files named README::
2868 2867
2869 2868 hg files -I "**/README"
2870 2869
2871 2870 - list all binary files::
2872 2871
2873 2872 hg files "set:binary()"
2874 2873
2875 2874 - find files containing a regular expression::
2876 2875
2877 2876 hg files "set:grep('bob')"
2878 2877
2879 2878 - search tracked file contents with xargs and grep::
2880 2879
2881 2880 hg files -0 | xargs -0 grep foo
2882 2881
2883 2882 See :hg:`help patterns` and :hg:`help filesets` for more information
2884 2883 on specifying file patterns.
2885 2884
2886 2885 Returns 0 if a match is found, 1 otherwise.
2887 2886
2888 2887 """
2889 2888
2890 2889 opts = pycompat.byteskwargs(opts)
2891 2890 rev = opts.get(b'rev')
2892 2891 if rev:
2893 2892 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2894 2893 ctx = logcmdutil.revsingle(repo, rev, None)
2895 2894
2896 2895 end = b'\n'
2897 2896 if opts.get(b'print0'):
2898 2897 end = b'\0'
2899 2898 fmt = b'%s' + end
2900 2899
2901 2900 m = scmutil.match(ctx, pats, opts)
2902 2901 ui.pager(b'files')
2903 2902 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2904 2903 with ui.formatter(b'files', opts) as fm:
2905 2904 return cmdutil.files(
2906 2905 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2907 2906 )
2908 2907
2909 2908
2910 2909 @command(
2911 2910 b'forget',
2912 2911 [
2913 2912 (b'i', b'interactive', None, _(b'use interactive mode')),
2914 2913 ]
2915 2914 + walkopts
2916 2915 + dryrunopts,
2917 2916 _(b'[OPTION]... FILE...'),
2918 2917 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2919 2918 helpbasic=True,
2920 2919 inferrepo=True,
2921 2920 )
2922 2921 def forget(ui, repo, *pats, **opts):
2923 2922 """forget the specified files on the next commit
2924 2923
2925 2924 Mark the specified files so they will no longer be tracked
2926 2925 after the next commit.
2927 2926
2928 2927 This only removes files from the current branch, not from the
2929 2928 entire project history, and it does not delete them from the
2930 2929 working directory.
2931 2930
2932 2931 To delete the file from the working directory, see :hg:`remove`.
2933 2932
2934 2933 To undo a forget before the next commit, see :hg:`add`.
2935 2934
2936 2935 .. container:: verbose
2937 2936
2938 2937 Examples:
2939 2938
2940 2939 - forget newly-added binary files::
2941 2940
2942 2941 hg forget "set:added() and binary()"
2943 2942
2944 2943 - forget files that would be excluded by .hgignore::
2945 2944
2946 2945 hg forget "set:hgignore()"
2947 2946
2948 2947 Returns 0 on success.
2949 2948 """
2950 2949
2951 2950 opts = pycompat.byteskwargs(opts)
2952 2951 if not pats:
2953 2952 raise error.InputError(_(b'no files specified'))
2954 2953
2955 2954 m = scmutil.match(repo[None], pats, opts)
2956 2955 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2957 2956 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2958 2957 rejected = cmdutil.forget(
2959 2958 ui,
2960 2959 repo,
2961 2960 m,
2962 2961 prefix=b"",
2963 2962 uipathfn=uipathfn,
2964 2963 explicitonly=False,
2965 2964 dryrun=dryrun,
2966 2965 interactive=interactive,
2967 2966 )[0]
2968 2967 return rejected and 1 or 0
2969 2968
2970 2969
2971 2970 @command(
2972 2971 b'graft',
2973 2972 [
2974 2973 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2975 2974 (
2976 2975 b'',
2977 2976 b'base',
2978 2977 b'',
2979 2978 _(b'base revision when doing the graft merge (ADVANCED)'),
2980 2979 _(b'REV'),
2981 2980 ),
2982 2981 (b'c', b'continue', False, _(b'resume interrupted graft')),
2983 2982 (b'', b'stop', False, _(b'stop interrupted graft')),
2984 2983 (b'', b'abort', False, _(b'abort interrupted graft')),
2985 2984 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2986 2985 (b'', b'log', None, _(b'append graft info to log message')),
2987 2986 (
2988 2987 b'',
2989 2988 b'no-commit',
2990 2989 None,
2991 2990 _(b"don't commit, just apply the changes in working directory"),
2992 2991 ),
2993 2992 (b'f', b'force', False, _(b'force graft')),
2994 2993 (
2995 2994 b'D',
2996 2995 b'currentdate',
2997 2996 False,
2998 2997 _(b'record the current date as commit date'),
2999 2998 ),
3000 2999 (
3001 3000 b'U',
3002 3001 b'currentuser',
3003 3002 False,
3004 3003 _(b'record the current user as committer'),
3005 3004 ),
3006 3005 ]
3007 3006 + commitopts2
3008 3007 + mergetoolopts
3009 3008 + dryrunopts,
3010 3009 _(b'[OPTION]... [-r REV]... REV...'),
3011 3010 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
3012 3011 )
3013 3012 def graft(ui, repo, *revs, **opts):
3014 3013 """copy changes from other branches onto the current branch
3015 3014
3016 3015 This command uses Mercurial's merge logic to copy individual
3017 3016 changes from other branches without merging branches in the
3018 3017 history graph. This is sometimes known as 'backporting' or
3019 3018 'cherry-picking'. By default, graft will copy user, date, and
3020 3019 description from the source changesets.
3021 3020
3022 3021 Changesets that are ancestors of the current revision, that have
3023 3022 already been grafted, or that are merges will be skipped.
3024 3023
3025 3024 If --log is specified, log messages will have a comment appended
3026 3025 of the form::
3027 3026
3028 3027 (grafted from CHANGESETHASH)
3029 3028
3030 3029 If --force is specified, revisions will be grafted even if they
3031 3030 are already ancestors of, or have been grafted to, the destination.
3032 3031 This is useful when the revisions have since been backed out.
3033 3032
3034 3033 If a graft merge results in conflicts, the graft process is
3035 3034 interrupted so that the current merge can be manually resolved.
3036 3035 Once all conflicts are addressed, the graft process can be
3037 3036 continued with the -c/--continue option.
3038 3037
3039 3038 The -c/--continue option reapplies all the earlier options.
3040 3039
3041 3040 .. container:: verbose
3042 3041
3043 3042 The --base option exposes more of how graft internally uses merge with a
3044 3043 custom base revision. --base can be used to specify another ancestor than
3045 3044 the first and only parent.
3046 3045
3047 3046 The command::
3048 3047
3049 3048 hg graft -r 345 --base 234
3050 3049
3051 3050 is thus pretty much the same as::
3052 3051
3053 3052 hg diff --from 234 --to 345 | hg import
3054 3053
3055 3054 but using merge to resolve conflicts and track moved files.
3056 3055
3057 3056 The result of a merge can thus be backported as a single commit by
3058 3057 specifying one of the merge parents as base, and thus effectively
3059 3058 grafting the changes from the other side.
3060 3059
3061 3060 It is also possible to collapse multiple changesets and clean up history
3062 3061 by specifying another ancestor as base, much like rebase --collapse
3063 3062 --keep.
3064 3063
3065 3064 The commit message can be tweaked after the fact using commit --amend .
3066 3065
3067 3066 For using non-ancestors as the base to backout changes, see the backout
3068 3067 command and the hidden --parent option.
3069 3068
3070 3069 .. container:: verbose
3071 3070
3072 3071 Examples:
3073 3072
3074 3073 - copy a single change to the stable branch and edit its description::
3075 3074
3076 3075 hg update stable
3077 3076 hg graft --edit 9393
3078 3077
3079 3078 - graft a range of changesets with one exception, updating dates::
3080 3079
3081 3080 hg graft -D "2085::2093 and not 2091"
3082 3081
3083 3082 - continue a graft after resolving conflicts::
3084 3083
3085 3084 hg graft -c
3086 3085
3087 3086 - show the source of a grafted changeset::
3088 3087
3089 3088 hg log --debug -r .
3090 3089
3091 3090 - show revisions sorted by date::
3092 3091
3093 3092 hg log -r "sort(all(), date)"
3094 3093
3095 3094 - backport the result of a merge as a single commit::
3096 3095
3097 3096 hg graft -r 123 --base 123^
3098 3097
3099 3098 - land a feature branch as one changeset::
3100 3099
3101 3100 hg up -cr default
3102 3101 hg graft -r featureX --base "ancestor('featureX', 'default')"
3103 3102
3104 3103 See :hg:`help revisions` for more about specifying revisions.
3105 3104
3106 3105 Returns 0 on successful completion, 1 if there are unresolved files.
3107 3106 """
3108 3107 with repo.wlock():
3109 3108 return _dograft(ui, repo, *revs, **opts)
3110 3109
3111 3110
3112 3111 def _dograft(ui, repo, *revs, **opts):
3113 3112 if revs and opts.get('rev'):
3114 3113 ui.warn(
3115 3114 _(
3116 3115 b'warning: inconsistent use of --rev might give unexpected '
3117 3116 b'revision ordering!\n'
3118 3117 )
3119 3118 )
3120 3119
3121 3120 revs = list(revs)
3122 3121 revs.extend(opts.get('rev'))
3123 3122 # a dict of data to be stored in state file
3124 3123 statedata = {}
3125 3124 # list of new nodes created by ongoing graft
3126 3125 statedata[b'newnodes'] = []
3127 3126
3128 3127 cmdutil.resolve_commit_options(ui, opts)
3129 3128
3130 3129 editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
3131 3130
3132 3131 cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
3133 3132
3134 3133 cont = False
3135 3134 if opts.get('no_commit'):
3136 3135 cmdutil.check_incompatible_arguments(
3137 3136 opts,
3138 3137 'no_commit',
3139 3138 ['edit', 'currentuser', 'currentdate', 'log'],
3140 3139 )
3141 3140
3142 3141 graftstate = statemod.cmdstate(repo, b'graftstate')
3143 3142
3144 3143 if opts.get('stop'):
3145 3144 cmdutil.check_incompatible_arguments(
3146 3145 opts,
3147 3146 'stop',
3148 3147 [
3149 3148 'edit',
3150 3149 'log',
3151 3150 'user',
3152 3151 'date',
3153 3152 'currentdate',
3154 3153 'currentuser',
3155 3154 'rev',
3156 3155 ],
3157 3156 )
3158 3157 return _stopgraft(ui, repo, graftstate)
3159 3158 elif opts.get('abort'):
3160 3159 cmdutil.check_incompatible_arguments(
3161 3160 opts,
3162 3161 'abort',
3163 3162 [
3164 3163 'edit',
3165 3164 'log',
3166 3165 'user',
3167 3166 'date',
3168 3167 'currentdate',
3169 3168 'currentuser',
3170 3169 'rev',
3171 3170 ],
3172 3171 )
3173 3172 return cmdutil.abortgraft(ui, repo, graftstate)
3174 3173 elif opts.get('continue'):
3175 3174 cont = True
3176 3175 if revs:
3177 3176 raise error.InputError(_(b"can't specify --continue and revisions"))
3178 3177 # read in unfinished revisions
3179 3178 if graftstate.exists():
3180 3179 statedata = cmdutil.readgraftstate(repo, graftstate)
3181 3180 if statedata.get(b'date'):
3182 3181 opts['date'] = statedata[b'date']
3183 3182 if statedata.get(b'user'):
3184 3183 opts['user'] = statedata[b'user']
3185 3184 if statedata.get(b'log'):
3186 3185 opts['log'] = True
3187 3186 if statedata.get(b'no_commit'):
3188 3187 opts['no_commit'] = statedata.get(b'no_commit')
3189 3188 if statedata.get(b'base'):
3190 3189 opts['base'] = statedata.get(b'base')
3191 3190 nodes = statedata[b'nodes']
3192 3191 revs = [repo[node].rev() for node in nodes]
3193 3192 else:
3194 3193 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3195 3194 else:
3196 3195 if not revs:
3197 3196 raise error.InputError(_(b'no revisions specified'))
3198 3197 cmdutil.checkunfinished(repo)
3199 3198 cmdutil.bailifchanged(repo)
3200 3199 revs = logcmdutil.revrange(repo, revs)
3201 3200
3202 3201 skipped = set()
3203 3202 basectx = None
3204 3203 if opts.get('base'):
3205 3204 basectx = logcmdutil.revsingle(repo, opts['base'], None)
3206 3205 if basectx is None:
3207 3206 # check for merges
3208 3207 for rev in repo.revs(b'%ld and merge()', revs):
3209 3208 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3210 3209 skipped.add(rev)
3211 3210 revs = [r for r in revs if r not in skipped]
3212 3211 if not revs:
3213 3212 return -1
3214 3213 if basectx is not None and len(revs) != 1:
3215 3214 raise error.InputError(_(b'only one revision allowed with --base '))
3216 3215
3217 3216 # Don't check in the --continue case, in effect retaining --force across
3218 3217 # --continues. That's because without --force, any revisions we decided to
3219 3218 # skip would have been filtered out here, so they wouldn't have made their
3220 3219 # way to the graftstate. With --force, any revisions we would have otherwise
3221 3220 # skipped would not have been filtered out, and if they hadn't been applied
3222 3221 # already, they'd have been in the graftstate.
3223 3222 if not (cont or opts.get('force')) and basectx is None:
3224 3223 # check for ancestors of dest branch
3225 3224 ancestors = repo.revs(b'%ld & (::.)', revs)
3226 3225 for rev in ancestors:
3227 3226 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3228 3227
3229 3228 revs = [r for r in revs if r not in ancestors]
3230 3229
3231 3230 if not revs:
3232 3231 return -1
3233 3232
3234 3233 # analyze revs for earlier grafts
3235 3234 ids = {}
3236 3235 for ctx in repo.set(b"%ld", revs):
3237 3236 ids[ctx.hex()] = ctx.rev()
3238 3237 n = ctx.extra().get(b'source')
3239 3238 if n:
3240 3239 ids[n] = ctx.rev()
3241 3240
3242 3241 # check ancestors for earlier grafts
3243 3242 ui.debug(b'scanning for duplicate grafts\n')
3244 3243
3245 3244 # The only changesets we can be sure doesn't contain grafts of any
3246 3245 # revs, are the ones that are common ancestors of *all* revs:
3247 3246 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3248 3247 ctx = repo[rev]
3249 3248 n = ctx.extra().get(b'source')
3250 3249 if n in ids:
3251 3250 try:
3252 3251 r = repo[n].rev()
3253 3252 except error.RepoLookupError:
3254 3253 r = None
3255 3254 if r in revs:
3256 3255 ui.warn(
3257 3256 _(
3258 3257 b'skipping revision %d:%s '
3259 3258 b'(already grafted to %d:%s)\n'
3260 3259 )
3261 3260 % (r, repo[r], rev, ctx)
3262 3261 )
3263 3262 revs.remove(r)
3264 3263 elif ids[n] in revs:
3265 3264 if r is None:
3266 3265 ui.warn(
3267 3266 _(
3268 3267 b'skipping already grafted revision %d:%s '
3269 3268 b'(%d:%s also has unknown origin %s)\n'
3270 3269 )
3271 3270 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3272 3271 )
3273 3272 else:
3274 3273 ui.warn(
3275 3274 _(
3276 3275 b'skipping already grafted revision %d:%s '
3277 3276 b'(%d:%s also has origin %d:%s)\n'
3278 3277 )
3279 3278 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3280 3279 )
3281 3280 revs.remove(ids[n])
3282 3281 elif ctx.hex() in ids:
3283 3282 r = ids[ctx.hex()]
3284 3283 if r in revs:
3285 3284 ui.warn(
3286 3285 _(
3287 3286 b'skipping already grafted revision %d:%s '
3288 3287 b'(was grafted from %d:%s)\n'
3289 3288 )
3290 3289 % (r, repo[r], rev, ctx)
3291 3290 )
3292 3291 revs.remove(r)
3293 3292 if not revs:
3294 3293 return -1
3295 3294
3296 3295 if opts.get('no_commit'):
3297 3296 statedata[b'no_commit'] = True
3298 3297 if opts.get('base'):
3299 3298 statedata[b'base'] = opts['base']
3300 3299 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3301 3300 desc = b'%d:%s "%s"' % (
3302 3301 ctx.rev(),
3303 3302 ctx,
3304 3303 ctx.description().split(b'\n', 1)[0],
3305 3304 )
3306 3305 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3307 3306 if names:
3308 3307 desc += b' (%s)' % b' '.join(names)
3309 3308 ui.status(_(b'grafting %s\n') % desc)
3310 3309 if opts.get('dry_run'):
3311 3310 continue
3312 3311
3313 3312 source = ctx.extra().get(b'source')
3314 3313 extra = {}
3315 3314 if source:
3316 3315 extra[b'source'] = source
3317 3316 extra[b'intermediate-source'] = ctx.hex()
3318 3317 else:
3319 3318 extra[b'source'] = ctx.hex()
3320 3319 user = ctx.user()
3321 3320 if opts.get('user'):
3322 3321 user = opts['user']
3323 3322 statedata[b'user'] = user
3324 3323 date = ctx.date()
3325 3324 if opts.get('date'):
3326 3325 date = opts['date']
3327 3326 statedata[b'date'] = date
3328 3327 message = ctx.description()
3329 3328 if opts.get('log'):
3330 3329 message += b'\n(grafted from %s)' % ctx.hex()
3331 3330 statedata[b'log'] = True
3332 3331
3333 3332 # we don't merge the first commit when continuing
3334 3333 if not cont:
3335 3334 # perform the graft merge with p1(rev) as 'ancestor'
3336 3335 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
3337 3336 base = ctx.p1() if basectx is None else basectx
3338 3337 with ui.configoverride(overrides, b'graft'):
3339 3338 stats = mergemod.graft(
3340 3339 repo, ctx, base, [b'local', b'graft', b'parent of graft']
3341 3340 )
3342 3341 # report any conflicts
3343 3342 if stats.unresolvedcount > 0:
3344 3343 # write out state for --continue
3345 3344 nodes = [repo[rev].hex() for rev in revs[pos:]]
3346 3345 statedata[b'nodes'] = nodes
3347 3346 stateversion = 1
3348 3347 graftstate.save(stateversion, statedata)
3349 3348 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3350 3349 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3351 3350 return 1
3352 3351 else:
3353 3352 cont = False
3354 3353
3355 3354 # commit if --no-commit is false
3356 3355 if not opts.get('no_commit'):
3357 3356 node = repo.commit(
3358 3357 text=message, user=user, date=date, extra=extra, editor=editor
3359 3358 )
3360 3359 if node is None:
3361 3360 ui.warn(
3362 3361 _(b'note: graft of %d:%s created no changes to commit\n')
3363 3362 % (ctx.rev(), ctx)
3364 3363 )
3365 3364 # checking that newnodes exist because old state files won't have it
3366 3365 elif statedata.get(b'newnodes') is not None:
3367 3366 nn = statedata[b'newnodes']
3368 3367 assert isinstance(nn, list) # list of bytes
3369 3368 nn.append(node)
3370 3369
3371 3370 # remove state when we complete successfully
3372 3371 if not opts.get('dry_run'):
3373 3372 graftstate.delete()
3374 3373
3375 3374 return 0
3376 3375
3377 3376
3378 3377 def _stopgraft(ui, repo, graftstate):
3379 3378 """stop the interrupted graft"""
3380 3379 if not graftstate.exists():
3381 3380 raise error.StateError(_(b"no interrupted graft found"))
3382 3381 pctx = repo[b'.']
3383 3382 mergemod.clean_update(pctx)
3384 3383 graftstate.delete()
3385 3384 ui.status(_(b"stopped the interrupted graft\n"))
3386 3385 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3387 3386 return 0
3388 3387
3389 3388
3390 3389 statemod.addunfinished(
3391 3390 b'graft',
3392 3391 fname=b'graftstate',
3393 3392 clearable=True,
3394 3393 stopflag=True,
3395 3394 continueflag=True,
3396 3395 abortfunc=cmdutil.hgabortgraft,
3397 3396 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3398 3397 )
3399 3398
3400 3399
3401 3400 @command(
3402 3401 b'grep',
3403 3402 [
3404 3403 (b'0', b'print0', None, _(b'end fields with NUL')),
3405 3404 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3406 3405 (
3407 3406 b'',
3408 3407 b'diff',
3409 3408 None,
3410 3409 _(
3411 3410 b'search revision differences for when the pattern was added '
3412 3411 b'or removed'
3413 3412 ),
3414 3413 ),
3415 3414 (b'a', b'text', None, _(b'treat all files as text')),
3416 3415 (
3417 3416 b'f',
3418 3417 b'follow',
3419 3418 None,
3420 3419 _(
3421 3420 b'follow changeset history,'
3422 3421 b' or file history across copies and renames'
3423 3422 ),
3424 3423 ),
3425 3424 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3426 3425 (
3427 3426 b'l',
3428 3427 b'files-with-matches',
3429 3428 None,
3430 3429 _(b'print only filenames and revisions that match'),
3431 3430 ),
3432 3431 (b'n', b'line-number', None, _(b'print matching line numbers')),
3433 3432 (
3434 3433 b'r',
3435 3434 b'rev',
3436 3435 [],
3437 3436 _(b'search files changed within revision range'),
3438 3437 _(b'REV'),
3439 3438 ),
3440 3439 (
3441 3440 b'',
3442 3441 b'all-files',
3443 3442 None,
3444 3443 _(
3445 3444 b'include all files in the changeset while grepping (DEPRECATED)'
3446 3445 ),
3447 3446 ),
3448 3447 (b'u', b'user', None, _(b'list the author (long with -v)')),
3449 3448 (b'd', b'date', None, _(b'list the date (short with -q)')),
3450 3449 ]
3451 3450 + formatteropts
3452 3451 + walkopts,
3453 3452 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3454 3453 helpcategory=command.CATEGORY_FILE_CONTENTS,
3455 3454 inferrepo=True,
3456 3455 intents={INTENT_READONLY},
3457 3456 )
3458 3457 def grep(ui, repo, pattern, *pats, **opts):
3459 3458 """search for a pattern in specified files
3460 3459
3461 3460 Search the working directory or revision history for a regular
3462 3461 expression in the specified files for the entire repository.
3463 3462
3464 3463 By default, grep searches the repository files in the working
3465 3464 directory and prints the files where it finds a match. To specify
3466 3465 historical revisions instead of the working directory, use the
3467 3466 --rev flag.
3468 3467
3469 3468 To search instead historical revision differences that contains a
3470 3469 change in match status ("-" for a match that becomes a non-match,
3471 3470 or "+" for a non-match that becomes a match), use the --diff flag.
3472 3471
3473 3472 PATTERN can be any Python (roughly Perl-compatible) regular
3474 3473 expression.
3475 3474
3476 3475 If no FILEs are specified and the --rev flag isn't supplied, all
3477 3476 files in the working directory are searched. When using the --rev
3478 3477 flag and specifying FILEs, use the --follow argument to also
3479 3478 follow the specified FILEs across renames and copies.
3480 3479
3481 3480 .. container:: verbose
3482 3481
3483 3482 Template:
3484 3483
3485 3484 The following keywords are supported in addition to the common template
3486 3485 keywords and functions. See also :hg:`help templates`.
3487 3486
3488 3487 :change: String. Character denoting insertion ``+`` or removal ``-``.
3489 3488 Available if ``--diff`` is specified.
3490 3489 :lineno: Integer. Line number of the match.
3491 3490 :path: String. Repository-absolute path of the file.
3492 3491 :texts: List of text chunks.
3493 3492
3494 3493 And each entry of ``{texts}`` provides the following sub-keywords.
3495 3494
3496 3495 :matched: Boolean. True if the chunk matches the specified pattern.
3497 3496 :text: String. Chunk content.
3498 3497
3499 3498 See :hg:`help templates.operators` for the list expansion syntax.
3500 3499
3501 3500 Returns 0 if a match is found, 1 otherwise.
3502 3501
3503 3502 """
3504 3503 cmdutil.check_incompatible_arguments(opts, 'all_files', ['all', 'diff'])
3505 3504 opts = pycompat.byteskwargs(opts)
3506 3505 diff = opts.get(b'all') or opts.get(b'diff')
3507 3506 follow = opts.get(b'follow')
3508 3507 if opts.get(b'all_files') is None and not diff:
3509 3508 opts[b'all_files'] = True
3510 3509 plaingrep = (
3511 3510 opts.get(b'all_files')
3512 3511 and not opts.get(b'rev')
3513 3512 and not opts.get(b'follow')
3514 3513 )
3515 3514 all_files = opts.get(b'all_files')
3516 3515 if plaingrep:
3517 3516 opts[b'rev'] = [b'wdir()']
3518 3517
3519 3518 reflags = re.M
3520 3519 if opts.get(b'ignore_case'):
3521 3520 reflags |= re.I
3522 3521 try:
3523 3522 regexp = util.re.compile(pattern, reflags)
3524 3523 except re.error as inst:
3525 3524 ui.warn(
3526 3525 _(b"grep: invalid match pattern: %s\n")
3527 3526 % stringutil.forcebytestr(inst)
3528 3527 )
3529 3528 return 1
3530 3529 sep, eol = b':', b'\n'
3531 3530 if opts.get(b'print0'):
3532 3531 sep = eol = b'\0'
3533 3532
3534 3533 searcher = grepmod.grepsearcher(
3535 3534 ui, repo, regexp, all_files=all_files, diff=diff, follow=follow
3536 3535 )
3537 3536
3538 3537 getfile = searcher._getfile
3539 3538
3540 3539 uipathfn = scmutil.getuipathfn(repo)
3541 3540
3542 3541 def display(fm, fn, ctx, pstates, states):
3543 3542 rev = scmutil.intrev(ctx)
3544 3543 if fm.isplain():
3545 3544 formatuser = ui.shortuser
3546 3545 else:
3547 3546 formatuser = pycompat.bytestr
3548 3547 if ui.quiet:
3549 3548 datefmt = b'%Y-%m-%d'
3550 3549 else:
3551 3550 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3552 3551 found = False
3553 3552
3554 3553 @util.cachefunc
3555 3554 def binary():
3556 3555 flog = getfile(fn)
3557 3556 try:
3558 3557 return stringutil.binary(flog.read(ctx.filenode(fn)))
3559 3558 except error.WdirUnsupported:
3560 3559 return ctx[fn].isbinary()
3561 3560
3562 3561 fieldnamemap = {b'linenumber': b'lineno'}
3563 3562 if diff:
3564 3563 iter = grepmod.difflinestates(pstates, states)
3565 3564 else:
3566 3565 iter = [(b'', l) for l in states]
3567 3566 for change, l in iter:
3568 3567 fm.startitem()
3569 3568 fm.context(ctx=ctx)
3570 3569 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3571 3570 fm.plain(uipathfn(fn), label=b'grep.filename')
3572 3571
3573 3572 cols = [
3574 3573 (b'rev', b'%d', rev, not plaingrep, b''),
3575 3574 (
3576 3575 b'linenumber',
3577 3576 b'%d',
3578 3577 l.linenum,
3579 3578 opts.get(b'line_number'),
3580 3579 b'',
3581 3580 ),
3582 3581 ]
3583 3582 if diff:
3584 3583 cols.append(
3585 3584 (
3586 3585 b'change',
3587 3586 b'%s',
3588 3587 change,
3589 3588 True,
3590 3589 b'grep.inserted '
3591 3590 if change == b'+'
3592 3591 else b'grep.deleted ',
3593 3592 )
3594 3593 )
3595 3594 cols.extend(
3596 3595 [
3597 3596 (
3598 3597 b'user',
3599 3598 b'%s',
3600 3599 formatuser(ctx.user()),
3601 3600 opts.get(b'user'),
3602 3601 b'',
3603 3602 ),
3604 3603 (
3605 3604 b'date',
3606 3605 b'%s',
3607 3606 fm.formatdate(ctx.date(), datefmt),
3608 3607 opts.get(b'date'),
3609 3608 b'',
3610 3609 ),
3611 3610 ]
3612 3611 )
3613 3612 for name, fmt, data, cond, extra_label in cols:
3614 3613 if cond:
3615 3614 fm.plain(sep, label=b'grep.sep')
3616 3615 field = fieldnamemap.get(name, name)
3617 3616 label = extra_label + (b'grep.%s' % name)
3618 3617 fm.condwrite(cond, field, fmt, data, label=label)
3619 3618 if not opts.get(b'files_with_matches'):
3620 3619 fm.plain(sep, label=b'grep.sep')
3621 3620 if not opts.get(b'text') and binary():
3622 3621 fm.plain(_(b" Binary file matches"))
3623 3622 else:
3624 3623 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3625 3624 fm.plain(eol)
3626 3625 found = True
3627 3626 if opts.get(b'files_with_matches'):
3628 3627 break
3629 3628 return found
3630 3629
3631 3630 def displaymatches(fm, l):
3632 3631 p = 0
3633 3632 for s, e in l.findpos(regexp):
3634 3633 if p < s:
3635 3634 fm.startitem()
3636 3635 fm.write(b'text', b'%s', l.line[p:s])
3637 3636 fm.data(matched=False)
3638 3637 fm.startitem()
3639 3638 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3640 3639 fm.data(matched=True)
3641 3640 p = e
3642 3641 if p < len(l.line):
3643 3642 fm.startitem()
3644 3643 fm.write(b'text', b'%s', l.line[p:])
3645 3644 fm.data(matched=False)
3646 3645 fm.end()
3647 3646
3648 3647 found = False
3649 3648
3650 3649 wopts = logcmdutil.walkopts(
3651 3650 pats=pats,
3652 3651 opts=opts,
3653 3652 revspec=opts[b'rev'],
3654 3653 include_pats=opts[b'include'],
3655 3654 exclude_pats=opts[b'exclude'],
3656 3655 follow=follow,
3657 3656 force_changelog_traversal=all_files,
3658 3657 filter_revisions_by_pats=not all_files,
3659 3658 )
3660 3659 revs, makefilematcher = logcmdutil.makewalker(repo, wopts)
3661 3660
3662 3661 ui.pager(b'grep')
3663 3662 fm = ui.formatter(b'grep', opts)
3664 3663 for fn, ctx, pstates, states in searcher.searchfiles(revs, makefilematcher):
3665 3664 r = display(fm, fn, ctx, pstates, states)
3666 3665 found = found or r
3667 3666 if r and not diff and not all_files:
3668 3667 searcher.skipfile(fn, ctx.rev())
3669 3668 fm.end()
3670 3669
3671 3670 return not found
3672 3671
3673 3672
3674 3673 @command(
3675 3674 b'heads',
3676 3675 [
3677 3676 (
3678 3677 b'r',
3679 3678 b'rev',
3680 3679 b'',
3681 3680 _(b'show only heads which are descendants of STARTREV'),
3682 3681 _(b'STARTREV'),
3683 3682 ),
3684 3683 (b't', b'topo', False, _(b'show topological heads only')),
3685 3684 (
3686 3685 b'a',
3687 3686 b'active',
3688 3687 False,
3689 3688 _(b'show active branchheads only (DEPRECATED)'),
3690 3689 ),
3691 3690 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3692 3691 ]
3693 3692 + templateopts,
3694 3693 _(b'[-ct] [-r STARTREV] [REV]...'),
3695 3694 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3696 3695 intents={INTENT_READONLY},
3697 3696 )
3698 3697 def heads(ui, repo, *branchrevs, **opts):
3699 3698 """show branch heads
3700 3699
3701 3700 With no arguments, show all open branch heads in the repository.
3702 3701 Branch heads are changesets that have no descendants on the
3703 3702 same branch. They are where development generally takes place and
3704 3703 are the usual targets for update and merge operations.
3705 3704
3706 3705 If one or more REVs are given, only open branch heads on the
3707 3706 branches associated with the specified changesets are shown. This
3708 3707 means that you can use :hg:`heads .` to see the heads on the
3709 3708 currently checked-out branch.
3710 3709
3711 3710 If -c/--closed is specified, also show branch heads marked closed
3712 3711 (see :hg:`commit --close-branch`).
3713 3712
3714 3713 If STARTREV is specified, only those heads that are descendants of
3715 3714 STARTREV will be displayed.
3716 3715
3717 3716 If -t/--topo is specified, named branch mechanics will be ignored and only
3718 3717 topological heads (changesets with no children) will be shown.
3719 3718
3720 3719 Returns 0 if matching heads are found, 1 if not.
3721 3720 """
3722 3721
3723 3722 opts = pycompat.byteskwargs(opts)
3724 3723 start = None
3725 3724 rev = opts.get(b'rev')
3726 3725 if rev:
3727 3726 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3728 3727 start = logcmdutil.revsingle(repo, rev, None).node()
3729 3728
3730 3729 if opts.get(b'topo'):
3731 3730 heads = [repo[h] for h in repo.heads(start)]
3732 3731 else:
3733 3732 heads = []
3734 3733 for branch in repo.branchmap():
3735 3734 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3736 3735 heads = [repo[h] for h in heads]
3737 3736
3738 3737 if branchrevs:
3739 3738 branches = {
3740 3739 repo[r].branch() for r in logcmdutil.revrange(repo, branchrevs)
3741 3740 }
3742 3741 heads = [h for h in heads if h.branch() in branches]
3743 3742
3744 3743 if opts.get(b'active') and branchrevs:
3745 3744 dagheads = repo.heads(start)
3746 3745 heads = [h for h in heads if h.node() in dagheads]
3747 3746
3748 3747 if branchrevs:
3749 3748 haveheads = {h.branch() for h in heads}
3750 3749 if branches - haveheads:
3751 3750 headless = b', '.join(b for b in branches - haveheads)
3752 3751 msg = _(b'no open branch heads found on branches %s')
3753 3752 if opts.get(b'rev'):
3754 3753 msg += _(b' (started at %s)') % opts[b'rev']
3755 3754 ui.warn((msg + b'\n') % headless)
3756 3755
3757 3756 if not heads:
3758 3757 return 1
3759 3758
3760 3759 ui.pager(b'heads')
3761 3760 heads = sorted(heads, key=lambda x: -(x.rev()))
3762 3761 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3763 3762 for ctx in heads:
3764 3763 displayer.show(ctx)
3765 3764 displayer.close()
3766 3765
3767 3766
3768 3767 @command(
3769 3768 b'help',
3770 3769 [
3771 3770 (b'e', b'extension', None, _(b'show only help for extensions')),
3772 3771 (b'c', b'command', None, _(b'show only help for commands')),
3773 3772 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3774 3773 (
3775 3774 b's',
3776 3775 b'system',
3777 3776 [],
3778 3777 _(b'show help for specific platform(s)'),
3779 3778 _(b'PLATFORM'),
3780 3779 ),
3781 3780 ],
3782 3781 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3783 3782 helpcategory=command.CATEGORY_HELP,
3784 3783 norepo=True,
3785 3784 intents={INTENT_READONLY},
3786 3785 )
3787 3786 def help_(ui, name=None, **opts):
3788 3787 """show help for a given topic or a help overview
3789 3788
3790 3789 With no arguments, print a list of commands with short help messages.
3791 3790
3792 3791 Given a topic, extension, or command name, print help for that
3793 3792 topic.
3794 3793
3795 3794 Returns 0 if successful.
3796 3795 """
3797 3796
3798 3797 keep = opts.get('system') or []
3799 3798 if len(keep) == 0:
3800 3799 if pycompat.sysplatform.startswith(b'win'):
3801 3800 keep.append(b'windows')
3802 3801 elif pycompat.sysplatform == b'OpenVMS':
3803 3802 keep.append(b'vms')
3804 3803 elif pycompat.sysplatform == b'plan9':
3805 3804 keep.append(b'plan9')
3806 3805 else:
3807 3806 keep.append(b'unix')
3808 3807 keep.append(pycompat.sysplatform.lower())
3809 3808 if ui.verbose:
3810 3809 keep.append(b'verbose')
3811 3810
3812 3811 commands = sys.modules[__name__]
3813 3812 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3814 3813 ui.pager(b'help')
3815 3814 ui.write(formatted)
3816 3815
3817 3816
3818 3817 @command(
3819 3818 b'identify|id',
3820 3819 [
3821 3820 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3822 3821 (b'n', b'num', None, _(b'show local revision number')),
3823 3822 (b'i', b'id', None, _(b'show global revision id')),
3824 3823 (b'b', b'branch', None, _(b'show branch')),
3825 3824 (b't', b'tags', None, _(b'show tags')),
3826 3825 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3827 3826 ]
3828 3827 + remoteopts
3829 3828 + formatteropts,
3830 3829 _(b'[-nibtB] [-r REV] [SOURCE]'),
3831 3830 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3832 3831 optionalrepo=True,
3833 3832 intents={INTENT_READONLY},
3834 3833 )
3835 3834 def identify(
3836 3835 ui,
3837 3836 repo,
3838 3837 source=None,
3839 3838 rev=None,
3840 3839 num=None,
3841 3840 id=None,
3842 3841 branch=None,
3843 3842 tags=None,
3844 3843 bookmarks=None,
3845 3844 **opts
3846 3845 ):
3847 3846 """identify the working directory or specified revision
3848 3847
3849 3848 Print a summary identifying the repository state at REV using one or
3850 3849 two parent hash identifiers, followed by a "+" if the working
3851 3850 directory has uncommitted changes, the branch name (if not default),
3852 3851 a list of tags, and a list of bookmarks.
3853 3852
3854 3853 When REV is not given, print a summary of the current state of the
3855 3854 repository including the working directory. Specify -r. to get information
3856 3855 of the working directory parent without scanning uncommitted changes.
3857 3856
3858 3857 Specifying a path to a repository root or Mercurial bundle will
3859 3858 cause lookup to operate on that repository/bundle.
3860 3859
3861 3860 .. container:: verbose
3862 3861
3863 3862 Template:
3864 3863
3865 3864 The following keywords are supported in addition to the common template
3866 3865 keywords and functions. See also :hg:`help templates`.
3867 3866
3868 3867 :dirty: String. Character ``+`` denoting if the working directory has
3869 3868 uncommitted changes.
3870 3869 :id: String. One or two nodes, optionally followed by ``+``.
3871 3870 :parents: List of strings. Parent nodes of the changeset.
3872 3871
3873 3872 Examples:
3874 3873
3875 3874 - generate a build identifier for the working directory::
3876 3875
3877 3876 hg id --id > build-id.dat
3878 3877
3879 3878 - find the revision corresponding to a tag::
3880 3879
3881 3880 hg id -n -r 1.3
3882 3881
3883 3882 - check the most recent revision of a remote repository::
3884 3883
3885 3884 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3886 3885
3887 3886 See :hg:`log` for generating more information about specific revisions,
3888 3887 including full hash identifiers.
3889 3888
3890 3889 Returns 0 if successful.
3891 3890 """
3892 3891
3893 3892 opts = pycompat.byteskwargs(opts)
3894 3893 if not repo and not source:
3895 3894 raise error.InputError(
3896 3895 _(b"there is no Mercurial repository here (.hg not found)")
3897 3896 )
3898 3897
3899 3898 default = not (num or id or branch or tags or bookmarks)
3900 3899 output = []
3901 3900 revs = []
3902 3901
3903 3902 peer = None
3904 3903 try:
3905 3904 if source:
3906 3905 source, branches = urlutil.get_unique_pull_path(
3907 3906 b'identify', repo, ui, source
3908 3907 )
3909 3908 # only pass ui when no repo
3910 3909 peer = hg.peer(repo or ui, opts, source)
3911 3910 repo = peer.local()
3912 3911 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3913 3912
3914 3913 fm = ui.formatter(b'identify', opts)
3915 3914 fm.startitem()
3916 3915
3917 3916 if not repo:
3918 3917 if num or branch or tags:
3919 3918 raise error.InputError(
3920 3919 _(b"can't query remote revision number, branch, or tags")
3921 3920 )
3922 3921 if not rev and revs:
3923 3922 rev = revs[0]
3924 3923 if not rev:
3925 3924 rev = b"tip"
3926 3925
3927 3926 remoterev = peer.lookup(rev)
3928 3927 hexrev = fm.hexfunc(remoterev)
3929 3928 if default or id:
3930 3929 output = [hexrev]
3931 3930 fm.data(id=hexrev)
3932 3931
3933 3932 @util.cachefunc
3934 3933 def getbms():
3935 3934 bms = []
3936 3935
3937 3936 if b'bookmarks' in peer.listkeys(b'namespaces'):
3938 3937 hexremoterev = hex(remoterev)
3939 3938 bms = [
3940 3939 bm
3941 3940 for bm, bmr in peer.listkeys(b'bookmarks').items()
3942 3941 if bmr == hexremoterev
3943 3942 ]
3944 3943
3945 3944 return sorted(bms)
3946 3945
3947 3946 if fm.isplain():
3948 3947 if bookmarks:
3949 3948 output.extend(getbms())
3950 3949 elif default and not ui.quiet:
3951 3950 # multiple bookmarks for a single parent separated by '/'
3952 3951 bm = b'/'.join(getbms())
3953 3952 if bm:
3954 3953 output.append(bm)
3955 3954 else:
3956 3955 fm.data(node=hex(remoterev))
3957 3956 if bookmarks or b'bookmarks' in fm.datahint():
3958 3957 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3959 3958 else:
3960 3959 if rev:
3961 3960 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3962 3961 ctx = logcmdutil.revsingle(repo, rev, None)
3963 3962
3964 3963 if ctx.rev() is None:
3965 3964 ctx = repo[None]
3966 3965 parents = ctx.parents()
3967 3966 taglist = []
3968 3967 for p in parents:
3969 3968 taglist.extend(p.tags())
3970 3969
3971 3970 dirty = b""
3972 3971 if ctx.dirty(missing=True, merge=False, branch=False):
3973 3972 dirty = b'+'
3974 3973 fm.data(dirty=dirty)
3975 3974
3976 3975 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3977 3976 if default or id:
3978 3977 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3979 3978 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3980 3979
3981 3980 if num:
3982 3981 numoutput = [b"%d" % p.rev() for p in parents]
3983 3982 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3984 3983
3985 3984 fm.data(
3986 3985 parents=fm.formatlist(
3987 3986 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3988 3987 )
3989 3988 )
3990 3989 else:
3991 3990 hexoutput = fm.hexfunc(ctx.node())
3992 3991 if default or id:
3993 3992 output = [hexoutput]
3994 3993 fm.data(id=hexoutput)
3995 3994
3996 3995 if num:
3997 3996 output.append(pycompat.bytestr(ctx.rev()))
3998 3997 taglist = ctx.tags()
3999 3998
4000 3999 if default and not ui.quiet:
4001 4000 b = ctx.branch()
4002 4001 if b != b'default':
4003 4002 output.append(b"(%s)" % b)
4004 4003
4005 4004 # multiple tags for a single parent separated by '/'
4006 4005 t = b'/'.join(taglist)
4007 4006 if t:
4008 4007 output.append(t)
4009 4008
4010 4009 # multiple bookmarks for a single parent separated by '/'
4011 4010 bm = b'/'.join(ctx.bookmarks())
4012 4011 if bm:
4013 4012 output.append(bm)
4014 4013 else:
4015 4014 if branch:
4016 4015 output.append(ctx.branch())
4017 4016
4018 4017 if tags:
4019 4018 output.extend(taglist)
4020 4019
4021 4020 if bookmarks:
4022 4021 output.extend(ctx.bookmarks())
4023 4022
4024 4023 fm.data(node=ctx.hex())
4025 4024 fm.data(branch=ctx.branch())
4026 4025 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4027 4026 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4028 4027 fm.context(ctx=ctx)
4029 4028
4030 4029 fm.plain(b"%s\n" % b' '.join(output))
4031 4030 fm.end()
4032 4031 finally:
4033 4032 if peer:
4034 4033 peer.close()
4035 4034
4036 4035
4037 4036 @command(
4038 4037 b'import|patch',
4039 4038 [
4040 4039 (
4041 4040 b'p',
4042 4041 b'strip',
4043 4042 1,
4044 4043 _(
4045 4044 b'directory strip option for patch. This has the same '
4046 4045 b'meaning as the corresponding patch option'
4047 4046 ),
4048 4047 _(b'NUM'),
4049 4048 ),
4050 4049 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4051 4050 (b'', b'secret', None, _(b'use the secret phase for committing')),
4052 4051 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4053 4052 (
4054 4053 b'f',
4055 4054 b'force',
4056 4055 None,
4057 4056 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4058 4057 ),
4059 4058 (
4060 4059 b'',
4061 4060 b'no-commit',
4062 4061 None,
4063 4062 _(b"don't commit, just update the working directory"),
4064 4063 ),
4065 4064 (
4066 4065 b'',
4067 4066 b'bypass',
4068 4067 None,
4069 4068 _(b"apply patch without touching the working directory"),
4070 4069 ),
4071 4070 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4072 4071 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4073 4072 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4074 4073 (
4075 4074 b'',
4076 4075 b'import-branch',
4077 4076 None,
4078 4077 _(b'use any branch information in patch (implied by --exact)'),
4079 4078 ),
4080 4079 ]
4081 4080 + commitopts
4082 4081 + commitopts2
4083 4082 + similarityopts,
4084 4083 _(b'[OPTION]... PATCH...'),
4085 4084 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4086 4085 )
4087 4086 def import_(ui, repo, patch1=None, *patches, **opts):
4088 4087 """import an ordered set of patches
4089 4088
4090 4089 Import a list of patches and commit them individually (unless
4091 4090 --no-commit is specified).
4092 4091
4093 4092 To read a patch from standard input (stdin), use "-" as the patch
4094 4093 name. If a URL is specified, the patch will be downloaded from
4095 4094 there.
4096 4095
4097 4096 Import first applies changes to the working directory (unless
4098 4097 --bypass is specified), import will abort if there are outstanding
4099 4098 changes.
4100 4099
4101 4100 Use --bypass to apply and commit patches directly to the
4102 4101 repository, without affecting the working directory. Without
4103 4102 --exact, patches will be applied on top of the working directory
4104 4103 parent revision.
4105 4104
4106 4105 You can import a patch straight from a mail message. Even patches
4107 4106 as attachments work (to use the body part, it must have type
4108 4107 text/plain or text/x-patch). From and Subject headers of email
4109 4108 message are used as default committer and commit message. All
4110 4109 text/plain body parts before first diff are added to the commit
4111 4110 message.
4112 4111
4113 4112 If the imported patch was generated by :hg:`export`, user and
4114 4113 description from patch override values from message headers and
4115 4114 body. Values given on command line with -m/--message and -u/--user
4116 4115 override these.
4117 4116
4118 4117 If --exact is specified, import will set the working directory to
4119 4118 the parent of each patch before applying it, and will abort if the
4120 4119 resulting changeset has a different ID than the one recorded in
4121 4120 the patch. This will guard against various ways that portable
4122 4121 patch formats and mail systems might fail to transfer Mercurial
4123 4122 data or metadata. See :hg:`bundle` for lossless transmission.
4124 4123
4125 4124 Use --partial to ensure a changeset will be created from the patch
4126 4125 even if some hunks fail to apply. Hunks that fail to apply will be
4127 4126 written to a <target-file>.rej file. Conflicts can then be resolved
4128 4127 by hand before :hg:`commit --amend` is run to update the created
4129 4128 changeset. This flag exists to let people import patches that
4130 4129 partially apply without losing the associated metadata (author,
4131 4130 date, description, ...).
4132 4131
4133 4132 .. note::
4134 4133
4135 4134 When no hunks apply cleanly, :hg:`import --partial` will create
4136 4135 an empty changeset, importing only the patch metadata.
4137 4136
4138 4137 With -s/--similarity, hg will attempt to discover renames and
4139 4138 copies in the patch in the same way as :hg:`addremove`.
4140 4139
4141 4140 It is possible to use external patch programs to perform the patch
4142 4141 by setting the ``ui.patch`` configuration option. For the default
4143 4142 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4144 4143 See :hg:`help config` for more information about configuration
4145 4144 files and how to use these options.
4146 4145
4147 4146 See :hg:`help dates` for a list of formats valid for -d/--date.
4148 4147
4149 4148 .. container:: verbose
4150 4149
4151 4150 Examples:
4152 4151
4153 4152 - import a traditional patch from a website and detect renames::
4154 4153
4155 4154 hg import -s 80 http://example.com/bugfix.patch
4156 4155
4157 4156 - import a changeset from an hgweb server::
4158 4157
4159 4158 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4160 4159
4161 4160 - import all the patches in an Unix-style mbox::
4162 4161
4163 4162 hg import incoming-patches.mbox
4164 4163
4165 4164 - import patches from stdin::
4166 4165
4167 4166 hg import -
4168 4167
4169 4168 - attempt to exactly restore an exported changeset (not always
4170 4169 possible)::
4171 4170
4172 4171 hg import --exact proposed-fix.patch
4173 4172
4174 4173 - use an external tool to apply a patch which is too fuzzy for
4175 4174 the default internal tool.
4176 4175
4177 4176 hg import --config ui.patch="patch --merge" fuzzy.patch
4178 4177
4179 4178 - change the default fuzzing from 2 to a less strict 7
4180 4179
4181 4180 hg import --config ui.fuzz=7 fuzz.patch
4182 4181
4183 4182 Returns 0 on success, 1 on partial success (see --partial).
4184 4183 """
4185 4184
4186 4185 cmdutil.check_incompatible_arguments(
4187 4186 opts, 'no_commit', ['bypass', 'secret']
4188 4187 )
4189 4188 cmdutil.check_incompatible_arguments(opts, 'exact', ['edit', 'prefix'])
4190 4189 opts = pycompat.byteskwargs(opts)
4191 4190 if not patch1:
4192 4191 raise error.InputError(_(b'need at least one patch to import'))
4193 4192
4194 4193 patches = (patch1,) + patches
4195 4194
4196 4195 date = opts.get(b'date')
4197 4196 if date:
4198 4197 opts[b'date'] = dateutil.parsedate(date)
4199 4198
4200 4199 exact = opts.get(b'exact')
4201 4200 update = not opts.get(b'bypass')
4202 4201 try:
4203 4202 sim = float(opts.get(b'similarity') or 0)
4204 4203 except ValueError:
4205 4204 raise error.InputError(_(b'similarity must be a number'))
4206 4205 if sim < 0 or sim > 100:
4207 4206 raise error.InputError(_(b'similarity must be between 0 and 100'))
4208 4207 if sim and not update:
4209 4208 raise error.InputError(_(b'cannot use --similarity with --bypass'))
4210 4209
4211 4210 base = opts[b"base"]
4212 4211 msgs = []
4213 4212 ret = 0
4214 4213
4215 4214 with repo.wlock():
4216 4215 if update:
4217 4216 cmdutil.checkunfinished(repo)
4218 4217 if exact or not opts.get(b'force'):
4219 4218 cmdutil.bailifchanged(repo)
4220 4219
4221 4220 if not opts.get(b'no_commit'):
4222 4221 lock = repo.lock
4223 4222 tr = lambda: repo.transaction(b'import')
4224 4223 dsguard = util.nullcontextmanager
4225 4224 else:
4226 4225 lock = util.nullcontextmanager
4227 4226 tr = util.nullcontextmanager
4228 4227 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4229 4228 with lock(), tr(), dsguard():
4230 4229 parents = repo[None].parents()
4231 4230 for patchurl in patches:
4232 4231 if patchurl == b'-':
4233 4232 ui.status(_(b'applying patch from stdin\n'))
4234 4233 patchfile = ui.fin
4235 4234 patchurl = b'stdin' # for error message
4236 4235 else:
4237 4236 patchurl = os.path.join(base, patchurl)
4238 4237 ui.status(_(b'applying %s\n') % patchurl)
4239 4238 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4240 4239
4241 4240 haspatch = False
4242 4241 for hunk in patch.split(patchfile):
4243 4242 with patch.extract(ui, hunk) as patchdata:
4244 4243 msg, node, rej = cmdutil.tryimportone(
4245 4244 ui, repo, patchdata, parents, opts, msgs, hg.clean
4246 4245 )
4247 4246 if msg:
4248 4247 haspatch = True
4249 4248 ui.note(msg + b'\n')
4250 4249 if update or exact:
4251 4250 parents = repo[None].parents()
4252 4251 else:
4253 4252 parents = [repo[node]]
4254 4253 if rej:
4255 4254 ui.write_err(_(b"patch applied partially\n"))
4256 4255 ui.write_err(
4257 4256 _(
4258 4257 b"(fix the .rej files and run "
4259 4258 b"`hg commit --amend`)\n"
4260 4259 )
4261 4260 )
4262 4261 ret = 1
4263 4262 break
4264 4263
4265 4264 if not haspatch:
4266 4265 raise error.InputError(_(b'%s: no diffs found') % patchurl)
4267 4266
4268 4267 if msgs:
4269 4268 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4270 4269 return ret
4271 4270
4272 4271
4273 4272 @command(
4274 4273 b'incoming|in',
4275 4274 [
4276 4275 (
4277 4276 b'f',
4278 4277 b'force',
4279 4278 None,
4280 4279 _(b'run even if remote repository is unrelated'),
4281 4280 ),
4282 4281 (b'n', b'newest-first', None, _(b'show newest record first')),
4283 4282 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4284 4283 (
4285 4284 b'r',
4286 4285 b'rev',
4287 4286 [],
4288 4287 _(b'a remote changeset intended to be added'),
4289 4288 _(b'REV'),
4290 4289 ),
4291 4290 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4292 4291 (
4293 4292 b'b',
4294 4293 b'branch',
4295 4294 [],
4296 4295 _(b'a specific branch you would like to pull'),
4297 4296 _(b'BRANCH'),
4298 4297 ),
4299 4298 ]
4300 4299 + logopts
4301 4300 + remoteopts
4302 4301 + subrepoopts,
4303 4302 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4304 4303 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4305 4304 )
4306 4305 def incoming(ui, repo, source=b"default", **opts):
4307 4306 """show new changesets found in source
4308 4307
4309 4308 Show new changesets found in the specified path/URL or the default
4310 4309 pull location. These are the changesets that would have been pulled
4311 4310 by :hg:`pull` at the time you issued this command.
4312 4311
4313 4312 See pull for valid source format details.
4314 4313
4315 4314 .. container:: verbose
4316 4315
4317 4316 With -B/--bookmarks, the result of bookmark comparison between
4318 4317 local and remote repositories is displayed. With -v/--verbose,
4319 4318 status is also displayed for each bookmark like below::
4320 4319
4321 4320 BM1 01234567890a added
4322 4321 BM2 1234567890ab advanced
4323 4322 BM3 234567890abc diverged
4324 4323 BM4 34567890abcd changed
4325 4324
4326 4325 The action taken locally when pulling depends on the
4327 4326 status of each bookmark:
4328 4327
4329 4328 :``added``: pull will create it
4330 4329 :``advanced``: pull will update it
4331 4330 :``diverged``: pull will create a divergent bookmark
4332 4331 :``changed``: result depends on remote changesets
4333 4332
4334 4333 From the point of view of pulling behavior, bookmark
4335 4334 existing only in the remote repository are treated as ``added``,
4336 4335 even if it is in fact locally deleted.
4337 4336
4338 4337 .. container:: verbose
4339 4338
4340 4339 For remote repository, using --bundle avoids downloading the
4341 4340 changesets twice if the incoming is followed by a pull.
4342 4341
4343 4342 Examples:
4344 4343
4345 4344 - show incoming changes with patches and full description::
4346 4345
4347 4346 hg incoming -vp
4348 4347
4349 4348 - show incoming changes excluding merges, store a bundle::
4350 4349
4351 4350 hg in -vpM --bundle incoming.hg
4352 4351 hg pull incoming.hg
4353 4352
4354 4353 - briefly list changes inside a bundle::
4355 4354
4356 4355 hg in changes.hg -T "{desc|firstline}\\n"
4357 4356
4358 4357 Returns 0 if there are incoming changes, 1 otherwise.
4359 4358 """
4360 4359 opts = pycompat.byteskwargs(opts)
4361 4360 if opts.get(b'graph'):
4362 4361 logcmdutil.checkunsupportedgraphflags([], opts)
4363 4362
4364 4363 def display(other, chlist, displayer):
4365 4364 revdag = logcmdutil.graphrevs(other, chlist, opts)
4366 4365 logcmdutil.displaygraph(
4367 4366 ui, repo, revdag, displayer, graphmod.asciiedges
4368 4367 )
4369 4368
4370 4369 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4371 4370 return 0
4372 4371
4373 4372 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'bundle'])
4374 4373
4375 4374 if opts.get(b'bookmarks'):
4376 4375 srcs = urlutil.get_pull_paths(repo, ui, [source])
4377 4376 for path in srcs:
4378 4377 source, branches = urlutil.parseurl(
4379 4378 path.rawloc, opts.get(b'branch')
4380 4379 )
4381 4380 other = hg.peer(repo, opts, source)
4382 4381 try:
4383 4382 if b'bookmarks' not in other.listkeys(b'namespaces'):
4384 4383 ui.warn(_(b"remote doesn't support bookmarks\n"))
4385 4384 return 0
4386 4385 ui.pager(b'incoming')
4387 4386 ui.status(
4388 4387 _(b'comparing with %s\n') % urlutil.hidepassword(source)
4389 4388 )
4390 4389 return bookmarks.incoming(
4391 4390 ui, repo, other, mode=path.bookmarks_mode
4392 4391 )
4393 4392 finally:
4394 4393 other.close()
4395 4394
4396 4395 return hg.incoming(ui, repo, source, opts)
4397 4396
4398 4397
4399 4398 @command(
4400 4399 b'init',
4401 4400 remoteopts,
4402 4401 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4403 4402 helpcategory=command.CATEGORY_REPO_CREATION,
4404 4403 helpbasic=True,
4405 4404 norepo=True,
4406 4405 )
4407 4406 def init(ui, dest=b".", **opts):
4408 4407 """create a new repository in the given directory
4409 4408
4410 4409 Initialize a new repository in the given directory. If the given
4411 4410 directory does not exist, it will be created.
4412 4411
4413 4412 If no directory is given, the current directory is used.
4414 4413
4415 4414 It is possible to specify an ``ssh://`` URL as the destination.
4416 4415 See :hg:`help urls` for more information.
4417 4416
4418 4417 Returns 0 on success.
4419 4418 """
4420 4419 opts = pycompat.byteskwargs(opts)
4421 4420 path = urlutil.get_clone_path(ui, dest)[1]
4422 4421 peer = hg.peer(ui, opts, path, create=True)
4423 4422 peer.close()
4424 4423
4425 4424
4426 4425 @command(
4427 4426 b'locate',
4428 4427 [
4429 4428 (
4430 4429 b'r',
4431 4430 b'rev',
4432 4431 b'',
4433 4432 _(b'search the repository as it is in REV'),
4434 4433 _(b'REV'),
4435 4434 ),
4436 4435 (
4437 4436 b'0',
4438 4437 b'print0',
4439 4438 None,
4440 4439 _(b'end filenames with NUL, for use with xargs'),
4441 4440 ),
4442 4441 (
4443 4442 b'f',
4444 4443 b'fullpath',
4445 4444 None,
4446 4445 _(b'print complete paths from the filesystem root'),
4447 4446 ),
4448 4447 ]
4449 4448 + walkopts,
4450 4449 _(b'[OPTION]... [PATTERN]...'),
4451 4450 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4452 4451 )
4453 4452 def locate(ui, repo, *pats, **opts):
4454 4453 """locate files matching specific patterns (DEPRECATED)
4455 4454
4456 4455 Print files under Mercurial control in the working directory whose
4457 4456 names match the given patterns.
4458 4457
4459 4458 By default, this command searches all directories in the working
4460 4459 directory. To search just the current directory and its
4461 4460 subdirectories, use "--include .".
4462 4461
4463 4462 If no patterns are given to match, this command prints the names
4464 4463 of all files under Mercurial control in the working directory.
4465 4464
4466 4465 If you want to feed the output of this command into the "xargs"
4467 4466 command, use the -0 option to both this command and "xargs". This
4468 4467 will avoid the problem of "xargs" treating single filenames that
4469 4468 contain whitespace as multiple filenames.
4470 4469
4471 4470 See :hg:`help files` for a more versatile command.
4472 4471
4473 4472 Returns 0 if a match is found, 1 otherwise.
4474 4473 """
4475 4474 opts = pycompat.byteskwargs(opts)
4476 4475 if opts.get(b'print0'):
4477 4476 end = b'\0'
4478 4477 else:
4479 4478 end = b'\n'
4480 4479 ctx = logcmdutil.revsingle(repo, opts.get(b'rev'), None)
4481 4480
4482 4481 ret = 1
4483 4482 m = scmutil.match(
4484 4483 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4485 4484 )
4486 4485
4487 4486 ui.pager(b'locate')
4488 4487 if ctx.rev() is None:
4489 4488 # When run on the working copy, "locate" includes removed files, so
4490 4489 # we get the list of files from the dirstate.
4491 4490 filesgen = sorted(repo.dirstate.matches(m))
4492 4491 else:
4493 4492 filesgen = ctx.matches(m)
4494 4493 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4495 4494 for abs in filesgen:
4496 4495 if opts.get(b'fullpath'):
4497 4496 ui.write(repo.wjoin(abs), end)
4498 4497 else:
4499 4498 ui.write(uipathfn(abs), end)
4500 4499 ret = 0
4501 4500
4502 4501 return ret
4503 4502
4504 4503
4505 4504 @command(
4506 4505 b'log|history',
4507 4506 [
4508 4507 (
4509 4508 b'f',
4510 4509 b'follow',
4511 4510 None,
4512 4511 _(
4513 4512 b'follow changeset history, or file history across copies and renames'
4514 4513 ),
4515 4514 ),
4516 4515 (
4517 4516 b'',
4518 4517 b'follow-first',
4519 4518 None,
4520 4519 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4521 4520 ),
4522 4521 (
4523 4522 b'd',
4524 4523 b'date',
4525 4524 b'',
4526 4525 _(b'show revisions matching date spec'),
4527 4526 _(b'DATE'),
4528 4527 ),
4529 4528 (b'C', b'copies', None, _(b'show copied files')),
4530 4529 (
4531 4530 b'k',
4532 4531 b'keyword',
4533 4532 [],
4534 4533 _(b'do case-insensitive search for a given text'),
4535 4534 _(b'TEXT'),
4536 4535 ),
4537 4536 (
4538 4537 b'r',
4539 4538 b'rev',
4540 4539 [],
4541 4540 _(b'revisions to select or follow from'),
4542 4541 _(b'REV'),
4543 4542 ),
4544 4543 (
4545 4544 b'L',
4546 4545 b'line-range',
4547 4546 [],
4548 4547 _(b'follow line range of specified file (EXPERIMENTAL)'),
4549 4548 _(b'FILE,RANGE'),
4550 4549 ),
4551 4550 (
4552 4551 b'',
4553 4552 b'removed',
4554 4553 None,
4555 4554 _(b'include revisions where files were removed'),
4556 4555 ),
4557 4556 (
4558 4557 b'm',
4559 4558 b'only-merges',
4560 4559 None,
4561 4560 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4562 4561 ),
4563 4562 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4564 4563 (
4565 4564 b'',
4566 4565 b'only-branch',
4567 4566 [],
4568 4567 _(
4569 4568 b'show only changesets within the given named branch (DEPRECATED)'
4570 4569 ),
4571 4570 _(b'BRANCH'),
4572 4571 ),
4573 4572 (
4574 4573 b'b',
4575 4574 b'branch',
4576 4575 [],
4577 4576 _(b'show changesets within the given named branch'),
4578 4577 _(b'BRANCH'),
4579 4578 ),
4580 4579 (
4581 4580 b'B',
4582 4581 b'bookmark',
4583 4582 [],
4584 4583 _(b"show changesets within the given bookmark"),
4585 4584 _(b'BOOKMARK'),
4586 4585 ),
4587 4586 (
4588 4587 b'P',
4589 4588 b'prune',
4590 4589 [],
4591 4590 _(b'do not display revision or any of its ancestors'),
4592 4591 _(b'REV'),
4593 4592 ),
4594 4593 ]
4595 4594 + logopts
4596 4595 + walkopts,
4597 4596 _(b'[OPTION]... [FILE]'),
4598 4597 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4599 4598 helpbasic=True,
4600 4599 inferrepo=True,
4601 4600 intents={INTENT_READONLY},
4602 4601 )
4603 4602 def log(ui, repo, *pats, **opts):
4604 4603 """show revision history of entire repository or files
4605 4604
4606 4605 Print the revision history of the specified files or the entire
4607 4606 project.
4608 4607
4609 4608 If no revision range is specified, the default is ``tip:0`` unless
4610 4609 --follow is set.
4611 4610
4612 4611 File history is shown without following rename or copy history of
4613 4612 files. Use -f/--follow with a filename to follow history across
4614 4613 renames and copies. --follow without a filename will only show
4615 4614 ancestors of the starting revisions. The starting revisions can be
4616 4615 specified by -r/--rev, which default to the working directory parent.
4617 4616
4618 4617 By default this command prints revision number and changeset id,
4619 4618 tags, non-trivial parents, user, date and time, and a summary for
4620 4619 each commit. When the -v/--verbose switch is used, the list of
4621 4620 changed files and full commit message are shown.
4622 4621
4623 4622 With --graph the revisions are shown as an ASCII art DAG with the most
4624 4623 recent changeset at the top.
4625 4624 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4626 4625 involved in an unresolved merge conflict, '_' closes a branch,
4627 4626 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4628 4627 changeset from the lines below is a parent of the 'o' merge on the same
4629 4628 line.
4630 4629 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4631 4630 of a '|' indicates one or more revisions in a path are omitted.
4632 4631
4633 4632 .. container:: verbose
4634 4633
4635 4634 Use -L/--line-range FILE,M:N options to follow the history of lines
4636 4635 from M to N in FILE. With -p/--patch only diff hunks affecting
4637 4636 specified line range will be shown. This option requires --follow;
4638 4637 it can be specified multiple times. Currently, this option is not
4639 4638 compatible with --graph. This option is experimental.
4640 4639
4641 4640 .. note::
4642 4641
4643 4642 :hg:`log --patch` may generate unexpected diff output for merge
4644 4643 changesets, as it will only compare the merge changeset against
4645 4644 its first parent. Also, only files different from BOTH parents
4646 4645 will appear in files:.
4647 4646
4648 4647 .. note::
4649 4648
4650 4649 For performance reasons, :hg:`log FILE` may omit duplicate changes
4651 4650 made on branches and will not show removals or mode changes. To
4652 4651 see all such changes, use the --removed switch.
4653 4652
4654 4653 .. container:: verbose
4655 4654
4656 4655 .. note::
4657 4656
4658 4657 The history resulting from -L/--line-range options depends on diff
4659 4658 options; for instance if white-spaces are ignored, respective changes
4660 4659 with only white-spaces in specified line range will not be listed.
4661 4660
4662 4661 .. container:: verbose
4663 4662
4664 4663 Some examples:
4665 4664
4666 4665 - changesets with full descriptions and file lists::
4667 4666
4668 4667 hg log -v
4669 4668
4670 4669 - changesets ancestral to the working directory::
4671 4670
4672 4671 hg log -f
4673 4672
4674 4673 - last 10 commits on the current branch::
4675 4674
4676 4675 hg log -l 10 -b .
4677 4676
4678 4677 - changesets showing all modifications of a file, including removals::
4679 4678
4680 4679 hg log --removed file.c
4681 4680
4682 4681 - all changesets that touch a directory, with diffs, excluding merges::
4683 4682
4684 4683 hg log -Mp lib/
4685 4684
4686 4685 - all revision numbers that match a keyword::
4687 4686
4688 4687 hg log -k bug --template "{rev}\\n"
4689 4688
4690 4689 - the full hash identifier of the working directory parent::
4691 4690
4692 4691 hg log -r . --template "{node}\\n"
4693 4692
4694 4693 - list available log templates::
4695 4694
4696 4695 hg log -T list
4697 4696
4698 4697 - check if a given changeset is included in a tagged release::
4699 4698
4700 4699 hg log -r "a21ccf and ancestor(1.9)"
4701 4700
4702 4701 - find all changesets by some user in a date range::
4703 4702
4704 4703 hg log -k alice -d "may 2008 to jul 2008"
4705 4704
4706 4705 - summary of all changesets after the last tag::
4707 4706
4708 4707 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4709 4708
4710 4709 - changesets touching lines 13 to 23 for file.c::
4711 4710
4712 4711 hg log -L file.c,13:23
4713 4712
4714 4713 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4715 4714 main.c with patch::
4716 4715
4717 4716 hg log -L file.c,13:23 -L main.c,2:6 -p
4718 4717
4719 4718 See :hg:`help dates` for a list of formats valid for -d/--date.
4720 4719
4721 4720 See :hg:`help revisions` for more about specifying and ordering
4722 4721 revisions.
4723 4722
4724 4723 See :hg:`help templates` for more about pre-packaged styles and
4725 4724 specifying custom templates. The default template used by the log
4726 4725 command can be customized via the ``command-templates.log`` configuration
4727 4726 setting.
4728 4727
4729 4728 Returns 0 on success.
4730 4729
4731 4730 """
4732 4731 opts = pycompat.byteskwargs(opts)
4733 4732 linerange = opts.get(b'line_range')
4734 4733
4735 4734 if linerange and not opts.get(b'follow'):
4736 4735 raise error.InputError(_(b'--line-range requires --follow'))
4737 4736
4738 4737 if linerange and pats:
4739 4738 # TODO: take pats as patterns with no line-range filter
4740 4739 raise error.InputError(
4741 4740 _(b'FILE arguments are not compatible with --line-range option')
4742 4741 )
4743 4742
4744 4743 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4745 4744 walk_opts = logcmdutil.parseopts(ui, pats, opts)
4746 4745 revs, differ = logcmdutil.getrevs(repo, walk_opts)
4747 4746 if linerange:
4748 4747 # TODO: should follow file history from logcmdutil._initialrevs(),
4749 4748 # then filter the result by logcmdutil._makerevset() and --limit
4750 4749 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4751 4750
4752 4751 getcopies = None
4753 4752 if opts.get(b'copies'):
4754 4753 endrev = None
4755 4754 if revs:
4756 4755 endrev = revs.max() + 1
4757 4756 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4758 4757
4759 4758 ui.pager(b'log')
4760 4759 displayer = logcmdutil.changesetdisplayer(
4761 4760 ui, repo, opts, differ, buffered=True
4762 4761 )
4763 4762 if opts.get(b'graph'):
4764 4763 displayfn = logcmdutil.displaygraphrevs
4765 4764 else:
4766 4765 displayfn = logcmdutil.displayrevs
4767 4766 displayfn(ui, repo, revs, displayer, getcopies)
4768 4767
4769 4768
4770 4769 @command(
4771 4770 b'manifest',
4772 4771 [
4773 4772 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4774 4773 (b'', b'all', False, _(b"list files from all revisions")),
4775 4774 ]
4776 4775 + formatteropts,
4777 4776 _(b'[-r REV]'),
4778 4777 helpcategory=command.CATEGORY_MAINTENANCE,
4779 4778 intents={INTENT_READONLY},
4780 4779 )
4781 4780 def manifest(ui, repo, node=None, rev=None, **opts):
4782 4781 """output the current or given revision of the project manifest
4783 4782
4784 4783 Print a list of version controlled files for the given revision.
4785 4784 If no revision is given, the first parent of the working directory
4786 4785 is used, or the null revision if no revision is checked out.
4787 4786
4788 4787 With -v, print file permissions, symlink and executable bits.
4789 4788 With --debug, print file revision hashes.
4790 4789
4791 4790 If option --all is specified, the list of all files from all revisions
4792 4791 is printed. This includes deleted and renamed files.
4793 4792
4794 4793 Returns 0 on success.
4795 4794 """
4796 4795 opts = pycompat.byteskwargs(opts)
4797 4796 fm = ui.formatter(b'manifest', opts)
4798 4797
4799 4798 if opts.get(b'all'):
4800 4799 if rev or node:
4801 4800 raise error.InputError(_(b"can't specify a revision with --all"))
4802 4801
4803 4802 res = set()
4804 4803 for rev in repo:
4805 4804 ctx = repo[rev]
4806 4805 res |= set(ctx.files())
4807 4806
4808 4807 ui.pager(b'manifest')
4809 4808 for f in sorted(res):
4810 4809 fm.startitem()
4811 4810 fm.write(b"path", b'%s\n', f)
4812 4811 fm.end()
4813 4812 return
4814 4813
4815 4814 if rev and node:
4816 4815 raise error.InputError(_(b"please specify just one revision"))
4817 4816
4818 4817 if not node:
4819 4818 node = rev
4820 4819
4821 4820 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4822 4821 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4823 4822 if node:
4824 4823 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4825 4824 ctx = logcmdutil.revsingle(repo, node)
4826 4825 mf = ctx.manifest()
4827 4826 ui.pager(b'manifest')
4828 4827 for f in ctx:
4829 4828 fm.startitem()
4830 4829 fm.context(ctx=ctx)
4831 4830 fl = ctx[f].flags()
4832 4831 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4833 4832 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4834 4833 fm.write(b'path', b'%s\n', f)
4835 4834 fm.end()
4836 4835
4837 4836
4838 4837 @command(
4839 4838 b'merge',
4840 4839 [
4841 4840 (
4842 4841 b'f',
4843 4842 b'force',
4844 4843 None,
4845 4844 _(b'force a merge including outstanding changes (DEPRECATED)'),
4846 4845 ),
4847 4846 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4848 4847 (
4849 4848 b'P',
4850 4849 b'preview',
4851 4850 None,
4852 4851 _(b'review revisions to merge (no merge is performed)'),
4853 4852 ),
4854 4853 (b'', b'abort', None, _(b'abort the ongoing merge')),
4855 4854 ]
4856 4855 + mergetoolopts,
4857 4856 _(b'[-P] [[-r] REV]'),
4858 4857 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4859 4858 helpbasic=True,
4860 4859 )
4861 4860 def merge(ui, repo, node=None, **opts):
4862 4861 """merge another revision into working directory
4863 4862
4864 4863 The current working directory is updated with all changes made in
4865 4864 the requested revision since the last common predecessor revision.
4866 4865
4867 4866 Files that changed between either parent are marked as changed for
4868 4867 the next commit and a commit must be performed before any further
4869 4868 updates to the repository are allowed. The next commit will have
4870 4869 two parents.
4871 4870
4872 4871 ``--tool`` can be used to specify the merge tool used for file
4873 4872 merges. It overrides the HGMERGE environment variable and your
4874 4873 configuration files. See :hg:`help merge-tools` for options.
4875 4874
4876 4875 If no revision is specified, the working directory's parent is a
4877 4876 head revision, and the current branch contains exactly one other
4878 4877 head, the other head is merged with by default. Otherwise, an
4879 4878 explicit revision with which to merge must be provided.
4880 4879
4881 4880 See :hg:`help resolve` for information on handling file conflicts.
4882 4881
4883 4882 To undo an uncommitted merge, use :hg:`merge --abort` which
4884 4883 will check out a clean copy of the original merge parent, losing
4885 4884 all changes.
4886 4885
4887 4886 Returns 0 on success, 1 if there are unresolved files.
4888 4887 """
4889 4888
4890 4889 opts = pycompat.byteskwargs(opts)
4891 4890 abort = opts.get(b'abort')
4892 4891 if abort and repo.dirstate.p2() == repo.nullid:
4893 4892 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4894 4893 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4895 4894 if abort:
4896 4895 state = cmdutil.getunfinishedstate(repo)
4897 4896 if state and state._opname != b'merge':
4898 4897 raise error.StateError(
4899 4898 _(b'cannot abort merge with %s in progress') % (state._opname),
4900 4899 hint=state.hint(),
4901 4900 )
4902 4901 if node:
4903 4902 raise error.InputError(_(b"cannot specify a node with --abort"))
4904 4903 return hg.abortmerge(repo.ui, repo)
4905 4904
4906 4905 if opts.get(b'rev') and node:
4907 4906 raise error.InputError(_(b"please specify just one revision"))
4908 4907 if not node:
4909 4908 node = opts.get(b'rev')
4910 4909
4911 4910 if node:
4912 4911 ctx = logcmdutil.revsingle(repo, node)
4913 4912 else:
4914 4913 if ui.configbool(b'commands', b'merge.require-rev'):
4915 4914 raise error.InputError(
4916 4915 _(
4917 4916 b'configuration requires specifying revision to merge '
4918 4917 b'with'
4919 4918 )
4920 4919 )
4921 4920 ctx = repo[destutil.destmerge(repo)]
4922 4921
4923 4922 if ctx.node() is None:
4924 4923 raise error.InputError(
4925 4924 _(b'merging with the working copy has no effect')
4926 4925 )
4927 4926
4928 4927 if opts.get(b'preview'):
4929 4928 # find nodes that are ancestors of p2 but not of p1
4930 4929 p1 = repo[b'.'].node()
4931 4930 p2 = ctx.node()
4932 4931 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4933 4932
4934 4933 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4935 4934 for node in nodes:
4936 4935 displayer.show(repo[node])
4937 4936 displayer.close()
4938 4937 return 0
4939 4938
4940 4939 # ui.forcemerge is an internal variable, do not document
4941 4940 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4942 4941 with ui.configoverride(overrides, b'merge'):
4943 4942 force = opts.get(b'force')
4944 4943 labels = [b'working copy', b'merge rev', b'common ancestor']
4945 4944 return hg.merge(ctx, force=force, labels=labels)
4946 4945
4947 4946
4948 4947 statemod.addunfinished(
4949 4948 b'merge',
4950 4949 fname=None,
4951 4950 clearable=True,
4952 4951 allowcommit=True,
4953 4952 cmdmsg=_(b'outstanding uncommitted merge'),
4954 4953 abortfunc=hg.abortmerge,
4955 4954 statushint=_(
4956 4955 b'To continue: hg commit\nTo abort: hg merge --abort'
4957 4956 ),
4958 4957 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4959 4958 )
4960 4959
4961 4960
4962 4961 @command(
4963 4962 b'outgoing|out',
4964 4963 [
4965 4964 (
4966 4965 b'f',
4967 4966 b'force',
4968 4967 None,
4969 4968 _(b'run even when the destination is unrelated'),
4970 4969 ),
4971 4970 (
4972 4971 b'r',
4973 4972 b'rev',
4974 4973 [],
4975 4974 _(b'a changeset intended to be included in the destination'),
4976 4975 _(b'REV'),
4977 4976 ),
4978 4977 (b'n', b'newest-first', None, _(b'show newest record first')),
4979 4978 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4980 4979 (
4981 4980 b'b',
4982 4981 b'branch',
4983 4982 [],
4984 4983 _(b'a specific branch you would like to push'),
4985 4984 _(b'BRANCH'),
4986 4985 ),
4987 4986 ]
4988 4987 + logopts
4989 4988 + remoteopts
4990 4989 + subrepoopts,
4991 4990 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]...'),
4992 4991 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4993 4992 )
4994 4993 def outgoing(ui, repo, *dests, **opts):
4995 4994 """show changesets not found in the destination
4996 4995
4997 4996 Show changesets not found in the specified destination repository
4998 4997 or the default push location. These are the changesets that would
4999 4998 be pushed if a push was requested.
5000 4999
5001 5000 See pull for details of valid destination formats.
5002 5001
5003 5002 .. container:: verbose
5004 5003
5005 5004 With -B/--bookmarks, the result of bookmark comparison between
5006 5005 local and remote repositories is displayed. With -v/--verbose,
5007 5006 status is also displayed for each bookmark like below::
5008 5007
5009 5008 BM1 01234567890a added
5010 5009 BM2 deleted
5011 5010 BM3 234567890abc advanced
5012 5011 BM4 34567890abcd diverged
5013 5012 BM5 4567890abcde changed
5014 5013
5015 5014 The action taken when pushing depends on the
5016 5015 status of each bookmark:
5017 5016
5018 5017 :``added``: push with ``-B`` will create it
5019 5018 :``deleted``: push with ``-B`` will delete it
5020 5019 :``advanced``: push will update it
5021 5020 :``diverged``: push with ``-B`` will update it
5022 5021 :``changed``: push with ``-B`` will update it
5023 5022
5024 5023 From the point of view of pushing behavior, bookmarks
5025 5024 existing only in the remote repository are treated as
5026 5025 ``deleted``, even if it is in fact added remotely.
5027 5026
5028 5027 Returns 0 if there are outgoing changes, 1 otherwise.
5029 5028 """
5030 5029 opts = pycompat.byteskwargs(opts)
5031 5030 if opts.get(b'bookmarks'):
5032 5031 for path in urlutil.get_push_paths(repo, ui, dests):
5033 5032 dest = path.pushloc or path.loc
5034 5033 other = hg.peer(repo, opts, dest)
5035 5034 try:
5036 5035 if b'bookmarks' not in other.listkeys(b'namespaces'):
5037 5036 ui.warn(_(b"remote doesn't support bookmarks\n"))
5038 5037 return 0
5039 5038 ui.status(
5040 5039 _(b'comparing with %s\n') % urlutil.hidepassword(dest)
5041 5040 )
5042 5041 ui.pager(b'outgoing')
5043 5042 return bookmarks.outgoing(ui, repo, other)
5044 5043 finally:
5045 5044 other.close()
5046 5045
5047 5046 return hg.outgoing(ui, repo, dests, opts)
5048 5047
5049 5048
5050 5049 @command(
5051 5050 b'parents',
5052 5051 [
5053 5052 (
5054 5053 b'r',
5055 5054 b'rev',
5056 5055 b'',
5057 5056 _(b'show parents of the specified revision'),
5058 5057 _(b'REV'),
5059 5058 ),
5060 5059 ]
5061 5060 + templateopts,
5062 5061 _(b'[-r REV] [FILE]'),
5063 5062 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5064 5063 inferrepo=True,
5065 5064 )
5066 5065 def parents(ui, repo, file_=None, **opts):
5067 5066 """show the parents of the working directory or revision (DEPRECATED)
5068 5067
5069 5068 Print the working directory's parent revisions. If a revision is
5070 5069 given via -r/--rev, the parent of that revision will be printed.
5071 5070 If a file argument is given, the revision in which the file was
5072 5071 last changed (before the working directory revision or the
5073 5072 argument to --rev if given) is printed.
5074 5073
5075 5074 This command is equivalent to::
5076 5075
5077 5076 hg log -r "p1()+p2()" or
5078 5077 hg log -r "p1(REV)+p2(REV)" or
5079 5078 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5080 5079 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5081 5080
5082 5081 See :hg:`summary` and :hg:`help revsets` for related information.
5083 5082
5084 5083 Returns 0 on success.
5085 5084 """
5086 5085
5087 5086 opts = pycompat.byteskwargs(opts)
5088 5087 rev = opts.get(b'rev')
5089 5088 if rev:
5090 5089 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5091 5090 ctx = logcmdutil.revsingle(repo, rev, None)
5092 5091
5093 5092 if file_:
5094 5093 m = scmutil.match(ctx, (file_,), opts)
5095 5094 if m.anypats() or len(m.files()) != 1:
5096 5095 raise error.InputError(_(b'can only specify an explicit filename'))
5097 5096 file_ = m.files()[0]
5098 5097 filenodes = []
5099 5098 for cp in ctx.parents():
5100 5099 if not cp:
5101 5100 continue
5102 5101 try:
5103 5102 filenodes.append(cp.filenode(file_))
5104 5103 except error.LookupError:
5105 5104 pass
5106 5105 if not filenodes:
5107 5106 raise error.InputError(_(b"'%s' not found in manifest") % file_)
5108 5107 p = []
5109 5108 for fn in filenodes:
5110 5109 fctx = repo.filectx(file_, fileid=fn)
5111 5110 p.append(fctx.node())
5112 5111 else:
5113 5112 p = [cp.node() for cp in ctx.parents()]
5114 5113
5115 5114 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5116 5115 for n in p:
5117 5116 if n != repo.nullid:
5118 5117 displayer.show(repo[n])
5119 5118 displayer.close()
5120 5119
5121 5120
5122 5121 @command(
5123 5122 b'paths',
5124 5123 formatteropts,
5125 5124 _(b'[NAME]'),
5126 5125 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5127 5126 optionalrepo=True,
5128 5127 intents={INTENT_READONLY},
5129 5128 )
5130 5129 def paths(ui, repo, search=None, **opts):
5131 5130 """show aliases for remote repositories
5132 5131
5133 5132 Show definition of symbolic path name NAME. If no name is given,
5134 5133 show definition of all available names.
5135 5134
5136 5135 Option -q/--quiet suppresses all output when searching for NAME
5137 5136 and shows only the path names when listing all definitions.
5138 5137
5139 5138 Path names are defined in the [paths] section of your
5140 5139 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5141 5140 repository, ``.hg/hgrc`` is used, too.
5142 5141
5143 5142 The path names ``default`` and ``default-push`` have a special
5144 5143 meaning. When performing a push or pull operation, they are used
5145 5144 as fallbacks if no location is specified on the command-line.
5146 5145 When ``default-push`` is set, it will be used for push and
5147 5146 ``default`` will be used for pull; otherwise ``default`` is used
5148 5147 as the fallback for both. When cloning a repository, the clone
5149 5148 source is written as ``default`` in ``.hg/hgrc``.
5150 5149
5151 5150 .. note::
5152 5151
5153 5152 ``default`` and ``default-push`` apply to all inbound (e.g.
5154 5153 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5155 5154 and :hg:`bundle`) operations.
5156 5155
5157 5156 See :hg:`help urls` for more information.
5158 5157
5159 5158 .. container:: verbose
5160 5159
5161 5160 Template:
5162 5161
5163 5162 The following keywords are supported. See also :hg:`help templates`.
5164 5163
5165 5164 :name: String. Symbolic name of the path alias.
5166 5165 :pushurl: String. URL for push operations.
5167 5166 :url: String. URL or directory path for the other operations.
5168 5167
5169 5168 Returns 0 on success.
5170 5169 """
5171 5170
5172 5171 opts = pycompat.byteskwargs(opts)
5173 5172
5174 5173 pathitems = urlutil.list_paths(ui, search)
5175 5174 ui.pager(b'paths')
5176 5175
5177 5176 fm = ui.formatter(b'paths', opts)
5178 5177 if fm.isplain():
5179 5178 hidepassword = urlutil.hidepassword
5180 5179 else:
5181 5180 hidepassword = bytes
5182 5181 if ui.quiet:
5183 5182 namefmt = b'%s\n'
5184 5183 else:
5185 5184 namefmt = b'%s = '
5186 5185 showsubopts = not search and not ui.quiet
5187 5186
5188 5187 for name, path in pathitems:
5189 5188 fm.startitem()
5190 5189 fm.condwrite(not search, b'name', namefmt, name)
5191 5190 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5192 5191 for subopt, value in sorted(path.suboptions.items()):
5193 5192 assert subopt not in (b'name', b'url')
5194 5193 if showsubopts:
5195 5194 fm.plain(b'%s:%s = ' % (name, subopt))
5196 5195 if isinstance(value, bool):
5197 5196 if value:
5198 5197 value = b'yes'
5199 5198 else:
5200 5199 value = b'no'
5201 5200 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5202 5201
5203 5202 fm.end()
5204 5203
5205 5204 if search and not pathitems:
5206 5205 if not ui.quiet:
5207 5206 ui.warn(_(b"not found!\n"))
5208 5207 return 1
5209 5208 else:
5210 5209 return 0
5211 5210
5212 5211
5213 5212 @command(
5214 5213 b'phase',
5215 5214 [
5216 5215 (b'p', b'public', False, _(b'set changeset phase to public')),
5217 5216 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5218 5217 (b's', b'secret', False, _(b'set changeset phase to secret')),
5219 5218 (b'f', b'force', False, _(b'allow to move boundary backward')),
5220 5219 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5221 5220 ],
5222 5221 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5223 5222 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5224 5223 )
5225 5224 def phase(ui, repo, *revs, **opts):
5226 5225 """set or show the current phase name
5227 5226
5228 5227 With no argument, show the phase name of the current revision(s).
5229 5228
5230 5229 With one of -p/--public, -d/--draft or -s/--secret, change the
5231 5230 phase value of the specified revisions.
5232 5231
5233 5232 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5234 5233 lower phase to a higher phase. Phases are ordered as follows::
5235 5234
5236 5235 public < draft < secret
5237 5236
5238 5237 Returns 0 on success, 1 if some phases could not be changed.
5239 5238
5240 5239 (For more information about the phases concept, see :hg:`help phases`.)
5241 5240 """
5242 5241 opts = pycompat.byteskwargs(opts)
5243 5242 # search for a unique phase argument
5244 5243 targetphase = None
5245 5244 for idx, name in enumerate(phases.cmdphasenames):
5246 5245 if opts[name]:
5247 5246 if targetphase is not None:
5248 5247 raise error.InputError(_(b'only one phase can be specified'))
5249 5248 targetphase = idx
5250 5249
5251 5250 # look for specified revision
5252 5251 revs = list(revs)
5253 5252 revs.extend(opts[b'rev'])
5254 5253 if revs:
5255 5254 revs = logcmdutil.revrange(repo, revs)
5256 5255 else:
5257 5256 # display both parents as the second parent phase can influence
5258 5257 # the phase of a merge commit
5259 5258 revs = [c.rev() for c in repo[None].parents()]
5260 5259
5261 5260 ret = 0
5262 5261 if targetphase is None:
5263 5262 # display
5264 5263 for r in revs:
5265 5264 ctx = repo[r]
5266 5265 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5267 5266 else:
5268 5267 with repo.lock(), repo.transaction(b"phase") as tr:
5269 5268 # set phase
5270 5269 if not revs:
5271 5270 raise error.InputError(_(b'empty revision set'))
5272 5271 nodes = [repo[r].node() for r in revs]
5273 5272 # moving revision from public to draft may hide them
5274 5273 # We have to check result on an unfiltered repository
5275 5274 unfi = repo.unfiltered()
5276 5275 getphase = unfi._phasecache.phase
5277 5276 olddata = [getphase(unfi, r) for r in unfi]
5278 5277 phases.advanceboundary(repo, tr, targetphase, nodes)
5279 5278 if opts[b'force']:
5280 5279 phases.retractboundary(repo, tr, targetphase, nodes)
5281 5280 getphase = unfi._phasecache.phase
5282 5281 newdata = [getphase(unfi, r) for r in unfi]
5283 5282 changes = sum(newdata[r] != olddata[r] for r in unfi)
5284 5283 cl = unfi.changelog
5285 5284 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5286 5285 if rejected:
5287 5286 ui.warn(
5288 5287 _(
5289 5288 b'cannot move %i changesets to a higher '
5290 5289 b'phase, use --force\n'
5291 5290 )
5292 5291 % len(rejected)
5293 5292 )
5294 5293 ret = 1
5295 5294 if changes:
5296 5295 msg = _(b'phase changed for %i changesets\n') % changes
5297 5296 if ret:
5298 5297 ui.status(msg)
5299 5298 else:
5300 5299 ui.note(msg)
5301 5300 else:
5302 5301 ui.warn(_(b'no phases changed\n'))
5303 5302 return ret
5304 5303
5305 5304
5306 5305 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5307 5306 """Run after a changegroup has been added via pull/unbundle
5308 5307
5309 5308 This takes arguments below:
5310 5309
5311 5310 :modheads: change of heads by pull/unbundle
5312 5311 :optupdate: updating working directory is needed or not
5313 5312 :checkout: update destination revision (or None to default destination)
5314 5313 :brev: a name, which might be a bookmark to be activated after updating
5315 5314
5316 5315 return True if update raise any conflict, False otherwise.
5317 5316 """
5318 5317 if modheads == 0:
5319 5318 return False
5320 5319 if optupdate:
5321 5320 try:
5322 5321 return hg.updatetotally(ui, repo, checkout, brev)
5323 5322 except error.UpdateAbort as inst:
5324 5323 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5325 5324 hint = inst.hint
5326 5325 raise error.UpdateAbort(msg, hint=hint)
5327 5326 if modheads is not None and modheads > 1:
5328 5327 currentbranchheads = len(repo.branchheads())
5329 5328 if currentbranchheads == modheads:
5330 5329 ui.status(
5331 5330 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5332 5331 )
5333 5332 elif currentbranchheads > 1:
5334 5333 ui.status(
5335 5334 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5336 5335 )
5337 5336 else:
5338 5337 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5339 5338 elif not ui.configbool(b'commands', b'update.requiredest'):
5340 5339 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5341 5340 return False
5342 5341
5343 5342
5344 5343 @command(
5345 5344 b'pull',
5346 5345 [
5347 5346 (
5348 5347 b'u',
5349 5348 b'update',
5350 5349 None,
5351 5350 _(b'update to new branch head if new descendants were pulled'),
5352 5351 ),
5353 5352 (
5354 5353 b'f',
5355 5354 b'force',
5356 5355 None,
5357 5356 _(b'run even when remote repository is unrelated'),
5358 5357 ),
5359 5358 (
5360 5359 b'',
5361 5360 b'confirm',
5362 5361 None,
5363 5362 _(b'confirm pull before applying changes'),
5364 5363 ),
5365 5364 (
5366 5365 b'r',
5367 5366 b'rev',
5368 5367 [],
5369 5368 _(b'a remote changeset intended to be added'),
5370 5369 _(b'REV'),
5371 5370 ),
5372 5371 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5373 5372 (
5374 5373 b'b',
5375 5374 b'branch',
5376 5375 [],
5377 5376 _(b'a specific branch you would like to pull'),
5378 5377 _(b'BRANCH'),
5379 5378 ),
5380 5379 ]
5381 5380 + remoteopts,
5382 5381 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]...'),
5383 5382 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5384 5383 helpbasic=True,
5385 5384 )
5386 5385 def pull(ui, repo, *sources, **opts):
5387 5386 """pull changes from the specified source
5388 5387
5389 5388 Pull changes from a remote repository to a local one.
5390 5389
5391 5390 This finds all changes from the repository at the specified path
5392 5391 or URL and adds them to a local repository (the current one unless
5393 5392 -R is specified). By default, this does not update the copy of the
5394 5393 project in the working directory.
5395 5394
5396 5395 When cloning from servers that support it, Mercurial may fetch
5397 5396 pre-generated data. When this is done, hooks operating on incoming
5398 5397 changesets and changegroups may fire more than once, once for each
5399 5398 pre-generated bundle and as well as for any additional remaining
5400 5399 data. See :hg:`help -e clonebundles` for more.
5401 5400
5402 5401 Use :hg:`incoming` if you want to see what would have been added
5403 5402 by a pull at the time you issued this command. If you then decide
5404 5403 to add those changes to the repository, you should use :hg:`pull
5405 5404 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5406 5405
5407 5406 If SOURCE is omitted, the 'default' path will be used.
5408 5407 See :hg:`help urls` for more information.
5409 5408
5410 5409 If multiple sources are specified, they will be pulled sequentially as if
5411 5410 the command was run multiple time. If --update is specify and the command
5412 5411 will stop at the first failed --update.
5413 5412
5414 5413 Specifying bookmark as ``.`` is equivalent to specifying the active
5415 5414 bookmark's name.
5416 5415
5417 5416 Returns 0 on success, 1 if an update had unresolved files.
5418 5417 """
5419 5418
5420 5419 opts = pycompat.byteskwargs(opts)
5421 5420 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5422 5421 b'update'
5423 5422 ):
5424 5423 msg = _(b'update destination required by configuration')
5425 5424 hint = _(b'use hg pull followed by hg update DEST')
5426 5425 raise error.InputError(msg, hint=hint)
5427 5426
5428 5427 for path in urlutil.get_pull_paths(repo, ui, sources):
5429 5428 source, branches = urlutil.parseurl(path.rawloc, opts.get(b'branch'))
5430 5429 ui.status(_(b'pulling from %s\n') % urlutil.hidepassword(source))
5431 5430 ui.flush()
5432 5431 other = hg.peer(repo, opts, source)
5433 5432 update_conflict = None
5434 5433 try:
5435 5434 revs, checkout = hg.addbranchrevs(
5436 5435 repo, other, branches, opts.get(b'rev')
5437 5436 )
5438 5437
5439 5438 pullopargs = {}
5440 5439
5441 5440 nodes = None
5442 5441 if opts.get(b'bookmark') or revs:
5443 5442 # The list of bookmark used here is the same used to actually update
5444 5443 # the bookmark names, to avoid the race from issue 4689 and we do
5445 5444 # all lookup and bookmark queries in one go so they see the same
5446 5445 # version of the server state (issue 4700).
5447 5446 nodes = []
5448 5447 fnodes = []
5449 5448 revs = revs or []
5450 5449 if revs and not other.capable(b'lookup'):
5451 5450 err = _(
5452 5451 b"other repository doesn't support revision lookup, "
5453 5452 b"so a rev cannot be specified."
5454 5453 )
5455 5454 raise error.Abort(err)
5456 5455 with other.commandexecutor() as e:
5457 5456 fremotebookmarks = e.callcommand(
5458 5457 b'listkeys', {b'namespace': b'bookmarks'}
5459 5458 )
5460 5459 for r in revs:
5461 5460 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5462 5461 remotebookmarks = fremotebookmarks.result()
5463 5462 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5464 5463 pullopargs[b'remotebookmarks'] = remotebookmarks
5465 5464 for b in opts.get(b'bookmark', []):
5466 5465 b = repo._bookmarks.expandname(b)
5467 5466 if b not in remotebookmarks:
5468 5467 raise error.InputError(
5469 5468 _(b'remote bookmark %s not found!') % b
5470 5469 )
5471 5470 nodes.append(remotebookmarks[b])
5472 5471 for i, rev in enumerate(revs):
5473 5472 node = fnodes[i].result()
5474 5473 nodes.append(node)
5475 5474 if rev == checkout:
5476 5475 checkout = node
5477 5476
5478 5477 wlock = util.nullcontextmanager()
5479 5478 if opts.get(b'update'):
5480 5479 wlock = repo.wlock()
5481 5480 with wlock:
5482 5481 pullopargs.update(opts.get(b'opargs', {}))
5483 5482 modheads = exchange.pull(
5484 5483 repo,
5485 5484 other,
5486 5485 path=path,
5487 5486 heads=nodes,
5488 5487 force=opts.get(b'force'),
5489 5488 bookmarks=opts.get(b'bookmark', ()),
5490 5489 opargs=pullopargs,
5491 5490 confirm=opts.get(b'confirm'),
5492 5491 ).cgresult
5493 5492
5494 5493 # brev is a name, which might be a bookmark to be activated at
5495 5494 # the end of the update. In other words, it is an explicit
5496 5495 # destination of the update
5497 5496 brev = None
5498 5497
5499 5498 if checkout:
5500 5499 checkout = repo.unfiltered().changelog.rev(checkout)
5501 5500
5502 5501 # order below depends on implementation of
5503 5502 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5504 5503 # because 'checkout' is determined without it.
5505 5504 if opts.get(b'rev'):
5506 5505 brev = opts[b'rev'][0]
5507 5506 elif opts.get(b'branch'):
5508 5507 brev = opts[b'branch'][0]
5509 5508 else:
5510 5509 brev = branches[0]
5511 5510 repo._subtoppath = source
5512 5511 try:
5513 5512 update_conflict = postincoming(
5514 5513 ui, repo, modheads, opts.get(b'update'), checkout, brev
5515 5514 )
5516 5515 except error.FilteredRepoLookupError as exc:
5517 5516 msg = _(b'cannot update to target: %s') % exc.args[0]
5518 5517 exc.args = (msg,) + exc.args[1:]
5519 5518 raise
5520 5519 finally:
5521 5520 del repo._subtoppath
5522 5521
5523 5522 finally:
5524 5523 other.close()
5525 5524 # skip the remaining pull source if they are some conflict.
5526 5525 if update_conflict:
5527 5526 break
5528 5527 if update_conflict:
5529 5528 return 1
5530 5529 else:
5531 5530 return 0
5532 5531
5533 5532
5534 5533 @command(
5535 5534 b'purge|clean',
5536 5535 [
5537 5536 (b'a', b'abort-on-err', None, _(b'abort if an error occurs')),
5538 5537 (b'', b'all', None, _(b'purge ignored files too')),
5539 5538 (b'i', b'ignored', None, _(b'purge only ignored files')),
5540 5539 (b'', b'dirs', None, _(b'purge empty directories')),
5541 5540 (b'', b'files', None, _(b'purge files')),
5542 5541 (b'p', b'print', None, _(b'print filenames instead of deleting them')),
5543 5542 (
5544 5543 b'0',
5545 5544 b'print0',
5546 5545 None,
5547 5546 _(
5548 5547 b'end filenames with NUL, for use with xargs'
5549 5548 b' (implies -p/--print)'
5550 5549 ),
5551 5550 ),
5552 5551 (b'', b'confirm', None, _(b'ask before permanently deleting files')),
5553 5552 ]
5554 5553 + cmdutil.walkopts,
5555 5554 _(b'hg purge [OPTION]... [DIR]...'),
5556 5555 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5557 5556 )
5558 5557 def purge(ui, repo, *dirs, **opts):
5559 5558 """removes files not tracked by Mercurial
5560 5559
5561 5560 Delete files not known to Mercurial. This is useful to test local
5562 5561 and uncommitted changes in an otherwise-clean source tree.
5563 5562
5564 5563 This means that purge will delete the following by default:
5565 5564
5566 5565 - Unknown files: files marked with "?" by :hg:`status`
5567 5566 - Empty directories: in fact Mercurial ignores directories unless
5568 5567 they contain files under source control management
5569 5568
5570 5569 But it will leave untouched:
5571 5570
5572 5571 - Modified and unmodified tracked files
5573 5572 - Ignored files (unless -i or --all is specified)
5574 5573 - New files added to the repository (with :hg:`add`)
5575 5574
5576 5575 The --files and --dirs options can be used to direct purge to delete
5577 5576 only files, only directories, or both. If neither option is given,
5578 5577 both will be deleted.
5579 5578
5580 5579 If directories are given on the command line, only files in these
5581 5580 directories are considered.
5582 5581
5583 5582 Be careful with purge, as you could irreversibly delete some files
5584 5583 you forgot to add to the repository. If you only want to print the
5585 5584 list of files that this program would delete, use the --print
5586 5585 option.
5587 5586 """
5588 5587 opts = pycompat.byteskwargs(opts)
5589 5588 cmdutil.check_at_most_one_arg(opts, b'all', b'ignored')
5590 5589
5591 5590 act = not opts.get(b'print')
5592 5591 eol = b'\n'
5593 5592 if opts.get(b'print0'):
5594 5593 eol = b'\0'
5595 5594 act = False # --print0 implies --print
5596 5595 if opts.get(b'all', False):
5597 5596 ignored = True
5598 5597 unknown = True
5599 5598 else:
5600 5599 ignored = opts.get(b'ignored', False)
5601 5600 unknown = not ignored
5602 5601
5603 5602 removefiles = opts.get(b'files')
5604 5603 removedirs = opts.get(b'dirs')
5605 5604 confirm = opts.get(b'confirm')
5606 5605 if confirm is None:
5607 5606 try:
5608 5607 extensions.find(b'purge')
5609 5608 confirm = False
5610 5609 except KeyError:
5611 5610 confirm = True
5612 5611
5613 5612 if not removefiles and not removedirs:
5614 5613 removefiles = True
5615 5614 removedirs = True
5616 5615
5617 5616 match = scmutil.match(repo[None], dirs, opts)
5618 5617
5619 5618 paths = mergemod.purge(
5620 5619 repo,
5621 5620 match,
5622 5621 unknown=unknown,
5623 5622 ignored=ignored,
5624 5623 removeemptydirs=removedirs,
5625 5624 removefiles=removefiles,
5626 5625 abortonerror=opts.get(b'abort_on_err'),
5627 5626 noop=not act,
5628 5627 confirm=confirm,
5629 5628 )
5630 5629
5631 5630 for path in paths:
5632 5631 if not act:
5633 5632 ui.write(b'%s%s' % (path, eol))
5634 5633
5635 5634
5636 5635 @command(
5637 5636 b'push',
5638 5637 [
5639 5638 (b'f', b'force', None, _(b'force push')),
5640 5639 (
5641 5640 b'r',
5642 5641 b'rev',
5643 5642 [],
5644 5643 _(b'a changeset intended to be included in the destination'),
5645 5644 _(b'REV'),
5646 5645 ),
5647 5646 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5648 5647 (b'', b'all-bookmarks', None, _(b"push all bookmarks (EXPERIMENTAL)")),
5649 5648 (
5650 5649 b'b',
5651 5650 b'branch',
5652 5651 [],
5653 5652 _(b'a specific branch you would like to push'),
5654 5653 _(b'BRANCH'),
5655 5654 ),
5656 5655 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5657 5656 (
5658 5657 b'',
5659 5658 b'pushvars',
5660 5659 [],
5661 5660 _(b'variables that can be sent to server (ADVANCED)'),
5662 5661 ),
5663 5662 (
5664 5663 b'',
5665 5664 b'publish',
5666 5665 False,
5667 5666 _(b'push the changeset as public (EXPERIMENTAL)'),
5668 5667 ),
5669 5668 ]
5670 5669 + remoteopts,
5671 5670 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]...'),
5672 5671 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5673 5672 helpbasic=True,
5674 5673 )
5675 5674 def push(ui, repo, *dests, **opts):
5676 5675 """push changes to the specified destination
5677 5676
5678 5677 Push changesets from the local repository to the specified
5679 5678 destination.
5680 5679
5681 5680 This operation is symmetrical to pull: it is identical to a pull
5682 5681 in the destination repository from the current one.
5683 5682
5684 5683 By default, push will not allow creation of new heads at the
5685 5684 destination, since multiple heads would make it unclear which head
5686 5685 to use. In this situation, it is recommended to pull and merge
5687 5686 before pushing.
5688 5687
5689 5688 Use --new-branch if you want to allow push to create a new named
5690 5689 branch that is not present at the destination. This allows you to
5691 5690 only create a new branch without forcing other changes.
5692 5691
5693 5692 .. note::
5694 5693
5695 5694 Extra care should be taken with the -f/--force option,
5696 5695 which will push all new heads on all branches, an action which will
5697 5696 almost always cause confusion for collaborators.
5698 5697
5699 5698 If -r/--rev is used, the specified revision and all its ancestors
5700 5699 will be pushed to the remote repository.
5701 5700
5702 5701 If -B/--bookmark is used, the specified bookmarked revision, its
5703 5702 ancestors, and the bookmark will be pushed to the remote
5704 5703 repository. Specifying ``.`` is equivalent to specifying the active
5705 5704 bookmark's name. Use the --all-bookmarks option for pushing all
5706 5705 current bookmarks.
5707 5706
5708 5707 Please see :hg:`help urls` for important details about ``ssh://``
5709 5708 URLs. If DESTINATION is omitted, a default path will be used.
5710 5709
5711 5710 When passed multiple destinations, push will process them one after the
5712 5711 other, but stop should an error occur.
5713 5712
5714 5713 .. container:: verbose
5715 5714
5716 5715 The --pushvars option sends strings to the server that become
5717 5716 environment variables prepended with ``HG_USERVAR_``. For example,
5718 5717 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5719 5718 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5720 5719
5721 5720 pushvars can provide for user-overridable hooks as well as set debug
5722 5721 levels. One example is having a hook that blocks commits containing
5723 5722 conflict markers, but enables the user to override the hook if the file
5724 5723 is using conflict markers for testing purposes or the file format has
5725 5724 strings that look like conflict markers.
5726 5725
5727 5726 By default, servers will ignore `--pushvars`. To enable it add the
5728 5727 following to your configuration file::
5729 5728
5730 5729 [push]
5731 5730 pushvars.server = true
5732 5731
5733 5732 Returns 0 if push was successful, 1 if nothing to push.
5734 5733 """
5735 5734
5736 5735 opts = pycompat.byteskwargs(opts)
5737 5736
5738 5737 if opts.get(b'all_bookmarks'):
5739 5738 cmdutil.check_incompatible_arguments(
5740 5739 opts,
5741 5740 b'all_bookmarks',
5742 5741 [b'bookmark', b'rev'],
5743 5742 )
5744 5743 opts[b'bookmark'] = list(repo._bookmarks)
5745 5744
5746 5745 if opts.get(b'bookmark'):
5747 5746 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5748 5747 for b in opts[b'bookmark']:
5749 5748 # translate -B options to -r so changesets get pushed
5750 5749 b = repo._bookmarks.expandname(b)
5751 5750 if b in repo._bookmarks:
5752 5751 opts.setdefault(b'rev', []).append(b)
5753 5752 else:
5754 5753 # if we try to push a deleted bookmark, translate it to null
5755 5754 # this lets simultaneous -r, -b options continue working
5756 5755 opts.setdefault(b'rev', []).append(b"null")
5757 5756
5758 5757 some_pushed = False
5759 5758 result = 0
5760 5759 for path in urlutil.get_push_paths(repo, ui, dests):
5761 5760 dest = path.pushloc or path.loc
5762 5761 branches = (path.branch, opts.get(b'branch') or [])
5763 5762 ui.status(_(b'pushing to %s\n') % urlutil.hidepassword(dest))
5764 5763 revs, checkout = hg.addbranchrevs(
5765 5764 repo, repo, branches, opts.get(b'rev')
5766 5765 )
5767 5766 other = hg.peer(repo, opts, dest)
5768 5767
5769 5768 try:
5770 5769 if revs:
5771 5770 revs = [repo[r].node() for r in logcmdutil.revrange(repo, revs)]
5772 5771 if not revs:
5773 5772 raise error.InputError(
5774 5773 _(b"specified revisions evaluate to an empty set"),
5775 5774 hint=_(b"use different revision arguments"),
5776 5775 )
5777 5776 elif path.pushrev:
5778 5777 # It doesn't make any sense to specify ancestor revisions. So limit
5779 5778 # to DAG heads to make discovery simpler.
5780 5779 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5781 5780 revs = scmutil.revrange(repo, [expr])
5782 5781 revs = [repo[rev].node() for rev in revs]
5783 5782 if not revs:
5784 5783 raise error.InputError(
5785 5784 _(
5786 5785 b'default push revset for path evaluates to an empty set'
5787 5786 )
5788 5787 )
5789 5788 elif ui.configbool(b'commands', b'push.require-revs'):
5790 5789 raise error.InputError(
5791 5790 _(b'no revisions specified to push'),
5792 5791 hint=_(b'did you mean "hg push -r ."?'),
5793 5792 )
5794 5793
5795 5794 repo._subtoppath = dest
5796 5795 try:
5797 5796 # push subrepos depth-first for coherent ordering
5798 5797 c = repo[b'.']
5799 5798 subs = c.substate # only repos that are committed
5800 5799 for s in sorted(subs):
5801 5800 sub_result = c.sub(s).push(opts)
5802 5801 if sub_result == 0:
5803 5802 return 1
5804 5803 finally:
5805 5804 del repo._subtoppath
5806 5805
5807 5806 opargs = dict(
5808 5807 opts.get(b'opargs', {})
5809 5808 ) # copy opargs since we may mutate it
5810 5809 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5811 5810
5812 5811 pushop = exchange.push(
5813 5812 repo,
5814 5813 other,
5815 5814 opts.get(b'force'),
5816 5815 revs=revs,
5817 5816 newbranch=opts.get(b'new_branch'),
5818 5817 bookmarks=opts.get(b'bookmark', ()),
5819 5818 publish=opts.get(b'publish'),
5820 5819 opargs=opargs,
5821 5820 )
5822 5821
5823 5822 if pushop.cgresult == 0:
5824 5823 result = 1
5825 5824 elif pushop.cgresult is not None:
5826 5825 some_pushed = True
5827 5826
5828 5827 if pushop.bkresult is not None:
5829 5828 if pushop.bkresult == 2:
5830 5829 result = 2
5831 5830 elif not result and pushop.bkresult:
5832 5831 result = 2
5833 5832
5834 5833 if result:
5835 5834 break
5836 5835
5837 5836 finally:
5838 5837 other.close()
5839 5838 if result == 0 and not some_pushed:
5840 5839 result = 1
5841 5840 return result
5842 5841
5843 5842
5844 5843 @command(
5845 5844 b'recover',
5846 5845 [
5847 5846 (b'', b'verify', False, b"run `hg verify` after successful recover"),
5848 5847 ],
5849 5848 helpcategory=command.CATEGORY_MAINTENANCE,
5850 5849 )
5851 5850 def recover(ui, repo, **opts):
5852 5851 """roll back an interrupted transaction
5853 5852
5854 5853 Recover from an interrupted commit or pull.
5855 5854
5856 5855 This command tries to fix the repository status after an
5857 5856 interrupted operation. It should only be necessary when Mercurial
5858 5857 suggests it.
5859 5858
5860 5859 Returns 0 if successful, 1 if nothing to recover or verify fails.
5861 5860 """
5862 5861 ret = repo.recover()
5863 5862 if ret:
5864 5863 if opts['verify']:
5865 5864 return hg.verify(repo)
5866 5865 else:
5867 5866 msg = _(
5868 5867 b"(verify step skipped, run `hg verify` to check your "
5869 5868 b"repository content)\n"
5870 5869 )
5871 5870 ui.warn(msg)
5872 5871 return 0
5873 5872 return 1
5874 5873
5875 5874
5876 5875 @command(
5877 5876 b'remove|rm',
5878 5877 [
5879 5878 (b'A', b'after', None, _(b'record delete for missing files')),
5880 5879 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5881 5880 ]
5882 5881 + subrepoopts
5883 5882 + walkopts
5884 5883 + dryrunopts,
5885 5884 _(b'[OPTION]... FILE...'),
5886 5885 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5887 5886 helpbasic=True,
5888 5887 inferrepo=True,
5889 5888 )
5890 5889 def remove(ui, repo, *pats, **opts):
5891 5890 """remove the specified files on the next commit
5892 5891
5893 5892 Schedule the indicated files for removal from the current branch.
5894 5893
5895 5894 This command schedules the files to be removed at the next commit.
5896 5895 To undo a remove before that, see :hg:`revert`. To undo added
5897 5896 files, see :hg:`forget`.
5898 5897
5899 5898 .. container:: verbose
5900 5899
5901 5900 -A/--after can be used to remove only files that have already
5902 5901 been deleted, -f/--force can be used to force deletion, and -Af
5903 5902 can be used to remove files from the next revision without
5904 5903 deleting them from the working directory.
5905 5904
5906 5905 The following table details the behavior of remove for different
5907 5906 file states (columns) and option combinations (rows). The file
5908 5907 states are Added [A], Clean [C], Modified [M] and Missing [!]
5909 5908 (as reported by :hg:`status`). The actions are Warn, Remove
5910 5909 (from branch) and Delete (from disk):
5911 5910
5912 5911 ========= == == == ==
5913 5912 opt/state A C M !
5914 5913 ========= == == == ==
5915 5914 none W RD W R
5916 5915 -f R RD RD R
5917 5916 -A W W W R
5918 5917 -Af R R R R
5919 5918 ========= == == == ==
5920 5919
5921 5920 .. note::
5922 5921
5923 5922 :hg:`remove` never deletes files in Added [A] state from the
5924 5923 working directory, not even if ``--force`` is specified.
5925 5924
5926 5925 Returns 0 on success, 1 if any warnings encountered.
5927 5926 """
5928 5927
5929 5928 opts = pycompat.byteskwargs(opts)
5930 5929 after, force = opts.get(b'after'), opts.get(b'force')
5931 5930 dryrun = opts.get(b'dry_run')
5932 5931 if not pats and not after:
5933 5932 raise error.InputError(_(b'no files specified'))
5934 5933
5935 5934 m = scmutil.match(repo[None], pats, opts)
5936 5935 subrepos = opts.get(b'subrepos')
5937 5936 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5938 5937 return cmdutil.remove(
5939 5938 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5940 5939 )
5941 5940
5942 5941
5943 5942 @command(
5944 5943 b'rename|move|mv',
5945 5944 [
5946 5945 (b'', b'forget', None, _(b'unmark a destination file as renamed')),
5947 5946 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5948 5947 (
5949 5948 b'',
5950 5949 b'at-rev',
5951 5950 b'',
5952 5951 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5953 5952 _(b'REV'),
5954 5953 ),
5955 5954 (
5956 5955 b'f',
5957 5956 b'force',
5958 5957 None,
5959 5958 _(b'forcibly move over an existing managed file'),
5960 5959 ),
5961 5960 ]
5962 5961 + walkopts
5963 5962 + dryrunopts,
5964 5963 _(b'[OPTION]... SOURCE... DEST'),
5965 5964 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5966 5965 )
5967 5966 def rename(ui, repo, *pats, **opts):
5968 5967 """rename files; equivalent of copy + remove
5969 5968
5970 5969 Mark dest as copies of sources; mark sources for deletion. If dest
5971 5970 is a directory, copies are put in that directory. If dest is a
5972 5971 file, there can only be one source.
5973 5972
5974 5973 By default, this command copies the contents of files as they
5975 5974 exist in the working directory. If invoked with -A/--after, the
5976 5975 operation is recorded, but no copying is performed.
5977 5976
5978 5977 To undo marking a destination file as renamed, use --forget. With that
5979 5978 option, all given (positional) arguments are unmarked as renames. The
5980 5979 destination file(s) will be left in place (still tracked). The source
5981 5980 file(s) will not be restored. Note that :hg:`rename --forget` behaves
5982 5981 the same way as :hg:`copy --forget`.
5983 5982
5984 5983 This command takes effect with the next commit by default.
5985 5984
5986 5985 Returns 0 on success, 1 if errors are encountered.
5987 5986 """
5988 5987 opts = pycompat.byteskwargs(opts)
5989 5988 with repo.wlock():
5990 5989 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5991 5990
5992 5991
5993 5992 @command(
5994 5993 b'resolve',
5995 5994 [
5996 5995 (b'a', b'all', None, _(b'select all unresolved files')),
5997 5996 (b'l', b'list', None, _(b'list state of files needing merge')),
5998 5997 (b'm', b'mark', None, _(b'mark files as resolved')),
5999 5998 (b'u', b'unmark', None, _(b'mark files as unresolved')),
6000 5999 (b'n', b'no-status', None, _(b'hide status prefix')),
6001 6000 (b'', b're-merge', None, _(b're-merge files')),
6002 6001 ]
6003 6002 + mergetoolopts
6004 6003 + walkopts
6005 6004 + formatteropts,
6006 6005 _(b'[OPTION]... [FILE]...'),
6007 6006 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6008 6007 inferrepo=True,
6009 6008 )
6010 6009 def resolve(ui, repo, *pats, **opts):
6011 6010 """redo merges or set/view the merge status of files
6012 6011
6013 6012 Merges with unresolved conflicts are often the result of
6014 6013 non-interactive merging using the ``internal:merge`` configuration
6015 6014 setting, or a command-line merge tool like ``diff3``. The resolve
6016 6015 command is used to manage the files involved in a merge, after
6017 6016 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
6018 6017 working directory must have two parents). See :hg:`help
6019 6018 merge-tools` for information on configuring merge tools.
6020 6019
6021 6020 The resolve command can be used in the following ways:
6022 6021
6023 6022 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
6024 6023 the specified files, discarding any previous merge attempts. Re-merging
6025 6024 is not performed for files already marked as resolved. Use ``--all/-a``
6026 6025 to select all unresolved files. ``--tool`` can be used to specify
6027 6026 the merge tool used for the given files. It overrides the HGMERGE
6028 6027 environment variable and your configuration files. Previous file
6029 6028 contents are saved with a ``.orig`` suffix.
6030 6029
6031 6030 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
6032 6031 (e.g. after having manually fixed-up the files). The default is
6033 6032 to mark all unresolved files.
6034 6033
6035 6034 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
6036 6035 default is to mark all resolved files.
6037 6036
6038 6037 - :hg:`resolve -l`: list files which had or still have conflicts.
6039 6038 In the printed list, ``U`` = unresolved and ``R`` = resolved.
6040 6039 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
6041 6040 the list. See :hg:`help filesets` for details.
6042 6041
6043 6042 .. note::
6044 6043
6045 6044 Mercurial will not let you commit files with unresolved merge
6046 6045 conflicts. You must use :hg:`resolve -m ...` before you can
6047 6046 commit after a conflicting merge.
6048 6047
6049 6048 .. container:: verbose
6050 6049
6051 6050 Template:
6052 6051
6053 6052 The following keywords are supported in addition to the common template
6054 6053 keywords and functions. See also :hg:`help templates`.
6055 6054
6056 6055 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
6057 6056 :path: String. Repository-absolute path of the file.
6058 6057
6059 6058 Returns 0 on success, 1 if any files fail a resolve attempt.
6060 6059 """
6061 6060
6062 6061 opts = pycompat.byteskwargs(opts)
6063 6062 confirm = ui.configbool(b'commands', b'resolve.confirm')
6064 6063 flaglist = b'all mark unmark list no_status re_merge'.split()
6065 6064 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
6066 6065
6067 6066 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
6068 6067 if actioncount > 1:
6069 6068 raise error.InputError(_(b"too many actions specified"))
6070 6069 elif actioncount == 0 and ui.configbool(
6071 6070 b'commands', b'resolve.explicit-re-merge'
6072 6071 ):
6073 6072 hint = _(b'use --mark, --unmark, --list or --re-merge')
6074 6073 raise error.InputError(_(b'no action specified'), hint=hint)
6075 6074 if pats and all:
6076 6075 raise error.InputError(_(b"can't specify --all and patterns"))
6077 6076 if not (all or pats or show or mark or unmark):
6078 6077 raise error.InputError(
6079 6078 _(b'no files or directories specified'),
6080 6079 hint=b'use --all to re-merge all unresolved files',
6081 6080 )
6082 6081
6083 6082 if confirm:
6084 6083 if all:
6085 6084 if ui.promptchoice(
6086 6085 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
6087 6086 ):
6088 6087 raise error.CanceledError(_(b'user quit'))
6089 6088 if mark and not pats:
6090 6089 if ui.promptchoice(
6091 6090 _(
6092 6091 b'mark all unresolved files as resolved (yn)?'
6093 6092 b'$$ &Yes $$ &No'
6094 6093 )
6095 6094 ):
6096 6095 raise error.CanceledError(_(b'user quit'))
6097 6096 if unmark and not pats:
6098 6097 if ui.promptchoice(
6099 6098 _(
6100 6099 b'mark all resolved files as unresolved (yn)?'
6101 6100 b'$$ &Yes $$ &No'
6102 6101 )
6103 6102 ):
6104 6103 raise error.CanceledError(_(b'user quit'))
6105 6104
6106 6105 uipathfn = scmutil.getuipathfn(repo)
6107 6106
6108 6107 if show:
6109 6108 ui.pager(b'resolve')
6110 6109 fm = ui.formatter(b'resolve', opts)
6111 6110 ms = mergestatemod.mergestate.read(repo)
6112 6111 wctx = repo[None]
6113 6112 m = scmutil.match(wctx, pats, opts)
6114 6113
6115 6114 # Labels and keys based on merge state. Unresolved path conflicts show
6116 6115 # as 'P'. Resolved path conflicts show as 'R', the same as normal
6117 6116 # resolved conflicts.
6118 6117 mergestateinfo = {
6119 6118 mergestatemod.MERGE_RECORD_UNRESOLVED: (
6120 6119 b'resolve.unresolved',
6121 6120 b'U',
6122 6121 ),
6123 6122 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
6124 6123 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
6125 6124 b'resolve.unresolved',
6126 6125 b'P',
6127 6126 ),
6128 6127 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
6129 6128 b'resolve.resolved',
6130 6129 b'R',
6131 6130 ),
6132 6131 }
6133 6132
6134 6133 for f in ms:
6135 6134 if not m(f):
6136 6135 continue
6137 6136
6138 6137 label, key = mergestateinfo[ms[f]]
6139 6138 fm.startitem()
6140 6139 fm.context(ctx=wctx)
6141 6140 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
6142 6141 fm.data(path=f)
6143 6142 fm.plain(b'%s\n' % uipathfn(f), label=label)
6144 6143 fm.end()
6145 6144 return 0
6146 6145
6147 6146 with repo.wlock():
6148 6147 ms = mergestatemod.mergestate.read(repo)
6149 6148
6150 6149 if not (ms.active() or repo.dirstate.p2() != repo.nullid):
6151 6150 raise error.StateError(
6152 6151 _(b'resolve command not applicable when not merging')
6153 6152 )
6154 6153
6155 6154 wctx = repo[None]
6156 6155 m = scmutil.match(wctx, pats, opts)
6157 6156 ret = 0
6158 6157 didwork = False
6159 6158
6160 6159 hasconflictmarkers = []
6161 6160 if mark:
6162 6161 markcheck = ui.config(b'commands', b'resolve.mark-check')
6163 6162 if markcheck not in [b'warn', b'abort']:
6164 6163 # Treat all invalid / unrecognized values as 'none'.
6165 6164 markcheck = False
6166 6165 for f in ms:
6167 6166 if not m(f):
6168 6167 continue
6169 6168
6170 6169 didwork = True
6171 6170
6172 6171 # path conflicts must be resolved manually
6173 6172 if ms[f] in (
6174 6173 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6175 6174 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6176 6175 ):
6177 6176 if mark:
6178 6177 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6179 6178 elif unmark:
6180 6179 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6181 6180 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6182 6181 ui.warn(
6183 6182 _(b'%s: path conflict must be resolved manually\n')
6184 6183 % uipathfn(f)
6185 6184 )
6186 6185 continue
6187 6186
6188 6187 if mark:
6189 6188 if markcheck:
6190 6189 fdata = repo.wvfs.tryread(f)
6191 6190 if (
6192 6191 filemerge.hasconflictmarkers(fdata)
6193 6192 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6194 6193 ):
6195 6194 hasconflictmarkers.append(f)
6196 6195 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6197 6196 elif unmark:
6198 6197 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6199 6198 else:
6200 6199 # backup pre-resolve (merge uses .orig for its own purposes)
6201 6200 a = repo.wjoin(f)
6202 6201 try:
6203 6202 util.copyfile(a, a + b".resolve")
6204 6203 except FileNotFoundError:
6205 6204 pass
6206 6205
6207 6206 try:
6208 6207 # preresolve file
6209 6208 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6210 6209 with ui.configoverride(overrides, b'resolve'):
6211 6210 r = ms.resolve(f, wctx)
6212 6211 if r:
6213 6212 ret = 1
6214 6213 finally:
6215 6214 ms.commit()
6216 6215
6217 6216 # replace filemerge's .orig file with our resolve file
6218 6217 try:
6219 6218 util.rename(
6220 6219 a + b".resolve", scmutil.backuppath(ui, repo, f)
6221 6220 )
6222 6221 except FileNotFoundError:
6223 6222 pass
6224 6223
6225 6224 if hasconflictmarkers:
6226 6225 ui.warn(
6227 6226 _(
6228 6227 b'warning: the following files still have conflict '
6229 6228 b'markers:\n'
6230 6229 )
6231 6230 + b''.join(
6232 6231 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6233 6232 )
6234 6233 )
6235 6234 if markcheck == b'abort' and not all and not pats:
6236 6235 raise error.StateError(
6237 6236 _(b'conflict markers detected'),
6238 6237 hint=_(b'use --all to mark anyway'),
6239 6238 )
6240 6239
6241 6240 ms.commit()
6242 6241 branchmerge = repo.dirstate.p2() != repo.nullid
6243 6242 # resolve is not doing a parent change here, however, `record updates`
6244 6243 # will call some dirstate API that at intended for parent changes call.
6245 6244 # Ideally we would not need this and could implement a lighter version
6246 6245 # of the recordupdateslogic that will not have to deal with the part
6247 6246 # related to parent changes. However this would requires that:
6248 6247 # - we are sure we passed around enough information at update/merge
6249 6248 # time to no longer needs it at `hg resolve time`
6250 6249 # - we are sure we store that information well enough to be able to reuse it
6251 6250 # - we are the necessary logic to reuse it right.
6252 6251 #
6253 6252 # All this should eventually happens, but in the mean time, we use this
6254 6253 # context manager slightly out of the context it should be.
6255 6254 with repo.dirstate.parentchange():
6256 6255 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6257 6256
6258 6257 if not didwork and pats:
6259 6258 hint = None
6260 6259 if not any([p for p in pats if p.find(b':') >= 0]):
6261 6260 pats = [b'path:%s' % p for p in pats]
6262 6261 m = scmutil.match(wctx, pats, opts)
6263 6262 for f in ms:
6264 6263 if not m(f):
6265 6264 continue
6266 6265
6267 6266 def flag(o):
6268 6267 if o == b're_merge':
6269 6268 return b'--re-merge '
6270 6269 return b'-%s ' % o[0:1]
6271 6270
6272 6271 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6273 6272 hint = _(b"(try: hg resolve %s%s)\n") % (
6274 6273 flags,
6275 6274 b' '.join(pats),
6276 6275 )
6277 6276 break
6278 6277 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6279 6278 if hint:
6280 6279 ui.warn(hint)
6281 6280
6282 6281 unresolvedf = ms.unresolvedcount()
6283 6282 if not unresolvedf:
6284 6283 ui.status(_(b'(no more unresolved files)\n'))
6285 6284 cmdutil.checkafterresolved(repo)
6286 6285
6287 6286 return ret
6288 6287
6289 6288
6290 6289 @command(
6291 6290 b'revert',
6292 6291 [
6293 6292 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6294 6293 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6295 6294 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6296 6295 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6297 6296 (b'i', b'interactive', None, _(b'interactively select the changes')),
6298 6297 ]
6299 6298 + walkopts
6300 6299 + dryrunopts,
6301 6300 _(b'[OPTION]... [-r REV] [NAME]...'),
6302 6301 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6303 6302 )
6304 6303 def revert(ui, repo, *pats, **opts):
6305 6304 """restore files to their checkout state
6306 6305
6307 6306 .. note::
6308 6307
6309 6308 To check out earlier revisions, you should use :hg:`update REV`.
6310 6309 To cancel an uncommitted merge (and lose your changes),
6311 6310 use :hg:`merge --abort`.
6312 6311
6313 6312 With no revision specified, revert the specified files or directories
6314 6313 to the contents they had in the parent of the working directory.
6315 6314 This restores the contents of files to an unmodified
6316 6315 state and unschedules adds, removes, copies, and renames. If the
6317 6316 working directory has two parents, you must explicitly specify a
6318 6317 revision.
6319 6318
6320 6319 Using the -r/--rev or -d/--date options, revert the given files or
6321 6320 directories to their states as of a specific revision. Because
6322 6321 revert does not change the working directory parents, this will
6323 6322 cause these files to appear modified. This can be helpful to "back
6324 6323 out" some or all of an earlier change. See :hg:`backout` for a
6325 6324 related method.
6326 6325
6327 6326 Modified files are saved with a .orig suffix before reverting.
6328 6327 To disable these backups, use --no-backup. It is possible to store
6329 6328 the backup files in a custom directory relative to the root of the
6330 6329 repository by setting the ``ui.origbackuppath`` configuration
6331 6330 option.
6332 6331
6333 6332 See :hg:`help dates` for a list of formats valid for -d/--date.
6334 6333
6335 6334 See :hg:`help backout` for a way to reverse the effect of an
6336 6335 earlier changeset.
6337 6336
6338 6337 Returns 0 on success.
6339 6338 """
6340 6339
6341 6340 opts = pycompat.byteskwargs(opts)
6342 6341 if opts.get(b"date"):
6343 6342 cmdutil.check_incompatible_arguments(opts, b'date', [b'rev'])
6344 6343 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6345 6344
6346 6345 parent, p2 = repo.dirstate.parents()
6347 6346 if not opts.get(b'rev') and p2 != repo.nullid:
6348 6347 # revert after merge is a trap for new users (issue2915)
6349 6348 raise error.InputError(
6350 6349 _(b'uncommitted merge with no revision specified'),
6351 6350 hint=_(b"use 'hg update' or see 'hg help revert'"),
6352 6351 )
6353 6352
6354 6353 rev = opts.get(b'rev')
6355 6354 if rev:
6356 6355 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6357 6356 ctx = logcmdutil.revsingle(repo, rev)
6358 6357
6359 6358 if not (
6360 6359 pats
6361 6360 or opts.get(b'include')
6362 6361 or opts.get(b'exclude')
6363 6362 or opts.get(b'all')
6364 6363 or opts.get(b'interactive')
6365 6364 ):
6366 6365 msg = _(b"no files or directories specified")
6367 6366 if p2 != repo.nullid:
6368 6367 hint = _(
6369 6368 b"uncommitted merge, use --all to discard all changes,"
6370 6369 b" or 'hg update -C .' to abort the merge"
6371 6370 )
6372 6371 raise error.InputError(msg, hint=hint)
6373 6372 dirty = any(repo.status())
6374 6373 node = ctx.node()
6375 6374 if node != parent:
6376 6375 if dirty:
6377 6376 hint = (
6378 6377 _(
6379 6378 b"uncommitted changes, use --all to discard all"
6380 6379 b" changes, or 'hg update %d' to update"
6381 6380 )
6382 6381 % ctx.rev()
6383 6382 )
6384 6383 else:
6385 6384 hint = (
6386 6385 _(
6387 6386 b"use --all to revert all files,"
6388 6387 b" or 'hg update %d' to update"
6389 6388 )
6390 6389 % ctx.rev()
6391 6390 )
6392 6391 elif dirty:
6393 6392 hint = _(b"uncommitted changes, use --all to discard all changes")
6394 6393 else:
6395 6394 hint = _(b"use --all to revert all files")
6396 6395 raise error.InputError(msg, hint=hint)
6397 6396
6398 6397 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6399 6398
6400 6399
6401 6400 @command(
6402 6401 b'rollback',
6403 6402 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6404 6403 helpcategory=command.CATEGORY_MAINTENANCE,
6405 6404 )
6406 6405 def rollback(ui, repo, **opts):
6407 6406 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6408 6407
6409 6408 Please use :hg:`commit --amend` instead of rollback to correct
6410 6409 mistakes in the last commit.
6411 6410
6412 6411 This command should be used with care. There is only one level of
6413 6412 rollback, and there is no way to undo a rollback. It will also
6414 6413 restore the dirstate at the time of the last transaction, losing
6415 6414 any dirstate changes since that time. This command does not alter
6416 6415 the working directory.
6417 6416
6418 6417 Transactions are used to encapsulate the effects of all commands
6419 6418 that create new changesets or propagate existing changesets into a
6420 6419 repository.
6421 6420
6422 6421 .. container:: verbose
6423 6422
6424 6423 For example, the following commands are transactional, and their
6425 6424 effects can be rolled back:
6426 6425
6427 6426 - commit
6428 6427 - import
6429 6428 - pull
6430 6429 - push (with this repository as the destination)
6431 6430 - unbundle
6432 6431
6433 6432 To avoid permanent data loss, rollback will refuse to rollback a
6434 6433 commit transaction if it isn't checked out. Use --force to
6435 6434 override this protection.
6436 6435
6437 6436 The rollback command can be entirely disabled by setting the
6438 6437 ``ui.rollback`` configuration setting to false. If you're here
6439 6438 because you want to use rollback and it's disabled, you can
6440 6439 re-enable the command by setting ``ui.rollback`` to true.
6441 6440
6442 6441 This command is not intended for use on public repositories. Once
6443 6442 changes are visible for pull by other users, rolling a transaction
6444 6443 back locally is ineffective (someone else may already have pulled
6445 6444 the changes). Furthermore, a race is possible with readers of the
6446 6445 repository; for example an in-progress pull from the repository
6447 6446 may fail if a rollback is performed.
6448 6447
6449 6448 Returns 0 on success, 1 if no rollback data is available.
6450 6449 """
6451 6450 if not ui.configbool(b'ui', b'rollback'):
6452 6451 raise error.Abort(
6453 6452 _(b'rollback is disabled because it is unsafe'),
6454 6453 hint=b'see `hg help -v rollback` for information',
6455 6454 )
6456 6455 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6457 6456
6458 6457
6459 6458 @command(
6460 6459 b'root',
6461 6460 [] + formatteropts,
6462 6461 intents={INTENT_READONLY},
6463 6462 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6464 6463 )
6465 6464 def root(ui, repo, **opts):
6466 6465 """print the root (top) of the current working directory
6467 6466
6468 6467 Print the root directory of the current repository.
6469 6468
6470 6469 .. container:: verbose
6471 6470
6472 6471 Template:
6473 6472
6474 6473 The following keywords are supported in addition to the common template
6475 6474 keywords and functions. See also :hg:`help templates`.
6476 6475
6477 6476 :hgpath: String. Path to the .hg directory.
6478 6477 :storepath: String. Path to the directory holding versioned data.
6479 6478
6480 6479 Returns 0 on success.
6481 6480 """
6482 6481 opts = pycompat.byteskwargs(opts)
6483 6482 with ui.formatter(b'root', opts) as fm:
6484 6483 fm.startitem()
6485 6484 fm.write(b'reporoot', b'%s\n', repo.root)
6486 6485 fm.data(hgpath=repo.path, storepath=repo.spath)
6487 6486
6488 6487
6489 6488 @command(
6490 6489 b'serve',
6491 6490 [
6492 6491 (
6493 6492 b'A',
6494 6493 b'accesslog',
6495 6494 b'',
6496 6495 _(b'name of access log file to write to'),
6497 6496 _(b'FILE'),
6498 6497 ),
6499 6498 (b'd', b'daemon', None, _(b'run server in background')),
6500 6499 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6501 6500 (
6502 6501 b'E',
6503 6502 b'errorlog',
6504 6503 b'',
6505 6504 _(b'name of error log file to write to'),
6506 6505 _(b'FILE'),
6507 6506 ),
6508 6507 # use string type, then we can check if something was passed
6509 6508 (
6510 6509 b'p',
6511 6510 b'port',
6512 6511 b'',
6513 6512 _(b'port to listen on (default: 8000)'),
6514 6513 _(b'PORT'),
6515 6514 ),
6516 6515 (
6517 6516 b'a',
6518 6517 b'address',
6519 6518 b'',
6520 6519 _(b'address to listen on (default: all interfaces)'),
6521 6520 _(b'ADDR'),
6522 6521 ),
6523 6522 (
6524 6523 b'',
6525 6524 b'prefix',
6526 6525 b'',
6527 6526 _(b'prefix path to serve from (default: server root)'),
6528 6527 _(b'PREFIX'),
6529 6528 ),
6530 6529 (
6531 6530 b'n',
6532 6531 b'name',
6533 6532 b'',
6534 6533 _(b'name to show in web pages (default: working directory)'),
6535 6534 _(b'NAME'),
6536 6535 ),
6537 6536 (
6538 6537 b'',
6539 6538 b'web-conf',
6540 6539 b'',
6541 6540 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6542 6541 _(b'FILE'),
6543 6542 ),
6544 6543 (
6545 6544 b'',
6546 6545 b'webdir-conf',
6547 6546 b'',
6548 6547 _(b'name of the hgweb config file (DEPRECATED)'),
6549 6548 _(b'FILE'),
6550 6549 ),
6551 6550 (
6552 6551 b'',
6553 6552 b'pid-file',
6554 6553 b'',
6555 6554 _(b'name of file to write process ID to'),
6556 6555 _(b'FILE'),
6557 6556 ),
6558 6557 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6559 6558 (
6560 6559 b'',
6561 6560 b'cmdserver',
6562 6561 b'',
6563 6562 _(b'for remote clients (ADVANCED)'),
6564 6563 _(b'MODE'),
6565 6564 ),
6566 6565 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6567 6566 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6568 6567 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6569 6568 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6570 6569 (b'', b'print-url', None, _(b'start and print only the URL')),
6571 6570 ]
6572 6571 + subrepoopts,
6573 6572 _(b'[OPTION]...'),
6574 6573 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6575 6574 helpbasic=True,
6576 6575 optionalrepo=True,
6577 6576 )
6578 6577 def serve(ui, repo, **opts):
6579 6578 """start stand-alone webserver
6580 6579
6581 6580 Start a local HTTP repository browser and pull server. You can use
6582 6581 this for ad-hoc sharing and browsing of repositories. It is
6583 6582 recommended to use a real web server to serve a repository for
6584 6583 longer periods of time.
6585 6584
6586 6585 Please note that the server does not implement access control.
6587 6586 This means that, by default, anybody can read from the server and
6588 6587 nobody can write to it by default. Set the ``web.allow-push``
6589 6588 option to ``*`` to allow everybody to push to the server. You
6590 6589 should use a real web server if you need to authenticate users.
6591 6590
6592 6591 By default, the server logs accesses to stdout and errors to
6593 6592 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6594 6593 files.
6595 6594
6596 6595 To have the server choose a free port number to listen on, specify
6597 6596 a port number of 0; in this case, the server will print the port
6598 6597 number it uses.
6599 6598
6600 6599 Returns 0 on success.
6601 6600 """
6602 6601
6603 6602 cmdutil.check_incompatible_arguments(opts, 'stdio', ['cmdserver'])
6604 6603 opts = pycompat.byteskwargs(opts)
6605 6604 if opts[b"print_url"] and ui.verbose:
6606 6605 raise error.InputError(_(b"cannot use --print-url with --verbose"))
6607 6606
6608 6607 if opts[b"stdio"]:
6609 6608 if repo is None:
6610 6609 raise error.RepoError(
6611 6610 _(b"there is no Mercurial repository here (.hg not found)")
6612 6611 )
6613 6612 s = wireprotoserver.sshserver(ui, repo)
6614 6613 s.serve_forever()
6615 6614 return
6616 6615
6617 6616 service = server.createservice(ui, repo, opts)
6618 6617 return server.runservice(opts, initfn=service.init, runfn=service.run)
6619 6618
6620 6619
6621 6620 @command(
6622 6621 b'shelve',
6623 6622 [
6624 6623 (
6625 6624 b'A',
6626 6625 b'addremove',
6627 6626 None,
6628 6627 _(b'mark new/missing files as added/removed before shelving'),
6629 6628 ),
6630 6629 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6631 6630 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6632 6631 (
6633 6632 b'',
6634 6633 b'date',
6635 6634 b'',
6636 6635 _(b'shelve with the specified commit date'),
6637 6636 _(b'DATE'),
6638 6637 ),
6639 6638 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6640 6639 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6641 6640 (
6642 6641 b'k',
6643 6642 b'keep',
6644 6643 False,
6645 6644 _(b'shelve, but keep changes in the working directory'),
6646 6645 ),
6647 6646 (b'l', b'list', None, _(b'list current shelves')),
6648 6647 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6649 6648 (
6650 6649 b'n',
6651 6650 b'name',
6652 6651 b'',
6653 6652 _(b'use the given name for the shelved commit'),
6654 6653 _(b'NAME'),
6655 6654 ),
6656 6655 (
6657 6656 b'p',
6658 6657 b'patch',
6659 6658 None,
6660 6659 _(
6661 6660 b'output patches for changes (provide the names of the shelved '
6662 6661 b'changes as positional arguments)'
6663 6662 ),
6664 6663 ),
6665 6664 (b'i', b'interactive', None, _(b'interactive mode')),
6666 6665 (
6667 6666 b'',
6668 6667 b'stat',
6669 6668 None,
6670 6669 _(
6671 6670 b'output diffstat-style summary of changes (provide the names of '
6672 6671 b'the shelved changes as positional arguments)'
6673 6672 ),
6674 6673 ),
6675 6674 ]
6676 6675 + cmdutil.walkopts,
6677 6676 _(b'hg shelve [OPTION]... [FILE]...'),
6678 6677 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6679 6678 )
6680 6679 def shelve(ui, repo, *pats, **opts):
6681 6680 """save and set aside changes from the working directory
6682 6681
6683 6682 Shelving takes files that "hg status" reports as not clean, saves
6684 6683 the modifications to a bundle (a shelved change), and reverts the
6685 6684 files so that their state in the working directory becomes clean.
6686 6685
6687 6686 To restore these changes to the working directory, using "hg
6688 6687 unshelve"; this will work even if you switch to a different
6689 6688 commit.
6690 6689
6691 6690 When no files are specified, "hg shelve" saves all not-clean
6692 6691 files. If specific files or directories are named, only changes to
6693 6692 those files are shelved.
6694 6693
6695 6694 In bare shelve (when no files are specified, without interactive,
6696 6695 include and exclude option), shelving remembers information if the
6697 6696 working directory was on newly created branch, in other words working
6698 6697 directory was on different branch than its first parent. In this
6699 6698 situation unshelving restores branch information to the working directory.
6700 6699
6701 6700 Each shelved change has a name that makes it easier to find later.
6702 6701 The name of a shelved change defaults to being based on the active
6703 6702 bookmark, or if there is no active bookmark, the current named
6704 6703 branch. To specify a different name, use ``--name``.
6705 6704
6706 6705 To see a list of existing shelved changes, use the ``--list``
6707 6706 option. For each shelved change, this will print its name, age,
6708 6707 and description; use ``--patch`` or ``--stat`` for more details.
6709 6708
6710 6709 To delete specific shelved changes, use ``--delete``. To delete
6711 6710 all shelved changes, use ``--cleanup``.
6712 6711 """
6713 6712 opts = pycompat.byteskwargs(opts)
6714 6713 allowables = [
6715 6714 (b'addremove', {b'create'}), # 'create' is pseudo action
6716 6715 (b'unknown', {b'create'}),
6717 6716 (b'cleanup', {b'cleanup'}),
6718 6717 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6719 6718 (b'delete', {b'delete'}),
6720 6719 (b'edit', {b'create'}),
6721 6720 (b'keep', {b'create'}),
6722 6721 (b'list', {b'list'}),
6723 6722 (b'message', {b'create'}),
6724 6723 (b'name', {b'create'}),
6725 6724 (b'patch', {b'patch', b'list'}),
6726 6725 (b'stat', {b'stat', b'list'}),
6727 6726 ]
6728 6727
6729 6728 def checkopt(opt):
6730 6729 if opts.get(opt):
6731 6730 for i, allowable in allowables:
6732 6731 if opts[i] and opt not in allowable:
6733 6732 raise error.InputError(
6734 6733 _(
6735 6734 b"options '--%s' and '--%s' may not be "
6736 6735 b"used together"
6737 6736 )
6738 6737 % (opt, i)
6739 6738 )
6740 6739 return True
6741 6740
6742 6741 if checkopt(b'cleanup'):
6743 6742 if pats:
6744 6743 raise error.InputError(
6745 6744 _(b"cannot specify names when using '--cleanup'")
6746 6745 )
6747 6746 return shelvemod.cleanupcmd(ui, repo)
6748 6747 elif checkopt(b'delete'):
6749 6748 return shelvemod.deletecmd(ui, repo, pats)
6750 6749 elif checkopt(b'list'):
6751 6750 return shelvemod.listcmd(ui, repo, pats, opts)
6752 6751 elif checkopt(b'patch') or checkopt(b'stat'):
6753 6752 return shelvemod.patchcmds(ui, repo, pats, opts)
6754 6753 else:
6755 6754 return shelvemod.createcmd(ui, repo, pats, opts)
6756 6755
6757 6756
6758 6757 _NOTTERSE = b'nothing'
6759 6758
6760 6759
6761 6760 @command(
6762 6761 b'status|st',
6763 6762 [
6764 6763 (b'A', b'all', None, _(b'show status of all files')),
6765 6764 (b'm', b'modified', None, _(b'show only modified files')),
6766 6765 (b'a', b'added', None, _(b'show only added files')),
6767 6766 (b'r', b'removed', None, _(b'show only removed files')),
6768 6767 (b'd', b'deleted', None, _(b'show only missing files')),
6769 6768 (b'c', b'clean', None, _(b'show only files without changes')),
6770 6769 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6771 6770 (b'i', b'ignored', None, _(b'show only ignored files')),
6772 6771 (b'n', b'no-status', None, _(b'hide status prefix')),
6773 6772 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6774 6773 (
6775 6774 b'C',
6776 6775 b'copies',
6777 6776 None,
6778 6777 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6779 6778 ),
6780 6779 (
6781 6780 b'0',
6782 6781 b'print0',
6783 6782 None,
6784 6783 _(b'end filenames with NUL, for use with xargs'),
6785 6784 ),
6786 6785 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6787 6786 (
6788 6787 b'',
6789 6788 b'change',
6790 6789 b'',
6791 6790 _(b'list the changed files of a revision'),
6792 6791 _(b'REV'),
6793 6792 ),
6794 6793 ]
6795 6794 + walkopts
6796 6795 + subrepoopts
6797 6796 + formatteropts,
6798 6797 _(b'[OPTION]... [FILE]...'),
6799 6798 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6800 6799 helpbasic=True,
6801 6800 inferrepo=True,
6802 6801 intents={INTENT_READONLY},
6803 6802 )
6804 6803 def status(ui, repo, *pats, **opts):
6805 6804 """show changed files in the working directory
6806 6805
6807 6806 Show status of files in the repository. If names are given, only
6808 6807 files that match are shown. Files that are clean or ignored or
6809 6808 the source of a copy/move operation, are not listed unless
6810 6809 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6811 6810 Unless options described with "show only ..." are given, the
6812 6811 options -mardu are used.
6813 6812
6814 6813 Option -q/--quiet hides untracked (unknown and ignored) files
6815 6814 unless explicitly requested with -u/--unknown or -i/--ignored.
6816 6815
6817 6816 .. note::
6818 6817
6819 6818 :hg:`status` may appear to disagree with diff if permissions have
6820 6819 changed or a merge has occurred. The standard diff format does
6821 6820 not report permission changes and diff only reports changes
6822 6821 relative to one merge parent.
6823 6822
6824 6823 If one revision is given, it is used as the base revision.
6825 6824 If two revisions are given, the differences between them are
6826 6825 shown. The --change option can also be used as a shortcut to list
6827 6826 the changed files of a revision from its first parent.
6828 6827
6829 6828 The codes used to show the status of files are::
6830 6829
6831 6830 M = modified
6832 6831 A = added
6833 6832 R = removed
6834 6833 C = clean
6835 6834 ! = missing (deleted by non-hg command, but still tracked)
6836 6835 ? = not tracked
6837 6836 I = ignored
6838 6837 = origin of the previous file (with --copies)
6839 6838
6840 6839 .. container:: verbose
6841 6840
6842 6841 The -t/--terse option abbreviates the output by showing only the directory
6843 6842 name if all the files in it share the same status. The option takes an
6844 6843 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6845 6844 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6846 6845 for 'ignored' and 'c' for clean.
6847 6846
6848 6847 It abbreviates only those statuses which are passed. Note that clean and
6849 6848 ignored files are not displayed with '--terse ic' unless the -c/--clean
6850 6849 and -i/--ignored options are also used.
6851 6850
6852 6851 The -v/--verbose option shows information when the repository is in an
6853 6852 unfinished merge, shelve, rebase state etc. You can have this behavior
6854 6853 turned on by default by enabling the ``commands.status.verbose`` option.
6855 6854
6856 6855 You can skip displaying some of these states by setting
6857 6856 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6858 6857 'histedit', 'merge', 'rebase', or 'unshelve'.
6859 6858
6860 6859 Template:
6861 6860
6862 6861 The following keywords are supported in addition to the common template
6863 6862 keywords and functions. See also :hg:`help templates`.
6864 6863
6865 6864 :path: String. Repository-absolute path of the file.
6866 6865 :source: String. Repository-absolute path of the file originated from.
6867 6866 Available if ``--copies`` is specified.
6868 6867 :status: String. Character denoting file's status.
6869 6868
6870 6869 Examples:
6871 6870
6872 6871 - show changes in the working directory relative to a
6873 6872 changeset::
6874 6873
6875 6874 hg status --rev 9353
6876 6875
6877 6876 - show changes in the working directory relative to the
6878 6877 current directory (see :hg:`help patterns` for more information)::
6879 6878
6880 6879 hg status re:
6881 6880
6882 6881 - show all changes including copies in an existing changeset::
6883 6882
6884 6883 hg status --copies --change 9353
6885 6884
6886 6885 - get a NUL separated list of added files, suitable for xargs::
6887 6886
6888 6887 hg status -an0
6889 6888
6890 6889 - show more information about the repository status, abbreviating
6891 6890 added, removed, modified, deleted, and untracked paths::
6892 6891
6893 6892 hg status -v -t mardu
6894 6893
6895 6894 Returns 0 on success.
6896 6895
6897 6896 """
6898 6897
6899 6898 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6900 6899 opts = pycompat.byteskwargs(opts)
6901 6900 revs = opts.get(b'rev', [])
6902 6901 change = opts.get(b'change', b'')
6903 6902 terse = opts.get(b'terse', _NOTTERSE)
6904 6903 if terse is _NOTTERSE:
6905 6904 if revs:
6906 6905 terse = b''
6907 6906 else:
6908 6907 terse = ui.config(b'commands', b'status.terse')
6909 6908
6910 6909 if revs and terse:
6911 6910 msg = _(b'cannot use --terse with --rev')
6912 6911 raise error.InputError(msg)
6913 6912 elif change:
6914 6913 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6915 6914 ctx2 = logcmdutil.revsingle(repo, change, None)
6916 6915 ctx1 = ctx2.p1()
6917 6916 else:
6918 6917 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6919 6918 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
6920 6919
6921 6920 forcerelativevalue = None
6922 6921 if ui.hasconfig(b'commands', b'status.relative'):
6923 6922 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6924 6923 uipathfn = scmutil.getuipathfn(
6925 6924 repo,
6926 6925 legacyrelativevalue=bool(pats),
6927 6926 forcerelativevalue=forcerelativevalue,
6928 6927 )
6929 6928
6930 6929 if opts.get(b'print0'):
6931 6930 end = b'\0'
6932 6931 else:
6933 6932 end = b'\n'
6934 6933 states = b'modified added removed deleted unknown ignored clean'.split()
6935 6934 show = [k for k in states if opts.get(k)]
6936 6935 if opts.get(b'all'):
6937 6936 show += ui.quiet and (states[:4] + [b'clean']) or states
6938 6937
6939 6938 if not show:
6940 6939 if ui.quiet:
6941 6940 show = states[:4]
6942 6941 else:
6943 6942 show = states[:5]
6944 6943
6945 6944 m = scmutil.match(ctx2, pats, opts)
6946 6945 if terse:
6947 6946 # we need to compute clean and unknown to terse
6948 6947 stat = repo.status(
6949 6948 ctx1.node(),
6950 6949 ctx2.node(),
6951 6950 m,
6952 6951 b'ignored' in show or b'i' in terse,
6953 6952 clean=True,
6954 6953 unknown=True,
6955 6954 listsubrepos=opts.get(b'subrepos'),
6956 6955 )
6957 6956
6958 6957 stat = cmdutil.tersedir(stat, terse)
6959 6958 else:
6960 6959 stat = repo.status(
6961 6960 ctx1.node(),
6962 6961 ctx2.node(),
6963 6962 m,
6964 6963 b'ignored' in show,
6965 6964 b'clean' in show,
6966 6965 b'unknown' in show,
6967 6966 opts.get(b'subrepos'),
6968 6967 )
6969 6968
6970 6969 changestates = zip(
6971 6970 states,
6972 6971 pycompat.iterbytestr(b'MAR!?IC'),
6973 6972 [getattr(stat, s.decode('utf8')) for s in states],
6974 6973 )
6975 6974
6976 6975 copy = {}
6977 6976 if (
6978 6977 opts.get(b'all')
6979 6978 or opts.get(b'copies')
6980 6979 or ui.configbool(b'ui', b'statuscopies')
6981 6980 ) and not opts.get(b'no_status'):
6982 6981 copy = copies.pathcopies(ctx1, ctx2, m)
6983 6982
6984 6983 morestatus = None
6985 6984 if (
6986 6985 (ui.verbose or ui.configbool(b'commands', b'status.verbose'))
6987 6986 and not ui.plain()
6988 6987 and not opts.get(b'print0')
6989 6988 ):
6990 6989 morestatus = cmdutil.readmorestatus(repo)
6991 6990
6992 6991 ui.pager(b'status')
6993 6992 fm = ui.formatter(b'status', opts)
6994 6993 fmt = b'%s' + end
6995 6994 showchar = not opts.get(b'no_status')
6996 6995
6997 6996 for state, char, files in changestates:
6998 6997 if state in show:
6999 6998 label = b'status.' + state
7000 6999 for f in files:
7001 7000 fm.startitem()
7002 7001 fm.context(ctx=ctx2)
7003 7002 fm.data(itemtype=b'file', path=f)
7004 7003 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
7005 7004 fm.plain(fmt % uipathfn(f), label=label)
7006 7005 if f in copy:
7007 7006 fm.data(source=copy[f])
7008 7007 fm.plain(
7009 7008 (b' %s' + end) % uipathfn(copy[f]),
7010 7009 label=b'status.copied',
7011 7010 )
7012 7011 if morestatus:
7013 7012 morestatus.formatfile(f, fm)
7014 7013
7015 7014 if morestatus:
7016 7015 morestatus.formatfooter(fm)
7017 7016 fm.end()
7018 7017
7019 7018
7020 7019 @command(
7021 7020 b'summary|sum',
7022 7021 [(b'', b'remote', None, _(b'check for push and pull'))],
7023 7022 b'[--remote]',
7024 7023 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7025 7024 helpbasic=True,
7026 7025 intents={INTENT_READONLY},
7027 7026 )
7028 7027 def summary(ui, repo, **opts):
7029 7028 """summarize working directory state
7030 7029
7031 7030 This generates a brief summary of the working directory state,
7032 7031 including parents, branch, commit status, phase and available updates.
7033 7032
7034 7033 With the --remote option, this will check the default paths for
7035 7034 incoming and outgoing changes. This can be time-consuming.
7036 7035
7037 7036 Returns 0 on success.
7038 7037 """
7039 7038
7040 7039 opts = pycompat.byteskwargs(opts)
7041 7040 ui.pager(b'summary')
7042 7041 ctx = repo[None]
7043 7042 parents = ctx.parents()
7044 7043 pnode = parents[0].node()
7045 7044 marks = []
7046 7045
7047 7046 try:
7048 7047 ms = mergestatemod.mergestate.read(repo)
7049 7048 except error.UnsupportedMergeRecords as e:
7050 7049 s = b' '.join(e.recordtypes)
7051 7050 ui.warn(
7052 7051 _(b'warning: merge state has unsupported record types: %s\n') % s
7053 7052 )
7054 7053 unresolved = []
7055 7054 else:
7056 7055 unresolved = list(ms.unresolved())
7057 7056
7058 7057 for p in parents:
7059 7058 # label with log.changeset (instead of log.parent) since this
7060 7059 # shows a working directory parent *changeset*:
7061 7060 # i18n: column positioning for "hg summary"
7062 7061 ui.write(
7063 7062 _(b'parent: %d:%s ') % (p.rev(), p),
7064 7063 label=logcmdutil.changesetlabels(p),
7065 7064 )
7066 7065 ui.write(b' '.join(p.tags()), label=b'log.tag')
7067 7066 if p.bookmarks():
7068 7067 marks.extend(p.bookmarks())
7069 7068 if p.rev() == -1:
7070 7069 if not len(repo):
7071 7070 ui.write(_(b' (empty repository)'))
7072 7071 else:
7073 7072 ui.write(_(b' (no revision checked out)'))
7074 7073 if p.obsolete():
7075 7074 ui.write(_(b' (obsolete)'))
7076 7075 if p.isunstable():
7077 7076 instabilities = (
7078 7077 ui.label(instability, b'trouble.%s' % instability)
7079 7078 for instability in p.instabilities()
7080 7079 )
7081 7080 ui.write(b' (' + b', '.join(instabilities) + b')')
7082 7081 ui.write(b'\n')
7083 7082 if p.description():
7084 7083 ui.status(
7085 7084 b' ' + p.description().splitlines()[0].strip() + b'\n',
7086 7085 label=b'log.summary',
7087 7086 )
7088 7087
7089 7088 branch = ctx.branch()
7090 7089 bheads = repo.branchheads(branch)
7091 7090 # i18n: column positioning for "hg summary"
7092 7091 m = _(b'branch: %s\n') % branch
7093 7092 if branch != b'default':
7094 7093 ui.write(m, label=b'log.branch')
7095 7094 else:
7096 7095 ui.status(m, label=b'log.branch')
7097 7096
7098 7097 if marks:
7099 7098 active = repo._activebookmark
7100 7099 # i18n: column positioning for "hg summary"
7101 7100 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
7102 7101 if active is not None:
7103 7102 if active in marks:
7104 7103 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
7105 7104 marks.remove(active)
7106 7105 else:
7107 7106 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
7108 7107 for m in marks:
7109 7108 ui.write(b' ' + m, label=b'log.bookmark')
7110 7109 ui.write(b'\n', label=b'log.bookmark')
7111 7110
7112 7111 status = repo.status(unknown=True)
7113 7112
7114 7113 c = repo.dirstate.copies()
7115 7114 copied, renamed = [], []
7116 7115 for d, s in c.items():
7117 7116 if s in status.removed:
7118 7117 status.removed.remove(s)
7119 7118 renamed.append(d)
7120 7119 else:
7121 7120 copied.append(d)
7122 7121 if d in status.added:
7123 7122 status.added.remove(d)
7124 7123
7125 7124 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7126 7125
7127 7126 labels = [
7128 7127 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7129 7128 (ui.label(_(b'%d added'), b'status.added'), status.added),
7130 7129 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7131 7130 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7132 7131 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7133 7132 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7134 7133 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7135 7134 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7136 7135 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7137 7136 ]
7138 7137 t = []
7139 7138 for l, s in labels:
7140 7139 if s:
7141 7140 t.append(l % len(s))
7142 7141
7143 7142 t = b', '.join(t)
7144 7143 cleanworkdir = False
7145 7144
7146 7145 if repo.vfs.exists(b'graftstate'):
7147 7146 t += _(b' (graft in progress)')
7148 7147 if repo.vfs.exists(b'updatestate'):
7149 7148 t += _(b' (interrupted update)')
7150 7149 elif len(parents) > 1:
7151 7150 t += _(b' (merge)')
7152 7151 elif branch != parents[0].branch():
7153 7152 t += _(b' (new branch)')
7154 7153 elif parents[0].closesbranch() and pnode in repo.branchheads(
7155 7154 branch, closed=True
7156 7155 ):
7157 7156 t += _(b' (head closed)')
7158 7157 elif not (
7159 7158 status.modified
7160 7159 or status.added
7161 7160 or status.removed
7162 7161 or renamed
7163 7162 or copied
7164 7163 or subs
7165 7164 ):
7166 7165 t += _(b' (clean)')
7167 7166 cleanworkdir = True
7168 7167 elif pnode not in bheads:
7169 7168 t += _(b' (new branch head)')
7170 7169
7171 7170 if parents:
7172 7171 pendingphase = max(p.phase() for p in parents)
7173 7172 else:
7174 7173 pendingphase = phases.public
7175 7174
7176 7175 if pendingphase > phases.newcommitphase(ui):
7177 7176 t += b' (%s)' % phases.phasenames[pendingphase]
7178 7177
7179 7178 if cleanworkdir:
7180 7179 # i18n: column positioning for "hg summary"
7181 7180 ui.status(_(b'commit: %s\n') % t.strip())
7182 7181 else:
7183 7182 # i18n: column positioning for "hg summary"
7184 7183 ui.write(_(b'commit: %s\n') % t.strip())
7185 7184
7186 7185 # all ancestors of branch heads - all ancestors of parent = new csets
7187 7186 new = len(
7188 7187 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7189 7188 )
7190 7189
7191 7190 if new == 0:
7192 7191 # i18n: column positioning for "hg summary"
7193 7192 ui.status(_(b'update: (current)\n'))
7194 7193 elif pnode not in bheads:
7195 7194 # i18n: column positioning for "hg summary"
7196 7195 ui.write(_(b'update: %d new changesets (update)\n') % new)
7197 7196 else:
7198 7197 # i18n: column positioning for "hg summary"
7199 7198 ui.write(
7200 7199 _(b'update: %d new changesets, %d branch heads (merge)\n')
7201 7200 % (new, len(bheads))
7202 7201 )
7203 7202
7204 7203 t = []
7205 7204 draft = len(repo.revs(b'draft()'))
7206 7205 if draft:
7207 7206 t.append(_(b'%d draft') % draft)
7208 7207 secret = len(repo.revs(b'secret()'))
7209 7208 if secret:
7210 7209 t.append(_(b'%d secret') % secret)
7211 7210
7212 7211 if draft or secret:
7213 7212 ui.status(_(b'phases: %s\n') % b', '.join(t))
7214 7213
7215 7214 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7216 7215 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7217 7216 numtrouble = len(repo.revs(trouble + b"()"))
7218 7217 # We write all the possibilities to ease translation
7219 7218 troublemsg = {
7220 7219 b"orphan": _(b"orphan: %d changesets"),
7221 7220 b"contentdivergent": _(b"content-divergent: %d changesets"),
7222 7221 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7223 7222 }
7224 7223 if numtrouble > 0:
7225 7224 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7226 7225
7227 7226 cmdutil.summaryhooks(ui, repo)
7228 7227
7229 7228 if opts.get(b'remote'):
7230 7229 needsincoming, needsoutgoing = True, True
7231 7230 else:
7232 7231 needsincoming, needsoutgoing = False, False
7233 7232 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7234 7233 if i:
7235 7234 needsincoming = True
7236 7235 if o:
7237 7236 needsoutgoing = True
7238 7237 if not needsincoming and not needsoutgoing:
7239 7238 return
7240 7239
7241 7240 def getincoming():
7242 7241 # XXX We should actually skip this if no default is specified, instead
7243 7242 # of passing "default" which will resolve as "./default/" if no default
7244 7243 # path is defined.
7245 7244 source, branches = urlutil.get_unique_pull_path(
7246 7245 b'summary', repo, ui, b'default'
7247 7246 )
7248 7247 sbranch = branches[0]
7249 7248 try:
7250 7249 other = hg.peer(repo, {}, source)
7251 7250 except error.RepoError:
7252 7251 if opts.get(b'remote'):
7253 7252 raise
7254 7253 return source, sbranch, None, None, None
7255 7254 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7256 7255 if revs:
7257 7256 revs = [other.lookup(rev) for rev in revs]
7258 7257 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(source))
7259 7258 with repo.ui.silent():
7260 7259 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7261 7260 return source, sbranch, other, commoninc, commoninc[1]
7262 7261
7263 7262 if needsincoming:
7264 7263 source, sbranch, sother, commoninc, incoming = getincoming()
7265 7264 else:
7266 7265 source = sbranch = sother = commoninc = incoming = None
7267 7266
7268 7267 def getoutgoing():
7269 7268 # XXX We should actually skip this if no default is specified, instead
7270 7269 # of passing "default" which will resolve as "./default/" if no default
7271 7270 # path is defined.
7272 7271 d = None
7273 7272 if b'default-push' in ui.paths:
7274 7273 d = b'default-push'
7275 7274 elif b'default' in ui.paths:
7276 7275 d = b'default'
7277 7276 if d is not None:
7278 7277 path = urlutil.get_unique_push_path(b'summary', repo, ui, d)
7279 7278 dest = path.pushloc or path.loc
7280 7279 dbranch = path.branch
7281 7280 else:
7282 7281 dest = b'default'
7283 7282 dbranch = None
7284 7283 revs, checkout = hg.addbranchrevs(repo, repo, (dbranch, []), None)
7285 7284 if source != dest:
7286 7285 try:
7287 7286 dother = hg.peer(repo, {}, dest)
7288 7287 except error.RepoError:
7289 7288 if opts.get(b'remote'):
7290 7289 raise
7291 7290 return dest, dbranch, None, None
7292 7291 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(dest))
7293 7292 elif sother is None:
7294 7293 # there is no explicit destination peer, but source one is invalid
7295 7294 return dest, dbranch, None, None
7296 7295 else:
7297 7296 dother = sother
7298 7297 if source != dest or (sbranch is not None and sbranch != dbranch):
7299 7298 common = None
7300 7299 else:
7301 7300 common = commoninc
7302 7301 if revs:
7303 7302 revs = [repo.lookup(rev) for rev in revs]
7304 7303 with repo.ui.silent():
7305 7304 outgoing = discovery.findcommonoutgoing(
7306 7305 repo, dother, onlyheads=revs, commoninc=common
7307 7306 )
7308 7307 return dest, dbranch, dother, outgoing
7309 7308
7310 7309 if needsoutgoing:
7311 7310 dest, dbranch, dother, outgoing = getoutgoing()
7312 7311 else:
7313 7312 dest = dbranch = dother = outgoing = None
7314 7313
7315 7314 if opts.get(b'remote'):
7316 7315 # Help pytype. --remote sets both `needsincoming` and `needsoutgoing`.
7317 7316 # The former always sets `sother` (or raises an exception if it can't);
7318 7317 # the latter always sets `outgoing`.
7319 7318 assert sother is not None
7320 7319 assert outgoing is not None
7321 7320
7322 7321 t = []
7323 7322 if incoming:
7324 7323 t.append(_(b'1 or more incoming'))
7325 7324 o = outgoing.missing
7326 7325 if o:
7327 7326 t.append(_(b'%d outgoing') % len(o))
7328 7327 other = dother or sother
7329 7328 if b'bookmarks' in other.listkeys(b'namespaces'):
7330 7329 counts = bookmarks.summary(repo, other)
7331 7330 if counts[0] > 0:
7332 7331 t.append(_(b'%d incoming bookmarks') % counts[0])
7333 7332 if counts[1] > 0:
7334 7333 t.append(_(b'%d outgoing bookmarks') % counts[1])
7335 7334
7336 7335 if t:
7337 7336 # i18n: column positioning for "hg summary"
7338 7337 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7339 7338 else:
7340 7339 # i18n: column positioning for "hg summary"
7341 7340 ui.status(_(b'remote: (synced)\n'))
7342 7341
7343 7342 cmdutil.summaryremotehooks(
7344 7343 ui,
7345 7344 repo,
7346 7345 opts,
7347 7346 (
7348 7347 (source, sbranch, sother, commoninc),
7349 7348 (dest, dbranch, dother, outgoing),
7350 7349 ),
7351 7350 )
7352 7351
7353 7352
7354 7353 @command(
7355 7354 b'tag',
7356 7355 [
7357 7356 (b'f', b'force', None, _(b'force tag')),
7358 7357 (b'l', b'local', None, _(b'make the tag local')),
7359 7358 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7360 7359 (b'', b'remove', None, _(b'remove a tag')),
7361 7360 # -l/--local is already there, commitopts cannot be used
7362 7361 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7363 7362 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7364 7363 ]
7365 7364 + commitopts2,
7366 7365 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7367 7366 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7368 7367 )
7369 7368 def tag(ui, repo, name1, *names, **opts):
7370 7369 """add one or more tags for the current or given revision
7371 7370
7372 7371 Name a particular revision using <name>.
7373 7372
7374 7373 Tags are used to name particular revisions of the repository and are
7375 7374 very useful to compare different revisions, to go back to significant
7376 7375 earlier versions or to mark branch points as releases, etc. Changing
7377 7376 an existing tag is normally disallowed; use -f/--force to override.
7378 7377
7379 7378 If no revision is given, the parent of the working directory is
7380 7379 used.
7381 7380
7382 7381 To facilitate version control, distribution, and merging of tags,
7383 7382 they are stored as a file named ".hgtags" which is managed similarly
7384 7383 to other project files and can be hand-edited if necessary. This
7385 7384 also means that tagging creates a new commit. The file
7386 7385 ".hg/localtags" is used for local tags (not shared among
7387 7386 repositories).
7388 7387
7389 7388 Tag commits are usually made at the head of a branch. If the parent
7390 7389 of the working directory is not a branch head, :hg:`tag` aborts; use
7391 7390 -f/--force to force the tag commit to be based on a non-head
7392 7391 changeset.
7393 7392
7394 7393 See :hg:`help dates` for a list of formats valid for -d/--date.
7395 7394
7396 7395 Since tag names have priority over branch names during revision
7397 7396 lookup, using an existing branch name as a tag name is discouraged.
7398 7397
7399 7398 Returns 0 on success.
7400 7399 """
7401 7400 cmdutil.check_incompatible_arguments(opts, 'remove', ['rev'])
7402 7401 opts = pycompat.byteskwargs(opts)
7403 7402 with repo.wlock(), repo.lock():
7404 7403 rev_ = b"."
7405 7404 names = [t.strip() for t in (name1,) + names]
7406 7405 if len(names) != len(set(names)):
7407 7406 raise error.InputError(_(b'tag names must be unique'))
7408 7407 for n in names:
7409 7408 scmutil.checknewlabel(repo, n, b'tag')
7410 7409 if not n:
7411 7410 raise error.InputError(
7412 7411 _(b'tag names cannot consist entirely of whitespace')
7413 7412 )
7414 7413 if opts.get(b'rev'):
7415 7414 rev_ = opts[b'rev']
7416 7415 message = opts.get(b'message')
7417 7416 if opts.get(b'remove'):
7418 7417 if opts.get(b'local'):
7419 7418 expectedtype = b'local'
7420 7419 else:
7421 7420 expectedtype = b'global'
7422 7421
7423 7422 for n in names:
7424 7423 if repo.tagtype(n) == b'global':
7425 7424 alltags = tagsmod.findglobaltags(ui, repo)
7426 7425 if alltags[n][0] == repo.nullid:
7427 7426 raise error.InputError(
7428 7427 _(b"tag '%s' is already removed") % n
7429 7428 )
7430 7429 if not repo.tagtype(n):
7431 7430 raise error.InputError(_(b"tag '%s' does not exist") % n)
7432 7431 if repo.tagtype(n) != expectedtype:
7433 7432 if expectedtype == b'global':
7434 7433 raise error.InputError(
7435 7434 _(b"tag '%s' is not a global tag") % n
7436 7435 )
7437 7436 else:
7438 7437 raise error.InputError(
7439 7438 _(b"tag '%s' is not a local tag") % n
7440 7439 )
7441 7440 rev_ = b'null'
7442 7441 if not message:
7443 7442 # we don't translate commit messages
7444 7443 message = b'Removed tag %s' % b', '.join(names)
7445 7444 elif not opts.get(b'force'):
7446 7445 for n in names:
7447 7446 if n in repo.tags():
7448 7447 raise error.InputError(
7449 7448 _(b"tag '%s' already exists (use -f to force)") % n
7450 7449 )
7451 7450 if not opts.get(b'local'):
7452 7451 p1, p2 = repo.dirstate.parents()
7453 7452 if p2 != repo.nullid:
7454 7453 raise error.StateError(_(b'uncommitted merge'))
7455 7454 bheads = repo.branchheads()
7456 7455 if not opts.get(b'force') and bheads and p1 not in bheads:
7457 7456 raise error.InputError(
7458 7457 _(
7459 7458 b'working directory is not at a branch head '
7460 7459 b'(use -f to force)'
7461 7460 )
7462 7461 )
7463 7462 node = logcmdutil.revsingle(repo, rev_).node()
7464 7463
7465 7464 if not message:
7466 7465 # we don't translate commit messages
7467 7466 message = b'Added tag %s for changeset %s' % (
7468 7467 b', '.join(names),
7469 7468 short(node),
7470 7469 )
7471 7470
7472 7471 date = opts.get(b'date')
7473 7472 if date:
7474 7473 date = dateutil.parsedate(date)
7475 7474
7476 7475 if opts.get(b'remove'):
7477 7476 editform = b'tag.remove'
7478 7477 else:
7479 7478 editform = b'tag.add'
7480 7479 editor = cmdutil.getcommiteditor(
7481 7480 editform=editform, **pycompat.strkwargs(opts)
7482 7481 )
7483 7482
7484 7483 # don't allow tagging the null rev
7485 7484 if (
7486 7485 not opts.get(b'remove')
7487 7486 and logcmdutil.revsingle(repo, rev_).rev() == nullrev
7488 7487 ):
7489 7488 raise error.InputError(_(b"cannot tag null revision"))
7490 7489
7491 7490 tagsmod.tag(
7492 7491 repo,
7493 7492 names,
7494 7493 node,
7495 7494 message,
7496 7495 opts.get(b'local'),
7497 7496 opts.get(b'user'),
7498 7497 date,
7499 7498 editor=editor,
7500 7499 )
7501 7500
7502 7501
7503 7502 @command(
7504 7503 b'tags',
7505 7504 formatteropts,
7506 7505 b'',
7507 7506 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7508 7507 intents={INTENT_READONLY},
7509 7508 )
7510 7509 def tags(ui, repo, **opts):
7511 7510 """list repository tags
7512 7511
7513 7512 This lists both regular and local tags. When the -v/--verbose
7514 7513 switch is used, a third column "local" is printed for local tags.
7515 7514 When the -q/--quiet switch is used, only the tag name is printed.
7516 7515
7517 7516 .. container:: verbose
7518 7517
7519 7518 Template:
7520 7519
7521 7520 The following keywords are supported in addition to the common template
7522 7521 keywords and functions such as ``{tag}``. See also
7523 7522 :hg:`help templates`.
7524 7523
7525 7524 :type: String. ``local`` for local tags.
7526 7525
7527 7526 Returns 0 on success.
7528 7527 """
7529 7528
7530 7529 opts = pycompat.byteskwargs(opts)
7531 7530 ui.pager(b'tags')
7532 7531 fm = ui.formatter(b'tags', opts)
7533 7532 hexfunc = fm.hexfunc
7534 7533
7535 7534 for t, n in reversed(repo.tagslist()):
7536 7535 hn = hexfunc(n)
7537 7536 label = b'tags.normal'
7538 7537 tagtype = repo.tagtype(t)
7539 7538 if not tagtype or tagtype == b'global':
7540 7539 tagtype = b''
7541 7540 else:
7542 7541 label = b'tags.' + tagtype
7543 7542
7544 7543 fm.startitem()
7545 7544 fm.context(repo=repo)
7546 7545 fm.write(b'tag', b'%s', t, label=label)
7547 7546 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7548 7547 fm.condwrite(
7549 7548 not ui.quiet,
7550 7549 b'rev node',
7551 7550 fmt,
7552 7551 repo.changelog.rev(n),
7553 7552 hn,
7554 7553 label=label,
7555 7554 )
7556 7555 fm.condwrite(
7557 7556 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7558 7557 )
7559 7558 fm.plain(b'\n')
7560 7559 fm.end()
7561 7560
7562 7561
7563 7562 @command(
7564 7563 b'tip',
7565 7564 [
7566 7565 (b'p', b'patch', None, _(b'show patch')),
7567 7566 (b'g', b'git', None, _(b'use git extended diff format')),
7568 7567 ]
7569 7568 + templateopts,
7570 7569 _(b'[-p] [-g]'),
7571 7570 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7572 7571 )
7573 7572 def tip(ui, repo, **opts):
7574 7573 """show the tip revision (DEPRECATED)
7575 7574
7576 7575 The tip revision (usually just called the tip) is the changeset
7577 7576 most recently added to the repository (and therefore the most
7578 7577 recently changed head).
7579 7578
7580 7579 If you have just made a commit, that commit will be the tip. If
7581 7580 you have just pulled changes from another repository, the tip of
7582 7581 that repository becomes the current tip. The "tip" tag is special
7583 7582 and cannot be renamed or assigned to a different changeset.
7584 7583
7585 7584 This command is deprecated, please use :hg:`heads` instead.
7586 7585
7587 7586 Returns 0 on success.
7588 7587 """
7589 7588 opts = pycompat.byteskwargs(opts)
7590 7589 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7591 7590 displayer.show(repo[b'tip'])
7592 7591 displayer.close()
7593 7592
7594 7593
7595 7594 @command(
7596 7595 b'unbundle',
7597 7596 [
7598 7597 (
7599 7598 b'u',
7600 7599 b'update',
7601 7600 None,
7602 7601 _(b'update to new branch head if changesets were unbundled'),
7603 7602 )
7604 7603 ],
7605 7604 _(b'[-u] FILE...'),
7606 7605 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7607 7606 )
7608 7607 def unbundle(ui, repo, fname1, *fnames, **opts):
7609 7608 """apply one or more bundle files
7610 7609
7611 7610 Apply one or more bundle files generated by :hg:`bundle`.
7612 7611
7613 7612 Returns 0 on success, 1 if an update has unresolved files.
7614 7613 """
7615 7614 fnames = (fname1,) + fnames
7616 7615
7617 7616 with repo.lock():
7618 7617 for fname in fnames:
7619 7618 f = hg.openpath(ui, fname)
7620 7619 gen = exchange.readbundle(ui, f, fname)
7621 7620 if isinstance(gen, streamclone.streamcloneapplier):
7622 7621 raise error.InputError(
7623 7622 _(
7624 7623 b'packed bundles cannot be applied with '
7625 7624 b'"hg unbundle"'
7626 7625 ),
7627 7626 hint=_(b'use "hg debugapplystreamclonebundle"'),
7628 7627 )
7629 7628 url = b'bundle:' + fname
7630 7629 try:
7631 7630 txnname = b'unbundle'
7632 7631 if not isinstance(gen, bundle2.unbundle20):
7633 7632 txnname = b'unbundle\n%s' % urlutil.hidepassword(url)
7634 7633 with repo.transaction(txnname) as tr:
7635 7634 op = bundle2.applybundle(
7636 7635 repo, gen, tr, source=b'unbundle', url=url
7637 7636 )
7638 7637 except error.BundleUnknownFeatureError as exc:
7639 7638 raise error.Abort(
7640 7639 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7641 7640 hint=_(
7642 7641 b"see https://mercurial-scm.org/"
7643 7642 b"wiki/BundleFeature for more "
7644 7643 b"information"
7645 7644 ),
7646 7645 )
7647 7646 modheads = bundle2.combinechangegroupresults(op)
7648 7647
7649 7648 if postincoming(ui, repo, modheads, opts.get('update'), None, None):
7650 7649 return 1
7651 7650 else:
7652 7651 return 0
7653 7652
7654 7653
7655 7654 @command(
7656 7655 b'unshelve',
7657 7656 [
7658 7657 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7659 7658 (
7660 7659 b'c',
7661 7660 b'continue',
7662 7661 None,
7663 7662 _(b'continue an incomplete unshelve operation'),
7664 7663 ),
7665 7664 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7666 7665 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7667 7666 (
7668 7667 b'n',
7669 7668 b'name',
7670 7669 b'',
7671 7670 _(b'restore shelved change with given name'),
7672 7671 _(b'NAME'),
7673 7672 ),
7674 7673 (b't', b'tool', b'', _(b'specify merge tool')),
7675 7674 (
7676 7675 b'',
7677 7676 b'date',
7678 7677 b'',
7679 7678 _(b'set date for temporary commits (DEPRECATED)'),
7680 7679 _(b'DATE'),
7681 7680 ),
7682 7681 ],
7683 7682 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7684 7683 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7685 7684 )
7686 7685 def unshelve(ui, repo, *shelved, **opts):
7687 7686 """restore a shelved change to the working directory
7688 7687
7689 7688 This command accepts an optional name of a shelved change to
7690 7689 restore. If none is given, the most recent shelved change is used.
7691 7690
7692 7691 If a shelved change is applied successfully, the bundle that
7693 7692 contains the shelved changes is moved to a backup location
7694 7693 (.hg/shelve-backup).
7695 7694
7696 7695 Since you can restore a shelved change on top of an arbitrary
7697 7696 commit, it is possible that unshelving will result in a conflict
7698 7697 between your changes and the commits you are unshelving onto. If
7699 7698 this occurs, you must resolve the conflict, then use
7700 7699 ``--continue`` to complete the unshelve operation. (The bundle
7701 7700 will not be moved until you successfully complete the unshelve.)
7702 7701
7703 7702 (Alternatively, you can use ``--abort`` to abandon an unshelve
7704 7703 that causes a conflict. This reverts the unshelved changes, and
7705 7704 leaves the bundle in place.)
7706 7705
7707 7706 If bare shelved change (without interactive, include and exclude
7708 7707 option) was done on newly created branch it would restore branch
7709 7708 information to the working directory.
7710 7709
7711 7710 After a successful unshelve, the shelved changes are stored in a
7712 7711 backup directory. Only the N most recent backups are kept. N
7713 7712 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7714 7713 configuration option.
7715 7714
7716 7715 .. container:: verbose
7717 7716
7718 7717 Timestamp in seconds is used to decide order of backups. More
7719 7718 than ``maxbackups`` backups are kept, if same timestamp
7720 7719 prevents from deciding exact order of them, for safety.
7721 7720
7722 7721 Selected changes can be unshelved with ``--interactive`` flag.
7723 7722 The working directory is updated with the selected changes, and
7724 7723 only the unselected changes remain shelved.
7725 7724 Note: The whole shelve is applied to working directory first before
7726 7725 running interactively. So, this will bring up all the conflicts between
7727 7726 working directory and the shelve, irrespective of which changes will be
7728 7727 unshelved.
7729 7728 """
7730 7729 with repo.wlock():
7731 7730 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7732 7731
7733 7732
7734 7733 statemod.addunfinished(
7735 7734 b'unshelve',
7736 7735 fname=b'shelvedstate',
7737 7736 continueflag=True,
7738 7737 abortfunc=shelvemod.hgabortunshelve,
7739 7738 continuefunc=shelvemod.hgcontinueunshelve,
7740 7739 cmdmsg=_(b'unshelve already in progress'),
7741 7740 )
7742 7741
7743 7742
7744 7743 @command(
7745 7744 b'update|up|checkout|co',
7746 7745 [
7747 7746 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7748 7747 (b'c', b'check', None, _(b'require clean working directory')),
7749 7748 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7750 7749 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7751 7750 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7752 7751 ]
7753 7752 + mergetoolopts,
7754 7753 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7755 7754 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7756 7755 helpbasic=True,
7757 7756 )
7758 7757 def update(ui, repo, node=None, **opts):
7759 7758 """update working directory (or switch revisions)
7760 7759
7761 7760 Update the repository's working directory to the specified
7762 7761 changeset. If no changeset is specified, update to the tip of the
7763 7762 current named branch and move the active bookmark (see :hg:`help
7764 7763 bookmarks`).
7765 7764
7766 7765 Update sets the working directory's parent revision to the specified
7767 7766 changeset (see :hg:`help parents`).
7768 7767
7769 7768 If the changeset is not a descendant or ancestor of the working
7770 7769 directory's parent and there are uncommitted changes, the update is
7771 7770 aborted. With the -c/--check option, the working directory is checked
7772 7771 for uncommitted changes; if none are found, the working directory is
7773 7772 updated to the specified changeset.
7774 7773
7775 7774 .. container:: verbose
7776 7775
7777 7776 The -C/--clean, -c/--check, and -m/--merge options control what
7778 7777 happens if the working directory contains uncommitted changes.
7779 7778 At most of one of them can be specified.
7780 7779
7781 7780 1. If no option is specified, and if
7782 7781 the requested changeset is an ancestor or descendant of
7783 7782 the working directory's parent, the uncommitted changes
7784 7783 are merged into the requested changeset and the merged
7785 7784 result is left uncommitted. If the requested changeset is
7786 7785 not an ancestor or descendant (that is, it is on another
7787 7786 branch), the update is aborted and the uncommitted changes
7788 7787 are preserved.
7789 7788
7790 7789 2. With the -m/--merge option, the update is allowed even if the
7791 7790 requested changeset is not an ancestor or descendant of
7792 7791 the working directory's parent.
7793 7792
7794 7793 3. With the -c/--check option, the update is aborted and the
7795 7794 uncommitted changes are preserved.
7796 7795
7797 7796 4. With the -C/--clean option, uncommitted changes are discarded and
7798 7797 the working directory is updated to the requested changeset.
7799 7798
7800 7799 To cancel an uncommitted merge (and lose your changes), use
7801 7800 :hg:`merge --abort`.
7802 7801
7803 7802 Use null as the changeset to remove the working directory (like
7804 7803 :hg:`clone -U`).
7805 7804
7806 7805 If you want to revert just one file to an older revision, use
7807 7806 :hg:`revert [-r REV] NAME`.
7808 7807
7809 7808 See :hg:`help dates` for a list of formats valid for -d/--date.
7810 7809
7811 7810 Returns 0 on success, 1 if there are unresolved files.
7812 7811 """
7813 7812 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7814 7813 rev = opts.get('rev')
7815 7814 date = opts.get('date')
7816 7815 clean = opts.get('clean')
7817 7816 check = opts.get('check')
7818 7817 merge = opts.get('merge')
7819 7818 if rev and node:
7820 7819 raise error.InputError(_(b"please specify just one revision"))
7821 7820
7822 7821 if ui.configbool(b'commands', b'update.requiredest'):
7823 7822 if not node and not rev and not date:
7824 7823 raise error.InputError(
7825 7824 _(b'you must specify a destination'),
7826 7825 hint=_(b'for example: hg update ".::"'),
7827 7826 )
7828 7827
7829 7828 if rev is None or rev == b'':
7830 7829 rev = node
7831 7830
7832 7831 if date and rev is not None:
7833 7832 raise error.InputError(_(b"you can't specify a revision and a date"))
7834 7833
7835 7834 updatecheck = None
7836 7835 if check or merge is not None and not merge:
7837 7836 updatecheck = b'abort'
7838 7837 elif merge or check is not None and not check:
7839 7838 updatecheck = b'none'
7840 7839
7841 7840 with repo.wlock():
7842 7841 cmdutil.clearunfinished(repo)
7843 7842 if date:
7844 7843 rev = cmdutil.finddate(ui, repo, date)
7845 7844
7846 7845 # if we defined a bookmark, we have to remember the original name
7847 7846 brev = rev
7848 7847 if rev:
7849 7848 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7850 7849 ctx = logcmdutil.revsingle(repo, rev, default=None)
7851 7850 rev = ctx.rev()
7852 7851 hidden = ctx.hidden()
7853 7852 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7854 7853 with ui.configoverride(overrides, b'update'):
7855 7854 ret = hg.updatetotally(
7856 7855 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7857 7856 )
7858 7857 if hidden:
7859 7858 ctxstr = ctx.hex()[:12]
7860 7859 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7861 7860
7862 7861 if ctx.obsolete():
7863 7862 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7864 7863 ui.warn(b"(%s)\n" % obsfatemsg)
7865 7864 return ret
7866 7865
7867 7866
7868 7867 @command(
7869 7868 b'verify',
7870 7869 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7871 7870 helpcategory=command.CATEGORY_MAINTENANCE,
7872 7871 )
7873 7872 def verify(ui, repo, **opts):
7874 7873 """verify the integrity of the repository
7875 7874
7876 7875 Verify the integrity of the current repository.
7877 7876
7878 7877 This will perform an extensive check of the repository's
7879 7878 integrity, validating the hashes and checksums of each entry in
7880 7879 the changelog, manifest, and tracked files, as well as the
7881 7880 integrity of their crosslinks and indices.
7882 7881
7883 7882 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7884 7883 for more information about recovery from corruption of the
7885 7884 repository.
7886 7885
7887 7886 Returns 0 on success, 1 if errors are encountered.
7888 7887 """
7889 7888 opts = pycompat.byteskwargs(opts)
7890 7889
7891 7890 level = None
7892 7891 if opts[b'full']:
7893 7892 level = verifymod.VERIFY_FULL
7894 7893 return hg.verify(repo, level)
7895 7894
7896 7895
7897 7896 @command(
7898 7897 b'version',
7899 7898 [] + formatteropts,
7900 7899 helpcategory=command.CATEGORY_HELP,
7901 7900 norepo=True,
7902 7901 intents={INTENT_READONLY},
7903 7902 )
7904 7903 def version_(ui, **opts):
7905 7904 """output version and copyright information
7906 7905
7907 7906 .. container:: verbose
7908 7907
7909 7908 Template:
7910 7909
7911 7910 The following keywords are supported. See also :hg:`help templates`.
7912 7911
7913 7912 :extensions: List of extensions.
7914 7913 :ver: String. Version number.
7915 7914
7916 7915 And each entry of ``{extensions}`` provides the following sub-keywords
7917 7916 in addition to ``{ver}``.
7918 7917
7919 7918 :bundled: Boolean. True if included in the release.
7920 7919 :name: String. Extension name.
7921 7920 """
7922 7921 opts = pycompat.byteskwargs(opts)
7923 7922 if ui.verbose:
7924 7923 ui.pager(b'version')
7925 7924 fm = ui.formatter(b"version", opts)
7926 7925 fm.startitem()
7927 7926 fm.write(
7928 7927 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7929 7928 )
7930 7929 license = _(
7931 7930 b"(see https://mercurial-scm.org for more information)\n"
7932 7931 b"\nCopyright (C) 2005-2022 Olivia Mackall and others\n"
7933 7932 b"This is free software; see the source for copying conditions. "
7934 7933 b"There is NO\nwarranty; "
7935 7934 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7936 7935 )
7937 7936 if not ui.quiet:
7938 7937 fm.plain(license)
7939 7938
7940 7939 if ui.verbose:
7941 7940 fm.plain(_(b"\nEnabled extensions:\n\n"))
7942 7941 # format names and versions into columns
7943 7942 names = []
7944 7943 vers = []
7945 7944 isinternals = []
7946 7945 for name, module in sorted(extensions.extensions()):
7947 7946 names.append(name)
7948 7947 vers.append(extensions.moduleversion(module) or None)
7949 7948 isinternals.append(extensions.ismoduleinternal(module))
7950 7949 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7951 7950 if names:
7952 7951 namefmt = b" %%-%ds " % max(len(n) for n in names)
7953 7952 places = [_(b"external"), _(b"internal")]
7954 7953 for n, v, p in zip(names, vers, isinternals):
7955 7954 fn.startitem()
7956 7955 fn.condwrite(ui.verbose, b"name", namefmt, n)
7957 7956 if ui.verbose:
7958 7957 fn.plain(b"%s " % places[p])
7959 7958 fn.data(bundled=p)
7960 7959 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7961 7960 if ui.verbose:
7962 7961 fn.plain(b"\n")
7963 7962 fn.end()
7964 7963 fm.end()
7965 7964
7966 7965
7967 7966 def loadcmdtable(ui, name, cmdtable):
7968 7967 """Load command functions from specified cmdtable"""
7969 7968 overrides = [cmd for cmd in cmdtable if cmd in table]
7970 7969 if overrides:
7971 7970 ui.warn(
7972 7971 _(b"extension '%s' overrides commands: %s\n")
7973 7972 % (name, b" ".join(overrides))
7974 7973 )
7975 7974 table.update(cmdtable)
General Comments 0
You need to be logged in to leave comments. Login now