##// END OF EJS Templates
strip: move into core...
Valentin Gatien-Baron -
r46469:a46efd42 default draft
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,4299 +1,4300 b''
1 1 # mq.py - patch queues for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Chris Mason <mason@suse.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 '''manage a stack of patches
9 9
10 10 This extension lets you work with a stack of patches in a Mercurial
11 11 repository. It manages two stacks of patches - all known patches, and
12 12 applied patches (subset of known patches).
13 13
14 14 Known patches are represented as patch files in the .hg/patches
15 15 directory. Applied patches are both patch files and changesets.
16 16
17 17 Common tasks (use :hg:`help COMMAND` for more details)::
18 18
19 19 create new patch qnew
20 20 import existing patch qimport
21 21
22 22 print patch series qseries
23 23 print applied patches qapplied
24 24
25 25 add known patch to applied stack qpush
26 26 remove patch from applied stack qpop
27 27 refresh contents of top applied patch qrefresh
28 28
29 29 By default, mq will automatically use git patches when required to
30 30 avoid losing file mode changes, copy records, binary files or empty
31 31 files creations or deletions. This behavior can be configured with::
32 32
33 33 [mq]
34 34 git = auto/keep/yes/no
35 35
36 36 If set to 'keep', mq will obey the [diff] section configuration while
37 37 preserving existing git patches upon qrefresh. If set to 'yes' or
38 38 'no', mq will override the [diff] section and always generate git or
39 39 regular patches, possibly losing data in the second case.
40 40
41 41 It may be desirable for mq changesets to be kept in the secret phase (see
42 42 :hg:`help phases`), which can be enabled with the following setting::
43 43
44 44 [mq]
45 45 secret = True
46 46
47 47 You will by default be managing a patch queue named "patches". You can
48 48 create other, independent patch queues with the :hg:`qqueue` command.
49 49
50 50 If the working directory contains uncommitted files, qpush, qpop and
51 51 qgoto abort immediately. If -f/--force is used, the changes are
52 52 discarded. Setting::
53 53
54 54 [mq]
55 55 keepchanges = True
56 56
57 57 make them behave as if --keep-changes were passed, and non-conflicting
58 58 local changes will be tolerated and preserved. If incompatible options
59 59 such as -f/--force or --exact are passed, this setting is ignored.
60 60
61 61 This extension used to provide a strip command. This command now lives
62 62 in the strip extension.
63 63 '''
64 64
65 65 from __future__ import absolute_import, print_function
66 66
67 67 import errno
68 68 import os
69 69 import re
70 70 import shutil
71 71 import sys
72 72 from mercurial.i18n import _
73 73 from mercurial.node import (
74 74 bin,
75 75 hex,
76 76 nullid,
77 77 nullrev,
78 78 short,
79 79 )
80 80 from mercurial.pycompat import (
81 81 delattr,
82 82 getattr,
83 83 open,
84 84 )
85 85 from mercurial import (
86 86 cmdutil,
87 87 commands,
88 88 dirstateguard,
89 89 encoding,
90 90 error,
91 91 extensions,
92 92 hg,
93 93 localrepo,
94 94 lock as lockmod,
95 95 logcmdutil,
96 96 patch as patchmod,
97 97 phases,
98 98 pycompat,
99 99 registrar,
100 100 revsetlang,
101 101 scmutil,
102 102 smartset,
103 strip,
103 104 subrepoutil,
104 105 util,
105 106 vfs as vfsmod,
106 107 )
107 108 from mercurial.utils import (
108 109 dateutil,
109 110 stringutil,
110 111 )
111 112
112 113 release = lockmod.release
113 114 seriesopts = [(b's', b'summary', None, _(b'print first line of patch header'))]
114 115
115 116 cmdtable = {}
116 117 command = registrar.command(cmdtable)
117 118 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
118 119 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
119 120 # be specifying the version(s) of Mercurial they are tested with, or
120 121 # leave the attribute unspecified.
121 122 testedwith = b'ships-with-hg-core'
122 123
123 124 configtable = {}
124 125 configitem = registrar.configitem(configtable)
125 126
126 127 configitem(
127 128 b'mq', b'git', default=b'auto',
128 129 )
129 130 configitem(
130 131 b'mq', b'keepchanges', default=False,
131 132 )
132 133 configitem(
133 134 b'mq', b'plain', default=False,
134 135 )
135 136 configitem(
136 137 b'mq', b'secret', default=False,
137 138 )
138 139
139 140 # force load strip extension formerly included in mq and import some utility
140 141 try:
141 stripext = extensions.find(b'strip')
142 extensions.find(b'strip')
142 143 except KeyError:
143 144 # note: load is lazy so we could avoid the try-except,
144 145 # but I (marmoute) prefer this explicit code.
145 146 class dummyui(object):
146 147 def debug(self, msg):
147 148 pass
148 149
149 150 def log(self, event, msgfmt, *msgargs, **opts):
150 151 pass
151 152
152 stripext = extensions.load(dummyui(), b'strip', b'')
153
154 strip = stripext.strip
153 extensions.load(dummyui(), b'strip', b'')
154
155 strip = strip.strip
155 156
156 157
157 158 def checksubstate(repo, baserev=None):
158 159 '''return list of subrepos at a different revision than substate.
159 160 Abort if any subrepos have uncommitted changes.'''
160 161 inclsubs = []
161 162 wctx = repo[None]
162 163 if baserev:
163 164 bctx = repo[baserev]
164 165 else:
165 166 bctx = wctx.p1()
166 167 for s in sorted(wctx.substate):
167 168 wctx.sub(s).bailifchanged(True)
168 169 if s not in bctx.substate or bctx.sub(s).dirty():
169 170 inclsubs.append(s)
170 171 return inclsubs
171 172
172 173
173 174 # Patch names looks like unix-file names.
174 175 # They must be joinable with queue directory and result in the patch path.
175 176 normname = util.normpath
176 177
177 178
178 179 class statusentry(object):
179 180 def __init__(self, node, name):
180 181 self.node, self.name = node, name
181 182
182 183 def __bytes__(self):
183 184 return hex(self.node) + b':' + self.name
184 185
185 186 __str__ = encoding.strmethod(__bytes__)
186 187 __repr__ = encoding.strmethod(__bytes__)
187 188
188 189
189 190 # The order of the headers in 'hg export' HG patches:
190 191 HGHEADERS = [
191 192 # '# HG changeset patch',
192 193 b'# User ',
193 194 b'# Date ',
194 195 b'# ',
195 196 b'# Branch ',
196 197 b'# Node ID ',
197 198 b'# Parent ', # can occur twice for merges - but that is not relevant for mq
198 199 ]
199 200 # The order of headers in plain 'mail style' patches:
200 201 PLAINHEADERS = {
201 202 b'from': 0,
202 203 b'date': 1,
203 204 b'subject': 2,
204 205 }
205 206
206 207
207 208 def inserthgheader(lines, header, value):
208 209 """Assuming lines contains a HG patch header, add a header line with value.
209 210 >>> try: inserthgheader([], b'# Date ', b'z')
210 211 ... except ValueError as inst: print("oops")
211 212 oops
212 213 >>> inserthgheader([b'# HG changeset patch'], b'# Date ', b'z')
213 214 ['# HG changeset patch', '# Date z']
214 215 >>> inserthgheader([b'# HG changeset patch', b''], b'# Date ', b'z')
215 216 ['# HG changeset patch', '# Date z', '']
216 217 >>> inserthgheader([b'# HG changeset patch', b'# User y'], b'# Date ', b'z')
217 218 ['# HG changeset patch', '# User y', '# Date z']
218 219 >>> inserthgheader([b'# HG changeset patch', b'# Date x', b'# User y'],
219 220 ... b'# User ', b'z')
220 221 ['# HG changeset patch', '# Date x', '# User z']
221 222 >>> inserthgheader([b'# HG changeset patch', b'# Date y'], b'# Date ', b'z')
222 223 ['# HG changeset patch', '# Date z']
223 224 >>> inserthgheader([b'# HG changeset patch', b'', b'# Date y'],
224 225 ... b'# Date ', b'z')
225 226 ['# HG changeset patch', '# Date z', '', '# Date y']
226 227 >>> inserthgheader([b'# HG changeset patch', b'# Parent y'],
227 228 ... b'# Date ', b'z')
228 229 ['# HG changeset patch', '# Date z', '# Parent y']
229 230 """
230 231 start = lines.index(b'# HG changeset patch') + 1
231 232 newindex = HGHEADERS.index(header)
232 233 bestpos = len(lines)
233 234 for i in range(start, len(lines)):
234 235 line = lines[i]
235 236 if not line.startswith(b'# '):
236 237 bestpos = min(bestpos, i)
237 238 break
238 239 for lineindex, h in enumerate(HGHEADERS):
239 240 if line.startswith(h):
240 241 if lineindex == newindex:
241 242 lines[i] = header + value
242 243 return lines
243 244 if lineindex > newindex:
244 245 bestpos = min(bestpos, i)
245 246 break # next line
246 247 lines.insert(bestpos, header + value)
247 248 return lines
248 249
249 250
250 251 def insertplainheader(lines, header, value):
251 252 """For lines containing a plain patch header, add a header line with value.
252 253 >>> insertplainheader([], b'Date', b'z')
253 254 ['Date: z']
254 255 >>> insertplainheader([b''], b'Date', b'z')
255 256 ['Date: z', '']
256 257 >>> insertplainheader([b'x'], b'Date', b'z')
257 258 ['Date: z', '', 'x']
258 259 >>> insertplainheader([b'From: y', b'x'], b'Date', b'z')
259 260 ['From: y', 'Date: z', '', 'x']
260 261 >>> insertplainheader([b' date : x', b' from : y', b''], b'From', b'z')
261 262 [' date : x', 'From: z', '']
262 263 >>> insertplainheader([b'', b'Date: y'], b'Date', b'z')
263 264 ['Date: z', '', 'Date: y']
264 265 >>> insertplainheader([b'foo: bar', b'DATE: z', b'x'], b'From', b'y')
265 266 ['From: y', 'foo: bar', 'DATE: z', '', 'x']
266 267 """
267 268 newprio = PLAINHEADERS[header.lower()]
268 269 bestpos = len(lines)
269 270 for i, line in enumerate(lines):
270 271 if b':' in line:
271 272 lheader = line.split(b':', 1)[0].strip().lower()
272 273 lprio = PLAINHEADERS.get(lheader, newprio + 1)
273 274 if lprio == newprio:
274 275 lines[i] = b'%s: %s' % (header, value)
275 276 return lines
276 277 if lprio > newprio and i < bestpos:
277 278 bestpos = i
278 279 else:
279 280 if line:
280 281 lines.insert(i, b'')
281 282 if i < bestpos:
282 283 bestpos = i
283 284 break
284 285 lines.insert(bestpos, b'%s: %s' % (header, value))
285 286 return lines
286 287
287 288
288 289 class patchheader(object):
289 290 def __init__(self, pf, plainmode=False):
290 291 def eatdiff(lines):
291 292 while lines:
292 293 l = lines[-1]
293 294 if (
294 295 l.startswith(b"diff -")
295 296 or l.startswith(b"Index:")
296 297 or l.startswith(b"===========")
297 298 ):
298 299 del lines[-1]
299 300 else:
300 301 break
301 302
302 303 def eatempty(lines):
303 304 while lines:
304 305 if not lines[-1].strip():
305 306 del lines[-1]
306 307 else:
307 308 break
308 309
309 310 message = []
310 311 comments = []
311 312 user = None
312 313 date = None
313 314 parent = None
314 315 format = None
315 316 subject = None
316 317 branch = None
317 318 nodeid = None
318 319 diffstart = 0
319 320
320 321 for line in open(pf, b'rb'):
321 322 line = line.rstrip()
322 323 if line.startswith(b'diff --git') or (
323 324 diffstart and line.startswith(b'+++ ')
324 325 ):
325 326 diffstart = 2
326 327 break
327 328 diffstart = 0 # reset
328 329 if line.startswith(b"--- "):
329 330 diffstart = 1
330 331 continue
331 332 elif format == b"hgpatch":
332 333 # parse values when importing the result of an hg export
333 334 if line.startswith(b"# User "):
334 335 user = line[7:]
335 336 elif line.startswith(b"# Date "):
336 337 date = line[7:]
337 338 elif line.startswith(b"# Parent "):
338 339 parent = line[9:].lstrip() # handle double trailing space
339 340 elif line.startswith(b"# Branch "):
340 341 branch = line[9:]
341 342 elif line.startswith(b"# Node ID "):
342 343 nodeid = line[10:]
343 344 elif not line.startswith(b"# ") and line:
344 345 message.append(line)
345 346 format = None
346 347 elif line == b'# HG changeset patch':
347 348 message = []
348 349 format = b"hgpatch"
349 350 elif format != b"tagdone" and (
350 351 line.startswith(b"Subject: ") or line.startswith(b"subject: ")
351 352 ):
352 353 subject = line[9:]
353 354 format = b"tag"
354 355 elif format != b"tagdone" and (
355 356 line.startswith(b"From: ") or line.startswith(b"from: ")
356 357 ):
357 358 user = line[6:]
358 359 format = b"tag"
359 360 elif format != b"tagdone" and (
360 361 line.startswith(b"Date: ") or line.startswith(b"date: ")
361 362 ):
362 363 date = line[6:]
363 364 format = b"tag"
364 365 elif format == b"tag" and line == b"":
365 366 # when looking for tags (subject: from: etc) they
366 367 # end once you find a blank line in the source
367 368 format = b"tagdone"
368 369 elif message or line:
369 370 message.append(line)
370 371 comments.append(line)
371 372
372 373 eatdiff(message)
373 374 eatdiff(comments)
374 375 # Remember the exact starting line of the patch diffs before consuming
375 376 # empty lines, for external use by TortoiseHg and others
376 377 self.diffstartline = len(comments)
377 378 eatempty(message)
378 379 eatempty(comments)
379 380
380 381 # make sure message isn't empty
381 382 if format and format.startswith(b"tag") and subject:
382 383 message.insert(0, subject)
383 384
384 385 self.message = message
385 386 self.comments = comments
386 387 self.user = user
387 388 self.date = date
388 389 self.parent = parent
389 390 # nodeid and branch are for external use by TortoiseHg and others
390 391 self.nodeid = nodeid
391 392 self.branch = branch
392 393 self.haspatch = diffstart > 1
393 394 self.plainmode = (
394 395 plainmode
395 396 or b'# HG changeset patch' not in self.comments
396 397 and any(
397 398 c.startswith(b'Date: ') or c.startswith(b'From: ')
398 399 for c in self.comments
399 400 )
400 401 )
401 402
402 403 def setuser(self, user):
403 404 try:
404 405 inserthgheader(self.comments, b'# User ', user)
405 406 except ValueError:
406 407 if self.plainmode:
407 408 insertplainheader(self.comments, b'From', user)
408 409 else:
409 410 tmp = [b'# HG changeset patch', b'# User ' + user]
410 411 self.comments = tmp + self.comments
411 412 self.user = user
412 413
413 414 def setdate(self, date):
414 415 try:
415 416 inserthgheader(self.comments, b'# Date ', date)
416 417 except ValueError:
417 418 if self.plainmode:
418 419 insertplainheader(self.comments, b'Date', date)
419 420 else:
420 421 tmp = [b'# HG changeset patch', b'# Date ' + date]
421 422 self.comments = tmp + self.comments
422 423 self.date = date
423 424
424 425 def setparent(self, parent):
425 426 try:
426 427 inserthgheader(self.comments, b'# Parent ', parent)
427 428 except ValueError:
428 429 if not self.plainmode:
429 430 tmp = [b'# HG changeset patch', b'# Parent ' + parent]
430 431 self.comments = tmp + self.comments
431 432 self.parent = parent
432 433
433 434 def setmessage(self, message):
434 435 if self.comments:
435 436 self._delmsg()
436 437 self.message = [message]
437 438 if message:
438 439 if self.plainmode and self.comments and self.comments[-1]:
439 440 self.comments.append(b'')
440 441 self.comments.append(message)
441 442
442 443 def __bytes__(self):
443 444 s = b'\n'.join(self.comments).rstrip()
444 445 if not s:
445 446 return b''
446 447 return s + b'\n\n'
447 448
448 449 __str__ = encoding.strmethod(__bytes__)
449 450
450 451 def _delmsg(self):
451 452 '''Remove existing message, keeping the rest of the comments fields.
452 453 If comments contains 'subject: ', message will prepend
453 454 the field and a blank line.'''
454 455 if self.message:
455 456 subj = b'subject: ' + self.message[0].lower()
456 457 for i in pycompat.xrange(len(self.comments)):
457 458 if subj == self.comments[i].lower():
458 459 del self.comments[i]
459 460 self.message = self.message[2:]
460 461 break
461 462 ci = 0
462 463 for mi in self.message:
463 464 while mi != self.comments[ci]:
464 465 ci += 1
465 466 del self.comments[ci]
466 467
467 468
468 469 def newcommit(repo, phase, *args, **kwargs):
469 470 """helper dedicated to ensure a commit respect mq.secret setting
470 471
471 472 It should be used instead of repo.commit inside the mq source for operation
472 473 creating new changeset.
473 474 """
474 475 repo = repo.unfiltered()
475 476 if phase is None:
476 477 if repo.ui.configbool(b'mq', b'secret'):
477 478 phase = phases.secret
478 479 overrides = {(b'ui', b'allowemptycommit'): True}
479 480 if phase is not None:
480 481 overrides[(b'phases', b'new-commit')] = phase
481 482 with repo.ui.configoverride(overrides, b'mq'):
482 483 repo.ui.setconfig(b'ui', b'allowemptycommit', True)
483 484 return repo.commit(*args, **kwargs)
484 485
485 486
486 487 class AbortNoCleanup(error.Abort):
487 488 pass
488 489
489 490
490 491 class queue(object):
491 492 def __init__(self, ui, baseui, path, patchdir=None):
492 493 self.basepath = path
493 494 try:
494 495 with open(os.path.join(path, b'patches.queue'), 'rb') as fh:
495 496 cur = fh.read().rstrip()
496 497
497 498 if not cur:
498 499 curpath = os.path.join(path, b'patches')
499 500 else:
500 501 curpath = os.path.join(path, b'patches-' + cur)
501 502 except IOError:
502 503 curpath = os.path.join(path, b'patches')
503 504 self.path = patchdir or curpath
504 505 self.opener = vfsmod.vfs(self.path)
505 506 self.ui = ui
506 507 self.baseui = baseui
507 508 self.applieddirty = False
508 509 self.seriesdirty = False
509 510 self.added = []
510 511 self.seriespath = b"series"
511 512 self.statuspath = b"status"
512 513 self.guardspath = b"guards"
513 514 self.activeguards = None
514 515 self.guardsdirty = False
515 516 # Handle mq.git as a bool with extended values
516 517 gitmode = ui.config(b'mq', b'git').lower()
517 518 boolmode = stringutil.parsebool(gitmode)
518 519 if boolmode is not None:
519 520 if boolmode:
520 521 gitmode = b'yes'
521 522 else:
522 523 gitmode = b'no'
523 524 self.gitmode = gitmode
524 525 # deprecated config: mq.plain
525 526 self.plainmode = ui.configbool(b'mq', b'plain')
526 527 self.checkapplied = True
527 528
528 529 @util.propertycache
529 530 def applied(self):
530 531 def parselines(lines):
531 532 for l in lines:
532 533 entry = l.split(b':', 1)
533 534 if len(entry) > 1:
534 535 n, name = entry
535 536 yield statusentry(bin(n), name)
536 537 elif l.strip():
537 538 self.ui.warn(
538 539 _(b'malformated mq status line: %s\n')
539 540 % stringutil.pprint(entry)
540 541 )
541 542 # else we ignore empty lines
542 543
543 544 try:
544 545 lines = self.opener.read(self.statuspath).splitlines()
545 546 return list(parselines(lines))
546 547 except IOError as e:
547 548 if e.errno == errno.ENOENT:
548 549 return []
549 550 raise
550 551
551 552 @util.propertycache
552 553 def fullseries(self):
553 554 try:
554 555 return self.opener.read(self.seriespath).splitlines()
555 556 except IOError as e:
556 557 if e.errno == errno.ENOENT:
557 558 return []
558 559 raise
559 560
560 561 @util.propertycache
561 562 def series(self):
562 563 self.parseseries()
563 564 return self.series
564 565
565 566 @util.propertycache
566 567 def seriesguards(self):
567 568 self.parseseries()
568 569 return self.seriesguards
569 570
570 571 def invalidate(self):
571 572 for a in 'applied fullseries series seriesguards'.split():
572 573 if a in self.__dict__:
573 574 delattr(self, a)
574 575 self.applieddirty = False
575 576 self.seriesdirty = False
576 577 self.guardsdirty = False
577 578 self.activeguards = None
578 579
579 580 def diffopts(self, opts=None, patchfn=None, plain=False):
580 581 """Return diff options tweaked for this mq use, possibly upgrading to
581 582 git format, and possibly plain and without lossy options."""
582 583 diffopts = patchmod.difffeatureopts(
583 584 self.ui,
584 585 opts,
585 586 git=True,
586 587 whitespace=not plain,
587 588 formatchanging=not plain,
588 589 )
589 590 if self.gitmode == b'auto':
590 591 diffopts.upgrade = True
591 592 elif self.gitmode == b'keep':
592 593 pass
593 594 elif self.gitmode in (b'yes', b'no'):
594 595 diffopts.git = self.gitmode == b'yes'
595 596 else:
596 597 raise error.Abort(
597 598 _(b'mq.git option can be auto/keep/yes/no got %s')
598 599 % self.gitmode
599 600 )
600 601 if patchfn:
601 602 diffopts = self.patchopts(diffopts, patchfn)
602 603 return diffopts
603 604
604 605 def patchopts(self, diffopts, *patches):
605 606 """Return a copy of input diff options with git set to true if
606 607 referenced patch is a git patch and should be preserved as such.
607 608 """
608 609 diffopts = diffopts.copy()
609 610 if not diffopts.git and self.gitmode == b'keep':
610 611 for patchfn in patches:
611 612 patchf = self.opener(patchfn, b'r')
612 613 # if the patch was a git patch, refresh it as a git patch
613 614 diffopts.git = any(
614 615 line.startswith(b'diff --git') for line in patchf
615 616 )
616 617 patchf.close()
617 618 return diffopts
618 619
619 620 def join(self, *p):
620 621 return os.path.join(self.path, *p)
621 622
622 623 def findseries(self, patch):
623 624 def matchpatch(l):
624 625 l = l.split(b'#', 1)[0]
625 626 return l.strip() == patch
626 627
627 628 for index, l in enumerate(self.fullseries):
628 629 if matchpatch(l):
629 630 return index
630 631 return None
631 632
632 633 guard_re = re.compile(br'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
633 634
634 635 def parseseries(self):
635 636 self.series = []
636 637 self.seriesguards = []
637 638 for l in self.fullseries:
638 639 h = l.find(b'#')
639 640 if h == -1:
640 641 patch = l
641 642 comment = b''
642 643 elif h == 0:
643 644 continue
644 645 else:
645 646 patch = l[:h]
646 647 comment = l[h:]
647 648 patch = patch.strip()
648 649 if patch:
649 650 if patch in self.series:
650 651 raise error.Abort(
651 652 _(b'%s appears more than once in %s')
652 653 % (patch, self.join(self.seriespath))
653 654 )
654 655 self.series.append(patch)
655 656 self.seriesguards.append(self.guard_re.findall(comment))
656 657
657 658 def checkguard(self, guard):
658 659 if not guard:
659 660 return _(b'guard cannot be an empty string')
660 661 bad_chars = b'# \t\r\n\f'
661 662 first = guard[0]
662 663 if first in b'-+':
663 664 return _(b'guard %r starts with invalid character: %r') % (
664 665 guard,
665 666 first,
666 667 )
667 668 for c in bad_chars:
668 669 if c in guard:
669 670 return _(b'invalid character in guard %r: %r') % (guard, c)
670 671
671 672 def setactive(self, guards):
672 673 for guard in guards:
673 674 bad = self.checkguard(guard)
674 675 if bad:
675 676 raise error.Abort(bad)
676 677 guards = sorted(set(guards))
677 678 self.ui.debug(b'active guards: %s\n' % b' '.join(guards))
678 679 self.activeguards = guards
679 680 self.guardsdirty = True
680 681
681 682 def active(self):
682 683 if self.activeguards is None:
683 684 self.activeguards = []
684 685 try:
685 686 guards = self.opener.read(self.guardspath).split()
686 687 except IOError as err:
687 688 if err.errno != errno.ENOENT:
688 689 raise
689 690 guards = []
690 691 for i, guard in enumerate(guards):
691 692 bad = self.checkguard(guard)
692 693 if bad:
693 694 self.ui.warn(
694 695 b'%s:%d: %s\n'
695 696 % (self.join(self.guardspath), i + 1, bad)
696 697 )
697 698 else:
698 699 self.activeguards.append(guard)
699 700 return self.activeguards
700 701
701 702 def setguards(self, idx, guards):
702 703 for g in guards:
703 704 if len(g) < 2:
704 705 raise error.Abort(_(b'guard %r too short') % g)
705 706 if g[0] not in b'-+':
706 707 raise error.Abort(_(b'guard %r starts with invalid char') % g)
707 708 bad = self.checkguard(g[1:])
708 709 if bad:
709 710 raise error.Abort(bad)
710 711 drop = self.guard_re.sub(b'', self.fullseries[idx])
711 712 self.fullseries[idx] = drop + b''.join([b' #' + g for g in guards])
712 713 self.parseseries()
713 714 self.seriesdirty = True
714 715
715 716 def pushable(self, idx):
716 717 if isinstance(idx, bytes):
717 718 idx = self.series.index(idx)
718 719 patchguards = self.seriesguards[idx]
719 720 if not patchguards:
720 721 return True, None
721 722 guards = self.active()
722 723 exactneg = [
723 724 g for g in patchguards if g.startswith(b'-') and g[1:] in guards
724 725 ]
725 726 if exactneg:
726 727 return False, stringutil.pprint(exactneg[0])
727 728 pos = [g for g in patchguards if g.startswith(b'+')]
728 729 exactpos = [g for g in pos if g[1:] in guards]
729 730 if pos:
730 731 if exactpos:
731 732 return True, stringutil.pprint(exactpos[0])
732 733 return False, b' '.join([stringutil.pprint(p) for p in pos])
733 734 return True, b''
734 735
735 736 def explainpushable(self, idx, all_patches=False):
736 737 if all_patches:
737 738 write = self.ui.write
738 739 else:
739 740 write = self.ui.warn
740 741
741 742 if all_patches or self.ui.verbose:
742 743 if isinstance(idx, bytes):
743 744 idx = self.series.index(idx)
744 745 pushable, why = self.pushable(idx)
745 746 if all_patches and pushable:
746 747 if why is None:
747 748 write(
748 749 _(b'allowing %s - no guards in effect\n')
749 750 % self.series[idx]
750 751 )
751 752 else:
752 753 if not why:
753 754 write(
754 755 _(b'allowing %s - no matching negative guards\n')
755 756 % self.series[idx]
756 757 )
757 758 else:
758 759 write(
759 760 _(b'allowing %s - guarded by %s\n')
760 761 % (self.series[idx], why)
761 762 )
762 763 if not pushable:
763 764 if why:
764 765 write(
765 766 _(b'skipping %s - guarded by %s\n')
766 767 % (self.series[idx], why)
767 768 )
768 769 else:
769 770 write(
770 771 _(b'skipping %s - no matching guards\n')
771 772 % self.series[idx]
772 773 )
773 774
774 775 def savedirty(self):
775 776 def writelist(items, path):
776 777 fp = self.opener(path, b'wb')
777 778 for i in items:
778 779 fp.write(b"%s\n" % i)
779 780 fp.close()
780 781
781 782 if self.applieddirty:
782 783 writelist(map(bytes, self.applied), self.statuspath)
783 784 self.applieddirty = False
784 785 if self.seriesdirty:
785 786 writelist(self.fullseries, self.seriespath)
786 787 self.seriesdirty = False
787 788 if self.guardsdirty:
788 789 writelist(self.activeguards, self.guardspath)
789 790 self.guardsdirty = False
790 791 if self.added:
791 792 qrepo = self.qrepo()
792 793 if qrepo:
793 794 qrepo[None].add(f for f in self.added if f not in qrepo[None])
794 795 self.added = []
795 796
796 797 def removeundo(self, repo):
797 798 undo = repo.sjoin(b'undo')
798 799 if not os.path.exists(undo):
799 800 return
800 801 try:
801 802 os.unlink(undo)
802 803 except OSError as inst:
803 804 self.ui.warn(
804 805 _(b'error removing undo: %s\n') % stringutil.forcebytestr(inst)
805 806 )
806 807
807 808 def backup(self, repo, files, copy=False):
808 809 # backup local changes in --force case
809 810 for f in sorted(files):
810 811 absf = repo.wjoin(f)
811 812 if os.path.lexists(absf):
812 813 absorig = scmutil.backuppath(self.ui, repo, f)
813 814 self.ui.note(
814 815 _(b'saving current version of %s as %s\n')
815 816 % (f, os.path.relpath(absorig))
816 817 )
817 818
818 819 if copy:
819 820 util.copyfile(absf, absorig)
820 821 else:
821 822 util.rename(absf, absorig)
822 823
823 824 def printdiff(
824 825 self,
825 826 repo,
826 827 diffopts,
827 828 node1,
828 829 node2=None,
829 830 files=None,
830 831 fp=None,
831 832 changes=None,
832 833 opts=None,
833 834 ):
834 835 if opts is None:
835 836 opts = {}
836 837 stat = opts.get(b'stat')
837 838 m = scmutil.match(repo[node1], files, opts)
838 839 logcmdutil.diffordiffstat(
839 840 self.ui,
840 841 repo,
841 842 diffopts,
842 843 repo[node1],
843 844 repo[node2],
844 845 m,
845 846 changes,
846 847 stat,
847 848 fp,
848 849 )
849 850
850 851 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
851 852 # first try just applying the patch
852 853 (err, n) = self.apply(
853 854 repo, [patch], update_status=False, strict=True, merge=rev
854 855 )
855 856
856 857 if err == 0:
857 858 return (err, n)
858 859
859 860 if n is None:
860 861 raise error.Abort(_(b"apply failed for patch %s") % patch)
861 862
862 863 self.ui.warn(_(b"patch didn't work out, merging %s\n") % patch)
863 864
864 865 # apply failed, strip away that rev and merge.
865 866 hg.clean(repo, head)
866 867 strip(self.ui, repo, [n], update=False, backup=False)
867 868
868 869 ctx = repo[rev]
869 870 ret = hg.merge(ctx, remind=False)
870 871 if ret:
871 872 raise error.Abort(_(b"update returned %d") % ret)
872 873 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
873 874 if n is None:
874 875 raise error.Abort(_(b"repo commit failed"))
875 876 try:
876 877 ph = patchheader(mergeq.join(patch), self.plainmode)
877 878 except Exception:
878 879 raise error.Abort(_(b"unable to read %s") % patch)
879 880
880 881 diffopts = self.patchopts(diffopts, patch)
881 882 patchf = self.opener(patch, b"w")
882 883 comments = bytes(ph)
883 884 if comments:
884 885 patchf.write(comments)
885 886 self.printdiff(repo, diffopts, head, n, fp=patchf)
886 887 patchf.close()
887 888 self.removeundo(repo)
888 889 return (0, n)
889 890
890 891 def qparents(self, repo, rev=None):
891 892 """return the mq handled parent or p1
892 893
893 894 In some case where mq get himself in being the parent of a merge the
894 895 appropriate parent may be p2.
895 896 (eg: an in progress merge started with mq disabled)
896 897
897 898 If no parent are managed by mq, p1 is returned.
898 899 """
899 900 if rev is None:
900 901 (p1, p2) = repo.dirstate.parents()
901 902 if p2 == nullid:
902 903 return p1
903 904 if not self.applied:
904 905 return None
905 906 return self.applied[-1].node
906 907 p1, p2 = repo.changelog.parents(rev)
907 908 if p2 != nullid and p2 in [x.node for x in self.applied]:
908 909 return p2
909 910 return p1
910 911
911 912 def mergepatch(self, repo, mergeq, series, diffopts):
912 913 if not self.applied:
913 914 # each of the patches merged in will have two parents. This
914 915 # can confuse the qrefresh, qdiff, and strip code because it
915 916 # needs to know which parent is actually in the patch queue.
916 917 # so, we insert a merge marker with only one parent. This way
917 918 # the first patch in the queue is never a merge patch
918 919 #
919 920 pname = b".hg.patches.merge.marker"
920 921 n = newcommit(repo, None, b'[mq]: merge marker', force=True)
921 922 self.removeundo(repo)
922 923 self.applied.append(statusentry(n, pname))
923 924 self.applieddirty = True
924 925
925 926 head = self.qparents(repo)
926 927
927 928 for patch in series:
928 929 patch = mergeq.lookup(patch, strict=True)
929 930 if not patch:
930 931 self.ui.warn(_(b"patch %s does not exist\n") % patch)
931 932 return (1, None)
932 933 pushable, reason = self.pushable(patch)
933 934 if not pushable:
934 935 self.explainpushable(patch, all_patches=True)
935 936 continue
936 937 info = mergeq.isapplied(patch)
937 938 if not info:
938 939 self.ui.warn(_(b"patch %s is not applied\n") % patch)
939 940 return (1, None)
940 941 rev = info[1]
941 942 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
942 943 if head:
943 944 self.applied.append(statusentry(head, patch))
944 945 self.applieddirty = True
945 946 if err:
946 947 return (err, head)
947 948 self.savedirty()
948 949 return (0, head)
949 950
950 951 def patch(self, repo, patchfile):
951 952 '''Apply patchfile to the working directory.
952 953 patchfile: name of patch file'''
953 954 files = set()
954 955 try:
955 956 fuzz = patchmod.patch(
956 957 self.ui, repo, patchfile, strip=1, files=files, eolmode=None
957 958 )
958 959 return (True, list(files), fuzz)
959 960 except Exception as inst:
960 961 self.ui.note(stringutil.forcebytestr(inst) + b'\n')
961 962 if not self.ui.verbose:
962 963 self.ui.warn(_(b"patch failed, unable to continue (try -v)\n"))
963 964 self.ui.traceback()
964 965 return (False, list(files), False)
965 966
966 967 def apply(
967 968 self,
968 969 repo,
969 970 series,
970 971 list=False,
971 972 update_status=True,
972 973 strict=False,
973 974 patchdir=None,
974 975 merge=None,
975 976 all_files=None,
976 977 tobackup=None,
977 978 keepchanges=False,
978 979 ):
979 980 wlock = lock = tr = None
980 981 try:
981 982 wlock = repo.wlock()
982 983 lock = repo.lock()
983 984 tr = repo.transaction(b"qpush")
984 985 try:
985 986 ret = self._apply(
986 987 repo,
987 988 series,
988 989 list,
989 990 update_status,
990 991 strict,
991 992 patchdir,
992 993 merge,
993 994 all_files=all_files,
994 995 tobackup=tobackup,
995 996 keepchanges=keepchanges,
996 997 )
997 998 tr.close()
998 999 self.savedirty()
999 1000 return ret
1000 1001 except AbortNoCleanup:
1001 1002 tr.close()
1002 1003 self.savedirty()
1003 1004 raise
1004 1005 except: # re-raises
1005 1006 try:
1006 1007 tr.abort()
1007 1008 finally:
1008 1009 self.invalidate()
1009 1010 raise
1010 1011 finally:
1011 1012 release(tr, lock, wlock)
1012 1013 self.removeundo(repo)
1013 1014
1014 1015 def _apply(
1015 1016 self,
1016 1017 repo,
1017 1018 series,
1018 1019 list=False,
1019 1020 update_status=True,
1020 1021 strict=False,
1021 1022 patchdir=None,
1022 1023 merge=None,
1023 1024 all_files=None,
1024 1025 tobackup=None,
1025 1026 keepchanges=False,
1026 1027 ):
1027 1028 """returns (error, hash)
1028 1029
1029 1030 error = 1 for unable to read, 2 for patch failed, 3 for patch
1030 1031 fuzz. tobackup is None or a set of files to backup before they
1031 1032 are modified by a patch.
1032 1033 """
1033 1034 # TODO unify with commands.py
1034 1035 if not patchdir:
1035 1036 patchdir = self.path
1036 1037 err = 0
1037 1038 n = None
1038 1039 for patchname in series:
1039 1040 pushable, reason = self.pushable(patchname)
1040 1041 if not pushable:
1041 1042 self.explainpushable(patchname, all_patches=True)
1042 1043 continue
1043 1044 self.ui.status(_(b"applying %s\n") % patchname)
1044 1045 pf = os.path.join(patchdir, patchname)
1045 1046
1046 1047 try:
1047 1048 ph = patchheader(self.join(patchname), self.plainmode)
1048 1049 except IOError:
1049 1050 self.ui.warn(_(b"unable to read %s\n") % patchname)
1050 1051 err = 1
1051 1052 break
1052 1053
1053 1054 message = ph.message
1054 1055 if not message:
1055 1056 # The commit message should not be translated
1056 1057 message = b"imported patch %s\n" % patchname
1057 1058 else:
1058 1059 if list:
1059 1060 # The commit message should not be translated
1060 1061 message.append(b"\nimported patch %s" % patchname)
1061 1062 message = b'\n'.join(message)
1062 1063
1063 1064 if ph.haspatch:
1064 1065 if tobackup:
1065 1066 touched = patchmod.changedfiles(self.ui, repo, pf)
1066 1067 touched = set(touched) & tobackup
1067 1068 if touched and keepchanges:
1068 1069 raise AbortNoCleanup(
1069 1070 _(b"conflicting local changes found"),
1070 1071 hint=_(b"did you forget to qrefresh?"),
1071 1072 )
1072 1073 self.backup(repo, touched, copy=True)
1073 1074 tobackup = tobackup - touched
1074 1075 (patcherr, files, fuzz) = self.patch(repo, pf)
1075 1076 if all_files is not None:
1076 1077 all_files.update(files)
1077 1078 patcherr = not patcherr
1078 1079 else:
1079 1080 self.ui.warn(_(b"patch %s is empty\n") % patchname)
1080 1081 patcherr, files, fuzz = 0, [], 0
1081 1082
1082 1083 if merge and files:
1083 1084 # Mark as removed/merged and update dirstate parent info
1084 1085 removed = []
1085 1086 merged = []
1086 1087 for f in files:
1087 1088 if os.path.lexists(repo.wjoin(f)):
1088 1089 merged.append(f)
1089 1090 else:
1090 1091 removed.append(f)
1091 1092 with repo.dirstate.parentchange():
1092 1093 for f in removed:
1093 1094 repo.dirstate.remove(f)
1094 1095 for f in merged:
1095 1096 repo.dirstate.merge(f)
1096 1097 p1 = repo.dirstate.p1()
1097 1098 repo.setparents(p1, merge)
1098 1099
1099 1100 if all_files and b'.hgsubstate' in all_files:
1100 1101 wctx = repo[None]
1101 1102 pctx = repo[b'.']
1102 1103 overwrite = False
1103 1104 mergedsubstate = subrepoutil.submerge(
1104 1105 repo, pctx, wctx, wctx, overwrite
1105 1106 )
1106 1107 files += mergedsubstate.keys()
1107 1108
1108 1109 match = scmutil.matchfiles(repo, files or [])
1109 1110 oldtip = repo.changelog.tip()
1110 1111 n = newcommit(
1111 1112 repo, None, message, ph.user, ph.date, match=match, force=True
1112 1113 )
1113 1114 if repo.changelog.tip() == oldtip:
1114 1115 raise error.Abort(
1115 1116 _(b"qpush exactly duplicates child changeset")
1116 1117 )
1117 1118 if n is None:
1118 1119 raise error.Abort(_(b"repository commit failed"))
1119 1120
1120 1121 if update_status:
1121 1122 self.applied.append(statusentry(n, patchname))
1122 1123
1123 1124 if patcherr:
1124 1125 self.ui.warn(
1125 1126 _(b"patch failed, rejects left in working directory\n")
1126 1127 )
1127 1128 err = 2
1128 1129 break
1129 1130
1130 1131 if fuzz and strict:
1131 1132 self.ui.warn(_(b"fuzz found when applying patch, stopping\n"))
1132 1133 err = 3
1133 1134 break
1134 1135 return (err, n)
1135 1136
1136 1137 def _cleanup(self, patches, numrevs, keep=False):
1137 1138 if not keep:
1138 1139 r = self.qrepo()
1139 1140 if r:
1140 1141 r[None].forget(patches)
1141 1142 for p in patches:
1142 1143 try:
1143 1144 os.unlink(self.join(p))
1144 1145 except OSError as inst:
1145 1146 if inst.errno != errno.ENOENT:
1146 1147 raise
1147 1148
1148 1149 qfinished = []
1149 1150 if numrevs:
1150 1151 qfinished = self.applied[:numrevs]
1151 1152 del self.applied[:numrevs]
1152 1153 self.applieddirty = True
1153 1154
1154 1155 unknown = []
1155 1156
1156 1157 sortedseries = []
1157 1158 for p in patches:
1158 1159 idx = self.findseries(p)
1159 1160 if idx is None:
1160 1161 sortedseries.append((-1, p))
1161 1162 else:
1162 1163 sortedseries.append((idx, p))
1163 1164
1164 1165 sortedseries.sort(reverse=True)
1165 1166 for (i, p) in sortedseries:
1166 1167 if i != -1:
1167 1168 del self.fullseries[i]
1168 1169 else:
1169 1170 unknown.append(p)
1170 1171
1171 1172 if unknown:
1172 1173 if numrevs:
1173 1174 rev = {entry.name: entry.node for entry in qfinished}
1174 1175 for p in unknown:
1175 1176 msg = _(b'revision %s refers to unknown patches: %s\n')
1176 1177 self.ui.warn(msg % (short(rev[p]), p))
1177 1178 else:
1178 1179 msg = _(b'unknown patches: %s\n')
1179 1180 raise error.Abort(b''.join(msg % p for p in unknown))
1180 1181
1181 1182 self.parseseries()
1182 1183 self.seriesdirty = True
1183 1184 return [entry.node for entry in qfinished]
1184 1185
1185 1186 def _revpatches(self, repo, revs):
1186 1187 firstrev = repo[self.applied[0].node].rev()
1187 1188 patches = []
1188 1189 for i, rev in enumerate(revs):
1189 1190
1190 1191 if rev < firstrev:
1191 1192 raise error.Abort(_(b'revision %d is not managed') % rev)
1192 1193
1193 1194 ctx = repo[rev]
1194 1195 base = self.applied[i].node
1195 1196 if ctx.node() != base:
1196 1197 msg = _(b'cannot delete revision %d above applied patches')
1197 1198 raise error.Abort(msg % rev)
1198 1199
1199 1200 patch = self.applied[i].name
1200 1201 for fmt in (b'[mq]: %s', b'imported patch %s'):
1201 1202 if ctx.description() == fmt % patch:
1202 1203 msg = _(b'patch %s finalized without changeset message\n')
1203 1204 repo.ui.status(msg % patch)
1204 1205 break
1205 1206
1206 1207 patches.append(patch)
1207 1208 return patches
1208 1209
1209 1210 def finish(self, repo, revs):
1210 1211 # Manually trigger phase computation to ensure phasedefaults is
1211 1212 # executed before we remove the patches.
1212 1213 repo._phasecache
1213 1214 patches = self._revpatches(repo, sorted(revs))
1214 1215 qfinished = self._cleanup(patches, len(patches))
1215 1216 if qfinished and repo.ui.configbool(b'mq', b'secret'):
1216 1217 # only use this logic when the secret option is added
1217 1218 oldqbase = repo[qfinished[0]]
1218 1219 tphase = phases.newcommitphase(repo.ui)
1219 1220 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
1220 1221 with repo.transaction(b'qfinish') as tr:
1221 1222 phases.advanceboundary(repo, tr, tphase, qfinished)
1222 1223
1223 1224 def delete(self, repo, patches, opts):
1224 1225 if not patches and not opts.get(b'rev'):
1225 1226 raise error.Abort(
1226 1227 _(b'qdelete requires at least one revision or patch name')
1227 1228 )
1228 1229
1229 1230 realpatches = []
1230 1231 for patch in patches:
1231 1232 patch = self.lookup(patch, strict=True)
1232 1233 info = self.isapplied(patch)
1233 1234 if info:
1234 1235 raise error.Abort(_(b"cannot delete applied patch %s") % patch)
1235 1236 if patch not in self.series:
1236 1237 raise error.Abort(_(b"patch %s not in series file") % patch)
1237 1238 if patch not in realpatches:
1238 1239 realpatches.append(patch)
1239 1240
1240 1241 numrevs = 0
1241 1242 if opts.get(b'rev'):
1242 1243 if not self.applied:
1243 1244 raise error.Abort(_(b'no patches applied'))
1244 1245 revs = scmutil.revrange(repo, opts.get(b'rev'))
1245 1246 revs.sort()
1246 1247 revpatches = self._revpatches(repo, revs)
1247 1248 realpatches += revpatches
1248 1249 numrevs = len(revpatches)
1249 1250
1250 1251 self._cleanup(realpatches, numrevs, opts.get(b'keep'))
1251 1252
1252 1253 def checktoppatch(self, repo):
1253 1254 '''check that working directory is at qtip'''
1254 1255 if self.applied:
1255 1256 top = self.applied[-1].node
1256 1257 patch = self.applied[-1].name
1257 1258 if repo.dirstate.p1() != top:
1258 1259 raise error.Abort(_(b"working directory revision is not qtip"))
1259 1260 return top, patch
1260 1261 return None, None
1261 1262
1262 1263 def putsubstate2changes(self, substatestate, changes):
1263 1264 if isinstance(changes, list):
1264 1265 mar = changes[:3]
1265 1266 else:
1266 1267 mar = (changes.modified, changes.added, changes.removed)
1267 1268 if any((b'.hgsubstate' in files for files in mar)):
1268 1269 return # already listed up
1269 1270 # not yet listed up
1270 1271 if substatestate in b'a?':
1271 1272 mar[1].append(b'.hgsubstate')
1272 1273 elif substatestate in b'r':
1273 1274 mar[2].append(b'.hgsubstate')
1274 1275 else: # modified
1275 1276 mar[0].append(b'.hgsubstate')
1276 1277
1277 1278 def checklocalchanges(self, repo, force=False, refresh=True):
1278 1279 excsuffix = b''
1279 1280 if refresh:
1280 1281 excsuffix = b', qrefresh first'
1281 1282 # plain versions for i18n tool to detect them
1282 1283 _(b"local changes found, qrefresh first")
1283 1284 _(b"local changed subrepos found, qrefresh first")
1284 1285
1285 1286 s = repo.status()
1286 1287 if not force:
1287 1288 cmdutil.checkunfinished(repo)
1288 1289 if s.modified or s.added or s.removed or s.deleted:
1289 1290 _(b"local changes found") # i18n tool detection
1290 1291 raise error.Abort(_(b"local changes found" + excsuffix))
1291 1292 if checksubstate(repo):
1292 1293 _(b"local changed subrepos found") # i18n tool detection
1293 1294 raise error.Abort(
1294 1295 _(b"local changed subrepos found" + excsuffix)
1295 1296 )
1296 1297 else:
1297 1298 cmdutil.checkunfinished(repo, skipmerge=True)
1298 1299 return s
1299 1300
1300 1301 _reserved = (b'series', b'status', b'guards', b'.', b'..')
1301 1302
1302 1303 def checkreservedname(self, name):
1303 1304 if name in self._reserved:
1304 1305 raise error.Abort(
1305 1306 _(b'"%s" cannot be used as the name of a patch') % name
1306 1307 )
1307 1308 if name != name.strip():
1308 1309 # whitespace is stripped by parseseries()
1309 1310 raise error.Abort(
1310 1311 _(b'patch name cannot begin or end with whitespace')
1311 1312 )
1312 1313 for prefix in (b'.hg', b'.mq'):
1313 1314 if name.startswith(prefix):
1314 1315 raise error.Abort(
1315 1316 _(b'patch name cannot begin with "%s"') % prefix
1316 1317 )
1317 1318 for c in (b'#', b':', b'\r', b'\n'):
1318 1319 if c in name:
1319 1320 raise error.Abort(
1320 1321 _(b'%r cannot be used in the name of a patch')
1321 1322 % pycompat.bytestr(c)
1322 1323 )
1323 1324
1324 1325 def checkpatchname(self, name, force=False):
1325 1326 self.checkreservedname(name)
1326 1327 if not force and os.path.exists(self.join(name)):
1327 1328 if os.path.isdir(self.join(name)):
1328 1329 raise error.Abort(
1329 1330 _(b'"%s" already exists as a directory') % name
1330 1331 )
1331 1332 else:
1332 1333 raise error.Abort(_(b'patch "%s" already exists') % name)
1333 1334
1334 1335 def makepatchname(self, title, fallbackname):
1335 1336 """Return a suitable filename for title, adding a suffix to make
1336 1337 it unique in the existing list"""
1337 1338 namebase = re.sub(br'[\s\W_]+', b'_', title.lower()).strip(b'_')
1338 1339 namebase = namebase[:75] # avoid too long name (issue5117)
1339 1340 if namebase:
1340 1341 try:
1341 1342 self.checkreservedname(namebase)
1342 1343 except error.Abort:
1343 1344 namebase = fallbackname
1344 1345 else:
1345 1346 namebase = fallbackname
1346 1347 name = namebase
1347 1348 i = 0
1348 1349 while True:
1349 1350 if name not in self.fullseries:
1350 1351 try:
1351 1352 self.checkpatchname(name)
1352 1353 break
1353 1354 except error.Abort:
1354 1355 pass
1355 1356 i += 1
1356 1357 name = b'%s__%d' % (namebase, i)
1357 1358 return name
1358 1359
1359 1360 def checkkeepchanges(self, keepchanges, force):
1360 1361 if force and keepchanges:
1361 1362 raise error.Abort(_(b'cannot use both --force and --keep-changes'))
1362 1363
1363 1364 def new(self, repo, patchfn, *pats, **opts):
1364 1365 """options:
1365 1366 msg: a string or a no-argument function returning a string
1366 1367 """
1367 1368 opts = pycompat.byteskwargs(opts)
1368 1369 msg = opts.get(b'msg')
1369 1370 edit = opts.get(b'edit')
1370 1371 editform = opts.get(b'editform', b'mq.qnew')
1371 1372 user = opts.get(b'user')
1372 1373 date = opts.get(b'date')
1373 1374 if date:
1374 1375 date = dateutil.parsedate(date)
1375 1376 diffopts = self.diffopts({b'git': opts.get(b'git')}, plain=True)
1376 1377 if opts.get(b'checkname', True):
1377 1378 self.checkpatchname(patchfn)
1378 1379 inclsubs = checksubstate(repo)
1379 1380 if inclsubs:
1380 1381 substatestate = repo.dirstate[b'.hgsubstate']
1381 1382 if opts.get(b'include') or opts.get(b'exclude') or pats:
1382 1383 # detect missing files in pats
1383 1384 def badfn(f, msg):
1384 1385 if f != b'.hgsubstate': # .hgsubstate is auto-created
1385 1386 raise error.Abort(b'%s: %s' % (f, msg))
1386 1387
1387 1388 match = scmutil.match(repo[None], pats, opts, badfn=badfn)
1388 1389 changes = repo.status(match=match)
1389 1390 else:
1390 1391 changes = self.checklocalchanges(repo, force=True)
1391 1392 commitfiles = list(inclsubs)
1392 1393 commitfiles.extend(changes.modified)
1393 1394 commitfiles.extend(changes.added)
1394 1395 commitfiles.extend(changes.removed)
1395 1396 match = scmutil.matchfiles(repo, commitfiles)
1396 1397 if len(repo[None].parents()) > 1:
1397 1398 raise error.Abort(_(b'cannot manage merge changesets'))
1398 1399 self.checktoppatch(repo)
1399 1400 insert = self.fullseriesend()
1400 1401 with repo.wlock():
1401 1402 try:
1402 1403 # if patch file write fails, abort early
1403 1404 p = self.opener(patchfn, b"w")
1404 1405 except IOError as e:
1405 1406 raise error.Abort(
1406 1407 _(b'cannot write patch "%s": %s')
1407 1408 % (patchfn, encoding.strtolocal(e.strerror))
1408 1409 )
1409 1410 try:
1410 1411 defaultmsg = b"[mq]: %s" % patchfn
1411 1412 editor = cmdutil.getcommiteditor(editform=editform)
1412 1413 if edit:
1413 1414
1414 1415 def finishdesc(desc):
1415 1416 if desc.rstrip():
1416 1417 return desc
1417 1418 else:
1418 1419 return defaultmsg
1419 1420
1420 1421 # i18n: this message is shown in editor with "HG: " prefix
1421 1422 extramsg = _(b'Leave message empty to use default message.')
1422 1423 editor = cmdutil.getcommiteditor(
1423 1424 finishdesc=finishdesc,
1424 1425 extramsg=extramsg,
1425 1426 editform=editform,
1426 1427 )
1427 1428 commitmsg = msg
1428 1429 else:
1429 1430 commitmsg = msg or defaultmsg
1430 1431
1431 1432 n = newcommit(
1432 1433 repo,
1433 1434 None,
1434 1435 commitmsg,
1435 1436 user,
1436 1437 date,
1437 1438 match=match,
1438 1439 force=True,
1439 1440 editor=editor,
1440 1441 )
1441 1442 if n is None:
1442 1443 raise error.Abort(_(b"repo commit failed"))
1443 1444 try:
1444 1445 self.fullseries[insert:insert] = [patchfn]
1445 1446 self.applied.append(statusentry(n, patchfn))
1446 1447 self.parseseries()
1447 1448 self.seriesdirty = True
1448 1449 self.applieddirty = True
1449 1450 nctx = repo[n]
1450 1451 ph = patchheader(self.join(patchfn), self.plainmode)
1451 1452 if user:
1452 1453 ph.setuser(user)
1453 1454 if date:
1454 1455 ph.setdate(b'%d %d' % date)
1455 1456 ph.setparent(hex(nctx.p1().node()))
1456 1457 msg = nctx.description().strip()
1457 1458 if msg == defaultmsg.strip():
1458 1459 msg = b''
1459 1460 ph.setmessage(msg)
1460 1461 p.write(bytes(ph))
1461 1462 if commitfiles:
1462 1463 parent = self.qparents(repo, n)
1463 1464 if inclsubs:
1464 1465 self.putsubstate2changes(substatestate, changes)
1465 1466 chunks = patchmod.diff(
1466 1467 repo,
1467 1468 node1=parent,
1468 1469 node2=n,
1469 1470 changes=changes,
1470 1471 opts=diffopts,
1471 1472 )
1472 1473 for chunk in chunks:
1473 1474 p.write(chunk)
1474 1475 p.close()
1475 1476 r = self.qrepo()
1476 1477 if r:
1477 1478 r[None].add([patchfn])
1478 1479 except: # re-raises
1479 1480 repo.rollback()
1480 1481 raise
1481 1482 except Exception:
1482 1483 patchpath = self.join(patchfn)
1483 1484 try:
1484 1485 os.unlink(patchpath)
1485 1486 except OSError:
1486 1487 self.ui.warn(_(b'error unlinking %s\n') % patchpath)
1487 1488 raise
1488 1489 self.removeundo(repo)
1489 1490
1490 1491 def isapplied(self, patch):
1491 1492 """returns (index, rev, patch)"""
1492 1493 for i, a in enumerate(self.applied):
1493 1494 if a.name == patch:
1494 1495 return (i, a.node, a.name)
1495 1496 return None
1496 1497
1497 1498 # if the exact patch name does not exist, we try a few
1498 1499 # variations. If strict is passed, we try only #1
1499 1500 #
1500 1501 # 1) a number (as string) to indicate an offset in the series file
1501 1502 # 2) a unique substring of the patch name was given
1502 1503 # 3) patchname[-+]num to indicate an offset in the series file
1503 1504 def lookup(self, patch, strict=False):
1504 1505 def partialname(s):
1505 1506 if s in self.series:
1506 1507 return s
1507 1508 matches = [x for x in self.series if s in x]
1508 1509 if len(matches) > 1:
1509 1510 self.ui.warn(_(b'patch name "%s" is ambiguous:\n') % s)
1510 1511 for m in matches:
1511 1512 self.ui.warn(b' %s\n' % m)
1512 1513 return None
1513 1514 if matches:
1514 1515 return matches[0]
1515 1516 if self.series and self.applied:
1516 1517 if s == b'qtip':
1517 1518 return self.series[self.seriesend(True) - 1]
1518 1519 if s == b'qbase':
1519 1520 return self.series[0]
1520 1521 return None
1521 1522
1522 1523 if patch in self.series:
1523 1524 return patch
1524 1525
1525 1526 if not os.path.isfile(self.join(patch)):
1526 1527 try:
1527 1528 sno = int(patch)
1528 1529 except (ValueError, OverflowError):
1529 1530 pass
1530 1531 else:
1531 1532 if -len(self.series) <= sno < len(self.series):
1532 1533 return self.series[sno]
1533 1534
1534 1535 if not strict:
1535 1536 res = partialname(patch)
1536 1537 if res:
1537 1538 return res
1538 1539 minus = patch.rfind(b'-')
1539 1540 if minus >= 0:
1540 1541 res = partialname(patch[:minus])
1541 1542 if res:
1542 1543 i = self.series.index(res)
1543 1544 try:
1544 1545 off = int(patch[minus + 1 :] or 1)
1545 1546 except (ValueError, OverflowError):
1546 1547 pass
1547 1548 else:
1548 1549 if i - off >= 0:
1549 1550 return self.series[i - off]
1550 1551 plus = patch.rfind(b'+')
1551 1552 if plus >= 0:
1552 1553 res = partialname(patch[:plus])
1553 1554 if res:
1554 1555 i = self.series.index(res)
1555 1556 try:
1556 1557 off = int(patch[plus + 1 :] or 1)
1557 1558 except (ValueError, OverflowError):
1558 1559 pass
1559 1560 else:
1560 1561 if i + off < len(self.series):
1561 1562 return self.series[i + off]
1562 1563 raise error.Abort(_(b"patch %s not in series") % patch)
1563 1564
1564 1565 def push(
1565 1566 self,
1566 1567 repo,
1567 1568 patch=None,
1568 1569 force=False,
1569 1570 list=False,
1570 1571 mergeq=None,
1571 1572 all=False,
1572 1573 move=False,
1573 1574 exact=False,
1574 1575 nobackup=False,
1575 1576 keepchanges=False,
1576 1577 ):
1577 1578 self.checkkeepchanges(keepchanges, force)
1578 1579 diffopts = self.diffopts()
1579 1580 with repo.wlock():
1580 1581 heads = []
1581 1582 for hs in repo.branchmap().iterheads():
1582 1583 heads.extend(hs)
1583 1584 if not heads:
1584 1585 heads = [nullid]
1585 1586 if repo.dirstate.p1() not in heads and not exact:
1586 1587 self.ui.status(_(b"(working directory not at a head)\n"))
1587 1588
1588 1589 if not self.series:
1589 1590 self.ui.warn(_(b'no patches in series\n'))
1590 1591 return 0
1591 1592
1592 1593 # Suppose our series file is: A B C and the current 'top'
1593 1594 # patch is B. qpush C should be performed (moving forward)
1594 1595 # qpush B is a NOP (no change) qpush A is an error (can't
1595 1596 # go backwards with qpush)
1596 1597 if patch:
1597 1598 patch = self.lookup(patch)
1598 1599 info = self.isapplied(patch)
1599 1600 if info and info[0] >= len(self.applied) - 1:
1600 1601 self.ui.warn(
1601 1602 _(b'qpush: %s is already at the top\n') % patch
1602 1603 )
1603 1604 return 0
1604 1605
1605 1606 pushable, reason = self.pushable(patch)
1606 1607 if pushable:
1607 1608 if self.series.index(patch) < self.seriesend():
1608 1609 raise error.Abort(
1609 1610 _(b"cannot push to a previous patch: %s") % patch
1610 1611 )
1611 1612 else:
1612 1613 if reason:
1613 1614 reason = _(b'guarded by %s') % reason
1614 1615 else:
1615 1616 reason = _(b'no matching guards')
1616 1617 self.ui.warn(
1617 1618 _(b"cannot push '%s' - %s\n") % (patch, reason)
1618 1619 )
1619 1620 return 1
1620 1621 elif all:
1621 1622 patch = self.series[-1]
1622 1623 if self.isapplied(patch):
1623 1624 self.ui.warn(_(b'all patches are currently applied\n'))
1624 1625 return 0
1625 1626
1626 1627 # Following the above example, starting at 'top' of B:
1627 1628 # qpush should be performed (pushes C), but a subsequent
1628 1629 # qpush without an argument is an error (nothing to
1629 1630 # apply). This allows a loop of "...while hg qpush..." to
1630 1631 # work as it detects an error when done
1631 1632 start = self.seriesend()
1632 1633 if start == len(self.series):
1633 1634 self.ui.warn(_(b'patch series already fully applied\n'))
1634 1635 return 1
1635 1636 if not force and not keepchanges:
1636 1637 self.checklocalchanges(repo, refresh=self.applied)
1637 1638
1638 1639 if exact:
1639 1640 if keepchanges:
1640 1641 raise error.Abort(
1641 1642 _(b"cannot use --exact and --keep-changes together")
1642 1643 )
1643 1644 if move:
1644 1645 raise error.Abort(
1645 1646 _(b'cannot use --exact and --move together')
1646 1647 )
1647 1648 if self.applied:
1648 1649 raise error.Abort(
1649 1650 _(b'cannot push --exact with applied patches')
1650 1651 )
1651 1652 root = self.series[start]
1652 1653 target = patchheader(self.join(root), self.plainmode).parent
1653 1654 if not target:
1654 1655 raise error.Abort(
1655 1656 _(b"%s does not have a parent recorded") % root
1656 1657 )
1657 1658 if not repo[target] == repo[b'.']:
1658 1659 hg.update(repo, target)
1659 1660
1660 1661 if move:
1661 1662 if not patch:
1662 1663 raise error.Abort(_(b"please specify the patch to move"))
1663 1664 for fullstart, rpn in enumerate(self.fullseries):
1664 1665 # strip markers for patch guards
1665 1666 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1666 1667 break
1667 1668 for i, rpn in enumerate(self.fullseries[fullstart:]):
1668 1669 # strip markers for patch guards
1669 1670 if self.guard_re.split(rpn, 1)[0] == patch:
1670 1671 break
1671 1672 index = fullstart + i
1672 1673 assert index < len(self.fullseries)
1673 1674 fullpatch = self.fullseries[index]
1674 1675 del self.fullseries[index]
1675 1676 self.fullseries.insert(fullstart, fullpatch)
1676 1677 self.parseseries()
1677 1678 self.seriesdirty = True
1678 1679
1679 1680 self.applieddirty = True
1680 1681 if start > 0:
1681 1682 self.checktoppatch(repo)
1682 1683 if not patch:
1683 1684 patch = self.series[start]
1684 1685 end = start + 1
1685 1686 else:
1686 1687 end = self.series.index(patch, start) + 1
1687 1688
1688 1689 tobackup = set()
1689 1690 if (not nobackup and force) or keepchanges:
1690 1691 status = self.checklocalchanges(repo, force=True)
1691 1692 if keepchanges:
1692 1693 tobackup.update(
1693 1694 status.modified
1694 1695 + status.added
1695 1696 + status.removed
1696 1697 + status.deleted
1697 1698 )
1698 1699 else:
1699 1700 tobackup.update(status.modified + status.added)
1700 1701
1701 1702 s = self.series[start:end]
1702 1703 all_files = set()
1703 1704 try:
1704 1705 if mergeq:
1705 1706 ret = self.mergepatch(repo, mergeq, s, diffopts)
1706 1707 else:
1707 1708 ret = self.apply(
1708 1709 repo,
1709 1710 s,
1710 1711 list,
1711 1712 all_files=all_files,
1712 1713 tobackup=tobackup,
1713 1714 keepchanges=keepchanges,
1714 1715 )
1715 1716 except AbortNoCleanup:
1716 1717 raise
1717 1718 except: # re-raises
1718 1719 self.ui.warn(_(b'cleaning up working directory...\n'))
1719 1720 cmdutil.revert(
1720 1721 self.ui, repo, repo[b'.'], no_backup=True,
1721 1722 )
1722 1723 # only remove unknown files that we know we touched or
1723 1724 # created while patching
1724 1725 for f in all_files:
1725 1726 if f not in repo.dirstate:
1726 1727 repo.wvfs.unlinkpath(f, ignoremissing=True)
1727 1728 self.ui.warn(_(b'done\n'))
1728 1729 raise
1729 1730
1730 1731 if not self.applied:
1731 1732 return ret[0]
1732 1733 top = self.applied[-1].name
1733 1734 if ret[0] and ret[0] > 1:
1734 1735 msg = _(b"errors during apply, please fix and qrefresh %s\n")
1735 1736 self.ui.write(msg % top)
1736 1737 else:
1737 1738 self.ui.write(_(b"now at: %s\n") % top)
1738 1739 return ret[0]
1739 1740
1740 1741 def pop(
1741 1742 self,
1742 1743 repo,
1743 1744 patch=None,
1744 1745 force=False,
1745 1746 update=True,
1746 1747 all=False,
1747 1748 nobackup=False,
1748 1749 keepchanges=False,
1749 1750 ):
1750 1751 self.checkkeepchanges(keepchanges, force)
1751 1752 with repo.wlock():
1752 1753 if patch:
1753 1754 # index, rev, patch
1754 1755 info = self.isapplied(patch)
1755 1756 if not info:
1756 1757 patch = self.lookup(patch)
1757 1758 info = self.isapplied(patch)
1758 1759 if not info:
1759 1760 raise error.Abort(_(b"patch %s is not applied") % patch)
1760 1761
1761 1762 if not self.applied:
1762 1763 # Allow qpop -a to work repeatedly,
1763 1764 # but not qpop without an argument
1764 1765 self.ui.warn(_(b"no patches applied\n"))
1765 1766 return not all
1766 1767
1767 1768 if all:
1768 1769 start = 0
1769 1770 elif patch:
1770 1771 start = info[0] + 1
1771 1772 else:
1772 1773 start = len(self.applied) - 1
1773 1774
1774 1775 if start >= len(self.applied):
1775 1776 self.ui.warn(_(b"qpop: %s is already at the top\n") % patch)
1776 1777 return
1777 1778
1778 1779 if not update:
1779 1780 parents = repo.dirstate.parents()
1780 1781 rr = [x.node for x in self.applied]
1781 1782 for p in parents:
1782 1783 if p in rr:
1783 1784 self.ui.warn(_(b"qpop: forcing dirstate update\n"))
1784 1785 update = True
1785 1786 else:
1786 1787 parents = [p.node() for p in repo[None].parents()]
1787 1788 update = any(
1788 1789 entry.node in parents for entry in self.applied[start:]
1789 1790 )
1790 1791
1791 1792 tobackup = set()
1792 1793 if update:
1793 1794 s = self.checklocalchanges(repo, force=force or keepchanges)
1794 1795 if force:
1795 1796 if not nobackup:
1796 1797 tobackup.update(s.modified + s.added)
1797 1798 elif keepchanges:
1798 1799 tobackup.update(
1799 1800 s.modified + s.added + s.removed + s.deleted
1800 1801 )
1801 1802
1802 1803 self.applieddirty = True
1803 1804 end = len(self.applied)
1804 1805 rev = self.applied[start].node
1805 1806
1806 1807 try:
1807 1808 heads = repo.changelog.heads(rev)
1808 1809 except error.LookupError:
1809 1810 node = short(rev)
1810 1811 raise error.Abort(_(b'trying to pop unknown node %s') % node)
1811 1812
1812 1813 if heads != [self.applied[-1].node]:
1813 1814 raise error.Abort(
1814 1815 _(
1815 1816 b"popping would remove a revision not "
1816 1817 b"managed by this patch queue"
1817 1818 )
1818 1819 )
1819 1820 if not repo[self.applied[-1].node].mutable():
1820 1821 raise error.Abort(
1821 1822 _(b"popping would remove a public revision"),
1822 1823 hint=_(b"see 'hg help phases' for details"),
1823 1824 )
1824 1825
1825 1826 # we know there are no local changes, so we can make a simplified
1826 1827 # form of hg.update.
1827 1828 if update:
1828 1829 qp = self.qparents(repo, rev)
1829 1830 ctx = repo[qp]
1830 1831 st = repo.status(qp, b'.')
1831 1832 m, a, r, d = st.modified, st.added, st.removed, st.deleted
1832 1833 if d:
1833 1834 raise error.Abort(_(b"deletions found between repo revs"))
1834 1835
1835 1836 tobackup = set(a + m + r) & tobackup
1836 1837 if keepchanges and tobackup:
1837 1838 raise error.Abort(_(b"local changes found, qrefresh first"))
1838 1839 self.backup(repo, tobackup)
1839 1840 with repo.dirstate.parentchange():
1840 1841 for f in a:
1841 1842 repo.wvfs.unlinkpath(f, ignoremissing=True)
1842 1843 repo.dirstate.drop(f)
1843 1844 for f in m + r:
1844 1845 fctx = ctx[f]
1845 1846 repo.wwrite(f, fctx.data(), fctx.flags())
1846 1847 repo.dirstate.normal(f)
1847 1848 repo.setparents(qp, nullid)
1848 1849 for patch in reversed(self.applied[start:end]):
1849 1850 self.ui.status(_(b"popping %s\n") % patch.name)
1850 1851 del self.applied[start:end]
1851 1852 strip(self.ui, repo, [rev], update=False, backup=False)
1852 1853 for s, state in repo[b'.'].substate.items():
1853 1854 repo[b'.'].sub(s).get(state)
1854 1855 if self.applied:
1855 1856 self.ui.write(_(b"now at: %s\n") % self.applied[-1].name)
1856 1857 else:
1857 1858 self.ui.write(_(b"patch queue now empty\n"))
1858 1859
1859 1860 def diff(self, repo, pats, opts):
1860 1861 top, patch = self.checktoppatch(repo)
1861 1862 if not top:
1862 1863 self.ui.write(_(b"no patches applied\n"))
1863 1864 return
1864 1865 qp = self.qparents(repo, top)
1865 1866 if opts.get(b'reverse'):
1866 1867 node1, node2 = None, qp
1867 1868 else:
1868 1869 node1, node2 = qp, None
1869 1870 diffopts = self.diffopts(opts, patch)
1870 1871 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1871 1872
1872 1873 def refresh(self, repo, pats=None, **opts):
1873 1874 opts = pycompat.byteskwargs(opts)
1874 1875 if not self.applied:
1875 1876 self.ui.write(_(b"no patches applied\n"))
1876 1877 return 1
1877 1878 msg = opts.get(b'msg', b'').rstrip()
1878 1879 edit = opts.get(b'edit')
1879 1880 editform = opts.get(b'editform', b'mq.qrefresh')
1880 1881 newuser = opts.get(b'user')
1881 1882 newdate = opts.get(b'date')
1882 1883 if newdate:
1883 1884 newdate = b'%d %d' % dateutil.parsedate(newdate)
1884 1885 wlock = repo.wlock()
1885 1886
1886 1887 try:
1887 1888 self.checktoppatch(repo)
1888 1889 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1889 1890 if repo.changelog.heads(top) != [top]:
1890 1891 raise error.Abort(
1891 1892 _(b"cannot qrefresh a revision with children")
1892 1893 )
1893 1894 if not repo[top].mutable():
1894 1895 raise error.Abort(
1895 1896 _(b"cannot qrefresh public revision"),
1896 1897 hint=_(b"see 'hg help phases' for details"),
1897 1898 )
1898 1899
1899 1900 cparents = repo.changelog.parents(top)
1900 1901 patchparent = self.qparents(repo, top)
1901 1902
1902 1903 inclsubs = checksubstate(repo, patchparent)
1903 1904 if inclsubs:
1904 1905 substatestate = repo.dirstate[b'.hgsubstate']
1905 1906
1906 1907 ph = patchheader(self.join(patchfn), self.plainmode)
1907 1908 diffopts = self.diffopts(
1908 1909 {b'git': opts.get(b'git')}, patchfn, plain=True
1909 1910 )
1910 1911 if newuser:
1911 1912 ph.setuser(newuser)
1912 1913 if newdate:
1913 1914 ph.setdate(newdate)
1914 1915 ph.setparent(hex(patchparent))
1915 1916
1916 1917 # only commit new patch when write is complete
1917 1918 patchf = self.opener(patchfn, b'w', atomictemp=True)
1918 1919
1919 1920 # update the dirstate in place, strip off the qtip commit
1920 1921 # and then commit.
1921 1922 #
1922 1923 # this should really read:
1923 1924 # st = repo.status(top, patchparent)
1924 1925 # but we do it backwards to take advantage of manifest/changelog
1925 1926 # caching against the next repo.status call
1926 1927 st = repo.status(patchparent, top)
1927 1928 mm, aa, dd = st.modified, st.added, st.removed
1928 1929 ctx = repo[top]
1929 1930 aaa = aa[:]
1930 1931 match1 = scmutil.match(repo[None], pats, opts)
1931 1932 # in short mode, we only diff the files included in the
1932 1933 # patch already plus specified files
1933 1934 if opts.get(b'short'):
1934 1935 # if amending a patch, we start with existing
1935 1936 # files plus specified files - unfiltered
1936 1937 match = scmutil.matchfiles(repo, mm + aa + dd + match1.files())
1937 1938 # filter with include/exclude options
1938 1939 match1 = scmutil.match(repo[None], opts=opts)
1939 1940 else:
1940 1941 match = scmutil.matchall(repo)
1941 1942 stb = repo.status(match=match)
1942 1943 m, a, r, d = stb.modified, stb.added, stb.removed, stb.deleted
1943 1944 mm = set(mm)
1944 1945 aa = set(aa)
1945 1946 dd = set(dd)
1946 1947
1947 1948 # we might end up with files that were added between
1948 1949 # qtip and the dirstate parent, but then changed in the
1949 1950 # local dirstate. in this case, we want them to only
1950 1951 # show up in the added section
1951 1952 for x in m:
1952 1953 if x not in aa:
1953 1954 mm.add(x)
1954 1955 # we might end up with files added by the local dirstate that
1955 1956 # were deleted by the patch. In this case, they should only
1956 1957 # show up in the changed section.
1957 1958 for x in a:
1958 1959 if x in dd:
1959 1960 dd.remove(x)
1960 1961 mm.add(x)
1961 1962 else:
1962 1963 aa.add(x)
1963 1964 # make sure any files deleted in the local dirstate
1964 1965 # are not in the add or change column of the patch
1965 1966 forget = []
1966 1967 for x in d + r:
1967 1968 if x in aa:
1968 1969 aa.remove(x)
1969 1970 forget.append(x)
1970 1971 continue
1971 1972 else:
1972 1973 mm.discard(x)
1973 1974 dd.add(x)
1974 1975
1975 1976 m = list(mm)
1976 1977 r = list(dd)
1977 1978 a = list(aa)
1978 1979
1979 1980 # create 'match' that includes the files to be recommitted.
1980 1981 # apply match1 via repo.status to ensure correct case handling.
1981 1982 st = repo.status(patchparent, match=match1)
1982 1983 cm, ca, cr, cd = st.modified, st.added, st.removed, st.deleted
1983 1984 allmatches = set(cm + ca + cr + cd)
1984 1985 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1985 1986
1986 1987 files = set(inclsubs)
1987 1988 for x in refreshchanges:
1988 1989 files.update(x)
1989 1990 match = scmutil.matchfiles(repo, files)
1990 1991
1991 1992 bmlist = repo[top].bookmarks()
1992 1993
1993 1994 dsguard = None
1994 1995 try:
1995 1996 dsguard = dirstateguard.dirstateguard(repo, b'mq.refresh')
1996 1997 if diffopts.git or diffopts.upgrade:
1997 1998 copies = {}
1998 1999 for dst in a:
1999 2000 src = repo.dirstate.copied(dst)
2000 2001 # during qfold, the source file for copies may
2001 2002 # be removed. Treat this as a simple add.
2002 2003 if src is not None and src in repo.dirstate:
2003 2004 copies.setdefault(src, []).append(dst)
2004 2005 repo.dirstate.add(dst)
2005 2006 # remember the copies between patchparent and qtip
2006 2007 for dst in aaa:
2007 2008 src = ctx[dst].copysource()
2008 2009 if src:
2009 2010 copies.setdefault(src, []).extend(
2010 2011 copies.get(dst, [])
2011 2012 )
2012 2013 if dst in a:
2013 2014 copies[src].append(dst)
2014 2015 # we can't copy a file created by the patch itself
2015 2016 if dst in copies:
2016 2017 del copies[dst]
2017 2018 for src, dsts in pycompat.iteritems(copies):
2018 2019 for dst in dsts:
2019 2020 repo.dirstate.copy(src, dst)
2020 2021 else:
2021 2022 for dst in a:
2022 2023 repo.dirstate.add(dst)
2023 2024 # Drop useless copy information
2024 2025 for f in list(repo.dirstate.copies()):
2025 2026 repo.dirstate.copy(None, f)
2026 2027 for f in r:
2027 2028 repo.dirstate.remove(f)
2028 2029 # if the patch excludes a modified file, mark that
2029 2030 # file with mtime=0 so status can see it.
2030 2031 mm = []
2031 2032 for i in pycompat.xrange(len(m) - 1, -1, -1):
2032 2033 if not match1(m[i]):
2033 2034 mm.append(m[i])
2034 2035 del m[i]
2035 2036 for f in m:
2036 2037 repo.dirstate.normal(f)
2037 2038 for f in mm:
2038 2039 repo.dirstate.normallookup(f)
2039 2040 for f in forget:
2040 2041 repo.dirstate.drop(f)
2041 2042
2042 2043 user = ph.user or ctx.user()
2043 2044
2044 2045 oldphase = repo[top].phase()
2045 2046
2046 2047 # assumes strip can roll itself back if interrupted
2047 2048 repo.setparents(*cparents)
2048 2049 self.applied.pop()
2049 2050 self.applieddirty = True
2050 2051 strip(self.ui, repo, [top], update=False, backup=False)
2051 2052 dsguard.close()
2052 2053 finally:
2053 2054 release(dsguard)
2054 2055
2055 2056 try:
2056 2057 # might be nice to attempt to roll back strip after this
2057 2058
2058 2059 defaultmsg = b"[mq]: %s" % patchfn
2059 2060 editor = cmdutil.getcommiteditor(editform=editform)
2060 2061 if edit:
2061 2062
2062 2063 def finishdesc(desc):
2063 2064 if desc.rstrip():
2064 2065 ph.setmessage(desc)
2065 2066 return desc
2066 2067 return defaultmsg
2067 2068
2068 2069 # i18n: this message is shown in editor with "HG: " prefix
2069 2070 extramsg = _(b'Leave message empty to use default message.')
2070 2071 editor = cmdutil.getcommiteditor(
2071 2072 finishdesc=finishdesc,
2072 2073 extramsg=extramsg,
2073 2074 editform=editform,
2074 2075 )
2075 2076 message = msg or b"\n".join(ph.message)
2076 2077 elif not msg:
2077 2078 if not ph.message:
2078 2079 message = defaultmsg
2079 2080 else:
2080 2081 message = b"\n".join(ph.message)
2081 2082 else:
2082 2083 message = msg
2083 2084 ph.setmessage(msg)
2084 2085
2085 2086 # Ensure we create a new changeset in the same phase than
2086 2087 # the old one.
2087 2088 lock = tr = None
2088 2089 try:
2089 2090 lock = repo.lock()
2090 2091 tr = repo.transaction(b'mq')
2091 2092 n = newcommit(
2092 2093 repo,
2093 2094 oldphase,
2094 2095 message,
2095 2096 user,
2096 2097 ph.date,
2097 2098 match=match,
2098 2099 force=True,
2099 2100 editor=editor,
2100 2101 )
2101 2102 # only write patch after a successful commit
2102 2103 c = [list(x) for x in refreshchanges]
2103 2104 if inclsubs:
2104 2105 self.putsubstate2changes(substatestate, c)
2105 2106 chunks = patchmod.diff(
2106 2107 repo, patchparent, changes=c, opts=diffopts
2107 2108 )
2108 2109 comments = bytes(ph)
2109 2110 if comments:
2110 2111 patchf.write(comments)
2111 2112 for chunk in chunks:
2112 2113 patchf.write(chunk)
2113 2114 patchf.close()
2114 2115
2115 2116 marks = repo._bookmarks
2116 2117 marks.applychanges(repo, tr, [(bm, n) for bm in bmlist])
2117 2118 tr.close()
2118 2119
2119 2120 self.applied.append(statusentry(n, patchfn))
2120 2121 finally:
2121 2122 lockmod.release(tr, lock)
2122 2123 except: # re-raises
2123 2124 ctx = repo[cparents[0]]
2124 2125 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2125 2126 self.savedirty()
2126 2127 self.ui.warn(
2127 2128 _(
2128 2129 b'qrefresh interrupted while patch was popped! '
2129 2130 b'(revert --all, qpush to recover)\n'
2130 2131 )
2131 2132 )
2132 2133 raise
2133 2134 finally:
2134 2135 wlock.release()
2135 2136 self.removeundo(repo)
2136 2137
2137 2138 def init(self, repo, create=False):
2138 2139 if not create and os.path.isdir(self.path):
2139 2140 raise error.Abort(_(b"patch queue directory already exists"))
2140 2141 try:
2141 2142 os.mkdir(self.path)
2142 2143 except OSError as inst:
2143 2144 if inst.errno != errno.EEXIST or not create:
2144 2145 raise
2145 2146 if create:
2146 2147 return self.qrepo(create=True)
2147 2148
2148 2149 def unapplied(self, repo, patch=None):
2149 2150 if patch and patch not in self.series:
2150 2151 raise error.Abort(_(b"patch %s is not in series file") % patch)
2151 2152 if not patch:
2152 2153 start = self.seriesend()
2153 2154 else:
2154 2155 start = self.series.index(patch) + 1
2155 2156 unapplied = []
2156 2157 for i in pycompat.xrange(start, len(self.series)):
2157 2158 pushable, reason = self.pushable(i)
2158 2159 if pushable:
2159 2160 unapplied.append((i, self.series[i]))
2160 2161 self.explainpushable(i)
2161 2162 return unapplied
2162 2163
2163 2164 def qseries(
2164 2165 self,
2165 2166 repo,
2166 2167 missing=None,
2167 2168 start=0,
2168 2169 length=None,
2169 2170 status=None,
2170 2171 summary=False,
2171 2172 ):
2172 2173 def displayname(pfx, patchname, state):
2173 2174 if pfx:
2174 2175 self.ui.write(pfx)
2175 2176 if summary:
2176 2177 ph = patchheader(self.join(patchname), self.plainmode)
2177 2178 if ph.message:
2178 2179 msg = ph.message[0]
2179 2180 else:
2180 2181 msg = b''
2181 2182
2182 2183 if self.ui.formatted():
2183 2184 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
2184 2185 if width > 0:
2185 2186 msg = stringutil.ellipsis(msg, width)
2186 2187 else:
2187 2188 msg = b''
2188 2189 self.ui.write(patchname, label=b'qseries.' + state)
2189 2190 self.ui.write(b': ')
2190 2191 self.ui.write(msg, label=b'qseries.message.' + state)
2191 2192 else:
2192 2193 self.ui.write(patchname, label=b'qseries.' + state)
2193 2194 self.ui.write(b'\n')
2194 2195
2195 2196 applied = {p.name for p in self.applied}
2196 2197 if length is None:
2197 2198 length = len(self.series) - start
2198 2199 if not missing:
2199 2200 if self.ui.verbose:
2200 2201 idxwidth = len(b"%d" % (start + length - 1))
2201 2202 for i in pycompat.xrange(start, start + length):
2202 2203 patch = self.series[i]
2203 2204 if patch in applied:
2204 2205 char, state = b'A', b'applied'
2205 2206 elif self.pushable(i)[0]:
2206 2207 char, state = b'U', b'unapplied'
2207 2208 else:
2208 2209 char, state = b'G', b'guarded'
2209 2210 pfx = b''
2210 2211 if self.ui.verbose:
2211 2212 pfx = b'%*d %s ' % (idxwidth, i, char)
2212 2213 elif status and status != char:
2213 2214 continue
2214 2215 displayname(pfx, patch, state)
2215 2216 else:
2216 2217 msng_list = []
2217 2218 for root, dirs, files in os.walk(self.path):
2218 2219 d = root[len(self.path) + 1 :]
2219 2220 for f in files:
2220 2221 fl = os.path.join(d, f)
2221 2222 if (
2222 2223 fl not in self.series
2223 2224 and fl
2224 2225 not in (
2225 2226 self.statuspath,
2226 2227 self.seriespath,
2227 2228 self.guardspath,
2228 2229 )
2229 2230 and not fl.startswith(b'.')
2230 2231 ):
2231 2232 msng_list.append(fl)
2232 2233 for x in sorted(msng_list):
2233 2234 pfx = self.ui.verbose and b'D ' or b''
2234 2235 displayname(pfx, x, b'missing')
2235 2236
2236 2237 def issaveline(self, l):
2237 2238 if l.name == b'.hg.patches.save.line':
2238 2239 return True
2239 2240
2240 2241 def qrepo(self, create=False):
2241 2242 ui = self.baseui.copy()
2242 2243 # copy back attributes set by ui.pager()
2243 2244 if self.ui.pageractive and not ui.pageractive:
2244 2245 ui.pageractive = self.ui.pageractive
2245 2246 # internal config: ui.formatted
2246 2247 ui.setconfig(
2247 2248 b'ui',
2248 2249 b'formatted',
2249 2250 self.ui.config(b'ui', b'formatted'),
2250 2251 b'mqpager',
2251 2252 )
2252 2253 ui.setconfig(
2253 2254 b'ui',
2254 2255 b'interactive',
2255 2256 self.ui.config(b'ui', b'interactive'),
2256 2257 b'mqpager',
2257 2258 )
2258 2259 if create or os.path.isdir(self.join(b".hg")):
2259 2260 return hg.repository(ui, path=self.path, create=create)
2260 2261
2261 2262 def restore(self, repo, rev, delete=None, qupdate=None):
2262 2263 desc = repo[rev].description().strip()
2263 2264 lines = desc.splitlines()
2264 2265 datastart = None
2265 2266 series = []
2266 2267 applied = []
2267 2268 qpp = None
2268 2269 for i, line in enumerate(lines):
2269 2270 if line == b'Patch Data:':
2270 2271 datastart = i + 1
2271 2272 elif line.startswith(b'Dirstate:'):
2272 2273 l = line.rstrip()
2273 2274 l = l[10:].split(b' ')
2274 2275 qpp = [bin(x) for x in l]
2275 2276 elif datastart is not None:
2276 2277 l = line.rstrip()
2277 2278 n, name = l.split(b':', 1)
2278 2279 if n:
2279 2280 applied.append(statusentry(bin(n), name))
2280 2281 else:
2281 2282 series.append(l)
2282 2283 if datastart is None:
2283 2284 self.ui.warn(_(b"no saved patch data found\n"))
2284 2285 return 1
2285 2286 self.ui.warn(_(b"restoring status: %s\n") % lines[0])
2286 2287 self.fullseries = series
2287 2288 self.applied = applied
2288 2289 self.parseseries()
2289 2290 self.seriesdirty = True
2290 2291 self.applieddirty = True
2291 2292 heads = repo.changelog.heads()
2292 2293 if delete:
2293 2294 if rev not in heads:
2294 2295 self.ui.warn(_(b"save entry has children, leaving it alone\n"))
2295 2296 else:
2296 2297 self.ui.warn(_(b"removing save entry %s\n") % short(rev))
2297 2298 pp = repo.dirstate.parents()
2298 2299 if rev in pp:
2299 2300 update = True
2300 2301 else:
2301 2302 update = False
2302 2303 strip(self.ui, repo, [rev], update=update, backup=False)
2303 2304 if qpp:
2304 2305 self.ui.warn(
2305 2306 _(b"saved queue repository parents: %s %s\n")
2306 2307 % (short(qpp[0]), short(qpp[1]))
2307 2308 )
2308 2309 if qupdate:
2309 2310 self.ui.status(_(b"updating queue directory\n"))
2310 2311 r = self.qrepo()
2311 2312 if not r:
2312 2313 self.ui.warn(_(b"unable to load queue repository\n"))
2313 2314 return 1
2314 2315 hg.clean(r, qpp[0])
2315 2316
2316 2317 def save(self, repo, msg=None):
2317 2318 if not self.applied:
2318 2319 self.ui.warn(_(b"save: no patches applied, exiting\n"))
2319 2320 return 1
2320 2321 if self.issaveline(self.applied[-1]):
2321 2322 self.ui.warn(_(b"status is already saved\n"))
2322 2323 return 1
2323 2324
2324 2325 if not msg:
2325 2326 msg = _(b"hg patches saved state")
2326 2327 else:
2327 2328 msg = b"hg patches: " + msg.rstrip(b'\r\n')
2328 2329 r = self.qrepo()
2329 2330 if r:
2330 2331 pp = r.dirstate.parents()
2331 2332 msg += b"\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
2332 2333 msg += b"\n\nPatch Data:\n"
2333 2334 msg += b''.join(b'%s\n' % x for x in self.applied)
2334 2335 msg += b''.join(b':%s\n' % x for x in self.fullseries)
2335 2336 n = repo.commit(msg, force=True)
2336 2337 if not n:
2337 2338 self.ui.warn(_(b"repo commit failed\n"))
2338 2339 return 1
2339 2340 self.applied.append(statusentry(n, b'.hg.patches.save.line'))
2340 2341 self.applieddirty = True
2341 2342 self.removeundo(repo)
2342 2343
2343 2344 def fullseriesend(self):
2344 2345 if self.applied:
2345 2346 p = self.applied[-1].name
2346 2347 end = self.findseries(p)
2347 2348 if end is None:
2348 2349 return len(self.fullseries)
2349 2350 return end + 1
2350 2351 return 0
2351 2352
2352 2353 def seriesend(self, all_patches=False):
2353 2354 """If all_patches is False, return the index of the next pushable patch
2354 2355 in the series, or the series length. If all_patches is True, return the
2355 2356 index of the first patch past the last applied one.
2356 2357 """
2357 2358 end = 0
2358 2359
2359 2360 def nextpatch(start):
2360 2361 if all_patches or start >= len(self.series):
2361 2362 return start
2362 2363 for i in pycompat.xrange(start, len(self.series)):
2363 2364 p, reason = self.pushable(i)
2364 2365 if p:
2365 2366 return i
2366 2367 self.explainpushable(i)
2367 2368 return len(self.series)
2368 2369
2369 2370 if self.applied:
2370 2371 p = self.applied[-1].name
2371 2372 try:
2372 2373 end = self.series.index(p)
2373 2374 except ValueError:
2374 2375 return 0
2375 2376 return nextpatch(end + 1)
2376 2377 return nextpatch(end)
2377 2378
2378 2379 def appliedname(self, index):
2379 2380 pname = self.applied[index].name
2380 2381 if not self.ui.verbose:
2381 2382 p = pname
2382 2383 else:
2383 2384 p = (b"%d" % self.series.index(pname)) + b" " + pname
2384 2385 return p
2385 2386
2386 2387 def qimport(
2387 2388 self,
2388 2389 repo,
2389 2390 files,
2390 2391 patchname=None,
2391 2392 rev=None,
2392 2393 existing=None,
2393 2394 force=None,
2394 2395 git=False,
2395 2396 ):
2396 2397 def checkseries(patchname):
2397 2398 if patchname in self.series:
2398 2399 raise error.Abort(
2399 2400 _(b'patch %s is already in the series file') % patchname
2400 2401 )
2401 2402
2402 2403 if rev:
2403 2404 if files:
2404 2405 raise error.Abort(
2405 2406 _(b'option "-r" not valid when importing files')
2406 2407 )
2407 2408 rev = scmutil.revrange(repo, rev)
2408 2409 rev.sort(reverse=True)
2409 2410 elif not files:
2410 2411 raise error.Abort(_(b'no files or revisions specified'))
2411 2412 if (len(files) > 1 or len(rev) > 1) and patchname:
2412 2413 raise error.Abort(
2413 2414 _(b'option "-n" not valid when importing multiple patches')
2414 2415 )
2415 2416 imported = []
2416 2417 if rev:
2417 2418 # If mq patches are applied, we can only import revisions
2418 2419 # that form a linear path to qbase.
2419 2420 # Otherwise, they should form a linear path to a head.
2420 2421 heads = repo.changelog.heads(repo.changelog.node(rev.first()))
2421 2422 if len(heads) > 1:
2422 2423 raise error.Abort(
2423 2424 _(b'revision %d is the root of more than one branch')
2424 2425 % rev.last()
2425 2426 )
2426 2427 if self.applied:
2427 2428 base = repo.changelog.node(rev.first())
2428 2429 if base in [n.node for n in self.applied]:
2429 2430 raise error.Abort(
2430 2431 _(b'revision %d is already managed') % rev.first()
2431 2432 )
2432 2433 if heads != [self.applied[-1].node]:
2433 2434 raise error.Abort(
2434 2435 _(b'revision %d is not the parent of the queue')
2435 2436 % rev.first()
2436 2437 )
2437 2438 base = repo.changelog.rev(self.applied[0].node)
2438 2439 lastparent = repo.changelog.parentrevs(base)[0]
2439 2440 else:
2440 2441 if heads != [repo.changelog.node(rev.first())]:
2441 2442 raise error.Abort(
2442 2443 _(b'revision %d has unmanaged children') % rev.first()
2443 2444 )
2444 2445 lastparent = None
2445 2446
2446 2447 diffopts = self.diffopts({b'git': git})
2447 2448 with repo.transaction(b'qimport') as tr:
2448 2449 for r in rev:
2449 2450 if not repo[r].mutable():
2450 2451 raise error.Abort(
2451 2452 _(b'revision %d is not mutable') % r,
2452 2453 hint=_(b"see 'hg help phases' " b'for details'),
2453 2454 )
2454 2455 p1, p2 = repo.changelog.parentrevs(r)
2455 2456 n = repo.changelog.node(r)
2456 2457 if p2 != nullrev:
2457 2458 raise error.Abort(
2458 2459 _(b'cannot import merge revision %d') % r
2459 2460 )
2460 2461 if lastparent and lastparent != r:
2461 2462 raise error.Abort(
2462 2463 _(b'revision %d is not the parent of %d')
2463 2464 % (r, lastparent)
2464 2465 )
2465 2466 lastparent = p1
2466 2467
2467 2468 if not patchname:
2468 2469 patchname = self.makepatchname(
2469 2470 repo[r].description().split(b'\n', 1)[0],
2470 2471 b'%d.diff' % r,
2471 2472 )
2472 2473 checkseries(patchname)
2473 2474 self.checkpatchname(patchname, force)
2474 2475 self.fullseries.insert(0, patchname)
2475 2476
2476 2477 with self.opener(patchname, b"w") as fp:
2477 2478 cmdutil.exportfile(repo, [n], fp, opts=diffopts)
2478 2479
2479 2480 se = statusentry(n, patchname)
2480 2481 self.applied.insert(0, se)
2481 2482
2482 2483 self.added.append(patchname)
2483 2484 imported.append(patchname)
2484 2485 patchname = None
2485 2486 if rev and repo.ui.configbool(b'mq', b'secret'):
2486 2487 # if we added anything with --rev, move the secret root
2487 2488 phases.retractboundary(repo, tr, phases.secret, [n])
2488 2489 self.parseseries()
2489 2490 self.applieddirty = True
2490 2491 self.seriesdirty = True
2491 2492
2492 2493 for i, filename in enumerate(files):
2493 2494 if existing:
2494 2495 if filename == b'-':
2495 2496 raise error.Abort(
2496 2497 _(b'-e is incompatible with import from -')
2497 2498 )
2498 2499 filename = normname(filename)
2499 2500 self.checkreservedname(filename)
2500 2501 if util.url(filename).islocal():
2501 2502 originpath = self.join(filename)
2502 2503 if not os.path.isfile(originpath):
2503 2504 raise error.Abort(
2504 2505 _(b"patch %s does not exist") % filename
2505 2506 )
2506 2507
2507 2508 if patchname:
2508 2509 self.checkpatchname(patchname, force)
2509 2510
2510 2511 self.ui.write(
2511 2512 _(b'renaming %s to %s\n') % (filename, patchname)
2512 2513 )
2513 2514 util.rename(originpath, self.join(patchname))
2514 2515 else:
2515 2516 patchname = filename
2516 2517
2517 2518 else:
2518 2519 if filename == b'-' and not patchname:
2519 2520 raise error.Abort(
2520 2521 _(b'need --name to import a patch from -')
2521 2522 )
2522 2523 elif not patchname:
2523 2524 patchname = normname(
2524 2525 os.path.basename(filename.rstrip(b'/'))
2525 2526 )
2526 2527 self.checkpatchname(patchname, force)
2527 2528 try:
2528 2529 if filename == b'-':
2529 2530 text = self.ui.fin.read()
2530 2531 else:
2531 2532 fp = hg.openpath(self.ui, filename)
2532 2533 text = fp.read()
2533 2534 fp.close()
2534 2535 except (OSError, IOError):
2535 2536 raise error.Abort(_(b"unable to read file %s") % filename)
2536 2537 patchf = self.opener(patchname, b"w")
2537 2538 patchf.write(text)
2538 2539 patchf.close()
2539 2540 if not force:
2540 2541 checkseries(patchname)
2541 2542 if patchname not in self.series:
2542 2543 index = self.fullseriesend() + i
2543 2544 self.fullseries[index:index] = [patchname]
2544 2545 self.parseseries()
2545 2546 self.seriesdirty = True
2546 2547 self.ui.warn(_(b"adding %s to series file\n") % patchname)
2547 2548 self.added.append(patchname)
2548 2549 imported.append(patchname)
2549 2550 patchname = None
2550 2551
2551 2552 self.removeundo(repo)
2552 2553 return imported
2553 2554
2554 2555
2555 2556 def fixkeepchangesopts(ui, opts):
2556 2557 if (
2557 2558 not ui.configbool(b'mq', b'keepchanges')
2558 2559 or opts.get(b'force')
2559 2560 or opts.get(b'exact')
2560 2561 ):
2561 2562 return opts
2562 2563 opts = dict(opts)
2563 2564 opts[b'keep_changes'] = True
2564 2565 return opts
2565 2566
2566 2567
2567 2568 @command(
2568 2569 b"qdelete|qremove|qrm",
2569 2570 [
2570 2571 (b'k', b'keep', None, _(b'keep patch file')),
2571 2572 (
2572 2573 b'r',
2573 2574 b'rev',
2574 2575 [],
2575 2576 _(b'stop managing a revision (DEPRECATED)'),
2576 2577 _(b'REV'),
2577 2578 ),
2578 2579 ],
2579 2580 _(b'hg qdelete [-k] [PATCH]...'),
2580 2581 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2581 2582 )
2582 2583 def delete(ui, repo, *patches, **opts):
2583 2584 """remove patches from queue
2584 2585
2585 2586 The patches must not be applied, and at least one patch is required. Exact
2586 2587 patch identifiers must be given. With -k/--keep, the patch files are
2587 2588 preserved in the patch directory.
2588 2589
2589 2590 To stop managing a patch and move it into permanent history,
2590 2591 use the :hg:`qfinish` command."""
2591 2592 q = repo.mq
2592 2593 q.delete(repo, patches, pycompat.byteskwargs(opts))
2593 2594 q.savedirty()
2594 2595 return 0
2595 2596
2596 2597
2597 2598 @command(
2598 2599 b"qapplied",
2599 2600 [(b'1', b'last', None, _(b'show only the preceding applied patch'))]
2600 2601 + seriesopts,
2601 2602 _(b'hg qapplied [-1] [-s] [PATCH]'),
2602 2603 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2603 2604 )
2604 2605 def applied(ui, repo, patch=None, **opts):
2605 2606 """print the patches already applied
2606 2607
2607 2608 Returns 0 on success."""
2608 2609
2609 2610 q = repo.mq
2610 2611 opts = pycompat.byteskwargs(opts)
2611 2612
2612 2613 if patch:
2613 2614 if patch not in q.series:
2614 2615 raise error.Abort(_(b"patch %s is not in series file") % patch)
2615 2616 end = q.series.index(patch) + 1
2616 2617 else:
2617 2618 end = q.seriesend(True)
2618 2619
2619 2620 if opts.get(b'last') and not end:
2620 2621 ui.write(_(b"no patches applied\n"))
2621 2622 return 1
2622 2623 elif opts.get(b'last') and end == 1:
2623 2624 ui.write(_(b"only one patch applied\n"))
2624 2625 return 1
2625 2626 elif opts.get(b'last'):
2626 2627 start = end - 2
2627 2628 end = 1
2628 2629 else:
2629 2630 start = 0
2630 2631
2631 2632 q.qseries(
2632 2633 repo, length=end, start=start, status=b'A', summary=opts.get(b'summary')
2633 2634 )
2634 2635
2635 2636
2636 2637 @command(
2637 2638 b"qunapplied",
2638 2639 [(b'1', b'first', None, _(b'show only the first patch'))] + seriesopts,
2639 2640 _(b'hg qunapplied [-1] [-s] [PATCH]'),
2640 2641 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2641 2642 )
2642 2643 def unapplied(ui, repo, patch=None, **opts):
2643 2644 """print the patches not yet applied
2644 2645
2645 2646 Returns 0 on success."""
2646 2647
2647 2648 q = repo.mq
2648 2649 opts = pycompat.byteskwargs(opts)
2649 2650 if patch:
2650 2651 if patch not in q.series:
2651 2652 raise error.Abort(_(b"patch %s is not in series file") % patch)
2652 2653 start = q.series.index(patch) + 1
2653 2654 else:
2654 2655 start = q.seriesend(True)
2655 2656
2656 2657 if start == len(q.series) and opts.get(b'first'):
2657 2658 ui.write(_(b"all patches applied\n"))
2658 2659 return 1
2659 2660
2660 2661 if opts.get(b'first'):
2661 2662 length = 1
2662 2663 else:
2663 2664 length = None
2664 2665 q.qseries(
2665 2666 repo,
2666 2667 start=start,
2667 2668 length=length,
2668 2669 status=b'U',
2669 2670 summary=opts.get(b'summary'),
2670 2671 )
2671 2672
2672 2673
2673 2674 @command(
2674 2675 b"qimport",
2675 2676 [
2676 2677 (b'e', b'existing', None, _(b'import file in patch directory')),
2677 2678 (b'n', b'name', b'', _(b'name of patch file'), _(b'NAME')),
2678 2679 (b'f', b'force', None, _(b'overwrite existing files')),
2679 2680 (
2680 2681 b'r',
2681 2682 b'rev',
2682 2683 [],
2683 2684 _(b'place existing revisions under mq control'),
2684 2685 _(b'REV'),
2685 2686 ),
2686 2687 (b'g', b'git', None, _(b'use git extended diff format')),
2687 2688 (b'P', b'push', None, _(b'qpush after importing')),
2688 2689 ],
2689 2690 _(b'hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'),
2690 2691 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2691 2692 )
2692 2693 def qimport(ui, repo, *filename, **opts):
2693 2694 """import a patch or existing changeset
2694 2695
2695 2696 The patch is inserted into the series after the last applied
2696 2697 patch. If no patches have been applied, qimport prepends the patch
2697 2698 to the series.
2698 2699
2699 2700 The patch will have the same name as its source file unless you
2700 2701 give it a new one with -n/--name.
2701 2702
2702 2703 You can register an existing patch inside the patch directory with
2703 2704 the -e/--existing flag.
2704 2705
2705 2706 With -f/--force, an existing patch of the same name will be
2706 2707 overwritten.
2707 2708
2708 2709 An existing changeset may be placed under mq control with -r/--rev
2709 2710 (e.g. qimport --rev . -n patch will place the current revision
2710 2711 under mq control). With -g/--git, patches imported with --rev will
2711 2712 use the git diff format. See the diffs help topic for information
2712 2713 on why this is important for preserving rename/copy information
2713 2714 and permission changes. Use :hg:`qfinish` to remove changesets
2714 2715 from mq control.
2715 2716
2716 2717 To import a patch from standard input, pass - as the patch file.
2717 2718 When importing from standard input, a patch name must be specified
2718 2719 using the --name flag.
2719 2720
2720 2721 To import an existing patch while renaming it::
2721 2722
2722 2723 hg qimport -e existing-patch -n new-name
2723 2724
2724 2725 Returns 0 if import succeeded.
2725 2726 """
2726 2727 opts = pycompat.byteskwargs(opts)
2727 2728 with repo.lock(): # cause this may move phase
2728 2729 q = repo.mq
2729 2730 try:
2730 2731 imported = q.qimport(
2731 2732 repo,
2732 2733 filename,
2733 2734 patchname=opts.get(b'name'),
2734 2735 existing=opts.get(b'existing'),
2735 2736 force=opts.get(b'force'),
2736 2737 rev=opts.get(b'rev'),
2737 2738 git=opts.get(b'git'),
2738 2739 )
2739 2740 finally:
2740 2741 q.savedirty()
2741 2742
2742 2743 if imported and opts.get(b'push') and not opts.get(b'rev'):
2743 2744 return q.push(repo, imported[-1])
2744 2745 return 0
2745 2746
2746 2747
2747 2748 def qinit(ui, repo, create):
2748 2749 """initialize a new queue repository
2749 2750
2750 2751 This command also creates a series file for ordering patches, and
2751 2752 an mq-specific .hgignore file in the queue repository, to exclude
2752 2753 the status and guards files (these contain mostly transient state).
2753 2754
2754 2755 Returns 0 if initialization succeeded."""
2755 2756 q = repo.mq
2756 2757 r = q.init(repo, create)
2757 2758 q.savedirty()
2758 2759 if r:
2759 2760 if not os.path.exists(r.wjoin(b'.hgignore')):
2760 2761 fp = r.wvfs(b'.hgignore', b'w')
2761 2762 fp.write(b'^\\.hg\n')
2762 2763 fp.write(b'^\\.mq\n')
2763 2764 fp.write(b'syntax: glob\n')
2764 2765 fp.write(b'status\n')
2765 2766 fp.write(b'guards\n')
2766 2767 fp.close()
2767 2768 if not os.path.exists(r.wjoin(b'series')):
2768 2769 r.wvfs(b'series', b'w').close()
2769 2770 r[None].add([b'.hgignore', b'series'])
2770 2771 commands.add(ui, r)
2771 2772 return 0
2772 2773
2773 2774
2774 2775 @command(
2775 2776 b"qinit",
2776 2777 [(b'c', b'create-repo', None, _(b'create queue repository'))],
2777 2778 _(b'hg qinit [-c]'),
2778 2779 helpcategory=command.CATEGORY_REPO_CREATION,
2779 2780 helpbasic=True,
2780 2781 )
2781 2782 def init(ui, repo, **opts):
2782 2783 """init a new queue repository (DEPRECATED)
2783 2784
2784 2785 The queue repository is unversioned by default. If
2785 2786 -c/--create-repo is specified, qinit will create a separate nested
2786 2787 repository for patches (qinit -c may also be run later to convert
2787 2788 an unversioned patch repository into a versioned one). You can use
2788 2789 qcommit to commit changes to this queue repository.
2789 2790
2790 2791 This command is deprecated. Without -c, it's implied by other relevant
2791 2792 commands. With -c, use :hg:`init --mq` instead."""
2792 2793 return qinit(ui, repo, create=opts.get('create_repo'))
2793 2794
2794 2795
2795 2796 @command(
2796 2797 b"qclone",
2797 2798 [
2798 2799 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
2799 2800 (
2800 2801 b'U',
2801 2802 b'noupdate',
2802 2803 None,
2803 2804 _(b'do not update the new working directories'),
2804 2805 ),
2805 2806 (
2806 2807 b'',
2807 2808 b'uncompressed',
2808 2809 None,
2809 2810 _(b'use uncompressed transfer (fast over LAN)'),
2810 2811 ),
2811 2812 (
2812 2813 b'p',
2813 2814 b'patches',
2814 2815 b'',
2815 2816 _(b'location of source patch repository'),
2816 2817 _(b'REPO'),
2817 2818 ),
2818 2819 ]
2819 2820 + cmdutil.remoteopts,
2820 2821 _(b'hg qclone [OPTION]... SOURCE [DEST]'),
2821 2822 helpcategory=command.CATEGORY_REPO_CREATION,
2822 2823 norepo=True,
2823 2824 )
2824 2825 def clone(ui, source, dest=None, **opts):
2825 2826 '''clone main and patch repository at same time
2826 2827
2827 2828 If source is local, destination will have no patches applied. If
2828 2829 source is remote, this command can not check if patches are
2829 2830 applied in source, so cannot guarantee that patches are not
2830 2831 applied in destination. If you clone remote repository, be sure
2831 2832 before that it has no patches applied.
2832 2833
2833 2834 Source patch repository is looked for in <src>/.hg/patches by
2834 2835 default. Use -p <url> to change.
2835 2836
2836 2837 The patch directory must be a nested Mercurial repository, as
2837 2838 would be created by :hg:`init --mq`.
2838 2839
2839 2840 Return 0 on success.
2840 2841 '''
2841 2842 opts = pycompat.byteskwargs(opts)
2842 2843
2843 2844 def patchdir(repo):
2844 2845 """compute a patch repo url from a repo object"""
2845 2846 url = repo.url()
2846 2847 if url.endswith(b'/'):
2847 2848 url = url[:-1]
2848 2849 return url + b'/.hg/patches'
2849 2850
2850 2851 # main repo (destination and sources)
2851 2852 if dest is None:
2852 2853 dest = hg.defaultdest(source)
2853 2854 sr = hg.peer(ui, opts, ui.expandpath(source))
2854 2855
2855 2856 # patches repo (source only)
2856 2857 if opts.get(b'patches'):
2857 2858 patchespath = ui.expandpath(opts.get(b'patches'))
2858 2859 else:
2859 2860 patchespath = patchdir(sr)
2860 2861 try:
2861 2862 hg.peer(ui, opts, patchespath)
2862 2863 except error.RepoError:
2863 2864 raise error.Abort(
2864 2865 _(b'versioned patch repository not found (see init --mq)')
2865 2866 )
2866 2867 qbase, destrev = None, None
2867 2868 if sr.local():
2868 2869 repo = sr.local()
2869 2870 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2870 2871 qbase = repo.mq.applied[0].node
2871 2872 if not hg.islocal(dest):
2872 2873 heads = set(repo.heads())
2873 2874 destrev = list(heads.difference(repo.heads(qbase)))
2874 2875 destrev.append(repo.changelog.parents(qbase)[0])
2875 2876 elif sr.capable(b'lookup'):
2876 2877 try:
2877 2878 qbase = sr.lookup(b'qbase')
2878 2879 except error.RepoError:
2879 2880 pass
2880 2881
2881 2882 ui.note(_(b'cloning main repository\n'))
2882 2883 sr, dr = hg.clone(
2883 2884 ui,
2884 2885 opts,
2885 2886 sr.url(),
2886 2887 dest,
2887 2888 pull=opts.get(b'pull'),
2888 2889 revs=destrev,
2889 2890 update=False,
2890 2891 stream=opts.get(b'uncompressed'),
2891 2892 )
2892 2893
2893 2894 ui.note(_(b'cloning patch repository\n'))
2894 2895 hg.clone(
2895 2896 ui,
2896 2897 opts,
2897 2898 opts.get(b'patches') or patchdir(sr),
2898 2899 patchdir(dr),
2899 2900 pull=opts.get(b'pull'),
2900 2901 update=not opts.get(b'noupdate'),
2901 2902 stream=opts.get(b'uncompressed'),
2902 2903 )
2903 2904
2904 2905 if dr.local():
2905 2906 repo = dr.local()
2906 2907 if qbase:
2907 2908 ui.note(
2908 2909 _(
2909 2910 b'stripping applied patches from destination '
2910 2911 b'repository\n'
2911 2912 )
2912 2913 )
2913 2914 strip(ui, repo, [qbase], update=False, backup=None)
2914 2915 if not opts.get(b'noupdate'):
2915 2916 ui.note(_(b'updating destination repository\n'))
2916 2917 hg.update(repo, repo.changelog.tip())
2917 2918
2918 2919
2919 2920 @command(
2920 2921 b"qcommit|qci",
2921 2922 commands.table[b"commit|ci"][1],
2922 2923 _(b'hg qcommit [OPTION]... [FILE]...'),
2923 2924 helpcategory=command.CATEGORY_COMMITTING,
2924 2925 inferrepo=True,
2925 2926 )
2926 2927 def commit(ui, repo, *pats, **opts):
2927 2928 """commit changes in the queue repository (DEPRECATED)
2928 2929
2929 2930 This command is deprecated; use :hg:`commit --mq` instead."""
2930 2931 q = repo.mq
2931 2932 r = q.qrepo()
2932 2933 if not r:
2933 2934 raise error.Abort(b'no queue repository')
2934 2935 commands.commit(r.ui, r, *pats, **opts)
2935 2936
2936 2937
2937 2938 @command(
2938 2939 b"qseries",
2939 2940 [(b'm', b'missing', None, _(b'print patches not in series')),] + seriesopts,
2940 2941 _(b'hg qseries [-ms]'),
2941 2942 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2942 2943 )
2943 2944 def series(ui, repo, **opts):
2944 2945 """print the entire series file
2945 2946
2946 2947 Returns 0 on success."""
2947 2948 repo.mq.qseries(
2948 2949 repo, missing=opts.get('missing'), summary=opts.get('summary')
2949 2950 )
2950 2951 return 0
2951 2952
2952 2953
2953 2954 @command(
2954 2955 b"qtop",
2955 2956 seriesopts,
2956 2957 _(b'hg qtop [-s]'),
2957 2958 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2958 2959 )
2959 2960 def top(ui, repo, **opts):
2960 2961 """print the name of the current patch
2961 2962
2962 2963 Returns 0 on success."""
2963 2964 q = repo.mq
2964 2965 if q.applied:
2965 2966 t = q.seriesend(True)
2966 2967 else:
2967 2968 t = 0
2968 2969
2969 2970 if t:
2970 2971 q.qseries(
2971 2972 repo,
2972 2973 start=t - 1,
2973 2974 length=1,
2974 2975 status=b'A',
2975 2976 summary=opts.get('summary'),
2976 2977 )
2977 2978 else:
2978 2979 ui.write(_(b"no patches applied\n"))
2979 2980 return 1
2980 2981
2981 2982
2982 2983 @command(
2983 2984 b"qnext",
2984 2985 seriesopts,
2985 2986 _(b'hg qnext [-s]'),
2986 2987 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2987 2988 )
2988 2989 def next(ui, repo, **opts):
2989 2990 """print the name of the next pushable patch
2990 2991
2991 2992 Returns 0 on success."""
2992 2993 q = repo.mq
2993 2994 end = q.seriesend()
2994 2995 if end == len(q.series):
2995 2996 ui.write(_(b"all patches applied\n"))
2996 2997 return 1
2997 2998 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2998 2999
2999 3000
3000 3001 @command(
3001 3002 b"qprev",
3002 3003 seriesopts,
3003 3004 _(b'hg qprev [-s]'),
3004 3005 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3005 3006 )
3006 3007 def prev(ui, repo, **opts):
3007 3008 """print the name of the preceding applied patch
3008 3009
3009 3010 Returns 0 on success."""
3010 3011 q = repo.mq
3011 3012 l = len(q.applied)
3012 3013 if l == 1:
3013 3014 ui.write(_(b"only one patch applied\n"))
3014 3015 return 1
3015 3016 if not l:
3016 3017 ui.write(_(b"no patches applied\n"))
3017 3018 return 1
3018 3019 idx = q.series.index(q.applied[-2].name)
3019 3020 q.qseries(
3020 3021 repo, start=idx, length=1, status=b'A', summary=opts.get('summary')
3021 3022 )
3022 3023
3023 3024
3024 3025 def setupheaderopts(ui, opts):
3025 3026 if not opts.get(b'user') and opts.get(b'currentuser'):
3026 3027 opts[b'user'] = ui.username()
3027 3028 if not opts.get(b'date') and opts.get(b'currentdate'):
3028 3029 opts[b'date'] = b"%d %d" % dateutil.makedate()
3029 3030
3030 3031
3031 3032 @command(
3032 3033 b"qnew",
3033 3034 [
3034 3035 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
3035 3036 (b'f', b'force', None, _(b'import uncommitted changes (DEPRECATED)')),
3036 3037 (b'g', b'git', None, _(b'use git extended diff format')),
3037 3038 (b'U', b'currentuser', None, _(b'add "From: <current user>" to patch')),
3038 3039 (b'u', b'user', b'', _(b'add "From: <USER>" to patch'), _(b'USER')),
3039 3040 (b'D', b'currentdate', None, _(b'add "Date: <current date>" to patch')),
3040 3041 (b'd', b'date', b'', _(b'add "Date: <DATE>" to patch'), _(b'DATE')),
3041 3042 ]
3042 3043 + cmdutil.walkopts
3043 3044 + cmdutil.commitopts,
3044 3045 _(b'hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'),
3045 3046 helpcategory=command.CATEGORY_COMMITTING,
3046 3047 helpbasic=True,
3047 3048 inferrepo=True,
3048 3049 )
3049 3050 def new(ui, repo, patch, *args, **opts):
3050 3051 """create a new patch
3051 3052
3052 3053 qnew creates a new patch on top of the currently-applied patch (if
3053 3054 any). The patch will be initialized with any outstanding changes
3054 3055 in the working directory. You may also use -I/--include,
3055 3056 -X/--exclude, and/or a list of files after the patch name to add
3056 3057 only changes to matching files to the new patch, leaving the rest
3057 3058 as uncommitted modifications.
3058 3059
3059 3060 -u/--user and -d/--date can be used to set the (given) user and
3060 3061 date, respectively. -U/--currentuser and -D/--currentdate set user
3061 3062 to current user and date to current date.
3062 3063
3063 3064 -e/--edit, -m/--message or -l/--logfile set the patch header as
3064 3065 well as the commit message. If none is specified, the header is
3065 3066 empty and the commit message is '[mq]: PATCH'.
3066 3067
3067 3068 Use the -g/--git option to keep the patch in the git extended diff
3068 3069 format. Read the diffs help topic for more information on why this
3069 3070 is important for preserving permission changes and copy/rename
3070 3071 information.
3071 3072
3072 3073 Returns 0 on successful creation of a new patch.
3073 3074 """
3074 3075 opts = pycompat.byteskwargs(opts)
3075 3076 msg = cmdutil.logmessage(ui, opts)
3076 3077 q = repo.mq
3077 3078 opts[b'msg'] = msg
3078 3079 setupheaderopts(ui, opts)
3079 3080 q.new(repo, patch, *args, **pycompat.strkwargs(opts))
3080 3081 q.savedirty()
3081 3082 return 0
3082 3083
3083 3084
3084 3085 @command(
3085 3086 b"qrefresh",
3086 3087 [
3087 3088 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
3088 3089 (b'g', b'git', None, _(b'use git extended diff format')),
3089 3090 (
3090 3091 b's',
3091 3092 b'short',
3092 3093 None,
3093 3094 _(b'refresh only files already in the patch and specified files'),
3094 3095 ),
3095 3096 (
3096 3097 b'U',
3097 3098 b'currentuser',
3098 3099 None,
3099 3100 _(b'add/update author field in patch with current user'),
3100 3101 ),
3101 3102 (
3102 3103 b'u',
3103 3104 b'user',
3104 3105 b'',
3105 3106 _(b'add/update author field in patch with given user'),
3106 3107 _(b'USER'),
3107 3108 ),
3108 3109 (
3109 3110 b'D',
3110 3111 b'currentdate',
3111 3112 None,
3112 3113 _(b'add/update date field in patch with current date'),
3113 3114 ),
3114 3115 (
3115 3116 b'd',
3116 3117 b'date',
3117 3118 b'',
3118 3119 _(b'add/update date field in patch with given date'),
3119 3120 _(b'DATE'),
3120 3121 ),
3121 3122 ]
3122 3123 + cmdutil.walkopts
3123 3124 + cmdutil.commitopts,
3124 3125 _(b'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'),
3125 3126 helpcategory=command.CATEGORY_COMMITTING,
3126 3127 helpbasic=True,
3127 3128 inferrepo=True,
3128 3129 )
3129 3130 def refresh(ui, repo, *pats, **opts):
3130 3131 """update the current patch
3131 3132
3132 3133 If any file patterns are provided, the refreshed patch will
3133 3134 contain only the modifications that match those patterns; the
3134 3135 remaining modifications will remain in the working directory.
3135 3136
3136 3137 If -s/--short is specified, files currently included in the patch
3137 3138 will be refreshed just like matched files and remain in the patch.
3138 3139
3139 3140 If -e/--edit is specified, Mercurial will start your configured editor for
3140 3141 you to enter a message. In case qrefresh fails, you will find a backup of
3141 3142 your message in ``.hg/last-message.txt``.
3142 3143
3143 3144 hg add/remove/copy/rename work as usual, though you might want to
3144 3145 use git-style patches (-g/--git or [diff] git=1) to track copies
3145 3146 and renames. See the diffs help topic for more information on the
3146 3147 git diff format.
3147 3148
3148 3149 Returns 0 on success.
3149 3150 """
3150 3151 opts = pycompat.byteskwargs(opts)
3151 3152 q = repo.mq
3152 3153 message = cmdutil.logmessage(ui, opts)
3153 3154 setupheaderopts(ui, opts)
3154 3155 with repo.wlock():
3155 3156 ret = q.refresh(repo, pats, msg=message, **pycompat.strkwargs(opts))
3156 3157 q.savedirty()
3157 3158 return ret
3158 3159
3159 3160
3160 3161 @command(
3161 3162 b"qdiff",
3162 3163 cmdutil.diffopts + cmdutil.diffopts2 + cmdutil.walkopts,
3163 3164 _(b'hg qdiff [OPTION]... [FILE]...'),
3164 3165 helpcategory=command.CATEGORY_FILE_CONTENTS,
3165 3166 helpbasic=True,
3166 3167 inferrepo=True,
3167 3168 )
3168 3169 def diff(ui, repo, *pats, **opts):
3169 3170 """diff of the current patch and subsequent modifications
3170 3171
3171 3172 Shows a diff which includes the current patch as well as any
3172 3173 changes which have been made in the working directory since the
3173 3174 last refresh (thus showing what the current patch would become
3174 3175 after a qrefresh).
3175 3176
3176 3177 Use :hg:`diff` if you only want to see the changes made since the
3177 3178 last qrefresh, or :hg:`export qtip` if you want to see changes
3178 3179 made by the current patch without including changes made since the
3179 3180 qrefresh.
3180 3181
3181 3182 Returns 0 on success.
3182 3183 """
3183 3184 ui.pager(b'qdiff')
3184 3185 repo.mq.diff(repo, pats, pycompat.byteskwargs(opts))
3185 3186 return 0
3186 3187
3187 3188
3188 3189 @command(
3189 3190 b'qfold',
3190 3191 [
3191 3192 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
3192 3193 (b'k', b'keep', None, _(b'keep folded patch files')),
3193 3194 ]
3194 3195 + cmdutil.commitopts,
3195 3196 _(b'hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'),
3196 3197 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
3197 3198 )
3198 3199 def fold(ui, repo, *files, **opts):
3199 3200 """fold the named patches into the current patch
3200 3201
3201 3202 Patches must not yet be applied. Each patch will be successively
3202 3203 applied to the current patch in the order given. If all the
3203 3204 patches apply successfully, the current patch will be refreshed
3204 3205 with the new cumulative patch, and the folded patches will be
3205 3206 deleted. With -k/--keep, the folded patch files will not be
3206 3207 removed afterwards.
3207 3208
3208 3209 The header for each folded patch will be concatenated with the
3209 3210 current patch header, separated by a line of ``* * *``.
3210 3211
3211 3212 Returns 0 on success."""
3212 3213 opts = pycompat.byteskwargs(opts)
3213 3214 q = repo.mq
3214 3215 if not files:
3215 3216 raise error.Abort(_(b'qfold requires at least one patch name'))
3216 3217 if not q.checktoppatch(repo)[0]:
3217 3218 raise error.Abort(_(b'no patches applied'))
3218 3219 q.checklocalchanges(repo)
3219 3220
3220 3221 message = cmdutil.logmessage(ui, opts)
3221 3222
3222 3223 parent = q.lookup(b'qtip')
3223 3224 patches = []
3224 3225 messages = []
3225 3226 for f in files:
3226 3227 p = q.lookup(f)
3227 3228 if p in patches or p == parent:
3228 3229 ui.warn(_(b'skipping already folded patch %s\n') % p)
3229 3230 if q.isapplied(p):
3230 3231 raise error.Abort(
3231 3232 _(b'qfold cannot fold already applied patch %s') % p
3232 3233 )
3233 3234 patches.append(p)
3234 3235
3235 3236 for p in patches:
3236 3237 if not message:
3237 3238 ph = patchheader(q.join(p), q.plainmode)
3238 3239 if ph.message:
3239 3240 messages.append(ph.message)
3240 3241 pf = q.join(p)
3241 3242 (patchsuccess, files, fuzz) = q.patch(repo, pf)
3242 3243 if not patchsuccess:
3243 3244 raise error.Abort(_(b'error folding patch %s') % p)
3244 3245
3245 3246 if not message:
3246 3247 ph = patchheader(q.join(parent), q.plainmode)
3247 3248 message = ph.message
3248 3249 for msg in messages:
3249 3250 if msg:
3250 3251 if message:
3251 3252 message.append(b'* * *')
3252 3253 message.extend(msg)
3253 3254 message = b'\n'.join(message)
3254 3255
3255 3256 diffopts = q.patchopts(q.diffopts(), *patches)
3256 3257 with repo.wlock():
3257 3258 q.refresh(
3258 3259 repo,
3259 3260 msg=message,
3260 3261 git=diffopts.git,
3261 3262 edit=opts.get(b'edit'),
3262 3263 editform=b'mq.qfold',
3263 3264 )
3264 3265 q.delete(repo, patches, opts)
3265 3266 q.savedirty()
3266 3267
3267 3268
3268 3269 @command(
3269 3270 b"qgoto",
3270 3271 [
3271 3272 (
3272 3273 b'',
3273 3274 b'keep-changes',
3274 3275 None,
3275 3276 _(b'tolerate non-conflicting local changes'),
3276 3277 ),
3277 3278 (b'f', b'force', None, _(b'overwrite any local changes')),
3278 3279 (b'', b'no-backup', None, _(b'do not save backup copies of files')),
3279 3280 ],
3280 3281 _(b'hg qgoto [OPTION]... PATCH'),
3281 3282 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3282 3283 )
3283 3284 def goto(ui, repo, patch, **opts):
3284 3285 '''push or pop patches until named patch is at top of stack
3285 3286
3286 3287 Returns 0 on success.'''
3287 3288 opts = pycompat.byteskwargs(opts)
3288 3289 opts = fixkeepchangesopts(ui, opts)
3289 3290 q = repo.mq
3290 3291 patch = q.lookup(patch)
3291 3292 nobackup = opts.get(b'no_backup')
3292 3293 keepchanges = opts.get(b'keep_changes')
3293 3294 if q.isapplied(patch):
3294 3295 ret = q.pop(
3295 3296 repo,
3296 3297 patch,
3297 3298 force=opts.get(b'force'),
3298 3299 nobackup=nobackup,
3299 3300 keepchanges=keepchanges,
3300 3301 )
3301 3302 else:
3302 3303 ret = q.push(
3303 3304 repo,
3304 3305 patch,
3305 3306 force=opts.get(b'force'),
3306 3307 nobackup=nobackup,
3307 3308 keepchanges=keepchanges,
3308 3309 )
3309 3310 q.savedirty()
3310 3311 return ret
3311 3312
3312 3313
3313 3314 @command(
3314 3315 b"qguard",
3315 3316 [
3316 3317 (b'l', b'list', None, _(b'list all patches and guards')),
3317 3318 (b'n', b'none', None, _(b'drop all guards')),
3318 3319 ],
3319 3320 _(b'hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'),
3320 3321 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3321 3322 )
3322 3323 def guard(ui, repo, *args, **opts):
3323 3324 '''set or print guards for a patch
3324 3325
3325 3326 Guards control whether a patch can be pushed. A patch with no
3326 3327 guards is always pushed. A patch with a positive guard ("+foo") is
3327 3328 pushed only if the :hg:`qselect` command has activated it. A patch with
3328 3329 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
3329 3330 has activated it.
3330 3331
3331 3332 With no arguments, print the currently active guards.
3332 3333 With arguments, set guards for the named patch.
3333 3334
3334 3335 .. note::
3335 3336
3336 3337 Specifying negative guards now requires '--'.
3337 3338
3338 3339 To set guards on another patch::
3339 3340
3340 3341 hg qguard other.patch -- +2.6.17 -stable
3341 3342
3342 3343 Returns 0 on success.
3343 3344 '''
3344 3345
3345 3346 def status(idx):
3346 3347 guards = q.seriesguards[idx] or [b'unguarded']
3347 3348 if q.series[idx] in applied:
3348 3349 state = b'applied'
3349 3350 elif q.pushable(idx)[0]:
3350 3351 state = b'unapplied'
3351 3352 else:
3352 3353 state = b'guarded'
3353 3354 label = b'qguard.patch qguard.%s qseries.%s' % (state, state)
3354 3355 ui.write(b'%s: ' % ui.label(q.series[idx], label))
3355 3356
3356 3357 for i, guard in enumerate(guards):
3357 3358 if guard.startswith(b'+'):
3358 3359 ui.write(guard, label=b'qguard.positive')
3359 3360 elif guard.startswith(b'-'):
3360 3361 ui.write(guard, label=b'qguard.negative')
3361 3362 else:
3362 3363 ui.write(guard, label=b'qguard.unguarded')
3363 3364 if i != len(guards) - 1:
3364 3365 ui.write(b' ')
3365 3366 ui.write(b'\n')
3366 3367
3367 3368 q = repo.mq
3368 3369 applied = {p.name for p in q.applied}
3369 3370 patch = None
3370 3371 args = list(args)
3371 3372 if opts.get('list'):
3372 3373 if args or opts.get('none'):
3373 3374 raise error.Abort(
3374 3375 _(b'cannot mix -l/--list with options or arguments')
3375 3376 )
3376 3377 for i in pycompat.xrange(len(q.series)):
3377 3378 status(i)
3378 3379 return
3379 3380 if not args or args[0][0:1] in b'-+':
3380 3381 if not q.applied:
3381 3382 raise error.Abort(_(b'no patches applied'))
3382 3383 patch = q.applied[-1].name
3383 3384 if patch is None and args[0][0:1] not in b'-+':
3384 3385 patch = args.pop(0)
3385 3386 if patch is None:
3386 3387 raise error.Abort(_(b'no patch to work with'))
3387 3388 if args or opts.get('none'):
3388 3389 idx = q.findseries(patch)
3389 3390 if idx is None:
3390 3391 raise error.Abort(_(b'no patch named %s') % patch)
3391 3392 q.setguards(idx, args)
3392 3393 q.savedirty()
3393 3394 else:
3394 3395 status(q.series.index(q.lookup(patch)))
3395 3396
3396 3397
3397 3398 @command(
3398 3399 b"qheader",
3399 3400 [],
3400 3401 _(b'hg qheader [PATCH]'),
3401 3402 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3402 3403 )
3403 3404 def header(ui, repo, patch=None):
3404 3405 """print the header of the topmost or specified patch
3405 3406
3406 3407 Returns 0 on success."""
3407 3408 q = repo.mq
3408 3409
3409 3410 if patch:
3410 3411 patch = q.lookup(patch)
3411 3412 else:
3412 3413 if not q.applied:
3413 3414 ui.write(_(b'no patches applied\n'))
3414 3415 return 1
3415 3416 patch = q.lookup(b'qtip')
3416 3417 ph = patchheader(q.join(patch), q.plainmode)
3417 3418
3418 3419 ui.write(b'\n'.join(ph.message) + b'\n')
3419 3420
3420 3421
3421 3422 def lastsavename(path):
3422 3423 (directory, base) = os.path.split(path)
3423 3424 names = os.listdir(directory)
3424 3425 namere = re.compile(b"%s.([0-9]+)" % base)
3425 3426 maxindex = None
3426 3427 maxname = None
3427 3428 for f in names:
3428 3429 m = namere.match(f)
3429 3430 if m:
3430 3431 index = int(m.group(1))
3431 3432 if maxindex is None or index > maxindex:
3432 3433 maxindex = index
3433 3434 maxname = f
3434 3435 if maxname:
3435 3436 return (os.path.join(directory, maxname), maxindex)
3436 3437 return (None, None)
3437 3438
3438 3439
3439 3440 def savename(path):
3440 3441 (last, index) = lastsavename(path)
3441 3442 if last is None:
3442 3443 index = 0
3443 3444 newpath = path + b".%d" % (index + 1)
3444 3445 return newpath
3445 3446
3446 3447
3447 3448 @command(
3448 3449 b"qpush",
3449 3450 [
3450 3451 (
3451 3452 b'',
3452 3453 b'keep-changes',
3453 3454 None,
3454 3455 _(b'tolerate non-conflicting local changes'),
3455 3456 ),
3456 3457 (b'f', b'force', None, _(b'apply on top of local changes')),
3457 3458 (
3458 3459 b'e',
3459 3460 b'exact',
3460 3461 None,
3461 3462 _(b'apply the target patch to its recorded parent'),
3462 3463 ),
3463 3464 (b'l', b'list', None, _(b'list patch name in commit text')),
3464 3465 (b'a', b'all', None, _(b'apply all patches')),
3465 3466 (b'm', b'merge', None, _(b'merge from another queue (DEPRECATED)')),
3466 3467 (b'n', b'name', b'', _(b'merge queue name (DEPRECATED)'), _(b'NAME')),
3467 3468 (
3468 3469 b'',
3469 3470 b'move',
3470 3471 None,
3471 3472 _(b'reorder patch series and apply only the patch'),
3472 3473 ),
3473 3474 (b'', b'no-backup', None, _(b'do not save backup copies of files')),
3474 3475 ],
3475 3476 _(b'hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'),
3476 3477 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3477 3478 helpbasic=True,
3478 3479 )
3479 3480 def push(ui, repo, patch=None, **opts):
3480 3481 """push the next patch onto the stack
3481 3482
3482 3483 By default, abort if the working directory contains uncommitted
3483 3484 changes. With --keep-changes, abort only if the uncommitted files
3484 3485 overlap with patched files. With -f/--force, backup and patch over
3485 3486 uncommitted changes.
3486 3487
3487 3488 Return 0 on success.
3488 3489 """
3489 3490 q = repo.mq
3490 3491 mergeq = None
3491 3492
3492 3493 opts = pycompat.byteskwargs(opts)
3493 3494 opts = fixkeepchangesopts(ui, opts)
3494 3495 if opts.get(b'merge'):
3495 3496 if opts.get(b'name'):
3496 3497 newpath = repo.vfs.join(opts.get(b'name'))
3497 3498 else:
3498 3499 newpath, i = lastsavename(q.path)
3499 3500 if not newpath:
3500 3501 ui.warn(_(b"no saved queues found, please use -n\n"))
3501 3502 return 1
3502 3503 mergeq = queue(ui, repo.baseui, repo.path, newpath)
3503 3504 ui.warn(_(b"merging with queue at: %s\n") % mergeq.path)
3504 3505 ret = q.push(
3505 3506 repo,
3506 3507 patch,
3507 3508 force=opts.get(b'force'),
3508 3509 list=opts.get(b'list'),
3509 3510 mergeq=mergeq,
3510 3511 all=opts.get(b'all'),
3511 3512 move=opts.get(b'move'),
3512 3513 exact=opts.get(b'exact'),
3513 3514 nobackup=opts.get(b'no_backup'),
3514 3515 keepchanges=opts.get(b'keep_changes'),
3515 3516 )
3516 3517 return ret
3517 3518
3518 3519
3519 3520 @command(
3520 3521 b"qpop",
3521 3522 [
3522 3523 (b'a', b'all', None, _(b'pop all patches')),
3523 3524 (b'n', b'name', b'', _(b'queue name to pop (DEPRECATED)'), _(b'NAME')),
3524 3525 (
3525 3526 b'',
3526 3527 b'keep-changes',
3527 3528 None,
3528 3529 _(b'tolerate non-conflicting local changes'),
3529 3530 ),
3530 3531 (b'f', b'force', None, _(b'forget any local changes to patched files')),
3531 3532 (b'', b'no-backup', None, _(b'do not save backup copies of files')),
3532 3533 ],
3533 3534 _(b'hg qpop [-a] [-f] [PATCH | INDEX]'),
3534 3535 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3535 3536 helpbasic=True,
3536 3537 )
3537 3538 def pop(ui, repo, patch=None, **opts):
3538 3539 """pop the current patch off the stack
3539 3540
3540 3541 Without argument, pops off the top of the patch stack. If given a
3541 3542 patch name, keeps popping off patches until the named patch is at
3542 3543 the top of the stack.
3543 3544
3544 3545 By default, abort if the working directory contains uncommitted
3545 3546 changes. With --keep-changes, abort only if the uncommitted files
3546 3547 overlap with patched files. With -f/--force, backup and discard
3547 3548 changes made to such files.
3548 3549
3549 3550 Return 0 on success.
3550 3551 """
3551 3552 opts = pycompat.byteskwargs(opts)
3552 3553 opts = fixkeepchangesopts(ui, opts)
3553 3554 localupdate = True
3554 3555 if opts.get(b'name'):
3555 3556 q = queue(ui, repo.baseui, repo.path, repo.vfs.join(opts.get(b'name')))
3556 3557 ui.warn(_(b'using patch queue: %s\n') % q.path)
3557 3558 localupdate = False
3558 3559 else:
3559 3560 q = repo.mq
3560 3561 ret = q.pop(
3561 3562 repo,
3562 3563 patch,
3563 3564 force=opts.get(b'force'),
3564 3565 update=localupdate,
3565 3566 all=opts.get(b'all'),
3566 3567 nobackup=opts.get(b'no_backup'),
3567 3568 keepchanges=opts.get(b'keep_changes'),
3568 3569 )
3569 3570 q.savedirty()
3570 3571 return ret
3571 3572
3572 3573
3573 3574 @command(
3574 3575 b"qrename|qmv",
3575 3576 [],
3576 3577 _(b'hg qrename PATCH1 [PATCH2]'),
3577 3578 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3578 3579 )
3579 3580 def rename(ui, repo, patch, name=None, **opts):
3580 3581 """rename a patch
3581 3582
3582 3583 With one argument, renames the current patch to PATCH1.
3583 3584 With two arguments, renames PATCH1 to PATCH2.
3584 3585
3585 3586 Returns 0 on success."""
3586 3587 q = repo.mq
3587 3588 if not name:
3588 3589 name = patch
3589 3590 patch = None
3590 3591
3591 3592 if patch:
3592 3593 patch = q.lookup(patch)
3593 3594 else:
3594 3595 if not q.applied:
3595 3596 ui.write(_(b'no patches applied\n'))
3596 3597 return
3597 3598 patch = q.lookup(b'qtip')
3598 3599 absdest = q.join(name)
3599 3600 if os.path.isdir(absdest):
3600 3601 name = normname(os.path.join(name, os.path.basename(patch)))
3601 3602 absdest = q.join(name)
3602 3603 q.checkpatchname(name)
3603 3604
3604 3605 ui.note(_(b'renaming %s to %s\n') % (patch, name))
3605 3606 i = q.findseries(patch)
3606 3607 guards = q.guard_re.findall(q.fullseries[i])
3607 3608 q.fullseries[i] = name + b''.join([b' #' + g for g in guards])
3608 3609 q.parseseries()
3609 3610 q.seriesdirty = True
3610 3611
3611 3612 info = q.isapplied(patch)
3612 3613 if info:
3613 3614 q.applied[info[0]] = statusentry(info[1], name)
3614 3615 q.applieddirty = True
3615 3616
3616 3617 destdir = os.path.dirname(absdest)
3617 3618 if not os.path.isdir(destdir):
3618 3619 os.makedirs(destdir)
3619 3620 util.rename(q.join(patch), absdest)
3620 3621 r = q.qrepo()
3621 3622 if r and patch in r.dirstate:
3622 3623 wctx = r[None]
3623 3624 with r.wlock():
3624 3625 if r.dirstate[patch] == b'a':
3625 3626 r.dirstate.drop(patch)
3626 3627 r.dirstate.add(name)
3627 3628 else:
3628 3629 wctx.copy(patch, name)
3629 3630 wctx.forget([patch])
3630 3631
3631 3632 q.savedirty()
3632 3633
3633 3634
3634 3635 @command(
3635 3636 b"qrestore",
3636 3637 [
3637 3638 (b'd', b'delete', None, _(b'delete save entry')),
3638 3639 (b'u', b'update', None, _(b'update queue working directory')),
3639 3640 ],
3640 3641 _(b'hg qrestore [-d] [-u] REV'),
3641 3642 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3642 3643 )
3643 3644 def restore(ui, repo, rev, **opts):
3644 3645 """restore the queue state saved by a revision (DEPRECATED)
3645 3646
3646 3647 This command is deprecated, use :hg:`rebase` instead."""
3647 3648 rev = repo.lookup(rev)
3648 3649 q = repo.mq
3649 3650 q.restore(repo, rev, delete=opts.get('delete'), qupdate=opts.get('update'))
3650 3651 q.savedirty()
3651 3652 return 0
3652 3653
3653 3654
3654 3655 @command(
3655 3656 b"qsave",
3656 3657 [
3657 3658 (b'c', b'copy', None, _(b'copy patch directory')),
3658 3659 (b'n', b'name', b'', _(b'copy directory name'), _(b'NAME')),
3659 3660 (b'e', b'empty', None, _(b'clear queue status file')),
3660 3661 (b'f', b'force', None, _(b'force copy')),
3661 3662 ]
3662 3663 + cmdutil.commitopts,
3663 3664 _(b'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
3664 3665 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3665 3666 )
3666 3667 def save(ui, repo, **opts):
3667 3668 """save current queue state (DEPRECATED)
3668 3669
3669 3670 This command is deprecated, use :hg:`rebase` instead."""
3670 3671 q = repo.mq
3671 3672 opts = pycompat.byteskwargs(opts)
3672 3673 message = cmdutil.logmessage(ui, opts)
3673 3674 ret = q.save(repo, msg=message)
3674 3675 if ret:
3675 3676 return ret
3676 3677 q.savedirty() # save to .hg/patches before copying
3677 3678 if opts.get(b'copy'):
3678 3679 path = q.path
3679 3680 if opts.get(b'name'):
3680 3681 newpath = os.path.join(q.basepath, opts.get(b'name'))
3681 3682 if os.path.exists(newpath):
3682 3683 if not os.path.isdir(newpath):
3683 3684 raise error.Abort(
3684 3685 _(b'destination %s exists and is not a directory')
3685 3686 % newpath
3686 3687 )
3687 3688 if not opts.get(b'force'):
3688 3689 raise error.Abort(
3689 3690 _(b'destination %s exists, use -f to force') % newpath
3690 3691 )
3691 3692 else:
3692 3693 newpath = savename(path)
3693 3694 ui.warn(_(b"copy %s to %s\n") % (path, newpath))
3694 3695 util.copyfiles(path, newpath)
3695 3696 if opts.get(b'empty'):
3696 3697 del q.applied[:]
3697 3698 q.applieddirty = True
3698 3699 q.savedirty()
3699 3700 return 0
3700 3701
3701 3702
3702 3703 @command(
3703 3704 b"qselect",
3704 3705 [
3705 3706 (b'n', b'none', None, _(b'disable all guards')),
3706 3707 (b's', b'series', None, _(b'list all guards in series file')),
3707 3708 (b'', b'pop', None, _(b'pop to before first guarded applied patch')),
3708 3709 (b'', b'reapply', None, _(b'pop, then reapply patches')),
3709 3710 ],
3710 3711 _(b'hg qselect [OPTION]... [GUARD]...'),
3711 3712 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3712 3713 )
3713 3714 def select(ui, repo, *args, **opts):
3714 3715 '''set or print guarded patches to push
3715 3716
3716 3717 Use the :hg:`qguard` command to set or print guards on patch, then use
3717 3718 qselect to tell mq which guards to use. A patch will be pushed if
3718 3719 it has no guards or any positive guards match the currently
3719 3720 selected guard, but will not be pushed if any negative guards
3720 3721 match the current guard. For example::
3721 3722
3722 3723 qguard foo.patch -- -stable (negative guard)
3723 3724 qguard bar.patch +stable (positive guard)
3724 3725 qselect stable
3725 3726
3726 3727 This activates the "stable" guard. mq will skip foo.patch (because
3727 3728 it has a negative match) but push bar.patch (because it has a
3728 3729 positive match).
3729 3730
3730 3731 With no arguments, prints the currently active guards.
3731 3732 With one argument, sets the active guard.
3732 3733
3733 3734 Use -n/--none to deactivate guards (no other arguments needed).
3734 3735 When no guards are active, patches with positive guards are
3735 3736 skipped and patches with negative guards are pushed.
3736 3737
3737 3738 qselect can change the guards on applied patches. It does not pop
3738 3739 guarded patches by default. Use --pop to pop back to the last
3739 3740 applied patch that is not guarded. Use --reapply (which implies
3740 3741 --pop) to push back to the current patch afterwards, but skip
3741 3742 guarded patches.
3742 3743
3743 3744 Use -s/--series to print a list of all guards in the series file
3744 3745 (no other arguments needed). Use -v for more information.
3745 3746
3746 3747 Returns 0 on success.'''
3747 3748
3748 3749 q = repo.mq
3749 3750 opts = pycompat.byteskwargs(opts)
3750 3751 guards = q.active()
3751 3752 pushable = lambda i: q.pushable(q.applied[i].name)[0]
3752 3753 if args or opts.get(b'none'):
3753 3754 old_unapplied = q.unapplied(repo)
3754 3755 old_guarded = [
3755 3756 i for i in pycompat.xrange(len(q.applied)) if not pushable(i)
3756 3757 ]
3757 3758 q.setactive(args)
3758 3759 q.savedirty()
3759 3760 if not args:
3760 3761 ui.status(_(b'guards deactivated\n'))
3761 3762 if not opts.get(b'pop') and not opts.get(b'reapply'):
3762 3763 unapplied = q.unapplied(repo)
3763 3764 guarded = [
3764 3765 i for i in pycompat.xrange(len(q.applied)) if not pushable(i)
3765 3766 ]
3766 3767 if len(unapplied) != len(old_unapplied):
3767 3768 ui.status(
3768 3769 _(
3769 3770 b'number of unguarded, unapplied patches has '
3770 3771 b'changed from %d to %d\n'
3771 3772 )
3772 3773 % (len(old_unapplied), len(unapplied))
3773 3774 )
3774 3775 if len(guarded) != len(old_guarded):
3775 3776 ui.status(
3776 3777 _(
3777 3778 b'number of guarded, applied patches has changed '
3778 3779 b'from %d to %d\n'
3779 3780 )
3780 3781 % (len(old_guarded), len(guarded))
3781 3782 )
3782 3783 elif opts.get(b'series'):
3783 3784 guards = {}
3784 3785 noguards = 0
3785 3786 for gs in q.seriesguards:
3786 3787 if not gs:
3787 3788 noguards += 1
3788 3789 for g in gs:
3789 3790 guards.setdefault(g, 0)
3790 3791 guards[g] += 1
3791 3792 if ui.verbose:
3792 3793 guards[b'NONE'] = noguards
3793 3794 guards = list(guards.items())
3794 3795 guards.sort(key=lambda x: x[0][1:])
3795 3796 if guards:
3796 3797 ui.note(_(b'guards in series file:\n'))
3797 3798 for guard, count in guards:
3798 3799 ui.note(b'%2d ' % count)
3799 3800 ui.write(guard, b'\n')
3800 3801 else:
3801 3802 ui.note(_(b'no guards in series file\n'))
3802 3803 else:
3803 3804 if guards:
3804 3805 ui.note(_(b'active guards:\n'))
3805 3806 for g in guards:
3806 3807 ui.write(g, b'\n')
3807 3808 else:
3808 3809 ui.write(_(b'no active guards\n'))
3809 3810 reapply = opts.get(b'reapply') and q.applied and q.applied[-1].name
3810 3811 popped = False
3811 3812 if opts.get(b'pop') or opts.get(b'reapply'):
3812 3813 for i in pycompat.xrange(len(q.applied)):
3813 3814 if not pushable(i):
3814 3815 ui.status(_(b'popping guarded patches\n'))
3815 3816 popped = True
3816 3817 if i == 0:
3817 3818 q.pop(repo, all=True)
3818 3819 else:
3819 3820 q.pop(repo, q.applied[i - 1].name)
3820 3821 break
3821 3822 if popped:
3822 3823 try:
3823 3824 if reapply:
3824 3825 ui.status(_(b'reapplying unguarded patches\n'))
3825 3826 q.push(repo, reapply)
3826 3827 finally:
3827 3828 q.savedirty()
3828 3829
3829 3830
3830 3831 @command(
3831 3832 b"qfinish",
3832 3833 [(b'a', b'applied', None, _(b'finish all applied changesets'))],
3833 3834 _(b'hg qfinish [-a] [REV]...'),
3834 3835 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3835 3836 )
3836 3837 def finish(ui, repo, *revrange, **opts):
3837 3838 """move applied patches into repository history
3838 3839
3839 3840 Finishes the specified revisions (corresponding to applied
3840 3841 patches) by moving them out of mq control into regular repository
3841 3842 history.
3842 3843
3843 3844 Accepts a revision range or the -a/--applied option. If --applied
3844 3845 is specified, all applied mq revisions are removed from mq
3845 3846 control. Otherwise, the given revisions must be at the base of the
3846 3847 stack of applied patches.
3847 3848
3848 3849 This can be especially useful if your changes have been applied to
3849 3850 an upstream repository, or if you are about to push your changes
3850 3851 to upstream.
3851 3852
3852 3853 Returns 0 on success.
3853 3854 """
3854 3855 if not opts.get('applied') and not revrange:
3855 3856 raise error.Abort(_(b'no revisions specified'))
3856 3857 elif opts.get('applied'):
3857 3858 revrange = (b'qbase::qtip',) + revrange
3858 3859
3859 3860 q = repo.mq
3860 3861 if not q.applied:
3861 3862 ui.status(_(b'no patches applied\n'))
3862 3863 return 0
3863 3864
3864 3865 revs = scmutil.revrange(repo, revrange)
3865 3866 if repo[b'.'].rev() in revs and repo[None].files():
3866 3867 ui.warn(_(b'warning: uncommitted changes in the working directory\n'))
3867 3868 # queue.finish may changes phases but leave the responsibility to lock the
3868 3869 # repo to the caller to avoid deadlock with wlock. This command code is
3869 3870 # responsibility for this locking.
3870 3871 with repo.lock():
3871 3872 q.finish(repo, revs)
3872 3873 q.savedirty()
3873 3874 return 0
3874 3875
3875 3876
3876 3877 @command(
3877 3878 b"qqueue",
3878 3879 [
3879 3880 (b'l', b'list', False, _(b'list all available queues')),
3880 3881 (b'', b'active', False, _(b'print name of active queue')),
3881 3882 (b'c', b'create', False, _(b'create new queue')),
3882 3883 (b'', b'rename', False, _(b'rename active queue')),
3883 3884 (b'', b'delete', False, _(b'delete reference to queue')),
3884 3885 (b'', b'purge', False, _(b'delete queue, and remove patch dir')),
3885 3886 ],
3886 3887 _(b'[OPTION] [QUEUE]'),
3887 3888 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3888 3889 )
3889 3890 def qqueue(ui, repo, name=None, **opts):
3890 3891 '''manage multiple patch queues
3891 3892
3892 3893 Supports switching between different patch queues, as well as creating
3893 3894 new patch queues and deleting existing ones.
3894 3895
3895 3896 Omitting a queue name or specifying -l/--list will show you the registered
3896 3897 queues - by default the "normal" patches queue is registered. The currently
3897 3898 active queue will be marked with "(active)". Specifying --active will print
3898 3899 only the name of the active queue.
3899 3900
3900 3901 To create a new queue, use -c/--create. The queue is automatically made
3901 3902 active, except in the case where there are applied patches from the
3902 3903 currently active queue in the repository. Then the queue will only be
3903 3904 created and switching will fail.
3904 3905
3905 3906 To delete an existing queue, use --delete. You cannot delete the currently
3906 3907 active queue.
3907 3908
3908 3909 Returns 0 on success.
3909 3910 '''
3910 3911 q = repo.mq
3911 3912 _defaultqueue = b'patches'
3912 3913 _allqueues = b'patches.queues'
3913 3914 _activequeue = b'patches.queue'
3914 3915
3915 3916 def _getcurrent():
3916 3917 cur = os.path.basename(q.path)
3917 3918 if cur.startswith(b'patches-'):
3918 3919 cur = cur[8:]
3919 3920 return cur
3920 3921
3921 3922 def _noqueues():
3922 3923 try:
3923 3924 fh = repo.vfs(_allqueues, b'r')
3924 3925 fh.close()
3925 3926 except IOError:
3926 3927 return True
3927 3928
3928 3929 return False
3929 3930
3930 3931 def _getqueues():
3931 3932 current = _getcurrent()
3932 3933
3933 3934 try:
3934 3935 fh = repo.vfs(_allqueues, b'r')
3935 3936 queues = [queue.strip() for queue in fh if queue.strip()]
3936 3937 fh.close()
3937 3938 if current not in queues:
3938 3939 queues.append(current)
3939 3940 except IOError:
3940 3941 queues = [_defaultqueue]
3941 3942
3942 3943 return sorted(queues)
3943 3944
3944 3945 def _setactive(name):
3945 3946 if q.applied:
3946 3947 raise error.Abort(
3947 3948 _(
3948 3949 b'new queue created, but cannot make active '
3949 3950 b'as patches are applied'
3950 3951 )
3951 3952 )
3952 3953 _setactivenocheck(name)
3953 3954
3954 3955 def _setactivenocheck(name):
3955 3956 fh = repo.vfs(_activequeue, b'w')
3956 3957 if name != b'patches':
3957 3958 fh.write(name)
3958 3959 fh.close()
3959 3960
3960 3961 def _addqueue(name):
3961 3962 fh = repo.vfs(_allqueues, b'a')
3962 3963 fh.write(b'%s\n' % (name,))
3963 3964 fh.close()
3964 3965
3965 3966 def _queuedir(name):
3966 3967 if name == b'patches':
3967 3968 return repo.vfs.join(b'patches')
3968 3969 else:
3969 3970 return repo.vfs.join(b'patches-' + name)
3970 3971
3971 3972 def _validname(name):
3972 3973 for n in name:
3973 3974 if n in b':\\/.':
3974 3975 return False
3975 3976 return True
3976 3977
3977 3978 def _delete(name):
3978 3979 if name not in existing:
3979 3980 raise error.Abort(_(b'cannot delete queue that does not exist'))
3980 3981
3981 3982 current = _getcurrent()
3982 3983
3983 3984 if name == current:
3984 3985 raise error.Abort(_(b'cannot delete currently active queue'))
3985 3986
3986 3987 fh = repo.vfs(b'patches.queues.new', b'w')
3987 3988 for queue in existing:
3988 3989 if queue == name:
3989 3990 continue
3990 3991 fh.write(b'%s\n' % (queue,))
3991 3992 fh.close()
3992 3993 repo.vfs.rename(b'patches.queues.new', _allqueues)
3993 3994
3994 3995 opts = pycompat.byteskwargs(opts)
3995 3996 if not name or opts.get(b'list') or opts.get(b'active'):
3996 3997 current = _getcurrent()
3997 3998 if opts.get(b'active'):
3998 3999 ui.write(b'%s\n' % (current,))
3999 4000 return
4000 4001 for queue in _getqueues():
4001 4002 ui.write(b'%s' % (queue,))
4002 4003 if queue == current and not ui.quiet:
4003 4004 ui.write(_(b' (active)\n'))
4004 4005 else:
4005 4006 ui.write(b'\n')
4006 4007 return
4007 4008
4008 4009 if not _validname(name):
4009 4010 raise error.Abort(
4010 4011 _(b'invalid queue name, may not contain the characters ":\\/."')
4011 4012 )
4012 4013
4013 4014 with repo.wlock():
4014 4015 existing = _getqueues()
4015 4016
4016 4017 if opts.get(b'create'):
4017 4018 if name in existing:
4018 4019 raise error.Abort(_(b'queue "%s" already exists') % name)
4019 4020 if _noqueues():
4020 4021 _addqueue(_defaultqueue)
4021 4022 _addqueue(name)
4022 4023 _setactive(name)
4023 4024 elif opts.get(b'rename'):
4024 4025 current = _getcurrent()
4025 4026 if name == current:
4026 4027 raise error.Abort(
4027 4028 _(b'can\'t rename "%s" to its current name') % name
4028 4029 )
4029 4030 if name in existing:
4030 4031 raise error.Abort(_(b'queue "%s" already exists') % name)
4031 4032
4032 4033 olddir = _queuedir(current)
4033 4034 newdir = _queuedir(name)
4034 4035
4035 4036 if os.path.exists(newdir):
4036 4037 raise error.Abort(
4037 4038 _(b'non-queue directory "%s" already exists') % newdir
4038 4039 )
4039 4040
4040 4041 fh = repo.vfs(b'patches.queues.new', b'w')
4041 4042 for queue in existing:
4042 4043 if queue == current:
4043 4044 fh.write(b'%s\n' % (name,))
4044 4045 if os.path.exists(olddir):
4045 4046 util.rename(olddir, newdir)
4046 4047 else:
4047 4048 fh.write(b'%s\n' % (queue,))
4048 4049 fh.close()
4049 4050 repo.vfs.rename(b'patches.queues.new', _allqueues)
4050 4051 _setactivenocheck(name)
4051 4052 elif opts.get(b'delete'):
4052 4053 _delete(name)
4053 4054 elif opts.get(b'purge'):
4054 4055 if name in existing:
4055 4056 _delete(name)
4056 4057 qdir = _queuedir(name)
4057 4058 if os.path.exists(qdir):
4058 4059 shutil.rmtree(qdir)
4059 4060 else:
4060 4061 if name not in existing:
4061 4062 raise error.Abort(_(b'use --create to create a new queue'))
4062 4063 _setactive(name)
4063 4064
4064 4065
4065 4066 def mqphasedefaults(repo, roots):
4066 4067 """callback used to set mq changeset as secret when no phase data exists"""
4067 4068 if repo.mq.applied:
4068 4069 if repo.ui.configbool(b'mq', b'secret'):
4069 4070 mqphase = phases.secret
4070 4071 else:
4071 4072 mqphase = phases.draft
4072 4073 qbase = repo[repo.mq.applied[0].node]
4073 4074 roots[mqphase].add(qbase.node())
4074 4075 return roots
4075 4076
4076 4077
4077 4078 def reposetup(ui, repo):
4078 4079 class mqrepo(repo.__class__):
4079 4080 @localrepo.unfilteredpropertycache
4080 4081 def mq(self):
4081 4082 return queue(self.ui, self.baseui, self.path)
4082 4083
4083 4084 def invalidateall(self):
4084 4085 super(mqrepo, self).invalidateall()
4085 4086 if localrepo.hasunfilteredcache(self, 'mq'):
4086 4087 # recreate mq in case queue path was changed
4087 4088 delattr(self.unfiltered(), 'mq')
4088 4089
4089 4090 def abortifwdirpatched(self, errmsg, force=False):
4090 4091 if self.mq.applied and self.mq.checkapplied and not force:
4091 4092 parents = self.dirstate.parents()
4092 4093 patches = [s.node for s in self.mq.applied]
4093 4094 if any(p in patches for p in parents):
4094 4095 raise error.Abort(errmsg)
4095 4096
4096 4097 def commit(
4097 4098 self,
4098 4099 text=b"",
4099 4100 user=None,
4100 4101 date=None,
4101 4102 match=None,
4102 4103 force=False,
4103 4104 editor=False,
4104 4105 extra=None,
4105 4106 ):
4106 4107 if extra is None:
4107 4108 extra = {}
4108 4109 self.abortifwdirpatched(
4109 4110 _(b'cannot commit over an applied mq patch'), force
4110 4111 )
4111 4112
4112 4113 return super(mqrepo, self).commit(
4113 4114 text, user, date, match, force, editor, extra
4114 4115 )
4115 4116
4116 4117 def checkpush(self, pushop):
4117 4118 if self.mq.applied and self.mq.checkapplied and not pushop.force:
4118 4119 outapplied = [e.node for e in self.mq.applied]
4119 4120 if pushop.revs:
4120 4121 # Assume applied patches have no non-patch descendants and
4121 4122 # are not on remote already. Filtering any changeset not
4122 4123 # pushed.
4123 4124 heads = set(pushop.revs)
4124 4125 for node in reversed(outapplied):
4125 4126 if node in heads:
4126 4127 break
4127 4128 else:
4128 4129 outapplied.pop()
4129 4130 # looking for pushed and shared changeset
4130 4131 for node in outapplied:
4131 4132 if self[node].phase() < phases.secret:
4132 4133 raise error.Abort(_(b'source has mq patches applied'))
4133 4134 # no non-secret patches pushed
4134 4135 super(mqrepo, self).checkpush(pushop)
4135 4136
4136 4137 def _findtags(self):
4137 4138 '''augment tags from base class with patch tags'''
4138 4139 result = super(mqrepo, self)._findtags()
4139 4140
4140 4141 q = self.mq
4141 4142 if not q.applied:
4142 4143 return result
4143 4144
4144 4145 mqtags = [(patch.node, patch.name) for patch in q.applied]
4145 4146
4146 4147 try:
4147 4148 # for now ignore filtering business
4148 4149 self.unfiltered().changelog.rev(mqtags[-1][0])
4149 4150 except error.LookupError:
4150 4151 self.ui.warn(
4151 4152 _(b'mq status file refers to unknown node %s\n')
4152 4153 % short(mqtags[-1][0])
4153 4154 )
4154 4155 return result
4155 4156
4156 4157 # do not add fake tags for filtered revisions
4157 4158 included = self.changelog.hasnode
4158 4159 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
4159 4160 if not mqtags:
4160 4161 return result
4161 4162
4162 4163 mqtags.append((mqtags[-1][0], b'qtip'))
4163 4164 mqtags.append((mqtags[0][0], b'qbase'))
4164 4165 mqtags.append((self.changelog.parents(mqtags[0][0])[0], b'qparent'))
4165 4166 tags = result[0]
4166 4167 for patch in mqtags:
4167 4168 if patch[1] in tags:
4168 4169 self.ui.warn(
4169 4170 _(b'tag %s overrides mq patch of the same name\n')
4170 4171 % patch[1]
4171 4172 )
4172 4173 else:
4173 4174 tags[patch[1]] = patch[0]
4174 4175
4175 4176 return result
4176 4177
4177 4178 if repo.local():
4178 4179 repo.__class__ = mqrepo
4179 4180
4180 4181 repo._phasedefaults.append(mqphasedefaults)
4181 4182
4182 4183
4183 4184 def mqimport(orig, ui, repo, *args, **kwargs):
4184 4185 if util.safehasattr(repo, b'abortifwdirpatched') and not kwargs.get(
4185 4186 'no_commit', False
4186 4187 ):
4187 4188 repo.abortifwdirpatched(
4188 4189 _(b'cannot import over an applied patch'), kwargs.get('force')
4189 4190 )
4190 4191 return orig(ui, repo, *args, **kwargs)
4191 4192
4192 4193
4193 4194 def mqinit(orig, ui, *args, **kwargs):
4194 4195 mq = kwargs.pop('mq', None)
4195 4196
4196 4197 if not mq:
4197 4198 return orig(ui, *args, **kwargs)
4198 4199
4199 4200 if args:
4200 4201 repopath = args[0]
4201 4202 if not hg.islocal(repopath):
4202 4203 raise error.Abort(
4203 4204 _(b'only a local queue repository may be initialized')
4204 4205 )
4205 4206 else:
4206 4207 repopath = cmdutil.findrepo(encoding.getcwd())
4207 4208 if not repopath:
4208 4209 raise error.Abort(
4209 4210 _(b'there is no Mercurial repository here (.hg not found)')
4210 4211 )
4211 4212 repo = hg.repository(ui, repopath)
4212 4213 return qinit(ui, repo, True)
4213 4214
4214 4215
4215 4216 def mqcommand(orig, ui, repo, *args, **kwargs):
4216 4217 """Add --mq option to operate on patch repository instead of main"""
4217 4218
4218 4219 # some commands do not like getting unknown options
4219 4220 mq = kwargs.pop('mq', None)
4220 4221
4221 4222 if not mq:
4222 4223 return orig(ui, repo, *args, **kwargs)
4223 4224
4224 4225 q = repo.mq
4225 4226 r = q.qrepo()
4226 4227 if not r:
4227 4228 raise error.Abort(_(b'no queue repository'))
4228 4229 return orig(r.ui, r, *args, **kwargs)
4229 4230
4230 4231
4231 4232 def summaryhook(ui, repo):
4232 4233 q = repo.mq
4233 4234 m = []
4234 4235 a, u = len(q.applied), len(q.unapplied(repo))
4235 4236 if a:
4236 4237 m.append(ui.label(_(b"%d applied"), b'qseries.applied') % a)
4237 4238 if u:
4238 4239 m.append(ui.label(_(b"%d unapplied"), b'qseries.unapplied') % u)
4239 4240 if m:
4240 4241 # i18n: column positioning for "hg summary"
4241 4242 ui.write(_(b"mq: %s\n") % b', '.join(m))
4242 4243 else:
4243 4244 # i18n: column positioning for "hg summary"
4244 4245 ui.note(_(b"mq: (empty queue)\n"))
4245 4246
4246 4247
4247 4248 revsetpredicate = registrar.revsetpredicate()
4248 4249
4249 4250
4250 4251 @revsetpredicate(b'mq()')
4251 4252 def revsetmq(repo, subset, x):
4252 4253 """Changesets managed by MQ.
4253 4254 """
4254 4255 revsetlang.getargs(x, 0, 0, _(b"mq takes no arguments"))
4255 4256 applied = {repo[r.node].rev() for r in repo.mq.applied}
4256 4257 return smartset.baseset([r for r in subset if r in applied])
4257 4258
4258 4259
4259 4260 # tell hggettext to extract docstrings from these functions:
4260 4261 i18nfunctions = [revsetmq]
4261 4262
4262 4263
4263 4264 def extsetup(ui):
4264 4265 # Ensure mq wrappers are called first, regardless of extension load order by
4265 4266 # NOT wrapping in uisetup() and instead deferring to init stage two here.
4266 4267 mqopt = [(b'', b'mq', None, _(b"operate on patch repository"))]
4267 4268
4268 4269 extensions.wrapcommand(commands.table, b'import', mqimport)
4269 4270 cmdutil.summaryhooks.add(b'mq', summaryhook)
4270 4271
4271 4272 entry = extensions.wrapcommand(commands.table, b'init', mqinit)
4272 4273 entry[1].extend(mqopt)
4273 4274
4274 4275 def dotable(cmdtable):
4275 4276 for cmd, entry in pycompat.iteritems(cmdtable):
4276 4277 cmd = cmdutil.parsealiases(cmd)[0]
4277 4278 func = entry[0]
4278 4279 if func.norepo:
4279 4280 continue
4280 4281 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
4281 4282 entry[1].extend(mqopt)
4282 4283
4283 4284 dotable(commands.table)
4284 4285
4285 4286 thismodule = sys.modules["hgext.mq"]
4286 4287 for extname, extmodule in extensions.extensions():
4287 4288 if extmodule != thismodule:
4288 4289 dotable(getattr(extmodule, 'cmdtable', {}))
4289 4290
4290 4291
4291 4292 colortable = {
4292 4293 b'qguard.negative': b'red',
4293 4294 b'qguard.positive': b'yellow',
4294 4295 b'qguard.unguarded': b'green',
4295 4296 b'qseries.applied': b'blue bold underline',
4296 4297 b'qseries.guarded': b'black bold',
4297 4298 b'qseries.missing': b'red bold',
4298 4299 b'qseries.unapplied': b'black bold',
4299 4300 }
@@ -1,287 +1,24 b''
1 """strip changesets and their descendants from history
1 """strip changesets and their descendants from history (DEPRECATED)
2
3 The functionality of this extension has been included in core Mercurial
4 since version 5.7. Please use :hg:`debugstrip ...` instead.
2 5
3 6 This extension allows you to strip changesets and all their descendants from the
4 7 repository. See the command help for details.
5 8 """
6 9 from __future__ import absolute_import
7 10
8 from mercurial.i18n import _
9 from mercurial.pycompat import getattr
10 11 from mercurial import (
11 bookmarks as bookmarksmod,
12 cmdutil,
13 error,
14 hg,
15 lock as lockmod,
16 mergestate as mergestatemod,
17 node as nodemod,
18 pycompat,
19 registrar,
20 repair,
21 scmutil,
22 util,
12 commands,
23 13 )
24 14
25 nullid = nodemod.nullid
26 release = lockmod.release
27
28 cmdtable = {}
29 command = registrar.command(cmdtable)
30 15 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
31 16 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
32 17 # be specifying the version(s) of Mercurial they are tested with, or
33 18 # leave the attribute unspecified.
34 19 testedwith = b'ships-with-hg-core'
35 20
36
37 def checklocalchanges(repo, force=False):
38 s = repo.status()
39 if not force:
40 cmdutil.checkunfinished(repo)
41 cmdutil.bailifchanged(repo)
42 else:
43 cmdutil.checkunfinished(repo, skipmerge=True)
44 return s
45
46
47 def _findupdatetarget(repo, nodes):
48 unode, p2 = repo.changelog.parents(nodes[0])
49 currentbranch = repo[None].branch()
50
51 if (
52 util.safehasattr(repo, b'mq')
53 and p2 != nullid
54 and p2 in [x.node for x in repo.mq.applied]
55 ):
56 unode = p2
57 elif currentbranch != repo[unode].branch():
58 pwdir = b'parents(wdir())'
59 revset = b'max(((parents(%ln::%r) + %r) - %ln::%r) and branch(%s))'
60 branchtarget = repo.revs(
61 revset, nodes, pwdir, pwdir, nodes, pwdir, currentbranch
62 )
63 if branchtarget:
64 cl = repo.changelog
65 unode = cl.node(branchtarget.first())
66
67 return unode
68
69
70 def strip(
71 ui,
72 repo,
73 revs,
74 update=True,
75 backup=True,
76 force=None,
77 bookmarks=None,
78 soft=False,
79 ):
80 with repo.wlock(), repo.lock():
81
82 if update:
83 checklocalchanges(repo, force=force)
84 urev = _findupdatetarget(repo, revs)
85 hg.clean(repo, urev)
86 repo.dirstate.write(repo.currenttransaction())
87
88 if soft:
89 repair.softstrip(ui, repo, revs, backup)
90 else:
91 repair.strip(ui, repo, revs, backup)
92
93 repomarks = repo._bookmarks
94 if bookmarks:
95 with repo.transaction(b'strip') as tr:
96 if repo._activebookmark in bookmarks:
97 bookmarksmod.deactivate(repo)
98 repomarks.applychanges(repo, tr, [(b, None) for b in bookmarks])
99 for bookmark in sorted(bookmarks):
100 ui.write(_(b"bookmark '%s' deleted\n") % bookmark)
101
102
103 @command(
104 b"strip",
105 [
106 (
107 b'r',
108 b'rev',
109 [],
110 _(
111 b'strip specified revision (optional, '
112 b'can specify revisions without this '
113 b'option)'
114 ),
115 _(b'REV'),
116 ),
117 (
118 b'f',
119 b'force',
120 None,
121 _(
122 b'force removal of changesets, discard '
123 b'uncommitted changes (no backup)'
124 ),
125 ),
126 (b'', b'no-backup', None, _(b'do not save backup bundle')),
127 (b'', b'nobackup', None, _(b'do not save backup bundle (DEPRECATED)'),),
128 (b'n', b'', None, _(b'ignored (DEPRECATED)')),
129 (
130 b'k',
131 b'keep',
132 None,
133 _(b"do not modify working directory during strip"),
134 ),
135 (
136 b'B',
137 b'bookmark',
138 [],
139 _(b"remove revs only reachable from given bookmark"),
140 _(b'BOOKMARK'),
141 ),
142 (
143 b'',
144 b'soft',
145 None,
146 _(b"simply drop changesets from visible history (EXPERIMENTAL)"),
147 ),
148 ],
149 _(b'hg strip [-k] [-f] [-B bookmark] [-r] REV...'),
150 helpcategory=command.CATEGORY_MAINTENANCE,
151 )
152 def stripcmd(ui, repo, *revs, **opts):
153 """strip changesets and all their descendants from the repository
154
155 The strip command removes the specified changesets and all their
156 descendants. If the working directory has uncommitted changes, the
157 operation is aborted unless the --force flag is supplied, in which
158 case changes will be discarded.
159
160 If a parent of the working directory is stripped, then the working
161 directory will automatically be updated to the most recent
162 available ancestor of the stripped parent after the operation
163 completes.
164
165 Any stripped changesets are stored in ``.hg/strip-backup`` as a
166 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
167 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
168 where BUNDLE is the bundle file created by the strip. Note that
169 the local revision numbers will in general be different after the
170 restore.
171
172 Use the --no-backup option to discard the backup bundle once the
173 operation completes.
174
175 Strip is not a history-rewriting operation and can be used on
176 changesets in the public phase. But if the stripped changesets have
177 been pushed to a remote repository you will likely pull them again.
178
179 Return 0 on success.
180 """
181 opts = pycompat.byteskwargs(opts)
182 backup = True
183 if opts.get(b'no_backup') or opts.get(b'nobackup'):
184 backup = False
185
186 cl = repo.changelog
187 revs = list(revs) + opts.get(b'rev')
188 revs = set(scmutil.revrange(repo, revs))
189
190 with repo.wlock():
191 bookmarks = set(opts.get(b'bookmark'))
192 if bookmarks:
193 repomarks = repo._bookmarks
194 if not bookmarks.issubset(repomarks):
195 raise error.Abort(
196 _(b"bookmark '%s' not found")
197 % b','.join(sorted(bookmarks - set(repomarks.keys())))
198 )
199
200 # If the requested bookmark is not the only one pointing to a
201 # a revision we have to only delete the bookmark and not strip
202 # anything. revsets cannot detect that case.
203 nodetobookmarks = {}
204 for mark, node in pycompat.iteritems(repomarks):
205 nodetobookmarks.setdefault(node, []).append(mark)
206 for marks in nodetobookmarks.values():
207 if bookmarks.issuperset(marks):
208 rsrevs = scmutil.bookmarkrevs(repo, marks[0])
209 revs.update(set(rsrevs))
210 if not revs:
211 with repo.lock(), repo.transaction(b'bookmark') as tr:
212 bmchanges = [(b, None) for b in bookmarks]
213 repomarks.applychanges(repo, tr, bmchanges)
214 for bookmark in sorted(bookmarks):
215 ui.write(_(b"bookmark '%s' deleted\n") % bookmark)
216
217 if not revs:
218 raise error.Abort(_(b'empty revision set'))
219
220 descendants = set(cl.descendants(revs))
221 strippedrevs = revs.union(descendants)
222 roots = revs.difference(descendants)
223
224 # if one of the wdir parent is stripped we'll need
225 # to update away to an earlier revision
226 update = any(
227 p != nullid and cl.rev(p) in strippedrevs
228 for p in repo.dirstate.parents()
229 )
230
231 rootnodes = {cl.node(r) for r in roots}
232
233 q = getattr(repo, 'mq', None)
234 if q is not None and q.applied:
235 # refresh queue state if we're about to strip
236 # applied patches
237 if cl.rev(repo.lookup(b'qtip')) in strippedrevs:
238 q.applieddirty = True
239 start = 0
240 end = len(q.applied)
241 for i, statusentry in enumerate(q.applied):
242 if statusentry.node in rootnodes:
243 # if one of the stripped roots is an applied
244 # patch, only part of the queue is stripped
245 start = i
246 break
247 del q.applied[start:end]
248 q.savedirty()
249
250 revs = sorted(rootnodes)
251 if update and opts.get(b'keep'):
252 urev = _findupdatetarget(repo, revs)
253 uctx = repo[urev]
254
255 # only reset the dirstate for files that would actually change
256 # between the working context and uctx
257 descendantrevs = repo.revs(b"only(., %d)", uctx.rev())
258 changedfiles = []
259 for rev in descendantrevs:
260 # blindly reset the files, regardless of what actually changed
261 changedfiles.extend(repo[rev].files())
262
263 # reset files that only changed in the dirstate too
264 dirstate = repo.dirstate
265 dirchanges = [f for f in dirstate if dirstate[f] != b'n']
266 changedfiles.extend(dirchanges)
267
268 repo.dirstate.rebuild(urev, uctx.manifest(), changedfiles)
269 repo.dirstate.write(repo.currenttransaction())
270
271 # clear resolve state
272 mergestatemod.mergestate.clean(repo)
273
274 update = False
275
276 strip(
277 ui,
278 repo,
279 revs,
280 backup=backup,
281 update=update,
282 force=opts.get(b'force'),
283 bookmarks=bookmarks,
284 soft=opts[b'soft'],
285 )
286
287 return 0
21 # This is a bit ugly, but a uisetup function that defines strip as an
22 # alias for debugstrip would override any user alias for strip,
23 # including aliases like "strip = strip --no-backup".
24 commands.command.rename(old=b'debugstrip', new=b'debugstrip|strip')
@@ -1,4578 +1,4580 b''
1 1 # debugcommands.py - command processing for debug* commands
2 2 #
3 3 # Copyright 2005-2016 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import codecs
11 11 import collections
12 12 import difflib
13 13 import errno
14 14 import glob
15 15 import operator
16 16 import os
17 17 import platform
18 18 import random
19 19 import re
20 20 import socket
21 21 import ssl
22 22 import stat
23 23 import string
24 24 import subprocess
25 25 import sys
26 26 import time
27 27
28 28 from .i18n import _
29 29 from .node import (
30 30 bin,
31 31 hex,
32 32 nullid,
33 33 nullrev,
34 34 short,
35 35 )
36 36 from .pycompat import (
37 37 getattr,
38 38 open,
39 39 )
40 40 from . import (
41 41 bundle2,
42 42 bundlerepo,
43 43 changegroup,
44 44 cmdutil,
45 45 color,
46 46 context,
47 47 copies,
48 48 dagparser,
49 49 encoding,
50 50 error,
51 51 exchange,
52 52 extensions,
53 53 filemerge,
54 54 filesetlang,
55 55 formatter,
56 56 hg,
57 57 httppeer,
58 58 localrepo,
59 59 lock as lockmod,
60 60 logcmdutil,
61 61 mergestate as mergestatemod,
62 62 metadata,
63 63 obsolete,
64 64 obsutil,
65 65 pathutil,
66 66 phases,
67 67 policy,
68 68 pvec,
69 69 pycompat,
70 70 registrar,
71 71 repair,
72 72 revlog,
73 73 revset,
74 74 revsetlang,
75 75 scmutil,
76 76 setdiscovery,
77 77 simplemerge,
78 78 sshpeer,
79 79 sslutil,
80 80 streamclone,
81 strip,
81 82 tags as tagsmod,
82 83 templater,
83 84 treediscovery,
84 85 upgrade,
85 86 url as urlmod,
86 87 util,
87 88 vfs as vfsmod,
88 89 wireprotoframing,
89 90 wireprotoserver,
90 91 wireprotov2peer,
91 92 )
92 93 from .utils import (
93 94 cborutil,
94 95 compression,
95 96 dateutil,
96 97 procutil,
97 98 stringutil,
98 99 )
99 100
100 101 from .revlogutils import (
101 102 deltas as deltautil,
102 103 nodemap,
103 104 sidedata,
104 105 )
105 106
106 107 release = lockmod.release
107 108
108 command = registrar.command()
109
109 table = {}
110 table.update(strip.command._table)
111 command = registrar.command(table)
110 112
111 113 @command(b'debugancestor', [], _(b'[INDEX] REV1 REV2'), optionalrepo=True)
112 114 def debugancestor(ui, repo, *args):
113 115 """find the ancestor revision of two revisions in a given index"""
114 116 if len(args) == 3:
115 117 index, rev1, rev2 = args
116 118 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False), index)
117 119 lookup = r.lookup
118 120 elif len(args) == 2:
119 121 if not repo:
120 122 raise error.Abort(
121 123 _(b'there is no Mercurial repository here (.hg not found)')
122 124 )
123 125 rev1, rev2 = args
124 126 r = repo.changelog
125 127 lookup = repo.lookup
126 128 else:
127 129 raise error.Abort(_(b'either two or three arguments required'))
128 130 a = r.ancestor(lookup(rev1), lookup(rev2))
129 131 ui.write(b'%d:%s\n' % (r.rev(a), hex(a)))
130 132
131 133
132 134 @command(b'debugantivirusrunning', [])
133 135 def debugantivirusrunning(ui, repo):
134 136 """attempt to trigger an antivirus scanner to see if one is active"""
135 137 with repo.cachevfs.open('eicar-test-file.com', b'wb') as f:
136 138 f.write(
137 139 util.b85decode(
138 140 # This is a base85-armored version of the EICAR test file. See
139 141 # https://en.wikipedia.org/wiki/EICAR_test_file for details.
140 142 b'ST#=}P$fV?P+K%yP+C|uG$>GBDK|qyDK~v2MM*<JQY}+dK~6+LQba95P'
141 143 b'E<)&Nm5l)EmTEQR4qnHOhq9iNGnJx'
142 144 )
143 145 )
144 146 # Give an AV engine time to scan the file.
145 147 time.sleep(2)
146 148 util.unlink(repo.cachevfs.join('eicar-test-file.com'))
147 149
148 150
149 151 @command(b'debugapplystreamclonebundle', [], b'FILE')
150 152 def debugapplystreamclonebundle(ui, repo, fname):
151 153 """apply a stream clone bundle file"""
152 154 f = hg.openpath(ui, fname)
153 155 gen = exchange.readbundle(ui, f, fname)
154 156 gen.apply(repo)
155 157
156 158
157 159 @command(
158 160 b'debugbuilddag',
159 161 [
160 162 (
161 163 b'm',
162 164 b'mergeable-file',
163 165 None,
164 166 _(b'add single file mergeable changes'),
165 167 ),
166 168 (
167 169 b'o',
168 170 b'overwritten-file',
169 171 None,
170 172 _(b'add single file all revs overwrite'),
171 173 ),
172 174 (b'n', b'new-file', None, _(b'add new file at each rev')),
173 175 ],
174 176 _(b'[OPTION]... [TEXT]'),
175 177 )
176 178 def debugbuilddag(
177 179 ui,
178 180 repo,
179 181 text=None,
180 182 mergeable_file=False,
181 183 overwritten_file=False,
182 184 new_file=False,
183 185 ):
184 186 """builds a repo with a given DAG from scratch in the current empty repo
185 187
186 188 The description of the DAG is read from stdin if not given on the
187 189 command line.
188 190
189 191 Elements:
190 192
191 193 - "+n" is a linear run of n nodes based on the current default parent
192 194 - "." is a single node based on the current default parent
193 195 - "$" resets the default parent to null (implied at the start);
194 196 otherwise the default parent is always the last node created
195 197 - "<p" sets the default parent to the backref p
196 198 - "*p" is a fork at parent p, which is a backref
197 199 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
198 200 - "/p2" is a merge of the preceding node and p2
199 201 - ":tag" defines a local tag for the preceding node
200 202 - "@branch" sets the named branch for subsequent nodes
201 203 - "#...\\n" is a comment up to the end of the line
202 204
203 205 Whitespace between the above elements is ignored.
204 206
205 207 A backref is either
206 208
207 209 - a number n, which references the node curr-n, where curr is the current
208 210 node, or
209 211 - the name of a local tag you placed earlier using ":tag", or
210 212 - empty to denote the default parent.
211 213
212 214 All string valued-elements are either strictly alphanumeric, or must
213 215 be enclosed in double quotes ("..."), with "\\" as escape character.
214 216 """
215 217
216 218 if text is None:
217 219 ui.status(_(b"reading DAG from stdin\n"))
218 220 text = ui.fin.read()
219 221
220 222 cl = repo.changelog
221 223 if len(cl) > 0:
222 224 raise error.Abort(_(b'repository is not empty'))
223 225
224 226 # determine number of revs in DAG
225 227 total = 0
226 228 for type, data in dagparser.parsedag(text):
227 229 if type == b'n':
228 230 total += 1
229 231
230 232 if mergeable_file:
231 233 linesperrev = 2
232 234 # make a file with k lines per rev
233 235 initialmergedlines = [
234 236 b'%d' % i for i in pycompat.xrange(0, total * linesperrev)
235 237 ]
236 238 initialmergedlines.append(b"")
237 239
238 240 tags = []
239 241 progress = ui.makeprogress(
240 242 _(b'building'), unit=_(b'revisions'), total=total
241 243 )
242 244 with progress, repo.wlock(), repo.lock(), repo.transaction(b"builddag"):
243 245 at = -1
244 246 atbranch = b'default'
245 247 nodeids = []
246 248 id = 0
247 249 progress.update(id)
248 250 for type, data in dagparser.parsedag(text):
249 251 if type == b'n':
250 252 ui.note((b'node %s\n' % pycompat.bytestr(data)))
251 253 id, ps = data
252 254
253 255 files = []
254 256 filecontent = {}
255 257
256 258 p2 = None
257 259 if mergeable_file:
258 260 fn = b"mf"
259 261 p1 = repo[ps[0]]
260 262 if len(ps) > 1:
261 263 p2 = repo[ps[1]]
262 264 pa = p1.ancestor(p2)
263 265 base, local, other = [
264 266 x[fn].data() for x in (pa, p1, p2)
265 267 ]
266 268 m3 = simplemerge.Merge3Text(base, local, other)
267 269 ml = [l.strip() for l in m3.merge_lines()]
268 270 ml.append(b"")
269 271 elif at > 0:
270 272 ml = p1[fn].data().split(b"\n")
271 273 else:
272 274 ml = initialmergedlines
273 275 ml[id * linesperrev] += b" r%i" % id
274 276 mergedtext = b"\n".join(ml)
275 277 files.append(fn)
276 278 filecontent[fn] = mergedtext
277 279
278 280 if overwritten_file:
279 281 fn = b"of"
280 282 files.append(fn)
281 283 filecontent[fn] = b"r%i\n" % id
282 284
283 285 if new_file:
284 286 fn = b"nf%i" % id
285 287 files.append(fn)
286 288 filecontent[fn] = b"r%i\n" % id
287 289 if len(ps) > 1:
288 290 if not p2:
289 291 p2 = repo[ps[1]]
290 292 for fn in p2:
291 293 if fn.startswith(b"nf"):
292 294 files.append(fn)
293 295 filecontent[fn] = p2[fn].data()
294 296
295 297 def fctxfn(repo, cx, path):
296 298 if path in filecontent:
297 299 return context.memfilectx(
298 300 repo, cx, path, filecontent[path]
299 301 )
300 302 return None
301 303
302 304 if len(ps) == 0 or ps[0] < 0:
303 305 pars = [None, None]
304 306 elif len(ps) == 1:
305 307 pars = [nodeids[ps[0]], None]
306 308 else:
307 309 pars = [nodeids[p] for p in ps]
308 310 cx = context.memctx(
309 311 repo,
310 312 pars,
311 313 b"r%i" % id,
312 314 files,
313 315 fctxfn,
314 316 date=(id, 0),
315 317 user=b"debugbuilddag",
316 318 extra={b'branch': atbranch},
317 319 )
318 320 nodeid = repo.commitctx(cx)
319 321 nodeids.append(nodeid)
320 322 at = id
321 323 elif type == b'l':
322 324 id, name = data
323 325 ui.note((b'tag %s\n' % name))
324 326 tags.append(b"%s %s\n" % (hex(repo.changelog.node(id)), name))
325 327 elif type == b'a':
326 328 ui.note((b'branch %s\n' % data))
327 329 atbranch = data
328 330 progress.update(id)
329 331
330 332 if tags:
331 333 repo.vfs.write(b"localtags", b"".join(tags))
332 334
333 335
334 336 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
335 337 indent_string = b' ' * indent
336 338 if all:
337 339 ui.writenoi18n(
338 340 b"%sformat: id, p1, p2, cset, delta base, len(delta)\n"
339 341 % indent_string
340 342 )
341 343
342 344 def showchunks(named):
343 345 ui.write(b"\n%s%s\n" % (indent_string, named))
344 346 for deltadata in gen.deltaiter():
345 347 node, p1, p2, cs, deltabase, delta, flags = deltadata
346 348 ui.write(
347 349 b"%s%s %s %s %s %s %d\n"
348 350 % (
349 351 indent_string,
350 352 hex(node),
351 353 hex(p1),
352 354 hex(p2),
353 355 hex(cs),
354 356 hex(deltabase),
355 357 len(delta),
356 358 )
357 359 )
358 360
359 361 gen.changelogheader()
360 362 showchunks(b"changelog")
361 363 gen.manifestheader()
362 364 showchunks(b"manifest")
363 365 for chunkdata in iter(gen.filelogheader, {}):
364 366 fname = chunkdata[b'filename']
365 367 showchunks(fname)
366 368 else:
367 369 if isinstance(gen, bundle2.unbundle20):
368 370 raise error.Abort(_(b'use debugbundle2 for this file'))
369 371 gen.changelogheader()
370 372 for deltadata in gen.deltaiter():
371 373 node, p1, p2, cs, deltabase, delta, flags = deltadata
372 374 ui.write(b"%s%s\n" % (indent_string, hex(node)))
373 375
374 376
375 377 def _debugobsmarkers(ui, part, indent=0, **opts):
376 378 """display version and markers contained in 'data'"""
377 379 opts = pycompat.byteskwargs(opts)
378 380 data = part.read()
379 381 indent_string = b' ' * indent
380 382 try:
381 383 version, markers = obsolete._readmarkers(data)
382 384 except error.UnknownVersion as exc:
383 385 msg = b"%sunsupported version: %s (%d bytes)\n"
384 386 msg %= indent_string, exc.version, len(data)
385 387 ui.write(msg)
386 388 else:
387 389 msg = b"%sversion: %d (%d bytes)\n"
388 390 msg %= indent_string, version, len(data)
389 391 ui.write(msg)
390 392 fm = ui.formatter(b'debugobsolete', opts)
391 393 for rawmarker in sorted(markers):
392 394 m = obsutil.marker(None, rawmarker)
393 395 fm.startitem()
394 396 fm.plain(indent_string)
395 397 cmdutil.showmarker(fm, m)
396 398 fm.end()
397 399
398 400
399 401 def _debugphaseheads(ui, data, indent=0):
400 402 """display version and markers contained in 'data'"""
401 403 indent_string = b' ' * indent
402 404 headsbyphase = phases.binarydecode(data)
403 405 for phase in phases.allphases:
404 406 for head in headsbyphase[phase]:
405 407 ui.write(indent_string)
406 408 ui.write(b'%s %s\n' % (hex(head), phases.phasenames[phase]))
407 409
408 410
409 411 def _quasirepr(thing):
410 412 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
411 413 return b'{%s}' % (
412 414 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing))
413 415 )
414 416 return pycompat.bytestr(repr(thing))
415 417
416 418
417 419 def _debugbundle2(ui, gen, all=None, **opts):
418 420 """lists the contents of a bundle2"""
419 421 if not isinstance(gen, bundle2.unbundle20):
420 422 raise error.Abort(_(b'not a bundle2 file'))
421 423 ui.write((b'Stream params: %s\n' % _quasirepr(gen.params)))
422 424 parttypes = opts.get('part_type', [])
423 425 for part in gen.iterparts():
424 426 if parttypes and part.type not in parttypes:
425 427 continue
426 428 msg = b'%s -- %s (mandatory: %r)\n'
427 429 ui.write((msg % (part.type, _quasirepr(part.params), part.mandatory)))
428 430 if part.type == b'changegroup':
429 431 version = part.params.get(b'version', b'01')
430 432 cg = changegroup.getunbundler(version, part, b'UN')
431 433 if not ui.quiet:
432 434 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
433 435 if part.type == b'obsmarkers':
434 436 if not ui.quiet:
435 437 _debugobsmarkers(ui, part, indent=4, **opts)
436 438 if part.type == b'phase-heads':
437 439 if not ui.quiet:
438 440 _debugphaseheads(ui, part, indent=4)
439 441
440 442
441 443 @command(
442 444 b'debugbundle',
443 445 [
444 446 (b'a', b'all', None, _(b'show all details')),
445 447 (b'', b'part-type', [], _(b'show only the named part type')),
446 448 (b'', b'spec', None, _(b'print the bundlespec of the bundle')),
447 449 ],
448 450 _(b'FILE'),
449 451 norepo=True,
450 452 )
451 453 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
452 454 """lists the contents of a bundle"""
453 455 with hg.openpath(ui, bundlepath) as f:
454 456 if spec:
455 457 spec = exchange.getbundlespec(ui, f)
456 458 ui.write(b'%s\n' % spec)
457 459 return
458 460
459 461 gen = exchange.readbundle(ui, f, bundlepath)
460 462 if isinstance(gen, bundle2.unbundle20):
461 463 return _debugbundle2(ui, gen, all=all, **opts)
462 464 _debugchangegroup(ui, gen, all=all, **opts)
463 465
464 466
465 467 @command(b'debugcapabilities', [], _(b'PATH'), norepo=True)
466 468 def debugcapabilities(ui, path, **opts):
467 469 """lists the capabilities of a remote peer"""
468 470 opts = pycompat.byteskwargs(opts)
469 471 peer = hg.peer(ui, opts, path)
470 472 caps = peer.capabilities()
471 473 ui.writenoi18n(b'Main capabilities:\n')
472 474 for c in sorted(caps):
473 475 ui.write(b' %s\n' % c)
474 476 b2caps = bundle2.bundle2caps(peer)
475 477 if b2caps:
476 478 ui.writenoi18n(b'Bundle2 capabilities:\n')
477 479 for key, values in sorted(pycompat.iteritems(b2caps)):
478 480 ui.write(b' %s\n' % key)
479 481 for v in values:
480 482 ui.write(b' %s\n' % v)
481 483
482 484
483 485 @command(b'debugchangedfiles', [], b'REV')
484 486 def debugchangedfiles(ui, repo, rev):
485 487 """list the stored files changes for a revision"""
486 488 ctx = scmutil.revsingle(repo, rev, None)
487 489 sd = repo.changelog.sidedata(ctx.rev())
488 490 files_block = sd.get(sidedata.SD_FILES)
489 491 if files_block is not None:
490 492 files = metadata.decode_files_sidedata(sd)
491 493 for f in sorted(files.touched):
492 494 if f in files.added:
493 495 action = b"added"
494 496 elif f in files.removed:
495 497 action = b"removed"
496 498 elif f in files.merged:
497 499 action = b"merged"
498 500 elif f in files.salvaged:
499 501 action = b"salvaged"
500 502 else:
501 503 action = b"touched"
502 504
503 505 copy_parent = b""
504 506 copy_source = b""
505 507 if f in files.copied_from_p1:
506 508 copy_parent = b"p1"
507 509 copy_source = files.copied_from_p1[f]
508 510 elif f in files.copied_from_p2:
509 511 copy_parent = b"p2"
510 512 copy_source = files.copied_from_p2[f]
511 513
512 514 data = (action, copy_parent, f, copy_source)
513 515 template = b"%-8s %2s: %s, %s;\n"
514 516 ui.write(template % data)
515 517
516 518
517 519 @command(b'debugcheckstate', [], b'')
518 520 def debugcheckstate(ui, repo):
519 521 """validate the correctness of the current dirstate"""
520 522 parent1, parent2 = repo.dirstate.parents()
521 523 m1 = repo[parent1].manifest()
522 524 m2 = repo[parent2].manifest()
523 525 errors = 0
524 526 for f in repo.dirstate:
525 527 state = repo.dirstate[f]
526 528 if state in b"nr" and f not in m1:
527 529 ui.warn(_(b"%s in state %s, but not in manifest1\n") % (f, state))
528 530 errors += 1
529 531 if state in b"a" and f in m1:
530 532 ui.warn(_(b"%s in state %s, but also in manifest1\n") % (f, state))
531 533 errors += 1
532 534 if state in b"m" and f not in m1 and f not in m2:
533 535 ui.warn(
534 536 _(b"%s in state %s, but not in either manifest\n") % (f, state)
535 537 )
536 538 errors += 1
537 539 for f in m1:
538 540 state = repo.dirstate[f]
539 541 if state not in b"nrm":
540 542 ui.warn(_(b"%s in manifest1, but listed as state %s") % (f, state))
541 543 errors += 1
542 544 if errors:
543 545 errstr = _(b".hg/dirstate inconsistent with current parent's manifest")
544 546 raise error.Abort(errstr)
545 547
546 548
547 549 @command(
548 550 b'debugcolor',
549 551 [(b'', b'style', None, _(b'show all configured styles'))],
550 552 b'hg debugcolor',
551 553 )
552 554 def debugcolor(ui, repo, **opts):
553 555 """show available color, effects or style"""
554 556 ui.writenoi18n(b'color mode: %s\n' % stringutil.pprint(ui._colormode))
555 557 if opts.get('style'):
556 558 return _debugdisplaystyle(ui)
557 559 else:
558 560 return _debugdisplaycolor(ui)
559 561
560 562
561 563 def _debugdisplaycolor(ui):
562 564 ui = ui.copy()
563 565 ui._styles.clear()
564 566 for effect in color._activeeffects(ui).keys():
565 567 ui._styles[effect] = effect
566 568 if ui._terminfoparams:
567 569 for k, v in ui.configitems(b'color'):
568 570 if k.startswith(b'color.'):
569 571 ui._styles[k] = k[6:]
570 572 elif k.startswith(b'terminfo.'):
571 573 ui._styles[k] = k[9:]
572 574 ui.write(_(b'available colors:\n'))
573 575 # sort label with a '_' after the other to group '_background' entry.
574 576 items = sorted(ui._styles.items(), key=lambda i: (b'_' in i[0], i[0], i[1]))
575 577 for colorname, label in items:
576 578 ui.write(b'%s\n' % colorname, label=label)
577 579
578 580
579 581 def _debugdisplaystyle(ui):
580 582 ui.write(_(b'available style:\n'))
581 583 if not ui._styles:
582 584 return
583 585 width = max(len(s) for s in ui._styles)
584 586 for label, effects in sorted(ui._styles.items()):
585 587 ui.write(b'%s' % label, label=label)
586 588 if effects:
587 589 # 50
588 590 ui.write(b': ')
589 591 ui.write(b' ' * (max(0, width - len(label))))
590 592 ui.write(b', '.join(ui.label(e, e) for e in effects.split()))
591 593 ui.write(b'\n')
592 594
593 595
594 596 @command(b'debugcreatestreamclonebundle', [], b'FILE')
595 597 def debugcreatestreamclonebundle(ui, repo, fname):
596 598 """create a stream clone bundle file
597 599
598 600 Stream bundles are special bundles that are essentially archives of
599 601 revlog files. They are commonly used for cloning very quickly.
600 602 """
601 603 # TODO we may want to turn this into an abort when this functionality
602 604 # is moved into `hg bundle`.
603 605 if phases.hassecret(repo):
604 606 ui.warn(
605 607 _(
606 608 b'(warning: stream clone bundle will contain secret '
607 609 b'revisions)\n'
608 610 )
609 611 )
610 612
611 613 requirements, gen = streamclone.generatebundlev1(repo)
612 614 changegroup.writechunks(ui, gen, fname)
613 615
614 616 ui.write(_(b'bundle requirements: %s\n') % b', '.join(sorted(requirements)))
615 617
616 618
617 619 @command(
618 620 b'debugdag',
619 621 [
620 622 (b't', b'tags', None, _(b'use tags as labels')),
621 623 (b'b', b'branches', None, _(b'annotate with branch names')),
622 624 (b'', b'dots', None, _(b'use dots for runs')),
623 625 (b's', b'spaces', None, _(b'separate elements by spaces')),
624 626 ],
625 627 _(b'[OPTION]... [FILE [REV]...]'),
626 628 optionalrepo=True,
627 629 )
628 630 def debugdag(ui, repo, file_=None, *revs, **opts):
629 631 """format the changelog or an index DAG as a concise textual description
630 632
631 633 If you pass a revlog index, the revlog's DAG is emitted. If you list
632 634 revision numbers, they get labeled in the output as rN.
633 635
634 636 Otherwise, the changelog DAG of the current repo is emitted.
635 637 """
636 638 spaces = opts.get('spaces')
637 639 dots = opts.get('dots')
638 640 if file_:
639 641 rlog = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False), file_)
640 642 revs = {int(r) for r in revs}
641 643
642 644 def events():
643 645 for r in rlog:
644 646 yield b'n', (r, list(p for p in rlog.parentrevs(r) if p != -1))
645 647 if r in revs:
646 648 yield b'l', (r, b"r%i" % r)
647 649
648 650 elif repo:
649 651 cl = repo.changelog
650 652 tags = opts.get('tags')
651 653 branches = opts.get('branches')
652 654 if tags:
653 655 labels = {}
654 656 for l, n in repo.tags().items():
655 657 labels.setdefault(cl.rev(n), []).append(l)
656 658
657 659 def events():
658 660 b = b"default"
659 661 for r in cl:
660 662 if branches:
661 663 newb = cl.read(cl.node(r))[5][b'branch']
662 664 if newb != b:
663 665 yield b'a', newb
664 666 b = newb
665 667 yield b'n', (r, list(p for p in cl.parentrevs(r) if p != -1))
666 668 if tags:
667 669 ls = labels.get(r)
668 670 if ls:
669 671 for l in ls:
670 672 yield b'l', (r, l)
671 673
672 674 else:
673 675 raise error.Abort(_(b'need repo for changelog dag'))
674 676
675 677 for line in dagparser.dagtextlines(
676 678 events(),
677 679 addspaces=spaces,
678 680 wraplabels=True,
679 681 wrapannotations=True,
680 682 wrapnonlinear=dots,
681 683 usedots=dots,
682 684 maxlinewidth=70,
683 685 ):
684 686 ui.write(line)
685 687 ui.write(b"\n")
686 688
687 689
688 690 @command(b'debugdata', cmdutil.debugrevlogopts, _(b'-c|-m|FILE REV'))
689 691 def debugdata(ui, repo, file_, rev=None, **opts):
690 692 """dump the contents of a data file revision"""
691 693 opts = pycompat.byteskwargs(opts)
692 694 if opts.get(b'changelog') or opts.get(b'manifest') or opts.get(b'dir'):
693 695 if rev is not None:
694 696 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
695 697 file_, rev = None, file_
696 698 elif rev is None:
697 699 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
698 700 r = cmdutil.openstorage(repo, b'debugdata', file_, opts)
699 701 try:
700 702 ui.write(r.rawdata(r.lookup(rev)))
701 703 except KeyError:
702 704 raise error.Abort(_(b'invalid revision identifier %s') % rev)
703 705
704 706
705 707 @command(
706 708 b'debugdate',
707 709 [(b'e', b'extended', None, _(b'try extended date formats'))],
708 710 _(b'[-e] DATE [RANGE]'),
709 711 norepo=True,
710 712 optionalrepo=True,
711 713 )
712 714 def debugdate(ui, date, range=None, **opts):
713 715 """parse and display a date"""
714 716 if opts["extended"]:
715 717 d = dateutil.parsedate(date, dateutil.extendeddateformats)
716 718 else:
717 719 d = dateutil.parsedate(date)
718 720 ui.writenoi18n(b"internal: %d %d\n" % d)
719 721 ui.writenoi18n(b"standard: %s\n" % dateutil.datestr(d))
720 722 if range:
721 723 m = dateutil.matchdate(range)
722 724 ui.writenoi18n(b"match: %s\n" % m(d[0]))
723 725
724 726
725 727 @command(
726 728 b'debugdeltachain',
727 729 cmdutil.debugrevlogopts + cmdutil.formatteropts,
728 730 _(b'-c|-m|FILE'),
729 731 optionalrepo=True,
730 732 )
731 733 def debugdeltachain(ui, repo, file_=None, **opts):
732 734 """dump information about delta chains in a revlog
733 735
734 736 Output can be templatized. Available template keywords are:
735 737
736 738 :``rev``: revision number
737 739 :``chainid``: delta chain identifier (numbered by unique base)
738 740 :``chainlen``: delta chain length to this revision
739 741 :``prevrev``: previous revision in delta chain
740 742 :``deltatype``: role of delta / how it was computed
741 743 :``compsize``: compressed size of revision
742 744 :``uncompsize``: uncompressed size of revision
743 745 :``chainsize``: total size of compressed revisions in chain
744 746 :``chainratio``: total chain size divided by uncompressed revision size
745 747 (new delta chains typically start at ratio 2.00)
746 748 :``lindist``: linear distance from base revision in delta chain to end
747 749 of this revision
748 750 :``extradist``: total size of revisions not part of this delta chain from
749 751 base of delta chain to end of this revision; a measurement
750 752 of how much extra data we need to read/seek across to read
751 753 the delta chain for this revision
752 754 :``extraratio``: extradist divided by chainsize; another representation of
753 755 how much unrelated data is needed to load this delta chain
754 756
755 757 If the repository is configured to use the sparse read, additional keywords
756 758 are available:
757 759
758 760 :``readsize``: total size of data read from the disk for a revision
759 761 (sum of the sizes of all the blocks)
760 762 :``largestblock``: size of the largest block of data read from the disk
761 763 :``readdensity``: density of useful bytes in the data read from the disk
762 764 :``srchunks``: in how many data hunks the whole revision would be read
763 765
764 766 The sparse read can be enabled with experimental.sparse-read = True
765 767 """
766 768 opts = pycompat.byteskwargs(opts)
767 769 r = cmdutil.openrevlog(repo, b'debugdeltachain', file_, opts)
768 770 index = r.index
769 771 start = r.start
770 772 length = r.length
771 773 generaldelta = r.version & revlog.FLAG_GENERALDELTA
772 774 withsparseread = getattr(r, '_withsparseread', False)
773 775
774 776 def revinfo(rev):
775 777 e = index[rev]
776 778 compsize = e[1]
777 779 uncompsize = e[2]
778 780 chainsize = 0
779 781
780 782 if generaldelta:
781 783 if e[3] == e[5]:
782 784 deltatype = b'p1'
783 785 elif e[3] == e[6]:
784 786 deltatype = b'p2'
785 787 elif e[3] == rev - 1:
786 788 deltatype = b'prev'
787 789 elif e[3] == rev:
788 790 deltatype = b'base'
789 791 else:
790 792 deltatype = b'other'
791 793 else:
792 794 if e[3] == rev:
793 795 deltatype = b'base'
794 796 else:
795 797 deltatype = b'prev'
796 798
797 799 chain = r._deltachain(rev)[0]
798 800 for iterrev in chain:
799 801 e = index[iterrev]
800 802 chainsize += e[1]
801 803
802 804 return compsize, uncompsize, deltatype, chain, chainsize
803 805
804 806 fm = ui.formatter(b'debugdeltachain', opts)
805 807
806 808 fm.plain(
807 809 b' rev chain# chainlen prev delta '
808 810 b'size rawsize chainsize ratio lindist extradist '
809 811 b'extraratio'
810 812 )
811 813 if withsparseread:
812 814 fm.plain(b' readsize largestblk rddensity srchunks')
813 815 fm.plain(b'\n')
814 816
815 817 chainbases = {}
816 818 for rev in r:
817 819 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
818 820 chainbase = chain[0]
819 821 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
820 822 basestart = start(chainbase)
821 823 revstart = start(rev)
822 824 lineardist = revstart + comp - basestart
823 825 extradist = lineardist - chainsize
824 826 try:
825 827 prevrev = chain[-2]
826 828 except IndexError:
827 829 prevrev = -1
828 830
829 831 if uncomp != 0:
830 832 chainratio = float(chainsize) / float(uncomp)
831 833 else:
832 834 chainratio = chainsize
833 835
834 836 if chainsize != 0:
835 837 extraratio = float(extradist) / float(chainsize)
836 838 else:
837 839 extraratio = extradist
838 840
839 841 fm.startitem()
840 842 fm.write(
841 843 b'rev chainid chainlen prevrev deltatype compsize '
842 844 b'uncompsize chainsize chainratio lindist extradist '
843 845 b'extraratio',
844 846 b'%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
845 847 rev,
846 848 chainid,
847 849 len(chain),
848 850 prevrev,
849 851 deltatype,
850 852 comp,
851 853 uncomp,
852 854 chainsize,
853 855 chainratio,
854 856 lineardist,
855 857 extradist,
856 858 extraratio,
857 859 rev=rev,
858 860 chainid=chainid,
859 861 chainlen=len(chain),
860 862 prevrev=prevrev,
861 863 deltatype=deltatype,
862 864 compsize=comp,
863 865 uncompsize=uncomp,
864 866 chainsize=chainsize,
865 867 chainratio=chainratio,
866 868 lindist=lineardist,
867 869 extradist=extradist,
868 870 extraratio=extraratio,
869 871 )
870 872 if withsparseread:
871 873 readsize = 0
872 874 largestblock = 0
873 875 srchunks = 0
874 876
875 877 for revschunk in deltautil.slicechunk(r, chain):
876 878 srchunks += 1
877 879 blkend = start(revschunk[-1]) + length(revschunk[-1])
878 880 blksize = blkend - start(revschunk[0])
879 881
880 882 readsize += blksize
881 883 if largestblock < blksize:
882 884 largestblock = blksize
883 885
884 886 if readsize:
885 887 readdensity = float(chainsize) / float(readsize)
886 888 else:
887 889 readdensity = 1
888 890
889 891 fm.write(
890 892 b'readsize largestblock readdensity srchunks',
891 893 b' %10d %10d %9.5f %8d',
892 894 readsize,
893 895 largestblock,
894 896 readdensity,
895 897 srchunks,
896 898 readsize=readsize,
897 899 largestblock=largestblock,
898 900 readdensity=readdensity,
899 901 srchunks=srchunks,
900 902 )
901 903
902 904 fm.plain(b'\n')
903 905
904 906 fm.end()
905 907
906 908
907 909 @command(
908 910 b'debugdirstate|debugstate',
909 911 [
910 912 (
911 913 b'',
912 914 b'nodates',
913 915 None,
914 916 _(b'do not display the saved mtime (DEPRECATED)'),
915 917 ),
916 918 (b'', b'dates', True, _(b'display the saved mtime')),
917 919 (b'', b'datesort', None, _(b'sort by saved mtime')),
918 920 ],
919 921 _(b'[OPTION]...'),
920 922 )
921 923 def debugstate(ui, repo, **opts):
922 924 """show the contents of the current dirstate"""
923 925
924 926 nodates = not opts['dates']
925 927 if opts.get('nodates') is not None:
926 928 nodates = True
927 929 datesort = opts.get('datesort')
928 930
929 931 if datesort:
930 932 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
931 933 else:
932 934 keyfunc = None # sort by filename
933 935 for file_, ent in sorted(pycompat.iteritems(repo.dirstate), key=keyfunc):
934 936 if ent[3] == -1:
935 937 timestr = b'unset '
936 938 elif nodates:
937 939 timestr = b'set '
938 940 else:
939 941 timestr = time.strftime(
940 942 "%Y-%m-%d %H:%M:%S ", time.localtime(ent[3])
941 943 )
942 944 timestr = encoding.strtolocal(timestr)
943 945 if ent[1] & 0o20000:
944 946 mode = b'lnk'
945 947 else:
946 948 mode = b'%3o' % (ent[1] & 0o777 & ~util.umask)
947 949 ui.write(b"%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
948 950 for f in repo.dirstate.copies():
949 951 ui.write(_(b"copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
950 952
951 953
952 954 @command(
953 955 b'debugdiscovery',
954 956 [
955 957 (b'', b'old', None, _(b'use old-style discovery')),
956 958 (
957 959 b'',
958 960 b'nonheads',
959 961 None,
960 962 _(b'use old-style discovery with non-heads included'),
961 963 ),
962 964 (b'', b'rev', [], b'restrict discovery to this set of revs'),
963 965 (b'', b'seed', b'12323', b'specify the random seed use for discovery'),
964 966 ]
965 967 + cmdutil.remoteopts,
966 968 _(b'[--rev REV] [OTHER]'),
967 969 )
968 970 def debugdiscovery(ui, repo, remoteurl=b"default", **opts):
969 971 """runs the changeset discovery protocol in isolation"""
970 972 opts = pycompat.byteskwargs(opts)
971 973 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
972 974 remote = hg.peer(repo, opts, remoteurl)
973 975 ui.status(_(b'comparing with %s\n') % util.hidepassword(remoteurl))
974 976
975 977 # make sure tests are repeatable
976 978 random.seed(int(opts[b'seed']))
977 979
978 980 if opts.get(b'old'):
979 981
980 982 def doit(pushedrevs, remoteheads, remote=remote):
981 983 if not util.safehasattr(remote, b'branches'):
982 984 # enable in-client legacy support
983 985 remote = localrepo.locallegacypeer(remote.local())
984 986 common, _in, hds = treediscovery.findcommonincoming(
985 987 repo, remote, force=True
986 988 )
987 989 common = set(common)
988 990 if not opts.get(b'nonheads'):
989 991 ui.writenoi18n(
990 992 b"unpruned common: %s\n"
991 993 % b" ".join(sorted(short(n) for n in common))
992 994 )
993 995
994 996 clnode = repo.changelog.node
995 997 common = repo.revs(b'heads(::%ln)', common)
996 998 common = {clnode(r) for r in common}
997 999 return common, hds
998 1000
999 1001 else:
1000 1002
1001 1003 def doit(pushedrevs, remoteheads, remote=remote):
1002 1004 nodes = None
1003 1005 if pushedrevs:
1004 1006 revs = scmutil.revrange(repo, pushedrevs)
1005 1007 nodes = [repo[r].node() for r in revs]
1006 1008 common, any, hds = setdiscovery.findcommonheads(
1007 1009 ui, repo, remote, ancestorsof=nodes
1008 1010 )
1009 1011 return common, hds
1010 1012
1011 1013 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
1012 1014 localrevs = opts[b'rev']
1013 1015 with util.timedcm('debug-discovery') as t:
1014 1016 common, hds = doit(localrevs, remoterevs)
1015 1017
1016 1018 # compute all statistics
1017 1019 common = set(common)
1018 1020 rheads = set(hds)
1019 1021 lheads = set(repo.heads())
1020 1022
1021 1023 data = {}
1022 1024 data[b'elapsed'] = t.elapsed
1023 1025 data[b'nb-common'] = len(common)
1024 1026 data[b'nb-common-local'] = len(common & lheads)
1025 1027 data[b'nb-common-remote'] = len(common & rheads)
1026 1028 data[b'nb-common-both'] = len(common & rheads & lheads)
1027 1029 data[b'nb-local'] = len(lheads)
1028 1030 data[b'nb-local-missing'] = data[b'nb-local'] - data[b'nb-common-local']
1029 1031 data[b'nb-remote'] = len(rheads)
1030 1032 data[b'nb-remote-unknown'] = data[b'nb-remote'] - data[b'nb-common-remote']
1031 1033 data[b'nb-revs'] = len(repo.revs(b'all()'))
1032 1034 data[b'nb-revs-common'] = len(repo.revs(b'::%ln', common))
1033 1035 data[b'nb-revs-missing'] = data[b'nb-revs'] - data[b'nb-revs-common']
1034 1036
1035 1037 # display discovery summary
1036 1038 ui.writenoi18n(b"elapsed time: %(elapsed)f seconds\n" % data)
1037 1039 ui.writenoi18n(b"heads summary:\n")
1038 1040 ui.writenoi18n(b" total common heads: %(nb-common)9d\n" % data)
1039 1041 ui.writenoi18n(b" also local heads: %(nb-common-local)9d\n" % data)
1040 1042 ui.writenoi18n(b" also remote heads: %(nb-common-remote)9d\n" % data)
1041 1043 ui.writenoi18n(b" both: %(nb-common-both)9d\n" % data)
1042 1044 ui.writenoi18n(b" local heads: %(nb-local)9d\n" % data)
1043 1045 ui.writenoi18n(b" common: %(nb-common-local)9d\n" % data)
1044 1046 ui.writenoi18n(b" missing: %(nb-local-missing)9d\n" % data)
1045 1047 ui.writenoi18n(b" remote heads: %(nb-remote)9d\n" % data)
1046 1048 ui.writenoi18n(b" common: %(nb-common-remote)9d\n" % data)
1047 1049 ui.writenoi18n(b" unknown: %(nb-remote-unknown)9d\n" % data)
1048 1050 ui.writenoi18n(b"local changesets: %(nb-revs)9d\n" % data)
1049 1051 ui.writenoi18n(b" common: %(nb-revs-common)9d\n" % data)
1050 1052 ui.writenoi18n(b" missing: %(nb-revs-missing)9d\n" % data)
1051 1053
1052 1054 if ui.verbose:
1053 1055 ui.writenoi18n(
1054 1056 b"common heads: %s\n" % b" ".join(sorted(short(n) for n in common))
1055 1057 )
1056 1058
1057 1059
1058 1060 _chunksize = 4 << 10
1059 1061
1060 1062
1061 1063 @command(
1062 1064 b'debugdownload', [(b'o', b'output', b'', _(b'path')),], optionalrepo=True
1063 1065 )
1064 1066 def debugdownload(ui, repo, url, output=None, **opts):
1065 1067 """download a resource using Mercurial logic and config
1066 1068 """
1067 1069 fh = urlmod.open(ui, url, output)
1068 1070
1069 1071 dest = ui
1070 1072 if output:
1071 1073 dest = open(output, b"wb", _chunksize)
1072 1074 try:
1073 1075 data = fh.read(_chunksize)
1074 1076 while data:
1075 1077 dest.write(data)
1076 1078 data = fh.read(_chunksize)
1077 1079 finally:
1078 1080 if output:
1079 1081 dest.close()
1080 1082
1081 1083
1082 1084 @command(b'debugextensions', cmdutil.formatteropts, [], optionalrepo=True)
1083 1085 def debugextensions(ui, repo, **opts):
1084 1086 '''show information about active extensions'''
1085 1087 opts = pycompat.byteskwargs(opts)
1086 1088 exts = extensions.extensions(ui)
1087 1089 hgver = util.version()
1088 1090 fm = ui.formatter(b'debugextensions', opts)
1089 1091 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
1090 1092 isinternal = extensions.ismoduleinternal(extmod)
1091 1093 extsource = None
1092 1094
1093 1095 if util.safehasattr(extmod, '__file__'):
1094 1096 extsource = pycompat.fsencode(extmod.__file__)
1095 1097 elif getattr(sys, 'oxidized', False):
1096 1098 extsource = pycompat.sysexecutable
1097 1099 if isinternal:
1098 1100 exttestedwith = [] # never expose magic string to users
1099 1101 else:
1100 1102 exttestedwith = getattr(extmod, 'testedwith', b'').split()
1101 1103 extbuglink = getattr(extmod, 'buglink', None)
1102 1104
1103 1105 fm.startitem()
1104 1106
1105 1107 if ui.quiet or ui.verbose:
1106 1108 fm.write(b'name', b'%s\n', extname)
1107 1109 else:
1108 1110 fm.write(b'name', b'%s', extname)
1109 1111 if isinternal or hgver in exttestedwith:
1110 1112 fm.plain(b'\n')
1111 1113 elif not exttestedwith:
1112 1114 fm.plain(_(b' (untested!)\n'))
1113 1115 else:
1114 1116 lasttestedversion = exttestedwith[-1]
1115 1117 fm.plain(b' (%s!)\n' % lasttestedversion)
1116 1118
1117 1119 fm.condwrite(
1118 1120 ui.verbose and extsource,
1119 1121 b'source',
1120 1122 _(b' location: %s\n'),
1121 1123 extsource or b"",
1122 1124 )
1123 1125
1124 1126 if ui.verbose:
1125 1127 fm.plain(_(b' bundled: %s\n') % [b'no', b'yes'][isinternal])
1126 1128 fm.data(bundled=isinternal)
1127 1129
1128 1130 fm.condwrite(
1129 1131 ui.verbose and exttestedwith,
1130 1132 b'testedwith',
1131 1133 _(b' tested with: %s\n'),
1132 1134 fm.formatlist(exttestedwith, name=b'ver'),
1133 1135 )
1134 1136
1135 1137 fm.condwrite(
1136 1138 ui.verbose and extbuglink,
1137 1139 b'buglink',
1138 1140 _(b' bug reporting: %s\n'),
1139 1141 extbuglink or b"",
1140 1142 )
1141 1143
1142 1144 fm.end()
1143 1145
1144 1146
1145 1147 @command(
1146 1148 b'debugfileset',
1147 1149 [
1148 1150 (
1149 1151 b'r',
1150 1152 b'rev',
1151 1153 b'',
1152 1154 _(b'apply the filespec on this revision'),
1153 1155 _(b'REV'),
1154 1156 ),
1155 1157 (
1156 1158 b'',
1157 1159 b'all-files',
1158 1160 False,
1159 1161 _(b'test files from all revisions and working directory'),
1160 1162 ),
1161 1163 (
1162 1164 b's',
1163 1165 b'show-matcher',
1164 1166 None,
1165 1167 _(b'print internal representation of matcher'),
1166 1168 ),
1167 1169 (
1168 1170 b'p',
1169 1171 b'show-stage',
1170 1172 [],
1171 1173 _(b'print parsed tree at the given stage'),
1172 1174 _(b'NAME'),
1173 1175 ),
1174 1176 ],
1175 1177 _(b'[-r REV] [--all-files] [OPTION]... FILESPEC'),
1176 1178 )
1177 1179 def debugfileset(ui, repo, expr, **opts):
1178 1180 '''parse and apply a fileset specification'''
1179 1181 from . import fileset
1180 1182
1181 1183 fileset.symbols # force import of fileset so we have predicates to optimize
1182 1184 opts = pycompat.byteskwargs(opts)
1183 1185 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
1184 1186
1185 1187 stages = [
1186 1188 (b'parsed', pycompat.identity),
1187 1189 (b'analyzed', filesetlang.analyze),
1188 1190 (b'optimized', filesetlang.optimize),
1189 1191 ]
1190 1192 stagenames = {n for n, f in stages}
1191 1193
1192 1194 showalways = set()
1193 1195 if ui.verbose and not opts[b'show_stage']:
1194 1196 # show parsed tree by --verbose (deprecated)
1195 1197 showalways.add(b'parsed')
1196 1198 if opts[b'show_stage'] == [b'all']:
1197 1199 showalways.update(stagenames)
1198 1200 else:
1199 1201 for n in opts[b'show_stage']:
1200 1202 if n not in stagenames:
1201 1203 raise error.Abort(_(b'invalid stage name: %s') % n)
1202 1204 showalways.update(opts[b'show_stage'])
1203 1205
1204 1206 tree = filesetlang.parse(expr)
1205 1207 for n, f in stages:
1206 1208 tree = f(tree)
1207 1209 if n in showalways:
1208 1210 if opts[b'show_stage'] or n != b'parsed':
1209 1211 ui.write(b"* %s:\n" % n)
1210 1212 ui.write(filesetlang.prettyformat(tree), b"\n")
1211 1213
1212 1214 files = set()
1213 1215 if opts[b'all_files']:
1214 1216 for r in repo:
1215 1217 c = repo[r]
1216 1218 files.update(c.files())
1217 1219 files.update(c.substate)
1218 1220 if opts[b'all_files'] or ctx.rev() is None:
1219 1221 wctx = repo[None]
1220 1222 files.update(
1221 1223 repo.dirstate.walk(
1222 1224 scmutil.matchall(repo),
1223 1225 subrepos=list(wctx.substate),
1224 1226 unknown=True,
1225 1227 ignored=True,
1226 1228 )
1227 1229 )
1228 1230 files.update(wctx.substate)
1229 1231 else:
1230 1232 files.update(ctx.files())
1231 1233 files.update(ctx.substate)
1232 1234
1233 1235 m = ctx.matchfileset(repo.getcwd(), expr)
1234 1236 if opts[b'show_matcher'] or (opts[b'show_matcher'] is None and ui.verbose):
1235 1237 ui.writenoi18n(b'* matcher:\n', stringutil.prettyrepr(m), b'\n')
1236 1238 for f in sorted(files):
1237 1239 if not m(f):
1238 1240 continue
1239 1241 ui.write(b"%s\n" % f)
1240 1242
1241 1243
1242 1244 @command(b'debugformat', [] + cmdutil.formatteropts)
1243 1245 def debugformat(ui, repo, **opts):
1244 1246 """display format information about the current repository
1245 1247
1246 1248 Use --verbose to get extra information about current config value and
1247 1249 Mercurial default."""
1248 1250 opts = pycompat.byteskwargs(opts)
1249 1251 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
1250 1252 maxvariantlength = max(len(b'format-variant'), maxvariantlength)
1251 1253
1252 1254 def makeformatname(name):
1253 1255 return b'%s:' + (b' ' * (maxvariantlength - len(name)))
1254 1256
1255 1257 fm = ui.formatter(b'debugformat', opts)
1256 1258 if fm.isplain():
1257 1259
1258 1260 def formatvalue(value):
1259 1261 if util.safehasattr(value, b'startswith'):
1260 1262 return value
1261 1263 if value:
1262 1264 return b'yes'
1263 1265 else:
1264 1266 return b'no'
1265 1267
1266 1268 else:
1267 1269 formatvalue = pycompat.identity
1268 1270
1269 1271 fm.plain(b'format-variant')
1270 1272 fm.plain(b' ' * (maxvariantlength - len(b'format-variant')))
1271 1273 fm.plain(b' repo')
1272 1274 if ui.verbose:
1273 1275 fm.plain(b' config default')
1274 1276 fm.plain(b'\n')
1275 1277 for fv in upgrade.allformatvariant:
1276 1278 fm.startitem()
1277 1279 repovalue = fv.fromrepo(repo)
1278 1280 configvalue = fv.fromconfig(repo)
1279 1281
1280 1282 if repovalue != configvalue:
1281 1283 namelabel = b'formatvariant.name.mismatchconfig'
1282 1284 repolabel = b'formatvariant.repo.mismatchconfig'
1283 1285 elif repovalue != fv.default:
1284 1286 namelabel = b'formatvariant.name.mismatchdefault'
1285 1287 repolabel = b'formatvariant.repo.mismatchdefault'
1286 1288 else:
1287 1289 namelabel = b'formatvariant.name.uptodate'
1288 1290 repolabel = b'formatvariant.repo.uptodate'
1289 1291
1290 1292 fm.write(b'name', makeformatname(fv.name), fv.name, label=namelabel)
1291 1293 fm.write(b'repo', b' %3s', formatvalue(repovalue), label=repolabel)
1292 1294 if fv.default != configvalue:
1293 1295 configlabel = b'formatvariant.config.special'
1294 1296 else:
1295 1297 configlabel = b'formatvariant.config.default'
1296 1298 fm.condwrite(
1297 1299 ui.verbose,
1298 1300 b'config',
1299 1301 b' %6s',
1300 1302 formatvalue(configvalue),
1301 1303 label=configlabel,
1302 1304 )
1303 1305 fm.condwrite(
1304 1306 ui.verbose,
1305 1307 b'default',
1306 1308 b' %7s',
1307 1309 formatvalue(fv.default),
1308 1310 label=b'formatvariant.default',
1309 1311 )
1310 1312 fm.plain(b'\n')
1311 1313 fm.end()
1312 1314
1313 1315
1314 1316 @command(b'debugfsinfo', [], _(b'[PATH]'), norepo=True)
1315 1317 def debugfsinfo(ui, path=b"."):
1316 1318 """show information detected about current filesystem"""
1317 1319 ui.writenoi18n(b'path: %s\n' % path)
1318 1320 ui.writenoi18n(
1319 1321 b'mounted on: %s\n' % (util.getfsmountpoint(path) or b'(unknown)')
1320 1322 )
1321 1323 ui.writenoi18n(b'exec: %s\n' % (util.checkexec(path) and b'yes' or b'no'))
1322 1324 ui.writenoi18n(b'fstype: %s\n' % (util.getfstype(path) or b'(unknown)'))
1323 1325 ui.writenoi18n(
1324 1326 b'symlink: %s\n' % (util.checklink(path) and b'yes' or b'no')
1325 1327 )
1326 1328 ui.writenoi18n(
1327 1329 b'hardlink: %s\n' % (util.checknlink(path) and b'yes' or b'no')
1328 1330 )
1329 1331 casesensitive = b'(unknown)'
1330 1332 try:
1331 1333 with pycompat.namedtempfile(prefix=b'.debugfsinfo', dir=path) as f:
1332 1334 casesensitive = util.fscasesensitive(f.name) and b'yes' or b'no'
1333 1335 except OSError:
1334 1336 pass
1335 1337 ui.writenoi18n(b'case-sensitive: %s\n' % casesensitive)
1336 1338
1337 1339
1338 1340 @command(
1339 1341 b'debuggetbundle',
1340 1342 [
1341 1343 (b'H', b'head', [], _(b'id of head node'), _(b'ID')),
1342 1344 (b'C', b'common', [], _(b'id of common node'), _(b'ID')),
1343 1345 (
1344 1346 b't',
1345 1347 b'type',
1346 1348 b'bzip2',
1347 1349 _(b'bundle compression type to use'),
1348 1350 _(b'TYPE'),
1349 1351 ),
1350 1352 ],
1351 1353 _(b'REPO FILE [-H|-C ID]...'),
1352 1354 norepo=True,
1353 1355 )
1354 1356 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1355 1357 """retrieves a bundle from a repo
1356 1358
1357 1359 Every ID must be a full-length hex node id string. Saves the bundle to the
1358 1360 given file.
1359 1361 """
1360 1362 opts = pycompat.byteskwargs(opts)
1361 1363 repo = hg.peer(ui, opts, repopath)
1362 1364 if not repo.capable(b'getbundle'):
1363 1365 raise error.Abort(b"getbundle() not supported by target repository")
1364 1366 args = {}
1365 1367 if common:
1366 1368 args['common'] = [bin(s) for s in common]
1367 1369 if head:
1368 1370 args['heads'] = [bin(s) for s in head]
1369 1371 # TODO: get desired bundlecaps from command line.
1370 1372 args['bundlecaps'] = None
1371 1373 bundle = repo.getbundle(b'debug', **args)
1372 1374
1373 1375 bundletype = opts.get(b'type', b'bzip2').lower()
1374 1376 btypes = {
1375 1377 b'none': b'HG10UN',
1376 1378 b'bzip2': b'HG10BZ',
1377 1379 b'gzip': b'HG10GZ',
1378 1380 b'bundle2': b'HG20',
1379 1381 }
1380 1382 bundletype = btypes.get(bundletype)
1381 1383 if bundletype not in bundle2.bundletypes:
1382 1384 raise error.Abort(_(b'unknown bundle type specified with --type'))
1383 1385 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1384 1386
1385 1387
1386 1388 @command(b'debugignore', [], b'[FILE]')
1387 1389 def debugignore(ui, repo, *files, **opts):
1388 1390 """display the combined ignore pattern and information about ignored files
1389 1391
1390 1392 With no argument display the combined ignore pattern.
1391 1393
1392 1394 Given space separated file names, shows if the given file is ignored and
1393 1395 if so, show the ignore rule (file and line number) that matched it.
1394 1396 """
1395 1397 ignore = repo.dirstate._ignore
1396 1398 if not files:
1397 1399 # Show all the patterns
1398 1400 ui.write(b"%s\n" % pycompat.byterepr(ignore))
1399 1401 else:
1400 1402 m = scmutil.match(repo[None], pats=files)
1401 1403 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1402 1404 for f in m.files():
1403 1405 nf = util.normpath(f)
1404 1406 ignored = None
1405 1407 ignoredata = None
1406 1408 if nf != b'.':
1407 1409 if ignore(nf):
1408 1410 ignored = nf
1409 1411 ignoredata = repo.dirstate._ignorefileandline(nf)
1410 1412 else:
1411 1413 for p in pathutil.finddirs(nf):
1412 1414 if ignore(p):
1413 1415 ignored = p
1414 1416 ignoredata = repo.dirstate._ignorefileandline(p)
1415 1417 break
1416 1418 if ignored:
1417 1419 if ignored == nf:
1418 1420 ui.write(_(b"%s is ignored\n") % uipathfn(f))
1419 1421 else:
1420 1422 ui.write(
1421 1423 _(
1422 1424 b"%s is ignored because of "
1423 1425 b"containing directory %s\n"
1424 1426 )
1425 1427 % (uipathfn(f), ignored)
1426 1428 )
1427 1429 ignorefile, lineno, line = ignoredata
1428 1430 ui.write(
1429 1431 _(b"(ignore rule in %s, line %d: '%s')\n")
1430 1432 % (ignorefile, lineno, line)
1431 1433 )
1432 1434 else:
1433 1435 ui.write(_(b"%s is not ignored\n") % uipathfn(f))
1434 1436
1435 1437
1436 1438 @command(
1437 1439 b'debugindex',
1438 1440 cmdutil.debugrevlogopts + cmdutil.formatteropts,
1439 1441 _(b'-c|-m|FILE'),
1440 1442 )
1441 1443 def debugindex(ui, repo, file_=None, **opts):
1442 1444 """dump index data for a storage primitive"""
1443 1445 opts = pycompat.byteskwargs(opts)
1444 1446 store = cmdutil.openstorage(repo, b'debugindex', file_, opts)
1445 1447
1446 1448 if ui.debugflag:
1447 1449 shortfn = hex
1448 1450 else:
1449 1451 shortfn = short
1450 1452
1451 1453 idlen = 12
1452 1454 for i in store:
1453 1455 idlen = len(shortfn(store.node(i)))
1454 1456 break
1455 1457
1456 1458 fm = ui.formatter(b'debugindex', opts)
1457 1459 fm.plain(
1458 1460 b' rev linkrev %s %s p2\n'
1459 1461 % (b'nodeid'.ljust(idlen), b'p1'.ljust(idlen))
1460 1462 )
1461 1463
1462 1464 for rev in store:
1463 1465 node = store.node(rev)
1464 1466 parents = store.parents(node)
1465 1467
1466 1468 fm.startitem()
1467 1469 fm.write(b'rev', b'%6d ', rev)
1468 1470 fm.write(b'linkrev', b'%7d ', store.linkrev(rev))
1469 1471 fm.write(b'node', b'%s ', shortfn(node))
1470 1472 fm.write(b'p1', b'%s ', shortfn(parents[0]))
1471 1473 fm.write(b'p2', b'%s', shortfn(parents[1]))
1472 1474 fm.plain(b'\n')
1473 1475
1474 1476 fm.end()
1475 1477
1476 1478
1477 1479 @command(
1478 1480 b'debugindexdot',
1479 1481 cmdutil.debugrevlogopts,
1480 1482 _(b'-c|-m|FILE'),
1481 1483 optionalrepo=True,
1482 1484 )
1483 1485 def debugindexdot(ui, repo, file_=None, **opts):
1484 1486 """dump an index DAG as a graphviz dot file"""
1485 1487 opts = pycompat.byteskwargs(opts)
1486 1488 r = cmdutil.openstorage(repo, b'debugindexdot', file_, opts)
1487 1489 ui.writenoi18n(b"digraph G {\n")
1488 1490 for i in r:
1489 1491 node = r.node(i)
1490 1492 pp = r.parents(node)
1491 1493 ui.write(b"\t%d -> %d\n" % (r.rev(pp[0]), i))
1492 1494 if pp[1] != nullid:
1493 1495 ui.write(b"\t%d -> %d\n" % (r.rev(pp[1]), i))
1494 1496 ui.write(b"}\n")
1495 1497
1496 1498
1497 1499 @command(b'debugindexstats', [])
1498 1500 def debugindexstats(ui, repo):
1499 1501 """show stats related to the changelog index"""
1500 1502 repo.changelog.shortest(nullid, 1)
1501 1503 index = repo.changelog.index
1502 1504 if not util.safehasattr(index, b'stats'):
1503 1505 raise error.Abort(_(b'debugindexstats only works with native code'))
1504 1506 for k, v in sorted(index.stats().items()):
1505 1507 ui.write(b'%s: %d\n' % (k, v))
1506 1508
1507 1509
1508 1510 @command(b'debuginstall', [] + cmdutil.formatteropts, b'', norepo=True)
1509 1511 def debuginstall(ui, **opts):
1510 1512 '''test Mercurial installation
1511 1513
1512 1514 Returns 0 on success.
1513 1515 '''
1514 1516 opts = pycompat.byteskwargs(opts)
1515 1517
1516 1518 problems = 0
1517 1519
1518 1520 fm = ui.formatter(b'debuginstall', opts)
1519 1521 fm.startitem()
1520 1522
1521 1523 # encoding might be unknown or wrong. don't translate these messages.
1522 1524 fm.write(b'encoding', b"checking encoding (%s)...\n", encoding.encoding)
1523 1525 err = None
1524 1526 try:
1525 1527 codecs.lookup(pycompat.sysstr(encoding.encoding))
1526 1528 except LookupError as inst:
1527 1529 err = stringutil.forcebytestr(inst)
1528 1530 problems += 1
1529 1531 fm.condwrite(
1530 1532 err,
1531 1533 b'encodingerror',
1532 1534 b" %s\n (check that your locale is properly set)\n",
1533 1535 err,
1534 1536 )
1535 1537
1536 1538 # Python
1537 1539 pythonlib = None
1538 1540 if util.safehasattr(os, '__file__'):
1539 1541 pythonlib = os.path.dirname(pycompat.fsencode(os.__file__))
1540 1542 elif getattr(sys, 'oxidized', False):
1541 1543 pythonlib = pycompat.sysexecutable
1542 1544
1543 1545 fm.write(
1544 1546 b'pythonexe',
1545 1547 _(b"checking Python executable (%s)\n"),
1546 1548 pycompat.sysexecutable or _(b"unknown"),
1547 1549 )
1548 1550 fm.write(
1549 1551 b'pythonimplementation',
1550 1552 _(b"checking Python implementation (%s)\n"),
1551 1553 pycompat.sysbytes(platform.python_implementation()),
1552 1554 )
1553 1555 fm.write(
1554 1556 b'pythonver',
1555 1557 _(b"checking Python version (%s)\n"),
1556 1558 (b"%d.%d.%d" % sys.version_info[:3]),
1557 1559 )
1558 1560 fm.write(
1559 1561 b'pythonlib',
1560 1562 _(b"checking Python lib (%s)...\n"),
1561 1563 pythonlib or _(b"unknown"),
1562 1564 )
1563 1565
1564 1566 try:
1565 1567 from . import rustext
1566 1568
1567 1569 rustext.__doc__ # trigger lazy import
1568 1570 except ImportError:
1569 1571 rustext = None
1570 1572
1571 1573 security = set(sslutil.supportedprotocols)
1572 1574 if sslutil.hassni:
1573 1575 security.add(b'sni')
1574 1576
1575 1577 fm.write(
1576 1578 b'pythonsecurity',
1577 1579 _(b"checking Python security support (%s)\n"),
1578 1580 fm.formatlist(sorted(security), name=b'protocol', fmt=b'%s', sep=b','),
1579 1581 )
1580 1582
1581 1583 # These are warnings, not errors. So don't increment problem count. This
1582 1584 # may change in the future.
1583 1585 if b'tls1.2' not in security:
1584 1586 fm.plain(
1585 1587 _(
1586 1588 b' TLS 1.2 not supported by Python install; '
1587 1589 b'network connections lack modern security\n'
1588 1590 )
1589 1591 )
1590 1592 if b'sni' not in security:
1591 1593 fm.plain(
1592 1594 _(
1593 1595 b' SNI not supported by Python install; may have '
1594 1596 b'connectivity issues with some servers\n'
1595 1597 )
1596 1598 )
1597 1599
1598 1600 fm.plain(
1599 1601 _(
1600 1602 b"checking Rust extensions (%s)\n"
1601 1603 % (b'missing' if rustext is None else b'installed')
1602 1604 ),
1603 1605 )
1604 1606
1605 1607 # TODO print CA cert info
1606 1608
1607 1609 # hg version
1608 1610 hgver = util.version()
1609 1611 fm.write(
1610 1612 b'hgver', _(b"checking Mercurial version (%s)\n"), hgver.split(b'+')[0]
1611 1613 )
1612 1614 fm.write(
1613 1615 b'hgverextra',
1614 1616 _(b"checking Mercurial custom build (%s)\n"),
1615 1617 b'+'.join(hgver.split(b'+')[1:]),
1616 1618 )
1617 1619
1618 1620 # compiled modules
1619 1621 hgmodules = None
1620 1622 if util.safehasattr(sys.modules[__name__], '__file__'):
1621 1623 hgmodules = os.path.dirname(pycompat.fsencode(__file__))
1622 1624 elif getattr(sys, 'oxidized', False):
1623 1625 hgmodules = pycompat.sysexecutable
1624 1626
1625 1627 fm.write(
1626 1628 b'hgmodulepolicy', _(b"checking module policy (%s)\n"), policy.policy
1627 1629 )
1628 1630 fm.write(
1629 1631 b'hgmodules',
1630 1632 _(b"checking installed modules (%s)...\n"),
1631 1633 hgmodules or _(b"unknown"),
1632 1634 )
1633 1635
1634 1636 rustandc = policy.policy in (b'rust+c', b'rust+c-allow')
1635 1637 rustext = rustandc # for now, that's the only case
1636 1638 cext = policy.policy in (b'c', b'allow') or rustandc
1637 1639 nopure = cext or rustext
1638 1640 if nopure:
1639 1641 err = None
1640 1642 try:
1641 1643 if cext:
1642 1644 from .cext import ( # pytype: disable=import-error
1643 1645 base85,
1644 1646 bdiff,
1645 1647 mpatch,
1646 1648 osutil,
1647 1649 )
1648 1650
1649 1651 # quiet pyflakes
1650 1652 dir(bdiff), dir(mpatch), dir(base85), dir(osutil)
1651 1653 if rustext:
1652 1654 from .rustext import ( # pytype: disable=import-error
1653 1655 ancestor,
1654 1656 dirstate,
1655 1657 )
1656 1658
1657 1659 dir(ancestor), dir(dirstate) # quiet pyflakes
1658 1660 except Exception as inst:
1659 1661 err = stringutil.forcebytestr(inst)
1660 1662 problems += 1
1661 1663 fm.condwrite(err, b'extensionserror', b" %s\n", err)
1662 1664
1663 1665 compengines = util.compengines._engines.values()
1664 1666 fm.write(
1665 1667 b'compengines',
1666 1668 _(b'checking registered compression engines (%s)\n'),
1667 1669 fm.formatlist(
1668 1670 sorted(e.name() for e in compengines),
1669 1671 name=b'compengine',
1670 1672 fmt=b'%s',
1671 1673 sep=b', ',
1672 1674 ),
1673 1675 )
1674 1676 fm.write(
1675 1677 b'compenginesavail',
1676 1678 _(b'checking available compression engines (%s)\n'),
1677 1679 fm.formatlist(
1678 1680 sorted(e.name() for e in compengines if e.available()),
1679 1681 name=b'compengine',
1680 1682 fmt=b'%s',
1681 1683 sep=b', ',
1682 1684 ),
1683 1685 )
1684 1686 wirecompengines = compression.compengines.supportedwireengines(
1685 1687 compression.SERVERROLE
1686 1688 )
1687 1689 fm.write(
1688 1690 b'compenginesserver',
1689 1691 _(
1690 1692 b'checking available compression engines '
1691 1693 b'for wire protocol (%s)\n'
1692 1694 ),
1693 1695 fm.formatlist(
1694 1696 [e.name() for e in wirecompengines if e.wireprotosupport()],
1695 1697 name=b'compengine',
1696 1698 fmt=b'%s',
1697 1699 sep=b', ',
1698 1700 ),
1699 1701 )
1700 1702 re2 = b'missing'
1701 1703 if util._re2:
1702 1704 re2 = b'available'
1703 1705 fm.plain(_(b'checking "re2" regexp engine (%s)\n') % re2)
1704 1706 fm.data(re2=bool(util._re2))
1705 1707
1706 1708 # templates
1707 1709 p = templater.templatedir()
1708 1710 fm.write(b'templatedirs', b'checking templates (%s)...\n', p or b'')
1709 1711 fm.condwrite(not p, b'', _(b" no template directories found\n"))
1710 1712 if p:
1711 1713 (m, fp) = templater.try_open_template(b"map-cmdline.default")
1712 1714 if m:
1713 1715 # template found, check if it is working
1714 1716 err = None
1715 1717 try:
1716 1718 templater.templater.frommapfile(m)
1717 1719 except Exception as inst:
1718 1720 err = stringutil.forcebytestr(inst)
1719 1721 p = None
1720 1722 fm.condwrite(err, b'defaulttemplateerror', b" %s\n", err)
1721 1723 else:
1722 1724 p = None
1723 1725 fm.condwrite(
1724 1726 p, b'defaulttemplate', _(b"checking default template (%s)\n"), m
1725 1727 )
1726 1728 fm.condwrite(
1727 1729 not m,
1728 1730 b'defaulttemplatenotfound',
1729 1731 _(b" template '%s' not found\n"),
1730 1732 b"default",
1731 1733 )
1732 1734 if not p:
1733 1735 problems += 1
1734 1736 fm.condwrite(
1735 1737 not p, b'', _(b" (templates seem to have been installed incorrectly)\n")
1736 1738 )
1737 1739
1738 1740 # editor
1739 1741 editor = ui.geteditor()
1740 1742 editor = util.expandpath(editor)
1741 1743 editorbin = procutil.shellsplit(editor)[0]
1742 1744 fm.write(b'editor', _(b"checking commit editor... (%s)\n"), editorbin)
1743 1745 cmdpath = procutil.findexe(editorbin)
1744 1746 fm.condwrite(
1745 1747 not cmdpath and editor == b'vi',
1746 1748 b'vinotfound',
1747 1749 _(
1748 1750 b" No commit editor set and can't find %s in PATH\n"
1749 1751 b" (specify a commit editor in your configuration"
1750 1752 b" file)\n"
1751 1753 ),
1752 1754 not cmdpath and editor == b'vi' and editorbin,
1753 1755 )
1754 1756 fm.condwrite(
1755 1757 not cmdpath and editor != b'vi',
1756 1758 b'editornotfound',
1757 1759 _(
1758 1760 b" Can't find editor '%s' in PATH\n"
1759 1761 b" (specify a commit editor in your configuration"
1760 1762 b" file)\n"
1761 1763 ),
1762 1764 not cmdpath and editorbin,
1763 1765 )
1764 1766 if not cmdpath and editor != b'vi':
1765 1767 problems += 1
1766 1768
1767 1769 # check username
1768 1770 username = None
1769 1771 err = None
1770 1772 try:
1771 1773 username = ui.username()
1772 1774 except error.Abort as e:
1773 1775 err = e.message
1774 1776 problems += 1
1775 1777
1776 1778 fm.condwrite(
1777 1779 username, b'username', _(b"checking username (%s)\n"), username
1778 1780 )
1779 1781 fm.condwrite(
1780 1782 err,
1781 1783 b'usernameerror',
1782 1784 _(
1783 1785 b"checking username...\n %s\n"
1784 1786 b" (specify a username in your configuration file)\n"
1785 1787 ),
1786 1788 err,
1787 1789 )
1788 1790
1789 1791 for name, mod in extensions.extensions():
1790 1792 handler = getattr(mod, 'debuginstall', None)
1791 1793 if handler is not None:
1792 1794 problems += handler(ui, fm)
1793 1795
1794 1796 fm.condwrite(not problems, b'', _(b"no problems detected\n"))
1795 1797 if not problems:
1796 1798 fm.data(problems=problems)
1797 1799 fm.condwrite(
1798 1800 problems,
1799 1801 b'problems',
1800 1802 _(b"%d problems detected, please check your install!\n"),
1801 1803 problems,
1802 1804 )
1803 1805 fm.end()
1804 1806
1805 1807 return problems
1806 1808
1807 1809
1808 1810 @command(b'debugknown', [], _(b'REPO ID...'), norepo=True)
1809 1811 def debugknown(ui, repopath, *ids, **opts):
1810 1812 """test whether node ids are known to a repo
1811 1813
1812 1814 Every ID must be a full-length hex node id string. Returns a list of 0s
1813 1815 and 1s indicating unknown/known.
1814 1816 """
1815 1817 opts = pycompat.byteskwargs(opts)
1816 1818 repo = hg.peer(ui, opts, repopath)
1817 1819 if not repo.capable(b'known'):
1818 1820 raise error.Abort(b"known() not supported by target repository")
1819 1821 flags = repo.known([bin(s) for s in ids])
1820 1822 ui.write(b"%s\n" % (b"".join([f and b"1" or b"0" for f in flags])))
1821 1823
1822 1824
1823 1825 @command(b'debuglabelcomplete', [], _(b'LABEL...'))
1824 1826 def debuglabelcomplete(ui, repo, *args):
1825 1827 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1826 1828 debugnamecomplete(ui, repo, *args)
1827 1829
1828 1830
1829 1831 @command(
1830 1832 b'debuglocks',
1831 1833 [
1832 1834 (b'L', b'force-lock', None, _(b'free the store lock (DANGEROUS)')),
1833 1835 (
1834 1836 b'W',
1835 1837 b'force-wlock',
1836 1838 None,
1837 1839 _(b'free the working state lock (DANGEROUS)'),
1838 1840 ),
1839 1841 (b's', b'set-lock', None, _(b'set the store lock until stopped')),
1840 1842 (
1841 1843 b'S',
1842 1844 b'set-wlock',
1843 1845 None,
1844 1846 _(b'set the working state lock until stopped'),
1845 1847 ),
1846 1848 ],
1847 1849 _(b'[OPTION]...'),
1848 1850 )
1849 1851 def debuglocks(ui, repo, **opts):
1850 1852 """show or modify state of locks
1851 1853
1852 1854 By default, this command will show which locks are held. This
1853 1855 includes the user and process holding the lock, the amount of time
1854 1856 the lock has been held, and the machine name where the process is
1855 1857 running if it's not local.
1856 1858
1857 1859 Locks protect the integrity of Mercurial's data, so should be
1858 1860 treated with care. System crashes or other interruptions may cause
1859 1861 locks to not be properly released, though Mercurial will usually
1860 1862 detect and remove such stale locks automatically.
1861 1863
1862 1864 However, detecting stale locks may not always be possible (for
1863 1865 instance, on a shared filesystem). Removing locks may also be
1864 1866 blocked by filesystem permissions.
1865 1867
1866 1868 Setting a lock will prevent other commands from changing the data.
1867 1869 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
1868 1870 The set locks are removed when the command exits.
1869 1871
1870 1872 Returns 0 if no locks are held.
1871 1873
1872 1874 """
1873 1875
1874 1876 if opts.get('force_lock'):
1875 1877 repo.svfs.unlink(b'lock')
1876 1878 if opts.get('force_wlock'):
1877 1879 repo.vfs.unlink(b'wlock')
1878 1880 if opts.get('force_lock') or opts.get('force_wlock'):
1879 1881 return 0
1880 1882
1881 1883 locks = []
1882 1884 try:
1883 1885 if opts.get('set_wlock'):
1884 1886 try:
1885 1887 locks.append(repo.wlock(False))
1886 1888 except error.LockHeld:
1887 1889 raise error.Abort(_(b'wlock is already held'))
1888 1890 if opts.get('set_lock'):
1889 1891 try:
1890 1892 locks.append(repo.lock(False))
1891 1893 except error.LockHeld:
1892 1894 raise error.Abort(_(b'lock is already held'))
1893 1895 if len(locks):
1894 1896 ui.promptchoice(_(b"ready to release the lock (y)? $$ &Yes"))
1895 1897 return 0
1896 1898 finally:
1897 1899 release(*locks)
1898 1900
1899 1901 now = time.time()
1900 1902 held = 0
1901 1903
1902 1904 def report(vfs, name, method):
1903 1905 # this causes stale locks to get reaped for more accurate reporting
1904 1906 try:
1905 1907 l = method(False)
1906 1908 except error.LockHeld:
1907 1909 l = None
1908 1910
1909 1911 if l:
1910 1912 l.release()
1911 1913 else:
1912 1914 try:
1913 1915 st = vfs.lstat(name)
1914 1916 age = now - st[stat.ST_MTIME]
1915 1917 user = util.username(st.st_uid)
1916 1918 locker = vfs.readlock(name)
1917 1919 if b":" in locker:
1918 1920 host, pid = locker.split(b':')
1919 1921 if host == socket.gethostname():
1920 1922 locker = b'user %s, process %s' % (user or b'None', pid)
1921 1923 else:
1922 1924 locker = b'user %s, process %s, host %s' % (
1923 1925 user or b'None',
1924 1926 pid,
1925 1927 host,
1926 1928 )
1927 1929 ui.writenoi18n(b"%-6s %s (%ds)\n" % (name + b":", locker, age))
1928 1930 return 1
1929 1931 except OSError as e:
1930 1932 if e.errno != errno.ENOENT:
1931 1933 raise
1932 1934
1933 1935 ui.writenoi18n(b"%-6s free\n" % (name + b":"))
1934 1936 return 0
1935 1937
1936 1938 held += report(repo.svfs, b"lock", repo.lock)
1937 1939 held += report(repo.vfs, b"wlock", repo.wlock)
1938 1940
1939 1941 return held
1940 1942
1941 1943
1942 1944 @command(
1943 1945 b'debugmanifestfulltextcache',
1944 1946 [
1945 1947 (b'', b'clear', False, _(b'clear the cache')),
1946 1948 (
1947 1949 b'a',
1948 1950 b'add',
1949 1951 [],
1950 1952 _(b'add the given manifest nodes to the cache'),
1951 1953 _(b'NODE'),
1952 1954 ),
1953 1955 ],
1954 1956 b'',
1955 1957 )
1956 1958 def debugmanifestfulltextcache(ui, repo, add=(), **opts):
1957 1959 """show, clear or amend the contents of the manifest fulltext cache"""
1958 1960
1959 1961 def getcache():
1960 1962 r = repo.manifestlog.getstorage(b'')
1961 1963 try:
1962 1964 return r._fulltextcache
1963 1965 except AttributeError:
1964 1966 msg = _(
1965 1967 b"Current revlog implementation doesn't appear to have a "
1966 1968 b"manifest fulltext cache\n"
1967 1969 )
1968 1970 raise error.Abort(msg)
1969 1971
1970 1972 if opts.get('clear'):
1971 1973 with repo.wlock():
1972 1974 cache = getcache()
1973 1975 cache.clear(clear_persisted_data=True)
1974 1976 return
1975 1977
1976 1978 if add:
1977 1979 with repo.wlock():
1978 1980 m = repo.manifestlog
1979 1981 store = m.getstorage(b'')
1980 1982 for n in add:
1981 1983 try:
1982 1984 manifest = m[store.lookup(n)]
1983 1985 except error.LookupError as e:
1984 1986 raise error.Abort(e, hint=b"Check your manifest node id")
1985 1987 manifest.read() # stores revisision in cache too
1986 1988 return
1987 1989
1988 1990 cache = getcache()
1989 1991 if not len(cache):
1990 1992 ui.write(_(b'cache empty\n'))
1991 1993 else:
1992 1994 ui.write(
1993 1995 _(
1994 1996 b'cache contains %d manifest entries, in order of most to '
1995 1997 b'least recent:\n'
1996 1998 )
1997 1999 % (len(cache),)
1998 2000 )
1999 2001 totalsize = 0
2000 2002 for nodeid in cache:
2001 2003 # Use cache.get to not update the LRU order
2002 2004 data = cache.peek(nodeid)
2003 2005 size = len(data)
2004 2006 totalsize += size + 24 # 20 bytes nodeid, 4 bytes size
2005 2007 ui.write(
2006 2008 _(b'id: %s, size %s\n') % (hex(nodeid), util.bytecount(size))
2007 2009 )
2008 2010 ondisk = cache._opener.stat(b'manifestfulltextcache').st_size
2009 2011 ui.write(
2010 2012 _(b'total cache data size %s, on-disk %s\n')
2011 2013 % (util.bytecount(totalsize), util.bytecount(ondisk))
2012 2014 )
2013 2015
2014 2016
2015 2017 @command(b'debugmergestate', [] + cmdutil.templateopts, b'')
2016 2018 def debugmergestate(ui, repo, *args, **opts):
2017 2019 """print merge state
2018 2020
2019 2021 Use --verbose to print out information about whether v1 or v2 merge state
2020 2022 was chosen."""
2021 2023
2022 2024 if ui.verbose:
2023 2025 ms = mergestatemod.mergestate(repo)
2024 2026
2025 2027 # sort so that reasonable information is on top
2026 2028 v1records = ms._readrecordsv1()
2027 2029 v2records = ms._readrecordsv2()
2028 2030
2029 2031 if not v1records and not v2records:
2030 2032 pass
2031 2033 elif not v2records:
2032 2034 ui.writenoi18n(b'no version 2 merge state\n')
2033 2035 elif ms._v1v2match(v1records, v2records):
2034 2036 ui.writenoi18n(b'v1 and v2 states match: using v2\n')
2035 2037 else:
2036 2038 ui.writenoi18n(b'v1 and v2 states mismatch: using v1\n')
2037 2039
2038 2040 opts = pycompat.byteskwargs(opts)
2039 2041 if not opts[b'template']:
2040 2042 opts[b'template'] = (
2041 2043 b'{if(commits, "", "no merge state found\n")}'
2042 2044 b'{commits % "{name}{if(label, " ({label})")}: {node}\n"}'
2043 2045 b'{files % "file: {path} (state \\"{state}\\")\n'
2044 2046 b'{if(local_path, "'
2045 2047 b' local path: {local_path} (hash {local_key}, flags \\"{local_flags}\\")\n'
2046 2048 b' ancestor path: {ancestor_path} (node {ancestor_node})\n'
2047 2049 b' other path: {other_path} (node {other_node})\n'
2048 2050 b'")}'
2049 2051 b'{if(rename_side, "'
2050 2052 b' rename side: {rename_side}\n'
2051 2053 b' renamed path: {renamed_path}\n'
2052 2054 b'")}'
2053 2055 b'{extras % " extra: {key} = {value}\n"}'
2054 2056 b'"}'
2055 2057 b'{extras % "extra: {file} ({key} = {value})\n"}'
2056 2058 )
2057 2059
2058 2060 ms = mergestatemod.mergestate.read(repo)
2059 2061
2060 2062 fm = ui.formatter(b'debugmergestate', opts)
2061 2063 fm.startitem()
2062 2064
2063 2065 fm_commits = fm.nested(b'commits')
2064 2066 if ms.active():
2065 2067 for name, node, label_index in (
2066 2068 (b'local', ms.local, 0),
2067 2069 (b'other', ms.other, 1),
2068 2070 ):
2069 2071 fm_commits.startitem()
2070 2072 fm_commits.data(name=name)
2071 2073 fm_commits.data(node=hex(node))
2072 2074 if ms._labels and len(ms._labels) > label_index:
2073 2075 fm_commits.data(label=ms._labels[label_index])
2074 2076 fm_commits.end()
2075 2077
2076 2078 fm_files = fm.nested(b'files')
2077 2079 if ms.active():
2078 2080 for f in ms:
2079 2081 fm_files.startitem()
2080 2082 fm_files.data(path=f)
2081 2083 state = ms._state[f]
2082 2084 fm_files.data(state=state[0])
2083 2085 if state[0] in (
2084 2086 mergestatemod.MERGE_RECORD_UNRESOLVED,
2085 2087 mergestatemod.MERGE_RECORD_RESOLVED,
2086 2088 ):
2087 2089 fm_files.data(local_key=state[1])
2088 2090 fm_files.data(local_path=state[2])
2089 2091 fm_files.data(ancestor_path=state[3])
2090 2092 fm_files.data(ancestor_node=state[4])
2091 2093 fm_files.data(other_path=state[5])
2092 2094 fm_files.data(other_node=state[6])
2093 2095 fm_files.data(local_flags=state[7])
2094 2096 elif state[0] in (
2095 2097 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
2096 2098 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
2097 2099 ):
2098 2100 fm_files.data(renamed_path=state[1])
2099 2101 fm_files.data(rename_side=state[2])
2100 2102 fm_extras = fm_files.nested(b'extras')
2101 2103 for k, v in sorted(ms.extras(f).items()):
2102 2104 fm_extras.startitem()
2103 2105 fm_extras.data(key=k)
2104 2106 fm_extras.data(value=v)
2105 2107 fm_extras.end()
2106 2108
2107 2109 fm_files.end()
2108 2110
2109 2111 fm_extras = fm.nested(b'extras')
2110 2112 for f, d in sorted(pycompat.iteritems(ms.allextras())):
2111 2113 if f in ms:
2112 2114 # If file is in mergestate, we have already processed it's extras
2113 2115 continue
2114 2116 for k, v in pycompat.iteritems(d):
2115 2117 fm_extras.startitem()
2116 2118 fm_extras.data(file=f)
2117 2119 fm_extras.data(key=k)
2118 2120 fm_extras.data(value=v)
2119 2121 fm_extras.end()
2120 2122
2121 2123 fm.end()
2122 2124
2123 2125
2124 2126 @command(b'debugnamecomplete', [], _(b'NAME...'))
2125 2127 def debugnamecomplete(ui, repo, *args):
2126 2128 '''complete "names" - tags, open branch names, bookmark names'''
2127 2129
2128 2130 names = set()
2129 2131 # since we previously only listed open branches, we will handle that
2130 2132 # specially (after this for loop)
2131 2133 for name, ns in pycompat.iteritems(repo.names):
2132 2134 if name != b'branches':
2133 2135 names.update(ns.listnames(repo))
2134 2136 names.update(
2135 2137 tag
2136 2138 for (tag, heads, tip, closed) in repo.branchmap().iterbranches()
2137 2139 if not closed
2138 2140 )
2139 2141 completions = set()
2140 2142 if not args:
2141 2143 args = [b'']
2142 2144 for a in args:
2143 2145 completions.update(n for n in names if n.startswith(a))
2144 2146 ui.write(b'\n'.join(sorted(completions)))
2145 2147 ui.write(b'\n')
2146 2148
2147 2149
2148 2150 @command(
2149 2151 b'debugnodemap',
2150 2152 [
2151 2153 (
2152 2154 b'',
2153 2155 b'dump-new',
2154 2156 False,
2155 2157 _(b'write a (new) persistent binary nodemap on stdin'),
2156 2158 ),
2157 2159 (b'', b'dump-disk', False, _(b'dump on-disk data on stdin')),
2158 2160 (
2159 2161 b'',
2160 2162 b'check',
2161 2163 False,
2162 2164 _(b'check that the data on disk data are correct.'),
2163 2165 ),
2164 2166 (
2165 2167 b'',
2166 2168 b'metadata',
2167 2169 False,
2168 2170 _(b'display the on disk meta data for the nodemap'),
2169 2171 ),
2170 2172 ],
2171 2173 )
2172 2174 def debugnodemap(ui, repo, **opts):
2173 2175 """write and inspect on disk nodemap
2174 2176 """
2175 2177 if opts['dump_new']:
2176 2178 unfi = repo.unfiltered()
2177 2179 cl = unfi.changelog
2178 2180 if util.safehasattr(cl.index, "nodemap_data_all"):
2179 2181 data = cl.index.nodemap_data_all()
2180 2182 else:
2181 2183 data = nodemap.persistent_data(cl.index)
2182 2184 ui.write(data)
2183 2185 elif opts['dump_disk']:
2184 2186 unfi = repo.unfiltered()
2185 2187 cl = unfi.changelog
2186 2188 nm_data = nodemap.persisted_data(cl)
2187 2189 if nm_data is not None:
2188 2190 docket, data = nm_data
2189 2191 ui.write(data[:])
2190 2192 elif opts['check']:
2191 2193 unfi = repo.unfiltered()
2192 2194 cl = unfi.changelog
2193 2195 nm_data = nodemap.persisted_data(cl)
2194 2196 if nm_data is not None:
2195 2197 docket, data = nm_data
2196 2198 return nodemap.check_data(ui, cl.index, data)
2197 2199 elif opts['metadata']:
2198 2200 unfi = repo.unfiltered()
2199 2201 cl = unfi.changelog
2200 2202 nm_data = nodemap.persisted_data(cl)
2201 2203 if nm_data is not None:
2202 2204 docket, data = nm_data
2203 2205 ui.write((b"uid: %s\n") % docket.uid)
2204 2206 ui.write((b"tip-rev: %d\n") % docket.tip_rev)
2205 2207 ui.write((b"tip-node: %s\n") % hex(docket.tip_node))
2206 2208 ui.write((b"data-length: %d\n") % docket.data_length)
2207 2209 ui.write((b"data-unused: %d\n") % docket.data_unused)
2208 2210 unused_perc = docket.data_unused * 100.0 / docket.data_length
2209 2211 ui.write((b"data-unused: %2.3f%%\n") % unused_perc)
2210 2212
2211 2213
2212 2214 @command(
2213 2215 b'debugobsolete',
2214 2216 [
2215 2217 (b'', b'flags', 0, _(b'markers flag')),
2216 2218 (
2217 2219 b'',
2218 2220 b'record-parents',
2219 2221 False,
2220 2222 _(b'record parent information for the precursor'),
2221 2223 ),
2222 2224 (b'r', b'rev', [], _(b'display markers relevant to REV')),
2223 2225 (
2224 2226 b'',
2225 2227 b'exclusive',
2226 2228 False,
2227 2229 _(b'restrict display to markers only relevant to REV'),
2228 2230 ),
2229 2231 (b'', b'index', False, _(b'display index of the marker')),
2230 2232 (b'', b'delete', [], _(b'delete markers specified by indices')),
2231 2233 ]
2232 2234 + cmdutil.commitopts2
2233 2235 + cmdutil.formatteropts,
2234 2236 _(b'[OBSOLETED [REPLACEMENT ...]]'),
2235 2237 )
2236 2238 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2237 2239 """create arbitrary obsolete marker
2238 2240
2239 2241 With no arguments, displays the list of obsolescence markers."""
2240 2242
2241 2243 opts = pycompat.byteskwargs(opts)
2242 2244
2243 2245 def parsenodeid(s):
2244 2246 try:
2245 2247 # We do not use revsingle/revrange functions here to accept
2246 2248 # arbitrary node identifiers, possibly not present in the
2247 2249 # local repository.
2248 2250 n = bin(s)
2249 2251 if len(n) != len(nullid):
2250 2252 raise TypeError()
2251 2253 return n
2252 2254 except TypeError:
2253 2255 raise error.Abort(
2254 2256 b'changeset references must be full hexadecimal '
2255 2257 b'node identifiers'
2256 2258 )
2257 2259
2258 2260 if opts.get(b'delete'):
2259 2261 indices = []
2260 2262 for v in opts.get(b'delete'):
2261 2263 try:
2262 2264 indices.append(int(v))
2263 2265 except ValueError:
2264 2266 raise error.Abort(
2265 2267 _(b'invalid index value: %r') % v,
2266 2268 hint=_(b'use integers for indices'),
2267 2269 )
2268 2270
2269 2271 if repo.currenttransaction():
2270 2272 raise error.Abort(
2271 2273 _(b'cannot delete obsmarkers in the middle of transaction.')
2272 2274 )
2273 2275
2274 2276 with repo.lock():
2275 2277 n = repair.deleteobsmarkers(repo.obsstore, indices)
2276 2278 ui.write(_(b'deleted %i obsolescence markers\n') % n)
2277 2279
2278 2280 return
2279 2281
2280 2282 if precursor is not None:
2281 2283 if opts[b'rev']:
2282 2284 raise error.Abort(b'cannot select revision when creating marker')
2283 2285 metadata = {}
2284 2286 metadata[b'user'] = encoding.fromlocal(opts[b'user'] or ui.username())
2285 2287 succs = tuple(parsenodeid(succ) for succ in successors)
2286 2288 l = repo.lock()
2287 2289 try:
2288 2290 tr = repo.transaction(b'debugobsolete')
2289 2291 try:
2290 2292 date = opts.get(b'date')
2291 2293 if date:
2292 2294 date = dateutil.parsedate(date)
2293 2295 else:
2294 2296 date = None
2295 2297 prec = parsenodeid(precursor)
2296 2298 parents = None
2297 2299 if opts[b'record_parents']:
2298 2300 if prec not in repo.unfiltered():
2299 2301 raise error.Abort(
2300 2302 b'cannot used --record-parents on '
2301 2303 b'unknown changesets'
2302 2304 )
2303 2305 parents = repo.unfiltered()[prec].parents()
2304 2306 parents = tuple(p.node() for p in parents)
2305 2307 repo.obsstore.create(
2306 2308 tr,
2307 2309 prec,
2308 2310 succs,
2309 2311 opts[b'flags'],
2310 2312 parents=parents,
2311 2313 date=date,
2312 2314 metadata=metadata,
2313 2315 ui=ui,
2314 2316 )
2315 2317 tr.close()
2316 2318 except ValueError as exc:
2317 2319 raise error.Abort(
2318 2320 _(b'bad obsmarker input: %s') % pycompat.bytestr(exc)
2319 2321 )
2320 2322 finally:
2321 2323 tr.release()
2322 2324 finally:
2323 2325 l.release()
2324 2326 else:
2325 2327 if opts[b'rev']:
2326 2328 revs = scmutil.revrange(repo, opts[b'rev'])
2327 2329 nodes = [repo[r].node() for r in revs]
2328 2330 markers = list(
2329 2331 obsutil.getmarkers(
2330 2332 repo, nodes=nodes, exclusive=opts[b'exclusive']
2331 2333 )
2332 2334 )
2333 2335 markers.sort(key=lambda x: x._data)
2334 2336 else:
2335 2337 markers = obsutil.getmarkers(repo)
2336 2338
2337 2339 markerstoiter = markers
2338 2340 isrelevant = lambda m: True
2339 2341 if opts.get(b'rev') and opts.get(b'index'):
2340 2342 markerstoiter = obsutil.getmarkers(repo)
2341 2343 markerset = set(markers)
2342 2344 isrelevant = lambda m: m in markerset
2343 2345
2344 2346 fm = ui.formatter(b'debugobsolete', opts)
2345 2347 for i, m in enumerate(markerstoiter):
2346 2348 if not isrelevant(m):
2347 2349 # marker can be irrelevant when we're iterating over a set
2348 2350 # of markers (markerstoiter) which is bigger than the set
2349 2351 # of markers we want to display (markers)
2350 2352 # this can happen if both --index and --rev options are
2351 2353 # provided and thus we need to iterate over all of the markers
2352 2354 # to get the correct indices, but only display the ones that
2353 2355 # are relevant to --rev value
2354 2356 continue
2355 2357 fm.startitem()
2356 2358 ind = i if opts.get(b'index') else None
2357 2359 cmdutil.showmarker(fm, m, index=ind)
2358 2360 fm.end()
2359 2361
2360 2362
2361 2363 @command(
2362 2364 b'debugp1copies',
2363 2365 [(b'r', b'rev', b'', _(b'revision to debug'), _(b'REV'))],
2364 2366 _(b'[-r REV]'),
2365 2367 )
2366 2368 def debugp1copies(ui, repo, **opts):
2367 2369 """dump copy information compared to p1"""
2368 2370
2369 2371 opts = pycompat.byteskwargs(opts)
2370 2372 ctx = scmutil.revsingle(repo, opts.get(b'rev'), default=None)
2371 2373 for dst, src in ctx.p1copies().items():
2372 2374 ui.write(b'%s -> %s\n' % (src, dst))
2373 2375
2374 2376
2375 2377 @command(
2376 2378 b'debugp2copies',
2377 2379 [(b'r', b'rev', b'', _(b'revision to debug'), _(b'REV'))],
2378 2380 _(b'[-r REV]'),
2379 2381 )
2380 2382 def debugp1copies(ui, repo, **opts):
2381 2383 """dump copy information compared to p2"""
2382 2384
2383 2385 opts = pycompat.byteskwargs(opts)
2384 2386 ctx = scmutil.revsingle(repo, opts.get(b'rev'), default=None)
2385 2387 for dst, src in ctx.p2copies().items():
2386 2388 ui.write(b'%s -> %s\n' % (src, dst))
2387 2389
2388 2390
2389 2391 @command(
2390 2392 b'debugpathcomplete',
2391 2393 [
2392 2394 (b'f', b'full', None, _(b'complete an entire path')),
2393 2395 (b'n', b'normal', None, _(b'show only normal files')),
2394 2396 (b'a', b'added', None, _(b'show only added files')),
2395 2397 (b'r', b'removed', None, _(b'show only removed files')),
2396 2398 ],
2397 2399 _(b'FILESPEC...'),
2398 2400 )
2399 2401 def debugpathcomplete(ui, repo, *specs, **opts):
2400 2402 '''complete part or all of a tracked path
2401 2403
2402 2404 This command supports shells that offer path name completion. It
2403 2405 currently completes only files already known to the dirstate.
2404 2406
2405 2407 Completion extends only to the next path segment unless
2406 2408 --full is specified, in which case entire paths are used.'''
2407 2409
2408 2410 def complete(path, acceptable):
2409 2411 dirstate = repo.dirstate
2410 2412 spec = os.path.normpath(os.path.join(encoding.getcwd(), path))
2411 2413 rootdir = repo.root + pycompat.ossep
2412 2414 if spec != repo.root and not spec.startswith(rootdir):
2413 2415 return [], []
2414 2416 if os.path.isdir(spec):
2415 2417 spec += b'/'
2416 2418 spec = spec[len(rootdir) :]
2417 2419 fixpaths = pycompat.ossep != b'/'
2418 2420 if fixpaths:
2419 2421 spec = spec.replace(pycompat.ossep, b'/')
2420 2422 speclen = len(spec)
2421 2423 fullpaths = opts['full']
2422 2424 files, dirs = set(), set()
2423 2425 adddir, addfile = dirs.add, files.add
2424 2426 for f, st in pycompat.iteritems(dirstate):
2425 2427 if f.startswith(spec) and st[0] in acceptable:
2426 2428 if fixpaths:
2427 2429 f = f.replace(b'/', pycompat.ossep)
2428 2430 if fullpaths:
2429 2431 addfile(f)
2430 2432 continue
2431 2433 s = f.find(pycompat.ossep, speclen)
2432 2434 if s >= 0:
2433 2435 adddir(f[:s])
2434 2436 else:
2435 2437 addfile(f)
2436 2438 return files, dirs
2437 2439
2438 2440 acceptable = b''
2439 2441 if opts['normal']:
2440 2442 acceptable += b'nm'
2441 2443 if opts['added']:
2442 2444 acceptable += b'a'
2443 2445 if opts['removed']:
2444 2446 acceptable += b'r'
2445 2447 cwd = repo.getcwd()
2446 2448 if not specs:
2447 2449 specs = [b'.']
2448 2450
2449 2451 files, dirs = set(), set()
2450 2452 for spec in specs:
2451 2453 f, d = complete(spec, acceptable or b'nmar')
2452 2454 files.update(f)
2453 2455 dirs.update(d)
2454 2456 files.update(dirs)
2455 2457 ui.write(b'\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
2456 2458 ui.write(b'\n')
2457 2459
2458 2460
2459 2461 @command(
2460 2462 b'debugpathcopies',
2461 2463 cmdutil.walkopts,
2462 2464 b'hg debugpathcopies REV1 REV2 [FILE]',
2463 2465 inferrepo=True,
2464 2466 )
2465 2467 def debugpathcopies(ui, repo, rev1, rev2, *pats, **opts):
2466 2468 """show copies between two revisions"""
2467 2469 ctx1 = scmutil.revsingle(repo, rev1)
2468 2470 ctx2 = scmutil.revsingle(repo, rev2)
2469 2471 m = scmutil.match(ctx1, pats, opts)
2470 2472 for dst, src in sorted(copies.pathcopies(ctx1, ctx2, m).items()):
2471 2473 ui.write(b'%s -> %s\n' % (src, dst))
2472 2474
2473 2475
2474 2476 @command(b'debugpeer', [], _(b'PATH'), norepo=True)
2475 2477 def debugpeer(ui, path):
2476 2478 """establish a connection to a peer repository"""
2477 2479 # Always enable peer request logging. Requires --debug to display
2478 2480 # though.
2479 2481 overrides = {
2480 2482 (b'devel', b'debug.peer-request'): True,
2481 2483 }
2482 2484
2483 2485 with ui.configoverride(overrides):
2484 2486 peer = hg.peer(ui, {}, path)
2485 2487
2486 2488 local = peer.local() is not None
2487 2489 canpush = peer.canpush()
2488 2490
2489 2491 ui.write(_(b'url: %s\n') % peer.url())
2490 2492 ui.write(_(b'local: %s\n') % (_(b'yes') if local else _(b'no')))
2491 2493 ui.write(_(b'pushable: %s\n') % (_(b'yes') if canpush else _(b'no')))
2492 2494
2493 2495
2494 2496 @command(
2495 2497 b'debugpickmergetool',
2496 2498 [
2497 2499 (b'r', b'rev', b'', _(b'check for files in this revision'), _(b'REV')),
2498 2500 (b'', b'changedelete', None, _(b'emulate merging change and delete')),
2499 2501 ]
2500 2502 + cmdutil.walkopts
2501 2503 + cmdutil.mergetoolopts,
2502 2504 _(b'[PATTERN]...'),
2503 2505 inferrepo=True,
2504 2506 )
2505 2507 def debugpickmergetool(ui, repo, *pats, **opts):
2506 2508 """examine which merge tool is chosen for specified file
2507 2509
2508 2510 As described in :hg:`help merge-tools`, Mercurial examines
2509 2511 configurations below in this order to decide which merge tool is
2510 2512 chosen for specified file.
2511 2513
2512 2514 1. ``--tool`` option
2513 2515 2. ``HGMERGE`` environment variable
2514 2516 3. configurations in ``merge-patterns`` section
2515 2517 4. configuration of ``ui.merge``
2516 2518 5. configurations in ``merge-tools`` section
2517 2519 6. ``hgmerge`` tool (for historical reason only)
2518 2520 7. default tool for fallback (``:merge`` or ``:prompt``)
2519 2521
2520 2522 This command writes out examination result in the style below::
2521 2523
2522 2524 FILE = MERGETOOL
2523 2525
2524 2526 By default, all files known in the first parent context of the
2525 2527 working directory are examined. Use file patterns and/or -I/-X
2526 2528 options to limit target files. -r/--rev is also useful to examine
2527 2529 files in another context without actual updating to it.
2528 2530
2529 2531 With --debug, this command shows warning messages while matching
2530 2532 against ``merge-patterns`` and so on, too. It is recommended to
2531 2533 use this option with explicit file patterns and/or -I/-X options,
2532 2534 because this option increases amount of output per file according
2533 2535 to configurations in hgrc.
2534 2536
2535 2537 With -v/--verbose, this command shows configurations below at
2536 2538 first (only if specified).
2537 2539
2538 2540 - ``--tool`` option
2539 2541 - ``HGMERGE`` environment variable
2540 2542 - configuration of ``ui.merge``
2541 2543
2542 2544 If merge tool is chosen before matching against
2543 2545 ``merge-patterns``, this command can't show any helpful
2544 2546 information, even with --debug. In such case, information above is
2545 2547 useful to know why a merge tool is chosen.
2546 2548 """
2547 2549 opts = pycompat.byteskwargs(opts)
2548 2550 overrides = {}
2549 2551 if opts[b'tool']:
2550 2552 overrides[(b'ui', b'forcemerge')] = opts[b'tool']
2551 2553 ui.notenoi18n(b'with --tool %r\n' % (pycompat.bytestr(opts[b'tool'])))
2552 2554
2553 2555 with ui.configoverride(overrides, b'debugmergepatterns'):
2554 2556 hgmerge = encoding.environ.get(b"HGMERGE")
2555 2557 if hgmerge is not None:
2556 2558 ui.notenoi18n(b'with HGMERGE=%r\n' % (pycompat.bytestr(hgmerge)))
2557 2559 uimerge = ui.config(b"ui", b"merge")
2558 2560 if uimerge:
2559 2561 ui.notenoi18n(b'with ui.merge=%r\n' % (pycompat.bytestr(uimerge)))
2560 2562
2561 2563 ctx = scmutil.revsingle(repo, opts.get(b'rev'))
2562 2564 m = scmutil.match(ctx, pats, opts)
2563 2565 changedelete = opts[b'changedelete']
2564 2566 for path in ctx.walk(m):
2565 2567 fctx = ctx[path]
2566 2568 try:
2567 2569 if not ui.debugflag:
2568 2570 ui.pushbuffer(error=True)
2569 2571 tool, toolpath = filemerge._picktool(
2570 2572 repo,
2571 2573 ui,
2572 2574 path,
2573 2575 fctx.isbinary(),
2574 2576 b'l' in fctx.flags(),
2575 2577 changedelete,
2576 2578 )
2577 2579 finally:
2578 2580 if not ui.debugflag:
2579 2581 ui.popbuffer()
2580 2582 ui.write(b'%s = %s\n' % (path, tool))
2581 2583
2582 2584
2583 2585 @command(b'debugpushkey', [], _(b'REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
2584 2586 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2585 2587 '''access the pushkey key/value protocol
2586 2588
2587 2589 With two args, list the keys in the given namespace.
2588 2590
2589 2591 With five args, set a key to new if it currently is set to old.
2590 2592 Reports success or failure.
2591 2593 '''
2592 2594
2593 2595 target = hg.peer(ui, {}, repopath)
2594 2596 if keyinfo:
2595 2597 key, old, new = keyinfo
2596 2598 with target.commandexecutor() as e:
2597 2599 r = e.callcommand(
2598 2600 b'pushkey',
2599 2601 {
2600 2602 b'namespace': namespace,
2601 2603 b'key': key,
2602 2604 b'old': old,
2603 2605 b'new': new,
2604 2606 },
2605 2607 ).result()
2606 2608
2607 2609 ui.status(pycompat.bytestr(r) + b'\n')
2608 2610 return not r
2609 2611 else:
2610 2612 for k, v in sorted(pycompat.iteritems(target.listkeys(namespace))):
2611 2613 ui.write(
2612 2614 b"%s\t%s\n" % (stringutil.escapestr(k), stringutil.escapestr(v))
2613 2615 )
2614 2616
2615 2617
2616 2618 @command(b'debugpvec', [], _(b'A B'))
2617 2619 def debugpvec(ui, repo, a, b=None):
2618 2620 ca = scmutil.revsingle(repo, a)
2619 2621 cb = scmutil.revsingle(repo, b)
2620 2622 pa = pvec.ctxpvec(ca)
2621 2623 pb = pvec.ctxpvec(cb)
2622 2624 if pa == pb:
2623 2625 rel = b"="
2624 2626 elif pa > pb:
2625 2627 rel = b">"
2626 2628 elif pa < pb:
2627 2629 rel = b"<"
2628 2630 elif pa | pb:
2629 2631 rel = b"|"
2630 2632 ui.write(_(b"a: %s\n") % pa)
2631 2633 ui.write(_(b"b: %s\n") % pb)
2632 2634 ui.write(_(b"depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2633 2635 ui.write(
2634 2636 _(b"delta: %d hdist: %d distance: %d relation: %s\n")
2635 2637 % (
2636 2638 abs(pa._depth - pb._depth),
2637 2639 pvec._hamming(pa._vec, pb._vec),
2638 2640 pa.distance(pb),
2639 2641 rel,
2640 2642 )
2641 2643 )
2642 2644
2643 2645
2644 2646 @command(
2645 2647 b'debugrebuilddirstate|debugrebuildstate',
2646 2648 [
2647 2649 (b'r', b'rev', b'', _(b'revision to rebuild to'), _(b'REV')),
2648 2650 (
2649 2651 b'',
2650 2652 b'minimal',
2651 2653 None,
2652 2654 _(
2653 2655 b'only rebuild files that are inconsistent with '
2654 2656 b'the working copy parent'
2655 2657 ),
2656 2658 ),
2657 2659 ],
2658 2660 _(b'[-r REV]'),
2659 2661 )
2660 2662 def debugrebuilddirstate(ui, repo, rev, **opts):
2661 2663 """rebuild the dirstate as it would look like for the given revision
2662 2664
2663 2665 If no revision is specified the first current parent will be used.
2664 2666
2665 2667 The dirstate will be set to the files of the given revision.
2666 2668 The actual working directory content or existing dirstate
2667 2669 information such as adds or removes is not considered.
2668 2670
2669 2671 ``minimal`` will only rebuild the dirstate status for files that claim to be
2670 2672 tracked but are not in the parent manifest, or that exist in the parent
2671 2673 manifest but are not in the dirstate. It will not change adds, removes, or
2672 2674 modified files that are in the working copy parent.
2673 2675
2674 2676 One use of this command is to make the next :hg:`status` invocation
2675 2677 check the actual file content.
2676 2678 """
2677 2679 ctx = scmutil.revsingle(repo, rev)
2678 2680 with repo.wlock():
2679 2681 dirstate = repo.dirstate
2680 2682 changedfiles = None
2681 2683 # See command doc for what minimal does.
2682 2684 if opts.get('minimal'):
2683 2685 manifestfiles = set(ctx.manifest().keys())
2684 2686 dirstatefiles = set(dirstate)
2685 2687 manifestonly = manifestfiles - dirstatefiles
2686 2688 dsonly = dirstatefiles - manifestfiles
2687 2689 dsnotadded = {f for f in dsonly if dirstate[f] != b'a'}
2688 2690 changedfiles = manifestonly | dsnotadded
2689 2691
2690 2692 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
2691 2693
2692 2694
2693 2695 @command(b'debugrebuildfncache', [], b'')
2694 2696 def debugrebuildfncache(ui, repo):
2695 2697 """rebuild the fncache file"""
2696 2698 repair.rebuildfncache(ui, repo)
2697 2699
2698 2700
2699 2701 @command(
2700 2702 b'debugrename',
2701 2703 [(b'r', b'rev', b'', _(b'revision to debug'), _(b'REV'))],
2702 2704 _(b'[-r REV] [FILE]...'),
2703 2705 )
2704 2706 def debugrename(ui, repo, *pats, **opts):
2705 2707 """dump rename information"""
2706 2708
2707 2709 opts = pycompat.byteskwargs(opts)
2708 2710 ctx = scmutil.revsingle(repo, opts.get(b'rev'))
2709 2711 m = scmutil.match(ctx, pats, opts)
2710 2712 for abs in ctx.walk(m):
2711 2713 fctx = ctx[abs]
2712 2714 o = fctx.filelog().renamed(fctx.filenode())
2713 2715 rel = repo.pathto(abs)
2714 2716 if o:
2715 2717 ui.write(_(b"%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2716 2718 else:
2717 2719 ui.write(_(b"%s not renamed\n") % rel)
2718 2720
2719 2721
2720 2722 @command(b'debugrequires|debugrequirements', [], b'')
2721 2723 def debugrequirements(ui, repo):
2722 2724 """ print the current repo requirements """
2723 2725 for r in sorted(repo.requirements):
2724 2726 ui.write(b"%s\n" % r)
2725 2727
2726 2728
2727 2729 @command(
2728 2730 b'debugrevlog',
2729 2731 cmdutil.debugrevlogopts + [(b'd', b'dump', False, _(b'dump index data'))],
2730 2732 _(b'-c|-m|FILE'),
2731 2733 optionalrepo=True,
2732 2734 )
2733 2735 def debugrevlog(ui, repo, file_=None, **opts):
2734 2736 """show data and statistics about a revlog"""
2735 2737 opts = pycompat.byteskwargs(opts)
2736 2738 r = cmdutil.openrevlog(repo, b'debugrevlog', file_, opts)
2737 2739
2738 2740 if opts.get(b"dump"):
2739 2741 numrevs = len(r)
2740 2742 ui.write(
2741 2743 (
2742 2744 b"# rev p1rev p2rev start end deltastart base p1 p2"
2743 2745 b" rawsize totalsize compression heads chainlen\n"
2744 2746 )
2745 2747 )
2746 2748 ts = 0
2747 2749 heads = set()
2748 2750
2749 2751 for rev in pycompat.xrange(numrevs):
2750 2752 dbase = r.deltaparent(rev)
2751 2753 if dbase == -1:
2752 2754 dbase = rev
2753 2755 cbase = r.chainbase(rev)
2754 2756 clen = r.chainlen(rev)
2755 2757 p1, p2 = r.parentrevs(rev)
2756 2758 rs = r.rawsize(rev)
2757 2759 ts = ts + rs
2758 2760 heads -= set(r.parentrevs(rev))
2759 2761 heads.add(rev)
2760 2762 try:
2761 2763 compression = ts / r.end(rev)
2762 2764 except ZeroDivisionError:
2763 2765 compression = 0
2764 2766 ui.write(
2765 2767 b"%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
2766 2768 b"%11d %5d %8d\n"
2767 2769 % (
2768 2770 rev,
2769 2771 p1,
2770 2772 p2,
2771 2773 r.start(rev),
2772 2774 r.end(rev),
2773 2775 r.start(dbase),
2774 2776 r.start(cbase),
2775 2777 r.start(p1),
2776 2778 r.start(p2),
2777 2779 rs,
2778 2780 ts,
2779 2781 compression,
2780 2782 len(heads),
2781 2783 clen,
2782 2784 )
2783 2785 )
2784 2786 return 0
2785 2787
2786 2788 v = r.version
2787 2789 format = v & 0xFFFF
2788 2790 flags = []
2789 2791 gdelta = False
2790 2792 if v & revlog.FLAG_INLINE_DATA:
2791 2793 flags.append(b'inline')
2792 2794 if v & revlog.FLAG_GENERALDELTA:
2793 2795 gdelta = True
2794 2796 flags.append(b'generaldelta')
2795 2797 if not flags:
2796 2798 flags = [b'(none)']
2797 2799
2798 2800 ### tracks merge vs single parent
2799 2801 nummerges = 0
2800 2802
2801 2803 ### tracks ways the "delta" are build
2802 2804 # nodelta
2803 2805 numempty = 0
2804 2806 numemptytext = 0
2805 2807 numemptydelta = 0
2806 2808 # full file content
2807 2809 numfull = 0
2808 2810 # intermediate snapshot against a prior snapshot
2809 2811 numsemi = 0
2810 2812 # snapshot count per depth
2811 2813 numsnapdepth = collections.defaultdict(lambda: 0)
2812 2814 # delta against previous revision
2813 2815 numprev = 0
2814 2816 # delta against first or second parent (not prev)
2815 2817 nump1 = 0
2816 2818 nump2 = 0
2817 2819 # delta against neither prev nor parents
2818 2820 numother = 0
2819 2821 # delta against prev that are also first or second parent
2820 2822 # (details of `numprev`)
2821 2823 nump1prev = 0
2822 2824 nump2prev = 0
2823 2825
2824 2826 # data about delta chain of each revs
2825 2827 chainlengths = []
2826 2828 chainbases = []
2827 2829 chainspans = []
2828 2830
2829 2831 # data about each revision
2830 2832 datasize = [None, 0, 0]
2831 2833 fullsize = [None, 0, 0]
2832 2834 semisize = [None, 0, 0]
2833 2835 # snapshot count per depth
2834 2836 snapsizedepth = collections.defaultdict(lambda: [None, 0, 0])
2835 2837 deltasize = [None, 0, 0]
2836 2838 chunktypecounts = {}
2837 2839 chunktypesizes = {}
2838 2840
2839 2841 def addsize(size, l):
2840 2842 if l[0] is None or size < l[0]:
2841 2843 l[0] = size
2842 2844 if size > l[1]:
2843 2845 l[1] = size
2844 2846 l[2] += size
2845 2847
2846 2848 numrevs = len(r)
2847 2849 for rev in pycompat.xrange(numrevs):
2848 2850 p1, p2 = r.parentrevs(rev)
2849 2851 delta = r.deltaparent(rev)
2850 2852 if format > 0:
2851 2853 addsize(r.rawsize(rev), datasize)
2852 2854 if p2 != nullrev:
2853 2855 nummerges += 1
2854 2856 size = r.length(rev)
2855 2857 if delta == nullrev:
2856 2858 chainlengths.append(0)
2857 2859 chainbases.append(r.start(rev))
2858 2860 chainspans.append(size)
2859 2861 if size == 0:
2860 2862 numempty += 1
2861 2863 numemptytext += 1
2862 2864 else:
2863 2865 numfull += 1
2864 2866 numsnapdepth[0] += 1
2865 2867 addsize(size, fullsize)
2866 2868 addsize(size, snapsizedepth[0])
2867 2869 else:
2868 2870 chainlengths.append(chainlengths[delta] + 1)
2869 2871 baseaddr = chainbases[delta]
2870 2872 revaddr = r.start(rev)
2871 2873 chainbases.append(baseaddr)
2872 2874 chainspans.append((revaddr - baseaddr) + size)
2873 2875 if size == 0:
2874 2876 numempty += 1
2875 2877 numemptydelta += 1
2876 2878 elif r.issnapshot(rev):
2877 2879 addsize(size, semisize)
2878 2880 numsemi += 1
2879 2881 depth = r.snapshotdepth(rev)
2880 2882 numsnapdepth[depth] += 1
2881 2883 addsize(size, snapsizedepth[depth])
2882 2884 else:
2883 2885 addsize(size, deltasize)
2884 2886 if delta == rev - 1:
2885 2887 numprev += 1
2886 2888 if delta == p1:
2887 2889 nump1prev += 1
2888 2890 elif delta == p2:
2889 2891 nump2prev += 1
2890 2892 elif delta == p1:
2891 2893 nump1 += 1
2892 2894 elif delta == p2:
2893 2895 nump2 += 1
2894 2896 elif delta != nullrev:
2895 2897 numother += 1
2896 2898
2897 2899 # Obtain data on the raw chunks in the revlog.
2898 2900 if util.safehasattr(r, b'_getsegmentforrevs'):
2899 2901 segment = r._getsegmentforrevs(rev, rev)[1]
2900 2902 else:
2901 2903 segment = r._revlog._getsegmentforrevs(rev, rev)[1]
2902 2904 if segment:
2903 2905 chunktype = bytes(segment[0:1])
2904 2906 else:
2905 2907 chunktype = b'empty'
2906 2908
2907 2909 if chunktype not in chunktypecounts:
2908 2910 chunktypecounts[chunktype] = 0
2909 2911 chunktypesizes[chunktype] = 0
2910 2912
2911 2913 chunktypecounts[chunktype] += 1
2912 2914 chunktypesizes[chunktype] += size
2913 2915
2914 2916 # Adjust size min value for empty cases
2915 2917 for size in (datasize, fullsize, semisize, deltasize):
2916 2918 if size[0] is None:
2917 2919 size[0] = 0
2918 2920
2919 2921 numdeltas = numrevs - numfull - numempty - numsemi
2920 2922 numoprev = numprev - nump1prev - nump2prev
2921 2923 totalrawsize = datasize[2]
2922 2924 datasize[2] /= numrevs
2923 2925 fulltotal = fullsize[2]
2924 2926 if numfull == 0:
2925 2927 fullsize[2] = 0
2926 2928 else:
2927 2929 fullsize[2] /= numfull
2928 2930 semitotal = semisize[2]
2929 2931 snaptotal = {}
2930 2932 if numsemi > 0:
2931 2933 semisize[2] /= numsemi
2932 2934 for depth in snapsizedepth:
2933 2935 snaptotal[depth] = snapsizedepth[depth][2]
2934 2936 snapsizedepth[depth][2] /= numsnapdepth[depth]
2935 2937
2936 2938 deltatotal = deltasize[2]
2937 2939 if numdeltas > 0:
2938 2940 deltasize[2] /= numdeltas
2939 2941 totalsize = fulltotal + semitotal + deltatotal
2940 2942 avgchainlen = sum(chainlengths) / numrevs
2941 2943 maxchainlen = max(chainlengths)
2942 2944 maxchainspan = max(chainspans)
2943 2945 compratio = 1
2944 2946 if totalsize:
2945 2947 compratio = totalrawsize / totalsize
2946 2948
2947 2949 basedfmtstr = b'%%%dd\n'
2948 2950 basepcfmtstr = b'%%%dd %s(%%5.2f%%%%)\n'
2949 2951
2950 2952 def dfmtstr(max):
2951 2953 return basedfmtstr % len(str(max))
2952 2954
2953 2955 def pcfmtstr(max, padding=0):
2954 2956 return basepcfmtstr % (len(str(max)), b' ' * padding)
2955 2957
2956 2958 def pcfmt(value, total):
2957 2959 if total:
2958 2960 return (value, 100 * float(value) / total)
2959 2961 else:
2960 2962 return value, 100.0
2961 2963
2962 2964 ui.writenoi18n(b'format : %d\n' % format)
2963 2965 ui.writenoi18n(b'flags : %s\n' % b', '.join(flags))
2964 2966
2965 2967 ui.write(b'\n')
2966 2968 fmt = pcfmtstr(totalsize)
2967 2969 fmt2 = dfmtstr(totalsize)
2968 2970 ui.writenoi18n(b'revisions : ' + fmt2 % numrevs)
2969 2971 ui.writenoi18n(b' merges : ' + fmt % pcfmt(nummerges, numrevs))
2970 2972 ui.writenoi18n(
2971 2973 b' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs)
2972 2974 )
2973 2975 ui.writenoi18n(b'revisions : ' + fmt2 % numrevs)
2974 2976 ui.writenoi18n(b' empty : ' + fmt % pcfmt(numempty, numrevs))
2975 2977 ui.writenoi18n(
2976 2978 b' text : '
2977 2979 + fmt % pcfmt(numemptytext, numemptytext + numemptydelta)
2978 2980 )
2979 2981 ui.writenoi18n(
2980 2982 b' delta : '
2981 2983 + fmt % pcfmt(numemptydelta, numemptytext + numemptydelta)
2982 2984 )
2983 2985 ui.writenoi18n(
2984 2986 b' snapshot : ' + fmt % pcfmt(numfull + numsemi, numrevs)
2985 2987 )
2986 2988 for depth in sorted(numsnapdepth):
2987 2989 ui.write(
2988 2990 (b' lvl-%-3d : ' % depth)
2989 2991 + fmt % pcfmt(numsnapdepth[depth], numrevs)
2990 2992 )
2991 2993 ui.writenoi18n(b' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
2992 2994 ui.writenoi18n(b'revision size : ' + fmt2 % totalsize)
2993 2995 ui.writenoi18n(
2994 2996 b' snapshot : ' + fmt % pcfmt(fulltotal + semitotal, totalsize)
2995 2997 )
2996 2998 for depth in sorted(numsnapdepth):
2997 2999 ui.write(
2998 3000 (b' lvl-%-3d : ' % depth)
2999 3001 + fmt % pcfmt(snaptotal[depth], totalsize)
3000 3002 )
3001 3003 ui.writenoi18n(b' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
3002 3004
3003 3005 def fmtchunktype(chunktype):
3004 3006 if chunktype == b'empty':
3005 3007 return b' %s : ' % chunktype
3006 3008 elif chunktype in pycompat.bytestr(string.ascii_letters):
3007 3009 return b' 0x%s (%s) : ' % (hex(chunktype), chunktype)
3008 3010 else:
3009 3011 return b' 0x%s : ' % hex(chunktype)
3010 3012
3011 3013 ui.write(b'\n')
3012 3014 ui.writenoi18n(b'chunks : ' + fmt2 % numrevs)
3013 3015 for chunktype in sorted(chunktypecounts):
3014 3016 ui.write(fmtchunktype(chunktype))
3015 3017 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
3016 3018 ui.writenoi18n(b'chunks size : ' + fmt2 % totalsize)
3017 3019 for chunktype in sorted(chunktypecounts):
3018 3020 ui.write(fmtchunktype(chunktype))
3019 3021 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
3020 3022
3021 3023 ui.write(b'\n')
3022 3024 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
3023 3025 ui.writenoi18n(b'avg chain length : ' + fmt % avgchainlen)
3024 3026 ui.writenoi18n(b'max chain length : ' + fmt % maxchainlen)
3025 3027 ui.writenoi18n(b'max chain reach : ' + fmt % maxchainspan)
3026 3028 ui.writenoi18n(b'compression ratio : ' + fmt % compratio)
3027 3029
3028 3030 if format > 0:
3029 3031 ui.write(b'\n')
3030 3032 ui.writenoi18n(
3031 3033 b'uncompressed data size (min/max/avg) : %d / %d / %d\n'
3032 3034 % tuple(datasize)
3033 3035 )
3034 3036 ui.writenoi18n(
3035 3037 b'full revision size (min/max/avg) : %d / %d / %d\n'
3036 3038 % tuple(fullsize)
3037 3039 )
3038 3040 ui.writenoi18n(
3039 3041 b'inter-snapshot size (min/max/avg) : %d / %d / %d\n'
3040 3042 % tuple(semisize)
3041 3043 )
3042 3044 for depth in sorted(snapsizedepth):
3043 3045 if depth == 0:
3044 3046 continue
3045 3047 ui.writenoi18n(
3046 3048 b' level-%-3d (min/max/avg) : %d / %d / %d\n'
3047 3049 % ((depth,) + tuple(snapsizedepth[depth]))
3048 3050 )
3049 3051 ui.writenoi18n(
3050 3052 b'delta size (min/max/avg) : %d / %d / %d\n'
3051 3053 % tuple(deltasize)
3052 3054 )
3053 3055
3054 3056 if numdeltas > 0:
3055 3057 ui.write(b'\n')
3056 3058 fmt = pcfmtstr(numdeltas)
3057 3059 fmt2 = pcfmtstr(numdeltas, 4)
3058 3060 ui.writenoi18n(
3059 3061 b'deltas against prev : ' + fmt % pcfmt(numprev, numdeltas)
3060 3062 )
3061 3063 if numprev > 0:
3062 3064 ui.writenoi18n(
3063 3065 b' where prev = p1 : ' + fmt2 % pcfmt(nump1prev, numprev)
3064 3066 )
3065 3067 ui.writenoi18n(
3066 3068 b' where prev = p2 : ' + fmt2 % pcfmt(nump2prev, numprev)
3067 3069 )
3068 3070 ui.writenoi18n(
3069 3071 b' other : ' + fmt2 % pcfmt(numoprev, numprev)
3070 3072 )
3071 3073 if gdelta:
3072 3074 ui.writenoi18n(
3073 3075 b'deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas)
3074 3076 )
3075 3077 ui.writenoi18n(
3076 3078 b'deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas)
3077 3079 )
3078 3080 ui.writenoi18n(
3079 3081 b'deltas against other : ' + fmt % pcfmt(numother, numdeltas)
3080 3082 )
3081 3083
3082 3084
3083 3085 @command(
3084 3086 b'debugrevlogindex',
3085 3087 cmdutil.debugrevlogopts
3086 3088 + [(b'f', b'format', 0, _(b'revlog format'), _(b'FORMAT'))],
3087 3089 _(b'[-f FORMAT] -c|-m|FILE'),
3088 3090 optionalrepo=True,
3089 3091 )
3090 3092 def debugrevlogindex(ui, repo, file_=None, **opts):
3091 3093 """dump the contents of a revlog index"""
3092 3094 opts = pycompat.byteskwargs(opts)
3093 3095 r = cmdutil.openrevlog(repo, b'debugrevlogindex', file_, opts)
3094 3096 format = opts.get(b'format', 0)
3095 3097 if format not in (0, 1):
3096 3098 raise error.Abort(_(b"unknown format %d") % format)
3097 3099
3098 3100 if ui.debugflag:
3099 3101 shortfn = hex
3100 3102 else:
3101 3103 shortfn = short
3102 3104
3103 3105 # There might not be anything in r, so have a sane default
3104 3106 idlen = 12
3105 3107 for i in r:
3106 3108 idlen = len(shortfn(r.node(i)))
3107 3109 break
3108 3110
3109 3111 if format == 0:
3110 3112 if ui.verbose:
3111 3113 ui.writenoi18n(
3112 3114 b" rev offset length linkrev %s %s p2\n"
3113 3115 % (b"nodeid".ljust(idlen), b"p1".ljust(idlen))
3114 3116 )
3115 3117 else:
3116 3118 ui.writenoi18n(
3117 3119 b" rev linkrev %s %s p2\n"
3118 3120 % (b"nodeid".ljust(idlen), b"p1".ljust(idlen))
3119 3121 )
3120 3122 elif format == 1:
3121 3123 if ui.verbose:
3122 3124 ui.writenoi18n(
3123 3125 (
3124 3126 b" rev flag offset length size link p1"
3125 3127 b" p2 %s\n"
3126 3128 )
3127 3129 % b"nodeid".rjust(idlen)
3128 3130 )
3129 3131 else:
3130 3132 ui.writenoi18n(
3131 3133 b" rev flag size link p1 p2 %s\n"
3132 3134 % b"nodeid".rjust(idlen)
3133 3135 )
3134 3136
3135 3137 for i in r:
3136 3138 node = r.node(i)
3137 3139 if format == 0:
3138 3140 try:
3139 3141 pp = r.parents(node)
3140 3142 except Exception:
3141 3143 pp = [nullid, nullid]
3142 3144 if ui.verbose:
3143 3145 ui.write(
3144 3146 b"% 6d % 9d % 7d % 7d %s %s %s\n"
3145 3147 % (
3146 3148 i,
3147 3149 r.start(i),
3148 3150 r.length(i),
3149 3151 r.linkrev(i),
3150 3152 shortfn(node),
3151 3153 shortfn(pp[0]),
3152 3154 shortfn(pp[1]),
3153 3155 )
3154 3156 )
3155 3157 else:
3156 3158 ui.write(
3157 3159 b"% 6d % 7d %s %s %s\n"
3158 3160 % (
3159 3161 i,
3160 3162 r.linkrev(i),
3161 3163 shortfn(node),
3162 3164 shortfn(pp[0]),
3163 3165 shortfn(pp[1]),
3164 3166 )
3165 3167 )
3166 3168 elif format == 1:
3167 3169 pr = r.parentrevs(i)
3168 3170 if ui.verbose:
3169 3171 ui.write(
3170 3172 b"% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d %s\n"
3171 3173 % (
3172 3174 i,
3173 3175 r.flags(i),
3174 3176 r.start(i),
3175 3177 r.length(i),
3176 3178 r.rawsize(i),
3177 3179 r.linkrev(i),
3178 3180 pr[0],
3179 3181 pr[1],
3180 3182 shortfn(node),
3181 3183 )
3182 3184 )
3183 3185 else:
3184 3186 ui.write(
3185 3187 b"% 6d %04x % 8d % 6d % 6d % 6d %s\n"
3186 3188 % (
3187 3189 i,
3188 3190 r.flags(i),
3189 3191 r.rawsize(i),
3190 3192 r.linkrev(i),
3191 3193 pr[0],
3192 3194 pr[1],
3193 3195 shortfn(node),
3194 3196 )
3195 3197 )
3196 3198
3197 3199
3198 3200 @command(
3199 3201 b'debugrevspec',
3200 3202 [
3201 3203 (
3202 3204 b'',
3203 3205 b'optimize',
3204 3206 None,
3205 3207 _(b'print parsed tree after optimizing (DEPRECATED)'),
3206 3208 ),
3207 3209 (
3208 3210 b'',
3209 3211 b'show-revs',
3210 3212 True,
3211 3213 _(b'print list of result revisions (default)'),
3212 3214 ),
3213 3215 (
3214 3216 b's',
3215 3217 b'show-set',
3216 3218 None,
3217 3219 _(b'print internal representation of result set'),
3218 3220 ),
3219 3221 (
3220 3222 b'p',
3221 3223 b'show-stage',
3222 3224 [],
3223 3225 _(b'print parsed tree at the given stage'),
3224 3226 _(b'NAME'),
3225 3227 ),
3226 3228 (b'', b'no-optimized', False, _(b'evaluate tree without optimization')),
3227 3229 (b'', b'verify-optimized', False, _(b'verify optimized result')),
3228 3230 ],
3229 3231 b'REVSPEC',
3230 3232 )
3231 3233 def debugrevspec(ui, repo, expr, **opts):
3232 3234 """parse and apply a revision specification
3233 3235
3234 3236 Use -p/--show-stage option to print the parsed tree at the given stages.
3235 3237 Use -p all to print tree at every stage.
3236 3238
3237 3239 Use --no-show-revs option with -s or -p to print only the set
3238 3240 representation or the parsed tree respectively.
3239 3241
3240 3242 Use --verify-optimized to compare the optimized result with the unoptimized
3241 3243 one. Returns 1 if the optimized result differs.
3242 3244 """
3243 3245 opts = pycompat.byteskwargs(opts)
3244 3246 aliases = ui.configitems(b'revsetalias')
3245 3247 stages = [
3246 3248 (b'parsed', lambda tree: tree),
3247 3249 (
3248 3250 b'expanded',
3249 3251 lambda tree: revsetlang.expandaliases(tree, aliases, ui.warn),
3250 3252 ),
3251 3253 (b'concatenated', revsetlang.foldconcat),
3252 3254 (b'analyzed', revsetlang.analyze),
3253 3255 (b'optimized', revsetlang.optimize),
3254 3256 ]
3255 3257 if opts[b'no_optimized']:
3256 3258 stages = stages[:-1]
3257 3259 if opts[b'verify_optimized'] and opts[b'no_optimized']:
3258 3260 raise error.Abort(
3259 3261 _(b'cannot use --verify-optimized with --no-optimized')
3260 3262 )
3261 3263 stagenames = {n for n, f in stages}
3262 3264
3263 3265 showalways = set()
3264 3266 showchanged = set()
3265 3267 if ui.verbose and not opts[b'show_stage']:
3266 3268 # show parsed tree by --verbose (deprecated)
3267 3269 showalways.add(b'parsed')
3268 3270 showchanged.update([b'expanded', b'concatenated'])
3269 3271 if opts[b'optimize']:
3270 3272 showalways.add(b'optimized')
3271 3273 if opts[b'show_stage'] and opts[b'optimize']:
3272 3274 raise error.Abort(_(b'cannot use --optimize with --show-stage'))
3273 3275 if opts[b'show_stage'] == [b'all']:
3274 3276 showalways.update(stagenames)
3275 3277 else:
3276 3278 for n in opts[b'show_stage']:
3277 3279 if n not in stagenames:
3278 3280 raise error.Abort(_(b'invalid stage name: %s') % n)
3279 3281 showalways.update(opts[b'show_stage'])
3280 3282
3281 3283 treebystage = {}
3282 3284 printedtree = None
3283 3285 tree = revsetlang.parse(expr, lookup=revset.lookupfn(repo))
3284 3286 for n, f in stages:
3285 3287 treebystage[n] = tree = f(tree)
3286 3288 if n in showalways or (n in showchanged and tree != printedtree):
3287 3289 if opts[b'show_stage'] or n != b'parsed':
3288 3290 ui.write(b"* %s:\n" % n)
3289 3291 ui.write(revsetlang.prettyformat(tree), b"\n")
3290 3292 printedtree = tree
3291 3293
3292 3294 if opts[b'verify_optimized']:
3293 3295 arevs = revset.makematcher(treebystage[b'analyzed'])(repo)
3294 3296 brevs = revset.makematcher(treebystage[b'optimized'])(repo)
3295 3297 if opts[b'show_set'] or (opts[b'show_set'] is None and ui.verbose):
3296 3298 ui.writenoi18n(
3297 3299 b"* analyzed set:\n", stringutil.prettyrepr(arevs), b"\n"
3298 3300 )
3299 3301 ui.writenoi18n(
3300 3302 b"* optimized set:\n", stringutil.prettyrepr(brevs), b"\n"
3301 3303 )
3302 3304 arevs = list(arevs)
3303 3305 brevs = list(brevs)
3304 3306 if arevs == brevs:
3305 3307 return 0
3306 3308 ui.writenoi18n(b'--- analyzed\n', label=b'diff.file_a')
3307 3309 ui.writenoi18n(b'+++ optimized\n', label=b'diff.file_b')
3308 3310 sm = difflib.SequenceMatcher(None, arevs, brevs)
3309 3311 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3310 3312 if tag in ('delete', 'replace'):
3311 3313 for c in arevs[alo:ahi]:
3312 3314 ui.write(b'-%d\n' % c, label=b'diff.deleted')
3313 3315 if tag in ('insert', 'replace'):
3314 3316 for c in brevs[blo:bhi]:
3315 3317 ui.write(b'+%d\n' % c, label=b'diff.inserted')
3316 3318 if tag == 'equal':
3317 3319 for c in arevs[alo:ahi]:
3318 3320 ui.write(b' %d\n' % c)
3319 3321 return 1
3320 3322
3321 3323 func = revset.makematcher(tree)
3322 3324 revs = func(repo)
3323 3325 if opts[b'show_set'] or (opts[b'show_set'] is None and ui.verbose):
3324 3326 ui.writenoi18n(b"* set:\n", stringutil.prettyrepr(revs), b"\n")
3325 3327 if not opts[b'show_revs']:
3326 3328 return
3327 3329 for c in revs:
3328 3330 ui.write(b"%d\n" % c)
3329 3331
3330 3332
3331 3333 @command(
3332 3334 b'debugserve',
3333 3335 [
3334 3336 (
3335 3337 b'',
3336 3338 b'sshstdio',
3337 3339 False,
3338 3340 _(b'run an SSH server bound to process handles'),
3339 3341 ),
3340 3342 (b'', b'logiofd', b'', _(b'file descriptor to log server I/O to')),
3341 3343 (b'', b'logiofile', b'', _(b'file to log server I/O to')),
3342 3344 ],
3343 3345 b'',
3344 3346 )
3345 3347 def debugserve(ui, repo, **opts):
3346 3348 """run a server with advanced settings
3347 3349
3348 3350 This command is similar to :hg:`serve`. It exists partially as a
3349 3351 workaround to the fact that ``hg serve --stdio`` must have specific
3350 3352 arguments for security reasons.
3351 3353 """
3352 3354 opts = pycompat.byteskwargs(opts)
3353 3355
3354 3356 if not opts[b'sshstdio']:
3355 3357 raise error.Abort(_(b'only --sshstdio is currently supported'))
3356 3358
3357 3359 logfh = None
3358 3360
3359 3361 if opts[b'logiofd'] and opts[b'logiofile']:
3360 3362 raise error.Abort(_(b'cannot use both --logiofd and --logiofile'))
3361 3363
3362 3364 if opts[b'logiofd']:
3363 3365 # Ideally we would be line buffered. But line buffering in binary
3364 3366 # mode isn't supported and emits a warning in Python 3.8+. Disabling
3365 3367 # buffering could have performance impacts. But since this isn't
3366 3368 # performance critical code, it should be fine.
3367 3369 try:
3368 3370 logfh = os.fdopen(int(opts[b'logiofd']), 'ab', 0)
3369 3371 except OSError as e:
3370 3372 if e.errno != errno.ESPIPE:
3371 3373 raise
3372 3374 # can't seek a pipe, so `ab` mode fails on py3
3373 3375 logfh = os.fdopen(int(opts[b'logiofd']), 'wb', 0)
3374 3376 elif opts[b'logiofile']:
3375 3377 logfh = open(opts[b'logiofile'], b'ab', 0)
3376 3378
3377 3379 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
3378 3380 s.serve_forever()
3379 3381
3380 3382
3381 3383 @command(b'debugsetparents', [], _(b'REV1 [REV2]'))
3382 3384 def debugsetparents(ui, repo, rev1, rev2=None):
3383 3385 """manually set the parents of the current working directory
3384 3386
3385 3387 This is useful for writing repository conversion tools, but should
3386 3388 be used with care. For example, neither the working directory nor the
3387 3389 dirstate is updated, so file status may be incorrect after running this
3388 3390 command.
3389 3391
3390 3392 Returns 0 on success.
3391 3393 """
3392 3394
3393 3395 node1 = scmutil.revsingle(repo, rev1).node()
3394 3396 node2 = scmutil.revsingle(repo, rev2, b'null').node()
3395 3397
3396 3398 with repo.wlock():
3397 3399 repo.setparents(node1, node2)
3398 3400
3399 3401
3400 3402 @command(b'debugsidedata', cmdutil.debugrevlogopts, _(b'-c|-m|FILE REV'))
3401 3403 def debugsidedata(ui, repo, file_, rev=None, **opts):
3402 3404 """dump the side data for a cl/manifest/file revision
3403 3405
3404 3406 Use --verbose to dump the sidedata content."""
3405 3407 opts = pycompat.byteskwargs(opts)
3406 3408 if opts.get(b'changelog') or opts.get(b'manifest') or opts.get(b'dir'):
3407 3409 if rev is not None:
3408 3410 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
3409 3411 file_, rev = None, file_
3410 3412 elif rev is None:
3411 3413 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
3412 3414 r = cmdutil.openstorage(repo, b'debugdata', file_, opts)
3413 3415 r = getattr(r, '_revlog', r)
3414 3416 try:
3415 3417 sidedata = r.sidedata(r.lookup(rev))
3416 3418 except KeyError:
3417 3419 raise error.Abort(_(b'invalid revision identifier %s') % rev)
3418 3420 if sidedata:
3419 3421 sidedata = list(sidedata.items())
3420 3422 sidedata.sort()
3421 3423 ui.writenoi18n(b'%d sidedata entries\n' % len(sidedata))
3422 3424 for key, value in sidedata:
3423 3425 ui.writenoi18n(b' entry-%04o size %d\n' % (key, len(value)))
3424 3426 if ui.verbose:
3425 3427 ui.writenoi18n(b' %s\n' % stringutil.pprint(value))
3426 3428
3427 3429
3428 3430 @command(b'debugssl', [], b'[SOURCE]', optionalrepo=True)
3429 3431 def debugssl(ui, repo, source=None, **opts):
3430 3432 '''test a secure connection to a server
3431 3433
3432 3434 This builds the certificate chain for the server on Windows, installing the
3433 3435 missing intermediates and trusted root via Windows Update if necessary. It
3434 3436 does nothing on other platforms.
3435 3437
3436 3438 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
3437 3439 that server is used. See :hg:`help urls` for more information.
3438 3440
3439 3441 If the update succeeds, retry the original operation. Otherwise, the cause
3440 3442 of the SSL error is likely another issue.
3441 3443 '''
3442 3444 if not pycompat.iswindows:
3443 3445 raise error.Abort(
3444 3446 _(b'certificate chain building is only possible on Windows')
3445 3447 )
3446 3448
3447 3449 if not source:
3448 3450 if not repo:
3449 3451 raise error.Abort(
3450 3452 _(
3451 3453 b"there is no Mercurial repository here, and no "
3452 3454 b"server specified"
3453 3455 )
3454 3456 )
3455 3457 source = b"default"
3456 3458
3457 3459 source, branches = hg.parseurl(ui.expandpath(source))
3458 3460 url = util.url(source)
3459 3461
3460 3462 defaultport = {b'https': 443, b'ssh': 22}
3461 3463 if url.scheme in defaultport:
3462 3464 try:
3463 3465 addr = (url.host, int(url.port or defaultport[url.scheme]))
3464 3466 except ValueError:
3465 3467 raise error.Abort(_(b"malformed port number in URL"))
3466 3468 else:
3467 3469 raise error.Abort(_(b"only https and ssh connections are supported"))
3468 3470
3469 3471 from . import win32
3470 3472
3471 3473 s = ssl.wrap_socket(
3472 3474 socket.socket(),
3473 3475 ssl_version=ssl.PROTOCOL_TLS,
3474 3476 cert_reqs=ssl.CERT_NONE,
3475 3477 ca_certs=None,
3476 3478 )
3477 3479
3478 3480 try:
3479 3481 s.connect(addr)
3480 3482 cert = s.getpeercert(True)
3481 3483
3482 3484 ui.status(_(b'checking the certificate chain for %s\n') % url.host)
3483 3485
3484 3486 complete = win32.checkcertificatechain(cert, build=False)
3485 3487
3486 3488 if not complete:
3487 3489 ui.status(_(b'certificate chain is incomplete, updating... '))
3488 3490
3489 3491 if not win32.checkcertificatechain(cert):
3490 3492 ui.status(_(b'failed.\n'))
3491 3493 else:
3492 3494 ui.status(_(b'done.\n'))
3493 3495 else:
3494 3496 ui.status(_(b'full certificate chain is available\n'))
3495 3497 finally:
3496 3498 s.close()
3497 3499
3498 3500
3499 3501 @command(
3500 3502 b"debugbackupbundle",
3501 3503 [
3502 3504 (
3503 3505 b"",
3504 3506 b"recover",
3505 3507 b"",
3506 3508 b"brings the specified changeset back into the repository",
3507 3509 )
3508 3510 ]
3509 3511 + cmdutil.logopts,
3510 3512 _(b"hg debugbackupbundle [--recover HASH]"),
3511 3513 )
3512 3514 def debugbackupbundle(ui, repo, *pats, **opts):
3513 3515 """lists the changesets available in backup bundles
3514 3516
3515 3517 Without any arguments, this command prints a list of the changesets in each
3516 3518 backup bundle.
3517 3519
3518 3520 --recover takes a changeset hash and unbundles the first bundle that
3519 3521 contains that hash, which puts that changeset back in your repository.
3520 3522
3521 3523 --verbose will print the entire commit message and the bundle path for that
3522 3524 backup.
3523 3525 """
3524 3526 backups = list(
3525 3527 filter(
3526 3528 os.path.isfile, glob.glob(repo.vfs.join(b"strip-backup") + b"/*.hg")
3527 3529 )
3528 3530 )
3529 3531 backups.sort(key=lambda x: os.path.getmtime(x), reverse=True)
3530 3532
3531 3533 opts = pycompat.byteskwargs(opts)
3532 3534 opts[b"bundle"] = b""
3533 3535 opts[b"force"] = None
3534 3536 limit = logcmdutil.getlimit(opts)
3535 3537
3536 3538 def display(other, chlist, displayer):
3537 3539 if opts.get(b"newest_first"):
3538 3540 chlist.reverse()
3539 3541 count = 0
3540 3542 for n in chlist:
3541 3543 if limit is not None and count >= limit:
3542 3544 break
3543 3545 parents = [True for p in other.changelog.parents(n) if p != nullid]
3544 3546 if opts.get(b"no_merges") and len(parents) == 2:
3545 3547 continue
3546 3548 count += 1
3547 3549 displayer.show(other[n])
3548 3550
3549 3551 recovernode = opts.get(b"recover")
3550 3552 if recovernode:
3551 3553 if scmutil.isrevsymbol(repo, recovernode):
3552 3554 ui.warn(_(b"%s already exists in the repo\n") % recovernode)
3553 3555 return
3554 3556 elif backups:
3555 3557 msg = _(
3556 3558 b"Recover changesets using: hg debugbackupbundle --recover "
3557 3559 b"<changeset hash>\n\nAvailable backup changesets:"
3558 3560 )
3559 3561 ui.status(msg, label=b"status.removed")
3560 3562 else:
3561 3563 ui.status(_(b"no backup changesets found\n"))
3562 3564 return
3563 3565
3564 3566 for backup in backups:
3565 3567 # Much of this is copied from the hg incoming logic
3566 3568 source = ui.expandpath(os.path.relpath(backup, encoding.getcwd()))
3567 3569 source, branches = hg.parseurl(source, opts.get(b"branch"))
3568 3570 try:
3569 3571 other = hg.peer(repo, opts, source)
3570 3572 except error.LookupError as ex:
3571 3573 msg = _(b"\nwarning: unable to open bundle %s") % source
3572 3574 hint = _(b"\n(missing parent rev %s)\n") % short(ex.name)
3573 3575 ui.warn(msg, hint=hint)
3574 3576 continue
3575 3577 revs, checkout = hg.addbranchrevs(
3576 3578 repo, other, branches, opts.get(b"rev")
3577 3579 )
3578 3580
3579 3581 if revs:
3580 3582 revs = [other.lookup(rev) for rev in revs]
3581 3583
3582 3584 quiet = ui.quiet
3583 3585 try:
3584 3586 ui.quiet = True
3585 3587 other, chlist, cleanupfn = bundlerepo.getremotechanges(
3586 3588 ui, repo, other, revs, opts[b"bundle"], opts[b"force"]
3587 3589 )
3588 3590 except error.LookupError:
3589 3591 continue
3590 3592 finally:
3591 3593 ui.quiet = quiet
3592 3594
3593 3595 try:
3594 3596 if not chlist:
3595 3597 continue
3596 3598 if recovernode:
3597 3599 with repo.lock(), repo.transaction(b"unbundle") as tr:
3598 3600 if scmutil.isrevsymbol(other, recovernode):
3599 3601 ui.status(_(b"Unbundling %s\n") % (recovernode))
3600 3602 f = hg.openpath(ui, source)
3601 3603 gen = exchange.readbundle(ui, f, source)
3602 3604 if isinstance(gen, bundle2.unbundle20):
3603 3605 bundle2.applybundle(
3604 3606 repo,
3605 3607 gen,
3606 3608 tr,
3607 3609 source=b"unbundle",
3608 3610 url=b"bundle:" + source,
3609 3611 )
3610 3612 else:
3611 3613 gen.apply(repo, b"unbundle", b"bundle:" + source)
3612 3614 break
3613 3615 else:
3614 3616 backupdate = encoding.strtolocal(
3615 3617 time.strftime(
3616 3618 "%a %H:%M, %Y-%m-%d",
3617 3619 time.localtime(os.path.getmtime(source)),
3618 3620 )
3619 3621 )
3620 3622 ui.status(b"\n%s\n" % (backupdate.ljust(50)))
3621 3623 if ui.verbose:
3622 3624 ui.status(b"%s%s\n" % (b"bundle:".ljust(13), source))
3623 3625 else:
3624 3626 opts[
3625 3627 b"template"
3626 3628 ] = b"{label('status.modified', node|short)} {desc|firstline}\n"
3627 3629 displayer = logcmdutil.changesetdisplayer(
3628 3630 ui, other, opts, False
3629 3631 )
3630 3632 display(other, chlist, displayer)
3631 3633 displayer.close()
3632 3634 finally:
3633 3635 cleanupfn()
3634 3636
3635 3637
3636 3638 @command(
3637 3639 b'debugsub',
3638 3640 [(b'r', b'rev', b'', _(b'revision to check'), _(b'REV'))],
3639 3641 _(b'[-r REV] [REV]'),
3640 3642 )
3641 3643 def debugsub(ui, repo, rev=None):
3642 3644 ctx = scmutil.revsingle(repo, rev, None)
3643 3645 for k, v in sorted(ctx.substate.items()):
3644 3646 ui.writenoi18n(b'path %s\n' % k)
3645 3647 ui.writenoi18n(b' source %s\n' % v[0])
3646 3648 ui.writenoi18n(b' revision %s\n' % v[1])
3647 3649
3648 3650
3649 3651 @command(
3650 3652 b'debugsuccessorssets',
3651 3653 [(b'', b'closest', False, _(b'return closest successors sets only'))],
3652 3654 _(b'[REV]'),
3653 3655 )
3654 3656 def debugsuccessorssets(ui, repo, *revs, **opts):
3655 3657 """show set of successors for revision
3656 3658
3657 3659 A successors set of changeset A is a consistent group of revisions that
3658 3660 succeed A. It contains non-obsolete changesets only unless closests
3659 3661 successors set is set.
3660 3662
3661 3663 In most cases a changeset A has a single successors set containing a single
3662 3664 successor (changeset A replaced by A').
3663 3665
3664 3666 A changeset that is made obsolete with no successors are called "pruned".
3665 3667 Such changesets have no successors sets at all.
3666 3668
3667 3669 A changeset that has been "split" will have a successors set containing
3668 3670 more than one successor.
3669 3671
3670 3672 A changeset that has been rewritten in multiple different ways is called
3671 3673 "divergent". Such changesets have multiple successor sets (each of which
3672 3674 may also be split, i.e. have multiple successors).
3673 3675
3674 3676 Results are displayed as follows::
3675 3677
3676 3678 <rev1>
3677 3679 <successors-1A>
3678 3680 <rev2>
3679 3681 <successors-2A>
3680 3682 <successors-2B1> <successors-2B2> <successors-2B3>
3681 3683
3682 3684 Here rev2 has two possible (i.e. divergent) successors sets. The first
3683 3685 holds one element, whereas the second holds three (i.e. the changeset has
3684 3686 been split).
3685 3687 """
3686 3688 # passed to successorssets caching computation from one call to another
3687 3689 cache = {}
3688 3690 ctx2str = bytes
3689 3691 node2str = short
3690 3692 for rev in scmutil.revrange(repo, revs):
3691 3693 ctx = repo[rev]
3692 3694 ui.write(b'%s\n' % ctx2str(ctx))
3693 3695 for succsset in obsutil.successorssets(
3694 3696 repo, ctx.node(), closest=opts['closest'], cache=cache
3695 3697 ):
3696 3698 if succsset:
3697 3699 ui.write(b' ')
3698 3700 ui.write(node2str(succsset[0]))
3699 3701 for node in succsset[1:]:
3700 3702 ui.write(b' ')
3701 3703 ui.write(node2str(node))
3702 3704 ui.write(b'\n')
3703 3705
3704 3706
3705 3707 @command(b'debugtagscache', [])
3706 3708 def debugtagscache(ui, repo):
3707 3709 """display the contents of .hg/cache/hgtagsfnodes1"""
3708 3710 cache = tagsmod.hgtagsfnodescache(repo.unfiltered())
3709 3711 for r in repo:
3710 3712 node = repo[r].node()
3711 3713 tagsnode = cache.getfnode(node, computemissing=False)
3712 3714 tagsnodedisplay = hex(tagsnode) if tagsnode else b'missing/invalid'
3713 3715 ui.write(b'%d %s %s\n' % (r, hex(node), tagsnodedisplay))
3714 3716
3715 3717
3716 3718 @command(
3717 3719 b'debugtemplate',
3718 3720 [
3719 3721 (b'r', b'rev', [], _(b'apply template on changesets'), _(b'REV')),
3720 3722 (b'D', b'define', [], _(b'define template keyword'), _(b'KEY=VALUE')),
3721 3723 ],
3722 3724 _(b'[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
3723 3725 optionalrepo=True,
3724 3726 )
3725 3727 def debugtemplate(ui, repo, tmpl, **opts):
3726 3728 """parse and apply a template
3727 3729
3728 3730 If -r/--rev is given, the template is processed as a log template and
3729 3731 applied to the given changesets. Otherwise, it is processed as a generic
3730 3732 template.
3731 3733
3732 3734 Use --verbose to print the parsed tree.
3733 3735 """
3734 3736 revs = None
3735 3737 if opts['rev']:
3736 3738 if repo is None:
3737 3739 raise error.RepoError(
3738 3740 _(b'there is no Mercurial repository here (.hg not found)')
3739 3741 )
3740 3742 revs = scmutil.revrange(repo, opts['rev'])
3741 3743
3742 3744 props = {}
3743 3745 for d in opts['define']:
3744 3746 try:
3745 3747 k, v = (e.strip() for e in d.split(b'=', 1))
3746 3748 if not k or k == b'ui':
3747 3749 raise ValueError
3748 3750 props[k] = v
3749 3751 except ValueError:
3750 3752 raise error.Abort(_(b'malformed keyword definition: %s') % d)
3751 3753
3752 3754 if ui.verbose:
3753 3755 aliases = ui.configitems(b'templatealias')
3754 3756 tree = templater.parse(tmpl)
3755 3757 ui.note(templater.prettyformat(tree), b'\n')
3756 3758 newtree = templater.expandaliases(tree, aliases)
3757 3759 if newtree != tree:
3758 3760 ui.notenoi18n(
3759 3761 b"* expanded:\n", templater.prettyformat(newtree), b'\n'
3760 3762 )
3761 3763
3762 3764 if revs is None:
3763 3765 tres = formatter.templateresources(ui, repo)
3764 3766 t = formatter.maketemplater(ui, tmpl, resources=tres)
3765 3767 if ui.verbose:
3766 3768 kwds, funcs = t.symbolsuseddefault()
3767 3769 ui.writenoi18n(b"* keywords: %s\n" % b', '.join(sorted(kwds)))
3768 3770 ui.writenoi18n(b"* functions: %s\n" % b', '.join(sorted(funcs)))
3769 3771 ui.write(t.renderdefault(props))
3770 3772 else:
3771 3773 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
3772 3774 if ui.verbose:
3773 3775 kwds, funcs = displayer.t.symbolsuseddefault()
3774 3776 ui.writenoi18n(b"* keywords: %s\n" % b', '.join(sorted(kwds)))
3775 3777 ui.writenoi18n(b"* functions: %s\n" % b', '.join(sorted(funcs)))
3776 3778 for r in revs:
3777 3779 displayer.show(repo[r], **pycompat.strkwargs(props))
3778 3780 displayer.close()
3779 3781
3780 3782
3781 3783 @command(
3782 3784 b'debuguigetpass',
3783 3785 [(b'p', b'prompt', b'', _(b'prompt text'), _(b'TEXT')),],
3784 3786 _(b'[-p TEXT]'),
3785 3787 norepo=True,
3786 3788 )
3787 3789 def debuguigetpass(ui, prompt=b''):
3788 3790 """show prompt to type password"""
3789 3791 r = ui.getpass(prompt)
3790 3792 ui.writenoi18n(b'response: %s\n' % r)
3791 3793
3792 3794
3793 3795 @command(
3794 3796 b'debuguiprompt',
3795 3797 [(b'p', b'prompt', b'', _(b'prompt text'), _(b'TEXT')),],
3796 3798 _(b'[-p TEXT]'),
3797 3799 norepo=True,
3798 3800 )
3799 3801 def debuguiprompt(ui, prompt=b''):
3800 3802 """show plain prompt"""
3801 3803 r = ui.prompt(prompt)
3802 3804 ui.writenoi18n(b'response: %s\n' % r)
3803 3805
3804 3806
3805 3807 @command(b'debugupdatecaches', [])
3806 3808 def debugupdatecaches(ui, repo, *pats, **opts):
3807 3809 """warm all known caches in the repository"""
3808 3810 with repo.wlock(), repo.lock():
3809 3811 repo.updatecaches(full=True)
3810 3812
3811 3813
3812 3814 @command(
3813 3815 b'debugupgraderepo',
3814 3816 [
3815 3817 (
3816 3818 b'o',
3817 3819 b'optimize',
3818 3820 [],
3819 3821 _(b'extra optimization to perform'),
3820 3822 _(b'NAME'),
3821 3823 ),
3822 3824 (b'', b'run', False, _(b'performs an upgrade')),
3823 3825 (b'', b'backup', True, _(b'keep the old repository content around')),
3824 3826 (b'', b'changelog', None, _(b'select the changelog for upgrade')),
3825 3827 (b'', b'manifest', None, _(b'select the manifest for upgrade')),
3826 3828 ],
3827 3829 )
3828 3830 def debugupgraderepo(ui, repo, run=False, optimize=None, backup=True, **opts):
3829 3831 """upgrade a repository to use different features
3830 3832
3831 3833 If no arguments are specified, the repository is evaluated for upgrade
3832 3834 and a list of problems and potential optimizations is printed.
3833 3835
3834 3836 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
3835 3837 can be influenced via additional arguments. More details will be provided
3836 3838 by the command output when run without ``--run``.
3837 3839
3838 3840 During the upgrade, the repository will be locked and no writes will be
3839 3841 allowed.
3840 3842
3841 3843 At the end of the upgrade, the repository may not be readable while new
3842 3844 repository data is swapped in. This window will be as long as it takes to
3843 3845 rename some directories inside the ``.hg`` directory. On most machines, this
3844 3846 should complete almost instantaneously and the chances of a consumer being
3845 3847 unable to access the repository should be low.
3846 3848
3847 3849 By default, all revlog will be upgraded. You can restrict this using flag
3848 3850 such as `--manifest`:
3849 3851
3850 3852 * `--manifest`: only optimize the manifest
3851 3853 * `--no-manifest`: optimize all revlog but the manifest
3852 3854 * `--changelog`: optimize the changelog only
3853 3855 * `--no-changelog --no-manifest`: optimize filelogs only
3854 3856 """
3855 3857 return upgrade.upgraderepo(
3856 3858 ui, repo, run=run, optimize=optimize, backup=backup, **opts
3857 3859 )
3858 3860
3859 3861
3860 3862 @command(
3861 3863 b'debugwalk', cmdutil.walkopts, _(b'[OPTION]... [FILE]...'), inferrepo=True
3862 3864 )
3863 3865 def debugwalk(ui, repo, *pats, **opts):
3864 3866 """show how files match on given patterns"""
3865 3867 opts = pycompat.byteskwargs(opts)
3866 3868 m = scmutil.match(repo[None], pats, opts)
3867 3869 if ui.verbose:
3868 3870 ui.writenoi18n(b'* matcher:\n', stringutil.prettyrepr(m), b'\n')
3869 3871 items = list(repo[None].walk(m))
3870 3872 if not items:
3871 3873 return
3872 3874 f = lambda fn: fn
3873 3875 if ui.configbool(b'ui', b'slash') and pycompat.ossep != b'/':
3874 3876 f = lambda fn: util.normpath(fn)
3875 3877 fmt = b'f %%-%ds %%-%ds %%s' % (
3876 3878 max([len(abs) for abs in items]),
3877 3879 max([len(repo.pathto(abs)) for abs in items]),
3878 3880 )
3879 3881 for abs in items:
3880 3882 line = fmt % (
3881 3883 abs,
3882 3884 f(repo.pathto(abs)),
3883 3885 m.exact(abs) and b'exact' or b'',
3884 3886 )
3885 3887 ui.write(b"%s\n" % line.rstrip())
3886 3888
3887 3889
3888 3890 @command(b'debugwhyunstable', [], _(b'REV'))
3889 3891 def debugwhyunstable(ui, repo, rev):
3890 3892 """explain instabilities of a changeset"""
3891 3893 for entry in obsutil.whyunstable(repo, scmutil.revsingle(repo, rev)):
3892 3894 dnodes = b''
3893 3895 if entry.get(b'divergentnodes'):
3894 3896 dnodes = (
3895 3897 b' '.join(
3896 3898 b'%s (%s)' % (ctx.hex(), ctx.phasestr())
3897 3899 for ctx in entry[b'divergentnodes']
3898 3900 )
3899 3901 + b' '
3900 3902 )
3901 3903 ui.write(
3902 3904 b'%s: %s%s %s\n'
3903 3905 % (entry[b'instability'], dnodes, entry[b'reason'], entry[b'node'])
3904 3906 )
3905 3907
3906 3908
3907 3909 @command(
3908 3910 b'debugwireargs',
3909 3911 [
3910 3912 (b'', b'three', b'', b'three'),
3911 3913 (b'', b'four', b'', b'four'),
3912 3914 (b'', b'five', b'', b'five'),
3913 3915 ]
3914 3916 + cmdutil.remoteopts,
3915 3917 _(b'REPO [OPTIONS]... [ONE [TWO]]'),
3916 3918 norepo=True,
3917 3919 )
3918 3920 def debugwireargs(ui, repopath, *vals, **opts):
3919 3921 opts = pycompat.byteskwargs(opts)
3920 3922 repo = hg.peer(ui, opts, repopath)
3921 3923 for opt in cmdutil.remoteopts:
3922 3924 del opts[opt[1]]
3923 3925 args = {}
3924 3926 for k, v in pycompat.iteritems(opts):
3925 3927 if v:
3926 3928 args[k] = v
3927 3929 args = pycompat.strkwargs(args)
3928 3930 # run twice to check that we don't mess up the stream for the next command
3929 3931 res1 = repo.debugwireargs(*vals, **args)
3930 3932 res2 = repo.debugwireargs(*vals, **args)
3931 3933 ui.write(b"%s\n" % res1)
3932 3934 if res1 != res2:
3933 3935 ui.warn(b"%s\n" % res2)
3934 3936
3935 3937
3936 3938 def _parsewirelangblocks(fh):
3937 3939 activeaction = None
3938 3940 blocklines = []
3939 3941 lastindent = 0
3940 3942
3941 3943 for line in fh:
3942 3944 line = line.rstrip()
3943 3945 if not line:
3944 3946 continue
3945 3947
3946 3948 if line.startswith(b'#'):
3947 3949 continue
3948 3950
3949 3951 if not line.startswith(b' '):
3950 3952 # New block. Flush previous one.
3951 3953 if activeaction:
3952 3954 yield activeaction, blocklines
3953 3955
3954 3956 activeaction = line
3955 3957 blocklines = []
3956 3958 lastindent = 0
3957 3959 continue
3958 3960
3959 3961 # Else we start with an indent.
3960 3962
3961 3963 if not activeaction:
3962 3964 raise error.Abort(_(b'indented line outside of block'))
3963 3965
3964 3966 indent = len(line) - len(line.lstrip())
3965 3967
3966 3968 # If this line is indented more than the last line, concatenate it.
3967 3969 if indent > lastindent and blocklines:
3968 3970 blocklines[-1] += line.lstrip()
3969 3971 else:
3970 3972 blocklines.append(line)
3971 3973 lastindent = indent
3972 3974
3973 3975 # Flush last block.
3974 3976 if activeaction:
3975 3977 yield activeaction, blocklines
3976 3978
3977 3979
3978 3980 @command(
3979 3981 b'debugwireproto',
3980 3982 [
3981 3983 (b'', b'localssh', False, _(b'start an SSH server for this repo')),
3982 3984 (b'', b'peer', b'', _(b'construct a specific version of the peer')),
3983 3985 (
3984 3986 b'',
3985 3987 b'noreadstderr',
3986 3988 False,
3987 3989 _(b'do not read from stderr of the remote'),
3988 3990 ),
3989 3991 (
3990 3992 b'',
3991 3993 b'nologhandshake',
3992 3994 False,
3993 3995 _(b'do not log I/O related to the peer handshake'),
3994 3996 ),
3995 3997 ]
3996 3998 + cmdutil.remoteopts,
3997 3999 _(b'[PATH]'),
3998 4000 optionalrepo=True,
3999 4001 )
4000 4002 def debugwireproto(ui, repo, path=None, **opts):
4001 4003 """send wire protocol commands to a server
4002 4004
4003 4005 This command can be used to issue wire protocol commands to remote
4004 4006 peers and to debug the raw data being exchanged.
4005 4007
4006 4008 ``--localssh`` will start an SSH server against the current repository
4007 4009 and connect to that. By default, the connection will perform a handshake
4008 4010 and establish an appropriate peer instance.
4009 4011
4010 4012 ``--peer`` can be used to bypass the handshake protocol and construct a
4011 4013 peer instance using the specified class type. Valid values are ``raw``,
4012 4014 ``http2``, ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending
4013 4015 raw data payloads and don't support higher-level command actions.
4014 4016
4015 4017 ``--noreadstderr`` can be used to disable automatic reading from stderr
4016 4018 of the peer (for SSH connections only). Disabling automatic reading of
4017 4019 stderr is useful for making output more deterministic.
4018 4020
4019 4021 Commands are issued via a mini language which is specified via stdin.
4020 4022 The language consists of individual actions to perform. An action is
4021 4023 defined by a block. A block is defined as a line with no leading
4022 4024 space followed by 0 or more lines with leading space. Blocks are
4023 4025 effectively a high-level command with additional metadata.
4024 4026
4025 4027 Lines beginning with ``#`` are ignored.
4026 4028
4027 4029 The following sections denote available actions.
4028 4030
4029 4031 raw
4030 4032 ---
4031 4033
4032 4034 Send raw data to the server.
4033 4035
4034 4036 The block payload contains the raw data to send as one atomic send
4035 4037 operation. The data may not actually be delivered in a single system
4036 4038 call: it depends on the abilities of the transport being used.
4037 4039
4038 4040 Each line in the block is de-indented and concatenated. Then, that
4039 4041 value is evaluated as a Python b'' literal. This allows the use of
4040 4042 backslash escaping, etc.
4041 4043
4042 4044 raw+
4043 4045 ----
4044 4046
4045 4047 Behaves like ``raw`` except flushes output afterwards.
4046 4048
4047 4049 command <X>
4048 4050 -----------
4049 4051
4050 4052 Send a request to run a named command, whose name follows the ``command``
4051 4053 string.
4052 4054
4053 4055 Arguments to the command are defined as lines in this block. The format of
4054 4056 each line is ``<key> <value>``. e.g.::
4055 4057
4056 4058 command listkeys
4057 4059 namespace bookmarks
4058 4060
4059 4061 If the value begins with ``eval:``, it will be interpreted as a Python
4060 4062 literal expression. Otherwise values are interpreted as Python b'' literals.
4061 4063 This allows sending complex types and encoding special byte sequences via
4062 4064 backslash escaping.
4063 4065
4064 4066 The following arguments have special meaning:
4065 4067
4066 4068 ``PUSHFILE``
4067 4069 When defined, the *push* mechanism of the peer will be used instead
4068 4070 of the static request-response mechanism and the content of the
4069 4071 file specified in the value of this argument will be sent as the
4070 4072 command payload.
4071 4073
4072 4074 This can be used to submit a local bundle file to the remote.
4073 4075
4074 4076 batchbegin
4075 4077 ----------
4076 4078
4077 4079 Instruct the peer to begin a batched send.
4078 4080
4079 4081 All ``command`` blocks are queued for execution until the next
4080 4082 ``batchsubmit`` block.
4081 4083
4082 4084 batchsubmit
4083 4085 -----------
4084 4086
4085 4087 Submit previously queued ``command`` blocks as a batch request.
4086 4088
4087 4089 This action MUST be paired with a ``batchbegin`` action.
4088 4090
4089 4091 httprequest <method> <path>
4090 4092 ---------------------------
4091 4093
4092 4094 (HTTP peer only)
4093 4095
4094 4096 Send an HTTP request to the peer.
4095 4097
4096 4098 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
4097 4099
4098 4100 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
4099 4101 headers to add to the request. e.g. ``Accept: foo``.
4100 4102
4101 4103 The following arguments are special:
4102 4104
4103 4105 ``BODYFILE``
4104 4106 The content of the file defined as the value to this argument will be
4105 4107 transferred verbatim as the HTTP request body.
4106 4108
4107 4109 ``frame <type> <flags> <payload>``
4108 4110 Send a unified protocol frame as part of the request body.
4109 4111
4110 4112 All frames will be collected and sent as the body to the HTTP
4111 4113 request.
4112 4114
4113 4115 close
4114 4116 -----
4115 4117
4116 4118 Close the connection to the server.
4117 4119
4118 4120 flush
4119 4121 -----
4120 4122
4121 4123 Flush data written to the server.
4122 4124
4123 4125 readavailable
4124 4126 -------------
4125 4127
4126 4128 Close the write end of the connection and read all available data from
4127 4129 the server.
4128 4130
4129 4131 If the connection to the server encompasses multiple pipes, we poll both
4130 4132 pipes and read available data.
4131 4133
4132 4134 readline
4133 4135 --------
4134 4136
4135 4137 Read a line of output from the server. If there are multiple output
4136 4138 pipes, reads only the main pipe.
4137 4139
4138 4140 ereadline
4139 4141 ---------
4140 4142
4141 4143 Like ``readline``, but read from the stderr pipe, if available.
4142 4144
4143 4145 read <X>
4144 4146 --------
4145 4147
4146 4148 ``read()`` N bytes from the server's main output pipe.
4147 4149
4148 4150 eread <X>
4149 4151 ---------
4150 4152
4151 4153 ``read()`` N bytes from the server's stderr pipe, if available.
4152 4154
4153 4155 Specifying Unified Frame-Based Protocol Frames
4154 4156 ----------------------------------------------
4155 4157
4156 4158 It is possible to emit a *Unified Frame-Based Protocol* by using special
4157 4159 syntax.
4158 4160
4159 4161 A frame is composed as a type, flags, and payload. These can be parsed
4160 4162 from a string of the form:
4161 4163
4162 4164 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
4163 4165
4164 4166 ``request-id`` and ``stream-id`` are integers defining the request and
4165 4167 stream identifiers.
4166 4168
4167 4169 ``type`` can be an integer value for the frame type or the string name
4168 4170 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
4169 4171 ``command-name``.
4170 4172
4171 4173 ``stream-flags`` and ``flags`` are a ``|`` delimited list of flag
4172 4174 components. Each component (and there can be just one) can be an integer
4173 4175 or a flag name for stream flags or frame flags, respectively. Values are
4174 4176 resolved to integers and then bitwise OR'd together.
4175 4177
4176 4178 ``payload`` represents the raw frame payload. If it begins with
4177 4179 ``cbor:``, the following string is evaluated as Python code and the
4178 4180 resulting object is fed into a CBOR encoder. Otherwise it is interpreted
4179 4181 as a Python byte string literal.
4180 4182 """
4181 4183 opts = pycompat.byteskwargs(opts)
4182 4184
4183 4185 if opts[b'localssh'] and not repo:
4184 4186 raise error.Abort(_(b'--localssh requires a repository'))
4185 4187
4186 4188 if opts[b'peer'] and opts[b'peer'] not in (
4187 4189 b'raw',
4188 4190 b'http2',
4189 4191 b'ssh1',
4190 4192 b'ssh2',
4191 4193 ):
4192 4194 raise error.Abort(
4193 4195 _(b'invalid value for --peer'),
4194 4196 hint=_(b'valid values are "raw", "ssh1", and "ssh2"'),
4195 4197 )
4196 4198
4197 4199 if path and opts[b'localssh']:
4198 4200 raise error.Abort(_(b'cannot specify --localssh with an explicit path'))
4199 4201
4200 4202 if ui.interactive():
4201 4203 ui.write(_(b'(waiting for commands on stdin)\n'))
4202 4204
4203 4205 blocks = list(_parsewirelangblocks(ui.fin))
4204 4206
4205 4207 proc = None
4206 4208 stdin = None
4207 4209 stdout = None
4208 4210 stderr = None
4209 4211 opener = None
4210 4212
4211 4213 if opts[b'localssh']:
4212 4214 # We start the SSH server in its own process so there is process
4213 4215 # separation. This prevents a whole class of potential bugs around
4214 4216 # shared state from interfering with server operation.
4215 4217 args = procutil.hgcmd() + [
4216 4218 b'-R',
4217 4219 repo.root,
4218 4220 b'debugserve',
4219 4221 b'--sshstdio',
4220 4222 ]
4221 4223 proc = subprocess.Popen(
4222 4224 pycompat.rapply(procutil.tonativestr, args),
4223 4225 stdin=subprocess.PIPE,
4224 4226 stdout=subprocess.PIPE,
4225 4227 stderr=subprocess.PIPE,
4226 4228 bufsize=0,
4227 4229 )
4228 4230
4229 4231 stdin = proc.stdin
4230 4232 stdout = proc.stdout
4231 4233 stderr = proc.stderr
4232 4234
4233 4235 # We turn the pipes into observers so we can log I/O.
4234 4236 if ui.verbose or opts[b'peer'] == b'raw':
4235 4237 stdin = util.makeloggingfileobject(
4236 4238 ui, proc.stdin, b'i', logdata=True
4237 4239 )
4238 4240 stdout = util.makeloggingfileobject(
4239 4241 ui, proc.stdout, b'o', logdata=True
4240 4242 )
4241 4243 stderr = util.makeloggingfileobject(
4242 4244 ui, proc.stderr, b'e', logdata=True
4243 4245 )
4244 4246
4245 4247 # --localssh also implies the peer connection settings.
4246 4248
4247 4249 url = b'ssh://localserver'
4248 4250 autoreadstderr = not opts[b'noreadstderr']
4249 4251
4250 4252 if opts[b'peer'] == b'ssh1':
4251 4253 ui.write(_(b'creating ssh peer for wire protocol version 1\n'))
4252 4254 peer = sshpeer.sshv1peer(
4253 4255 ui,
4254 4256 url,
4255 4257 proc,
4256 4258 stdin,
4257 4259 stdout,
4258 4260 stderr,
4259 4261 None,
4260 4262 autoreadstderr=autoreadstderr,
4261 4263 )
4262 4264 elif opts[b'peer'] == b'ssh2':
4263 4265 ui.write(_(b'creating ssh peer for wire protocol version 2\n'))
4264 4266 peer = sshpeer.sshv2peer(
4265 4267 ui,
4266 4268 url,
4267 4269 proc,
4268 4270 stdin,
4269 4271 stdout,
4270 4272 stderr,
4271 4273 None,
4272 4274 autoreadstderr=autoreadstderr,
4273 4275 )
4274 4276 elif opts[b'peer'] == b'raw':
4275 4277 ui.write(_(b'using raw connection to peer\n'))
4276 4278 peer = None
4277 4279 else:
4278 4280 ui.write(_(b'creating ssh peer from handshake results\n'))
4279 4281 peer = sshpeer.makepeer(
4280 4282 ui,
4281 4283 url,
4282 4284 proc,
4283 4285 stdin,
4284 4286 stdout,
4285 4287 stderr,
4286 4288 autoreadstderr=autoreadstderr,
4287 4289 )
4288 4290
4289 4291 elif path:
4290 4292 # We bypass hg.peer() so we can proxy the sockets.
4291 4293 # TODO consider not doing this because we skip
4292 4294 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
4293 4295 u = util.url(path)
4294 4296 if u.scheme != b'http':
4295 4297 raise error.Abort(_(b'only http:// paths are currently supported'))
4296 4298
4297 4299 url, authinfo = u.authinfo()
4298 4300 openerargs = {
4299 4301 'useragent': b'Mercurial debugwireproto',
4300 4302 }
4301 4303
4302 4304 # Turn pipes/sockets into observers so we can log I/O.
4303 4305 if ui.verbose:
4304 4306 openerargs.update(
4305 4307 {
4306 4308 'loggingfh': ui,
4307 4309 'loggingname': b's',
4308 4310 'loggingopts': {'logdata': True, 'logdataapis': False,},
4309 4311 }
4310 4312 )
4311 4313
4312 4314 if ui.debugflag:
4313 4315 openerargs['loggingopts']['logdataapis'] = True
4314 4316
4315 4317 # Don't send default headers when in raw mode. This allows us to
4316 4318 # bypass most of the behavior of our URL handling code so we can
4317 4319 # have near complete control over what's sent on the wire.
4318 4320 if opts[b'peer'] == b'raw':
4319 4321 openerargs['sendaccept'] = False
4320 4322
4321 4323 opener = urlmod.opener(ui, authinfo, **openerargs)
4322 4324
4323 4325 if opts[b'peer'] == b'http2':
4324 4326 ui.write(_(b'creating http peer for wire protocol version 2\n'))
4325 4327 # We go through makepeer() because we need an API descriptor for
4326 4328 # the peer instance to be useful.
4327 4329 with ui.configoverride(
4328 4330 {(b'experimental', b'httppeer.advertise-v2'): True}
4329 4331 ):
4330 4332 if opts[b'nologhandshake']:
4331 4333 ui.pushbuffer()
4332 4334
4333 4335 peer = httppeer.makepeer(ui, path, opener=opener)
4334 4336
4335 4337 if opts[b'nologhandshake']:
4336 4338 ui.popbuffer()
4337 4339
4338 4340 if not isinstance(peer, httppeer.httpv2peer):
4339 4341 raise error.Abort(
4340 4342 _(
4341 4343 b'could not instantiate HTTP peer for '
4342 4344 b'wire protocol version 2'
4343 4345 ),
4344 4346 hint=_(
4345 4347 b'the server may not have the feature '
4346 4348 b'enabled or is not allowing this '
4347 4349 b'client version'
4348 4350 ),
4349 4351 )
4350 4352
4351 4353 elif opts[b'peer'] == b'raw':
4352 4354 ui.write(_(b'using raw connection to peer\n'))
4353 4355 peer = None
4354 4356 elif opts[b'peer']:
4355 4357 raise error.Abort(
4356 4358 _(b'--peer %s not supported with HTTP peers') % opts[b'peer']
4357 4359 )
4358 4360 else:
4359 4361 peer = httppeer.makepeer(ui, path, opener=opener)
4360 4362
4361 4363 # We /could/ populate stdin/stdout with sock.makefile()...
4362 4364 else:
4363 4365 raise error.Abort(_(b'unsupported connection configuration'))
4364 4366
4365 4367 batchedcommands = None
4366 4368
4367 4369 # Now perform actions based on the parsed wire language instructions.
4368 4370 for action, lines in blocks:
4369 4371 if action in (b'raw', b'raw+'):
4370 4372 if not stdin:
4371 4373 raise error.Abort(_(b'cannot call raw/raw+ on this peer'))
4372 4374
4373 4375 # Concatenate the data together.
4374 4376 data = b''.join(l.lstrip() for l in lines)
4375 4377 data = stringutil.unescapestr(data)
4376 4378 stdin.write(data)
4377 4379
4378 4380 if action == b'raw+':
4379 4381 stdin.flush()
4380 4382 elif action == b'flush':
4381 4383 if not stdin:
4382 4384 raise error.Abort(_(b'cannot call flush on this peer'))
4383 4385 stdin.flush()
4384 4386 elif action.startswith(b'command'):
4385 4387 if not peer:
4386 4388 raise error.Abort(
4387 4389 _(
4388 4390 b'cannot send commands unless peer instance '
4389 4391 b'is available'
4390 4392 )
4391 4393 )
4392 4394
4393 4395 command = action.split(b' ', 1)[1]
4394 4396
4395 4397 args = {}
4396 4398 for line in lines:
4397 4399 # We need to allow empty values.
4398 4400 fields = line.lstrip().split(b' ', 1)
4399 4401 if len(fields) == 1:
4400 4402 key = fields[0]
4401 4403 value = b''
4402 4404 else:
4403 4405 key, value = fields
4404 4406
4405 4407 if value.startswith(b'eval:'):
4406 4408 value = stringutil.evalpythonliteral(value[5:])
4407 4409 else:
4408 4410 value = stringutil.unescapestr(value)
4409 4411
4410 4412 args[key] = value
4411 4413
4412 4414 if batchedcommands is not None:
4413 4415 batchedcommands.append((command, args))
4414 4416 continue
4415 4417
4416 4418 ui.status(_(b'sending %s command\n') % command)
4417 4419
4418 4420 if b'PUSHFILE' in args:
4419 4421 with open(args[b'PUSHFILE'], 'rb') as fh:
4420 4422 del args[b'PUSHFILE']
4421 4423 res, output = peer._callpush(
4422 4424 command, fh, **pycompat.strkwargs(args)
4423 4425 )
4424 4426 ui.status(_(b'result: %s\n') % stringutil.escapestr(res))
4425 4427 ui.status(
4426 4428 _(b'remote output: %s\n') % stringutil.escapestr(output)
4427 4429 )
4428 4430 else:
4429 4431 with peer.commandexecutor() as e:
4430 4432 res = e.callcommand(command, args).result()
4431 4433
4432 4434 if isinstance(res, wireprotov2peer.commandresponse):
4433 4435 val = res.objects()
4434 4436 ui.status(
4435 4437 _(b'response: %s\n')
4436 4438 % stringutil.pprint(val, bprefix=True, indent=2)
4437 4439 )
4438 4440 else:
4439 4441 ui.status(
4440 4442 _(b'response: %s\n')
4441 4443 % stringutil.pprint(res, bprefix=True, indent=2)
4442 4444 )
4443 4445
4444 4446 elif action == b'batchbegin':
4445 4447 if batchedcommands is not None:
4446 4448 raise error.Abort(_(b'nested batchbegin not allowed'))
4447 4449
4448 4450 batchedcommands = []
4449 4451 elif action == b'batchsubmit':
4450 4452 # There is a batching API we could go through. But it would be
4451 4453 # difficult to normalize requests into function calls. It is easier
4452 4454 # to bypass this layer and normalize to commands + args.
4453 4455 ui.status(
4454 4456 _(b'sending batch with %d sub-commands\n')
4455 4457 % len(batchedcommands)
4456 4458 )
4457 4459 assert peer is not None
4458 4460 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
4459 4461 ui.status(
4460 4462 _(b'response #%d: %s\n') % (i, stringutil.escapestr(chunk))
4461 4463 )
4462 4464
4463 4465 batchedcommands = None
4464 4466
4465 4467 elif action.startswith(b'httprequest '):
4466 4468 if not opener:
4467 4469 raise error.Abort(
4468 4470 _(b'cannot use httprequest without an HTTP peer')
4469 4471 )
4470 4472
4471 4473 request = action.split(b' ', 2)
4472 4474 if len(request) != 3:
4473 4475 raise error.Abort(
4474 4476 _(
4475 4477 b'invalid httprequest: expected format is '
4476 4478 b'"httprequest <method> <path>'
4477 4479 )
4478 4480 )
4479 4481
4480 4482 method, httppath = request[1:]
4481 4483 headers = {}
4482 4484 body = None
4483 4485 frames = []
4484 4486 for line in lines:
4485 4487 line = line.lstrip()
4486 4488 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
4487 4489 if m:
4488 4490 # Headers need to use native strings.
4489 4491 key = pycompat.strurl(m.group(1))
4490 4492 value = pycompat.strurl(m.group(2))
4491 4493 headers[key] = value
4492 4494 continue
4493 4495
4494 4496 if line.startswith(b'BODYFILE '):
4495 4497 with open(line.split(b' ', 1), b'rb') as fh:
4496 4498 body = fh.read()
4497 4499 elif line.startswith(b'frame '):
4498 4500 frame = wireprotoframing.makeframefromhumanstring(
4499 4501 line[len(b'frame ') :]
4500 4502 )
4501 4503
4502 4504 frames.append(frame)
4503 4505 else:
4504 4506 raise error.Abort(
4505 4507 _(b'unknown argument to httprequest: %s') % line
4506 4508 )
4507 4509
4508 4510 url = path + httppath
4509 4511
4510 4512 if frames:
4511 4513 body = b''.join(bytes(f) for f in frames)
4512 4514
4513 4515 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
4514 4516
4515 4517 # urllib.Request insists on using has_data() as a proxy for
4516 4518 # determining the request method. Override that to use our
4517 4519 # explicitly requested method.
4518 4520 req.get_method = lambda: pycompat.sysstr(method)
4519 4521
4520 4522 try:
4521 4523 res = opener.open(req)
4522 4524 body = res.read()
4523 4525 except util.urlerr.urlerror as e:
4524 4526 # read() method must be called, but only exists in Python 2
4525 4527 getattr(e, 'read', lambda: None)()
4526 4528 continue
4527 4529
4528 4530 ct = res.headers.get('Content-Type')
4529 4531 if ct == 'application/mercurial-cbor':
4530 4532 ui.write(
4531 4533 _(b'cbor> %s\n')
4532 4534 % stringutil.pprint(
4533 4535 cborutil.decodeall(body), bprefix=True, indent=2
4534 4536 )
4535 4537 )
4536 4538
4537 4539 elif action == b'close':
4538 4540 assert peer is not None
4539 4541 peer.close()
4540 4542 elif action == b'readavailable':
4541 4543 if not stdout or not stderr:
4542 4544 raise error.Abort(
4543 4545 _(b'readavailable not available on this peer')
4544 4546 )
4545 4547
4546 4548 stdin.close()
4547 4549 stdout.read()
4548 4550 stderr.read()
4549 4551
4550 4552 elif action == b'readline':
4551 4553 if not stdout:
4552 4554 raise error.Abort(_(b'readline not available on this peer'))
4553 4555 stdout.readline()
4554 4556 elif action == b'ereadline':
4555 4557 if not stderr:
4556 4558 raise error.Abort(_(b'ereadline not available on this peer'))
4557 4559 stderr.readline()
4558 4560 elif action.startswith(b'read '):
4559 4561 count = int(action.split(b' ', 1)[1])
4560 4562 if not stdout:
4561 4563 raise error.Abort(_(b'read not available on this peer'))
4562 4564 stdout.read(count)
4563 4565 elif action.startswith(b'eread '):
4564 4566 count = int(action.split(b' ', 1)[1])
4565 4567 if not stderr:
4566 4568 raise error.Abort(_(b'eread not available on this peer'))
4567 4569 stderr.read(count)
4568 4570 else:
4569 4571 raise error.Abort(_(b'unknown action: %s') % action)
4570 4572
4571 4573 if batchedcommands is not None:
4572 4574 raise error.Abort(_(b'unclosed "batchbegin" request'))
4573 4575
4574 4576 if peer:
4575 4577 peer.close()
4576 4578
4577 4579 if proc:
4578 4580 proc.kill()
@@ -1,534 +1,539 b''
1 1 # registrar.py - utilities to register function for specific purpose
2 2 #
3 3 # Copyright FUJIWARA Katsunori <foozy@lares.dti.ne.jp> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 from . import (
11 11 configitems,
12 12 error,
13 13 pycompat,
14 14 util,
15 15 )
16 16
17 17 # unlike the other registered items, config options are neither functions or
18 18 # classes. Registering the option is just small function call.
19 19 #
20 20 # We still add the official API to the registrar module for consistency with
21 21 # the other items extensions want might to register.
22 22 configitem = configitems.getitemregister
23 23
24 24
25 25 class _funcregistrarbase(object):
26 26 """Base of decorator to register a function for specific purpose
27 27
28 28 This decorator stores decorated functions into own dict 'table'.
29 29
30 30 The least derived class can be defined by overriding 'formatdoc',
31 31 for example::
32 32
33 33 class keyword(_funcregistrarbase):
34 34 _docformat = ":%s: %s"
35 35
36 36 This should be used as below:
37 37
38 38 keyword = registrar.keyword()
39 39
40 40 @keyword('bar')
41 41 def barfunc(*args, **kwargs):
42 42 '''Explanation of bar keyword ....
43 43 '''
44 44 pass
45 45
46 46 In this case:
47 47
48 48 - 'barfunc' is stored as 'bar' in '_table' of an instance 'keyword' above
49 49 - 'barfunc.__doc__' becomes ":bar: Explanation of bar keyword"
50 50 """
51 51
52 52 def __init__(self, table=None):
53 53 if table is None:
54 54 self._table = {}
55 55 else:
56 56 self._table = table
57 57
58 58 def __call__(self, decl, *args, **kwargs):
59 59 return lambda func: self._doregister(func, decl, *args, **kwargs)
60 60
61 61 def _doregister(self, func, decl, *args, **kwargs):
62 62 name = self._getname(decl)
63 63
64 64 if name in self._table:
65 65 msg = b'duplicate registration for name: "%s"' % name
66 66 raise error.ProgrammingError(msg)
67 67
68 68 if func.__doc__ and not util.safehasattr(func, '_origdoc'):
69 69 func._origdoc = func.__doc__.strip()
70 70 doc = pycompat.sysbytes(func._origdoc)
71 71 func.__doc__ = pycompat.sysstr(self._formatdoc(decl, doc))
72 72
73 73 self._table[name] = func
74 74 self._extrasetup(name, func, *args, **kwargs)
75 75
76 76 return func
77 77
78 78 def _merge(self, registrarbase):
79 79 """Merge the entries of the given registrar object into this one.
80 80
81 81 The other registrar object must not contain any entries already in the
82 82 current one, or a ProgrammmingError is raised. Additionally, the types
83 83 of the two registrars must match.
84 84 """
85 85 if not isinstance(registrarbase, type(self)):
86 86 msg = b"cannot merge different types of registrar"
87 87 raise error.ProgrammingError(msg)
88 88
89 89 dups = set(registrarbase._table).intersection(self._table)
90 90
91 91 if dups:
92 92 msg = b'duplicate registration for names: "%s"' % b'", "'.join(dups)
93 93 raise error.ProgrammingError(msg)
94 94
95 95 self._table.update(registrarbase._table)
96 96
97 97 def _parsefuncdecl(self, decl):
98 98 """Parse function declaration and return the name of function in it
99 99 """
100 100 i = decl.find(b'(')
101 101 if i >= 0:
102 102 return decl[:i]
103 103 else:
104 104 return decl
105 105
106 106 def _getname(self, decl):
107 107 """Return the name of the registered function from decl
108 108
109 109 Derived class should override this, if it allows more
110 110 descriptive 'decl' string than just a name.
111 111 """
112 112 return decl
113 113
114 114 _docformat = None
115 115
116 116 def _formatdoc(self, decl, doc):
117 117 """Return formatted document of the registered function for help
118 118
119 119 'doc' is '__doc__.strip()' of the registered function.
120 120 """
121 121 return self._docformat % (decl, doc)
122 122
123 123 def _extrasetup(self, name, func):
124 124 """Execute extra setup for registered function, if needed
125 125 """
126 126
127 127
128 128 class command(_funcregistrarbase):
129 129 """Decorator to register a command function to table
130 130
131 131 This class receives a command table as its argument. The table should
132 132 be a dict.
133 133
134 134 The created object can be used as a decorator for adding commands to
135 135 that command table. This accepts multiple arguments to define a command.
136 136
137 137 The first argument is the command name (as bytes).
138 138
139 139 The `options` keyword argument is an iterable of tuples defining command
140 140 arguments. See ``mercurial.fancyopts.fancyopts()`` for the format of each
141 141 tuple.
142 142
143 143 The `synopsis` argument defines a short, one line summary of how to use the
144 144 command. This shows up in the help output.
145 145
146 146 There are three arguments that control what repository (if any) is found
147 147 and passed to the decorated function: `norepo`, `optionalrepo`, and
148 148 `inferrepo`.
149 149
150 150 The `norepo` argument defines whether the command does not require a
151 151 local repository. Most commands operate against a repository, thus the
152 152 default is False. When True, no repository will be passed.
153 153
154 154 The `optionalrepo` argument defines whether the command optionally requires
155 155 a local repository. If no repository can be found, None will be passed
156 156 to the decorated function.
157 157
158 158 The `inferrepo` argument defines whether to try to find a repository from
159 159 the command line arguments. If True, arguments will be examined for
160 160 potential repository locations. See ``findrepo()``. If a repository is
161 161 found, it will be used and passed to the decorated function.
162 162
163 163 The `intents` argument defines a set of intended actions or capabilities
164 164 the command is taking. These intents can be used to affect the construction
165 165 of the repository object passed to the command. For example, commands
166 166 declaring that they are read-only could receive a repository that doesn't
167 167 have any methods allowing repository mutation. Other intents could be used
168 168 to prevent the command from running if the requested intent could not be
169 169 fulfilled.
170 170
171 171 If `helpcategory` is set (usually to one of the constants in the help
172 172 module), the command will be displayed under that category in the help's
173 173 list of commands.
174 174
175 175 The following intents are defined:
176 176
177 177 readonly
178 178 The command is read-only
179 179
180 180 The signature of the decorated function looks like this:
181 181 def cmd(ui[, repo] [, <args>] [, <options>])
182 182
183 183 `repo` is required if `norepo` is False.
184 184 `<args>` are positional args (or `*args`) arguments, of non-option
185 185 arguments from the command line.
186 186 `<options>` are keyword arguments (or `**options`) of option arguments
187 187 from the command line.
188 188
189 189 See the WritingExtensions and MercurialApi documentation for more exhaustive
190 190 descriptions and examples.
191 191 """
192 192
193 193 # Command categories for grouping them in help output.
194 194 # These can also be specified for aliases, like:
195 195 # [alias]
196 196 # myalias = something
197 197 # myalias:category = repo
198 198 CATEGORY_REPO_CREATION = b'repo'
199 199 CATEGORY_REMOTE_REPO_MANAGEMENT = b'remote'
200 200 CATEGORY_COMMITTING = b'commit'
201 201 CATEGORY_CHANGE_MANAGEMENT = b'management'
202 202 CATEGORY_CHANGE_ORGANIZATION = b'organization'
203 203 CATEGORY_FILE_CONTENTS = b'files'
204 204 CATEGORY_CHANGE_NAVIGATION = b'navigation'
205 205 CATEGORY_WORKING_DIRECTORY = b'wdir'
206 206 CATEGORY_IMPORT_EXPORT = b'import'
207 207 CATEGORY_MAINTENANCE = b'maintenance'
208 208 CATEGORY_HELP = b'help'
209 209 CATEGORY_MISC = b'misc'
210 210 CATEGORY_NONE = b'none'
211 211
212 212 def _doregister(
213 213 self,
214 214 func,
215 215 name,
216 216 options=(),
217 217 synopsis=None,
218 218 norepo=False,
219 219 optionalrepo=False,
220 220 inferrepo=False,
221 221 intents=None,
222 222 helpcategory=None,
223 223 helpbasic=False,
224 224 ):
225 225 func.norepo = norepo
226 226 func.optionalrepo = optionalrepo
227 227 func.inferrepo = inferrepo
228 228 func.intents = intents or set()
229 229 func.helpcategory = helpcategory
230 230 func.helpbasic = helpbasic
231 231 if synopsis:
232 232 self._table[name] = func, list(options), synopsis
233 233 else:
234 234 self._table[name] = func, list(options)
235 235 return func
236 236
237 def rename(self, old, new):
238 """rename a command. Used to add aliases, debugstrip ->
239 debugstrip|strip
240 """
241 self._table[new] = self._table.pop(old)
237 242
238 243 INTENT_READONLY = b'readonly'
239 244
240 245
241 246 class revsetpredicate(_funcregistrarbase):
242 247 """Decorator to register revset predicate
243 248
244 249 Usage::
245 250
246 251 revsetpredicate = registrar.revsetpredicate()
247 252
248 253 @revsetpredicate('mypredicate(arg1, arg2[, arg3])')
249 254 def mypredicatefunc(repo, subset, x):
250 255 '''Explanation of this revset predicate ....
251 256 '''
252 257 pass
253 258
254 259 The first string argument is used also in online help.
255 260
256 261 Optional argument 'safe' indicates whether a predicate is safe for
257 262 DoS attack (False by default).
258 263
259 264 Optional argument 'takeorder' indicates whether a predicate function
260 265 takes ordering policy as the last argument.
261 266
262 267 Optional argument 'weight' indicates the estimated run-time cost, useful
263 268 for static optimization, default is 1. Higher weight means more expensive.
264 269 Usually, revsets that are fast and return only one revision has a weight of
265 270 0.5 (ex. a symbol); revsets with O(changelog) complexity and read only the
266 271 changelog have weight 10 (ex. author); revsets reading manifest deltas have
267 272 weight 30 (ex. adds); revset reading manifest contents have weight 100
268 273 (ex. contains). Note: those values are flexible. If the revset has a
269 274 same big-O time complexity as 'contains', but with a smaller constant, it
270 275 might have a weight of 90.
271 276
272 277 'revsetpredicate' instance in example above can be used to
273 278 decorate multiple functions.
274 279
275 280 Decorated functions are registered automatically at loading
276 281 extension, if an instance named as 'revsetpredicate' is used for
277 282 decorating in extension.
278 283
279 284 Otherwise, explicit 'revset.loadpredicate()' is needed.
280 285 """
281 286
282 287 _getname = _funcregistrarbase._parsefuncdecl
283 288 _docformat = b"``%s``\n %s"
284 289
285 290 def _extrasetup(self, name, func, safe=False, takeorder=False, weight=1):
286 291 func._safe = safe
287 292 func._takeorder = takeorder
288 293 func._weight = weight
289 294
290 295
291 296 class filesetpredicate(_funcregistrarbase):
292 297 """Decorator to register fileset predicate
293 298
294 299 Usage::
295 300
296 301 filesetpredicate = registrar.filesetpredicate()
297 302
298 303 @filesetpredicate('mypredicate()')
299 304 def mypredicatefunc(mctx, x):
300 305 '''Explanation of this fileset predicate ....
301 306 '''
302 307 pass
303 308
304 309 The first string argument is used also in online help.
305 310
306 311 Optional argument 'callstatus' indicates whether a predicate
307 312 implies 'matchctx.status()' at runtime or not (False, by
308 313 default).
309 314
310 315 Optional argument 'weight' indicates the estimated run-time cost, useful
311 316 for static optimization, default is 1. Higher weight means more expensive.
312 317 There are predefined weights in the 'filesetlang' module.
313 318
314 319 ====== =============================================================
315 320 Weight Description and examples
316 321 ====== =============================================================
317 322 0.5 basic match patterns (e.g. a symbol)
318 323 10 computing status (e.g. added()) or accessing a few files
319 324 30 reading file content for each (e.g. grep())
320 325 50 scanning working directory (ignored())
321 326 ====== =============================================================
322 327
323 328 'filesetpredicate' instance in example above can be used to
324 329 decorate multiple functions.
325 330
326 331 Decorated functions are registered automatically at loading
327 332 extension, if an instance named as 'filesetpredicate' is used for
328 333 decorating in extension.
329 334
330 335 Otherwise, explicit 'fileset.loadpredicate()' is needed.
331 336 """
332 337
333 338 _getname = _funcregistrarbase._parsefuncdecl
334 339 _docformat = b"``%s``\n %s"
335 340
336 341 def _extrasetup(self, name, func, callstatus=False, weight=1):
337 342 func._callstatus = callstatus
338 343 func._weight = weight
339 344
340 345
341 346 class _templateregistrarbase(_funcregistrarbase):
342 347 """Base of decorator to register functions as template specific one
343 348 """
344 349
345 350 _docformat = b":%s: %s"
346 351
347 352
348 353 class templatekeyword(_templateregistrarbase):
349 354 """Decorator to register template keyword
350 355
351 356 Usage::
352 357
353 358 templatekeyword = registrar.templatekeyword()
354 359
355 360 # new API (since Mercurial 4.6)
356 361 @templatekeyword('mykeyword', requires={'repo', 'ctx'})
357 362 def mykeywordfunc(context, mapping):
358 363 '''Explanation of this template keyword ....
359 364 '''
360 365 pass
361 366
362 367 The first string argument is used also in online help.
363 368
364 369 Optional argument 'requires' should be a collection of resource names
365 370 which the template keyword depends on.
366 371
367 372 'templatekeyword' instance in example above can be used to
368 373 decorate multiple functions.
369 374
370 375 Decorated functions are registered automatically at loading
371 376 extension, if an instance named as 'templatekeyword' is used for
372 377 decorating in extension.
373 378
374 379 Otherwise, explicit 'templatekw.loadkeyword()' is needed.
375 380 """
376 381
377 382 def _extrasetup(self, name, func, requires=()):
378 383 func._requires = requires
379 384
380 385
381 386 class templatefilter(_templateregistrarbase):
382 387 """Decorator to register template filer
383 388
384 389 Usage::
385 390
386 391 templatefilter = registrar.templatefilter()
387 392
388 393 @templatefilter('myfilter', intype=bytes)
389 394 def myfilterfunc(text):
390 395 '''Explanation of this template filter ....
391 396 '''
392 397 pass
393 398
394 399 The first string argument is used also in online help.
395 400
396 401 Optional argument 'intype' defines the type of the input argument,
397 402 which should be (bytes, int, templateutil.date, or None for any.)
398 403
399 404 'templatefilter' instance in example above can be used to
400 405 decorate multiple functions.
401 406
402 407 Decorated functions are registered automatically at loading
403 408 extension, if an instance named as 'templatefilter' is used for
404 409 decorating in extension.
405 410
406 411 Otherwise, explicit 'templatefilters.loadkeyword()' is needed.
407 412 """
408 413
409 414 def _extrasetup(self, name, func, intype=None):
410 415 func._intype = intype
411 416
412 417
413 418 class templatefunc(_templateregistrarbase):
414 419 """Decorator to register template function
415 420
416 421 Usage::
417 422
418 423 templatefunc = registrar.templatefunc()
419 424
420 425 @templatefunc('myfunc(arg1, arg2[, arg3])', argspec='arg1 arg2 arg3',
421 426 requires={'ctx'})
422 427 def myfuncfunc(context, mapping, args):
423 428 '''Explanation of this template function ....
424 429 '''
425 430 pass
426 431
427 432 The first string argument is used also in online help.
428 433
429 434 If optional 'argspec' is defined, the function will receive 'args' as
430 435 a dict of named arguments. Otherwise 'args' is a list of positional
431 436 arguments.
432 437
433 438 Optional argument 'requires' should be a collection of resource names
434 439 which the template function depends on.
435 440
436 441 'templatefunc' instance in example above can be used to
437 442 decorate multiple functions.
438 443
439 444 Decorated functions are registered automatically at loading
440 445 extension, if an instance named as 'templatefunc' is used for
441 446 decorating in extension.
442 447
443 448 Otherwise, explicit 'templatefuncs.loadfunction()' is needed.
444 449 """
445 450
446 451 _getname = _funcregistrarbase._parsefuncdecl
447 452
448 453 def _extrasetup(self, name, func, argspec=None, requires=()):
449 454 func._argspec = argspec
450 455 func._requires = requires
451 456
452 457
453 458 class internalmerge(_funcregistrarbase):
454 459 """Decorator to register in-process merge tool
455 460
456 461 Usage::
457 462
458 463 internalmerge = registrar.internalmerge()
459 464
460 465 @internalmerge('mymerge', internalmerge.mergeonly,
461 466 onfailure=None, precheck=None,
462 467 binary=False, symlink=False):
463 468 def mymergefunc(repo, mynode, orig, fcd, fco, fca,
464 469 toolconf, files, labels=None):
465 470 '''Explanation of this internal merge tool ....
466 471 '''
467 472 return 1, False # means "conflicted", "no deletion needed"
468 473
469 474 The first string argument is used to compose actual merge tool name,
470 475 ":name" and "internal:name" (the latter is historical one).
471 476
472 477 The second argument is one of merge types below:
473 478
474 479 ========== ======== ======== =========
475 480 merge type precheck premerge fullmerge
476 481 ========== ======== ======== =========
477 482 nomerge x x x
478 483 mergeonly o x o
479 484 fullmerge o o o
480 485 ========== ======== ======== =========
481 486
482 487 Optional argument 'onfailure' is the format of warning message
483 488 to be used at failure of merging (target filename is specified
484 489 at formatting). Or, None or so, if warning message should be
485 490 suppressed.
486 491
487 492 Optional argument 'precheck' is the function to be used
488 493 before actual invocation of internal merge tool itself.
489 494 It takes as same arguments as internal merge tool does, other than
490 495 'files' and 'labels'. If it returns false value, merging is aborted
491 496 immediately (and file is marked as "unresolved").
492 497
493 498 Optional argument 'binary' is a binary files capability of internal
494 499 merge tool. 'nomerge' merge type implies binary=True.
495 500
496 501 Optional argument 'symlink' is a symlinks capability of inetrnal
497 502 merge function. 'nomerge' merge type implies symlink=True.
498 503
499 504 'internalmerge' instance in example above can be used to
500 505 decorate multiple functions.
501 506
502 507 Decorated functions are registered automatically at loading
503 508 extension, if an instance named as 'internalmerge' is used for
504 509 decorating in extension.
505 510
506 511 Otherwise, explicit 'filemerge.loadinternalmerge()' is needed.
507 512 """
508 513
509 514 _docformat = b"``:%s``\n %s"
510 515
511 516 # merge type definitions:
512 517 nomerge = None
513 518 mergeonly = b'mergeonly' # just the full merge, no premerge
514 519 fullmerge = b'fullmerge' # both premerge and merge
515 520
516 521 def _extrasetup(
517 522 self,
518 523 name,
519 524 func,
520 525 mergetype,
521 526 onfailure=None,
522 527 precheck=None,
523 528 binary=False,
524 529 symlink=False,
525 530 ):
526 531 func.mergetype = mergetype
527 532 func.onfailure = onfailure
528 533 func.precheck = precheck
529 534
530 535 binarycap = binary or mergetype == self.nomerge
531 536 symlinkcap = symlink or mergetype == self.nomerge
532 537
533 538 # actual capabilities, which this internal merge tool has
534 539 func.capabilities = {b"binary": binarycap, b"symlink": symlinkcap}
@@ -1,287 +1,277 b''
1 """strip changesets and their descendants from history
2
3 This extension allows you to strip changesets and all their descendants from the
4 repository. See the command help for details.
5 """
6 1 from __future__ import absolute_import
7 2
8 from mercurial.i18n import _
9 from mercurial.pycompat import getattr
10 from mercurial import (
3 from .i18n import _
4 from .pycompat import getattr
5 from . import (
11 6 bookmarks as bookmarksmod,
12 7 cmdutil,
13 8 error,
14 9 hg,
15 10 lock as lockmod,
16 11 mergestate as mergestatemod,
17 12 node as nodemod,
18 13 pycompat,
19 14 registrar,
20 15 repair,
21 16 scmutil,
22 17 util,
23 18 )
24 19
25 20 nullid = nodemod.nullid
26 21 release = lockmod.release
27 22
28 23 cmdtable = {}
29 24 command = registrar.command(cmdtable)
30 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
31 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
32 # be specifying the version(s) of Mercurial they are tested with, or
33 # leave the attribute unspecified.
34 testedwith = b'ships-with-hg-core'
35 25
36 26
37 27 def checklocalchanges(repo, force=False):
38 28 s = repo.status()
39 29 if not force:
40 30 cmdutil.checkunfinished(repo)
41 31 cmdutil.bailifchanged(repo)
42 32 else:
43 33 cmdutil.checkunfinished(repo, skipmerge=True)
44 34 return s
45 35
46 36
47 37 def _findupdatetarget(repo, nodes):
48 38 unode, p2 = repo.changelog.parents(nodes[0])
49 39 currentbranch = repo[None].branch()
50 40
51 41 if (
52 42 util.safehasattr(repo, b'mq')
53 43 and p2 != nullid
54 44 and p2 in [x.node for x in repo.mq.applied]
55 45 ):
56 46 unode = p2
57 47 elif currentbranch != repo[unode].branch():
58 48 pwdir = b'parents(wdir())'
59 49 revset = b'max(((parents(%ln::%r) + %r) - %ln::%r) and branch(%s))'
60 50 branchtarget = repo.revs(
61 51 revset, nodes, pwdir, pwdir, nodes, pwdir, currentbranch
62 52 )
63 53 if branchtarget:
64 54 cl = repo.changelog
65 55 unode = cl.node(branchtarget.first())
66 56
67 57 return unode
68 58
69 59
70 60 def strip(
71 61 ui,
72 62 repo,
73 63 revs,
74 64 update=True,
75 65 backup=True,
76 66 force=None,
77 67 bookmarks=None,
78 68 soft=False,
79 69 ):
80 70 with repo.wlock(), repo.lock():
81 71
82 72 if update:
83 73 checklocalchanges(repo, force=force)
84 74 urev = _findupdatetarget(repo, revs)
85 75 hg.clean(repo, urev)
86 76 repo.dirstate.write(repo.currenttransaction())
87 77
88 78 if soft:
89 79 repair.softstrip(ui, repo, revs, backup)
90 80 else:
91 81 repair.strip(ui, repo, revs, backup)
92 82
93 83 repomarks = repo._bookmarks
94 84 if bookmarks:
95 85 with repo.transaction(b'strip') as tr:
96 86 if repo._activebookmark in bookmarks:
97 87 bookmarksmod.deactivate(repo)
98 88 repomarks.applychanges(repo, tr, [(b, None) for b in bookmarks])
99 89 for bookmark in sorted(bookmarks):
100 90 ui.write(_(b"bookmark '%s' deleted\n") % bookmark)
101 91
102 92
103 93 @command(
104 b"strip",
94 b"debugstrip",
105 95 [
106 96 (
107 97 b'r',
108 98 b'rev',
109 99 [],
110 100 _(
111 101 b'strip specified revision (optional, '
112 102 b'can specify revisions without this '
113 103 b'option)'
114 104 ),
115 105 _(b'REV'),
116 106 ),
117 107 (
118 108 b'f',
119 109 b'force',
120 110 None,
121 111 _(
122 112 b'force removal of changesets, discard '
123 113 b'uncommitted changes (no backup)'
124 114 ),
125 115 ),
126 116 (b'', b'no-backup', None, _(b'do not save backup bundle')),
127 117 (b'', b'nobackup', None, _(b'do not save backup bundle (DEPRECATED)'),),
128 118 (b'n', b'', None, _(b'ignored (DEPRECATED)')),
129 119 (
130 120 b'k',
131 121 b'keep',
132 122 None,
133 123 _(b"do not modify working directory during strip"),
134 124 ),
135 125 (
136 126 b'B',
137 127 b'bookmark',
138 128 [],
139 129 _(b"remove revs only reachable from given bookmark"),
140 130 _(b'BOOKMARK'),
141 131 ),
142 132 (
143 133 b'',
144 134 b'soft',
145 135 None,
146 136 _(b"simply drop changesets from visible history (EXPERIMENTAL)"),
147 137 ),
148 138 ],
149 _(b'hg strip [-k] [-f] [-B bookmark] [-r] REV...'),
139 _(b'hg debugstrip [-k] [-f] [-B bookmark] [-r] REV...'),
150 140 helpcategory=command.CATEGORY_MAINTENANCE,
151 141 )
152 def stripcmd(ui, repo, *revs, **opts):
142 def debugstrip(ui, repo, *revs, **opts):
153 143 """strip changesets and all their descendants from the repository
154 144
155 145 The strip command removes the specified changesets and all their
156 146 descendants. If the working directory has uncommitted changes, the
157 147 operation is aborted unless the --force flag is supplied, in which
158 148 case changes will be discarded.
159 149
160 150 If a parent of the working directory is stripped, then the working
161 151 directory will automatically be updated to the most recent
162 152 available ancestor of the stripped parent after the operation
163 153 completes.
164 154
165 155 Any stripped changesets are stored in ``.hg/strip-backup`` as a
166 156 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
167 157 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
168 158 where BUNDLE is the bundle file created by the strip. Note that
169 159 the local revision numbers will in general be different after the
170 160 restore.
171 161
172 162 Use the --no-backup option to discard the backup bundle once the
173 163 operation completes.
174 164
175 165 Strip is not a history-rewriting operation and can be used on
176 166 changesets in the public phase. But if the stripped changesets have
177 167 been pushed to a remote repository you will likely pull them again.
178 168
179 169 Return 0 on success.
180 170 """
181 171 opts = pycompat.byteskwargs(opts)
182 172 backup = True
183 173 if opts.get(b'no_backup') or opts.get(b'nobackup'):
184 174 backup = False
185 175
186 176 cl = repo.changelog
187 177 revs = list(revs) + opts.get(b'rev')
188 178 revs = set(scmutil.revrange(repo, revs))
189 179
190 180 with repo.wlock():
191 181 bookmarks = set(opts.get(b'bookmark'))
192 182 if bookmarks:
193 183 repomarks = repo._bookmarks
194 184 if not bookmarks.issubset(repomarks):
195 185 raise error.Abort(
196 186 _(b"bookmark '%s' not found")
197 187 % b','.join(sorted(bookmarks - set(repomarks.keys())))
198 188 )
199 189
200 190 # If the requested bookmark is not the only one pointing to a
201 191 # a revision we have to only delete the bookmark and not strip
202 192 # anything. revsets cannot detect that case.
203 193 nodetobookmarks = {}
204 194 for mark, node in pycompat.iteritems(repomarks):
205 195 nodetobookmarks.setdefault(node, []).append(mark)
206 196 for marks in nodetobookmarks.values():
207 197 if bookmarks.issuperset(marks):
208 198 rsrevs = scmutil.bookmarkrevs(repo, marks[0])
209 199 revs.update(set(rsrevs))
210 200 if not revs:
211 201 with repo.lock(), repo.transaction(b'bookmark') as tr:
212 202 bmchanges = [(b, None) for b in bookmarks]
213 203 repomarks.applychanges(repo, tr, bmchanges)
214 204 for bookmark in sorted(bookmarks):
215 205 ui.write(_(b"bookmark '%s' deleted\n") % bookmark)
216 206
217 207 if not revs:
218 208 raise error.Abort(_(b'empty revision set'))
219 209
220 210 descendants = set(cl.descendants(revs))
221 211 strippedrevs = revs.union(descendants)
222 212 roots = revs.difference(descendants)
223 213
224 214 # if one of the wdir parent is stripped we'll need
225 215 # to update away to an earlier revision
226 216 update = any(
227 217 p != nullid and cl.rev(p) in strippedrevs
228 218 for p in repo.dirstate.parents()
229 219 )
230 220
231 221 rootnodes = {cl.node(r) for r in roots}
232 222
233 223 q = getattr(repo, 'mq', None)
234 224 if q is not None and q.applied:
235 225 # refresh queue state if we're about to strip
236 226 # applied patches
237 227 if cl.rev(repo.lookup(b'qtip')) in strippedrevs:
238 228 q.applieddirty = True
239 229 start = 0
240 230 end = len(q.applied)
241 231 for i, statusentry in enumerate(q.applied):
242 232 if statusentry.node in rootnodes:
243 233 # if one of the stripped roots is an applied
244 234 # patch, only part of the queue is stripped
245 235 start = i
246 236 break
247 237 del q.applied[start:end]
248 238 q.savedirty()
249 239
250 240 revs = sorted(rootnodes)
251 241 if update and opts.get(b'keep'):
252 242 urev = _findupdatetarget(repo, revs)
253 243 uctx = repo[urev]
254 244
255 245 # only reset the dirstate for files that would actually change
256 246 # between the working context and uctx
257 247 descendantrevs = repo.revs(b"only(., %d)", uctx.rev())
258 248 changedfiles = []
259 249 for rev in descendantrevs:
260 250 # blindly reset the files, regardless of what actually changed
261 251 changedfiles.extend(repo[rev].files())
262 252
263 253 # reset files that only changed in the dirstate too
264 254 dirstate = repo.dirstate
265 255 dirchanges = [f for f in dirstate if dirstate[f] != b'n']
266 256 changedfiles.extend(dirchanges)
267 257
268 258 repo.dirstate.rebuild(urev, uctx.manifest(), changedfiles)
269 259 repo.dirstate.write(repo.currenttransaction())
270 260
271 261 # clear resolve state
272 262 mergestatemod.mergestate.clean(repo)
273 263
274 264 update = False
275 265
276 266 strip(
277 267 ui,
278 268 repo,
279 269 revs,
280 270 backup=backup,
281 271 update=update,
282 272 force=opts.get(b'force'),
283 273 bookmarks=bookmarks,
284 274 soft=opts[b'soft'],
285 275 )
286 276
287 277 return 0
@@ -1,19 +1,22 b''
1 1 == New Features ==
2 2
3 3
4 4
5 * `hg strip`, from the strip extension, is now a core command, `hg
6 debugstrip`. The extension remains for compatibility.
7
5 8 == New Experimental Features ==
6 9
7 10
8 11
9 12 == Bug Fixes ==
10 13
11 14
12 15
13 16 == Backwards Compatibility Changes ==
14 17
15 18
16 19
17 20 == Internal API Changes ==
18 21
19 22
@@ -1,437 +1,439 b''
1 1 Show all commands except debug commands
2 2 $ hg debugcomplete
3 3 abort
4 4 add
5 5 addremove
6 6 annotate
7 7 archive
8 8 backout
9 9 bisect
10 10 bookmarks
11 11 branch
12 12 branches
13 13 bundle
14 14 cat
15 15 clone
16 16 commit
17 17 config
18 18 continue
19 19 copy
20 20 diff
21 21 export
22 22 files
23 23 forget
24 24 graft
25 25 grep
26 26 heads
27 27 help
28 28 identify
29 29 import
30 30 incoming
31 31 init
32 32 locate
33 33 log
34 34 manifest
35 35 merge
36 36 outgoing
37 37 parents
38 38 paths
39 39 phase
40 40 pull
41 41 push
42 42 recover
43 43 remove
44 44 rename
45 45 resolve
46 46 revert
47 47 rollback
48 48 root
49 49 serve
50 50 shelve
51 51 status
52 52 summary
53 53 tag
54 54 tags
55 55 tip
56 56 unbundle
57 57 unshelve
58 58 update
59 59 verify
60 60 version
61 61
62 62 Show all commands that start with "a"
63 63 $ hg debugcomplete a
64 64 abort
65 65 add
66 66 addremove
67 67 annotate
68 68 archive
69 69
70 70 Do not show debug commands if there are other candidates
71 71 $ hg debugcomplete d
72 72 diff
73 73
74 74 Show debug commands if there are no other candidates
75 75 $ hg debugcomplete debug
76 76 debugancestor
77 77 debugantivirusrunning
78 78 debugapplystreamclonebundle
79 79 debugbackupbundle
80 80 debugbuilddag
81 81 debugbundle
82 82 debugcapabilities
83 83 debugchangedfiles
84 84 debugcheckstate
85 85 debugcolor
86 86 debugcommands
87 87 debugcomplete
88 88 debugconfig
89 89 debugcreatestreamclonebundle
90 90 debugdag
91 91 debugdata
92 92 debugdate
93 93 debugdeltachain
94 94 debugdirstate
95 95 debugdiscovery
96 96 debugdownload
97 97 debugextensions
98 98 debugfileset
99 99 debugformat
100 100 debugfsinfo
101 101 debuggetbundle
102 102 debugignore
103 103 debugindex
104 104 debugindexdot
105 105 debugindexstats
106 106 debuginstall
107 107 debugknown
108 108 debuglabelcomplete
109 109 debuglocks
110 110 debugmanifestfulltextcache
111 111 debugmergestate
112 112 debugnamecomplete
113 113 debugnodemap
114 114 debugobsolete
115 115 debugp1copies
116 116 debugp2copies
117 117 debugpathcomplete
118 118 debugpathcopies
119 119 debugpeer
120 120 debugpickmergetool
121 121 debugpushkey
122 122 debugpvec
123 123 debugrebuilddirstate
124 124 debugrebuildfncache
125 125 debugrename
126 126 debugrequires
127 127 debugrevlog
128 128 debugrevlogindex
129 129 debugrevspec
130 130 debugserve
131 131 debugsetparents
132 132 debugsidedata
133 133 debugssl
134 debugstrip
134 135 debugsub
135 136 debugsuccessorssets
136 137 debugtagscache
137 138 debugtemplate
138 139 debuguigetpass
139 140 debuguiprompt
140 141 debugupdatecaches
141 142 debugupgraderepo
142 143 debugwalk
143 144 debugwhyunstable
144 145 debugwireargs
145 146 debugwireproto
146 147
147 148 Do not show the alias of a debug command if there are other candidates
148 149 (this should hide rawcommit)
149 150 $ hg debugcomplete r
150 151 recover
151 152 remove
152 153 rename
153 154 resolve
154 155 revert
155 156 rollback
156 157 root
157 158 Show the alias of a debug command if there are no other candidates
158 159 $ hg debugcomplete rawc
159 160
160 161
161 162 Show the global options
162 163 $ hg debugcomplete --options | sort
163 164 --color
164 165 --config
165 166 --cwd
166 167 --debug
167 168 --debugger
168 169 --encoding
169 170 --encodingmode
170 171 --help
171 172 --hidden
172 173 --noninteractive
173 174 --pager
174 175 --profile
175 176 --quiet
176 177 --repository
177 178 --time
178 179 --traceback
179 180 --verbose
180 181 --version
181 182 -R
182 183 -h
183 184 -q
184 185 -v
185 186 -y
186 187
187 188 Show the options for the "serve" command
188 189 $ hg debugcomplete --options serve | sort
189 190 --accesslog
190 191 --address
191 192 --certificate
192 193 --cmdserver
193 194 --color
194 195 --config
195 196 --cwd
196 197 --daemon
197 198 --daemon-postexec
198 199 --debug
199 200 --debugger
200 201 --encoding
201 202 --encodingmode
202 203 --errorlog
203 204 --help
204 205 --hidden
205 206 --ipv6
206 207 --name
207 208 --noninteractive
208 209 --pager
209 210 --pid-file
210 211 --port
211 212 --prefix
212 213 --print-url
213 214 --profile
214 215 --quiet
215 216 --repository
216 217 --stdio
217 218 --style
218 219 --subrepos
219 220 --templates
220 221 --time
221 222 --traceback
222 223 --verbose
223 224 --version
224 225 --web-conf
225 226 -6
226 227 -A
227 228 -E
228 229 -R
229 230 -S
230 231 -a
231 232 -d
232 233 -h
233 234 -n
234 235 -p
235 236 -q
236 237 -t
237 238 -v
238 239 -y
239 240
240 241 Show an error if we use --options with an ambiguous abbreviation
241 242 $ hg debugcomplete --options s
242 243 hg: command 's' is ambiguous:
243 244 serve shelve showconfig status summary
244 245 [255]
245 246
246 247 Show all commands + options
247 248 $ hg debugcommands
248 249 abort: dry-run
249 250 add: include, exclude, subrepos, dry-run
250 251 addremove: similarity, subrepos, include, exclude, dry-run
251 252 annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, skip, ignore-all-space, ignore-space-change, ignore-blank-lines, ignore-space-at-eol, include, exclude, template
252 253 archive: no-decode, prefix, rev, type, subrepos, include, exclude
253 254 backout: merge, commit, no-commit, parent, rev, edit, tool, include, exclude, message, logfile, date, user
254 255 bisect: reset, good, bad, skip, extend, command, noupdate
255 256 bookmarks: force, rev, delete, rename, inactive, list, template
256 257 branch: force, clean, rev
257 258 branches: active, closed, rev, template
258 259 bundle: force, rev, branch, base, all, type, ssh, remotecmd, insecure
259 260 cat: output, rev, decode, include, exclude, template
260 261 clone: noupdate, updaterev, rev, branch, pull, uncompressed, stream, ssh, remotecmd, insecure
261 262 commit: addremove, close-branch, amend, secret, edit, force-close-branch, interactive, include, exclude, message, logfile, date, user, subrepos
262 263 config: untrusted, edit, local, shared, non-shared, global, template
263 264 continue: dry-run
264 265 copy: forget, after, at-rev, force, include, exclude, dry-run
265 266 debugancestor:
266 267 debugantivirusrunning:
267 268 debugapplystreamclonebundle:
268 269 debugbackupbundle: recover, patch, git, limit, no-merges, stat, graph, style, template
269 270 debugbuilddag: mergeable-file, overwritten-file, new-file
270 271 debugbundle: all, part-type, spec
271 272 debugcapabilities:
272 273 debugchangedfiles:
273 274 debugcheckstate:
274 275 debugcolor: style
275 276 debugcommands:
276 277 debugcomplete: options
277 278 debugcreatestreamclonebundle:
278 279 debugdag: tags, branches, dots, spaces
279 280 debugdata: changelog, manifest, dir
280 281 debugdate: extended
281 282 debugdeltachain: changelog, manifest, dir, template
282 283 debugdirstate: nodates, dates, datesort
283 284 debugdiscovery: old, nonheads, rev, seed, ssh, remotecmd, insecure
284 285 debugdownload: output
285 286 debugextensions: template
286 287 debugfileset: rev, all-files, show-matcher, show-stage
287 288 debugformat: template
288 289 debugfsinfo:
289 290 debuggetbundle: head, common, type
290 291 debugignore:
291 292 debugindex: changelog, manifest, dir, template
292 293 debugindexdot: changelog, manifest, dir
293 294 debugindexstats:
294 295 debuginstall: template
295 296 debugknown:
296 297 debuglabelcomplete:
297 298 debuglocks: force-lock, force-wlock, set-lock, set-wlock
298 299 debugmanifestfulltextcache: clear, add
299 300 debugmergestate: style, template
300 301 debugnamecomplete:
301 302 debugnodemap: dump-new, dump-disk, check, metadata
302 303 debugobsolete: flags, record-parents, rev, exclusive, index, delete, date, user, template
303 304 debugp1copies: rev
304 305 debugp2copies: rev
305 306 debugpathcomplete: full, normal, added, removed
306 307 debugpathcopies: include, exclude
307 308 debugpeer:
308 309 debugpickmergetool: rev, changedelete, include, exclude, tool
309 310 debugpushkey:
310 311 debugpvec:
311 312 debugrebuilddirstate: rev, minimal
312 313 debugrebuildfncache:
313 314 debugrename: rev
314 315 debugrequires:
315 316 debugrevlog: changelog, manifest, dir, dump
316 317 debugrevlogindex: changelog, manifest, dir, format
317 318 debugrevspec: optimize, show-revs, show-set, show-stage, no-optimized, verify-optimized
318 319 debugserve: sshstdio, logiofd, logiofile
319 320 debugsetparents:
320 321 debugsidedata: changelog, manifest, dir
321 322 debugssl:
323 debugstrip: rev, force, no-backup, nobackup, , keep, bookmark, soft
322 324 debugsub: rev
323 325 debugsuccessorssets: closest
324 326 debugtagscache:
325 327 debugtemplate: rev, define
326 328 debuguigetpass: prompt
327 329 debuguiprompt: prompt
328 330 debugupdatecaches:
329 331 debugupgraderepo: optimize, run, backup, changelog, manifest
330 332 debugwalk: include, exclude
331 333 debugwhyunstable:
332 334 debugwireargs: three, four, five, ssh, remotecmd, insecure
333 335 debugwireproto: localssh, peer, noreadstderr, nologhandshake, ssh, remotecmd, insecure
334 336 diff: rev, change, text, git, binary, nodates, noprefix, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, ignore-space-at-eol, unified, stat, root, include, exclude, subrepos
335 337 export: bookmark, output, switch-parent, rev, text, git, binary, nodates, template
336 338 files: rev, print0, include, exclude, template, subrepos
337 339 forget: interactive, include, exclude, dry-run
338 340 graft: rev, base, continue, stop, abort, edit, log, no-commit, force, currentdate, currentuser, date, user, tool, dry-run
339 341 grep: print0, all, diff, text, follow, ignore-case, files-with-matches, line-number, rev, all-files, user, date, template, include, exclude
340 342 heads: rev, topo, active, closed, style, template
341 343 help: extension, command, keyword, system
342 344 identify: rev, num, id, branch, tags, bookmarks, ssh, remotecmd, insecure, template
343 345 import: strip, base, secret, edit, force, no-commit, bypass, partial, exact, prefix, import-branch, message, logfile, date, user, similarity
344 346 incoming: force, newest-first, bundle, rev, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
345 347 init: ssh, remotecmd, insecure
346 348 locate: rev, print0, fullpath, include, exclude
347 349 log: follow, follow-first, date, copies, keyword, rev, line-range, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
348 350 manifest: rev, all, template
349 351 merge: force, rev, preview, abort, tool
350 352 outgoing: force, rev, newest-first, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
351 353 parents: rev, style, template
352 354 paths: template
353 355 phase: public, draft, secret, force, rev
354 356 pull: update, force, confirm, rev, bookmark, branch, ssh, remotecmd, insecure
355 357 push: force, rev, bookmark, all-bookmarks, branch, new-branch, pushvars, publish, ssh, remotecmd, insecure
356 358 recover: verify
357 359 remove: after, force, subrepos, include, exclude, dry-run
358 360 rename: after, at-rev, force, include, exclude, dry-run
359 361 resolve: all, list, mark, unmark, no-status, re-merge, tool, include, exclude, template
360 362 revert: all, date, rev, no-backup, interactive, include, exclude, dry-run
361 363 rollback: dry-run, force
362 364 root: template
363 365 serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate, print-url, subrepos
364 366 shelve: addremove, unknown, cleanup, date, delete, edit, keep, list, message, name, patch, interactive, stat, include, exclude
365 367 status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, terse, copies, print0, rev, change, include, exclude, subrepos, template
366 368 summary: remote
367 369 tag: force, local, rev, remove, edit, message, date, user
368 370 tags: template
369 371 tip: patch, git, style, template
370 372 unbundle: update
371 373 unshelve: abort, continue, interactive, keep, name, tool, date
372 374 update: clean, check, merge, date, rev, tool
373 375 verify: full
374 376 version: template
375 377
376 378 $ hg init a
377 379 $ cd a
378 380 $ echo fee > fee
379 381 $ hg ci -q -Amfee
380 382 $ hg tag fee
381 383 $ mkdir fie
382 384 $ echo dead > fie/dead
383 385 $ echo live > fie/live
384 386 $ hg bookmark fo
385 387 $ hg branch -q fie
386 388 $ hg ci -q -Amfie
387 389 $ echo fo > fo
388 390 $ hg branch -qf default
389 391 $ hg ci -q -Amfo
390 392 $ echo Fum > Fum
391 393 $ hg ci -q -AmFum
392 394 $ hg bookmark Fum
393 395
394 396 Test debugpathcomplete
395 397
396 398 $ hg debugpathcomplete f
397 399 fee
398 400 fie
399 401 fo
400 402 $ hg debugpathcomplete -f f
401 403 fee
402 404 fie/dead
403 405 fie/live
404 406 fo
405 407
406 408 $ hg rm Fum
407 409 $ hg debugpathcomplete -r F
408 410 Fum
409 411
410 412 Test debugnamecomplete
411 413
412 414 $ hg debugnamecomplete
413 415 Fum
414 416 default
415 417 fee
416 418 fie
417 419 fo
418 420 tip
419 421 $ hg debugnamecomplete f
420 422 fee
421 423 fie
422 424 fo
423 425
424 426 Test debuglabelcomplete, a deprecated name for debugnamecomplete that is still
425 427 used for completions in some shells.
426 428
427 429 $ hg debuglabelcomplete
428 430 Fum
429 431 default
430 432 fee
431 433 fie
432 434 fo
433 435 tip
434 436 $ hg debuglabelcomplete f
435 437 fee
436 438 fie
437 439 fo
@@ -1,3919 +1,3919 b''
1 1 Short help:
2 2
3 3 $ hg
4 4 Mercurial Distributed SCM
5 5
6 6 basic commands:
7 7
8 8 add add the specified files on the next commit
9 9 annotate show changeset information by line for each file
10 10 clone make a copy of an existing repository
11 11 commit commit the specified files or all outstanding changes
12 12 diff diff repository (or selected files)
13 13 export dump the header and diffs for one or more changesets
14 14 forget forget the specified files on the next commit
15 15 init create a new repository in the given directory
16 16 log show revision history of entire repository or files
17 17 merge merge another revision into working directory
18 18 pull pull changes from the specified source
19 19 push push changes to the specified destination
20 20 remove remove the specified files on the next commit
21 21 serve start stand-alone webserver
22 22 status show changed files in the working directory
23 23 summary summarize working directory state
24 24 update update working directory (or switch revisions)
25 25
26 26 (use 'hg help' for the full list of commands or 'hg -v' for details)
27 27
28 28 $ hg -q
29 29 add add the specified files on the next commit
30 30 annotate show changeset information by line for each file
31 31 clone make a copy of an existing repository
32 32 commit commit the specified files or all outstanding changes
33 33 diff diff repository (or selected files)
34 34 export dump the header and diffs for one or more changesets
35 35 forget forget the specified files on the next commit
36 36 init create a new repository in the given directory
37 37 log show revision history of entire repository or files
38 38 merge merge another revision into working directory
39 39 pull pull changes from the specified source
40 40 push push changes to the specified destination
41 41 remove remove the specified files on the next commit
42 42 serve start stand-alone webserver
43 43 status show changed files in the working directory
44 44 summary summarize working directory state
45 45 update update working directory (or switch revisions)
46 46
47 47 Extra extensions will be printed in help output in a non-reliable order since
48 48 the extension is unknown.
49 49 #if no-extraextensions
50 50
51 51 $ hg help
52 52 Mercurial Distributed SCM
53 53
54 54 list of commands:
55 55
56 56 Repository creation:
57 57
58 58 clone make a copy of an existing repository
59 59 init create a new repository in the given directory
60 60
61 61 Remote repository management:
62 62
63 63 incoming show new changesets found in source
64 64 outgoing show changesets not found in the destination
65 65 paths show aliases for remote repositories
66 66 pull pull changes from the specified source
67 67 push push changes to the specified destination
68 68 serve start stand-alone webserver
69 69
70 70 Change creation:
71 71
72 72 commit commit the specified files or all outstanding changes
73 73
74 74 Change manipulation:
75 75
76 76 backout reverse effect of earlier changeset
77 77 graft copy changes from other branches onto the current branch
78 78 merge merge another revision into working directory
79 79
80 80 Change organization:
81 81
82 82 bookmarks create a new bookmark or list existing bookmarks
83 83 branch set or show the current branch name
84 84 branches list repository named branches
85 85 phase set or show the current phase name
86 86 tag add one or more tags for the current or given revision
87 87 tags list repository tags
88 88
89 89 File content management:
90 90
91 91 annotate show changeset information by line for each file
92 92 cat output the current or given revision of files
93 93 copy mark files as copied for the next commit
94 94 diff diff repository (or selected files)
95 95 grep search for a pattern in specified files
96 96
97 97 Change navigation:
98 98
99 99 bisect subdivision search of changesets
100 100 heads show branch heads
101 101 identify identify the working directory or specified revision
102 102 log show revision history of entire repository or files
103 103
104 104 Working directory management:
105 105
106 106 add add the specified files on the next commit
107 107 addremove add all new files, delete all missing files
108 108 files list tracked files
109 109 forget forget the specified files on the next commit
110 110 remove remove the specified files on the next commit
111 111 rename rename files; equivalent of copy + remove
112 112 resolve redo merges or set/view the merge status of files
113 113 revert restore files to their checkout state
114 114 root print the root (top) of the current working directory
115 115 shelve save and set aside changes from the working directory
116 116 status show changed files in the working directory
117 117 summary summarize working directory state
118 118 unshelve restore a shelved change to the working directory
119 119 update update working directory (or switch revisions)
120 120
121 121 Change import/export:
122 122
123 123 archive create an unversioned archive of a repository revision
124 124 bundle create a bundle file
125 125 export dump the header and diffs for one or more changesets
126 126 import import an ordered set of patches
127 127 unbundle apply one or more bundle files
128 128
129 129 Repository maintenance:
130 130
131 131 manifest output the current or given revision of the project manifest
132 132 recover roll back an interrupted transaction
133 133 verify verify the integrity of the repository
134 134
135 135 Help:
136 136
137 137 config show combined config settings from all hgrc files
138 138 help show help for a given topic or a help overview
139 139 version output version and copyright information
140 140
141 141 additional help topics:
142 142
143 143 Mercurial identifiers:
144 144
145 145 filesets Specifying File Sets
146 146 hgignore Syntax for Mercurial Ignore Files
147 147 patterns File Name Patterns
148 148 revisions Specifying Revisions
149 149 urls URL Paths
150 150
151 151 Mercurial output:
152 152
153 153 color Colorizing Outputs
154 154 dates Date Formats
155 155 diffs Diff Formats
156 156 templating Template Usage
157 157
158 158 Mercurial configuration:
159 159
160 160 config Configuration Files
161 161 environment Environment Variables
162 162 extensions Using Additional Features
163 163 flags Command-line flags
164 164 hgweb Configuring hgweb
165 165 merge-tools Merge Tools
166 166 pager Pager Support
167 167
168 168 Concepts:
169 169
170 170 bundlespec Bundle File Formats
171 171 glossary Glossary
172 172 phases Working with Phases
173 173 subrepos Subrepositories
174 174
175 175 Miscellaneous:
176 176
177 177 deprecated Deprecated Features
178 178 internals Technical implementation topics
179 179 scripting Using Mercurial from scripts and automation
180 180
181 181 (use 'hg help -v' to show built-in aliases and global options)
182 182
183 183 $ hg -q help
184 184 Repository creation:
185 185
186 186 clone make a copy of an existing repository
187 187 init create a new repository in the given directory
188 188
189 189 Remote repository management:
190 190
191 191 incoming show new changesets found in source
192 192 outgoing show changesets not found in the destination
193 193 paths show aliases for remote repositories
194 194 pull pull changes from the specified source
195 195 push push changes to the specified destination
196 196 serve start stand-alone webserver
197 197
198 198 Change creation:
199 199
200 200 commit commit the specified files or all outstanding changes
201 201
202 202 Change manipulation:
203 203
204 204 backout reverse effect of earlier changeset
205 205 graft copy changes from other branches onto the current branch
206 206 merge merge another revision into working directory
207 207
208 208 Change organization:
209 209
210 210 bookmarks create a new bookmark or list existing bookmarks
211 211 branch set or show the current branch name
212 212 branches list repository named branches
213 213 phase set or show the current phase name
214 214 tag add one or more tags for the current or given revision
215 215 tags list repository tags
216 216
217 217 File content management:
218 218
219 219 annotate show changeset information by line for each file
220 220 cat output the current or given revision of files
221 221 copy mark files as copied for the next commit
222 222 diff diff repository (or selected files)
223 223 grep search for a pattern in specified files
224 224
225 225 Change navigation:
226 226
227 227 bisect subdivision search of changesets
228 228 heads show branch heads
229 229 identify identify the working directory or specified revision
230 230 log show revision history of entire repository or files
231 231
232 232 Working directory management:
233 233
234 234 add add the specified files on the next commit
235 235 addremove add all new files, delete all missing files
236 236 files list tracked files
237 237 forget forget the specified files on the next commit
238 238 remove remove the specified files on the next commit
239 239 rename rename files; equivalent of copy + remove
240 240 resolve redo merges or set/view the merge status of files
241 241 revert restore files to their checkout state
242 242 root print the root (top) of the current working directory
243 243 shelve save and set aside changes from the working directory
244 244 status show changed files in the working directory
245 245 summary summarize working directory state
246 246 unshelve restore a shelved change to the working directory
247 247 update update working directory (or switch revisions)
248 248
249 249 Change import/export:
250 250
251 251 archive create an unversioned archive of a repository revision
252 252 bundle create a bundle file
253 253 export dump the header and diffs for one or more changesets
254 254 import import an ordered set of patches
255 255 unbundle apply one or more bundle files
256 256
257 257 Repository maintenance:
258 258
259 259 manifest output the current or given revision of the project manifest
260 260 recover roll back an interrupted transaction
261 261 verify verify the integrity of the repository
262 262
263 263 Help:
264 264
265 265 config show combined config settings from all hgrc files
266 266 help show help for a given topic or a help overview
267 267 version output version and copyright information
268 268
269 269 additional help topics:
270 270
271 271 Mercurial identifiers:
272 272
273 273 filesets Specifying File Sets
274 274 hgignore Syntax for Mercurial Ignore Files
275 275 patterns File Name Patterns
276 276 revisions Specifying Revisions
277 277 urls URL Paths
278 278
279 279 Mercurial output:
280 280
281 281 color Colorizing Outputs
282 282 dates Date Formats
283 283 diffs Diff Formats
284 284 templating Template Usage
285 285
286 286 Mercurial configuration:
287 287
288 288 config Configuration Files
289 289 environment Environment Variables
290 290 extensions Using Additional Features
291 291 flags Command-line flags
292 292 hgweb Configuring hgweb
293 293 merge-tools Merge Tools
294 294 pager Pager Support
295 295
296 296 Concepts:
297 297
298 298 bundlespec Bundle File Formats
299 299 glossary Glossary
300 300 phases Working with Phases
301 301 subrepos Subrepositories
302 302
303 303 Miscellaneous:
304 304
305 305 deprecated Deprecated Features
306 306 internals Technical implementation topics
307 307 scripting Using Mercurial from scripts and automation
308 308
309 309 Test extension help:
310 310 $ hg help extensions --config extensions.rebase= --config extensions.children=
311 311 Using Additional Features
312 312 """""""""""""""""""""""""
313 313
314 314 Mercurial has the ability to add new features through the use of
315 315 extensions. Extensions may add new commands, add options to existing
316 316 commands, change the default behavior of commands, or implement hooks.
317 317
318 318 To enable the "foo" extension, either shipped with Mercurial or in the
319 319 Python search path, create an entry for it in your configuration file,
320 320 like this:
321 321
322 322 [extensions]
323 323 foo =
324 324
325 325 You may also specify the full path to an extension:
326 326
327 327 [extensions]
328 328 myfeature = ~/.hgext/myfeature.py
329 329
330 330 See 'hg help config' for more information on configuration files.
331 331
332 332 Extensions are not loaded by default for a variety of reasons: they can
333 333 increase startup overhead; they may be meant for advanced usage only; they
334 334 may provide potentially dangerous abilities (such as letting you destroy
335 335 or modify history); they might not be ready for prime time; or they may
336 336 alter some usual behaviors of stock Mercurial. It is thus up to the user
337 337 to activate extensions as needed.
338 338
339 339 To explicitly disable an extension enabled in a configuration file of
340 340 broader scope, prepend its path with !:
341 341
342 342 [extensions]
343 343 # disabling extension bar residing in /path/to/extension/bar.py
344 344 bar = !/path/to/extension/bar.py
345 345 # ditto, but no path was supplied for extension baz
346 346 baz = !
347 347
348 348 enabled extensions:
349 349
350 350 children command to display child changesets (DEPRECATED)
351 351 rebase command to move sets of revisions to a different ancestor
352 352
353 353 disabled extensions:
354 354
355 355 acl hooks for controlling repository access
356 356 blackbox log repository events to a blackbox for debugging
357 357 bugzilla hooks for integrating with the Bugzilla bug tracker
358 358 censor erase file content at a given revision
359 359 churn command to display statistics about repository history
360 360 clonebundles advertise pre-generated bundles to seed clones
361 361 closehead close arbitrary heads without checking them out first
362 362 convert import revisions from foreign VCS repositories into
363 363 Mercurial
364 364 eol automatically manage newlines in repository files
365 365 extdiff command to allow external programs to compare revisions
366 366 factotum http authentication with factotum
367 367 fastexport export repositories as git fast-import stream
368 368 githelp try mapping git commands to Mercurial commands
369 369 gpg commands to sign and verify changesets
370 370 hgk browse the repository in a graphical way
371 371 highlight syntax highlighting for hgweb (requires Pygments)
372 372 histedit interactive history editing
373 373 keyword expand keywords in tracked files
374 374 largefiles track large binary files
375 375 mq manage a stack of patches
376 376 notify hooks for sending email push notifications
377 377 patchbomb command to send changesets as (a series of) patch emails
378 378 purge command to delete untracked files from the working
379 379 directory
380 380 relink recreates hardlinks between repository clones
381 381 schemes extend schemes with shortcuts to repository swarms
382 382 share share a common history between several working directories
383 strip strip changesets and their descendants from history
384 383 transplant command to transplant changesets from another branch
385 384 win32mbcs allow the use of MBCS paths with problematic encodings
386 385 zeroconf discover and advertise repositories on the local network
387 386
388 387 #endif
389 388
390 389 Verify that deprecated extensions are included if --verbose:
391 390
392 391 $ hg -v help extensions | grep children
393 392 children command to display child changesets (DEPRECATED)
394 393
395 394 Verify that extension keywords appear in help templates
396 395
397 396 $ hg help --config extensions.transplant= templating|grep transplant > /dev/null
398 397
399 398 Test short command list with verbose option
400 399
401 400 $ hg -v help shortlist
402 401 Mercurial Distributed SCM
403 402
404 403 basic commands:
405 404
406 405 abort abort an unfinished operation (EXPERIMENTAL)
407 406 add add the specified files on the next commit
408 407 annotate, blame
409 408 show changeset information by line for each file
410 409 clone make a copy of an existing repository
411 410 commit, ci commit the specified files or all outstanding changes
412 411 continue resumes an interrupted operation (EXPERIMENTAL)
413 412 diff diff repository (or selected files)
414 413 export dump the header and diffs for one or more changesets
415 414 forget forget the specified files on the next commit
416 415 init create a new repository in the given directory
417 416 log, history show revision history of entire repository or files
418 417 merge merge another revision into working directory
419 418 pull pull changes from the specified source
420 419 push push changes to the specified destination
421 420 remove, rm remove the specified files on the next commit
422 421 serve start stand-alone webserver
423 422 status, st show changed files in the working directory
424 423 summary, sum summarize working directory state
425 424 update, up, checkout, co
426 425 update working directory (or switch revisions)
427 426
428 427 global options ([+] can be repeated):
429 428
430 429 -R --repository REPO repository root directory or name of overlay bundle
431 430 file
432 431 --cwd DIR change working directory
433 432 -y --noninteractive do not prompt, automatically pick the first choice for
434 433 all prompts
435 434 -q --quiet suppress output
436 435 -v --verbose enable additional output
437 436 --color TYPE when to colorize (boolean, always, auto, never, or
438 437 debug)
439 438 --config CONFIG [+] set/override config option (use 'section.name=value')
440 439 --debug enable debugging output
441 440 --debugger start debugger
442 441 --encoding ENCODE set the charset encoding (default: ascii)
443 442 --encodingmode MODE set the charset encoding mode (default: strict)
444 443 --traceback always print a traceback on exception
445 444 --time time how long the command takes
446 445 --profile print command execution profile
447 446 --version output version information and exit
448 447 -h --help display help and exit
449 448 --hidden consider hidden changesets
450 449 --pager TYPE when to paginate (boolean, always, auto, or never)
451 450 (default: auto)
452 451
453 452 (use 'hg help' for the full list of commands)
454 453
455 454 $ hg add -h
456 455 hg add [OPTION]... [FILE]...
457 456
458 457 add the specified files on the next commit
459 458
460 459 Schedule files to be version controlled and added to the repository.
461 460
462 461 The files will be added to the repository at the next commit. To undo an
463 462 add before that, see 'hg forget'.
464 463
465 464 If no names are given, add all files to the repository (except files
466 465 matching ".hgignore").
467 466
468 467 Returns 0 if all files are successfully added.
469 468
470 469 options ([+] can be repeated):
471 470
472 471 -I --include PATTERN [+] include names matching the given patterns
473 472 -X --exclude PATTERN [+] exclude names matching the given patterns
474 473 -S --subrepos recurse into subrepositories
475 474 -n --dry-run do not perform actions, just print output
476 475
477 476 (some details hidden, use --verbose to show complete help)
478 477
479 478 Verbose help for add
480 479
481 480 $ hg add -hv
482 481 hg add [OPTION]... [FILE]...
483 482
484 483 add the specified files on the next commit
485 484
486 485 Schedule files to be version controlled and added to the repository.
487 486
488 487 The files will be added to the repository at the next commit. To undo an
489 488 add before that, see 'hg forget'.
490 489
491 490 If no names are given, add all files to the repository (except files
492 491 matching ".hgignore").
493 492
494 493 Examples:
495 494
496 495 - New (unknown) files are added automatically by 'hg add':
497 496
498 497 $ ls
499 498 foo.c
500 499 $ hg status
501 500 ? foo.c
502 501 $ hg add
503 502 adding foo.c
504 503 $ hg status
505 504 A foo.c
506 505
507 506 - Specific files to be added can be specified:
508 507
509 508 $ ls
510 509 bar.c foo.c
511 510 $ hg status
512 511 ? bar.c
513 512 ? foo.c
514 513 $ hg add bar.c
515 514 $ hg status
516 515 A bar.c
517 516 ? foo.c
518 517
519 518 Returns 0 if all files are successfully added.
520 519
521 520 options ([+] can be repeated):
522 521
523 522 -I --include PATTERN [+] include names matching the given patterns
524 523 -X --exclude PATTERN [+] exclude names matching the given patterns
525 524 -S --subrepos recurse into subrepositories
526 525 -n --dry-run do not perform actions, just print output
527 526
528 527 global options ([+] can be repeated):
529 528
530 529 -R --repository REPO repository root directory or name of overlay bundle
531 530 file
532 531 --cwd DIR change working directory
533 532 -y --noninteractive do not prompt, automatically pick the first choice for
534 533 all prompts
535 534 -q --quiet suppress output
536 535 -v --verbose enable additional output
537 536 --color TYPE when to colorize (boolean, always, auto, never, or
538 537 debug)
539 538 --config CONFIG [+] set/override config option (use 'section.name=value')
540 539 --debug enable debugging output
541 540 --debugger start debugger
542 541 --encoding ENCODE set the charset encoding (default: ascii)
543 542 --encodingmode MODE set the charset encoding mode (default: strict)
544 543 --traceback always print a traceback on exception
545 544 --time time how long the command takes
546 545 --profile print command execution profile
547 546 --version output version information and exit
548 547 -h --help display help and exit
549 548 --hidden consider hidden changesets
550 549 --pager TYPE when to paginate (boolean, always, auto, or never)
551 550 (default: auto)
552 551
553 552 Test the textwidth config option
554 553
555 554 $ hg root -h --config ui.textwidth=50
556 555 hg root
557 556
558 557 print the root (top) of the current working
559 558 directory
560 559
561 560 Print the root directory of the current
562 561 repository.
563 562
564 563 Returns 0 on success.
565 564
566 565 options:
567 566
568 567 -T --template TEMPLATE display with template
569 568
570 569 (some details hidden, use --verbose to show
571 570 complete help)
572 571
573 572 Test help option with version option
574 573
575 574 $ hg add -h --version
576 575 Mercurial Distributed SCM (version *) (glob)
577 576 (see https://mercurial-scm.org for more information)
578 577
579 578 Copyright (C) 2005-* Matt Mackall and others (glob)
580 579 This is free software; see the source for copying conditions. There is NO
581 580 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
582 581
583 582 $ hg add --skjdfks
584 583 hg add: option --skjdfks not recognized
585 584 hg add [OPTION]... [FILE]...
586 585
587 586 add the specified files on the next commit
588 587
589 588 options ([+] can be repeated):
590 589
591 590 -I --include PATTERN [+] include names matching the given patterns
592 591 -X --exclude PATTERN [+] exclude names matching the given patterns
593 592 -S --subrepos recurse into subrepositories
594 593 -n --dry-run do not perform actions, just print output
595 594
596 595 (use 'hg add -h' to show more help)
597 596 [255]
598 597
599 598 Test ambiguous command help
600 599
601 600 $ hg help ad
602 601 list of commands:
603 602
604 603 add add the specified files on the next commit
605 604 addremove add all new files, delete all missing files
606 605
607 606 (use 'hg help -v ad' to show built-in aliases and global options)
608 607
609 608 Test command without options
610 609
611 610 $ hg help verify
612 611 hg verify
613 612
614 613 verify the integrity of the repository
615 614
616 615 Verify the integrity of the current repository.
617 616
618 617 This will perform an extensive check of the repository's integrity,
619 618 validating the hashes and checksums of each entry in the changelog,
620 619 manifest, and tracked files, as well as the integrity of their crosslinks
621 620 and indices.
622 621
623 622 Please see https://mercurial-scm.org/wiki/RepositoryCorruption for more
624 623 information about recovery from corruption of the repository.
625 624
626 625 Returns 0 on success, 1 if errors are encountered.
627 626
628 627 options:
629 628
630 629 (some details hidden, use --verbose to show complete help)
631 630
632 631 $ hg help diff
633 632 hg diff [OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...
634 633
635 634 diff repository (or selected files)
636 635
637 636 Show differences between revisions for the specified files.
638 637
639 638 Differences between files are shown using the unified diff format.
640 639
641 640 Note:
642 641 'hg diff' may generate unexpected results for merges, as it will
643 642 default to comparing against the working directory's first parent
644 643 changeset if no revisions are specified.
645 644
646 645 When two revision arguments are given, then changes are shown between
647 646 those revisions. If only one revision is specified then that revision is
648 647 compared to the working directory, and, when no revisions are specified,
649 648 the working directory files are compared to its first parent.
650 649
651 650 Alternatively you can specify -c/--change with a revision to see the
652 651 changes in that changeset relative to its first parent.
653 652
654 653 Without the -a/--text option, diff will avoid generating diffs of files it
655 654 detects as binary. With -a, diff will generate a diff anyway, probably
656 655 with undesirable results.
657 656
658 657 Use the -g/--git option to generate diffs in the git extended diff format.
659 658 For more information, read 'hg help diffs'.
660 659
661 660 Returns 0 on success.
662 661
663 662 options ([+] can be repeated):
664 663
665 664 -r --rev REV [+] revision
666 665 -c --change REV change made by revision
667 666 -a --text treat all files as text
668 667 -g --git use git extended diff format
669 668 --binary generate binary diffs in git mode (default)
670 669 --nodates omit dates from diff headers
671 670 --noprefix omit a/ and b/ prefixes from filenames
672 671 -p --show-function show which function each change is in
673 672 --reverse produce a diff that undoes the changes
674 673 -w --ignore-all-space ignore white space when comparing lines
675 674 -b --ignore-space-change ignore changes in the amount of white space
676 675 -B --ignore-blank-lines ignore changes whose lines are all blank
677 676 -Z --ignore-space-at-eol ignore changes in whitespace at EOL
678 677 -U --unified NUM number of lines of context to show
679 678 --stat output diffstat-style summary of changes
680 679 --root DIR produce diffs relative to subdirectory
681 680 -I --include PATTERN [+] include names matching the given patterns
682 681 -X --exclude PATTERN [+] exclude names matching the given patterns
683 682 -S --subrepos recurse into subrepositories
684 683
685 684 (some details hidden, use --verbose to show complete help)
686 685
687 686 $ hg help status
688 687 hg status [OPTION]... [FILE]...
689 688
690 689 aliases: st
691 690
692 691 show changed files in the working directory
693 692
694 693 Show status of files in the repository. If names are given, only files
695 694 that match are shown. Files that are clean or ignored or the source of a
696 695 copy/move operation, are not listed unless -c/--clean, -i/--ignored,
697 696 -C/--copies or -A/--all are given. Unless options described with "show
698 697 only ..." are given, the options -mardu are used.
699 698
700 699 Option -q/--quiet hides untracked (unknown and ignored) files unless
701 700 explicitly requested with -u/--unknown or -i/--ignored.
702 701
703 702 Note:
704 703 'hg status' may appear to disagree with diff if permissions have
705 704 changed or a merge has occurred. The standard diff format does not
706 705 report permission changes and diff only reports changes relative to one
707 706 merge parent.
708 707
709 708 If one revision is given, it is used as the base revision. If two
710 709 revisions are given, the differences between them are shown. The --change
711 710 option can also be used as a shortcut to list the changed files of a
712 711 revision from its first parent.
713 712
714 713 The codes used to show the status of files are:
715 714
716 715 M = modified
717 716 A = added
718 717 R = removed
719 718 C = clean
720 719 ! = missing (deleted by non-hg command, but still tracked)
721 720 ? = not tracked
722 721 I = ignored
723 722 = origin of the previous file (with --copies)
724 723
725 724 Returns 0 on success.
726 725
727 726 options ([+] can be repeated):
728 727
729 728 -A --all show status of all files
730 729 -m --modified show only modified files
731 730 -a --added show only added files
732 731 -r --removed show only removed files
733 732 -d --deleted show only missing files
734 733 -c --clean show only files without changes
735 734 -u --unknown show only unknown (not tracked) files
736 735 -i --ignored show only ignored files
737 736 -n --no-status hide status prefix
738 737 -C --copies show source of copied files
739 738 -0 --print0 end filenames with NUL, for use with xargs
740 739 --rev REV [+] show difference from revision
741 740 --change REV list the changed files of a revision
742 741 -I --include PATTERN [+] include names matching the given patterns
743 742 -X --exclude PATTERN [+] exclude names matching the given patterns
744 743 -S --subrepos recurse into subrepositories
745 744 -T --template TEMPLATE display with template
746 745
747 746 (some details hidden, use --verbose to show complete help)
748 747
749 748 $ hg -q help status
750 749 hg status [OPTION]... [FILE]...
751 750
752 751 show changed files in the working directory
753 752
754 753 $ hg help foo
755 754 abort: no such help topic: foo
756 755 (try 'hg help --keyword foo')
757 756 [255]
758 757
759 758 $ hg skjdfks
760 759 hg: unknown command 'skjdfks'
761 760 (use 'hg help' for a list of commands)
762 761 [255]
763 762
764 763 Typoed command gives suggestion
765 764 $ hg puls
766 765 hg: unknown command 'puls'
767 766 (did you mean one of pull, push?)
768 767 [255]
769 768
770 769 Not enabled extension gets suggested
771 770
772 771 $ hg rebase
773 772 hg: unknown command 'rebase'
774 773 'rebase' is provided by the following extension:
775 774
776 775 rebase command to move sets of revisions to a different ancestor
777 776
778 777 (use 'hg help extensions' for information on enabling extensions)
779 778 [255]
780 779
781 780 Disabled extension gets suggested
782 781 $ hg --config extensions.rebase=! rebase
783 782 hg: unknown command 'rebase'
784 783 'rebase' is provided by the following extension:
785 784
786 785 rebase command to move sets of revisions to a different ancestor
787 786
788 787 (use 'hg help extensions' for information on enabling extensions)
789 788 [255]
790 789
791 790 Checking that help adapts based on the config:
792 791
793 792 $ hg help diff --config ui.tweakdefaults=true | egrep -e '^ *(-g|config)'
794 793 -g --[no-]git use git extended diff format (default: on from
795 794 config)
796 795
797 796 Make sure that we don't run afoul of the help system thinking that
798 797 this is a section and erroring out weirdly.
799 798
800 799 $ hg .log
801 800 hg: unknown command '.log'
802 801 (did you mean log?)
803 802 [255]
804 803
805 804 $ hg log.
806 805 hg: unknown command 'log.'
807 806 (did you mean log?)
808 807 [255]
809 808 $ hg pu.lh
810 809 hg: unknown command 'pu.lh'
811 810 (did you mean one of pull, push?)
812 811 [255]
813 812
814 813 $ cat > helpext.py <<EOF
815 814 > import os
816 815 > from mercurial import commands, fancyopts, registrar
817 816 >
818 817 > def func(arg):
819 818 > return '%sfoo' % arg
820 819 > class customopt(fancyopts.customopt):
821 820 > def newstate(self, oldstate, newparam, abort):
822 821 > return '%sbar' % oldstate
823 822 > cmdtable = {}
824 823 > command = registrar.command(cmdtable)
825 824 >
826 825 > @command(b'nohelp',
827 826 > [(b'', b'longdesc', 3, b'x'*67),
828 827 > (b'n', b'', None, b'normal desc'),
829 828 > (b'', b'newline', b'', b'line1\nline2'),
830 829 > (b'', b'default-off', False, b'enable X'),
831 830 > (b'', b'default-on', True, b'enable Y'),
832 831 > (b'', b'callableopt', func, b'adds foo'),
833 832 > (b'', b'customopt', customopt(''), b'adds bar'),
834 833 > (b'', b'customopt-withdefault', customopt('foo'), b'adds bar')],
835 834 > b'hg nohelp',
836 835 > norepo=True)
837 836 > @command(b'debugoptADV', [(b'', b'aopt', None, b'option is (ADVANCED)')])
838 837 > @command(b'debugoptDEP', [(b'', b'dopt', None, b'option is (DEPRECATED)')])
839 838 > @command(b'debugoptEXP', [(b'', b'eopt', None, b'option is (EXPERIMENTAL)')])
840 839 > def nohelp(ui, *args, **kwargs):
841 840 > pass
842 841 >
843 842 > @command(b'hashelp', [], b'hg hashelp', norepo=True)
844 843 > def hashelp(ui, *args, **kwargs):
845 844 > """Extension command's help"""
846 845 >
847 846 > def uisetup(ui):
848 847 > ui.setconfig(b'alias', b'shellalias', b'!echo hi', b'helpext')
849 848 > ui.setconfig(b'alias', b'hgalias', b'summary', b'helpext')
850 849 > ui.setconfig(b'alias', b'hgalias:doc', b'My doc', b'helpext')
851 850 > ui.setconfig(b'alias', b'hgalias:category', b'navigation', b'helpext')
852 851 > ui.setconfig(b'alias', b'hgaliasnodoc', b'summary', b'helpext')
853 852 >
854 853 > EOF
855 854 $ echo '[extensions]' >> $HGRCPATH
856 855 $ echo "helpext = `pwd`/helpext.py" >> $HGRCPATH
857 856
858 857 Test for aliases
859 858
860 859 $ hg help | grep hgalias
861 860 hgalias My doc
862 861
863 862 $ hg help hgalias
864 863 hg hgalias [--remote]
865 864
866 865 alias for: hg summary
867 866
868 867 My doc
869 868
870 869 defined by: helpext
871 870
872 871 options:
873 872
874 873 --remote check for push and pull
875 874
876 875 (some details hidden, use --verbose to show complete help)
877 876 $ hg help hgaliasnodoc
878 877 hg hgaliasnodoc [--remote]
879 878
880 879 alias for: hg summary
881 880
882 881 summarize working directory state
883 882
884 883 This generates a brief summary of the working directory state, including
885 884 parents, branch, commit status, phase and available updates.
886 885
887 886 With the --remote option, this will check the default paths for incoming
888 887 and outgoing changes. This can be time-consuming.
889 888
890 889 Returns 0 on success.
891 890
892 891 defined by: helpext
893 892
894 893 options:
895 894
896 895 --remote check for push and pull
897 896
898 897 (some details hidden, use --verbose to show complete help)
899 898
900 899 $ hg help shellalias
901 900 hg shellalias
902 901
903 902 shell alias for: echo hi
904 903
905 904 (no help text available)
906 905
907 906 defined by: helpext
908 907
909 908 (some details hidden, use --verbose to show complete help)
910 909
911 910 Test command with no help text
912 911
913 912 $ hg help nohelp
914 913 hg nohelp
915 914
916 915 (no help text available)
917 916
918 917 options:
919 918
920 919 --longdesc VALUE
921 920 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
922 921 xxxxxxxxxxxxxxxxxxxxxxx (default: 3)
923 922 -n -- normal desc
924 923 --newline VALUE line1 line2
925 924 --default-off enable X
926 925 --[no-]default-on enable Y (default: on)
927 926 --callableopt VALUE adds foo
928 927 --customopt VALUE adds bar
929 928 --customopt-withdefault VALUE adds bar (default: foo)
930 929
931 930 (some details hidden, use --verbose to show complete help)
932 931
933 932 Test that default list of commands includes extension commands that have help,
934 933 but not those that don't, except in verbose mode, when a keyword is passed, or
935 934 when help about the extension is requested.
936 935
937 936 #if no-extraextensions
938 937
939 938 $ hg help | grep hashelp
940 939 hashelp Extension command's help
941 940 $ hg help | grep nohelp
942 941 [1]
943 942 $ hg help -v | grep nohelp
944 943 nohelp (no help text available)
945 944
946 945 $ hg help -k nohelp
947 946 Commands:
948 947
949 948 nohelp hg nohelp
950 949
951 950 Extension Commands:
952 951
953 952 nohelp (no help text available)
954 953
955 954 $ hg help helpext
956 955 helpext extension - no help text available
957 956
958 957 list of commands:
959 958
960 959 hashelp Extension command's help
961 960 nohelp (no help text available)
962 961
963 962 (use 'hg help -v helpext' to show built-in aliases and global options)
964 963
965 964 #endif
966 965
967 966 Test list of internal help commands
968 967
969 968 $ hg help debug
970 969 debug commands (internal and unsupported):
971 970
972 971 debugancestor
973 972 find the ancestor revision of two revisions in a given index
974 973 debugantivirusrunning
975 974 attempt to trigger an antivirus scanner to see if one is active
976 975 debugapplystreamclonebundle
977 976 apply a stream clone bundle file
978 977 debugbackupbundle
979 978 lists the changesets available in backup bundles
980 979 debugbuilddag
981 980 builds a repo with a given DAG from scratch in the current
982 981 empty repo
983 982 debugbundle lists the contents of a bundle
984 983 debugcapabilities
985 984 lists the capabilities of a remote peer
986 985 debugchangedfiles
987 986 list the stored files changes for a revision
988 987 debugcheckstate
989 988 validate the correctness of the current dirstate
990 989 debugcolor show available color, effects or style
991 990 debugcommands
992 991 list all available commands and options
993 992 debugcomplete
994 993 returns the completion list associated with the given command
995 994 debugcreatestreamclonebundle
996 995 create a stream clone bundle file
997 996 debugdag format the changelog or an index DAG as a concise textual
998 997 description
999 998 debugdata dump the contents of a data file revision
1000 999 debugdate parse and display a date
1001 1000 debugdeltachain
1002 1001 dump information about delta chains in a revlog
1003 1002 debugdirstate
1004 1003 show the contents of the current dirstate
1005 1004 debugdiscovery
1006 1005 runs the changeset discovery protocol in isolation
1007 1006 debugdownload
1008 1007 download a resource using Mercurial logic and config
1009 1008 debugextensions
1010 1009 show information about active extensions
1011 1010 debugfileset parse and apply a fileset specification
1012 1011 debugformat display format information about the current repository
1013 1012 debugfsinfo show information detected about current filesystem
1014 1013 debuggetbundle
1015 1014 retrieves a bundle from a repo
1016 1015 debugignore display the combined ignore pattern and information about
1017 1016 ignored files
1018 1017 debugindex dump index data for a storage primitive
1019 1018 debugindexdot
1020 1019 dump an index DAG as a graphviz dot file
1021 1020 debugindexstats
1022 1021 show stats related to the changelog index
1023 1022 debuginstall test Mercurial installation
1024 1023 debugknown test whether node ids are known to a repo
1025 1024 debuglocks show or modify state of locks
1026 1025 debugmanifestfulltextcache
1027 1026 show, clear or amend the contents of the manifest fulltext
1028 1027 cache
1029 1028 debugmergestate
1030 1029 print merge state
1031 1030 debugnamecomplete
1032 1031 complete "names" - tags, open branch names, bookmark names
1033 1032 debugnodemap write and inspect on disk nodemap
1034 1033 debugobsolete
1035 1034 create arbitrary obsolete marker
1036 1035 debugoptADV (no help text available)
1037 1036 debugoptDEP (no help text available)
1038 1037 debugoptEXP (no help text available)
1039 1038 debugp1copies
1040 1039 dump copy information compared to p1
1041 1040 debugp2copies
1042 1041 dump copy information compared to p2
1043 1042 debugpathcomplete
1044 1043 complete part or all of a tracked path
1045 1044 debugpathcopies
1046 1045 show copies between two revisions
1047 1046 debugpeer establish a connection to a peer repository
1048 1047 debugpickmergetool
1049 1048 examine which merge tool is chosen for specified file
1050 1049 debugpushkey access the pushkey key/value protocol
1051 1050 debugpvec (no help text available)
1052 1051 debugrebuilddirstate
1053 1052 rebuild the dirstate as it would look like for the given
1054 1053 revision
1055 1054 debugrebuildfncache
1056 1055 rebuild the fncache file
1057 1056 debugrename dump rename information
1058 1057 debugrequires
1059 1058 print the current repo requirements
1060 1059 debugrevlog show data and statistics about a revlog
1061 1060 debugrevlogindex
1062 1061 dump the contents of a revlog index
1063 1062 debugrevspec parse and apply a revision specification
1064 1063 debugserve run a server with advanced settings
1065 1064 debugsetparents
1066 1065 manually set the parents of the current working directory
1067 1066 debugsidedata
1068 1067 dump the side data for a cl/manifest/file revision
1069 1068 debugssl test a secure connection to a server
1069 debugstrip strip changesets and all their descendants from the repository
1070 1070 debugsub (no help text available)
1071 1071 debugsuccessorssets
1072 1072 show set of successors for revision
1073 1073 debugtagscache
1074 1074 display the contents of .hg/cache/hgtagsfnodes1
1075 1075 debugtemplate
1076 1076 parse and apply a template
1077 1077 debuguigetpass
1078 1078 show prompt to type password
1079 1079 debuguiprompt
1080 1080 show plain prompt
1081 1081 debugupdatecaches
1082 1082 warm all known caches in the repository
1083 1083 debugupgraderepo
1084 1084 upgrade a repository to use different features
1085 1085 debugwalk show how files match on given patterns
1086 1086 debugwhyunstable
1087 1087 explain instabilities of a changeset
1088 1088 debugwireargs
1089 1089 (no help text available)
1090 1090 debugwireproto
1091 1091 send wire protocol commands to a server
1092 1092
1093 1093 (use 'hg help -v debug' to show built-in aliases and global options)
1094 1094
1095 1095 internals topic renders index of available sub-topics
1096 1096
1097 1097 $ hg help internals
1098 1098 Technical implementation topics
1099 1099 """""""""""""""""""""""""""""""
1100 1100
1101 1101 To access a subtopic, use "hg help internals.{subtopic-name}"
1102 1102
1103 1103 bid-merge Bid Merge Algorithm
1104 1104 bundle2 Bundle2
1105 1105 bundles Bundles
1106 1106 cbor CBOR
1107 1107 censor Censor
1108 1108 changegroups Changegroups
1109 1109 config Config Registrar
1110 1110 extensions Extension API
1111 1111 mergestate Mergestate
1112 1112 requirements Repository Requirements
1113 1113 revlogs Revision Logs
1114 1114 wireprotocol Wire Protocol
1115 1115 wireprotocolrpc
1116 1116 Wire Protocol RPC
1117 1117 wireprotocolv2
1118 1118 Wire Protocol Version 2
1119 1119
1120 1120 sub-topics can be accessed
1121 1121
1122 1122 $ hg help internals.changegroups
1123 1123 Changegroups
1124 1124 """"""""""""
1125 1125
1126 1126 Changegroups are representations of repository revlog data, specifically
1127 1127 the changelog data, root/flat manifest data, treemanifest data, and
1128 1128 filelogs.
1129 1129
1130 1130 There are 3 versions of changegroups: "1", "2", and "3". From a high-
1131 1131 level, versions "1" and "2" are almost exactly the same, with the only
1132 1132 difference being an additional item in the *delta header*. Version "3"
1133 1133 adds support for storage flags in the *delta header* and optionally
1134 1134 exchanging treemanifests (enabled by setting an option on the
1135 1135 "changegroup" part in the bundle2).
1136 1136
1137 1137 Changegroups when not exchanging treemanifests consist of 3 logical
1138 1138 segments:
1139 1139
1140 1140 +---------------------------------+
1141 1141 | | | |
1142 1142 | changeset | manifest | filelogs |
1143 1143 | | | |
1144 1144 | | | |
1145 1145 +---------------------------------+
1146 1146
1147 1147 When exchanging treemanifests, there are 4 logical segments:
1148 1148
1149 1149 +-------------------------------------------------+
1150 1150 | | | | |
1151 1151 | changeset | root | treemanifests | filelogs |
1152 1152 | | manifest | | |
1153 1153 | | | | |
1154 1154 +-------------------------------------------------+
1155 1155
1156 1156 The principle building block of each segment is a *chunk*. A *chunk* is a
1157 1157 framed piece of data:
1158 1158
1159 1159 +---------------------------------------+
1160 1160 | | |
1161 1161 | length | data |
1162 1162 | (4 bytes) | (<length - 4> bytes) |
1163 1163 | | |
1164 1164 +---------------------------------------+
1165 1165
1166 1166 All integers are big-endian signed integers. Each chunk starts with a
1167 1167 32-bit integer indicating the length of the entire chunk (including the
1168 1168 length field itself).
1169 1169
1170 1170 There is a special case chunk that has a value of 0 for the length
1171 1171 ("0x00000000"). We call this an *empty chunk*.
1172 1172
1173 1173 Delta Groups
1174 1174 ============
1175 1175
1176 1176 A *delta group* expresses the content of a revlog as a series of deltas,
1177 1177 or patches against previous revisions.
1178 1178
1179 1179 Delta groups consist of 0 or more *chunks* followed by the *empty chunk*
1180 1180 to signal the end of the delta group:
1181 1181
1182 1182 +------------------------------------------------------------------------+
1183 1183 | | | | | |
1184 1184 | chunk0 length | chunk0 data | chunk1 length | chunk1 data | 0x0 |
1185 1185 | (4 bytes) | (various) | (4 bytes) | (various) | (4 bytes) |
1186 1186 | | | | | |
1187 1187 +------------------------------------------------------------------------+
1188 1188
1189 1189 Each *chunk*'s data consists of the following:
1190 1190
1191 1191 +---------------------------------------+
1192 1192 | | |
1193 1193 | delta header | delta data |
1194 1194 | (various by version) | (various) |
1195 1195 | | |
1196 1196 +---------------------------------------+
1197 1197
1198 1198 The *delta data* is a series of *delta*s that describe a diff from an
1199 1199 existing entry (either that the recipient already has, or previously
1200 1200 specified in the bundle/changegroup).
1201 1201
1202 1202 The *delta header* is different between versions "1", "2", and "3" of the
1203 1203 changegroup format.
1204 1204
1205 1205 Version 1 (headerlen=80):
1206 1206
1207 1207 +------------------------------------------------------+
1208 1208 | | | | |
1209 1209 | node | p1 node | p2 node | link node |
1210 1210 | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) |
1211 1211 | | | | |
1212 1212 +------------------------------------------------------+
1213 1213
1214 1214 Version 2 (headerlen=100):
1215 1215
1216 1216 +------------------------------------------------------------------+
1217 1217 | | | | | |
1218 1218 | node | p1 node | p2 node | base node | link node |
1219 1219 | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) |
1220 1220 | | | | | |
1221 1221 +------------------------------------------------------------------+
1222 1222
1223 1223 Version 3 (headerlen=102):
1224 1224
1225 1225 +------------------------------------------------------------------------------+
1226 1226 | | | | | | |
1227 1227 | node | p1 node | p2 node | base node | link node | flags |
1228 1228 | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (2 bytes) |
1229 1229 | | | | | | |
1230 1230 +------------------------------------------------------------------------------+
1231 1231
1232 1232 The *delta data* consists of "chunklen - 4 - headerlen" bytes, which
1233 1233 contain a series of *delta*s, densely packed (no separators). These deltas
1234 1234 describe a diff from an existing entry (either that the recipient already
1235 1235 has, or previously specified in the bundle/changegroup). The format is
1236 1236 described more fully in "hg help internals.bdiff", but briefly:
1237 1237
1238 1238 +---------------------------------------------------------------+
1239 1239 | | | | |
1240 1240 | start offset | end offset | new length | content |
1241 1241 | (4 bytes) | (4 bytes) | (4 bytes) | (<new length> bytes) |
1242 1242 | | | | |
1243 1243 +---------------------------------------------------------------+
1244 1244
1245 1245 Please note that the length field in the delta data does *not* include
1246 1246 itself.
1247 1247
1248 1248 In version 1, the delta is always applied against the previous node from
1249 1249 the changegroup or the first parent if this is the first entry in the
1250 1250 changegroup.
1251 1251
1252 1252 In version 2 and up, the delta base node is encoded in the entry in the
1253 1253 changegroup. This allows the delta to be expressed against any parent,
1254 1254 which can result in smaller deltas and more efficient encoding of data.
1255 1255
1256 1256 The *flags* field holds bitwise flags affecting the processing of revision
1257 1257 data. The following flags are defined:
1258 1258
1259 1259 32768
1260 1260 Censored revision. The revision's fulltext has been replaced by censor
1261 1261 metadata. May only occur on file revisions.
1262 1262
1263 1263 16384
1264 1264 Ellipsis revision. Revision hash does not match data (likely due to
1265 1265 rewritten parents).
1266 1266
1267 1267 8192
1268 1268 Externally stored. The revision fulltext contains "key:value" "\n"
1269 1269 delimited metadata defining an object stored elsewhere. Used by the LFS
1270 1270 extension.
1271 1271
1272 1272 For historical reasons, the integer values are identical to revlog version
1273 1273 1 per-revision storage flags and correspond to bits being set in this
1274 1274 2-byte field. Bits were allocated starting from the most-significant bit,
1275 1275 hence the reverse ordering and allocation of these flags.
1276 1276
1277 1277 Changeset Segment
1278 1278 =================
1279 1279
1280 1280 The *changeset segment* consists of a single *delta group* holding
1281 1281 changelog data. The *empty chunk* at the end of the *delta group* denotes
1282 1282 the boundary to the *manifest segment*.
1283 1283
1284 1284 Manifest Segment
1285 1285 ================
1286 1286
1287 1287 The *manifest segment* consists of a single *delta group* holding manifest
1288 1288 data. If treemanifests are in use, it contains only the manifest for the
1289 1289 root directory of the repository. Otherwise, it contains the entire
1290 1290 manifest data. The *empty chunk* at the end of the *delta group* denotes
1291 1291 the boundary to the next segment (either the *treemanifests segment* or
1292 1292 the *filelogs segment*, depending on version and the request options).
1293 1293
1294 1294 Treemanifests Segment
1295 1295 ---------------------
1296 1296
1297 1297 The *treemanifests segment* only exists in changegroup version "3", and
1298 1298 only if the 'treemanifest' param is part of the bundle2 changegroup part
1299 1299 (it is not possible to use changegroup version 3 outside of bundle2).
1300 1300 Aside from the filenames in the *treemanifests segment* containing a
1301 1301 trailing "/" character, it behaves identically to the *filelogs segment*
1302 1302 (see below). The final sub-segment is followed by an *empty chunk*
1303 1303 (logically, a sub-segment with filename size 0). This denotes the boundary
1304 1304 to the *filelogs segment*.
1305 1305
1306 1306 Filelogs Segment
1307 1307 ================
1308 1308
1309 1309 The *filelogs segment* consists of multiple sub-segments, each
1310 1310 corresponding to an individual file whose data is being described:
1311 1311
1312 1312 +--------------------------------------------------+
1313 1313 | | | | | |
1314 1314 | filelog0 | filelog1 | filelog2 | ... | 0x0 |
1315 1315 | | | | | (4 bytes) |
1316 1316 | | | | | |
1317 1317 +--------------------------------------------------+
1318 1318
1319 1319 The final filelog sub-segment is followed by an *empty chunk* (logically,
1320 1320 a sub-segment with filename size 0). This denotes the end of the segment
1321 1321 and of the overall changegroup.
1322 1322
1323 1323 Each filelog sub-segment consists of the following:
1324 1324
1325 1325 +------------------------------------------------------+
1326 1326 | | | |
1327 1327 | filename length | filename | delta group |
1328 1328 | (4 bytes) | (<length - 4> bytes) | (various) |
1329 1329 | | | |
1330 1330 +------------------------------------------------------+
1331 1331
1332 1332 That is, a *chunk* consisting of the filename (not terminated or padded)
1333 1333 followed by N chunks constituting the *delta group* for this file. The
1334 1334 *empty chunk* at the end of each *delta group* denotes the boundary to the
1335 1335 next filelog sub-segment.
1336 1336
1337 1337 non-existent subtopics print an error
1338 1338
1339 1339 $ hg help internals.foo
1340 1340 abort: no such help topic: internals.foo
1341 1341 (try 'hg help --keyword foo')
1342 1342 [255]
1343 1343
1344 1344 test advanced, deprecated and experimental options are hidden in command help
1345 1345 $ hg help debugoptADV
1346 1346 hg debugoptADV
1347 1347
1348 1348 (no help text available)
1349 1349
1350 1350 options:
1351 1351
1352 1352 (some details hidden, use --verbose to show complete help)
1353 1353 $ hg help debugoptDEP
1354 1354 hg debugoptDEP
1355 1355
1356 1356 (no help text available)
1357 1357
1358 1358 options:
1359 1359
1360 1360 (some details hidden, use --verbose to show complete help)
1361 1361
1362 1362 $ hg help debugoptEXP
1363 1363 hg debugoptEXP
1364 1364
1365 1365 (no help text available)
1366 1366
1367 1367 options:
1368 1368
1369 1369 (some details hidden, use --verbose to show complete help)
1370 1370
1371 1371 test advanced, deprecated and experimental options are shown with -v
1372 1372 $ hg help -v debugoptADV | grep aopt
1373 1373 --aopt option is (ADVANCED)
1374 1374 $ hg help -v debugoptDEP | grep dopt
1375 1375 --dopt option is (DEPRECATED)
1376 1376 $ hg help -v debugoptEXP | grep eopt
1377 1377 --eopt option is (EXPERIMENTAL)
1378 1378
1379 1379 #if gettext
1380 1380 test deprecated option is hidden with translation with untranslated description
1381 1381 (use many globy for not failing on changed transaction)
1382 1382 $ LANGUAGE=sv hg help debugoptDEP
1383 1383 hg debugoptDEP
1384 1384
1385 1385 (*) (glob)
1386 1386
1387 1387 options:
1388 1388
1389 1389 (some details hidden, use --verbose to show complete help)
1390 1390 #endif
1391 1391
1392 1392 Test commands that collide with topics (issue4240)
1393 1393
1394 1394 $ hg config -hq
1395 1395 hg config [-u] [NAME]...
1396 1396
1397 1397 show combined config settings from all hgrc files
1398 1398 $ hg showconfig -hq
1399 1399 hg config [-u] [NAME]...
1400 1400
1401 1401 show combined config settings from all hgrc files
1402 1402
1403 1403 Test a help topic
1404 1404
1405 1405 $ hg help dates
1406 1406 Date Formats
1407 1407 """"""""""""
1408 1408
1409 1409 Some commands allow the user to specify a date, e.g.:
1410 1410
1411 1411 - backout, commit, import, tag: Specify the commit date.
1412 1412 - log, revert, update: Select revision(s) by date.
1413 1413
1414 1414 Many date formats are valid. Here are some examples:
1415 1415
1416 1416 - "Wed Dec 6 13:18:29 2006" (local timezone assumed)
1417 1417 - "Dec 6 13:18 -0600" (year assumed, time offset provided)
1418 1418 - "Dec 6 13:18 UTC" (UTC and GMT are aliases for +0000)
1419 1419 - "Dec 6" (midnight)
1420 1420 - "13:18" (today assumed)
1421 1421 - "3:39" (3:39AM assumed)
1422 1422 - "3:39pm" (15:39)
1423 1423 - "2006-12-06 13:18:29" (ISO 8601 format)
1424 1424 - "2006-12-6 13:18"
1425 1425 - "2006-12-6"
1426 1426 - "12-6"
1427 1427 - "12/6"
1428 1428 - "12/6/6" (Dec 6 2006)
1429 1429 - "today" (midnight)
1430 1430 - "yesterday" (midnight)
1431 1431 - "now" - right now
1432 1432
1433 1433 Lastly, there is Mercurial's internal format:
1434 1434
1435 1435 - "1165411109 0" (Wed Dec 6 13:18:29 2006 UTC)
1436 1436
1437 1437 This is the internal representation format for dates. The first number is
1438 1438 the number of seconds since the epoch (1970-01-01 00:00 UTC). The second
1439 1439 is the offset of the local timezone, in seconds west of UTC (negative if
1440 1440 the timezone is east of UTC).
1441 1441
1442 1442 The log command also accepts date ranges:
1443 1443
1444 1444 - "<DATE" - at or before a given date/time
1445 1445 - ">DATE" - on or after a given date/time
1446 1446 - "DATE to DATE" - a date range, inclusive
1447 1447 - "-DAYS" - within a given number of days of today
1448 1448
1449 1449 Test repeated config section name
1450 1450
1451 1451 $ hg help config.host
1452 1452 "http_proxy.host"
1453 1453 Host name and (optional) port of the proxy server, for example
1454 1454 "myproxy:8000".
1455 1455
1456 1456 "smtp.host"
1457 1457 Host name of mail server, e.g. "mail.example.com".
1458 1458
1459 1459
1460 1460 Test section name with dot
1461 1461
1462 1462 $ hg help config.ui.username
1463 1463 "ui.username"
1464 1464 The committer of a changeset created when running "commit". Typically
1465 1465 a person's name and email address, e.g. "Fred Widget
1466 1466 <fred@example.com>". Environment variables in the username are
1467 1467 expanded.
1468 1468
1469 1469 (default: "$EMAIL" or "username@hostname". If the username in hgrc is
1470 1470 empty, e.g. if the system admin set "username =" in the system hgrc,
1471 1471 it has to be specified manually or in a different hgrc file)
1472 1472
1473 1473
1474 1474 $ hg help config.annotate.git
1475 1475 abort: help section not found: config.annotate.git
1476 1476 [255]
1477 1477
1478 1478 $ hg help config.update.check
1479 1479 "commands.update.check"
1480 1480 Determines what level of checking 'hg update' will perform before
1481 1481 moving to a destination revision. Valid values are "abort", "none",
1482 1482 "linear", and "noconflict". "abort" always fails if the working
1483 1483 directory has uncommitted changes. "none" performs no checking, and
1484 1484 may result in a merge with uncommitted changes. "linear" allows any
1485 1485 update as long as it follows a straight line in the revision history,
1486 1486 and may trigger a merge with uncommitted changes. "noconflict" will
1487 1487 allow any update which would not trigger a merge with uncommitted
1488 1488 changes, if any are present. (default: "linear")
1489 1489
1490 1490
1491 1491 $ hg help config.commands.update.check
1492 1492 "commands.update.check"
1493 1493 Determines what level of checking 'hg update' will perform before
1494 1494 moving to a destination revision. Valid values are "abort", "none",
1495 1495 "linear", and "noconflict". "abort" always fails if the working
1496 1496 directory has uncommitted changes. "none" performs no checking, and
1497 1497 may result in a merge with uncommitted changes. "linear" allows any
1498 1498 update as long as it follows a straight line in the revision history,
1499 1499 and may trigger a merge with uncommitted changes. "noconflict" will
1500 1500 allow any update which would not trigger a merge with uncommitted
1501 1501 changes, if any are present. (default: "linear")
1502 1502
1503 1503
1504 1504 $ hg help config.ommands.update.check
1505 1505 abort: help section not found: config.ommands.update.check
1506 1506 [255]
1507 1507
1508 1508 Unrelated trailing paragraphs shouldn't be included
1509 1509
1510 1510 $ hg help config.extramsg | grep '^$'
1511 1511
1512 1512
1513 1513 Test capitalized section name
1514 1514
1515 1515 $ hg help scripting.HGPLAIN > /dev/null
1516 1516
1517 1517 Help subsection:
1518 1518
1519 1519 $ hg help config.charsets |grep "Email example:" > /dev/null
1520 1520 [1]
1521 1521
1522 1522 Show nested definitions
1523 1523 ("profiling.type"[break]"ls"[break]"stat"[break])
1524 1524
1525 1525 $ hg help config.type | egrep '^$'|wc -l
1526 1526 \s*3 (re)
1527 1527
1528 1528 $ hg help config.profiling.type.ls
1529 1529 "profiling.type.ls"
1530 1530 Use Python's built-in instrumenting profiler. This profiler works on
1531 1531 all platforms, but each line number it reports is the first line of
1532 1532 a function. This restriction makes it difficult to identify the
1533 1533 expensive parts of a non-trivial function.
1534 1534
1535 1535
1536 1536 Separate sections from subsections
1537 1537
1538 1538 $ hg help config.format | egrep '^ ("|-)|^\s*$' | uniq
1539 1539 "format"
1540 1540 --------
1541 1541
1542 1542 "usegeneraldelta"
1543 1543
1544 1544 "dotencode"
1545 1545
1546 1546 "usefncache"
1547 1547
1548 1548 "usestore"
1549 1549
1550 1550 "sparse-revlog"
1551 1551
1552 1552 "revlog-compression"
1553 1553
1554 1554 "bookmarks-in-store"
1555 1555
1556 1556 "profiling"
1557 1557 -----------
1558 1558
1559 1559 "format"
1560 1560
1561 1561 "progress"
1562 1562 ----------
1563 1563
1564 1564 "format"
1565 1565
1566 1566
1567 1567 Last item in help config.*:
1568 1568
1569 1569 $ hg help config.`hg help config|grep '^ "'| \
1570 1570 > tail -1|sed 's![ "]*!!g'`| \
1571 1571 > grep 'hg help -c config' > /dev/null
1572 1572 [1]
1573 1573
1574 1574 note to use help -c for general hg help config:
1575 1575
1576 1576 $ hg help config |grep 'hg help -c config' > /dev/null
1577 1577
1578 1578 Test templating help
1579 1579
1580 1580 $ hg help templating | egrep '(desc|diffstat|firstline|nonempty) '
1581 1581 desc String. The text of the changeset description.
1582 1582 diffstat String. Statistics of changes with the following format:
1583 1583 firstline Any text. Returns the first line of text.
1584 1584 nonempty Any text. Returns '(none)' if the string is empty.
1585 1585
1586 1586 Test deprecated items
1587 1587
1588 1588 $ hg help -v templating | grep currentbookmark
1589 1589 currentbookmark
1590 1590 $ hg help templating | (grep currentbookmark || true)
1591 1591
1592 1592 Test help hooks
1593 1593
1594 1594 $ cat > helphook1.py <<EOF
1595 1595 > from mercurial import help
1596 1596 >
1597 1597 > def rewrite(ui, topic, doc):
1598 1598 > return doc + b'\nhelphook1\n'
1599 1599 >
1600 1600 > def extsetup(ui):
1601 1601 > help.addtopichook(b'revisions', rewrite)
1602 1602 > EOF
1603 1603 $ cat > helphook2.py <<EOF
1604 1604 > from mercurial import help
1605 1605 >
1606 1606 > def rewrite(ui, topic, doc):
1607 1607 > return doc + b'\nhelphook2\n'
1608 1608 >
1609 1609 > def extsetup(ui):
1610 1610 > help.addtopichook(b'revisions', rewrite)
1611 1611 > EOF
1612 1612 $ echo '[extensions]' >> $HGRCPATH
1613 1613 $ echo "helphook1 = `pwd`/helphook1.py" >> $HGRCPATH
1614 1614 $ echo "helphook2 = `pwd`/helphook2.py" >> $HGRCPATH
1615 1615 $ hg help revsets | grep helphook
1616 1616 helphook1
1617 1617 helphook2
1618 1618
1619 1619 help -c should only show debug --debug
1620 1620
1621 1621 $ hg help -c --debug|egrep debug|wc -l|egrep '^\s*0\s*$'
1622 1622 [1]
1623 1623
1624 1624 help -c should only show deprecated for -v
1625 1625
1626 1626 $ hg help -c -v|egrep DEPRECATED|wc -l|egrep '^\s*0\s*$'
1627 1627 [1]
1628 1628
1629 1629 Test -s / --system
1630 1630
1631 1631 $ hg help config.files -s windows |grep 'etc/mercurial' | \
1632 1632 > wc -l | sed -e 's/ //g'
1633 1633 0
1634 1634 $ hg help config.files --system unix | grep 'USER' | \
1635 1635 > wc -l | sed -e 's/ //g'
1636 1636 0
1637 1637
1638 1638 Test -e / -c / -k combinations
1639 1639
1640 1640 $ hg help -c|egrep '^[A-Z].*:|^ debug'
1641 1641 Commands:
1642 1642 $ hg help -e|egrep '^[A-Z].*:|^ debug'
1643 1643 Extensions:
1644 1644 $ hg help -k|egrep '^[A-Z].*:|^ debug'
1645 1645 Topics:
1646 1646 Commands:
1647 1647 Extensions:
1648 1648 Extension Commands:
1649 1649 $ hg help -c schemes
1650 1650 abort: no such help topic: schemes
1651 1651 (try 'hg help --keyword schemes')
1652 1652 [255]
1653 1653 $ hg help -e schemes |head -1
1654 1654 schemes extension - extend schemes with shortcuts to repository swarms
1655 1655 $ hg help -c -k dates |egrep '^(Topics|Extensions|Commands):'
1656 1656 Commands:
1657 1657 $ hg help -e -k a |egrep '^(Topics|Extensions|Commands):'
1658 1658 Extensions:
1659 1659 $ hg help -e -c -k date |egrep '^(Topics|Extensions|Commands):'
1660 1660 Extensions:
1661 1661 Commands:
1662 1662 $ hg help -c commit > /dev/null
1663 1663 $ hg help -e -c commit > /dev/null
1664 1664 $ hg help -e commit
1665 1665 abort: no such help topic: commit
1666 1666 (try 'hg help --keyword commit')
1667 1667 [255]
1668 1668
1669 1669 Test keyword search help
1670 1670
1671 1671 $ cat > prefixedname.py <<EOF
1672 1672 > '''matched against word "clone"
1673 1673 > '''
1674 1674 > EOF
1675 1675 $ echo '[extensions]' >> $HGRCPATH
1676 1676 $ echo "dot.dot.prefixedname = `pwd`/prefixedname.py" >> $HGRCPATH
1677 1677 $ hg help -k clone
1678 1678 Topics:
1679 1679
1680 1680 config Configuration Files
1681 1681 extensions Using Additional Features
1682 1682 glossary Glossary
1683 1683 phases Working with Phases
1684 1684 subrepos Subrepositories
1685 1685 urls URL Paths
1686 1686
1687 1687 Commands:
1688 1688
1689 1689 bookmarks create a new bookmark or list existing bookmarks
1690 1690 clone make a copy of an existing repository
1691 1691 paths show aliases for remote repositories
1692 1692 pull pull changes from the specified source
1693 1693 update update working directory (or switch revisions)
1694 1694
1695 1695 Extensions:
1696 1696
1697 1697 clonebundles advertise pre-generated bundles to seed clones
1698 1698 narrow create clones which fetch history data for subset of files
1699 1699 (EXPERIMENTAL)
1700 1700 prefixedname matched against word "clone"
1701 1701 relink recreates hardlinks between repository clones
1702 1702
1703 1703 Extension Commands:
1704 1704
1705 1705 qclone clone main and patch repository at same time
1706 1706
1707 1707 Test unfound topic
1708 1708
1709 1709 $ hg help nonexistingtopicthatwillneverexisteverever
1710 1710 abort: no such help topic: nonexistingtopicthatwillneverexisteverever
1711 1711 (try 'hg help --keyword nonexistingtopicthatwillneverexisteverever')
1712 1712 [255]
1713 1713
1714 1714 Test unfound keyword
1715 1715
1716 1716 $ hg help --keyword nonexistingwordthatwillneverexisteverever
1717 1717 abort: no matches
1718 1718 (try 'hg help' for a list of topics)
1719 1719 [255]
1720 1720
1721 1721 Test omit indicating for help
1722 1722
1723 1723 $ cat > addverboseitems.py <<EOF
1724 1724 > r'''extension to test omit indicating.
1725 1725 >
1726 1726 > This paragraph is never omitted (for extension)
1727 1727 >
1728 1728 > .. container:: verbose
1729 1729 >
1730 1730 > This paragraph is omitted,
1731 1731 > if :hg:\`help\` is invoked without \`\`-v\`\` (for extension)
1732 1732 >
1733 1733 > This paragraph is never omitted, too (for extension)
1734 1734 > '''
1735 1735 > from __future__ import absolute_import
1736 1736 > from mercurial import commands, help
1737 1737 > testtopic = br"""This paragraph is never omitted (for topic).
1738 1738 >
1739 1739 > .. container:: verbose
1740 1740 >
1741 1741 > This paragraph is omitted,
1742 1742 > if :hg:\`help\` is invoked without \`\`-v\`\` (for topic)
1743 1743 >
1744 1744 > This paragraph is never omitted, too (for topic)
1745 1745 > """
1746 1746 > def extsetup(ui):
1747 1747 > help.helptable.append(([b"topic-containing-verbose"],
1748 1748 > b"This is the topic to test omit indicating.",
1749 1749 > lambda ui: testtopic))
1750 1750 > EOF
1751 1751 $ echo '[extensions]' >> $HGRCPATH
1752 1752 $ echo "addverboseitems = `pwd`/addverboseitems.py" >> $HGRCPATH
1753 1753 $ hg help addverboseitems
1754 1754 addverboseitems extension - extension to test omit indicating.
1755 1755
1756 1756 This paragraph is never omitted (for extension)
1757 1757
1758 1758 This paragraph is never omitted, too (for extension)
1759 1759
1760 1760 (some details hidden, use --verbose to show complete help)
1761 1761
1762 1762 no commands defined
1763 1763 $ hg help -v addverboseitems
1764 1764 addverboseitems extension - extension to test omit indicating.
1765 1765
1766 1766 This paragraph is never omitted (for extension)
1767 1767
1768 1768 This paragraph is omitted, if 'hg help' is invoked without "-v" (for
1769 1769 extension)
1770 1770
1771 1771 This paragraph is never omitted, too (for extension)
1772 1772
1773 1773 no commands defined
1774 1774 $ hg help topic-containing-verbose
1775 1775 This is the topic to test omit indicating.
1776 1776 """"""""""""""""""""""""""""""""""""""""""
1777 1777
1778 1778 This paragraph is never omitted (for topic).
1779 1779
1780 1780 This paragraph is never omitted, too (for topic)
1781 1781
1782 1782 (some details hidden, use --verbose to show complete help)
1783 1783 $ hg help -v topic-containing-verbose
1784 1784 This is the topic to test omit indicating.
1785 1785 """"""""""""""""""""""""""""""""""""""""""
1786 1786
1787 1787 This paragraph is never omitted (for topic).
1788 1788
1789 1789 This paragraph is omitted, if 'hg help' is invoked without "-v" (for
1790 1790 topic)
1791 1791
1792 1792 This paragraph is never omitted, too (for topic)
1793 1793
1794 1794 Test section lookup
1795 1795
1796 1796 $ hg help revset.merge
1797 1797 "merge()"
1798 1798 Changeset is a merge changeset.
1799 1799
1800 1800 $ hg help glossary.dag
1801 1801 DAG
1802 1802 The repository of changesets of a distributed version control system
1803 1803 (DVCS) can be described as a directed acyclic graph (DAG), consisting
1804 1804 of nodes and edges, where nodes correspond to changesets and edges
1805 1805 imply a parent -> child relation. This graph can be visualized by
1806 1806 graphical tools such as 'hg log --graph'. In Mercurial, the DAG is
1807 1807 limited by the requirement for children to have at most two parents.
1808 1808
1809 1809
1810 1810 $ hg help hgrc.paths
1811 1811 "paths"
1812 1812 -------
1813 1813
1814 1814 Assigns symbolic names and behavior to repositories.
1815 1815
1816 1816 Options are symbolic names defining the URL or directory that is the
1817 1817 location of the repository. Example:
1818 1818
1819 1819 [paths]
1820 1820 my_server = https://example.com/my_repo
1821 1821 local_path = /home/me/repo
1822 1822
1823 1823 These symbolic names can be used from the command line. To pull from
1824 1824 "my_server": 'hg pull my_server'. To push to "local_path": 'hg push
1825 1825 local_path'.
1826 1826
1827 1827 Options containing colons (":") denote sub-options that can influence
1828 1828 behavior for that specific path. Example:
1829 1829
1830 1830 [paths]
1831 1831 my_server = https://example.com/my_path
1832 1832 my_server:pushurl = ssh://example.com/my_path
1833 1833
1834 1834 The following sub-options can be defined:
1835 1835
1836 1836 "pushurl"
1837 1837 The URL to use for push operations. If not defined, the location
1838 1838 defined by the path's main entry is used.
1839 1839
1840 1840 "pushrev"
1841 1841 A revset defining which revisions to push by default.
1842 1842
1843 1843 When 'hg push' is executed without a "-r" argument, the revset defined
1844 1844 by this sub-option is evaluated to determine what to push.
1845 1845
1846 1846 For example, a value of "." will push the working directory's revision
1847 1847 by default.
1848 1848
1849 1849 Revsets specifying bookmarks will not result in the bookmark being
1850 1850 pushed.
1851 1851
1852 1852 The following special named paths exist:
1853 1853
1854 1854 "default"
1855 1855 The URL or directory to use when no source or remote is specified.
1856 1856
1857 1857 'hg clone' will automatically define this path to the location the
1858 1858 repository was cloned from.
1859 1859
1860 1860 "default-push"
1861 1861 (deprecated) The URL or directory for the default 'hg push' location.
1862 1862 "default:pushurl" should be used instead.
1863 1863
1864 1864 $ hg help glossary.mcguffin
1865 1865 abort: help section not found: glossary.mcguffin
1866 1866 [255]
1867 1867
1868 1868 $ hg help glossary.mc.guffin
1869 1869 abort: help section not found: glossary.mc.guffin
1870 1870 [255]
1871 1871
1872 1872 $ hg help template.files
1873 1873 files List of strings. All files modified, added, or removed by
1874 1874 this changeset.
1875 1875 files(pattern)
1876 1876 All files of the current changeset matching the pattern. See
1877 1877 'hg help patterns'.
1878 1878
1879 1879 Test section lookup by translated message
1880 1880
1881 1881 str.lower() instead of encoding.lower(str) on translated message might
1882 1882 make message meaningless, because some encoding uses 0x41(A) - 0x5a(Z)
1883 1883 as the second or later byte of multi-byte character.
1884 1884
1885 1885 For example, "\x8bL\x98^" (translation of "record" in ja_JP.cp932)
1886 1886 contains 0x4c (L). str.lower() replaces 0x4c(L) by 0x6c(l) and this
1887 1887 replacement makes message meaningless.
1888 1888
1889 1889 This tests that section lookup by translated string isn't broken by
1890 1890 such str.lower().
1891 1891
1892 1892 $ "$PYTHON" <<EOF
1893 1893 > def escape(s):
1894 1894 > return b''.join(b'\\u%x' % ord(uc) for uc in s.decode('cp932'))
1895 1895 > # translation of "record" in ja_JP.cp932
1896 1896 > upper = b"\x8bL\x98^"
1897 1897 > # str.lower()-ed section name should be treated as different one
1898 1898 > lower = b"\x8bl\x98^"
1899 1899 > with open('ambiguous.py', 'wb') as fp:
1900 1900 > fp.write(b"""# ambiguous section names in ja_JP.cp932
1901 1901 > u'''summary of extension
1902 1902 >
1903 1903 > %s
1904 1904 > ----
1905 1905 >
1906 1906 > Upper name should show only this message
1907 1907 >
1908 1908 > %s
1909 1909 > ----
1910 1910 >
1911 1911 > Lower name should show only this message
1912 1912 >
1913 1913 > subsequent section
1914 1914 > ------------------
1915 1915 >
1916 1916 > This should be hidden at 'hg help ambiguous' with section name.
1917 1917 > '''
1918 1918 > """ % (escape(upper), escape(lower)))
1919 1919 > EOF
1920 1920
1921 1921 $ cat >> $HGRCPATH <<EOF
1922 1922 > [extensions]
1923 1923 > ambiguous = ./ambiguous.py
1924 1924 > EOF
1925 1925
1926 1926 $ "$PYTHON" <<EOF | sh
1927 1927 > from mercurial.utils import procutil
1928 1928 > upper = b"\x8bL\x98^"
1929 1929 > procutil.stdout.write(b"hg --encoding cp932 help -e ambiguous.%s\n" % upper)
1930 1930 > EOF
1931 1931 \x8bL\x98^ (esc)
1932 1932 ----
1933 1933
1934 1934 Upper name should show only this message
1935 1935
1936 1936
1937 1937 $ "$PYTHON" <<EOF | sh
1938 1938 > from mercurial.utils import procutil
1939 1939 > lower = b"\x8bl\x98^"
1940 1940 > procutil.stdout.write(b"hg --encoding cp932 help -e ambiguous.%s\n" % lower)
1941 1941 > EOF
1942 1942 \x8bl\x98^ (esc)
1943 1943 ----
1944 1944
1945 1945 Lower name should show only this message
1946 1946
1947 1947
1948 1948 $ cat >> $HGRCPATH <<EOF
1949 1949 > [extensions]
1950 1950 > ambiguous = !
1951 1951 > EOF
1952 1952
1953 1953 Show help content of disabled extensions
1954 1954
1955 1955 $ cat >> $HGRCPATH <<EOF
1956 1956 > [extensions]
1957 1957 > ambiguous = !./ambiguous.py
1958 1958 > EOF
1959 1959 $ hg help -e ambiguous
1960 1960 ambiguous extension - (no help text available)
1961 1961
1962 1962 (use 'hg help extensions' for information on enabling extensions)
1963 1963
1964 1964 Test dynamic list of merge tools only shows up once
1965 1965 $ hg help merge-tools
1966 1966 Merge Tools
1967 1967 """""""""""
1968 1968
1969 1969 To merge files Mercurial uses merge tools.
1970 1970
1971 1971 A merge tool combines two different versions of a file into a merged file.
1972 1972 Merge tools are given the two files and the greatest common ancestor of
1973 1973 the two file versions, so they can determine the changes made on both
1974 1974 branches.
1975 1975
1976 1976 Merge tools are used both for 'hg resolve', 'hg merge', 'hg update', 'hg
1977 1977 backout' and in several extensions.
1978 1978
1979 1979 Usually, the merge tool tries to automatically reconcile the files by
1980 1980 combining all non-overlapping changes that occurred separately in the two
1981 1981 different evolutions of the same initial base file. Furthermore, some
1982 1982 interactive merge programs make it easier to manually resolve conflicting
1983 1983 merges, either in a graphical way, or by inserting some conflict markers.
1984 1984 Mercurial does not include any interactive merge programs but relies on
1985 1985 external tools for that.
1986 1986
1987 1987 Available merge tools
1988 1988 =====================
1989 1989
1990 1990 External merge tools and their properties are configured in the merge-
1991 1991 tools configuration section - see hgrc(5) - but they can often just be
1992 1992 named by their executable.
1993 1993
1994 1994 A merge tool is generally usable if its executable can be found on the
1995 1995 system and if it can handle the merge. The executable is found if it is an
1996 1996 absolute or relative executable path or the name of an application in the
1997 1997 executable search path. The tool is assumed to be able to handle the merge
1998 1998 if it can handle symlinks if the file is a symlink, if it can handle
1999 1999 binary files if the file is binary, and if a GUI is available if the tool
2000 2000 requires a GUI.
2001 2001
2002 2002 There are some internal merge tools which can be used. The internal merge
2003 2003 tools are:
2004 2004
2005 2005 ":dump"
2006 2006 Creates three versions of the files to merge, containing the contents of
2007 2007 local, other and base. These files can then be used to perform a merge
2008 2008 manually. If the file to be merged is named "a.txt", these files will
2009 2009 accordingly be named "a.txt.local", "a.txt.other" and "a.txt.base" and
2010 2010 they will be placed in the same directory as "a.txt".
2011 2011
2012 2012 This implies premerge. Therefore, files aren't dumped, if premerge runs
2013 2013 successfully. Use :forcedump to forcibly write files out.
2014 2014
2015 2015 (actual capabilities: binary, symlink)
2016 2016
2017 2017 ":fail"
2018 2018 Rather than attempting to merge files that were modified on both
2019 2019 branches, it marks them as unresolved. The resolve command must be used
2020 2020 to resolve these conflicts.
2021 2021
2022 2022 (actual capabilities: binary, symlink)
2023 2023
2024 2024 ":forcedump"
2025 2025 Creates three versions of the files as same as :dump, but omits
2026 2026 premerge.
2027 2027
2028 2028 (actual capabilities: binary, symlink)
2029 2029
2030 2030 ":local"
2031 2031 Uses the local 'p1()' version of files as the merged version.
2032 2032
2033 2033 (actual capabilities: binary, symlink)
2034 2034
2035 2035 ":merge"
2036 2036 Uses the internal non-interactive simple merge algorithm for merging
2037 2037 files. It will fail if there are any conflicts and leave markers in the
2038 2038 partially merged file. Markers will have two sections, one for each side
2039 2039 of merge.
2040 2040
2041 2041 ":merge-local"
2042 2042 Like :merge, but resolve all conflicts non-interactively in favor of the
2043 2043 local 'p1()' changes.
2044 2044
2045 2045 ":merge-other"
2046 2046 Like :merge, but resolve all conflicts non-interactively in favor of the
2047 2047 other 'p2()' changes.
2048 2048
2049 2049 ":merge3"
2050 2050 Uses the internal non-interactive simple merge algorithm for merging
2051 2051 files. It will fail if there are any conflicts and leave markers in the
2052 2052 partially merged file. Marker will have three sections, one from each
2053 2053 side of the merge and one for the base content.
2054 2054
2055 2055 ":other"
2056 2056 Uses the other 'p2()' version of files as the merged version.
2057 2057
2058 2058 (actual capabilities: binary, symlink)
2059 2059
2060 2060 ":prompt"
2061 2061 Asks the user which of the local 'p1()' or the other 'p2()' version to
2062 2062 keep as the merged version.
2063 2063
2064 2064 (actual capabilities: binary, symlink)
2065 2065
2066 2066 ":tagmerge"
2067 2067 Uses the internal tag merge algorithm (experimental).
2068 2068
2069 2069 ":union"
2070 2070 Uses the internal non-interactive simple merge algorithm for merging
2071 2071 files. It will use both left and right sides for conflict regions. No
2072 2072 markers are inserted.
2073 2073
2074 2074 Internal tools are always available and do not require a GUI but will by
2075 2075 default not handle symlinks or binary files. See next section for detail
2076 2076 about "actual capabilities" described above.
2077 2077
2078 2078 Choosing a merge tool
2079 2079 =====================
2080 2080
2081 2081 Mercurial uses these rules when deciding which merge tool to use:
2082 2082
2083 2083 1. If a tool has been specified with the --tool option to merge or
2084 2084 resolve, it is used. If it is the name of a tool in the merge-tools
2085 2085 configuration, its configuration is used. Otherwise the specified tool
2086 2086 must be executable by the shell.
2087 2087 2. If the "HGMERGE" environment variable is present, its value is used and
2088 2088 must be executable by the shell.
2089 2089 3. If the filename of the file to be merged matches any of the patterns in
2090 2090 the merge-patterns configuration section, the first usable merge tool
2091 2091 corresponding to a matching pattern is used.
2092 2092 4. If ui.merge is set it will be considered next. If the value is not the
2093 2093 name of a configured tool, the specified value is used and must be
2094 2094 executable by the shell. Otherwise the named tool is used if it is
2095 2095 usable.
2096 2096 5. If any usable merge tools are present in the merge-tools configuration
2097 2097 section, the one with the highest priority is used.
2098 2098 6. If a program named "hgmerge" can be found on the system, it is used -
2099 2099 but it will by default not be used for symlinks and binary files.
2100 2100 7. If the file to be merged is not binary and is not a symlink, then
2101 2101 internal ":merge" is used.
2102 2102 8. Otherwise, ":prompt" is used.
2103 2103
2104 2104 For historical reason, Mercurial treats merge tools as below while
2105 2105 examining rules above.
2106 2106
2107 2107 step specified via binary symlink
2108 2108 ----------------------------------
2109 2109 1. --tool o/o o/o
2110 2110 2. HGMERGE o/o o/o
2111 2111 3. merge-patterns o/o(*) x/?(*)
2112 2112 4. ui.merge x/?(*) x/?(*)
2113 2113
2114 2114 Each capability column indicates Mercurial behavior for internal/external
2115 2115 merge tools at examining each rule.
2116 2116
2117 2117 - "o": "assume that a tool has capability"
2118 2118 - "x": "assume that a tool does not have capability"
2119 2119 - "?": "check actual capability of a tool"
2120 2120
2121 2121 If "merge.strict-capability-check" configuration is true, Mercurial checks
2122 2122 capabilities of merge tools strictly in (*) cases above (= each capability
2123 2123 column becomes "?/?"). It is false by default for backward compatibility.
2124 2124
2125 2125 Note:
2126 2126 After selecting a merge program, Mercurial will by default attempt to
2127 2127 merge the files using a simple merge algorithm first. Only if it
2128 2128 doesn't succeed because of conflicting changes will Mercurial actually
2129 2129 execute the merge program. Whether to use the simple merge algorithm
2130 2130 first can be controlled by the premerge setting of the merge tool.
2131 2131 Premerge is enabled by default unless the file is binary or a symlink.
2132 2132
2133 2133 See the merge-tools and ui sections of hgrc(5) for details on the
2134 2134 configuration of merge tools.
2135 2135
2136 2136 Compression engines listed in `hg help bundlespec`
2137 2137
2138 2138 $ hg help bundlespec | grep gzip
2139 2139 "v1" bundles can only use the "gzip", "bzip2", and "none" compression
2140 2140 An algorithm that produces smaller bundles than "gzip".
2141 2141 This engine will likely produce smaller bundles than "gzip" but will be
2142 2142 "gzip"
2143 2143 better compression than "gzip". It also frequently yields better (?)
2144 2144
2145 2145 Test usage of section marks in help documents
2146 2146
2147 2147 $ cd "$TESTDIR"/../doc
2148 2148 $ "$PYTHON" check-seclevel.py
2149 2149 $ cd $TESTTMP
2150 2150
2151 2151 #if serve
2152 2152
2153 2153 Test the help pages in hgweb.
2154 2154
2155 2155 Dish up an empty repo; serve it cold.
2156 2156
2157 2157 $ hg init "$TESTTMP/test"
2158 2158 $ hg serve -R "$TESTTMP/test" -n test -p $HGPORT -d --pid-file=hg.pid
2159 2159 $ cat hg.pid >> $DAEMON_PIDS
2160 2160
2161 2161 $ get-with-headers.py $LOCALIP:$HGPORT "help"
2162 2162 200 Script output follows
2163 2163
2164 2164 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
2165 2165 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
2166 2166 <head>
2167 2167 <link rel="icon" href="/static/hgicon.png" type="image/png" />
2168 2168 <meta name="robots" content="index, nofollow" />
2169 2169 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
2170 2170 <script type="text/javascript" src="/static/mercurial.js"></script>
2171 2171
2172 2172 <title>Help: Index</title>
2173 2173 </head>
2174 2174 <body>
2175 2175
2176 2176 <div class="container">
2177 2177 <div class="menu">
2178 2178 <div class="logo">
2179 2179 <a href="https://mercurial-scm.org/">
2180 2180 <img src="/static/hglogo.png" alt="mercurial" /></a>
2181 2181 </div>
2182 2182 <ul>
2183 2183 <li><a href="/shortlog">log</a></li>
2184 2184 <li><a href="/graph">graph</a></li>
2185 2185 <li><a href="/tags">tags</a></li>
2186 2186 <li><a href="/bookmarks">bookmarks</a></li>
2187 2187 <li><a href="/branches">branches</a></li>
2188 2188 </ul>
2189 2189 <ul>
2190 2190 <li class="active">help</li>
2191 2191 </ul>
2192 2192 </div>
2193 2193
2194 2194 <div class="main">
2195 2195 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
2196 2196
2197 2197 <form class="search" action="/log">
2198 2198
2199 2199 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
2200 2200 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
2201 2201 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
2202 2202 </form>
2203 2203 <table class="bigtable">
2204 2204 <tr><td colspan="2"><h2><a name="topics" href="#topics">Topics</a></h2></td></tr>
2205 2205
2206 2206 <tr><td>
2207 2207 <a href="/help/bundlespec">
2208 2208 bundlespec
2209 2209 </a>
2210 2210 </td><td>
2211 2211 Bundle File Formats
2212 2212 </td></tr>
2213 2213 <tr><td>
2214 2214 <a href="/help/color">
2215 2215 color
2216 2216 </a>
2217 2217 </td><td>
2218 2218 Colorizing Outputs
2219 2219 </td></tr>
2220 2220 <tr><td>
2221 2221 <a href="/help/config">
2222 2222 config
2223 2223 </a>
2224 2224 </td><td>
2225 2225 Configuration Files
2226 2226 </td></tr>
2227 2227 <tr><td>
2228 2228 <a href="/help/dates">
2229 2229 dates
2230 2230 </a>
2231 2231 </td><td>
2232 2232 Date Formats
2233 2233 </td></tr>
2234 2234 <tr><td>
2235 2235 <a href="/help/deprecated">
2236 2236 deprecated
2237 2237 </a>
2238 2238 </td><td>
2239 2239 Deprecated Features
2240 2240 </td></tr>
2241 2241 <tr><td>
2242 2242 <a href="/help/diffs">
2243 2243 diffs
2244 2244 </a>
2245 2245 </td><td>
2246 2246 Diff Formats
2247 2247 </td></tr>
2248 2248 <tr><td>
2249 2249 <a href="/help/environment">
2250 2250 environment
2251 2251 </a>
2252 2252 </td><td>
2253 2253 Environment Variables
2254 2254 </td></tr>
2255 2255 <tr><td>
2256 2256 <a href="/help/extensions">
2257 2257 extensions
2258 2258 </a>
2259 2259 </td><td>
2260 2260 Using Additional Features
2261 2261 </td></tr>
2262 2262 <tr><td>
2263 2263 <a href="/help/filesets">
2264 2264 filesets
2265 2265 </a>
2266 2266 </td><td>
2267 2267 Specifying File Sets
2268 2268 </td></tr>
2269 2269 <tr><td>
2270 2270 <a href="/help/flags">
2271 2271 flags
2272 2272 </a>
2273 2273 </td><td>
2274 2274 Command-line flags
2275 2275 </td></tr>
2276 2276 <tr><td>
2277 2277 <a href="/help/glossary">
2278 2278 glossary
2279 2279 </a>
2280 2280 </td><td>
2281 2281 Glossary
2282 2282 </td></tr>
2283 2283 <tr><td>
2284 2284 <a href="/help/hgignore">
2285 2285 hgignore
2286 2286 </a>
2287 2287 </td><td>
2288 2288 Syntax for Mercurial Ignore Files
2289 2289 </td></tr>
2290 2290 <tr><td>
2291 2291 <a href="/help/hgweb">
2292 2292 hgweb
2293 2293 </a>
2294 2294 </td><td>
2295 2295 Configuring hgweb
2296 2296 </td></tr>
2297 2297 <tr><td>
2298 2298 <a href="/help/internals">
2299 2299 internals
2300 2300 </a>
2301 2301 </td><td>
2302 2302 Technical implementation topics
2303 2303 </td></tr>
2304 2304 <tr><td>
2305 2305 <a href="/help/merge-tools">
2306 2306 merge-tools
2307 2307 </a>
2308 2308 </td><td>
2309 2309 Merge Tools
2310 2310 </td></tr>
2311 2311 <tr><td>
2312 2312 <a href="/help/pager">
2313 2313 pager
2314 2314 </a>
2315 2315 </td><td>
2316 2316 Pager Support
2317 2317 </td></tr>
2318 2318 <tr><td>
2319 2319 <a href="/help/patterns">
2320 2320 patterns
2321 2321 </a>
2322 2322 </td><td>
2323 2323 File Name Patterns
2324 2324 </td></tr>
2325 2325 <tr><td>
2326 2326 <a href="/help/phases">
2327 2327 phases
2328 2328 </a>
2329 2329 </td><td>
2330 2330 Working with Phases
2331 2331 </td></tr>
2332 2332 <tr><td>
2333 2333 <a href="/help/revisions">
2334 2334 revisions
2335 2335 </a>
2336 2336 </td><td>
2337 2337 Specifying Revisions
2338 2338 </td></tr>
2339 2339 <tr><td>
2340 2340 <a href="/help/scripting">
2341 2341 scripting
2342 2342 </a>
2343 2343 </td><td>
2344 2344 Using Mercurial from scripts and automation
2345 2345 </td></tr>
2346 2346 <tr><td>
2347 2347 <a href="/help/subrepos">
2348 2348 subrepos
2349 2349 </a>
2350 2350 </td><td>
2351 2351 Subrepositories
2352 2352 </td></tr>
2353 2353 <tr><td>
2354 2354 <a href="/help/templating">
2355 2355 templating
2356 2356 </a>
2357 2357 </td><td>
2358 2358 Template Usage
2359 2359 </td></tr>
2360 2360 <tr><td>
2361 2361 <a href="/help/urls">
2362 2362 urls
2363 2363 </a>
2364 2364 </td><td>
2365 2365 URL Paths
2366 2366 </td></tr>
2367 2367 <tr><td>
2368 2368 <a href="/help/topic-containing-verbose">
2369 2369 topic-containing-verbose
2370 2370 </a>
2371 2371 </td><td>
2372 2372 This is the topic to test omit indicating.
2373 2373 </td></tr>
2374 2374
2375 2375
2376 2376 <tr><td colspan="2"><h2><a name="main" href="#main">Main Commands</a></h2></td></tr>
2377 2377
2378 2378 <tr><td>
2379 2379 <a href="/help/abort">
2380 2380 abort
2381 2381 </a>
2382 2382 </td><td>
2383 2383 abort an unfinished operation (EXPERIMENTAL)
2384 2384 </td></tr>
2385 2385 <tr><td>
2386 2386 <a href="/help/add">
2387 2387 add
2388 2388 </a>
2389 2389 </td><td>
2390 2390 add the specified files on the next commit
2391 2391 </td></tr>
2392 2392 <tr><td>
2393 2393 <a href="/help/annotate">
2394 2394 annotate
2395 2395 </a>
2396 2396 </td><td>
2397 2397 show changeset information by line for each file
2398 2398 </td></tr>
2399 2399 <tr><td>
2400 2400 <a href="/help/clone">
2401 2401 clone
2402 2402 </a>
2403 2403 </td><td>
2404 2404 make a copy of an existing repository
2405 2405 </td></tr>
2406 2406 <tr><td>
2407 2407 <a href="/help/commit">
2408 2408 commit
2409 2409 </a>
2410 2410 </td><td>
2411 2411 commit the specified files or all outstanding changes
2412 2412 </td></tr>
2413 2413 <tr><td>
2414 2414 <a href="/help/continue">
2415 2415 continue
2416 2416 </a>
2417 2417 </td><td>
2418 2418 resumes an interrupted operation (EXPERIMENTAL)
2419 2419 </td></tr>
2420 2420 <tr><td>
2421 2421 <a href="/help/diff">
2422 2422 diff
2423 2423 </a>
2424 2424 </td><td>
2425 2425 diff repository (or selected files)
2426 2426 </td></tr>
2427 2427 <tr><td>
2428 2428 <a href="/help/export">
2429 2429 export
2430 2430 </a>
2431 2431 </td><td>
2432 2432 dump the header and diffs for one or more changesets
2433 2433 </td></tr>
2434 2434 <tr><td>
2435 2435 <a href="/help/forget">
2436 2436 forget
2437 2437 </a>
2438 2438 </td><td>
2439 2439 forget the specified files on the next commit
2440 2440 </td></tr>
2441 2441 <tr><td>
2442 2442 <a href="/help/init">
2443 2443 init
2444 2444 </a>
2445 2445 </td><td>
2446 2446 create a new repository in the given directory
2447 2447 </td></tr>
2448 2448 <tr><td>
2449 2449 <a href="/help/log">
2450 2450 log
2451 2451 </a>
2452 2452 </td><td>
2453 2453 show revision history of entire repository or files
2454 2454 </td></tr>
2455 2455 <tr><td>
2456 2456 <a href="/help/merge">
2457 2457 merge
2458 2458 </a>
2459 2459 </td><td>
2460 2460 merge another revision into working directory
2461 2461 </td></tr>
2462 2462 <tr><td>
2463 2463 <a href="/help/pull">
2464 2464 pull
2465 2465 </a>
2466 2466 </td><td>
2467 2467 pull changes from the specified source
2468 2468 </td></tr>
2469 2469 <tr><td>
2470 2470 <a href="/help/push">
2471 2471 push
2472 2472 </a>
2473 2473 </td><td>
2474 2474 push changes to the specified destination
2475 2475 </td></tr>
2476 2476 <tr><td>
2477 2477 <a href="/help/remove">
2478 2478 remove
2479 2479 </a>
2480 2480 </td><td>
2481 2481 remove the specified files on the next commit
2482 2482 </td></tr>
2483 2483 <tr><td>
2484 2484 <a href="/help/serve">
2485 2485 serve
2486 2486 </a>
2487 2487 </td><td>
2488 2488 start stand-alone webserver
2489 2489 </td></tr>
2490 2490 <tr><td>
2491 2491 <a href="/help/status">
2492 2492 status
2493 2493 </a>
2494 2494 </td><td>
2495 2495 show changed files in the working directory
2496 2496 </td></tr>
2497 2497 <tr><td>
2498 2498 <a href="/help/summary">
2499 2499 summary
2500 2500 </a>
2501 2501 </td><td>
2502 2502 summarize working directory state
2503 2503 </td></tr>
2504 2504 <tr><td>
2505 2505 <a href="/help/update">
2506 2506 update
2507 2507 </a>
2508 2508 </td><td>
2509 2509 update working directory (or switch revisions)
2510 2510 </td></tr>
2511 2511
2512 2512
2513 2513
2514 2514 <tr><td colspan="2"><h2><a name="other" href="#other">Other Commands</a></h2></td></tr>
2515 2515
2516 2516 <tr><td>
2517 2517 <a href="/help/addremove">
2518 2518 addremove
2519 2519 </a>
2520 2520 </td><td>
2521 2521 add all new files, delete all missing files
2522 2522 </td></tr>
2523 2523 <tr><td>
2524 2524 <a href="/help/archive">
2525 2525 archive
2526 2526 </a>
2527 2527 </td><td>
2528 2528 create an unversioned archive of a repository revision
2529 2529 </td></tr>
2530 2530 <tr><td>
2531 2531 <a href="/help/backout">
2532 2532 backout
2533 2533 </a>
2534 2534 </td><td>
2535 2535 reverse effect of earlier changeset
2536 2536 </td></tr>
2537 2537 <tr><td>
2538 2538 <a href="/help/bisect">
2539 2539 bisect
2540 2540 </a>
2541 2541 </td><td>
2542 2542 subdivision search of changesets
2543 2543 </td></tr>
2544 2544 <tr><td>
2545 2545 <a href="/help/bookmarks">
2546 2546 bookmarks
2547 2547 </a>
2548 2548 </td><td>
2549 2549 create a new bookmark or list existing bookmarks
2550 2550 </td></tr>
2551 2551 <tr><td>
2552 2552 <a href="/help/branch">
2553 2553 branch
2554 2554 </a>
2555 2555 </td><td>
2556 2556 set or show the current branch name
2557 2557 </td></tr>
2558 2558 <tr><td>
2559 2559 <a href="/help/branches">
2560 2560 branches
2561 2561 </a>
2562 2562 </td><td>
2563 2563 list repository named branches
2564 2564 </td></tr>
2565 2565 <tr><td>
2566 2566 <a href="/help/bundle">
2567 2567 bundle
2568 2568 </a>
2569 2569 </td><td>
2570 2570 create a bundle file
2571 2571 </td></tr>
2572 2572 <tr><td>
2573 2573 <a href="/help/cat">
2574 2574 cat
2575 2575 </a>
2576 2576 </td><td>
2577 2577 output the current or given revision of files
2578 2578 </td></tr>
2579 2579 <tr><td>
2580 2580 <a href="/help/config">
2581 2581 config
2582 2582 </a>
2583 2583 </td><td>
2584 2584 show combined config settings from all hgrc files
2585 2585 </td></tr>
2586 2586 <tr><td>
2587 2587 <a href="/help/copy">
2588 2588 copy
2589 2589 </a>
2590 2590 </td><td>
2591 2591 mark files as copied for the next commit
2592 2592 </td></tr>
2593 2593 <tr><td>
2594 2594 <a href="/help/files">
2595 2595 files
2596 2596 </a>
2597 2597 </td><td>
2598 2598 list tracked files
2599 2599 </td></tr>
2600 2600 <tr><td>
2601 2601 <a href="/help/graft">
2602 2602 graft
2603 2603 </a>
2604 2604 </td><td>
2605 2605 copy changes from other branches onto the current branch
2606 2606 </td></tr>
2607 2607 <tr><td>
2608 2608 <a href="/help/grep">
2609 2609 grep
2610 2610 </a>
2611 2611 </td><td>
2612 2612 search for a pattern in specified files
2613 2613 </td></tr>
2614 2614 <tr><td>
2615 2615 <a href="/help/hashelp">
2616 2616 hashelp
2617 2617 </a>
2618 2618 </td><td>
2619 2619 Extension command's help
2620 2620 </td></tr>
2621 2621 <tr><td>
2622 2622 <a href="/help/heads">
2623 2623 heads
2624 2624 </a>
2625 2625 </td><td>
2626 2626 show branch heads
2627 2627 </td></tr>
2628 2628 <tr><td>
2629 2629 <a href="/help/help">
2630 2630 help
2631 2631 </a>
2632 2632 </td><td>
2633 2633 show help for a given topic or a help overview
2634 2634 </td></tr>
2635 2635 <tr><td>
2636 2636 <a href="/help/hgalias">
2637 2637 hgalias
2638 2638 </a>
2639 2639 </td><td>
2640 2640 My doc
2641 2641 </td></tr>
2642 2642 <tr><td>
2643 2643 <a href="/help/hgaliasnodoc">
2644 2644 hgaliasnodoc
2645 2645 </a>
2646 2646 </td><td>
2647 2647 summarize working directory state
2648 2648 </td></tr>
2649 2649 <tr><td>
2650 2650 <a href="/help/identify">
2651 2651 identify
2652 2652 </a>
2653 2653 </td><td>
2654 2654 identify the working directory or specified revision
2655 2655 </td></tr>
2656 2656 <tr><td>
2657 2657 <a href="/help/import">
2658 2658 import
2659 2659 </a>
2660 2660 </td><td>
2661 2661 import an ordered set of patches
2662 2662 </td></tr>
2663 2663 <tr><td>
2664 2664 <a href="/help/incoming">
2665 2665 incoming
2666 2666 </a>
2667 2667 </td><td>
2668 2668 show new changesets found in source
2669 2669 </td></tr>
2670 2670 <tr><td>
2671 2671 <a href="/help/manifest">
2672 2672 manifest
2673 2673 </a>
2674 2674 </td><td>
2675 2675 output the current or given revision of the project manifest
2676 2676 </td></tr>
2677 2677 <tr><td>
2678 2678 <a href="/help/nohelp">
2679 2679 nohelp
2680 2680 </a>
2681 2681 </td><td>
2682 2682 (no help text available)
2683 2683 </td></tr>
2684 2684 <tr><td>
2685 2685 <a href="/help/outgoing">
2686 2686 outgoing
2687 2687 </a>
2688 2688 </td><td>
2689 2689 show changesets not found in the destination
2690 2690 </td></tr>
2691 2691 <tr><td>
2692 2692 <a href="/help/paths">
2693 2693 paths
2694 2694 </a>
2695 2695 </td><td>
2696 2696 show aliases for remote repositories
2697 2697 </td></tr>
2698 2698 <tr><td>
2699 2699 <a href="/help/phase">
2700 2700 phase
2701 2701 </a>
2702 2702 </td><td>
2703 2703 set or show the current phase name
2704 2704 </td></tr>
2705 2705 <tr><td>
2706 2706 <a href="/help/recover">
2707 2707 recover
2708 2708 </a>
2709 2709 </td><td>
2710 2710 roll back an interrupted transaction
2711 2711 </td></tr>
2712 2712 <tr><td>
2713 2713 <a href="/help/rename">
2714 2714 rename
2715 2715 </a>
2716 2716 </td><td>
2717 2717 rename files; equivalent of copy + remove
2718 2718 </td></tr>
2719 2719 <tr><td>
2720 2720 <a href="/help/resolve">
2721 2721 resolve
2722 2722 </a>
2723 2723 </td><td>
2724 2724 redo merges or set/view the merge status of files
2725 2725 </td></tr>
2726 2726 <tr><td>
2727 2727 <a href="/help/revert">
2728 2728 revert
2729 2729 </a>
2730 2730 </td><td>
2731 2731 restore files to their checkout state
2732 2732 </td></tr>
2733 2733 <tr><td>
2734 2734 <a href="/help/root">
2735 2735 root
2736 2736 </a>
2737 2737 </td><td>
2738 2738 print the root (top) of the current working directory
2739 2739 </td></tr>
2740 2740 <tr><td>
2741 2741 <a href="/help/shellalias">
2742 2742 shellalias
2743 2743 </a>
2744 2744 </td><td>
2745 2745 (no help text available)
2746 2746 </td></tr>
2747 2747 <tr><td>
2748 2748 <a href="/help/shelve">
2749 2749 shelve
2750 2750 </a>
2751 2751 </td><td>
2752 2752 save and set aside changes from the working directory
2753 2753 </td></tr>
2754 2754 <tr><td>
2755 2755 <a href="/help/tag">
2756 2756 tag
2757 2757 </a>
2758 2758 </td><td>
2759 2759 add one or more tags for the current or given revision
2760 2760 </td></tr>
2761 2761 <tr><td>
2762 2762 <a href="/help/tags">
2763 2763 tags
2764 2764 </a>
2765 2765 </td><td>
2766 2766 list repository tags
2767 2767 </td></tr>
2768 2768 <tr><td>
2769 2769 <a href="/help/unbundle">
2770 2770 unbundle
2771 2771 </a>
2772 2772 </td><td>
2773 2773 apply one or more bundle files
2774 2774 </td></tr>
2775 2775 <tr><td>
2776 2776 <a href="/help/unshelve">
2777 2777 unshelve
2778 2778 </a>
2779 2779 </td><td>
2780 2780 restore a shelved change to the working directory
2781 2781 </td></tr>
2782 2782 <tr><td>
2783 2783 <a href="/help/verify">
2784 2784 verify
2785 2785 </a>
2786 2786 </td><td>
2787 2787 verify the integrity of the repository
2788 2788 </td></tr>
2789 2789 <tr><td>
2790 2790 <a href="/help/version">
2791 2791 version
2792 2792 </a>
2793 2793 </td><td>
2794 2794 output version and copyright information
2795 2795 </td></tr>
2796 2796
2797 2797
2798 2798 </table>
2799 2799 </div>
2800 2800 </div>
2801 2801
2802 2802
2803 2803
2804 2804 </body>
2805 2805 </html>
2806 2806
2807 2807
2808 2808 $ get-with-headers.py $LOCALIP:$HGPORT "help/add"
2809 2809 200 Script output follows
2810 2810
2811 2811 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
2812 2812 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
2813 2813 <head>
2814 2814 <link rel="icon" href="/static/hgicon.png" type="image/png" />
2815 2815 <meta name="robots" content="index, nofollow" />
2816 2816 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
2817 2817 <script type="text/javascript" src="/static/mercurial.js"></script>
2818 2818
2819 2819 <title>Help: add</title>
2820 2820 </head>
2821 2821 <body>
2822 2822
2823 2823 <div class="container">
2824 2824 <div class="menu">
2825 2825 <div class="logo">
2826 2826 <a href="https://mercurial-scm.org/">
2827 2827 <img src="/static/hglogo.png" alt="mercurial" /></a>
2828 2828 </div>
2829 2829 <ul>
2830 2830 <li><a href="/shortlog">log</a></li>
2831 2831 <li><a href="/graph">graph</a></li>
2832 2832 <li><a href="/tags">tags</a></li>
2833 2833 <li><a href="/bookmarks">bookmarks</a></li>
2834 2834 <li><a href="/branches">branches</a></li>
2835 2835 </ul>
2836 2836 <ul>
2837 2837 <li class="active"><a href="/help">help</a></li>
2838 2838 </ul>
2839 2839 </div>
2840 2840
2841 2841 <div class="main">
2842 2842 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
2843 2843 <h3>Help: add</h3>
2844 2844
2845 2845 <form class="search" action="/log">
2846 2846
2847 2847 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
2848 2848 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
2849 2849 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
2850 2850 </form>
2851 2851 <div id="doc">
2852 2852 <p>
2853 2853 hg add [OPTION]... [FILE]...
2854 2854 </p>
2855 2855 <p>
2856 2856 add the specified files on the next commit
2857 2857 </p>
2858 2858 <p>
2859 2859 Schedule files to be version controlled and added to the
2860 2860 repository.
2861 2861 </p>
2862 2862 <p>
2863 2863 The files will be added to the repository at the next commit. To
2864 2864 undo an add before that, see 'hg forget'.
2865 2865 </p>
2866 2866 <p>
2867 2867 If no names are given, add all files to the repository (except
2868 2868 files matching &quot;.hgignore&quot;).
2869 2869 </p>
2870 2870 <p>
2871 2871 Examples:
2872 2872 </p>
2873 2873 <ul>
2874 2874 <li> New (unknown) files are added automatically by 'hg add':
2875 2875 <pre>
2876 2876 \$ ls (re)
2877 2877 foo.c
2878 2878 \$ hg status (re)
2879 2879 ? foo.c
2880 2880 \$ hg add (re)
2881 2881 adding foo.c
2882 2882 \$ hg status (re)
2883 2883 A foo.c
2884 2884 </pre>
2885 2885 <li> Specific files to be added can be specified:
2886 2886 <pre>
2887 2887 \$ ls (re)
2888 2888 bar.c foo.c
2889 2889 \$ hg status (re)
2890 2890 ? bar.c
2891 2891 ? foo.c
2892 2892 \$ hg add bar.c (re)
2893 2893 \$ hg status (re)
2894 2894 A bar.c
2895 2895 ? foo.c
2896 2896 </pre>
2897 2897 </ul>
2898 2898 <p>
2899 2899 Returns 0 if all files are successfully added.
2900 2900 </p>
2901 2901 <p>
2902 2902 options ([+] can be repeated):
2903 2903 </p>
2904 2904 <table>
2905 2905 <tr><td>-I</td>
2906 2906 <td>--include PATTERN [+]</td>
2907 2907 <td>include names matching the given patterns</td></tr>
2908 2908 <tr><td>-X</td>
2909 2909 <td>--exclude PATTERN [+]</td>
2910 2910 <td>exclude names matching the given patterns</td></tr>
2911 2911 <tr><td>-S</td>
2912 2912 <td>--subrepos</td>
2913 2913 <td>recurse into subrepositories</td></tr>
2914 2914 <tr><td>-n</td>
2915 2915 <td>--dry-run</td>
2916 2916 <td>do not perform actions, just print output</td></tr>
2917 2917 </table>
2918 2918 <p>
2919 2919 global options ([+] can be repeated):
2920 2920 </p>
2921 2921 <table>
2922 2922 <tr><td>-R</td>
2923 2923 <td>--repository REPO</td>
2924 2924 <td>repository root directory or name of overlay bundle file</td></tr>
2925 2925 <tr><td></td>
2926 2926 <td>--cwd DIR</td>
2927 2927 <td>change working directory</td></tr>
2928 2928 <tr><td>-y</td>
2929 2929 <td>--noninteractive</td>
2930 2930 <td>do not prompt, automatically pick the first choice for all prompts</td></tr>
2931 2931 <tr><td>-q</td>
2932 2932 <td>--quiet</td>
2933 2933 <td>suppress output</td></tr>
2934 2934 <tr><td>-v</td>
2935 2935 <td>--verbose</td>
2936 2936 <td>enable additional output</td></tr>
2937 2937 <tr><td></td>
2938 2938 <td>--color TYPE</td>
2939 2939 <td>when to colorize (boolean, always, auto, never, or debug)</td></tr>
2940 2940 <tr><td></td>
2941 2941 <td>--config CONFIG [+]</td>
2942 2942 <td>set/override config option (use 'section.name=value')</td></tr>
2943 2943 <tr><td></td>
2944 2944 <td>--debug</td>
2945 2945 <td>enable debugging output</td></tr>
2946 2946 <tr><td></td>
2947 2947 <td>--debugger</td>
2948 2948 <td>start debugger</td></tr>
2949 2949 <tr><td></td>
2950 2950 <td>--encoding ENCODE</td>
2951 2951 <td>set the charset encoding (default: ascii)</td></tr>
2952 2952 <tr><td></td>
2953 2953 <td>--encodingmode MODE</td>
2954 2954 <td>set the charset encoding mode (default: strict)</td></tr>
2955 2955 <tr><td></td>
2956 2956 <td>--traceback</td>
2957 2957 <td>always print a traceback on exception</td></tr>
2958 2958 <tr><td></td>
2959 2959 <td>--time</td>
2960 2960 <td>time how long the command takes</td></tr>
2961 2961 <tr><td></td>
2962 2962 <td>--profile</td>
2963 2963 <td>print command execution profile</td></tr>
2964 2964 <tr><td></td>
2965 2965 <td>--version</td>
2966 2966 <td>output version information and exit</td></tr>
2967 2967 <tr><td>-h</td>
2968 2968 <td>--help</td>
2969 2969 <td>display help and exit</td></tr>
2970 2970 <tr><td></td>
2971 2971 <td>--hidden</td>
2972 2972 <td>consider hidden changesets</td></tr>
2973 2973 <tr><td></td>
2974 2974 <td>--pager TYPE</td>
2975 2975 <td>when to paginate (boolean, always, auto, or never) (default: auto)</td></tr>
2976 2976 </table>
2977 2977
2978 2978 </div>
2979 2979 </div>
2980 2980 </div>
2981 2981
2982 2982
2983 2983
2984 2984 </body>
2985 2985 </html>
2986 2986
2987 2987
2988 2988 $ get-with-headers.py $LOCALIP:$HGPORT "help/remove"
2989 2989 200 Script output follows
2990 2990
2991 2991 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
2992 2992 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
2993 2993 <head>
2994 2994 <link rel="icon" href="/static/hgicon.png" type="image/png" />
2995 2995 <meta name="robots" content="index, nofollow" />
2996 2996 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
2997 2997 <script type="text/javascript" src="/static/mercurial.js"></script>
2998 2998
2999 2999 <title>Help: remove</title>
3000 3000 </head>
3001 3001 <body>
3002 3002
3003 3003 <div class="container">
3004 3004 <div class="menu">
3005 3005 <div class="logo">
3006 3006 <a href="https://mercurial-scm.org/">
3007 3007 <img src="/static/hglogo.png" alt="mercurial" /></a>
3008 3008 </div>
3009 3009 <ul>
3010 3010 <li><a href="/shortlog">log</a></li>
3011 3011 <li><a href="/graph">graph</a></li>
3012 3012 <li><a href="/tags">tags</a></li>
3013 3013 <li><a href="/bookmarks">bookmarks</a></li>
3014 3014 <li><a href="/branches">branches</a></li>
3015 3015 </ul>
3016 3016 <ul>
3017 3017 <li class="active"><a href="/help">help</a></li>
3018 3018 </ul>
3019 3019 </div>
3020 3020
3021 3021 <div class="main">
3022 3022 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
3023 3023 <h3>Help: remove</h3>
3024 3024
3025 3025 <form class="search" action="/log">
3026 3026
3027 3027 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
3028 3028 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
3029 3029 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
3030 3030 </form>
3031 3031 <div id="doc">
3032 3032 <p>
3033 3033 hg remove [OPTION]... FILE...
3034 3034 </p>
3035 3035 <p>
3036 3036 aliases: rm
3037 3037 </p>
3038 3038 <p>
3039 3039 remove the specified files on the next commit
3040 3040 </p>
3041 3041 <p>
3042 3042 Schedule the indicated files for removal from the current branch.
3043 3043 </p>
3044 3044 <p>
3045 3045 This command schedules the files to be removed at the next commit.
3046 3046 To undo a remove before that, see 'hg revert'. To undo added
3047 3047 files, see 'hg forget'.
3048 3048 </p>
3049 3049 <p>
3050 3050 -A/--after can be used to remove only files that have already
3051 3051 been deleted, -f/--force can be used to force deletion, and -Af
3052 3052 can be used to remove files from the next revision without
3053 3053 deleting them from the working directory.
3054 3054 </p>
3055 3055 <p>
3056 3056 The following table details the behavior of remove for different
3057 3057 file states (columns) and option combinations (rows). The file
3058 3058 states are Added [A], Clean [C], Modified [M] and Missing [!]
3059 3059 (as reported by 'hg status'). The actions are Warn, Remove
3060 3060 (from branch) and Delete (from disk):
3061 3061 </p>
3062 3062 <table>
3063 3063 <tr><td>opt/state</td>
3064 3064 <td>A</td>
3065 3065 <td>C</td>
3066 3066 <td>M</td>
3067 3067 <td>!</td></tr>
3068 3068 <tr><td>none</td>
3069 3069 <td>W</td>
3070 3070 <td>RD</td>
3071 3071 <td>W</td>
3072 3072 <td>R</td></tr>
3073 3073 <tr><td>-f</td>
3074 3074 <td>R</td>
3075 3075 <td>RD</td>
3076 3076 <td>RD</td>
3077 3077 <td>R</td></tr>
3078 3078 <tr><td>-A</td>
3079 3079 <td>W</td>
3080 3080 <td>W</td>
3081 3081 <td>W</td>
3082 3082 <td>R</td></tr>
3083 3083 <tr><td>-Af</td>
3084 3084 <td>R</td>
3085 3085 <td>R</td>
3086 3086 <td>R</td>
3087 3087 <td>R</td></tr>
3088 3088 </table>
3089 3089 <p>
3090 3090 <b>Note:</b>
3091 3091 </p>
3092 3092 <p>
3093 3093 'hg remove' never deletes files in Added [A] state from the
3094 3094 working directory, not even if &quot;--force&quot; is specified.
3095 3095 </p>
3096 3096 <p>
3097 3097 Returns 0 on success, 1 if any warnings encountered.
3098 3098 </p>
3099 3099 <p>
3100 3100 options ([+] can be repeated):
3101 3101 </p>
3102 3102 <table>
3103 3103 <tr><td>-A</td>
3104 3104 <td>--after</td>
3105 3105 <td>record delete for missing files</td></tr>
3106 3106 <tr><td>-f</td>
3107 3107 <td>--force</td>
3108 3108 <td>forget added files, delete modified files</td></tr>
3109 3109 <tr><td>-S</td>
3110 3110 <td>--subrepos</td>
3111 3111 <td>recurse into subrepositories</td></tr>
3112 3112 <tr><td>-I</td>
3113 3113 <td>--include PATTERN [+]</td>
3114 3114 <td>include names matching the given patterns</td></tr>
3115 3115 <tr><td>-X</td>
3116 3116 <td>--exclude PATTERN [+]</td>
3117 3117 <td>exclude names matching the given patterns</td></tr>
3118 3118 <tr><td>-n</td>
3119 3119 <td>--dry-run</td>
3120 3120 <td>do not perform actions, just print output</td></tr>
3121 3121 </table>
3122 3122 <p>
3123 3123 global options ([+] can be repeated):
3124 3124 </p>
3125 3125 <table>
3126 3126 <tr><td>-R</td>
3127 3127 <td>--repository REPO</td>
3128 3128 <td>repository root directory or name of overlay bundle file</td></tr>
3129 3129 <tr><td></td>
3130 3130 <td>--cwd DIR</td>
3131 3131 <td>change working directory</td></tr>
3132 3132 <tr><td>-y</td>
3133 3133 <td>--noninteractive</td>
3134 3134 <td>do not prompt, automatically pick the first choice for all prompts</td></tr>
3135 3135 <tr><td>-q</td>
3136 3136 <td>--quiet</td>
3137 3137 <td>suppress output</td></tr>
3138 3138 <tr><td>-v</td>
3139 3139 <td>--verbose</td>
3140 3140 <td>enable additional output</td></tr>
3141 3141 <tr><td></td>
3142 3142 <td>--color TYPE</td>
3143 3143 <td>when to colorize (boolean, always, auto, never, or debug)</td></tr>
3144 3144 <tr><td></td>
3145 3145 <td>--config CONFIG [+]</td>
3146 3146 <td>set/override config option (use 'section.name=value')</td></tr>
3147 3147 <tr><td></td>
3148 3148 <td>--debug</td>
3149 3149 <td>enable debugging output</td></tr>
3150 3150 <tr><td></td>
3151 3151 <td>--debugger</td>
3152 3152 <td>start debugger</td></tr>
3153 3153 <tr><td></td>
3154 3154 <td>--encoding ENCODE</td>
3155 3155 <td>set the charset encoding (default: ascii)</td></tr>
3156 3156 <tr><td></td>
3157 3157 <td>--encodingmode MODE</td>
3158 3158 <td>set the charset encoding mode (default: strict)</td></tr>
3159 3159 <tr><td></td>
3160 3160 <td>--traceback</td>
3161 3161 <td>always print a traceback on exception</td></tr>
3162 3162 <tr><td></td>
3163 3163 <td>--time</td>
3164 3164 <td>time how long the command takes</td></tr>
3165 3165 <tr><td></td>
3166 3166 <td>--profile</td>
3167 3167 <td>print command execution profile</td></tr>
3168 3168 <tr><td></td>
3169 3169 <td>--version</td>
3170 3170 <td>output version information and exit</td></tr>
3171 3171 <tr><td>-h</td>
3172 3172 <td>--help</td>
3173 3173 <td>display help and exit</td></tr>
3174 3174 <tr><td></td>
3175 3175 <td>--hidden</td>
3176 3176 <td>consider hidden changesets</td></tr>
3177 3177 <tr><td></td>
3178 3178 <td>--pager TYPE</td>
3179 3179 <td>when to paginate (boolean, always, auto, or never) (default: auto)</td></tr>
3180 3180 </table>
3181 3181
3182 3182 </div>
3183 3183 </div>
3184 3184 </div>
3185 3185
3186 3186
3187 3187
3188 3188 </body>
3189 3189 </html>
3190 3190
3191 3191
3192 3192 $ get-with-headers.py $LOCALIP:$HGPORT "help/dates"
3193 3193 200 Script output follows
3194 3194
3195 3195 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
3196 3196 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
3197 3197 <head>
3198 3198 <link rel="icon" href="/static/hgicon.png" type="image/png" />
3199 3199 <meta name="robots" content="index, nofollow" />
3200 3200 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
3201 3201 <script type="text/javascript" src="/static/mercurial.js"></script>
3202 3202
3203 3203 <title>Help: dates</title>
3204 3204 </head>
3205 3205 <body>
3206 3206
3207 3207 <div class="container">
3208 3208 <div class="menu">
3209 3209 <div class="logo">
3210 3210 <a href="https://mercurial-scm.org/">
3211 3211 <img src="/static/hglogo.png" alt="mercurial" /></a>
3212 3212 </div>
3213 3213 <ul>
3214 3214 <li><a href="/shortlog">log</a></li>
3215 3215 <li><a href="/graph">graph</a></li>
3216 3216 <li><a href="/tags">tags</a></li>
3217 3217 <li><a href="/bookmarks">bookmarks</a></li>
3218 3218 <li><a href="/branches">branches</a></li>
3219 3219 </ul>
3220 3220 <ul>
3221 3221 <li class="active"><a href="/help">help</a></li>
3222 3222 </ul>
3223 3223 </div>
3224 3224
3225 3225 <div class="main">
3226 3226 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
3227 3227 <h3>Help: dates</h3>
3228 3228
3229 3229 <form class="search" action="/log">
3230 3230
3231 3231 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
3232 3232 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
3233 3233 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
3234 3234 </form>
3235 3235 <div id="doc">
3236 3236 <h1>Date Formats</h1>
3237 3237 <p>
3238 3238 Some commands allow the user to specify a date, e.g.:
3239 3239 </p>
3240 3240 <ul>
3241 3241 <li> backout, commit, import, tag: Specify the commit date.
3242 3242 <li> log, revert, update: Select revision(s) by date.
3243 3243 </ul>
3244 3244 <p>
3245 3245 Many date formats are valid. Here are some examples:
3246 3246 </p>
3247 3247 <ul>
3248 3248 <li> &quot;Wed Dec 6 13:18:29 2006&quot; (local timezone assumed)
3249 3249 <li> &quot;Dec 6 13:18 -0600&quot; (year assumed, time offset provided)
3250 3250 <li> &quot;Dec 6 13:18 UTC&quot; (UTC and GMT are aliases for +0000)
3251 3251 <li> &quot;Dec 6&quot; (midnight)
3252 3252 <li> &quot;13:18&quot; (today assumed)
3253 3253 <li> &quot;3:39&quot; (3:39AM assumed)
3254 3254 <li> &quot;3:39pm&quot; (15:39)
3255 3255 <li> &quot;2006-12-06 13:18:29&quot; (ISO 8601 format)
3256 3256 <li> &quot;2006-12-6 13:18&quot;
3257 3257 <li> &quot;2006-12-6&quot;
3258 3258 <li> &quot;12-6&quot;
3259 3259 <li> &quot;12/6&quot;
3260 3260 <li> &quot;12/6/6&quot; (Dec 6 2006)
3261 3261 <li> &quot;today&quot; (midnight)
3262 3262 <li> &quot;yesterday&quot; (midnight)
3263 3263 <li> &quot;now&quot; - right now
3264 3264 </ul>
3265 3265 <p>
3266 3266 Lastly, there is Mercurial's internal format:
3267 3267 </p>
3268 3268 <ul>
3269 3269 <li> &quot;1165411109 0&quot; (Wed Dec 6 13:18:29 2006 UTC)
3270 3270 </ul>
3271 3271 <p>
3272 3272 This is the internal representation format for dates. The first number
3273 3273 is the number of seconds since the epoch (1970-01-01 00:00 UTC). The
3274 3274 second is the offset of the local timezone, in seconds west of UTC
3275 3275 (negative if the timezone is east of UTC).
3276 3276 </p>
3277 3277 <p>
3278 3278 The log command also accepts date ranges:
3279 3279 </p>
3280 3280 <ul>
3281 3281 <li> &quot;&lt;DATE&quot; - at or before a given date/time
3282 3282 <li> &quot;&gt;DATE&quot; - on or after a given date/time
3283 3283 <li> &quot;DATE to DATE&quot; - a date range, inclusive
3284 3284 <li> &quot;-DAYS&quot; - within a given number of days of today
3285 3285 </ul>
3286 3286
3287 3287 </div>
3288 3288 </div>
3289 3289 </div>
3290 3290
3291 3291
3292 3292
3293 3293 </body>
3294 3294 </html>
3295 3295
3296 3296
3297 3297 $ get-with-headers.py $LOCALIP:$HGPORT "help/pager"
3298 3298 200 Script output follows
3299 3299
3300 3300 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
3301 3301 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
3302 3302 <head>
3303 3303 <link rel="icon" href="/static/hgicon.png" type="image/png" />
3304 3304 <meta name="robots" content="index, nofollow" />
3305 3305 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
3306 3306 <script type="text/javascript" src="/static/mercurial.js"></script>
3307 3307
3308 3308 <title>Help: pager</title>
3309 3309 </head>
3310 3310 <body>
3311 3311
3312 3312 <div class="container">
3313 3313 <div class="menu">
3314 3314 <div class="logo">
3315 3315 <a href="https://mercurial-scm.org/">
3316 3316 <img src="/static/hglogo.png" alt="mercurial" /></a>
3317 3317 </div>
3318 3318 <ul>
3319 3319 <li><a href="/shortlog">log</a></li>
3320 3320 <li><a href="/graph">graph</a></li>
3321 3321 <li><a href="/tags">tags</a></li>
3322 3322 <li><a href="/bookmarks">bookmarks</a></li>
3323 3323 <li><a href="/branches">branches</a></li>
3324 3324 </ul>
3325 3325 <ul>
3326 3326 <li class="active"><a href="/help">help</a></li>
3327 3327 </ul>
3328 3328 </div>
3329 3329
3330 3330 <div class="main">
3331 3331 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
3332 3332 <h3>Help: pager</h3>
3333 3333
3334 3334 <form class="search" action="/log">
3335 3335
3336 3336 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
3337 3337 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
3338 3338 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
3339 3339 </form>
3340 3340 <div id="doc">
3341 3341 <h1>Pager Support</h1>
3342 3342 <p>
3343 3343 Some Mercurial commands can produce a lot of output, and Mercurial will
3344 3344 attempt to use a pager to make those commands more pleasant.
3345 3345 </p>
3346 3346 <p>
3347 3347 To set the pager that should be used, set the application variable:
3348 3348 </p>
3349 3349 <pre>
3350 3350 [pager]
3351 3351 pager = less -FRX
3352 3352 </pre>
3353 3353 <p>
3354 3354 If no pager is set in the user or repository configuration, Mercurial uses the
3355 3355 environment variable $PAGER. If $PAGER is not set, pager.pager from the default
3356 3356 or system configuration is used. If none of these are set, a default pager will
3357 3357 be used, typically 'less' on Unix and 'more' on Windows.
3358 3358 </p>
3359 3359 <p>
3360 3360 You can disable the pager for certain commands by adding them to the
3361 3361 pager.ignore list:
3362 3362 </p>
3363 3363 <pre>
3364 3364 [pager]
3365 3365 ignore = version, help, update
3366 3366 </pre>
3367 3367 <p>
3368 3368 To ignore global commands like 'hg version' or 'hg help', you have
3369 3369 to specify them in your user configuration file.
3370 3370 </p>
3371 3371 <p>
3372 3372 To control whether the pager is used at all for an individual command,
3373 3373 you can use --pager=&lt;value&gt;:
3374 3374 </p>
3375 3375 <ul>
3376 3376 <li> use as needed: 'auto'.
3377 3377 <li> require the pager: 'yes' or 'on'.
3378 3378 <li> suppress the pager: 'no' or 'off' (any unrecognized value will also work).
3379 3379 </ul>
3380 3380 <p>
3381 3381 To globally turn off all attempts to use a pager, set:
3382 3382 </p>
3383 3383 <pre>
3384 3384 [ui]
3385 3385 paginate = never
3386 3386 </pre>
3387 3387 <p>
3388 3388 which will prevent the pager from running.
3389 3389 </p>
3390 3390
3391 3391 </div>
3392 3392 </div>
3393 3393 </div>
3394 3394
3395 3395
3396 3396
3397 3397 </body>
3398 3398 </html>
3399 3399
3400 3400
3401 3401 Sub-topic indexes rendered properly
3402 3402
3403 3403 $ get-with-headers.py $LOCALIP:$HGPORT "help/internals"
3404 3404 200 Script output follows
3405 3405
3406 3406 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
3407 3407 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
3408 3408 <head>
3409 3409 <link rel="icon" href="/static/hgicon.png" type="image/png" />
3410 3410 <meta name="robots" content="index, nofollow" />
3411 3411 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
3412 3412 <script type="text/javascript" src="/static/mercurial.js"></script>
3413 3413
3414 3414 <title>Help: internals</title>
3415 3415 </head>
3416 3416 <body>
3417 3417
3418 3418 <div class="container">
3419 3419 <div class="menu">
3420 3420 <div class="logo">
3421 3421 <a href="https://mercurial-scm.org/">
3422 3422 <img src="/static/hglogo.png" alt="mercurial" /></a>
3423 3423 </div>
3424 3424 <ul>
3425 3425 <li><a href="/shortlog">log</a></li>
3426 3426 <li><a href="/graph">graph</a></li>
3427 3427 <li><a href="/tags">tags</a></li>
3428 3428 <li><a href="/bookmarks">bookmarks</a></li>
3429 3429 <li><a href="/branches">branches</a></li>
3430 3430 </ul>
3431 3431 <ul>
3432 3432 <li><a href="/help">help</a></li>
3433 3433 </ul>
3434 3434 </div>
3435 3435
3436 3436 <div class="main">
3437 3437 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
3438 3438
3439 3439 <form class="search" action="/log">
3440 3440
3441 3441 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
3442 3442 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
3443 3443 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
3444 3444 </form>
3445 3445 <table class="bigtable">
3446 3446 <tr><td colspan="2"><h2><a name="topics" href="#topics">Topics</a></h2></td></tr>
3447 3447
3448 3448 <tr><td>
3449 3449 <a href="/help/internals.bid-merge">
3450 3450 bid-merge
3451 3451 </a>
3452 3452 </td><td>
3453 3453 Bid Merge Algorithm
3454 3454 </td></tr>
3455 3455 <tr><td>
3456 3456 <a href="/help/internals.bundle2">
3457 3457 bundle2
3458 3458 </a>
3459 3459 </td><td>
3460 3460 Bundle2
3461 3461 </td></tr>
3462 3462 <tr><td>
3463 3463 <a href="/help/internals.bundles">
3464 3464 bundles
3465 3465 </a>
3466 3466 </td><td>
3467 3467 Bundles
3468 3468 </td></tr>
3469 3469 <tr><td>
3470 3470 <a href="/help/internals.cbor">
3471 3471 cbor
3472 3472 </a>
3473 3473 </td><td>
3474 3474 CBOR
3475 3475 </td></tr>
3476 3476 <tr><td>
3477 3477 <a href="/help/internals.censor">
3478 3478 censor
3479 3479 </a>
3480 3480 </td><td>
3481 3481 Censor
3482 3482 </td></tr>
3483 3483 <tr><td>
3484 3484 <a href="/help/internals.changegroups">
3485 3485 changegroups
3486 3486 </a>
3487 3487 </td><td>
3488 3488 Changegroups
3489 3489 </td></tr>
3490 3490 <tr><td>
3491 3491 <a href="/help/internals.config">
3492 3492 config
3493 3493 </a>
3494 3494 </td><td>
3495 3495 Config Registrar
3496 3496 </td></tr>
3497 3497 <tr><td>
3498 3498 <a href="/help/internals.extensions">
3499 3499 extensions
3500 3500 </a>
3501 3501 </td><td>
3502 3502 Extension API
3503 3503 </td></tr>
3504 3504 <tr><td>
3505 3505 <a href="/help/internals.mergestate">
3506 3506 mergestate
3507 3507 </a>
3508 3508 </td><td>
3509 3509 Mergestate
3510 3510 </td></tr>
3511 3511 <tr><td>
3512 3512 <a href="/help/internals.requirements">
3513 3513 requirements
3514 3514 </a>
3515 3515 </td><td>
3516 3516 Repository Requirements
3517 3517 </td></tr>
3518 3518 <tr><td>
3519 3519 <a href="/help/internals.revlogs">
3520 3520 revlogs
3521 3521 </a>
3522 3522 </td><td>
3523 3523 Revision Logs
3524 3524 </td></tr>
3525 3525 <tr><td>
3526 3526 <a href="/help/internals.wireprotocol">
3527 3527 wireprotocol
3528 3528 </a>
3529 3529 </td><td>
3530 3530 Wire Protocol
3531 3531 </td></tr>
3532 3532 <tr><td>
3533 3533 <a href="/help/internals.wireprotocolrpc">
3534 3534 wireprotocolrpc
3535 3535 </a>
3536 3536 </td><td>
3537 3537 Wire Protocol RPC
3538 3538 </td></tr>
3539 3539 <tr><td>
3540 3540 <a href="/help/internals.wireprotocolv2">
3541 3541 wireprotocolv2
3542 3542 </a>
3543 3543 </td><td>
3544 3544 Wire Protocol Version 2
3545 3545 </td></tr>
3546 3546
3547 3547
3548 3548
3549 3549
3550 3550
3551 3551 </table>
3552 3552 </div>
3553 3553 </div>
3554 3554
3555 3555
3556 3556
3557 3557 </body>
3558 3558 </html>
3559 3559
3560 3560
3561 3561 Sub-topic topics rendered properly
3562 3562
3563 3563 $ get-with-headers.py $LOCALIP:$HGPORT "help/internals.changegroups"
3564 3564 200 Script output follows
3565 3565
3566 3566 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
3567 3567 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
3568 3568 <head>
3569 3569 <link rel="icon" href="/static/hgicon.png" type="image/png" />
3570 3570 <meta name="robots" content="index, nofollow" />
3571 3571 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
3572 3572 <script type="text/javascript" src="/static/mercurial.js"></script>
3573 3573
3574 3574 <title>Help: internals.changegroups</title>
3575 3575 </head>
3576 3576 <body>
3577 3577
3578 3578 <div class="container">
3579 3579 <div class="menu">
3580 3580 <div class="logo">
3581 3581 <a href="https://mercurial-scm.org/">
3582 3582 <img src="/static/hglogo.png" alt="mercurial" /></a>
3583 3583 </div>
3584 3584 <ul>
3585 3585 <li><a href="/shortlog">log</a></li>
3586 3586 <li><a href="/graph">graph</a></li>
3587 3587 <li><a href="/tags">tags</a></li>
3588 3588 <li><a href="/bookmarks">bookmarks</a></li>
3589 3589 <li><a href="/branches">branches</a></li>
3590 3590 </ul>
3591 3591 <ul>
3592 3592 <li class="active"><a href="/help">help</a></li>
3593 3593 </ul>
3594 3594 </div>
3595 3595
3596 3596 <div class="main">
3597 3597 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
3598 3598 <h3>Help: internals.changegroups</h3>
3599 3599
3600 3600 <form class="search" action="/log">
3601 3601
3602 3602 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
3603 3603 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
3604 3604 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
3605 3605 </form>
3606 3606 <div id="doc">
3607 3607 <h1>Changegroups</h1>
3608 3608 <p>
3609 3609 Changegroups are representations of repository revlog data, specifically
3610 3610 the changelog data, root/flat manifest data, treemanifest data, and
3611 3611 filelogs.
3612 3612 </p>
3613 3613 <p>
3614 3614 There are 3 versions of changegroups: &quot;1&quot;, &quot;2&quot;, and &quot;3&quot;. From a
3615 3615 high-level, versions &quot;1&quot; and &quot;2&quot; are almost exactly the same, with the
3616 3616 only difference being an additional item in the *delta header*. Version
3617 3617 &quot;3&quot; adds support for storage flags in the *delta header* and optionally
3618 3618 exchanging treemanifests (enabled by setting an option on the
3619 3619 &quot;changegroup&quot; part in the bundle2).
3620 3620 </p>
3621 3621 <p>
3622 3622 Changegroups when not exchanging treemanifests consist of 3 logical
3623 3623 segments:
3624 3624 </p>
3625 3625 <pre>
3626 3626 +---------------------------------+
3627 3627 | | | |
3628 3628 | changeset | manifest | filelogs |
3629 3629 | | | |
3630 3630 | | | |
3631 3631 +---------------------------------+
3632 3632 </pre>
3633 3633 <p>
3634 3634 When exchanging treemanifests, there are 4 logical segments:
3635 3635 </p>
3636 3636 <pre>
3637 3637 +-------------------------------------------------+
3638 3638 | | | | |
3639 3639 | changeset | root | treemanifests | filelogs |
3640 3640 | | manifest | | |
3641 3641 | | | | |
3642 3642 +-------------------------------------------------+
3643 3643 </pre>
3644 3644 <p>
3645 3645 The principle building block of each segment is a *chunk*. A *chunk*
3646 3646 is a framed piece of data:
3647 3647 </p>
3648 3648 <pre>
3649 3649 +---------------------------------------+
3650 3650 | | |
3651 3651 | length | data |
3652 3652 | (4 bytes) | (&lt;length - 4&gt; bytes) |
3653 3653 | | |
3654 3654 +---------------------------------------+
3655 3655 </pre>
3656 3656 <p>
3657 3657 All integers are big-endian signed integers. Each chunk starts with a 32-bit
3658 3658 integer indicating the length of the entire chunk (including the length field
3659 3659 itself).
3660 3660 </p>
3661 3661 <p>
3662 3662 There is a special case chunk that has a value of 0 for the length
3663 3663 (&quot;0x00000000&quot;). We call this an *empty chunk*.
3664 3664 </p>
3665 3665 <h2>Delta Groups</h2>
3666 3666 <p>
3667 3667 A *delta group* expresses the content of a revlog as a series of deltas,
3668 3668 or patches against previous revisions.
3669 3669 </p>
3670 3670 <p>
3671 3671 Delta groups consist of 0 or more *chunks* followed by the *empty chunk*
3672 3672 to signal the end of the delta group:
3673 3673 </p>
3674 3674 <pre>
3675 3675 +------------------------------------------------------------------------+
3676 3676 | | | | | |
3677 3677 | chunk0 length | chunk0 data | chunk1 length | chunk1 data | 0x0 |
3678 3678 | (4 bytes) | (various) | (4 bytes) | (various) | (4 bytes) |
3679 3679 | | | | | |
3680 3680 +------------------------------------------------------------------------+
3681 3681 </pre>
3682 3682 <p>
3683 3683 Each *chunk*'s data consists of the following:
3684 3684 </p>
3685 3685 <pre>
3686 3686 +---------------------------------------+
3687 3687 | | |
3688 3688 | delta header | delta data |
3689 3689 | (various by version) | (various) |
3690 3690 | | |
3691 3691 +---------------------------------------+
3692 3692 </pre>
3693 3693 <p>
3694 3694 The *delta data* is a series of *delta*s that describe a diff from an existing
3695 3695 entry (either that the recipient already has, or previously specified in the
3696 3696 bundle/changegroup).
3697 3697 </p>
3698 3698 <p>
3699 3699 The *delta header* is different between versions &quot;1&quot;, &quot;2&quot;, and
3700 3700 &quot;3&quot; of the changegroup format.
3701 3701 </p>
3702 3702 <p>
3703 3703 Version 1 (headerlen=80):
3704 3704 </p>
3705 3705 <pre>
3706 3706 +------------------------------------------------------+
3707 3707 | | | | |
3708 3708 | node | p1 node | p2 node | link node |
3709 3709 | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) |
3710 3710 | | | | |
3711 3711 +------------------------------------------------------+
3712 3712 </pre>
3713 3713 <p>
3714 3714 Version 2 (headerlen=100):
3715 3715 </p>
3716 3716 <pre>
3717 3717 +------------------------------------------------------------------+
3718 3718 | | | | | |
3719 3719 | node | p1 node | p2 node | base node | link node |
3720 3720 | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) |
3721 3721 | | | | | |
3722 3722 +------------------------------------------------------------------+
3723 3723 </pre>
3724 3724 <p>
3725 3725 Version 3 (headerlen=102):
3726 3726 </p>
3727 3727 <pre>
3728 3728 +------------------------------------------------------------------------------+
3729 3729 | | | | | | |
3730 3730 | node | p1 node | p2 node | base node | link node | flags |
3731 3731 | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (2 bytes) |
3732 3732 | | | | | | |
3733 3733 +------------------------------------------------------------------------------+
3734 3734 </pre>
3735 3735 <p>
3736 3736 The *delta data* consists of &quot;chunklen - 4 - headerlen&quot; bytes, which contain a
3737 3737 series of *delta*s, densely packed (no separators). These deltas describe a diff
3738 3738 from an existing entry (either that the recipient already has, or previously
3739 3739 specified in the bundle/changegroup). The format is described more fully in
3740 3740 &quot;hg help internals.bdiff&quot;, but briefly:
3741 3741 </p>
3742 3742 <pre>
3743 3743 +---------------------------------------------------------------+
3744 3744 | | | | |
3745 3745 | start offset | end offset | new length | content |
3746 3746 | (4 bytes) | (4 bytes) | (4 bytes) | (&lt;new length&gt; bytes) |
3747 3747 | | | | |
3748 3748 +---------------------------------------------------------------+
3749 3749 </pre>
3750 3750 <p>
3751 3751 Please note that the length field in the delta data does *not* include itself.
3752 3752 </p>
3753 3753 <p>
3754 3754 In version 1, the delta is always applied against the previous node from
3755 3755 the changegroup or the first parent if this is the first entry in the
3756 3756 changegroup.
3757 3757 </p>
3758 3758 <p>
3759 3759 In version 2 and up, the delta base node is encoded in the entry in the
3760 3760 changegroup. This allows the delta to be expressed against any parent,
3761 3761 which can result in smaller deltas and more efficient encoding of data.
3762 3762 </p>
3763 3763 <p>
3764 3764 The *flags* field holds bitwise flags affecting the processing of revision
3765 3765 data. The following flags are defined:
3766 3766 </p>
3767 3767 <dl>
3768 3768 <dt>32768
3769 3769 <dd>Censored revision. The revision's fulltext has been replaced by censor metadata. May only occur on file revisions.
3770 3770 <dt>16384
3771 3771 <dd>Ellipsis revision. Revision hash does not match data (likely due to rewritten parents).
3772 3772 <dt>8192
3773 3773 <dd>Externally stored. The revision fulltext contains &quot;key:value&quot; &quot;\n&quot; delimited metadata defining an object stored elsewhere. Used by the LFS extension.
3774 3774 </dl>
3775 3775 <p>
3776 3776 For historical reasons, the integer values are identical to revlog version 1
3777 3777 per-revision storage flags and correspond to bits being set in this 2-byte
3778 3778 field. Bits were allocated starting from the most-significant bit, hence the
3779 3779 reverse ordering and allocation of these flags.
3780 3780 </p>
3781 3781 <h2>Changeset Segment</h2>
3782 3782 <p>
3783 3783 The *changeset segment* consists of a single *delta group* holding
3784 3784 changelog data. The *empty chunk* at the end of the *delta group* denotes
3785 3785 the boundary to the *manifest segment*.
3786 3786 </p>
3787 3787 <h2>Manifest Segment</h2>
3788 3788 <p>
3789 3789 The *manifest segment* consists of a single *delta group* holding manifest
3790 3790 data. If treemanifests are in use, it contains only the manifest for the
3791 3791 root directory of the repository. Otherwise, it contains the entire
3792 3792 manifest data. The *empty chunk* at the end of the *delta group* denotes
3793 3793 the boundary to the next segment (either the *treemanifests segment* or the
3794 3794 *filelogs segment*, depending on version and the request options).
3795 3795 </p>
3796 3796 <h3>Treemanifests Segment</h3>
3797 3797 <p>
3798 3798 The *treemanifests segment* only exists in changegroup version &quot;3&quot;, and
3799 3799 only if the 'treemanifest' param is part of the bundle2 changegroup part
3800 3800 (it is not possible to use changegroup version 3 outside of bundle2).
3801 3801 Aside from the filenames in the *treemanifests segment* containing a
3802 3802 trailing &quot;/&quot; character, it behaves identically to the *filelogs segment*
3803 3803 (see below). The final sub-segment is followed by an *empty chunk* (logically,
3804 3804 a sub-segment with filename size 0). This denotes the boundary to the
3805 3805 *filelogs segment*.
3806 3806 </p>
3807 3807 <h2>Filelogs Segment</h2>
3808 3808 <p>
3809 3809 The *filelogs segment* consists of multiple sub-segments, each
3810 3810 corresponding to an individual file whose data is being described:
3811 3811 </p>
3812 3812 <pre>
3813 3813 +--------------------------------------------------+
3814 3814 | | | | | |
3815 3815 | filelog0 | filelog1 | filelog2 | ... | 0x0 |
3816 3816 | | | | | (4 bytes) |
3817 3817 | | | | | |
3818 3818 +--------------------------------------------------+
3819 3819 </pre>
3820 3820 <p>
3821 3821 The final filelog sub-segment is followed by an *empty chunk* (logically,
3822 3822 a sub-segment with filename size 0). This denotes the end of the segment
3823 3823 and of the overall changegroup.
3824 3824 </p>
3825 3825 <p>
3826 3826 Each filelog sub-segment consists of the following:
3827 3827 </p>
3828 3828 <pre>
3829 3829 +------------------------------------------------------+
3830 3830 | | | |
3831 3831 | filename length | filename | delta group |
3832 3832 | (4 bytes) | (&lt;length - 4&gt; bytes) | (various) |
3833 3833 | | | |
3834 3834 +------------------------------------------------------+
3835 3835 </pre>
3836 3836 <p>
3837 3837 That is, a *chunk* consisting of the filename (not terminated or padded)
3838 3838 followed by N chunks constituting the *delta group* for this file. The
3839 3839 *empty chunk* at the end of each *delta group* denotes the boundary to the
3840 3840 next filelog sub-segment.
3841 3841 </p>
3842 3842
3843 3843 </div>
3844 3844 </div>
3845 3845 </div>
3846 3846
3847 3847
3848 3848
3849 3849 </body>
3850 3850 </html>
3851 3851
3852 3852
3853 3853 $ get-with-headers.py 127.0.0.1:$HGPORT "help/unknowntopic"
3854 3854 404 Not Found
3855 3855
3856 3856 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
3857 3857 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
3858 3858 <head>
3859 3859 <link rel="icon" href="/static/hgicon.png" type="image/png" />
3860 3860 <meta name="robots" content="index, nofollow" />
3861 3861 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
3862 3862 <script type="text/javascript" src="/static/mercurial.js"></script>
3863 3863
3864 3864 <title>test: error</title>
3865 3865 </head>
3866 3866 <body>
3867 3867
3868 3868 <div class="container">
3869 3869 <div class="menu">
3870 3870 <div class="logo">
3871 3871 <a href="https://mercurial-scm.org/">
3872 3872 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
3873 3873 </div>
3874 3874 <ul>
3875 3875 <li><a href="/shortlog">log</a></li>
3876 3876 <li><a href="/graph">graph</a></li>
3877 3877 <li><a href="/tags">tags</a></li>
3878 3878 <li><a href="/bookmarks">bookmarks</a></li>
3879 3879 <li><a href="/branches">branches</a></li>
3880 3880 </ul>
3881 3881 <ul>
3882 3882 <li><a href="/help">help</a></li>
3883 3883 </ul>
3884 3884 </div>
3885 3885
3886 3886 <div class="main">
3887 3887
3888 3888 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
3889 3889 <h3>error</h3>
3890 3890
3891 3891
3892 3892 <form class="search" action="/log">
3893 3893
3894 3894 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
3895 3895 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
3896 3896 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
3897 3897 </form>
3898 3898
3899 3899 <div class="description">
3900 3900 <p>
3901 3901 An error occurred while processing your request:
3902 3902 </p>
3903 3903 <p>
3904 3904 Not Found
3905 3905 </p>
3906 3906 </div>
3907 3907 </div>
3908 3908 </div>
3909 3909
3910 3910
3911 3911
3912 3912 </body>
3913 3913 </html>
3914 3914
3915 3915 [1]
3916 3916
3917 3917 $ killdaemons.py
3918 3918
3919 3919 #endif
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now