##// END OF EJS Templates
debugobsolete: add formatter support (issue5134)...
Yuya Nishihara -
r29795:142ae018 default
parent child Browse files
Show More
@@ -1,3562 +1,3563 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import os
12 12 import re
13 13 import sys
14 14 import tempfile
15 15
16 16 from .i18n import _
17 17 from .node import (
18 18 bin,
19 19 hex,
20 20 nullid,
21 21 nullrev,
22 22 short,
23 23 )
24 24
25 25 from . import (
26 26 bookmarks,
27 27 changelog,
28 28 copies,
29 29 crecord as crecordmod,
30 30 encoding,
31 31 error,
32 32 formatter,
33 33 graphmod,
34 34 lock as lockmod,
35 35 match as matchmod,
36 36 obsolete,
37 37 patch,
38 38 pathutil,
39 39 phases,
40 40 repair,
41 41 revlog,
42 42 revset,
43 43 scmutil,
44 44 templatekw,
45 45 templater,
46 46 util,
47 47 )
48 48 stringio = util.stringio
49 49
50 50 def ishunk(x):
51 51 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
52 52 return isinstance(x, hunkclasses)
53 53
54 54 def newandmodified(chunks, originalchunks):
55 55 newlyaddedandmodifiedfiles = set()
56 56 for chunk in chunks:
57 57 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
58 58 originalchunks:
59 59 newlyaddedandmodifiedfiles.add(chunk.header.filename())
60 60 return newlyaddedandmodifiedfiles
61 61
62 62 def parsealiases(cmd):
63 63 return cmd.lstrip("^").split("|")
64 64
65 65 def setupwrapcolorwrite(ui):
66 66 # wrap ui.write so diff output can be labeled/colorized
67 67 def wrapwrite(orig, *args, **kw):
68 68 label = kw.pop('label', '')
69 69 for chunk, l in patch.difflabel(lambda: args):
70 70 orig(chunk, label=label + l)
71 71
72 72 oldwrite = ui.write
73 73 def wrap(*args, **kwargs):
74 74 return wrapwrite(oldwrite, *args, **kwargs)
75 75 setattr(ui, 'write', wrap)
76 76 return oldwrite
77 77
78 78 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
79 79 if usecurses:
80 80 if testfile:
81 81 recordfn = crecordmod.testdecorator(testfile,
82 82 crecordmod.testchunkselector)
83 83 else:
84 84 recordfn = crecordmod.chunkselector
85 85
86 86 return crecordmod.filterpatch(ui, originalhunks, recordfn)
87 87
88 88 else:
89 89 return patch.filterpatch(ui, originalhunks, operation)
90 90
91 91 def recordfilter(ui, originalhunks, operation=None):
92 92 """ Prompts the user to filter the originalhunks and return a list of
93 93 selected hunks.
94 94 *operation* is used for to build ui messages to indicate the user what
95 95 kind of filtering they are doing: reverting, committing, shelving, etc.
96 96 (see patch.filterpatch).
97 97 """
98 98 usecurses = crecordmod.checkcurses(ui)
99 99 testfile = ui.config('experimental', 'crecordtest', None)
100 100 oldwrite = setupwrapcolorwrite(ui)
101 101 try:
102 102 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
103 103 testfile, operation)
104 104 finally:
105 105 ui.write = oldwrite
106 106 return newchunks, newopts
107 107
108 108 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
109 109 filterfn, *pats, **opts):
110 110 from . import merge as mergemod
111 111 if not ui.interactive():
112 112 if cmdsuggest:
113 113 msg = _('running non-interactively, use %s instead') % cmdsuggest
114 114 else:
115 115 msg = _('running non-interactively')
116 116 raise error.Abort(msg)
117 117
118 118 # make sure username is set before going interactive
119 119 if not opts.get('user'):
120 120 ui.username() # raise exception, username not provided
121 121
122 122 def recordfunc(ui, repo, message, match, opts):
123 123 """This is generic record driver.
124 124
125 125 Its job is to interactively filter local changes, and
126 126 accordingly prepare working directory into a state in which the
127 127 job can be delegated to a non-interactive commit command such as
128 128 'commit' or 'qrefresh'.
129 129
130 130 After the actual job is done by non-interactive command, the
131 131 working directory is restored to its original state.
132 132
133 133 In the end we'll record interesting changes, and everything else
134 134 will be left in place, so the user can continue working.
135 135 """
136 136
137 137 checkunfinished(repo, commit=True)
138 138 wctx = repo[None]
139 139 merge = len(wctx.parents()) > 1
140 140 if merge:
141 141 raise error.Abort(_('cannot partially commit a merge '
142 142 '(use "hg commit" instead)'))
143 143
144 144 def fail(f, msg):
145 145 raise error.Abort('%s: %s' % (f, msg))
146 146
147 147 force = opts.get('force')
148 148 if not force:
149 149 vdirs = []
150 150 match.explicitdir = vdirs.append
151 151 match.bad = fail
152 152
153 153 status = repo.status(match=match)
154 154 if not force:
155 155 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
156 156 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
157 157 diffopts.nodates = True
158 158 diffopts.git = True
159 159 diffopts.showfunc = True
160 160 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
161 161 originalchunks = patch.parsepatch(originaldiff)
162 162
163 163 # 1. filter patch, since we are intending to apply subset of it
164 164 try:
165 165 chunks, newopts = filterfn(ui, originalchunks)
166 166 except patch.PatchError as err:
167 167 raise error.Abort(_('error parsing patch: %s') % err)
168 168 opts.update(newopts)
169 169
170 170 # We need to keep a backup of files that have been newly added and
171 171 # modified during the recording process because there is a previous
172 172 # version without the edit in the workdir
173 173 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
174 174 contenders = set()
175 175 for h in chunks:
176 176 try:
177 177 contenders.update(set(h.files()))
178 178 except AttributeError:
179 179 pass
180 180
181 181 changed = status.modified + status.added + status.removed
182 182 newfiles = [f for f in changed if f in contenders]
183 183 if not newfiles:
184 184 ui.status(_('no changes to record\n'))
185 185 return 0
186 186
187 187 modified = set(status.modified)
188 188
189 189 # 2. backup changed files, so we can restore them in the end
190 190
191 191 if backupall:
192 192 tobackup = changed
193 193 else:
194 194 tobackup = [f for f in newfiles if f in modified or f in \
195 195 newlyaddedandmodifiedfiles]
196 196 backups = {}
197 197 if tobackup:
198 198 backupdir = repo.join('record-backups')
199 199 try:
200 200 os.mkdir(backupdir)
201 201 except OSError as err:
202 202 if err.errno != errno.EEXIST:
203 203 raise
204 204 try:
205 205 # backup continues
206 206 for f in tobackup:
207 207 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
208 208 dir=backupdir)
209 209 os.close(fd)
210 210 ui.debug('backup %r as %r\n' % (f, tmpname))
211 211 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
212 212 backups[f] = tmpname
213 213
214 214 fp = stringio()
215 215 for c in chunks:
216 216 fname = c.filename()
217 217 if fname in backups:
218 218 c.write(fp)
219 219 dopatch = fp.tell()
220 220 fp.seek(0)
221 221
222 222 # 2.5 optionally review / modify patch in text editor
223 223 if opts.get('review', False):
224 224 patchtext = (crecordmod.diffhelptext
225 225 + crecordmod.patchhelptext
226 226 + fp.read())
227 227 reviewedpatch = ui.edit(patchtext, "",
228 228 extra={"suffix": ".diff"})
229 229 fp.truncate(0)
230 230 fp.write(reviewedpatch)
231 231 fp.seek(0)
232 232
233 233 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
234 234 # 3a. apply filtered patch to clean repo (clean)
235 235 if backups:
236 236 # Equivalent to hg.revert
237 237 m = scmutil.matchfiles(repo, backups.keys())
238 238 mergemod.update(repo, repo.dirstate.p1(),
239 239 False, True, matcher=m)
240 240
241 241 # 3b. (apply)
242 242 if dopatch:
243 243 try:
244 244 ui.debug('applying patch\n')
245 245 ui.debug(fp.getvalue())
246 246 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
247 247 except patch.PatchError as err:
248 248 raise error.Abort(str(err))
249 249 del fp
250 250
251 251 # 4. We prepared working directory according to filtered
252 252 # patch. Now is the time to delegate the job to
253 253 # commit/qrefresh or the like!
254 254
255 255 # Make all of the pathnames absolute.
256 256 newfiles = [repo.wjoin(nf) for nf in newfiles]
257 257 return commitfunc(ui, repo, *newfiles, **opts)
258 258 finally:
259 259 # 5. finally restore backed-up files
260 260 try:
261 261 dirstate = repo.dirstate
262 262 for realname, tmpname in backups.iteritems():
263 263 ui.debug('restoring %r to %r\n' % (tmpname, realname))
264 264
265 265 if dirstate[realname] == 'n':
266 266 # without normallookup, restoring timestamp
267 267 # may cause partially committed files
268 268 # to be treated as unmodified
269 269 dirstate.normallookup(realname)
270 270
271 271 # copystat=True here and above are a hack to trick any
272 272 # editors that have f open that we haven't modified them.
273 273 #
274 274 # Also note that this racy as an editor could notice the
275 275 # file's mtime before we've finished writing it.
276 276 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
277 277 os.unlink(tmpname)
278 278 if tobackup:
279 279 os.rmdir(backupdir)
280 280 except OSError:
281 281 pass
282 282
283 283 def recordinwlock(ui, repo, message, match, opts):
284 284 with repo.wlock():
285 285 return recordfunc(ui, repo, message, match, opts)
286 286
287 287 return commit(ui, repo, recordinwlock, pats, opts)
288 288
289 289 def findpossible(cmd, table, strict=False):
290 290 """
291 291 Return cmd -> (aliases, command table entry)
292 292 for each matching command.
293 293 Return debug commands (or their aliases) only if no normal command matches.
294 294 """
295 295 choice = {}
296 296 debugchoice = {}
297 297
298 298 if cmd in table:
299 299 # short-circuit exact matches, "log" alias beats "^log|history"
300 300 keys = [cmd]
301 301 else:
302 302 keys = table.keys()
303 303
304 304 allcmds = []
305 305 for e in keys:
306 306 aliases = parsealiases(e)
307 307 allcmds.extend(aliases)
308 308 found = None
309 309 if cmd in aliases:
310 310 found = cmd
311 311 elif not strict:
312 312 for a in aliases:
313 313 if a.startswith(cmd):
314 314 found = a
315 315 break
316 316 if found is not None:
317 317 if aliases[0].startswith("debug") or found.startswith("debug"):
318 318 debugchoice[found] = (aliases, table[e])
319 319 else:
320 320 choice[found] = (aliases, table[e])
321 321
322 322 if not choice and debugchoice:
323 323 choice = debugchoice
324 324
325 325 return choice, allcmds
326 326
327 327 def findcmd(cmd, table, strict=True):
328 328 """Return (aliases, command table entry) for command string."""
329 329 choice, allcmds = findpossible(cmd, table, strict)
330 330
331 331 if cmd in choice:
332 332 return choice[cmd]
333 333
334 334 if len(choice) > 1:
335 335 clist = choice.keys()
336 336 clist.sort()
337 337 raise error.AmbiguousCommand(cmd, clist)
338 338
339 339 if choice:
340 340 return choice.values()[0]
341 341
342 342 raise error.UnknownCommand(cmd, allcmds)
343 343
344 344 def findrepo(p):
345 345 while not os.path.isdir(os.path.join(p, ".hg")):
346 346 oldp, p = p, os.path.dirname(p)
347 347 if p == oldp:
348 348 return None
349 349
350 350 return p
351 351
352 352 def bailifchanged(repo, merge=True):
353 353 if merge and repo.dirstate.p2() != nullid:
354 354 raise error.Abort(_('outstanding uncommitted merge'))
355 355 modified, added, removed, deleted = repo.status()[:4]
356 356 if modified or added or removed or deleted:
357 357 raise error.Abort(_('uncommitted changes'))
358 358 ctx = repo[None]
359 359 for s in sorted(ctx.substate):
360 360 ctx.sub(s).bailifchanged()
361 361
362 362 def logmessage(ui, opts):
363 363 """ get the log message according to -m and -l option """
364 364 message = opts.get('message')
365 365 logfile = opts.get('logfile')
366 366
367 367 if message and logfile:
368 368 raise error.Abort(_('options --message and --logfile are mutually '
369 369 'exclusive'))
370 370 if not message and logfile:
371 371 try:
372 372 if logfile == '-':
373 373 message = ui.fin.read()
374 374 else:
375 375 message = '\n'.join(util.readfile(logfile).splitlines())
376 376 except IOError as inst:
377 377 raise error.Abort(_("can't read commit message '%s': %s") %
378 378 (logfile, inst.strerror))
379 379 return message
380 380
381 381 def mergeeditform(ctxorbool, baseformname):
382 382 """return appropriate editform name (referencing a committemplate)
383 383
384 384 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
385 385 merging is committed.
386 386
387 387 This returns baseformname with '.merge' appended if it is a merge,
388 388 otherwise '.normal' is appended.
389 389 """
390 390 if isinstance(ctxorbool, bool):
391 391 if ctxorbool:
392 392 return baseformname + ".merge"
393 393 elif 1 < len(ctxorbool.parents()):
394 394 return baseformname + ".merge"
395 395
396 396 return baseformname + ".normal"
397 397
398 398 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
399 399 editform='', **opts):
400 400 """get appropriate commit message editor according to '--edit' option
401 401
402 402 'finishdesc' is a function to be called with edited commit message
403 403 (= 'description' of the new changeset) just after editing, but
404 404 before checking empty-ness. It should return actual text to be
405 405 stored into history. This allows to change description before
406 406 storing.
407 407
408 408 'extramsg' is a extra message to be shown in the editor instead of
409 409 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
410 410 is automatically added.
411 411
412 412 'editform' is a dot-separated list of names, to distinguish
413 413 the purpose of commit text editing.
414 414
415 415 'getcommiteditor' returns 'commitforceeditor' regardless of
416 416 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
417 417 they are specific for usage in MQ.
418 418 """
419 419 if edit or finishdesc or extramsg:
420 420 return lambda r, c, s: commitforceeditor(r, c, s,
421 421 finishdesc=finishdesc,
422 422 extramsg=extramsg,
423 423 editform=editform)
424 424 elif editform:
425 425 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
426 426 else:
427 427 return commiteditor
428 428
429 429 def loglimit(opts):
430 430 """get the log limit according to option -l/--limit"""
431 431 limit = opts.get('limit')
432 432 if limit:
433 433 try:
434 434 limit = int(limit)
435 435 except ValueError:
436 436 raise error.Abort(_('limit must be a positive integer'))
437 437 if limit <= 0:
438 438 raise error.Abort(_('limit must be positive'))
439 439 else:
440 440 limit = None
441 441 return limit
442 442
443 443 def makefilename(repo, pat, node, desc=None,
444 444 total=None, seqno=None, revwidth=None, pathname=None):
445 445 node_expander = {
446 446 'H': lambda: hex(node),
447 447 'R': lambda: str(repo.changelog.rev(node)),
448 448 'h': lambda: short(node),
449 449 'm': lambda: re.sub('[^\w]', '_', str(desc))
450 450 }
451 451 expander = {
452 452 '%': lambda: '%',
453 453 'b': lambda: os.path.basename(repo.root),
454 454 }
455 455
456 456 try:
457 457 if node:
458 458 expander.update(node_expander)
459 459 if node:
460 460 expander['r'] = (lambda:
461 461 str(repo.changelog.rev(node)).zfill(revwidth or 0))
462 462 if total is not None:
463 463 expander['N'] = lambda: str(total)
464 464 if seqno is not None:
465 465 expander['n'] = lambda: str(seqno)
466 466 if total is not None and seqno is not None:
467 467 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
468 468 if pathname is not None:
469 469 expander['s'] = lambda: os.path.basename(pathname)
470 470 expander['d'] = lambda: os.path.dirname(pathname) or '.'
471 471 expander['p'] = lambda: pathname
472 472
473 473 newname = []
474 474 patlen = len(pat)
475 475 i = 0
476 476 while i < patlen:
477 477 c = pat[i]
478 478 if c == '%':
479 479 i += 1
480 480 c = pat[i]
481 481 c = expander[c]()
482 482 newname.append(c)
483 483 i += 1
484 484 return ''.join(newname)
485 485 except KeyError as inst:
486 486 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
487 487 inst.args[0])
488 488
489 489 class _unclosablefile(object):
490 490 def __init__(self, fp):
491 491 self._fp = fp
492 492
493 493 def close(self):
494 494 pass
495 495
496 496 def __iter__(self):
497 497 return iter(self._fp)
498 498
499 499 def __getattr__(self, attr):
500 500 return getattr(self._fp, attr)
501 501
502 502 def makefileobj(repo, pat, node=None, desc=None, total=None,
503 503 seqno=None, revwidth=None, mode='wb', modemap=None,
504 504 pathname=None):
505 505
506 506 writable = mode not in ('r', 'rb')
507 507
508 508 if not pat or pat == '-':
509 509 if writable:
510 510 fp = repo.ui.fout
511 511 else:
512 512 fp = repo.ui.fin
513 513 return _unclosablefile(fp)
514 514 if util.safehasattr(pat, 'write') and writable:
515 515 return pat
516 516 if util.safehasattr(pat, 'read') and 'r' in mode:
517 517 return pat
518 518 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
519 519 if modemap is not None:
520 520 mode = modemap.get(fn, mode)
521 521 if mode == 'wb':
522 522 modemap[fn] = 'ab'
523 523 return open(fn, mode)
524 524
525 525 def openrevlog(repo, cmd, file_, opts):
526 526 """opens the changelog, manifest, a filelog or a given revlog"""
527 527 cl = opts['changelog']
528 528 mf = opts['manifest']
529 529 dir = opts['dir']
530 530 msg = None
531 531 if cl and mf:
532 532 msg = _('cannot specify --changelog and --manifest at the same time')
533 533 elif cl and dir:
534 534 msg = _('cannot specify --changelog and --dir at the same time')
535 535 elif cl or mf or dir:
536 536 if file_:
537 537 msg = _('cannot specify filename with --changelog or --manifest')
538 538 elif not repo:
539 539 msg = _('cannot specify --changelog or --manifest or --dir '
540 540 'without a repository')
541 541 if msg:
542 542 raise error.Abort(msg)
543 543
544 544 r = None
545 545 if repo:
546 546 if cl:
547 547 r = repo.unfiltered().changelog
548 548 elif dir:
549 549 if 'treemanifest' not in repo.requirements:
550 550 raise error.Abort(_("--dir can only be used on repos with "
551 551 "treemanifest enabled"))
552 552 dirlog = repo.manifest.dirlog(dir)
553 553 if len(dirlog):
554 554 r = dirlog
555 555 elif mf:
556 556 r = repo.manifest
557 557 elif file_:
558 558 filelog = repo.file(file_)
559 559 if len(filelog):
560 560 r = filelog
561 561 if not r:
562 562 if not file_:
563 563 raise error.CommandError(cmd, _('invalid arguments'))
564 564 if not os.path.isfile(file_):
565 565 raise error.Abort(_("revlog '%s' not found") % file_)
566 566 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
567 567 file_[:-2] + ".i")
568 568 return r
569 569
570 570 def copy(ui, repo, pats, opts, rename=False):
571 571 # called with the repo lock held
572 572 #
573 573 # hgsep => pathname that uses "/" to separate directories
574 574 # ossep => pathname that uses os.sep to separate directories
575 575 cwd = repo.getcwd()
576 576 targets = {}
577 577 after = opts.get("after")
578 578 dryrun = opts.get("dry_run")
579 579 wctx = repo[None]
580 580
581 581 def walkpat(pat):
582 582 srcs = []
583 583 if after:
584 584 badstates = '?'
585 585 else:
586 586 badstates = '?r'
587 587 m = scmutil.match(repo[None], [pat], opts, globbed=True)
588 588 for abs in repo.walk(m):
589 589 state = repo.dirstate[abs]
590 590 rel = m.rel(abs)
591 591 exact = m.exact(abs)
592 592 if state in badstates:
593 593 if exact and state == '?':
594 594 ui.warn(_('%s: not copying - file is not managed\n') % rel)
595 595 if exact and state == 'r':
596 596 ui.warn(_('%s: not copying - file has been marked for'
597 597 ' remove\n') % rel)
598 598 continue
599 599 # abs: hgsep
600 600 # rel: ossep
601 601 srcs.append((abs, rel, exact))
602 602 return srcs
603 603
604 604 # abssrc: hgsep
605 605 # relsrc: ossep
606 606 # otarget: ossep
607 607 def copyfile(abssrc, relsrc, otarget, exact):
608 608 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
609 609 if '/' in abstarget:
610 610 # We cannot normalize abstarget itself, this would prevent
611 611 # case only renames, like a => A.
612 612 abspath, absname = abstarget.rsplit('/', 1)
613 613 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
614 614 reltarget = repo.pathto(abstarget, cwd)
615 615 target = repo.wjoin(abstarget)
616 616 src = repo.wjoin(abssrc)
617 617 state = repo.dirstate[abstarget]
618 618
619 619 scmutil.checkportable(ui, abstarget)
620 620
621 621 # check for collisions
622 622 prevsrc = targets.get(abstarget)
623 623 if prevsrc is not None:
624 624 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
625 625 (reltarget, repo.pathto(abssrc, cwd),
626 626 repo.pathto(prevsrc, cwd)))
627 627 return
628 628
629 629 # check for overwrites
630 630 exists = os.path.lexists(target)
631 631 samefile = False
632 632 if exists and abssrc != abstarget:
633 633 if (repo.dirstate.normalize(abssrc) ==
634 634 repo.dirstate.normalize(abstarget)):
635 635 if not rename:
636 636 ui.warn(_("%s: can't copy - same file\n") % reltarget)
637 637 return
638 638 exists = False
639 639 samefile = True
640 640
641 641 if not after and exists or after and state in 'mn':
642 642 if not opts['force']:
643 643 ui.warn(_('%s: not overwriting - file exists\n') %
644 644 reltarget)
645 645 return
646 646
647 647 if after:
648 648 if not exists:
649 649 if rename:
650 650 ui.warn(_('%s: not recording move - %s does not exist\n') %
651 651 (relsrc, reltarget))
652 652 else:
653 653 ui.warn(_('%s: not recording copy - %s does not exist\n') %
654 654 (relsrc, reltarget))
655 655 return
656 656 elif not dryrun:
657 657 try:
658 658 if exists:
659 659 os.unlink(target)
660 660 targetdir = os.path.dirname(target) or '.'
661 661 if not os.path.isdir(targetdir):
662 662 os.makedirs(targetdir)
663 663 if samefile:
664 664 tmp = target + "~hgrename"
665 665 os.rename(src, tmp)
666 666 os.rename(tmp, target)
667 667 else:
668 668 util.copyfile(src, target)
669 669 srcexists = True
670 670 except IOError as inst:
671 671 if inst.errno == errno.ENOENT:
672 672 ui.warn(_('%s: deleted in working directory\n') % relsrc)
673 673 srcexists = False
674 674 else:
675 675 ui.warn(_('%s: cannot copy - %s\n') %
676 676 (relsrc, inst.strerror))
677 677 return True # report a failure
678 678
679 679 if ui.verbose or not exact:
680 680 if rename:
681 681 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
682 682 else:
683 683 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
684 684
685 685 targets[abstarget] = abssrc
686 686
687 687 # fix up dirstate
688 688 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
689 689 dryrun=dryrun, cwd=cwd)
690 690 if rename and not dryrun:
691 691 if not after and srcexists and not samefile:
692 692 util.unlinkpath(repo.wjoin(abssrc))
693 693 wctx.forget([abssrc])
694 694
695 695 # pat: ossep
696 696 # dest ossep
697 697 # srcs: list of (hgsep, hgsep, ossep, bool)
698 698 # return: function that takes hgsep and returns ossep
699 699 def targetpathfn(pat, dest, srcs):
700 700 if os.path.isdir(pat):
701 701 abspfx = pathutil.canonpath(repo.root, cwd, pat)
702 702 abspfx = util.localpath(abspfx)
703 703 if destdirexists:
704 704 striplen = len(os.path.split(abspfx)[0])
705 705 else:
706 706 striplen = len(abspfx)
707 707 if striplen:
708 708 striplen += len(os.sep)
709 709 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
710 710 elif destdirexists:
711 711 res = lambda p: os.path.join(dest,
712 712 os.path.basename(util.localpath(p)))
713 713 else:
714 714 res = lambda p: dest
715 715 return res
716 716
717 717 # pat: ossep
718 718 # dest ossep
719 719 # srcs: list of (hgsep, hgsep, ossep, bool)
720 720 # return: function that takes hgsep and returns ossep
721 721 def targetpathafterfn(pat, dest, srcs):
722 722 if matchmod.patkind(pat):
723 723 # a mercurial pattern
724 724 res = lambda p: os.path.join(dest,
725 725 os.path.basename(util.localpath(p)))
726 726 else:
727 727 abspfx = pathutil.canonpath(repo.root, cwd, pat)
728 728 if len(abspfx) < len(srcs[0][0]):
729 729 # A directory. Either the target path contains the last
730 730 # component of the source path or it does not.
731 731 def evalpath(striplen):
732 732 score = 0
733 733 for s in srcs:
734 734 t = os.path.join(dest, util.localpath(s[0])[striplen:])
735 735 if os.path.lexists(t):
736 736 score += 1
737 737 return score
738 738
739 739 abspfx = util.localpath(abspfx)
740 740 striplen = len(abspfx)
741 741 if striplen:
742 742 striplen += len(os.sep)
743 743 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
744 744 score = evalpath(striplen)
745 745 striplen1 = len(os.path.split(abspfx)[0])
746 746 if striplen1:
747 747 striplen1 += len(os.sep)
748 748 if evalpath(striplen1) > score:
749 749 striplen = striplen1
750 750 res = lambda p: os.path.join(dest,
751 751 util.localpath(p)[striplen:])
752 752 else:
753 753 # a file
754 754 if destdirexists:
755 755 res = lambda p: os.path.join(dest,
756 756 os.path.basename(util.localpath(p)))
757 757 else:
758 758 res = lambda p: dest
759 759 return res
760 760
761 761 pats = scmutil.expandpats(pats)
762 762 if not pats:
763 763 raise error.Abort(_('no source or destination specified'))
764 764 if len(pats) == 1:
765 765 raise error.Abort(_('no destination specified'))
766 766 dest = pats.pop()
767 767 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
768 768 if not destdirexists:
769 769 if len(pats) > 1 or matchmod.patkind(pats[0]):
770 770 raise error.Abort(_('with multiple sources, destination must be an '
771 771 'existing directory'))
772 772 if util.endswithsep(dest):
773 773 raise error.Abort(_('destination %s is not a directory') % dest)
774 774
775 775 tfn = targetpathfn
776 776 if after:
777 777 tfn = targetpathafterfn
778 778 copylist = []
779 779 for pat in pats:
780 780 srcs = walkpat(pat)
781 781 if not srcs:
782 782 continue
783 783 copylist.append((tfn(pat, dest, srcs), srcs))
784 784 if not copylist:
785 785 raise error.Abort(_('no files to copy'))
786 786
787 787 errors = 0
788 788 for targetpath, srcs in copylist:
789 789 for abssrc, relsrc, exact in srcs:
790 790 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
791 791 errors += 1
792 792
793 793 if errors:
794 794 ui.warn(_('(consider using --after)\n'))
795 795
796 796 return errors != 0
797 797
798 798 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
799 799 runargs=None, appendpid=False):
800 800 '''Run a command as a service.'''
801 801
802 802 def writepid(pid):
803 803 if opts['pid_file']:
804 804 if appendpid:
805 805 mode = 'a'
806 806 else:
807 807 mode = 'w'
808 808 fp = open(opts['pid_file'], mode)
809 809 fp.write(str(pid) + '\n')
810 810 fp.close()
811 811
812 812 if opts['daemon'] and not opts['daemon_postexec']:
813 813 # Signal child process startup with file removal
814 814 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
815 815 os.close(lockfd)
816 816 try:
817 817 if not runargs:
818 818 runargs = util.hgcmd() + sys.argv[1:]
819 819 runargs.append('--daemon-postexec=unlink:%s' % lockpath)
820 820 # Don't pass --cwd to the child process, because we've already
821 821 # changed directory.
822 822 for i in xrange(1, len(runargs)):
823 823 if runargs[i].startswith('--cwd='):
824 824 del runargs[i]
825 825 break
826 826 elif runargs[i].startswith('--cwd'):
827 827 del runargs[i:i + 2]
828 828 break
829 829 def condfn():
830 830 return not os.path.exists(lockpath)
831 831 pid = util.rundetached(runargs, condfn)
832 832 if pid < 0:
833 833 raise error.Abort(_('child process failed to start'))
834 834 writepid(pid)
835 835 finally:
836 836 try:
837 837 os.unlink(lockpath)
838 838 except OSError as e:
839 839 if e.errno != errno.ENOENT:
840 840 raise
841 841 if parentfn:
842 842 return parentfn(pid)
843 843 else:
844 844 return
845 845
846 846 if initfn:
847 847 initfn()
848 848
849 849 if not opts['daemon']:
850 850 writepid(util.getpid())
851 851
852 852 if opts['daemon_postexec']:
853 853 try:
854 854 os.setsid()
855 855 except AttributeError:
856 856 pass
857 857 for inst in opts['daemon_postexec']:
858 858 if inst.startswith('unlink:'):
859 859 lockpath = inst[7:]
860 860 os.unlink(lockpath)
861 861 elif inst.startswith('chdir:'):
862 862 os.chdir(inst[6:])
863 863 elif inst != 'none':
864 864 raise error.Abort(_('invalid value for --daemon-postexec: %s')
865 865 % inst)
866 866 util.hidewindow()
867 867 sys.stdout.flush()
868 868 sys.stderr.flush()
869 869
870 870 nullfd = os.open(os.devnull, os.O_RDWR)
871 871 logfilefd = nullfd
872 872 if logfile:
873 873 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
874 874 os.dup2(nullfd, 0)
875 875 os.dup2(logfilefd, 1)
876 876 os.dup2(logfilefd, 2)
877 877 if nullfd not in (0, 1, 2):
878 878 os.close(nullfd)
879 879 if logfile and logfilefd not in (0, 1, 2):
880 880 os.close(logfilefd)
881 881
882 882 if runfn:
883 883 return runfn()
884 884
885 885 ## facility to let extension process additional data into an import patch
886 886 # list of identifier to be executed in order
887 887 extrapreimport = [] # run before commit
888 888 extrapostimport = [] # run after commit
889 889 # mapping from identifier to actual import function
890 890 #
891 891 # 'preimport' are run before the commit is made and are provided the following
892 892 # arguments:
893 893 # - repo: the localrepository instance,
894 894 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
895 895 # - extra: the future extra dictionary of the changeset, please mutate it,
896 896 # - opts: the import options.
897 897 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
898 898 # mutation of in memory commit and more. Feel free to rework the code to get
899 899 # there.
900 900 extrapreimportmap = {}
901 901 # 'postimport' are run after the commit is made and are provided the following
902 902 # argument:
903 903 # - ctx: the changectx created by import.
904 904 extrapostimportmap = {}
905 905
906 906 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
907 907 """Utility function used by commands.import to import a single patch
908 908
909 909 This function is explicitly defined here to help the evolve extension to
910 910 wrap this part of the import logic.
911 911
912 912 The API is currently a bit ugly because it a simple code translation from
913 913 the import command. Feel free to make it better.
914 914
915 915 :hunk: a patch (as a binary string)
916 916 :parents: nodes that will be parent of the created commit
917 917 :opts: the full dict of option passed to the import command
918 918 :msgs: list to save commit message to.
919 919 (used in case we need to save it when failing)
920 920 :updatefunc: a function that update a repo to a given node
921 921 updatefunc(<repo>, <node>)
922 922 """
923 923 # avoid cycle context -> subrepo -> cmdutil
924 924 from . import context
925 925 extractdata = patch.extract(ui, hunk)
926 926 tmpname = extractdata.get('filename')
927 927 message = extractdata.get('message')
928 928 user = opts.get('user') or extractdata.get('user')
929 929 date = opts.get('date') or extractdata.get('date')
930 930 branch = extractdata.get('branch')
931 931 nodeid = extractdata.get('nodeid')
932 932 p1 = extractdata.get('p1')
933 933 p2 = extractdata.get('p2')
934 934
935 935 nocommit = opts.get('no_commit')
936 936 importbranch = opts.get('import_branch')
937 937 update = not opts.get('bypass')
938 938 strip = opts["strip"]
939 939 prefix = opts["prefix"]
940 940 sim = float(opts.get('similarity') or 0)
941 941 if not tmpname:
942 942 return (None, None, False)
943 943
944 944 rejects = False
945 945
946 946 try:
947 947 cmdline_message = logmessage(ui, opts)
948 948 if cmdline_message:
949 949 # pickup the cmdline msg
950 950 message = cmdline_message
951 951 elif message:
952 952 # pickup the patch msg
953 953 message = message.strip()
954 954 else:
955 955 # launch the editor
956 956 message = None
957 957 ui.debug('message:\n%s\n' % message)
958 958
959 959 if len(parents) == 1:
960 960 parents.append(repo[nullid])
961 961 if opts.get('exact'):
962 962 if not nodeid or not p1:
963 963 raise error.Abort(_('not a Mercurial patch'))
964 964 p1 = repo[p1]
965 965 p2 = repo[p2 or nullid]
966 966 elif p2:
967 967 try:
968 968 p1 = repo[p1]
969 969 p2 = repo[p2]
970 970 # Without any options, consider p2 only if the
971 971 # patch is being applied on top of the recorded
972 972 # first parent.
973 973 if p1 != parents[0]:
974 974 p1 = parents[0]
975 975 p2 = repo[nullid]
976 976 except error.RepoError:
977 977 p1, p2 = parents
978 978 if p2.node() == nullid:
979 979 ui.warn(_("warning: import the patch as a normal revision\n"
980 980 "(use --exact to import the patch as a merge)\n"))
981 981 else:
982 982 p1, p2 = parents
983 983
984 984 n = None
985 985 if update:
986 986 if p1 != parents[0]:
987 987 updatefunc(repo, p1.node())
988 988 if p2 != parents[1]:
989 989 repo.setparents(p1.node(), p2.node())
990 990
991 991 if opts.get('exact') or importbranch:
992 992 repo.dirstate.setbranch(branch or 'default')
993 993
994 994 partial = opts.get('partial', False)
995 995 files = set()
996 996 try:
997 997 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
998 998 files=files, eolmode=None, similarity=sim / 100.0)
999 999 except patch.PatchError as e:
1000 1000 if not partial:
1001 1001 raise error.Abort(str(e))
1002 1002 if partial:
1003 1003 rejects = True
1004 1004
1005 1005 files = list(files)
1006 1006 if nocommit:
1007 1007 if message:
1008 1008 msgs.append(message)
1009 1009 else:
1010 1010 if opts.get('exact') or p2:
1011 1011 # If you got here, you either use --force and know what
1012 1012 # you are doing or used --exact or a merge patch while
1013 1013 # being updated to its first parent.
1014 1014 m = None
1015 1015 else:
1016 1016 m = scmutil.matchfiles(repo, files or [])
1017 1017 editform = mergeeditform(repo[None], 'import.normal')
1018 1018 if opts.get('exact'):
1019 1019 editor = None
1020 1020 else:
1021 1021 editor = getcommiteditor(editform=editform, **opts)
1022 1022 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
1023 1023 extra = {}
1024 1024 for idfunc in extrapreimport:
1025 1025 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1026 1026 try:
1027 1027 if partial:
1028 1028 repo.ui.setconfig('ui', 'allowemptycommit', True)
1029 1029 n = repo.commit(message, user,
1030 1030 date, match=m,
1031 1031 editor=editor, extra=extra)
1032 1032 for idfunc in extrapostimport:
1033 1033 extrapostimportmap[idfunc](repo[n])
1034 1034 finally:
1035 1035 repo.ui.restoreconfig(allowemptyback)
1036 1036 else:
1037 1037 if opts.get('exact') or importbranch:
1038 1038 branch = branch or 'default'
1039 1039 else:
1040 1040 branch = p1.branch()
1041 1041 store = patch.filestore()
1042 1042 try:
1043 1043 files = set()
1044 1044 try:
1045 1045 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1046 1046 files, eolmode=None)
1047 1047 except patch.PatchError as e:
1048 1048 raise error.Abort(str(e))
1049 1049 if opts.get('exact'):
1050 1050 editor = None
1051 1051 else:
1052 1052 editor = getcommiteditor(editform='import.bypass')
1053 1053 memctx = context.makememctx(repo, (p1.node(), p2.node()),
1054 1054 message,
1055 1055 user,
1056 1056 date,
1057 1057 branch, files, store,
1058 1058 editor=editor)
1059 1059 n = memctx.commit()
1060 1060 finally:
1061 1061 store.close()
1062 1062 if opts.get('exact') and nocommit:
1063 1063 # --exact with --no-commit is still useful in that it does merge
1064 1064 # and branch bits
1065 1065 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1066 1066 elif opts.get('exact') and hex(n) != nodeid:
1067 1067 raise error.Abort(_('patch is damaged or loses information'))
1068 1068 msg = _('applied to working directory')
1069 1069 if n:
1070 1070 # i18n: refers to a short changeset id
1071 1071 msg = _('created %s') % short(n)
1072 1072 return (msg, n, rejects)
1073 1073 finally:
1074 1074 os.unlink(tmpname)
1075 1075
1076 1076 # facility to let extensions include additional data in an exported patch
1077 1077 # list of identifiers to be executed in order
1078 1078 extraexport = []
1079 1079 # mapping from identifier to actual export function
1080 1080 # function as to return a string to be added to the header or None
1081 1081 # it is given two arguments (sequencenumber, changectx)
1082 1082 extraexportmap = {}
1083 1083
1084 1084 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1085 1085 opts=None, match=None):
1086 1086 '''export changesets as hg patches.'''
1087 1087
1088 1088 total = len(revs)
1089 1089 revwidth = max([len(str(rev)) for rev in revs])
1090 1090 filemode = {}
1091 1091
1092 1092 def single(rev, seqno, fp):
1093 1093 ctx = repo[rev]
1094 1094 node = ctx.node()
1095 1095 parents = [p.node() for p in ctx.parents() if p]
1096 1096 branch = ctx.branch()
1097 1097 if switch_parent:
1098 1098 parents.reverse()
1099 1099
1100 1100 if parents:
1101 1101 prev = parents[0]
1102 1102 else:
1103 1103 prev = nullid
1104 1104
1105 1105 shouldclose = False
1106 1106 if not fp and len(template) > 0:
1107 1107 desc_lines = ctx.description().rstrip().split('\n')
1108 1108 desc = desc_lines[0] #Commit always has a first line.
1109 1109 fp = makefileobj(repo, template, node, desc=desc, total=total,
1110 1110 seqno=seqno, revwidth=revwidth, mode='wb',
1111 1111 modemap=filemode)
1112 1112 shouldclose = True
1113 1113 if fp and not getattr(fp, 'name', '<unnamed>').startswith('<'):
1114 1114 repo.ui.note("%s\n" % fp.name)
1115 1115
1116 1116 if not fp:
1117 1117 write = repo.ui.write
1118 1118 else:
1119 1119 def write(s, **kw):
1120 1120 fp.write(s)
1121 1121
1122 1122 write("# HG changeset patch\n")
1123 1123 write("# User %s\n" % ctx.user())
1124 1124 write("# Date %d %d\n" % ctx.date())
1125 1125 write("# %s\n" % util.datestr(ctx.date()))
1126 1126 if branch and branch != 'default':
1127 1127 write("# Branch %s\n" % branch)
1128 1128 write("# Node ID %s\n" % hex(node))
1129 1129 write("# Parent %s\n" % hex(prev))
1130 1130 if len(parents) > 1:
1131 1131 write("# Parent %s\n" % hex(parents[1]))
1132 1132
1133 1133 for headerid in extraexport:
1134 1134 header = extraexportmap[headerid](seqno, ctx)
1135 1135 if header is not None:
1136 1136 write('# %s\n' % header)
1137 1137 write(ctx.description().rstrip())
1138 1138 write("\n\n")
1139 1139
1140 1140 for chunk, label in patch.diffui(repo, prev, node, match, opts=opts):
1141 1141 write(chunk, label=label)
1142 1142
1143 1143 if shouldclose:
1144 1144 fp.close()
1145 1145
1146 1146 for seqno, rev in enumerate(revs):
1147 1147 single(rev, seqno + 1, fp)
1148 1148
1149 1149 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1150 1150 changes=None, stat=False, fp=None, prefix='',
1151 1151 root='', listsubrepos=False):
1152 1152 '''show diff or diffstat.'''
1153 1153 if fp is None:
1154 1154 write = ui.write
1155 1155 else:
1156 1156 def write(s, **kw):
1157 1157 fp.write(s)
1158 1158
1159 1159 if root:
1160 1160 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1161 1161 else:
1162 1162 relroot = ''
1163 1163 if relroot != '':
1164 1164 # XXX relative roots currently don't work if the root is within a
1165 1165 # subrepo
1166 1166 uirelroot = match.uipath(relroot)
1167 1167 relroot += '/'
1168 1168 for matchroot in match.files():
1169 1169 if not matchroot.startswith(relroot):
1170 1170 ui.warn(_('warning: %s not inside relative root %s\n') % (
1171 1171 match.uipath(matchroot), uirelroot))
1172 1172
1173 1173 if stat:
1174 1174 diffopts = diffopts.copy(context=0)
1175 1175 width = 80
1176 1176 if not ui.plain():
1177 1177 width = ui.termwidth()
1178 1178 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1179 1179 prefix=prefix, relroot=relroot)
1180 1180 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1181 1181 width=width,
1182 1182 git=diffopts.git):
1183 1183 write(chunk, label=label)
1184 1184 else:
1185 1185 for chunk, label in patch.diffui(repo, node1, node2, match,
1186 1186 changes, diffopts, prefix=prefix,
1187 1187 relroot=relroot):
1188 1188 write(chunk, label=label)
1189 1189
1190 1190 if listsubrepos:
1191 1191 ctx1 = repo[node1]
1192 1192 ctx2 = repo[node2]
1193 1193 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1194 1194 tempnode2 = node2
1195 1195 try:
1196 1196 if node2 is not None:
1197 1197 tempnode2 = ctx2.substate[subpath][1]
1198 1198 except KeyError:
1199 1199 # A subrepo that existed in node1 was deleted between node1 and
1200 1200 # node2 (inclusive). Thus, ctx2's substate won't contain that
1201 1201 # subpath. The best we can do is to ignore it.
1202 1202 tempnode2 = None
1203 1203 submatch = matchmod.subdirmatcher(subpath, match)
1204 1204 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1205 1205 stat=stat, fp=fp, prefix=prefix)
1206 1206
1207 1207 class changeset_printer(object):
1208 1208 '''show changeset information when templating not requested.'''
1209 1209
1210 1210 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1211 1211 self.ui = ui
1212 1212 self.repo = repo
1213 1213 self.buffered = buffered
1214 1214 self.matchfn = matchfn
1215 1215 self.diffopts = diffopts
1216 1216 self.header = {}
1217 1217 self.hunk = {}
1218 1218 self.lastheader = None
1219 1219 self.footer = None
1220 1220
1221 1221 def flush(self, ctx):
1222 1222 rev = ctx.rev()
1223 1223 if rev in self.header:
1224 1224 h = self.header[rev]
1225 1225 if h != self.lastheader:
1226 1226 self.lastheader = h
1227 1227 self.ui.write(h)
1228 1228 del self.header[rev]
1229 1229 if rev in self.hunk:
1230 1230 self.ui.write(self.hunk[rev])
1231 1231 del self.hunk[rev]
1232 1232 return 1
1233 1233 return 0
1234 1234
1235 1235 def close(self):
1236 1236 if self.footer:
1237 1237 self.ui.write(self.footer)
1238 1238
1239 1239 def show(self, ctx, copies=None, matchfn=None, **props):
1240 1240 if self.buffered:
1241 1241 self.ui.pushbuffer(labeled=True)
1242 1242 self._show(ctx, copies, matchfn, props)
1243 1243 self.hunk[ctx.rev()] = self.ui.popbuffer()
1244 1244 else:
1245 1245 self._show(ctx, copies, matchfn, props)
1246 1246
1247 1247 def _show(self, ctx, copies, matchfn, props):
1248 1248 '''show a single changeset or file revision'''
1249 1249 changenode = ctx.node()
1250 1250 rev = ctx.rev()
1251 1251 if self.ui.debugflag:
1252 1252 hexfunc = hex
1253 1253 else:
1254 1254 hexfunc = short
1255 1255 # as of now, wctx.node() and wctx.rev() return None, but we want to
1256 1256 # show the same values as {node} and {rev} templatekw
1257 1257 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1258 1258
1259 1259 if self.ui.quiet:
1260 1260 self.ui.write("%d:%s\n" % revnode, label='log.node')
1261 1261 return
1262 1262
1263 1263 date = util.datestr(ctx.date())
1264 1264
1265 1265 # i18n: column positioning for "hg log"
1266 1266 self.ui.write(_("changeset: %d:%s\n") % revnode,
1267 1267 label='log.changeset changeset.%s' % ctx.phasestr())
1268 1268
1269 1269 # branches are shown first before any other names due to backwards
1270 1270 # compatibility
1271 1271 branch = ctx.branch()
1272 1272 # don't show the default branch name
1273 1273 if branch != 'default':
1274 1274 # i18n: column positioning for "hg log"
1275 1275 self.ui.write(_("branch: %s\n") % branch,
1276 1276 label='log.branch')
1277 1277
1278 1278 for nsname, ns in self.repo.names.iteritems():
1279 1279 # branches has special logic already handled above, so here we just
1280 1280 # skip it
1281 1281 if nsname == 'branches':
1282 1282 continue
1283 1283 # we will use the templatename as the color name since those two
1284 1284 # should be the same
1285 1285 for name in ns.names(self.repo, changenode):
1286 1286 self.ui.write(ns.logfmt % name,
1287 1287 label='log.%s' % ns.colorname)
1288 1288 if self.ui.debugflag:
1289 1289 # i18n: column positioning for "hg log"
1290 1290 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1291 1291 label='log.phase')
1292 1292 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1293 1293 label = 'log.parent changeset.%s' % pctx.phasestr()
1294 1294 # i18n: column positioning for "hg log"
1295 1295 self.ui.write(_("parent: %d:%s\n")
1296 1296 % (pctx.rev(), hexfunc(pctx.node())),
1297 1297 label=label)
1298 1298
1299 1299 if self.ui.debugflag and rev is not None:
1300 1300 mnode = ctx.manifestnode()
1301 1301 # i18n: column positioning for "hg log"
1302 1302 self.ui.write(_("manifest: %d:%s\n") %
1303 1303 (self.repo.manifest.rev(mnode), hex(mnode)),
1304 1304 label='ui.debug log.manifest')
1305 1305 # i18n: column positioning for "hg log"
1306 1306 self.ui.write(_("user: %s\n") % ctx.user(),
1307 1307 label='log.user')
1308 1308 # i18n: column positioning for "hg log"
1309 1309 self.ui.write(_("date: %s\n") % date,
1310 1310 label='log.date')
1311 1311
1312 1312 if self.ui.debugflag:
1313 1313 files = ctx.p1().status(ctx)[:3]
1314 1314 for key, value in zip([# i18n: column positioning for "hg log"
1315 1315 _("files:"),
1316 1316 # i18n: column positioning for "hg log"
1317 1317 _("files+:"),
1318 1318 # i18n: column positioning for "hg log"
1319 1319 _("files-:")], files):
1320 1320 if value:
1321 1321 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1322 1322 label='ui.debug log.files')
1323 1323 elif ctx.files() and self.ui.verbose:
1324 1324 # i18n: column positioning for "hg log"
1325 1325 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1326 1326 label='ui.note log.files')
1327 1327 if copies and self.ui.verbose:
1328 1328 copies = ['%s (%s)' % c for c in copies]
1329 1329 # i18n: column positioning for "hg log"
1330 1330 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1331 1331 label='ui.note log.copies')
1332 1332
1333 1333 extra = ctx.extra()
1334 1334 if extra and self.ui.debugflag:
1335 1335 for key, value in sorted(extra.items()):
1336 1336 # i18n: column positioning for "hg log"
1337 1337 self.ui.write(_("extra: %s=%s\n")
1338 1338 % (key, value.encode('string_escape')),
1339 1339 label='ui.debug log.extra')
1340 1340
1341 1341 description = ctx.description().strip()
1342 1342 if description:
1343 1343 if self.ui.verbose:
1344 1344 self.ui.write(_("description:\n"),
1345 1345 label='ui.note log.description')
1346 1346 self.ui.write(description,
1347 1347 label='ui.note log.description')
1348 1348 self.ui.write("\n\n")
1349 1349 else:
1350 1350 # i18n: column positioning for "hg log"
1351 1351 self.ui.write(_("summary: %s\n") %
1352 1352 description.splitlines()[0],
1353 1353 label='log.summary')
1354 1354 self.ui.write("\n")
1355 1355
1356 1356 self.showpatch(ctx, matchfn)
1357 1357
1358 1358 def showpatch(self, ctx, matchfn):
1359 1359 if not matchfn:
1360 1360 matchfn = self.matchfn
1361 1361 if matchfn:
1362 1362 stat = self.diffopts.get('stat')
1363 1363 diff = self.diffopts.get('patch')
1364 1364 diffopts = patch.diffallopts(self.ui, self.diffopts)
1365 1365 node = ctx.node()
1366 1366 prev = ctx.p1().node()
1367 1367 if stat:
1368 1368 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1369 1369 match=matchfn, stat=True)
1370 1370 if diff:
1371 1371 if stat:
1372 1372 self.ui.write("\n")
1373 1373 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1374 1374 match=matchfn, stat=False)
1375 1375 self.ui.write("\n")
1376 1376
1377 1377 class jsonchangeset(changeset_printer):
1378 1378 '''format changeset information.'''
1379 1379
1380 1380 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1381 1381 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1382 1382 self.cache = {}
1383 1383 self._first = True
1384 1384
1385 1385 def close(self):
1386 1386 if not self._first:
1387 1387 self.ui.write("\n]\n")
1388 1388 else:
1389 1389 self.ui.write("[]\n")
1390 1390
1391 1391 def _show(self, ctx, copies, matchfn, props):
1392 1392 '''show a single changeset or file revision'''
1393 1393 rev = ctx.rev()
1394 1394 if rev is None:
1395 1395 jrev = jnode = 'null'
1396 1396 else:
1397 1397 jrev = str(rev)
1398 1398 jnode = '"%s"' % hex(ctx.node())
1399 1399 j = encoding.jsonescape
1400 1400
1401 1401 if self._first:
1402 1402 self.ui.write("[\n {")
1403 1403 self._first = False
1404 1404 else:
1405 1405 self.ui.write(",\n {")
1406 1406
1407 1407 if self.ui.quiet:
1408 1408 self.ui.write(('\n "rev": %s') % jrev)
1409 1409 self.ui.write((',\n "node": %s') % jnode)
1410 1410 self.ui.write('\n }')
1411 1411 return
1412 1412
1413 1413 self.ui.write(('\n "rev": %s') % jrev)
1414 1414 self.ui.write((',\n "node": %s') % jnode)
1415 1415 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1416 1416 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1417 1417 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1418 1418 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1419 1419 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1420 1420
1421 1421 self.ui.write((',\n "bookmarks": [%s]') %
1422 1422 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1423 1423 self.ui.write((',\n "tags": [%s]') %
1424 1424 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1425 1425 self.ui.write((',\n "parents": [%s]') %
1426 1426 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1427 1427
1428 1428 if self.ui.debugflag:
1429 1429 if rev is None:
1430 1430 jmanifestnode = 'null'
1431 1431 else:
1432 1432 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1433 1433 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1434 1434
1435 1435 self.ui.write((',\n "extra": {%s}') %
1436 1436 ", ".join('"%s": "%s"' % (j(k), j(v))
1437 1437 for k, v in ctx.extra().items()))
1438 1438
1439 1439 files = ctx.p1().status(ctx)
1440 1440 self.ui.write((',\n "modified": [%s]') %
1441 1441 ", ".join('"%s"' % j(f) for f in files[0]))
1442 1442 self.ui.write((',\n "added": [%s]') %
1443 1443 ", ".join('"%s"' % j(f) for f in files[1]))
1444 1444 self.ui.write((',\n "removed": [%s]') %
1445 1445 ", ".join('"%s"' % j(f) for f in files[2]))
1446 1446
1447 1447 elif self.ui.verbose:
1448 1448 self.ui.write((',\n "files": [%s]') %
1449 1449 ", ".join('"%s"' % j(f) for f in ctx.files()))
1450 1450
1451 1451 if copies:
1452 1452 self.ui.write((',\n "copies": {%s}') %
1453 1453 ", ".join('"%s": "%s"' % (j(k), j(v))
1454 1454 for k, v in copies))
1455 1455
1456 1456 matchfn = self.matchfn
1457 1457 if matchfn:
1458 1458 stat = self.diffopts.get('stat')
1459 1459 diff = self.diffopts.get('patch')
1460 1460 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1461 1461 node, prev = ctx.node(), ctx.p1().node()
1462 1462 if stat:
1463 1463 self.ui.pushbuffer()
1464 1464 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1465 1465 match=matchfn, stat=True)
1466 1466 self.ui.write((',\n "diffstat": "%s"')
1467 1467 % j(self.ui.popbuffer()))
1468 1468 if diff:
1469 1469 self.ui.pushbuffer()
1470 1470 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1471 1471 match=matchfn, stat=False)
1472 1472 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1473 1473
1474 1474 self.ui.write("\n }")
1475 1475
1476 1476 class changeset_templater(changeset_printer):
1477 1477 '''format changeset information.'''
1478 1478
1479 1479 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1480 1480 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1481 1481 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1482 1482 filters = {'formatnode': formatnode}
1483 1483 defaulttempl = {
1484 1484 'parent': '{rev}:{node|formatnode} ',
1485 1485 'manifest': '{rev}:{node|formatnode}',
1486 1486 'file_copy': '{name} ({source})',
1487 1487 'extra': '{key}={value|stringescape}'
1488 1488 }
1489 1489 # filecopy is preserved for compatibility reasons
1490 1490 defaulttempl['filecopy'] = defaulttempl['file_copy']
1491 1491 assert not (tmpl and mapfile)
1492 1492 if mapfile:
1493 1493 self.t = templater.templater.frommapfile(mapfile, filters=filters,
1494 1494 cache=defaulttempl)
1495 1495 else:
1496 1496 self.t = formatter.maketemplater(ui, 'changeset', tmpl,
1497 1497 filters=filters,
1498 1498 cache=defaulttempl)
1499 1499
1500 1500 self.cache = {}
1501 1501
1502 1502 # find correct templates for current mode
1503 1503 tmplmodes = [
1504 1504 (True, None),
1505 1505 (self.ui.verbose, 'verbose'),
1506 1506 (self.ui.quiet, 'quiet'),
1507 1507 (self.ui.debugflag, 'debug'),
1508 1508 ]
1509 1509
1510 1510 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1511 1511 'docheader': '', 'docfooter': ''}
1512 1512 for mode, postfix in tmplmodes:
1513 1513 for t in self._parts:
1514 1514 cur = t
1515 1515 if postfix:
1516 1516 cur += "_" + postfix
1517 1517 if mode and cur in self.t:
1518 1518 self._parts[t] = cur
1519 1519
1520 1520 if self._parts['docheader']:
1521 1521 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1522 1522
1523 1523 def close(self):
1524 1524 if self._parts['docfooter']:
1525 1525 if not self.footer:
1526 1526 self.footer = ""
1527 1527 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1528 1528 return super(changeset_templater, self).close()
1529 1529
1530 1530 def _show(self, ctx, copies, matchfn, props):
1531 1531 '''show a single changeset or file revision'''
1532 1532 props = props.copy()
1533 1533 props.update(templatekw.keywords)
1534 1534 props['templ'] = self.t
1535 1535 props['ctx'] = ctx
1536 1536 props['repo'] = self.repo
1537 1537 props['ui'] = self.repo.ui
1538 1538 props['revcache'] = {'copies': copies}
1539 1539 props['cache'] = self.cache
1540 1540
1541 1541 # write header
1542 1542 if self._parts['header']:
1543 1543 h = templater.stringify(self.t(self._parts['header'], **props))
1544 1544 if self.buffered:
1545 1545 self.header[ctx.rev()] = h
1546 1546 else:
1547 1547 if self.lastheader != h:
1548 1548 self.lastheader = h
1549 1549 self.ui.write(h)
1550 1550
1551 1551 # write changeset metadata, then patch if requested
1552 1552 key = self._parts['changeset']
1553 1553 self.ui.write(templater.stringify(self.t(key, **props)))
1554 1554 self.showpatch(ctx, matchfn)
1555 1555
1556 1556 if self._parts['footer']:
1557 1557 if not self.footer:
1558 1558 self.footer = templater.stringify(
1559 1559 self.t(self._parts['footer'], **props))
1560 1560
1561 1561 def gettemplate(ui, tmpl, style):
1562 1562 """
1563 1563 Find the template matching the given template spec or style.
1564 1564 """
1565 1565
1566 1566 # ui settings
1567 1567 if not tmpl and not style: # template are stronger than style
1568 1568 tmpl = ui.config('ui', 'logtemplate')
1569 1569 if tmpl:
1570 1570 return templater.unquotestring(tmpl), None
1571 1571 else:
1572 1572 style = util.expandpath(ui.config('ui', 'style', ''))
1573 1573
1574 1574 if not tmpl and style:
1575 1575 mapfile = style
1576 1576 if not os.path.split(mapfile)[0]:
1577 1577 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1578 1578 or templater.templatepath(mapfile))
1579 1579 if mapname:
1580 1580 mapfile = mapname
1581 1581 return None, mapfile
1582 1582
1583 1583 if not tmpl:
1584 1584 return None, None
1585 1585
1586 1586 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1587 1587
1588 1588 def show_changeset(ui, repo, opts, buffered=False):
1589 1589 """show one changeset using template or regular display.
1590 1590
1591 1591 Display format will be the first non-empty hit of:
1592 1592 1. option 'template'
1593 1593 2. option 'style'
1594 1594 3. [ui] setting 'logtemplate'
1595 1595 4. [ui] setting 'style'
1596 1596 If all of these values are either the unset or the empty string,
1597 1597 regular display via changeset_printer() is done.
1598 1598 """
1599 1599 # options
1600 1600 matchfn = None
1601 1601 if opts.get('patch') or opts.get('stat'):
1602 1602 matchfn = scmutil.matchall(repo)
1603 1603
1604 1604 if opts.get('template') == 'json':
1605 1605 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1606 1606
1607 1607 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1608 1608
1609 1609 if not tmpl and not mapfile:
1610 1610 return changeset_printer(ui, repo, matchfn, opts, buffered)
1611 1611
1612 1612 return changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile, buffered)
1613 1613
1614 def showmarker(ui, marker, index=None):
1614 def showmarker(fm, marker, index=None):
1615 1615 """utility function to display obsolescence marker in a readable way
1616 1616
1617 1617 To be used by debug function."""
1618 1618 if index is not None:
1619 ui.write("%i " % index)
1620 ui.write(hex(marker.precnode()))
1621 for repl in marker.succnodes():
1622 ui.write(' ')
1623 ui.write(hex(repl))
1624 ui.write(' %X ' % marker.flags())
1619 fm.write('index', '%i ', index)
1620 fm.write('precnode', '%s ', hex(marker.precnode()))
1621 succs = marker.succnodes()
1622 fm.condwrite(succs, 'succnodes', '%s ',
1623 fm.formatlist(map(hex, succs), name='node'))
1624 fm.write('flag', '%X ', marker.flags())
1625 1625 parents = marker.parentnodes()
1626 1626 if parents is not None:
1627 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1628 ui.write('(%s) ' % util.datestr(marker.date()))
1629 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1630 sorted(marker.metadata().items())
1631 if t[0] != 'date')))
1632 ui.write('\n')
1627 fm.write('parentnodes', '{%s} ',
1628 fm.formatlist(map(hex, parents), name='node', sep=', '))
1629 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1630 meta = marker.metadata().copy()
1631 meta.pop('date', None)
1632 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
1633 fm.plain('\n')
1633 1634
1634 1635 def finddate(ui, repo, date):
1635 1636 """Find the tipmost changeset that matches the given date spec"""
1636 1637
1637 1638 df = util.matchdate(date)
1638 1639 m = scmutil.matchall(repo)
1639 1640 results = {}
1640 1641
1641 1642 def prep(ctx, fns):
1642 1643 d = ctx.date()
1643 1644 if df(d[0]):
1644 1645 results[ctx.rev()] = d
1645 1646
1646 1647 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1647 1648 rev = ctx.rev()
1648 1649 if rev in results:
1649 1650 ui.status(_("found revision %s from %s\n") %
1650 1651 (rev, util.datestr(results[rev])))
1651 1652 return str(rev)
1652 1653
1653 1654 raise error.Abort(_("revision matching date not found"))
1654 1655
1655 1656 def increasingwindows(windowsize=8, sizelimit=512):
1656 1657 while True:
1657 1658 yield windowsize
1658 1659 if windowsize < sizelimit:
1659 1660 windowsize *= 2
1660 1661
1661 1662 class FileWalkError(Exception):
1662 1663 pass
1663 1664
1664 1665 def walkfilerevs(repo, match, follow, revs, fncache):
1665 1666 '''Walks the file history for the matched files.
1666 1667
1667 1668 Returns the changeset revs that are involved in the file history.
1668 1669
1669 1670 Throws FileWalkError if the file history can't be walked using
1670 1671 filelogs alone.
1671 1672 '''
1672 1673 wanted = set()
1673 1674 copies = []
1674 1675 minrev, maxrev = min(revs), max(revs)
1675 1676 def filerevgen(filelog, last):
1676 1677 """
1677 1678 Only files, no patterns. Check the history of each file.
1678 1679
1679 1680 Examines filelog entries within minrev, maxrev linkrev range
1680 1681 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1681 1682 tuples in backwards order
1682 1683 """
1683 1684 cl_count = len(repo)
1684 1685 revs = []
1685 1686 for j in xrange(0, last + 1):
1686 1687 linkrev = filelog.linkrev(j)
1687 1688 if linkrev < minrev:
1688 1689 continue
1689 1690 # only yield rev for which we have the changelog, it can
1690 1691 # happen while doing "hg log" during a pull or commit
1691 1692 if linkrev >= cl_count:
1692 1693 break
1693 1694
1694 1695 parentlinkrevs = []
1695 1696 for p in filelog.parentrevs(j):
1696 1697 if p != nullrev:
1697 1698 parentlinkrevs.append(filelog.linkrev(p))
1698 1699 n = filelog.node(j)
1699 1700 revs.append((linkrev, parentlinkrevs,
1700 1701 follow and filelog.renamed(n)))
1701 1702
1702 1703 return reversed(revs)
1703 1704 def iterfiles():
1704 1705 pctx = repo['.']
1705 1706 for filename in match.files():
1706 1707 if follow:
1707 1708 if filename not in pctx:
1708 1709 raise error.Abort(_('cannot follow file not in parent '
1709 1710 'revision: "%s"') % filename)
1710 1711 yield filename, pctx[filename].filenode()
1711 1712 else:
1712 1713 yield filename, None
1713 1714 for filename_node in copies:
1714 1715 yield filename_node
1715 1716
1716 1717 for file_, node in iterfiles():
1717 1718 filelog = repo.file(file_)
1718 1719 if not len(filelog):
1719 1720 if node is None:
1720 1721 # A zero count may be a directory or deleted file, so
1721 1722 # try to find matching entries on the slow path.
1722 1723 if follow:
1723 1724 raise error.Abort(
1724 1725 _('cannot follow nonexistent file: "%s"') % file_)
1725 1726 raise FileWalkError("Cannot walk via filelog")
1726 1727 else:
1727 1728 continue
1728 1729
1729 1730 if node is None:
1730 1731 last = len(filelog) - 1
1731 1732 else:
1732 1733 last = filelog.rev(node)
1733 1734
1734 1735 # keep track of all ancestors of the file
1735 1736 ancestors = set([filelog.linkrev(last)])
1736 1737
1737 1738 # iterate from latest to oldest revision
1738 1739 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1739 1740 if not follow:
1740 1741 if rev > maxrev:
1741 1742 continue
1742 1743 else:
1743 1744 # Note that last might not be the first interesting
1744 1745 # rev to us:
1745 1746 # if the file has been changed after maxrev, we'll
1746 1747 # have linkrev(last) > maxrev, and we still need
1747 1748 # to explore the file graph
1748 1749 if rev not in ancestors:
1749 1750 continue
1750 1751 # XXX insert 1327 fix here
1751 1752 if flparentlinkrevs:
1752 1753 ancestors.update(flparentlinkrevs)
1753 1754
1754 1755 fncache.setdefault(rev, []).append(file_)
1755 1756 wanted.add(rev)
1756 1757 if copied:
1757 1758 copies.append(copied)
1758 1759
1759 1760 return wanted
1760 1761
1761 1762 class _followfilter(object):
1762 1763 def __init__(self, repo, onlyfirst=False):
1763 1764 self.repo = repo
1764 1765 self.startrev = nullrev
1765 1766 self.roots = set()
1766 1767 self.onlyfirst = onlyfirst
1767 1768
1768 1769 def match(self, rev):
1769 1770 def realparents(rev):
1770 1771 if self.onlyfirst:
1771 1772 return self.repo.changelog.parentrevs(rev)[0:1]
1772 1773 else:
1773 1774 return filter(lambda x: x != nullrev,
1774 1775 self.repo.changelog.parentrevs(rev))
1775 1776
1776 1777 if self.startrev == nullrev:
1777 1778 self.startrev = rev
1778 1779 return True
1779 1780
1780 1781 if rev > self.startrev:
1781 1782 # forward: all descendants
1782 1783 if not self.roots:
1783 1784 self.roots.add(self.startrev)
1784 1785 for parent in realparents(rev):
1785 1786 if parent in self.roots:
1786 1787 self.roots.add(rev)
1787 1788 return True
1788 1789 else:
1789 1790 # backwards: all parents
1790 1791 if not self.roots:
1791 1792 self.roots.update(realparents(self.startrev))
1792 1793 if rev in self.roots:
1793 1794 self.roots.remove(rev)
1794 1795 self.roots.update(realparents(rev))
1795 1796 return True
1796 1797
1797 1798 return False
1798 1799
1799 1800 def walkchangerevs(repo, match, opts, prepare):
1800 1801 '''Iterate over files and the revs in which they changed.
1801 1802
1802 1803 Callers most commonly need to iterate backwards over the history
1803 1804 in which they are interested. Doing so has awful (quadratic-looking)
1804 1805 performance, so we use iterators in a "windowed" way.
1805 1806
1806 1807 We walk a window of revisions in the desired order. Within the
1807 1808 window, we first walk forwards to gather data, then in the desired
1808 1809 order (usually backwards) to display it.
1809 1810
1810 1811 This function returns an iterator yielding contexts. Before
1811 1812 yielding each context, the iterator will first call the prepare
1812 1813 function on each context in the window in forward order.'''
1813 1814
1814 1815 follow = opts.get('follow') or opts.get('follow_first')
1815 1816 revs = _logrevs(repo, opts)
1816 1817 if not revs:
1817 1818 return []
1818 1819 wanted = set()
1819 1820 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1820 1821 opts.get('removed'))
1821 1822 fncache = {}
1822 1823 change = repo.changectx
1823 1824
1824 1825 # First step is to fill wanted, the set of revisions that we want to yield.
1825 1826 # When it does not induce extra cost, we also fill fncache for revisions in
1826 1827 # wanted: a cache of filenames that were changed (ctx.files()) and that
1827 1828 # match the file filtering conditions.
1828 1829
1829 1830 if match.always():
1830 1831 # No files, no patterns. Display all revs.
1831 1832 wanted = revs
1832 1833 elif not slowpath:
1833 1834 # We only have to read through the filelog to find wanted revisions
1834 1835
1835 1836 try:
1836 1837 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1837 1838 except FileWalkError:
1838 1839 slowpath = True
1839 1840
1840 1841 # We decided to fall back to the slowpath because at least one
1841 1842 # of the paths was not a file. Check to see if at least one of them
1842 1843 # existed in history, otherwise simply return
1843 1844 for path in match.files():
1844 1845 if path == '.' or path in repo.store:
1845 1846 break
1846 1847 else:
1847 1848 return []
1848 1849
1849 1850 if slowpath:
1850 1851 # We have to read the changelog to match filenames against
1851 1852 # changed files
1852 1853
1853 1854 if follow:
1854 1855 raise error.Abort(_('can only follow copies/renames for explicit '
1855 1856 'filenames'))
1856 1857
1857 1858 # The slow path checks files modified in every changeset.
1858 1859 # This is really slow on large repos, so compute the set lazily.
1859 1860 class lazywantedset(object):
1860 1861 def __init__(self):
1861 1862 self.set = set()
1862 1863 self.revs = set(revs)
1863 1864
1864 1865 # No need to worry about locality here because it will be accessed
1865 1866 # in the same order as the increasing window below.
1866 1867 def __contains__(self, value):
1867 1868 if value in self.set:
1868 1869 return True
1869 1870 elif not value in self.revs:
1870 1871 return False
1871 1872 else:
1872 1873 self.revs.discard(value)
1873 1874 ctx = change(value)
1874 1875 matches = filter(match, ctx.files())
1875 1876 if matches:
1876 1877 fncache[value] = matches
1877 1878 self.set.add(value)
1878 1879 return True
1879 1880 return False
1880 1881
1881 1882 def discard(self, value):
1882 1883 self.revs.discard(value)
1883 1884 self.set.discard(value)
1884 1885
1885 1886 wanted = lazywantedset()
1886 1887
1887 1888 # it might be worthwhile to do this in the iterator if the rev range
1888 1889 # is descending and the prune args are all within that range
1889 1890 for rev in opts.get('prune', ()):
1890 1891 rev = repo[rev].rev()
1891 1892 ff = _followfilter(repo)
1892 1893 stop = min(revs[0], revs[-1])
1893 1894 for x in xrange(rev, stop - 1, -1):
1894 1895 if ff.match(x):
1895 1896 wanted = wanted - [x]
1896 1897
1897 1898 # Now that wanted is correctly initialized, we can iterate over the
1898 1899 # revision range, yielding only revisions in wanted.
1899 1900 def iterate():
1900 1901 if follow and match.always():
1901 1902 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1902 1903 def want(rev):
1903 1904 return ff.match(rev) and rev in wanted
1904 1905 else:
1905 1906 def want(rev):
1906 1907 return rev in wanted
1907 1908
1908 1909 it = iter(revs)
1909 1910 stopiteration = False
1910 1911 for windowsize in increasingwindows():
1911 1912 nrevs = []
1912 1913 for i in xrange(windowsize):
1913 1914 rev = next(it, None)
1914 1915 if rev is None:
1915 1916 stopiteration = True
1916 1917 break
1917 1918 elif want(rev):
1918 1919 nrevs.append(rev)
1919 1920 for rev in sorted(nrevs):
1920 1921 fns = fncache.get(rev)
1921 1922 ctx = change(rev)
1922 1923 if not fns:
1923 1924 def fns_generator():
1924 1925 for f in ctx.files():
1925 1926 if match(f):
1926 1927 yield f
1927 1928 fns = fns_generator()
1928 1929 prepare(ctx, fns)
1929 1930 for rev in nrevs:
1930 1931 yield change(rev)
1931 1932
1932 1933 if stopiteration:
1933 1934 break
1934 1935
1935 1936 return iterate()
1936 1937
1937 1938 def _makefollowlogfilematcher(repo, files, followfirst):
1938 1939 # When displaying a revision with --patch --follow FILE, we have
1939 1940 # to know which file of the revision must be diffed. With
1940 1941 # --follow, we want the names of the ancestors of FILE in the
1941 1942 # revision, stored in "fcache". "fcache" is populated by
1942 1943 # reproducing the graph traversal already done by --follow revset
1943 1944 # and relating linkrevs to file names (which is not "correct" but
1944 1945 # good enough).
1945 1946 fcache = {}
1946 1947 fcacheready = [False]
1947 1948 pctx = repo['.']
1948 1949
1949 1950 def populate():
1950 1951 for fn in files:
1951 1952 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1952 1953 for c in i:
1953 1954 fcache.setdefault(c.linkrev(), set()).add(c.path())
1954 1955
1955 1956 def filematcher(rev):
1956 1957 if not fcacheready[0]:
1957 1958 # Lazy initialization
1958 1959 fcacheready[0] = True
1959 1960 populate()
1960 1961 return scmutil.matchfiles(repo, fcache.get(rev, []))
1961 1962
1962 1963 return filematcher
1963 1964
1964 1965 def _makenofollowlogfilematcher(repo, pats, opts):
1965 1966 '''hook for extensions to override the filematcher for non-follow cases'''
1966 1967 return None
1967 1968
1968 1969 def _makelogrevset(repo, pats, opts, revs):
1969 1970 """Return (expr, filematcher) where expr is a revset string built
1970 1971 from log options and file patterns or None. If --stat or --patch
1971 1972 are not passed filematcher is None. Otherwise it is a callable
1972 1973 taking a revision number and returning a match objects filtering
1973 1974 the files to be detailed when displaying the revision.
1974 1975 """
1975 1976 opt2revset = {
1976 1977 'no_merges': ('not merge()', None),
1977 1978 'only_merges': ('merge()', None),
1978 1979 '_ancestors': ('ancestors(%(val)s)', None),
1979 1980 '_fancestors': ('_firstancestors(%(val)s)', None),
1980 1981 '_descendants': ('descendants(%(val)s)', None),
1981 1982 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1982 1983 '_matchfiles': ('_matchfiles(%(val)s)', None),
1983 1984 'date': ('date(%(val)r)', None),
1984 1985 'branch': ('branch(%(val)r)', ' or '),
1985 1986 '_patslog': ('filelog(%(val)r)', ' or '),
1986 1987 '_patsfollow': ('follow(%(val)r)', ' or '),
1987 1988 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1988 1989 'keyword': ('keyword(%(val)r)', ' or '),
1989 1990 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1990 1991 'user': ('user(%(val)r)', ' or '),
1991 1992 }
1992 1993
1993 1994 opts = dict(opts)
1994 1995 # follow or not follow?
1995 1996 follow = opts.get('follow') or opts.get('follow_first')
1996 1997 if opts.get('follow_first'):
1997 1998 followfirst = 1
1998 1999 else:
1999 2000 followfirst = 0
2000 2001 # --follow with FILE behavior depends on revs...
2001 2002 it = iter(revs)
2002 2003 startrev = next(it)
2003 2004 followdescendants = startrev < next(it, startrev)
2004 2005
2005 2006 # branch and only_branch are really aliases and must be handled at
2006 2007 # the same time
2007 2008 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2008 2009 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2009 2010 # pats/include/exclude are passed to match.match() directly in
2010 2011 # _matchfiles() revset but walkchangerevs() builds its matcher with
2011 2012 # scmutil.match(). The difference is input pats are globbed on
2012 2013 # platforms without shell expansion (windows).
2013 2014 wctx = repo[None]
2014 2015 match, pats = scmutil.matchandpats(wctx, pats, opts)
2015 2016 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2016 2017 opts.get('removed'))
2017 2018 if not slowpath:
2018 2019 for f in match.files():
2019 2020 if follow and f not in wctx:
2020 2021 # If the file exists, it may be a directory, so let it
2021 2022 # take the slow path.
2022 2023 if os.path.exists(repo.wjoin(f)):
2023 2024 slowpath = True
2024 2025 continue
2025 2026 else:
2026 2027 raise error.Abort(_('cannot follow file not in parent '
2027 2028 'revision: "%s"') % f)
2028 2029 filelog = repo.file(f)
2029 2030 if not filelog:
2030 2031 # A zero count may be a directory or deleted file, so
2031 2032 # try to find matching entries on the slow path.
2032 2033 if follow:
2033 2034 raise error.Abort(
2034 2035 _('cannot follow nonexistent file: "%s"') % f)
2035 2036 slowpath = True
2036 2037
2037 2038 # We decided to fall back to the slowpath because at least one
2038 2039 # of the paths was not a file. Check to see if at least one of them
2039 2040 # existed in history - in that case, we'll continue down the
2040 2041 # slowpath; otherwise, we can turn off the slowpath
2041 2042 if slowpath:
2042 2043 for path in match.files():
2043 2044 if path == '.' or path in repo.store:
2044 2045 break
2045 2046 else:
2046 2047 slowpath = False
2047 2048
2048 2049 fpats = ('_patsfollow', '_patsfollowfirst')
2049 2050 fnopats = (('_ancestors', '_fancestors'),
2050 2051 ('_descendants', '_fdescendants'))
2051 2052 if slowpath:
2052 2053 # See walkchangerevs() slow path.
2053 2054 #
2054 2055 # pats/include/exclude cannot be represented as separate
2055 2056 # revset expressions as their filtering logic applies at file
2056 2057 # level. For instance "-I a -X a" matches a revision touching
2057 2058 # "a" and "b" while "file(a) and not file(b)" does
2058 2059 # not. Besides, filesets are evaluated against the working
2059 2060 # directory.
2060 2061 matchargs = ['r:', 'd:relpath']
2061 2062 for p in pats:
2062 2063 matchargs.append('p:' + p)
2063 2064 for p in opts.get('include', []):
2064 2065 matchargs.append('i:' + p)
2065 2066 for p in opts.get('exclude', []):
2066 2067 matchargs.append('x:' + p)
2067 2068 matchargs = ','.join(('%r' % p) for p in matchargs)
2068 2069 opts['_matchfiles'] = matchargs
2069 2070 if follow:
2070 2071 opts[fnopats[0][followfirst]] = '.'
2071 2072 else:
2072 2073 if follow:
2073 2074 if pats:
2074 2075 # follow() revset interprets its file argument as a
2075 2076 # manifest entry, so use match.files(), not pats.
2076 2077 opts[fpats[followfirst]] = list(match.files())
2077 2078 else:
2078 2079 op = fnopats[followdescendants][followfirst]
2079 2080 opts[op] = 'rev(%d)' % startrev
2080 2081 else:
2081 2082 opts['_patslog'] = list(pats)
2082 2083
2083 2084 filematcher = None
2084 2085 if opts.get('patch') or opts.get('stat'):
2085 2086 # When following files, track renames via a special matcher.
2086 2087 # If we're forced to take the slowpath it means we're following
2087 2088 # at least one pattern/directory, so don't bother with rename tracking.
2088 2089 if follow and not match.always() and not slowpath:
2089 2090 # _makefollowlogfilematcher expects its files argument to be
2090 2091 # relative to the repo root, so use match.files(), not pats.
2091 2092 filematcher = _makefollowlogfilematcher(repo, match.files(),
2092 2093 followfirst)
2093 2094 else:
2094 2095 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2095 2096 if filematcher is None:
2096 2097 filematcher = lambda rev: match
2097 2098
2098 2099 expr = []
2099 2100 for op, val in sorted(opts.iteritems()):
2100 2101 if not val:
2101 2102 continue
2102 2103 if op not in opt2revset:
2103 2104 continue
2104 2105 revop, andor = opt2revset[op]
2105 2106 if '%(val)' not in revop:
2106 2107 expr.append(revop)
2107 2108 else:
2108 2109 if not isinstance(val, list):
2109 2110 e = revop % {'val': val}
2110 2111 else:
2111 2112 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2112 2113 expr.append(e)
2113 2114
2114 2115 if expr:
2115 2116 expr = '(' + ' and '.join(expr) + ')'
2116 2117 else:
2117 2118 expr = None
2118 2119 return expr, filematcher
2119 2120
2120 2121 def _logrevs(repo, opts):
2121 2122 # Default --rev value depends on --follow but --follow behavior
2122 2123 # depends on revisions resolved from --rev...
2123 2124 follow = opts.get('follow') or opts.get('follow_first')
2124 2125 if opts.get('rev'):
2125 2126 revs = scmutil.revrange(repo, opts['rev'])
2126 2127 elif follow and repo.dirstate.p1() == nullid:
2127 2128 revs = revset.baseset()
2128 2129 elif follow:
2129 2130 revs = repo.revs('reverse(:.)')
2130 2131 else:
2131 2132 revs = revset.spanset(repo)
2132 2133 revs.reverse()
2133 2134 return revs
2134 2135
2135 2136 def getgraphlogrevs(repo, pats, opts):
2136 2137 """Return (revs, expr, filematcher) where revs is an iterable of
2137 2138 revision numbers, expr is a revset string built from log options
2138 2139 and file patterns or None, and used to filter 'revs'. If --stat or
2139 2140 --patch are not passed filematcher is None. Otherwise it is a
2140 2141 callable taking a revision number and returning a match objects
2141 2142 filtering the files to be detailed when displaying the revision.
2142 2143 """
2143 2144 limit = loglimit(opts)
2144 2145 revs = _logrevs(repo, opts)
2145 2146 if not revs:
2146 2147 return revset.baseset(), None, None
2147 2148 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2148 2149 if opts.get('rev'):
2149 2150 # User-specified revs might be unsorted, but don't sort before
2150 2151 # _makelogrevset because it might depend on the order of revs
2151 2152 if not (revs.isdescending() or revs.istopo()):
2152 2153 revs.sort(reverse=True)
2153 2154 if expr:
2154 2155 # Revset matchers often operate faster on revisions in changelog
2155 2156 # order, because most filters deal with the changelog.
2156 2157 revs.reverse()
2157 2158 matcher = revset.match(repo.ui, expr)
2158 2159 # Revset matches can reorder revisions. "A or B" typically returns
2159 2160 # returns the revision matching A then the revision matching B. Sort
2160 2161 # again to fix that.
2161 2162 revs = matcher(repo, revs)
2162 2163 revs.sort(reverse=True)
2163 2164 if limit is not None:
2164 2165 limitedrevs = []
2165 2166 for idx, rev in enumerate(revs):
2166 2167 if idx >= limit:
2167 2168 break
2168 2169 limitedrevs.append(rev)
2169 2170 revs = revset.baseset(limitedrevs)
2170 2171
2171 2172 return revs, expr, filematcher
2172 2173
2173 2174 def getlogrevs(repo, pats, opts):
2174 2175 """Return (revs, expr, filematcher) where revs is an iterable of
2175 2176 revision numbers, expr is a revset string built from log options
2176 2177 and file patterns or None, and used to filter 'revs'. If --stat or
2177 2178 --patch are not passed filematcher is None. Otherwise it is a
2178 2179 callable taking a revision number and returning a match objects
2179 2180 filtering the files to be detailed when displaying the revision.
2180 2181 """
2181 2182 limit = loglimit(opts)
2182 2183 revs = _logrevs(repo, opts)
2183 2184 if not revs:
2184 2185 return revset.baseset([]), None, None
2185 2186 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2186 2187 if expr:
2187 2188 # Revset matchers often operate faster on revisions in changelog
2188 2189 # order, because most filters deal with the changelog.
2189 2190 if not opts.get('rev'):
2190 2191 revs.reverse()
2191 2192 matcher = revset.match(repo.ui, expr)
2192 2193 # Revset matches can reorder revisions. "A or B" typically returns
2193 2194 # returns the revision matching A then the revision matching B. Sort
2194 2195 # again to fix that.
2195 2196 fixopts = ['branch', 'only_branch', 'keyword', 'user']
2196 2197 oldrevs = revs
2197 2198 revs = matcher(repo, revs)
2198 2199 if not opts.get('rev'):
2199 2200 revs.sort(reverse=True)
2200 2201 elif len(pats) > 1 or any(len(opts.get(op, [])) > 1 for op in fixopts):
2201 2202 # XXX "A or B" is known to change the order; fix it by filtering
2202 2203 # matched set again (issue5100)
2203 2204 revs = oldrevs & revs
2204 2205 if limit is not None:
2205 2206 limitedrevs = []
2206 2207 for idx, r in enumerate(revs):
2207 2208 if limit <= idx:
2208 2209 break
2209 2210 limitedrevs.append(r)
2210 2211 revs = revset.baseset(limitedrevs)
2211 2212
2212 2213 return revs, expr, filematcher
2213 2214
2214 2215 def _graphnodeformatter(ui, displayer):
2215 2216 spec = ui.config('ui', 'graphnodetemplate')
2216 2217 if not spec:
2217 2218 return templatekw.showgraphnode # fast path for "{graphnode}"
2218 2219
2219 2220 templ = formatter.gettemplater(ui, 'graphnode', spec)
2220 2221 cache = {}
2221 2222 if isinstance(displayer, changeset_templater):
2222 2223 cache = displayer.cache # reuse cache of slow templates
2223 2224 props = templatekw.keywords.copy()
2224 2225 props['templ'] = templ
2225 2226 props['cache'] = cache
2226 2227 def formatnode(repo, ctx):
2227 2228 props['ctx'] = ctx
2228 2229 props['repo'] = repo
2229 2230 props['ui'] = repo.ui
2230 2231 props['revcache'] = {}
2231 2232 return templater.stringify(templ('graphnode', **props))
2232 2233 return formatnode
2233 2234
2234 2235 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2235 2236 filematcher=None):
2236 2237 formatnode = _graphnodeformatter(ui, displayer)
2237 2238 state = graphmod.asciistate()
2238 2239 styles = state['styles']
2239 2240
2240 2241 # only set graph styling if HGPLAIN is not set.
2241 2242 if ui.plain('graph'):
2242 2243 # set all edge styles to |, the default pre-3.8 behaviour
2243 2244 styles.update(dict.fromkeys(styles, '|'))
2244 2245 else:
2245 2246 edgetypes = {
2246 2247 'parent': graphmod.PARENT,
2247 2248 'grandparent': graphmod.GRANDPARENT,
2248 2249 'missing': graphmod.MISSINGPARENT
2249 2250 }
2250 2251 for name, key in edgetypes.items():
2251 2252 # experimental config: experimental.graphstyle.*
2252 2253 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2253 2254 styles[key])
2254 2255 if not styles[key]:
2255 2256 styles[key] = None
2256 2257
2257 2258 # experimental config: experimental.graphshorten
2258 2259 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2259 2260
2260 2261 for rev, type, ctx, parents in dag:
2261 2262 char = formatnode(repo, ctx)
2262 2263 copies = None
2263 2264 if getrenamed and ctx.rev():
2264 2265 copies = []
2265 2266 for fn in ctx.files():
2266 2267 rename = getrenamed(fn, ctx.rev())
2267 2268 if rename:
2268 2269 copies.append((fn, rename[0]))
2269 2270 revmatchfn = None
2270 2271 if filematcher is not None:
2271 2272 revmatchfn = filematcher(ctx.rev())
2272 2273 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2273 2274 lines = displayer.hunk.pop(rev).split('\n')
2274 2275 if not lines[-1]:
2275 2276 del lines[-1]
2276 2277 displayer.flush(ctx)
2277 2278 edges = edgefn(type, char, lines, state, rev, parents)
2278 2279 for type, char, lines, coldata in edges:
2279 2280 graphmod.ascii(ui, state, type, char, lines, coldata)
2280 2281 displayer.close()
2281 2282
2282 2283 def graphlog(ui, repo, *pats, **opts):
2283 2284 # Parameters are identical to log command ones
2284 2285 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2285 2286 revdag = graphmod.dagwalker(repo, revs)
2286 2287
2287 2288 getrenamed = None
2288 2289 if opts.get('copies'):
2289 2290 endrev = None
2290 2291 if opts.get('rev'):
2291 2292 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2292 2293 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2293 2294 displayer = show_changeset(ui, repo, opts, buffered=True)
2294 2295 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2295 2296 filematcher)
2296 2297
2297 2298 def checkunsupportedgraphflags(pats, opts):
2298 2299 for op in ["newest_first"]:
2299 2300 if op in opts and opts[op]:
2300 2301 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2301 2302 % op.replace("_", "-"))
2302 2303
2303 2304 def graphrevs(repo, nodes, opts):
2304 2305 limit = loglimit(opts)
2305 2306 nodes.reverse()
2306 2307 if limit is not None:
2307 2308 nodes = nodes[:limit]
2308 2309 return graphmod.nodes(repo, nodes)
2309 2310
2310 2311 def add(ui, repo, match, prefix, explicitonly, **opts):
2311 2312 join = lambda f: os.path.join(prefix, f)
2312 2313 bad = []
2313 2314
2314 2315 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2315 2316 names = []
2316 2317 wctx = repo[None]
2317 2318 cca = None
2318 2319 abort, warn = scmutil.checkportabilityalert(ui)
2319 2320 if abort or warn:
2320 2321 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2321 2322
2322 2323 badmatch = matchmod.badmatch(match, badfn)
2323 2324 dirstate = repo.dirstate
2324 2325 # We don't want to just call wctx.walk here, since it would return a lot of
2325 2326 # clean files, which we aren't interested in and takes time.
2326 2327 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2327 2328 True, False, full=False)):
2328 2329 exact = match.exact(f)
2329 2330 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2330 2331 if cca:
2331 2332 cca(f)
2332 2333 names.append(f)
2333 2334 if ui.verbose or not exact:
2334 2335 ui.status(_('adding %s\n') % match.rel(f))
2335 2336
2336 2337 for subpath in sorted(wctx.substate):
2337 2338 sub = wctx.sub(subpath)
2338 2339 try:
2339 2340 submatch = matchmod.subdirmatcher(subpath, match)
2340 2341 if opts.get('subrepos'):
2341 2342 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2342 2343 else:
2343 2344 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2344 2345 except error.LookupError:
2345 2346 ui.status(_("skipping missing subrepository: %s\n")
2346 2347 % join(subpath))
2347 2348
2348 2349 if not opts.get('dry_run'):
2349 2350 rejected = wctx.add(names, prefix)
2350 2351 bad.extend(f for f in rejected if f in match.files())
2351 2352 return bad
2352 2353
2353 2354 def forget(ui, repo, match, prefix, explicitonly):
2354 2355 join = lambda f: os.path.join(prefix, f)
2355 2356 bad = []
2356 2357 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2357 2358 wctx = repo[None]
2358 2359 forgot = []
2359 2360
2360 2361 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2361 2362 forget = sorted(s[0] + s[1] + s[3] + s[6])
2362 2363 if explicitonly:
2363 2364 forget = [f for f in forget if match.exact(f)]
2364 2365
2365 2366 for subpath in sorted(wctx.substate):
2366 2367 sub = wctx.sub(subpath)
2367 2368 try:
2368 2369 submatch = matchmod.subdirmatcher(subpath, match)
2369 2370 subbad, subforgot = sub.forget(submatch, prefix)
2370 2371 bad.extend([subpath + '/' + f for f in subbad])
2371 2372 forgot.extend([subpath + '/' + f for f in subforgot])
2372 2373 except error.LookupError:
2373 2374 ui.status(_("skipping missing subrepository: %s\n")
2374 2375 % join(subpath))
2375 2376
2376 2377 if not explicitonly:
2377 2378 for f in match.files():
2378 2379 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2379 2380 if f not in forgot:
2380 2381 if repo.wvfs.exists(f):
2381 2382 # Don't complain if the exact case match wasn't given.
2382 2383 # But don't do this until after checking 'forgot', so
2383 2384 # that subrepo files aren't normalized, and this op is
2384 2385 # purely from data cached by the status walk above.
2385 2386 if repo.dirstate.normalize(f) in repo.dirstate:
2386 2387 continue
2387 2388 ui.warn(_('not removing %s: '
2388 2389 'file is already untracked\n')
2389 2390 % match.rel(f))
2390 2391 bad.append(f)
2391 2392
2392 2393 for f in forget:
2393 2394 if ui.verbose or not match.exact(f):
2394 2395 ui.status(_('removing %s\n') % match.rel(f))
2395 2396
2396 2397 rejected = wctx.forget(forget, prefix)
2397 2398 bad.extend(f for f in rejected if f in match.files())
2398 2399 forgot.extend(f for f in forget if f not in rejected)
2399 2400 return bad, forgot
2400 2401
2401 2402 def files(ui, ctx, m, fm, fmt, subrepos):
2402 2403 rev = ctx.rev()
2403 2404 ret = 1
2404 2405 ds = ctx.repo().dirstate
2405 2406
2406 2407 for f in ctx.matches(m):
2407 2408 if rev is None and ds[f] == 'r':
2408 2409 continue
2409 2410 fm.startitem()
2410 2411 if ui.verbose:
2411 2412 fc = ctx[f]
2412 2413 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2413 2414 fm.data(abspath=f)
2414 2415 fm.write('path', fmt, m.rel(f))
2415 2416 ret = 0
2416 2417
2417 2418 for subpath in sorted(ctx.substate):
2418 2419 if subrepos or m.matchessubrepo(subpath):
2419 2420 sub = ctx.sub(subpath)
2420 2421 try:
2421 2422 submatch = matchmod.subdirmatcher(subpath, m)
2422 2423 recurse = m.exact(subpath) or subrepos
2423 2424 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2424 2425 ret = 0
2425 2426 except error.LookupError:
2426 2427 ui.status(_("skipping missing subrepository: %s\n")
2427 2428 % m.abs(subpath))
2428 2429
2429 2430 return ret
2430 2431
2431 2432 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2432 2433 join = lambda f: os.path.join(prefix, f)
2433 2434 ret = 0
2434 2435 s = repo.status(match=m, clean=True)
2435 2436 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2436 2437
2437 2438 wctx = repo[None]
2438 2439
2439 2440 if warnings is None:
2440 2441 warnings = []
2441 2442 warn = True
2442 2443 else:
2443 2444 warn = False
2444 2445
2445 2446 subs = sorted(wctx.substate)
2446 2447 total = len(subs)
2447 2448 count = 0
2448 2449 for subpath in subs:
2449 2450 count += 1
2450 2451 if subrepos or m.matchessubrepo(subpath):
2451 2452 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2452 2453
2453 2454 sub = wctx.sub(subpath)
2454 2455 try:
2455 2456 submatch = matchmod.subdirmatcher(subpath, m)
2456 2457 if sub.removefiles(submatch, prefix, after, force, subrepos,
2457 2458 warnings):
2458 2459 ret = 1
2459 2460 except error.LookupError:
2460 2461 warnings.append(_("skipping missing subrepository: %s\n")
2461 2462 % join(subpath))
2462 2463 ui.progress(_('searching'), None)
2463 2464
2464 2465 # warn about failure to delete explicit files/dirs
2465 2466 deleteddirs = util.dirs(deleted)
2466 2467 files = m.files()
2467 2468 total = len(files)
2468 2469 count = 0
2469 2470 for f in files:
2470 2471 def insubrepo():
2471 2472 for subpath in wctx.substate:
2472 2473 if f.startswith(subpath + '/'):
2473 2474 return True
2474 2475 return False
2475 2476
2476 2477 count += 1
2477 2478 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2478 2479 isdir = f in deleteddirs or wctx.hasdir(f)
2479 2480 if (f in repo.dirstate or isdir or f == '.'
2480 2481 or insubrepo() or f in subs):
2481 2482 continue
2482 2483
2483 2484 if repo.wvfs.exists(f):
2484 2485 if repo.wvfs.isdir(f):
2485 2486 warnings.append(_('not removing %s: no tracked files\n')
2486 2487 % m.rel(f))
2487 2488 else:
2488 2489 warnings.append(_('not removing %s: file is untracked\n')
2489 2490 % m.rel(f))
2490 2491 # missing files will generate a warning elsewhere
2491 2492 ret = 1
2492 2493 ui.progress(_('deleting'), None)
2493 2494
2494 2495 if force:
2495 2496 list = modified + deleted + clean + added
2496 2497 elif after:
2497 2498 list = deleted
2498 2499 remaining = modified + added + clean
2499 2500 total = len(remaining)
2500 2501 count = 0
2501 2502 for f in remaining:
2502 2503 count += 1
2503 2504 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2504 2505 warnings.append(_('not removing %s: file still exists\n')
2505 2506 % m.rel(f))
2506 2507 ret = 1
2507 2508 ui.progress(_('skipping'), None)
2508 2509 else:
2509 2510 list = deleted + clean
2510 2511 total = len(modified) + len(added)
2511 2512 count = 0
2512 2513 for f in modified:
2513 2514 count += 1
2514 2515 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2515 2516 warnings.append(_('not removing %s: file is modified (use -f'
2516 2517 ' to force removal)\n') % m.rel(f))
2517 2518 ret = 1
2518 2519 for f in added:
2519 2520 count += 1
2520 2521 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2521 2522 warnings.append(_('not removing %s: file has been marked for add'
2522 2523 ' (use forget to undo)\n') % m.rel(f))
2523 2524 ret = 1
2524 2525 ui.progress(_('skipping'), None)
2525 2526
2526 2527 list = sorted(list)
2527 2528 total = len(list)
2528 2529 count = 0
2529 2530 for f in list:
2530 2531 count += 1
2531 2532 if ui.verbose or not m.exact(f):
2532 2533 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2533 2534 ui.status(_('removing %s\n') % m.rel(f))
2534 2535 ui.progress(_('deleting'), None)
2535 2536
2536 2537 with repo.wlock():
2537 2538 if not after:
2538 2539 for f in list:
2539 2540 if f in added:
2540 2541 continue # we never unlink added files on remove
2541 2542 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2542 2543 repo[None].forget(list)
2543 2544
2544 2545 if warn:
2545 2546 for warning in warnings:
2546 2547 ui.warn(warning)
2547 2548
2548 2549 return ret
2549 2550
2550 2551 def cat(ui, repo, ctx, matcher, prefix, **opts):
2551 2552 err = 1
2552 2553
2553 2554 def write(path):
2554 2555 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2555 2556 pathname=os.path.join(prefix, path))
2556 2557 data = ctx[path].data()
2557 2558 if opts.get('decode'):
2558 2559 data = repo.wwritedata(path, data)
2559 2560 fp.write(data)
2560 2561 fp.close()
2561 2562
2562 2563 # Automation often uses hg cat on single files, so special case it
2563 2564 # for performance to avoid the cost of parsing the manifest.
2564 2565 if len(matcher.files()) == 1 and not matcher.anypats():
2565 2566 file = matcher.files()[0]
2566 2567 mf = repo.manifest
2567 2568 mfnode = ctx.manifestnode()
2568 2569 if mfnode and mf.find(mfnode, file)[0]:
2569 2570 write(file)
2570 2571 return 0
2571 2572
2572 2573 for abs in ctx.walk(matcher):
2573 2574 write(abs)
2574 2575 err = 0
2575 2576
2576 2577 for subpath in sorted(ctx.substate):
2577 2578 sub = ctx.sub(subpath)
2578 2579 try:
2579 2580 submatch = matchmod.subdirmatcher(subpath, matcher)
2580 2581
2581 2582 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2582 2583 **opts):
2583 2584 err = 0
2584 2585 except error.RepoLookupError:
2585 2586 ui.status(_("skipping missing subrepository: %s\n")
2586 2587 % os.path.join(prefix, subpath))
2587 2588
2588 2589 return err
2589 2590
2590 2591 def commit(ui, repo, commitfunc, pats, opts):
2591 2592 '''commit the specified files or all outstanding changes'''
2592 2593 date = opts.get('date')
2593 2594 if date:
2594 2595 opts['date'] = util.parsedate(date)
2595 2596 message = logmessage(ui, opts)
2596 2597 matcher = scmutil.match(repo[None], pats, opts)
2597 2598
2598 2599 # extract addremove carefully -- this function can be called from a command
2599 2600 # that doesn't support addremove
2600 2601 if opts.get('addremove'):
2601 2602 if scmutil.addremove(repo, matcher, "", opts) != 0:
2602 2603 raise error.Abort(
2603 2604 _("failed to mark all new/missing files as added/removed"))
2604 2605
2605 2606 return commitfunc(ui, repo, message, matcher, opts)
2606 2607
2607 2608 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2608 2609 # avoid cycle context -> subrepo -> cmdutil
2609 2610 from . import context
2610 2611
2611 2612 # amend will reuse the existing user if not specified, but the obsolete
2612 2613 # marker creation requires that the current user's name is specified.
2613 2614 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2614 2615 ui.username() # raise exception if username not set
2615 2616
2616 2617 ui.note(_('amending changeset %s\n') % old)
2617 2618 base = old.p1()
2618 2619 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2619 2620
2620 2621 wlock = lock = newid = None
2621 2622 try:
2622 2623 wlock = repo.wlock()
2623 2624 lock = repo.lock()
2624 2625 with repo.transaction('amend') as tr:
2625 2626 # See if we got a message from -m or -l, if not, open the editor
2626 2627 # with the message of the changeset to amend
2627 2628 message = logmessage(ui, opts)
2628 2629 # ensure logfile does not conflict with later enforcement of the
2629 2630 # message. potential logfile content has been processed by
2630 2631 # `logmessage` anyway.
2631 2632 opts.pop('logfile')
2632 2633 # First, do a regular commit to record all changes in the working
2633 2634 # directory (if there are any)
2634 2635 ui.callhooks = False
2635 2636 activebookmark = repo._bookmarks.active
2636 2637 try:
2637 2638 repo._bookmarks.active = None
2638 2639 opts['message'] = 'temporary amend commit for %s' % old
2639 2640 node = commit(ui, repo, commitfunc, pats, opts)
2640 2641 finally:
2641 2642 repo._bookmarks.active = activebookmark
2642 2643 repo._bookmarks.recordchange(tr)
2643 2644 ui.callhooks = True
2644 2645 ctx = repo[node]
2645 2646
2646 2647 # Participating changesets:
2647 2648 #
2648 2649 # node/ctx o - new (intermediate) commit that contains changes
2649 2650 # | from working dir to go into amending commit
2650 2651 # | (or a workingctx if there were no changes)
2651 2652 # |
2652 2653 # old o - changeset to amend
2653 2654 # |
2654 2655 # base o - parent of amending changeset
2655 2656
2656 2657 # Update extra dict from amended commit (e.g. to preserve graft
2657 2658 # source)
2658 2659 extra.update(old.extra())
2659 2660
2660 2661 # Also update it from the intermediate commit or from the wctx
2661 2662 extra.update(ctx.extra())
2662 2663
2663 2664 if len(old.parents()) > 1:
2664 2665 # ctx.files() isn't reliable for merges, so fall back to the
2665 2666 # slower repo.status() method
2666 2667 files = set([fn for st in repo.status(base, old)[:3]
2667 2668 for fn in st])
2668 2669 else:
2669 2670 files = set(old.files())
2670 2671
2671 2672 # Second, we use either the commit we just did, or if there were no
2672 2673 # changes the parent of the working directory as the version of the
2673 2674 # files in the final amend commit
2674 2675 if node:
2675 2676 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2676 2677
2677 2678 user = ctx.user()
2678 2679 date = ctx.date()
2679 2680 # Recompute copies (avoid recording a -> b -> a)
2680 2681 copied = copies.pathcopies(base, ctx)
2681 2682 if old.p2:
2682 2683 copied.update(copies.pathcopies(old.p2(), ctx))
2683 2684
2684 2685 # Prune files which were reverted by the updates: if old
2685 2686 # introduced file X and our intermediate commit, node,
2686 2687 # renamed that file, then those two files are the same and
2687 2688 # we can discard X from our list of files. Likewise if X
2688 2689 # was deleted, it's no longer relevant
2689 2690 files.update(ctx.files())
2690 2691
2691 2692 def samefile(f):
2692 2693 if f in ctx.manifest():
2693 2694 a = ctx.filectx(f)
2694 2695 if f in base.manifest():
2695 2696 b = base.filectx(f)
2696 2697 return (not a.cmp(b)
2697 2698 and a.flags() == b.flags())
2698 2699 else:
2699 2700 return False
2700 2701 else:
2701 2702 return f not in base.manifest()
2702 2703 files = [f for f in files if not samefile(f)]
2703 2704
2704 2705 def filectxfn(repo, ctx_, path):
2705 2706 try:
2706 2707 fctx = ctx[path]
2707 2708 flags = fctx.flags()
2708 2709 mctx = context.memfilectx(repo,
2709 2710 fctx.path(), fctx.data(),
2710 2711 islink='l' in flags,
2711 2712 isexec='x' in flags,
2712 2713 copied=copied.get(path))
2713 2714 return mctx
2714 2715 except KeyError:
2715 2716 return None
2716 2717 else:
2717 2718 ui.note(_('copying changeset %s to %s\n') % (old, base))
2718 2719
2719 2720 # Use version of files as in the old cset
2720 2721 def filectxfn(repo, ctx_, path):
2721 2722 try:
2722 2723 return old.filectx(path)
2723 2724 except KeyError:
2724 2725 return None
2725 2726
2726 2727 user = opts.get('user') or old.user()
2727 2728 date = opts.get('date') or old.date()
2728 2729 editform = mergeeditform(old, 'commit.amend')
2729 2730 editor = getcommiteditor(editform=editform, **opts)
2730 2731 if not message:
2731 2732 editor = getcommiteditor(edit=True, editform=editform)
2732 2733 message = old.description()
2733 2734
2734 2735 pureextra = extra.copy()
2735 2736 extra['amend_source'] = old.hex()
2736 2737
2737 2738 new = context.memctx(repo,
2738 2739 parents=[base.node(), old.p2().node()],
2739 2740 text=message,
2740 2741 files=files,
2741 2742 filectxfn=filectxfn,
2742 2743 user=user,
2743 2744 date=date,
2744 2745 extra=extra,
2745 2746 editor=editor)
2746 2747
2747 2748 newdesc = changelog.stripdesc(new.description())
2748 2749 if ((not node)
2749 2750 and newdesc == old.description()
2750 2751 and user == old.user()
2751 2752 and date == old.date()
2752 2753 and pureextra == old.extra()):
2753 2754 # nothing changed. continuing here would create a new node
2754 2755 # anyway because of the amend_source noise.
2755 2756 #
2756 2757 # This not what we expect from amend.
2757 2758 return old.node()
2758 2759
2759 2760 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2760 2761 try:
2761 2762 if opts.get('secret'):
2762 2763 commitphase = 'secret'
2763 2764 else:
2764 2765 commitphase = old.phase()
2765 2766 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2766 2767 newid = repo.commitctx(new)
2767 2768 finally:
2768 2769 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2769 2770 if newid != old.node():
2770 2771 # Reroute the working copy parent to the new changeset
2771 2772 repo.setparents(newid, nullid)
2772 2773
2773 2774 # Move bookmarks from old parent to amend commit
2774 2775 bms = repo.nodebookmarks(old.node())
2775 2776 if bms:
2776 2777 marks = repo._bookmarks
2777 2778 for bm in bms:
2778 2779 ui.debug('moving bookmarks %r from %s to %s\n' %
2779 2780 (marks, old.hex(), hex(newid)))
2780 2781 marks[bm] = newid
2781 2782 marks.recordchange(tr)
2782 2783 #commit the whole amend process
2783 2784 if createmarkers:
2784 2785 # mark the new changeset as successor of the rewritten one
2785 2786 new = repo[newid]
2786 2787 obs = [(old, (new,))]
2787 2788 if node:
2788 2789 obs.append((ctx, ()))
2789 2790
2790 2791 obsolete.createmarkers(repo, obs)
2791 2792 if not createmarkers and newid != old.node():
2792 2793 # Strip the intermediate commit (if there was one) and the amended
2793 2794 # commit
2794 2795 if node:
2795 2796 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2796 2797 ui.note(_('stripping amended changeset %s\n') % old)
2797 2798 repair.strip(ui, repo, old.node(), topic='amend-backup')
2798 2799 finally:
2799 2800 lockmod.release(lock, wlock)
2800 2801 return newid
2801 2802
2802 2803 def commiteditor(repo, ctx, subs, editform=''):
2803 2804 if ctx.description():
2804 2805 return ctx.description()
2805 2806 return commitforceeditor(repo, ctx, subs, editform=editform,
2806 2807 unchangedmessagedetection=True)
2807 2808
2808 2809 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2809 2810 editform='', unchangedmessagedetection=False):
2810 2811 if not extramsg:
2811 2812 extramsg = _("Leave message empty to abort commit.")
2812 2813
2813 2814 forms = [e for e in editform.split('.') if e]
2814 2815 forms.insert(0, 'changeset')
2815 2816 templatetext = None
2816 2817 while forms:
2817 2818 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2818 2819 if tmpl:
2819 2820 templatetext = committext = buildcommittemplate(
2820 2821 repo, ctx, subs, extramsg, tmpl)
2821 2822 break
2822 2823 forms.pop()
2823 2824 else:
2824 2825 committext = buildcommittext(repo, ctx, subs, extramsg)
2825 2826
2826 2827 # run editor in the repository root
2827 2828 olddir = os.getcwd()
2828 2829 os.chdir(repo.root)
2829 2830
2830 2831 # make in-memory changes visible to external process
2831 2832 tr = repo.currenttransaction()
2832 2833 repo.dirstate.write(tr)
2833 2834 pending = tr and tr.writepending() and repo.root
2834 2835
2835 2836 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2836 2837 editform=editform, pending=pending)
2837 2838 text = re.sub("(?m)^HG:.*(\n|$)", "", editortext)
2838 2839 os.chdir(olddir)
2839 2840
2840 2841 if finishdesc:
2841 2842 text = finishdesc(text)
2842 2843 if not text.strip():
2843 2844 raise error.Abort(_("empty commit message"))
2844 2845 if unchangedmessagedetection and editortext == templatetext:
2845 2846 raise error.Abort(_("commit message unchanged"))
2846 2847
2847 2848 return text
2848 2849
2849 2850 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2850 2851 ui = repo.ui
2851 2852 tmpl, mapfile = gettemplate(ui, tmpl, None)
2852 2853
2853 2854 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2854 2855
2855 2856 for k, v in repo.ui.configitems('committemplate'):
2856 2857 if k != 'changeset':
2857 2858 t.t.cache[k] = v
2858 2859
2859 2860 if not extramsg:
2860 2861 extramsg = '' # ensure that extramsg is string
2861 2862
2862 2863 ui.pushbuffer()
2863 2864 t.show(ctx, extramsg=extramsg)
2864 2865 return ui.popbuffer()
2865 2866
2866 2867 def hgprefix(msg):
2867 2868 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2868 2869
2869 2870 def buildcommittext(repo, ctx, subs, extramsg):
2870 2871 edittext = []
2871 2872 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2872 2873 if ctx.description():
2873 2874 edittext.append(ctx.description())
2874 2875 edittext.append("")
2875 2876 edittext.append("") # Empty line between message and comments.
2876 2877 edittext.append(hgprefix(_("Enter commit message."
2877 2878 " Lines beginning with 'HG:' are removed.")))
2878 2879 edittext.append(hgprefix(extramsg))
2879 2880 edittext.append("HG: --")
2880 2881 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2881 2882 if ctx.p2():
2882 2883 edittext.append(hgprefix(_("branch merge")))
2883 2884 if ctx.branch():
2884 2885 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2885 2886 if bookmarks.isactivewdirparent(repo):
2886 2887 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2887 2888 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2888 2889 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2889 2890 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2890 2891 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2891 2892 if not added and not modified and not removed:
2892 2893 edittext.append(hgprefix(_("no files changed")))
2893 2894 edittext.append("")
2894 2895
2895 2896 return "\n".join(edittext)
2896 2897
2897 2898 def commitstatus(repo, node, branch, bheads=None, opts=None):
2898 2899 if opts is None:
2899 2900 opts = {}
2900 2901 ctx = repo[node]
2901 2902 parents = ctx.parents()
2902 2903
2903 2904 if (not opts.get('amend') and bheads and node not in bheads and not
2904 2905 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2905 2906 repo.ui.status(_('created new head\n'))
2906 2907 # The message is not printed for initial roots. For the other
2907 2908 # changesets, it is printed in the following situations:
2908 2909 #
2909 2910 # Par column: for the 2 parents with ...
2910 2911 # N: null or no parent
2911 2912 # B: parent is on another named branch
2912 2913 # C: parent is a regular non head changeset
2913 2914 # H: parent was a branch head of the current branch
2914 2915 # Msg column: whether we print "created new head" message
2915 2916 # In the following, it is assumed that there already exists some
2916 2917 # initial branch heads of the current branch, otherwise nothing is
2917 2918 # printed anyway.
2918 2919 #
2919 2920 # Par Msg Comment
2920 2921 # N N y additional topo root
2921 2922 #
2922 2923 # B N y additional branch root
2923 2924 # C N y additional topo head
2924 2925 # H N n usual case
2925 2926 #
2926 2927 # B B y weird additional branch root
2927 2928 # C B y branch merge
2928 2929 # H B n merge with named branch
2929 2930 #
2930 2931 # C C y additional head from merge
2931 2932 # C H n merge with a head
2932 2933 #
2933 2934 # H H n head merge: head count decreases
2934 2935
2935 2936 if not opts.get('close_branch'):
2936 2937 for r in parents:
2937 2938 if r.closesbranch() and r.branch() == branch:
2938 2939 repo.ui.status(_('reopening closed branch head %d\n') % r)
2939 2940
2940 2941 if repo.ui.debugflag:
2941 2942 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2942 2943 elif repo.ui.verbose:
2943 2944 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2944 2945
2945 2946 def postcommitstatus(repo, pats, opts):
2946 2947 return repo.status(match=scmutil.match(repo[None], pats, opts))
2947 2948
2948 2949 def revert(ui, repo, ctx, parents, *pats, **opts):
2949 2950 parent, p2 = parents
2950 2951 node = ctx.node()
2951 2952
2952 2953 mf = ctx.manifest()
2953 2954 if node == p2:
2954 2955 parent = p2
2955 2956
2956 2957 # need all matching names in dirstate and manifest of target rev,
2957 2958 # so have to walk both. do not print errors if files exist in one
2958 2959 # but not other. in both cases, filesets should be evaluated against
2959 2960 # workingctx to get consistent result (issue4497). this means 'set:**'
2960 2961 # cannot be used to select missing files from target rev.
2961 2962
2962 2963 # `names` is a mapping for all elements in working copy and target revision
2963 2964 # The mapping is in the form:
2964 2965 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2965 2966 names = {}
2966 2967
2967 2968 with repo.wlock():
2968 2969 ## filling of the `names` mapping
2969 2970 # walk dirstate to fill `names`
2970 2971
2971 2972 interactive = opts.get('interactive', False)
2972 2973 wctx = repo[None]
2973 2974 m = scmutil.match(wctx, pats, opts)
2974 2975
2975 2976 # we'll need this later
2976 2977 targetsubs = sorted(s for s in wctx.substate if m(s))
2977 2978
2978 2979 if not m.always():
2979 2980 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2980 2981 names[abs] = m.rel(abs), m.exact(abs)
2981 2982
2982 2983 # walk target manifest to fill `names`
2983 2984
2984 2985 def badfn(path, msg):
2985 2986 if path in names:
2986 2987 return
2987 2988 if path in ctx.substate:
2988 2989 return
2989 2990 path_ = path + '/'
2990 2991 for f in names:
2991 2992 if f.startswith(path_):
2992 2993 return
2993 2994 ui.warn("%s: %s\n" % (m.rel(path), msg))
2994 2995
2995 2996 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2996 2997 if abs not in names:
2997 2998 names[abs] = m.rel(abs), m.exact(abs)
2998 2999
2999 3000 # Find status of all file in `names`.
3000 3001 m = scmutil.matchfiles(repo, names)
3001 3002
3002 3003 changes = repo.status(node1=node, match=m,
3003 3004 unknown=True, ignored=True, clean=True)
3004 3005 else:
3005 3006 changes = repo.status(node1=node, match=m)
3006 3007 for kind in changes:
3007 3008 for abs in kind:
3008 3009 names[abs] = m.rel(abs), m.exact(abs)
3009 3010
3010 3011 m = scmutil.matchfiles(repo, names)
3011 3012
3012 3013 modified = set(changes.modified)
3013 3014 added = set(changes.added)
3014 3015 removed = set(changes.removed)
3015 3016 _deleted = set(changes.deleted)
3016 3017 unknown = set(changes.unknown)
3017 3018 unknown.update(changes.ignored)
3018 3019 clean = set(changes.clean)
3019 3020 modadded = set()
3020 3021
3021 3022 # split between files known in target manifest and the others
3022 3023 smf = set(mf)
3023 3024
3024 3025 # determine the exact nature of the deleted changesets
3025 3026 deladded = _deleted - smf
3026 3027 deleted = _deleted - deladded
3027 3028
3028 3029 # We need to account for the state of the file in the dirstate,
3029 3030 # even when we revert against something else than parent. This will
3030 3031 # slightly alter the behavior of revert (doing back up or not, delete
3031 3032 # or just forget etc).
3032 3033 if parent == node:
3033 3034 dsmodified = modified
3034 3035 dsadded = added
3035 3036 dsremoved = removed
3036 3037 # store all local modifications, useful later for rename detection
3037 3038 localchanges = dsmodified | dsadded
3038 3039 modified, added, removed = set(), set(), set()
3039 3040 else:
3040 3041 changes = repo.status(node1=parent, match=m)
3041 3042 dsmodified = set(changes.modified)
3042 3043 dsadded = set(changes.added)
3043 3044 dsremoved = set(changes.removed)
3044 3045 # store all local modifications, useful later for rename detection
3045 3046 localchanges = dsmodified | dsadded
3046 3047
3047 3048 # only take into account for removes between wc and target
3048 3049 clean |= dsremoved - removed
3049 3050 dsremoved &= removed
3050 3051 # distinct between dirstate remove and other
3051 3052 removed -= dsremoved
3052 3053
3053 3054 modadded = added & dsmodified
3054 3055 added -= modadded
3055 3056
3056 3057 # tell newly modified apart.
3057 3058 dsmodified &= modified
3058 3059 dsmodified |= modified & dsadded # dirstate added may need backup
3059 3060 modified -= dsmodified
3060 3061
3061 3062 # We need to wait for some post-processing to update this set
3062 3063 # before making the distinction. The dirstate will be used for
3063 3064 # that purpose.
3064 3065 dsadded = added
3065 3066
3066 3067 # in case of merge, files that are actually added can be reported as
3067 3068 # modified, we need to post process the result
3068 3069 if p2 != nullid:
3069 3070 mergeadd = dsmodified - smf
3070 3071 dsadded |= mergeadd
3071 3072 dsmodified -= mergeadd
3072 3073
3073 3074 # if f is a rename, update `names` to also revert the source
3074 3075 cwd = repo.getcwd()
3075 3076 for f in localchanges:
3076 3077 src = repo.dirstate.copied(f)
3077 3078 # XXX should we check for rename down to target node?
3078 3079 if src and src not in names and repo.dirstate[src] == 'r':
3079 3080 dsremoved.add(src)
3080 3081 names[src] = (repo.pathto(src, cwd), True)
3081 3082
3082 3083 # distinguish between file to forget and the other
3083 3084 added = set()
3084 3085 for abs in dsadded:
3085 3086 if repo.dirstate[abs] != 'a':
3086 3087 added.add(abs)
3087 3088 dsadded -= added
3088 3089
3089 3090 for abs in deladded:
3090 3091 if repo.dirstate[abs] == 'a':
3091 3092 dsadded.add(abs)
3092 3093 deladded -= dsadded
3093 3094
3094 3095 # For files marked as removed, we check if an unknown file is present at
3095 3096 # the same path. If a such file exists it may need to be backed up.
3096 3097 # Making the distinction at this stage helps have simpler backup
3097 3098 # logic.
3098 3099 removunk = set()
3099 3100 for abs in removed:
3100 3101 target = repo.wjoin(abs)
3101 3102 if os.path.lexists(target):
3102 3103 removunk.add(abs)
3103 3104 removed -= removunk
3104 3105
3105 3106 dsremovunk = set()
3106 3107 for abs in dsremoved:
3107 3108 target = repo.wjoin(abs)
3108 3109 if os.path.lexists(target):
3109 3110 dsremovunk.add(abs)
3110 3111 dsremoved -= dsremovunk
3111 3112
3112 3113 # action to be actually performed by revert
3113 3114 # (<list of file>, message>) tuple
3114 3115 actions = {'revert': ([], _('reverting %s\n')),
3115 3116 'add': ([], _('adding %s\n')),
3116 3117 'remove': ([], _('removing %s\n')),
3117 3118 'drop': ([], _('removing %s\n')),
3118 3119 'forget': ([], _('forgetting %s\n')),
3119 3120 'undelete': ([], _('undeleting %s\n')),
3120 3121 'noop': (None, _('no changes needed to %s\n')),
3121 3122 'unknown': (None, _('file not managed: %s\n')),
3122 3123 }
3123 3124
3124 3125 # "constant" that convey the backup strategy.
3125 3126 # All set to `discard` if `no-backup` is set do avoid checking
3126 3127 # no_backup lower in the code.
3127 3128 # These values are ordered for comparison purposes
3128 3129 backupinteractive = 3 # do backup if interactively modified
3129 3130 backup = 2 # unconditionally do backup
3130 3131 check = 1 # check if the existing file differs from target
3131 3132 discard = 0 # never do backup
3132 3133 if opts.get('no_backup'):
3133 3134 backupinteractive = backup = check = discard
3134 3135 if interactive:
3135 3136 dsmodifiedbackup = backupinteractive
3136 3137 else:
3137 3138 dsmodifiedbackup = backup
3138 3139 tobackup = set()
3139 3140
3140 3141 backupanddel = actions['remove']
3141 3142 if not opts.get('no_backup'):
3142 3143 backupanddel = actions['drop']
3143 3144
3144 3145 disptable = (
3145 3146 # dispatch table:
3146 3147 # file state
3147 3148 # action
3148 3149 # make backup
3149 3150
3150 3151 ## Sets that results that will change file on disk
3151 3152 # Modified compared to target, no local change
3152 3153 (modified, actions['revert'], discard),
3153 3154 # Modified compared to target, but local file is deleted
3154 3155 (deleted, actions['revert'], discard),
3155 3156 # Modified compared to target, local change
3156 3157 (dsmodified, actions['revert'], dsmodifiedbackup),
3157 3158 # Added since target
3158 3159 (added, actions['remove'], discard),
3159 3160 # Added in working directory
3160 3161 (dsadded, actions['forget'], discard),
3161 3162 # Added since target, have local modification
3162 3163 (modadded, backupanddel, backup),
3163 3164 # Added since target but file is missing in working directory
3164 3165 (deladded, actions['drop'], discard),
3165 3166 # Removed since target, before working copy parent
3166 3167 (removed, actions['add'], discard),
3167 3168 # Same as `removed` but an unknown file exists at the same path
3168 3169 (removunk, actions['add'], check),
3169 3170 # Removed since targe, marked as such in working copy parent
3170 3171 (dsremoved, actions['undelete'], discard),
3171 3172 # Same as `dsremoved` but an unknown file exists at the same path
3172 3173 (dsremovunk, actions['undelete'], check),
3173 3174 ## the following sets does not result in any file changes
3174 3175 # File with no modification
3175 3176 (clean, actions['noop'], discard),
3176 3177 # Existing file, not tracked anywhere
3177 3178 (unknown, actions['unknown'], discard),
3178 3179 )
3179 3180
3180 3181 for abs, (rel, exact) in sorted(names.items()):
3181 3182 # target file to be touch on disk (relative to cwd)
3182 3183 target = repo.wjoin(abs)
3183 3184 # search the entry in the dispatch table.
3184 3185 # if the file is in any of these sets, it was touched in the working
3185 3186 # directory parent and we are sure it needs to be reverted.
3186 3187 for table, (xlist, msg), dobackup in disptable:
3187 3188 if abs not in table:
3188 3189 continue
3189 3190 if xlist is not None:
3190 3191 xlist.append(abs)
3191 3192 if dobackup:
3192 3193 # If in interactive mode, don't automatically create
3193 3194 # .orig files (issue4793)
3194 3195 if dobackup == backupinteractive:
3195 3196 tobackup.add(abs)
3196 3197 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3197 3198 bakname = scmutil.origpath(ui, repo, rel)
3198 3199 ui.note(_('saving current version of %s as %s\n') %
3199 3200 (rel, bakname))
3200 3201 if not opts.get('dry_run'):
3201 3202 if interactive:
3202 3203 util.copyfile(target, bakname)
3203 3204 else:
3204 3205 util.rename(target, bakname)
3205 3206 if ui.verbose or not exact:
3206 3207 if not isinstance(msg, basestring):
3207 3208 msg = msg(abs)
3208 3209 ui.status(msg % rel)
3209 3210 elif exact:
3210 3211 ui.warn(msg % rel)
3211 3212 break
3212 3213
3213 3214 if not opts.get('dry_run'):
3214 3215 needdata = ('revert', 'add', 'undelete')
3215 3216 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3216 3217 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3217 3218
3218 3219 if targetsubs:
3219 3220 # Revert the subrepos on the revert list
3220 3221 for sub in targetsubs:
3221 3222 try:
3222 3223 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3223 3224 except KeyError:
3224 3225 raise error.Abort("subrepository '%s' does not exist in %s!"
3225 3226 % (sub, short(ctx.node())))
3226 3227
3227 3228 def _revertprefetch(repo, ctx, *files):
3228 3229 """Let extension changing the storage layer prefetch content"""
3229 3230 pass
3230 3231
3231 3232 def _performrevert(repo, parents, ctx, actions, interactive=False,
3232 3233 tobackup=None):
3233 3234 """function that actually perform all the actions computed for revert
3234 3235
3235 3236 This is an independent function to let extension to plug in and react to
3236 3237 the imminent revert.
3237 3238
3238 3239 Make sure you have the working directory locked when calling this function.
3239 3240 """
3240 3241 parent, p2 = parents
3241 3242 node = ctx.node()
3242 3243 excluded_files = []
3243 3244 matcher_opts = {"exclude": excluded_files}
3244 3245
3245 3246 def checkout(f):
3246 3247 fc = ctx[f]
3247 3248 repo.wwrite(f, fc.data(), fc.flags())
3248 3249
3249 3250 audit_path = pathutil.pathauditor(repo.root)
3250 3251 for f in actions['forget'][0]:
3251 3252 if interactive:
3252 3253 choice = \
3253 3254 repo.ui.promptchoice(
3254 3255 _("forget added file %s (yn)?$$ &Yes $$ &No")
3255 3256 % f)
3256 3257 if choice == 0:
3257 3258 repo.dirstate.drop(f)
3258 3259 else:
3259 3260 excluded_files.append(repo.wjoin(f))
3260 3261 else:
3261 3262 repo.dirstate.drop(f)
3262 3263 for f in actions['remove'][0]:
3263 3264 audit_path(f)
3264 3265 try:
3265 3266 util.unlinkpath(repo.wjoin(f))
3266 3267 except OSError:
3267 3268 pass
3268 3269 repo.dirstate.remove(f)
3269 3270 for f in actions['drop'][0]:
3270 3271 audit_path(f)
3271 3272 repo.dirstate.remove(f)
3272 3273
3273 3274 normal = None
3274 3275 if node == parent:
3275 3276 # We're reverting to our parent. If possible, we'd like status
3276 3277 # to report the file as clean. We have to use normallookup for
3277 3278 # merges to avoid losing information about merged/dirty files.
3278 3279 if p2 != nullid:
3279 3280 normal = repo.dirstate.normallookup
3280 3281 else:
3281 3282 normal = repo.dirstate.normal
3282 3283
3283 3284 newlyaddedandmodifiedfiles = set()
3284 3285 if interactive:
3285 3286 # Prompt the user for changes to revert
3286 3287 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3287 3288 m = scmutil.match(ctx, torevert, matcher_opts)
3288 3289 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3289 3290 diffopts.nodates = True
3290 3291 diffopts.git = True
3291 3292 reversehunks = repo.ui.configbool('experimental',
3292 3293 'revertalternateinteractivemode',
3293 3294 True)
3294 3295 if reversehunks:
3295 3296 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3296 3297 else:
3297 3298 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3298 3299 originalchunks = patch.parsepatch(diff)
3299 3300 operation = 'discard' if node == parent else 'revert'
3300 3301
3301 3302 try:
3302 3303
3303 3304 chunks, opts = recordfilter(repo.ui, originalchunks,
3304 3305 operation=operation)
3305 3306 if reversehunks:
3306 3307 chunks = patch.reversehunks(chunks)
3307 3308
3308 3309 except patch.PatchError as err:
3309 3310 raise error.Abort(_('error parsing patch: %s') % err)
3310 3311
3311 3312 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3312 3313 if tobackup is None:
3313 3314 tobackup = set()
3314 3315 # Apply changes
3315 3316 fp = stringio()
3316 3317 for c in chunks:
3317 3318 # Create a backup file only if this hunk should be backed up
3318 3319 if ishunk(c) and c.header.filename() in tobackup:
3319 3320 abs = c.header.filename()
3320 3321 target = repo.wjoin(abs)
3321 3322 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3322 3323 util.copyfile(target, bakname)
3323 3324 tobackup.remove(abs)
3324 3325 c.write(fp)
3325 3326 dopatch = fp.tell()
3326 3327 fp.seek(0)
3327 3328 if dopatch:
3328 3329 try:
3329 3330 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3330 3331 except patch.PatchError as err:
3331 3332 raise error.Abort(str(err))
3332 3333 del fp
3333 3334 else:
3334 3335 for f in actions['revert'][0]:
3335 3336 checkout(f)
3336 3337 if normal:
3337 3338 normal(f)
3338 3339
3339 3340 for f in actions['add'][0]:
3340 3341 # Don't checkout modified files, they are already created by the diff
3341 3342 if f not in newlyaddedandmodifiedfiles:
3342 3343 checkout(f)
3343 3344 repo.dirstate.add(f)
3344 3345
3345 3346 normal = repo.dirstate.normallookup
3346 3347 if node == parent and p2 == nullid:
3347 3348 normal = repo.dirstate.normal
3348 3349 for f in actions['undelete'][0]:
3349 3350 checkout(f)
3350 3351 normal(f)
3351 3352
3352 3353 copied = copies.pathcopies(repo[parent], ctx)
3353 3354
3354 3355 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3355 3356 if f in copied:
3356 3357 repo.dirstate.copy(copied[f], f)
3357 3358
3358 3359 def command(table):
3359 3360 """Returns a function object to be used as a decorator for making commands.
3360 3361
3361 3362 This function receives a command table as its argument. The table should
3362 3363 be a dict.
3363 3364
3364 3365 The returned function can be used as a decorator for adding commands
3365 3366 to that command table. This function accepts multiple arguments to define
3366 3367 a command.
3367 3368
3368 3369 The first argument is the command name.
3369 3370
3370 3371 The options argument is an iterable of tuples defining command arguments.
3371 3372 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3372 3373
3373 3374 The synopsis argument defines a short, one line summary of how to use the
3374 3375 command. This shows up in the help output.
3375 3376
3376 3377 The norepo argument defines whether the command does not require a
3377 3378 local repository. Most commands operate against a repository, thus the
3378 3379 default is False.
3379 3380
3380 3381 The optionalrepo argument defines whether the command optionally requires
3381 3382 a local repository.
3382 3383
3383 3384 The inferrepo argument defines whether to try to find a repository from the
3384 3385 command line arguments. If True, arguments will be examined for potential
3385 3386 repository locations. See ``findrepo()``. If a repository is found, it
3386 3387 will be used.
3387 3388 """
3388 3389 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3389 3390 inferrepo=False):
3390 3391 def decorator(func):
3391 3392 func.norepo = norepo
3392 3393 func.optionalrepo = optionalrepo
3393 3394 func.inferrepo = inferrepo
3394 3395 if synopsis:
3395 3396 table[name] = func, list(options), synopsis
3396 3397 else:
3397 3398 table[name] = func, list(options)
3398 3399 return func
3399 3400 return decorator
3400 3401
3401 3402 return cmd
3402 3403
3403 3404 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3404 3405 # commands.outgoing. "missing" is "missing" of the result of
3405 3406 # "findcommonoutgoing()"
3406 3407 outgoinghooks = util.hooks()
3407 3408
3408 3409 # a list of (ui, repo) functions called by commands.summary
3409 3410 summaryhooks = util.hooks()
3410 3411
3411 3412 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3412 3413 #
3413 3414 # functions should return tuple of booleans below, if 'changes' is None:
3414 3415 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3415 3416 #
3416 3417 # otherwise, 'changes' is a tuple of tuples below:
3417 3418 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3418 3419 # - (desturl, destbranch, destpeer, outgoing)
3419 3420 summaryremotehooks = util.hooks()
3420 3421
3421 3422 # A list of state files kept by multistep operations like graft.
3422 3423 # Since graft cannot be aborted, it is considered 'clearable' by update.
3423 3424 # note: bisect is intentionally excluded
3424 3425 # (state file, clearable, allowcommit, error, hint)
3425 3426 unfinishedstates = [
3426 3427 ('graftstate', True, False, _('graft in progress'),
3427 3428 _("use 'hg graft --continue' or 'hg update' to abort")),
3428 3429 ('updatestate', True, False, _('last update was interrupted'),
3429 3430 _("use 'hg update' to get a consistent checkout"))
3430 3431 ]
3431 3432
3432 3433 def checkunfinished(repo, commit=False):
3433 3434 '''Look for an unfinished multistep operation, like graft, and abort
3434 3435 if found. It's probably good to check this right before
3435 3436 bailifchanged().
3436 3437 '''
3437 3438 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3438 3439 if commit and allowcommit:
3439 3440 continue
3440 3441 if repo.vfs.exists(f):
3441 3442 raise error.Abort(msg, hint=hint)
3442 3443
3443 3444 def clearunfinished(repo):
3444 3445 '''Check for unfinished operations (as above), and clear the ones
3445 3446 that are clearable.
3446 3447 '''
3447 3448 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3448 3449 if not clearable and repo.vfs.exists(f):
3449 3450 raise error.Abort(msg, hint=hint)
3450 3451 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3451 3452 if clearable and repo.vfs.exists(f):
3452 3453 util.unlink(repo.join(f))
3453 3454
3454 3455 afterresolvedstates = [
3455 3456 ('graftstate',
3456 3457 _('hg graft --continue')),
3457 3458 ]
3458 3459
3459 3460 def howtocontinue(repo):
3460 3461 '''Check for an unfinished operation and return the command to finish
3461 3462 it.
3462 3463
3463 3464 afterresolvedstates tupples define a .hg/{file} and the corresponding
3464 3465 command needed to finish it.
3465 3466
3466 3467 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3467 3468 a boolean.
3468 3469 '''
3469 3470 contmsg = _("continue: %s")
3470 3471 for f, msg in afterresolvedstates:
3471 3472 if repo.vfs.exists(f):
3472 3473 return contmsg % msg, True
3473 3474 workingctx = repo[None]
3474 3475 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3475 3476 for s in workingctx.substate)
3476 3477 if dirty:
3477 3478 return contmsg % _("hg commit"), False
3478 3479 return None, None
3479 3480
3480 3481 def checkafterresolved(repo):
3481 3482 '''Inform the user about the next action after completing hg resolve
3482 3483
3483 3484 If there's a matching afterresolvedstates, howtocontinue will yield
3484 3485 repo.ui.warn as the reporter.
3485 3486
3486 3487 Otherwise, it will yield repo.ui.note.
3487 3488 '''
3488 3489 msg, warning = howtocontinue(repo)
3489 3490 if msg is not None:
3490 3491 if warning:
3491 3492 repo.ui.warn("%s\n" % msg)
3492 3493 else:
3493 3494 repo.ui.note("%s\n" % msg)
3494 3495
3495 3496 def wrongtooltocontinue(repo, task):
3496 3497 '''Raise an abort suggesting how to properly continue if there is an
3497 3498 active task.
3498 3499
3499 3500 Uses howtocontinue() to find the active task.
3500 3501
3501 3502 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3502 3503 a hint.
3503 3504 '''
3504 3505 after = howtocontinue(repo)
3505 3506 hint = None
3506 3507 if after[1]:
3507 3508 hint = after[0]
3508 3509 raise error.Abort(_('no %s in progress') % task, hint=hint)
3509 3510
3510 3511 class dirstateguard(object):
3511 3512 '''Restore dirstate at unexpected failure.
3512 3513
3513 3514 At the construction, this class does:
3514 3515
3515 3516 - write current ``repo.dirstate`` out, and
3516 3517 - save ``.hg/dirstate`` into the backup file
3517 3518
3518 3519 This restores ``.hg/dirstate`` from backup file, if ``release()``
3519 3520 is invoked before ``close()``.
3520 3521
3521 3522 This just removes the backup file at ``close()`` before ``release()``.
3522 3523 '''
3523 3524
3524 3525 def __init__(self, repo, name):
3525 3526 self._repo = repo
3526 3527 self._suffix = '.backup.%s.%d' % (name, id(self))
3527 3528 repo.dirstate.savebackup(repo.currenttransaction(), self._suffix)
3528 3529 self._active = True
3529 3530 self._closed = False
3530 3531
3531 3532 def __del__(self):
3532 3533 if self._active: # still active
3533 3534 # this may occur, even if this class is used correctly:
3534 3535 # for example, releasing other resources like transaction
3535 3536 # may raise exception before ``dirstateguard.release`` in
3536 3537 # ``release(tr, ....)``.
3537 3538 self._abort()
3538 3539
3539 3540 def close(self):
3540 3541 if not self._active: # already inactivated
3541 3542 msg = (_("can't close already inactivated backup: dirstate%s")
3542 3543 % self._suffix)
3543 3544 raise error.Abort(msg)
3544 3545
3545 3546 self._repo.dirstate.clearbackup(self._repo.currenttransaction(),
3546 3547 self._suffix)
3547 3548 self._active = False
3548 3549 self._closed = True
3549 3550
3550 3551 def _abort(self):
3551 3552 self._repo.dirstate.restorebackup(self._repo.currenttransaction(),
3552 3553 self._suffix)
3553 3554 self._active = False
3554 3555
3555 3556 def release(self):
3556 3557 if not self._closed:
3557 3558 if not self._active: # already inactivated
3558 3559 msg = (_("can't release already inactivated backup:"
3559 3560 " dirstate%s")
3560 3561 % self._suffix)
3561 3562 raise error.Abort(msg)
3562 3563 self._abort()
@@ -1,7279 +1,7282 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import difflib
11 11 import errno
12 12 import operator
13 13 import os
14 14 import random
15 15 import re
16 16 import shlex
17 17 import socket
18 18 import sys
19 19 import tempfile
20 20 import time
21 21
22 22 from .i18n import _
23 23 from .node import (
24 24 bin,
25 25 hex,
26 26 nullhex,
27 27 nullid,
28 28 nullrev,
29 29 short,
30 30 )
31 31 from . import (
32 32 archival,
33 33 bookmarks,
34 34 bundle2,
35 35 changegroup,
36 36 cmdutil,
37 37 commandserver,
38 38 context,
39 39 copies,
40 40 dagparser,
41 41 dagutil,
42 42 destutil,
43 43 discovery,
44 44 encoding,
45 45 error,
46 46 exchange,
47 47 extensions,
48 48 fileset,
49 49 formatter,
50 50 graphmod,
51 51 hbisect,
52 52 help,
53 53 hg,
54 54 hgweb,
55 55 localrepo,
56 56 lock as lockmod,
57 57 merge as mergemod,
58 58 minirst,
59 59 obsolete,
60 60 patch,
61 61 phases,
62 62 policy,
63 63 pvec,
64 64 repair,
65 65 revlog,
66 66 revset,
67 67 scmutil,
68 68 setdiscovery,
69 69 simplemerge,
70 70 sshserver,
71 71 streamclone,
72 72 templatekw,
73 73 templater,
74 74 treediscovery,
75 75 ui as uimod,
76 76 util,
77 77 )
78 78
79 79 release = lockmod.release
80 80
81 81 table = {}
82 82
83 83 command = cmdutil.command(table)
84 84
85 85 # label constants
86 86 # until 3.5, bookmarks.current was the advertised name, not
87 87 # bookmarks.active, so we must use both to avoid breaking old
88 88 # custom styles
89 89 activebookmarklabel = 'bookmarks.active bookmarks.current'
90 90
91 91 # common command options
92 92
93 93 globalopts = [
94 94 ('R', 'repository', '',
95 95 _('repository root directory or name of overlay bundle file'),
96 96 _('REPO')),
97 97 ('', 'cwd', '',
98 98 _('change working directory'), _('DIR')),
99 99 ('y', 'noninteractive', None,
100 100 _('do not prompt, automatically pick the first choice for all prompts')),
101 101 ('q', 'quiet', None, _('suppress output')),
102 102 ('v', 'verbose', None, _('enable additional output')),
103 103 ('', 'config', [],
104 104 _('set/override config option (use \'section.name=value\')'),
105 105 _('CONFIG')),
106 106 ('', 'debug', None, _('enable debugging output')),
107 107 ('', 'debugger', None, _('start debugger')),
108 108 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
109 109 _('ENCODE')),
110 110 ('', 'encodingmode', encoding.encodingmode,
111 111 _('set the charset encoding mode'), _('MODE')),
112 112 ('', 'traceback', None, _('always print a traceback on exception')),
113 113 ('', 'time', None, _('time how long the command takes')),
114 114 ('', 'profile', None, _('print command execution profile')),
115 115 ('', 'version', None, _('output version information and exit')),
116 116 ('h', 'help', None, _('display help and exit')),
117 117 ('', 'hidden', False, _('consider hidden changesets')),
118 118 ]
119 119
120 120 dryrunopts = [('n', 'dry-run', None,
121 121 _('do not perform actions, just print output'))]
122 122
123 123 remoteopts = [
124 124 ('e', 'ssh', '',
125 125 _('specify ssh command to use'), _('CMD')),
126 126 ('', 'remotecmd', '',
127 127 _('specify hg command to run on the remote side'), _('CMD')),
128 128 ('', 'insecure', None,
129 129 _('do not verify server certificate (ignoring web.cacerts config)')),
130 130 ]
131 131
132 132 walkopts = [
133 133 ('I', 'include', [],
134 134 _('include names matching the given patterns'), _('PATTERN')),
135 135 ('X', 'exclude', [],
136 136 _('exclude names matching the given patterns'), _('PATTERN')),
137 137 ]
138 138
139 139 commitopts = [
140 140 ('m', 'message', '',
141 141 _('use text as commit message'), _('TEXT')),
142 142 ('l', 'logfile', '',
143 143 _('read commit message from file'), _('FILE')),
144 144 ]
145 145
146 146 commitopts2 = [
147 147 ('d', 'date', '',
148 148 _('record the specified date as commit date'), _('DATE')),
149 149 ('u', 'user', '',
150 150 _('record the specified user as committer'), _('USER')),
151 151 ]
152 152
153 153 # hidden for now
154 154 formatteropts = [
155 155 ('T', 'template', '',
156 156 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
157 157 ]
158 158
159 159 templateopts = [
160 160 ('', 'style', '',
161 161 _('display using template map file (DEPRECATED)'), _('STYLE')),
162 162 ('T', 'template', '',
163 163 _('display with template'), _('TEMPLATE')),
164 164 ]
165 165
166 166 logopts = [
167 167 ('p', 'patch', None, _('show patch')),
168 168 ('g', 'git', None, _('use git extended diff format')),
169 169 ('l', 'limit', '',
170 170 _('limit number of changes displayed'), _('NUM')),
171 171 ('M', 'no-merges', None, _('do not show merges')),
172 172 ('', 'stat', None, _('output diffstat-style summary of changes')),
173 173 ('G', 'graph', None, _("show the revision DAG")),
174 174 ] + templateopts
175 175
176 176 diffopts = [
177 177 ('a', 'text', None, _('treat all files as text')),
178 178 ('g', 'git', None, _('use git extended diff format')),
179 179 ('', 'nodates', None, _('omit dates from diff headers'))
180 180 ]
181 181
182 182 diffwsopts = [
183 183 ('w', 'ignore-all-space', None,
184 184 _('ignore white space when comparing lines')),
185 185 ('b', 'ignore-space-change', None,
186 186 _('ignore changes in the amount of white space')),
187 187 ('B', 'ignore-blank-lines', None,
188 188 _('ignore changes whose lines are all blank')),
189 189 ]
190 190
191 191 diffopts2 = [
192 192 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
193 193 ('p', 'show-function', None, _('show which function each change is in')),
194 194 ('', 'reverse', None, _('produce a diff that undoes the changes')),
195 195 ] + diffwsopts + [
196 196 ('U', 'unified', '',
197 197 _('number of lines of context to show'), _('NUM')),
198 198 ('', 'stat', None, _('output diffstat-style summary of changes')),
199 199 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
200 200 ]
201 201
202 202 mergetoolopts = [
203 203 ('t', 'tool', '', _('specify merge tool')),
204 204 ]
205 205
206 206 similarityopts = [
207 207 ('s', 'similarity', '',
208 208 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
209 209 ]
210 210
211 211 subrepoopts = [
212 212 ('S', 'subrepos', None,
213 213 _('recurse into subrepositories'))
214 214 ]
215 215
216 216 debugrevlogopts = [
217 217 ('c', 'changelog', False, _('open changelog')),
218 218 ('m', 'manifest', False, _('open manifest')),
219 219 ('', 'dir', '', _('open directory manifest')),
220 220 ]
221 221
222 222 # Commands start here, listed alphabetically
223 223
224 224 @command('^add',
225 225 walkopts + subrepoopts + dryrunopts,
226 226 _('[OPTION]... [FILE]...'),
227 227 inferrepo=True)
228 228 def add(ui, repo, *pats, **opts):
229 229 """add the specified files on the next commit
230 230
231 231 Schedule files to be version controlled and added to the
232 232 repository.
233 233
234 234 The files will be added to the repository at the next commit. To
235 235 undo an add before that, see :hg:`forget`.
236 236
237 237 If no names are given, add all files to the repository (except
238 238 files matching ``.hgignore``).
239 239
240 240 .. container:: verbose
241 241
242 242 Examples:
243 243
244 244 - New (unknown) files are added
245 245 automatically by :hg:`add`::
246 246
247 247 $ ls
248 248 foo.c
249 249 $ hg status
250 250 ? foo.c
251 251 $ hg add
252 252 adding foo.c
253 253 $ hg status
254 254 A foo.c
255 255
256 256 - Specific files to be added can be specified::
257 257
258 258 $ ls
259 259 bar.c foo.c
260 260 $ hg status
261 261 ? bar.c
262 262 ? foo.c
263 263 $ hg add bar.c
264 264 $ hg status
265 265 A bar.c
266 266 ? foo.c
267 267
268 268 Returns 0 if all files are successfully added.
269 269 """
270 270
271 271 m = scmutil.match(repo[None], pats, opts)
272 272 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
273 273 return rejected and 1 or 0
274 274
275 275 @command('addremove',
276 276 similarityopts + subrepoopts + walkopts + dryrunopts,
277 277 _('[OPTION]... [FILE]...'),
278 278 inferrepo=True)
279 279 def addremove(ui, repo, *pats, **opts):
280 280 """add all new files, delete all missing files
281 281
282 282 Add all new files and remove all missing files from the
283 283 repository.
284 284
285 285 Unless names are given, new files are ignored if they match any of
286 286 the patterns in ``.hgignore``. As with add, these changes take
287 287 effect at the next commit.
288 288
289 289 Use the -s/--similarity option to detect renamed files. This
290 290 option takes a percentage between 0 (disabled) and 100 (files must
291 291 be identical) as its parameter. With a parameter greater than 0,
292 292 this compares every removed file with every added file and records
293 293 those similar enough as renames. Detecting renamed files this way
294 294 can be expensive. After using this option, :hg:`status -C` can be
295 295 used to check which files were identified as moved or renamed. If
296 296 not specified, -s/--similarity defaults to 100 and only renames of
297 297 identical files are detected.
298 298
299 299 .. container:: verbose
300 300
301 301 Examples:
302 302
303 303 - A number of files (bar.c and foo.c) are new,
304 304 while foobar.c has been removed (without using :hg:`remove`)
305 305 from the repository::
306 306
307 307 $ ls
308 308 bar.c foo.c
309 309 $ hg status
310 310 ! foobar.c
311 311 ? bar.c
312 312 ? foo.c
313 313 $ hg addremove
314 314 adding bar.c
315 315 adding foo.c
316 316 removing foobar.c
317 317 $ hg status
318 318 A bar.c
319 319 A foo.c
320 320 R foobar.c
321 321
322 322 - A file foobar.c was moved to foo.c without using :hg:`rename`.
323 323 Afterwards, it was edited slightly::
324 324
325 325 $ ls
326 326 foo.c
327 327 $ hg status
328 328 ! foobar.c
329 329 ? foo.c
330 330 $ hg addremove --similarity 90
331 331 removing foobar.c
332 332 adding foo.c
333 333 recording removal of foobar.c as rename to foo.c (94% similar)
334 334 $ hg status -C
335 335 A foo.c
336 336 foobar.c
337 337 R foobar.c
338 338
339 339 Returns 0 if all files are successfully added.
340 340 """
341 341 try:
342 342 sim = float(opts.get('similarity') or 100)
343 343 except ValueError:
344 344 raise error.Abort(_('similarity must be a number'))
345 345 if sim < 0 or sim > 100:
346 346 raise error.Abort(_('similarity must be between 0 and 100'))
347 347 matcher = scmutil.match(repo[None], pats, opts)
348 348 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
349 349
350 350 @command('^annotate|blame',
351 351 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
352 352 ('', 'follow', None,
353 353 _('follow copies/renames and list the filename (DEPRECATED)')),
354 354 ('', 'no-follow', None, _("don't follow copies and renames")),
355 355 ('a', 'text', None, _('treat all files as text')),
356 356 ('u', 'user', None, _('list the author (long with -v)')),
357 357 ('f', 'file', None, _('list the filename')),
358 358 ('d', 'date', None, _('list the date (short with -q)')),
359 359 ('n', 'number', None, _('list the revision number (default)')),
360 360 ('c', 'changeset', None, _('list the changeset')),
361 361 ('l', 'line-number', None, _('show line number at the first appearance'))
362 362 ] + diffwsopts + walkopts + formatteropts,
363 363 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
364 364 inferrepo=True)
365 365 def annotate(ui, repo, *pats, **opts):
366 366 """show changeset information by line for each file
367 367
368 368 List changes in files, showing the revision id responsible for
369 369 each line.
370 370
371 371 This command is useful for discovering when a change was made and
372 372 by whom.
373 373
374 374 If you include --file, --user, or --date, the revision number is
375 375 suppressed unless you also include --number.
376 376
377 377 Without the -a/--text option, annotate will avoid processing files
378 378 it detects as binary. With -a, annotate will annotate the file
379 379 anyway, although the results will probably be neither useful
380 380 nor desirable.
381 381
382 382 Returns 0 on success.
383 383 """
384 384 if not pats:
385 385 raise error.Abort(_('at least one filename or pattern is required'))
386 386
387 387 if opts.get('follow'):
388 388 # --follow is deprecated and now just an alias for -f/--file
389 389 # to mimic the behavior of Mercurial before version 1.5
390 390 opts['file'] = True
391 391
392 392 ctx = scmutil.revsingle(repo, opts.get('rev'))
393 393
394 394 fm = ui.formatter('annotate', opts)
395 395 if ui.quiet:
396 396 datefunc = util.shortdate
397 397 else:
398 398 datefunc = util.datestr
399 399 if ctx.rev() is None:
400 400 def hexfn(node):
401 401 if node is None:
402 402 return None
403 403 else:
404 404 return fm.hexfunc(node)
405 405 if opts.get('changeset'):
406 406 # omit "+" suffix which is appended to node hex
407 407 def formatrev(rev):
408 408 if rev is None:
409 409 return '%d' % ctx.p1().rev()
410 410 else:
411 411 return '%d' % rev
412 412 else:
413 413 def formatrev(rev):
414 414 if rev is None:
415 415 return '%d+' % ctx.p1().rev()
416 416 else:
417 417 return '%d ' % rev
418 418 def formathex(hex):
419 419 if hex is None:
420 420 return '%s+' % fm.hexfunc(ctx.p1().node())
421 421 else:
422 422 return '%s ' % hex
423 423 else:
424 424 hexfn = fm.hexfunc
425 425 formatrev = formathex = str
426 426
427 427 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
428 428 ('number', ' ', lambda x: x[0].rev(), formatrev),
429 429 ('changeset', ' ', lambda x: hexfn(x[0].node()), formathex),
430 430 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
431 431 ('file', ' ', lambda x: x[0].path(), str),
432 432 ('line_number', ':', lambda x: x[1], str),
433 433 ]
434 434 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
435 435
436 436 if (not opts.get('user') and not opts.get('changeset')
437 437 and not opts.get('date') and not opts.get('file')):
438 438 opts['number'] = True
439 439
440 440 linenumber = opts.get('line_number') is not None
441 441 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
442 442 raise error.Abort(_('at least one of -n/-c is required for -l'))
443 443
444 444 if fm:
445 445 def makefunc(get, fmt):
446 446 return get
447 447 else:
448 448 def makefunc(get, fmt):
449 449 return lambda x: fmt(get(x))
450 450 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
451 451 if opts.get(op)]
452 452 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
453 453 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
454 454 if opts.get(op))
455 455
456 456 def bad(x, y):
457 457 raise error.Abort("%s: %s" % (x, y))
458 458
459 459 m = scmutil.match(ctx, pats, opts, badfn=bad)
460 460
461 461 follow = not opts.get('no_follow')
462 462 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
463 463 whitespace=True)
464 464 for abs in ctx.walk(m):
465 465 fctx = ctx[abs]
466 466 if not opts.get('text') and util.binary(fctx.data()):
467 467 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
468 468 continue
469 469
470 470 lines = fctx.annotate(follow=follow, linenumber=linenumber,
471 471 diffopts=diffopts)
472 472 if not lines:
473 473 continue
474 474 formats = []
475 475 pieces = []
476 476
477 477 for f, sep in funcmap:
478 478 l = [f(n) for n, dummy in lines]
479 479 if fm:
480 480 formats.append(['%s' for x in l])
481 481 else:
482 482 sizes = [encoding.colwidth(x) for x in l]
483 483 ml = max(sizes)
484 484 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
485 485 pieces.append(l)
486 486
487 487 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
488 488 fm.startitem()
489 489 fm.write(fields, "".join(f), *p)
490 490 fm.write('line', ": %s", l[1])
491 491
492 492 if not lines[-1][1].endswith('\n'):
493 493 fm.plain('\n')
494 494
495 495 fm.end()
496 496
497 497 @command('archive',
498 498 [('', 'no-decode', None, _('do not pass files through decoders')),
499 499 ('p', 'prefix', '', _('directory prefix for files in archive'),
500 500 _('PREFIX')),
501 501 ('r', 'rev', '', _('revision to distribute'), _('REV')),
502 502 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
503 503 ] + subrepoopts + walkopts,
504 504 _('[OPTION]... DEST'))
505 505 def archive(ui, repo, dest, **opts):
506 506 '''create an unversioned archive of a repository revision
507 507
508 508 By default, the revision used is the parent of the working
509 509 directory; use -r/--rev to specify a different revision.
510 510
511 511 The archive type is automatically detected based on file
512 512 extension (to override, use -t/--type).
513 513
514 514 .. container:: verbose
515 515
516 516 Examples:
517 517
518 518 - create a zip file containing the 1.0 release::
519 519
520 520 hg archive -r 1.0 project-1.0.zip
521 521
522 522 - create a tarball excluding .hg files::
523 523
524 524 hg archive project.tar.gz -X ".hg*"
525 525
526 526 Valid types are:
527 527
528 528 :``files``: a directory full of files (default)
529 529 :``tar``: tar archive, uncompressed
530 530 :``tbz2``: tar archive, compressed using bzip2
531 531 :``tgz``: tar archive, compressed using gzip
532 532 :``uzip``: zip archive, uncompressed
533 533 :``zip``: zip archive, compressed using deflate
534 534
535 535 The exact name of the destination archive or directory is given
536 536 using a format string; see :hg:`help export` for details.
537 537
538 538 Each member added to an archive file has a directory prefix
539 539 prepended. Use -p/--prefix to specify a format string for the
540 540 prefix. The default is the basename of the archive, with suffixes
541 541 removed.
542 542
543 543 Returns 0 on success.
544 544 '''
545 545
546 546 ctx = scmutil.revsingle(repo, opts.get('rev'))
547 547 if not ctx:
548 548 raise error.Abort(_('no working directory: please specify a revision'))
549 549 node = ctx.node()
550 550 dest = cmdutil.makefilename(repo, dest, node)
551 551 if os.path.realpath(dest) == repo.root:
552 552 raise error.Abort(_('repository root cannot be destination'))
553 553
554 554 kind = opts.get('type') or archival.guesskind(dest) or 'files'
555 555 prefix = opts.get('prefix')
556 556
557 557 if dest == '-':
558 558 if kind == 'files':
559 559 raise error.Abort(_('cannot archive plain files to stdout'))
560 560 dest = cmdutil.makefileobj(repo, dest)
561 561 if not prefix:
562 562 prefix = os.path.basename(repo.root) + '-%h'
563 563
564 564 prefix = cmdutil.makefilename(repo, prefix, node)
565 565 matchfn = scmutil.match(ctx, [], opts)
566 566 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
567 567 matchfn, prefix, subrepos=opts.get('subrepos'))
568 568
569 569 @command('backout',
570 570 [('', 'merge', None, _('merge with old dirstate parent after backout')),
571 571 ('', 'commit', None,
572 572 _('commit if no conflicts were encountered (DEPRECATED)')),
573 573 ('', 'no-commit', None, _('do not commit')),
574 574 ('', 'parent', '',
575 575 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
576 576 ('r', 'rev', '', _('revision to backout'), _('REV')),
577 577 ('e', 'edit', False, _('invoke editor on commit messages')),
578 578 ] + mergetoolopts + walkopts + commitopts + commitopts2,
579 579 _('[OPTION]... [-r] REV'))
580 580 def backout(ui, repo, node=None, rev=None, **opts):
581 581 '''reverse effect of earlier changeset
582 582
583 583 Prepare a new changeset with the effect of REV undone in the
584 584 current working directory. If no conflicts were encountered,
585 585 it will be committed immediately.
586 586
587 587 If REV is the parent of the working directory, then this new changeset
588 588 is committed automatically (unless --no-commit is specified).
589 589
590 590 .. note::
591 591
592 592 :hg:`backout` cannot be used to fix either an unwanted or
593 593 incorrect merge.
594 594
595 595 .. container:: verbose
596 596
597 597 Examples:
598 598
599 599 - Reverse the effect of the parent of the working directory.
600 600 This backout will be committed immediately::
601 601
602 602 hg backout -r .
603 603
604 604 - Reverse the effect of previous bad revision 23::
605 605
606 606 hg backout -r 23
607 607
608 608 - Reverse the effect of previous bad revision 23 and
609 609 leave changes uncommitted::
610 610
611 611 hg backout -r 23 --no-commit
612 612 hg commit -m "Backout revision 23"
613 613
614 614 By default, the pending changeset will have one parent,
615 615 maintaining a linear history. With --merge, the pending
616 616 changeset will instead have two parents: the old parent of the
617 617 working directory and a new child of REV that simply undoes REV.
618 618
619 619 Before version 1.7, the behavior without --merge was equivalent
620 620 to specifying --merge followed by :hg:`update --clean .` to
621 621 cancel the merge and leave the child of REV as a head to be
622 622 merged separately.
623 623
624 624 See :hg:`help dates` for a list of formats valid for -d/--date.
625 625
626 626 See :hg:`help revert` for a way to restore files to the state
627 627 of another revision.
628 628
629 629 Returns 0 on success, 1 if nothing to backout or there are unresolved
630 630 files.
631 631 '''
632 632 wlock = lock = None
633 633 try:
634 634 wlock = repo.wlock()
635 635 lock = repo.lock()
636 636 return _dobackout(ui, repo, node, rev, **opts)
637 637 finally:
638 638 release(lock, wlock)
639 639
640 640 def _dobackout(ui, repo, node=None, rev=None, **opts):
641 641 if opts.get('commit') and opts.get('no_commit'):
642 642 raise error.Abort(_("cannot use --commit with --no-commit"))
643 643 if opts.get('merge') and opts.get('no_commit'):
644 644 raise error.Abort(_("cannot use --merge with --no-commit"))
645 645
646 646 if rev and node:
647 647 raise error.Abort(_("please specify just one revision"))
648 648
649 649 if not rev:
650 650 rev = node
651 651
652 652 if not rev:
653 653 raise error.Abort(_("please specify a revision to backout"))
654 654
655 655 date = opts.get('date')
656 656 if date:
657 657 opts['date'] = util.parsedate(date)
658 658
659 659 cmdutil.checkunfinished(repo)
660 660 cmdutil.bailifchanged(repo)
661 661 node = scmutil.revsingle(repo, rev).node()
662 662
663 663 op1, op2 = repo.dirstate.parents()
664 664 if not repo.changelog.isancestor(node, op1):
665 665 raise error.Abort(_('cannot backout change that is not an ancestor'))
666 666
667 667 p1, p2 = repo.changelog.parents(node)
668 668 if p1 == nullid:
669 669 raise error.Abort(_('cannot backout a change with no parents'))
670 670 if p2 != nullid:
671 671 if not opts.get('parent'):
672 672 raise error.Abort(_('cannot backout a merge changeset'))
673 673 p = repo.lookup(opts['parent'])
674 674 if p not in (p1, p2):
675 675 raise error.Abort(_('%s is not a parent of %s') %
676 676 (short(p), short(node)))
677 677 parent = p
678 678 else:
679 679 if opts.get('parent'):
680 680 raise error.Abort(_('cannot use --parent on non-merge changeset'))
681 681 parent = p1
682 682
683 683 # the backout should appear on the same branch
684 684 branch = repo.dirstate.branch()
685 685 bheads = repo.branchheads(branch)
686 686 rctx = scmutil.revsingle(repo, hex(parent))
687 687 if not opts.get('merge') and op1 != node:
688 688 dsguard = cmdutil.dirstateguard(repo, 'backout')
689 689 try:
690 690 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
691 691 'backout')
692 692 stats = mergemod.update(repo, parent, True, True, node, False)
693 693 repo.setparents(op1, op2)
694 694 dsguard.close()
695 695 hg._showstats(repo, stats)
696 696 if stats[3]:
697 697 repo.ui.status(_("use 'hg resolve' to retry unresolved "
698 698 "file merges\n"))
699 699 return 1
700 700 finally:
701 701 ui.setconfig('ui', 'forcemerge', '', '')
702 702 lockmod.release(dsguard)
703 703 else:
704 704 hg.clean(repo, node, show_stats=False)
705 705 repo.dirstate.setbranch(branch)
706 706 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
707 707
708 708 if opts.get('no_commit'):
709 709 msg = _("changeset %s backed out, "
710 710 "don't forget to commit.\n")
711 711 ui.status(msg % short(node))
712 712 return 0
713 713
714 714 def commitfunc(ui, repo, message, match, opts):
715 715 editform = 'backout'
716 716 e = cmdutil.getcommiteditor(editform=editform, **opts)
717 717 if not message:
718 718 # we don't translate commit messages
719 719 message = "Backed out changeset %s" % short(node)
720 720 e = cmdutil.getcommiteditor(edit=True, editform=editform)
721 721 return repo.commit(message, opts.get('user'), opts.get('date'),
722 722 match, editor=e)
723 723 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
724 724 if not newnode:
725 725 ui.status(_("nothing changed\n"))
726 726 return 1
727 727 cmdutil.commitstatus(repo, newnode, branch, bheads)
728 728
729 729 def nice(node):
730 730 return '%d:%s' % (repo.changelog.rev(node), short(node))
731 731 ui.status(_('changeset %s backs out changeset %s\n') %
732 732 (nice(repo.changelog.tip()), nice(node)))
733 733 if opts.get('merge') and op1 != node:
734 734 hg.clean(repo, op1, show_stats=False)
735 735 ui.status(_('merging with changeset %s\n')
736 736 % nice(repo.changelog.tip()))
737 737 try:
738 738 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
739 739 'backout')
740 740 return hg.merge(repo, hex(repo.changelog.tip()))
741 741 finally:
742 742 ui.setconfig('ui', 'forcemerge', '', '')
743 743 return 0
744 744
745 745 @command('bisect',
746 746 [('r', 'reset', False, _('reset bisect state')),
747 747 ('g', 'good', False, _('mark changeset good')),
748 748 ('b', 'bad', False, _('mark changeset bad')),
749 749 ('s', 'skip', False, _('skip testing changeset')),
750 750 ('e', 'extend', False, _('extend the bisect range')),
751 751 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
752 752 ('U', 'noupdate', False, _('do not update to target'))],
753 753 _("[-gbsr] [-U] [-c CMD] [REV]"))
754 754 def bisect(ui, repo, rev=None, extra=None, command=None,
755 755 reset=None, good=None, bad=None, skip=None, extend=None,
756 756 noupdate=None):
757 757 """subdivision search of changesets
758 758
759 759 This command helps to find changesets which introduce problems. To
760 760 use, mark the earliest changeset you know exhibits the problem as
761 761 bad, then mark the latest changeset which is free from the problem
762 762 as good. Bisect will update your working directory to a revision
763 763 for testing (unless the -U/--noupdate option is specified). Once
764 764 you have performed tests, mark the working directory as good or
765 765 bad, and bisect will either update to another candidate changeset
766 766 or announce that it has found the bad revision.
767 767
768 768 As a shortcut, you can also use the revision argument to mark a
769 769 revision as good or bad without checking it out first.
770 770
771 771 If you supply a command, it will be used for automatic bisection.
772 772 The environment variable HG_NODE will contain the ID of the
773 773 changeset being tested. The exit status of the command will be
774 774 used to mark revisions as good or bad: status 0 means good, 125
775 775 means to skip the revision, 127 (command not found) will abort the
776 776 bisection, and any other non-zero exit status means the revision
777 777 is bad.
778 778
779 779 .. container:: verbose
780 780
781 781 Some examples:
782 782
783 783 - start a bisection with known bad revision 34, and good revision 12::
784 784
785 785 hg bisect --bad 34
786 786 hg bisect --good 12
787 787
788 788 - advance the current bisection by marking current revision as good or
789 789 bad::
790 790
791 791 hg bisect --good
792 792 hg bisect --bad
793 793
794 794 - mark the current revision, or a known revision, to be skipped (e.g. if
795 795 that revision is not usable because of another issue)::
796 796
797 797 hg bisect --skip
798 798 hg bisect --skip 23
799 799
800 800 - skip all revisions that do not touch directories ``foo`` or ``bar``::
801 801
802 802 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
803 803
804 804 - forget the current bisection::
805 805
806 806 hg bisect --reset
807 807
808 808 - use 'make && make tests' to automatically find the first broken
809 809 revision::
810 810
811 811 hg bisect --reset
812 812 hg bisect --bad 34
813 813 hg bisect --good 12
814 814 hg bisect --command "make && make tests"
815 815
816 816 - see all changesets whose states are already known in the current
817 817 bisection::
818 818
819 819 hg log -r "bisect(pruned)"
820 820
821 821 - see the changeset currently being bisected (especially useful
822 822 if running with -U/--noupdate)::
823 823
824 824 hg log -r "bisect(current)"
825 825
826 826 - see all changesets that took part in the current bisection::
827 827
828 828 hg log -r "bisect(range)"
829 829
830 830 - you can even get a nice graph::
831 831
832 832 hg log --graph -r "bisect(range)"
833 833
834 834 See :hg:`help revsets` for more about the `bisect()` keyword.
835 835
836 836 Returns 0 on success.
837 837 """
838 838 def extendbisectrange(nodes, good):
839 839 # bisect is incomplete when it ends on a merge node and
840 840 # one of the parent was not checked.
841 841 parents = repo[nodes[0]].parents()
842 842 if len(parents) > 1:
843 843 if good:
844 844 side = state['bad']
845 845 else:
846 846 side = state['good']
847 847 num = len(set(i.node() for i in parents) & set(side))
848 848 if num == 1:
849 849 return parents[0].ancestor(parents[1])
850 850 return None
851 851
852 852 def print_result(nodes, good):
853 853 displayer = cmdutil.show_changeset(ui, repo, {})
854 854 if len(nodes) == 1:
855 855 # narrowed it down to a single revision
856 856 if good:
857 857 ui.write(_("The first good revision is:\n"))
858 858 else:
859 859 ui.write(_("The first bad revision is:\n"))
860 860 displayer.show(repo[nodes[0]])
861 861 extendnode = extendbisectrange(nodes, good)
862 862 if extendnode is not None:
863 863 ui.write(_('Not all ancestors of this changeset have been'
864 864 ' checked.\nUse bisect --extend to continue the '
865 865 'bisection from\nthe common ancestor, %s.\n')
866 866 % extendnode)
867 867 else:
868 868 # multiple possible revisions
869 869 if good:
870 870 ui.write(_("Due to skipped revisions, the first "
871 871 "good revision could be any of:\n"))
872 872 else:
873 873 ui.write(_("Due to skipped revisions, the first "
874 874 "bad revision could be any of:\n"))
875 875 for n in nodes:
876 876 displayer.show(repo[n])
877 877 displayer.close()
878 878
879 879 def check_state(state, interactive=True):
880 880 if not state['good'] or not state['bad']:
881 881 if (good or bad or skip or reset) and interactive:
882 882 return
883 883 if not state['good']:
884 884 raise error.Abort(_('cannot bisect (no known good revisions)'))
885 885 else:
886 886 raise error.Abort(_('cannot bisect (no known bad revisions)'))
887 887 return True
888 888
889 889 # backward compatibility
890 890 if rev in "good bad reset init".split():
891 891 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
892 892 cmd, rev, extra = rev, extra, None
893 893 if cmd == "good":
894 894 good = True
895 895 elif cmd == "bad":
896 896 bad = True
897 897 else:
898 898 reset = True
899 899 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
900 900 raise error.Abort(_('incompatible arguments'))
901 901
902 902 cmdutil.checkunfinished(repo)
903 903
904 904 if reset:
905 905 p = repo.join("bisect.state")
906 906 if os.path.exists(p):
907 907 os.unlink(p)
908 908 return
909 909
910 910 state = hbisect.load_state(repo)
911 911
912 912 if command:
913 913 changesets = 1
914 914 if noupdate:
915 915 try:
916 916 node = state['current'][0]
917 917 except LookupError:
918 918 raise error.Abort(_('current bisect revision is unknown - '
919 919 'start a new bisect to fix'))
920 920 else:
921 921 node, p2 = repo.dirstate.parents()
922 922 if p2 != nullid:
923 923 raise error.Abort(_('current bisect revision is a merge'))
924 924 try:
925 925 while changesets:
926 926 # update state
927 927 state['current'] = [node]
928 928 hbisect.save_state(repo, state)
929 929 status = ui.system(command, environ={'HG_NODE': hex(node)})
930 930 if status == 125:
931 931 transition = "skip"
932 932 elif status == 0:
933 933 transition = "good"
934 934 # status < 0 means process was killed
935 935 elif status == 127:
936 936 raise error.Abort(_("failed to execute %s") % command)
937 937 elif status < 0:
938 938 raise error.Abort(_("%s killed") % command)
939 939 else:
940 940 transition = "bad"
941 941 ctx = scmutil.revsingle(repo, rev, node)
942 942 rev = None # clear for future iterations
943 943 state[transition].append(ctx.node())
944 944 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
945 945 check_state(state, interactive=False)
946 946 # bisect
947 947 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
948 948 # update to next check
949 949 node = nodes[0]
950 950 if not noupdate:
951 951 cmdutil.bailifchanged(repo)
952 952 hg.clean(repo, node, show_stats=False)
953 953 finally:
954 954 state['current'] = [node]
955 955 hbisect.save_state(repo, state)
956 956 print_result(nodes, bgood)
957 957 return
958 958
959 959 # update state
960 960
961 961 if rev:
962 962 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
963 963 else:
964 964 nodes = [repo.lookup('.')]
965 965
966 966 if good or bad or skip:
967 967 if good:
968 968 state['good'] += nodes
969 969 elif bad:
970 970 state['bad'] += nodes
971 971 elif skip:
972 972 state['skip'] += nodes
973 973 hbisect.save_state(repo, state)
974 974
975 975 if not check_state(state):
976 976 return
977 977
978 978 # actually bisect
979 979 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
980 980 if extend:
981 981 if not changesets:
982 982 extendnode = extendbisectrange(nodes, good)
983 983 if extendnode is not None:
984 984 ui.write(_("Extending search to changeset %d:%s\n")
985 985 % (extendnode.rev(), extendnode))
986 986 state['current'] = [extendnode.node()]
987 987 hbisect.save_state(repo, state)
988 988 if noupdate:
989 989 return
990 990 cmdutil.bailifchanged(repo)
991 991 return hg.clean(repo, extendnode.node())
992 992 raise error.Abort(_("nothing to extend"))
993 993
994 994 if changesets == 0:
995 995 print_result(nodes, good)
996 996 else:
997 997 assert len(nodes) == 1 # only a single node can be tested next
998 998 node = nodes[0]
999 999 # compute the approximate number of remaining tests
1000 1000 tests, size = 0, 2
1001 1001 while size <= changesets:
1002 1002 tests, size = tests + 1, size * 2
1003 1003 rev = repo.changelog.rev(node)
1004 1004 ui.write(_("Testing changeset %d:%s "
1005 1005 "(%d changesets remaining, ~%d tests)\n")
1006 1006 % (rev, short(node), changesets, tests))
1007 1007 state['current'] = [node]
1008 1008 hbisect.save_state(repo, state)
1009 1009 if not noupdate:
1010 1010 cmdutil.bailifchanged(repo)
1011 1011 return hg.clean(repo, node)
1012 1012
1013 1013 @command('bookmarks|bookmark',
1014 1014 [('f', 'force', False, _('force')),
1015 1015 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
1016 1016 ('d', 'delete', False, _('delete a given bookmark')),
1017 1017 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
1018 1018 ('i', 'inactive', False, _('mark a bookmark inactive')),
1019 1019 ] + formatteropts,
1020 1020 _('hg bookmarks [OPTIONS]... [NAME]...'))
1021 1021 def bookmark(ui, repo, *names, **opts):
1022 1022 '''create a new bookmark or list existing bookmarks
1023 1023
1024 1024 Bookmarks are labels on changesets to help track lines of development.
1025 1025 Bookmarks are unversioned and can be moved, renamed and deleted.
1026 1026 Deleting or moving a bookmark has no effect on the associated changesets.
1027 1027
1028 1028 Creating or updating to a bookmark causes it to be marked as 'active'.
1029 1029 The active bookmark is indicated with a '*'.
1030 1030 When a commit is made, the active bookmark will advance to the new commit.
1031 1031 A plain :hg:`update` will also advance an active bookmark, if possible.
1032 1032 Updating away from a bookmark will cause it to be deactivated.
1033 1033
1034 1034 Bookmarks can be pushed and pulled between repositories (see
1035 1035 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1036 1036 diverged, a new 'divergent bookmark' of the form 'name@path' will
1037 1037 be created. Using :hg:`merge` will resolve the divergence.
1038 1038
1039 1039 A bookmark named '@' has the special property that :hg:`clone` will
1040 1040 check it out by default if it exists.
1041 1041
1042 1042 .. container:: verbose
1043 1043
1044 1044 Examples:
1045 1045
1046 1046 - create an active bookmark for a new line of development::
1047 1047
1048 1048 hg book new-feature
1049 1049
1050 1050 - create an inactive bookmark as a place marker::
1051 1051
1052 1052 hg book -i reviewed
1053 1053
1054 1054 - create an inactive bookmark on another changeset::
1055 1055
1056 1056 hg book -r .^ tested
1057 1057
1058 1058 - rename bookmark turkey to dinner::
1059 1059
1060 1060 hg book -m turkey dinner
1061 1061
1062 1062 - move the '@' bookmark from another branch::
1063 1063
1064 1064 hg book -f @
1065 1065 '''
1066 1066 force = opts.get('force')
1067 1067 rev = opts.get('rev')
1068 1068 delete = opts.get('delete')
1069 1069 rename = opts.get('rename')
1070 1070 inactive = opts.get('inactive')
1071 1071
1072 1072 def checkformat(mark):
1073 1073 mark = mark.strip()
1074 1074 if not mark:
1075 1075 raise error.Abort(_("bookmark names cannot consist entirely of "
1076 1076 "whitespace"))
1077 1077 scmutil.checknewlabel(repo, mark, 'bookmark')
1078 1078 return mark
1079 1079
1080 1080 def checkconflict(repo, mark, cur, force=False, target=None):
1081 1081 if mark in marks and not force:
1082 1082 if target:
1083 1083 if marks[mark] == target and target == cur:
1084 1084 # re-activating a bookmark
1085 1085 return
1086 1086 anc = repo.changelog.ancestors([repo[target].rev()])
1087 1087 bmctx = repo[marks[mark]]
1088 1088 divs = [repo[b].node() for b in marks
1089 1089 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
1090 1090
1091 1091 # allow resolving a single divergent bookmark even if moving
1092 1092 # the bookmark across branches when a revision is specified
1093 1093 # that contains a divergent bookmark
1094 1094 if bmctx.rev() not in anc and target in divs:
1095 1095 bookmarks.deletedivergent(repo, [target], mark)
1096 1096 return
1097 1097
1098 1098 deletefrom = [b for b in divs
1099 1099 if repo[b].rev() in anc or b == target]
1100 1100 bookmarks.deletedivergent(repo, deletefrom, mark)
1101 1101 if bookmarks.validdest(repo, bmctx, repo[target]):
1102 1102 ui.status(_("moving bookmark '%s' forward from %s\n") %
1103 1103 (mark, short(bmctx.node())))
1104 1104 return
1105 1105 raise error.Abort(_("bookmark '%s' already exists "
1106 1106 "(use -f to force)") % mark)
1107 1107 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
1108 1108 and not force):
1109 1109 raise error.Abort(
1110 1110 _("a bookmark cannot have the name of an existing branch"))
1111 1111
1112 1112 if delete and rename:
1113 1113 raise error.Abort(_("--delete and --rename are incompatible"))
1114 1114 if delete and rev:
1115 1115 raise error.Abort(_("--rev is incompatible with --delete"))
1116 1116 if rename and rev:
1117 1117 raise error.Abort(_("--rev is incompatible with --rename"))
1118 1118 if not names and (delete or rev):
1119 1119 raise error.Abort(_("bookmark name required"))
1120 1120
1121 1121 if delete or rename or names or inactive:
1122 1122 wlock = lock = tr = None
1123 1123 try:
1124 1124 wlock = repo.wlock()
1125 1125 lock = repo.lock()
1126 1126 cur = repo.changectx('.').node()
1127 1127 marks = repo._bookmarks
1128 1128 if delete:
1129 1129 tr = repo.transaction('bookmark')
1130 1130 for mark in names:
1131 1131 if mark not in marks:
1132 1132 raise error.Abort(_("bookmark '%s' does not exist") %
1133 1133 mark)
1134 1134 if mark == repo._activebookmark:
1135 1135 bookmarks.deactivate(repo)
1136 1136 del marks[mark]
1137 1137
1138 1138 elif rename:
1139 1139 tr = repo.transaction('bookmark')
1140 1140 if not names:
1141 1141 raise error.Abort(_("new bookmark name required"))
1142 1142 elif len(names) > 1:
1143 1143 raise error.Abort(_("only one new bookmark name allowed"))
1144 1144 mark = checkformat(names[0])
1145 1145 if rename not in marks:
1146 1146 raise error.Abort(_("bookmark '%s' does not exist")
1147 1147 % rename)
1148 1148 checkconflict(repo, mark, cur, force)
1149 1149 marks[mark] = marks[rename]
1150 1150 if repo._activebookmark == rename and not inactive:
1151 1151 bookmarks.activate(repo, mark)
1152 1152 del marks[rename]
1153 1153 elif names:
1154 1154 tr = repo.transaction('bookmark')
1155 1155 newact = None
1156 1156 for mark in names:
1157 1157 mark = checkformat(mark)
1158 1158 if newact is None:
1159 1159 newact = mark
1160 1160 if inactive and mark == repo._activebookmark:
1161 1161 bookmarks.deactivate(repo)
1162 1162 return
1163 1163 tgt = cur
1164 1164 if rev:
1165 1165 tgt = scmutil.revsingle(repo, rev).node()
1166 1166 checkconflict(repo, mark, cur, force, tgt)
1167 1167 marks[mark] = tgt
1168 1168 if not inactive and cur == marks[newact] and not rev:
1169 1169 bookmarks.activate(repo, newact)
1170 1170 elif cur != tgt and newact == repo._activebookmark:
1171 1171 bookmarks.deactivate(repo)
1172 1172 elif inactive:
1173 1173 if len(marks) == 0:
1174 1174 ui.status(_("no bookmarks set\n"))
1175 1175 elif not repo._activebookmark:
1176 1176 ui.status(_("no active bookmark\n"))
1177 1177 else:
1178 1178 bookmarks.deactivate(repo)
1179 1179 if tr is not None:
1180 1180 marks.recordchange(tr)
1181 1181 tr.close()
1182 1182 finally:
1183 1183 lockmod.release(tr, lock, wlock)
1184 1184 else: # show bookmarks
1185 1185 fm = ui.formatter('bookmarks', opts)
1186 1186 hexfn = fm.hexfunc
1187 1187 marks = repo._bookmarks
1188 1188 if len(marks) == 0 and not fm:
1189 1189 ui.status(_("no bookmarks set\n"))
1190 1190 for bmark, n in sorted(marks.iteritems()):
1191 1191 active = repo._activebookmark
1192 1192 if bmark == active:
1193 1193 prefix, label = '*', activebookmarklabel
1194 1194 else:
1195 1195 prefix, label = ' ', ''
1196 1196
1197 1197 fm.startitem()
1198 1198 if not ui.quiet:
1199 1199 fm.plain(' %s ' % prefix, label=label)
1200 1200 fm.write('bookmark', '%s', bmark, label=label)
1201 1201 pad = " " * (25 - encoding.colwidth(bmark))
1202 1202 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1203 1203 repo.changelog.rev(n), hexfn(n), label=label)
1204 1204 fm.data(active=(bmark == active))
1205 1205 fm.plain('\n')
1206 1206 fm.end()
1207 1207
1208 1208 @command('branch',
1209 1209 [('f', 'force', None,
1210 1210 _('set branch name even if it shadows an existing branch')),
1211 1211 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1212 1212 _('[-fC] [NAME]'))
1213 1213 def branch(ui, repo, label=None, **opts):
1214 1214 """set or show the current branch name
1215 1215
1216 1216 .. note::
1217 1217
1218 1218 Branch names are permanent and global. Use :hg:`bookmark` to create a
1219 1219 light-weight bookmark instead. See :hg:`help glossary` for more
1220 1220 information about named branches and bookmarks.
1221 1221
1222 1222 With no argument, show the current branch name. With one argument,
1223 1223 set the working directory branch name (the branch will not exist
1224 1224 in the repository until the next commit). Standard practice
1225 1225 recommends that primary development take place on the 'default'
1226 1226 branch.
1227 1227
1228 1228 Unless -f/--force is specified, branch will not let you set a
1229 1229 branch name that already exists.
1230 1230
1231 1231 Use -C/--clean to reset the working directory branch to that of
1232 1232 the parent of the working directory, negating a previous branch
1233 1233 change.
1234 1234
1235 1235 Use the command :hg:`update` to switch to an existing branch. Use
1236 1236 :hg:`commit --close-branch` to mark this branch head as closed.
1237 1237 When all heads of a branch are closed, the branch will be
1238 1238 considered closed.
1239 1239
1240 1240 Returns 0 on success.
1241 1241 """
1242 1242 if label:
1243 1243 label = label.strip()
1244 1244
1245 1245 if not opts.get('clean') and not label:
1246 1246 ui.write("%s\n" % repo.dirstate.branch())
1247 1247 return
1248 1248
1249 1249 with repo.wlock():
1250 1250 if opts.get('clean'):
1251 1251 label = repo[None].p1().branch()
1252 1252 repo.dirstate.setbranch(label)
1253 1253 ui.status(_('reset working directory to branch %s\n') % label)
1254 1254 elif label:
1255 1255 if not opts.get('force') and label in repo.branchmap():
1256 1256 if label not in [p.branch() for p in repo[None].parents()]:
1257 1257 raise error.Abort(_('a branch of the same name already'
1258 1258 ' exists'),
1259 1259 # i18n: "it" refers to an existing branch
1260 1260 hint=_("use 'hg update' to switch to it"))
1261 1261 scmutil.checknewlabel(repo, label, 'branch')
1262 1262 repo.dirstate.setbranch(label)
1263 1263 ui.status(_('marked working directory as branch %s\n') % label)
1264 1264
1265 1265 # find any open named branches aside from default
1266 1266 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1267 1267 if n != "default" and not c]
1268 1268 if not others:
1269 1269 ui.status(_('(branches are permanent and global, '
1270 1270 'did you want a bookmark?)\n'))
1271 1271
1272 1272 @command('branches',
1273 1273 [('a', 'active', False,
1274 1274 _('show only branches that have unmerged heads (DEPRECATED)')),
1275 1275 ('c', 'closed', False, _('show normal and closed branches')),
1276 1276 ] + formatteropts,
1277 1277 _('[-c]'))
1278 1278 def branches(ui, repo, active=False, closed=False, **opts):
1279 1279 """list repository named branches
1280 1280
1281 1281 List the repository's named branches, indicating which ones are
1282 1282 inactive. If -c/--closed is specified, also list branches which have
1283 1283 been marked closed (see :hg:`commit --close-branch`).
1284 1284
1285 1285 Use the command :hg:`update` to switch to an existing branch.
1286 1286
1287 1287 Returns 0.
1288 1288 """
1289 1289
1290 1290 fm = ui.formatter('branches', opts)
1291 1291 hexfunc = fm.hexfunc
1292 1292
1293 1293 allheads = set(repo.heads())
1294 1294 branches = []
1295 1295 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1296 1296 isactive = not isclosed and bool(set(heads) & allheads)
1297 1297 branches.append((tag, repo[tip], isactive, not isclosed))
1298 1298 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1299 1299 reverse=True)
1300 1300
1301 1301 for tag, ctx, isactive, isopen in branches:
1302 1302 if active and not isactive:
1303 1303 continue
1304 1304 if isactive:
1305 1305 label = 'branches.active'
1306 1306 notice = ''
1307 1307 elif not isopen:
1308 1308 if not closed:
1309 1309 continue
1310 1310 label = 'branches.closed'
1311 1311 notice = _(' (closed)')
1312 1312 else:
1313 1313 label = 'branches.inactive'
1314 1314 notice = _(' (inactive)')
1315 1315 current = (tag == repo.dirstate.branch())
1316 1316 if current:
1317 1317 label = 'branches.current'
1318 1318
1319 1319 fm.startitem()
1320 1320 fm.write('branch', '%s', tag, label=label)
1321 1321 rev = ctx.rev()
1322 1322 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1323 1323 fmt = ' ' * padsize + ' %d:%s'
1324 1324 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1325 1325 label='log.changeset changeset.%s' % ctx.phasestr())
1326 1326 fm.data(active=isactive, closed=not isopen, current=current)
1327 1327 if not ui.quiet:
1328 1328 fm.plain(notice)
1329 1329 fm.plain('\n')
1330 1330 fm.end()
1331 1331
1332 1332 @command('bundle',
1333 1333 [('f', 'force', None, _('run even when the destination is unrelated')),
1334 1334 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1335 1335 _('REV')),
1336 1336 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1337 1337 _('BRANCH')),
1338 1338 ('', 'base', [],
1339 1339 _('a base changeset assumed to be available at the destination'),
1340 1340 _('REV')),
1341 1341 ('a', 'all', None, _('bundle all changesets in the repository')),
1342 1342 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1343 1343 ] + remoteopts,
1344 1344 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1345 1345 def bundle(ui, repo, fname, dest=None, **opts):
1346 1346 """create a changegroup file
1347 1347
1348 1348 Generate a changegroup file collecting changesets to be added
1349 1349 to a repository.
1350 1350
1351 1351 To create a bundle containing all changesets, use -a/--all
1352 1352 (or --base null). Otherwise, hg assumes the destination will have
1353 1353 all the nodes you specify with --base parameters. Otherwise, hg
1354 1354 will assume the repository has all the nodes in destination, or
1355 1355 default-push/default if no destination is specified.
1356 1356
1357 1357 You can change bundle format with the -t/--type option. You can
1358 1358 specify a compression, a bundle version or both using a dash
1359 1359 (comp-version). The available compression methods are: none, bzip2,
1360 1360 and gzip (by default, bundles are compressed using bzip2). The
1361 1361 available formats are: v1, v2 (default to most suitable).
1362 1362
1363 1363 The bundle file can then be transferred using conventional means
1364 1364 and applied to another repository with the unbundle or pull
1365 1365 command. This is useful when direct push and pull are not
1366 1366 available or when exporting an entire repository is undesirable.
1367 1367
1368 1368 Applying bundles preserves all changeset contents including
1369 1369 permissions, copy/rename information, and revision history.
1370 1370
1371 1371 Returns 0 on success, 1 if no changes found.
1372 1372 """
1373 1373 revs = None
1374 1374 if 'rev' in opts:
1375 1375 revstrings = opts['rev']
1376 1376 revs = scmutil.revrange(repo, revstrings)
1377 1377 if revstrings and not revs:
1378 1378 raise error.Abort(_('no commits to bundle'))
1379 1379
1380 1380 bundletype = opts.get('type', 'bzip2').lower()
1381 1381 try:
1382 1382 bcompression, cgversion, params = exchange.parsebundlespec(
1383 1383 repo, bundletype, strict=False)
1384 1384 except error.UnsupportedBundleSpecification as e:
1385 1385 raise error.Abort(str(e),
1386 1386 hint=_('see "hg help bundle" for supported '
1387 1387 'values for --type'))
1388 1388
1389 1389 # Packed bundles are a pseudo bundle format for now.
1390 1390 if cgversion == 's1':
1391 1391 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1392 1392 hint=_("use 'hg debugcreatestreamclonebundle'"))
1393 1393
1394 1394 if opts.get('all'):
1395 1395 if dest:
1396 1396 raise error.Abort(_("--all is incompatible with specifying "
1397 1397 "a destination"))
1398 1398 if opts.get('base'):
1399 1399 ui.warn(_("ignoring --base because --all was specified\n"))
1400 1400 base = ['null']
1401 1401 else:
1402 1402 base = scmutil.revrange(repo, opts.get('base'))
1403 1403 # TODO: get desired bundlecaps from command line.
1404 1404 bundlecaps = None
1405 1405 if cgversion not in changegroup.supportedoutgoingversions(repo):
1406 1406 raise error.Abort(_("repository does not support bundle version %s") %
1407 1407 cgversion)
1408 1408
1409 1409 if base:
1410 1410 if dest:
1411 1411 raise error.Abort(_("--base is incompatible with specifying "
1412 1412 "a destination"))
1413 1413 common = [repo.lookup(rev) for rev in base]
1414 1414 heads = revs and map(repo.lookup, revs) or revs
1415 1415 cg = changegroup.getchangegroup(repo, 'bundle', heads=heads,
1416 1416 common=common, bundlecaps=bundlecaps,
1417 1417 version=cgversion)
1418 1418 outgoing = None
1419 1419 else:
1420 1420 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1421 1421 dest, branches = hg.parseurl(dest, opts.get('branch'))
1422 1422 other = hg.peer(repo, opts, dest)
1423 1423 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1424 1424 heads = revs and map(repo.lookup, revs) or revs
1425 1425 outgoing = discovery.findcommonoutgoing(repo, other,
1426 1426 onlyheads=heads,
1427 1427 force=opts.get('force'),
1428 1428 portable=True)
1429 1429 cg = changegroup.getlocalchangegroup(repo, 'bundle', outgoing,
1430 1430 bundlecaps, version=cgversion)
1431 1431 if not cg:
1432 1432 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1433 1433 return 1
1434 1434
1435 1435 if cgversion == '01': #bundle1
1436 1436 if bcompression is None:
1437 1437 bcompression = 'UN'
1438 1438 bversion = 'HG10' + bcompression
1439 1439 bcompression = None
1440 1440 else:
1441 1441 assert cgversion == '02'
1442 1442 bversion = 'HG20'
1443 1443
1444 1444 bundle2.writebundle(ui, cg, fname, bversion, compression=bcompression)
1445 1445
1446 1446 @command('cat',
1447 1447 [('o', 'output', '',
1448 1448 _('print output to file with formatted name'), _('FORMAT')),
1449 1449 ('r', 'rev', '', _('print the given revision'), _('REV')),
1450 1450 ('', 'decode', None, _('apply any matching decode filter')),
1451 1451 ] + walkopts,
1452 1452 _('[OPTION]... FILE...'),
1453 1453 inferrepo=True)
1454 1454 def cat(ui, repo, file1, *pats, **opts):
1455 1455 """output the current or given revision of files
1456 1456
1457 1457 Print the specified files as they were at the given revision. If
1458 1458 no revision is given, the parent of the working directory is used.
1459 1459
1460 1460 Output may be to a file, in which case the name of the file is
1461 1461 given using a format string. The formatting rules as follows:
1462 1462
1463 1463 :``%%``: literal "%" character
1464 1464 :``%s``: basename of file being printed
1465 1465 :``%d``: dirname of file being printed, or '.' if in repository root
1466 1466 :``%p``: root-relative path name of file being printed
1467 1467 :``%H``: changeset hash (40 hexadecimal digits)
1468 1468 :``%R``: changeset revision number
1469 1469 :``%h``: short-form changeset hash (12 hexadecimal digits)
1470 1470 :``%r``: zero-padded changeset revision number
1471 1471 :``%b``: basename of the exporting repository
1472 1472
1473 1473 Returns 0 on success.
1474 1474 """
1475 1475 ctx = scmutil.revsingle(repo, opts.get('rev'))
1476 1476 m = scmutil.match(ctx, (file1,) + pats, opts)
1477 1477
1478 1478 return cmdutil.cat(ui, repo, ctx, m, '', **opts)
1479 1479
1480 1480 @command('^clone',
1481 1481 [('U', 'noupdate', None, _('the clone will include an empty working '
1482 1482 'directory (only a repository)')),
1483 1483 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1484 1484 _('REV')),
1485 1485 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1486 1486 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1487 1487 ('', 'pull', None, _('use pull protocol to copy metadata')),
1488 1488 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1489 1489 ] + remoteopts,
1490 1490 _('[OPTION]... SOURCE [DEST]'),
1491 1491 norepo=True)
1492 1492 def clone(ui, source, dest=None, **opts):
1493 1493 """make a copy of an existing repository
1494 1494
1495 1495 Create a copy of an existing repository in a new directory.
1496 1496
1497 1497 If no destination directory name is specified, it defaults to the
1498 1498 basename of the source.
1499 1499
1500 1500 The location of the source is added to the new repository's
1501 1501 ``.hg/hgrc`` file, as the default to be used for future pulls.
1502 1502
1503 1503 Only local paths and ``ssh://`` URLs are supported as
1504 1504 destinations. For ``ssh://`` destinations, no working directory or
1505 1505 ``.hg/hgrc`` will be created on the remote side.
1506 1506
1507 1507 If the source repository has a bookmark called '@' set, that
1508 1508 revision will be checked out in the new repository by default.
1509 1509
1510 1510 To check out a particular version, use -u/--update, or
1511 1511 -U/--noupdate to create a clone with no working directory.
1512 1512
1513 1513 To pull only a subset of changesets, specify one or more revisions
1514 1514 identifiers with -r/--rev or branches with -b/--branch. The
1515 1515 resulting clone will contain only the specified changesets and
1516 1516 their ancestors. These options (or 'clone src#rev dest') imply
1517 1517 --pull, even for local source repositories.
1518 1518
1519 1519 .. note::
1520 1520
1521 1521 Specifying a tag will include the tagged changeset but not the
1522 1522 changeset containing the tag.
1523 1523
1524 1524 .. container:: verbose
1525 1525
1526 1526 For efficiency, hardlinks are used for cloning whenever the
1527 1527 source and destination are on the same filesystem (note this
1528 1528 applies only to the repository data, not to the working
1529 1529 directory). Some filesystems, such as AFS, implement hardlinking
1530 1530 incorrectly, but do not report errors. In these cases, use the
1531 1531 --pull option to avoid hardlinking.
1532 1532
1533 1533 In some cases, you can clone repositories and the working
1534 1534 directory using full hardlinks with ::
1535 1535
1536 1536 $ cp -al REPO REPOCLONE
1537 1537
1538 1538 This is the fastest way to clone, but it is not always safe. The
1539 1539 operation is not atomic (making sure REPO is not modified during
1540 1540 the operation is up to you) and you have to make sure your
1541 1541 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1542 1542 so). Also, this is not compatible with certain extensions that
1543 1543 place their metadata under the .hg directory, such as mq.
1544 1544
1545 1545 Mercurial will update the working directory to the first applicable
1546 1546 revision from this list:
1547 1547
1548 1548 a) null if -U or the source repository has no changesets
1549 1549 b) if -u . and the source repository is local, the first parent of
1550 1550 the source repository's working directory
1551 1551 c) the changeset specified with -u (if a branch name, this means the
1552 1552 latest head of that branch)
1553 1553 d) the changeset specified with -r
1554 1554 e) the tipmost head specified with -b
1555 1555 f) the tipmost head specified with the url#branch source syntax
1556 1556 g) the revision marked with the '@' bookmark, if present
1557 1557 h) the tipmost head of the default branch
1558 1558 i) tip
1559 1559
1560 1560 When cloning from servers that support it, Mercurial may fetch
1561 1561 pre-generated data from a server-advertised URL. When this is done,
1562 1562 hooks operating on incoming changesets and changegroups may fire twice,
1563 1563 once for the bundle fetched from the URL and another for any additional
1564 1564 data not fetched from this URL. In addition, if an error occurs, the
1565 1565 repository may be rolled back to a partial clone. This behavior may
1566 1566 change in future releases. See :hg:`help -e clonebundles` for more.
1567 1567
1568 1568 Examples:
1569 1569
1570 1570 - clone a remote repository to a new directory named hg/::
1571 1571
1572 1572 hg clone http://selenic.com/hg
1573 1573
1574 1574 - create a lightweight local clone::
1575 1575
1576 1576 hg clone project/ project-feature/
1577 1577
1578 1578 - clone from an absolute path on an ssh server (note double-slash)::
1579 1579
1580 1580 hg clone ssh://user@server//home/projects/alpha/
1581 1581
1582 1582 - do a high-speed clone over a LAN while checking out a
1583 1583 specified version::
1584 1584
1585 1585 hg clone --uncompressed http://server/repo -u 1.5
1586 1586
1587 1587 - create a repository without changesets after a particular revision::
1588 1588
1589 1589 hg clone -r 04e544 experimental/ good/
1590 1590
1591 1591 - clone (and track) a particular named branch::
1592 1592
1593 1593 hg clone http://selenic.com/hg#stable
1594 1594
1595 1595 See :hg:`help urls` for details on specifying URLs.
1596 1596
1597 1597 Returns 0 on success.
1598 1598 """
1599 1599 if opts.get('noupdate') and opts.get('updaterev'):
1600 1600 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1601 1601
1602 1602 r = hg.clone(ui, opts, source, dest,
1603 1603 pull=opts.get('pull'),
1604 1604 stream=opts.get('uncompressed'),
1605 1605 rev=opts.get('rev'),
1606 1606 update=opts.get('updaterev') or not opts.get('noupdate'),
1607 1607 branch=opts.get('branch'),
1608 1608 shareopts=opts.get('shareopts'))
1609 1609
1610 1610 return r is None
1611 1611
1612 1612 @command('^commit|ci',
1613 1613 [('A', 'addremove', None,
1614 1614 _('mark new/missing files as added/removed before committing')),
1615 1615 ('', 'close-branch', None,
1616 1616 _('mark a branch head as closed')),
1617 1617 ('', 'amend', None, _('amend the parent of the working directory')),
1618 1618 ('s', 'secret', None, _('use the secret phase for committing')),
1619 1619 ('e', 'edit', None, _('invoke editor on commit messages')),
1620 1620 ('i', 'interactive', None, _('use interactive mode')),
1621 1621 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1622 1622 _('[OPTION]... [FILE]...'),
1623 1623 inferrepo=True)
1624 1624 def commit(ui, repo, *pats, **opts):
1625 1625 """commit the specified files or all outstanding changes
1626 1626
1627 1627 Commit changes to the given files into the repository. Unlike a
1628 1628 centralized SCM, this operation is a local operation. See
1629 1629 :hg:`push` for a way to actively distribute your changes.
1630 1630
1631 1631 If a list of files is omitted, all changes reported by :hg:`status`
1632 1632 will be committed.
1633 1633
1634 1634 If you are committing the result of a merge, do not provide any
1635 1635 filenames or -I/-X filters.
1636 1636
1637 1637 If no commit message is specified, Mercurial starts your
1638 1638 configured editor where you can enter a message. In case your
1639 1639 commit fails, you will find a backup of your message in
1640 1640 ``.hg/last-message.txt``.
1641 1641
1642 1642 The --close-branch flag can be used to mark the current branch
1643 1643 head closed. When all heads of a branch are closed, the branch
1644 1644 will be considered closed and no longer listed.
1645 1645
1646 1646 The --amend flag can be used to amend the parent of the
1647 1647 working directory with a new commit that contains the changes
1648 1648 in the parent in addition to those currently reported by :hg:`status`,
1649 1649 if there are any. The old commit is stored in a backup bundle in
1650 1650 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1651 1651 on how to restore it).
1652 1652
1653 1653 Message, user and date are taken from the amended commit unless
1654 1654 specified. When a message isn't specified on the command line,
1655 1655 the editor will open with the message of the amended commit.
1656 1656
1657 1657 It is not possible to amend public changesets (see :hg:`help phases`)
1658 1658 or changesets that have children.
1659 1659
1660 1660 See :hg:`help dates` for a list of formats valid for -d/--date.
1661 1661
1662 1662 Returns 0 on success, 1 if nothing changed.
1663 1663
1664 1664 .. container:: verbose
1665 1665
1666 1666 Examples:
1667 1667
1668 1668 - commit all files ending in .py::
1669 1669
1670 1670 hg commit --include "set:**.py"
1671 1671
1672 1672 - commit all non-binary files::
1673 1673
1674 1674 hg commit --exclude "set:binary()"
1675 1675
1676 1676 - amend the current commit and set the date to now::
1677 1677
1678 1678 hg commit --amend --date now
1679 1679 """
1680 1680 wlock = lock = None
1681 1681 try:
1682 1682 wlock = repo.wlock()
1683 1683 lock = repo.lock()
1684 1684 return _docommit(ui, repo, *pats, **opts)
1685 1685 finally:
1686 1686 release(lock, wlock)
1687 1687
1688 1688 def _docommit(ui, repo, *pats, **opts):
1689 1689 if opts.get('interactive'):
1690 1690 opts.pop('interactive')
1691 1691 cmdutil.dorecord(ui, repo, commit, None, False,
1692 1692 cmdutil.recordfilter, *pats, **opts)
1693 1693 return
1694 1694
1695 1695 if opts.get('subrepos'):
1696 1696 if opts.get('amend'):
1697 1697 raise error.Abort(_('cannot amend with --subrepos'))
1698 1698 # Let --subrepos on the command line override config setting.
1699 1699 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1700 1700
1701 1701 cmdutil.checkunfinished(repo, commit=True)
1702 1702
1703 1703 branch = repo[None].branch()
1704 1704 bheads = repo.branchheads(branch)
1705 1705
1706 1706 extra = {}
1707 1707 if opts.get('close_branch'):
1708 1708 extra['close'] = 1
1709 1709
1710 1710 if not bheads:
1711 1711 raise error.Abort(_('can only close branch heads'))
1712 1712 elif opts.get('amend'):
1713 1713 if repo[None].parents()[0].p1().branch() != branch and \
1714 1714 repo[None].parents()[0].p2().branch() != branch:
1715 1715 raise error.Abort(_('can only close branch heads'))
1716 1716
1717 1717 if opts.get('amend'):
1718 1718 if ui.configbool('ui', 'commitsubrepos'):
1719 1719 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1720 1720
1721 1721 old = repo['.']
1722 1722 if not old.mutable():
1723 1723 raise error.Abort(_('cannot amend public changesets'))
1724 1724 if len(repo[None].parents()) > 1:
1725 1725 raise error.Abort(_('cannot amend while merging'))
1726 1726 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1727 1727 if not allowunstable and old.children():
1728 1728 raise error.Abort(_('cannot amend changeset with children'))
1729 1729
1730 1730 # Currently histedit gets confused if an amend happens while histedit
1731 1731 # is in progress. Since we have a checkunfinished command, we are
1732 1732 # temporarily honoring it.
1733 1733 #
1734 1734 # Note: eventually this guard will be removed. Please do not expect
1735 1735 # this behavior to remain.
1736 1736 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1737 1737 cmdutil.checkunfinished(repo)
1738 1738
1739 1739 # commitfunc is used only for temporary amend commit by cmdutil.amend
1740 1740 def commitfunc(ui, repo, message, match, opts):
1741 1741 return repo.commit(message,
1742 1742 opts.get('user') or old.user(),
1743 1743 opts.get('date') or old.date(),
1744 1744 match,
1745 1745 extra=extra)
1746 1746
1747 1747 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1748 1748 if node == old.node():
1749 1749 ui.status(_("nothing changed\n"))
1750 1750 return 1
1751 1751 else:
1752 1752 def commitfunc(ui, repo, message, match, opts):
1753 1753 backup = ui.backupconfig('phases', 'new-commit')
1754 1754 baseui = repo.baseui
1755 1755 basebackup = baseui.backupconfig('phases', 'new-commit')
1756 1756 try:
1757 1757 if opts.get('secret'):
1758 1758 ui.setconfig('phases', 'new-commit', 'secret', 'commit')
1759 1759 # Propagate to subrepos
1760 1760 baseui.setconfig('phases', 'new-commit', 'secret', 'commit')
1761 1761
1762 1762 editform = cmdutil.mergeeditform(repo[None], 'commit.normal')
1763 1763 editor = cmdutil.getcommiteditor(editform=editform, **opts)
1764 1764 return repo.commit(message, opts.get('user'), opts.get('date'),
1765 1765 match,
1766 1766 editor=editor,
1767 1767 extra=extra)
1768 1768 finally:
1769 1769 ui.restoreconfig(backup)
1770 1770 repo.baseui.restoreconfig(basebackup)
1771 1771
1772 1772
1773 1773 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1774 1774
1775 1775 if not node:
1776 1776 stat = cmdutil.postcommitstatus(repo, pats, opts)
1777 1777 if stat[3]:
1778 1778 ui.status(_("nothing changed (%d missing files, see "
1779 1779 "'hg status')\n") % len(stat[3]))
1780 1780 else:
1781 1781 ui.status(_("nothing changed\n"))
1782 1782 return 1
1783 1783
1784 1784 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1785 1785
1786 1786 @command('config|showconfig|debugconfig',
1787 1787 [('u', 'untrusted', None, _('show untrusted configuration options')),
1788 1788 ('e', 'edit', None, _('edit user config')),
1789 1789 ('l', 'local', None, _('edit repository config')),
1790 1790 ('g', 'global', None, _('edit global config'))],
1791 1791 _('[-u] [NAME]...'),
1792 1792 optionalrepo=True)
1793 1793 def config(ui, repo, *values, **opts):
1794 1794 """show combined config settings from all hgrc files
1795 1795
1796 1796 With no arguments, print names and values of all config items.
1797 1797
1798 1798 With one argument of the form section.name, print just the value
1799 1799 of that config item.
1800 1800
1801 1801 With multiple arguments, print names and values of all config
1802 1802 items with matching section names.
1803 1803
1804 1804 With --edit, start an editor on the user-level config file. With
1805 1805 --global, edit the system-wide config file. With --local, edit the
1806 1806 repository-level config file.
1807 1807
1808 1808 With --debug, the source (filename and line number) is printed
1809 1809 for each config item.
1810 1810
1811 1811 See :hg:`help config` for more information about config files.
1812 1812
1813 1813 Returns 0 on success, 1 if NAME does not exist.
1814 1814
1815 1815 """
1816 1816
1817 1817 if opts.get('edit') or opts.get('local') or opts.get('global'):
1818 1818 if opts.get('local') and opts.get('global'):
1819 1819 raise error.Abort(_("can't use --local and --global together"))
1820 1820
1821 1821 if opts.get('local'):
1822 1822 if not repo:
1823 1823 raise error.Abort(_("can't use --local outside a repository"))
1824 1824 paths = [repo.join('hgrc')]
1825 1825 elif opts.get('global'):
1826 1826 paths = scmutil.systemrcpath()
1827 1827 else:
1828 1828 paths = scmutil.userrcpath()
1829 1829
1830 1830 for f in paths:
1831 1831 if os.path.exists(f):
1832 1832 break
1833 1833 else:
1834 1834 if opts.get('global'):
1835 1835 samplehgrc = uimod.samplehgrcs['global']
1836 1836 elif opts.get('local'):
1837 1837 samplehgrc = uimod.samplehgrcs['local']
1838 1838 else:
1839 1839 samplehgrc = uimod.samplehgrcs['user']
1840 1840
1841 1841 f = paths[0]
1842 1842 fp = open(f, "w")
1843 1843 fp.write(samplehgrc)
1844 1844 fp.close()
1845 1845
1846 1846 editor = ui.geteditor()
1847 1847 ui.system("%s \"%s\"" % (editor, f),
1848 1848 onerr=error.Abort, errprefix=_("edit failed"))
1849 1849 return
1850 1850
1851 1851 for f in scmutil.rcpath():
1852 1852 ui.debug('read config from: %s\n' % f)
1853 1853 untrusted = bool(opts.get('untrusted'))
1854 1854 if values:
1855 1855 sections = [v for v in values if '.' not in v]
1856 1856 items = [v for v in values if '.' in v]
1857 1857 if len(items) > 1 or items and sections:
1858 1858 raise error.Abort(_('only one config item permitted'))
1859 1859 matched = False
1860 1860 for section, name, value in ui.walkconfig(untrusted=untrusted):
1861 1861 value = str(value).replace('\n', '\\n')
1862 1862 sectname = section + '.' + name
1863 1863 if values:
1864 1864 for v in values:
1865 1865 if v == section:
1866 1866 ui.debug('%s: ' %
1867 1867 ui.configsource(section, name, untrusted))
1868 1868 ui.write('%s=%s\n' % (sectname, value))
1869 1869 matched = True
1870 1870 elif v == sectname:
1871 1871 ui.debug('%s: ' %
1872 1872 ui.configsource(section, name, untrusted))
1873 1873 ui.write(value, '\n')
1874 1874 matched = True
1875 1875 else:
1876 1876 ui.debug('%s: ' %
1877 1877 ui.configsource(section, name, untrusted))
1878 1878 ui.write('%s=%s\n' % (sectname, value))
1879 1879 matched = True
1880 1880 if matched:
1881 1881 return 0
1882 1882 return 1
1883 1883
1884 1884 @command('copy|cp',
1885 1885 [('A', 'after', None, _('record a copy that has already occurred')),
1886 1886 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1887 1887 ] + walkopts + dryrunopts,
1888 1888 _('[OPTION]... [SOURCE]... DEST'))
1889 1889 def copy(ui, repo, *pats, **opts):
1890 1890 """mark files as copied for the next commit
1891 1891
1892 1892 Mark dest as having copies of source files. If dest is a
1893 1893 directory, copies are put in that directory. If dest is a file,
1894 1894 the source must be a single file.
1895 1895
1896 1896 By default, this command copies the contents of files as they
1897 1897 exist in the working directory. If invoked with -A/--after, the
1898 1898 operation is recorded, but no copying is performed.
1899 1899
1900 1900 This command takes effect with the next commit. To undo a copy
1901 1901 before that, see :hg:`revert`.
1902 1902
1903 1903 Returns 0 on success, 1 if errors are encountered.
1904 1904 """
1905 1905 with repo.wlock(False):
1906 1906 return cmdutil.copy(ui, repo, pats, opts)
1907 1907
1908 1908 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
1909 1909 def debugancestor(ui, repo, *args):
1910 1910 """find the ancestor revision of two revisions in a given index"""
1911 1911 if len(args) == 3:
1912 1912 index, rev1, rev2 = args
1913 1913 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1914 1914 lookup = r.lookup
1915 1915 elif len(args) == 2:
1916 1916 if not repo:
1917 1917 raise error.Abort(_("there is no Mercurial repository here "
1918 1918 "(.hg not found)"))
1919 1919 rev1, rev2 = args
1920 1920 r = repo.changelog
1921 1921 lookup = repo.lookup
1922 1922 else:
1923 1923 raise error.Abort(_('either two or three arguments required'))
1924 1924 a = r.ancestor(lookup(rev1), lookup(rev2))
1925 1925 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1926 1926
1927 1927 @command('debugbuilddag',
1928 1928 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1929 1929 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1930 1930 ('n', 'new-file', None, _('add new file at each rev'))],
1931 1931 _('[OPTION]... [TEXT]'))
1932 1932 def debugbuilddag(ui, repo, text=None,
1933 1933 mergeable_file=False,
1934 1934 overwritten_file=False,
1935 1935 new_file=False):
1936 1936 """builds a repo with a given DAG from scratch in the current empty repo
1937 1937
1938 1938 The description of the DAG is read from stdin if not given on the
1939 1939 command line.
1940 1940
1941 1941 Elements:
1942 1942
1943 1943 - "+n" is a linear run of n nodes based on the current default parent
1944 1944 - "." is a single node based on the current default parent
1945 1945 - "$" resets the default parent to null (implied at the start);
1946 1946 otherwise the default parent is always the last node created
1947 1947 - "<p" sets the default parent to the backref p
1948 1948 - "*p" is a fork at parent p, which is a backref
1949 1949 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1950 1950 - "/p2" is a merge of the preceding node and p2
1951 1951 - ":tag" defines a local tag for the preceding node
1952 1952 - "@branch" sets the named branch for subsequent nodes
1953 1953 - "#...\\n" is a comment up to the end of the line
1954 1954
1955 1955 Whitespace between the above elements is ignored.
1956 1956
1957 1957 A backref is either
1958 1958
1959 1959 - a number n, which references the node curr-n, where curr is the current
1960 1960 node, or
1961 1961 - the name of a local tag you placed earlier using ":tag", or
1962 1962 - empty to denote the default parent.
1963 1963
1964 1964 All string valued-elements are either strictly alphanumeric, or must
1965 1965 be enclosed in double quotes ("..."), with "\\" as escape character.
1966 1966 """
1967 1967
1968 1968 if text is None:
1969 1969 ui.status(_("reading DAG from stdin\n"))
1970 1970 text = ui.fin.read()
1971 1971
1972 1972 cl = repo.changelog
1973 1973 if len(cl) > 0:
1974 1974 raise error.Abort(_('repository is not empty'))
1975 1975
1976 1976 # determine number of revs in DAG
1977 1977 total = 0
1978 1978 for type, data in dagparser.parsedag(text):
1979 1979 if type == 'n':
1980 1980 total += 1
1981 1981
1982 1982 if mergeable_file:
1983 1983 linesperrev = 2
1984 1984 # make a file with k lines per rev
1985 1985 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1986 1986 initialmergedlines.append("")
1987 1987
1988 1988 tags = []
1989 1989
1990 1990 wlock = lock = tr = None
1991 1991 try:
1992 1992 wlock = repo.wlock()
1993 1993 lock = repo.lock()
1994 1994 tr = repo.transaction("builddag")
1995 1995
1996 1996 at = -1
1997 1997 atbranch = 'default'
1998 1998 nodeids = []
1999 1999 id = 0
2000 2000 ui.progress(_('building'), id, unit=_('revisions'), total=total)
2001 2001 for type, data in dagparser.parsedag(text):
2002 2002 if type == 'n':
2003 2003 ui.note(('node %s\n' % str(data)))
2004 2004 id, ps = data
2005 2005
2006 2006 files = []
2007 2007 fctxs = {}
2008 2008
2009 2009 p2 = None
2010 2010 if mergeable_file:
2011 2011 fn = "mf"
2012 2012 p1 = repo[ps[0]]
2013 2013 if len(ps) > 1:
2014 2014 p2 = repo[ps[1]]
2015 2015 pa = p1.ancestor(p2)
2016 2016 base, local, other = [x[fn].data() for x in (pa, p1,
2017 2017 p2)]
2018 2018 m3 = simplemerge.Merge3Text(base, local, other)
2019 2019 ml = [l.strip() for l in m3.merge_lines()]
2020 2020 ml.append("")
2021 2021 elif at > 0:
2022 2022 ml = p1[fn].data().split("\n")
2023 2023 else:
2024 2024 ml = initialmergedlines
2025 2025 ml[id * linesperrev] += " r%i" % id
2026 2026 mergedtext = "\n".join(ml)
2027 2027 files.append(fn)
2028 2028 fctxs[fn] = context.memfilectx(repo, fn, mergedtext)
2029 2029
2030 2030 if overwritten_file:
2031 2031 fn = "of"
2032 2032 files.append(fn)
2033 2033 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
2034 2034
2035 2035 if new_file:
2036 2036 fn = "nf%i" % id
2037 2037 files.append(fn)
2038 2038 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
2039 2039 if len(ps) > 1:
2040 2040 if not p2:
2041 2041 p2 = repo[ps[1]]
2042 2042 for fn in p2:
2043 2043 if fn.startswith("nf"):
2044 2044 files.append(fn)
2045 2045 fctxs[fn] = p2[fn]
2046 2046
2047 2047 def fctxfn(repo, cx, path):
2048 2048 return fctxs.get(path)
2049 2049
2050 2050 if len(ps) == 0 or ps[0] < 0:
2051 2051 pars = [None, None]
2052 2052 elif len(ps) == 1:
2053 2053 pars = [nodeids[ps[0]], None]
2054 2054 else:
2055 2055 pars = [nodeids[p] for p in ps]
2056 2056 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
2057 2057 date=(id, 0),
2058 2058 user="debugbuilddag",
2059 2059 extra={'branch': atbranch})
2060 2060 nodeid = repo.commitctx(cx)
2061 2061 nodeids.append(nodeid)
2062 2062 at = id
2063 2063 elif type == 'l':
2064 2064 id, name = data
2065 2065 ui.note(('tag %s\n' % name))
2066 2066 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
2067 2067 elif type == 'a':
2068 2068 ui.note(('branch %s\n' % data))
2069 2069 atbranch = data
2070 2070 ui.progress(_('building'), id, unit=_('revisions'), total=total)
2071 2071 tr.close()
2072 2072
2073 2073 if tags:
2074 2074 repo.vfs.write("localtags", "".join(tags))
2075 2075 finally:
2076 2076 ui.progress(_('building'), None)
2077 2077 release(tr, lock, wlock)
2078 2078
2079 2079 @command('debugbundle',
2080 2080 [('a', 'all', None, _('show all details')),
2081 2081 ('', 'spec', None, _('print the bundlespec of the bundle'))],
2082 2082 _('FILE'),
2083 2083 norepo=True)
2084 2084 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
2085 2085 """lists the contents of a bundle"""
2086 2086 with hg.openpath(ui, bundlepath) as f:
2087 2087 if spec:
2088 2088 spec = exchange.getbundlespec(ui, f)
2089 2089 ui.write('%s\n' % spec)
2090 2090 return
2091 2091
2092 2092 gen = exchange.readbundle(ui, f, bundlepath)
2093 2093 if isinstance(gen, bundle2.unbundle20):
2094 2094 return _debugbundle2(ui, gen, all=all, **opts)
2095 2095 _debugchangegroup(ui, gen, all=all, **opts)
2096 2096
2097 2097 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
2098 2098 indent_string = ' ' * indent
2099 2099 if all:
2100 2100 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
2101 2101 % indent_string)
2102 2102
2103 2103 def showchunks(named):
2104 2104 ui.write("\n%s%s\n" % (indent_string, named))
2105 2105 chain = None
2106 2106 for chunkdata in iter(lambda: gen.deltachunk(chain), {}):
2107 2107 node = chunkdata['node']
2108 2108 p1 = chunkdata['p1']
2109 2109 p2 = chunkdata['p2']
2110 2110 cs = chunkdata['cs']
2111 2111 deltabase = chunkdata['deltabase']
2112 2112 delta = chunkdata['delta']
2113 2113 ui.write("%s%s %s %s %s %s %s\n" %
2114 2114 (indent_string, hex(node), hex(p1), hex(p2),
2115 2115 hex(cs), hex(deltabase), len(delta)))
2116 2116 chain = node
2117 2117
2118 2118 chunkdata = gen.changelogheader()
2119 2119 showchunks("changelog")
2120 2120 chunkdata = gen.manifestheader()
2121 2121 showchunks("manifest")
2122 2122 for chunkdata in iter(gen.filelogheader, {}):
2123 2123 fname = chunkdata['filename']
2124 2124 showchunks(fname)
2125 2125 else:
2126 2126 if isinstance(gen, bundle2.unbundle20):
2127 2127 raise error.Abort(_('use debugbundle2 for this file'))
2128 2128 chunkdata = gen.changelogheader()
2129 2129 chain = None
2130 2130 for chunkdata in iter(lambda: gen.deltachunk(chain), {}):
2131 2131 node = chunkdata['node']
2132 2132 ui.write("%s%s\n" % (indent_string, hex(node)))
2133 2133 chain = node
2134 2134
2135 2135 def _debugbundle2(ui, gen, all=None, **opts):
2136 2136 """lists the contents of a bundle2"""
2137 2137 if not isinstance(gen, bundle2.unbundle20):
2138 2138 raise error.Abort(_('not a bundle2 file'))
2139 2139 ui.write(('Stream params: %s\n' % repr(gen.params)))
2140 2140 for part in gen.iterparts():
2141 2141 ui.write('%s -- %r\n' % (part.type, repr(part.params)))
2142 2142 if part.type == 'changegroup':
2143 2143 version = part.params.get('version', '01')
2144 2144 cg = changegroup.getunbundler(version, part, 'UN')
2145 2145 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
2146 2146
2147 2147 @command('debugcreatestreamclonebundle', [], 'FILE')
2148 2148 def debugcreatestreamclonebundle(ui, repo, fname):
2149 2149 """create a stream clone bundle file
2150 2150
2151 2151 Stream bundles are special bundles that are essentially archives of
2152 2152 revlog files. They are commonly used for cloning very quickly.
2153 2153 """
2154 2154 requirements, gen = streamclone.generatebundlev1(repo)
2155 2155 changegroup.writechunks(ui, gen, fname)
2156 2156
2157 2157 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
2158 2158
2159 2159 @command('debugapplystreamclonebundle', [], 'FILE')
2160 2160 def debugapplystreamclonebundle(ui, repo, fname):
2161 2161 """apply a stream clone bundle file"""
2162 2162 f = hg.openpath(ui, fname)
2163 2163 gen = exchange.readbundle(ui, f, fname)
2164 2164 gen.apply(repo)
2165 2165
2166 2166 @command('debugcheckstate', [], '')
2167 2167 def debugcheckstate(ui, repo):
2168 2168 """validate the correctness of the current dirstate"""
2169 2169 parent1, parent2 = repo.dirstate.parents()
2170 2170 m1 = repo[parent1].manifest()
2171 2171 m2 = repo[parent2].manifest()
2172 2172 errors = 0
2173 2173 for f in repo.dirstate:
2174 2174 state = repo.dirstate[f]
2175 2175 if state in "nr" and f not in m1:
2176 2176 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
2177 2177 errors += 1
2178 2178 if state in "a" and f in m1:
2179 2179 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
2180 2180 errors += 1
2181 2181 if state in "m" and f not in m1 and f not in m2:
2182 2182 ui.warn(_("%s in state %s, but not in either manifest\n") %
2183 2183 (f, state))
2184 2184 errors += 1
2185 2185 for f in m1:
2186 2186 state = repo.dirstate[f]
2187 2187 if state not in "nrm":
2188 2188 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
2189 2189 errors += 1
2190 2190 if errors:
2191 2191 error = _(".hg/dirstate inconsistent with current parent's manifest")
2192 2192 raise error.Abort(error)
2193 2193
2194 2194 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
2195 2195 def debugcommands(ui, cmd='', *args):
2196 2196 """list all available commands and options"""
2197 2197 for cmd, vals in sorted(table.iteritems()):
2198 2198 cmd = cmd.split('|')[0].strip('^')
2199 2199 opts = ', '.join([i[1] for i in vals[1]])
2200 2200 ui.write('%s: %s\n' % (cmd, opts))
2201 2201
2202 2202 @command('debugcomplete',
2203 2203 [('o', 'options', None, _('show the command options'))],
2204 2204 _('[-o] CMD'),
2205 2205 norepo=True)
2206 2206 def debugcomplete(ui, cmd='', **opts):
2207 2207 """returns the completion list associated with the given command"""
2208 2208
2209 2209 if opts.get('options'):
2210 2210 options = []
2211 2211 otables = [globalopts]
2212 2212 if cmd:
2213 2213 aliases, entry = cmdutil.findcmd(cmd, table, False)
2214 2214 otables.append(entry[1])
2215 2215 for t in otables:
2216 2216 for o in t:
2217 2217 if "(DEPRECATED)" in o[3]:
2218 2218 continue
2219 2219 if o[0]:
2220 2220 options.append('-%s' % o[0])
2221 2221 options.append('--%s' % o[1])
2222 2222 ui.write("%s\n" % "\n".join(options))
2223 2223 return
2224 2224
2225 2225 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2226 2226 if ui.verbose:
2227 2227 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
2228 2228 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
2229 2229
2230 2230 @command('debugdag',
2231 2231 [('t', 'tags', None, _('use tags as labels')),
2232 2232 ('b', 'branches', None, _('annotate with branch names')),
2233 2233 ('', 'dots', None, _('use dots for runs')),
2234 2234 ('s', 'spaces', None, _('separate elements by spaces'))],
2235 2235 _('[OPTION]... [FILE [REV]...]'),
2236 2236 optionalrepo=True)
2237 2237 def debugdag(ui, repo, file_=None, *revs, **opts):
2238 2238 """format the changelog or an index DAG as a concise textual description
2239 2239
2240 2240 If you pass a revlog index, the revlog's DAG is emitted. If you list
2241 2241 revision numbers, they get labeled in the output as rN.
2242 2242
2243 2243 Otherwise, the changelog DAG of the current repo is emitted.
2244 2244 """
2245 2245 spaces = opts.get('spaces')
2246 2246 dots = opts.get('dots')
2247 2247 if file_:
2248 2248 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
2249 2249 revs = set((int(r) for r in revs))
2250 2250 def events():
2251 2251 for r in rlog:
2252 2252 yield 'n', (r, list(p for p in rlog.parentrevs(r)
2253 2253 if p != -1))
2254 2254 if r in revs:
2255 2255 yield 'l', (r, "r%i" % r)
2256 2256 elif repo:
2257 2257 cl = repo.changelog
2258 2258 tags = opts.get('tags')
2259 2259 branches = opts.get('branches')
2260 2260 if tags:
2261 2261 labels = {}
2262 2262 for l, n in repo.tags().items():
2263 2263 labels.setdefault(cl.rev(n), []).append(l)
2264 2264 def events():
2265 2265 b = "default"
2266 2266 for r in cl:
2267 2267 if branches:
2268 2268 newb = cl.read(cl.node(r))[5]['branch']
2269 2269 if newb != b:
2270 2270 yield 'a', newb
2271 2271 b = newb
2272 2272 yield 'n', (r, list(p for p in cl.parentrevs(r)
2273 2273 if p != -1))
2274 2274 if tags:
2275 2275 ls = labels.get(r)
2276 2276 if ls:
2277 2277 for l in ls:
2278 2278 yield 'l', (r, l)
2279 2279 else:
2280 2280 raise error.Abort(_('need repo for changelog dag'))
2281 2281
2282 2282 for line in dagparser.dagtextlines(events(),
2283 2283 addspaces=spaces,
2284 2284 wraplabels=True,
2285 2285 wrapannotations=True,
2286 2286 wrapnonlinear=dots,
2287 2287 usedots=dots,
2288 2288 maxlinewidth=70):
2289 2289 ui.write(line)
2290 2290 ui.write("\n")
2291 2291
2292 2292 @command('debugdata', debugrevlogopts, _('-c|-m|FILE REV'))
2293 2293 def debugdata(ui, repo, file_, rev=None, **opts):
2294 2294 """dump the contents of a data file revision"""
2295 2295 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
2296 2296 if rev is not None:
2297 2297 raise error.CommandError('debugdata', _('invalid arguments'))
2298 2298 file_, rev = None, file_
2299 2299 elif rev is None:
2300 2300 raise error.CommandError('debugdata', _('invalid arguments'))
2301 2301 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
2302 2302 try:
2303 2303 ui.write(r.revision(r.lookup(rev)))
2304 2304 except KeyError:
2305 2305 raise error.Abort(_('invalid revision identifier %s') % rev)
2306 2306
2307 2307 @command('debugdate',
2308 2308 [('e', 'extended', None, _('try extended date formats'))],
2309 2309 _('[-e] DATE [RANGE]'),
2310 2310 norepo=True, optionalrepo=True)
2311 2311 def debugdate(ui, date, range=None, **opts):
2312 2312 """parse and display a date"""
2313 2313 if opts["extended"]:
2314 2314 d = util.parsedate(date, util.extendeddateformats)
2315 2315 else:
2316 2316 d = util.parsedate(date)
2317 2317 ui.write(("internal: %s %s\n") % d)
2318 2318 ui.write(("standard: %s\n") % util.datestr(d))
2319 2319 if range:
2320 2320 m = util.matchdate(range)
2321 2321 ui.write(("match: %s\n") % m(d[0]))
2322 2322
2323 2323 @command('debugdiscovery',
2324 2324 [('', 'old', None, _('use old-style discovery')),
2325 2325 ('', 'nonheads', None,
2326 2326 _('use old-style discovery with non-heads included')),
2327 2327 ] + remoteopts,
2328 2328 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
2329 2329 def debugdiscovery(ui, repo, remoteurl="default", **opts):
2330 2330 """runs the changeset discovery protocol in isolation"""
2331 2331 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
2332 2332 opts.get('branch'))
2333 2333 remote = hg.peer(repo, opts, remoteurl)
2334 2334 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
2335 2335
2336 2336 # make sure tests are repeatable
2337 2337 random.seed(12323)
2338 2338
2339 2339 def doit(localheads, remoteheads, remote=remote):
2340 2340 if opts.get('old'):
2341 2341 if localheads:
2342 2342 raise error.Abort('cannot use localheads with old style '
2343 2343 'discovery')
2344 2344 if not util.safehasattr(remote, 'branches'):
2345 2345 # enable in-client legacy support
2346 2346 remote = localrepo.locallegacypeer(remote.local())
2347 2347 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
2348 2348 force=True)
2349 2349 common = set(common)
2350 2350 if not opts.get('nonheads'):
2351 2351 ui.write(("unpruned common: %s\n") %
2352 2352 " ".join(sorted(short(n) for n in common)))
2353 2353 dag = dagutil.revlogdag(repo.changelog)
2354 2354 all = dag.ancestorset(dag.internalizeall(common))
2355 2355 common = dag.externalizeall(dag.headsetofconnecteds(all))
2356 2356 else:
2357 2357 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
2358 2358 common = set(common)
2359 2359 rheads = set(hds)
2360 2360 lheads = set(repo.heads())
2361 2361 ui.write(("common heads: %s\n") %
2362 2362 " ".join(sorted(short(n) for n in common)))
2363 2363 if lheads <= common:
2364 2364 ui.write(("local is subset\n"))
2365 2365 elif rheads <= common:
2366 2366 ui.write(("remote is subset\n"))
2367 2367
2368 2368 serverlogs = opts.get('serverlog')
2369 2369 if serverlogs:
2370 2370 for filename in serverlogs:
2371 2371 with open(filename, 'r') as logfile:
2372 2372 line = logfile.readline()
2373 2373 while line:
2374 2374 parts = line.strip().split(';')
2375 2375 op = parts[1]
2376 2376 if op == 'cg':
2377 2377 pass
2378 2378 elif op == 'cgss':
2379 2379 doit(parts[2].split(' '), parts[3].split(' '))
2380 2380 elif op == 'unb':
2381 2381 doit(parts[3].split(' '), parts[2].split(' '))
2382 2382 line = logfile.readline()
2383 2383 else:
2384 2384 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
2385 2385 opts.get('remote_head'))
2386 2386 localrevs = opts.get('local_head')
2387 2387 doit(localrevs, remoterevs)
2388 2388
2389 2389 @command('debugextensions', formatteropts, [], norepo=True)
2390 2390 def debugextensions(ui, **opts):
2391 2391 '''show information about active extensions'''
2392 2392 exts = extensions.extensions(ui)
2393 2393 hgver = util.version()
2394 2394 fm = ui.formatter('debugextensions', opts)
2395 2395 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
2396 2396 extsource = extmod.__file__
2397 2397 exttestedwith = getattr(extmod, 'testedwith', '').split()
2398 2398 extbuglink = getattr(extmod, 'buglink', None)
2399 2399
2400 2400 fm.startitem()
2401 2401
2402 2402 if ui.quiet or ui.verbose:
2403 2403 fm.write('name', '%s\n', extname)
2404 2404 else:
2405 2405 fm.write('name', '%s', extname)
2406 2406 if not exttestedwith:
2407 2407 fm.plain(_(' (untested!)\n'))
2408 2408 elif exttestedwith == ['internal'] or hgver in exttestedwith:
2409 2409 fm.plain('\n')
2410 2410 else:
2411 2411 lasttestedversion = exttestedwith[-1]
2412 2412 fm.plain(' (%s!)\n' % lasttestedversion)
2413 2413
2414 2414 fm.condwrite(ui.verbose and extsource, 'source',
2415 2415 _(' location: %s\n'), extsource or "")
2416 2416
2417 2417 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
2418 2418 _(' tested with: %s\n'),
2419 2419 fm.formatlist(exttestedwith, name='ver'))
2420 2420
2421 2421 fm.condwrite(ui.verbose and extbuglink, 'buglink',
2422 2422 _(' bug reporting: %s\n'), extbuglink or "")
2423 2423
2424 2424 fm.end()
2425 2425
2426 2426 @command('debugfileset',
2427 2427 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
2428 2428 _('[-r REV] FILESPEC'))
2429 2429 def debugfileset(ui, repo, expr, **opts):
2430 2430 '''parse and apply a fileset specification'''
2431 2431 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2432 2432 if ui.verbose:
2433 2433 tree = fileset.parse(expr)
2434 2434 ui.note(fileset.prettyformat(tree), "\n")
2435 2435
2436 2436 for f in ctx.getfileset(expr):
2437 2437 ui.write("%s\n" % f)
2438 2438
2439 2439 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
2440 2440 def debugfsinfo(ui, path="."):
2441 2441 """show information detected about current filesystem"""
2442 2442 util.writefile('.debugfsinfo', '')
2443 2443 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
2444 2444 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
2445 2445 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
2446 2446 ui.write(('case-sensitive: %s\n') % (util.checkcase('.debugfsinfo')
2447 2447 and 'yes' or 'no'))
2448 2448 os.unlink('.debugfsinfo')
2449 2449
2450 2450 @command('debuggetbundle',
2451 2451 [('H', 'head', [], _('id of head node'), _('ID')),
2452 2452 ('C', 'common', [], _('id of common node'), _('ID')),
2453 2453 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
2454 2454 _('REPO FILE [-H|-C ID]...'),
2455 2455 norepo=True)
2456 2456 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
2457 2457 """retrieves a bundle from a repo
2458 2458
2459 2459 Every ID must be a full-length hex node id string. Saves the bundle to the
2460 2460 given file.
2461 2461 """
2462 2462 repo = hg.peer(ui, opts, repopath)
2463 2463 if not repo.capable('getbundle'):
2464 2464 raise error.Abort("getbundle() not supported by target repository")
2465 2465 args = {}
2466 2466 if common:
2467 2467 args['common'] = [bin(s) for s in common]
2468 2468 if head:
2469 2469 args['heads'] = [bin(s) for s in head]
2470 2470 # TODO: get desired bundlecaps from command line.
2471 2471 args['bundlecaps'] = None
2472 2472 bundle = repo.getbundle('debug', **args)
2473 2473
2474 2474 bundletype = opts.get('type', 'bzip2').lower()
2475 2475 btypes = {'none': 'HG10UN',
2476 2476 'bzip2': 'HG10BZ',
2477 2477 'gzip': 'HG10GZ',
2478 2478 'bundle2': 'HG20'}
2479 2479 bundletype = btypes.get(bundletype)
2480 2480 if bundletype not in bundle2.bundletypes:
2481 2481 raise error.Abort(_('unknown bundle type specified with --type'))
2482 2482 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
2483 2483
2484 2484 @command('debugignore', [], '[FILE]')
2485 2485 def debugignore(ui, repo, *files, **opts):
2486 2486 """display the combined ignore pattern and information about ignored files
2487 2487
2488 2488 With no argument display the combined ignore pattern.
2489 2489
2490 2490 Given space separated file names, shows if the given file is ignored and
2491 2491 if so, show the ignore rule (file and line number) that matched it.
2492 2492 """
2493 2493 ignore = repo.dirstate._ignore
2494 2494 if not files:
2495 2495 # Show all the patterns
2496 2496 includepat = getattr(ignore, 'includepat', None)
2497 2497 if includepat is not None:
2498 2498 ui.write("%s\n" % includepat)
2499 2499 else:
2500 2500 raise error.Abort(_("no ignore patterns found"))
2501 2501 else:
2502 2502 for f in files:
2503 2503 nf = util.normpath(f)
2504 2504 ignored = None
2505 2505 ignoredata = None
2506 2506 if nf != '.':
2507 2507 if ignore(nf):
2508 2508 ignored = nf
2509 2509 ignoredata = repo.dirstate._ignorefileandline(nf)
2510 2510 else:
2511 2511 for p in util.finddirs(nf):
2512 2512 if ignore(p):
2513 2513 ignored = p
2514 2514 ignoredata = repo.dirstate._ignorefileandline(p)
2515 2515 break
2516 2516 if ignored:
2517 2517 if ignored == nf:
2518 2518 ui.write(_("%s is ignored\n") % f)
2519 2519 else:
2520 2520 ui.write(_("%s is ignored because of "
2521 2521 "containing folder %s\n")
2522 2522 % (f, ignored))
2523 2523 ignorefile, lineno, line = ignoredata
2524 2524 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
2525 2525 % (ignorefile, lineno, line))
2526 2526 else:
2527 2527 ui.write(_("%s is not ignored\n") % f)
2528 2528
2529 2529 @command('debugindex', debugrevlogopts +
2530 2530 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2531 2531 _('[-f FORMAT] -c|-m|FILE'),
2532 2532 optionalrepo=True)
2533 2533 def debugindex(ui, repo, file_=None, **opts):
2534 2534 """dump the contents of an index file"""
2535 2535 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
2536 2536 format = opts.get('format', 0)
2537 2537 if format not in (0, 1):
2538 2538 raise error.Abort(_("unknown format %d") % format)
2539 2539
2540 2540 generaldelta = r.version & revlog.REVLOGGENERALDELTA
2541 2541 if generaldelta:
2542 2542 basehdr = ' delta'
2543 2543 else:
2544 2544 basehdr = ' base'
2545 2545
2546 2546 if ui.debugflag:
2547 2547 shortfn = hex
2548 2548 else:
2549 2549 shortfn = short
2550 2550
2551 2551 # There might not be anything in r, so have a sane default
2552 2552 idlen = 12
2553 2553 for i in r:
2554 2554 idlen = len(shortfn(r.node(i)))
2555 2555 break
2556 2556
2557 2557 if format == 0:
2558 2558 ui.write((" rev offset length " + basehdr + " linkrev"
2559 2559 " %s %s p2\n") % ("nodeid".ljust(idlen), "p1".ljust(idlen)))
2560 2560 elif format == 1:
2561 2561 ui.write((" rev flag offset length"
2562 2562 " size " + basehdr + " link p1 p2"
2563 2563 " %s\n") % "nodeid".rjust(idlen))
2564 2564
2565 2565 for i in r:
2566 2566 node = r.node(i)
2567 2567 if generaldelta:
2568 2568 base = r.deltaparent(i)
2569 2569 else:
2570 2570 base = r.chainbase(i)
2571 2571 if format == 0:
2572 2572 try:
2573 2573 pp = r.parents(node)
2574 2574 except Exception:
2575 2575 pp = [nullid, nullid]
2576 2576 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
2577 2577 i, r.start(i), r.length(i), base, r.linkrev(i),
2578 2578 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
2579 2579 elif format == 1:
2580 2580 pr = r.parentrevs(i)
2581 2581 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
2582 2582 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2583 2583 base, r.linkrev(i), pr[0], pr[1], shortfn(node)))
2584 2584
2585 2585 @command('debugindexdot', debugrevlogopts,
2586 2586 _('-c|-m|FILE'), optionalrepo=True)
2587 2587 def debugindexdot(ui, repo, file_=None, **opts):
2588 2588 """dump an index DAG as a graphviz dot file"""
2589 2589 r = cmdutil.openrevlog(repo, 'debugindexdot', file_, opts)
2590 2590 ui.write(("digraph G {\n"))
2591 2591 for i in r:
2592 2592 node = r.node(i)
2593 2593 pp = r.parents(node)
2594 2594 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
2595 2595 if pp[1] != nullid:
2596 2596 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
2597 2597 ui.write("}\n")
2598 2598
2599 2599 @command('debugdeltachain',
2600 2600 debugrevlogopts + formatteropts,
2601 2601 _('-c|-m|FILE'),
2602 2602 optionalrepo=True)
2603 2603 def debugdeltachain(ui, repo, file_=None, **opts):
2604 2604 """dump information about delta chains in a revlog
2605 2605
2606 2606 Output can be templatized. Available template keywords are:
2607 2607
2608 2608 :``rev``: revision number
2609 2609 :``chainid``: delta chain identifier (numbered by unique base)
2610 2610 :``chainlen``: delta chain length to this revision
2611 2611 :``prevrev``: previous revision in delta chain
2612 2612 :``deltatype``: role of delta / how it was computed
2613 2613 :``compsize``: compressed size of revision
2614 2614 :``uncompsize``: uncompressed size of revision
2615 2615 :``chainsize``: total size of compressed revisions in chain
2616 2616 :``chainratio``: total chain size divided by uncompressed revision size
2617 2617 (new delta chains typically start at ratio 2.00)
2618 2618 :``lindist``: linear distance from base revision in delta chain to end
2619 2619 of this revision
2620 2620 :``extradist``: total size of revisions not part of this delta chain from
2621 2621 base of delta chain to end of this revision; a measurement
2622 2622 of how much extra data we need to read/seek across to read
2623 2623 the delta chain for this revision
2624 2624 :``extraratio``: extradist divided by chainsize; another representation of
2625 2625 how much unrelated data is needed to load this delta chain
2626 2626 """
2627 2627 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
2628 2628 index = r.index
2629 2629 generaldelta = r.version & revlog.REVLOGGENERALDELTA
2630 2630
2631 2631 def revinfo(rev):
2632 2632 e = index[rev]
2633 2633 compsize = e[1]
2634 2634 uncompsize = e[2]
2635 2635 chainsize = 0
2636 2636
2637 2637 if generaldelta:
2638 2638 if e[3] == e[5]:
2639 2639 deltatype = 'p1'
2640 2640 elif e[3] == e[6]:
2641 2641 deltatype = 'p2'
2642 2642 elif e[3] == rev - 1:
2643 2643 deltatype = 'prev'
2644 2644 elif e[3] == rev:
2645 2645 deltatype = 'base'
2646 2646 else:
2647 2647 deltatype = 'other'
2648 2648 else:
2649 2649 if e[3] == rev:
2650 2650 deltatype = 'base'
2651 2651 else:
2652 2652 deltatype = 'prev'
2653 2653
2654 2654 chain = r._deltachain(rev)[0]
2655 2655 for iterrev in chain:
2656 2656 e = index[iterrev]
2657 2657 chainsize += e[1]
2658 2658
2659 2659 return compsize, uncompsize, deltatype, chain, chainsize
2660 2660
2661 2661 fm = ui.formatter('debugdeltachain', opts)
2662 2662
2663 2663 fm.plain(' rev chain# chainlen prev delta '
2664 2664 'size rawsize chainsize ratio lindist extradist '
2665 2665 'extraratio\n')
2666 2666
2667 2667 chainbases = {}
2668 2668 for rev in r:
2669 2669 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
2670 2670 chainbase = chain[0]
2671 2671 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
2672 2672 basestart = r.start(chainbase)
2673 2673 revstart = r.start(rev)
2674 2674 lineardist = revstart + comp - basestart
2675 2675 extradist = lineardist - chainsize
2676 2676 try:
2677 2677 prevrev = chain[-2]
2678 2678 except IndexError:
2679 2679 prevrev = -1
2680 2680
2681 2681 chainratio = float(chainsize) / float(uncomp)
2682 2682 extraratio = float(extradist) / float(chainsize)
2683 2683
2684 2684 fm.startitem()
2685 2685 fm.write('rev chainid chainlen prevrev deltatype compsize '
2686 2686 'uncompsize chainsize chainratio lindist extradist '
2687 2687 'extraratio',
2688 2688 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f\n',
2689 2689 rev, chainid, len(chain), prevrev, deltatype, comp,
2690 2690 uncomp, chainsize, chainratio, lineardist, extradist,
2691 2691 extraratio,
2692 2692 rev=rev, chainid=chainid, chainlen=len(chain),
2693 2693 prevrev=prevrev, deltatype=deltatype, compsize=comp,
2694 2694 uncompsize=uncomp, chainsize=chainsize,
2695 2695 chainratio=chainratio, lindist=lineardist,
2696 2696 extradist=extradist, extraratio=extraratio)
2697 2697
2698 2698 fm.end()
2699 2699
2700 2700 @command('debuginstall', [] + formatteropts, '', norepo=True)
2701 2701 def debuginstall(ui, **opts):
2702 2702 '''test Mercurial installation
2703 2703
2704 2704 Returns 0 on success.
2705 2705 '''
2706 2706
2707 2707 def writetemp(contents):
2708 2708 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
2709 2709 f = os.fdopen(fd, "wb")
2710 2710 f.write(contents)
2711 2711 f.close()
2712 2712 return name
2713 2713
2714 2714 problems = 0
2715 2715
2716 2716 fm = ui.formatter('debuginstall', opts)
2717 2717 fm.startitem()
2718 2718
2719 2719 # encoding
2720 2720 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
2721 2721 err = None
2722 2722 try:
2723 2723 encoding.fromlocal("test")
2724 2724 except error.Abort as inst:
2725 2725 err = inst
2726 2726 problems += 1
2727 2727 fm.condwrite(err, 'encodingerror', _(" %s\n"
2728 2728 " (check that your locale is properly set)\n"), err)
2729 2729
2730 2730 # Python
2731 2731 fm.write('pythonexe', _("checking Python executable (%s)\n"),
2732 2732 sys.executable)
2733 2733 fm.write('pythonver', _("checking Python version (%s)\n"),
2734 2734 ("%s.%s.%s" % sys.version_info[:3]))
2735 2735 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
2736 2736 os.path.dirname(os.__file__))
2737 2737
2738 2738 # hg version
2739 2739 hgver = util.version()
2740 2740 fm.write('hgver', _("checking Mercurial version (%s)\n"),
2741 2741 hgver.split('+')[0])
2742 2742 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
2743 2743 '+'.join(hgver.split('+')[1:]))
2744 2744
2745 2745 # compiled modules
2746 2746 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
2747 2747 policy.policy)
2748 2748 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
2749 2749 os.path.dirname(__file__))
2750 2750
2751 2751 err = None
2752 2752 try:
2753 2753 from . import (
2754 2754 base85,
2755 2755 bdiff,
2756 2756 mpatch,
2757 2757 osutil,
2758 2758 )
2759 2759 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2760 2760 except Exception as inst:
2761 2761 err = inst
2762 2762 problems += 1
2763 2763 fm.condwrite(err, 'extensionserror', " %s\n", err)
2764 2764
2765 2765 # templates
2766 2766 p = templater.templatepaths()
2767 2767 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
2768 2768 fm.condwrite(not p, '', _(" no template directories found\n"))
2769 2769 if p:
2770 2770 m = templater.templatepath("map-cmdline.default")
2771 2771 if m:
2772 2772 # template found, check if it is working
2773 2773 err = None
2774 2774 try:
2775 2775 templater.templater.frommapfile(m)
2776 2776 except Exception as inst:
2777 2777 err = inst
2778 2778 p = None
2779 2779 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
2780 2780 else:
2781 2781 p = None
2782 2782 fm.condwrite(p, 'defaulttemplate',
2783 2783 _("checking default template (%s)\n"), m)
2784 2784 fm.condwrite(not m, 'defaulttemplatenotfound',
2785 2785 _(" template '%s' not found\n"), "default")
2786 2786 if not p:
2787 2787 problems += 1
2788 2788 fm.condwrite(not p, '',
2789 2789 _(" (templates seem to have been installed incorrectly)\n"))
2790 2790
2791 2791 # editor
2792 2792 editor = ui.geteditor()
2793 2793 editor = util.expandpath(editor)
2794 2794 fm.write('editor', _("checking commit editor... (%s)\n"), editor)
2795 2795 cmdpath = util.findexe(shlex.split(editor)[0])
2796 2796 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
2797 2797 _(" No commit editor set and can't find %s in PATH\n"
2798 2798 " (specify a commit editor in your configuration"
2799 2799 " file)\n"), not cmdpath and editor == 'vi' and editor)
2800 2800 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
2801 2801 _(" Can't find editor '%s' in PATH\n"
2802 2802 " (specify a commit editor in your configuration"
2803 2803 " file)\n"), not cmdpath and editor)
2804 2804 if not cmdpath and editor != 'vi':
2805 2805 problems += 1
2806 2806
2807 2807 # check username
2808 2808 username = None
2809 2809 err = None
2810 2810 try:
2811 2811 username = ui.username()
2812 2812 except error.Abort as e:
2813 2813 err = e
2814 2814 problems += 1
2815 2815
2816 2816 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
2817 2817 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
2818 2818 " (specify a username in your configuration file)\n"), err)
2819 2819
2820 2820 fm.condwrite(not problems, '',
2821 2821 _("no problems detected\n"))
2822 2822 if not problems:
2823 2823 fm.data(problems=problems)
2824 2824 fm.condwrite(problems, 'problems',
2825 2825 _("%s problems detected,"
2826 2826 " please check your install!\n"), problems)
2827 2827 fm.end()
2828 2828
2829 2829 return problems
2830 2830
2831 2831 @command('debugknown', [], _('REPO ID...'), norepo=True)
2832 2832 def debugknown(ui, repopath, *ids, **opts):
2833 2833 """test whether node ids are known to a repo
2834 2834
2835 2835 Every ID must be a full-length hex node id string. Returns a list of 0s
2836 2836 and 1s indicating unknown/known.
2837 2837 """
2838 2838 repo = hg.peer(ui, opts, repopath)
2839 2839 if not repo.capable('known'):
2840 2840 raise error.Abort("known() not supported by target repository")
2841 2841 flags = repo.known([bin(s) for s in ids])
2842 2842 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2843 2843
2844 2844 @command('debuglabelcomplete', [], _('LABEL...'))
2845 2845 def debuglabelcomplete(ui, repo, *args):
2846 2846 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
2847 2847 debugnamecomplete(ui, repo, *args)
2848 2848
2849 2849 @command('debugmergestate', [], '')
2850 2850 def debugmergestate(ui, repo, *args):
2851 2851 """print merge state
2852 2852
2853 2853 Use --verbose to print out information about whether v1 or v2 merge state
2854 2854 was chosen."""
2855 2855 def _hashornull(h):
2856 2856 if h == nullhex:
2857 2857 return 'null'
2858 2858 else:
2859 2859 return h
2860 2860
2861 2861 def printrecords(version):
2862 2862 ui.write(('* version %s records\n') % version)
2863 2863 if version == 1:
2864 2864 records = v1records
2865 2865 else:
2866 2866 records = v2records
2867 2867
2868 2868 for rtype, record in records:
2869 2869 # pretty print some record types
2870 2870 if rtype == 'L':
2871 2871 ui.write(('local: %s\n') % record)
2872 2872 elif rtype == 'O':
2873 2873 ui.write(('other: %s\n') % record)
2874 2874 elif rtype == 'm':
2875 2875 driver, mdstate = record.split('\0', 1)
2876 2876 ui.write(('merge driver: %s (state "%s")\n')
2877 2877 % (driver, mdstate))
2878 2878 elif rtype in 'FDC':
2879 2879 r = record.split('\0')
2880 2880 f, state, hash, lfile, afile, anode, ofile = r[0:7]
2881 2881 if version == 1:
2882 2882 onode = 'not stored in v1 format'
2883 2883 flags = r[7]
2884 2884 else:
2885 2885 onode, flags = r[7:9]
2886 2886 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
2887 2887 % (f, rtype, state, _hashornull(hash)))
2888 2888 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
2889 2889 ui.write((' ancestor path: %s (node %s)\n')
2890 2890 % (afile, _hashornull(anode)))
2891 2891 ui.write((' other path: %s (node %s)\n')
2892 2892 % (ofile, _hashornull(onode)))
2893 2893 elif rtype == 'f':
2894 2894 filename, rawextras = record.split('\0', 1)
2895 2895 extras = rawextras.split('\0')
2896 2896 i = 0
2897 2897 extrastrings = []
2898 2898 while i < len(extras):
2899 2899 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
2900 2900 i += 2
2901 2901
2902 2902 ui.write(('file extras: %s (%s)\n')
2903 2903 % (filename, ', '.join(extrastrings)))
2904 2904 elif rtype == 'l':
2905 2905 labels = record.split('\0', 2)
2906 2906 labels = [l for l in labels if len(l) > 0]
2907 2907 ui.write(('labels:\n'))
2908 2908 ui.write((' local: %s\n' % labels[0]))
2909 2909 ui.write((' other: %s\n' % labels[1]))
2910 2910 if len(labels) > 2:
2911 2911 ui.write((' base: %s\n' % labels[2]))
2912 2912 else:
2913 2913 ui.write(('unrecognized entry: %s\t%s\n')
2914 2914 % (rtype, record.replace('\0', '\t')))
2915 2915
2916 2916 # Avoid mergestate.read() since it may raise an exception for unsupported
2917 2917 # merge state records. We shouldn't be doing this, but this is OK since this
2918 2918 # command is pretty low-level.
2919 2919 ms = mergemod.mergestate(repo)
2920 2920
2921 2921 # sort so that reasonable information is on top
2922 2922 v1records = ms._readrecordsv1()
2923 2923 v2records = ms._readrecordsv2()
2924 2924 order = 'LOml'
2925 2925 def key(r):
2926 2926 idx = order.find(r[0])
2927 2927 if idx == -1:
2928 2928 return (1, r[1])
2929 2929 else:
2930 2930 return (0, idx)
2931 2931 v1records.sort(key=key)
2932 2932 v2records.sort(key=key)
2933 2933
2934 2934 if not v1records and not v2records:
2935 2935 ui.write(('no merge state found\n'))
2936 2936 elif not v2records:
2937 2937 ui.note(('no version 2 merge state\n'))
2938 2938 printrecords(1)
2939 2939 elif ms._v1v2match(v1records, v2records):
2940 2940 ui.note(('v1 and v2 states match: using v2\n'))
2941 2941 printrecords(2)
2942 2942 else:
2943 2943 ui.note(('v1 and v2 states mismatch: using v1\n'))
2944 2944 printrecords(1)
2945 2945 if ui.verbose:
2946 2946 printrecords(2)
2947 2947
2948 2948 @command('debugnamecomplete', [], _('NAME...'))
2949 2949 def debugnamecomplete(ui, repo, *args):
2950 2950 '''complete "names" - tags, open branch names, bookmark names'''
2951 2951
2952 2952 names = set()
2953 2953 # since we previously only listed open branches, we will handle that
2954 2954 # specially (after this for loop)
2955 2955 for name, ns in repo.names.iteritems():
2956 2956 if name != 'branches':
2957 2957 names.update(ns.listnames(repo))
2958 2958 names.update(tag for (tag, heads, tip, closed)
2959 2959 in repo.branchmap().iterbranches() if not closed)
2960 2960 completions = set()
2961 2961 if not args:
2962 2962 args = ['']
2963 2963 for a in args:
2964 2964 completions.update(n for n in names if n.startswith(a))
2965 2965 ui.write('\n'.join(sorted(completions)))
2966 2966 ui.write('\n')
2967 2967
2968 2968 @command('debuglocks',
2969 2969 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
2970 2970 ('W', 'force-wlock', None,
2971 2971 _('free the working state lock (DANGEROUS)'))],
2972 2972 _('[OPTION]...'))
2973 2973 def debuglocks(ui, repo, **opts):
2974 2974 """show or modify state of locks
2975 2975
2976 2976 By default, this command will show which locks are held. This
2977 2977 includes the user and process holding the lock, the amount of time
2978 2978 the lock has been held, and the machine name where the process is
2979 2979 running if it's not local.
2980 2980
2981 2981 Locks protect the integrity of Mercurial's data, so should be
2982 2982 treated with care. System crashes or other interruptions may cause
2983 2983 locks to not be properly released, though Mercurial will usually
2984 2984 detect and remove such stale locks automatically.
2985 2985
2986 2986 However, detecting stale locks may not always be possible (for
2987 2987 instance, on a shared filesystem). Removing locks may also be
2988 2988 blocked by filesystem permissions.
2989 2989
2990 2990 Returns 0 if no locks are held.
2991 2991
2992 2992 """
2993 2993
2994 2994 if opts.get('force_lock'):
2995 2995 repo.svfs.unlink('lock')
2996 2996 if opts.get('force_wlock'):
2997 2997 repo.vfs.unlink('wlock')
2998 2998 if opts.get('force_lock') or opts.get('force_lock'):
2999 2999 return 0
3000 3000
3001 3001 now = time.time()
3002 3002 held = 0
3003 3003
3004 3004 def report(vfs, name, method):
3005 3005 # this causes stale locks to get reaped for more accurate reporting
3006 3006 try:
3007 3007 l = method(False)
3008 3008 except error.LockHeld:
3009 3009 l = None
3010 3010
3011 3011 if l:
3012 3012 l.release()
3013 3013 else:
3014 3014 try:
3015 3015 stat = vfs.lstat(name)
3016 3016 age = now - stat.st_mtime
3017 3017 user = util.username(stat.st_uid)
3018 3018 locker = vfs.readlock(name)
3019 3019 if ":" in locker:
3020 3020 host, pid = locker.split(':')
3021 3021 if host == socket.gethostname():
3022 3022 locker = 'user %s, process %s' % (user, pid)
3023 3023 else:
3024 3024 locker = 'user %s, process %s, host %s' \
3025 3025 % (user, pid, host)
3026 3026 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
3027 3027 return 1
3028 3028 except OSError as e:
3029 3029 if e.errno != errno.ENOENT:
3030 3030 raise
3031 3031
3032 3032 ui.write(("%-6s free\n") % (name + ":"))
3033 3033 return 0
3034 3034
3035 3035 held += report(repo.svfs, "lock", repo.lock)
3036 3036 held += report(repo.vfs, "wlock", repo.wlock)
3037 3037
3038 3038 return held
3039 3039
3040 3040 @command('debugobsolete',
3041 3041 [('', 'flags', 0, _('markers flag')),
3042 3042 ('', 'record-parents', False,
3043 3043 _('record parent information for the precursor')),
3044 3044 ('r', 'rev', [], _('display markers relevant to REV')),
3045 3045 ('', 'index', False, _('display index of the marker')),
3046 3046 ('', 'delete', [], _('delete markers specified by indices')),
3047 ] + commitopts2,
3047 ] + commitopts2 + formatteropts,
3048 3048 _('[OBSOLETED [REPLACEMENT ...]]'))
3049 3049 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
3050 3050 """create arbitrary obsolete marker
3051 3051
3052 3052 With no arguments, displays the list of obsolescence markers."""
3053 3053
3054 3054 def parsenodeid(s):
3055 3055 try:
3056 3056 # We do not use revsingle/revrange functions here to accept
3057 3057 # arbitrary node identifiers, possibly not present in the
3058 3058 # local repository.
3059 3059 n = bin(s)
3060 3060 if len(n) != len(nullid):
3061 3061 raise TypeError()
3062 3062 return n
3063 3063 except TypeError:
3064 3064 raise error.Abort('changeset references must be full hexadecimal '
3065 3065 'node identifiers')
3066 3066
3067 3067 if opts.get('delete'):
3068 3068 indices = []
3069 3069 for v in opts.get('delete'):
3070 3070 try:
3071 3071 indices.append(int(v))
3072 3072 except ValueError:
3073 3073 raise error.Abort(_('invalid index value: %r') % v,
3074 3074 hint=_('use integers for indices'))
3075 3075
3076 3076 if repo.currenttransaction():
3077 3077 raise error.Abort(_('cannot delete obsmarkers in the middle '
3078 3078 'of transaction.'))
3079 3079
3080 3080 with repo.lock():
3081 3081 n = repair.deleteobsmarkers(repo.obsstore, indices)
3082 3082 ui.write(_('deleted %i obsolescense markers\n') % n)
3083 3083
3084 3084 return
3085 3085
3086 3086 if precursor is not None:
3087 3087 if opts['rev']:
3088 3088 raise error.Abort('cannot select revision when creating marker')
3089 3089 metadata = {}
3090 3090 metadata['user'] = opts['user'] or ui.username()
3091 3091 succs = tuple(parsenodeid(succ) for succ in successors)
3092 3092 l = repo.lock()
3093 3093 try:
3094 3094 tr = repo.transaction('debugobsolete')
3095 3095 try:
3096 3096 date = opts.get('date')
3097 3097 if date:
3098 3098 date = util.parsedate(date)
3099 3099 else:
3100 3100 date = None
3101 3101 prec = parsenodeid(precursor)
3102 3102 parents = None
3103 3103 if opts['record_parents']:
3104 3104 if prec not in repo.unfiltered():
3105 3105 raise error.Abort('cannot used --record-parents on '
3106 3106 'unknown changesets')
3107 3107 parents = repo.unfiltered()[prec].parents()
3108 3108 parents = tuple(p.node() for p in parents)
3109 3109 repo.obsstore.create(tr, prec, succs, opts['flags'],
3110 3110 parents=parents, date=date,
3111 3111 metadata=metadata)
3112 3112 tr.close()
3113 3113 except ValueError as exc:
3114 3114 raise error.Abort(_('bad obsmarker input: %s') % exc)
3115 3115 finally:
3116 3116 tr.release()
3117 3117 finally:
3118 3118 l.release()
3119 3119 else:
3120 3120 if opts['rev']:
3121 3121 revs = scmutil.revrange(repo, opts['rev'])
3122 3122 nodes = [repo[r].node() for r in revs]
3123 3123 markers = list(obsolete.getmarkers(repo, nodes=nodes))
3124 3124 markers.sort(key=lambda x: x._data)
3125 3125 else:
3126 3126 markers = obsolete.getmarkers(repo)
3127 3127
3128 3128 markerstoiter = markers
3129 3129 isrelevant = lambda m: True
3130 3130 if opts.get('rev') and opts.get('index'):
3131 3131 markerstoiter = obsolete.getmarkers(repo)
3132 3132 markerset = set(markers)
3133 3133 isrelevant = lambda m: m in markerset
3134 3134
3135 fm = ui.formatter('debugobsolete', opts)
3135 3136 for i, m in enumerate(markerstoiter):
3136 3137 if not isrelevant(m):
3137 3138 # marker can be irrelevant when we're iterating over a set
3138 3139 # of markers (markerstoiter) which is bigger than the set
3139 3140 # of markers we want to display (markers)
3140 3141 # this can happen if both --index and --rev options are
3141 3142 # provided and thus we need to iterate over all of the markers
3142 3143 # to get the correct indices, but only display the ones that
3143 3144 # are relevant to --rev value
3144 3145 continue
3146 fm.startitem()
3145 3147 ind = i if opts.get('index') else None
3146 cmdutil.showmarker(ui, m, index=ind)
3148 cmdutil.showmarker(fm, m, index=ind)
3149 fm.end()
3147 3150
3148 3151 @command('debugpathcomplete',
3149 3152 [('f', 'full', None, _('complete an entire path')),
3150 3153 ('n', 'normal', None, _('show only normal files')),
3151 3154 ('a', 'added', None, _('show only added files')),
3152 3155 ('r', 'removed', None, _('show only removed files'))],
3153 3156 _('FILESPEC...'))
3154 3157 def debugpathcomplete(ui, repo, *specs, **opts):
3155 3158 '''complete part or all of a tracked path
3156 3159
3157 3160 This command supports shells that offer path name completion. It
3158 3161 currently completes only files already known to the dirstate.
3159 3162
3160 3163 Completion extends only to the next path segment unless
3161 3164 --full is specified, in which case entire paths are used.'''
3162 3165
3163 3166 def complete(path, acceptable):
3164 3167 dirstate = repo.dirstate
3165 3168 spec = os.path.normpath(os.path.join(os.getcwd(), path))
3166 3169 rootdir = repo.root + os.sep
3167 3170 if spec != repo.root and not spec.startswith(rootdir):
3168 3171 return [], []
3169 3172 if os.path.isdir(spec):
3170 3173 spec += '/'
3171 3174 spec = spec[len(rootdir):]
3172 3175 fixpaths = os.sep != '/'
3173 3176 if fixpaths:
3174 3177 spec = spec.replace(os.sep, '/')
3175 3178 speclen = len(spec)
3176 3179 fullpaths = opts['full']
3177 3180 files, dirs = set(), set()
3178 3181 adddir, addfile = dirs.add, files.add
3179 3182 for f, st in dirstate.iteritems():
3180 3183 if f.startswith(spec) and st[0] in acceptable:
3181 3184 if fixpaths:
3182 3185 f = f.replace('/', os.sep)
3183 3186 if fullpaths:
3184 3187 addfile(f)
3185 3188 continue
3186 3189 s = f.find(os.sep, speclen)
3187 3190 if s >= 0:
3188 3191 adddir(f[:s])
3189 3192 else:
3190 3193 addfile(f)
3191 3194 return files, dirs
3192 3195
3193 3196 acceptable = ''
3194 3197 if opts['normal']:
3195 3198 acceptable += 'nm'
3196 3199 if opts['added']:
3197 3200 acceptable += 'a'
3198 3201 if opts['removed']:
3199 3202 acceptable += 'r'
3200 3203 cwd = repo.getcwd()
3201 3204 if not specs:
3202 3205 specs = ['.']
3203 3206
3204 3207 files, dirs = set(), set()
3205 3208 for spec in specs:
3206 3209 f, d = complete(spec, acceptable or 'nmar')
3207 3210 files.update(f)
3208 3211 dirs.update(d)
3209 3212 files.update(dirs)
3210 3213 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
3211 3214 ui.write('\n')
3212 3215
3213 3216 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
3214 3217 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
3215 3218 '''access the pushkey key/value protocol
3216 3219
3217 3220 With two args, list the keys in the given namespace.
3218 3221
3219 3222 With five args, set a key to new if it currently is set to old.
3220 3223 Reports success or failure.
3221 3224 '''
3222 3225
3223 3226 target = hg.peer(ui, {}, repopath)
3224 3227 if keyinfo:
3225 3228 key, old, new = keyinfo
3226 3229 r = target.pushkey(namespace, key, old, new)
3227 3230 ui.status(str(r) + '\n')
3228 3231 return not r
3229 3232 else:
3230 3233 for k, v in sorted(target.listkeys(namespace).iteritems()):
3231 3234 ui.write("%s\t%s\n" % (k.encode('string-escape'),
3232 3235 v.encode('string-escape')))
3233 3236
3234 3237 @command('debugpvec', [], _('A B'))
3235 3238 def debugpvec(ui, repo, a, b=None):
3236 3239 ca = scmutil.revsingle(repo, a)
3237 3240 cb = scmutil.revsingle(repo, b)
3238 3241 pa = pvec.ctxpvec(ca)
3239 3242 pb = pvec.ctxpvec(cb)
3240 3243 if pa == pb:
3241 3244 rel = "="
3242 3245 elif pa > pb:
3243 3246 rel = ">"
3244 3247 elif pa < pb:
3245 3248 rel = "<"
3246 3249 elif pa | pb:
3247 3250 rel = "|"
3248 3251 ui.write(_("a: %s\n") % pa)
3249 3252 ui.write(_("b: %s\n") % pb)
3250 3253 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
3251 3254 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
3252 3255 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
3253 3256 pa.distance(pb), rel))
3254 3257
3255 3258 @command('debugrebuilddirstate|debugrebuildstate',
3256 3259 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
3257 3260 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
3258 3261 'the working copy parent')),
3259 3262 ],
3260 3263 _('[-r REV]'))
3261 3264 def debugrebuilddirstate(ui, repo, rev, **opts):
3262 3265 """rebuild the dirstate as it would look like for the given revision
3263 3266
3264 3267 If no revision is specified the first current parent will be used.
3265 3268
3266 3269 The dirstate will be set to the files of the given revision.
3267 3270 The actual working directory content or existing dirstate
3268 3271 information such as adds or removes is not considered.
3269 3272
3270 3273 ``minimal`` will only rebuild the dirstate status for files that claim to be
3271 3274 tracked but are not in the parent manifest, or that exist in the parent
3272 3275 manifest but are not in the dirstate. It will not change adds, removes, or
3273 3276 modified files that are in the working copy parent.
3274 3277
3275 3278 One use of this command is to make the next :hg:`status` invocation
3276 3279 check the actual file content.
3277 3280 """
3278 3281 ctx = scmutil.revsingle(repo, rev)
3279 3282 with repo.wlock():
3280 3283 dirstate = repo.dirstate
3281 3284 changedfiles = None
3282 3285 # See command doc for what minimal does.
3283 3286 if opts.get('minimal'):
3284 3287 manifestfiles = set(ctx.manifest().keys())
3285 3288 dirstatefiles = set(dirstate)
3286 3289 manifestonly = manifestfiles - dirstatefiles
3287 3290 dsonly = dirstatefiles - manifestfiles
3288 3291 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
3289 3292 changedfiles = manifestonly | dsnotadded
3290 3293
3291 3294 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
3292 3295
3293 3296 @command('debugrebuildfncache', [], '')
3294 3297 def debugrebuildfncache(ui, repo):
3295 3298 """rebuild the fncache file"""
3296 3299 repair.rebuildfncache(ui, repo)
3297 3300
3298 3301 @command('debugrename',
3299 3302 [('r', 'rev', '', _('revision to debug'), _('REV'))],
3300 3303 _('[-r REV] FILE'))
3301 3304 def debugrename(ui, repo, file1, *pats, **opts):
3302 3305 """dump rename information"""
3303 3306
3304 3307 ctx = scmutil.revsingle(repo, opts.get('rev'))
3305 3308 m = scmutil.match(ctx, (file1,) + pats, opts)
3306 3309 for abs in ctx.walk(m):
3307 3310 fctx = ctx[abs]
3308 3311 o = fctx.filelog().renamed(fctx.filenode())
3309 3312 rel = m.rel(abs)
3310 3313 if o:
3311 3314 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
3312 3315 else:
3313 3316 ui.write(_("%s not renamed\n") % rel)
3314 3317
3315 3318 @command('debugrevlog', debugrevlogopts +
3316 3319 [('d', 'dump', False, _('dump index data'))],
3317 3320 _('-c|-m|FILE'),
3318 3321 optionalrepo=True)
3319 3322 def debugrevlog(ui, repo, file_=None, **opts):
3320 3323 """show data and statistics about a revlog"""
3321 3324 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
3322 3325
3323 3326 if opts.get("dump"):
3324 3327 numrevs = len(r)
3325 3328 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
3326 3329 " rawsize totalsize compression heads chainlen\n"))
3327 3330 ts = 0
3328 3331 heads = set()
3329 3332
3330 3333 for rev in xrange(numrevs):
3331 3334 dbase = r.deltaparent(rev)
3332 3335 if dbase == -1:
3333 3336 dbase = rev
3334 3337 cbase = r.chainbase(rev)
3335 3338 clen = r.chainlen(rev)
3336 3339 p1, p2 = r.parentrevs(rev)
3337 3340 rs = r.rawsize(rev)
3338 3341 ts = ts + rs
3339 3342 heads -= set(r.parentrevs(rev))
3340 3343 heads.add(rev)
3341 3344 try:
3342 3345 compression = ts / r.end(rev)
3343 3346 except ZeroDivisionError:
3344 3347 compression = 0
3345 3348 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
3346 3349 "%11d %5d %8d\n" %
3347 3350 (rev, p1, p2, r.start(rev), r.end(rev),
3348 3351 r.start(dbase), r.start(cbase),
3349 3352 r.start(p1), r.start(p2),
3350 3353 rs, ts, compression, len(heads), clen))
3351 3354 return 0
3352 3355
3353 3356 v = r.version
3354 3357 format = v & 0xFFFF
3355 3358 flags = []
3356 3359 gdelta = False
3357 3360 if v & revlog.REVLOGNGINLINEDATA:
3358 3361 flags.append('inline')
3359 3362 if v & revlog.REVLOGGENERALDELTA:
3360 3363 gdelta = True
3361 3364 flags.append('generaldelta')
3362 3365 if not flags:
3363 3366 flags = ['(none)']
3364 3367
3365 3368 nummerges = 0
3366 3369 numfull = 0
3367 3370 numprev = 0
3368 3371 nump1 = 0
3369 3372 nump2 = 0
3370 3373 numother = 0
3371 3374 nump1prev = 0
3372 3375 nump2prev = 0
3373 3376 chainlengths = []
3374 3377
3375 3378 datasize = [None, 0, 0L]
3376 3379 fullsize = [None, 0, 0L]
3377 3380 deltasize = [None, 0, 0L]
3378 3381
3379 3382 def addsize(size, l):
3380 3383 if l[0] is None or size < l[0]:
3381 3384 l[0] = size
3382 3385 if size > l[1]:
3383 3386 l[1] = size
3384 3387 l[2] += size
3385 3388
3386 3389 numrevs = len(r)
3387 3390 for rev in xrange(numrevs):
3388 3391 p1, p2 = r.parentrevs(rev)
3389 3392 delta = r.deltaparent(rev)
3390 3393 if format > 0:
3391 3394 addsize(r.rawsize(rev), datasize)
3392 3395 if p2 != nullrev:
3393 3396 nummerges += 1
3394 3397 size = r.length(rev)
3395 3398 if delta == nullrev:
3396 3399 chainlengths.append(0)
3397 3400 numfull += 1
3398 3401 addsize(size, fullsize)
3399 3402 else:
3400 3403 chainlengths.append(chainlengths[delta] + 1)
3401 3404 addsize(size, deltasize)
3402 3405 if delta == rev - 1:
3403 3406 numprev += 1
3404 3407 if delta == p1:
3405 3408 nump1prev += 1
3406 3409 elif delta == p2:
3407 3410 nump2prev += 1
3408 3411 elif delta == p1:
3409 3412 nump1 += 1
3410 3413 elif delta == p2:
3411 3414 nump2 += 1
3412 3415 elif delta != nullrev:
3413 3416 numother += 1
3414 3417
3415 3418 # Adjust size min value for empty cases
3416 3419 for size in (datasize, fullsize, deltasize):
3417 3420 if size[0] is None:
3418 3421 size[0] = 0
3419 3422
3420 3423 numdeltas = numrevs - numfull
3421 3424 numoprev = numprev - nump1prev - nump2prev
3422 3425 totalrawsize = datasize[2]
3423 3426 datasize[2] /= numrevs
3424 3427 fulltotal = fullsize[2]
3425 3428 fullsize[2] /= numfull
3426 3429 deltatotal = deltasize[2]
3427 3430 if numrevs - numfull > 0:
3428 3431 deltasize[2] /= numrevs - numfull
3429 3432 totalsize = fulltotal + deltatotal
3430 3433 avgchainlen = sum(chainlengths) / numrevs
3431 3434 maxchainlen = max(chainlengths)
3432 3435 compratio = 1
3433 3436 if totalsize:
3434 3437 compratio = totalrawsize / totalsize
3435 3438
3436 3439 basedfmtstr = '%%%dd\n'
3437 3440 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
3438 3441
3439 3442 def dfmtstr(max):
3440 3443 return basedfmtstr % len(str(max))
3441 3444 def pcfmtstr(max, padding=0):
3442 3445 return basepcfmtstr % (len(str(max)), ' ' * padding)
3443 3446
3444 3447 def pcfmt(value, total):
3445 3448 if total:
3446 3449 return (value, 100 * float(value) / total)
3447 3450 else:
3448 3451 return value, 100.0
3449 3452
3450 3453 ui.write(('format : %d\n') % format)
3451 3454 ui.write(('flags : %s\n') % ', '.join(flags))
3452 3455
3453 3456 ui.write('\n')
3454 3457 fmt = pcfmtstr(totalsize)
3455 3458 fmt2 = dfmtstr(totalsize)
3456 3459 ui.write(('revisions : ') + fmt2 % numrevs)
3457 3460 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
3458 3461 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
3459 3462 ui.write(('revisions : ') + fmt2 % numrevs)
3460 3463 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
3461 3464 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
3462 3465 ui.write(('revision size : ') + fmt2 % totalsize)
3463 3466 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
3464 3467 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
3465 3468
3466 3469 ui.write('\n')
3467 3470 fmt = dfmtstr(max(avgchainlen, compratio))
3468 3471 ui.write(('avg chain length : ') + fmt % avgchainlen)
3469 3472 ui.write(('max chain length : ') + fmt % maxchainlen)
3470 3473 ui.write(('compression ratio : ') + fmt % compratio)
3471 3474
3472 3475 if format > 0:
3473 3476 ui.write('\n')
3474 3477 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
3475 3478 % tuple(datasize))
3476 3479 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
3477 3480 % tuple(fullsize))
3478 3481 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
3479 3482 % tuple(deltasize))
3480 3483
3481 3484 if numdeltas > 0:
3482 3485 ui.write('\n')
3483 3486 fmt = pcfmtstr(numdeltas)
3484 3487 fmt2 = pcfmtstr(numdeltas, 4)
3485 3488 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
3486 3489 if numprev > 0:
3487 3490 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
3488 3491 numprev))
3489 3492 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
3490 3493 numprev))
3491 3494 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
3492 3495 numprev))
3493 3496 if gdelta:
3494 3497 ui.write(('deltas against p1 : ')
3495 3498 + fmt % pcfmt(nump1, numdeltas))
3496 3499 ui.write(('deltas against p2 : ')
3497 3500 + fmt % pcfmt(nump2, numdeltas))
3498 3501 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
3499 3502 numdeltas))
3500 3503
3501 3504 @command('debugrevspec',
3502 3505 [('', 'optimize', None, _('print parsed tree after optimizing'))],
3503 3506 ('REVSPEC'))
3504 3507 def debugrevspec(ui, repo, expr, **opts):
3505 3508 """parse and apply a revision specification
3506 3509
3507 3510 Use --verbose to print the parsed tree before and after aliases
3508 3511 expansion.
3509 3512 """
3510 3513 if ui.verbose:
3511 3514 tree = revset.parse(expr, lookup=repo.__contains__)
3512 3515 ui.note(revset.prettyformat(tree), "\n")
3513 3516 newtree = revset.expandaliases(ui, tree)
3514 3517 if newtree != tree:
3515 3518 ui.note(("* expanded:\n"), revset.prettyformat(newtree), "\n")
3516 3519 tree = newtree
3517 3520 newtree = revset.foldconcat(tree)
3518 3521 if newtree != tree:
3519 3522 ui.note(("* concatenated:\n"), revset.prettyformat(newtree), "\n")
3520 3523 if opts["optimize"]:
3521 3524 optimizedtree = revset.optimize(newtree)
3522 3525 ui.note(("* optimized:\n"),
3523 3526 revset.prettyformat(optimizedtree), "\n")
3524 3527 func = revset.match(ui, expr, repo)
3525 3528 revs = func(repo)
3526 3529 if ui.verbose:
3527 3530 ui.note(("* set:\n"), revset.prettyformatset(revs), "\n")
3528 3531 for c in revs:
3529 3532 ui.write("%s\n" % c)
3530 3533
3531 3534 @command('debugsetparents', [], _('REV1 [REV2]'))
3532 3535 def debugsetparents(ui, repo, rev1, rev2=None):
3533 3536 """manually set the parents of the current working directory
3534 3537
3535 3538 This is useful for writing repository conversion tools, but should
3536 3539 be used with care. For example, neither the working directory nor the
3537 3540 dirstate is updated, so file status may be incorrect after running this
3538 3541 command.
3539 3542
3540 3543 Returns 0 on success.
3541 3544 """
3542 3545
3543 3546 r1 = scmutil.revsingle(repo, rev1).node()
3544 3547 r2 = scmutil.revsingle(repo, rev2, 'null').node()
3545 3548
3546 3549 with repo.wlock():
3547 3550 repo.setparents(r1, r2)
3548 3551
3549 3552 @command('debugdirstate|debugstate',
3550 3553 [('', 'nodates', None, _('do not display the saved mtime')),
3551 3554 ('', 'datesort', None, _('sort by saved mtime'))],
3552 3555 _('[OPTION]...'))
3553 3556 def debugstate(ui, repo, **opts):
3554 3557 """show the contents of the current dirstate"""
3555 3558
3556 3559 nodates = opts.get('nodates')
3557 3560 datesort = opts.get('datesort')
3558 3561
3559 3562 timestr = ""
3560 3563 if datesort:
3561 3564 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
3562 3565 else:
3563 3566 keyfunc = None # sort by filename
3564 3567 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
3565 3568 if ent[3] == -1:
3566 3569 timestr = 'unset '
3567 3570 elif nodates:
3568 3571 timestr = 'set '
3569 3572 else:
3570 3573 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
3571 3574 time.localtime(ent[3]))
3572 3575 if ent[1] & 0o20000:
3573 3576 mode = 'lnk'
3574 3577 else:
3575 3578 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
3576 3579 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
3577 3580 for f in repo.dirstate.copies():
3578 3581 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
3579 3582
3580 3583 @command('debugsub',
3581 3584 [('r', 'rev', '',
3582 3585 _('revision to check'), _('REV'))],
3583 3586 _('[-r REV] [REV]'))
3584 3587 def debugsub(ui, repo, rev=None):
3585 3588 ctx = scmutil.revsingle(repo, rev, None)
3586 3589 for k, v in sorted(ctx.substate.items()):
3587 3590 ui.write(('path %s\n') % k)
3588 3591 ui.write((' source %s\n') % v[0])
3589 3592 ui.write((' revision %s\n') % v[1])
3590 3593
3591 3594 @command('debugsuccessorssets',
3592 3595 [],
3593 3596 _('[REV]'))
3594 3597 def debugsuccessorssets(ui, repo, *revs):
3595 3598 """show set of successors for revision
3596 3599
3597 3600 A successors set of changeset A is a consistent group of revisions that
3598 3601 succeed A. It contains non-obsolete changesets only.
3599 3602
3600 3603 In most cases a changeset A has a single successors set containing a single
3601 3604 successor (changeset A replaced by A').
3602 3605
3603 3606 A changeset that is made obsolete with no successors are called "pruned".
3604 3607 Such changesets have no successors sets at all.
3605 3608
3606 3609 A changeset that has been "split" will have a successors set containing
3607 3610 more than one successor.
3608 3611
3609 3612 A changeset that has been rewritten in multiple different ways is called
3610 3613 "divergent". Such changesets have multiple successor sets (each of which
3611 3614 may also be split, i.e. have multiple successors).
3612 3615
3613 3616 Results are displayed as follows::
3614 3617
3615 3618 <rev1>
3616 3619 <successors-1A>
3617 3620 <rev2>
3618 3621 <successors-2A>
3619 3622 <successors-2B1> <successors-2B2> <successors-2B3>
3620 3623
3621 3624 Here rev2 has two possible (i.e. divergent) successors sets. The first
3622 3625 holds one element, whereas the second holds three (i.e. the changeset has
3623 3626 been split).
3624 3627 """
3625 3628 # passed to successorssets caching computation from one call to another
3626 3629 cache = {}
3627 3630 ctx2str = str
3628 3631 node2str = short
3629 3632 if ui.debug():
3630 3633 def ctx2str(ctx):
3631 3634 return ctx.hex()
3632 3635 node2str = hex
3633 3636 for rev in scmutil.revrange(repo, revs):
3634 3637 ctx = repo[rev]
3635 3638 ui.write('%s\n'% ctx2str(ctx))
3636 3639 for succsset in obsolete.successorssets(repo, ctx.node(), cache):
3637 3640 if succsset:
3638 3641 ui.write(' ')
3639 3642 ui.write(node2str(succsset[0]))
3640 3643 for node in succsset[1:]:
3641 3644 ui.write(' ')
3642 3645 ui.write(node2str(node))
3643 3646 ui.write('\n')
3644 3647
3645 3648 @command('debugtemplate',
3646 3649 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
3647 3650 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
3648 3651 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
3649 3652 optionalrepo=True)
3650 3653 def debugtemplate(ui, repo, tmpl, **opts):
3651 3654 """parse and apply a template
3652 3655
3653 3656 If -r/--rev is given, the template is processed as a log template and
3654 3657 applied to the given changesets. Otherwise, it is processed as a generic
3655 3658 template.
3656 3659
3657 3660 Use --verbose to print the parsed tree.
3658 3661 """
3659 3662 revs = None
3660 3663 if opts['rev']:
3661 3664 if repo is None:
3662 3665 raise error.RepoError(_('there is no Mercurial repository here '
3663 3666 '(.hg not found)'))
3664 3667 revs = scmutil.revrange(repo, opts['rev'])
3665 3668
3666 3669 props = {}
3667 3670 for d in opts['define']:
3668 3671 try:
3669 3672 k, v = (e.strip() for e in d.split('=', 1))
3670 3673 if not k:
3671 3674 raise ValueError
3672 3675 props[k] = v
3673 3676 except ValueError:
3674 3677 raise error.Abort(_('malformed keyword definition: %s') % d)
3675 3678
3676 3679 if ui.verbose:
3677 3680 aliases = ui.configitems('templatealias')
3678 3681 tree = templater.parse(tmpl)
3679 3682 ui.note(templater.prettyformat(tree), '\n')
3680 3683 newtree = templater.expandaliases(tree, aliases)
3681 3684 if newtree != tree:
3682 3685 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
3683 3686
3684 3687 mapfile = None
3685 3688 if revs is None:
3686 3689 k = 'debugtemplate'
3687 3690 t = formatter.maketemplater(ui, k, tmpl)
3688 3691 ui.write(templater.stringify(t(k, **props)))
3689 3692 else:
3690 3693 displayer = cmdutil.changeset_templater(ui, repo, None, opts, tmpl,
3691 3694 mapfile, buffered=False)
3692 3695 for r in revs:
3693 3696 displayer.show(repo[r], **props)
3694 3697 displayer.close()
3695 3698
3696 3699 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'), inferrepo=True)
3697 3700 def debugwalk(ui, repo, *pats, **opts):
3698 3701 """show how files match on given patterns"""
3699 3702 m = scmutil.match(repo[None], pats, opts)
3700 3703 items = list(repo.walk(m))
3701 3704 if not items:
3702 3705 return
3703 3706 f = lambda fn: fn
3704 3707 if ui.configbool('ui', 'slash') and os.sep != '/':
3705 3708 f = lambda fn: util.normpath(fn)
3706 3709 fmt = 'f %%-%ds %%-%ds %%s' % (
3707 3710 max([len(abs) for abs in items]),
3708 3711 max([len(m.rel(abs)) for abs in items]))
3709 3712 for abs in items:
3710 3713 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
3711 3714 ui.write("%s\n" % line.rstrip())
3712 3715
3713 3716 @command('debugwireargs',
3714 3717 [('', 'three', '', 'three'),
3715 3718 ('', 'four', '', 'four'),
3716 3719 ('', 'five', '', 'five'),
3717 3720 ] + remoteopts,
3718 3721 _('REPO [OPTIONS]... [ONE [TWO]]'),
3719 3722 norepo=True)
3720 3723 def debugwireargs(ui, repopath, *vals, **opts):
3721 3724 repo = hg.peer(ui, opts, repopath)
3722 3725 for opt in remoteopts:
3723 3726 del opts[opt[1]]
3724 3727 args = {}
3725 3728 for k, v in opts.iteritems():
3726 3729 if v:
3727 3730 args[k] = v
3728 3731 # run twice to check that we don't mess up the stream for the next command
3729 3732 res1 = repo.debugwireargs(*vals, **args)
3730 3733 res2 = repo.debugwireargs(*vals, **args)
3731 3734 ui.write("%s\n" % res1)
3732 3735 if res1 != res2:
3733 3736 ui.warn("%s\n" % res2)
3734 3737
3735 3738 @command('^diff',
3736 3739 [('r', 'rev', [], _('revision'), _('REV')),
3737 3740 ('c', 'change', '', _('change made by revision'), _('REV'))
3738 3741 ] + diffopts + diffopts2 + walkopts + subrepoopts,
3739 3742 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
3740 3743 inferrepo=True)
3741 3744 def diff(ui, repo, *pats, **opts):
3742 3745 """diff repository (or selected files)
3743 3746
3744 3747 Show differences between revisions for the specified files.
3745 3748
3746 3749 Differences between files are shown using the unified diff format.
3747 3750
3748 3751 .. note::
3749 3752
3750 3753 :hg:`diff` may generate unexpected results for merges, as it will
3751 3754 default to comparing against the working directory's first
3752 3755 parent changeset if no revisions are specified.
3753 3756
3754 3757 When two revision arguments are given, then changes are shown
3755 3758 between those revisions. If only one revision is specified then
3756 3759 that revision is compared to the working directory, and, when no
3757 3760 revisions are specified, the working directory files are compared
3758 3761 to its first parent.
3759 3762
3760 3763 Alternatively you can specify -c/--change with a revision to see
3761 3764 the changes in that changeset relative to its first parent.
3762 3765
3763 3766 Without the -a/--text option, diff will avoid generating diffs of
3764 3767 files it detects as binary. With -a, diff will generate a diff
3765 3768 anyway, probably with undesirable results.
3766 3769
3767 3770 Use the -g/--git option to generate diffs in the git extended diff
3768 3771 format. For more information, read :hg:`help diffs`.
3769 3772
3770 3773 .. container:: verbose
3771 3774
3772 3775 Examples:
3773 3776
3774 3777 - compare a file in the current working directory to its parent::
3775 3778
3776 3779 hg diff foo.c
3777 3780
3778 3781 - compare two historical versions of a directory, with rename info::
3779 3782
3780 3783 hg diff --git -r 1.0:1.2 lib/
3781 3784
3782 3785 - get change stats relative to the last change on some date::
3783 3786
3784 3787 hg diff --stat -r "date('may 2')"
3785 3788
3786 3789 - diff all newly-added files that contain a keyword::
3787 3790
3788 3791 hg diff "set:added() and grep(GNU)"
3789 3792
3790 3793 - compare a revision and its parents::
3791 3794
3792 3795 hg diff -c 9353 # compare against first parent
3793 3796 hg diff -r 9353^:9353 # same using revset syntax
3794 3797 hg diff -r 9353^2:9353 # compare against the second parent
3795 3798
3796 3799 Returns 0 on success.
3797 3800 """
3798 3801
3799 3802 revs = opts.get('rev')
3800 3803 change = opts.get('change')
3801 3804 stat = opts.get('stat')
3802 3805 reverse = opts.get('reverse')
3803 3806
3804 3807 if revs and change:
3805 3808 msg = _('cannot specify --rev and --change at the same time')
3806 3809 raise error.Abort(msg)
3807 3810 elif change:
3808 3811 node2 = scmutil.revsingle(repo, change, None).node()
3809 3812 node1 = repo[node2].p1().node()
3810 3813 else:
3811 3814 node1, node2 = scmutil.revpair(repo, revs)
3812 3815
3813 3816 if reverse:
3814 3817 node1, node2 = node2, node1
3815 3818
3816 3819 diffopts = patch.diffallopts(ui, opts)
3817 3820 m = scmutil.match(repo[node2], pats, opts)
3818 3821 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
3819 3822 listsubrepos=opts.get('subrepos'),
3820 3823 root=opts.get('root'))
3821 3824
3822 3825 @command('^export',
3823 3826 [('o', 'output', '',
3824 3827 _('print output to file with formatted name'), _('FORMAT')),
3825 3828 ('', 'switch-parent', None, _('diff against the second parent')),
3826 3829 ('r', 'rev', [], _('revisions to export'), _('REV')),
3827 3830 ] + diffopts,
3828 3831 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
3829 3832 def export(ui, repo, *changesets, **opts):
3830 3833 """dump the header and diffs for one or more changesets
3831 3834
3832 3835 Print the changeset header and diffs for one or more revisions.
3833 3836 If no revision is given, the parent of the working directory is used.
3834 3837
3835 3838 The information shown in the changeset header is: author, date,
3836 3839 branch name (if non-default), changeset hash, parent(s) and commit
3837 3840 comment.
3838 3841
3839 3842 .. note::
3840 3843
3841 3844 :hg:`export` may generate unexpected diff output for merge
3842 3845 changesets, as it will compare the merge changeset against its
3843 3846 first parent only.
3844 3847
3845 3848 Output may be to a file, in which case the name of the file is
3846 3849 given using a format string. The formatting rules are as follows:
3847 3850
3848 3851 :``%%``: literal "%" character
3849 3852 :``%H``: changeset hash (40 hexadecimal digits)
3850 3853 :``%N``: number of patches being generated
3851 3854 :``%R``: changeset revision number
3852 3855 :``%b``: basename of the exporting repository
3853 3856 :``%h``: short-form changeset hash (12 hexadecimal digits)
3854 3857 :``%m``: first line of the commit message (only alphanumeric characters)
3855 3858 :``%n``: zero-padded sequence number, starting at 1
3856 3859 :``%r``: zero-padded changeset revision number
3857 3860
3858 3861 Without the -a/--text option, export will avoid generating diffs
3859 3862 of files it detects as binary. With -a, export will generate a
3860 3863 diff anyway, probably with undesirable results.
3861 3864
3862 3865 Use the -g/--git option to generate diffs in the git extended diff
3863 3866 format. See :hg:`help diffs` for more information.
3864 3867
3865 3868 With the --switch-parent option, the diff will be against the
3866 3869 second parent. It can be useful to review a merge.
3867 3870
3868 3871 .. container:: verbose
3869 3872
3870 3873 Examples:
3871 3874
3872 3875 - use export and import to transplant a bugfix to the current
3873 3876 branch::
3874 3877
3875 3878 hg export -r 9353 | hg import -
3876 3879
3877 3880 - export all the changesets between two revisions to a file with
3878 3881 rename information::
3879 3882
3880 3883 hg export --git -r 123:150 > changes.txt
3881 3884
3882 3885 - split outgoing changes into a series of patches with
3883 3886 descriptive names::
3884 3887
3885 3888 hg export -r "outgoing()" -o "%n-%m.patch"
3886 3889
3887 3890 Returns 0 on success.
3888 3891 """
3889 3892 changesets += tuple(opts.get('rev', []))
3890 3893 if not changesets:
3891 3894 changesets = ['.']
3892 3895 revs = scmutil.revrange(repo, changesets)
3893 3896 if not revs:
3894 3897 raise error.Abort(_("export requires at least one changeset"))
3895 3898 if len(revs) > 1:
3896 3899 ui.note(_('exporting patches:\n'))
3897 3900 else:
3898 3901 ui.note(_('exporting patch:\n'))
3899 3902 cmdutil.export(repo, revs, template=opts.get('output'),
3900 3903 switch_parent=opts.get('switch_parent'),
3901 3904 opts=patch.diffallopts(ui, opts))
3902 3905
3903 3906 @command('files',
3904 3907 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3905 3908 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3906 3909 ] + walkopts + formatteropts + subrepoopts,
3907 3910 _('[OPTION]... [PATTERN]...'))
3908 3911 def files(ui, repo, *pats, **opts):
3909 3912 """list tracked files
3910 3913
3911 3914 Print files under Mercurial control in the working directory or
3912 3915 specified revision whose names match the given patterns (excluding
3913 3916 removed files).
3914 3917
3915 3918 If no patterns are given to match, this command prints the names
3916 3919 of all files under Mercurial control in the working directory.
3917 3920
3918 3921 .. container:: verbose
3919 3922
3920 3923 Examples:
3921 3924
3922 3925 - list all files under the current directory::
3923 3926
3924 3927 hg files .
3925 3928
3926 3929 - shows sizes and flags for current revision::
3927 3930
3928 3931 hg files -vr .
3929 3932
3930 3933 - list all files named README::
3931 3934
3932 3935 hg files -I "**/README"
3933 3936
3934 3937 - list all binary files::
3935 3938
3936 3939 hg files "set:binary()"
3937 3940
3938 3941 - find files containing a regular expression::
3939 3942
3940 3943 hg files "set:grep('bob')"
3941 3944
3942 3945 - search tracked file contents with xargs and grep::
3943 3946
3944 3947 hg files -0 | xargs -0 grep foo
3945 3948
3946 3949 See :hg:`help patterns` and :hg:`help filesets` for more information
3947 3950 on specifying file patterns.
3948 3951
3949 3952 Returns 0 if a match is found, 1 otherwise.
3950 3953
3951 3954 """
3952 3955 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3953 3956
3954 3957 end = '\n'
3955 3958 if opts.get('print0'):
3956 3959 end = '\0'
3957 3960 fm = ui.formatter('files', opts)
3958 3961 fmt = '%s' + end
3959 3962
3960 3963 m = scmutil.match(ctx, pats, opts)
3961 3964 ret = cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
3962 3965
3963 3966 fm.end()
3964 3967
3965 3968 return ret
3966 3969
3967 3970 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
3968 3971 def forget(ui, repo, *pats, **opts):
3969 3972 """forget the specified files on the next commit
3970 3973
3971 3974 Mark the specified files so they will no longer be tracked
3972 3975 after the next commit.
3973 3976
3974 3977 This only removes files from the current branch, not from the
3975 3978 entire project history, and it does not delete them from the
3976 3979 working directory.
3977 3980
3978 3981 To delete the file from the working directory, see :hg:`remove`.
3979 3982
3980 3983 To undo a forget before the next commit, see :hg:`add`.
3981 3984
3982 3985 .. container:: verbose
3983 3986
3984 3987 Examples:
3985 3988
3986 3989 - forget newly-added binary files::
3987 3990
3988 3991 hg forget "set:added() and binary()"
3989 3992
3990 3993 - forget files that would be excluded by .hgignore::
3991 3994
3992 3995 hg forget "set:hgignore()"
3993 3996
3994 3997 Returns 0 on success.
3995 3998 """
3996 3999
3997 4000 if not pats:
3998 4001 raise error.Abort(_('no files specified'))
3999 4002
4000 4003 m = scmutil.match(repo[None], pats, opts)
4001 4004 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
4002 4005 return rejected and 1 or 0
4003 4006
4004 4007 @command(
4005 4008 'graft',
4006 4009 [('r', 'rev', [], _('revisions to graft'), _('REV')),
4007 4010 ('c', 'continue', False, _('resume interrupted graft')),
4008 4011 ('e', 'edit', False, _('invoke editor on commit messages')),
4009 4012 ('', 'log', None, _('append graft info to log message')),
4010 4013 ('f', 'force', False, _('force graft')),
4011 4014 ('D', 'currentdate', False,
4012 4015 _('record the current date as commit date')),
4013 4016 ('U', 'currentuser', False,
4014 4017 _('record the current user as committer'), _('DATE'))]
4015 4018 + commitopts2 + mergetoolopts + dryrunopts,
4016 4019 _('[OPTION]... [-r REV]... REV...'))
4017 4020 def graft(ui, repo, *revs, **opts):
4018 4021 '''copy changes from other branches onto the current branch
4019 4022
4020 4023 This command uses Mercurial's merge logic to copy individual
4021 4024 changes from other branches without merging branches in the
4022 4025 history graph. This is sometimes known as 'backporting' or
4023 4026 'cherry-picking'. By default, graft will copy user, date, and
4024 4027 description from the source changesets.
4025 4028
4026 4029 Changesets that are ancestors of the current revision, that have
4027 4030 already been grafted, or that are merges will be skipped.
4028 4031
4029 4032 If --log is specified, log messages will have a comment appended
4030 4033 of the form::
4031 4034
4032 4035 (grafted from CHANGESETHASH)
4033 4036
4034 4037 If --force is specified, revisions will be grafted even if they
4035 4038 are already ancestors of or have been grafted to the destination.
4036 4039 This is useful when the revisions have since been backed out.
4037 4040
4038 4041 If a graft merge results in conflicts, the graft process is
4039 4042 interrupted so that the current merge can be manually resolved.
4040 4043 Once all conflicts are addressed, the graft process can be
4041 4044 continued with the -c/--continue option.
4042 4045
4043 4046 .. note::
4044 4047
4045 4048 The -c/--continue option does not reapply earlier options, except
4046 4049 for --force.
4047 4050
4048 4051 .. container:: verbose
4049 4052
4050 4053 Examples:
4051 4054
4052 4055 - copy a single change to the stable branch and edit its description::
4053 4056
4054 4057 hg update stable
4055 4058 hg graft --edit 9393
4056 4059
4057 4060 - graft a range of changesets with one exception, updating dates::
4058 4061
4059 4062 hg graft -D "2085::2093 and not 2091"
4060 4063
4061 4064 - continue a graft after resolving conflicts::
4062 4065
4063 4066 hg graft -c
4064 4067
4065 4068 - show the source of a grafted changeset::
4066 4069
4067 4070 hg log --debug -r .
4068 4071
4069 4072 - show revisions sorted by date::
4070 4073
4071 4074 hg log -r "sort(all(), date)"
4072 4075
4073 4076 See :hg:`help revisions` and :hg:`help revsets` for more about
4074 4077 specifying revisions.
4075 4078
4076 4079 Returns 0 on successful completion.
4077 4080 '''
4078 4081 with repo.wlock():
4079 4082 return _dograft(ui, repo, *revs, **opts)
4080 4083
4081 4084 def _dograft(ui, repo, *revs, **opts):
4082 4085 if revs and opts.get('rev'):
4083 4086 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
4084 4087 'revision ordering!\n'))
4085 4088
4086 4089 revs = list(revs)
4087 4090 revs.extend(opts.get('rev'))
4088 4091
4089 4092 if not opts.get('user') and opts.get('currentuser'):
4090 4093 opts['user'] = ui.username()
4091 4094 if not opts.get('date') and opts.get('currentdate'):
4092 4095 opts['date'] = "%d %d" % util.makedate()
4093 4096
4094 4097 editor = cmdutil.getcommiteditor(editform='graft', **opts)
4095 4098
4096 4099 cont = False
4097 4100 if opts.get('continue'):
4098 4101 cont = True
4099 4102 if revs:
4100 4103 raise error.Abort(_("can't specify --continue and revisions"))
4101 4104 # read in unfinished revisions
4102 4105 try:
4103 4106 nodes = repo.vfs.read('graftstate').splitlines()
4104 4107 revs = [repo[node].rev() for node in nodes]
4105 4108 except IOError as inst:
4106 4109 if inst.errno != errno.ENOENT:
4107 4110 raise
4108 4111 cmdutil.wrongtooltocontinue(repo, _('graft'))
4109 4112 else:
4110 4113 cmdutil.checkunfinished(repo)
4111 4114 cmdutil.bailifchanged(repo)
4112 4115 if not revs:
4113 4116 raise error.Abort(_('no revisions specified'))
4114 4117 revs = scmutil.revrange(repo, revs)
4115 4118
4116 4119 skipped = set()
4117 4120 # check for merges
4118 4121 for rev in repo.revs('%ld and merge()', revs):
4119 4122 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
4120 4123 skipped.add(rev)
4121 4124 revs = [r for r in revs if r not in skipped]
4122 4125 if not revs:
4123 4126 return -1
4124 4127
4125 4128 # Don't check in the --continue case, in effect retaining --force across
4126 4129 # --continues. That's because without --force, any revisions we decided to
4127 4130 # skip would have been filtered out here, so they wouldn't have made their
4128 4131 # way to the graftstate. With --force, any revisions we would have otherwise
4129 4132 # skipped would not have been filtered out, and if they hadn't been applied
4130 4133 # already, they'd have been in the graftstate.
4131 4134 if not (cont or opts.get('force')):
4132 4135 # check for ancestors of dest branch
4133 4136 crev = repo['.'].rev()
4134 4137 ancestors = repo.changelog.ancestors([crev], inclusive=True)
4135 4138 # Cannot use x.remove(y) on smart set, this has to be a list.
4136 4139 # XXX make this lazy in the future
4137 4140 revs = list(revs)
4138 4141 # don't mutate while iterating, create a copy
4139 4142 for rev in list(revs):
4140 4143 if rev in ancestors:
4141 4144 ui.warn(_('skipping ancestor revision %d:%s\n') %
4142 4145 (rev, repo[rev]))
4143 4146 # XXX remove on list is slow
4144 4147 revs.remove(rev)
4145 4148 if not revs:
4146 4149 return -1
4147 4150
4148 4151 # analyze revs for earlier grafts
4149 4152 ids = {}
4150 4153 for ctx in repo.set("%ld", revs):
4151 4154 ids[ctx.hex()] = ctx.rev()
4152 4155 n = ctx.extra().get('source')
4153 4156 if n:
4154 4157 ids[n] = ctx.rev()
4155 4158
4156 4159 # check ancestors for earlier grafts
4157 4160 ui.debug('scanning for duplicate grafts\n')
4158 4161
4159 4162 for rev in repo.changelog.findmissingrevs(revs, [crev]):
4160 4163 ctx = repo[rev]
4161 4164 n = ctx.extra().get('source')
4162 4165 if n in ids:
4163 4166 try:
4164 4167 r = repo[n].rev()
4165 4168 except error.RepoLookupError:
4166 4169 r = None
4167 4170 if r in revs:
4168 4171 ui.warn(_('skipping revision %d:%s '
4169 4172 '(already grafted to %d:%s)\n')
4170 4173 % (r, repo[r], rev, ctx))
4171 4174 revs.remove(r)
4172 4175 elif ids[n] in revs:
4173 4176 if r is None:
4174 4177 ui.warn(_('skipping already grafted revision %d:%s '
4175 4178 '(%d:%s also has unknown origin %s)\n')
4176 4179 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
4177 4180 else:
4178 4181 ui.warn(_('skipping already grafted revision %d:%s '
4179 4182 '(%d:%s also has origin %d:%s)\n')
4180 4183 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
4181 4184 revs.remove(ids[n])
4182 4185 elif ctx.hex() in ids:
4183 4186 r = ids[ctx.hex()]
4184 4187 ui.warn(_('skipping already grafted revision %d:%s '
4185 4188 '(was grafted from %d:%s)\n') %
4186 4189 (r, repo[r], rev, ctx))
4187 4190 revs.remove(r)
4188 4191 if not revs:
4189 4192 return -1
4190 4193
4191 4194 for pos, ctx in enumerate(repo.set("%ld", revs)):
4192 4195 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
4193 4196 ctx.description().split('\n', 1)[0])
4194 4197 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
4195 4198 if names:
4196 4199 desc += ' (%s)' % ' '.join(names)
4197 4200 ui.status(_('grafting %s\n') % desc)
4198 4201 if opts.get('dry_run'):
4199 4202 continue
4200 4203
4201 4204 source = ctx.extra().get('source')
4202 4205 extra = {}
4203 4206 if source:
4204 4207 extra['source'] = source
4205 4208 extra['intermediate-source'] = ctx.hex()
4206 4209 else:
4207 4210 extra['source'] = ctx.hex()
4208 4211 user = ctx.user()
4209 4212 if opts.get('user'):
4210 4213 user = opts['user']
4211 4214 date = ctx.date()
4212 4215 if opts.get('date'):
4213 4216 date = opts['date']
4214 4217 message = ctx.description()
4215 4218 if opts.get('log'):
4216 4219 message += '\n(grafted from %s)' % ctx.hex()
4217 4220
4218 4221 # we don't merge the first commit when continuing
4219 4222 if not cont:
4220 4223 # perform the graft merge with p1(rev) as 'ancestor'
4221 4224 try:
4222 4225 # ui.forcemerge is an internal variable, do not document
4223 4226 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4224 4227 'graft')
4225 4228 stats = mergemod.graft(repo, ctx, ctx.p1(),
4226 4229 ['local', 'graft'])
4227 4230 finally:
4228 4231 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
4229 4232 # report any conflicts
4230 4233 if stats and stats[3] > 0:
4231 4234 # write out state for --continue
4232 4235 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
4233 4236 repo.vfs.write('graftstate', ''.join(nodelines))
4234 4237 extra = ''
4235 4238 if opts.get('user'):
4236 4239 extra += ' --user %s' % util.shellquote(opts['user'])
4237 4240 if opts.get('date'):
4238 4241 extra += ' --date %s' % util.shellquote(opts['date'])
4239 4242 if opts.get('log'):
4240 4243 extra += ' --log'
4241 4244 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
4242 4245 raise error.Abort(
4243 4246 _("unresolved conflicts, can't continue"),
4244 4247 hint=hint)
4245 4248 else:
4246 4249 cont = False
4247 4250
4248 4251 # commit
4249 4252 node = repo.commit(text=message, user=user,
4250 4253 date=date, extra=extra, editor=editor)
4251 4254 if node is None:
4252 4255 ui.warn(
4253 4256 _('note: graft of %d:%s created no changes to commit\n') %
4254 4257 (ctx.rev(), ctx))
4255 4258
4256 4259 # remove state when we complete successfully
4257 4260 if not opts.get('dry_run'):
4258 4261 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
4259 4262
4260 4263 return 0
4261 4264
4262 4265 @command('grep',
4263 4266 [('0', 'print0', None, _('end fields with NUL')),
4264 4267 ('', 'all', None, _('print all revisions that match')),
4265 4268 ('a', 'text', None, _('treat all files as text')),
4266 4269 ('f', 'follow', None,
4267 4270 _('follow changeset history,'
4268 4271 ' or file history across copies and renames')),
4269 4272 ('i', 'ignore-case', None, _('ignore case when matching')),
4270 4273 ('l', 'files-with-matches', None,
4271 4274 _('print only filenames and revisions that match')),
4272 4275 ('n', 'line-number', None, _('print matching line numbers')),
4273 4276 ('r', 'rev', [],
4274 4277 _('only search files changed within revision range'), _('REV')),
4275 4278 ('u', 'user', None, _('list the author (long with -v)')),
4276 4279 ('d', 'date', None, _('list the date (short with -q)')),
4277 4280 ] + walkopts,
4278 4281 _('[OPTION]... PATTERN [FILE]...'),
4279 4282 inferrepo=True)
4280 4283 def grep(ui, repo, pattern, *pats, **opts):
4281 4284 """search for a pattern in specified files and revisions
4282 4285
4283 4286 Search revisions of files for a regular expression.
4284 4287
4285 4288 This command behaves differently than Unix grep. It only accepts
4286 4289 Python/Perl regexps. It searches repository history, not the
4287 4290 working directory. It always prints the revision number in which a
4288 4291 match appears.
4289 4292
4290 4293 By default, grep only prints output for the first revision of a
4291 4294 file in which it finds a match. To get it to print every revision
4292 4295 that contains a change in match status ("-" for a match that
4293 4296 becomes a non-match, or "+" for a non-match that becomes a match),
4294 4297 use the --all flag.
4295 4298
4296 4299 Returns 0 if a match is found, 1 otherwise.
4297 4300 """
4298 4301 reflags = re.M
4299 4302 if opts.get('ignore_case'):
4300 4303 reflags |= re.I
4301 4304 try:
4302 4305 regexp = util.re.compile(pattern, reflags)
4303 4306 except re.error as inst:
4304 4307 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
4305 4308 return 1
4306 4309 sep, eol = ':', '\n'
4307 4310 if opts.get('print0'):
4308 4311 sep = eol = '\0'
4309 4312
4310 4313 getfile = util.lrucachefunc(repo.file)
4311 4314
4312 4315 def matchlines(body):
4313 4316 begin = 0
4314 4317 linenum = 0
4315 4318 while begin < len(body):
4316 4319 match = regexp.search(body, begin)
4317 4320 if not match:
4318 4321 break
4319 4322 mstart, mend = match.span()
4320 4323 linenum += body.count('\n', begin, mstart) + 1
4321 4324 lstart = body.rfind('\n', begin, mstart) + 1 or begin
4322 4325 begin = body.find('\n', mend) + 1 or len(body) + 1
4323 4326 lend = begin - 1
4324 4327 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
4325 4328
4326 4329 class linestate(object):
4327 4330 def __init__(self, line, linenum, colstart, colend):
4328 4331 self.line = line
4329 4332 self.linenum = linenum
4330 4333 self.colstart = colstart
4331 4334 self.colend = colend
4332 4335
4333 4336 def __hash__(self):
4334 4337 return hash((self.linenum, self.line))
4335 4338
4336 4339 def __eq__(self, other):
4337 4340 return self.line == other.line
4338 4341
4339 4342 def __iter__(self):
4340 4343 yield (self.line[:self.colstart], '')
4341 4344 yield (self.line[self.colstart:self.colend], 'grep.match')
4342 4345 rest = self.line[self.colend:]
4343 4346 while rest != '':
4344 4347 match = regexp.search(rest)
4345 4348 if not match:
4346 4349 yield (rest, '')
4347 4350 break
4348 4351 mstart, mend = match.span()
4349 4352 yield (rest[:mstart], '')
4350 4353 yield (rest[mstart:mend], 'grep.match')
4351 4354 rest = rest[mend:]
4352 4355
4353 4356 matches = {}
4354 4357 copies = {}
4355 4358 def grepbody(fn, rev, body):
4356 4359 matches[rev].setdefault(fn, [])
4357 4360 m = matches[rev][fn]
4358 4361 for lnum, cstart, cend, line in matchlines(body):
4359 4362 s = linestate(line, lnum, cstart, cend)
4360 4363 m.append(s)
4361 4364
4362 4365 def difflinestates(a, b):
4363 4366 sm = difflib.SequenceMatcher(None, a, b)
4364 4367 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
4365 4368 if tag == 'insert':
4366 4369 for i in xrange(blo, bhi):
4367 4370 yield ('+', b[i])
4368 4371 elif tag == 'delete':
4369 4372 for i in xrange(alo, ahi):
4370 4373 yield ('-', a[i])
4371 4374 elif tag == 'replace':
4372 4375 for i in xrange(alo, ahi):
4373 4376 yield ('-', a[i])
4374 4377 for i in xrange(blo, bhi):
4375 4378 yield ('+', b[i])
4376 4379
4377 4380 def display(fn, ctx, pstates, states):
4378 4381 rev = ctx.rev()
4379 4382 if ui.quiet:
4380 4383 datefunc = util.shortdate
4381 4384 else:
4382 4385 datefunc = util.datestr
4383 4386 found = False
4384 4387 @util.cachefunc
4385 4388 def binary():
4386 4389 flog = getfile(fn)
4387 4390 return util.binary(flog.read(ctx.filenode(fn)))
4388 4391
4389 4392 if opts.get('all'):
4390 4393 iter = difflinestates(pstates, states)
4391 4394 else:
4392 4395 iter = [('', l) for l in states]
4393 4396 for change, l in iter:
4394 4397 cols = [(fn, 'grep.filename'), (str(rev), 'grep.rev')]
4395 4398
4396 4399 if opts.get('line_number'):
4397 4400 cols.append((str(l.linenum), 'grep.linenumber'))
4398 4401 if opts.get('all'):
4399 4402 cols.append((change, 'grep.change'))
4400 4403 if opts.get('user'):
4401 4404 cols.append((ui.shortuser(ctx.user()), 'grep.user'))
4402 4405 if opts.get('date'):
4403 4406 cols.append((datefunc(ctx.date()), 'grep.date'))
4404 4407 for col, label in cols[:-1]:
4405 4408 ui.write(col, label=label)
4406 4409 ui.write(sep, label='grep.sep')
4407 4410 ui.write(cols[-1][0], label=cols[-1][1])
4408 4411 if not opts.get('files_with_matches'):
4409 4412 ui.write(sep, label='grep.sep')
4410 4413 if not opts.get('text') and binary():
4411 4414 ui.write(_(" Binary file matches"))
4412 4415 else:
4413 4416 for s, label in l:
4414 4417 ui.write(s, label=label)
4415 4418 ui.write(eol)
4416 4419 found = True
4417 4420 if opts.get('files_with_matches'):
4418 4421 break
4419 4422 return found
4420 4423
4421 4424 skip = {}
4422 4425 revfiles = {}
4423 4426 matchfn = scmutil.match(repo[None], pats, opts)
4424 4427 found = False
4425 4428 follow = opts.get('follow')
4426 4429
4427 4430 def prep(ctx, fns):
4428 4431 rev = ctx.rev()
4429 4432 pctx = ctx.p1()
4430 4433 parent = pctx.rev()
4431 4434 matches.setdefault(rev, {})
4432 4435 matches.setdefault(parent, {})
4433 4436 files = revfiles.setdefault(rev, [])
4434 4437 for fn in fns:
4435 4438 flog = getfile(fn)
4436 4439 try:
4437 4440 fnode = ctx.filenode(fn)
4438 4441 except error.LookupError:
4439 4442 continue
4440 4443
4441 4444 copied = flog.renamed(fnode)
4442 4445 copy = follow and copied and copied[0]
4443 4446 if copy:
4444 4447 copies.setdefault(rev, {})[fn] = copy
4445 4448 if fn in skip:
4446 4449 if copy:
4447 4450 skip[copy] = True
4448 4451 continue
4449 4452 files.append(fn)
4450 4453
4451 4454 if fn not in matches[rev]:
4452 4455 grepbody(fn, rev, flog.read(fnode))
4453 4456
4454 4457 pfn = copy or fn
4455 4458 if pfn not in matches[parent]:
4456 4459 try:
4457 4460 fnode = pctx.filenode(pfn)
4458 4461 grepbody(pfn, parent, flog.read(fnode))
4459 4462 except error.LookupError:
4460 4463 pass
4461 4464
4462 4465 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4463 4466 rev = ctx.rev()
4464 4467 parent = ctx.p1().rev()
4465 4468 for fn in sorted(revfiles.get(rev, [])):
4466 4469 states = matches[rev][fn]
4467 4470 copy = copies.get(rev, {}).get(fn)
4468 4471 if fn in skip:
4469 4472 if copy:
4470 4473 skip[copy] = True
4471 4474 continue
4472 4475 pstates = matches.get(parent, {}).get(copy or fn, [])
4473 4476 if pstates or states:
4474 4477 r = display(fn, ctx, pstates, states)
4475 4478 found = found or r
4476 4479 if r and not opts.get('all'):
4477 4480 skip[fn] = True
4478 4481 if copy:
4479 4482 skip[copy] = True
4480 4483 del matches[rev]
4481 4484 del revfiles[rev]
4482 4485
4483 4486 return not found
4484 4487
4485 4488 @command('heads',
4486 4489 [('r', 'rev', '',
4487 4490 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
4488 4491 ('t', 'topo', False, _('show topological heads only')),
4489 4492 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
4490 4493 ('c', 'closed', False, _('show normal and closed branch heads')),
4491 4494 ] + templateopts,
4492 4495 _('[-ct] [-r STARTREV] [REV]...'))
4493 4496 def heads(ui, repo, *branchrevs, **opts):
4494 4497 """show branch heads
4495 4498
4496 4499 With no arguments, show all open branch heads in the repository.
4497 4500 Branch heads are changesets that have no descendants on the
4498 4501 same branch. They are where development generally takes place and
4499 4502 are the usual targets for update and merge operations.
4500 4503
4501 4504 If one or more REVs are given, only open branch heads on the
4502 4505 branches associated with the specified changesets are shown. This
4503 4506 means that you can use :hg:`heads .` to see the heads on the
4504 4507 currently checked-out branch.
4505 4508
4506 4509 If -c/--closed is specified, also show branch heads marked closed
4507 4510 (see :hg:`commit --close-branch`).
4508 4511
4509 4512 If STARTREV is specified, only those heads that are descendants of
4510 4513 STARTREV will be displayed.
4511 4514
4512 4515 If -t/--topo is specified, named branch mechanics will be ignored and only
4513 4516 topological heads (changesets with no children) will be shown.
4514 4517
4515 4518 Returns 0 if matching heads are found, 1 if not.
4516 4519 """
4517 4520
4518 4521 start = None
4519 4522 if 'rev' in opts:
4520 4523 start = scmutil.revsingle(repo, opts['rev'], None).node()
4521 4524
4522 4525 if opts.get('topo'):
4523 4526 heads = [repo[h] for h in repo.heads(start)]
4524 4527 else:
4525 4528 heads = []
4526 4529 for branch in repo.branchmap():
4527 4530 heads += repo.branchheads(branch, start, opts.get('closed'))
4528 4531 heads = [repo[h] for h in heads]
4529 4532
4530 4533 if branchrevs:
4531 4534 branches = set(repo[br].branch() for br in branchrevs)
4532 4535 heads = [h for h in heads if h.branch() in branches]
4533 4536
4534 4537 if opts.get('active') and branchrevs:
4535 4538 dagheads = repo.heads(start)
4536 4539 heads = [h for h in heads if h.node() in dagheads]
4537 4540
4538 4541 if branchrevs:
4539 4542 haveheads = set(h.branch() for h in heads)
4540 4543 if branches - haveheads:
4541 4544 headless = ', '.join(b for b in branches - haveheads)
4542 4545 msg = _('no open branch heads found on branches %s')
4543 4546 if opts.get('rev'):
4544 4547 msg += _(' (started at %s)') % opts['rev']
4545 4548 ui.warn((msg + '\n') % headless)
4546 4549
4547 4550 if not heads:
4548 4551 return 1
4549 4552
4550 4553 heads = sorted(heads, key=lambda x: -x.rev())
4551 4554 displayer = cmdutil.show_changeset(ui, repo, opts)
4552 4555 for ctx in heads:
4553 4556 displayer.show(ctx)
4554 4557 displayer.close()
4555 4558
4556 4559 @command('help',
4557 4560 [('e', 'extension', None, _('show only help for extensions')),
4558 4561 ('c', 'command', None, _('show only help for commands')),
4559 4562 ('k', 'keyword', None, _('show topics matching keyword')),
4560 4563 ('s', 'system', [], _('show help for specific platform(s)')),
4561 4564 ],
4562 4565 _('[-ecks] [TOPIC]'),
4563 4566 norepo=True)
4564 4567 def help_(ui, name=None, **opts):
4565 4568 """show help for a given topic or a help overview
4566 4569
4567 4570 With no arguments, print a list of commands with short help messages.
4568 4571
4569 4572 Given a topic, extension, or command name, print help for that
4570 4573 topic.
4571 4574
4572 4575 Returns 0 if successful.
4573 4576 """
4574 4577
4575 4578 textwidth = ui.configint('ui', 'textwidth', 78)
4576 4579 termwidth = ui.termwidth() - 2
4577 4580 if textwidth <= 0 or termwidth < textwidth:
4578 4581 textwidth = termwidth
4579 4582
4580 4583 keep = opts.get('system') or []
4581 4584 if len(keep) == 0:
4582 4585 if sys.platform.startswith('win'):
4583 4586 keep.append('windows')
4584 4587 elif sys.platform == 'OpenVMS':
4585 4588 keep.append('vms')
4586 4589 elif sys.platform == 'plan9':
4587 4590 keep.append('plan9')
4588 4591 else:
4589 4592 keep.append('unix')
4590 4593 keep.append(sys.platform.lower())
4591 4594 if ui.verbose:
4592 4595 keep.append('verbose')
4593 4596
4594 4597 section = None
4595 4598 subtopic = None
4596 4599 if name and '.' in name:
4597 4600 name, remaining = name.split('.', 1)
4598 4601 remaining = encoding.lower(remaining)
4599 4602 if '.' in remaining:
4600 4603 subtopic, section = remaining.split('.', 1)
4601 4604 else:
4602 4605 if name in help.subtopics:
4603 4606 subtopic = remaining
4604 4607 else:
4605 4608 section = remaining
4606 4609
4607 4610 text = help.help_(ui, name, subtopic=subtopic, **opts)
4608 4611
4609 4612 formatted, pruned = minirst.format(text, textwidth, keep=keep,
4610 4613 section=section)
4611 4614
4612 4615 # We could have been given a weird ".foo" section without a name
4613 4616 # to look for, or we could have simply failed to found "foo.bar"
4614 4617 # because bar isn't a section of foo
4615 4618 if section and not (formatted and name):
4616 4619 raise error.Abort(_("help section not found"))
4617 4620
4618 4621 if 'verbose' in pruned:
4619 4622 keep.append('omitted')
4620 4623 else:
4621 4624 keep.append('notomitted')
4622 4625 formatted, pruned = minirst.format(text, textwidth, keep=keep,
4623 4626 section=section)
4624 4627 ui.write(formatted)
4625 4628
4626 4629
4627 4630 @command('identify|id',
4628 4631 [('r', 'rev', '',
4629 4632 _('identify the specified revision'), _('REV')),
4630 4633 ('n', 'num', None, _('show local revision number')),
4631 4634 ('i', 'id', None, _('show global revision id')),
4632 4635 ('b', 'branch', None, _('show branch')),
4633 4636 ('t', 'tags', None, _('show tags')),
4634 4637 ('B', 'bookmarks', None, _('show bookmarks')),
4635 4638 ] + remoteopts,
4636 4639 _('[-nibtB] [-r REV] [SOURCE]'),
4637 4640 optionalrepo=True)
4638 4641 def identify(ui, repo, source=None, rev=None,
4639 4642 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
4640 4643 """identify the working directory or specified revision
4641 4644
4642 4645 Print a summary identifying the repository state at REV using one or
4643 4646 two parent hash identifiers, followed by a "+" if the working
4644 4647 directory has uncommitted changes, the branch name (if not default),
4645 4648 a list of tags, and a list of bookmarks.
4646 4649
4647 4650 When REV is not given, print a summary of the current state of the
4648 4651 repository.
4649 4652
4650 4653 Specifying a path to a repository root or Mercurial bundle will
4651 4654 cause lookup to operate on that repository/bundle.
4652 4655
4653 4656 .. container:: verbose
4654 4657
4655 4658 Examples:
4656 4659
4657 4660 - generate a build identifier for the working directory::
4658 4661
4659 4662 hg id --id > build-id.dat
4660 4663
4661 4664 - find the revision corresponding to a tag::
4662 4665
4663 4666 hg id -n -r 1.3
4664 4667
4665 4668 - check the most recent revision of a remote repository::
4666 4669
4667 4670 hg id -r tip http://selenic.com/hg/
4668 4671
4669 4672 See :hg:`log` for generating more information about specific revisions,
4670 4673 including full hash identifiers.
4671 4674
4672 4675 Returns 0 if successful.
4673 4676 """
4674 4677
4675 4678 if not repo and not source:
4676 4679 raise error.Abort(_("there is no Mercurial repository here "
4677 4680 "(.hg not found)"))
4678 4681
4679 4682 if ui.debugflag:
4680 4683 hexfunc = hex
4681 4684 else:
4682 4685 hexfunc = short
4683 4686 default = not (num or id or branch or tags or bookmarks)
4684 4687 output = []
4685 4688 revs = []
4686 4689
4687 4690 if source:
4688 4691 source, branches = hg.parseurl(ui.expandpath(source))
4689 4692 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
4690 4693 repo = peer.local()
4691 4694 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
4692 4695
4693 4696 if not repo:
4694 4697 if num or branch or tags:
4695 4698 raise error.Abort(
4696 4699 _("can't query remote revision number, branch, or tags"))
4697 4700 if not rev and revs:
4698 4701 rev = revs[0]
4699 4702 if not rev:
4700 4703 rev = "tip"
4701 4704
4702 4705 remoterev = peer.lookup(rev)
4703 4706 if default or id:
4704 4707 output = [hexfunc(remoterev)]
4705 4708
4706 4709 def getbms():
4707 4710 bms = []
4708 4711
4709 4712 if 'bookmarks' in peer.listkeys('namespaces'):
4710 4713 hexremoterev = hex(remoterev)
4711 4714 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
4712 4715 if bmr == hexremoterev]
4713 4716
4714 4717 return sorted(bms)
4715 4718
4716 4719 if bookmarks:
4717 4720 output.extend(getbms())
4718 4721 elif default and not ui.quiet:
4719 4722 # multiple bookmarks for a single parent separated by '/'
4720 4723 bm = '/'.join(getbms())
4721 4724 if bm:
4722 4725 output.append(bm)
4723 4726 else:
4724 4727 ctx = scmutil.revsingle(repo, rev, None)
4725 4728
4726 4729 if ctx.rev() is None:
4727 4730 ctx = repo[None]
4728 4731 parents = ctx.parents()
4729 4732 taglist = []
4730 4733 for p in parents:
4731 4734 taglist.extend(p.tags())
4732 4735
4733 4736 changed = ""
4734 4737 if default or id or num:
4735 4738 if (any(repo.status())
4736 4739 or any(ctx.sub(s).dirty() for s in ctx.substate)):
4737 4740 changed = '+'
4738 4741 if default or id:
4739 4742 output = ["%s%s" %
4740 4743 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
4741 4744 if num:
4742 4745 output.append("%s%s" %
4743 4746 ('+'.join([str(p.rev()) for p in parents]), changed))
4744 4747 else:
4745 4748 if default or id:
4746 4749 output = [hexfunc(ctx.node())]
4747 4750 if num:
4748 4751 output.append(str(ctx.rev()))
4749 4752 taglist = ctx.tags()
4750 4753
4751 4754 if default and not ui.quiet:
4752 4755 b = ctx.branch()
4753 4756 if b != 'default':
4754 4757 output.append("(%s)" % b)
4755 4758
4756 4759 # multiple tags for a single parent separated by '/'
4757 4760 t = '/'.join(taglist)
4758 4761 if t:
4759 4762 output.append(t)
4760 4763
4761 4764 # multiple bookmarks for a single parent separated by '/'
4762 4765 bm = '/'.join(ctx.bookmarks())
4763 4766 if bm:
4764 4767 output.append(bm)
4765 4768 else:
4766 4769 if branch:
4767 4770 output.append(ctx.branch())
4768 4771
4769 4772 if tags:
4770 4773 output.extend(taglist)
4771 4774
4772 4775 if bookmarks:
4773 4776 output.extend(ctx.bookmarks())
4774 4777
4775 4778 ui.write("%s\n" % ' '.join(output))
4776 4779
4777 4780 @command('import|patch',
4778 4781 [('p', 'strip', 1,
4779 4782 _('directory strip option for patch. This has the same '
4780 4783 'meaning as the corresponding patch option'), _('NUM')),
4781 4784 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
4782 4785 ('e', 'edit', False, _('invoke editor on commit messages')),
4783 4786 ('f', 'force', None,
4784 4787 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
4785 4788 ('', 'no-commit', None,
4786 4789 _("don't commit, just update the working directory")),
4787 4790 ('', 'bypass', None,
4788 4791 _("apply patch without touching the working directory")),
4789 4792 ('', 'partial', None,
4790 4793 _('commit even if some hunks fail')),
4791 4794 ('', 'exact', None,
4792 4795 _('abort if patch would apply lossily')),
4793 4796 ('', 'prefix', '',
4794 4797 _('apply patch to subdirectory'), _('DIR')),
4795 4798 ('', 'import-branch', None,
4796 4799 _('use any branch information in patch (implied by --exact)'))] +
4797 4800 commitopts + commitopts2 + similarityopts,
4798 4801 _('[OPTION]... PATCH...'))
4799 4802 def import_(ui, repo, patch1=None, *patches, **opts):
4800 4803 """import an ordered set of patches
4801 4804
4802 4805 Import a list of patches and commit them individually (unless
4803 4806 --no-commit is specified).
4804 4807
4805 4808 To read a patch from standard input, use "-" as the patch name. If
4806 4809 a URL is specified, the patch will be downloaded from there.
4807 4810
4808 4811 Import first applies changes to the working directory (unless
4809 4812 --bypass is specified), import will abort if there are outstanding
4810 4813 changes.
4811 4814
4812 4815 Use --bypass to apply and commit patches directly to the
4813 4816 repository, without affecting the working directory. Without
4814 4817 --exact, patches will be applied on top of the working directory
4815 4818 parent revision.
4816 4819
4817 4820 You can import a patch straight from a mail message. Even patches
4818 4821 as attachments work (to use the body part, it must have type
4819 4822 text/plain or text/x-patch). From and Subject headers of email
4820 4823 message are used as default committer and commit message. All
4821 4824 text/plain body parts before first diff are added to the commit
4822 4825 message.
4823 4826
4824 4827 If the imported patch was generated by :hg:`export`, user and
4825 4828 description from patch override values from message headers and
4826 4829 body. Values given on command line with -m/--message and -u/--user
4827 4830 override these.
4828 4831
4829 4832 If --exact is specified, import will set the working directory to
4830 4833 the parent of each patch before applying it, and will abort if the
4831 4834 resulting changeset has a different ID than the one recorded in
4832 4835 the patch. This will guard against various ways that portable
4833 4836 patch formats and mail systems might fail to transfer Mercurial
4834 4837 data or metadata. See :hg:`bundle` for lossless transmission.
4835 4838
4836 4839 Use --partial to ensure a changeset will be created from the patch
4837 4840 even if some hunks fail to apply. Hunks that fail to apply will be
4838 4841 written to a <target-file>.rej file. Conflicts can then be resolved
4839 4842 by hand before :hg:`commit --amend` is run to update the created
4840 4843 changeset. This flag exists to let people import patches that
4841 4844 partially apply without losing the associated metadata (author,
4842 4845 date, description, ...).
4843 4846
4844 4847 .. note::
4845 4848
4846 4849 When no hunks apply cleanly, :hg:`import --partial` will create
4847 4850 an empty changeset, importing only the patch metadata.
4848 4851
4849 4852 With -s/--similarity, hg will attempt to discover renames and
4850 4853 copies in the patch in the same way as :hg:`addremove`.
4851 4854
4852 4855 It is possible to use external patch programs to perform the patch
4853 4856 by setting the ``ui.patch`` configuration option. For the default
4854 4857 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4855 4858 See :hg:`help config` for more information about configuration
4856 4859 files and how to use these options.
4857 4860
4858 4861 See :hg:`help dates` for a list of formats valid for -d/--date.
4859 4862
4860 4863 .. container:: verbose
4861 4864
4862 4865 Examples:
4863 4866
4864 4867 - import a traditional patch from a website and detect renames::
4865 4868
4866 4869 hg import -s 80 http://example.com/bugfix.patch
4867 4870
4868 4871 - import a changeset from an hgweb server::
4869 4872
4870 4873 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
4871 4874
4872 4875 - import all the patches in an Unix-style mbox::
4873 4876
4874 4877 hg import incoming-patches.mbox
4875 4878
4876 4879 - attempt to exactly restore an exported changeset (not always
4877 4880 possible)::
4878 4881
4879 4882 hg import --exact proposed-fix.patch
4880 4883
4881 4884 - use an external tool to apply a patch which is too fuzzy for
4882 4885 the default internal tool.
4883 4886
4884 4887 hg import --config ui.patch="patch --merge" fuzzy.patch
4885 4888
4886 4889 - change the default fuzzing from 2 to a less strict 7
4887 4890
4888 4891 hg import --config ui.fuzz=7 fuzz.patch
4889 4892
4890 4893 Returns 0 on success, 1 on partial success (see --partial).
4891 4894 """
4892 4895
4893 4896 if not patch1:
4894 4897 raise error.Abort(_('need at least one patch to import'))
4895 4898
4896 4899 patches = (patch1,) + patches
4897 4900
4898 4901 date = opts.get('date')
4899 4902 if date:
4900 4903 opts['date'] = util.parsedate(date)
4901 4904
4902 4905 exact = opts.get('exact')
4903 4906 update = not opts.get('bypass')
4904 4907 if not update and opts.get('no_commit'):
4905 4908 raise error.Abort(_('cannot use --no-commit with --bypass'))
4906 4909 try:
4907 4910 sim = float(opts.get('similarity') or 0)
4908 4911 except ValueError:
4909 4912 raise error.Abort(_('similarity must be a number'))
4910 4913 if sim < 0 or sim > 100:
4911 4914 raise error.Abort(_('similarity must be between 0 and 100'))
4912 4915 if sim and not update:
4913 4916 raise error.Abort(_('cannot use --similarity with --bypass'))
4914 4917 if exact:
4915 4918 if opts.get('edit'):
4916 4919 raise error.Abort(_('cannot use --exact with --edit'))
4917 4920 if opts.get('prefix'):
4918 4921 raise error.Abort(_('cannot use --exact with --prefix'))
4919 4922
4920 4923 base = opts["base"]
4921 4924 wlock = dsguard = lock = tr = None
4922 4925 msgs = []
4923 4926 ret = 0
4924 4927
4925 4928
4926 4929 try:
4927 4930 wlock = repo.wlock()
4928 4931
4929 4932 if update:
4930 4933 cmdutil.checkunfinished(repo)
4931 4934 if (exact or not opts.get('force')):
4932 4935 cmdutil.bailifchanged(repo)
4933 4936
4934 4937 if not opts.get('no_commit'):
4935 4938 lock = repo.lock()
4936 4939 tr = repo.transaction('import')
4937 4940 else:
4938 4941 dsguard = cmdutil.dirstateguard(repo, 'import')
4939 4942 parents = repo[None].parents()
4940 4943 for patchurl in patches:
4941 4944 if patchurl == '-':
4942 4945 ui.status(_('applying patch from stdin\n'))
4943 4946 patchfile = ui.fin
4944 4947 patchurl = 'stdin' # for error message
4945 4948 else:
4946 4949 patchurl = os.path.join(base, patchurl)
4947 4950 ui.status(_('applying %s\n') % patchurl)
4948 4951 patchfile = hg.openpath(ui, patchurl)
4949 4952
4950 4953 haspatch = False
4951 4954 for hunk in patch.split(patchfile):
4952 4955 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
4953 4956 parents, opts,
4954 4957 msgs, hg.clean)
4955 4958 if msg:
4956 4959 haspatch = True
4957 4960 ui.note(msg + '\n')
4958 4961 if update or exact:
4959 4962 parents = repo[None].parents()
4960 4963 else:
4961 4964 parents = [repo[node]]
4962 4965 if rej:
4963 4966 ui.write_err(_("patch applied partially\n"))
4964 4967 ui.write_err(_("(fix the .rej files and run "
4965 4968 "`hg commit --amend`)\n"))
4966 4969 ret = 1
4967 4970 break
4968 4971
4969 4972 if not haspatch:
4970 4973 raise error.Abort(_('%s: no diffs found') % patchurl)
4971 4974
4972 4975 if tr:
4973 4976 tr.close()
4974 4977 if msgs:
4975 4978 repo.savecommitmessage('\n* * *\n'.join(msgs))
4976 4979 if dsguard:
4977 4980 dsguard.close()
4978 4981 return ret
4979 4982 finally:
4980 4983 if tr:
4981 4984 tr.release()
4982 4985 release(lock, dsguard, wlock)
4983 4986
4984 4987 @command('incoming|in',
4985 4988 [('f', 'force', None,
4986 4989 _('run even if remote repository is unrelated')),
4987 4990 ('n', 'newest-first', None, _('show newest record first')),
4988 4991 ('', 'bundle', '',
4989 4992 _('file to store the bundles into'), _('FILE')),
4990 4993 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4991 4994 ('B', 'bookmarks', False, _("compare bookmarks")),
4992 4995 ('b', 'branch', [],
4993 4996 _('a specific branch you would like to pull'), _('BRANCH')),
4994 4997 ] + logopts + remoteopts + subrepoopts,
4995 4998 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
4996 4999 def incoming(ui, repo, source="default", **opts):
4997 5000 """show new changesets found in source
4998 5001
4999 5002 Show new changesets found in the specified path/URL or the default
5000 5003 pull location. These are the changesets that would have been pulled
5001 5004 if a pull at the time you issued this command.
5002 5005
5003 5006 See pull for valid source format details.
5004 5007
5005 5008 .. container:: verbose
5006 5009
5007 5010 With -B/--bookmarks, the result of bookmark comparison between
5008 5011 local and remote repositories is displayed. With -v/--verbose,
5009 5012 status is also displayed for each bookmark like below::
5010 5013
5011 5014 BM1 01234567890a added
5012 5015 BM2 1234567890ab advanced
5013 5016 BM3 234567890abc diverged
5014 5017 BM4 34567890abcd changed
5015 5018
5016 5019 The action taken locally when pulling depends on the
5017 5020 status of each bookmark:
5018 5021
5019 5022 :``added``: pull will create it
5020 5023 :``advanced``: pull will update it
5021 5024 :``diverged``: pull will create a divergent bookmark
5022 5025 :``changed``: result depends on remote changesets
5023 5026
5024 5027 From the point of view of pulling behavior, bookmark
5025 5028 existing only in the remote repository are treated as ``added``,
5026 5029 even if it is in fact locally deleted.
5027 5030
5028 5031 .. container:: verbose
5029 5032
5030 5033 For remote repository, using --bundle avoids downloading the
5031 5034 changesets twice if the incoming is followed by a pull.
5032 5035
5033 5036 Examples:
5034 5037
5035 5038 - show incoming changes with patches and full description::
5036 5039
5037 5040 hg incoming -vp
5038 5041
5039 5042 - show incoming changes excluding merges, store a bundle::
5040 5043
5041 5044 hg in -vpM --bundle incoming.hg
5042 5045 hg pull incoming.hg
5043 5046
5044 5047 - briefly list changes inside a bundle::
5045 5048
5046 5049 hg in changes.hg -T "{desc|firstline}\\n"
5047 5050
5048 5051 Returns 0 if there are incoming changes, 1 otherwise.
5049 5052 """
5050 5053 if opts.get('graph'):
5051 5054 cmdutil.checkunsupportedgraphflags([], opts)
5052 5055 def display(other, chlist, displayer):
5053 5056 revdag = cmdutil.graphrevs(other, chlist, opts)
5054 5057 cmdutil.displaygraph(ui, repo, revdag, displayer,
5055 5058 graphmod.asciiedges)
5056 5059
5057 5060 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
5058 5061 return 0
5059 5062
5060 5063 if opts.get('bundle') and opts.get('subrepos'):
5061 5064 raise error.Abort(_('cannot combine --bundle and --subrepos'))
5062 5065
5063 5066 if opts.get('bookmarks'):
5064 5067 source, branches = hg.parseurl(ui.expandpath(source),
5065 5068 opts.get('branch'))
5066 5069 other = hg.peer(repo, opts, source)
5067 5070 if 'bookmarks' not in other.listkeys('namespaces'):
5068 5071 ui.warn(_("remote doesn't support bookmarks\n"))
5069 5072 return 0
5070 5073 ui.status(_('comparing with %s\n') % util.hidepassword(source))
5071 5074 return bookmarks.incoming(ui, repo, other)
5072 5075
5073 5076 repo._subtoppath = ui.expandpath(source)
5074 5077 try:
5075 5078 return hg.incoming(ui, repo, source, opts)
5076 5079 finally:
5077 5080 del repo._subtoppath
5078 5081
5079 5082
5080 5083 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
5081 5084 norepo=True)
5082 5085 def init(ui, dest=".", **opts):
5083 5086 """create a new repository in the given directory
5084 5087
5085 5088 Initialize a new repository in the given directory. If the given
5086 5089 directory does not exist, it will be created.
5087 5090
5088 5091 If no directory is given, the current directory is used.
5089 5092
5090 5093 It is possible to specify an ``ssh://`` URL as the destination.
5091 5094 See :hg:`help urls` for more information.
5092 5095
5093 5096 Returns 0 on success.
5094 5097 """
5095 5098 hg.peer(ui, opts, ui.expandpath(dest), create=True)
5096 5099
5097 5100 @command('locate',
5098 5101 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
5099 5102 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5100 5103 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
5101 5104 ] + walkopts,
5102 5105 _('[OPTION]... [PATTERN]...'))
5103 5106 def locate(ui, repo, *pats, **opts):
5104 5107 """locate files matching specific patterns (DEPRECATED)
5105 5108
5106 5109 Print files under Mercurial control in the working directory whose
5107 5110 names match the given patterns.
5108 5111
5109 5112 By default, this command searches all directories in the working
5110 5113 directory. To search just the current directory and its
5111 5114 subdirectories, use "--include .".
5112 5115
5113 5116 If no patterns are given to match, this command prints the names
5114 5117 of all files under Mercurial control in the working directory.
5115 5118
5116 5119 If you want to feed the output of this command into the "xargs"
5117 5120 command, use the -0 option to both this command and "xargs". This
5118 5121 will avoid the problem of "xargs" treating single filenames that
5119 5122 contain whitespace as multiple filenames.
5120 5123
5121 5124 See :hg:`help files` for a more versatile command.
5122 5125
5123 5126 Returns 0 if a match is found, 1 otherwise.
5124 5127 """
5125 5128 if opts.get('print0'):
5126 5129 end = '\0'
5127 5130 else:
5128 5131 end = '\n'
5129 5132 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
5130 5133
5131 5134 ret = 1
5132 5135 ctx = repo[rev]
5133 5136 m = scmutil.match(ctx, pats, opts, default='relglob',
5134 5137 badfn=lambda x, y: False)
5135 5138
5136 5139 for abs in ctx.matches(m):
5137 5140 if opts.get('fullpath'):
5138 5141 ui.write(repo.wjoin(abs), end)
5139 5142 else:
5140 5143 ui.write(((pats and m.rel(abs)) or abs), end)
5141 5144 ret = 0
5142 5145
5143 5146 return ret
5144 5147
5145 5148 @command('^log|history',
5146 5149 [('f', 'follow', None,
5147 5150 _('follow changeset history, or file history across copies and renames')),
5148 5151 ('', 'follow-first', None,
5149 5152 _('only follow the first parent of merge changesets (DEPRECATED)')),
5150 5153 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
5151 5154 ('C', 'copies', None, _('show copied files')),
5152 5155 ('k', 'keyword', [],
5153 5156 _('do case-insensitive search for a given text'), _('TEXT')),
5154 5157 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
5155 5158 ('', 'removed', None, _('include revisions where files were removed')),
5156 5159 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
5157 5160 ('u', 'user', [], _('revisions committed by user'), _('USER')),
5158 5161 ('', 'only-branch', [],
5159 5162 _('show only changesets within the given named branch (DEPRECATED)'),
5160 5163 _('BRANCH')),
5161 5164 ('b', 'branch', [],
5162 5165 _('show changesets within the given named branch'), _('BRANCH')),
5163 5166 ('P', 'prune', [],
5164 5167 _('do not display revision or any of its ancestors'), _('REV')),
5165 5168 ] + logopts + walkopts,
5166 5169 _('[OPTION]... [FILE]'),
5167 5170 inferrepo=True)
5168 5171 def log(ui, repo, *pats, **opts):
5169 5172 """show revision history of entire repository or files
5170 5173
5171 5174 Print the revision history of the specified files or the entire
5172 5175 project.
5173 5176
5174 5177 If no revision range is specified, the default is ``tip:0`` unless
5175 5178 --follow is set, in which case the working directory parent is
5176 5179 used as the starting revision.
5177 5180
5178 5181 File history is shown without following rename or copy history of
5179 5182 files. Use -f/--follow with a filename to follow history across
5180 5183 renames and copies. --follow without a filename will only show
5181 5184 ancestors or descendants of the starting revision.
5182 5185
5183 5186 By default this command prints revision number and changeset id,
5184 5187 tags, non-trivial parents, user, date and time, and a summary for
5185 5188 each commit. When the -v/--verbose switch is used, the list of
5186 5189 changed files and full commit message are shown.
5187 5190
5188 5191 With --graph the revisions are shown as an ASCII art DAG with the most
5189 5192 recent changeset at the top.
5190 5193 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
5191 5194 and '+' represents a fork where the changeset from the lines below is a
5192 5195 parent of the 'o' merge on the same line.
5193 5196
5194 5197 .. note::
5195 5198
5196 5199 :hg:`log --patch` may generate unexpected diff output for merge
5197 5200 changesets, as it will only compare the merge changeset against
5198 5201 its first parent. Also, only files different from BOTH parents
5199 5202 will appear in files:.
5200 5203
5201 5204 .. note::
5202 5205
5203 5206 For performance reasons, :hg:`log FILE` may omit duplicate changes
5204 5207 made on branches and will not show removals or mode changes. To
5205 5208 see all such changes, use the --removed switch.
5206 5209
5207 5210 .. container:: verbose
5208 5211
5209 5212 Some examples:
5210 5213
5211 5214 - changesets with full descriptions and file lists::
5212 5215
5213 5216 hg log -v
5214 5217
5215 5218 - changesets ancestral to the working directory::
5216 5219
5217 5220 hg log -f
5218 5221
5219 5222 - last 10 commits on the current branch::
5220 5223
5221 5224 hg log -l 10 -b .
5222 5225
5223 5226 - changesets showing all modifications of a file, including removals::
5224 5227
5225 5228 hg log --removed file.c
5226 5229
5227 5230 - all changesets that touch a directory, with diffs, excluding merges::
5228 5231
5229 5232 hg log -Mp lib/
5230 5233
5231 5234 - all revision numbers that match a keyword::
5232 5235
5233 5236 hg log -k bug --template "{rev}\\n"
5234 5237
5235 5238 - the full hash identifier of the working directory parent::
5236 5239
5237 5240 hg log -r . --template "{node}\\n"
5238 5241
5239 5242 - list available log templates::
5240 5243
5241 5244 hg log -T list
5242 5245
5243 5246 - check if a given changeset is included in a tagged release::
5244 5247
5245 5248 hg log -r "a21ccf and ancestor(1.9)"
5246 5249
5247 5250 - find all changesets by some user in a date range::
5248 5251
5249 5252 hg log -k alice -d "may 2008 to jul 2008"
5250 5253
5251 5254 - summary of all changesets after the last tag::
5252 5255
5253 5256 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
5254 5257
5255 5258 See :hg:`help dates` for a list of formats valid for -d/--date.
5256 5259
5257 5260 See :hg:`help revisions` and :hg:`help revsets` for more about
5258 5261 specifying and ordering revisions.
5259 5262
5260 5263 See :hg:`help templates` for more about pre-packaged styles and
5261 5264 specifying custom templates.
5262 5265
5263 5266 Returns 0 on success.
5264 5267
5265 5268 """
5266 5269 if opts.get('follow') and opts.get('rev'):
5267 5270 opts['rev'] = [revset.formatspec('reverse(::%lr)', opts.get('rev'))]
5268 5271 del opts['follow']
5269 5272
5270 5273 if opts.get('graph'):
5271 5274 return cmdutil.graphlog(ui, repo, *pats, **opts)
5272 5275
5273 5276 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
5274 5277 limit = cmdutil.loglimit(opts)
5275 5278 count = 0
5276 5279
5277 5280 getrenamed = None
5278 5281 if opts.get('copies'):
5279 5282 endrev = None
5280 5283 if opts.get('rev'):
5281 5284 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
5282 5285 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
5283 5286
5284 5287 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
5285 5288 for rev in revs:
5286 5289 if count == limit:
5287 5290 break
5288 5291 ctx = repo[rev]
5289 5292 copies = None
5290 5293 if getrenamed is not None and rev:
5291 5294 copies = []
5292 5295 for fn in ctx.files():
5293 5296 rename = getrenamed(fn, rev)
5294 5297 if rename:
5295 5298 copies.append((fn, rename[0]))
5296 5299 if filematcher:
5297 5300 revmatchfn = filematcher(ctx.rev())
5298 5301 else:
5299 5302 revmatchfn = None
5300 5303 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
5301 5304 if displayer.flush(ctx):
5302 5305 count += 1
5303 5306
5304 5307 displayer.close()
5305 5308
5306 5309 @command('manifest',
5307 5310 [('r', 'rev', '', _('revision to display'), _('REV')),
5308 5311 ('', 'all', False, _("list files from all revisions"))]
5309 5312 + formatteropts,
5310 5313 _('[-r REV]'))
5311 5314 def manifest(ui, repo, node=None, rev=None, **opts):
5312 5315 """output the current or given revision of the project manifest
5313 5316
5314 5317 Print a list of version controlled files for the given revision.
5315 5318 If no revision is given, the first parent of the working directory
5316 5319 is used, or the null revision if no revision is checked out.
5317 5320
5318 5321 With -v, print file permissions, symlink and executable bits.
5319 5322 With --debug, print file revision hashes.
5320 5323
5321 5324 If option --all is specified, the list of all files from all revisions
5322 5325 is printed. This includes deleted and renamed files.
5323 5326
5324 5327 Returns 0 on success.
5325 5328 """
5326 5329
5327 5330 fm = ui.formatter('manifest', opts)
5328 5331
5329 5332 if opts.get('all'):
5330 5333 if rev or node:
5331 5334 raise error.Abort(_("can't specify a revision with --all"))
5332 5335
5333 5336 res = []
5334 5337 prefix = "data/"
5335 5338 suffix = ".i"
5336 5339 plen = len(prefix)
5337 5340 slen = len(suffix)
5338 5341 with repo.lock():
5339 5342 for fn, b, size in repo.store.datafiles():
5340 5343 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
5341 5344 res.append(fn[plen:-slen])
5342 5345 for f in res:
5343 5346 fm.startitem()
5344 5347 fm.write("path", '%s\n', f)
5345 5348 fm.end()
5346 5349 return
5347 5350
5348 5351 if rev and node:
5349 5352 raise error.Abort(_("please specify just one revision"))
5350 5353
5351 5354 if not node:
5352 5355 node = rev
5353 5356
5354 5357 char = {'l': '@', 'x': '*', '': ''}
5355 5358 mode = {'l': '644', 'x': '755', '': '644'}
5356 5359 ctx = scmutil.revsingle(repo, node)
5357 5360 mf = ctx.manifest()
5358 5361 for f in ctx:
5359 5362 fm.startitem()
5360 5363 fl = ctx[f].flags()
5361 5364 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
5362 5365 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
5363 5366 fm.write('path', '%s\n', f)
5364 5367 fm.end()
5365 5368
5366 5369 @command('^merge',
5367 5370 [('f', 'force', None,
5368 5371 _('force a merge including outstanding changes (DEPRECATED)')),
5369 5372 ('r', 'rev', '', _('revision to merge'), _('REV')),
5370 5373 ('P', 'preview', None,
5371 5374 _('review revisions to merge (no merge is performed)'))
5372 5375 ] + mergetoolopts,
5373 5376 _('[-P] [[-r] REV]'))
5374 5377 def merge(ui, repo, node=None, **opts):
5375 5378 """merge another revision into working directory
5376 5379
5377 5380 The current working directory is updated with all changes made in
5378 5381 the requested revision since the last common predecessor revision.
5379 5382
5380 5383 Files that changed between either parent are marked as changed for
5381 5384 the next commit and a commit must be performed before any further
5382 5385 updates to the repository are allowed. The next commit will have
5383 5386 two parents.
5384 5387
5385 5388 ``--tool`` can be used to specify the merge tool used for file
5386 5389 merges. It overrides the HGMERGE environment variable and your
5387 5390 configuration files. See :hg:`help merge-tools` for options.
5388 5391
5389 5392 If no revision is specified, the working directory's parent is a
5390 5393 head revision, and the current branch contains exactly one other
5391 5394 head, the other head is merged with by default. Otherwise, an
5392 5395 explicit revision with which to merge with must be provided.
5393 5396
5394 5397 See :hg:`help resolve` for information on handling file conflicts.
5395 5398
5396 5399 To undo an uncommitted merge, use :hg:`update --clean .` which
5397 5400 will check out a clean copy of the original merge parent, losing
5398 5401 all changes.
5399 5402
5400 5403 Returns 0 on success, 1 if there are unresolved files.
5401 5404 """
5402 5405
5403 5406 if opts.get('rev') and node:
5404 5407 raise error.Abort(_("please specify just one revision"))
5405 5408 if not node:
5406 5409 node = opts.get('rev')
5407 5410
5408 5411 if node:
5409 5412 node = scmutil.revsingle(repo, node).node()
5410 5413
5411 5414 if not node:
5412 5415 node = repo[destutil.destmerge(repo)].node()
5413 5416
5414 5417 if opts.get('preview'):
5415 5418 # find nodes that are ancestors of p2 but not of p1
5416 5419 p1 = repo.lookup('.')
5417 5420 p2 = repo.lookup(node)
5418 5421 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
5419 5422
5420 5423 displayer = cmdutil.show_changeset(ui, repo, opts)
5421 5424 for node in nodes:
5422 5425 displayer.show(repo[node])
5423 5426 displayer.close()
5424 5427 return 0
5425 5428
5426 5429 try:
5427 5430 # ui.forcemerge is an internal variable, do not document
5428 5431 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
5429 5432 force = opts.get('force')
5430 5433 return hg.merge(repo, node, force=force, mergeforce=force)
5431 5434 finally:
5432 5435 ui.setconfig('ui', 'forcemerge', '', 'merge')
5433 5436
5434 5437 @command('outgoing|out',
5435 5438 [('f', 'force', None, _('run even when the destination is unrelated')),
5436 5439 ('r', 'rev', [],
5437 5440 _('a changeset intended to be included in the destination'), _('REV')),
5438 5441 ('n', 'newest-first', None, _('show newest record first')),
5439 5442 ('B', 'bookmarks', False, _('compare bookmarks')),
5440 5443 ('b', 'branch', [], _('a specific branch you would like to push'),
5441 5444 _('BRANCH')),
5442 5445 ] + logopts + remoteopts + subrepoopts,
5443 5446 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
5444 5447 def outgoing(ui, repo, dest=None, **opts):
5445 5448 """show changesets not found in the destination
5446 5449
5447 5450 Show changesets not found in the specified destination repository
5448 5451 or the default push location. These are the changesets that would
5449 5452 be pushed if a push was requested.
5450 5453
5451 5454 See pull for details of valid destination formats.
5452 5455
5453 5456 .. container:: verbose
5454 5457
5455 5458 With -B/--bookmarks, the result of bookmark comparison between
5456 5459 local and remote repositories is displayed. With -v/--verbose,
5457 5460 status is also displayed for each bookmark like below::
5458 5461
5459 5462 BM1 01234567890a added
5460 5463 BM2 deleted
5461 5464 BM3 234567890abc advanced
5462 5465 BM4 34567890abcd diverged
5463 5466 BM5 4567890abcde changed
5464 5467
5465 5468 The action taken when pushing depends on the
5466 5469 status of each bookmark:
5467 5470
5468 5471 :``added``: push with ``-B`` will create it
5469 5472 :``deleted``: push with ``-B`` will delete it
5470 5473 :``advanced``: push will update it
5471 5474 :``diverged``: push with ``-B`` will update it
5472 5475 :``changed``: push with ``-B`` will update it
5473 5476
5474 5477 From the point of view of pushing behavior, bookmarks
5475 5478 existing only in the remote repository are treated as
5476 5479 ``deleted``, even if it is in fact added remotely.
5477 5480
5478 5481 Returns 0 if there are outgoing changes, 1 otherwise.
5479 5482 """
5480 5483 if opts.get('graph'):
5481 5484 cmdutil.checkunsupportedgraphflags([], opts)
5482 5485 o, other = hg._outgoing(ui, repo, dest, opts)
5483 5486 if not o:
5484 5487 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5485 5488 return
5486 5489
5487 5490 revdag = cmdutil.graphrevs(repo, o, opts)
5488 5491 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
5489 5492 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
5490 5493 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5491 5494 return 0
5492 5495
5493 5496 if opts.get('bookmarks'):
5494 5497 dest = ui.expandpath(dest or 'default-push', dest or 'default')
5495 5498 dest, branches = hg.parseurl(dest, opts.get('branch'))
5496 5499 other = hg.peer(repo, opts, dest)
5497 5500 if 'bookmarks' not in other.listkeys('namespaces'):
5498 5501 ui.warn(_("remote doesn't support bookmarks\n"))
5499 5502 return 0
5500 5503 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
5501 5504 return bookmarks.outgoing(ui, repo, other)
5502 5505
5503 5506 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
5504 5507 try:
5505 5508 return hg.outgoing(ui, repo, dest, opts)
5506 5509 finally:
5507 5510 del repo._subtoppath
5508 5511
5509 5512 @command('parents',
5510 5513 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
5511 5514 ] + templateopts,
5512 5515 _('[-r REV] [FILE]'),
5513 5516 inferrepo=True)
5514 5517 def parents(ui, repo, file_=None, **opts):
5515 5518 """show the parents of the working directory or revision (DEPRECATED)
5516 5519
5517 5520 Print the working directory's parent revisions. If a revision is
5518 5521 given via -r/--rev, the parent of that revision will be printed.
5519 5522 If a file argument is given, the revision in which the file was
5520 5523 last changed (before the working directory revision or the
5521 5524 argument to --rev if given) is printed.
5522 5525
5523 5526 This command is equivalent to::
5524 5527
5525 5528 hg log -r "p1()+p2()" or
5526 5529 hg log -r "p1(REV)+p2(REV)" or
5527 5530 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5528 5531 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5529 5532
5530 5533 See :hg:`summary` and :hg:`help revsets` for related information.
5531 5534
5532 5535 Returns 0 on success.
5533 5536 """
5534 5537
5535 5538 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
5536 5539
5537 5540 if file_:
5538 5541 m = scmutil.match(ctx, (file_,), opts)
5539 5542 if m.anypats() or len(m.files()) != 1:
5540 5543 raise error.Abort(_('can only specify an explicit filename'))
5541 5544 file_ = m.files()[0]
5542 5545 filenodes = []
5543 5546 for cp in ctx.parents():
5544 5547 if not cp:
5545 5548 continue
5546 5549 try:
5547 5550 filenodes.append(cp.filenode(file_))
5548 5551 except error.LookupError:
5549 5552 pass
5550 5553 if not filenodes:
5551 5554 raise error.Abort(_("'%s' not found in manifest!") % file_)
5552 5555 p = []
5553 5556 for fn in filenodes:
5554 5557 fctx = repo.filectx(file_, fileid=fn)
5555 5558 p.append(fctx.node())
5556 5559 else:
5557 5560 p = [cp.node() for cp in ctx.parents()]
5558 5561
5559 5562 displayer = cmdutil.show_changeset(ui, repo, opts)
5560 5563 for n in p:
5561 5564 if n != nullid:
5562 5565 displayer.show(repo[n])
5563 5566 displayer.close()
5564 5567
5565 5568 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True)
5566 5569 def paths(ui, repo, search=None, **opts):
5567 5570 """show aliases for remote repositories
5568 5571
5569 5572 Show definition of symbolic path name NAME. If no name is given,
5570 5573 show definition of all available names.
5571 5574
5572 5575 Option -q/--quiet suppresses all output when searching for NAME
5573 5576 and shows only the path names when listing all definitions.
5574 5577
5575 5578 Path names are defined in the [paths] section of your
5576 5579 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5577 5580 repository, ``.hg/hgrc`` is used, too.
5578 5581
5579 5582 The path names ``default`` and ``default-push`` have a special
5580 5583 meaning. When performing a push or pull operation, they are used
5581 5584 as fallbacks if no location is specified on the command-line.
5582 5585 When ``default-push`` is set, it will be used for push and
5583 5586 ``default`` will be used for pull; otherwise ``default`` is used
5584 5587 as the fallback for both. When cloning a repository, the clone
5585 5588 source is written as ``default`` in ``.hg/hgrc``.
5586 5589
5587 5590 .. note::
5588 5591
5589 5592 ``default`` and ``default-push`` apply to all inbound (e.g.
5590 5593 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5591 5594 and :hg:`bundle`) operations.
5592 5595
5593 5596 See :hg:`help urls` for more information.
5594 5597
5595 5598 Returns 0 on success.
5596 5599 """
5597 5600 if search:
5598 5601 pathitems = [(name, path) for name, path in ui.paths.iteritems()
5599 5602 if name == search]
5600 5603 else:
5601 5604 pathitems = sorted(ui.paths.iteritems())
5602 5605
5603 5606 fm = ui.formatter('paths', opts)
5604 5607 if fm:
5605 5608 hidepassword = str
5606 5609 else:
5607 5610 hidepassword = util.hidepassword
5608 5611 if ui.quiet:
5609 5612 namefmt = '%s\n'
5610 5613 else:
5611 5614 namefmt = '%s = '
5612 5615 showsubopts = not search and not ui.quiet
5613 5616
5614 5617 for name, path in pathitems:
5615 5618 fm.startitem()
5616 5619 fm.condwrite(not search, 'name', namefmt, name)
5617 5620 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
5618 5621 for subopt, value in sorted(path.suboptions.items()):
5619 5622 assert subopt not in ('name', 'url')
5620 5623 if showsubopts:
5621 5624 fm.plain('%s:%s = ' % (name, subopt))
5622 5625 fm.condwrite(showsubopts, subopt, '%s\n', value)
5623 5626
5624 5627 fm.end()
5625 5628
5626 5629 if search and not pathitems:
5627 5630 if not ui.quiet:
5628 5631 ui.warn(_("not found!\n"))
5629 5632 return 1
5630 5633 else:
5631 5634 return 0
5632 5635
5633 5636 @command('phase',
5634 5637 [('p', 'public', False, _('set changeset phase to public')),
5635 5638 ('d', 'draft', False, _('set changeset phase to draft')),
5636 5639 ('s', 'secret', False, _('set changeset phase to secret')),
5637 5640 ('f', 'force', False, _('allow to move boundary backward')),
5638 5641 ('r', 'rev', [], _('target revision'), _('REV')),
5639 5642 ],
5640 5643 _('[-p|-d|-s] [-f] [-r] [REV...]'))
5641 5644 def phase(ui, repo, *revs, **opts):
5642 5645 """set or show the current phase name
5643 5646
5644 5647 With no argument, show the phase name of the current revision(s).
5645 5648
5646 5649 With one of -p/--public, -d/--draft or -s/--secret, change the
5647 5650 phase value of the specified revisions.
5648 5651
5649 5652 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
5650 5653 lower phase to an higher phase. Phases are ordered as follows::
5651 5654
5652 5655 public < draft < secret
5653 5656
5654 5657 Returns 0 on success, 1 if some phases could not be changed.
5655 5658
5656 5659 (For more information about the phases concept, see :hg:`help phases`.)
5657 5660 """
5658 5661 # search for a unique phase argument
5659 5662 targetphase = None
5660 5663 for idx, name in enumerate(phases.phasenames):
5661 5664 if opts[name]:
5662 5665 if targetphase is not None:
5663 5666 raise error.Abort(_('only one phase can be specified'))
5664 5667 targetphase = idx
5665 5668
5666 5669 # look for specified revision
5667 5670 revs = list(revs)
5668 5671 revs.extend(opts['rev'])
5669 5672 if not revs:
5670 5673 # display both parents as the second parent phase can influence
5671 5674 # the phase of a merge commit
5672 5675 revs = [c.rev() for c in repo[None].parents()]
5673 5676
5674 5677 revs = scmutil.revrange(repo, revs)
5675 5678
5676 5679 lock = None
5677 5680 ret = 0
5678 5681 if targetphase is None:
5679 5682 # display
5680 5683 for r in revs:
5681 5684 ctx = repo[r]
5682 5685 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5683 5686 else:
5684 5687 tr = None
5685 5688 lock = repo.lock()
5686 5689 try:
5687 5690 tr = repo.transaction("phase")
5688 5691 # set phase
5689 5692 if not revs:
5690 5693 raise error.Abort(_('empty revision set'))
5691 5694 nodes = [repo[r].node() for r in revs]
5692 5695 # moving revision from public to draft may hide them
5693 5696 # We have to check result on an unfiltered repository
5694 5697 unfi = repo.unfiltered()
5695 5698 getphase = unfi._phasecache.phase
5696 5699 olddata = [getphase(unfi, r) for r in unfi]
5697 5700 phases.advanceboundary(repo, tr, targetphase, nodes)
5698 5701 if opts['force']:
5699 5702 phases.retractboundary(repo, tr, targetphase, nodes)
5700 5703 tr.close()
5701 5704 finally:
5702 5705 if tr is not None:
5703 5706 tr.release()
5704 5707 lock.release()
5705 5708 getphase = unfi._phasecache.phase
5706 5709 newdata = [getphase(unfi, r) for r in unfi]
5707 5710 changes = sum(newdata[r] != olddata[r] for r in unfi)
5708 5711 cl = unfi.changelog
5709 5712 rejected = [n for n in nodes
5710 5713 if newdata[cl.rev(n)] < targetphase]
5711 5714 if rejected:
5712 5715 ui.warn(_('cannot move %i changesets to a higher '
5713 5716 'phase, use --force\n') % len(rejected))
5714 5717 ret = 1
5715 5718 if changes:
5716 5719 msg = _('phase changed for %i changesets\n') % changes
5717 5720 if ret:
5718 5721 ui.status(msg)
5719 5722 else:
5720 5723 ui.note(msg)
5721 5724 else:
5722 5725 ui.warn(_('no phases changed\n'))
5723 5726 return ret
5724 5727
5725 5728 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5726 5729 """Run after a changegroup has been added via pull/unbundle
5727 5730
5728 5731 This takes arguments below:
5729 5732
5730 5733 :modheads: change of heads by pull/unbundle
5731 5734 :optupdate: updating working directory is needed or not
5732 5735 :checkout: update destination revision (or None to default destination)
5733 5736 :brev: a name, which might be a bookmark to be activated after updating
5734 5737 """
5735 5738 if modheads == 0:
5736 5739 return
5737 5740 if optupdate:
5738 5741 try:
5739 5742 return hg.updatetotally(ui, repo, checkout, brev)
5740 5743 except error.UpdateAbort as inst:
5741 5744 msg = _("not updating: %s") % str(inst)
5742 5745 hint = inst.hint
5743 5746 raise error.UpdateAbort(msg, hint=hint)
5744 5747 if modheads > 1:
5745 5748 currentbranchheads = len(repo.branchheads())
5746 5749 if currentbranchheads == modheads:
5747 5750 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
5748 5751 elif currentbranchheads > 1:
5749 5752 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
5750 5753 "merge)\n"))
5751 5754 else:
5752 5755 ui.status(_("(run 'hg heads' to see heads)\n"))
5753 5756 else:
5754 5757 ui.status(_("(run 'hg update' to get a working copy)\n"))
5755 5758
5756 5759 @command('^pull',
5757 5760 [('u', 'update', None,
5758 5761 _('update to new branch head if changesets were pulled')),
5759 5762 ('f', 'force', None, _('run even when remote repository is unrelated')),
5760 5763 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
5761 5764 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
5762 5765 ('b', 'branch', [], _('a specific branch you would like to pull'),
5763 5766 _('BRANCH')),
5764 5767 ] + remoteopts,
5765 5768 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
5766 5769 def pull(ui, repo, source="default", **opts):
5767 5770 """pull changes from the specified source
5768 5771
5769 5772 Pull changes from a remote repository to a local one.
5770 5773
5771 5774 This finds all changes from the repository at the specified path
5772 5775 or URL and adds them to a local repository (the current one unless
5773 5776 -R is specified). By default, this does not update the copy of the
5774 5777 project in the working directory.
5775 5778
5776 5779 Use :hg:`incoming` if you want to see what would have been added
5777 5780 by a pull at the time you issued this command. If you then decide
5778 5781 to add those changes to the repository, you should use :hg:`pull
5779 5782 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5780 5783
5781 5784 If SOURCE is omitted, the 'default' path will be used.
5782 5785 See :hg:`help urls` for more information.
5783 5786
5784 5787 Specifying bookmark as ``.`` is equivalent to specifying the active
5785 5788 bookmark's name.
5786 5789
5787 5790 Returns 0 on success, 1 if an update had unresolved files.
5788 5791 """
5789 5792 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
5790 5793 ui.status(_('pulling from %s\n') % util.hidepassword(source))
5791 5794 other = hg.peer(repo, opts, source)
5792 5795 try:
5793 5796 revs, checkout = hg.addbranchrevs(repo, other, branches,
5794 5797 opts.get('rev'))
5795 5798
5796 5799
5797 5800 pullopargs = {}
5798 5801 if opts.get('bookmark'):
5799 5802 if not revs:
5800 5803 revs = []
5801 5804 # The list of bookmark used here is not the one used to actually
5802 5805 # update the bookmark name. This can result in the revision pulled
5803 5806 # not ending up with the name of the bookmark because of a race
5804 5807 # condition on the server. (See issue 4689 for details)
5805 5808 remotebookmarks = other.listkeys('bookmarks')
5806 5809 pullopargs['remotebookmarks'] = remotebookmarks
5807 5810 for b in opts['bookmark']:
5808 5811 b = repo._bookmarks.expandname(b)
5809 5812 if b not in remotebookmarks:
5810 5813 raise error.Abort(_('remote bookmark %s not found!') % b)
5811 5814 revs.append(remotebookmarks[b])
5812 5815
5813 5816 if revs:
5814 5817 try:
5815 5818 # When 'rev' is a bookmark name, we cannot guarantee that it
5816 5819 # will be updated with that name because of a race condition
5817 5820 # server side. (See issue 4689 for details)
5818 5821 oldrevs = revs
5819 5822 revs = [] # actually, nodes
5820 5823 for r in oldrevs:
5821 5824 node = other.lookup(r)
5822 5825 revs.append(node)
5823 5826 if r == checkout:
5824 5827 checkout = node
5825 5828 except error.CapabilityError:
5826 5829 err = _("other repository doesn't support revision lookup, "
5827 5830 "so a rev cannot be specified.")
5828 5831 raise error.Abort(err)
5829 5832
5830 5833 pullopargs.update(opts.get('opargs', {}))
5831 5834 modheads = exchange.pull(repo, other, heads=revs,
5832 5835 force=opts.get('force'),
5833 5836 bookmarks=opts.get('bookmark', ()),
5834 5837 opargs=pullopargs).cgresult
5835 5838
5836 5839 # brev is a name, which might be a bookmark to be activated at
5837 5840 # the end of the update. In other words, it is an explicit
5838 5841 # destination of the update
5839 5842 brev = None
5840 5843
5841 5844 if checkout:
5842 5845 checkout = str(repo.changelog.rev(checkout))
5843 5846
5844 5847 # order below depends on implementation of
5845 5848 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5846 5849 # because 'checkout' is determined without it.
5847 5850 if opts.get('rev'):
5848 5851 brev = opts['rev'][0]
5849 5852 elif opts.get('branch'):
5850 5853 brev = opts['branch'][0]
5851 5854 else:
5852 5855 brev = branches[0]
5853 5856 repo._subtoppath = source
5854 5857 try:
5855 5858 ret = postincoming(ui, repo, modheads, opts.get('update'),
5856 5859 checkout, brev)
5857 5860
5858 5861 finally:
5859 5862 del repo._subtoppath
5860 5863
5861 5864 finally:
5862 5865 other.close()
5863 5866 return ret
5864 5867
5865 5868 @command('^push',
5866 5869 [('f', 'force', None, _('force push')),
5867 5870 ('r', 'rev', [],
5868 5871 _('a changeset intended to be included in the destination'),
5869 5872 _('REV')),
5870 5873 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
5871 5874 ('b', 'branch', [],
5872 5875 _('a specific branch you would like to push'), _('BRANCH')),
5873 5876 ('', 'new-branch', False, _('allow pushing a new branch')),
5874 5877 ] + remoteopts,
5875 5878 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
5876 5879 def push(ui, repo, dest=None, **opts):
5877 5880 """push changes to the specified destination
5878 5881
5879 5882 Push changesets from the local repository to the specified
5880 5883 destination.
5881 5884
5882 5885 This operation is symmetrical to pull: it is identical to a pull
5883 5886 in the destination repository from the current one.
5884 5887
5885 5888 By default, push will not allow creation of new heads at the
5886 5889 destination, since multiple heads would make it unclear which head
5887 5890 to use. In this situation, it is recommended to pull and merge
5888 5891 before pushing.
5889 5892
5890 5893 Use --new-branch if you want to allow push to create a new named
5891 5894 branch that is not present at the destination. This allows you to
5892 5895 only create a new branch without forcing other changes.
5893 5896
5894 5897 .. note::
5895 5898
5896 5899 Extra care should be taken with the -f/--force option,
5897 5900 which will push all new heads on all branches, an action which will
5898 5901 almost always cause confusion for collaborators.
5899 5902
5900 5903 If -r/--rev is used, the specified revision and all its ancestors
5901 5904 will be pushed to the remote repository.
5902 5905
5903 5906 If -B/--bookmark is used, the specified bookmarked revision, its
5904 5907 ancestors, and the bookmark will be pushed to the remote
5905 5908 repository. Specifying ``.`` is equivalent to specifying the active
5906 5909 bookmark's name.
5907 5910
5908 5911 Please see :hg:`help urls` for important details about ``ssh://``
5909 5912 URLs. If DESTINATION is omitted, a default path will be used.
5910 5913
5911 5914 Returns 0 if push was successful, 1 if nothing to push.
5912 5915 """
5913 5916
5914 5917 if opts.get('bookmark'):
5915 5918 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
5916 5919 for b in opts['bookmark']:
5917 5920 # translate -B options to -r so changesets get pushed
5918 5921 b = repo._bookmarks.expandname(b)
5919 5922 if b in repo._bookmarks:
5920 5923 opts.setdefault('rev', []).append(b)
5921 5924 else:
5922 5925 # if we try to push a deleted bookmark, translate it to null
5923 5926 # this lets simultaneous -r, -b options continue working
5924 5927 opts.setdefault('rev', []).append("null")
5925 5928
5926 5929 path = ui.paths.getpath(dest, default=('default-push', 'default'))
5927 5930 if not path:
5928 5931 raise error.Abort(_('default repository not configured!'),
5929 5932 hint=_('see the "path" section in "hg help config"'))
5930 5933 dest = path.pushloc or path.loc
5931 5934 branches = (path.branch, opts.get('branch') or [])
5932 5935 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
5933 5936 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
5934 5937 other = hg.peer(repo, opts, dest)
5935 5938
5936 5939 if revs:
5937 5940 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
5938 5941 if not revs:
5939 5942 raise error.Abort(_("specified revisions evaluate to an empty set"),
5940 5943 hint=_("use different revision arguments"))
5941 5944 elif path.pushrev:
5942 5945 # It doesn't make any sense to specify ancestor revisions. So limit
5943 5946 # to DAG heads to make discovery simpler.
5944 5947 expr = revset.formatspec('heads(%r)', path.pushrev)
5945 5948 revs = scmutil.revrange(repo, [expr])
5946 5949 revs = [repo[rev].node() for rev in revs]
5947 5950 if not revs:
5948 5951 raise error.Abort(_('default push revset for path evaluates to an '
5949 5952 'empty set'))
5950 5953
5951 5954 repo._subtoppath = dest
5952 5955 try:
5953 5956 # push subrepos depth-first for coherent ordering
5954 5957 c = repo['']
5955 5958 subs = c.substate # only repos that are committed
5956 5959 for s in sorted(subs):
5957 5960 result = c.sub(s).push(opts)
5958 5961 if result == 0:
5959 5962 return not result
5960 5963 finally:
5961 5964 del repo._subtoppath
5962 5965 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
5963 5966 newbranch=opts.get('new_branch'),
5964 5967 bookmarks=opts.get('bookmark', ()),
5965 5968 opargs=opts.get('opargs'))
5966 5969
5967 5970 result = not pushop.cgresult
5968 5971
5969 5972 if pushop.bkresult is not None:
5970 5973 if pushop.bkresult == 2:
5971 5974 result = 2
5972 5975 elif not result and pushop.bkresult:
5973 5976 result = 2
5974 5977
5975 5978 return result
5976 5979
5977 5980 @command('recover', [])
5978 5981 def recover(ui, repo):
5979 5982 """roll back an interrupted transaction
5980 5983
5981 5984 Recover from an interrupted commit or pull.
5982 5985
5983 5986 This command tries to fix the repository status after an
5984 5987 interrupted operation. It should only be necessary when Mercurial
5985 5988 suggests it.
5986 5989
5987 5990 Returns 0 if successful, 1 if nothing to recover or verify fails.
5988 5991 """
5989 5992 if repo.recover():
5990 5993 return hg.verify(repo)
5991 5994 return 1
5992 5995
5993 5996 @command('^remove|rm',
5994 5997 [('A', 'after', None, _('record delete for missing files')),
5995 5998 ('f', 'force', None,
5996 5999 _('forget added files, delete modified files')),
5997 6000 ] + subrepoopts + walkopts,
5998 6001 _('[OPTION]... FILE...'),
5999 6002 inferrepo=True)
6000 6003 def remove(ui, repo, *pats, **opts):
6001 6004 """remove the specified files on the next commit
6002 6005
6003 6006 Schedule the indicated files for removal from the current branch.
6004 6007
6005 6008 This command schedules the files to be removed at the next commit.
6006 6009 To undo a remove before that, see :hg:`revert`. To undo added
6007 6010 files, see :hg:`forget`.
6008 6011
6009 6012 .. container:: verbose
6010 6013
6011 6014 -A/--after can be used to remove only files that have already
6012 6015 been deleted, -f/--force can be used to force deletion, and -Af
6013 6016 can be used to remove files from the next revision without
6014 6017 deleting them from the working directory.
6015 6018
6016 6019 The following table details the behavior of remove for different
6017 6020 file states (columns) and option combinations (rows). The file
6018 6021 states are Added [A], Clean [C], Modified [M] and Missing [!]
6019 6022 (as reported by :hg:`status`). The actions are Warn, Remove
6020 6023 (from branch) and Delete (from disk):
6021 6024
6022 6025 ========= == == == ==
6023 6026 opt/state A C M !
6024 6027 ========= == == == ==
6025 6028 none W RD W R
6026 6029 -f R RD RD R
6027 6030 -A W W W R
6028 6031 -Af R R R R
6029 6032 ========= == == == ==
6030 6033
6031 6034 .. note::
6032 6035
6033 6036 :hg:`remove` never deletes files in Added [A] state from the
6034 6037 working directory, not even if ``--force`` is specified.
6035 6038
6036 6039 Returns 0 on success, 1 if any warnings encountered.
6037 6040 """
6038 6041
6039 6042 after, force = opts.get('after'), opts.get('force')
6040 6043 if not pats and not after:
6041 6044 raise error.Abort(_('no files specified'))
6042 6045
6043 6046 m = scmutil.match(repo[None], pats, opts)
6044 6047 subrepos = opts.get('subrepos')
6045 6048 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
6046 6049
6047 6050 @command('rename|move|mv',
6048 6051 [('A', 'after', None, _('record a rename that has already occurred')),
6049 6052 ('f', 'force', None, _('forcibly copy over an existing managed file')),
6050 6053 ] + walkopts + dryrunopts,
6051 6054 _('[OPTION]... SOURCE... DEST'))
6052 6055 def rename(ui, repo, *pats, **opts):
6053 6056 """rename files; equivalent of copy + remove
6054 6057
6055 6058 Mark dest as copies of sources; mark sources for deletion. If dest
6056 6059 is a directory, copies are put in that directory. If dest is a
6057 6060 file, there can only be one source.
6058 6061
6059 6062 By default, this command copies the contents of files as they
6060 6063 exist in the working directory. If invoked with -A/--after, the
6061 6064 operation is recorded, but no copying is performed.
6062 6065
6063 6066 This command takes effect at the next commit. To undo a rename
6064 6067 before that, see :hg:`revert`.
6065 6068
6066 6069 Returns 0 on success, 1 if errors are encountered.
6067 6070 """
6068 6071 with repo.wlock(False):
6069 6072 return cmdutil.copy(ui, repo, pats, opts, rename=True)
6070 6073
6071 6074 @command('resolve',
6072 6075 [('a', 'all', None, _('select all unresolved files')),
6073 6076 ('l', 'list', None, _('list state of files needing merge')),
6074 6077 ('m', 'mark', None, _('mark files as resolved')),
6075 6078 ('u', 'unmark', None, _('mark files as unresolved')),
6076 6079 ('n', 'no-status', None, _('hide status prefix'))]
6077 6080 + mergetoolopts + walkopts + formatteropts,
6078 6081 _('[OPTION]... [FILE]...'),
6079 6082 inferrepo=True)
6080 6083 def resolve(ui, repo, *pats, **opts):
6081 6084 """redo merges or set/view the merge status of files
6082 6085
6083 6086 Merges with unresolved conflicts are often the result of
6084 6087 non-interactive merging using the ``internal:merge`` configuration
6085 6088 setting, or a command-line merge tool like ``diff3``. The resolve
6086 6089 command is used to manage the files involved in a merge, after
6087 6090 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
6088 6091 working directory must have two parents). See :hg:`help
6089 6092 merge-tools` for information on configuring merge tools.
6090 6093
6091 6094 The resolve command can be used in the following ways:
6092 6095
6093 6096 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
6094 6097 files, discarding any previous merge attempts. Re-merging is not
6095 6098 performed for files already marked as resolved. Use ``--all/-a``
6096 6099 to select all unresolved files. ``--tool`` can be used to specify
6097 6100 the merge tool used for the given files. It overrides the HGMERGE
6098 6101 environment variable and your configuration files. Previous file
6099 6102 contents are saved with a ``.orig`` suffix.
6100 6103
6101 6104 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
6102 6105 (e.g. after having manually fixed-up the files). The default is
6103 6106 to mark all unresolved files.
6104 6107
6105 6108 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
6106 6109 default is to mark all resolved files.
6107 6110
6108 6111 - :hg:`resolve -l`: list files which had or still have conflicts.
6109 6112 In the printed list, ``U`` = unresolved and ``R`` = resolved.
6110 6113
6111 6114 .. note::
6112 6115
6113 6116 Mercurial will not let you commit files with unresolved merge
6114 6117 conflicts. You must use :hg:`resolve -m ...` before you can
6115 6118 commit after a conflicting merge.
6116 6119
6117 6120 Returns 0 on success, 1 if any files fail a resolve attempt.
6118 6121 """
6119 6122
6120 6123 flaglist = 'all mark unmark list no_status'.split()
6121 6124 all, mark, unmark, show, nostatus = \
6122 6125 [opts.get(o) for o in flaglist]
6123 6126
6124 6127 if (show and (mark or unmark)) or (mark and unmark):
6125 6128 raise error.Abort(_("too many options specified"))
6126 6129 if pats and all:
6127 6130 raise error.Abort(_("can't specify --all and patterns"))
6128 6131 if not (all or pats or show or mark or unmark):
6129 6132 raise error.Abort(_('no files or directories specified'),
6130 6133 hint=('use --all to re-merge all unresolved files'))
6131 6134
6132 6135 if show:
6133 6136 fm = ui.formatter('resolve', opts)
6134 6137 ms = mergemod.mergestate.read(repo)
6135 6138 m = scmutil.match(repo[None], pats, opts)
6136 6139 for f in ms:
6137 6140 if not m(f):
6138 6141 continue
6139 6142 l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
6140 6143 'd': 'driverresolved'}[ms[f]]
6141 6144 fm.startitem()
6142 6145 fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
6143 6146 fm.write('path', '%s\n', f, label=l)
6144 6147 fm.end()
6145 6148 return 0
6146 6149
6147 6150 with repo.wlock():
6148 6151 ms = mergemod.mergestate.read(repo)
6149 6152
6150 6153 if not (ms.active() or repo.dirstate.p2() != nullid):
6151 6154 raise error.Abort(
6152 6155 _('resolve command not applicable when not merging'))
6153 6156
6154 6157 wctx = repo[None]
6155 6158
6156 6159 if ms.mergedriver and ms.mdstate() == 'u':
6157 6160 proceed = mergemod.driverpreprocess(repo, ms, wctx)
6158 6161 ms.commit()
6159 6162 # allow mark and unmark to go through
6160 6163 if not mark and not unmark and not proceed:
6161 6164 return 1
6162 6165
6163 6166 m = scmutil.match(wctx, pats, opts)
6164 6167 ret = 0
6165 6168 didwork = False
6166 6169 runconclude = False
6167 6170
6168 6171 tocomplete = []
6169 6172 for f in ms:
6170 6173 if not m(f):
6171 6174 continue
6172 6175
6173 6176 didwork = True
6174 6177
6175 6178 # don't let driver-resolved files be marked, and run the conclude
6176 6179 # step if asked to resolve
6177 6180 if ms[f] == "d":
6178 6181 exact = m.exact(f)
6179 6182 if mark:
6180 6183 if exact:
6181 6184 ui.warn(_('not marking %s as it is driver-resolved\n')
6182 6185 % f)
6183 6186 elif unmark:
6184 6187 if exact:
6185 6188 ui.warn(_('not unmarking %s as it is driver-resolved\n')
6186 6189 % f)
6187 6190 else:
6188 6191 runconclude = True
6189 6192 continue
6190 6193
6191 6194 if mark:
6192 6195 ms.mark(f, "r")
6193 6196 elif unmark:
6194 6197 ms.mark(f, "u")
6195 6198 else:
6196 6199 # backup pre-resolve (merge uses .orig for its own purposes)
6197 6200 a = repo.wjoin(f)
6198 6201 try:
6199 6202 util.copyfile(a, a + ".resolve")
6200 6203 except (IOError, OSError) as inst:
6201 6204 if inst.errno != errno.ENOENT:
6202 6205 raise
6203 6206
6204 6207 try:
6205 6208 # preresolve file
6206 6209 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
6207 6210 'resolve')
6208 6211 complete, r = ms.preresolve(f, wctx)
6209 6212 if not complete:
6210 6213 tocomplete.append(f)
6211 6214 elif r:
6212 6215 ret = 1
6213 6216 finally:
6214 6217 ui.setconfig('ui', 'forcemerge', '', 'resolve')
6215 6218 ms.commit()
6216 6219
6217 6220 # replace filemerge's .orig file with our resolve file, but only
6218 6221 # for merges that are complete
6219 6222 if complete:
6220 6223 try:
6221 6224 util.rename(a + ".resolve",
6222 6225 scmutil.origpath(ui, repo, a))
6223 6226 except OSError as inst:
6224 6227 if inst.errno != errno.ENOENT:
6225 6228 raise
6226 6229
6227 6230 for f in tocomplete:
6228 6231 try:
6229 6232 # resolve file
6230 6233 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
6231 6234 'resolve')
6232 6235 r = ms.resolve(f, wctx)
6233 6236 if r:
6234 6237 ret = 1
6235 6238 finally:
6236 6239 ui.setconfig('ui', 'forcemerge', '', 'resolve')
6237 6240 ms.commit()
6238 6241
6239 6242 # replace filemerge's .orig file with our resolve file
6240 6243 a = repo.wjoin(f)
6241 6244 try:
6242 6245 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
6243 6246 except OSError as inst:
6244 6247 if inst.errno != errno.ENOENT:
6245 6248 raise
6246 6249
6247 6250 ms.commit()
6248 6251 ms.recordactions()
6249 6252
6250 6253 if not didwork and pats:
6251 6254 hint = None
6252 6255 if not any([p for p in pats if p.find(':') >= 0]):
6253 6256 pats = ['path:%s' % p for p in pats]
6254 6257 m = scmutil.match(wctx, pats, opts)
6255 6258 for f in ms:
6256 6259 if not m(f):
6257 6260 continue
6258 6261 flags = ''.join(['-%s ' % o[0] for o in flaglist
6259 6262 if opts.get(o)])
6260 6263 hint = _("(try: hg resolve %s%s)\n") % (
6261 6264 flags,
6262 6265 ' '.join(pats))
6263 6266 break
6264 6267 ui.warn(_("arguments do not match paths that need resolving\n"))
6265 6268 if hint:
6266 6269 ui.warn(hint)
6267 6270 elif ms.mergedriver and ms.mdstate() != 's':
6268 6271 # run conclude step when either a driver-resolved file is requested
6269 6272 # or there are no driver-resolved files
6270 6273 # we can't use 'ret' to determine whether any files are unresolved
6271 6274 # because we might not have tried to resolve some
6272 6275 if ((runconclude or not list(ms.driverresolved()))
6273 6276 and not list(ms.unresolved())):
6274 6277 proceed = mergemod.driverconclude(repo, ms, wctx)
6275 6278 ms.commit()
6276 6279 if not proceed:
6277 6280 return 1
6278 6281
6279 6282 # Nudge users into finishing an unfinished operation
6280 6283 unresolvedf = list(ms.unresolved())
6281 6284 driverresolvedf = list(ms.driverresolved())
6282 6285 if not unresolvedf and not driverresolvedf:
6283 6286 ui.status(_('(no more unresolved files)\n'))
6284 6287 cmdutil.checkafterresolved(repo)
6285 6288 elif not unresolvedf:
6286 6289 ui.status(_('(no more unresolved files -- '
6287 6290 'run "hg resolve --all" to conclude)\n'))
6288 6291
6289 6292 return ret
6290 6293
6291 6294 @command('revert',
6292 6295 [('a', 'all', None, _('revert all changes when no arguments given')),
6293 6296 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6294 6297 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
6295 6298 ('C', 'no-backup', None, _('do not save backup copies of files')),
6296 6299 ('i', 'interactive', None,
6297 6300 _('interactively select the changes (EXPERIMENTAL)')),
6298 6301 ] + walkopts + dryrunopts,
6299 6302 _('[OPTION]... [-r REV] [NAME]...'))
6300 6303 def revert(ui, repo, *pats, **opts):
6301 6304 """restore files to their checkout state
6302 6305
6303 6306 .. note::
6304 6307
6305 6308 To check out earlier revisions, you should use :hg:`update REV`.
6306 6309 To cancel an uncommitted merge (and lose your changes),
6307 6310 use :hg:`update --clean .`.
6308 6311
6309 6312 With no revision specified, revert the specified files or directories
6310 6313 to the contents they had in the parent of the working directory.
6311 6314 This restores the contents of files to an unmodified
6312 6315 state and unschedules adds, removes, copies, and renames. If the
6313 6316 working directory has two parents, you must explicitly specify a
6314 6317 revision.
6315 6318
6316 6319 Using the -r/--rev or -d/--date options, revert the given files or
6317 6320 directories to their states as of a specific revision. Because
6318 6321 revert does not change the working directory parents, this will
6319 6322 cause these files to appear modified. This can be helpful to "back
6320 6323 out" some or all of an earlier change. See :hg:`backout` for a
6321 6324 related method.
6322 6325
6323 6326 Modified files are saved with a .orig suffix before reverting.
6324 6327 To disable these backups, use --no-backup. It is possible to store
6325 6328 the backup files in a custom directory relative to the root of the
6326 6329 repository by setting the ``ui.origbackuppath`` configuration
6327 6330 option.
6328 6331
6329 6332 See :hg:`help dates` for a list of formats valid for -d/--date.
6330 6333
6331 6334 See :hg:`help backout` for a way to reverse the effect of an
6332 6335 earlier changeset.
6333 6336
6334 6337 Returns 0 on success.
6335 6338 """
6336 6339
6337 6340 if opts.get("date"):
6338 6341 if opts.get("rev"):
6339 6342 raise error.Abort(_("you can't specify a revision and a date"))
6340 6343 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
6341 6344
6342 6345 parent, p2 = repo.dirstate.parents()
6343 6346 if not opts.get('rev') and p2 != nullid:
6344 6347 # revert after merge is a trap for new users (issue2915)
6345 6348 raise error.Abort(_('uncommitted merge with no revision specified'),
6346 6349 hint=_("use 'hg update' or see 'hg help revert'"))
6347 6350
6348 6351 ctx = scmutil.revsingle(repo, opts.get('rev'))
6349 6352
6350 6353 if (not (pats or opts.get('include') or opts.get('exclude') or
6351 6354 opts.get('all') or opts.get('interactive'))):
6352 6355 msg = _("no files or directories specified")
6353 6356 if p2 != nullid:
6354 6357 hint = _("uncommitted merge, use --all to discard all changes,"
6355 6358 " or 'hg update -C .' to abort the merge")
6356 6359 raise error.Abort(msg, hint=hint)
6357 6360 dirty = any(repo.status())
6358 6361 node = ctx.node()
6359 6362 if node != parent:
6360 6363 if dirty:
6361 6364 hint = _("uncommitted changes, use --all to discard all"
6362 6365 " changes, or 'hg update %s' to update") % ctx.rev()
6363 6366 else:
6364 6367 hint = _("use --all to revert all files,"
6365 6368 " or 'hg update %s' to update") % ctx.rev()
6366 6369 elif dirty:
6367 6370 hint = _("uncommitted changes, use --all to discard all changes")
6368 6371 else:
6369 6372 hint = _("use --all to revert all files")
6370 6373 raise error.Abort(msg, hint=hint)
6371 6374
6372 6375 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
6373 6376
6374 6377 @command('rollback', dryrunopts +
6375 6378 [('f', 'force', False, _('ignore safety measures'))])
6376 6379 def rollback(ui, repo, **opts):
6377 6380 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6378 6381
6379 6382 Please use :hg:`commit --amend` instead of rollback to correct
6380 6383 mistakes in the last commit.
6381 6384
6382 6385 This command should be used with care. There is only one level of
6383 6386 rollback, and there is no way to undo a rollback. It will also
6384 6387 restore the dirstate at the time of the last transaction, losing
6385 6388 any dirstate changes since that time. This command does not alter
6386 6389 the working directory.
6387 6390
6388 6391 Transactions are used to encapsulate the effects of all commands
6389 6392 that create new changesets or propagate existing changesets into a
6390 6393 repository.
6391 6394
6392 6395 .. container:: verbose
6393 6396
6394 6397 For example, the following commands are transactional, and their
6395 6398 effects can be rolled back:
6396 6399
6397 6400 - commit
6398 6401 - import
6399 6402 - pull
6400 6403 - push (with this repository as the destination)
6401 6404 - unbundle
6402 6405
6403 6406 To avoid permanent data loss, rollback will refuse to rollback a
6404 6407 commit transaction if it isn't checked out. Use --force to
6405 6408 override this protection.
6406 6409
6407 6410 The rollback command can be entirely disabled by setting the
6408 6411 ``ui.rollback`` configuration setting to false. If you're here
6409 6412 because you want to use rollback and it's disabled, you can
6410 6413 re-enable the command by setting ``ui.rollback`` to true.
6411 6414
6412 6415 This command is not intended for use on public repositories. Once
6413 6416 changes are visible for pull by other users, rolling a transaction
6414 6417 back locally is ineffective (someone else may already have pulled
6415 6418 the changes). Furthermore, a race is possible with readers of the
6416 6419 repository; for example an in-progress pull from the repository
6417 6420 may fail if a rollback is performed.
6418 6421
6419 6422 Returns 0 on success, 1 if no rollback data is available.
6420 6423 """
6421 6424 if not ui.configbool('ui', 'rollback', True):
6422 6425 raise error.Abort(_('rollback is disabled because it is unsafe'),
6423 6426 hint=('see `hg help -v rollback` for information'))
6424 6427 return repo.rollback(dryrun=opts.get('dry_run'),
6425 6428 force=opts.get('force'))
6426 6429
6427 6430 @command('root', [])
6428 6431 def root(ui, repo):
6429 6432 """print the root (top) of the current working directory
6430 6433
6431 6434 Print the root directory of the current repository.
6432 6435
6433 6436 Returns 0 on success.
6434 6437 """
6435 6438 ui.write(repo.root + "\n")
6436 6439
6437 6440 @command('^serve',
6438 6441 [('A', 'accesslog', '', _('name of access log file to write to'),
6439 6442 _('FILE')),
6440 6443 ('d', 'daemon', None, _('run server in background')),
6441 6444 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
6442 6445 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
6443 6446 # use string type, then we can check if something was passed
6444 6447 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
6445 6448 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
6446 6449 _('ADDR')),
6447 6450 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
6448 6451 _('PREFIX')),
6449 6452 ('n', 'name', '',
6450 6453 _('name to show in web pages (default: working directory)'), _('NAME')),
6451 6454 ('', 'web-conf', '',
6452 6455 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
6453 6456 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
6454 6457 _('FILE')),
6455 6458 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
6456 6459 ('', 'stdio', None, _('for remote clients')),
6457 6460 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
6458 6461 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
6459 6462 ('', 'style', '', _('template style to use'), _('STYLE')),
6460 6463 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
6461 6464 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
6462 6465 _('[OPTION]...'),
6463 6466 optionalrepo=True)
6464 6467 def serve(ui, repo, **opts):
6465 6468 """start stand-alone webserver
6466 6469
6467 6470 Start a local HTTP repository browser and pull server. You can use
6468 6471 this for ad-hoc sharing and browsing of repositories. It is
6469 6472 recommended to use a real web server to serve a repository for
6470 6473 longer periods of time.
6471 6474
6472 6475 Please note that the server does not implement access control.
6473 6476 This means that, by default, anybody can read from the server and
6474 6477 nobody can write to it by default. Set the ``web.allow_push``
6475 6478 option to ``*`` to allow everybody to push to the server. You
6476 6479 should use a real web server if you need to authenticate users.
6477 6480
6478 6481 By default, the server logs accesses to stdout and errors to
6479 6482 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6480 6483 files.
6481 6484
6482 6485 To have the server choose a free port number to listen on, specify
6483 6486 a port number of 0; in this case, the server will print the port
6484 6487 number it uses.
6485 6488
6486 6489 Returns 0 on success.
6487 6490 """
6488 6491
6489 6492 if opts["stdio"] and opts["cmdserver"]:
6490 6493 raise error.Abort(_("cannot use --stdio with --cmdserver"))
6491 6494
6492 6495 if opts["stdio"]:
6493 6496 if repo is None:
6494 6497 raise error.RepoError(_("there is no Mercurial repository here"
6495 6498 " (.hg not found)"))
6496 6499 s = sshserver.sshserver(ui, repo)
6497 6500 s.serve_forever()
6498 6501
6499 6502 if opts["cmdserver"]:
6500 6503 service = commandserver.createservice(ui, repo, opts)
6501 6504 else:
6502 6505 service = hgweb.createservice(ui, repo, opts)
6503 6506 return cmdutil.service(opts, initfn=service.init, runfn=service.run)
6504 6507
6505 6508 @command('^status|st',
6506 6509 [('A', 'all', None, _('show status of all files')),
6507 6510 ('m', 'modified', None, _('show only modified files')),
6508 6511 ('a', 'added', None, _('show only added files')),
6509 6512 ('r', 'removed', None, _('show only removed files')),
6510 6513 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
6511 6514 ('c', 'clean', None, _('show only files without changes')),
6512 6515 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
6513 6516 ('i', 'ignored', None, _('show only ignored files')),
6514 6517 ('n', 'no-status', None, _('hide status prefix')),
6515 6518 ('C', 'copies', None, _('show source of copied files')),
6516 6519 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
6517 6520 ('', 'rev', [], _('show difference from revision'), _('REV')),
6518 6521 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
6519 6522 ] + walkopts + subrepoopts + formatteropts,
6520 6523 _('[OPTION]... [FILE]...'),
6521 6524 inferrepo=True)
6522 6525 def status(ui, repo, *pats, **opts):
6523 6526 """show changed files in the working directory
6524 6527
6525 6528 Show status of files in the repository. If names are given, only
6526 6529 files that match are shown. Files that are clean or ignored or
6527 6530 the source of a copy/move operation, are not listed unless
6528 6531 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6529 6532 Unless options described with "show only ..." are given, the
6530 6533 options -mardu are used.
6531 6534
6532 6535 Option -q/--quiet hides untracked (unknown and ignored) files
6533 6536 unless explicitly requested with -u/--unknown or -i/--ignored.
6534 6537
6535 6538 .. note::
6536 6539
6537 6540 :hg:`status` may appear to disagree with diff if permissions have
6538 6541 changed or a merge has occurred. The standard diff format does
6539 6542 not report permission changes and diff only reports changes
6540 6543 relative to one merge parent.
6541 6544
6542 6545 If one revision is given, it is used as the base revision.
6543 6546 If two revisions are given, the differences between them are
6544 6547 shown. The --change option can also be used as a shortcut to list
6545 6548 the changed files of a revision from its first parent.
6546 6549
6547 6550 The codes used to show the status of files are::
6548 6551
6549 6552 M = modified
6550 6553 A = added
6551 6554 R = removed
6552 6555 C = clean
6553 6556 ! = missing (deleted by non-hg command, but still tracked)
6554 6557 ? = not tracked
6555 6558 I = ignored
6556 6559 = origin of the previous file (with --copies)
6557 6560
6558 6561 .. container:: verbose
6559 6562
6560 6563 Examples:
6561 6564
6562 6565 - show changes in the working directory relative to a
6563 6566 changeset::
6564 6567
6565 6568 hg status --rev 9353
6566 6569
6567 6570 - show changes in the working directory relative to the
6568 6571 current directory (see :hg:`help patterns` for more information)::
6569 6572
6570 6573 hg status re:
6571 6574
6572 6575 - show all changes including copies in an existing changeset::
6573 6576
6574 6577 hg status --copies --change 9353
6575 6578
6576 6579 - get a NUL separated list of added files, suitable for xargs::
6577 6580
6578 6581 hg status -an0
6579 6582
6580 6583 Returns 0 on success.
6581 6584 """
6582 6585
6583 6586 revs = opts.get('rev')
6584 6587 change = opts.get('change')
6585 6588
6586 6589 if revs and change:
6587 6590 msg = _('cannot specify --rev and --change at the same time')
6588 6591 raise error.Abort(msg)
6589 6592 elif change:
6590 6593 node2 = scmutil.revsingle(repo, change, None).node()
6591 6594 node1 = repo[node2].p1().node()
6592 6595 else:
6593 6596 node1, node2 = scmutil.revpair(repo, revs)
6594 6597
6595 6598 if pats:
6596 6599 cwd = repo.getcwd()
6597 6600 else:
6598 6601 cwd = ''
6599 6602
6600 6603 if opts.get('print0'):
6601 6604 end = '\0'
6602 6605 else:
6603 6606 end = '\n'
6604 6607 copy = {}
6605 6608 states = 'modified added removed deleted unknown ignored clean'.split()
6606 6609 show = [k for k in states if opts.get(k)]
6607 6610 if opts.get('all'):
6608 6611 show += ui.quiet and (states[:4] + ['clean']) or states
6609 6612 if not show:
6610 6613 if ui.quiet:
6611 6614 show = states[:4]
6612 6615 else:
6613 6616 show = states[:5]
6614 6617
6615 6618 m = scmutil.match(repo[node2], pats, opts)
6616 6619 stat = repo.status(node1, node2, m,
6617 6620 'ignored' in show, 'clean' in show, 'unknown' in show,
6618 6621 opts.get('subrepos'))
6619 6622 changestates = zip(states, 'MAR!?IC', stat)
6620 6623
6621 6624 if (opts.get('all') or opts.get('copies')
6622 6625 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
6623 6626 copy = copies.pathcopies(repo[node1], repo[node2], m)
6624 6627
6625 6628 fm = ui.formatter('status', opts)
6626 6629 fmt = '%s' + end
6627 6630 showchar = not opts.get('no_status')
6628 6631
6629 6632 for state, char, files in changestates:
6630 6633 if state in show:
6631 6634 label = 'status.' + state
6632 6635 for f in files:
6633 6636 fm.startitem()
6634 6637 fm.condwrite(showchar, 'status', '%s ', char, label=label)
6635 6638 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
6636 6639 if f in copy:
6637 6640 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
6638 6641 label='status.copied')
6639 6642 fm.end()
6640 6643
6641 6644 @command('^summary|sum',
6642 6645 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
6643 6646 def summary(ui, repo, **opts):
6644 6647 """summarize working directory state
6645 6648
6646 6649 This generates a brief summary of the working directory state,
6647 6650 including parents, branch, commit status, phase and available updates.
6648 6651
6649 6652 With the --remote option, this will check the default paths for
6650 6653 incoming and outgoing changes. This can be time-consuming.
6651 6654
6652 6655 Returns 0 on success.
6653 6656 """
6654 6657
6655 6658 ctx = repo[None]
6656 6659 parents = ctx.parents()
6657 6660 pnode = parents[0].node()
6658 6661 marks = []
6659 6662
6660 6663 ms = None
6661 6664 try:
6662 6665 ms = mergemod.mergestate.read(repo)
6663 6666 except error.UnsupportedMergeRecords as e:
6664 6667 s = ' '.join(e.recordtypes)
6665 6668 ui.warn(
6666 6669 _('warning: merge state has unsupported record types: %s\n') % s)
6667 6670 unresolved = 0
6668 6671 else:
6669 6672 unresolved = [f for f in ms if ms[f] == 'u']
6670 6673
6671 6674 for p in parents:
6672 6675 # label with log.changeset (instead of log.parent) since this
6673 6676 # shows a working directory parent *changeset*:
6674 6677 # i18n: column positioning for "hg summary"
6675 6678 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
6676 6679 label='log.changeset changeset.%s' % p.phasestr())
6677 6680 ui.write(' '.join(p.tags()), label='log.tag')
6678 6681 if p.bookmarks():
6679 6682 marks.extend(p.bookmarks())
6680 6683 if p.rev() == -1:
6681 6684 if not len(repo):
6682 6685 ui.write(_(' (empty repository)'))
6683 6686 else:
6684 6687 ui.write(_(' (no revision checked out)'))
6685 6688 ui.write('\n')
6686 6689 if p.description():
6687 6690 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
6688 6691 label='log.summary')
6689 6692
6690 6693 branch = ctx.branch()
6691 6694 bheads = repo.branchheads(branch)
6692 6695 # i18n: column positioning for "hg summary"
6693 6696 m = _('branch: %s\n') % branch
6694 6697 if branch != 'default':
6695 6698 ui.write(m, label='log.branch')
6696 6699 else:
6697 6700 ui.status(m, label='log.branch')
6698 6701
6699 6702 if marks:
6700 6703 active = repo._activebookmark
6701 6704 # i18n: column positioning for "hg summary"
6702 6705 ui.write(_('bookmarks:'), label='log.bookmark')
6703 6706 if active is not None:
6704 6707 if active in marks:
6705 6708 ui.write(' *' + active, label=activebookmarklabel)
6706 6709 marks.remove(active)
6707 6710 else:
6708 6711 ui.write(' [%s]' % active, label=activebookmarklabel)
6709 6712 for m in marks:
6710 6713 ui.write(' ' + m, label='log.bookmark')
6711 6714 ui.write('\n', label='log.bookmark')
6712 6715
6713 6716 status = repo.status(unknown=True)
6714 6717
6715 6718 c = repo.dirstate.copies()
6716 6719 copied, renamed = [], []
6717 6720 for d, s in c.iteritems():
6718 6721 if s in status.removed:
6719 6722 status.removed.remove(s)
6720 6723 renamed.append(d)
6721 6724 else:
6722 6725 copied.append(d)
6723 6726 if d in status.added:
6724 6727 status.added.remove(d)
6725 6728
6726 6729 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
6727 6730
6728 6731 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
6729 6732 (ui.label(_('%d added'), 'status.added'), status.added),
6730 6733 (ui.label(_('%d removed'), 'status.removed'), status.removed),
6731 6734 (ui.label(_('%d renamed'), 'status.copied'), renamed),
6732 6735 (ui.label(_('%d copied'), 'status.copied'), copied),
6733 6736 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
6734 6737 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
6735 6738 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
6736 6739 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
6737 6740 t = []
6738 6741 for l, s in labels:
6739 6742 if s:
6740 6743 t.append(l % len(s))
6741 6744
6742 6745 t = ', '.join(t)
6743 6746 cleanworkdir = False
6744 6747
6745 6748 if repo.vfs.exists('graftstate'):
6746 6749 t += _(' (graft in progress)')
6747 6750 if repo.vfs.exists('updatestate'):
6748 6751 t += _(' (interrupted update)')
6749 6752 elif len(parents) > 1:
6750 6753 t += _(' (merge)')
6751 6754 elif branch != parents[0].branch():
6752 6755 t += _(' (new branch)')
6753 6756 elif (parents[0].closesbranch() and
6754 6757 pnode in repo.branchheads(branch, closed=True)):
6755 6758 t += _(' (head closed)')
6756 6759 elif not (status.modified or status.added or status.removed or renamed or
6757 6760 copied or subs):
6758 6761 t += _(' (clean)')
6759 6762 cleanworkdir = True
6760 6763 elif pnode not in bheads:
6761 6764 t += _(' (new branch head)')
6762 6765
6763 6766 if parents:
6764 6767 pendingphase = max(p.phase() for p in parents)
6765 6768 else:
6766 6769 pendingphase = phases.public
6767 6770
6768 6771 if pendingphase > phases.newcommitphase(ui):
6769 6772 t += ' (%s)' % phases.phasenames[pendingphase]
6770 6773
6771 6774 if cleanworkdir:
6772 6775 # i18n: column positioning for "hg summary"
6773 6776 ui.status(_('commit: %s\n') % t.strip())
6774 6777 else:
6775 6778 # i18n: column positioning for "hg summary"
6776 6779 ui.write(_('commit: %s\n') % t.strip())
6777 6780
6778 6781 # all ancestors of branch heads - all ancestors of parent = new csets
6779 6782 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
6780 6783 bheads))
6781 6784
6782 6785 if new == 0:
6783 6786 # i18n: column positioning for "hg summary"
6784 6787 ui.status(_('update: (current)\n'))
6785 6788 elif pnode not in bheads:
6786 6789 # i18n: column positioning for "hg summary"
6787 6790 ui.write(_('update: %d new changesets (update)\n') % new)
6788 6791 else:
6789 6792 # i18n: column positioning for "hg summary"
6790 6793 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
6791 6794 (new, len(bheads)))
6792 6795
6793 6796 t = []
6794 6797 draft = len(repo.revs('draft()'))
6795 6798 if draft:
6796 6799 t.append(_('%d draft') % draft)
6797 6800 secret = len(repo.revs('secret()'))
6798 6801 if secret:
6799 6802 t.append(_('%d secret') % secret)
6800 6803
6801 6804 if draft or secret:
6802 6805 ui.status(_('phases: %s\n') % ', '.join(t))
6803 6806
6804 6807 if obsolete.isenabled(repo, obsolete.createmarkersopt):
6805 6808 for trouble in ("unstable", "divergent", "bumped"):
6806 6809 numtrouble = len(repo.revs(trouble + "()"))
6807 6810 # We write all the possibilities to ease translation
6808 6811 troublemsg = {
6809 6812 "unstable": _("unstable: %d changesets"),
6810 6813 "divergent": _("divergent: %d changesets"),
6811 6814 "bumped": _("bumped: %d changesets"),
6812 6815 }
6813 6816 if numtrouble > 0:
6814 6817 ui.status(troublemsg[trouble] % numtrouble + "\n")
6815 6818
6816 6819 cmdutil.summaryhooks(ui, repo)
6817 6820
6818 6821 if opts.get('remote'):
6819 6822 needsincoming, needsoutgoing = True, True
6820 6823 else:
6821 6824 needsincoming, needsoutgoing = False, False
6822 6825 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
6823 6826 if i:
6824 6827 needsincoming = True
6825 6828 if o:
6826 6829 needsoutgoing = True
6827 6830 if not needsincoming and not needsoutgoing:
6828 6831 return
6829 6832
6830 6833 def getincoming():
6831 6834 source, branches = hg.parseurl(ui.expandpath('default'))
6832 6835 sbranch = branches[0]
6833 6836 try:
6834 6837 other = hg.peer(repo, {}, source)
6835 6838 except error.RepoError:
6836 6839 if opts.get('remote'):
6837 6840 raise
6838 6841 return source, sbranch, None, None, None
6839 6842 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
6840 6843 if revs:
6841 6844 revs = [other.lookup(rev) for rev in revs]
6842 6845 ui.debug('comparing with %s\n' % util.hidepassword(source))
6843 6846 repo.ui.pushbuffer()
6844 6847 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
6845 6848 repo.ui.popbuffer()
6846 6849 return source, sbranch, other, commoninc, commoninc[1]
6847 6850
6848 6851 if needsincoming:
6849 6852 source, sbranch, sother, commoninc, incoming = getincoming()
6850 6853 else:
6851 6854 source = sbranch = sother = commoninc = incoming = None
6852 6855
6853 6856 def getoutgoing():
6854 6857 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
6855 6858 dbranch = branches[0]
6856 6859 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
6857 6860 if source != dest:
6858 6861 try:
6859 6862 dother = hg.peer(repo, {}, dest)
6860 6863 except error.RepoError:
6861 6864 if opts.get('remote'):
6862 6865 raise
6863 6866 return dest, dbranch, None, None
6864 6867 ui.debug('comparing with %s\n' % util.hidepassword(dest))
6865 6868 elif sother is None:
6866 6869 # there is no explicit destination peer, but source one is invalid
6867 6870 return dest, dbranch, None, None
6868 6871 else:
6869 6872 dother = sother
6870 6873 if (source != dest or (sbranch is not None and sbranch != dbranch)):
6871 6874 common = None
6872 6875 else:
6873 6876 common = commoninc
6874 6877 if revs:
6875 6878 revs = [repo.lookup(rev) for rev in revs]
6876 6879 repo.ui.pushbuffer()
6877 6880 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
6878 6881 commoninc=common)
6879 6882 repo.ui.popbuffer()
6880 6883 return dest, dbranch, dother, outgoing
6881 6884
6882 6885 if needsoutgoing:
6883 6886 dest, dbranch, dother, outgoing = getoutgoing()
6884 6887 else:
6885 6888 dest = dbranch = dother = outgoing = None
6886 6889
6887 6890 if opts.get('remote'):
6888 6891 t = []
6889 6892 if incoming:
6890 6893 t.append(_('1 or more incoming'))
6891 6894 o = outgoing.missing
6892 6895 if o:
6893 6896 t.append(_('%d outgoing') % len(o))
6894 6897 other = dother or sother
6895 6898 if 'bookmarks' in other.listkeys('namespaces'):
6896 6899 counts = bookmarks.summary(repo, other)
6897 6900 if counts[0] > 0:
6898 6901 t.append(_('%d incoming bookmarks') % counts[0])
6899 6902 if counts[1] > 0:
6900 6903 t.append(_('%d outgoing bookmarks') % counts[1])
6901 6904
6902 6905 if t:
6903 6906 # i18n: column positioning for "hg summary"
6904 6907 ui.write(_('remote: %s\n') % (', '.join(t)))
6905 6908 else:
6906 6909 # i18n: column positioning for "hg summary"
6907 6910 ui.status(_('remote: (synced)\n'))
6908 6911
6909 6912 cmdutil.summaryremotehooks(ui, repo, opts,
6910 6913 ((source, sbranch, sother, commoninc),
6911 6914 (dest, dbranch, dother, outgoing)))
6912 6915
6913 6916 @command('tag',
6914 6917 [('f', 'force', None, _('force tag')),
6915 6918 ('l', 'local', None, _('make the tag local')),
6916 6919 ('r', 'rev', '', _('revision to tag'), _('REV')),
6917 6920 ('', 'remove', None, _('remove a tag')),
6918 6921 # -l/--local is already there, commitopts cannot be used
6919 6922 ('e', 'edit', None, _('invoke editor on commit messages')),
6920 6923 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
6921 6924 ] + commitopts2,
6922 6925 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
6923 6926 def tag(ui, repo, name1, *names, **opts):
6924 6927 """add one or more tags for the current or given revision
6925 6928
6926 6929 Name a particular revision using <name>.
6927 6930
6928 6931 Tags are used to name particular revisions of the repository and are
6929 6932 very useful to compare different revisions, to go back to significant
6930 6933 earlier versions or to mark branch points as releases, etc. Changing
6931 6934 an existing tag is normally disallowed; use -f/--force to override.
6932 6935
6933 6936 If no revision is given, the parent of the working directory is
6934 6937 used.
6935 6938
6936 6939 To facilitate version control, distribution, and merging of tags,
6937 6940 they are stored as a file named ".hgtags" which is managed similarly
6938 6941 to other project files and can be hand-edited if necessary. This
6939 6942 also means that tagging creates a new commit. The file
6940 6943 ".hg/localtags" is used for local tags (not shared among
6941 6944 repositories).
6942 6945
6943 6946 Tag commits are usually made at the head of a branch. If the parent
6944 6947 of the working directory is not a branch head, :hg:`tag` aborts; use
6945 6948 -f/--force to force the tag commit to be based on a non-head
6946 6949 changeset.
6947 6950
6948 6951 See :hg:`help dates` for a list of formats valid for -d/--date.
6949 6952
6950 6953 Since tag names have priority over branch names during revision
6951 6954 lookup, using an existing branch name as a tag name is discouraged.
6952 6955
6953 6956 Returns 0 on success.
6954 6957 """
6955 6958 wlock = lock = None
6956 6959 try:
6957 6960 wlock = repo.wlock()
6958 6961 lock = repo.lock()
6959 6962 rev_ = "."
6960 6963 names = [t.strip() for t in (name1,) + names]
6961 6964 if len(names) != len(set(names)):
6962 6965 raise error.Abort(_('tag names must be unique'))
6963 6966 for n in names:
6964 6967 scmutil.checknewlabel(repo, n, 'tag')
6965 6968 if not n:
6966 6969 raise error.Abort(_('tag names cannot consist entirely of '
6967 6970 'whitespace'))
6968 6971 if opts.get('rev') and opts.get('remove'):
6969 6972 raise error.Abort(_("--rev and --remove are incompatible"))
6970 6973 if opts.get('rev'):
6971 6974 rev_ = opts['rev']
6972 6975 message = opts.get('message')
6973 6976 if opts.get('remove'):
6974 6977 if opts.get('local'):
6975 6978 expectedtype = 'local'
6976 6979 else:
6977 6980 expectedtype = 'global'
6978 6981
6979 6982 for n in names:
6980 6983 if not repo.tagtype(n):
6981 6984 raise error.Abort(_("tag '%s' does not exist") % n)
6982 6985 if repo.tagtype(n) != expectedtype:
6983 6986 if expectedtype == 'global':
6984 6987 raise error.Abort(_("tag '%s' is not a global tag") % n)
6985 6988 else:
6986 6989 raise error.Abort(_("tag '%s' is not a local tag") % n)
6987 6990 rev_ = 'null'
6988 6991 if not message:
6989 6992 # we don't translate commit messages
6990 6993 message = 'Removed tag %s' % ', '.join(names)
6991 6994 elif not opts.get('force'):
6992 6995 for n in names:
6993 6996 if n in repo.tags():
6994 6997 raise error.Abort(_("tag '%s' already exists "
6995 6998 "(use -f to force)") % n)
6996 6999 if not opts.get('local'):
6997 7000 p1, p2 = repo.dirstate.parents()
6998 7001 if p2 != nullid:
6999 7002 raise error.Abort(_('uncommitted merge'))
7000 7003 bheads = repo.branchheads()
7001 7004 if not opts.get('force') and bheads and p1 not in bheads:
7002 7005 raise error.Abort(_('not at a branch head (use -f to force)'))
7003 7006 r = scmutil.revsingle(repo, rev_).node()
7004 7007
7005 7008 if not message:
7006 7009 # we don't translate commit messages
7007 7010 message = ('Added tag %s for changeset %s' %
7008 7011 (', '.join(names), short(r)))
7009 7012
7010 7013 date = opts.get('date')
7011 7014 if date:
7012 7015 date = util.parsedate(date)
7013 7016
7014 7017 if opts.get('remove'):
7015 7018 editform = 'tag.remove'
7016 7019 else:
7017 7020 editform = 'tag.add'
7018 7021 editor = cmdutil.getcommiteditor(editform=editform, **opts)
7019 7022
7020 7023 # don't allow tagging the null rev
7021 7024 if (not opts.get('remove') and
7022 7025 scmutil.revsingle(repo, rev_).rev() == nullrev):
7023 7026 raise error.Abort(_("cannot tag null revision"))
7024 7027
7025 7028 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date,
7026 7029 editor=editor)
7027 7030 finally:
7028 7031 release(lock, wlock)
7029 7032
7030 7033 @command('tags', formatteropts, '')
7031 7034 def tags(ui, repo, **opts):
7032 7035 """list repository tags
7033 7036
7034 7037 This lists both regular and local tags. When the -v/--verbose
7035 7038 switch is used, a third column "local" is printed for local tags.
7036 7039 When the -q/--quiet switch is used, only the tag name is printed.
7037 7040
7038 7041 Returns 0 on success.
7039 7042 """
7040 7043
7041 7044 fm = ui.formatter('tags', opts)
7042 7045 hexfunc = fm.hexfunc
7043 7046 tagtype = ""
7044 7047
7045 7048 for t, n in reversed(repo.tagslist()):
7046 7049 hn = hexfunc(n)
7047 7050 label = 'tags.normal'
7048 7051 tagtype = ''
7049 7052 if repo.tagtype(t) == 'local':
7050 7053 label = 'tags.local'
7051 7054 tagtype = 'local'
7052 7055
7053 7056 fm.startitem()
7054 7057 fm.write('tag', '%s', t, label=label)
7055 7058 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
7056 7059 fm.condwrite(not ui.quiet, 'rev node', fmt,
7057 7060 repo.changelog.rev(n), hn, label=label)
7058 7061 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
7059 7062 tagtype, label=label)
7060 7063 fm.plain('\n')
7061 7064 fm.end()
7062 7065
7063 7066 @command('tip',
7064 7067 [('p', 'patch', None, _('show patch')),
7065 7068 ('g', 'git', None, _('use git extended diff format')),
7066 7069 ] + templateopts,
7067 7070 _('[-p] [-g]'))
7068 7071 def tip(ui, repo, **opts):
7069 7072 """show the tip revision (DEPRECATED)
7070 7073
7071 7074 The tip revision (usually just called the tip) is the changeset
7072 7075 most recently added to the repository (and therefore the most
7073 7076 recently changed head).
7074 7077
7075 7078 If you have just made a commit, that commit will be the tip. If
7076 7079 you have just pulled changes from another repository, the tip of
7077 7080 that repository becomes the current tip. The "tip" tag is special
7078 7081 and cannot be renamed or assigned to a different changeset.
7079 7082
7080 7083 This command is deprecated, please use :hg:`heads` instead.
7081 7084
7082 7085 Returns 0 on success.
7083 7086 """
7084 7087 displayer = cmdutil.show_changeset(ui, repo, opts)
7085 7088 displayer.show(repo['tip'])
7086 7089 displayer.close()
7087 7090
7088 7091 @command('unbundle',
7089 7092 [('u', 'update', None,
7090 7093 _('update to new branch head if changesets were unbundled'))],
7091 7094 _('[-u] FILE...'))
7092 7095 def unbundle(ui, repo, fname1, *fnames, **opts):
7093 7096 """apply one or more changegroup files
7094 7097
7095 7098 Apply one or more compressed changegroup files generated by the
7096 7099 bundle command.
7097 7100
7098 7101 Returns 0 on success, 1 if an update has unresolved files.
7099 7102 """
7100 7103 fnames = (fname1,) + fnames
7101 7104
7102 7105 with repo.lock():
7103 7106 for fname in fnames:
7104 7107 f = hg.openpath(ui, fname)
7105 7108 gen = exchange.readbundle(ui, f, fname)
7106 7109 if isinstance(gen, bundle2.unbundle20):
7107 7110 tr = repo.transaction('unbundle')
7108 7111 try:
7109 7112 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
7110 7113 url='bundle:' + fname)
7111 7114 tr.close()
7112 7115 except error.BundleUnknownFeatureError as exc:
7113 7116 raise error.Abort(_('%s: unknown bundle feature, %s')
7114 7117 % (fname, exc),
7115 7118 hint=_("see https://mercurial-scm.org/"
7116 7119 "wiki/BundleFeature for more "
7117 7120 "information"))
7118 7121 finally:
7119 7122 if tr:
7120 7123 tr.release()
7121 7124 changes = [r.get('return', 0)
7122 7125 for r in op.records['changegroup']]
7123 7126 modheads = changegroup.combineresults(changes)
7124 7127 elif isinstance(gen, streamclone.streamcloneapplier):
7125 7128 raise error.Abort(
7126 7129 _('packed bundles cannot be applied with '
7127 7130 '"hg unbundle"'),
7128 7131 hint=_('use "hg debugapplystreamclonebundle"'))
7129 7132 else:
7130 7133 modheads = gen.apply(repo, 'unbundle', 'bundle:' + fname)
7131 7134
7132 7135 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7133 7136
7134 7137 @command('^update|up|checkout|co',
7135 7138 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
7136 7139 ('c', 'check', None, _('require clean working directory')),
7137 7140 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
7138 7141 ('r', 'rev', '', _('revision'), _('REV'))
7139 7142 ] + mergetoolopts,
7140 7143 _('[-c] [-C] [-d DATE] [[-r] REV]'))
7141 7144 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
7142 7145 tool=None):
7143 7146 """update working directory (or switch revisions)
7144 7147
7145 7148 Update the repository's working directory to the specified
7146 7149 changeset. If no changeset is specified, update to the tip of the
7147 7150 current named branch and move the active bookmark (see :hg:`help
7148 7151 bookmarks`).
7149 7152
7150 7153 Update sets the working directory's parent revision to the specified
7151 7154 changeset (see :hg:`help parents`).
7152 7155
7153 7156 If the changeset is not a descendant or ancestor of the working
7154 7157 directory's parent, the update is aborted. With the -c/--check
7155 7158 option, the working directory is checked for uncommitted changes; if
7156 7159 none are found, the working directory is updated to the specified
7157 7160 changeset.
7158 7161
7159 7162 .. container:: verbose
7160 7163
7161 7164 The following rules apply when the working directory contains
7162 7165 uncommitted changes:
7163 7166
7164 7167 1. If neither -c/--check nor -C/--clean is specified, and if
7165 7168 the requested changeset is an ancestor or descendant of
7166 7169 the working directory's parent, the uncommitted changes
7167 7170 are merged into the requested changeset and the merged
7168 7171 result is left uncommitted. If the requested changeset is
7169 7172 not an ancestor or descendant (that is, it is on another
7170 7173 branch), the update is aborted and the uncommitted changes
7171 7174 are preserved.
7172 7175
7173 7176 2. With the -c/--check option, the update is aborted and the
7174 7177 uncommitted changes are preserved.
7175 7178
7176 7179 3. With the -C/--clean option, uncommitted changes are discarded and
7177 7180 the working directory is updated to the requested changeset.
7178 7181
7179 7182 To cancel an uncommitted merge (and lose your changes), use
7180 7183 :hg:`update --clean .`.
7181 7184
7182 7185 Use null as the changeset to remove the working directory (like
7183 7186 :hg:`clone -U`).
7184 7187
7185 7188 If you want to revert just one file to an older revision, use
7186 7189 :hg:`revert [-r REV] NAME`.
7187 7190
7188 7191 See :hg:`help dates` for a list of formats valid for -d/--date.
7189 7192
7190 7193 Returns 0 on success, 1 if there are unresolved files.
7191 7194 """
7192 7195 if rev and node:
7193 7196 raise error.Abort(_("please specify just one revision"))
7194 7197
7195 7198 if rev is None or rev == '':
7196 7199 rev = node
7197 7200
7198 7201 if date and rev is not None:
7199 7202 raise error.Abort(_("you can't specify a revision and a date"))
7200 7203
7201 7204 if check and clean:
7202 7205 raise error.Abort(_("cannot specify both -c/--check and -C/--clean"))
7203 7206
7204 7207 with repo.wlock():
7205 7208 cmdutil.clearunfinished(repo)
7206 7209
7207 7210 if date:
7208 7211 rev = cmdutil.finddate(ui, repo, date)
7209 7212
7210 7213 # if we defined a bookmark, we have to remember the original name
7211 7214 brev = rev
7212 7215 rev = scmutil.revsingle(repo, rev, rev).rev()
7213 7216
7214 7217 if check:
7215 7218 cmdutil.bailifchanged(repo, merge=False)
7216 7219
7217 7220 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
7218 7221
7219 7222 return hg.updatetotally(ui, repo, rev, brev, clean=clean, check=check)
7220 7223
7221 7224 @command('verify', [])
7222 7225 def verify(ui, repo):
7223 7226 """verify the integrity of the repository
7224 7227
7225 7228 Verify the integrity of the current repository.
7226 7229
7227 7230 This will perform an extensive check of the repository's
7228 7231 integrity, validating the hashes and checksums of each entry in
7229 7232 the changelog, manifest, and tracked files, as well as the
7230 7233 integrity of their crosslinks and indices.
7231 7234
7232 7235 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7233 7236 for more information about recovery from corruption of the
7234 7237 repository.
7235 7238
7236 7239 Returns 0 on success, 1 if errors are encountered.
7237 7240 """
7238 7241 return hg.verify(repo)
7239 7242
7240 7243 @command('version', [], norepo=True)
7241 7244 def version_(ui):
7242 7245 """output version and copyright information"""
7243 7246 ui.write(_("Mercurial Distributed SCM (version %s)\n")
7244 7247 % util.version())
7245 7248 ui.status(_(
7246 7249 "(see https://mercurial-scm.org for more information)\n"
7247 7250 "\nCopyright (C) 2005-2016 Matt Mackall and others\n"
7248 7251 "This is free software; see the source for copying conditions. "
7249 7252 "There is NO\nwarranty; "
7250 7253 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7251 7254 ))
7252 7255
7253 7256 ui.note(_("\nEnabled extensions:\n\n"))
7254 7257 if ui.verbose:
7255 7258 # format names and versions into columns
7256 7259 names = []
7257 7260 vers = []
7258 7261 place = []
7259 7262 for name, module in extensions.extensions():
7260 7263 names.append(name)
7261 7264 vers.append(extensions.moduleversion(module))
7262 7265 if extensions.ismoduleinternal(module):
7263 7266 place.append(_("internal"))
7264 7267 else:
7265 7268 place.append(_("external"))
7266 7269 if names:
7267 7270 maxnamelen = max(len(n) for n in names)
7268 7271 for i, name in enumerate(names):
7269 7272 ui.write(" %-*s %s %s\n" %
7270 7273 (maxnamelen, name, place[i], vers[i]))
7271 7274
7272 7275 def loadcmdtable(ui, name, cmdtable):
7273 7276 """Load command functions from specified cmdtable
7274 7277 """
7275 7278 overrides = [cmd for cmd in cmdtable if cmd in table]
7276 7279 if overrides:
7277 7280 ui.warn(_("extension '%s' overrides commands: %s\n")
7278 7281 % (name, " ".join(overrides)))
7279 7282 table.update(cmdtable)
@@ -1,352 +1,352 b''
1 1 Show all commands except debug commands
2 2 $ hg debugcomplete
3 3 add
4 4 addremove
5 5 annotate
6 6 archive
7 7 backout
8 8 bisect
9 9 bookmarks
10 10 branch
11 11 branches
12 12 bundle
13 13 cat
14 14 clone
15 15 commit
16 16 config
17 17 copy
18 18 diff
19 19 export
20 20 files
21 21 forget
22 22 graft
23 23 grep
24 24 heads
25 25 help
26 26 identify
27 27 import
28 28 incoming
29 29 init
30 30 locate
31 31 log
32 32 manifest
33 33 merge
34 34 outgoing
35 35 parents
36 36 paths
37 37 phase
38 38 pull
39 39 push
40 40 recover
41 41 remove
42 42 rename
43 43 resolve
44 44 revert
45 45 rollback
46 46 root
47 47 serve
48 48 status
49 49 summary
50 50 tag
51 51 tags
52 52 tip
53 53 unbundle
54 54 update
55 55 verify
56 56 version
57 57
58 58 Show all commands that start with "a"
59 59 $ hg debugcomplete a
60 60 add
61 61 addremove
62 62 annotate
63 63 archive
64 64
65 65 Do not show debug commands if there are other candidates
66 66 $ hg debugcomplete d
67 67 diff
68 68
69 69 Show debug commands if there are no other candidates
70 70 $ hg debugcomplete debug
71 71 debugancestor
72 72 debugapplystreamclonebundle
73 73 debugbuilddag
74 74 debugbundle
75 75 debugcheckstate
76 76 debugcommands
77 77 debugcomplete
78 78 debugconfig
79 79 debugcreatestreamclonebundle
80 80 debugdag
81 81 debugdata
82 82 debugdate
83 83 debugdeltachain
84 84 debugdirstate
85 85 debugdiscovery
86 86 debugextensions
87 87 debugfileset
88 88 debugfsinfo
89 89 debuggetbundle
90 90 debugignore
91 91 debugindex
92 92 debugindexdot
93 93 debuginstall
94 94 debugknown
95 95 debuglabelcomplete
96 96 debuglocks
97 97 debugmergestate
98 98 debugnamecomplete
99 99 debugobsolete
100 100 debugpathcomplete
101 101 debugpushkey
102 102 debugpvec
103 103 debugrebuilddirstate
104 104 debugrebuildfncache
105 105 debugrename
106 106 debugrevlog
107 107 debugrevspec
108 108 debugsetparents
109 109 debugsub
110 110 debugsuccessorssets
111 111 debugtemplate
112 112 debugwalk
113 113 debugwireargs
114 114
115 115 Do not show the alias of a debug command if there are other candidates
116 116 (this should hide rawcommit)
117 117 $ hg debugcomplete r
118 118 recover
119 119 remove
120 120 rename
121 121 resolve
122 122 revert
123 123 rollback
124 124 root
125 125 Show the alias of a debug command if there are no other candidates
126 126 $ hg debugcomplete rawc
127 127
128 128
129 129 Show the global options
130 130 $ hg debugcomplete --options | sort
131 131 --config
132 132 --cwd
133 133 --debug
134 134 --debugger
135 135 --encoding
136 136 --encodingmode
137 137 --help
138 138 --hidden
139 139 --noninteractive
140 140 --profile
141 141 --quiet
142 142 --repository
143 143 --time
144 144 --traceback
145 145 --verbose
146 146 --version
147 147 -R
148 148 -h
149 149 -q
150 150 -v
151 151 -y
152 152
153 153 Show the options for the "serve" command
154 154 $ hg debugcomplete --options serve | sort
155 155 --accesslog
156 156 --address
157 157 --certificate
158 158 --cmdserver
159 159 --config
160 160 --cwd
161 161 --daemon
162 162 --daemon-postexec
163 163 --debug
164 164 --debugger
165 165 --encoding
166 166 --encodingmode
167 167 --errorlog
168 168 --help
169 169 --hidden
170 170 --ipv6
171 171 --name
172 172 --noninteractive
173 173 --pid-file
174 174 --port
175 175 --prefix
176 176 --profile
177 177 --quiet
178 178 --repository
179 179 --stdio
180 180 --style
181 181 --templates
182 182 --time
183 183 --traceback
184 184 --verbose
185 185 --version
186 186 --web-conf
187 187 -6
188 188 -A
189 189 -E
190 190 -R
191 191 -a
192 192 -d
193 193 -h
194 194 -n
195 195 -p
196 196 -q
197 197 -t
198 198 -v
199 199 -y
200 200
201 201 Show an error if we use --options with an ambiguous abbreviation
202 202 $ hg debugcomplete --options s
203 203 hg: command 's' is ambiguous:
204 204 serve showconfig status summary
205 205 [255]
206 206
207 207 Show all commands + options
208 208 $ hg debugcommands
209 209 add: include, exclude, subrepos, dry-run
210 210 annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, ignore-all-space, ignore-space-change, ignore-blank-lines, include, exclude, template
211 211 clone: noupdate, updaterev, rev, branch, pull, uncompressed, ssh, remotecmd, insecure
212 212 commit: addremove, close-branch, amend, secret, edit, interactive, include, exclude, message, logfile, date, user, subrepos
213 213 diff: rev, change, text, git, nodates, noprefix, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, unified, stat, root, include, exclude, subrepos
214 214 export: output, switch-parent, rev, text, git, nodates
215 215 forget: include, exclude
216 216 init: ssh, remotecmd, insecure
217 217 log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
218 218 merge: force, rev, preview, tool
219 219 pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
220 220 push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
221 221 remove: after, force, subrepos, include, exclude
222 222 serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate
223 223 status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos, template
224 224 summary: remote
225 225 update: clean, check, date, rev, tool
226 226 addremove: similarity, subrepos, include, exclude, dry-run
227 227 archive: no-decode, prefix, rev, type, subrepos, include, exclude
228 228 backout: merge, commit, no-commit, parent, rev, edit, tool, include, exclude, message, logfile, date, user
229 229 bisect: reset, good, bad, skip, extend, command, noupdate
230 230 bookmarks: force, rev, delete, rename, inactive, template
231 231 branch: force, clean
232 232 branches: active, closed, template
233 233 bundle: force, rev, branch, base, all, type, ssh, remotecmd, insecure
234 234 cat: output, rev, decode, include, exclude
235 235 config: untrusted, edit, local, global
236 236 copy: after, force, include, exclude, dry-run
237 237 debugancestor:
238 238 debugapplystreamclonebundle:
239 239 debugbuilddag: mergeable-file, overwritten-file, new-file
240 240 debugbundle: all, spec
241 241 debugcheckstate:
242 242 debugcommands:
243 243 debugcomplete: options
244 244 debugcreatestreamclonebundle:
245 245 debugdag: tags, branches, dots, spaces
246 246 debugdata: changelog, manifest, dir
247 247 debugdate: extended
248 248 debugdeltachain: changelog, manifest, dir, template
249 249 debugdirstate: nodates, datesort
250 250 debugdiscovery: old, nonheads, ssh, remotecmd, insecure
251 251 debugextensions: template
252 252 debugfileset: rev
253 253 debugfsinfo:
254 254 debuggetbundle: head, common, type
255 255 debugignore:
256 256 debugindex: changelog, manifest, dir, format
257 257 debugindexdot: changelog, manifest, dir
258 258 debuginstall: template
259 259 debugknown:
260 260 debuglabelcomplete:
261 261 debuglocks: force-lock, force-wlock
262 262 debugmergestate:
263 263 debugnamecomplete:
264 debugobsolete: flags, record-parents, rev, index, delete, date, user
264 debugobsolete: flags, record-parents, rev, index, delete, date, user, template
265 265 debugpathcomplete: full, normal, added, removed
266 266 debugpushkey:
267 267 debugpvec:
268 268 debugrebuilddirstate: rev, minimal
269 269 debugrebuildfncache:
270 270 debugrename: rev
271 271 debugrevlog: changelog, manifest, dir, dump
272 272 debugrevspec: optimize
273 273 debugsetparents:
274 274 debugsub: rev
275 275 debugsuccessorssets:
276 276 debugtemplate: rev, define
277 277 debugwalk: include, exclude
278 278 debugwireargs: three, four, five, ssh, remotecmd, insecure
279 279 files: rev, print0, include, exclude, template, subrepos
280 280 graft: rev, continue, edit, log, force, currentdate, currentuser, date, user, tool, dry-run
281 281 grep: print0, all, text, follow, ignore-case, files-with-matches, line-number, rev, user, date, include, exclude
282 282 heads: rev, topo, active, closed, style, template
283 283 help: extension, command, keyword, system
284 284 identify: rev, num, id, branch, tags, bookmarks, ssh, remotecmd, insecure
285 285 import: strip, base, edit, force, no-commit, bypass, partial, exact, prefix, import-branch, message, logfile, date, user, similarity
286 286 incoming: force, newest-first, bundle, rev, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
287 287 locate: rev, print0, fullpath, include, exclude
288 288 manifest: rev, all, template
289 289 outgoing: force, rev, newest-first, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
290 290 parents: rev, style, template
291 291 paths: template
292 292 phase: public, draft, secret, force, rev
293 293 recover:
294 294 rename: after, force, include, exclude, dry-run
295 295 resolve: all, list, mark, unmark, no-status, tool, include, exclude, template
296 296 revert: all, date, rev, no-backup, interactive, include, exclude, dry-run
297 297 rollback: dry-run, force
298 298 root:
299 299 tag: force, local, rev, remove, edit, message, date, user
300 300 tags: template
301 301 tip: patch, git, style, template
302 302 unbundle: update
303 303 verify:
304 304 version:
305 305
306 306 $ hg init a
307 307 $ cd a
308 308 $ echo fee > fee
309 309 $ hg ci -q -Amfee
310 310 $ hg tag fee
311 311 $ mkdir fie
312 312 $ echo dead > fie/dead
313 313 $ echo live > fie/live
314 314 $ hg bookmark fo
315 315 $ hg branch -q fie
316 316 $ hg ci -q -Amfie
317 317 $ echo fo > fo
318 318 $ hg branch -qf default
319 319 $ hg ci -q -Amfo
320 320 $ echo Fum > Fum
321 321 $ hg ci -q -AmFum
322 322 $ hg bookmark Fum
323 323
324 324 Test debugpathcomplete
325 325
326 326 $ hg debugpathcomplete f
327 327 fee
328 328 fie
329 329 fo
330 330 $ hg debugpathcomplete -f f
331 331 fee
332 332 fie/dead
333 333 fie/live
334 334 fo
335 335
336 336 $ hg rm Fum
337 337 $ hg debugpathcomplete -r F
338 338 Fum
339 339
340 340 Test debugnamecomplete
341 341
342 342 $ hg debugnamecomplete
343 343 Fum
344 344 default
345 345 fee
346 346 fie
347 347 fo
348 348 tip
349 349 $ hg debugnamecomplete f
350 350 fee
351 351 fie
352 352 fo
@@ -1,1126 +1,1219 b''
1 1 $ cat >> $HGRCPATH << EOF
2 2 > [phases]
3 3 > # public changeset are not obsolete
4 4 > publish=false
5 5 > [ui]
6 6 > logtemplate="{rev}:{node|short} ({phase}) [{tags} {bookmarks}] {desc|firstline}\n"
7 7 > EOF
8 8 $ mkcommit() {
9 9 > echo "$1" > "$1"
10 10 > hg add "$1"
11 11 > hg ci -m "add $1"
12 12 > }
13 13 $ getid() {
14 14 > hg log -T "{node}\n" --hidden -r "desc('$1')"
15 15 > }
16 16
17 17 $ cat > debugkeys.py <<EOF
18 18 > def reposetup(ui, repo):
19 19 > class debugkeysrepo(repo.__class__):
20 20 > def listkeys(self, namespace):
21 21 > ui.write('listkeys %s\n' % (namespace,))
22 22 > return super(debugkeysrepo, self).listkeys(namespace)
23 23 >
24 24 > if repo.local():
25 25 > repo.__class__ = debugkeysrepo
26 26 > EOF
27 27
28 28 $ hg init tmpa
29 29 $ cd tmpa
30 30 $ mkcommit kill_me
31 31
32 32 Checking that the feature is properly disabled
33 33
34 34 $ hg debugobsolete -d '0 0' `getid kill_me` -u babar
35 35 abort: creating obsolete markers is not enabled on this repo
36 36 [255]
37 37
38 38 Enabling it
39 39
40 40 $ cat >> $HGRCPATH << EOF
41 41 > [experimental]
42 42 > evolution=createmarkers,exchange
43 43 > EOF
44 44
45 45 Killing a single changeset without replacement
46 46
47 47 $ hg debugobsolete 0
48 48 abort: changeset references must be full hexadecimal node identifiers
49 49 [255]
50 50 $ hg debugobsolete '00'
51 51 abort: changeset references must be full hexadecimal node identifiers
52 52 [255]
53 53 $ hg debugobsolete -d '0 0' `getid kill_me` -u babar
54 54 $ hg debugobsolete
55 55 97b7c2d76b1845ed3eb988cd612611e72406cef0 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'babar'}
56 56
57 57 (test that mercurial is not confused)
58 58
59 59 $ hg up null --quiet # having 0 as parent prevents it to be hidden
60 60 $ hg tip
61 61 -1:000000000000 (public) [tip ]
62 62 $ hg up --hidden tip --quiet
63 63
64 64 Killing a single changeset with itself should fail
65 65 (simple local safeguard)
66 66
67 67 $ hg debugobsolete `getid kill_me` `getid kill_me`
68 68 abort: bad obsmarker input: in-marker cycle with 97b7c2d76b1845ed3eb988cd612611e72406cef0
69 69 [255]
70 70
71 71 $ cd ..
72 72
73 73 Killing a single changeset with replacement
74 74 (and testing the format option)
75 75
76 76 $ hg init tmpb
77 77 $ cd tmpb
78 78 $ mkcommit a
79 79 $ mkcommit b
80 80 $ mkcommit original_c
81 81 $ hg up "desc('b')"
82 82 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
83 83 $ mkcommit new_c
84 84 created new head
85 85 $ hg log -r 'hidden()' --template '{rev}:{node|short} {desc}\n' --hidden
86 86 $ hg debugobsolete --config format.obsstore-version=0 --flag 12 `getid original_c` `getid new_c` -d '121 120'
87 87 $ hg log -r 'hidden()' --template '{rev}:{node|short} {desc}\n' --hidden
88 88 2:245bde4270cd add original_c
89 89 $ hg debugrevlog -cd
90 90 # rev p1rev p2rev start end deltastart base p1 p2 rawsize totalsize compression heads chainlen
91 91 0 -1 -1 0 59 0 0 0 0 58 58 0 1 0
92 92 1 0 -1 59 118 59 59 0 0 58 116 0 1 0
93 93 2 1 -1 118 193 118 118 59 0 76 192 0 1 0
94 94 3 1 -1 193 260 193 193 59 0 66 258 0 2 0
95 95 $ hg debugobsolete
96 96 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
97 97
98 98 (check for version number of the obsstore)
99 99
100 100 $ dd bs=1 count=1 if=.hg/store/obsstore 2>/dev/null
101 101 \x00 (no-eol) (esc)
102 102
103 103 do it again (it read the obsstore before adding new changeset)
104 104
105 105 $ hg up '.^'
106 106 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
107 107 $ mkcommit new_2_c
108 108 created new head
109 109 $ hg debugobsolete -d '1337 0' `getid new_c` `getid new_2_c`
110 110 $ hg debugobsolete
111 111 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
112 112 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
113 113
114 114 Register two markers with a missing node
115 115
116 116 $ hg up '.^'
117 117 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
118 118 $ mkcommit new_3_c
119 119 created new head
120 120 $ hg debugobsolete -d '1338 0' `getid new_2_c` 1337133713371337133713371337133713371337
121 121 $ hg debugobsolete -d '1339 0' 1337133713371337133713371337133713371337 `getid new_3_c`
122 122 $ hg debugobsolete
123 123 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
124 124 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
125 125 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
126 126 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
127 127
128 128 Test the --index option of debugobsolete command
129 129 $ hg debugobsolete --index
130 130 0 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
131 131 1 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
132 132 2 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
133 133 3 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
134 134
135 135 Refuse pathological nullid successors
136 136 $ hg debugobsolete -d '9001 0' 1337133713371337133713371337133713371337 0000000000000000000000000000000000000000
137 137 transaction abort!
138 138 rollback completed
139 139 abort: bad obsolescence marker detected: invalid successors nullid
140 140 [255]
141 141
142 142 Check that graphlog detect that a changeset is obsolete:
143 143
144 144 $ hg log -G
145 145 @ 5:5601fb93a350 (draft) [tip ] add new_3_c
146 146 |
147 147 o 1:7c3bad9141dc (draft) [ ] add b
148 148 |
149 149 o 0:1f0dee641bb7 (draft) [ ] add a
150 150
151 151
152 152 check that heads does not report them
153 153
154 154 $ hg heads
155 155 5:5601fb93a350 (draft) [tip ] add new_3_c
156 156 $ hg heads --hidden
157 157 5:5601fb93a350 (draft) [tip ] add new_3_c
158 158 4:ca819180edb9 (draft) [ ] add new_2_c
159 159 3:cdbce2fbb163 (draft) [ ] add new_c
160 160 2:245bde4270cd (draft) [ ] add original_c
161 161
162 162
163 163 check that summary does not report them
164 164
165 165 $ hg init ../sink
166 166 $ echo '[paths]' >> .hg/hgrc
167 167 $ echo 'default=../sink' >> .hg/hgrc
168 168 $ hg summary --remote
169 169 parent: 5:5601fb93a350 tip
170 170 add new_3_c
171 171 branch: default
172 172 commit: (clean)
173 173 update: (current)
174 174 phases: 3 draft
175 175 remote: 3 outgoing
176 176
177 177 $ hg summary --remote --hidden
178 178 parent: 5:5601fb93a350 tip
179 179 add new_3_c
180 180 branch: default
181 181 commit: (clean)
182 182 update: 3 new changesets, 4 branch heads (merge)
183 183 phases: 6 draft
184 184 remote: 3 outgoing
185 185
186 186 check that various commands work well with filtering
187 187
188 188 $ hg tip
189 189 5:5601fb93a350 (draft) [tip ] add new_3_c
190 190 $ hg log -r 6
191 191 abort: unknown revision '6'!
192 192 [255]
193 193 $ hg log -r 4
194 194 abort: hidden revision '4'!
195 195 (use --hidden to access hidden revisions)
196 196 [255]
197 197 $ hg debugrevspec 'rev(6)'
198 198 $ hg debugrevspec 'rev(4)'
199 199 $ hg debugrevspec 'null'
200 200 -1
201 201
202 202 Check that public changeset are not accounted as obsolete:
203 203
204 204 $ hg --hidden phase --public 2
205 205 $ hg log -G
206 206 @ 5:5601fb93a350 (draft) [tip ] add new_3_c
207 207 |
208 208 | o 2:245bde4270cd (public) [ ] add original_c
209 209 |/
210 210 o 1:7c3bad9141dc (public) [ ] add b
211 211 |
212 212 o 0:1f0dee641bb7 (public) [ ] add a
213 213
214 214
215 215 And that bumped changeset are detected
216 216 --------------------------------------
217 217
218 218 If we didn't filtered obsolete changesets out, 3 and 4 would show up too. Also
219 219 note that the bumped changeset (5:5601fb93a350) is not a direct successor of
220 220 the public changeset
221 221
222 222 $ hg log --hidden -r 'bumped()'
223 223 5:5601fb93a350 (draft) [tip ] add new_3_c
224 224
225 225 And that we can't push bumped changeset
226 226
227 227 $ hg push ../tmpa -r 0 --force #(make repo related)
228 228 pushing to ../tmpa
229 229 searching for changes
230 230 warning: repository is unrelated
231 231 adding changesets
232 232 adding manifests
233 233 adding file changes
234 234 added 1 changesets with 1 changes to 1 files (+1 heads)
235 235 $ hg push ../tmpa
236 236 pushing to ../tmpa
237 237 searching for changes
238 238 abort: push includes bumped changeset: 5601fb93a350!
239 239 [255]
240 240
241 241 Fixing "bumped" situation
242 242 We need to create a clone of 5 and add a special marker with a flag
243 243
244 244 $ hg summary
245 245 parent: 5:5601fb93a350 tip
246 246 add new_3_c
247 247 branch: default
248 248 commit: (clean)
249 249 update: 1 new changesets, 2 branch heads (merge)
250 250 phases: 1 draft
251 251 bumped: 1 changesets
252 252 $ hg up '5^'
253 253 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
254 254 $ hg revert -ar 5
255 255 adding new_3_c
256 256 $ hg ci -m 'add n3w_3_c'
257 257 created new head
258 258 $ hg debugobsolete -d '1338 0' --flags 1 `getid new_3_c` `getid n3w_3_c`
259 259 $ hg log -r 'bumped()'
260 260 $ hg log -G
261 261 @ 6:6f9641995072 (draft) [tip ] add n3w_3_c
262 262 |
263 263 | o 2:245bde4270cd (public) [ ] add original_c
264 264 |/
265 265 o 1:7c3bad9141dc (public) [ ] add b
266 266 |
267 267 o 0:1f0dee641bb7 (public) [ ] add a
268 268
269 269
270 270 $ cd ..
271 271
272 272 Revision 0 is hidden
273 273 --------------------
274 274
275 275 $ hg init rev0hidden
276 276 $ cd rev0hidden
277 277
278 278 $ mkcommit kill0
279 279 $ hg up -q null
280 280 $ hg debugobsolete `getid kill0`
281 281 $ mkcommit a
282 282 $ mkcommit b
283 283
284 284 Should pick the first visible revision as "repo" node
285 285
286 286 $ hg archive ../archive-null
287 287 $ cat ../archive-null/.hg_archival.txt
288 288 repo: 1f0dee641bb7258c56bd60e93edfa2405381c41e
289 289 node: 7c3bad9141dcb46ff89abf5f61856facd56e476c
290 290 branch: default
291 291 latesttag: null
292 292 latesttagdistance: 2
293 293 changessincelatesttag: 2
294 294
295 295
296 296 $ cd ..
297 297
298 298 Exchange Test
299 299 ============================
300 300
301 301 Destination repo does not have any data
302 302 ---------------------------------------
303 303
304 304 Simple incoming test
305 305
306 306 $ hg init tmpc
307 307 $ cd tmpc
308 308 $ hg incoming ../tmpb
309 309 comparing with ../tmpb
310 310 0:1f0dee641bb7 (public) [ ] add a
311 311 1:7c3bad9141dc (public) [ ] add b
312 312 2:245bde4270cd (public) [ ] add original_c
313 313 6:6f9641995072 (draft) [tip ] add n3w_3_c
314 314
315 315 Try to pull markers
316 316 (extinct changeset are excluded but marker are pushed)
317 317
318 318 $ hg pull ../tmpb
319 319 pulling from ../tmpb
320 320 requesting all changes
321 321 adding changesets
322 322 adding manifests
323 323 adding file changes
324 324 added 4 changesets with 4 changes to 4 files (+1 heads)
325 325 5 new obsolescence markers
326 326 (run 'hg heads' to see heads, 'hg merge' to merge)
327 327 $ hg debugobsolete
328 328 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
329 329 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
330 330 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
331 331 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
332 332 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
333 333
334 334 Rollback//Transaction support
335 335
336 336 $ hg debugobsolete -d '1340 0' aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
337 337 $ hg debugobsolete
338 338 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
339 339 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
340 340 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
341 341 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
342 342 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
343 343 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 0 (Thu Jan 01 00:22:20 1970 +0000) {'user': 'test'}
344 344 $ hg rollback -n
345 345 repository tip rolled back to revision 3 (undo debugobsolete)
346 346 $ hg rollback
347 347 repository tip rolled back to revision 3 (undo debugobsolete)
348 348 $ hg debugobsolete
349 349 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
350 350 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
351 351 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
352 352 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
353 353 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
354 354
355 355 $ cd ..
356 356
357 357 Try to push markers
358 358
359 359 $ hg init tmpd
360 360 $ hg -R tmpb push tmpd
361 361 pushing to tmpd
362 362 searching for changes
363 363 adding changesets
364 364 adding manifests
365 365 adding file changes
366 366 added 4 changesets with 4 changes to 4 files (+1 heads)
367 367 5 new obsolescence markers
368 368 $ hg -R tmpd debugobsolete | sort
369 369 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
370 370 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
371 371 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
372 372 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
373 373 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
374 374
375 375 Check obsolete keys are exchanged only if source has an obsolete store
376 376
377 377 $ hg init empty
378 378 $ hg --config extensions.debugkeys=debugkeys.py -R empty push tmpd
379 379 pushing to tmpd
380 380 listkeys phases
381 381 listkeys bookmarks
382 382 no changes found
383 383 listkeys phases
384 384 [1]
385 385
386 386 clone support
387 387 (markers are copied and extinct changesets are included to allow hardlinks)
388 388
389 389 $ hg clone tmpb clone-dest
390 390 updating to branch default
391 391 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
392 392 $ hg -R clone-dest log -G --hidden
393 393 @ 6:6f9641995072 (draft) [tip ] add n3w_3_c
394 394 |
395 395 | x 5:5601fb93a350 (draft) [ ] add new_3_c
396 396 |/
397 397 | x 4:ca819180edb9 (draft) [ ] add new_2_c
398 398 |/
399 399 | x 3:cdbce2fbb163 (draft) [ ] add new_c
400 400 |/
401 401 | o 2:245bde4270cd (public) [ ] add original_c
402 402 |/
403 403 o 1:7c3bad9141dc (public) [ ] add b
404 404 |
405 405 o 0:1f0dee641bb7 (public) [ ] add a
406 406
407 407 $ hg -R clone-dest debugobsolete
408 408 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
409 409 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
410 410 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
411 411 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
412 412 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
413 413
414 414
415 415 Destination repo have existing data
416 416 ---------------------------------------
417 417
418 418 On pull
419 419
420 420 $ hg init tmpe
421 421 $ cd tmpe
422 422 $ hg debugobsolete -d '1339 0' 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00
423 423 $ hg pull ../tmpb
424 424 pulling from ../tmpb
425 425 requesting all changes
426 426 adding changesets
427 427 adding manifests
428 428 adding file changes
429 429 added 4 changesets with 4 changes to 4 files (+1 heads)
430 430 5 new obsolescence markers
431 431 (run 'hg heads' to see heads, 'hg merge' to merge)
432 432 $ hg debugobsolete
433 433 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
434 434 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
435 435 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
436 436 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
437 437 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
438 438 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
439 439
440 440
441 441 On push
442 442
443 443 $ hg push ../tmpc
444 444 pushing to ../tmpc
445 445 searching for changes
446 446 no changes found
447 447 1 new obsolescence markers
448 448 [1]
449 449 $ hg -R ../tmpc debugobsolete
450 450 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
451 451 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
452 452 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
453 453 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
454 454 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
455 455 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
456 456
457 457 detect outgoing obsolete and unstable
458 458 ---------------------------------------
459 459
460 460
461 461 $ hg log -G
462 462 o 3:6f9641995072 (draft) [tip ] add n3w_3_c
463 463 |
464 464 | o 2:245bde4270cd (public) [ ] add original_c
465 465 |/
466 466 o 1:7c3bad9141dc (public) [ ] add b
467 467 |
468 468 o 0:1f0dee641bb7 (public) [ ] add a
469 469
470 470 $ hg up 'desc("n3w_3_c")'
471 471 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
472 472 $ mkcommit original_d
473 473 $ mkcommit original_e
474 474 $ hg debugobsolete --record-parents `getid original_d` -d '0 0'
475 475 $ hg debugobsolete | grep `getid original_d`
476 476 94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
477 477 $ hg log -r 'obsolete()'
478 478 4:94b33453f93b (draft) [ ] add original_d
479 479 $ hg summary
480 480 parent: 5:cda648ca50f5 tip
481 481 add original_e
482 482 branch: default
483 483 commit: (clean)
484 484 update: 1 new changesets, 2 branch heads (merge)
485 485 phases: 3 draft
486 486 unstable: 1 changesets
487 487 $ hg log -G -r '::unstable()'
488 488 @ 5:cda648ca50f5 (draft) [tip ] add original_e
489 489 |
490 490 x 4:94b33453f93b (draft) [ ] add original_d
491 491 |
492 492 o 3:6f9641995072 (draft) [ ] add n3w_3_c
493 493 |
494 494 o 1:7c3bad9141dc (public) [ ] add b
495 495 |
496 496 o 0:1f0dee641bb7 (public) [ ] add a
497 497
498 498
499 499 refuse to push obsolete changeset
500 500
501 501 $ hg push ../tmpc/ -r 'desc("original_d")'
502 502 pushing to ../tmpc/
503 503 searching for changes
504 504 abort: push includes obsolete changeset: 94b33453f93b!
505 505 [255]
506 506
507 507 refuse to push unstable changeset
508 508
509 509 $ hg push ../tmpc/
510 510 pushing to ../tmpc/
511 511 searching for changes
512 512 abort: push includes unstable changeset: cda648ca50f5!
513 513 [255]
514 514
515 515 Test that extinct changeset are properly detected
516 516
517 517 $ hg log -r 'extinct()'
518 518
519 519 Don't try to push extinct changeset
520 520
521 521 $ hg init ../tmpf
522 522 $ hg out ../tmpf
523 523 comparing with ../tmpf
524 524 searching for changes
525 525 0:1f0dee641bb7 (public) [ ] add a
526 526 1:7c3bad9141dc (public) [ ] add b
527 527 2:245bde4270cd (public) [ ] add original_c
528 528 3:6f9641995072 (draft) [ ] add n3w_3_c
529 529 4:94b33453f93b (draft) [ ] add original_d
530 530 5:cda648ca50f5 (draft) [tip ] add original_e
531 531 $ hg push ../tmpf -f # -f because be push unstable too
532 532 pushing to ../tmpf
533 533 searching for changes
534 534 adding changesets
535 535 adding manifests
536 536 adding file changes
537 537 added 6 changesets with 6 changes to 6 files (+1 heads)
538 538 7 new obsolescence markers
539 539
540 540 no warning displayed
541 541
542 542 $ hg push ../tmpf
543 543 pushing to ../tmpf
544 544 searching for changes
545 545 no changes found
546 546 [1]
547 547
548 548 Do not warn about new head when the new head is a successors of a remote one
549 549
550 550 $ hg log -G
551 551 @ 5:cda648ca50f5 (draft) [tip ] add original_e
552 552 |
553 553 x 4:94b33453f93b (draft) [ ] add original_d
554 554 |
555 555 o 3:6f9641995072 (draft) [ ] add n3w_3_c
556 556 |
557 557 | o 2:245bde4270cd (public) [ ] add original_c
558 558 |/
559 559 o 1:7c3bad9141dc (public) [ ] add b
560 560 |
561 561 o 0:1f0dee641bb7 (public) [ ] add a
562 562
563 563 $ hg up -q 'desc(n3w_3_c)'
564 564 $ mkcommit obsolete_e
565 565 created new head
566 566 $ hg debugobsolete `getid 'original_e'` `getid 'obsolete_e'`
567 567 $ hg outgoing ../tmpf # parasite hg outgoing testin
568 568 comparing with ../tmpf
569 569 searching for changes
570 570 6:3de5eca88c00 (draft) [tip ] add obsolete_e
571 571 $ hg push ../tmpf
572 572 pushing to ../tmpf
573 573 searching for changes
574 574 adding changesets
575 575 adding manifests
576 576 adding file changes
577 577 added 1 changesets with 1 changes to 1 files (+1 heads)
578 578 1 new obsolescence markers
579 579
580 580 test relevance computation
581 581 ---------------------------------------
582 582
583 583 Checking simple case of "marker relevance".
584 584
585 585
586 586 Reminder of the repo situation
587 587
588 588 $ hg log --hidden --graph
589 589 @ 6:3de5eca88c00 (draft) [tip ] add obsolete_e
590 590 |
591 591 | x 5:cda648ca50f5 (draft) [ ] add original_e
592 592 | |
593 593 | x 4:94b33453f93b (draft) [ ] add original_d
594 594 |/
595 595 o 3:6f9641995072 (draft) [ ] add n3w_3_c
596 596 |
597 597 | o 2:245bde4270cd (public) [ ] add original_c
598 598 |/
599 599 o 1:7c3bad9141dc (public) [ ] add b
600 600 |
601 601 o 0:1f0dee641bb7 (public) [ ] add a
602 602
603 603
604 604 List of all markers
605 605
606 606 $ hg debugobsolete
607 607 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
608 608 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
609 609 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
610 610 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
611 611 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
612 612 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
613 613 94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
614 614 cda648ca50f50482b7055c0b0c4c117bba6733d9 3de5eca88c00aa039da7399a220f4a5221faa585 0 (*) {'user': 'test'} (glob)
615 615
616 616 List of changesets with no chain
617 617
618 618 $ hg debugobsolete --hidden --rev ::2
619 619
620 620 List of changesets that are included on marker chain
621 621
622 622 $ hg debugobsolete --hidden --rev 6
623 623 cda648ca50f50482b7055c0b0c4c117bba6733d9 3de5eca88c00aa039da7399a220f4a5221faa585 0 (*) {'user': 'test'} (glob)
624 624
625 625 List of changesets with a longer chain, (including a pruned children)
626 626
627 627 $ hg debugobsolete --hidden --rev 3
628 628 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
629 629 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
630 630 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
631 631 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
632 632 94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
633 633 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
634 634 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
635 635
636 636 List of both
637 637
638 638 $ hg debugobsolete --hidden --rev 3::6
639 639 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
640 640 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
641 641 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
642 642 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
643 643 94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
644 644 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
645 645 cda648ca50f50482b7055c0b0c4c117bba6733d9 3de5eca88c00aa039da7399a220f4a5221faa585 0 (*) {'user': 'test'} (glob)
646 646 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
647 647
648 List of all markers in JSON
649
650 $ hg debugobsolete -Tjson
651 [
652 {
653 "date": [1339.0, 0],
654 "flag": 0,
655 "metadata": {"user": "test"},
656 "precnode": "1339133913391339133913391339133913391339",
657 "succnodes": ["ca819180edb99ed25ceafb3e9584ac287e240b00"]
658 },
659 {
660 "date": [1339.0, 0],
661 "flag": 0,
662 "metadata": {"user": "test"},
663 "precnode": "1337133713371337133713371337133713371337",
664 "succnodes": ["5601fb93a350734d935195fee37f4054c529ff39"]
665 },
666 {
667 "date": [121.0, 120],
668 "flag": 12,
669 "metadata": {"user": "test"},
670 "precnode": "245bde4270cd1072a27757984f9cda8ba26f08ca",
671 "succnodes": ["cdbce2fbb16313928851e97e0d85413f3f7eb77f"]
672 },
673 {
674 "date": [1338.0, 0],
675 "flag": 1,
676 "metadata": {"user": "test"},
677 "precnode": "5601fb93a350734d935195fee37f4054c529ff39",
678 "succnodes": ["6f96419950729f3671185b847352890f074f7557"]
679 },
680 {
681 "date": [1338.0, 0],
682 "flag": 0,
683 "metadata": {"user": "test"},
684 "precnode": "ca819180edb99ed25ceafb3e9584ac287e240b00",
685 "succnodes": ["1337133713371337133713371337133713371337"]
686 },
687 {
688 "date": [1337.0, 0],
689 "flag": 0,
690 "metadata": {"user": "test"},
691 "precnode": "cdbce2fbb16313928851e97e0d85413f3f7eb77f",
692 "succnodes": ["ca819180edb99ed25ceafb3e9584ac287e240b00"]
693 },
694 {
695 "date": [0.0, 0],
696 "flag": 0,
697 "metadata": {"user": "test"},
698 "parentnodes": ["6f96419950729f3671185b847352890f074f7557"],
699 "precnode": "94b33453f93bdb8d457ef9b770851a618bf413e1",
700 "succnodes": []
701 },
702 {
703 "date": *, (glob)
704 "flag": 0,
705 "metadata": {"user": "test"},
706 "precnode": "cda648ca50f50482b7055c0b0c4c117bba6733d9",
707 "succnodes": ["3de5eca88c00aa039da7399a220f4a5221faa585"]
708 }
709 ]
710
711 Template keywords
712
713 $ hg debugobsolete -r6 -T '{succnodes % "{node|short}"} {date|shortdate}\n'
714 3de5eca88c00 ????-??-?? (glob)
715 $ hg debugobsolete -r6 -T '{join(metadata % "{key}={value}", " ")}\n'
716 user=test
717 $ hg debugobsolete -r6 -T '{metadata}\n'
718 'user': 'test'
719 $ hg debugobsolete -r6 -T '{flag} {get(metadata, "user")}\n'
720 0 test
721
648 722 #if serve
649 723
650 724 Test the debug output for exchange
651 725 ----------------------------------
652 726
653 727 $ hg pull ../tmpb --config 'experimental.obsmarkers-exchange-debug=True' # bundle2
654 728 pulling from ../tmpb
655 729 searching for changes
656 730 no changes found
657 731 obsmarker-exchange: 346 bytes received
658 732
659 733 check hgweb does not explode
660 734 ====================================
661 735
662 736 $ hg unbundle $TESTDIR/bundles/hgweb+obs.hg
663 737 adding changesets
664 738 adding manifests
665 739 adding file changes
666 740 added 62 changesets with 63 changes to 9 files (+60 heads)
667 741 (run 'hg heads .' to see heads, 'hg merge' to merge)
668 742 $ for node in `hg log -r 'desc(babar_)' --template '{node}\n'`;
669 743 > do
670 744 > hg debugobsolete $node
671 745 > done
672 746 $ hg up tip
673 747 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
674 748
675 749 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
676 750 $ cat hg.pid >> $DAEMON_PIDS
677 751
678 752 check changelog view
679 753
680 754 $ get-with-headers.py --headeronly localhost:$HGPORT 'shortlog/'
681 755 200 Script output follows
682 756
683 757 check graph view
684 758
685 759 $ get-with-headers.py --headeronly localhost:$HGPORT 'graph'
686 760 200 Script output follows
687 761
688 762 check filelog view
689 763
690 764 $ get-with-headers.py --headeronly localhost:$HGPORT 'log/'`hg log -r . -T "{node}"`/'babar'
691 765 200 Script output follows
692 766
693 767 $ get-with-headers.py --headeronly localhost:$HGPORT 'rev/68'
694 768 200 Script output follows
695 769 $ get-with-headers.py --headeronly localhost:$HGPORT 'rev/67'
696 770 404 Not Found
697 771 [1]
698 772
699 773 check that web.view config option:
700 774
701 775 $ killdaemons.py hg.pid
702 776 $ cat >> .hg/hgrc << EOF
703 777 > [web]
704 778 > view=all
705 779 > EOF
706 780 $ wait
707 781 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
708 782 $ get-with-headers.py --headeronly localhost:$HGPORT 'rev/67'
709 783 200 Script output follows
710 784 $ killdaemons.py hg.pid
711 785
712 786 Checking _enable=False warning if obsolete marker exists
713 787
714 788 $ echo '[experimental]' >> $HGRCPATH
715 789 $ echo "evolution=" >> $HGRCPATH
716 790 $ hg log -r tip
717 791 obsolete feature not enabled but 68 markers found!
718 792 68:c15e9edfca13 (draft) [tip ] add celestine
719 793
720 794 reenable for later test
721 795
722 796 $ echo '[experimental]' >> $HGRCPATH
723 797 $ echo "evolution=createmarkers,exchange" >> $HGRCPATH
724 798
725 799 #endif
726 800
727 801 Test incoming/outcoming with changesets obsoleted remotely, known locally
728 802 ===============================================================================
729 803
730 804 This test issue 3805
731 805
732 806 $ hg init repo-issue3805
733 807 $ cd repo-issue3805
734 808 $ echo "base" > base
735 809 $ hg ci -Am "base"
736 810 adding base
737 811 $ echo "foo" > foo
738 812 $ hg ci -Am "A"
739 813 adding foo
740 814 $ hg clone . ../other-issue3805
741 815 updating to branch default
742 816 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
743 817 $ echo "bar" >> foo
744 818 $ hg ci --amend
745 819 $ cd ../other-issue3805
746 820 $ hg log -G
747 821 @ 1:29f0c6921ddd (draft) [tip ] A
748 822 |
749 823 o 0:d20a80d4def3 (draft) [ ] base
750 824
751 825 $ hg log -G -R ../repo-issue3805
752 826 @ 3:323a9c3ddd91 (draft) [tip ] A
753 827 |
754 828 o 0:d20a80d4def3 (draft) [ ] base
755 829
756 830 $ hg incoming
757 831 comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
758 832 searching for changes
759 833 3:323a9c3ddd91 (draft) [tip ] A
760 834 $ hg incoming --bundle ../issue3805.hg
761 835 comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
762 836 searching for changes
763 837 3:323a9c3ddd91 (draft) [tip ] A
764 838 $ hg outgoing
765 839 comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
766 840 searching for changes
767 841 1:29f0c6921ddd (draft) [tip ] A
768 842
769 843 #if serve
770 844
771 845 $ hg serve -R ../repo-issue3805 -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
772 846 $ cat hg.pid >> $DAEMON_PIDS
773 847
774 848 $ hg incoming http://localhost:$HGPORT
775 849 comparing with http://localhost:$HGPORT/
776 850 searching for changes
777 851 2:323a9c3ddd91 (draft) [tip ] A
778 852 $ hg outgoing http://localhost:$HGPORT
779 853 comparing with http://localhost:$HGPORT/
780 854 searching for changes
781 855 1:29f0c6921ddd (draft) [tip ] A
782 856
783 857 $ killdaemons.py
784 858
785 859 #endif
786 860
787 861 This test issue 3814
788 862
789 863 (nothing to push but locally hidden changeset)
790 864
791 865 $ cd ..
792 866 $ hg init repo-issue3814
793 867 $ cd repo-issue3805
794 868 $ hg push -r 323a9c3ddd91 ../repo-issue3814
795 869 pushing to ../repo-issue3814
796 870 searching for changes
797 871 adding changesets
798 872 adding manifests
799 873 adding file changes
800 874 added 2 changesets with 2 changes to 2 files
801 875 2 new obsolescence markers
802 876 $ hg out ../repo-issue3814
803 877 comparing with ../repo-issue3814
804 878 searching for changes
805 879 no changes found
806 880 [1]
807 881
808 882 Test that a local tag blocks a changeset from being hidden
809 883
810 884 $ hg tag -l visible -r 1 --hidden
811 885 $ hg log -G
812 886 @ 3:323a9c3ddd91 (draft) [tip ] A
813 887 |
814 888 | x 1:29f0c6921ddd (draft) [visible ] A
815 889 |/
816 890 o 0:d20a80d4def3 (draft) [ ] base
817 891
818 892 Test that removing a local tag does not cause some commands to fail
819 893
820 894 $ hg tag -l -r tip tiptag
821 895 $ hg tags
822 896 tiptag 3:323a9c3ddd91
823 897 tip 3:323a9c3ddd91
824 898 visible 1:29f0c6921ddd
825 899 $ hg --config extensions.strip= strip -r tip --no-backup
826 900 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
827 901 $ hg tags
828 902 visible 1:29f0c6921ddd
829 903 tip 1:29f0c6921ddd
830 904
831 905 Test bundle overlay onto hidden revision
832 906
833 907 $ cd ..
834 908 $ hg init repo-bundleoverlay
835 909 $ cd repo-bundleoverlay
836 910 $ echo "A" > foo
837 911 $ hg ci -Am "A"
838 912 adding foo
839 913 $ echo "B" >> foo
840 914 $ hg ci -m "B"
841 915 $ hg up 0
842 916 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
843 917 $ echo "C" >> foo
844 918 $ hg ci -m "C"
845 919 created new head
846 920 $ hg log -G
847 921 @ 2:c186d7714947 (draft) [tip ] C
848 922 |
849 923 | o 1:44526ebb0f98 (draft) [ ] B
850 924 |/
851 925 o 0:4b34ecfb0d56 (draft) [ ] A
852 926
853 927
854 928 $ hg clone -r1 . ../other-bundleoverlay
855 929 adding changesets
856 930 adding manifests
857 931 adding file changes
858 932 added 2 changesets with 2 changes to 1 files
859 933 updating to branch default
860 934 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
861 935 $ cd ../other-bundleoverlay
862 936 $ echo "B+" >> foo
863 937 $ hg ci --amend -m "B+"
864 938 $ hg log -G --hidden
865 939 @ 3:b7d587542d40 (draft) [tip ] B+
866 940 |
867 941 | x 2:eb95e9297e18 (draft) [ ] temporary amend commit for 44526ebb0f98
868 942 | |
869 943 | x 1:44526ebb0f98 (draft) [ ] B
870 944 |/
871 945 o 0:4b34ecfb0d56 (draft) [ ] A
872 946
873 947
874 948 $ hg incoming ../repo-bundleoverlay --bundle ../bundleoverlay.hg
875 949 comparing with ../repo-bundleoverlay
876 950 searching for changes
877 951 1:44526ebb0f98 (draft) [ ] B
878 952 2:c186d7714947 (draft) [tip ] C
879 953 $ hg log -G -R ../bundleoverlay.hg
880 954 o 4:c186d7714947 (draft) [tip ] C
881 955 |
882 956 | @ 3:b7d587542d40 (draft) [ ] B+
883 957 |/
884 958 o 0:4b34ecfb0d56 (draft) [ ] A
885 959
886 960
887 961 #if serve
888 962
889 963 Test issue 4506
890 964
891 965 $ cd ..
892 966 $ hg init repo-issue4506
893 967 $ cd repo-issue4506
894 968 $ echo "0" > foo
895 969 $ hg add foo
896 970 $ hg ci -m "content-0"
897 971
898 972 $ hg up null
899 973 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
900 974 $ echo "1" > bar
901 975 $ hg add bar
902 976 $ hg ci -m "content-1"
903 977 created new head
904 978 $ hg up 0
905 979 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
906 980 $ hg graft 1
907 981 grafting 1:1c9eddb02162 "content-1" (tip)
908 982
909 983 $ hg debugobsolete `hg log -r1 -T'{node}'` `hg log -r2 -T'{node}'`
910 984
911 985 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
912 986 $ cat hg.pid >> $DAEMON_PIDS
913 987
914 988 $ get-with-headers.py --headeronly localhost:$HGPORT 'rev/1'
915 989 404 Not Found
916 990 [1]
917 991 $ get-with-headers.py --headeronly localhost:$HGPORT 'file/tip/bar'
918 992 200 Script output follows
919 993 $ get-with-headers.py --headeronly localhost:$HGPORT 'annotate/tip/bar'
920 994 200 Script output follows
921 995
922 996 $ killdaemons.py
923 997
924 998 #endif
925 999
926 1000 Test heads computation on pending index changes with obsolescence markers
927 1001 $ cd ..
928 1002 $ cat >$TESTTMP/test_extension.py << EOF
929 1003 > from mercurial import cmdutil
930 1004 > from mercurial.i18n import _
931 1005 >
932 1006 > cmdtable = {}
933 1007 > command = cmdutil.command(cmdtable)
934 1008 > @command("amendtransient",[], _('hg amendtransient [rev]'))
935 1009 > def amend(ui, repo, *pats, **opts):
936 1010 > def commitfunc(ui, repo, message, match, opts):
937 1011 > return repo.commit(message, repo['.'].user(), repo['.'].date(), match)
938 1012 > opts['message'] = 'Test'
939 1013 > opts['logfile'] = None
940 1014 > cmdutil.amend(ui, repo, commitfunc, repo['.'], {}, pats, opts)
941 1015 > ui.write('%s\n' % repo.changelog.headrevs())
942 1016 > EOF
943 1017 $ cat >> $HGRCPATH << EOF
944 1018 > [extensions]
945 1019 > testextension=$TESTTMP/test_extension.py
946 1020 > EOF
947 1021 $ hg init repo-issue-nativerevs-pending-changes
948 1022 $ cd repo-issue-nativerevs-pending-changes
949 1023 $ mkcommit a
950 1024 $ mkcommit b
951 1025 $ hg up ".^"
952 1026 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
953 1027 $ echo aa > a
954 1028 $ hg amendtransient
955 1029 [1, 3]
956 1030
957 1031 Check that corrupted hidden cache does not crash
958 1032
959 1033 $ printf "" > .hg/cache/hidden
960 1034 $ hg log -r . -T '{node}' --debug
961 1035 corrupted hidden cache
962 1036 8fd96dfc63e51ed5a8af1bec18eb4b19dbf83812 (no-eol)
963 1037 $ hg log -r . -T '{node}' --debug
964 1038 8fd96dfc63e51ed5a8af1bec18eb4b19dbf83812 (no-eol)
965 1039
966 1040 #if unix-permissions
967 1041 Check that wrong hidden cache permission does not crash
968 1042
969 1043 $ chmod 000 .hg/cache/hidden
970 1044 $ hg log -r . -T '{node}' --debug
971 1045 cannot read hidden cache
972 1046 error writing hidden changesets cache
973 1047 8fd96dfc63e51ed5a8af1bec18eb4b19dbf83812 (no-eol)
974 1048 #endif
975 1049
976 1050 Test cache consistency for the visible filter
977 1051 1) We want to make sure that the cached filtered revs are invalidated when
978 1052 bookmarks change
979 1053 $ cd ..
980 1054 $ cat >$TESTTMP/test_extension.py << EOF
981 1055 > import weakref
982 1056 > from mercurial import cmdutil, extensions, bookmarks, repoview
983 1057 > def _bookmarkchanged(orig, bkmstoreinst, *args, **kwargs):
984 1058 > reporef = weakref.ref(bkmstoreinst._repo)
985 1059 > def trhook(tr):
986 1060 > repo = reporef()
987 1061 > hidden1 = repoview.computehidden(repo)
988 1062 > hidden = repoview.filterrevs(repo, 'visible')
989 1063 > if sorted(hidden1) != sorted(hidden):
990 1064 > print "cache inconsistency"
991 1065 > bkmstoreinst._repo.currenttransaction().addpostclose('test_extension', trhook)
992 1066 > orig(bkmstoreinst, *args, **kwargs)
993 1067 > def extsetup(ui):
994 1068 > extensions.wrapfunction(bookmarks.bmstore, 'recordchange',
995 1069 > _bookmarkchanged)
996 1070 > EOF
997 1071
998 1072 $ hg init repo-cache-inconsistency
999 1073 $ cd repo-issue-nativerevs-pending-changes
1000 1074 $ mkcommit a
1001 1075 a already tracked!
1002 1076 $ mkcommit b
1003 1077 $ hg id
1004 1078 13bedc178fce tip
1005 1079 $ echo "hello" > b
1006 1080 $ hg commit --amend -m "message"
1007 1081 $ hg book bookb -r 13bedc178fce --hidden
1008 1082 $ hg log -r 13bedc178fce
1009 1083 5:13bedc178fce (draft) [ bookb] add b
1010 1084 $ hg book -d bookb
1011 1085 $ hg log -r 13bedc178fce
1012 1086 abort: hidden revision '13bedc178fce'!
1013 1087 (use --hidden to access hidden revisions)
1014 1088 [255]
1015 1089
1016 1090 Empty out the test extension, as it isn't compatible with later parts
1017 1091 of the test.
1018 1092 $ echo > $TESTTMP/test_extension.py
1019 1093
1020 1094 Test ability to pull changeset with locally applying obsolescence markers
1021 1095 (issue4945)
1022 1096
1023 1097 $ cd ..
1024 1098 $ hg init issue4845
1025 1099 $ cd issue4845
1026 1100
1027 1101 $ echo foo > f0
1028 1102 $ hg add f0
1029 1103 $ hg ci -m '0'
1030 1104 $ echo foo > f1
1031 1105 $ hg add f1
1032 1106 $ hg ci -m '1'
1033 1107 $ echo foo > f2
1034 1108 $ hg add f2
1035 1109 $ hg ci -m '2'
1036 1110
1037 1111 $ echo bar > f2
1038 1112 $ hg commit --amend --config experimetnal.evolution=createmarkers
1039 1113 $ hg log -G
1040 1114 @ 4:b0551702f918 (draft) [tip ] 2
1041 1115 |
1042 1116 o 1:e016b03fd86f (draft) [ ] 1
1043 1117 |
1044 1118 o 0:a78f55e5508c (draft) [ ] 0
1045 1119
1046 1120 $ hg log -G --hidden
1047 1121 @ 4:b0551702f918 (draft) [tip ] 2
1048 1122 |
1049 1123 | x 3:f27abbcc1f77 (draft) [ ] temporary amend commit for e008cf283490
1050 1124 | |
1051 1125 | x 2:e008cf283490 (draft) [ ] 2
1052 1126 |/
1053 1127 o 1:e016b03fd86f (draft) [ ] 1
1054 1128 |
1055 1129 o 0:a78f55e5508c (draft) [ ] 0
1056 1130
1057 1131
1058 1132 $ hg strip -r 1 --config extensions.strip=
1059 1133 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
1060 1134 saved backup bundle to $TESTTMP/tmpe/issue4845/.hg/strip-backup/e016b03fd86f-c41c6bcc-backup.hg (glob)
1061 1135 $ hg log -G
1062 1136 @ 0:a78f55e5508c (draft) [tip ] 0
1063 1137
1064 1138 $ hg log -G --hidden
1065 1139 @ 0:a78f55e5508c (draft) [tip ] 0
1066 1140
1067 1141
1068 1142 $ hg pull .hg/strip-backup/*
1069 1143 pulling from .hg/strip-backup/e016b03fd86f-c41c6bcc-backup.hg
1070 1144 searching for changes
1071 1145 adding changesets
1072 1146 adding manifests
1073 1147 adding file changes
1074 1148 added 2 changesets with 2 changes to 2 files
1075 1149 (run 'hg update' to get a working copy)
1076 1150 $ hg log -G
1077 1151 o 2:b0551702f918 (draft) [tip ] 2
1078 1152 |
1079 1153 o 1:e016b03fd86f (draft) [ ] 1
1080 1154 |
1081 1155 @ 0:a78f55e5508c (draft) [ ] 0
1082 1156
1083 1157 $ hg log -G --hidden
1084 1158 o 2:b0551702f918 (draft) [tip ] 2
1085 1159 |
1086 1160 o 1:e016b03fd86f (draft) [ ] 1
1087 1161 |
1088 1162 @ 0:a78f55e5508c (draft) [ ] 0
1089 1163
1090 1164 Test that 'hg debugobsolete --index --rev' can show indices of obsmarkers when
1091 1165 only a subset of those are displayed (because of --rev option)
1092 1166 $ hg init doindexrev
1093 1167 $ cd doindexrev
1094 1168 $ echo a > a
1095 1169 $ hg ci -Am a
1096 1170 adding a
1097 1171 $ hg ci --amend -m aa
1098 1172 $ echo b > b
1099 1173 $ hg ci -Am b
1100 1174 adding b
1101 1175 $ hg ci --amend -m bb
1102 1176 $ echo c > c
1103 1177 $ hg ci -Am c
1104 1178 adding c
1105 1179 $ hg ci --amend -m cc
1106 1180 $ echo d > d
1107 1181 $ hg ci -Am d
1108 1182 adding d
1109 1183 $ hg ci --amend -m dd
1110 1184 $ hg debugobsolete --index --rev "3+7"
1111 1185 1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 \(.*\) {'user': 'test'} (re)
1112 1186 3 4715cf767440ed891755448016c2b8cf70760c30 7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d 0 \(.*\) {'user': 'test'} (re)
1187 $ hg debugobsolete --index --rev "3+7" -Tjson
1188 [
1189 {
1190 "date": *, (glob)
1191 "flag": 0,
1192 "index": 1,
1193 "metadata": {"user": "test"},
1194 "precnode": "6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1",
1195 "succnodes": ["d27fb9b066076fd921277a4b9e8b9cb48c95bc6a"]
1196 },
1197 {
1198 "date": *, (glob)
1199 "flag": 0,
1200 "index": 3,
1201 "metadata": {"user": "test"},
1202 "precnode": "4715cf767440ed891755448016c2b8cf70760c30",
1203 "succnodes": ["7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d"]
1204 }
1205 ]
1113 1206
1114 1207 Test the --delete option of debugobsolete command
1115 1208 $ hg debugobsolete --index
1116 1209 0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b f9bd49731b0b175e42992a3c8fa6c678b2bc11f1 0 \(.*\) {'user': 'test'} (re)
1117 1210 1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 \(.*\) {'user': 'test'} (re)
1118 1211 2 1ab51af8f9b41ef8c7f6f3312d4706d870b1fb74 29346082e4a9e27042b62d2da0e2de211c027621 0 \(.*\) {'user': 'test'} (re)
1119 1212 3 4715cf767440ed891755448016c2b8cf70760c30 7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d 0 \(.*\) {'user': 'test'} (re)
1120 1213 $ hg debugobsolete --delete 1 --delete 3
1121 1214 deleted 2 obsolescense markers
1122 1215 $ hg debugobsolete
1123 1216 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b f9bd49731b0b175e42992a3c8fa6c678b2bc11f1 0 \(.*\) {'user': 'test'} (re)
1124 1217 1ab51af8f9b41ef8c7f6f3312d4706d870b1fb74 29346082e4a9e27042b62d2da0e2de211c027621 0 \(.*\) {'user': 'test'} (re)
1125 1218 $ cd ..
1126 1219
General Comments 0
You need to be logged in to leave comments. Login now