##// END OF EJS Templates
merge with stable
Matt Mackall -
r20702:2764148a merge default
parent child Browse files
Show More
@@ -1,2348 +1,2352 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 node import hex, nullid, nullrev, short
9 9 from i18n import _
10 10 import os, sys, errno, re, tempfile
11 11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 12 import match as matchmod
13 13 import context, repair, graphmod, revset, phases, obsolete, pathutil
14 14 import changelog
15 15 import bookmarks
16 16 import lock as lockmod
17 17
18 18 def parsealiases(cmd):
19 19 return cmd.lstrip("^").split("|")
20 20
21 21 def findpossible(cmd, table, strict=False):
22 22 """
23 23 Return cmd -> (aliases, command table entry)
24 24 for each matching command.
25 25 Return debug commands (or their aliases) only if no normal command matches.
26 26 """
27 27 choice = {}
28 28 debugchoice = {}
29 29
30 30 if cmd in table:
31 31 # short-circuit exact matches, "log" alias beats "^log|history"
32 32 keys = [cmd]
33 33 else:
34 34 keys = table.keys()
35 35
36 36 for e in keys:
37 37 aliases = parsealiases(e)
38 38 found = None
39 39 if cmd in aliases:
40 40 found = cmd
41 41 elif not strict:
42 42 for a in aliases:
43 43 if a.startswith(cmd):
44 44 found = a
45 45 break
46 46 if found is not None:
47 47 if aliases[0].startswith("debug") or found.startswith("debug"):
48 48 debugchoice[found] = (aliases, table[e])
49 49 else:
50 50 choice[found] = (aliases, table[e])
51 51
52 52 if not choice and debugchoice:
53 53 choice = debugchoice
54 54
55 55 return choice
56 56
57 57 def findcmd(cmd, table, strict=True):
58 58 """Return (aliases, command table entry) for command string."""
59 59 choice = findpossible(cmd, table, strict)
60 60
61 61 if cmd in choice:
62 62 return choice[cmd]
63 63
64 64 if len(choice) > 1:
65 65 clist = choice.keys()
66 66 clist.sort()
67 67 raise error.AmbiguousCommand(cmd, clist)
68 68
69 69 if choice:
70 70 return choice.values()[0]
71 71
72 72 raise error.UnknownCommand(cmd)
73 73
74 74 def findrepo(p):
75 75 while not os.path.isdir(os.path.join(p, ".hg")):
76 76 oldp, p = p, os.path.dirname(p)
77 77 if p == oldp:
78 78 return None
79 79
80 80 return p
81 81
82 82 def bailifchanged(repo):
83 83 if repo.dirstate.p2() != nullid:
84 84 raise util.Abort(_('outstanding uncommitted merge'))
85 85 modified, added, removed, deleted = repo.status()[:4]
86 86 if modified or added or removed or deleted:
87 87 raise util.Abort(_('uncommitted changes'))
88 88 ctx = repo[None]
89 89 for s in sorted(ctx.substate):
90 90 if ctx.sub(s).dirty():
91 91 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
92 92
93 93 def logmessage(ui, opts):
94 94 """ get the log message according to -m and -l option """
95 95 message = opts.get('message')
96 96 logfile = opts.get('logfile')
97 97
98 98 if message and logfile:
99 99 raise util.Abort(_('options --message and --logfile are mutually '
100 100 'exclusive'))
101 101 if not message and logfile:
102 102 try:
103 103 if logfile == '-':
104 104 message = ui.fin.read()
105 105 else:
106 106 message = '\n'.join(util.readfile(logfile).splitlines())
107 107 except IOError, inst:
108 108 raise util.Abort(_("can't read commit message '%s': %s") %
109 109 (logfile, inst.strerror))
110 110 return message
111 111
112 112 def loglimit(opts):
113 113 """get the log limit according to option -l/--limit"""
114 114 limit = opts.get('limit')
115 115 if limit:
116 116 try:
117 117 limit = int(limit)
118 118 except ValueError:
119 119 raise util.Abort(_('limit must be a positive integer'))
120 120 if limit <= 0:
121 121 raise util.Abort(_('limit must be positive'))
122 122 else:
123 123 limit = None
124 124 return limit
125 125
126 126 def makefilename(repo, pat, node, desc=None,
127 127 total=None, seqno=None, revwidth=None, pathname=None):
128 128 node_expander = {
129 129 'H': lambda: hex(node),
130 130 'R': lambda: str(repo.changelog.rev(node)),
131 131 'h': lambda: short(node),
132 132 'm': lambda: re.sub('[^\w]', '_', str(desc))
133 133 }
134 134 expander = {
135 135 '%': lambda: '%',
136 136 'b': lambda: os.path.basename(repo.root),
137 137 }
138 138
139 139 try:
140 140 if node:
141 141 expander.update(node_expander)
142 142 if node:
143 143 expander['r'] = (lambda:
144 144 str(repo.changelog.rev(node)).zfill(revwidth or 0))
145 145 if total is not None:
146 146 expander['N'] = lambda: str(total)
147 147 if seqno is not None:
148 148 expander['n'] = lambda: str(seqno)
149 149 if total is not None and seqno is not None:
150 150 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
151 151 if pathname is not None:
152 152 expander['s'] = lambda: os.path.basename(pathname)
153 153 expander['d'] = lambda: os.path.dirname(pathname) or '.'
154 154 expander['p'] = lambda: pathname
155 155
156 156 newname = []
157 157 patlen = len(pat)
158 158 i = 0
159 159 while i < patlen:
160 160 c = pat[i]
161 161 if c == '%':
162 162 i += 1
163 163 c = pat[i]
164 164 c = expander[c]()
165 165 newname.append(c)
166 166 i += 1
167 167 return ''.join(newname)
168 168 except KeyError, inst:
169 169 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
170 170 inst.args[0])
171 171
172 172 def makefileobj(repo, pat, node=None, desc=None, total=None,
173 173 seqno=None, revwidth=None, mode='wb', modemap=None,
174 174 pathname=None):
175 175
176 176 writable = mode not in ('r', 'rb')
177 177
178 178 if not pat or pat == '-':
179 179 fp = writable and repo.ui.fout or repo.ui.fin
180 180 if util.safehasattr(fp, 'fileno'):
181 181 return os.fdopen(os.dup(fp.fileno()), mode)
182 182 else:
183 183 # if this fp can't be duped properly, return
184 184 # a dummy object that can be closed
185 185 class wrappedfileobj(object):
186 186 noop = lambda x: None
187 187 def __init__(self, f):
188 188 self.f = f
189 189 def __getattr__(self, attr):
190 190 if attr == 'close':
191 191 return self.noop
192 192 else:
193 193 return getattr(self.f, attr)
194 194
195 195 return wrappedfileobj(fp)
196 196 if util.safehasattr(pat, 'write') and writable:
197 197 return pat
198 198 if util.safehasattr(pat, 'read') and 'r' in mode:
199 199 return pat
200 200 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
201 201 if modemap is not None:
202 202 mode = modemap.get(fn, mode)
203 203 if mode == 'wb':
204 204 modemap[fn] = 'ab'
205 205 return open(fn, mode)
206 206
207 207 def openrevlog(repo, cmd, file_, opts):
208 208 """opens the changelog, manifest, a filelog or a given revlog"""
209 209 cl = opts['changelog']
210 210 mf = opts['manifest']
211 211 msg = None
212 212 if cl and mf:
213 213 msg = _('cannot specify --changelog and --manifest at the same time')
214 214 elif cl or mf:
215 215 if file_:
216 216 msg = _('cannot specify filename with --changelog or --manifest')
217 217 elif not repo:
218 218 msg = _('cannot specify --changelog or --manifest '
219 219 'without a repository')
220 220 if msg:
221 221 raise util.Abort(msg)
222 222
223 223 r = None
224 224 if repo:
225 225 if cl:
226 226 r = repo.changelog
227 227 elif mf:
228 228 r = repo.manifest
229 229 elif file_:
230 230 filelog = repo.file(file_)
231 231 if len(filelog):
232 232 r = filelog
233 233 if not r:
234 234 if not file_:
235 235 raise error.CommandError(cmd, _('invalid arguments'))
236 236 if not os.path.isfile(file_):
237 237 raise util.Abort(_("revlog '%s' not found") % file_)
238 238 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
239 239 file_[:-2] + ".i")
240 240 return r
241 241
242 242 def copy(ui, repo, pats, opts, rename=False):
243 243 # called with the repo lock held
244 244 #
245 245 # hgsep => pathname that uses "/" to separate directories
246 246 # ossep => pathname that uses os.sep to separate directories
247 247 cwd = repo.getcwd()
248 248 targets = {}
249 249 after = opts.get("after")
250 250 dryrun = opts.get("dry_run")
251 251 wctx = repo[None]
252 252
253 253 def walkpat(pat):
254 254 srcs = []
255 255 badstates = after and '?' or '?r'
256 256 m = scmutil.match(repo[None], [pat], opts, globbed=True)
257 257 for abs in repo.walk(m):
258 258 state = repo.dirstate[abs]
259 259 rel = m.rel(abs)
260 260 exact = m.exact(abs)
261 261 if state in badstates:
262 262 if exact and state == '?':
263 263 ui.warn(_('%s: not copying - file is not managed\n') % rel)
264 264 if exact and state == 'r':
265 265 ui.warn(_('%s: not copying - file has been marked for'
266 266 ' remove\n') % rel)
267 267 continue
268 268 # abs: hgsep
269 269 # rel: ossep
270 270 srcs.append((abs, rel, exact))
271 271 return srcs
272 272
273 273 # abssrc: hgsep
274 274 # relsrc: ossep
275 275 # otarget: ossep
276 276 def copyfile(abssrc, relsrc, otarget, exact):
277 277 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
278 278 if '/' in abstarget:
279 279 # We cannot normalize abstarget itself, this would prevent
280 280 # case only renames, like a => A.
281 281 abspath, absname = abstarget.rsplit('/', 1)
282 282 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
283 283 reltarget = repo.pathto(abstarget, cwd)
284 284 target = repo.wjoin(abstarget)
285 285 src = repo.wjoin(abssrc)
286 286 state = repo.dirstate[abstarget]
287 287
288 288 scmutil.checkportable(ui, abstarget)
289 289
290 290 # check for collisions
291 291 prevsrc = targets.get(abstarget)
292 292 if prevsrc is not None:
293 293 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
294 294 (reltarget, repo.pathto(abssrc, cwd),
295 295 repo.pathto(prevsrc, cwd)))
296 296 return
297 297
298 298 # check for overwrites
299 299 exists = os.path.lexists(target)
300 300 samefile = False
301 301 if exists and abssrc != abstarget:
302 302 if (repo.dirstate.normalize(abssrc) ==
303 303 repo.dirstate.normalize(abstarget)):
304 304 if not rename:
305 305 ui.warn(_("%s: can't copy - same file\n") % reltarget)
306 306 return
307 307 exists = False
308 308 samefile = True
309 309
310 310 if not after and exists or after and state in 'mn':
311 311 if not opts['force']:
312 312 ui.warn(_('%s: not overwriting - file exists\n') %
313 313 reltarget)
314 314 return
315 315
316 316 if after:
317 317 if not exists:
318 318 if rename:
319 319 ui.warn(_('%s: not recording move - %s does not exist\n') %
320 320 (relsrc, reltarget))
321 321 else:
322 322 ui.warn(_('%s: not recording copy - %s does not exist\n') %
323 323 (relsrc, reltarget))
324 324 return
325 325 elif not dryrun:
326 326 try:
327 327 if exists:
328 328 os.unlink(target)
329 329 targetdir = os.path.dirname(target) or '.'
330 330 if not os.path.isdir(targetdir):
331 331 os.makedirs(targetdir)
332 332 if samefile:
333 333 tmp = target + "~hgrename"
334 334 os.rename(src, tmp)
335 335 os.rename(tmp, target)
336 336 else:
337 337 util.copyfile(src, target)
338 338 srcexists = True
339 339 except IOError, inst:
340 340 if inst.errno == errno.ENOENT:
341 341 ui.warn(_('%s: deleted in working copy\n') % relsrc)
342 342 srcexists = False
343 343 else:
344 344 ui.warn(_('%s: cannot copy - %s\n') %
345 345 (relsrc, inst.strerror))
346 346 return True # report a failure
347 347
348 348 if ui.verbose or not exact:
349 349 if rename:
350 350 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
351 351 else:
352 352 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
353 353
354 354 targets[abstarget] = abssrc
355 355
356 356 # fix up dirstate
357 357 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
358 358 dryrun=dryrun, cwd=cwd)
359 359 if rename and not dryrun:
360 360 if not after and srcexists and not samefile:
361 361 util.unlinkpath(repo.wjoin(abssrc))
362 362 wctx.forget([abssrc])
363 363
364 364 # pat: ossep
365 365 # dest ossep
366 366 # srcs: list of (hgsep, hgsep, ossep, bool)
367 367 # return: function that takes hgsep and returns ossep
368 368 def targetpathfn(pat, dest, srcs):
369 369 if os.path.isdir(pat):
370 370 abspfx = pathutil.canonpath(repo.root, cwd, pat)
371 371 abspfx = util.localpath(abspfx)
372 372 if destdirexists:
373 373 striplen = len(os.path.split(abspfx)[0])
374 374 else:
375 375 striplen = len(abspfx)
376 376 if striplen:
377 377 striplen += len(os.sep)
378 378 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
379 379 elif destdirexists:
380 380 res = lambda p: os.path.join(dest,
381 381 os.path.basename(util.localpath(p)))
382 382 else:
383 383 res = lambda p: dest
384 384 return res
385 385
386 386 # pat: ossep
387 387 # dest ossep
388 388 # srcs: list of (hgsep, hgsep, ossep, bool)
389 389 # return: function that takes hgsep and returns ossep
390 390 def targetpathafterfn(pat, dest, srcs):
391 391 if matchmod.patkind(pat):
392 392 # a mercurial pattern
393 393 res = lambda p: os.path.join(dest,
394 394 os.path.basename(util.localpath(p)))
395 395 else:
396 396 abspfx = pathutil.canonpath(repo.root, cwd, pat)
397 397 if len(abspfx) < len(srcs[0][0]):
398 398 # A directory. Either the target path contains the last
399 399 # component of the source path or it does not.
400 400 def evalpath(striplen):
401 401 score = 0
402 402 for s in srcs:
403 403 t = os.path.join(dest, util.localpath(s[0])[striplen:])
404 404 if os.path.lexists(t):
405 405 score += 1
406 406 return score
407 407
408 408 abspfx = util.localpath(abspfx)
409 409 striplen = len(abspfx)
410 410 if striplen:
411 411 striplen += len(os.sep)
412 412 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
413 413 score = evalpath(striplen)
414 414 striplen1 = len(os.path.split(abspfx)[0])
415 415 if striplen1:
416 416 striplen1 += len(os.sep)
417 417 if evalpath(striplen1) > score:
418 418 striplen = striplen1
419 419 res = lambda p: os.path.join(dest,
420 420 util.localpath(p)[striplen:])
421 421 else:
422 422 # a file
423 423 if destdirexists:
424 424 res = lambda p: os.path.join(dest,
425 425 os.path.basename(util.localpath(p)))
426 426 else:
427 427 res = lambda p: dest
428 428 return res
429 429
430 430
431 431 pats = scmutil.expandpats(pats)
432 432 if not pats:
433 433 raise util.Abort(_('no source or destination specified'))
434 434 if len(pats) == 1:
435 435 raise util.Abort(_('no destination specified'))
436 436 dest = pats.pop()
437 437 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
438 438 if not destdirexists:
439 439 if len(pats) > 1 or matchmod.patkind(pats[0]):
440 440 raise util.Abort(_('with multiple sources, destination must be an '
441 441 'existing directory'))
442 442 if util.endswithsep(dest):
443 443 raise util.Abort(_('destination %s is not a directory') % dest)
444 444
445 445 tfn = targetpathfn
446 446 if after:
447 447 tfn = targetpathafterfn
448 448 copylist = []
449 449 for pat in pats:
450 450 srcs = walkpat(pat)
451 451 if not srcs:
452 452 continue
453 453 copylist.append((tfn(pat, dest, srcs), srcs))
454 454 if not copylist:
455 455 raise util.Abort(_('no files to copy'))
456 456
457 457 errors = 0
458 458 for targetpath, srcs in copylist:
459 459 for abssrc, relsrc, exact in srcs:
460 460 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
461 461 errors += 1
462 462
463 463 if errors:
464 464 ui.warn(_('(consider using --after)\n'))
465 465
466 466 return errors != 0
467 467
468 468 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
469 469 runargs=None, appendpid=False):
470 470 '''Run a command as a service.'''
471 471
472 472 def writepid(pid):
473 473 if opts['pid_file']:
474 474 mode = appendpid and 'a' or 'w'
475 475 fp = open(opts['pid_file'], mode)
476 476 fp.write(str(pid) + '\n')
477 477 fp.close()
478 478
479 479 if opts['daemon'] and not opts['daemon_pipefds']:
480 480 # Signal child process startup with file removal
481 481 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
482 482 os.close(lockfd)
483 483 try:
484 484 if not runargs:
485 485 runargs = util.hgcmd() + sys.argv[1:]
486 486 runargs.append('--daemon-pipefds=%s' % lockpath)
487 487 # Don't pass --cwd to the child process, because we've already
488 488 # changed directory.
489 489 for i in xrange(1, len(runargs)):
490 490 if runargs[i].startswith('--cwd='):
491 491 del runargs[i]
492 492 break
493 493 elif runargs[i].startswith('--cwd'):
494 494 del runargs[i:i + 2]
495 495 break
496 496 def condfn():
497 497 return not os.path.exists(lockpath)
498 498 pid = util.rundetached(runargs, condfn)
499 499 if pid < 0:
500 500 raise util.Abort(_('child process failed to start'))
501 501 writepid(pid)
502 502 finally:
503 503 try:
504 504 os.unlink(lockpath)
505 505 except OSError, e:
506 506 if e.errno != errno.ENOENT:
507 507 raise
508 508 if parentfn:
509 509 return parentfn(pid)
510 510 else:
511 511 return
512 512
513 513 if initfn:
514 514 initfn()
515 515
516 516 if not opts['daemon']:
517 517 writepid(os.getpid())
518 518
519 519 if opts['daemon_pipefds']:
520 520 lockpath = opts['daemon_pipefds']
521 521 try:
522 522 os.setsid()
523 523 except AttributeError:
524 524 pass
525 525 os.unlink(lockpath)
526 526 util.hidewindow()
527 527 sys.stdout.flush()
528 528 sys.stderr.flush()
529 529
530 530 nullfd = os.open(os.devnull, os.O_RDWR)
531 531 logfilefd = nullfd
532 532 if logfile:
533 533 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
534 534 os.dup2(nullfd, 0)
535 535 os.dup2(logfilefd, 1)
536 536 os.dup2(logfilefd, 2)
537 537 if nullfd not in (0, 1, 2):
538 538 os.close(nullfd)
539 539 if logfile and logfilefd not in (0, 1, 2):
540 540 os.close(logfilefd)
541 541
542 542 if runfn:
543 543 return runfn()
544 544
545 545 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
546 546 """Utility function used by commands.import to import a single patch
547 547
548 548 This function is explicitly defined here to help the evolve extension to
549 549 wrap this part of the import logic.
550 550
551 551 The API is currently a bit ugly because it a simple code translation from
552 552 the import command. Feel free to make it better.
553 553
554 554 :hunk: a patch (as a binary string)
555 555 :parents: nodes that will be parent of the created commit
556 556 :opts: the full dict of option passed to the import command
557 557 :msgs: list to save commit message to.
558 558 (used in case we need to save it when failing)
559 559 :updatefunc: a function that update a repo to a given node
560 560 updatefunc(<repo>, <node>)
561 561 """
562 562 tmpname, message, user, date, branch, nodeid, p1, p2 = \
563 563 patch.extract(ui, hunk)
564 564
565 565 editor = commiteditor
566 566 if opts.get('edit'):
567 567 editor = commitforceeditor
568 568 update = not opts.get('bypass')
569 569 strip = opts["strip"]
570 570 sim = float(opts.get('similarity') or 0)
571 571 if not tmpname:
572 572 return (None, None)
573 573 msg = _('applied to working directory')
574 574
575 575 try:
576 576 cmdline_message = logmessage(ui, opts)
577 577 if cmdline_message:
578 578 # pickup the cmdline msg
579 579 message = cmdline_message
580 580 elif message:
581 581 # pickup the patch msg
582 582 message = message.strip()
583 583 else:
584 584 # launch the editor
585 585 message = None
586 586 ui.debug('message:\n%s\n' % message)
587 587
588 588 if len(parents) == 1:
589 589 parents.append(repo[nullid])
590 590 if opts.get('exact'):
591 591 if not nodeid or not p1:
592 592 raise util.Abort(_('not a Mercurial patch'))
593 593 p1 = repo[p1]
594 594 p2 = repo[p2 or nullid]
595 595 elif p2:
596 596 try:
597 597 p1 = repo[p1]
598 598 p2 = repo[p2]
599 599 # Without any options, consider p2 only if the
600 600 # patch is being applied on top of the recorded
601 601 # first parent.
602 602 if p1 != parents[0]:
603 603 p1 = parents[0]
604 604 p2 = repo[nullid]
605 605 except error.RepoError:
606 606 p1, p2 = parents
607 607 else:
608 608 p1, p2 = parents
609 609
610 610 n = None
611 611 if update:
612 612 if p1 != parents[0]:
613 613 updatefunc(repo, p1.node())
614 614 if p2 != parents[1]:
615 615 repo.setparents(p1.node(), p2.node())
616 616
617 617 if opts.get('exact') or opts.get('import_branch'):
618 618 repo.dirstate.setbranch(branch or 'default')
619 619
620 620 files = set()
621 621 patch.patch(ui, repo, tmpname, strip=strip, files=files,
622 622 eolmode=None, similarity=sim / 100.0)
623 623 files = list(files)
624 624 if opts.get('no_commit'):
625 625 if message:
626 626 msgs.append(message)
627 627 else:
628 628 if opts.get('exact') or p2:
629 629 # If you got here, you either use --force and know what
630 630 # you are doing or used --exact or a merge patch while
631 631 # being updated to its first parent.
632 632 m = None
633 633 else:
634 634 m = scmutil.matchfiles(repo, files or [])
635 635 n = repo.commit(message, opts.get('user') or user,
636 636 opts.get('date') or date, match=m,
637 637 editor=editor)
638 638 else:
639 639 if opts.get('exact') or opts.get('import_branch'):
640 640 branch = branch or 'default'
641 641 else:
642 642 branch = p1.branch()
643 643 store = patch.filestore()
644 644 try:
645 645 files = set()
646 646 try:
647 647 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
648 648 files, eolmode=None)
649 649 except patch.PatchError, e:
650 650 raise util.Abort(str(e))
651 651 memctx = context.makememctx(repo, (p1.node(), p2.node()),
652 652 message,
653 653 opts.get('user') or user,
654 654 opts.get('date') or date,
655 655 branch, files, store,
656 656 editor=commiteditor)
657 657 repo.savecommitmessage(memctx.description())
658 658 n = memctx.commit()
659 659 finally:
660 660 store.close()
661 661 if opts.get('exact') and hex(n) != nodeid:
662 662 raise util.Abort(_('patch is damaged or loses information'))
663 663 if n:
664 664 # i18n: refers to a short changeset id
665 665 msg = _('created %s') % short(n)
666 666 return (msg, n)
667 667 finally:
668 668 os.unlink(tmpname)
669 669
670 670 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
671 671 opts=None):
672 672 '''export changesets as hg patches.'''
673 673
674 674 total = len(revs)
675 675 revwidth = max([len(str(rev)) for rev in revs])
676 676 filemode = {}
677 677
678 678 def single(rev, seqno, fp):
679 679 ctx = repo[rev]
680 680 node = ctx.node()
681 681 parents = [p.node() for p in ctx.parents() if p]
682 682 branch = ctx.branch()
683 683 if switch_parent:
684 684 parents.reverse()
685 685 prev = (parents and parents[0]) or nullid
686 686
687 687 shouldclose = False
688 688 if not fp and len(template) > 0:
689 689 desc_lines = ctx.description().rstrip().split('\n')
690 690 desc = desc_lines[0] #Commit always has a first line.
691 691 fp = makefileobj(repo, template, node, desc=desc, total=total,
692 692 seqno=seqno, revwidth=revwidth, mode='wb',
693 693 modemap=filemode)
694 694 if fp != template:
695 695 shouldclose = True
696 696 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
697 697 repo.ui.note("%s\n" % fp.name)
698 698
699 699 if not fp:
700 700 write = repo.ui.write
701 701 else:
702 702 def write(s, **kw):
703 703 fp.write(s)
704 704
705 705
706 706 write("# HG changeset patch\n")
707 707 write("# User %s\n" % ctx.user())
708 708 write("# Date %d %d\n" % ctx.date())
709 709 write("# %s\n" % util.datestr(ctx.date()))
710 710 if branch and branch != 'default':
711 711 write("# Branch %s\n" % branch)
712 712 write("# Node ID %s\n" % hex(node))
713 713 write("# Parent %s\n" % hex(prev))
714 714 if len(parents) > 1:
715 715 write("# Parent %s\n" % hex(parents[1]))
716 716 write(ctx.description().rstrip())
717 717 write("\n\n")
718 718
719 719 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
720 720 write(chunk, label=label)
721 721
722 722 if shouldclose:
723 723 fp.close()
724 724
725 725 for seqno, rev in enumerate(revs):
726 726 single(rev, seqno + 1, fp)
727 727
728 728 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
729 729 changes=None, stat=False, fp=None, prefix='',
730 730 listsubrepos=False):
731 731 '''show diff or diffstat.'''
732 732 if fp is None:
733 733 write = ui.write
734 734 else:
735 735 def write(s, **kw):
736 736 fp.write(s)
737 737
738 738 if stat:
739 739 diffopts = diffopts.copy(context=0)
740 740 width = 80
741 741 if not ui.plain():
742 742 width = ui.termwidth()
743 743 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
744 744 prefix=prefix)
745 745 for chunk, label in patch.diffstatui(util.iterlines(chunks),
746 746 width=width,
747 747 git=diffopts.git):
748 748 write(chunk, label=label)
749 749 else:
750 750 for chunk, label in patch.diffui(repo, node1, node2, match,
751 751 changes, diffopts, prefix=prefix):
752 752 write(chunk, label=label)
753 753
754 754 if listsubrepos:
755 755 ctx1 = repo[node1]
756 756 ctx2 = repo[node2]
757 757 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
758 758 tempnode2 = node2
759 759 try:
760 760 if node2 is not None:
761 761 tempnode2 = ctx2.substate[subpath][1]
762 762 except KeyError:
763 763 # A subrepo that existed in node1 was deleted between node1 and
764 764 # node2 (inclusive). Thus, ctx2's substate won't contain that
765 765 # subpath. The best we can do is to ignore it.
766 766 tempnode2 = None
767 767 submatch = matchmod.narrowmatcher(subpath, match)
768 768 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
769 769 stat=stat, fp=fp, prefix=prefix)
770 770
771 771 class changeset_printer(object):
772 772 '''show changeset information when templating not requested.'''
773 773
774 774 def __init__(self, ui, repo, patch, diffopts, buffered):
775 775 self.ui = ui
776 776 self.repo = repo
777 777 self.buffered = buffered
778 778 self.patch = patch
779 779 self.diffopts = diffopts
780 780 self.header = {}
781 781 self.hunk = {}
782 782 self.lastheader = None
783 783 self.footer = None
784 784
785 785 def flush(self, rev):
786 786 if rev in self.header:
787 787 h = self.header[rev]
788 788 if h != self.lastheader:
789 789 self.lastheader = h
790 790 self.ui.write(h)
791 791 del self.header[rev]
792 792 if rev in self.hunk:
793 793 self.ui.write(self.hunk[rev])
794 794 del self.hunk[rev]
795 795 return 1
796 796 return 0
797 797
798 798 def close(self):
799 799 if self.footer:
800 800 self.ui.write(self.footer)
801 801
802 802 def show(self, ctx, copies=None, matchfn=None, **props):
803 803 if self.buffered:
804 804 self.ui.pushbuffer()
805 805 self._show(ctx, copies, matchfn, props)
806 806 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
807 807 else:
808 808 self._show(ctx, copies, matchfn, props)
809 809
810 810 def _show(self, ctx, copies, matchfn, props):
811 811 '''show a single changeset or file revision'''
812 812 changenode = ctx.node()
813 813 rev = ctx.rev()
814 814
815 815 if self.ui.quiet:
816 816 self.ui.write("%d:%s\n" % (rev, short(changenode)),
817 817 label='log.node')
818 818 return
819 819
820 820 log = self.repo.changelog
821 821 date = util.datestr(ctx.date())
822 822
823 823 hexfunc = self.ui.debugflag and hex or short
824 824
825 825 parents = [(p, hexfunc(log.node(p)))
826 826 for p in self._meaningful_parentrevs(log, rev)]
827 827
828 828 # i18n: column positioning for "hg log"
829 829 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
830 830 label='log.changeset changeset.%s' % ctx.phasestr())
831 831
832 832 branch = ctx.branch()
833 833 # don't show the default branch name
834 834 if branch != 'default':
835 835 # i18n: column positioning for "hg log"
836 836 self.ui.write(_("branch: %s\n") % branch,
837 837 label='log.branch')
838 838 for bookmark in self.repo.nodebookmarks(changenode):
839 839 # i18n: column positioning for "hg log"
840 840 self.ui.write(_("bookmark: %s\n") % bookmark,
841 841 label='log.bookmark')
842 842 for tag in self.repo.nodetags(changenode):
843 843 # i18n: column positioning for "hg log"
844 844 self.ui.write(_("tag: %s\n") % tag,
845 845 label='log.tag')
846 846 if self.ui.debugflag and ctx.phase():
847 847 # i18n: column positioning for "hg log"
848 848 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
849 849 label='log.phase')
850 850 for parent in parents:
851 851 # i18n: column positioning for "hg log"
852 852 self.ui.write(_("parent: %d:%s\n") % parent,
853 853 label='log.parent changeset.%s' % ctx.phasestr())
854 854
855 855 if self.ui.debugflag:
856 856 mnode = ctx.manifestnode()
857 857 # i18n: column positioning for "hg log"
858 858 self.ui.write(_("manifest: %d:%s\n") %
859 859 (self.repo.manifest.rev(mnode), hex(mnode)),
860 860 label='ui.debug log.manifest')
861 861 # i18n: column positioning for "hg log"
862 862 self.ui.write(_("user: %s\n") % ctx.user(),
863 863 label='log.user')
864 864 # i18n: column positioning for "hg log"
865 865 self.ui.write(_("date: %s\n") % date,
866 866 label='log.date')
867 867
868 868 if self.ui.debugflag:
869 869 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
870 870 for key, value in zip([# i18n: column positioning for "hg log"
871 871 _("files:"),
872 872 # i18n: column positioning for "hg log"
873 873 _("files+:"),
874 874 # i18n: column positioning for "hg log"
875 875 _("files-:")], files):
876 876 if value:
877 877 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
878 878 label='ui.debug log.files')
879 879 elif ctx.files() and self.ui.verbose:
880 880 # i18n: column positioning for "hg log"
881 881 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
882 882 label='ui.note log.files')
883 883 if copies and self.ui.verbose:
884 884 copies = ['%s (%s)' % c for c in copies]
885 885 # i18n: column positioning for "hg log"
886 886 self.ui.write(_("copies: %s\n") % ' '.join(copies),
887 887 label='ui.note log.copies')
888 888
889 889 extra = ctx.extra()
890 890 if extra and self.ui.debugflag:
891 891 for key, value in sorted(extra.items()):
892 892 # i18n: column positioning for "hg log"
893 893 self.ui.write(_("extra: %s=%s\n")
894 894 % (key, value.encode('string_escape')),
895 895 label='ui.debug log.extra')
896 896
897 897 description = ctx.description().strip()
898 898 if description:
899 899 if self.ui.verbose:
900 900 self.ui.write(_("description:\n"),
901 901 label='ui.note log.description')
902 902 self.ui.write(description,
903 903 label='ui.note log.description')
904 904 self.ui.write("\n\n")
905 905 else:
906 906 # i18n: column positioning for "hg log"
907 907 self.ui.write(_("summary: %s\n") %
908 908 description.splitlines()[0],
909 909 label='log.summary')
910 910 self.ui.write("\n")
911 911
912 912 self.showpatch(changenode, matchfn)
913 913
914 914 def showpatch(self, node, matchfn):
915 915 if not matchfn:
916 916 matchfn = self.patch
917 917 if matchfn:
918 918 stat = self.diffopts.get('stat')
919 919 diff = self.diffopts.get('patch')
920 920 diffopts = patch.diffopts(self.ui, self.diffopts)
921 921 prev = self.repo.changelog.parents(node)[0]
922 922 if stat:
923 923 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
924 924 match=matchfn, stat=True)
925 925 if diff:
926 926 if stat:
927 927 self.ui.write("\n")
928 928 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
929 929 match=matchfn, stat=False)
930 930 self.ui.write("\n")
931 931
932 932 def _meaningful_parentrevs(self, log, rev):
933 933 """Return list of meaningful (or all if debug) parentrevs for rev.
934 934
935 935 For merges (two non-nullrev revisions) both parents are meaningful.
936 936 Otherwise the first parent revision is considered meaningful if it
937 937 is not the preceding revision.
938 938 """
939 939 parents = log.parentrevs(rev)
940 940 if not self.ui.debugflag and parents[1] == nullrev:
941 941 if parents[0] >= rev - 1:
942 942 parents = []
943 943 else:
944 944 parents = [parents[0]]
945 945 return parents
946 946
947 947
948 948 class changeset_templater(changeset_printer):
949 949 '''format changeset information.'''
950 950
951 951 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
952 952 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
953 953 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
954 954 defaulttempl = {
955 955 'parent': '{rev}:{node|formatnode} ',
956 956 'manifest': '{rev}:{node|formatnode}',
957 957 'file_copy': '{name} ({source})',
958 958 'extra': '{key}={value|stringescape}'
959 959 }
960 960 # filecopy is preserved for compatibility reasons
961 961 defaulttempl['filecopy'] = defaulttempl['file_copy']
962 962 self.t = templater.templater(mapfile, {'formatnode': formatnode},
963 963 cache=defaulttempl)
964 964 if tmpl:
965 965 self.t.cache['changeset'] = tmpl
966 966
967 967 self.cache = {}
968 968
969 969 def _meaningful_parentrevs(self, ctx):
970 970 """Return list of meaningful (or all if debug) parentrevs for rev.
971 971 """
972 972 parents = ctx.parents()
973 973 if len(parents) > 1:
974 974 return parents
975 975 if self.ui.debugflag:
976 976 return [parents[0], self.repo['null']]
977 977 if parents[0].rev() >= ctx.rev() - 1:
978 978 return []
979 979 return parents
980 980
981 981 def _show(self, ctx, copies, matchfn, props):
982 982 '''show a single changeset or file revision'''
983 983
984 984 showlist = templatekw.showlist
985 985
986 986 # showparents() behaviour depends on ui trace level which
987 987 # causes unexpected behaviours at templating level and makes
988 988 # it harder to extract it in a standalone function. Its
989 989 # behaviour cannot be changed so leave it here for now.
990 990 def showparents(**args):
991 991 ctx = args['ctx']
992 992 parents = [[('rev', p.rev()), ('node', p.hex())]
993 993 for p in self._meaningful_parentrevs(ctx)]
994 994 return showlist('parent', parents, **args)
995 995
996 996 props = props.copy()
997 997 props.update(templatekw.keywords)
998 998 props['parents'] = showparents
999 999 props['templ'] = self.t
1000 1000 props['ctx'] = ctx
1001 1001 props['repo'] = self.repo
1002 1002 props['revcache'] = {'copies': copies}
1003 1003 props['cache'] = self.cache
1004 1004
1005 1005 # find correct templates for current mode
1006 1006
1007 1007 tmplmodes = [
1008 1008 (True, None),
1009 1009 (self.ui.verbose, 'verbose'),
1010 1010 (self.ui.quiet, 'quiet'),
1011 1011 (self.ui.debugflag, 'debug'),
1012 1012 ]
1013 1013
1014 1014 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1015 1015 for mode, postfix in tmplmodes:
1016 1016 for type in types:
1017 1017 cur = postfix and ('%s_%s' % (type, postfix)) or type
1018 1018 if mode and cur in self.t:
1019 1019 types[type] = cur
1020 1020
1021 1021 try:
1022 1022
1023 1023 # write header
1024 1024 if types['header']:
1025 1025 h = templater.stringify(self.t(types['header'], **props))
1026 1026 if self.buffered:
1027 1027 self.header[ctx.rev()] = h
1028 1028 else:
1029 1029 if self.lastheader != h:
1030 1030 self.lastheader = h
1031 1031 self.ui.write(h)
1032 1032
1033 1033 # write changeset metadata, then patch if requested
1034 1034 key = types['changeset']
1035 1035 self.ui.write(templater.stringify(self.t(key, **props)))
1036 1036 self.showpatch(ctx.node(), matchfn)
1037 1037
1038 1038 if types['footer']:
1039 1039 if not self.footer:
1040 1040 self.footer = templater.stringify(self.t(types['footer'],
1041 1041 **props))
1042 1042
1043 1043 except KeyError, inst:
1044 1044 msg = _("%s: no key named '%s'")
1045 1045 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1046 1046 except SyntaxError, inst:
1047 1047 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1048 1048
1049 1049 def gettemplate(ui, tmpl, style):
1050 1050 """
1051 1051 Find the template matching the given template spec or style.
1052 1052 """
1053 1053
1054 1054 # ui settings
1055 1055 if not tmpl and not style:
1056 1056 tmpl = ui.config('ui', 'logtemplate')
1057 1057 if tmpl:
1058 1058 try:
1059 1059 tmpl = templater.parsestring(tmpl)
1060 1060 except SyntaxError:
1061 1061 tmpl = templater.parsestring(tmpl, quoted=False)
1062 1062 return tmpl, None
1063 1063 else:
1064 1064 style = util.expandpath(ui.config('ui', 'style', ''))
1065 1065
1066 1066 if style:
1067 1067 mapfile = style
1068 1068 if not os.path.split(mapfile)[0]:
1069 1069 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1070 1070 or templater.templatepath(mapfile))
1071 1071 if mapname:
1072 1072 mapfile = mapname
1073 1073 return None, mapfile
1074 1074
1075 1075 if not tmpl:
1076 1076 return None, None
1077 1077
1078 1078 # looks like a literal template?
1079 1079 if '{' in tmpl:
1080 1080 return tmpl, None
1081 1081
1082 1082 # perhaps a stock style?
1083 1083 if not os.path.split(tmpl)[0]:
1084 1084 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1085 1085 or templater.templatepath(tmpl))
1086 1086 if mapname and os.path.isfile(mapname):
1087 1087 return None, mapname
1088 1088
1089 1089 # perhaps it's a reference to [templates]
1090 1090 t = ui.config('templates', tmpl)
1091 1091 if t:
1092 1092 try:
1093 1093 tmpl = templater.parsestring(t)
1094 1094 except SyntaxError:
1095 1095 tmpl = templater.parsestring(t, quoted=False)
1096 1096 return tmpl, None
1097 1097
1098 1098 # perhaps it's a path to a map or a template
1099 1099 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1100 1100 # is it a mapfile for a style?
1101 1101 if os.path.basename(tmpl).startswith("map-"):
1102 1102 return None, os.path.realpath(tmpl)
1103 1103 tmpl = open(tmpl).read()
1104 1104 return tmpl, None
1105 1105
1106 1106 # constant string?
1107 1107 return tmpl, None
1108 1108
1109 1109 def show_changeset(ui, repo, opts, buffered=False):
1110 1110 """show one changeset using template or regular display.
1111 1111
1112 1112 Display format will be the first non-empty hit of:
1113 1113 1. option 'template'
1114 1114 2. option 'style'
1115 1115 3. [ui] setting 'logtemplate'
1116 1116 4. [ui] setting 'style'
1117 1117 If all of these values are either the unset or the empty string,
1118 1118 regular display via changeset_printer() is done.
1119 1119 """
1120 1120 # options
1121 1121 patch = None
1122 1122 if opts.get('patch') or opts.get('stat'):
1123 1123 patch = scmutil.matchall(repo)
1124 1124
1125 1125 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1126 1126
1127 1127 if not tmpl and not mapfile:
1128 1128 return changeset_printer(ui, repo, patch, opts, buffered)
1129 1129
1130 1130 try:
1131 1131 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1132 1132 except SyntaxError, inst:
1133 1133 raise util.Abort(inst.args[0])
1134 1134 return t
1135 1135
1136 1136 def showmarker(ui, marker):
1137 1137 """utility function to display obsolescence marker in a readable way
1138 1138
1139 1139 To be used by debug function."""
1140 1140 ui.write(hex(marker.precnode()))
1141 1141 for repl in marker.succnodes():
1142 1142 ui.write(' ')
1143 1143 ui.write(hex(repl))
1144 1144 ui.write(' %X ' % marker._data[2])
1145 1145 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1146 1146 sorted(marker.metadata().items()))))
1147 1147 ui.write('\n')
1148 1148
1149 1149 def finddate(ui, repo, date):
1150 1150 """Find the tipmost changeset that matches the given date spec"""
1151 1151
1152 1152 df = util.matchdate(date)
1153 1153 m = scmutil.matchall(repo)
1154 1154 results = {}
1155 1155
1156 1156 def prep(ctx, fns):
1157 1157 d = ctx.date()
1158 1158 if df(d[0]):
1159 1159 results[ctx.rev()] = d
1160 1160
1161 1161 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1162 1162 rev = ctx.rev()
1163 1163 if rev in results:
1164 1164 ui.status(_("found revision %s from %s\n") %
1165 1165 (rev, util.datestr(results[rev])))
1166 1166 return str(rev)
1167 1167
1168 1168 raise util.Abort(_("revision matching date not found"))
1169 1169
1170 1170 def increasingwindows(windowsize=8, sizelimit=512):
1171 1171 while True:
1172 1172 yield windowsize
1173 1173 if windowsize < sizelimit:
1174 1174 windowsize *= 2
1175 1175
1176 1176 class FileWalkError(Exception):
1177 1177 pass
1178 1178
1179 1179 def walkfilerevs(repo, match, follow, revs, fncache):
1180 1180 '''Walks the file history for the matched files.
1181 1181
1182 1182 Returns the changeset revs that are involved in the file history.
1183 1183
1184 1184 Throws FileWalkError if the file history can't be walked using
1185 1185 filelogs alone.
1186 1186 '''
1187 1187 wanted = set()
1188 1188 copies = []
1189 1189 minrev, maxrev = min(revs), max(revs)
1190 1190 def filerevgen(filelog, last):
1191 1191 """
1192 1192 Only files, no patterns. Check the history of each file.
1193 1193
1194 1194 Examines filelog entries within minrev, maxrev linkrev range
1195 1195 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1196 1196 tuples in backwards order
1197 1197 """
1198 1198 cl_count = len(repo)
1199 1199 revs = []
1200 1200 for j in xrange(0, last + 1):
1201 1201 linkrev = filelog.linkrev(j)
1202 1202 if linkrev < minrev:
1203 1203 continue
1204 1204 # only yield rev for which we have the changelog, it can
1205 1205 # happen while doing "hg log" during a pull or commit
1206 1206 if linkrev >= cl_count:
1207 1207 break
1208 1208
1209 1209 parentlinkrevs = []
1210 1210 for p in filelog.parentrevs(j):
1211 1211 if p != nullrev:
1212 1212 parentlinkrevs.append(filelog.linkrev(p))
1213 1213 n = filelog.node(j)
1214 1214 revs.append((linkrev, parentlinkrevs,
1215 1215 follow and filelog.renamed(n)))
1216 1216
1217 1217 return reversed(revs)
1218 1218 def iterfiles():
1219 1219 pctx = repo['.']
1220 1220 for filename in match.files():
1221 1221 if follow:
1222 1222 if filename not in pctx:
1223 1223 raise util.Abort(_('cannot follow file not in parent '
1224 1224 'revision: "%s"') % filename)
1225 1225 yield filename, pctx[filename].filenode()
1226 1226 else:
1227 1227 yield filename, None
1228 1228 for filename_node in copies:
1229 1229 yield filename_node
1230 1230
1231 1231 for file_, node in iterfiles():
1232 1232 filelog = repo.file(file_)
1233 1233 if not len(filelog):
1234 1234 if node is None:
1235 1235 # A zero count may be a directory or deleted file, so
1236 1236 # try to find matching entries on the slow path.
1237 1237 if follow:
1238 1238 raise util.Abort(
1239 1239 _('cannot follow nonexistent file: "%s"') % file_)
1240 1240 raise FileWalkError("Cannot walk via filelog")
1241 1241 else:
1242 1242 continue
1243 1243
1244 1244 if node is None:
1245 1245 last = len(filelog) - 1
1246 1246 else:
1247 1247 last = filelog.rev(node)
1248 1248
1249 1249
1250 1250 # keep track of all ancestors of the file
1251 1251 ancestors = set([filelog.linkrev(last)])
1252 1252
1253 1253 # iterate from latest to oldest revision
1254 1254 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1255 1255 if not follow:
1256 1256 if rev > maxrev:
1257 1257 continue
1258 1258 else:
1259 1259 # Note that last might not be the first interesting
1260 1260 # rev to us:
1261 1261 # if the file has been changed after maxrev, we'll
1262 1262 # have linkrev(last) > maxrev, and we still need
1263 1263 # to explore the file graph
1264 1264 if rev not in ancestors:
1265 1265 continue
1266 1266 # XXX insert 1327 fix here
1267 1267 if flparentlinkrevs:
1268 1268 ancestors.update(flparentlinkrevs)
1269 1269
1270 1270 fncache.setdefault(rev, []).append(file_)
1271 1271 wanted.add(rev)
1272 1272 if copied:
1273 1273 copies.append(copied)
1274 1274
1275 1275 return wanted
1276 1276
1277 1277 def walkchangerevs(repo, match, opts, prepare):
1278 1278 '''Iterate over files and the revs in which they changed.
1279 1279
1280 1280 Callers most commonly need to iterate backwards over the history
1281 1281 in which they are interested. Doing so has awful (quadratic-looking)
1282 1282 performance, so we use iterators in a "windowed" way.
1283 1283
1284 1284 We walk a window of revisions in the desired order. Within the
1285 1285 window, we first walk forwards to gather data, then in the desired
1286 1286 order (usually backwards) to display it.
1287 1287
1288 1288 This function returns an iterator yielding contexts. Before
1289 1289 yielding each context, the iterator will first call the prepare
1290 1290 function on each context in the window in forward order.'''
1291 1291
1292 1292 follow = opts.get('follow') or opts.get('follow_first')
1293 1293
1294 1294 if opts.get('rev'):
1295 1295 revs = scmutil.revrange(repo, opts.get('rev'))
1296 1296 elif follow:
1297 1297 revs = repo.revs('reverse(:.)')
1298 1298 else:
1299 1299 revs = revset.baseset(repo)
1300 1300 revs.reverse()
1301 1301 if not revs:
1302 1302 return []
1303 1303 wanted = set()
1304 1304 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1305 1305 fncache = {}
1306 1306 change = repo.changectx
1307 1307
1308 1308 # First step is to fill wanted, the set of revisions that we want to yield.
1309 1309 # When it does not induce extra cost, we also fill fncache for revisions in
1310 1310 # wanted: a cache of filenames that were changed (ctx.files()) and that
1311 1311 # match the file filtering conditions.
1312 1312
1313 1313 if not slowpath and not match.files():
1314 1314 # No files, no patterns. Display all revs.
1315 1315 wanted = revs
1316 1316
1317 1317 if not slowpath and match.files():
1318 1318 # We only have to read through the filelog to find wanted revisions
1319 1319
1320 1320 try:
1321 1321 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1322 1322 except FileWalkError:
1323 1323 slowpath = True
1324 1324
1325 1325 # We decided to fall back to the slowpath because at least one
1326 1326 # of the paths was not a file. Check to see if at least one of them
1327 1327 # existed in history, otherwise simply return
1328 1328 for path in match.files():
1329 1329 if path == '.' or path in repo.store:
1330 1330 break
1331 1331 else:
1332 1332 return []
1333 1333
1334 1334 if slowpath:
1335 1335 # We have to read the changelog to match filenames against
1336 1336 # changed files
1337 1337
1338 1338 if follow:
1339 1339 raise util.Abort(_('can only follow copies/renames for explicit '
1340 1340 'filenames'))
1341 1341
1342 1342 # The slow path checks files modified in every changeset.
1343 1343 # This is really slow on large repos, so compute the set lazily.
1344 1344 class lazywantedset(object):
1345 1345 def __init__(self):
1346 1346 self.set = set()
1347 1347 self.revs = set(revs)
1348 1348
1349 1349 # No need to worry about locality here because it will be accessed
1350 1350 # in the same order as the increasing window below.
1351 1351 def __contains__(self, value):
1352 1352 if value in self.set:
1353 1353 return True
1354 1354 elif not value in self.revs:
1355 1355 return False
1356 1356 else:
1357 1357 self.revs.discard(value)
1358 1358 ctx = change(value)
1359 1359 matches = filter(match, ctx.files())
1360 1360 if matches:
1361 1361 fncache[value] = matches
1362 1362 self.set.add(value)
1363 1363 return True
1364 1364 return False
1365 1365
1366 1366 def discard(self, value):
1367 1367 self.revs.discard(value)
1368 1368 self.set.discard(value)
1369 1369
1370 1370 wanted = lazywantedset()
1371 1371
1372 1372 class followfilter(object):
1373 1373 def __init__(self, onlyfirst=False):
1374 1374 self.startrev = nullrev
1375 1375 self.roots = set()
1376 1376 self.onlyfirst = onlyfirst
1377 1377
1378 1378 def match(self, rev):
1379 1379 def realparents(rev):
1380 1380 if self.onlyfirst:
1381 1381 return repo.changelog.parentrevs(rev)[0:1]
1382 1382 else:
1383 1383 return filter(lambda x: x != nullrev,
1384 1384 repo.changelog.parentrevs(rev))
1385 1385
1386 1386 if self.startrev == nullrev:
1387 1387 self.startrev = rev
1388 1388 return True
1389 1389
1390 1390 if rev > self.startrev:
1391 1391 # forward: all descendants
1392 1392 if not self.roots:
1393 1393 self.roots.add(self.startrev)
1394 1394 for parent in realparents(rev):
1395 1395 if parent in self.roots:
1396 1396 self.roots.add(rev)
1397 1397 return True
1398 1398 else:
1399 1399 # backwards: all parents
1400 1400 if not self.roots:
1401 1401 self.roots.update(realparents(self.startrev))
1402 1402 if rev in self.roots:
1403 1403 self.roots.remove(rev)
1404 1404 self.roots.update(realparents(rev))
1405 1405 return True
1406 1406
1407 1407 return False
1408 1408
1409 1409 # it might be worthwhile to do this in the iterator if the rev range
1410 1410 # is descending and the prune args are all within that range
1411 1411 for rev in opts.get('prune', ()):
1412 1412 rev = repo[rev].rev()
1413 1413 ff = followfilter()
1414 1414 stop = min(revs[0], revs[-1])
1415 1415 for x in xrange(rev, stop - 1, -1):
1416 1416 if ff.match(x):
1417 1417 wanted = wanted - [x]
1418 1418
1419 1419 # Now that wanted is correctly initialized, we can iterate over the
1420 1420 # revision range, yielding only revisions in wanted.
1421 1421 def iterate():
1422 1422 if follow and not match.files():
1423 1423 ff = followfilter(onlyfirst=opts.get('follow_first'))
1424 1424 def want(rev):
1425 1425 return ff.match(rev) and rev in wanted
1426 1426 else:
1427 1427 def want(rev):
1428 1428 return rev in wanted
1429 1429
1430 1430 it = iter(revs)
1431 1431 stopiteration = False
1432 1432 for windowsize in increasingwindows():
1433 1433 nrevs = []
1434 1434 for i in xrange(windowsize):
1435 1435 try:
1436 1436 rev = it.next()
1437 1437 if want(rev):
1438 1438 nrevs.append(rev)
1439 1439 except (StopIteration):
1440 1440 stopiteration = True
1441 1441 break
1442 1442 for rev in sorted(nrevs):
1443 1443 fns = fncache.get(rev)
1444 1444 ctx = change(rev)
1445 1445 if not fns:
1446 1446 def fns_generator():
1447 1447 for f in ctx.files():
1448 1448 if match(f):
1449 1449 yield f
1450 1450 fns = fns_generator()
1451 1451 prepare(ctx, fns)
1452 1452 for rev in nrevs:
1453 1453 yield change(rev)
1454 1454
1455 1455 if stopiteration:
1456 1456 break
1457 1457
1458 1458 return iterate()
1459 1459
1460 1460 def _makegraphfilematcher(repo, pats, followfirst):
1461 1461 # When displaying a revision with --patch --follow FILE, we have
1462 1462 # to know which file of the revision must be diffed. With
1463 1463 # --follow, we want the names of the ancestors of FILE in the
1464 1464 # revision, stored in "fcache". "fcache" is populated by
1465 1465 # reproducing the graph traversal already done by --follow revset
1466 1466 # and relating linkrevs to file names (which is not "correct" but
1467 1467 # good enough).
1468 1468 fcache = {}
1469 1469 fcacheready = [False]
1470 1470 pctx = repo['.']
1471 1471 wctx = repo[None]
1472 1472
1473 1473 def populate():
1474 1474 for fn in pats:
1475 1475 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1476 1476 for c in i:
1477 1477 fcache.setdefault(c.linkrev(), set()).add(c.path())
1478 1478
1479 1479 def filematcher(rev):
1480 1480 if not fcacheready[0]:
1481 1481 # Lazy initialization
1482 1482 fcacheready[0] = True
1483 1483 populate()
1484 1484 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1485 1485
1486 1486 return filematcher
1487 1487
1488 1488 def _makegraphlogrevset(repo, pats, opts, revs):
1489 1489 """Return (expr, filematcher) where expr is a revset string built
1490 1490 from log options and file patterns or None. If --stat or --patch
1491 1491 are not passed filematcher is None. Otherwise it is a callable
1492 1492 taking a revision number and returning a match objects filtering
1493 1493 the files to be detailed when displaying the revision.
1494 1494 """
1495 1495 opt2revset = {
1496 1496 'no_merges': ('not merge()', None),
1497 1497 'only_merges': ('merge()', None),
1498 1498 '_ancestors': ('ancestors(%(val)s)', None),
1499 1499 '_fancestors': ('_firstancestors(%(val)s)', None),
1500 1500 '_descendants': ('descendants(%(val)s)', None),
1501 1501 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1502 1502 '_matchfiles': ('_matchfiles(%(val)s)', None),
1503 1503 'date': ('date(%(val)r)', None),
1504 1504 'branch': ('branch(%(val)r)', ' or '),
1505 1505 '_patslog': ('filelog(%(val)r)', ' or '),
1506 1506 '_patsfollow': ('follow(%(val)r)', ' or '),
1507 1507 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1508 1508 'keyword': ('keyword(%(val)r)', ' or '),
1509 1509 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1510 1510 'user': ('user(%(val)r)', ' or '),
1511 1511 }
1512 1512
1513 1513 opts = dict(opts)
1514 1514 # follow or not follow?
1515 1515 follow = opts.get('follow') or opts.get('follow_first')
1516 1516 followfirst = opts.get('follow_first') and 1 or 0
1517 1517 # --follow with FILE behaviour depends on revs...
1518 1518 startrev = revs[0]
1519 1519 followdescendants = (len(revs) > 1 and revs[0] < revs[1]) and 1 or 0
1520 1520
1521 1521 # branch and only_branch are really aliases and must be handled at
1522 1522 # the same time
1523 1523 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1524 1524 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1525 1525 # pats/include/exclude are passed to match.match() directly in
1526 1526 # _matchfiles() revset but walkchangerevs() builds its matcher with
1527 1527 # scmutil.match(). The difference is input pats are globbed on
1528 1528 # platforms without shell expansion (windows).
1529 1529 pctx = repo[None]
1530 1530 match, pats = scmutil.matchandpats(pctx, pats, opts)
1531 1531 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1532 1532 if not slowpath:
1533 1533 for f in match.files():
1534 1534 if follow and f not in pctx:
1535 1535 raise util.Abort(_('cannot follow file not in parent '
1536 1536 'revision: "%s"') % f)
1537 1537 filelog = repo.file(f)
1538 1538 if not filelog:
1539 1539 # A zero count may be a directory or deleted file, so
1540 1540 # try to find matching entries on the slow path.
1541 1541 if follow:
1542 1542 raise util.Abort(
1543 1543 _('cannot follow nonexistent file: "%s"') % f)
1544 1544 slowpath = True
1545 1545
1546 1546 # We decided to fall back to the slowpath because at least one
1547 1547 # of the paths was not a file. Check to see if at least one of them
1548 1548 # existed in history - in that case, we'll continue down the
1549 1549 # slowpath; otherwise, we can turn off the slowpath
1550 1550 if slowpath:
1551 1551 for path in match.files():
1552 1552 if path == '.' or path in repo.store:
1553 1553 break
1554 1554 else:
1555 1555 slowpath = False
1556 1556
1557 1557 if slowpath:
1558 1558 # See walkchangerevs() slow path.
1559 1559 #
1560 1560 if follow:
1561 1561 raise util.Abort(_('can only follow copies/renames for explicit '
1562 1562 'filenames'))
1563 1563 # pats/include/exclude cannot be represented as separate
1564 1564 # revset expressions as their filtering logic applies at file
1565 1565 # level. For instance "-I a -X a" matches a revision touching
1566 1566 # "a" and "b" while "file(a) and not file(b)" does
1567 1567 # not. Besides, filesets are evaluated against the working
1568 1568 # directory.
1569 1569 matchargs = ['r:', 'd:relpath']
1570 1570 for p in pats:
1571 1571 matchargs.append('p:' + p)
1572 1572 for p in opts.get('include', []):
1573 1573 matchargs.append('i:' + p)
1574 1574 for p in opts.get('exclude', []):
1575 1575 matchargs.append('x:' + p)
1576 1576 matchargs = ','.join(('%r' % p) for p in matchargs)
1577 1577 opts['_matchfiles'] = matchargs
1578 1578 else:
1579 1579 if follow:
1580 1580 fpats = ('_patsfollow', '_patsfollowfirst')
1581 1581 fnopats = (('_ancestors', '_fancestors'),
1582 1582 ('_descendants', '_fdescendants'))
1583 1583 if pats:
1584 1584 # follow() revset interprets its file argument as a
1585 1585 # manifest entry, so use match.files(), not pats.
1586 1586 opts[fpats[followfirst]] = list(match.files())
1587 1587 else:
1588 1588 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1589 1589 else:
1590 1590 opts['_patslog'] = list(pats)
1591 1591
1592 1592 filematcher = None
1593 1593 if opts.get('patch') or opts.get('stat'):
1594 1594 if follow:
1595 1595 filematcher = _makegraphfilematcher(repo, pats, followfirst)
1596 1596 else:
1597 1597 filematcher = lambda rev: match
1598 1598
1599 1599 expr = []
1600 1600 for op, val in opts.iteritems():
1601 1601 if not val:
1602 1602 continue
1603 1603 if op not in opt2revset:
1604 1604 continue
1605 1605 revop, andor = opt2revset[op]
1606 1606 if '%(val)' not in revop:
1607 1607 expr.append(revop)
1608 1608 else:
1609 1609 if not isinstance(val, list):
1610 1610 e = revop % {'val': val}
1611 1611 else:
1612 1612 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1613 1613 expr.append(e)
1614 1614
1615 1615 if expr:
1616 1616 expr = '(' + ' and '.join(expr) + ')'
1617 1617 else:
1618 1618 expr = None
1619 1619 return expr, filematcher
1620 1620
1621 1621 def getgraphlogrevs(repo, pats, opts):
1622 1622 """Return (revs, expr, filematcher) where revs is an iterable of
1623 1623 revision numbers, expr is a revset string built from log options
1624 1624 and file patterns or None, and used to filter 'revs'. If --stat or
1625 1625 --patch are not passed filematcher is None. Otherwise it is a
1626 1626 callable taking a revision number and returning a match objects
1627 1627 filtering the files to be detailed when displaying the revision.
1628 1628 """
1629 1629 if not len(repo):
1630 1630 return [], None, None
1631 1631 limit = loglimit(opts)
1632 1632 # Default --rev value depends on --follow but --follow behaviour
1633 1633 # depends on revisions resolved from --rev...
1634 1634 follow = opts.get('follow') or opts.get('follow_first')
1635 1635 possiblyunsorted = False # whether revs might need sorting
1636 1636 if opts.get('rev'):
1637 1637 revs = scmutil.revrange(repo, opts['rev'])
1638 1638 # Don't sort here because _makegraphlogrevset might depend on the
1639 1639 # order of revs
1640 1640 possiblyunsorted = True
1641 1641 else:
1642 1642 if follow and len(repo) > 0:
1643 1643 revs = repo.revs('reverse(:.)')
1644 1644 else:
1645 1645 revs = revset.baseset(repo.changelog)
1646 1646 revs.reverse()
1647 1647 if not revs:
1648 1648 return [], None, None
1649 1649 revs = revset.baseset(revs)
1650 1650 expr, filematcher = _makegraphlogrevset(repo, pats, opts, revs)
1651 1651 if possiblyunsorted:
1652 1652 revs.sort(reverse=True)
1653 1653 if expr:
1654 1654 # Revset matchers often operate faster on revisions in changelog
1655 1655 # order, because most filters deal with the changelog.
1656 1656 revs.reverse()
1657 1657 matcher = revset.match(repo.ui, expr)
1658 1658 # Revset matches can reorder revisions. "A or B" typically returns
1659 1659 # returns the revision matching A then the revision matching B. Sort
1660 1660 # again to fix that.
1661 1661 revs = matcher(repo, revs)
1662 1662 revs.sort(reverse=True)
1663 1663 if limit is not None:
1664 1664 revs = revs[:limit]
1665 1665
1666 1666 return revs, expr, filematcher
1667 1667
1668 1668 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1669 1669 filematcher=None):
1670 1670 seen, state = [], graphmod.asciistate()
1671 1671 for rev, type, ctx, parents in dag:
1672 1672 char = 'o'
1673 1673 if ctx.node() in showparents:
1674 1674 char = '@'
1675 1675 elif ctx.obsolete():
1676 1676 char = 'x'
1677 1677 copies = None
1678 1678 if getrenamed and ctx.rev():
1679 1679 copies = []
1680 1680 for fn in ctx.files():
1681 1681 rename = getrenamed(fn, ctx.rev())
1682 1682 if rename:
1683 1683 copies.append((fn, rename[0]))
1684 1684 revmatchfn = None
1685 1685 if filematcher is not None:
1686 1686 revmatchfn = filematcher(ctx.rev())
1687 1687 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1688 1688 lines = displayer.hunk.pop(rev).split('\n')
1689 1689 if not lines[-1]:
1690 1690 del lines[-1]
1691 1691 displayer.flush(rev)
1692 1692 edges = edgefn(type, char, lines, seen, rev, parents)
1693 1693 for type, char, lines, coldata in edges:
1694 1694 graphmod.ascii(ui, state, type, char, lines, coldata)
1695 1695 displayer.close()
1696 1696
1697 1697 def graphlog(ui, repo, *pats, **opts):
1698 1698 # Parameters are identical to log command ones
1699 1699 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1700 1700 revdag = graphmod.dagwalker(repo, revs)
1701 1701
1702 1702 getrenamed = None
1703 1703 if opts.get('copies'):
1704 1704 endrev = None
1705 1705 if opts.get('rev'):
1706 1706 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
1707 1707 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1708 1708 displayer = show_changeset(ui, repo, opts, buffered=True)
1709 1709 showparents = [ctx.node() for ctx in repo[None].parents()]
1710 1710 displaygraph(ui, revdag, displayer, showparents,
1711 1711 graphmod.asciiedges, getrenamed, filematcher)
1712 1712
1713 1713 def checkunsupportedgraphflags(pats, opts):
1714 1714 for op in ["newest_first"]:
1715 1715 if op in opts and opts[op]:
1716 1716 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1717 1717 % op.replace("_", "-"))
1718 1718
1719 1719 def graphrevs(repo, nodes, opts):
1720 1720 limit = loglimit(opts)
1721 1721 nodes.reverse()
1722 1722 if limit is not None:
1723 1723 nodes = nodes[:limit]
1724 1724 return graphmod.nodes(repo, nodes)
1725 1725
1726 1726 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1727 1727 join = lambda f: os.path.join(prefix, f)
1728 1728 bad = []
1729 1729 oldbad = match.bad
1730 1730 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1731 1731 names = []
1732 1732 wctx = repo[None]
1733 1733 cca = None
1734 1734 abort, warn = scmutil.checkportabilityalert(ui)
1735 1735 if abort or warn:
1736 1736 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1737 1737 for f in repo.walk(match):
1738 1738 exact = match.exact(f)
1739 1739 if exact or not explicitonly and f not in repo.dirstate:
1740 1740 if cca:
1741 1741 cca(f)
1742 1742 names.append(f)
1743 1743 if ui.verbose or not exact:
1744 1744 ui.status(_('adding %s\n') % match.rel(join(f)))
1745 1745
1746 1746 for subpath in sorted(wctx.substate):
1747 1747 sub = wctx.sub(subpath)
1748 1748 try:
1749 1749 submatch = matchmod.narrowmatcher(subpath, match)
1750 1750 if listsubrepos:
1751 1751 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1752 1752 False))
1753 1753 else:
1754 1754 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1755 1755 True))
1756 1756 except error.LookupError:
1757 1757 ui.status(_("skipping missing subrepository: %s\n")
1758 1758 % join(subpath))
1759 1759
1760 1760 if not dryrun:
1761 1761 rejected = wctx.add(names, prefix)
1762 1762 bad.extend(f for f in rejected if f in match.files())
1763 1763 return bad
1764 1764
1765 1765 def forget(ui, repo, match, prefix, explicitonly):
1766 1766 join = lambda f: os.path.join(prefix, f)
1767 1767 bad = []
1768 1768 oldbad = match.bad
1769 1769 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1770 1770 wctx = repo[None]
1771 1771 forgot = []
1772 1772 s = repo.status(match=match, clean=True)
1773 1773 forget = sorted(s[0] + s[1] + s[3] + s[6])
1774 1774 if explicitonly:
1775 1775 forget = [f for f in forget if match.exact(f)]
1776 1776
1777 1777 for subpath in sorted(wctx.substate):
1778 1778 sub = wctx.sub(subpath)
1779 1779 try:
1780 1780 submatch = matchmod.narrowmatcher(subpath, match)
1781 1781 subbad, subforgot = sub.forget(ui, submatch, prefix)
1782 1782 bad.extend([subpath + '/' + f for f in subbad])
1783 1783 forgot.extend([subpath + '/' + f for f in subforgot])
1784 1784 except error.LookupError:
1785 1785 ui.status(_("skipping missing subrepository: %s\n")
1786 1786 % join(subpath))
1787 1787
1788 1788 if not explicitonly:
1789 1789 for f in match.files():
1790 1790 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1791 1791 if f not in forgot:
1792 1792 if os.path.exists(match.rel(join(f))):
1793 1793 ui.warn(_('not removing %s: '
1794 1794 'file is already untracked\n')
1795 1795 % match.rel(join(f)))
1796 1796 bad.append(f)
1797 1797
1798 1798 for f in forget:
1799 1799 if ui.verbose or not match.exact(f):
1800 1800 ui.status(_('removing %s\n') % match.rel(join(f)))
1801 1801
1802 1802 rejected = wctx.forget(forget, prefix)
1803 1803 bad.extend(f for f in rejected if f in match.files())
1804 1804 forgot.extend(forget)
1805 1805 return bad, forgot
1806 1806
1807 1807 def duplicatecopies(repo, rev, fromrev):
1808 1808 '''reproduce copies from fromrev to rev in the dirstate'''
1809 1809 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1810 1810 # copies.pathcopies returns backward renames, so dst might not
1811 1811 # actually be in the dirstate
1812 1812 if repo.dirstate[dst] in "nma":
1813 1813 repo.dirstate.copy(src, dst)
1814 1814
1815 1815 def commit(ui, repo, commitfunc, pats, opts):
1816 1816 '''commit the specified files or all outstanding changes'''
1817 1817 date = opts.get('date')
1818 1818 if date:
1819 1819 opts['date'] = util.parsedate(date)
1820 1820 message = logmessage(ui, opts)
1821 1821
1822 1822 # extract addremove carefully -- this function can be called from a command
1823 1823 # that doesn't support addremove
1824 1824 if opts.get('addremove'):
1825 1825 scmutil.addremove(repo, pats, opts)
1826 1826
1827 1827 return commitfunc(ui, repo, message,
1828 1828 scmutil.match(repo[None], pats, opts), opts)
1829 1829
1830 1830 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1831 1831 ui.note(_('amending changeset %s\n') % old)
1832 1832 base = old.p1()
1833 1833
1834 1834 wlock = lock = newid = None
1835 1835 try:
1836 1836 wlock = repo.wlock()
1837 1837 lock = repo.lock()
1838 1838 tr = repo.transaction('amend')
1839 1839 try:
1840 1840 # See if we got a message from -m or -l, if not, open the editor
1841 1841 # with the message of the changeset to amend
1842 1842 message = logmessage(ui, opts)
1843 1843 # ensure logfile does not conflict with later enforcement of the
1844 1844 # message. potential logfile content has been processed by
1845 1845 # `logmessage` anyway.
1846 1846 opts.pop('logfile')
1847 1847 # First, do a regular commit to record all changes in the working
1848 1848 # directory (if there are any)
1849 1849 ui.callhooks = False
1850 1850 currentbookmark = repo._bookmarkcurrent
1851 1851 try:
1852 1852 repo._bookmarkcurrent = None
1853 1853 opts['message'] = 'temporary amend commit for %s' % old
1854 1854 node = commit(ui, repo, commitfunc, pats, opts)
1855 1855 finally:
1856 1856 repo._bookmarkcurrent = currentbookmark
1857 1857 ui.callhooks = True
1858 1858 ctx = repo[node]
1859 1859
1860 1860 # Participating changesets:
1861 1861 #
1862 1862 # node/ctx o - new (intermediate) commit that contains changes
1863 1863 # | from working dir to go into amending commit
1864 1864 # | (or a workingctx if there were no changes)
1865 1865 # |
1866 1866 # old o - changeset to amend
1867 1867 # |
1868 1868 # base o - parent of amending changeset
1869 1869
1870 1870 # Update extra dict from amended commit (e.g. to preserve graft
1871 1871 # source)
1872 1872 extra.update(old.extra())
1873 1873
1874 1874 # Also update it from the intermediate commit or from the wctx
1875 1875 extra.update(ctx.extra())
1876 1876
1877 1877 if len(old.parents()) > 1:
1878 1878 # ctx.files() isn't reliable for merges, so fall back to the
1879 1879 # slower repo.status() method
1880 1880 files = set([fn for st in repo.status(base, old)[:3]
1881 1881 for fn in st])
1882 1882 else:
1883 1883 files = set(old.files())
1884 1884
1885 1885 # Second, we use either the commit we just did, or if there were no
1886 1886 # changes the parent of the working directory as the version of the
1887 1887 # files in the final amend commit
1888 1888 if node:
1889 1889 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1890 1890
1891 1891 user = ctx.user()
1892 1892 date = ctx.date()
1893 1893 # Recompute copies (avoid recording a -> b -> a)
1894 1894 copied = copies.pathcopies(base, ctx)
1895 1895
1896 1896 # Prune files which were reverted by the updates: if old
1897 1897 # introduced file X and our intermediate commit, node,
1898 1898 # renamed that file, then those two files are the same and
1899 1899 # we can discard X from our list of files. Likewise if X
1900 1900 # was deleted, it's no longer relevant
1901 1901 files.update(ctx.files())
1902 1902
1903 1903 def samefile(f):
1904 1904 if f in ctx.manifest():
1905 1905 a = ctx.filectx(f)
1906 1906 if f in base.manifest():
1907 1907 b = base.filectx(f)
1908 1908 return (not a.cmp(b)
1909 1909 and a.flags() == b.flags())
1910 1910 else:
1911 1911 return False
1912 1912 else:
1913 1913 return f not in base.manifest()
1914 1914 files = [f for f in files if not samefile(f)]
1915 1915
1916 1916 def filectxfn(repo, ctx_, path):
1917 1917 try:
1918 1918 fctx = ctx[path]
1919 1919 flags = fctx.flags()
1920 1920 mctx = context.memfilectx(fctx.path(), fctx.data(),
1921 1921 islink='l' in flags,
1922 1922 isexec='x' in flags,
1923 1923 copied=copied.get(path))
1924 1924 return mctx
1925 1925 except KeyError:
1926 1926 raise IOError
1927 1927 else:
1928 1928 ui.note(_('copying changeset %s to %s\n') % (old, base))
1929 1929
1930 1930 # Use version of files as in the old cset
1931 1931 def filectxfn(repo, ctx_, path):
1932 1932 try:
1933 1933 return old.filectx(path)
1934 1934 except KeyError:
1935 1935 raise IOError
1936 1936
1937 1937 user = opts.get('user') or old.user()
1938 1938 date = opts.get('date') or old.date()
1939 1939 editmsg = False
1940 1940 if not message:
1941 1941 editmsg = True
1942 1942 message = old.description()
1943 1943
1944 1944 pureextra = extra.copy()
1945 1945 extra['amend_source'] = old.hex()
1946 1946
1947 1947 new = context.memctx(repo,
1948 1948 parents=[base.node(), old.p2().node()],
1949 1949 text=message,
1950 1950 files=files,
1951 1951 filectxfn=filectxfn,
1952 1952 user=user,
1953 1953 date=date,
1954 1954 extra=extra)
1955 1955 if editmsg:
1956 1956 new._text = commitforceeditor(repo, new, [])
1957 1957
1958 1958 newdesc = changelog.stripdesc(new.description())
1959 1959 if ((not node)
1960 1960 and newdesc == old.description()
1961 1961 and user == old.user()
1962 1962 and date == old.date()
1963 1963 and pureextra == old.extra()):
1964 1964 # nothing changed. continuing here would create a new node
1965 1965 # anyway because of the amend_source noise.
1966 1966 #
1967 1967 # This not what we expect from amend.
1968 1968 return old.node()
1969 1969
1970 1970 ph = repo.ui.config('phases', 'new-commit', phases.draft)
1971 1971 try:
1972 repo.ui.setconfig('phases', 'new-commit', old.phase())
1972 if opts.get('secret'):
1973 commitphase = 'secret'
1974 else:
1975 commitphase = old.phase()
1976 repo.ui.setconfig('phases', 'new-commit', commitphase)
1973 1977 newid = repo.commitctx(new)
1974 1978 finally:
1975 1979 repo.ui.setconfig('phases', 'new-commit', ph)
1976 1980 if newid != old.node():
1977 1981 # Reroute the working copy parent to the new changeset
1978 1982 repo.setparents(newid, nullid)
1979 1983
1980 1984 # Move bookmarks from old parent to amend commit
1981 1985 bms = repo.nodebookmarks(old.node())
1982 1986 if bms:
1983 1987 marks = repo._bookmarks
1984 1988 for bm in bms:
1985 1989 marks[bm] = newid
1986 1990 marks.write()
1987 1991 #commit the whole amend process
1988 1992 if obsolete._enabled and newid != old.node():
1989 1993 # mark the new changeset as successor of the rewritten one
1990 1994 new = repo[newid]
1991 1995 obs = [(old, (new,))]
1992 1996 if node:
1993 1997 obs.append((ctx, ()))
1994 1998
1995 1999 obsolete.createmarkers(repo, obs)
1996 2000 tr.close()
1997 2001 finally:
1998 2002 tr.release()
1999 2003 if (not obsolete._enabled) and newid != old.node():
2000 2004 # Strip the intermediate commit (if there was one) and the amended
2001 2005 # commit
2002 2006 if node:
2003 2007 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2004 2008 ui.note(_('stripping amended changeset %s\n') % old)
2005 2009 repair.strip(ui, repo, old.node(), topic='amend-backup')
2006 2010 finally:
2007 2011 if newid is None:
2008 2012 repo.dirstate.invalidate()
2009 2013 lockmod.release(lock, wlock)
2010 2014 return newid
2011 2015
2012 2016 def commiteditor(repo, ctx, subs):
2013 2017 if ctx.description():
2014 2018 return ctx.description()
2015 2019 return commitforceeditor(repo, ctx, subs)
2016 2020
2017 2021 def commitforceeditor(repo, ctx, subs):
2018 2022 edittext = []
2019 2023 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2020 2024 if ctx.description():
2021 2025 edittext.append(ctx.description())
2022 2026 edittext.append("")
2023 2027 edittext.append("") # Empty line between message and comments.
2024 2028 edittext.append(_("HG: Enter commit message."
2025 2029 " Lines beginning with 'HG:' are removed."))
2026 2030 edittext.append(_("HG: Leave message empty to abort commit."))
2027 2031 edittext.append("HG: --")
2028 2032 edittext.append(_("HG: user: %s") % ctx.user())
2029 2033 if ctx.p2():
2030 2034 edittext.append(_("HG: branch merge"))
2031 2035 if ctx.branch():
2032 2036 edittext.append(_("HG: branch '%s'") % ctx.branch())
2033 2037 if bookmarks.iscurrent(repo):
2034 2038 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2035 2039 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2036 2040 edittext.extend([_("HG: added %s") % f for f in added])
2037 2041 edittext.extend([_("HG: changed %s") % f for f in modified])
2038 2042 edittext.extend([_("HG: removed %s") % f for f in removed])
2039 2043 if not added and not modified and not removed:
2040 2044 edittext.append(_("HG: no files changed"))
2041 2045 edittext.append("")
2042 2046 # run editor in the repository root
2043 2047 olddir = os.getcwd()
2044 2048 os.chdir(repo.root)
2045 2049 text = repo.ui.edit("\n".join(edittext), ctx.user(), ctx.extra())
2046 2050 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2047 2051 os.chdir(olddir)
2048 2052
2049 2053 if not text.strip():
2050 2054 raise util.Abort(_("empty commit message"))
2051 2055
2052 2056 return text
2053 2057
2054 2058 def commitstatus(repo, node, branch, bheads=None, opts={}):
2055 2059 ctx = repo[node]
2056 2060 parents = ctx.parents()
2057 2061
2058 2062 if (not opts.get('amend') and bheads and node not in bheads and not
2059 2063 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2060 2064 repo.ui.status(_('created new head\n'))
2061 2065 # The message is not printed for initial roots. For the other
2062 2066 # changesets, it is printed in the following situations:
2063 2067 #
2064 2068 # Par column: for the 2 parents with ...
2065 2069 # N: null or no parent
2066 2070 # B: parent is on another named branch
2067 2071 # C: parent is a regular non head changeset
2068 2072 # H: parent was a branch head of the current branch
2069 2073 # Msg column: whether we print "created new head" message
2070 2074 # In the following, it is assumed that there already exists some
2071 2075 # initial branch heads of the current branch, otherwise nothing is
2072 2076 # printed anyway.
2073 2077 #
2074 2078 # Par Msg Comment
2075 2079 # N N y additional topo root
2076 2080 #
2077 2081 # B N y additional branch root
2078 2082 # C N y additional topo head
2079 2083 # H N n usual case
2080 2084 #
2081 2085 # B B y weird additional branch root
2082 2086 # C B y branch merge
2083 2087 # H B n merge with named branch
2084 2088 #
2085 2089 # C C y additional head from merge
2086 2090 # C H n merge with a head
2087 2091 #
2088 2092 # H H n head merge: head count decreases
2089 2093
2090 2094 if not opts.get('close_branch'):
2091 2095 for r in parents:
2092 2096 if r.closesbranch() and r.branch() == branch:
2093 2097 repo.ui.status(_('reopening closed branch head %d\n') % r)
2094 2098
2095 2099 if repo.ui.debugflag:
2096 2100 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2097 2101 elif repo.ui.verbose:
2098 2102 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2099 2103
2100 2104 def revert(ui, repo, ctx, parents, *pats, **opts):
2101 2105 parent, p2 = parents
2102 2106 node = ctx.node()
2103 2107
2104 2108 mf = ctx.manifest()
2105 2109 if node == parent:
2106 2110 pmf = mf
2107 2111 else:
2108 2112 pmf = None
2109 2113
2110 2114 # need all matching names in dirstate and manifest of target rev,
2111 2115 # so have to walk both. do not print errors if files exist in one
2112 2116 # but not other.
2113 2117
2114 2118 names = {}
2115 2119
2116 2120 wlock = repo.wlock()
2117 2121 try:
2118 2122 # walk dirstate.
2119 2123
2120 2124 m = scmutil.match(repo[None], pats, opts)
2121 2125 m.bad = lambda x, y: False
2122 2126 for abs in repo.walk(m):
2123 2127 names[abs] = m.rel(abs), m.exact(abs)
2124 2128
2125 2129 # walk target manifest.
2126 2130
2127 2131 def badfn(path, msg):
2128 2132 if path in names:
2129 2133 return
2130 2134 if path in ctx.substate:
2131 2135 return
2132 2136 path_ = path + '/'
2133 2137 for f in names:
2134 2138 if f.startswith(path_):
2135 2139 return
2136 2140 ui.warn("%s: %s\n" % (m.rel(path), msg))
2137 2141
2138 2142 m = scmutil.match(ctx, pats, opts)
2139 2143 m.bad = badfn
2140 2144 for abs in ctx.walk(m):
2141 2145 if abs not in names:
2142 2146 names[abs] = m.rel(abs), m.exact(abs)
2143 2147
2144 2148 # get the list of subrepos that must be reverted
2145 2149 targetsubs = sorted(s for s in ctx.substate if m(s))
2146 2150 m = scmutil.matchfiles(repo, names)
2147 2151 changes = repo.status(match=m)[:4]
2148 2152 modified, added, removed, deleted = map(set, changes)
2149 2153
2150 2154 # if f is a rename, also revert the source
2151 2155 cwd = repo.getcwd()
2152 2156 for f in added:
2153 2157 src = repo.dirstate.copied(f)
2154 2158 if src and src not in names and repo.dirstate[src] == 'r':
2155 2159 removed.add(src)
2156 2160 names[src] = (repo.pathto(src, cwd), True)
2157 2161
2158 2162 def removeforget(abs):
2159 2163 if repo.dirstate[abs] == 'a':
2160 2164 return _('forgetting %s\n')
2161 2165 return _('removing %s\n')
2162 2166
2163 2167 revert = ([], _('reverting %s\n'))
2164 2168 add = ([], _('adding %s\n'))
2165 2169 remove = ([], removeforget)
2166 2170 undelete = ([], _('undeleting %s\n'))
2167 2171
2168 2172 disptable = (
2169 2173 # dispatch table:
2170 2174 # file state
2171 2175 # action if in target manifest
2172 2176 # action if not in target manifest
2173 2177 # make backup if in target manifest
2174 2178 # make backup if not in target manifest
2175 2179 (modified, revert, remove, True, True),
2176 2180 (added, revert, remove, True, False),
2177 2181 (removed, undelete, None, True, False),
2178 2182 (deleted, revert, remove, False, False),
2179 2183 )
2180 2184
2181 2185 for abs, (rel, exact) in sorted(names.items()):
2182 2186 mfentry = mf.get(abs)
2183 2187 target = repo.wjoin(abs)
2184 2188 def handle(xlist, dobackup):
2185 2189 xlist[0].append(abs)
2186 2190 if (dobackup and not opts.get('no_backup') and
2187 2191 os.path.lexists(target) and
2188 2192 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2189 2193 bakname = "%s.orig" % rel
2190 2194 ui.note(_('saving current version of %s as %s\n') %
2191 2195 (rel, bakname))
2192 2196 if not opts.get('dry_run'):
2193 2197 util.rename(target, bakname)
2194 2198 if ui.verbose or not exact:
2195 2199 msg = xlist[1]
2196 2200 if not isinstance(msg, basestring):
2197 2201 msg = msg(abs)
2198 2202 ui.status(msg % rel)
2199 2203 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2200 2204 if abs not in table:
2201 2205 continue
2202 2206 # file has changed in dirstate
2203 2207 if mfentry:
2204 2208 handle(hitlist, backuphit)
2205 2209 elif misslist is not None:
2206 2210 handle(misslist, backupmiss)
2207 2211 break
2208 2212 else:
2209 2213 if abs not in repo.dirstate:
2210 2214 if mfentry:
2211 2215 handle(add, True)
2212 2216 elif exact:
2213 2217 ui.warn(_('file not managed: %s\n') % rel)
2214 2218 continue
2215 2219 # file has not changed in dirstate
2216 2220 if node == parent:
2217 2221 if exact:
2218 2222 ui.warn(_('no changes needed to %s\n') % rel)
2219 2223 continue
2220 2224 if pmf is None:
2221 2225 # only need parent manifest in this unlikely case,
2222 2226 # so do not read by default
2223 2227 pmf = repo[parent].manifest()
2224 2228 if abs in pmf and mfentry:
2225 2229 # if version of file is same in parent and target
2226 2230 # manifests, do nothing
2227 2231 if (pmf[abs] != mfentry or
2228 2232 pmf.flags(abs) != mf.flags(abs)):
2229 2233 handle(revert, False)
2230 2234 else:
2231 2235 handle(remove, False)
2232 2236 if not opts.get('dry_run'):
2233 2237 _performrevert(repo, parents, ctx, revert, add, remove, undelete)
2234 2238
2235 2239 if targetsubs:
2236 2240 # Revert the subrepos on the revert list
2237 2241 for sub in targetsubs:
2238 2242 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2239 2243 finally:
2240 2244 wlock.release()
2241 2245
2242 2246 def _performrevert(repo, parents, ctx, revert, add, remove, undelete):
2243 2247 """function that actually perform all the action computed for revert
2244 2248
2245 2249 This is an independent function to let extension to plug in and react to
2246 2250 the imminent revert.
2247 2251
2248 2252 Make sure you have the working directory locked when caling this function.
2249 2253 """
2250 2254 parent, p2 = parents
2251 2255 node = ctx.node()
2252 2256 def checkout(f):
2253 2257 fc = ctx[f]
2254 2258 repo.wwrite(f, fc.data(), fc.flags())
2255 2259
2256 2260 audit_path = pathutil.pathauditor(repo.root)
2257 2261 for f in remove[0]:
2258 2262 if repo.dirstate[f] == 'a':
2259 2263 repo.dirstate.drop(f)
2260 2264 continue
2261 2265 audit_path(f)
2262 2266 try:
2263 2267 util.unlinkpath(repo.wjoin(f))
2264 2268 except OSError:
2265 2269 pass
2266 2270 repo.dirstate.remove(f)
2267 2271
2268 2272 normal = None
2269 2273 if node == parent:
2270 2274 # We're reverting to our parent. If possible, we'd like status
2271 2275 # to report the file as clean. We have to use normallookup for
2272 2276 # merges to avoid losing information about merged/dirty files.
2273 2277 if p2 != nullid:
2274 2278 normal = repo.dirstate.normallookup
2275 2279 else:
2276 2280 normal = repo.dirstate.normal
2277 2281 for f in revert[0]:
2278 2282 checkout(f)
2279 2283 if normal:
2280 2284 normal(f)
2281 2285
2282 2286 for f in add[0]:
2283 2287 checkout(f)
2284 2288 repo.dirstate.add(f)
2285 2289
2286 2290 normal = repo.dirstate.normallookup
2287 2291 if node == parent and p2 == nullid:
2288 2292 normal = repo.dirstate.normal
2289 2293 for f in undelete[0]:
2290 2294 checkout(f)
2291 2295 normal(f)
2292 2296
2293 2297 copied = copies.pathcopies(repo[parent], ctx)
2294 2298
2295 2299 for f in add[0] + undelete[0] + revert[0]:
2296 2300 if f in copied:
2297 2301 repo.dirstate.copy(copied[f], f)
2298 2302
2299 2303 def command(table):
2300 2304 '''returns a function object bound to table which can be used as
2301 2305 a decorator for populating table as a command table'''
2302 2306
2303 2307 def cmd(name, options=(), synopsis=None):
2304 2308 def decorator(func):
2305 2309 if synopsis:
2306 2310 table[name] = func, list(options), synopsis
2307 2311 else:
2308 2312 table[name] = func, list(options)
2309 2313 return func
2310 2314 return decorator
2311 2315
2312 2316 return cmd
2313 2317
2314 2318 # a list of (ui, repo) functions called by commands.summary
2315 2319 summaryhooks = util.hooks()
2316 2320
2317 2321 # A list of state files kept by multistep operations like graft.
2318 2322 # Since graft cannot be aborted, it is considered 'clearable' by update.
2319 2323 # note: bisect is intentionally excluded
2320 2324 # (state file, clearable, allowcommit, error, hint)
2321 2325 unfinishedstates = [
2322 2326 ('graftstate', True, False, _('graft in progress'),
2323 2327 _("use 'hg graft --continue' or 'hg update' to abort")),
2324 2328 ('updatestate', True, False, _('last update was interrupted'),
2325 2329 _("use 'hg update' to get a consistent checkout"))
2326 2330 ]
2327 2331
2328 2332 def checkunfinished(repo, commit=False):
2329 2333 '''Look for an unfinished multistep operation, like graft, and abort
2330 2334 if found. It's probably good to check this right before
2331 2335 bailifchanged().
2332 2336 '''
2333 2337 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2334 2338 if commit and allowcommit:
2335 2339 continue
2336 2340 if repo.vfs.exists(f):
2337 2341 raise util.Abort(msg, hint=hint)
2338 2342
2339 2343 def clearunfinished(repo):
2340 2344 '''Check for unfinished operations (as above), and clear the ones
2341 2345 that are clearable.
2342 2346 '''
2343 2347 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2344 2348 if not clearable and repo.vfs.exists(f):
2345 2349 raise util.Abort(msg, hint=hint)
2346 2350 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2347 2351 if clearable and repo.vfs.exists(f):
2348 2352 util.unlink(repo.join(f))
@@ -1,5883 +1,5878 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 node import hex, bin, nullid, nullrev, short
9 9 from lock import release
10 10 from i18n import _
11 11 import os, re, difflib, time, tempfile, errno
12 12 import hg, scmutil, util, revlog, copies, error, bookmarks
13 13 import patch, help, encoding, templatekw, discovery
14 14 import archival, changegroup, cmdutil, hbisect
15 15 import sshserver, hgweb, commandserver
16 16 from hgweb import server as hgweb_server
17 17 import merge as mergemod
18 18 import minirst, revset, fileset
19 19 import dagparser, context, simplemerge, graphmod
20 20 import random
21 21 import setdiscovery, treediscovery, dagutil, pvec, localrepo
22 22 import phases, obsolete
23 23
24 24 table = {}
25 25
26 26 command = cmdutil.command(table)
27 27
28 28 # common command options
29 29
30 30 globalopts = [
31 31 ('R', 'repository', '',
32 32 _('repository root directory or name of overlay bundle file'),
33 33 _('REPO')),
34 34 ('', 'cwd', '',
35 35 _('change working directory'), _('DIR')),
36 36 ('y', 'noninteractive', None,
37 37 _('do not prompt, automatically pick the first choice for all prompts')),
38 38 ('q', 'quiet', None, _('suppress output')),
39 39 ('v', 'verbose', None, _('enable additional output')),
40 40 ('', 'config', [],
41 41 _('set/override config option (use \'section.name=value\')'),
42 42 _('CONFIG')),
43 43 ('', 'debug', None, _('enable debugging output')),
44 44 ('', 'debugger', None, _('start debugger')),
45 45 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
46 46 _('ENCODE')),
47 47 ('', 'encodingmode', encoding.encodingmode,
48 48 _('set the charset encoding mode'), _('MODE')),
49 49 ('', 'traceback', None, _('always print a traceback on exception')),
50 50 ('', 'time', None, _('time how long the command takes')),
51 51 ('', 'profile', None, _('print command execution profile')),
52 52 ('', 'version', None, _('output version information and exit')),
53 53 ('h', 'help', None, _('display help and exit')),
54 54 ('', 'hidden', False, _('consider hidden changesets')),
55 55 ]
56 56
57 57 dryrunopts = [('n', 'dry-run', None,
58 58 _('do not perform actions, just print output'))]
59 59
60 60 remoteopts = [
61 61 ('e', 'ssh', '',
62 62 _('specify ssh command to use'), _('CMD')),
63 63 ('', 'remotecmd', '',
64 64 _('specify hg command to run on the remote side'), _('CMD')),
65 65 ('', 'insecure', None,
66 66 _('do not verify server certificate (ignoring web.cacerts config)')),
67 67 ]
68 68
69 69 walkopts = [
70 70 ('I', 'include', [],
71 71 _('include names matching the given patterns'), _('PATTERN')),
72 72 ('X', 'exclude', [],
73 73 _('exclude names matching the given patterns'), _('PATTERN')),
74 74 ]
75 75
76 76 commitopts = [
77 77 ('m', 'message', '',
78 78 _('use text as commit message'), _('TEXT')),
79 79 ('l', 'logfile', '',
80 80 _('read commit message from file'), _('FILE')),
81 81 ]
82 82
83 83 commitopts2 = [
84 84 ('d', 'date', '',
85 85 _('record the specified date as commit date'), _('DATE')),
86 86 ('u', 'user', '',
87 87 _('record the specified user as committer'), _('USER')),
88 88 ]
89 89
90 90 templateopts = [
91 91 ('', 'style', '',
92 92 _('display using template map file (DEPRECATED)'), _('STYLE')),
93 93 ('T', 'template', '',
94 94 _('display with template'), _('TEMPLATE')),
95 95 ]
96 96
97 97 logopts = [
98 98 ('p', 'patch', None, _('show patch')),
99 99 ('g', 'git', None, _('use git extended diff format')),
100 100 ('l', 'limit', '',
101 101 _('limit number of changes displayed'), _('NUM')),
102 102 ('M', 'no-merges', None, _('do not show merges')),
103 103 ('', 'stat', None, _('output diffstat-style summary of changes')),
104 104 ('G', 'graph', None, _("show the revision DAG")),
105 105 ] + templateopts
106 106
107 107 diffopts = [
108 108 ('a', 'text', None, _('treat all files as text')),
109 109 ('g', 'git', None, _('use git extended diff format')),
110 110 ('', 'nodates', None, _('omit dates from diff headers'))
111 111 ]
112 112
113 113 diffwsopts = [
114 114 ('w', 'ignore-all-space', None,
115 115 _('ignore white space when comparing lines')),
116 116 ('b', 'ignore-space-change', None,
117 117 _('ignore changes in the amount of white space')),
118 118 ('B', 'ignore-blank-lines', None,
119 119 _('ignore changes whose lines are all blank')),
120 120 ]
121 121
122 122 diffopts2 = [
123 123 ('p', 'show-function', None, _('show which function each change is in')),
124 124 ('', 'reverse', None, _('produce a diff that undoes the changes')),
125 125 ] + diffwsopts + [
126 126 ('U', 'unified', '',
127 127 _('number of lines of context to show'), _('NUM')),
128 128 ('', 'stat', None, _('output diffstat-style summary of changes')),
129 129 ]
130 130
131 131 mergetoolopts = [
132 132 ('t', 'tool', '', _('specify merge tool')),
133 133 ]
134 134
135 135 similarityopts = [
136 136 ('s', 'similarity', '',
137 137 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
138 138 ]
139 139
140 140 subrepoopts = [
141 141 ('S', 'subrepos', None,
142 142 _('recurse into subrepositories'))
143 143 ]
144 144
145 145 # Commands start here, listed alphabetically
146 146
147 147 @command('^add',
148 148 walkopts + subrepoopts + dryrunopts,
149 149 _('[OPTION]... [FILE]...'))
150 150 def add(ui, repo, *pats, **opts):
151 151 """add the specified files on the next commit
152 152
153 153 Schedule files to be version controlled and added to the
154 154 repository.
155 155
156 156 The files will be added to the repository at the next commit. To
157 157 undo an add before that, see :hg:`forget`.
158 158
159 159 If no names are given, add all files to the repository.
160 160
161 161 .. container:: verbose
162 162
163 163 An example showing how new (unknown) files are added
164 164 automatically by :hg:`add`::
165 165
166 166 $ ls
167 167 foo.c
168 168 $ hg status
169 169 ? foo.c
170 170 $ hg add
171 171 adding foo.c
172 172 $ hg status
173 173 A foo.c
174 174
175 175 Returns 0 if all files are successfully added.
176 176 """
177 177
178 178 m = scmutil.match(repo[None], pats, opts)
179 179 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
180 180 opts.get('subrepos'), prefix="", explicitonly=False)
181 181 return rejected and 1 or 0
182 182
183 183 @command('addremove',
184 184 similarityopts + walkopts + dryrunopts,
185 185 _('[OPTION]... [FILE]...'))
186 186 def addremove(ui, repo, *pats, **opts):
187 187 """add all new files, delete all missing files
188 188
189 189 Add all new files and remove all missing files from the
190 190 repository.
191 191
192 192 New files are ignored if they match any of the patterns in
193 193 ``.hgignore``. As with add, these changes take effect at the next
194 194 commit.
195 195
196 196 Use the -s/--similarity option to detect renamed files. This
197 197 option takes a percentage between 0 (disabled) and 100 (files must
198 198 be identical) as its parameter. With a parameter greater than 0,
199 199 this compares every removed file with every added file and records
200 200 those similar enough as renames. Detecting renamed files this way
201 201 can be expensive. After using this option, :hg:`status -C` can be
202 202 used to check which files were identified as moved or renamed. If
203 203 not specified, -s/--similarity defaults to 100 and only renames of
204 204 identical files are detected.
205 205
206 206 Returns 0 if all files are successfully added.
207 207 """
208 208 try:
209 209 sim = float(opts.get('similarity') or 100)
210 210 except ValueError:
211 211 raise util.Abort(_('similarity must be a number'))
212 212 if sim < 0 or sim > 100:
213 213 raise util.Abort(_('similarity must be between 0 and 100'))
214 214 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
215 215
216 216 @command('^annotate|blame',
217 217 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
218 218 ('', 'follow', None,
219 219 _('follow copies/renames and list the filename (DEPRECATED)')),
220 220 ('', 'no-follow', None, _("don't follow copies and renames")),
221 221 ('a', 'text', None, _('treat all files as text')),
222 222 ('u', 'user', None, _('list the author (long with -v)')),
223 223 ('f', 'file', None, _('list the filename')),
224 224 ('d', 'date', None, _('list the date (short with -q)')),
225 225 ('n', 'number', None, _('list the revision number (default)')),
226 226 ('c', 'changeset', None, _('list the changeset')),
227 227 ('l', 'line-number', None, _('show line number at the first appearance'))
228 228 ] + diffwsopts + walkopts,
229 229 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
230 230 def annotate(ui, repo, *pats, **opts):
231 231 """show changeset information by line for each file
232 232
233 233 List changes in files, showing the revision id responsible for
234 234 each line
235 235
236 236 This command is useful for discovering when a change was made and
237 237 by whom.
238 238
239 239 Without the -a/--text option, annotate will avoid processing files
240 240 it detects as binary. With -a, annotate will annotate the file
241 241 anyway, although the results will probably be neither useful
242 242 nor desirable.
243 243
244 244 Returns 0 on success.
245 245 """
246 246 if opts.get('follow'):
247 247 # --follow is deprecated and now just an alias for -f/--file
248 248 # to mimic the behavior of Mercurial before version 1.5
249 249 opts['file'] = True
250 250
251 251 datefunc = ui.quiet and util.shortdate or util.datestr
252 252 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
253 253
254 254 if not pats:
255 255 raise util.Abort(_('at least one filename or pattern is required'))
256 256
257 257 hexfn = ui.debugflag and hex or short
258 258
259 259 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
260 260 ('number', ' ', lambda x: str(x[0].rev())),
261 261 ('changeset', ' ', lambda x: hexfn(x[0].node())),
262 262 ('date', ' ', getdate),
263 263 ('file', ' ', lambda x: x[0].path()),
264 264 ('line_number', ':', lambda x: str(x[1])),
265 265 ]
266 266
267 267 if (not opts.get('user') and not opts.get('changeset')
268 268 and not opts.get('date') and not opts.get('file')):
269 269 opts['number'] = True
270 270
271 271 linenumber = opts.get('line_number') is not None
272 272 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
273 273 raise util.Abort(_('at least one of -n/-c is required for -l'))
274 274
275 275 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
276 276 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
277 277
278 278 def bad(x, y):
279 279 raise util.Abort("%s: %s" % (x, y))
280 280
281 281 ctx = scmutil.revsingle(repo, opts.get('rev'))
282 282 m = scmutil.match(ctx, pats, opts)
283 283 m.bad = bad
284 284 follow = not opts.get('no_follow')
285 285 diffopts = patch.diffopts(ui, opts, section='annotate')
286 286 for abs in ctx.walk(m):
287 287 fctx = ctx[abs]
288 288 if not opts.get('text') and util.binary(fctx.data()):
289 289 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
290 290 continue
291 291
292 292 lines = fctx.annotate(follow=follow, linenumber=linenumber,
293 293 diffopts=diffopts)
294 294 pieces = []
295 295
296 296 for f, sep in funcmap:
297 297 l = [f(n) for n, dummy in lines]
298 298 if l:
299 299 sized = [(x, encoding.colwidth(x)) for x in l]
300 300 ml = max([w for x, w in sized])
301 301 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
302 302 for x, w in sized])
303 303
304 304 if pieces:
305 305 for p, l in zip(zip(*pieces), lines):
306 306 ui.write("%s: %s" % ("".join(p), l[1]))
307 307
308 308 if lines and not lines[-1][1].endswith('\n'):
309 309 ui.write('\n')
310 310
311 311 @command('archive',
312 312 [('', 'no-decode', None, _('do not pass files through decoders')),
313 313 ('p', 'prefix', '', _('directory prefix for files in archive'),
314 314 _('PREFIX')),
315 315 ('r', 'rev', '', _('revision to distribute'), _('REV')),
316 316 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
317 317 ] + subrepoopts + walkopts,
318 318 _('[OPTION]... DEST'))
319 319 def archive(ui, repo, dest, **opts):
320 320 '''create an unversioned archive of a repository revision
321 321
322 322 By default, the revision used is the parent of the working
323 323 directory; use -r/--rev to specify a different revision.
324 324
325 325 The archive type is automatically detected based on file
326 326 extension (or override using -t/--type).
327 327
328 328 .. container:: verbose
329 329
330 330 Examples:
331 331
332 332 - create a zip file containing the 1.0 release::
333 333
334 334 hg archive -r 1.0 project-1.0.zip
335 335
336 336 - create a tarball excluding .hg files::
337 337
338 338 hg archive project.tar.gz -X ".hg*"
339 339
340 340 Valid types are:
341 341
342 342 :``files``: a directory full of files (default)
343 343 :``tar``: tar archive, uncompressed
344 344 :``tbz2``: tar archive, compressed using bzip2
345 345 :``tgz``: tar archive, compressed using gzip
346 346 :``uzip``: zip archive, uncompressed
347 347 :``zip``: zip archive, compressed using deflate
348 348
349 349 The exact name of the destination archive or directory is given
350 350 using a format string; see :hg:`help export` for details.
351 351
352 352 Each member added to an archive file has a directory prefix
353 353 prepended. Use -p/--prefix to specify a format string for the
354 354 prefix. The default is the basename of the archive, with suffixes
355 355 removed.
356 356
357 357 Returns 0 on success.
358 358 '''
359 359
360 360 ctx = scmutil.revsingle(repo, opts.get('rev'))
361 361 if not ctx:
362 362 raise util.Abort(_('no working directory: please specify a revision'))
363 363 node = ctx.node()
364 364 dest = cmdutil.makefilename(repo, dest, node)
365 365 if os.path.realpath(dest) == repo.root:
366 366 raise util.Abort(_('repository root cannot be destination'))
367 367
368 368 kind = opts.get('type') or archival.guesskind(dest) or 'files'
369 369 prefix = opts.get('prefix')
370 370
371 371 if dest == '-':
372 372 if kind == 'files':
373 373 raise util.Abort(_('cannot archive plain files to stdout'))
374 374 dest = cmdutil.makefileobj(repo, dest)
375 375 if not prefix:
376 376 prefix = os.path.basename(repo.root) + '-%h'
377 377
378 378 prefix = cmdutil.makefilename(repo, prefix, node)
379 379 matchfn = scmutil.match(ctx, [], opts)
380 380 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
381 381 matchfn, prefix, subrepos=opts.get('subrepos'))
382 382
383 383 @command('backout',
384 384 [('', 'merge', None, _('merge with old dirstate parent after backout')),
385 385 ('', 'parent', '',
386 386 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
387 387 ('r', 'rev', '', _('revision to backout'), _('REV')),
388 388 ] + mergetoolopts + walkopts + commitopts + commitopts2,
389 389 _('[OPTION]... [-r] REV'))
390 390 def backout(ui, repo, node=None, rev=None, **opts):
391 391 '''reverse effect of earlier changeset
392 392
393 393 Prepare a new changeset with the effect of REV undone in the
394 394 current working directory.
395 395
396 396 If REV is the parent of the working directory, then this new changeset
397 397 is committed automatically. Otherwise, hg needs to merge the
398 398 changes and the merged result is left uncommitted.
399 399
400 400 .. note::
401 401
402 402 backout cannot be used to fix either an unwanted or
403 403 incorrect merge.
404 404
405 405 .. container:: verbose
406 406
407 407 By default, the pending changeset will have one parent,
408 408 maintaining a linear history. With --merge, the pending
409 409 changeset will instead have two parents: the old parent of the
410 410 working directory and a new child of REV that simply undoes REV.
411 411
412 412 Before version 1.7, the behavior without --merge was equivalent
413 413 to specifying --merge followed by :hg:`update --clean .` to
414 414 cancel the merge and leave the child of REV as a head to be
415 415 merged separately.
416 416
417 417 See :hg:`help dates` for a list of formats valid for -d/--date.
418 418
419 419 Returns 0 on success.
420 420 '''
421 421 if rev and node:
422 422 raise util.Abort(_("please specify just one revision"))
423 423
424 424 if not rev:
425 425 rev = node
426 426
427 427 if not rev:
428 428 raise util.Abort(_("please specify a revision to backout"))
429 429
430 430 date = opts.get('date')
431 431 if date:
432 432 opts['date'] = util.parsedate(date)
433 433
434 434 cmdutil.checkunfinished(repo)
435 435 cmdutil.bailifchanged(repo)
436 436 node = scmutil.revsingle(repo, rev).node()
437 437
438 438 op1, op2 = repo.dirstate.parents()
439 439 a = repo.changelog.ancestor(op1, node)
440 440 if a != node:
441 441 raise util.Abort(_('cannot backout change on a different branch'))
442 442
443 443 p1, p2 = repo.changelog.parents(node)
444 444 if p1 == nullid:
445 445 raise util.Abort(_('cannot backout a change with no parents'))
446 446 if p2 != nullid:
447 447 if not opts.get('parent'):
448 448 raise util.Abort(_('cannot backout a merge changeset'))
449 449 p = repo.lookup(opts['parent'])
450 450 if p not in (p1, p2):
451 451 raise util.Abort(_('%s is not a parent of %s') %
452 452 (short(p), short(node)))
453 453 parent = p
454 454 else:
455 455 if opts.get('parent'):
456 456 raise util.Abort(_('cannot use --parent on non-merge changeset'))
457 457 parent = p1
458 458
459 459 # the backout should appear on the same branch
460 460 wlock = repo.wlock()
461 461 try:
462 462 branch = repo.dirstate.branch()
463 463 bheads = repo.branchheads(branch)
464 464 rctx = scmutil.revsingle(repo, hex(parent))
465 465 if not opts.get('merge') and op1 != node:
466 466 try:
467 467 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
468 468 stats = mergemod.update(repo, parent, True, True, False,
469 469 node, False)
470 470 repo.setparents(op1, op2)
471 471 hg._showstats(repo, stats)
472 472 if stats[3]:
473 473 repo.ui.status(_("use 'hg resolve' to retry unresolved "
474 474 "file merges\n"))
475 475 else:
476 476 msg = _("changeset %s backed out, "
477 477 "don't forget to commit.\n")
478 478 ui.status(msg % short(node))
479 479 return stats[3] > 0
480 480 finally:
481 481 ui.setconfig('ui', 'forcemerge', '')
482 482 else:
483 483 hg.clean(repo, node, show_stats=False)
484 484 repo.dirstate.setbranch(branch)
485 485 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
486 486
487 487
488 488 e = cmdutil.commiteditor
489 489 if not opts['message'] and not opts['logfile']:
490 490 # we don't translate commit messages
491 491 opts['message'] = "Backed out changeset %s" % short(node)
492 492 e = cmdutil.commitforceeditor
493 493
494 494 def commitfunc(ui, repo, message, match, opts):
495 495 return repo.commit(message, opts.get('user'), opts.get('date'),
496 496 match, editor=e)
497 497 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
498 498 cmdutil.commitstatus(repo, newnode, branch, bheads)
499 499
500 500 def nice(node):
501 501 return '%d:%s' % (repo.changelog.rev(node), short(node))
502 502 ui.status(_('changeset %s backs out changeset %s\n') %
503 503 (nice(repo.changelog.tip()), nice(node)))
504 504 if opts.get('merge') and op1 != node:
505 505 hg.clean(repo, op1, show_stats=False)
506 506 ui.status(_('merging with changeset %s\n')
507 507 % nice(repo.changelog.tip()))
508 508 try:
509 509 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
510 510 return hg.merge(repo, hex(repo.changelog.tip()))
511 511 finally:
512 512 ui.setconfig('ui', 'forcemerge', '')
513 513 finally:
514 514 wlock.release()
515 515 return 0
516 516
517 517 @command('bisect',
518 518 [('r', 'reset', False, _('reset bisect state')),
519 519 ('g', 'good', False, _('mark changeset good')),
520 520 ('b', 'bad', False, _('mark changeset bad')),
521 521 ('s', 'skip', False, _('skip testing changeset')),
522 522 ('e', 'extend', False, _('extend the bisect range')),
523 523 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
524 524 ('U', 'noupdate', False, _('do not update to target'))],
525 525 _("[-gbsr] [-U] [-c CMD] [REV]"))
526 526 def bisect(ui, repo, rev=None, extra=None, command=None,
527 527 reset=None, good=None, bad=None, skip=None, extend=None,
528 528 noupdate=None):
529 529 """subdivision search of changesets
530 530
531 531 This command helps to find changesets which introduce problems. To
532 532 use, mark the earliest changeset you know exhibits the problem as
533 533 bad, then mark the latest changeset which is free from the problem
534 534 as good. Bisect will update your working directory to a revision
535 535 for testing (unless the -U/--noupdate option is specified). Once
536 536 you have performed tests, mark the working directory as good or
537 537 bad, and bisect will either update to another candidate changeset
538 538 or announce that it has found the bad revision.
539 539
540 540 As a shortcut, you can also use the revision argument to mark a
541 541 revision as good or bad without checking it out first.
542 542
543 543 If you supply a command, it will be used for automatic bisection.
544 544 The environment variable HG_NODE will contain the ID of the
545 545 changeset being tested. The exit status of the command will be
546 546 used to mark revisions as good or bad: status 0 means good, 125
547 547 means to skip the revision, 127 (command not found) will abort the
548 548 bisection, and any other non-zero exit status means the revision
549 549 is bad.
550 550
551 551 .. container:: verbose
552 552
553 553 Some examples:
554 554
555 555 - start a bisection with known bad revision 34, and good revision 12::
556 556
557 557 hg bisect --bad 34
558 558 hg bisect --good 12
559 559
560 560 - advance the current bisection by marking current revision as good or
561 561 bad::
562 562
563 563 hg bisect --good
564 564 hg bisect --bad
565 565
566 566 - mark the current revision, or a known revision, to be skipped (e.g. if
567 567 that revision is not usable because of another issue)::
568 568
569 569 hg bisect --skip
570 570 hg bisect --skip 23
571 571
572 572 - skip all revisions that do not touch directories ``foo`` or ``bar``::
573 573
574 574 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
575 575
576 576 - forget the current bisection::
577 577
578 578 hg bisect --reset
579 579
580 580 - use 'make && make tests' to automatically find the first broken
581 581 revision::
582 582
583 583 hg bisect --reset
584 584 hg bisect --bad 34
585 585 hg bisect --good 12
586 586 hg bisect --command "make && make tests"
587 587
588 588 - see all changesets whose states are already known in the current
589 589 bisection::
590 590
591 591 hg log -r "bisect(pruned)"
592 592
593 593 - see the changeset currently being bisected (especially useful
594 594 if running with -U/--noupdate)::
595 595
596 596 hg log -r "bisect(current)"
597 597
598 598 - see all changesets that took part in the current bisection::
599 599
600 600 hg log -r "bisect(range)"
601 601
602 602 - you can even get a nice graph::
603 603
604 604 hg log --graph -r "bisect(range)"
605 605
606 606 See :hg:`help revsets` for more about the `bisect()` keyword.
607 607
608 608 Returns 0 on success.
609 609 """
610 610 def extendbisectrange(nodes, good):
611 611 # bisect is incomplete when it ends on a merge node and
612 612 # one of the parent was not checked.
613 613 parents = repo[nodes[0]].parents()
614 614 if len(parents) > 1:
615 615 side = good and state['bad'] or state['good']
616 616 num = len(set(i.node() for i in parents) & set(side))
617 617 if num == 1:
618 618 return parents[0].ancestor(parents[1])
619 619 return None
620 620
621 621 def print_result(nodes, good):
622 622 displayer = cmdutil.show_changeset(ui, repo, {})
623 623 if len(nodes) == 1:
624 624 # narrowed it down to a single revision
625 625 if good:
626 626 ui.write(_("The first good revision is:\n"))
627 627 else:
628 628 ui.write(_("The first bad revision is:\n"))
629 629 displayer.show(repo[nodes[0]])
630 630 extendnode = extendbisectrange(nodes, good)
631 631 if extendnode is not None:
632 632 ui.write(_('Not all ancestors of this changeset have been'
633 633 ' checked.\nUse bisect --extend to continue the '
634 634 'bisection from\nthe common ancestor, %s.\n')
635 635 % extendnode)
636 636 else:
637 637 # multiple possible revisions
638 638 if good:
639 639 ui.write(_("Due to skipped revisions, the first "
640 640 "good revision could be any of:\n"))
641 641 else:
642 642 ui.write(_("Due to skipped revisions, the first "
643 643 "bad revision could be any of:\n"))
644 644 for n in nodes:
645 645 displayer.show(repo[n])
646 646 displayer.close()
647 647
648 648 def check_state(state, interactive=True):
649 649 if not state['good'] or not state['bad']:
650 650 if (good or bad or skip or reset) and interactive:
651 651 return
652 652 if not state['good']:
653 653 raise util.Abort(_('cannot bisect (no known good revisions)'))
654 654 else:
655 655 raise util.Abort(_('cannot bisect (no known bad revisions)'))
656 656 return True
657 657
658 658 # backward compatibility
659 659 if rev in "good bad reset init".split():
660 660 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
661 661 cmd, rev, extra = rev, extra, None
662 662 if cmd == "good":
663 663 good = True
664 664 elif cmd == "bad":
665 665 bad = True
666 666 else:
667 667 reset = True
668 668 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
669 669 raise util.Abort(_('incompatible arguments'))
670 670
671 671 cmdutil.checkunfinished(repo)
672 672
673 673 if reset:
674 674 p = repo.join("bisect.state")
675 675 if os.path.exists(p):
676 676 os.unlink(p)
677 677 return
678 678
679 679 state = hbisect.load_state(repo)
680 680
681 681 if command:
682 682 changesets = 1
683 683 if noupdate:
684 684 try:
685 685 node = state['current'][0]
686 686 except LookupError:
687 687 raise util.Abort(_('current bisect revision is unknown - '
688 688 'start a new bisect to fix'))
689 689 else:
690 690 node, p2 = repo.dirstate.parents()
691 691 if p2 != nullid:
692 692 raise util.Abort(_('current bisect revision is a merge'))
693 693 try:
694 694 while changesets:
695 695 # update state
696 696 state['current'] = [node]
697 697 hbisect.save_state(repo, state)
698 698 status = util.system(command,
699 699 environ={'HG_NODE': hex(node)},
700 700 out=ui.fout)
701 701 if status == 125:
702 702 transition = "skip"
703 703 elif status == 0:
704 704 transition = "good"
705 705 # status < 0 means process was killed
706 706 elif status == 127:
707 707 raise util.Abort(_("failed to execute %s") % command)
708 708 elif status < 0:
709 709 raise util.Abort(_("%s killed") % command)
710 710 else:
711 711 transition = "bad"
712 712 ctx = scmutil.revsingle(repo, rev, node)
713 713 rev = None # clear for future iterations
714 714 state[transition].append(ctx.node())
715 715 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
716 716 check_state(state, interactive=False)
717 717 # bisect
718 718 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
719 719 # update to next check
720 720 node = nodes[0]
721 721 if not noupdate:
722 722 cmdutil.bailifchanged(repo)
723 723 hg.clean(repo, node, show_stats=False)
724 724 finally:
725 725 state['current'] = [node]
726 726 hbisect.save_state(repo, state)
727 727 print_result(nodes, bgood)
728 728 return
729 729
730 730 # update state
731 731
732 732 if rev:
733 733 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
734 734 else:
735 735 nodes = [repo.lookup('.')]
736 736
737 737 if good or bad or skip:
738 738 if good:
739 739 state['good'] += nodes
740 740 elif bad:
741 741 state['bad'] += nodes
742 742 elif skip:
743 743 state['skip'] += nodes
744 744 hbisect.save_state(repo, state)
745 745
746 746 if not check_state(state):
747 747 return
748 748
749 749 # actually bisect
750 750 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
751 751 if extend:
752 752 if not changesets:
753 753 extendnode = extendbisectrange(nodes, good)
754 754 if extendnode is not None:
755 755 ui.write(_("Extending search to changeset %d:%s\n"
756 756 % (extendnode.rev(), extendnode)))
757 757 state['current'] = [extendnode.node()]
758 758 hbisect.save_state(repo, state)
759 759 if noupdate:
760 760 return
761 761 cmdutil.bailifchanged(repo)
762 762 return hg.clean(repo, extendnode.node())
763 763 raise util.Abort(_("nothing to extend"))
764 764
765 765 if changesets == 0:
766 766 print_result(nodes, good)
767 767 else:
768 768 assert len(nodes) == 1 # only a single node can be tested next
769 769 node = nodes[0]
770 770 # compute the approximate number of remaining tests
771 771 tests, size = 0, 2
772 772 while size <= changesets:
773 773 tests, size = tests + 1, size * 2
774 774 rev = repo.changelog.rev(node)
775 775 ui.write(_("Testing changeset %d:%s "
776 776 "(%d changesets remaining, ~%d tests)\n")
777 777 % (rev, short(node), changesets, tests))
778 778 state['current'] = [node]
779 779 hbisect.save_state(repo, state)
780 780 if not noupdate:
781 781 cmdutil.bailifchanged(repo)
782 782 return hg.clean(repo, node)
783 783
784 784 @command('bookmarks|bookmark',
785 785 [('f', 'force', False, _('force')),
786 786 ('r', 'rev', '', _('revision'), _('REV')),
787 787 ('d', 'delete', False, _('delete a given bookmark')),
788 788 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
789 789 ('i', 'inactive', False, _('mark a bookmark inactive'))],
790 790 _('hg bookmarks [OPTIONS]... [NAME]...'))
791 791 def bookmark(ui, repo, *names, **opts):
792 792 '''track a line of development with movable markers
793 793
794 794 Bookmarks are pointers to certain commits that move when committing.
795 795 Bookmarks are local. They can be renamed, copied and deleted. It is
796 796 possible to use :hg:`merge NAME` to merge from a given bookmark, and
797 797 :hg:`update NAME` to update to a given bookmark.
798 798
799 799 You can use :hg:`bookmark NAME` to set a bookmark on the working
800 800 directory's parent revision with the given name. If you specify
801 801 a revision using -r REV (where REV may be an existing bookmark),
802 802 the bookmark is assigned to that revision.
803 803
804 804 Bookmarks can be pushed and pulled between repositories (see :hg:`help
805 805 push` and :hg:`help pull`). This requires both the local and remote
806 806 repositories to support bookmarks. For versions prior to 1.8, this means
807 807 the bookmarks extension must be enabled.
808 808
809 809 If you set a bookmark called '@', new clones of the repository will
810 810 have that revision checked out (and the bookmark made active) by
811 811 default.
812 812
813 813 With -i/--inactive, the new bookmark will not be made the active
814 814 bookmark. If -r/--rev is given, the new bookmark will not be made
815 815 active even if -i/--inactive is not given. If no NAME is given, the
816 816 current active bookmark will be marked inactive.
817 817 '''
818 818 force = opts.get('force')
819 819 rev = opts.get('rev')
820 820 delete = opts.get('delete')
821 821 rename = opts.get('rename')
822 822 inactive = opts.get('inactive')
823 823
824 824 def checkformat(mark):
825 825 mark = mark.strip()
826 826 if not mark:
827 827 raise util.Abort(_("bookmark names cannot consist entirely of "
828 828 "whitespace"))
829 829 scmutil.checknewlabel(repo, mark, 'bookmark')
830 830 return mark
831 831
832 832 def checkconflict(repo, mark, cur, force=False, target=None):
833 833 if mark in marks and not force:
834 834 if target:
835 835 if marks[mark] == target and target == cur:
836 836 # re-activating a bookmark
837 837 return
838 838 anc = repo.changelog.ancestors([repo[target].rev()])
839 839 bmctx = repo[marks[mark]]
840 840 divs = [repo[b].node() for b in marks
841 841 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
842 842
843 843 # allow resolving a single divergent bookmark even if moving
844 844 # the bookmark across branches when a revision is specified
845 845 # that contains a divergent bookmark
846 846 if bmctx.rev() not in anc and target in divs:
847 847 bookmarks.deletedivergent(repo, [target], mark)
848 848 return
849 849
850 850 deletefrom = [b for b in divs
851 851 if repo[b].rev() in anc or b == target]
852 852 bookmarks.deletedivergent(repo, deletefrom, mark)
853 853 if bookmarks.validdest(repo, bmctx, repo[target]):
854 854 ui.status(_("moving bookmark '%s' forward from %s\n") %
855 855 (mark, short(bmctx.node())))
856 856 return
857 857 raise util.Abort(_("bookmark '%s' already exists "
858 858 "(use -f to force)") % mark)
859 859 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
860 860 and not force):
861 861 raise util.Abort(
862 862 _("a bookmark cannot have the name of an existing branch"))
863 863
864 864 if delete and rename:
865 865 raise util.Abort(_("--delete and --rename are incompatible"))
866 866 if delete and rev:
867 867 raise util.Abort(_("--rev is incompatible with --delete"))
868 868 if rename and rev:
869 869 raise util.Abort(_("--rev is incompatible with --rename"))
870 870 if not names and (delete or rev):
871 871 raise util.Abort(_("bookmark name required"))
872 872
873 873 if delete or rename or names or inactive:
874 874 wlock = repo.wlock()
875 875 try:
876 876 cur = repo.changectx('.').node()
877 877 marks = repo._bookmarks
878 878 if delete:
879 879 for mark in names:
880 880 if mark not in marks:
881 881 raise util.Abort(_("bookmark '%s' does not exist") %
882 882 mark)
883 883 if mark == repo._bookmarkcurrent:
884 884 bookmarks.unsetcurrent(repo)
885 885 del marks[mark]
886 886 marks.write()
887 887
888 888 elif rename:
889 889 if not names:
890 890 raise util.Abort(_("new bookmark name required"))
891 891 elif len(names) > 1:
892 892 raise util.Abort(_("only one new bookmark name allowed"))
893 893 mark = checkformat(names[0])
894 894 if rename not in marks:
895 895 raise util.Abort(_("bookmark '%s' does not exist") % rename)
896 896 checkconflict(repo, mark, cur, force)
897 897 marks[mark] = marks[rename]
898 898 if repo._bookmarkcurrent == rename and not inactive:
899 899 bookmarks.setcurrent(repo, mark)
900 900 del marks[rename]
901 901 marks.write()
902 902
903 903 elif names:
904 904 newact = None
905 905 for mark in names:
906 906 mark = checkformat(mark)
907 907 if newact is None:
908 908 newact = mark
909 909 if inactive and mark == repo._bookmarkcurrent:
910 910 bookmarks.unsetcurrent(repo)
911 911 return
912 912 tgt = cur
913 913 if rev:
914 914 tgt = scmutil.revsingle(repo, rev).node()
915 915 checkconflict(repo, mark, cur, force, tgt)
916 916 marks[mark] = tgt
917 917 if not inactive and cur == marks[newact] and not rev:
918 918 bookmarks.setcurrent(repo, newact)
919 919 elif cur != tgt and newact == repo._bookmarkcurrent:
920 920 bookmarks.unsetcurrent(repo)
921 921 marks.write()
922 922
923 923 elif inactive:
924 924 if len(marks) == 0:
925 925 ui.status(_("no bookmarks set\n"))
926 926 elif not repo._bookmarkcurrent:
927 927 ui.status(_("no active bookmark\n"))
928 928 else:
929 929 bookmarks.unsetcurrent(repo)
930 930 finally:
931 931 wlock.release()
932 932 else: # show bookmarks
933 933 hexfn = ui.debugflag and hex or short
934 934 marks = repo._bookmarks
935 935 if len(marks) == 0:
936 936 ui.status(_("no bookmarks set\n"))
937 937 else:
938 938 for bmark, n in sorted(marks.iteritems()):
939 939 current = repo._bookmarkcurrent
940 940 if bmark == current:
941 941 prefix, label = '*', 'bookmarks.current'
942 942 else:
943 943 prefix, label = ' ', ''
944 944
945 945 if ui.quiet:
946 946 ui.write("%s\n" % bmark, label=label)
947 947 else:
948 948 ui.write(" %s %-25s %d:%s\n" % (
949 949 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
950 950 label=label)
951 951
952 952 @command('branch',
953 953 [('f', 'force', None,
954 954 _('set branch name even if it shadows an existing branch')),
955 955 ('C', 'clean', None, _('reset branch name to parent branch name'))],
956 956 _('[-fC] [NAME]'))
957 957 def branch(ui, repo, label=None, **opts):
958 958 """set or show the current branch name
959 959
960 960 .. note::
961 961
962 962 Branch names are permanent and global. Use :hg:`bookmark` to create a
963 963 light-weight bookmark instead. See :hg:`help glossary` for more
964 964 information about named branches and bookmarks.
965 965
966 966 With no argument, show the current branch name. With one argument,
967 967 set the working directory branch name (the branch will not exist
968 968 in the repository until the next commit). Standard practice
969 969 recommends that primary development take place on the 'default'
970 970 branch.
971 971
972 972 Unless -f/--force is specified, branch will not let you set a
973 973 branch name that already exists, even if it's inactive.
974 974
975 975 Use -C/--clean to reset the working directory branch to that of
976 976 the parent of the working directory, negating a previous branch
977 977 change.
978 978
979 979 Use the command :hg:`update` to switch to an existing branch. Use
980 980 :hg:`commit --close-branch` to mark this branch as closed.
981 981
982 982 Returns 0 on success.
983 983 """
984 984 if label:
985 985 label = label.strip()
986 986
987 987 if not opts.get('clean') and not label:
988 988 ui.write("%s\n" % repo.dirstate.branch())
989 989 return
990 990
991 991 wlock = repo.wlock()
992 992 try:
993 993 if opts.get('clean'):
994 994 label = repo[None].p1().branch()
995 995 repo.dirstate.setbranch(label)
996 996 ui.status(_('reset working directory to branch %s\n') % label)
997 997 elif label:
998 998 if not opts.get('force') and label in repo.branchmap():
999 999 if label not in [p.branch() for p in repo.parents()]:
1000 1000 raise util.Abort(_('a branch of the same name already'
1001 1001 ' exists'),
1002 1002 # i18n: "it" refers to an existing branch
1003 1003 hint=_("use 'hg update' to switch to it"))
1004 1004 scmutil.checknewlabel(repo, label, 'branch')
1005 1005 repo.dirstate.setbranch(label)
1006 1006 ui.status(_('marked working directory as branch %s\n') % label)
1007 1007 ui.status(_('(branches are permanent and global, '
1008 1008 'did you want a bookmark?)\n'))
1009 1009 finally:
1010 1010 wlock.release()
1011 1011
1012 1012 @command('branches',
1013 1013 [('a', 'active', False, _('show only branches that have unmerged heads')),
1014 1014 ('c', 'closed', False, _('show normal and closed branches'))],
1015 1015 _('[-ac]'))
1016 1016 def branches(ui, repo, active=False, closed=False):
1017 1017 """list repository named branches
1018 1018
1019 1019 List the repository's named branches, indicating which ones are
1020 1020 inactive. If -c/--closed is specified, also list branches which have
1021 1021 been marked closed (see :hg:`commit --close-branch`).
1022 1022
1023 1023 If -a/--active is specified, only show active branches. A branch
1024 1024 is considered active if it contains repository heads.
1025 1025
1026 1026 Use the command :hg:`update` to switch to an existing branch.
1027 1027
1028 1028 Returns 0.
1029 1029 """
1030 1030
1031 1031 hexfunc = ui.debugflag and hex or short
1032 1032
1033 1033 allheads = set(repo.heads())
1034 1034 branches = []
1035 1035 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1036 1036 isactive = not isclosed and bool(set(heads) & allheads)
1037 1037 branches.append((tag, repo[tip], isactive, not isclosed))
1038 1038 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1039 1039 reverse=True)
1040 1040
1041 1041 for tag, ctx, isactive, isopen in branches:
1042 1042 if (not active) or isactive:
1043 1043 if isactive:
1044 1044 label = 'branches.active'
1045 1045 notice = ''
1046 1046 elif not isopen:
1047 1047 if not closed:
1048 1048 continue
1049 1049 label = 'branches.closed'
1050 1050 notice = _(' (closed)')
1051 1051 else:
1052 1052 label = 'branches.inactive'
1053 1053 notice = _(' (inactive)')
1054 1054 if tag == repo.dirstate.branch():
1055 1055 label = 'branches.current'
1056 1056 rev = str(ctx.rev()).rjust(31 - encoding.colwidth(tag))
1057 1057 rev = ui.label('%s:%s' % (rev, hexfunc(ctx.node())),
1058 1058 'log.changeset changeset.%s' % ctx.phasestr())
1059 1059 labeledtag = ui.label(tag, label)
1060 1060 if ui.quiet:
1061 1061 ui.write("%s\n" % labeledtag)
1062 1062 else:
1063 1063 ui.write("%s %s%s\n" % (labeledtag, rev, notice))
1064 1064
1065 1065 @command('bundle',
1066 1066 [('f', 'force', None, _('run even when the destination is unrelated')),
1067 1067 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1068 1068 _('REV')),
1069 1069 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1070 1070 _('BRANCH')),
1071 1071 ('', 'base', [],
1072 1072 _('a base changeset assumed to be available at the destination'),
1073 1073 _('REV')),
1074 1074 ('a', 'all', None, _('bundle all changesets in the repository')),
1075 1075 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1076 1076 ] + remoteopts,
1077 1077 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1078 1078 def bundle(ui, repo, fname, dest=None, **opts):
1079 1079 """create a changegroup file
1080 1080
1081 1081 Generate a compressed changegroup file collecting changesets not
1082 1082 known to be in another repository.
1083 1083
1084 1084 If you omit the destination repository, then hg assumes the
1085 1085 destination will have all the nodes you specify with --base
1086 1086 parameters. To create a bundle containing all changesets, use
1087 1087 -a/--all (or --base null).
1088 1088
1089 1089 You can change compression method with the -t/--type option.
1090 1090 The available compression methods are: none, bzip2, and
1091 1091 gzip (by default, bundles are compressed using bzip2).
1092 1092
1093 1093 The bundle file can then be transferred using conventional means
1094 1094 and applied to another repository with the unbundle or pull
1095 1095 command. This is useful when direct push and pull are not
1096 1096 available or when exporting an entire repository is undesirable.
1097 1097
1098 1098 Applying bundles preserves all changeset contents including
1099 1099 permissions, copy/rename information, and revision history.
1100 1100
1101 1101 Returns 0 on success, 1 if no changes found.
1102 1102 """
1103 1103 revs = None
1104 1104 if 'rev' in opts:
1105 1105 revs = scmutil.revrange(repo, opts['rev'])
1106 1106
1107 1107 bundletype = opts.get('type', 'bzip2').lower()
1108 1108 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1109 1109 bundletype = btypes.get(bundletype)
1110 1110 if bundletype not in changegroup.bundletypes:
1111 1111 raise util.Abort(_('unknown bundle type specified with --type'))
1112 1112
1113 1113 if opts.get('all'):
1114 1114 base = ['null']
1115 1115 else:
1116 1116 base = scmutil.revrange(repo, opts.get('base'))
1117 1117 # TODO: get desired bundlecaps from command line.
1118 1118 bundlecaps = None
1119 1119 if base:
1120 1120 if dest:
1121 1121 raise util.Abort(_("--base is incompatible with specifying "
1122 1122 "a destination"))
1123 1123 common = [repo.lookup(rev) for rev in base]
1124 1124 heads = revs and map(repo.lookup, revs) or revs
1125 1125 cg = repo.getbundle('bundle', heads=heads, common=common,
1126 1126 bundlecaps=bundlecaps)
1127 1127 outgoing = None
1128 1128 else:
1129 1129 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1130 1130 dest, branches = hg.parseurl(dest, opts.get('branch'))
1131 1131 other = hg.peer(repo, opts, dest)
1132 1132 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1133 1133 heads = revs and map(repo.lookup, revs) or revs
1134 1134 outgoing = discovery.findcommonoutgoing(repo, other,
1135 1135 onlyheads=heads,
1136 1136 force=opts.get('force'),
1137 1137 portable=True)
1138 1138 cg = repo.getlocalbundle('bundle', outgoing, bundlecaps)
1139 1139 if not cg:
1140 1140 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1141 1141 return 1
1142 1142
1143 1143 changegroup.writebundle(cg, fname, bundletype)
1144 1144
1145 1145 @command('cat',
1146 1146 [('o', 'output', '',
1147 1147 _('print output to file with formatted name'), _('FORMAT')),
1148 1148 ('r', 'rev', '', _('print the given revision'), _('REV')),
1149 1149 ('', 'decode', None, _('apply any matching decode filter')),
1150 1150 ] + walkopts,
1151 1151 _('[OPTION]... FILE...'))
1152 1152 def cat(ui, repo, file1, *pats, **opts):
1153 1153 """output the current or given revision of files
1154 1154
1155 1155 Print the specified files as they were at the given revision. If
1156 1156 no revision is given, the parent of the working directory is used.
1157 1157
1158 1158 Output may be to a file, in which case the name of the file is
1159 1159 given using a format string. The formatting rules are the same as
1160 1160 for the export command, with the following additions:
1161 1161
1162 1162 :``%s``: basename of file being printed
1163 1163 :``%d``: dirname of file being printed, or '.' if in repository root
1164 1164 :``%p``: root-relative path name of file being printed
1165 1165
1166 1166 Returns 0 on success.
1167 1167 """
1168 1168 ctx = scmutil.revsingle(repo, opts.get('rev'))
1169 1169 err = 1
1170 1170 m = scmutil.match(ctx, (file1,) + pats, opts)
1171 1171
1172 1172 def write(path):
1173 1173 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1174 1174 pathname=path)
1175 1175 data = ctx[path].data()
1176 1176 if opts.get('decode'):
1177 1177 data = repo.wwritedata(path, data)
1178 1178 fp.write(data)
1179 1179 fp.close()
1180 1180
1181 1181 # Automation often uses hg cat on single files, so special case it
1182 1182 # for performance to avoid the cost of parsing the manifest.
1183 1183 if len(m.files()) == 1 and not m.anypats():
1184 1184 file = m.files()[0]
1185 1185 mf = repo.manifest
1186 1186 mfnode = ctx._changeset[0]
1187 1187 if mf.find(mfnode, file)[0]:
1188 1188 write(file)
1189 1189 return 0
1190 1190
1191 1191 for abs in ctx.walk(m):
1192 1192 write(abs)
1193 1193 err = 0
1194 1194 return err
1195 1195
1196 1196 @command('^clone',
1197 1197 [('U', 'noupdate', None,
1198 1198 _('the clone will include an empty working copy (only a repository)')),
1199 1199 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1200 1200 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1201 1201 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1202 1202 ('', 'pull', None, _('use pull protocol to copy metadata')),
1203 1203 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1204 1204 ] + remoteopts,
1205 1205 _('[OPTION]... SOURCE [DEST]'))
1206 1206 def clone(ui, source, dest=None, **opts):
1207 1207 """make a copy of an existing repository
1208 1208
1209 1209 Create a copy of an existing repository in a new directory.
1210 1210
1211 1211 If no destination directory name is specified, it defaults to the
1212 1212 basename of the source.
1213 1213
1214 1214 The location of the source is added to the new repository's
1215 1215 ``.hg/hgrc`` file, as the default to be used for future pulls.
1216 1216
1217 1217 Only local paths and ``ssh://`` URLs are supported as
1218 1218 destinations. For ``ssh://`` destinations, no working directory or
1219 1219 ``.hg/hgrc`` will be created on the remote side.
1220 1220
1221 1221 To pull only a subset of changesets, specify one or more revisions
1222 1222 identifiers with -r/--rev or branches with -b/--branch. The
1223 1223 resulting clone will contain only the specified changesets and
1224 1224 their ancestors. These options (or 'clone src#rev dest') imply
1225 1225 --pull, even for local source repositories. Note that specifying a
1226 1226 tag will include the tagged changeset but not the changeset
1227 1227 containing the tag.
1228 1228
1229 1229 If the source repository has a bookmark called '@' set, that
1230 1230 revision will be checked out in the new repository by default.
1231 1231
1232 1232 To check out a particular version, use -u/--update, or
1233 1233 -U/--noupdate to create a clone with no working directory.
1234 1234
1235 1235 .. container:: verbose
1236 1236
1237 1237 For efficiency, hardlinks are used for cloning whenever the
1238 1238 source and destination are on the same filesystem (note this
1239 1239 applies only to the repository data, not to the working
1240 1240 directory). Some filesystems, such as AFS, implement hardlinking
1241 1241 incorrectly, but do not report errors. In these cases, use the
1242 1242 --pull option to avoid hardlinking.
1243 1243
1244 1244 In some cases, you can clone repositories and the working
1245 1245 directory using full hardlinks with ::
1246 1246
1247 1247 $ cp -al REPO REPOCLONE
1248 1248
1249 1249 This is the fastest way to clone, but it is not always safe. The
1250 1250 operation is not atomic (making sure REPO is not modified during
1251 1251 the operation is up to you) and you have to make sure your
1252 1252 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1253 1253 so). Also, this is not compatible with certain extensions that
1254 1254 place their metadata under the .hg directory, such as mq.
1255 1255
1256 1256 Mercurial will update the working directory to the first applicable
1257 1257 revision from this list:
1258 1258
1259 1259 a) null if -U or the source repository has no changesets
1260 1260 b) if -u . and the source repository is local, the first parent of
1261 1261 the source repository's working directory
1262 1262 c) the changeset specified with -u (if a branch name, this means the
1263 1263 latest head of that branch)
1264 1264 d) the changeset specified with -r
1265 1265 e) the tipmost head specified with -b
1266 1266 f) the tipmost head specified with the url#branch source syntax
1267 1267 g) the revision marked with the '@' bookmark, if present
1268 1268 h) the tipmost head of the default branch
1269 1269 i) tip
1270 1270
1271 1271 Examples:
1272 1272
1273 1273 - clone a remote repository to a new directory named hg/::
1274 1274
1275 1275 hg clone http://selenic.com/hg
1276 1276
1277 1277 - create a lightweight local clone::
1278 1278
1279 1279 hg clone project/ project-feature/
1280 1280
1281 1281 - clone from an absolute path on an ssh server (note double-slash)::
1282 1282
1283 1283 hg clone ssh://user@server//home/projects/alpha/
1284 1284
1285 1285 - do a high-speed clone over a LAN while checking out a
1286 1286 specified version::
1287 1287
1288 1288 hg clone --uncompressed http://server/repo -u 1.5
1289 1289
1290 1290 - create a repository without changesets after a particular revision::
1291 1291
1292 1292 hg clone -r 04e544 experimental/ good/
1293 1293
1294 1294 - clone (and track) a particular named branch::
1295 1295
1296 1296 hg clone http://selenic.com/hg#stable
1297 1297
1298 1298 See :hg:`help urls` for details on specifying URLs.
1299 1299
1300 1300 Returns 0 on success.
1301 1301 """
1302 1302 if opts.get('noupdate') and opts.get('updaterev'):
1303 1303 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1304 1304
1305 1305 r = hg.clone(ui, opts, source, dest,
1306 1306 pull=opts.get('pull'),
1307 1307 stream=opts.get('uncompressed'),
1308 1308 rev=opts.get('rev'),
1309 1309 update=opts.get('updaterev') or not opts.get('noupdate'),
1310 1310 branch=opts.get('branch'))
1311 1311
1312 1312 return r is None
1313 1313
1314 1314 @command('^commit|ci',
1315 1315 [('A', 'addremove', None,
1316 1316 _('mark new/missing files as added/removed before committing')),
1317 1317 ('', 'close-branch', None,
1318 1318 _('mark a branch as closed, hiding it from the branch list')),
1319 1319 ('', 'amend', None, _('amend the parent of the working dir')),
1320 1320 ('s', 'secret', None, _('use the secret phase for committing')),
1321 1321 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1322 1322 _('[OPTION]... [FILE]...'))
1323 1323 def commit(ui, repo, *pats, **opts):
1324 1324 """commit the specified files or all outstanding changes
1325 1325
1326 1326 Commit changes to the given files into the repository. Unlike a
1327 1327 centralized SCM, this operation is a local operation. See
1328 1328 :hg:`push` for a way to actively distribute your changes.
1329 1329
1330 1330 If a list of files is omitted, all changes reported by :hg:`status`
1331 1331 will be committed.
1332 1332
1333 1333 If you are committing the result of a merge, do not provide any
1334 1334 filenames or -I/-X filters.
1335 1335
1336 1336 If no commit message is specified, Mercurial starts your
1337 1337 configured editor where you can enter a message. In case your
1338 1338 commit fails, you will find a backup of your message in
1339 1339 ``.hg/last-message.txt``.
1340 1340
1341 1341 The --amend flag can be used to amend the parent of the
1342 1342 working directory with a new commit that contains the changes
1343 1343 in the parent in addition to those currently reported by :hg:`status`,
1344 1344 if there are any. The old commit is stored in a backup bundle in
1345 1345 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1346 1346 on how to restore it).
1347 1347
1348 1348 Message, user and date are taken from the amended commit unless
1349 1349 specified. When a message isn't specified on the command line,
1350 1350 the editor will open with the message of the amended commit.
1351 1351
1352 1352 It is not possible to amend public changesets (see :hg:`help phases`)
1353 1353 or changesets that have children.
1354 1354
1355 1355 See :hg:`help dates` for a list of formats valid for -d/--date.
1356 1356
1357 1357 Returns 0 on success, 1 if nothing changed.
1358 1358 """
1359 1359 if opts.get('subrepos'):
1360 1360 if opts.get('amend'):
1361 1361 raise util.Abort(_('cannot amend with --subrepos'))
1362 1362 # Let --subrepos on the command line override config setting.
1363 1363 ui.setconfig('ui', 'commitsubrepos', True)
1364 1364
1365 1365 # Save this for restoring it later
1366 1366 oldcommitphase = ui.config('phases', 'new-commit')
1367 1367
1368 1368 cmdutil.checkunfinished(repo, commit=True)
1369 1369
1370 1370 branch = repo[None].branch()
1371 1371 bheads = repo.branchheads(branch)
1372 1372
1373 1373 extra = {}
1374 1374 if opts.get('close_branch'):
1375 1375 extra['close'] = 1
1376 1376
1377 1377 if not bheads:
1378 1378 raise util.Abort(_('can only close branch heads'))
1379 1379 elif opts.get('amend'):
1380 1380 if repo.parents()[0].p1().branch() != branch and \
1381 1381 repo.parents()[0].p2().branch() != branch:
1382 1382 raise util.Abort(_('can only close branch heads'))
1383 1383
1384 1384 if opts.get('amend'):
1385 1385 if ui.configbool('ui', 'commitsubrepos'):
1386 1386 raise util.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1387 1387
1388 1388 old = repo['.']
1389 1389 if old.phase() == phases.public:
1390 1390 raise util.Abort(_('cannot amend public changesets'))
1391 1391 if len(repo[None].parents()) > 1:
1392 1392 raise util.Abort(_('cannot amend while merging'))
1393 1393 if (not obsolete._enabled) and old.children():
1394 1394 raise util.Abort(_('cannot amend changeset with children'))
1395 1395
1396 1396 e = cmdutil.commiteditor
1397 1397 if opts.get('force_editor'):
1398 1398 e = cmdutil.commitforceeditor
1399 1399
1400 # commitfunc is used only for temporary amend commit by cmdutil.amend
1400 1401 def commitfunc(ui, repo, message, match, opts):
1401 1402 editor = e
1402 1403 # message contains text from -m or -l, if it's empty,
1403 1404 # open the editor with the old message
1404 1405 if not message:
1405 1406 message = old.description()
1406 1407 editor = cmdutil.commitforceeditor
1407 try:
1408 if opts.get('secret'):
1409 ui.setconfig('phases', 'new-commit', 'secret')
1410
1411 return repo.commit(message,
1412 opts.get('user') or old.user(),
1413 opts.get('date') or old.date(),
1414 match,
1415 editor=editor,
1416 extra=extra)
1417 finally:
1418 ui.setconfig('phases', 'new-commit', oldcommitphase)
1408 return repo.commit(message,
1409 opts.get('user') or old.user(),
1410 opts.get('date') or old.date(),
1411 match,
1412 editor=editor,
1413 extra=extra)
1419 1414
1420 1415 current = repo._bookmarkcurrent
1421 1416 marks = old.bookmarks()
1422 1417 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1423 1418 if node == old.node():
1424 1419 ui.status(_("nothing changed\n"))
1425 1420 return 1
1426 1421 elif marks:
1427 1422 ui.debug('moving bookmarks %r from %s to %s\n' %
1428 1423 (marks, old.hex(), hex(node)))
1429 1424 newmarks = repo._bookmarks
1430 1425 for bm in marks:
1431 1426 newmarks[bm] = node
1432 1427 if bm == current:
1433 1428 bookmarks.setcurrent(repo, bm)
1434 1429 newmarks.write()
1435 1430 else:
1436 1431 e = cmdutil.commiteditor
1437 1432 if opts.get('force_editor'):
1438 1433 e = cmdutil.commitforceeditor
1439 1434
1440 1435 def commitfunc(ui, repo, message, match, opts):
1441 1436 try:
1442 1437 if opts.get('secret'):
1443 1438 ui.setconfig('phases', 'new-commit', 'secret')
1444 1439
1445 1440 return repo.commit(message, opts.get('user'), opts.get('date'),
1446 1441 match, editor=e, extra=extra)
1447 1442 finally:
1448 1443 ui.setconfig('phases', 'new-commit', oldcommitphase)
1449 1444
1450 1445
1451 1446 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1452 1447
1453 1448 if not node:
1454 1449 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1455 1450 if stat[3]:
1456 1451 ui.status(_("nothing changed (%d missing files, see "
1457 1452 "'hg status')\n") % len(stat[3]))
1458 1453 else:
1459 1454 ui.status(_("nothing changed\n"))
1460 1455 return 1
1461 1456
1462 1457 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1463 1458
1464 1459 @command('config|showconfig|debugconfig',
1465 1460 [('u', 'untrusted', None, _('show untrusted configuration options')),
1466 1461 ('e', 'edit', None, _('start editor'))],
1467 1462 _('[-u] [NAME]...'))
1468 1463 def config(ui, repo, *values, **opts):
1469 1464 """show combined config settings from all hgrc files
1470 1465
1471 1466 With no arguments, print names and values of all config items.
1472 1467
1473 1468 With one argument of the form section.name, print just the value
1474 1469 of that config item.
1475 1470
1476 1471 With multiple arguments, print names and values of all config
1477 1472 items with matching section names.
1478 1473
1479 1474 With --debug, the source (filename and line number) is printed
1480 1475 for each config item.
1481 1476
1482 1477 Returns 0 on success.
1483 1478 """
1484 1479
1485 1480 if opts.get('edit'):
1486 1481 paths = scmutil.userrcpath()
1487 1482 for f in paths:
1488 1483 if os.path.exists(f):
1489 1484 break
1490 1485 else:
1491 1486 f = paths[0]
1492 1487 fp = open(f, "w")
1493 1488 fp.write(
1494 1489 '# example config (see "hg help config" for more info)\n'
1495 1490 '\n'
1496 1491 '[ui]\n'
1497 1492 '# name and email, e.g.\n'
1498 1493 '# username = Jane Doe <jdoe@example.com>\n'
1499 1494 'username =\n'
1500 1495 '\n'
1501 1496 '[extensions]\n'
1502 1497 '# uncomment these lines to enable some popular extensions\n'
1503 1498 '# (see "hg help extensions" for more info)\n'
1504 1499 '# pager =\n'
1505 1500 '# progress =\n'
1506 1501 '# color =\n')
1507 1502 fp.close()
1508 1503
1509 1504 editor = ui.geteditor()
1510 1505 util.system("%s \"%s\"" % (editor, f),
1511 1506 onerr=util.Abort, errprefix=_("edit failed"),
1512 1507 out=ui.fout)
1513 1508 return
1514 1509
1515 1510 for f in scmutil.rcpath():
1516 1511 ui.debug('read config from: %s\n' % f)
1517 1512 untrusted = bool(opts.get('untrusted'))
1518 1513 if values:
1519 1514 sections = [v for v in values if '.' not in v]
1520 1515 items = [v for v in values if '.' in v]
1521 1516 if len(items) > 1 or items and sections:
1522 1517 raise util.Abort(_('only one config item permitted'))
1523 1518 for section, name, value in ui.walkconfig(untrusted=untrusted):
1524 1519 value = str(value).replace('\n', '\\n')
1525 1520 sectname = section + '.' + name
1526 1521 if values:
1527 1522 for v in values:
1528 1523 if v == section:
1529 1524 ui.debug('%s: ' %
1530 1525 ui.configsource(section, name, untrusted))
1531 1526 ui.write('%s=%s\n' % (sectname, value))
1532 1527 elif v == sectname:
1533 1528 ui.debug('%s: ' %
1534 1529 ui.configsource(section, name, untrusted))
1535 1530 ui.write(value, '\n')
1536 1531 else:
1537 1532 ui.debug('%s: ' %
1538 1533 ui.configsource(section, name, untrusted))
1539 1534 ui.write('%s=%s\n' % (sectname, value))
1540 1535
1541 1536 @command('copy|cp',
1542 1537 [('A', 'after', None, _('record a copy that has already occurred')),
1543 1538 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1544 1539 ] + walkopts + dryrunopts,
1545 1540 _('[OPTION]... [SOURCE]... DEST'))
1546 1541 def copy(ui, repo, *pats, **opts):
1547 1542 """mark files as copied for the next commit
1548 1543
1549 1544 Mark dest as having copies of source files. If dest is a
1550 1545 directory, copies are put in that directory. If dest is a file,
1551 1546 the source must be a single file.
1552 1547
1553 1548 By default, this command copies the contents of files as they
1554 1549 exist in the working directory. If invoked with -A/--after, the
1555 1550 operation is recorded, but no copying is performed.
1556 1551
1557 1552 This command takes effect with the next commit. To undo a copy
1558 1553 before that, see :hg:`revert`.
1559 1554
1560 1555 Returns 0 on success, 1 if errors are encountered.
1561 1556 """
1562 1557 wlock = repo.wlock(False)
1563 1558 try:
1564 1559 return cmdutil.copy(ui, repo, pats, opts)
1565 1560 finally:
1566 1561 wlock.release()
1567 1562
1568 1563 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1569 1564 def debugancestor(ui, repo, *args):
1570 1565 """find the ancestor revision of two revisions in a given index"""
1571 1566 if len(args) == 3:
1572 1567 index, rev1, rev2 = args
1573 1568 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1574 1569 lookup = r.lookup
1575 1570 elif len(args) == 2:
1576 1571 if not repo:
1577 1572 raise util.Abort(_("there is no Mercurial repository here "
1578 1573 "(.hg not found)"))
1579 1574 rev1, rev2 = args
1580 1575 r = repo.changelog
1581 1576 lookup = repo.lookup
1582 1577 else:
1583 1578 raise util.Abort(_('either two or three arguments required'))
1584 1579 a = r.ancestor(lookup(rev1), lookup(rev2))
1585 1580 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1586 1581
1587 1582 @command('debugbuilddag',
1588 1583 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1589 1584 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1590 1585 ('n', 'new-file', None, _('add new file at each rev'))],
1591 1586 _('[OPTION]... [TEXT]'))
1592 1587 def debugbuilddag(ui, repo, text=None,
1593 1588 mergeable_file=False,
1594 1589 overwritten_file=False,
1595 1590 new_file=False):
1596 1591 """builds a repo with a given DAG from scratch in the current empty repo
1597 1592
1598 1593 The description of the DAG is read from stdin if not given on the
1599 1594 command line.
1600 1595
1601 1596 Elements:
1602 1597
1603 1598 - "+n" is a linear run of n nodes based on the current default parent
1604 1599 - "." is a single node based on the current default parent
1605 1600 - "$" resets the default parent to null (implied at the start);
1606 1601 otherwise the default parent is always the last node created
1607 1602 - "<p" sets the default parent to the backref p
1608 1603 - "*p" is a fork at parent p, which is a backref
1609 1604 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1610 1605 - "/p2" is a merge of the preceding node and p2
1611 1606 - ":tag" defines a local tag for the preceding node
1612 1607 - "@branch" sets the named branch for subsequent nodes
1613 1608 - "#...\\n" is a comment up to the end of the line
1614 1609
1615 1610 Whitespace between the above elements is ignored.
1616 1611
1617 1612 A backref is either
1618 1613
1619 1614 - a number n, which references the node curr-n, where curr is the current
1620 1615 node, or
1621 1616 - the name of a local tag you placed earlier using ":tag", or
1622 1617 - empty to denote the default parent.
1623 1618
1624 1619 All string valued-elements are either strictly alphanumeric, or must
1625 1620 be enclosed in double quotes ("..."), with "\\" as escape character.
1626 1621 """
1627 1622
1628 1623 if text is None:
1629 1624 ui.status(_("reading DAG from stdin\n"))
1630 1625 text = ui.fin.read()
1631 1626
1632 1627 cl = repo.changelog
1633 1628 if len(cl) > 0:
1634 1629 raise util.Abort(_('repository is not empty'))
1635 1630
1636 1631 # determine number of revs in DAG
1637 1632 total = 0
1638 1633 for type, data in dagparser.parsedag(text):
1639 1634 if type == 'n':
1640 1635 total += 1
1641 1636
1642 1637 if mergeable_file:
1643 1638 linesperrev = 2
1644 1639 # make a file with k lines per rev
1645 1640 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1646 1641 initialmergedlines.append("")
1647 1642
1648 1643 tags = []
1649 1644
1650 1645 lock = tr = None
1651 1646 try:
1652 1647 lock = repo.lock()
1653 1648 tr = repo.transaction("builddag")
1654 1649
1655 1650 at = -1
1656 1651 atbranch = 'default'
1657 1652 nodeids = []
1658 1653 id = 0
1659 1654 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1660 1655 for type, data in dagparser.parsedag(text):
1661 1656 if type == 'n':
1662 1657 ui.note(('node %s\n' % str(data)))
1663 1658 id, ps = data
1664 1659
1665 1660 files = []
1666 1661 fctxs = {}
1667 1662
1668 1663 p2 = None
1669 1664 if mergeable_file:
1670 1665 fn = "mf"
1671 1666 p1 = repo[ps[0]]
1672 1667 if len(ps) > 1:
1673 1668 p2 = repo[ps[1]]
1674 1669 pa = p1.ancestor(p2)
1675 1670 base, local, other = [x[fn].data() for x in (pa, p1,
1676 1671 p2)]
1677 1672 m3 = simplemerge.Merge3Text(base, local, other)
1678 1673 ml = [l.strip() for l in m3.merge_lines()]
1679 1674 ml.append("")
1680 1675 elif at > 0:
1681 1676 ml = p1[fn].data().split("\n")
1682 1677 else:
1683 1678 ml = initialmergedlines
1684 1679 ml[id * linesperrev] += " r%i" % id
1685 1680 mergedtext = "\n".join(ml)
1686 1681 files.append(fn)
1687 1682 fctxs[fn] = context.memfilectx(fn, mergedtext)
1688 1683
1689 1684 if overwritten_file:
1690 1685 fn = "of"
1691 1686 files.append(fn)
1692 1687 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1693 1688
1694 1689 if new_file:
1695 1690 fn = "nf%i" % id
1696 1691 files.append(fn)
1697 1692 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1698 1693 if len(ps) > 1:
1699 1694 if not p2:
1700 1695 p2 = repo[ps[1]]
1701 1696 for fn in p2:
1702 1697 if fn.startswith("nf"):
1703 1698 files.append(fn)
1704 1699 fctxs[fn] = p2[fn]
1705 1700
1706 1701 def fctxfn(repo, cx, path):
1707 1702 return fctxs.get(path)
1708 1703
1709 1704 if len(ps) == 0 or ps[0] < 0:
1710 1705 pars = [None, None]
1711 1706 elif len(ps) == 1:
1712 1707 pars = [nodeids[ps[0]], None]
1713 1708 else:
1714 1709 pars = [nodeids[p] for p in ps]
1715 1710 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1716 1711 date=(id, 0),
1717 1712 user="debugbuilddag",
1718 1713 extra={'branch': atbranch})
1719 1714 nodeid = repo.commitctx(cx)
1720 1715 nodeids.append(nodeid)
1721 1716 at = id
1722 1717 elif type == 'l':
1723 1718 id, name = data
1724 1719 ui.note(('tag %s\n' % name))
1725 1720 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1726 1721 elif type == 'a':
1727 1722 ui.note(('branch %s\n' % data))
1728 1723 atbranch = data
1729 1724 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1730 1725 tr.close()
1731 1726
1732 1727 if tags:
1733 1728 repo.opener.write("localtags", "".join(tags))
1734 1729 finally:
1735 1730 ui.progress(_('building'), None)
1736 1731 release(tr, lock)
1737 1732
1738 1733 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1739 1734 def debugbundle(ui, bundlepath, all=None, **opts):
1740 1735 """lists the contents of a bundle"""
1741 1736 f = hg.openpath(ui, bundlepath)
1742 1737 try:
1743 1738 gen = changegroup.readbundle(f, bundlepath)
1744 1739 if all:
1745 1740 ui.write(("format: id, p1, p2, cset, delta base, len(delta)\n"))
1746 1741
1747 1742 def showchunks(named):
1748 1743 ui.write("\n%s\n" % named)
1749 1744 chain = None
1750 1745 while True:
1751 1746 chunkdata = gen.deltachunk(chain)
1752 1747 if not chunkdata:
1753 1748 break
1754 1749 node = chunkdata['node']
1755 1750 p1 = chunkdata['p1']
1756 1751 p2 = chunkdata['p2']
1757 1752 cs = chunkdata['cs']
1758 1753 deltabase = chunkdata['deltabase']
1759 1754 delta = chunkdata['delta']
1760 1755 ui.write("%s %s %s %s %s %s\n" %
1761 1756 (hex(node), hex(p1), hex(p2),
1762 1757 hex(cs), hex(deltabase), len(delta)))
1763 1758 chain = node
1764 1759
1765 1760 chunkdata = gen.changelogheader()
1766 1761 showchunks("changelog")
1767 1762 chunkdata = gen.manifestheader()
1768 1763 showchunks("manifest")
1769 1764 while True:
1770 1765 chunkdata = gen.filelogheader()
1771 1766 if not chunkdata:
1772 1767 break
1773 1768 fname = chunkdata['filename']
1774 1769 showchunks(fname)
1775 1770 else:
1776 1771 chunkdata = gen.changelogheader()
1777 1772 chain = None
1778 1773 while True:
1779 1774 chunkdata = gen.deltachunk(chain)
1780 1775 if not chunkdata:
1781 1776 break
1782 1777 node = chunkdata['node']
1783 1778 ui.write("%s\n" % hex(node))
1784 1779 chain = node
1785 1780 finally:
1786 1781 f.close()
1787 1782
1788 1783 @command('debugcheckstate', [], '')
1789 1784 def debugcheckstate(ui, repo):
1790 1785 """validate the correctness of the current dirstate"""
1791 1786 parent1, parent2 = repo.dirstate.parents()
1792 1787 m1 = repo[parent1].manifest()
1793 1788 m2 = repo[parent2].manifest()
1794 1789 errors = 0
1795 1790 for f in repo.dirstate:
1796 1791 state = repo.dirstate[f]
1797 1792 if state in "nr" and f not in m1:
1798 1793 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1799 1794 errors += 1
1800 1795 if state in "a" and f in m1:
1801 1796 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1802 1797 errors += 1
1803 1798 if state in "m" and f not in m1 and f not in m2:
1804 1799 ui.warn(_("%s in state %s, but not in either manifest\n") %
1805 1800 (f, state))
1806 1801 errors += 1
1807 1802 for f in m1:
1808 1803 state = repo.dirstate[f]
1809 1804 if state not in "nrm":
1810 1805 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1811 1806 errors += 1
1812 1807 if errors:
1813 1808 error = _(".hg/dirstate inconsistent with current parent's manifest")
1814 1809 raise util.Abort(error)
1815 1810
1816 1811 @command('debugcommands', [], _('[COMMAND]'))
1817 1812 def debugcommands(ui, cmd='', *args):
1818 1813 """list all available commands and options"""
1819 1814 for cmd, vals in sorted(table.iteritems()):
1820 1815 cmd = cmd.split('|')[0].strip('^')
1821 1816 opts = ', '.join([i[1] for i in vals[1]])
1822 1817 ui.write('%s: %s\n' % (cmd, opts))
1823 1818
1824 1819 @command('debugcomplete',
1825 1820 [('o', 'options', None, _('show the command options'))],
1826 1821 _('[-o] CMD'))
1827 1822 def debugcomplete(ui, cmd='', **opts):
1828 1823 """returns the completion list associated with the given command"""
1829 1824
1830 1825 if opts.get('options'):
1831 1826 options = []
1832 1827 otables = [globalopts]
1833 1828 if cmd:
1834 1829 aliases, entry = cmdutil.findcmd(cmd, table, False)
1835 1830 otables.append(entry[1])
1836 1831 for t in otables:
1837 1832 for o in t:
1838 1833 if "(DEPRECATED)" in o[3]:
1839 1834 continue
1840 1835 if o[0]:
1841 1836 options.append('-%s' % o[0])
1842 1837 options.append('--%s' % o[1])
1843 1838 ui.write("%s\n" % "\n".join(options))
1844 1839 return
1845 1840
1846 1841 cmdlist = cmdutil.findpossible(cmd, table)
1847 1842 if ui.verbose:
1848 1843 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1849 1844 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1850 1845
1851 1846 @command('debugdag',
1852 1847 [('t', 'tags', None, _('use tags as labels')),
1853 1848 ('b', 'branches', None, _('annotate with branch names')),
1854 1849 ('', 'dots', None, _('use dots for runs')),
1855 1850 ('s', 'spaces', None, _('separate elements by spaces'))],
1856 1851 _('[OPTION]... [FILE [REV]...]'))
1857 1852 def debugdag(ui, repo, file_=None, *revs, **opts):
1858 1853 """format the changelog or an index DAG as a concise textual description
1859 1854
1860 1855 If you pass a revlog index, the revlog's DAG is emitted. If you list
1861 1856 revision numbers, they get labeled in the output as rN.
1862 1857
1863 1858 Otherwise, the changelog DAG of the current repo is emitted.
1864 1859 """
1865 1860 spaces = opts.get('spaces')
1866 1861 dots = opts.get('dots')
1867 1862 if file_:
1868 1863 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1869 1864 revs = set((int(r) for r in revs))
1870 1865 def events():
1871 1866 for r in rlog:
1872 1867 yield 'n', (r, list(set(p for p in rlog.parentrevs(r)
1873 1868 if p != -1)))
1874 1869 if r in revs:
1875 1870 yield 'l', (r, "r%i" % r)
1876 1871 elif repo:
1877 1872 cl = repo.changelog
1878 1873 tags = opts.get('tags')
1879 1874 branches = opts.get('branches')
1880 1875 if tags:
1881 1876 labels = {}
1882 1877 for l, n in repo.tags().items():
1883 1878 labels.setdefault(cl.rev(n), []).append(l)
1884 1879 def events():
1885 1880 b = "default"
1886 1881 for r in cl:
1887 1882 if branches:
1888 1883 newb = cl.read(cl.node(r))[5]['branch']
1889 1884 if newb != b:
1890 1885 yield 'a', newb
1891 1886 b = newb
1892 1887 yield 'n', (r, list(set(p for p in cl.parentrevs(r)
1893 1888 if p != -1)))
1894 1889 if tags:
1895 1890 ls = labels.get(r)
1896 1891 if ls:
1897 1892 for l in ls:
1898 1893 yield 'l', (r, l)
1899 1894 else:
1900 1895 raise util.Abort(_('need repo for changelog dag'))
1901 1896
1902 1897 for line in dagparser.dagtextlines(events(),
1903 1898 addspaces=spaces,
1904 1899 wraplabels=True,
1905 1900 wrapannotations=True,
1906 1901 wrapnonlinear=dots,
1907 1902 usedots=dots,
1908 1903 maxlinewidth=70):
1909 1904 ui.write(line)
1910 1905 ui.write("\n")
1911 1906
1912 1907 @command('debugdata',
1913 1908 [('c', 'changelog', False, _('open changelog')),
1914 1909 ('m', 'manifest', False, _('open manifest'))],
1915 1910 _('-c|-m|FILE REV'))
1916 1911 def debugdata(ui, repo, file_, rev=None, **opts):
1917 1912 """dump the contents of a data file revision"""
1918 1913 if opts.get('changelog') or opts.get('manifest'):
1919 1914 file_, rev = None, file_
1920 1915 elif rev is None:
1921 1916 raise error.CommandError('debugdata', _('invalid arguments'))
1922 1917 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1923 1918 try:
1924 1919 ui.write(r.revision(r.lookup(rev)))
1925 1920 except KeyError:
1926 1921 raise util.Abort(_('invalid revision identifier %s') % rev)
1927 1922
1928 1923 @command('debugdate',
1929 1924 [('e', 'extended', None, _('try extended date formats'))],
1930 1925 _('[-e] DATE [RANGE]'))
1931 1926 def debugdate(ui, date, range=None, **opts):
1932 1927 """parse and display a date"""
1933 1928 if opts["extended"]:
1934 1929 d = util.parsedate(date, util.extendeddateformats)
1935 1930 else:
1936 1931 d = util.parsedate(date)
1937 1932 ui.write(("internal: %s %s\n") % d)
1938 1933 ui.write(("standard: %s\n") % util.datestr(d))
1939 1934 if range:
1940 1935 m = util.matchdate(range)
1941 1936 ui.write(("match: %s\n") % m(d[0]))
1942 1937
1943 1938 @command('debugdiscovery',
1944 1939 [('', 'old', None, _('use old-style discovery')),
1945 1940 ('', 'nonheads', None,
1946 1941 _('use old-style discovery with non-heads included')),
1947 1942 ] + remoteopts,
1948 1943 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1949 1944 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1950 1945 """runs the changeset discovery protocol in isolation"""
1951 1946 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
1952 1947 opts.get('branch'))
1953 1948 remote = hg.peer(repo, opts, remoteurl)
1954 1949 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1955 1950
1956 1951 # make sure tests are repeatable
1957 1952 random.seed(12323)
1958 1953
1959 1954 def doit(localheads, remoteheads, remote=remote):
1960 1955 if opts.get('old'):
1961 1956 if localheads:
1962 1957 raise util.Abort('cannot use localheads with old style '
1963 1958 'discovery')
1964 1959 if not util.safehasattr(remote, 'branches'):
1965 1960 # enable in-client legacy support
1966 1961 remote = localrepo.locallegacypeer(remote.local())
1967 1962 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1968 1963 force=True)
1969 1964 common = set(common)
1970 1965 if not opts.get('nonheads'):
1971 1966 ui.write(("unpruned common: %s\n") %
1972 1967 " ".join(sorted(short(n) for n in common)))
1973 1968 dag = dagutil.revlogdag(repo.changelog)
1974 1969 all = dag.ancestorset(dag.internalizeall(common))
1975 1970 common = dag.externalizeall(dag.headsetofconnecteds(all))
1976 1971 else:
1977 1972 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1978 1973 common = set(common)
1979 1974 rheads = set(hds)
1980 1975 lheads = set(repo.heads())
1981 1976 ui.write(("common heads: %s\n") %
1982 1977 " ".join(sorted(short(n) for n in common)))
1983 1978 if lheads <= common:
1984 1979 ui.write(("local is subset\n"))
1985 1980 elif rheads <= common:
1986 1981 ui.write(("remote is subset\n"))
1987 1982
1988 1983 serverlogs = opts.get('serverlog')
1989 1984 if serverlogs:
1990 1985 for filename in serverlogs:
1991 1986 logfile = open(filename, 'r')
1992 1987 try:
1993 1988 line = logfile.readline()
1994 1989 while line:
1995 1990 parts = line.strip().split(';')
1996 1991 op = parts[1]
1997 1992 if op == 'cg':
1998 1993 pass
1999 1994 elif op == 'cgss':
2000 1995 doit(parts[2].split(' '), parts[3].split(' '))
2001 1996 elif op == 'unb':
2002 1997 doit(parts[3].split(' '), parts[2].split(' '))
2003 1998 line = logfile.readline()
2004 1999 finally:
2005 2000 logfile.close()
2006 2001
2007 2002 else:
2008 2003 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
2009 2004 opts.get('remote_head'))
2010 2005 localrevs = opts.get('local_head')
2011 2006 doit(localrevs, remoterevs)
2012 2007
2013 2008 @command('debugfileset',
2014 2009 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
2015 2010 _('[-r REV] FILESPEC'))
2016 2011 def debugfileset(ui, repo, expr, **opts):
2017 2012 '''parse and apply a fileset specification'''
2018 2013 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2019 2014 if ui.verbose:
2020 2015 tree = fileset.parse(expr)[0]
2021 2016 ui.note(tree, "\n")
2022 2017
2023 2018 for f in ctx.getfileset(expr):
2024 2019 ui.write("%s\n" % f)
2025 2020
2026 2021 @command('debugfsinfo', [], _('[PATH]'))
2027 2022 def debugfsinfo(ui, path="."):
2028 2023 """show information detected about current filesystem"""
2029 2024 util.writefile('.debugfsinfo', '')
2030 2025 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
2031 2026 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
2032 2027 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
2033 2028 ui.write(('case-sensitive: %s\n') % (util.checkcase('.debugfsinfo')
2034 2029 and 'yes' or 'no'))
2035 2030 os.unlink('.debugfsinfo')
2036 2031
2037 2032 @command('debuggetbundle',
2038 2033 [('H', 'head', [], _('id of head node'), _('ID')),
2039 2034 ('C', 'common', [], _('id of common node'), _('ID')),
2040 2035 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
2041 2036 _('REPO FILE [-H|-C ID]...'))
2042 2037 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
2043 2038 """retrieves a bundle from a repo
2044 2039
2045 2040 Every ID must be a full-length hex node id string. Saves the bundle to the
2046 2041 given file.
2047 2042 """
2048 2043 repo = hg.peer(ui, opts, repopath)
2049 2044 if not repo.capable('getbundle'):
2050 2045 raise util.Abort("getbundle() not supported by target repository")
2051 2046 args = {}
2052 2047 if common:
2053 2048 args['common'] = [bin(s) for s in common]
2054 2049 if head:
2055 2050 args['heads'] = [bin(s) for s in head]
2056 2051 # TODO: get desired bundlecaps from command line.
2057 2052 args['bundlecaps'] = None
2058 2053 bundle = repo.getbundle('debug', **args)
2059 2054
2060 2055 bundletype = opts.get('type', 'bzip2').lower()
2061 2056 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
2062 2057 bundletype = btypes.get(bundletype)
2063 2058 if bundletype not in changegroup.bundletypes:
2064 2059 raise util.Abort(_('unknown bundle type specified with --type'))
2065 2060 changegroup.writebundle(bundle, bundlepath, bundletype)
2066 2061
2067 2062 @command('debugignore', [], '')
2068 2063 def debugignore(ui, repo, *values, **opts):
2069 2064 """display the combined ignore pattern"""
2070 2065 ignore = repo.dirstate._ignore
2071 2066 includepat = getattr(ignore, 'includepat', None)
2072 2067 if includepat is not None:
2073 2068 ui.write("%s\n" % includepat)
2074 2069 else:
2075 2070 raise util.Abort(_("no ignore patterns found"))
2076 2071
2077 2072 @command('debugindex',
2078 2073 [('c', 'changelog', False, _('open changelog')),
2079 2074 ('m', 'manifest', False, _('open manifest')),
2080 2075 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2081 2076 _('[-f FORMAT] -c|-m|FILE'))
2082 2077 def debugindex(ui, repo, file_=None, **opts):
2083 2078 """dump the contents of an index file"""
2084 2079 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
2085 2080 format = opts.get('format', 0)
2086 2081 if format not in (0, 1):
2087 2082 raise util.Abort(_("unknown format %d") % format)
2088 2083
2089 2084 generaldelta = r.version & revlog.REVLOGGENERALDELTA
2090 2085 if generaldelta:
2091 2086 basehdr = ' delta'
2092 2087 else:
2093 2088 basehdr = ' base'
2094 2089
2095 2090 if format == 0:
2096 2091 ui.write(" rev offset length " + basehdr + " linkrev"
2097 2092 " nodeid p1 p2\n")
2098 2093 elif format == 1:
2099 2094 ui.write(" rev flag offset length"
2100 2095 " size " + basehdr + " link p1 p2"
2101 2096 " nodeid\n")
2102 2097
2103 2098 for i in r:
2104 2099 node = r.node(i)
2105 2100 if generaldelta:
2106 2101 base = r.deltaparent(i)
2107 2102 else:
2108 2103 base = r.chainbase(i)
2109 2104 if format == 0:
2110 2105 try:
2111 2106 pp = r.parents(node)
2112 2107 except Exception:
2113 2108 pp = [nullid, nullid]
2114 2109 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
2115 2110 i, r.start(i), r.length(i), base, r.linkrev(i),
2116 2111 short(node), short(pp[0]), short(pp[1])))
2117 2112 elif format == 1:
2118 2113 pr = r.parentrevs(i)
2119 2114 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
2120 2115 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2121 2116 base, r.linkrev(i), pr[0], pr[1], short(node)))
2122 2117
2123 2118 @command('debugindexdot', [], _('FILE'))
2124 2119 def debugindexdot(ui, repo, file_):
2125 2120 """dump an index DAG as a graphviz dot file"""
2126 2121 r = None
2127 2122 if repo:
2128 2123 filelog = repo.file(file_)
2129 2124 if len(filelog):
2130 2125 r = filelog
2131 2126 if not r:
2132 2127 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
2133 2128 ui.write(("digraph G {\n"))
2134 2129 for i in r:
2135 2130 node = r.node(i)
2136 2131 pp = r.parents(node)
2137 2132 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
2138 2133 if pp[1] != nullid:
2139 2134 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
2140 2135 ui.write("}\n")
2141 2136
2142 2137 @command('debuginstall', [], '')
2143 2138 def debuginstall(ui):
2144 2139 '''test Mercurial installation
2145 2140
2146 2141 Returns 0 on success.
2147 2142 '''
2148 2143
2149 2144 def writetemp(contents):
2150 2145 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
2151 2146 f = os.fdopen(fd, "wb")
2152 2147 f.write(contents)
2153 2148 f.close()
2154 2149 return name
2155 2150
2156 2151 problems = 0
2157 2152
2158 2153 # encoding
2159 2154 ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
2160 2155 try:
2161 2156 encoding.fromlocal("test")
2162 2157 except util.Abort, inst:
2163 2158 ui.write(" %s\n" % inst)
2164 2159 ui.write(_(" (check that your locale is properly set)\n"))
2165 2160 problems += 1
2166 2161
2167 2162 # Python lib
2168 2163 ui.status(_("checking Python lib (%s)...\n")
2169 2164 % os.path.dirname(os.__file__))
2170 2165
2171 2166 # compiled modules
2172 2167 ui.status(_("checking installed modules (%s)...\n")
2173 2168 % os.path.dirname(__file__))
2174 2169 try:
2175 2170 import bdiff, mpatch, base85, osutil
2176 2171 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2177 2172 except Exception, inst:
2178 2173 ui.write(" %s\n" % inst)
2179 2174 ui.write(_(" One or more extensions could not be found"))
2180 2175 ui.write(_(" (check that you compiled the extensions)\n"))
2181 2176 problems += 1
2182 2177
2183 2178 # templates
2184 2179 import templater
2185 2180 p = templater.templatepath()
2186 2181 ui.status(_("checking templates (%s)...\n") % ' '.join(p))
2187 2182 if p:
2188 2183 m = templater.templatepath("map-cmdline.default")
2189 2184 if m:
2190 2185 # template found, check if it is working
2191 2186 try:
2192 2187 templater.templater(m)
2193 2188 except Exception, inst:
2194 2189 ui.write(" %s\n" % inst)
2195 2190 p = None
2196 2191 else:
2197 2192 ui.write(_(" template 'default' not found\n"))
2198 2193 p = None
2199 2194 else:
2200 2195 ui.write(_(" no template directories found\n"))
2201 2196 if not p:
2202 2197 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
2203 2198 problems += 1
2204 2199
2205 2200 # editor
2206 2201 ui.status(_("checking commit editor...\n"))
2207 2202 editor = ui.geteditor()
2208 2203 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
2209 2204 if not cmdpath:
2210 2205 if editor == 'vi':
2211 2206 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
2212 2207 ui.write(_(" (specify a commit editor in your configuration"
2213 2208 " file)\n"))
2214 2209 else:
2215 2210 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
2216 2211 ui.write(_(" (specify a commit editor in your configuration"
2217 2212 " file)\n"))
2218 2213 problems += 1
2219 2214
2220 2215 # check username
2221 2216 ui.status(_("checking username...\n"))
2222 2217 try:
2223 2218 ui.username()
2224 2219 except util.Abort, e:
2225 2220 ui.write(" %s\n" % e)
2226 2221 ui.write(_(" (specify a username in your configuration file)\n"))
2227 2222 problems += 1
2228 2223
2229 2224 if not problems:
2230 2225 ui.status(_("no problems detected\n"))
2231 2226 else:
2232 2227 ui.write(_("%s problems detected,"
2233 2228 " please check your install!\n") % problems)
2234 2229
2235 2230 return problems
2236 2231
2237 2232 @command('debugknown', [], _('REPO ID...'))
2238 2233 def debugknown(ui, repopath, *ids, **opts):
2239 2234 """test whether node ids are known to a repo
2240 2235
2241 2236 Every ID must be a full-length hex node id string. Returns a list of 0s
2242 2237 and 1s indicating unknown/known.
2243 2238 """
2244 2239 repo = hg.peer(ui, opts, repopath)
2245 2240 if not repo.capable('known'):
2246 2241 raise util.Abort("known() not supported by target repository")
2247 2242 flags = repo.known([bin(s) for s in ids])
2248 2243 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2249 2244
2250 2245 @command('debuglabelcomplete', [], _('LABEL...'))
2251 2246 def debuglabelcomplete(ui, repo, *args):
2252 2247 '''complete "labels" - tags, open branch names, bookmark names'''
2253 2248
2254 2249 labels = set()
2255 2250 labels.update(t[0] for t in repo.tagslist())
2256 2251 labels.update(repo._bookmarks.keys())
2257 2252 labels.update(tag for (tag, heads, tip, closed)
2258 2253 in repo.branchmap().iterbranches() if not closed)
2259 2254 completions = set()
2260 2255 if not args:
2261 2256 args = ['']
2262 2257 for a in args:
2263 2258 completions.update(l for l in labels if l.startswith(a))
2264 2259 ui.write('\n'.join(sorted(completions)))
2265 2260 ui.write('\n')
2266 2261
2267 2262 @command('debugobsolete',
2268 2263 [('', 'flags', 0, _('markers flag')),
2269 2264 ] + commitopts2,
2270 2265 _('[OBSOLETED [REPLACEMENT] [REPL... ]'))
2271 2266 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2272 2267 """create arbitrary obsolete marker
2273 2268
2274 2269 With no arguments, displays the list of obsolescence markers."""
2275 2270 def parsenodeid(s):
2276 2271 try:
2277 2272 # We do not use revsingle/revrange functions here to accept
2278 2273 # arbitrary node identifiers, possibly not present in the
2279 2274 # local repository.
2280 2275 n = bin(s)
2281 2276 if len(n) != len(nullid):
2282 2277 raise TypeError()
2283 2278 return n
2284 2279 except TypeError:
2285 2280 raise util.Abort('changeset references must be full hexadecimal '
2286 2281 'node identifiers')
2287 2282
2288 2283 if precursor is not None:
2289 2284 metadata = {}
2290 2285 if 'date' in opts:
2291 2286 metadata['date'] = opts['date']
2292 2287 metadata['user'] = opts['user'] or ui.username()
2293 2288 succs = tuple(parsenodeid(succ) for succ in successors)
2294 2289 l = repo.lock()
2295 2290 try:
2296 2291 tr = repo.transaction('debugobsolete')
2297 2292 try:
2298 2293 repo.obsstore.create(tr, parsenodeid(precursor), succs,
2299 2294 opts['flags'], metadata)
2300 2295 tr.close()
2301 2296 finally:
2302 2297 tr.release()
2303 2298 finally:
2304 2299 l.release()
2305 2300 else:
2306 2301 for m in obsolete.allmarkers(repo):
2307 2302 cmdutil.showmarker(ui, m)
2308 2303
2309 2304 @command('debugpathcomplete',
2310 2305 [('f', 'full', None, _('complete an entire path')),
2311 2306 ('n', 'normal', None, _('show only normal files')),
2312 2307 ('a', 'added', None, _('show only added files')),
2313 2308 ('r', 'removed', None, _('show only removed files'))],
2314 2309 _('FILESPEC...'))
2315 2310 def debugpathcomplete(ui, repo, *specs, **opts):
2316 2311 '''complete part or all of a tracked path
2317 2312
2318 2313 This command supports shells that offer path name completion. It
2319 2314 currently completes only files already known to the dirstate.
2320 2315
2321 2316 Completion extends only to the next path segment unless
2322 2317 --full is specified, in which case entire paths are used.'''
2323 2318
2324 2319 def complete(path, acceptable):
2325 2320 dirstate = repo.dirstate
2326 2321 spec = os.path.normpath(os.path.join(os.getcwd(), path))
2327 2322 rootdir = repo.root + os.sep
2328 2323 if spec != repo.root and not spec.startswith(rootdir):
2329 2324 return [], []
2330 2325 if os.path.isdir(spec):
2331 2326 spec += '/'
2332 2327 spec = spec[len(rootdir):]
2333 2328 fixpaths = os.sep != '/'
2334 2329 if fixpaths:
2335 2330 spec = spec.replace(os.sep, '/')
2336 2331 speclen = len(spec)
2337 2332 fullpaths = opts['full']
2338 2333 files, dirs = set(), set()
2339 2334 adddir, addfile = dirs.add, files.add
2340 2335 for f, st in dirstate.iteritems():
2341 2336 if f.startswith(spec) and st[0] in acceptable:
2342 2337 if fixpaths:
2343 2338 f = f.replace('/', os.sep)
2344 2339 if fullpaths:
2345 2340 addfile(f)
2346 2341 continue
2347 2342 s = f.find(os.sep, speclen)
2348 2343 if s >= 0:
2349 2344 adddir(f[:s])
2350 2345 else:
2351 2346 addfile(f)
2352 2347 return files, dirs
2353 2348
2354 2349 acceptable = ''
2355 2350 if opts['normal']:
2356 2351 acceptable += 'nm'
2357 2352 if opts['added']:
2358 2353 acceptable += 'a'
2359 2354 if opts['removed']:
2360 2355 acceptable += 'r'
2361 2356 cwd = repo.getcwd()
2362 2357 if not specs:
2363 2358 specs = ['.']
2364 2359
2365 2360 files, dirs = set(), set()
2366 2361 for spec in specs:
2367 2362 f, d = complete(spec, acceptable or 'nmar')
2368 2363 files.update(f)
2369 2364 dirs.update(d)
2370 2365 files.update(dirs)
2371 2366 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
2372 2367 ui.write('\n')
2373 2368
2374 2369 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
2375 2370 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2376 2371 '''access the pushkey key/value protocol
2377 2372
2378 2373 With two args, list the keys in the given namespace.
2379 2374
2380 2375 With five args, set a key to new if it currently is set to old.
2381 2376 Reports success or failure.
2382 2377 '''
2383 2378
2384 2379 target = hg.peer(ui, {}, repopath)
2385 2380 if keyinfo:
2386 2381 key, old, new = keyinfo
2387 2382 r = target.pushkey(namespace, key, old, new)
2388 2383 ui.status(str(r) + '\n')
2389 2384 return not r
2390 2385 else:
2391 2386 for k, v in sorted(target.listkeys(namespace).iteritems()):
2392 2387 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2393 2388 v.encode('string-escape')))
2394 2389
2395 2390 @command('debugpvec', [], _('A B'))
2396 2391 def debugpvec(ui, repo, a, b=None):
2397 2392 ca = scmutil.revsingle(repo, a)
2398 2393 cb = scmutil.revsingle(repo, b)
2399 2394 pa = pvec.ctxpvec(ca)
2400 2395 pb = pvec.ctxpvec(cb)
2401 2396 if pa == pb:
2402 2397 rel = "="
2403 2398 elif pa > pb:
2404 2399 rel = ">"
2405 2400 elif pa < pb:
2406 2401 rel = "<"
2407 2402 elif pa | pb:
2408 2403 rel = "|"
2409 2404 ui.write(_("a: %s\n") % pa)
2410 2405 ui.write(_("b: %s\n") % pb)
2411 2406 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2412 2407 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2413 2408 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2414 2409 pa.distance(pb), rel))
2415 2410
2416 2411 @command('debugrebuilddirstate|debugrebuildstate',
2417 2412 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2418 2413 _('[-r REV]'))
2419 2414 def debugrebuilddirstate(ui, repo, rev):
2420 2415 """rebuild the dirstate as it would look like for the given revision
2421 2416
2422 2417 If no revision is specified the first current parent will be used.
2423 2418
2424 2419 The dirstate will be set to the files of the given revision.
2425 2420 The actual working directory content or existing dirstate
2426 2421 information such as adds or removes is not considered.
2427 2422
2428 2423 One use of this command is to make the next :hg:`status` invocation
2429 2424 check the actual file content.
2430 2425 """
2431 2426 ctx = scmutil.revsingle(repo, rev)
2432 2427 wlock = repo.wlock()
2433 2428 try:
2434 2429 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2435 2430 finally:
2436 2431 wlock.release()
2437 2432
2438 2433 @command('debugrename',
2439 2434 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2440 2435 _('[-r REV] FILE'))
2441 2436 def debugrename(ui, repo, file1, *pats, **opts):
2442 2437 """dump rename information"""
2443 2438
2444 2439 ctx = scmutil.revsingle(repo, opts.get('rev'))
2445 2440 m = scmutil.match(ctx, (file1,) + pats, opts)
2446 2441 for abs in ctx.walk(m):
2447 2442 fctx = ctx[abs]
2448 2443 o = fctx.filelog().renamed(fctx.filenode())
2449 2444 rel = m.rel(abs)
2450 2445 if o:
2451 2446 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2452 2447 else:
2453 2448 ui.write(_("%s not renamed\n") % rel)
2454 2449
2455 2450 @command('debugrevlog',
2456 2451 [('c', 'changelog', False, _('open changelog')),
2457 2452 ('m', 'manifest', False, _('open manifest')),
2458 2453 ('d', 'dump', False, _('dump index data'))],
2459 2454 _('-c|-m|FILE'))
2460 2455 def debugrevlog(ui, repo, file_=None, **opts):
2461 2456 """show data and statistics about a revlog"""
2462 2457 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2463 2458
2464 2459 if opts.get("dump"):
2465 2460 numrevs = len(r)
2466 2461 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2467 2462 " rawsize totalsize compression heads\n")
2468 2463 ts = 0
2469 2464 heads = set()
2470 2465 for rev in xrange(numrevs):
2471 2466 dbase = r.deltaparent(rev)
2472 2467 if dbase == -1:
2473 2468 dbase = rev
2474 2469 cbase = r.chainbase(rev)
2475 2470 p1, p2 = r.parentrevs(rev)
2476 2471 rs = r.rawsize(rev)
2477 2472 ts = ts + rs
2478 2473 heads -= set(r.parentrevs(rev))
2479 2474 heads.add(rev)
2480 2475 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
2481 2476 (rev, p1, p2, r.start(rev), r.end(rev),
2482 2477 r.start(dbase), r.start(cbase),
2483 2478 r.start(p1), r.start(p2),
2484 2479 rs, ts, ts / r.end(rev), len(heads)))
2485 2480 return 0
2486 2481
2487 2482 v = r.version
2488 2483 format = v & 0xFFFF
2489 2484 flags = []
2490 2485 gdelta = False
2491 2486 if v & revlog.REVLOGNGINLINEDATA:
2492 2487 flags.append('inline')
2493 2488 if v & revlog.REVLOGGENERALDELTA:
2494 2489 gdelta = True
2495 2490 flags.append('generaldelta')
2496 2491 if not flags:
2497 2492 flags = ['(none)']
2498 2493
2499 2494 nummerges = 0
2500 2495 numfull = 0
2501 2496 numprev = 0
2502 2497 nump1 = 0
2503 2498 nump2 = 0
2504 2499 numother = 0
2505 2500 nump1prev = 0
2506 2501 nump2prev = 0
2507 2502 chainlengths = []
2508 2503
2509 2504 datasize = [None, 0, 0L]
2510 2505 fullsize = [None, 0, 0L]
2511 2506 deltasize = [None, 0, 0L]
2512 2507
2513 2508 def addsize(size, l):
2514 2509 if l[0] is None or size < l[0]:
2515 2510 l[0] = size
2516 2511 if size > l[1]:
2517 2512 l[1] = size
2518 2513 l[2] += size
2519 2514
2520 2515 numrevs = len(r)
2521 2516 for rev in xrange(numrevs):
2522 2517 p1, p2 = r.parentrevs(rev)
2523 2518 delta = r.deltaparent(rev)
2524 2519 if format > 0:
2525 2520 addsize(r.rawsize(rev), datasize)
2526 2521 if p2 != nullrev:
2527 2522 nummerges += 1
2528 2523 size = r.length(rev)
2529 2524 if delta == nullrev:
2530 2525 chainlengths.append(0)
2531 2526 numfull += 1
2532 2527 addsize(size, fullsize)
2533 2528 else:
2534 2529 chainlengths.append(chainlengths[delta] + 1)
2535 2530 addsize(size, deltasize)
2536 2531 if delta == rev - 1:
2537 2532 numprev += 1
2538 2533 if delta == p1:
2539 2534 nump1prev += 1
2540 2535 elif delta == p2:
2541 2536 nump2prev += 1
2542 2537 elif delta == p1:
2543 2538 nump1 += 1
2544 2539 elif delta == p2:
2545 2540 nump2 += 1
2546 2541 elif delta != nullrev:
2547 2542 numother += 1
2548 2543
2549 2544 # Adjust size min value for empty cases
2550 2545 for size in (datasize, fullsize, deltasize):
2551 2546 if size[0] is None:
2552 2547 size[0] = 0
2553 2548
2554 2549 numdeltas = numrevs - numfull
2555 2550 numoprev = numprev - nump1prev - nump2prev
2556 2551 totalrawsize = datasize[2]
2557 2552 datasize[2] /= numrevs
2558 2553 fulltotal = fullsize[2]
2559 2554 fullsize[2] /= numfull
2560 2555 deltatotal = deltasize[2]
2561 2556 if numrevs - numfull > 0:
2562 2557 deltasize[2] /= numrevs - numfull
2563 2558 totalsize = fulltotal + deltatotal
2564 2559 avgchainlen = sum(chainlengths) / numrevs
2565 2560 compratio = totalrawsize / totalsize
2566 2561
2567 2562 basedfmtstr = '%%%dd\n'
2568 2563 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2569 2564
2570 2565 def dfmtstr(max):
2571 2566 return basedfmtstr % len(str(max))
2572 2567 def pcfmtstr(max, padding=0):
2573 2568 return basepcfmtstr % (len(str(max)), ' ' * padding)
2574 2569
2575 2570 def pcfmt(value, total):
2576 2571 return (value, 100 * float(value) / total)
2577 2572
2578 2573 ui.write(('format : %d\n') % format)
2579 2574 ui.write(('flags : %s\n') % ', '.join(flags))
2580 2575
2581 2576 ui.write('\n')
2582 2577 fmt = pcfmtstr(totalsize)
2583 2578 fmt2 = dfmtstr(totalsize)
2584 2579 ui.write(('revisions : ') + fmt2 % numrevs)
2585 2580 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2586 2581 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2587 2582 ui.write(('revisions : ') + fmt2 % numrevs)
2588 2583 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
2589 2584 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2590 2585 ui.write(('revision size : ') + fmt2 % totalsize)
2591 2586 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
2592 2587 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2593 2588
2594 2589 ui.write('\n')
2595 2590 fmt = dfmtstr(max(avgchainlen, compratio))
2596 2591 ui.write(('avg chain length : ') + fmt % avgchainlen)
2597 2592 ui.write(('compression ratio : ') + fmt % compratio)
2598 2593
2599 2594 if format > 0:
2600 2595 ui.write('\n')
2601 2596 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2602 2597 % tuple(datasize))
2603 2598 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2604 2599 % tuple(fullsize))
2605 2600 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2606 2601 % tuple(deltasize))
2607 2602
2608 2603 if numdeltas > 0:
2609 2604 ui.write('\n')
2610 2605 fmt = pcfmtstr(numdeltas)
2611 2606 fmt2 = pcfmtstr(numdeltas, 4)
2612 2607 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2613 2608 if numprev > 0:
2614 2609 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2615 2610 numprev))
2616 2611 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2617 2612 numprev))
2618 2613 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2619 2614 numprev))
2620 2615 if gdelta:
2621 2616 ui.write(('deltas against p1 : ')
2622 2617 + fmt % pcfmt(nump1, numdeltas))
2623 2618 ui.write(('deltas against p2 : ')
2624 2619 + fmt % pcfmt(nump2, numdeltas))
2625 2620 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2626 2621 numdeltas))
2627 2622
2628 2623 @command('debugrevspec',
2629 2624 [('', 'optimize', None, _('print parsed tree after optimizing'))],
2630 2625 ('REVSPEC'))
2631 2626 def debugrevspec(ui, repo, expr, **opts):
2632 2627 """parse and apply a revision specification
2633 2628
2634 2629 Use --verbose to print the parsed tree before and after aliases
2635 2630 expansion.
2636 2631 """
2637 2632 if ui.verbose:
2638 2633 tree = revset.parse(expr)[0]
2639 2634 ui.note(revset.prettyformat(tree), "\n")
2640 2635 newtree = revset.findaliases(ui, tree)
2641 2636 if newtree != tree:
2642 2637 ui.note(revset.prettyformat(newtree), "\n")
2643 2638 if opts["optimize"]:
2644 2639 weight, optimizedtree = revset.optimize(newtree, True)
2645 2640 ui.note("* optimized:\n", revset.prettyformat(optimizedtree), "\n")
2646 2641 func = revset.match(ui, expr)
2647 2642 for c in func(repo, revset.spanset(repo)):
2648 2643 ui.write("%s\n" % c)
2649 2644
2650 2645 @command('debugsetparents', [], _('REV1 [REV2]'))
2651 2646 def debugsetparents(ui, repo, rev1, rev2=None):
2652 2647 """manually set the parents of the current working directory
2653 2648
2654 2649 This is useful for writing repository conversion tools, but should
2655 2650 be used with care.
2656 2651
2657 2652 Returns 0 on success.
2658 2653 """
2659 2654
2660 2655 r1 = scmutil.revsingle(repo, rev1).node()
2661 2656 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2662 2657
2663 2658 wlock = repo.wlock()
2664 2659 try:
2665 2660 repo.setparents(r1, r2)
2666 2661 finally:
2667 2662 wlock.release()
2668 2663
2669 2664 @command('debugdirstate|debugstate',
2670 2665 [('', 'nodates', None, _('do not display the saved mtime')),
2671 2666 ('', 'datesort', None, _('sort by saved mtime'))],
2672 2667 _('[OPTION]...'))
2673 2668 def debugstate(ui, repo, nodates=None, datesort=None):
2674 2669 """show the contents of the current dirstate"""
2675 2670 timestr = ""
2676 2671 showdate = not nodates
2677 2672 if datesort:
2678 2673 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2679 2674 else:
2680 2675 keyfunc = None # sort by filename
2681 2676 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2682 2677 if showdate:
2683 2678 if ent[3] == -1:
2684 2679 # Pad or slice to locale representation
2685 2680 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2686 2681 time.localtime(0)))
2687 2682 timestr = 'unset'
2688 2683 timestr = (timestr[:locale_len] +
2689 2684 ' ' * (locale_len - len(timestr)))
2690 2685 else:
2691 2686 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2692 2687 time.localtime(ent[3]))
2693 2688 if ent[1] & 020000:
2694 2689 mode = 'lnk'
2695 2690 else:
2696 2691 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2697 2692 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2698 2693 for f in repo.dirstate.copies():
2699 2694 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2700 2695
2701 2696 @command('debugsub',
2702 2697 [('r', 'rev', '',
2703 2698 _('revision to check'), _('REV'))],
2704 2699 _('[-r REV] [REV]'))
2705 2700 def debugsub(ui, repo, rev=None):
2706 2701 ctx = scmutil.revsingle(repo, rev, None)
2707 2702 for k, v in sorted(ctx.substate.items()):
2708 2703 ui.write(('path %s\n') % k)
2709 2704 ui.write((' source %s\n') % v[0])
2710 2705 ui.write((' revision %s\n') % v[1])
2711 2706
2712 2707 @command('debugsuccessorssets',
2713 2708 [],
2714 2709 _('[REV]'))
2715 2710 def debugsuccessorssets(ui, repo, *revs):
2716 2711 """show set of successors for revision
2717 2712
2718 2713 A successors set of changeset A is a consistent group of revisions that
2719 2714 succeed A. It contains non-obsolete changesets only.
2720 2715
2721 2716 In most cases a changeset A has a single successors set containing a single
2722 2717 successor (changeset A replaced by A').
2723 2718
2724 2719 A changeset that is made obsolete with no successors are called "pruned".
2725 2720 Such changesets have no successors sets at all.
2726 2721
2727 2722 A changeset that has been "split" will have a successors set containing
2728 2723 more than one successor.
2729 2724
2730 2725 A changeset that has been rewritten in multiple different ways is called
2731 2726 "divergent". Such changesets have multiple successor sets (each of which
2732 2727 may also be split, i.e. have multiple successors).
2733 2728
2734 2729 Results are displayed as follows::
2735 2730
2736 2731 <rev1>
2737 2732 <successors-1A>
2738 2733 <rev2>
2739 2734 <successors-2A>
2740 2735 <successors-2B1> <successors-2B2> <successors-2B3>
2741 2736
2742 2737 Here rev2 has two possible (i.e. divergent) successors sets. The first
2743 2738 holds one element, whereas the second holds three (i.e. the changeset has
2744 2739 been split).
2745 2740 """
2746 2741 # passed to successorssets caching computation from one call to another
2747 2742 cache = {}
2748 2743 ctx2str = str
2749 2744 node2str = short
2750 2745 if ui.debug():
2751 2746 def ctx2str(ctx):
2752 2747 return ctx.hex()
2753 2748 node2str = hex
2754 2749 for rev in scmutil.revrange(repo, revs):
2755 2750 ctx = repo[rev]
2756 2751 ui.write('%s\n'% ctx2str(ctx))
2757 2752 for succsset in obsolete.successorssets(repo, ctx.node(), cache):
2758 2753 if succsset:
2759 2754 ui.write(' ')
2760 2755 ui.write(node2str(succsset[0]))
2761 2756 for node in succsset[1:]:
2762 2757 ui.write(' ')
2763 2758 ui.write(node2str(node))
2764 2759 ui.write('\n')
2765 2760
2766 2761 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2767 2762 def debugwalk(ui, repo, *pats, **opts):
2768 2763 """show how files match on given patterns"""
2769 2764 m = scmutil.match(repo[None], pats, opts)
2770 2765 items = list(repo.walk(m))
2771 2766 if not items:
2772 2767 return
2773 2768 f = lambda fn: fn
2774 2769 if ui.configbool('ui', 'slash') and os.sep != '/':
2775 2770 f = lambda fn: util.normpath(fn)
2776 2771 fmt = 'f %%-%ds %%-%ds %%s' % (
2777 2772 max([len(abs) for abs in items]),
2778 2773 max([len(m.rel(abs)) for abs in items]))
2779 2774 for abs in items:
2780 2775 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2781 2776 ui.write("%s\n" % line.rstrip())
2782 2777
2783 2778 @command('debugwireargs',
2784 2779 [('', 'three', '', 'three'),
2785 2780 ('', 'four', '', 'four'),
2786 2781 ('', 'five', '', 'five'),
2787 2782 ] + remoteopts,
2788 2783 _('REPO [OPTIONS]... [ONE [TWO]]'))
2789 2784 def debugwireargs(ui, repopath, *vals, **opts):
2790 2785 repo = hg.peer(ui, opts, repopath)
2791 2786 for opt in remoteopts:
2792 2787 del opts[opt[1]]
2793 2788 args = {}
2794 2789 for k, v in opts.iteritems():
2795 2790 if v:
2796 2791 args[k] = v
2797 2792 # run twice to check that we don't mess up the stream for the next command
2798 2793 res1 = repo.debugwireargs(*vals, **args)
2799 2794 res2 = repo.debugwireargs(*vals, **args)
2800 2795 ui.write("%s\n" % res1)
2801 2796 if res1 != res2:
2802 2797 ui.warn("%s\n" % res2)
2803 2798
2804 2799 @command('^diff',
2805 2800 [('r', 'rev', [], _('revision'), _('REV')),
2806 2801 ('c', 'change', '', _('change made by revision'), _('REV'))
2807 2802 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2808 2803 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2809 2804 def diff(ui, repo, *pats, **opts):
2810 2805 """diff repository (or selected files)
2811 2806
2812 2807 Show differences between revisions for the specified files.
2813 2808
2814 2809 Differences between files are shown using the unified diff format.
2815 2810
2816 2811 .. note::
2817 2812
2818 2813 diff may generate unexpected results for merges, as it will
2819 2814 default to comparing against the working directory's first
2820 2815 parent changeset if no revisions are specified.
2821 2816
2822 2817 When two revision arguments are given, then changes are shown
2823 2818 between those revisions. If only one revision is specified then
2824 2819 that revision is compared to the working directory, and, when no
2825 2820 revisions are specified, the working directory files are compared
2826 2821 to its parent.
2827 2822
2828 2823 Alternatively you can specify -c/--change with a revision to see
2829 2824 the changes in that changeset relative to its first parent.
2830 2825
2831 2826 Without the -a/--text option, diff will avoid generating diffs of
2832 2827 files it detects as binary. With -a, diff will generate a diff
2833 2828 anyway, probably with undesirable results.
2834 2829
2835 2830 Use the -g/--git option to generate diffs in the git extended diff
2836 2831 format. For more information, read :hg:`help diffs`.
2837 2832
2838 2833 .. container:: verbose
2839 2834
2840 2835 Examples:
2841 2836
2842 2837 - compare a file in the current working directory to its parent::
2843 2838
2844 2839 hg diff foo.c
2845 2840
2846 2841 - compare two historical versions of a directory, with rename info::
2847 2842
2848 2843 hg diff --git -r 1.0:1.2 lib/
2849 2844
2850 2845 - get change stats relative to the last change on some date::
2851 2846
2852 2847 hg diff --stat -r "date('may 2')"
2853 2848
2854 2849 - diff all newly-added files that contain a keyword::
2855 2850
2856 2851 hg diff "set:added() and grep(GNU)"
2857 2852
2858 2853 - compare a revision and its parents::
2859 2854
2860 2855 hg diff -c 9353 # compare against first parent
2861 2856 hg diff -r 9353^:9353 # same using revset syntax
2862 2857 hg diff -r 9353^2:9353 # compare against the second parent
2863 2858
2864 2859 Returns 0 on success.
2865 2860 """
2866 2861
2867 2862 revs = opts.get('rev')
2868 2863 change = opts.get('change')
2869 2864 stat = opts.get('stat')
2870 2865 reverse = opts.get('reverse')
2871 2866
2872 2867 if revs and change:
2873 2868 msg = _('cannot specify --rev and --change at the same time')
2874 2869 raise util.Abort(msg)
2875 2870 elif change:
2876 2871 node2 = scmutil.revsingle(repo, change, None).node()
2877 2872 node1 = repo[node2].p1().node()
2878 2873 else:
2879 2874 node1, node2 = scmutil.revpair(repo, revs)
2880 2875
2881 2876 if reverse:
2882 2877 node1, node2 = node2, node1
2883 2878
2884 2879 diffopts = patch.diffopts(ui, opts)
2885 2880 m = scmutil.match(repo[node2], pats, opts)
2886 2881 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2887 2882 listsubrepos=opts.get('subrepos'))
2888 2883
2889 2884 @command('^export',
2890 2885 [('o', 'output', '',
2891 2886 _('print output to file with formatted name'), _('FORMAT')),
2892 2887 ('', 'switch-parent', None, _('diff against the second parent')),
2893 2888 ('r', 'rev', [], _('revisions to export'), _('REV')),
2894 2889 ] + diffopts,
2895 2890 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
2896 2891 def export(ui, repo, *changesets, **opts):
2897 2892 """dump the header and diffs for one or more changesets
2898 2893
2899 2894 Print the changeset header and diffs for one or more revisions.
2900 2895 If no revision is given, the parent of the working directory is used.
2901 2896
2902 2897 The information shown in the changeset header is: author, date,
2903 2898 branch name (if non-default), changeset hash, parent(s) and commit
2904 2899 comment.
2905 2900
2906 2901 .. note::
2907 2902
2908 2903 export may generate unexpected diff output for merge
2909 2904 changesets, as it will compare the merge changeset against its
2910 2905 first parent only.
2911 2906
2912 2907 Output may be to a file, in which case the name of the file is
2913 2908 given using a format string. The formatting rules are as follows:
2914 2909
2915 2910 :``%%``: literal "%" character
2916 2911 :``%H``: changeset hash (40 hexadecimal digits)
2917 2912 :``%N``: number of patches being generated
2918 2913 :``%R``: changeset revision number
2919 2914 :``%b``: basename of the exporting repository
2920 2915 :``%h``: short-form changeset hash (12 hexadecimal digits)
2921 2916 :``%m``: first line of the commit message (only alphanumeric characters)
2922 2917 :``%n``: zero-padded sequence number, starting at 1
2923 2918 :``%r``: zero-padded changeset revision number
2924 2919
2925 2920 Without the -a/--text option, export will avoid generating diffs
2926 2921 of files it detects as binary. With -a, export will generate a
2927 2922 diff anyway, probably with undesirable results.
2928 2923
2929 2924 Use the -g/--git option to generate diffs in the git extended diff
2930 2925 format. See :hg:`help diffs` for more information.
2931 2926
2932 2927 With the --switch-parent option, the diff will be against the
2933 2928 second parent. It can be useful to review a merge.
2934 2929
2935 2930 .. container:: verbose
2936 2931
2937 2932 Examples:
2938 2933
2939 2934 - use export and import to transplant a bugfix to the current
2940 2935 branch::
2941 2936
2942 2937 hg export -r 9353 | hg import -
2943 2938
2944 2939 - export all the changesets between two revisions to a file with
2945 2940 rename information::
2946 2941
2947 2942 hg export --git -r 123:150 > changes.txt
2948 2943
2949 2944 - split outgoing changes into a series of patches with
2950 2945 descriptive names::
2951 2946
2952 2947 hg export -r "outgoing()" -o "%n-%m.patch"
2953 2948
2954 2949 Returns 0 on success.
2955 2950 """
2956 2951 changesets += tuple(opts.get('rev', []))
2957 2952 if not changesets:
2958 2953 changesets = ['.']
2959 2954 revs = scmutil.revrange(repo, changesets)
2960 2955 if not revs:
2961 2956 raise util.Abort(_("export requires at least one changeset"))
2962 2957 if len(revs) > 1:
2963 2958 ui.note(_('exporting patches:\n'))
2964 2959 else:
2965 2960 ui.note(_('exporting patch:\n'))
2966 2961 cmdutil.export(repo, revs, template=opts.get('output'),
2967 2962 switch_parent=opts.get('switch_parent'),
2968 2963 opts=patch.diffopts(ui, opts))
2969 2964
2970 2965 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2971 2966 def forget(ui, repo, *pats, **opts):
2972 2967 """forget the specified files on the next commit
2973 2968
2974 2969 Mark the specified files so they will no longer be tracked
2975 2970 after the next commit.
2976 2971
2977 2972 This only removes files from the current branch, not from the
2978 2973 entire project history, and it does not delete them from the
2979 2974 working directory.
2980 2975
2981 2976 To undo a forget before the next commit, see :hg:`add`.
2982 2977
2983 2978 .. container:: verbose
2984 2979
2985 2980 Examples:
2986 2981
2987 2982 - forget newly-added binary files::
2988 2983
2989 2984 hg forget "set:added() and binary()"
2990 2985
2991 2986 - forget files that would be excluded by .hgignore::
2992 2987
2993 2988 hg forget "set:hgignore()"
2994 2989
2995 2990 Returns 0 on success.
2996 2991 """
2997 2992
2998 2993 if not pats:
2999 2994 raise util.Abort(_('no files specified'))
3000 2995
3001 2996 m = scmutil.match(repo[None], pats, opts)
3002 2997 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
3003 2998 return rejected and 1 or 0
3004 2999
3005 3000 @command(
3006 3001 'graft',
3007 3002 [('r', 'rev', [], _('revisions to graft'), _('REV')),
3008 3003 ('c', 'continue', False, _('resume interrupted graft')),
3009 3004 ('e', 'edit', False, _('invoke editor on commit messages')),
3010 3005 ('', 'log', None, _('append graft info to log message')),
3011 3006 ('D', 'currentdate', False,
3012 3007 _('record the current date as commit date')),
3013 3008 ('U', 'currentuser', False,
3014 3009 _('record the current user as committer'), _('DATE'))]
3015 3010 + commitopts2 + mergetoolopts + dryrunopts,
3016 3011 _('[OPTION]... [-r] REV...'))
3017 3012 def graft(ui, repo, *revs, **opts):
3018 3013 '''copy changes from other branches onto the current branch
3019 3014
3020 3015 This command uses Mercurial's merge logic to copy individual
3021 3016 changes from other branches without merging branches in the
3022 3017 history graph. This is sometimes known as 'backporting' or
3023 3018 'cherry-picking'. By default, graft will copy user, date, and
3024 3019 description from the source changesets.
3025 3020
3026 3021 Changesets that are ancestors of the current revision, that have
3027 3022 already been grafted, or that are merges will be skipped.
3028 3023
3029 3024 If --log is specified, log messages will have a comment appended
3030 3025 of the form::
3031 3026
3032 3027 (grafted from CHANGESETHASH)
3033 3028
3034 3029 If a graft merge results in conflicts, the graft process is
3035 3030 interrupted so that the current merge can be manually resolved.
3036 3031 Once all conflicts are addressed, the graft process can be
3037 3032 continued with the -c/--continue option.
3038 3033
3039 3034 .. note::
3040 3035
3041 3036 The -c/--continue option does not reapply earlier options.
3042 3037
3043 3038 .. container:: verbose
3044 3039
3045 3040 Examples:
3046 3041
3047 3042 - copy a single change to the stable branch and edit its description::
3048 3043
3049 3044 hg update stable
3050 3045 hg graft --edit 9393
3051 3046
3052 3047 - graft a range of changesets with one exception, updating dates::
3053 3048
3054 3049 hg graft -D "2085::2093 and not 2091"
3055 3050
3056 3051 - continue a graft after resolving conflicts::
3057 3052
3058 3053 hg graft -c
3059 3054
3060 3055 - show the source of a grafted changeset::
3061 3056
3062 3057 hg log --debug -r .
3063 3058
3064 3059 Returns 0 on successful completion.
3065 3060 '''
3066 3061
3067 3062 revs = list(revs)
3068 3063 revs.extend(opts['rev'])
3069 3064
3070 3065 if not opts.get('user') and opts.get('currentuser'):
3071 3066 opts['user'] = ui.username()
3072 3067 if not opts.get('date') and opts.get('currentdate'):
3073 3068 opts['date'] = "%d %d" % util.makedate()
3074 3069
3075 3070 editor = None
3076 3071 if opts.get('edit'):
3077 3072 editor = cmdutil.commitforceeditor
3078 3073
3079 3074 cont = False
3080 3075 if opts['continue']:
3081 3076 cont = True
3082 3077 if revs:
3083 3078 raise util.Abort(_("can't specify --continue and revisions"))
3084 3079 # read in unfinished revisions
3085 3080 try:
3086 3081 nodes = repo.opener.read('graftstate').splitlines()
3087 3082 revs = [repo[node].rev() for node in nodes]
3088 3083 except IOError, inst:
3089 3084 if inst.errno != errno.ENOENT:
3090 3085 raise
3091 3086 raise util.Abort(_("no graft state found, can't continue"))
3092 3087 else:
3093 3088 cmdutil.checkunfinished(repo)
3094 3089 cmdutil.bailifchanged(repo)
3095 3090 if not revs:
3096 3091 raise util.Abort(_('no revisions specified'))
3097 3092 revs = scmutil.revrange(repo, revs)
3098 3093
3099 3094 # check for merges
3100 3095 for rev in repo.revs('%ld and merge()', revs):
3101 3096 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
3102 3097 revs.remove(rev)
3103 3098 if not revs:
3104 3099 return -1
3105 3100
3106 3101 # check for ancestors of dest branch
3107 3102 crev = repo['.'].rev()
3108 3103 ancestors = repo.changelog.ancestors([crev], inclusive=True)
3109 3104 # don't mutate while iterating, create a copy
3110 3105 for rev in list(revs):
3111 3106 if rev in ancestors:
3112 3107 ui.warn(_('skipping ancestor revision %s\n') % rev)
3113 3108 revs.remove(rev)
3114 3109 if not revs:
3115 3110 return -1
3116 3111
3117 3112 # analyze revs for earlier grafts
3118 3113 ids = {}
3119 3114 for ctx in repo.set("%ld", revs):
3120 3115 ids[ctx.hex()] = ctx.rev()
3121 3116 n = ctx.extra().get('source')
3122 3117 if n:
3123 3118 ids[n] = ctx.rev()
3124 3119
3125 3120 # check ancestors for earlier grafts
3126 3121 ui.debug('scanning for duplicate grafts\n')
3127 3122
3128 3123 for rev in repo.changelog.findmissingrevs(revs, [crev]):
3129 3124 ctx = repo[rev]
3130 3125 n = ctx.extra().get('source')
3131 3126 if n in ids:
3132 3127 r = repo[n].rev()
3133 3128 if r in revs:
3134 3129 ui.warn(_('skipping revision %s (already grafted to %s)\n')
3135 3130 % (r, rev))
3136 3131 revs.remove(r)
3137 3132 elif ids[n] in revs:
3138 3133 ui.warn(_('skipping already grafted revision %s '
3139 3134 '(%s also has origin %d)\n') % (ids[n], rev, r))
3140 3135 revs.remove(ids[n])
3141 3136 elif ctx.hex() in ids:
3142 3137 r = ids[ctx.hex()]
3143 3138 ui.warn(_('skipping already grafted revision %s '
3144 3139 '(was grafted from %d)\n') % (r, rev))
3145 3140 revs.remove(r)
3146 3141 if not revs:
3147 3142 return -1
3148 3143
3149 3144 wlock = repo.wlock()
3150 3145 try:
3151 3146 current = repo['.']
3152 3147 for pos, ctx in enumerate(repo.set("%ld", revs)):
3153 3148
3154 3149 ui.status(_('grafting revision %s\n') % ctx.rev())
3155 3150 if opts.get('dry_run'):
3156 3151 continue
3157 3152
3158 3153 source = ctx.extra().get('source')
3159 3154 if not source:
3160 3155 source = ctx.hex()
3161 3156 extra = {'source': source}
3162 3157 user = ctx.user()
3163 3158 if opts.get('user'):
3164 3159 user = opts['user']
3165 3160 date = ctx.date()
3166 3161 if opts.get('date'):
3167 3162 date = opts['date']
3168 3163 message = ctx.description()
3169 3164 if opts.get('log'):
3170 3165 message += '\n(grafted from %s)' % ctx.hex()
3171 3166
3172 3167 # we don't merge the first commit when continuing
3173 3168 if not cont:
3174 3169 # perform the graft merge with p1(rev) as 'ancestor'
3175 3170 try:
3176 3171 # ui.forcemerge is an internal variable, do not document
3177 3172 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
3178 3173 stats = mergemod.update(repo, ctx.node(), True, True, False,
3179 3174 ctx.p1().node())
3180 3175 finally:
3181 3176 repo.ui.setconfig('ui', 'forcemerge', '')
3182 3177 # report any conflicts
3183 3178 if stats and stats[3] > 0:
3184 3179 # write out state for --continue
3185 3180 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
3186 3181 repo.opener.write('graftstate', ''.join(nodelines))
3187 3182 raise util.Abort(
3188 3183 _("unresolved conflicts, can't continue"),
3189 3184 hint=_('use hg resolve and hg graft --continue'))
3190 3185 else:
3191 3186 cont = False
3192 3187
3193 3188 # drop the second merge parent
3194 3189 repo.setparents(current.node(), nullid)
3195 3190 repo.dirstate.write()
3196 3191 # fix up dirstate for copies and renames
3197 3192 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
3198 3193
3199 3194 # commit
3200 3195 node = repo.commit(text=message, user=user,
3201 3196 date=date, extra=extra, editor=editor)
3202 3197 if node is None:
3203 3198 ui.status(_('graft for revision %s is empty\n') % ctx.rev())
3204 3199 else:
3205 3200 current = repo[node]
3206 3201 finally:
3207 3202 wlock.release()
3208 3203
3209 3204 # remove state when we complete successfully
3210 3205 if not opts.get('dry_run'):
3211 3206 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
3212 3207
3213 3208 return 0
3214 3209
3215 3210 @command('grep',
3216 3211 [('0', 'print0', None, _('end fields with NUL')),
3217 3212 ('', 'all', None, _('print all revisions that match')),
3218 3213 ('a', 'text', None, _('treat all files as text')),
3219 3214 ('f', 'follow', None,
3220 3215 _('follow changeset history,'
3221 3216 ' or file history across copies and renames')),
3222 3217 ('i', 'ignore-case', None, _('ignore case when matching')),
3223 3218 ('l', 'files-with-matches', None,
3224 3219 _('print only filenames and revisions that match')),
3225 3220 ('n', 'line-number', None, _('print matching line numbers')),
3226 3221 ('r', 'rev', [],
3227 3222 _('only search files changed within revision range'), _('REV')),
3228 3223 ('u', 'user', None, _('list the author (long with -v)')),
3229 3224 ('d', 'date', None, _('list the date (short with -q)')),
3230 3225 ] + walkopts,
3231 3226 _('[OPTION]... PATTERN [FILE]...'))
3232 3227 def grep(ui, repo, pattern, *pats, **opts):
3233 3228 """search for a pattern in specified files and revisions
3234 3229
3235 3230 Search revisions of files for a regular expression.
3236 3231
3237 3232 This command behaves differently than Unix grep. It only accepts
3238 3233 Python/Perl regexps. It searches repository history, not the
3239 3234 working directory. It always prints the revision number in which a
3240 3235 match appears.
3241 3236
3242 3237 By default, grep only prints output for the first revision of a
3243 3238 file in which it finds a match. To get it to print every revision
3244 3239 that contains a change in match status ("-" for a match that
3245 3240 becomes a non-match, or "+" for a non-match that becomes a match),
3246 3241 use the --all flag.
3247 3242
3248 3243 Returns 0 if a match is found, 1 otherwise.
3249 3244 """
3250 3245 reflags = re.M
3251 3246 if opts.get('ignore_case'):
3252 3247 reflags |= re.I
3253 3248 try:
3254 3249 regexp = util.compilere(pattern, reflags)
3255 3250 except re.error, inst:
3256 3251 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
3257 3252 return 1
3258 3253 sep, eol = ':', '\n'
3259 3254 if opts.get('print0'):
3260 3255 sep = eol = '\0'
3261 3256
3262 3257 getfile = util.lrucachefunc(repo.file)
3263 3258
3264 3259 def matchlines(body):
3265 3260 begin = 0
3266 3261 linenum = 0
3267 3262 while begin < len(body):
3268 3263 match = regexp.search(body, begin)
3269 3264 if not match:
3270 3265 break
3271 3266 mstart, mend = match.span()
3272 3267 linenum += body.count('\n', begin, mstart) + 1
3273 3268 lstart = body.rfind('\n', begin, mstart) + 1 or begin
3274 3269 begin = body.find('\n', mend) + 1 or len(body) + 1
3275 3270 lend = begin - 1
3276 3271 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3277 3272
3278 3273 class linestate(object):
3279 3274 def __init__(self, line, linenum, colstart, colend):
3280 3275 self.line = line
3281 3276 self.linenum = linenum
3282 3277 self.colstart = colstart
3283 3278 self.colend = colend
3284 3279
3285 3280 def __hash__(self):
3286 3281 return hash((self.linenum, self.line))
3287 3282
3288 3283 def __eq__(self, other):
3289 3284 return self.line == other.line
3290 3285
3291 3286 matches = {}
3292 3287 copies = {}
3293 3288 def grepbody(fn, rev, body):
3294 3289 matches[rev].setdefault(fn, [])
3295 3290 m = matches[rev][fn]
3296 3291 for lnum, cstart, cend, line in matchlines(body):
3297 3292 s = linestate(line, lnum, cstart, cend)
3298 3293 m.append(s)
3299 3294
3300 3295 def difflinestates(a, b):
3301 3296 sm = difflib.SequenceMatcher(None, a, b)
3302 3297 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3303 3298 if tag == 'insert':
3304 3299 for i in xrange(blo, bhi):
3305 3300 yield ('+', b[i])
3306 3301 elif tag == 'delete':
3307 3302 for i in xrange(alo, ahi):
3308 3303 yield ('-', a[i])
3309 3304 elif tag == 'replace':
3310 3305 for i in xrange(alo, ahi):
3311 3306 yield ('-', a[i])
3312 3307 for i in xrange(blo, bhi):
3313 3308 yield ('+', b[i])
3314 3309
3315 3310 def display(fn, ctx, pstates, states):
3316 3311 rev = ctx.rev()
3317 3312 datefunc = ui.quiet and util.shortdate or util.datestr
3318 3313 found = False
3319 3314 filerevmatches = {}
3320 3315 def binary():
3321 3316 flog = getfile(fn)
3322 3317 return util.binary(flog.read(ctx.filenode(fn)))
3323 3318
3324 3319 if opts.get('all'):
3325 3320 iter = difflinestates(pstates, states)
3326 3321 else:
3327 3322 iter = [('', l) for l in states]
3328 3323 for change, l in iter:
3329 3324 cols = [(fn, 'grep.filename'), (str(rev), 'grep.rev')]
3330 3325 before, match, after = None, None, None
3331 3326
3332 3327 if opts.get('line_number'):
3333 3328 cols.append((str(l.linenum), 'grep.linenumber'))
3334 3329 if opts.get('all'):
3335 3330 cols.append((change, 'grep.change'))
3336 3331 if opts.get('user'):
3337 3332 cols.append((ui.shortuser(ctx.user()), 'grep.user'))
3338 3333 if opts.get('date'):
3339 3334 cols.append((datefunc(ctx.date()), 'grep.date'))
3340 3335 if opts.get('files_with_matches'):
3341 3336 c = (fn, rev)
3342 3337 if c in filerevmatches:
3343 3338 continue
3344 3339 filerevmatches[c] = 1
3345 3340 else:
3346 3341 before = l.line[:l.colstart]
3347 3342 match = l.line[l.colstart:l.colend]
3348 3343 after = l.line[l.colend:]
3349 3344 for col, label in cols[:-1]:
3350 3345 ui.write(col, label=label)
3351 3346 ui.write(sep, label='grep.sep')
3352 3347 ui.write(cols[-1][0], label=cols[-1][1])
3353 3348 if before is not None:
3354 3349 ui.write(sep, label='grep.sep')
3355 3350 if not opts.get('text') and binary():
3356 3351 ui.write(" Binary file matches")
3357 3352 else:
3358 3353 ui.write(before)
3359 3354 ui.write(match, label='grep.match')
3360 3355 ui.write(after)
3361 3356 ui.write(eol)
3362 3357 found = True
3363 3358 return found
3364 3359
3365 3360 skip = {}
3366 3361 revfiles = {}
3367 3362 matchfn = scmutil.match(repo[None], pats, opts)
3368 3363 found = False
3369 3364 follow = opts.get('follow')
3370 3365
3371 3366 def prep(ctx, fns):
3372 3367 rev = ctx.rev()
3373 3368 pctx = ctx.p1()
3374 3369 parent = pctx.rev()
3375 3370 matches.setdefault(rev, {})
3376 3371 matches.setdefault(parent, {})
3377 3372 files = revfiles.setdefault(rev, [])
3378 3373 for fn in fns:
3379 3374 flog = getfile(fn)
3380 3375 try:
3381 3376 fnode = ctx.filenode(fn)
3382 3377 except error.LookupError:
3383 3378 continue
3384 3379
3385 3380 copied = flog.renamed(fnode)
3386 3381 copy = follow and copied and copied[0]
3387 3382 if copy:
3388 3383 copies.setdefault(rev, {})[fn] = copy
3389 3384 if fn in skip:
3390 3385 if copy:
3391 3386 skip[copy] = True
3392 3387 continue
3393 3388 files.append(fn)
3394 3389
3395 3390 if fn not in matches[rev]:
3396 3391 grepbody(fn, rev, flog.read(fnode))
3397 3392
3398 3393 pfn = copy or fn
3399 3394 if pfn not in matches[parent]:
3400 3395 try:
3401 3396 fnode = pctx.filenode(pfn)
3402 3397 grepbody(pfn, parent, flog.read(fnode))
3403 3398 except error.LookupError:
3404 3399 pass
3405 3400
3406 3401 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3407 3402 rev = ctx.rev()
3408 3403 parent = ctx.p1().rev()
3409 3404 for fn in sorted(revfiles.get(rev, [])):
3410 3405 states = matches[rev][fn]
3411 3406 copy = copies.get(rev, {}).get(fn)
3412 3407 if fn in skip:
3413 3408 if copy:
3414 3409 skip[copy] = True
3415 3410 continue
3416 3411 pstates = matches.get(parent, {}).get(copy or fn, [])
3417 3412 if pstates or states:
3418 3413 r = display(fn, ctx, pstates, states)
3419 3414 found = found or r
3420 3415 if r and not opts.get('all'):
3421 3416 skip[fn] = True
3422 3417 if copy:
3423 3418 skip[copy] = True
3424 3419 del matches[rev]
3425 3420 del revfiles[rev]
3426 3421
3427 3422 return not found
3428 3423
3429 3424 @command('heads',
3430 3425 [('r', 'rev', '',
3431 3426 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3432 3427 ('t', 'topo', False, _('show topological heads only')),
3433 3428 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3434 3429 ('c', 'closed', False, _('show normal and closed branch heads')),
3435 3430 ] + templateopts,
3436 3431 _('[-ct] [-r STARTREV] [REV]...'))
3437 3432 def heads(ui, repo, *branchrevs, **opts):
3438 3433 """show branch heads
3439 3434
3440 3435 With no arguments, show all open branch heads in the repository.
3441 3436 Branch heads are changesets that have no descendants on the
3442 3437 same branch. They are where development generally takes place and
3443 3438 are the usual targets for update and merge operations.
3444 3439
3445 3440 If one or more REVs are given, only open branch heads on the
3446 3441 branches associated with the specified changesets are shown. This
3447 3442 means that you can use :hg:`heads .` to see the heads on the
3448 3443 currently checked-out branch.
3449 3444
3450 3445 If -c/--closed is specified, also show branch heads marked closed
3451 3446 (see :hg:`commit --close-branch`).
3452 3447
3453 3448 If STARTREV is specified, only those heads that are descendants of
3454 3449 STARTREV will be displayed.
3455 3450
3456 3451 If -t/--topo is specified, named branch mechanics will be ignored and only
3457 3452 topological heads (changesets with no children) will be shown.
3458 3453
3459 3454 Returns 0 if matching heads are found, 1 if not.
3460 3455 """
3461 3456
3462 3457 start = None
3463 3458 if 'rev' in opts:
3464 3459 start = scmutil.revsingle(repo, opts['rev'], None).node()
3465 3460
3466 3461 if opts.get('topo'):
3467 3462 heads = [repo[h] for h in repo.heads(start)]
3468 3463 else:
3469 3464 heads = []
3470 3465 for branch in repo.branchmap():
3471 3466 heads += repo.branchheads(branch, start, opts.get('closed'))
3472 3467 heads = [repo[h] for h in heads]
3473 3468
3474 3469 if branchrevs:
3475 3470 branches = set(repo[br].branch() for br in branchrevs)
3476 3471 heads = [h for h in heads if h.branch() in branches]
3477 3472
3478 3473 if opts.get('active') and branchrevs:
3479 3474 dagheads = repo.heads(start)
3480 3475 heads = [h for h in heads if h.node() in dagheads]
3481 3476
3482 3477 if branchrevs:
3483 3478 haveheads = set(h.branch() for h in heads)
3484 3479 if branches - haveheads:
3485 3480 headless = ', '.join(b for b in branches - haveheads)
3486 3481 msg = _('no open branch heads found on branches %s')
3487 3482 if opts.get('rev'):
3488 3483 msg += _(' (started at %s)') % opts['rev']
3489 3484 ui.warn((msg + '\n') % headless)
3490 3485
3491 3486 if not heads:
3492 3487 return 1
3493 3488
3494 3489 heads = sorted(heads, key=lambda x: -x.rev())
3495 3490 displayer = cmdutil.show_changeset(ui, repo, opts)
3496 3491 for ctx in heads:
3497 3492 displayer.show(ctx)
3498 3493 displayer.close()
3499 3494
3500 3495 @command('help',
3501 3496 [('e', 'extension', None, _('show only help for extensions')),
3502 3497 ('c', 'command', None, _('show only help for commands')),
3503 3498 ('k', 'keyword', '', _('show topics matching keyword')),
3504 3499 ],
3505 3500 _('[-ec] [TOPIC]'))
3506 3501 def help_(ui, name=None, **opts):
3507 3502 """show help for a given topic or a help overview
3508 3503
3509 3504 With no arguments, print a list of commands with short help messages.
3510 3505
3511 3506 Given a topic, extension, or command name, print help for that
3512 3507 topic.
3513 3508
3514 3509 Returns 0 if successful.
3515 3510 """
3516 3511
3517 3512 textwidth = min(ui.termwidth(), 80) - 2
3518 3513
3519 3514 keep = ui.verbose and ['verbose'] or []
3520 3515 text = help.help_(ui, name, **opts)
3521 3516
3522 3517 formatted, pruned = minirst.format(text, textwidth, keep=keep)
3523 3518 if 'verbose' in pruned:
3524 3519 keep.append('omitted')
3525 3520 else:
3526 3521 keep.append('notomitted')
3527 3522 formatted, pruned = minirst.format(text, textwidth, keep=keep)
3528 3523 ui.write(formatted)
3529 3524
3530 3525
3531 3526 @command('identify|id',
3532 3527 [('r', 'rev', '',
3533 3528 _('identify the specified revision'), _('REV')),
3534 3529 ('n', 'num', None, _('show local revision number')),
3535 3530 ('i', 'id', None, _('show global revision id')),
3536 3531 ('b', 'branch', None, _('show branch')),
3537 3532 ('t', 'tags', None, _('show tags')),
3538 3533 ('B', 'bookmarks', None, _('show bookmarks')),
3539 3534 ] + remoteopts,
3540 3535 _('[-nibtB] [-r REV] [SOURCE]'))
3541 3536 def identify(ui, repo, source=None, rev=None,
3542 3537 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3543 3538 """identify the working copy or specified revision
3544 3539
3545 3540 Print a summary identifying the repository state at REV using one or
3546 3541 two parent hash identifiers, followed by a "+" if the working
3547 3542 directory has uncommitted changes, the branch name (if not default),
3548 3543 a list of tags, and a list of bookmarks.
3549 3544
3550 3545 When REV is not given, print a summary of the current state of the
3551 3546 repository.
3552 3547
3553 3548 Specifying a path to a repository root or Mercurial bundle will
3554 3549 cause lookup to operate on that repository/bundle.
3555 3550
3556 3551 .. container:: verbose
3557 3552
3558 3553 Examples:
3559 3554
3560 3555 - generate a build identifier for the working directory::
3561 3556
3562 3557 hg id --id > build-id.dat
3563 3558
3564 3559 - find the revision corresponding to a tag::
3565 3560
3566 3561 hg id -n -r 1.3
3567 3562
3568 3563 - check the most recent revision of a remote repository::
3569 3564
3570 3565 hg id -r tip http://selenic.com/hg/
3571 3566
3572 3567 Returns 0 if successful.
3573 3568 """
3574 3569
3575 3570 if not repo and not source:
3576 3571 raise util.Abort(_("there is no Mercurial repository here "
3577 3572 "(.hg not found)"))
3578 3573
3579 3574 hexfunc = ui.debugflag and hex or short
3580 3575 default = not (num or id or branch or tags or bookmarks)
3581 3576 output = []
3582 3577 revs = []
3583 3578
3584 3579 if source:
3585 3580 source, branches = hg.parseurl(ui.expandpath(source))
3586 3581 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3587 3582 repo = peer.local()
3588 3583 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3589 3584
3590 3585 if not repo:
3591 3586 if num or branch or tags:
3592 3587 raise util.Abort(
3593 3588 _("can't query remote revision number, branch, or tags"))
3594 3589 if not rev and revs:
3595 3590 rev = revs[0]
3596 3591 if not rev:
3597 3592 rev = "tip"
3598 3593
3599 3594 remoterev = peer.lookup(rev)
3600 3595 if default or id:
3601 3596 output = [hexfunc(remoterev)]
3602 3597
3603 3598 def getbms():
3604 3599 bms = []
3605 3600
3606 3601 if 'bookmarks' in peer.listkeys('namespaces'):
3607 3602 hexremoterev = hex(remoterev)
3608 3603 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3609 3604 if bmr == hexremoterev]
3610 3605
3611 3606 return sorted(bms)
3612 3607
3613 3608 if bookmarks:
3614 3609 output.extend(getbms())
3615 3610 elif default and not ui.quiet:
3616 3611 # multiple bookmarks for a single parent separated by '/'
3617 3612 bm = '/'.join(getbms())
3618 3613 if bm:
3619 3614 output.append(bm)
3620 3615 else:
3621 3616 if not rev:
3622 3617 ctx = repo[None]
3623 3618 parents = ctx.parents()
3624 3619 changed = ""
3625 3620 if default or id or num:
3626 3621 if (util.any(repo.status())
3627 3622 or util.any(ctx.sub(s).dirty() for s in ctx.substate)):
3628 3623 changed = '+'
3629 3624 if default or id:
3630 3625 output = ["%s%s" %
3631 3626 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3632 3627 if num:
3633 3628 output.append("%s%s" %
3634 3629 ('+'.join([str(p.rev()) for p in parents]), changed))
3635 3630 else:
3636 3631 ctx = scmutil.revsingle(repo, rev)
3637 3632 if default or id:
3638 3633 output = [hexfunc(ctx.node())]
3639 3634 if num:
3640 3635 output.append(str(ctx.rev()))
3641 3636
3642 3637 if default and not ui.quiet:
3643 3638 b = ctx.branch()
3644 3639 if b != 'default':
3645 3640 output.append("(%s)" % b)
3646 3641
3647 3642 # multiple tags for a single parent separated by '/'
3648 3643 t = '/'.join(ctx.tags())
3649 3644 if t:
3650 3645 output.append(t)
3651 3646
3652 3647 # multiple bookmarks for a single parent separated by '/'
3653 3648 bm = '/'.join(ctx.bookmarks())
3654 3649 if bm:
3655 3650 output.append(bm)
3656 3651 else:
3657 3652 if branch:
3658 3653 output.append(ctx.branch())
3659 3654
3660 3655 if tags:
3661 3656 output.extend(ctx.tags())
3662 3657
3663 3658 if bookmarks:
3664 3659 output.extend(ctx.bookmarks())
3665 3660
3666 3661 ui.write("%s\n" % ' '.join(output))
3667 3662
3668 3663 @command('import|patch',
3669 3664 [('p', 'strip', 1,
3670 3665 _('directory strip option for patch. This has the same '
3671 3666 'meaning as the corresponding patch option'), _('NUM')),
3672 3667 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3673 3668 ('e', 'edit', False, _('invoke editor on commit messages')),
3674 3669 ('f', 'force', None,
3675 3670 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3676 3671 ('', 'no-commit', None,
3677 3672 _("don't commit, just update the working directory")),
3678 3673 ('', 'bypass', None,
3679 3674 _("apply patch without touching the working directory")),
3680 3675 ('', 'exact', None,
3681 3676 _('apply patch to the nodes from which it was generated')),
3682 3677 ('', 'import-branch', None,
3683 3678 _('use any branch information in patch (implied by --exact)'))] +
3684 3679 commitopts + commitopts2 + similarityopts,
3685 3680 _('[OPTION]... PATCH...'))
3686 3681 def import_(ui, repo, patch1=None, *patches, **opts):
3687 3682 """import an ordered set of patches
3688 3683
3689 3684 Import a list of patches and commit them individually (unless
3690 3685 --no-commit is specified).
3691 3686
3692 3687 Because import first applies changes to the working directory,
3693 3688 import will abort if there are outstanding changes.
3694 3689
3695 3690 You can import a patch straight from a mail message. Even patches
3696 3691 as attachments work (to use the body part, it must have type
3697 3692 text/plain or text/x-patch). From and Subject headers of email
3698 3693 message are used as default committer and commit message. All
3699 3694 text/plain body parts before first diff are added to commit
3700 3695 message.
3701 3696
3702 3697 If the imported patch was generated by :hg:`export`, user and
3703 3698 description from patch override values from message headers and
3704 3699 body. Values given on command line with -m/--message and -u/--user
3705 3700 override these.
3706 3701
3707 3702 If --exact is specified, import will set the working directory to
3708 3703 the parent of each patch before applying it, and will abort if the
3709 3704 resulting changeset has a different ID than the one recorded in
3710 3705 the patch. This may happen due to character set problems or other
3711 3706 deficiencies in the text patch format.
3712 3707
3713 3708 Use --bypass to apply and commit patches directly to the
3714 3709 repository, not touching the working directory. Without --exact,
3715 3710 patches will be applied on top of the working directory parent
3716 3711 revision.
3717 3712
3718 3713 With -s/--similarity, hg will attempt to discover renames and
3719 3714 copies in the patch in the same way as :hg:`addremove`.
3720 3715
3721 3716 To read a patch from standard input, use "-" as the patch name. If
3722 3717 a URL is specified, the patch will be downloaded from it.
3723 3718 See :hg:`help dates` for a list of formats valid for -d/--date.
3724 3719
3725 3720 .. container:: verbose
3726 3721
3727 3722 Examples:
3728 3723
3729 3724 - import a traditional patch from a website and detect renames::
3730 3725
3731 3726 hg import -s 80 http://example.com/bugfix.patch
3732 3727
3733 3728 - import a changeset from an hgweb server::
3734 3729
3735 3730 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
3736 3731
3737 3732 - import all the patches in an Unix-style mbox::
3738 3733
3739 3734 hg import incoming-patches.mbox
3740 3735
3741 3736 - attempt to exactly restore an exported changeset (not always
3742 3737 possible)::
3743 3738
3744 3739 hg import --exact proposed-fix.patch
3745 3740
3746 3741 Returns 0 on success.
3747 3742 """
3748 3743
3749 3744 if not patch1:
3750 3745 raise util.Abort(_('need at least one patch to import'))
3751 3746
3752 3747 patches = (patch1,) + patches
3753 3748
3754 3749 date = opts.get('date')
3755 3750 if date:
3756 3751 opts['date'] = util.parsedate(date)
3757 3752
3758 3753 update = not opts.get('bypass')
3759 3754 if not update and opts.get('no_commit'):
3760 3755 raise util.Abort(_('cannot use --no-commit with --bypass'))
3761 3756 try:
3762 3757 sim = float(opts.get('similarity') or 0)
3763 3758 except ValueError:
3764 3759 raise util.Abort(_('similarity must be a number'))
3765 3760 if sim < 0 or sim > 100:
3766 3761 raise util.Abort(_('similarity must be between 0 and 100'))
3767 3762 if sim and not update:
3768 3763 raise util.Abort(_('cannot use --similarity with --bypass'))
3769 3764
3770 3765 if update:
3771 3766 cmdutil.checkunfinished(repo)
3772 3767 if (opts.get('exact') or not opts.get('force')) and update:
3773 3768 cmdutil.bailifchanged(repo)
3774 3769
3775 3770 base = opts["base"]
3776 3771 wlock = lock = tr = None
3777 3772 msgs = []
3778 3773
3779 3774
3780 3775 try:
3781 3776 try:
3782 3777 wlock = repo.wlock()
3783 3778 if not opts.get('no_commit'):
3784 3779 lock = repo.lock()
3785 3780 tr = repo.transaction('import')
3786 3781 parents = repo.parents()
3787 3782 for patchurl in patches:
3788 3783 if patchurl == '-':
3789 3784 ui.status(_('applying patch from stdin\n'))
3790 3785 patchfile = ui.fin
3791 3786 patchurl = 'stdin' # for error message
3792 3787 else:
3793 3788 patchurl = os.path.join(base, patchurl)
3794 3789 ui.status(_('applying %s\n') % patchurl)
3795 3790 patchfile = hg.openpath(ui, patchurl)
3796 3791
3797 3792 haspatch = False
3798 3793 for hunk in patch.split(patchfile):
3799 3794 (msg, node) = cmdutil.tryimportone(ui, repo, hunk, parents,
3800 3795 opts, msgs, hg.clean)
3801 3796 if msg:
3802 3797 haspatch = True
3803 3798 ui.note(msg + '\n')
3804 3799 if update or opts.get('exact'):
3805 3800 parents = repo.parents()
3806 3801 else:
3807 3802 parents = [repo[node]]
3808 3803
3809 3804 if not haspatch:
3810 3805 raise util.Abort(_('%s: no diffs found') % patchurl)
3811 3806
3812 3807 if tr:
3813 3808 tr.close()
3814 3809 if msgs:
3815 3810 repo.savecommitmessage('\n* * *\n'.join(msgs))
3816 3811 except: # re-raises
3817 3812 # wlock.release() indirectly calls dirstate.write(): since
3818 3813 # we're crashing, we do not want to change the working dir
3819 3814 # parent after all, so make sure it writes nothing
3820 3815 repo.dirstate.invalidate()
3821 3816 raise
3822 3817 finally:
3823 3818 if tr:
3824 3819 tr.release()
3825 3820 release(lock, wlock)
3826 3821
3827 3822 @command('incoming|in',
3828 3823 [('f', 'force', None,
3829 3824 _('run even if remote repository is unrelated')),
3830 3825 ('n', 'newest-first', None, _('show newest record first')),
3831 3826 ('', 'bundle', '',
3832 3827 _('file to store the bundles into'), _('FILE')),
3833 3828 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3834 3829 ('B', 'bookmarks', False, _("compare bookmarks")),
3835 3830 ('b', 'branch', [],
3836 3831 _('a specific branch you would like to pull'), _('BRANCH')),
3837 3832 ] + logopts + remoteopts + subrepoopts,
3838 3833 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3839 3834 def incoming(ui, repo, source="default", **opts):
3840 3835 """show new changesets found in source
3841 3836
3842 3837 Show new changesets found in the specified path/URL or the default
3843 3838 pull location. These are the changesets that would have been pulled
3844 3839 if a pull at the time you issued this command.
3845 3840
3846 3841 For remote repository, using --bundle avoids downloading the
3847 3842 changesets twice if the incoming is followed by a pull.
3848 3843
3849 3844 See pull for valid source format details.
3850 3845
3851 3846 Returns 0 if there are incoming changes, 1 otherwise.
3852 3847 """
3853 3848 if opts.get('graph'):
3854 3849 cmdutil.checkunsupportedgraphflags([], opts)
3855 3850 def display(other, chlist, displayer):
3856 3851 revdag = cmdutil.graphrevs(other, chlist, opts)
3857 3852 showparents = [ctx.node() for ctx in repo[None].parents()]
3858 3853 cmdutil.displaygraph(ui, revdag, displayer, showparents,
3859 3854 graphmod.asciiedges)
3860 3855
3861 3856 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3862 3857 return 0
3863 3858
3864 3859 if opts.get('bundle') and opts.get('subrepos'):
3865 3860 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3866 3861
3867 3862 if opts.get('bookmarks'):
3868 3863 source, branches = hg.parseurl(ui.expandpath(source),
3869 3864 opts.get('branch'))
3870 3865 other = hg.peer(repo, opts, source)
3871 3866 if 'bookmarks' not in other.listkeys('namespaces'):
3872 3867 ui.warn(_("remote doesn't support bookmarks\n"))
3873 3868 return 0
3874 3869 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3875 3870 return bookmarks.diff(ui, repo, other)
3876 3871
3877 3872 repo._subtoppath = ui.expandpath(source)
3878 3873 try:
3879 3874 return hg.incoming(ui, repo, source, opts)
3880 3875 finally:
3881 3876 del repo._subtoppath
3882 3877
3883 3878
3884 3879 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3885 3880 def init(ui, dest=".", **opts):
3886 3881 """create a new repository in the given directory
3887 3882
3888 3883 Initialize a new repository in the given directory. If the given
3889 3884 directory does not exist, it will be created.
3890 3885
3891 3886 If no directory is given, the current directory is used.
3892 3887
3893 3888 It is possible to specify an ``ssh://`` URL as the destination.
3894 3889 See :hg:`help urls` for more information.
3895 3890
3896 3891 Returns 0 on success.
3897 3892 """
3898 3893 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3899 3894
3900 3895 @command('locate',
3901 3896 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3902 3897 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3903 3898 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3904 3899 ] + walkopts,
3905 3900 _('[OPTION]... [PATTERN]...'))
3906 3901 def locate(ui, repo, *pats, **opts):
3907 3902 """locate files matching specific patterns
3908 3903
3909 3904 Print files under Mercurial control in the working directory whose
3910 3905 names match the given patterns.
3911 3906
3912 3907 By default, this command searches all directories in the working
3913 3908 directory. To search just the current directory and its
3914 3909 subdirectories, use "--include .".
3915 3910
3916 3911 If no patterns are given to match, this command prints the names
3917 3912 of all files under Mercurial control in the working directory.
3918 3913
3919 3914 If you want to feed the output of this command into the "xargs"
3920 3915 command, use the -0 option to both this command and "xargs". This
3921 3916 will avoid the problem of "xargs" treating single filenames that
3922 3917 contain whitespace as multiple filenames.
3923 3918
3924 3919 Returns 0 if a match is found, 1 otherwise.
3925 3920 """
3926 3921 end = opts.get('print0') and '\0' or '\n'
3927 3922 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3928 3923
3929 3924 ret = 1
3930 3925 m = scmutil.match(repo[rev], pats, opts, default='relglob')
3931 3926 m.bad = lambda x, y: False
3932 3927 for abs in repo[rev].walk(m):
3933 3928 if not rev and abs not in repo.dirstate:
3934 3929 continue
3935 3930 if opts.get('fullpath'):
3936 3931 ui.write(repo.wjoin(abs), end)
3937 3932 else:
3938 3933 ui.write(((pats and m.rel(abs)) or abs), end)
3939 3934 ret = 0
3940 3935
3941 3936 return ret
3942 3937
3943 3938 @command('^log|history',
3944 3939 [('f', 'follow', None,
3945 3940 _('follow changeset history, or file history across copies and renames')),
3946 3941 ('', 'follow-first', None,
3947 3942 _('only follow the first parent of merge changesets (DEPRECATED)')),
3948 3943 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3949 3944 ('C', 'copies', None, _('show copied files')),
3950 3945 ('k', 'keyword', [],
3951 3946 _('do case-insensitive search for a given text'), _('TEXT')),
3952 3947 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3953 3948 ('', 'removed', None, _('include revisions where files were removed')),
3954 3949 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3955 3950 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3956 3951 ('', 'only-branch', [],
3957 3952 _('show only changesets within the given named branch (DEPRECATED)'),
3958 3953 _('BRANCH')),
3959 3954 ('b', 'branch', [],
3960 3955 _('show changesets within the given named branch'), _('BRANCH')),
3961 3956 ('P', 'prune', [],
3962 3957 _('do not display revision or any of its ancestors'), _('REV')),
3963 3958 ] + logopts + walkopts,
3964 3959 _('[OPTION]... [FILE]'))
3965 3960 def log(ui, repo, *pats, **opts):
3966 3961 """show revision history of entire repository or files
3967 3962
3968 3963 Print the revision history of the specified files or the entire
3969 3964 project.
3970 3965
3971 3966 If no revision range is specified, the default is ``tip:0`` unless
3972 3967 --follow is set, in which case the working directory parent is
3973 3968 used as the starting revision.
3974 3969
3975 3970 File history is shown without following rename or copy history of
3976 3971 files. Use -f/--follow with a filename to follow history across
3977 3972 renames and copies. --follow without a filename will only show
3978 3973 ancestors or descendants of the starting revision.
3979 3974
3980 3975 By default this command prints revision number and changeset id,
3981 3976 tags, non-trivial parents, user, date and time, and a summary for
3982 3977 each commit. When the -v/--verbose switch is used, the list of
3983 3978 changed files and full commit message are shown.
3984 3979
3985 3980 With --graph the revisions are shown as an ASCII art DAG with the most
3986 3981 recent changeset at the top.
3987 3982 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
3988 3983 and '+' represents a fork where the changeset from the lines below is a
3989 3984 parent of the 'o' merge on the same same line.
3990 3985
3991 3986 .. note::
3992 3987
3993 3988 log -p/--patch may generate unexpected diff output for merge
3994 3989 changesets, as it will only compare the merge changeset against
3995 3990 its first parent. Also, only files different from BOTH parents
3996 3991 will appear in files:.
3997 3992
3998 3993 .. note::
3999 3994
4000 3995 for performance reasons, log FILE may omit duplicate changes
4001 3996 made on branches and will not show deletions. To see all
4002 3997 changes including duplicates and deletions, use the --removed
4003 3998 switch.
4004 3999
4005 4000 .. container:: verbose
4006 4001
4007 4002 Some examples:
4008 4003
4009 4004 - changesets with full descriptions and file lists::
4010 4005
4011 4006 hg log -v
4012 4007
4013 4008 - changesets ancestral to the working directory::
4014 4009
4015 4010 hg log -f
4016 4011
4017 4012 - last 10 commits on the current branch::
4018 4013
4019 4014 hg log -l 10 -b .
4020 4015
4021 4016 - changesets showing all modifications of a file, including removals::
4022 4017
4023 4018 hg log --removed file.c
4024 4019
4025 4020 - all changesets that touch a directory, with diffs, excluding merges::
4026 4021
4027 4022 hg log -Mp lib/
4028 4023
4029 4024 - all revision numbers that match a keyword::
4030 4025
4031 4026 hg log -k bug --template "{rev}\\n"
4032 4027
4033 4028 - check if a given changeset is included is a tagged release::
4034 4029
4035 4030 hg log -r "a21ccf and ancestor(1.9)"
4036 4031
4037 4032 - find all changesets by some user in a date range::
4038 4033
4039 4034 hg log -k alice -d "may 2008 to jul 2008"
4040 4035
4041 4036 - summary of all changesets after the last tag::
4042 4037
4043 4038 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4044 4039
4045 4040 See :hg:`help dates` for a list of formats valid for -d/--date.
4046 4041
4047 4042 See :hg:`help revisions` and :hg:`help revsets` for more about
4048 4043 specifying revisions.
4049 4044
4050 4045 See :hg:`help templates` for more about pre-packaged styles and
4051 4046 specifying custom templates.
4052 4047
4053 4048 Returns 0 on success.
4054 4049 """
4055 4050 if opts.get('graph'):
4056 4051 return cmdutil.graphlog(ui, repo, *pats, **opts)
4057 4052
4058 4053 matchfn = scmutil.match(repo[None], pats, opts)
4059 4054 limit = cmdutil.loglimit(opts)
4060 4055 count = 0
4061 4056
4062 4057 getrenamed, endrev = None, None
4063 4058 if opts.get('copies'):
4064 4059 if opts.get('rev'):
4065 4060 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
4066 4061 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
4067 4062
4068 4063 df = False
4069 4064 if opts.get("date"):
4070 4065 df = util.matchdate(opts["date"])
4071 4066
4072 4067 branches = opts.get('branch', []) + opts.get('only_branch', [])
4073 4068 opts['branch'] = [repo.lookupbranch(b) for b in branches]
4074 4069
4075 4070 displayer = cmdutil.show_changeset(ui, repo, opts, True)
4076 4071 def prep(ctx, fns):
4077 4072 rev = ctx.rev()
4078 4073 parents = [p for p in repo.changelog.parentrevs(rev)
4079 4074 if p != nullrev]
4080 4075 if opts.get('no_merges') and len(parents) == 2:
4081 4076 return
4082 4077 if opts.get('only_merges') and len(parents) != 2:
4083 4078 return
4084 4079 if opts.get('branch') and ctx.branch() not in opts['branch']:
4085 4080 return
4086 4081 if df and not df(ctx.date()[0]):
4087 4082 return
4088 4083
4089 4084 lower = encoding.lower
4090 4085 if opts.get('user'):
4091 4086 luser = lower(ctx.user())
4092 4087 for k in [lower(x) for x in opts['user']]:
4093 4088 if (k in luser):
4094 4089 break
4095 4090 else:
4096 4091 return
4097 4092 if opts.get('keyword'):
4098 4093 luser = lower(ctx.user())
4099 4094 ldesc = lower(ctx.description())
4100 4095 lfiles = lower(" ".join(ctx.files()))
4101 4096 for k in [lower(x) for x in opts['keyword']]:
4102 4097 if (k in luser or k in ldesc or k in lfiles):
4103 4098 break
4104 4099 else:
4105 4100 return
4106 4101
4107 4102 copies = None
4108 4103 if getrenamed is not None and rev:
4109 4104 copies = []
4110 4105 for fn in ctx.files():
4111 4106 rename = getrenamed(fn, rev)
4112 4107 if rename:
4113 4108 copies.append((fn, rename[0]))
4114 4109
4115 4110 revmatchfn = None
4116 4111 if opts.get('patch') or opts.get('stat'):
4117 4112 if opts.get('follow') or opts.get('follow_first'):
4118 4113 # note: this might be wrong when following through merges
4119 4114 revmatchfn = scmutil.match(repo[None], fns, default='path')
4120 4115 else:
4121 4116 revmatchfn = matchfn
4122 4117
4123 4118 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4124 4119
4125 4120 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4126 4121 if displayer.flush(ctx.rev()):
4127 4122 count += 1
4128 4123 if count == limit:
4129 4124 break
4130 4125 displayer.close()
4131 4126
4132 4127 @command('manifest',
4133 4128 [('r', 'rev', '', _('revision to display'), _('REV')),
4134 4129 ('', 'all', False, _("list files from all revisions"))],
4135 4130 _('[-r REV]'))
4136 4131 def manifest(ui, repo, node=None, rev=None, **opts):
4137 4132 """output the current or given revision of the project manifest
4138 4133
4139 4134 Print a list of version controlled files for the given revision.
4140 4135 If no revision is given, the first parent of the working directory
4141 4136 is used, or the null revision if no revision is checked out.
4142 4137
4143 4138 With -v, print file permissions, symlink and executable bits.
4144 4139 With --debug, print file revision hashes.
4145 4140
4146 4141 If option --all is specified, the list of all files from all revisions
4147 4142 is printed. This includes deleted and renamed files.
4148 4143
4149 4144 Returns 0 on success.
4150 4145 """
4151 4146
4152 4147 fm = ui.formatter('manifest', opts)
4153 4148
4154 4149 if opts.get('all'):
4155 4150 if rev or node:
4156 4151 raise util.Abort(_("can't specify a revision with --all"))
4157 4152
4158 4153 res = []
4159 4154 prefix = "data/"
4160 4155 suffix = ".i"
4161 4156 plen = len(prefix)
4162 4157 slen = len(suffix)
4163 4158 lock = repo.lock()
4164 4159 try:
4165 4160 for fn, b, size in repo.store.datafiles():
4166 4161 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4167 4162 res.append(fn[plen:-slen])
4168 4163 finally:
4169 4164 lock.release()
4170 4165 for f in res:
4171 4166 fm.startitem()
4172 4167 fm.write("path", '%s\n', f)
4173 4168 fm.end()
4174 4169 return
4175 4170
4176 4171 if rev and node:
4177 4172 raise util.Abort(_("please specify just one revision"))
4178 4173
4179 4174 if not node:
4180 4175 node = rev
4181 4176
4182 4177 char = {'l': '@', 'x': '*', '': ''}
4183 4178 mode = {'l': '644', 'x': '755', '': '644'}
4184 4179 ctx = scmutil.revsingle(repo, node)
4185 4180 mf = ctx.manifest()
4186 4181 for f in ctx:
4187 4182 fm.startitem()
4188 4183 fl = ctx[f].flags()
4189 4184 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
4190 4185 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
4191 4186 fm.write('path', '%s\n', f)
4192 4187 fm.end()
4193 4188
4194 4189 @command('^merge',
4195 4190 [('f', 'force', None,
4196 4191 _('force a merge including outstanding changes (DEPRECATED)')),
4197 4192 ('r', 'rev', '', _('revision to merge'), _('REV')),
4198 4193 ('P', 'preview', None,
4199 4194 _('review revisions to merge (no merge is performed)'))
4200 4195 ] + mergetoolopts,
4201 4196 _('[-P] [-f] [[-r] REV]'))
4202 4197 def merge(ui, repo, node=None, **opts):
4203 4198 """merge working directory with another revision
4204 4199
4205 4200 The current working directory is updated with all changes made in
4206 4201 the requested revision since the last common predecessor revision.
4207 4202
4208 4203 Files that changed between either parent are marked as changed for
4209 4204 the next commit and a commit must be performed before any further
4210 4205 updates to the repository are allowed. The next commit will have
4211 4206 two parents.
4212 4207
4213 4208 ``--tool`` can be used to specify the merge tool used for file
4214 4209 merges. It overrides the HGMERGE environment variable and your
4215 4210 configuration files. See :hg:`help merge-tools` for options.
4216 4211
4217 4212 If no revision is specified, the working directory's parent is a
4218 4213 head revision, and the current branch contains exactly one other
4219 4214 head, the other head is merged with by default. Otherwise, an
4220 4215 explicit revision with which to merge with must be provided.
4221 4216
4222 4217 :hg:`resolve` must be used to resolve unresolved files.
4223 4218
4224 4219 To undo an uncommitted merge, use :hg:`update --clean .` which
4225 4220 will check out a clean copy of the original merge parent, losing
4226 4221 all changes.
4227 4222
4228 4223 Returns 0 on success, 1 if there are unresolved files.
4229 4224 """
4230 4225
4231 4226 if opts.get('rev') and node:
4232 4227 raise util.Abort(_("please specify just one revision"))
4233 4228 if not node:
4234 4229 node = opts.get('rev')
4235 4230
4236 4231 if node:
4237 4232 node = scmutil.revsingle(repo, node).node()
4238 4233
4239 4234 if not node and repo._bookmarkcurrent:
4240 4235 bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
4241 4236 curhead = repo[repo._bookmarkcurrent].node()
4242 4237 if len(bmheads) == 2:
4243 4238 if curhead == bmheads[0]:
4244 4239 node = bmheads[1]
4245 4240 else:
4246 4241 node = bmheads[0]
4247 4242 elif len(bmheads) > 2:
4248 4243 raise util.Abort(_("multiple matching bookmarks to merge - "
4249 4244 "please merge with an explicit rev or bookmark"),
4250 4245 hint=_("run 'hg heads' to see all heads"))
4251 4246 elif len(bmheads) <= 1:
4252 4247 raise util.Abort(_("no matching bookmark to merge - "
4253 4248 "please merge with an explicit rev or bookmark"),
4254 4249 hint=_("run 'hg heads' to see all heads"))
4255 4250
4256 4251 if not node and not repo._bookmarkcurrent:
4257 4252 branch = repo[None].branch()
4258 4253 bheads = repo.branchheads(branch)
4259 4254 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
4260 4255
4261 4256 if len(nbhs) > 2:
4262 4257 raise util.Abort(_("branch '%s' has %d heads - "
4263 4258 "please merge with an explicit rev")
4264 4259 % (branch, len(bheads)),
4265 4260 hint=_("run 'hg heads .' to see heads"))
4266 4261
4267 4262 parent = repo.dirstate.p1()
4268 4263 if len(nbhs) <= 1:
4269 4264 if len(bheads) > 1:
4270 4265 raise util.Abort(_("heads are bookmarked - "
4271 4266 "please merge with an explicit rev"),
4272 4267 hint=_("run 'hg heads' to see all heads"))
4273 4268 if len(repo.heads()) > 1:
4274 4269 raise util.Abort(_("branch '%s' has one head - "
4275 4270 "please merge with an explicit rev")
4276 4271 % branch,
4277 4272 hint=_("run 'hg heads' to see all heads"))
4278 4273 msg, hint = _('nothing to merge'), None
4279 4274 if parent != repo.lookup(branch):
4280 4275 hint = _("use 'hg update' instead")
4281 4276 raise util.Abort(msg, hint=hint)
4282 4277
4283 4278 if parent not in bheads:
4284 4279 raise util.Abort(_('working directory not at a head revision'),
4285 4280 hint=_("use 'hg update' or merge with an "
4286 4281 "explicit revision"))
4287 4282 if parent == nbhs[0]:
4288 4283 node = nbhs[-1]
4289 4284 else:
4290 4285 node = nbhs[0]
4291 4286
4292 4287 if opts.get('preview'):
4293 4288 # find nodes that are ancestors of p2 but not of p1
4294 4289 p1 = repo.lookup('.')
4295 4290 p2 = repo.lookup(node)
4296 4291 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4297 4292
4298 4293 displayer = cmdutil.show_changeset(ui, repo, opts)
4299 4294 for node in nodes:
4300 4295 displayer.show(repo[node])
4301 4296 displayer.close()
4302 4297 return 0
4303 4298
4304 4299 try:
4305 4300 # ui.forcemerge is an internal variable, do not document
4306 4301 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4307 4302 return hg.merge(repo, node, force=opts.get('force'))
4308 4303 finally:
4309 4304 ui.setconfig('ui', 'forcemerge', '')
4310 4305
4311 4306 @command('outgoing|out',
4312 4307 [('f', 'force', None, _('run even when the destination is unrelated')),
4313 4308 ('r', 'rev', [],
4314 4309 _('a changeset intended to be included in the destination'), _('REV')),
4315 4310 ('n', 'newest-first', None, _('show newest record first')),
4316 4311 ('B', 'bookmarks', False, _('compare bookmarks')),
4317 4312 ('b', 'branch', [], _('a specific branch you would like to push'),
4318 4313 _('BRANCH')),
4319 4314 ] + logopts + remoteopts + subrepoopts,
4320 4315 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4321 4316 def outgoing(ui, repo, dest=None, **opts):
4322 4317 """show changesets not found in the destination
4323 4318
4324 4319 Show changesets not found in the specified destination repository
4325 4320 or the default push location. These are the changesets that would
4326 4321 be pushed if a push was requested.
4327 4322
4328 4323 See pull for details of valid destination formats.
4329 4324
4330 4325 Returns 0 if there are outgoing changes, 1 otherwise.
4331 4326 """
4332 4327 if opts.get('graph'):
4333 4328 cmdutil.checkunsupportedgraphflags([], opts)
4334 4329 o = hg._outgoing(ui, repo, dest, opts)
4335 4330 if o is None:
4336 4331 return
4337 4332
4338 4333 revdag = cmdutil.graphrevs(repo, o, opts)
4339 4334 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4340 4335 showparents = [ctx.node() for ctx in repo[None].parents()]
4341 4336 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4342 4337 graphmod.asciiedges)
4343 4338 return 0
4344 4339
4345 4340 if opts.get('bookmarks'):
4346 4341 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4347 4342 dest, branches = hg.parseurl(dest, opts.get('branch'))
4348 4343 other = hg.peer(repo, opts, dest)
4349 4344 if 'bookmarks' not in other.listkeys('namespaces'):
4350 4345 ui.warn(_("remote doesn't support bookmarks\n"))
4351 4346 return 0
4352 4347 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4353 4348 return bookmarks.diff(ui, other, repo)
4354 4349
4355 4350 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4356 4351 try:
4357 4352 return hg.outgoing(ui, repo, dest, opts)
4358 4353 finally:
4359 4354 del repo._subtoppath
4360 4355
4361 4356 @command('parents',
4362 4357 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4363 4358 ] + templateopts,
4364 4359 _('[-r REV] [FILE]'))
4365 4360 def parents(ui, repo, file_=None, **opts):
4366 4361 """show the parents of the working directory or revision
4367 4362
4368 4363 Print the working directory's parent revisions. If a revision is
4369 4364 given via -r/--rev, the parent of that revision will be printed.
4370 4365 If a file argument is given, the revision in which the file was
4371 4366 last changed (before the working directory revision or the
4372 4367 argument to --rev if given) is printed.
4373 4368
4374 4369 Returns 0 on success.
4375 4370 """
4376 4371
4377 4372 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4378 4373
4379 4374 if file_:
4380 4375 m = scmutil.match(ctx, (file_,), opts)
4381 4376 if m.anypats() or len(m.files()) != 1:
4382 4377 raise util.Abort(_('can only specify an explicit filename'))
4383 4378 file_ = m.files()[0]
4384 4379 filenodes = []
4385 4380 for cp in ctx.parents():
4386 4381 if not cp:
4387 4382 continue
4388 4383 try:
4389 4384 filenodes.append(cp.filenode(file_))
4390 4385 except error.LookupError:
4391 4386 pass
4392 4387 if not filenodes:
4393 4388 raise util.Abort(_("'%s' not found in manifest!") % file_)
4394 4389 p = []
4395 4390 for fn in filenodes:
4396 4391 fctx = repo.filectx(file_, fileid=fn)
4397 4392 p.append(fctx.node())
4398 4393 else:
4399 4394 p = [cp.node() for cp in ctx.parents()]
4400 4395
4401 4396 displayer = cmdutil.show_changeset(ui, repo, opts)
4402 4397 for n in p:
4403 4398 if n != nullid:
4404 4399 displayer.show(repo[n])
4405 4400 displayer.close()
4406 4401
4407 4402 @command('paths', [], _('[NAME]'))
4408 4403 def paths(ui, repo, search=None):
4409 4404 """show aliases for remote repositories
4410 4405
4411 4406 Show definition of symbolic path name NAME. If no name is given,
4412 4407 show definition of all available names.
4413 4408
4414 4409 Option -q/--quiet suppresses all output when searching for NAME
4415 4410 and shows only the path names when listing all definitions.
4416 4411
4417 4412 Path names are defined in the [paths] section of your
4418 4413 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4419 4414 repository, ``.hg/hgrc`` is used, too.
4420 4415
4421 4416 The path names ``default`` and ``default-push`` have a special
4422 4417 meaning. When performing a push or pull operation, they are used
4423 4418 as fallbacks if no location is specified on the command-line.
4424 4419 When ``default-push`` is set, it will be used for push and
4425 4420 ``default`` will be used for pull; otherwise ``default`` is used
4426 4421 as the fallback for both. When cloning a repository, the clone
4427 4422 source is written as ``default`` in ``.hg/hgrc``. Note that
4428 4423 ``default`` and ``default-push`` apply to all inbound (e.g.
4429 4424 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4430 4425 :hg:`bundle`) operations.
4431 4426
4432 4427 See :hg:`help urls` for more information.
4433 4428
4434 4429 Returns 0 on success.
4435 4430 """
4436 4431 if search:
4437 4432 for name, path in ui.configitems("paths"):
4438 4433 if name == search:
4439 4434 ui.status("%s\n" % util.hidepassword(path))
4440 4435 return
4441 4436 if not ui.quiet:
4442 4437 ui.warn(_("not found!\n"))
4443 4438 return 1
4444 4439 else:
4445 4440 for name, path in ui.configitems("paths"):
4446 4441 if ui.quiet:
4447 4442 ui.write("%s\n" % name)
4448 4443 else:
4449 4444 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4450 4445
4451 4446 @command('phase',
4452 4447 [('p', 'public', False, _('set changeset phase to public')),
4453 4448 ('d', 'draft', False, _('set changeset phase to draft')),
4454 4449 ('s', 'secret', False, _('set changeset phase to secret')),
4455 4450 ('f', 'force', False, _('allow to move boundary backward')),
4456 4451 ('r', 'rev', [], _('target revision'), _('REV')),
4457 4452 ],
4458 4453 _('[-p|-d|-s] [-f] [-r] REV...'))
4459 4454 def phase(ui, repo, *revs, **opts):
4460 4455 """set or show the current phase name
4461 4456
4462 4457 With no argument, show the phase name of specified revisions.
4463 4458
4464 4459 With one of -p/--public, -d/--draft or -s/--secret, change the
4465 4460 phase value of the specified revisions.
4466 4461
4467 4462 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4468 4463 lower phase to an higher phase. Phases are ordered as follows::
4469 4464
4470 4465 public < draft < secret
4471 4466
4472 4467 Returns 0 on success, 1 if no phases were changed or some could not
4473 4468 be changed.
4474 4469 """
4475 4470 # search for a unique phase argument
4476 4471 targetphase = None
4477 4472 for idx, name in enumerate(phases.phasenames):
4478 4473 if opts[name]:
4479 4474 if targetphase is not None:
4480 4475 raise util.Abort(_('only one phase can be specified'))
4481 4476 targetphase = idx
4482 4477
4483 4478 # look for specified revision
4484 4479 revs = list(revs)
4485 4480 revs.extend(opts['rev'])
4486 4481 if not revs:
4487 4482 raise util.Abort(_('no revisions specified'))
4488 4483
4489 4484 revs = scmutil.revrange(repo, revs)
4490 4485
4491 4486 lock = None
4492 4487 ret = 0
4493 4488 if targetphase is None:
4494 4489 # display
4495 4490 for r in revs:
4496 4491 ctx = repo[r]
4497 4492 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4498 4493 else:
4499 4494 lock = repo.lock()
4500 4495 try:
4501 4496 # set phase
4502 4497 if not revs:
4503 4498 raise util.Abort(_('empty revision set'))
4504 4499 nodes = [repo[r].node() for r in revs]
4505 4500 olddata = repo._phasecache.getphaserevs(repo)[:]
4506 4501 phases.advanceboundary(repo, targetphase, nodes)
4507 4502 if opts['force']:
4508 4503 phases.retractboundary(repo, targetphase, nodes)
4509 4504 finally:
4510 4505 lock.release()
4511 4506 # moving revision from public to draft may hide them
4512 4507 # We have to check result on an unfiltered repository
4513 4508 unfi = repo.unfiltered()
4514 4509 newdata = repo._phasecache.getphaserevs(unfi)
4515 4510 changes = sum(o != newdata[i] for i, o in enumerate(olddata))
4516 4511 cl = unfi.changelog
4517 4512 rejected = [n for n in nodes
4518 4513 if newdata[cl.rev(n)] < targetphase]
4519 4514 if rejected:
4520 4515 ui.warn(_('cannot move %i changesets to a higher '
4521 4516 'phase, use --force\n') % len(rejected))
4522 4517 ret = 1
4523 4518 if changes:
4524 4519 msg = _('phase changed for %i changesets\n') % changes
4525 4520 if ret:
4526 4521 ui.status(msg)
4527 4522 else:
4528 4523 ui.note(msg)
4529 4524 else:
4530 4525 ui.warn(_('no phases changed\n'))
4531 4526 ret = 1
4532 4527 return ret
4533 4528
4534 4529 def postincoming(ui, repo, modheads, optupdate, checkout):
4535 4530 if modheads == 0:
4536 4531 return
4537 4532 if optupdate:
4538 4533 checkout, movemarkfrom = bookmarks.calculateupdate(ui, repo, checkout)
4539 4534 try:
4540 4535 ret = hg.update(repo, checkout)
4541 4536 except util.Abort, inst:
4542 4537 ui.warn(_("not updating: %s\n") % str(inst))
4543 4538 if inst.hint:
4544 4539 ui.warn(_("(%s)\n") % inst.hint)
4545 4540 return 0
4546 4541 if not ret and not checkout:
4547 4542 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4548 4543 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4549 4544 return ret
4550 4545 if modheads > 1:
4551 4546 currentbranchheads = len(repo.branchheads())
4552 4547 if currentbranchheads == modheads:
4553 4548 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4554 4549 elif currentbranchheads > 1:
4555 4550 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4556 4551 "merge)\n"))
4557 4552 else:
4558 4553 ui.status(_("(run 'hg heads' to see heads)\n"))
4559 4554 else:
4560 4555 ui.status(_("(run 'hg update' to get a working copy)\n"))
4561 4556
4562 4557 @command('^pull',
4563 4558 [('u', 'update', None,
4564 4559 _('update to new branch head if changesets were pulled')),
4565 4560 ('f', 'force', None, _('run even when remote repository is unrelated')),
4566 4561 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4567 4562 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4568 4563 ('b', 'branch', [], _('a specific branch you would like to pull'),
4569 4564 _('BRANCH')),
4570 4565 ] + remoteopts,
4571 4566 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4572 4567 def pull(ui, repo, source="default", **opts):
4573 4568 """pull changes from the specified source
4574 4569
4575 4570 Pull changes from a remote repository to a local one.
4576 4571
4577 4572 This finds all changes from the repository at the specified path
4578 4573 or URL and adds them to a local repository (the current one unless
4579 4574 -R is specified). By default, this does not update the copy of the
4580 4575 project in the working directory.
4581 4576
4582 4577 Use :hg:`incoming` if you want to see what would have been added
4583 4578 by a pull at the time you issued this command. If you then decide
4584 4579 to add those changes to the repository, you should use :hg:`pull
4585 4580 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4586 4581
4587 4582 If SOURCE is omitted, the 'default' path will be used.
4588 4583 See :hg:`help urls` for more information.
4589 4584
4590 4585 Returns 0 on success, 1 if an update had unresolved files.
4591 4586 """
4592 4587 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4593 4588 other = hg.peer(repo, opts, source)
4594 4589 try:
4595 4590 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4596 4591 revs, checkout = hg.addbranchrevs(repo, other, branches,
4597 4592 opts.get('rev'))
4598 4593
4599 4594 remotebookmarks = other.listkeys('bookmarks')
4600 4595
4601 4596 if opts.get('bookmark'):
4602 4597 if not revs:
4603 4598 revs = []
4604 4599 for b in opts['bookmark']:
4605 4600 if b not in remotebookmarks:
4606 4601 raise util.Abort(_('remote bookmark %s not found!') % b)
4607 4602 revs.append(remotebookmarks[b])
4608 4603
4609 4604 if revs:
4610 4605 try:
4611 4606 revs = [other.lookup(rev) for rev in revs]
4612 4607 except error.CapabilityError:
4613 4608 err = _("other repository doesn't support revision lookup, "
4614 4609 "so a rev cannot be specified.")
4615 4610 raise util.Abort(err)
4616 4611
4617 4612 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
4618 4613 bookmarks.updatefromremote(ui, repo, remotebookmarks, source)
4619 4614 if checkout:
4620 4615 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4621 4616 repo._subtoppath = source
4622 4617 try:
4623 4618 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4624 4619
4625 4620 finally:
4626 4621 del repo._subtoppath
4627 4622
4628 4623 # update specified bookmarks
4629 4624 if opts.get('bookmark'):
4630 4625 marks = repo._bookmarks
4631 4626 for b in opts['bookmark']:
4632 4627 # explicit pull overrides local bookmark if any
4633 4628 ui.status(_("importing bookmark %s\n") % b)
4634 4629 marks[b] = repo[remotebookmarks[b]].node()
4635 4630 marks.write()
4636 4631 finally:
4637 4632 other.close()
4638 4633 return ret
4639 4634
4640 4635 @command('^push',
4641 4636 [('f', 'force', None, _('force push')),
4642 4637 ('r', 'rev', [],
4643 4638 _('a changeset intended to be included in the destination'),
4644 4639 _('REV')),
4645 4640 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4646 4641 ('b', 'branch', [],
4647 4642 _('a specific branch you would like to push'), _('BRANCH')),
4648 4643 ('', 'new-branch', False, _('allow pushing a new branch')),
4649 4644 ] + remoteopts,
4650 4645 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4651 4646 def push(ui, repo, dest=None, **opts):
4652 4647 """push changes to the specified destination
4653 4648
4654 4649 Push changesets from the local repository to the specified
4655 4650 destination.
4656 4651
4657 4652 This operation is symmetrical to pull: it is identical to a pull
4658 4653 in the destination repository from the current one.
4659 4654
4660 4655 By default, push will not allow creation of new heads at the
4661 4656 destination, since multiple heads would make it unclear which head
4662 4657 to use. In this situation, it is recommended to pull and merge
4663 4658 before pushing.
4664 4659
4665 4660 Use --new-branch if you want to allow push to create a new named
4666 4661 branch that is not present at the destination. This allows you to
4667 4662 only create a new branch without forcing other changes.
4668 4663
4669 4664 .. note::
4670 4665
4671 4666 Extra care should be taken with the -f/--force option,
4672 4667 which will push all new heads on all branches, an action which will
4673 4668 almost always cause confusion for collaborators.
4674 4669
4675 4670 If -r/--rev is used, the specified revision and all its ancestors
4676 4671 will be pushed to the remote repository.
4677 4672
4678 4673 If -B/--bookmark is used, the specified bookmarked revision, its
4679 4674 ancestors, and the bookmark will be pushed to the remote
4680 4675 repository.
4681 4676
4682 4677 Please see :hg:`help urls` for important details about ``ssh://``
4683 4678 URLs. If DESTINATION is omitted, a default path will be used.
4684 4679
4685 4680 Returns 0 if push was successful, 1 if nothing to push.
4686 4681 """
4687 4682
4688 4683 if opts.get('bookmark'):
4689 4684 ui.setconfig('bookmarks', 'pushing', opts['bookmark'])
4690 4685 for b in opts['bookmark']:
4691 4686 # translate -B options to -r so changesets get pushed
4692 4687 if b in repo._bookmarks:
4693 4688 opts.setdefault('rev', []).append(b)
4694 4689 else:
4695 4690 # if we try to push a deleted bookmark, translate it to null
4696 4691 # this lets simultaneous -r, -b options continue working
4697 4692 opts.setdefault('rev', []).append("null")
4698 4693
4699 4694 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4700 4695 dest, branches = hg.parseurl(dest, opts.get('branch'))
4701 4696 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4702 4697 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4703 4698 try:
4704 4699 other = hg.peer(repo, opts, dest)
4705 4700 except error.RepoError:
4706 4701 if dest == "default-push":
4707 4702 raise util.Abort(_("default repository not configured!"),
4708 4703 hint=_('see the "path" section in "hg help config"'))
4709 4704 else:
4710 4705 raise
4711 4706
4712 4707 if revs:
4713 4708 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4714 4709
4715 4710 repo._subtoppath = dest
4716 4711 try:
4717 4712 # push subrepos depth-first for coherent ordering
4718 4713 c = repo['']
4719 4714 subs = c.substate # only repos that are committed
4720 4715 for s in sorted(subs):
4721 4716 if c.sub(s).push(opts) == 0:
4722 4717 return False
4723 4718 finally:
4724 4719 del repo._subtoppath
4725 4720 result = repo.push(other, opts.get('force'), revs=revs,
4726 4721 newbranch=opts.get('new_branch'))
4727 4722
4728 4723 result = not result
4729 4724
4730 4725 if opts.get('bookmark'):
4731 4726 bresult = bookmarks.pushtoremote(ui, repo, other, opts['bookmark'])
4732 4727 if bresult == 2:
4733 4728 return 2
4734 4729 if not result and bresult:
4735 4730 result = 2
4736 4731
4737 4732 return result
4738 4733
4739 4734 @command('recover', [])
4740 4735 def recover(ui, repo):
4741 4736 """roll back an interrupted transaction
4742 4737
4743 4738 Recover from an interrupted commit or pull.
4744 4739
4745 4740 This command tries to fix the repository status after an
4746 4741 interrupted operation. It should only be necessary when Mercurial
4747 4742 suggests it.
4748 4743
4749 4744 Returns 0 if successful, 1 if nothing to recover or verify fails.
4750 4745 """
4751 4746 if repo.recover():
4752 4747 return hg.verify(repo)
4753 4748 return 1
4754 4749
4755 4750 @command('^remove|rm',
4756 4751 [('A', 'after', None, _('record delete for missing files')),
4757 4752 ('f', 'force', None,
4758 4753 _('remove (and delete) file even if added or modified')),
4759 4754 ] + walkopts,
4760 4755 _('[OPTION]... FILE...'))
4761 4756 def remove(ui, repo, *pats, **opts):
4762 4757 """remove the specified files on the next commit
4763 4758
4764 4759 Schedule the indicated files for removal from the current branch.
4765 4760
4766 4761 This command schedules the files to be removed at the next commit.
4767 4762 To undo a remove before that, see :hg:`revert`. To undo added
4768 4763 files, see :hg:`forget`.
4769 4764
4770 4765 .. container:: verbose
4771 4766
4772 4767 -A/--after can be used to remove only files that have already
4773 4768 been deleted, -f/--force can be used to force deletion, and -Af
4774 4769 can be used to remove files from the next revision without
4775 4770 deleting them from the working directory.
4776 4771
4777 4772 The following table details the behavior of remove for different
4778 4773 file states (columns) and option combinations (rows). The file
4779 4774 states are Added [A], Clean [C], Modified [M] and Missing [!]
4780 4775 (as reported by :hg:`status`). The actions are Warn, Remove
4781 4776 (from branch) and Delete (from disk):
4782 4777
4783 4778 ========= == == == ==
4784 4779 opt/state A C M !
4785 4780 ========= == == == ==
4786 4781 none W RD W R
4787 4782 -f R RD RD R
4788 4783 -A W W W R
4789 4784 -Af R R R R
4790 4785 ========= == == == ==
4791 4786
4792 4787 Note that remove never deletes files in Added [A] state from the
4793 4788 working directory, not even if option --force is specified.
4794 4789
4795 4790 Returns 0 on success, 1 if any warnings encountered.
4796 4791 """
4797 4792
4798 4793 ret = 0
4799 4794 after, force = opts.get('after'), opts.get('force')
4800 4795 if not pats and not after:
4801 4796 raise util.Abort(_('no files specified'))
4802 4797
4803 4798 m = scmutil.match(repo[None], pats, opts)
4804 4799 s = repo.status(match=m, clean=True)
4805 4800 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
4806 4801
4807 4802 # warn about failure to delete explicit files/dirs
4808 4803 wctx = repo[None]
4809 4804 for f in m.files():
4810 4805 if f in repo.dirstate or f in wctx.dirs():
4811 4806 continue
4812 4807 if os.path.exists(m.rel(f)):
4813 4808 if os.path.isdir(m.rel(f)):
4814 4809 ui.warn(_('not removing %s: no tracked files\n') % m.rel(f))
4815 4810 else:
4816 4811 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
4817 4812 # missing files will generate a warning elsewhere
4818 4813 ret = 1
4819 4814
4820 4815 if force:
4821 4816 list = modified + deleted + clean + added
4822 4817 elif after:
4823 4818 list = deleted
4824 4819 for f in modified + added + clean:
4825 4820 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
4826 4821 ret = 1
4827 4822 else:
4828 4823 list = deleted + clean
4829 4824 for f in modified:
4830 4825 ui.warn(_('not removing %s: file is modified (use -f'
4831 4826 ' to force removal)\n') % m.rel(f))
4832 4827 ret = 1
4833 4828 for f in added:
4834 4829 ui.warn(_('not removing %s: file has been marked for add'
4835 4830 ' (use forget to undo)\n') % m.rel(f))
4836 4831 ret = 1
4837 4832
4838 4833 for f in sorted(list):
4839 4834 if ui.verbose or not m.exact(f):
4840 4835 ui.status(_('removing %s\n') % m.rel(f))
4841 4836
4842 4837 wlock = repo.wlock()
4843 4838 try:
4844 4839 if not after:
4845 4840 for f in list:
4846 4841 if f in added:
4847 4842 continue # we never unlink added files on remove
4848 4843 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
4849 4844 repo[None].forget(list)
4850 4845 finally:
4851 4846 wlock.release()
4852 4847
4853 4848 return ret
4854 4849
4855 4850 @command('rename|move|mv',
4856 4851 [('A', 'after', None, _('record a rename that has already occurred')),
4857 4852 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4858 4853 ] + walkopts + dryrunopts,
4859 4854 _('[OPTION]... SOURCE... DEST'))
4860 4855 def rename(ui, repo, *pats, **opts):
4861 4856 """rename files; equivalent of copy + remove
4862 4857
4863 4858 Mark dest as copies of sources; mark sources for deletion. If dest
4864 4859 is a directory, copies are put in that directory. If dest is a
4865 4860 file, there can only be one source.
4866 4861
4867 4862 By default, this command copies the contents of files as they
4868 4863 exist in the working directory. If invoked with -A/--after, the
4869 4864 operation is recorded, but no copying is performed.
4870 4865
4871 4866 This command takes effect at the next commit. To undo a rename
4872 4867 before that, see :hg:`revert`.
4873 4868
4874 4869 Returns 0 on success, 1 if errors are encountered.
4875 4870 """
4876 4871 wlock = repo.wlock(False)
4877 4872 try:
4878 4873 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4879 4874 finally:
4880 4875 wlock.release()
4881 4876
4882 4877 @command('resolve',
4883 4878 [('a', 'all', None, _('select all unresolved files')),
4884 4879 ('l', 'list', None, _('list state of files needing merge')),
4885 4880 ('m', 'mark', None, _('mark files as resolved')),
4886 4881 ('u', 'unmark', None, _('mark files as unresolved')),
4887 4882 ('n', 'no-status', None, _('hide status prefix'))]
4888 4883 + mergetoolopts + walkopts,
4889 4884 _('[OPTION]... [FILE]...'))
4890 4885 def resolve(ui, repo, *pats, **opts):
4891 4886 """redo merges or set/view the merge status of files
4892 4887
4893 4888 Merges with unresolved conflicts are often the result of
4894 4889 non-interactive merging using the ``internal:merge`` configuration
4895 4890 setting, or a command-line merge tool like ``diff3``. The resolve
4896 4891 command is used to manage the files involved in a merge, after
4897 4892 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4898 4893 working directory must have two parents). See :hg:`help
4899 4894 merge-tools` for information on configuring merge tools.
4900 4895
4901 4896 The resolve command can be used in the following ways:
4902 4897
4903 4898 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4904 4899 files, discarding any previous merge attempts. Re-merging is not
4905 4900 performed for files already marked as resolved. Use ``--all/-a``
4906 4901 to select all unresolved files. ``--tool`` can be used to specify
4907 4902 the merge tool used for the given files. It overrides the HGMERGE
4908 4903 environment variable and your configuration files. Previous file
4909 4904 contents are saved with a ``.orig`` suffix.
4910 4905
4911 4906 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4912 4907 (e.g. after having manually fixed-up the files). The default is
4913 4908 to mark all unresolved files.
4914 4909
4915 4910 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4916 4911 default is to mark all resolved files.
4917 4912
4918 4913 - :hg:`resolve -l`: list files which had or still have conflicts.
4919 4914 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4920 4915
4921 4916 Note that Mercurial will not let you commit files with unresolved
4922 4917 merge conflicts. You must use :hg:`resolve -m ...` before you can
4923 4918 commit after a conflicting merge.
4924 4919
4925 4920 Returns 0 on success, 1 if any files fail a resolve attempt.
4926 4921 """
4927 4922
4928 4923 all, mark, unmark, show, nostatus = \
4929 4924 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
4930 4925
4931 4926 if (show and (mark or unmark)) or (mark and unmark):
4932 4927 raise util.Abort(_("too many options specified"))
4933 4928 if pats and all:
4934 4929 raise util.Abort(_("can't specify --all and patterns"))
4935 4930 if not (all or pats or show or mark or unmark):
4936 4931 raise util.Abort(_('no files or directories specified; '
4937 4932 'use --all to remerge all files'))
4938 4933
4939 4934 ms = mergemod.mergestate(repo)
4940 4935 m = scmutil.match(repo[None], pats, opts)
4941 4936 ret = 0
4942 4937
4943 4938 for f in ms:
4944 4939 if m(f):
4945 4940 if show:
4946 4941 if nostatus:
4947 4942 ui.write("%s\n" % f)
4948 4943 else:
4949 4944 ui.write("%s %s\n" % (ms[f].upper(), f),
4950 4945 label='resolve.' +
4951 4946 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4952 4947 elif mark:
4953 4948 ms.mark(f, "r")
4954 4949 elif unmark:
4955 4950 ms.mark(f, "u")
4956 4951 else:
4957 4952 wctx = repo[None]
4958 4953
4959 4954 # backup pre-resolve (merge uses .orig for its own purposes)
4960 4955 a = repo.wjoin(f)
4961 4956 util.copyfile(a, a + ".resolve")
4962 4957
4963 4958 try:
4964 4959 # resolve file
4965 4960 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4966 4961 if ms.resolve(f, wctx):
4967 4962 ret = 1
4968 4963 finally:
4969 4964 ui.setconfig('ui', 'forcemerge', '')
4970 4965 ms.commit()
4971 4966
4972 4967 # replace filemerge's .orig file with our resolve file
4973 4968 util.rename(a + ".resolve", a + ".orig")
4974 4969
4975 4970 ms.commit()
4976 4971 return ret
4977 4972
4978 4973 @command('revert',
4979 4974 [('a', 'all', None, _('revert all changes when no arguments given')),
4980 4975 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4981 4976 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4982 4977 ('C', 'no-backup', None, _('do not save backup copies of files')),
4983 4978 ] + walkopts + dryrunopts,
4984 4979 _('[OPTION]... [-r REV] [NAME]...'))
4985 4980 def revert(ui, repo, *pats, **opts):
4986 4981 """restore files to their checkout state
4987 4982
4988 4983 .. note::
4989 4984
4990 4985 To check out earlier revisions, you should use :hg:`update REV`.
4991 4986 To cancel an uncommitted merge (and lose your changes),
4992 4987 use :hg:`update --clean .`.
4993 4988
4994 4989 With no revision specified, revert the specified files or directories
4995 4990 to the contents they had in the parent of the working directory.
4996 4991 This restores the contents of files to an unmodified
4997 4992 state and unschedules adds, removes, copies, and renames. If the
4998 4993 working directory has two parents, you must explicitly specify a
4999 4994 revision.
5000 4995
5001 4996 Using the -r/--rev or -d/--date options, revert the given files or
5002 4997 directories to their states as of a specific revision. Because
5003 4998 revert does not change the working directory parents, this will
5004 4999 cause these files to appear modified. This can be helpful to "back
5005 5000 out" some or all of an earlier change. See :hg:`backout` for a
5006 5001 related method.
5007 5002
5008 5003 Modified files are saved with a .orig suffix before reverting.
5009 5004 To disable these backups, use --no-backup.
5010 5005
5011 5006 See :hg:`help dates` for a list of formats valid for -d/--date.
5012 5007
5013 5008 Returns 0 on success.
5014 5009 """
5015 5010
5016 5011 if opts.get("date"):
5017 5012 if opts.get("rev"):
5018 5013 raise util.Abort(_("you can't specify a revision and a date"))
5019 5014 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5020 5015
5021 5016 parent, p2 = repo.dirstate.parents()
5022 5017 if not opts.get('rev') and p2 != nullid:
5023 5018 # revert after merge is a trap for new users (issue2915)
5024 5019 raise util.Abort(_('uncommitted merge with no revision specified'),
5025 5020 hint=_('use "hg update" or see "hg help revert"'))
5026 5021
5027 5022 ctx = scmutil.revsingle(repo, opts.get('rev'))
5028 5023
5029 5024 if not pats and not opts.get('all'):
5030 5025 msg = _("no files or directories specified")
5031 5026 if p2 != nullid:
5032 5027 hint = _("uncommitted merge, use --all to discard all changes,"
5033 5028 " or 'hg update -C .' to abort the merge")
5034 5029 raise util.Abort(msg, hint=hint)
5035 5030 dirty = util.any(repo.status())
5036 5031 node = ctx.node()
5037 5032 if node != parent:
5038 5033 if dirty:
5039 5034 hint = _("uncommitted changes, use --all to discard all"
5040 5035 " changes, or 'hg update %s' to update") % ctx.rev()
5041 5036 else:
5042 5037 hint = _("use --all to revert all files,"
5043 5038 " or 'hg update %s' to update") % ctx.rev()
5044 5039 elif dirty:
5045 5040 hint = _("uncommitted changes, use --all to discard all changes")
5046 5041 else:
5047 5042 hint = _("use --all to revert all files")
5048 5043 raise util.Abort(msg, hint=hint)
5049 5044
5050 5045 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
5051 5046
5052 5047 @command('rollback', dryrunopts +
5053 5048 [('f', 'force', False, _('ignore safety measures'))])
5054 5049 def rollback(ui, repo, **opts):
5055 5050 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5056 5051
5057 5052 Please use :hg:`commit --amend` instead of rollback to correct
5058 5053 mistakes in the last commit.
5059 5054
5060 5055 This command should be used with care. There is only one level of
5061 5056 rollback, and there is no way to undo a rollback. It will also
5062 5057 restore the dirstate at the time of the last transaction, losing
5063 5058 any dirstate changes since that time. This command does not alter
5064 5059 the working directory.
5065 5060
5066 5061 Transactions are used to encapsulate the effects of all commands
5067 5062 that create new changesets or propagate existing changesets into a
5068 5063 repository.
5069 5064
5070 5065 .. container:: verbose
5071 5066
5072 5067 For example, the following commands are transactional, and their
5073 5068 effects can be rolled back:
5074 5069
5075 5070 - commit
5076 5071 - import
5077 5072 - pull
5078 5073 - push (with this repository as the destination)
5079 5074 - unbundle
5080 5075
5081 5076 To avoid permanent data loss, rollback will refuse to rollback a
5082 5077 commit transaction if it isn't checked out. Use --force to
5083 5078 override this protection.
5084 5079
5085 5080 This command is not intended for use on public repositories. Once
5086 5081 changes are visible for pull by other users, rolling a transaction
5087 5082 back locally is ineffective (someone else may already have pulled
5088 5083 the changes). Furthermore, a race is possible with readers of the
5089 5084 repository; for example an in-progress pull from the repository
5090 5085 may fail if a rollback is performed.
5091 5086
5092 5087 Returns 0 on success, 1 if no rollback data is available.
5093 5088 """
5094 5089 return repo.rollback(dryrun=opts.get('dry_run'),
5095 5090 force=opts.get('force'))
5096 5091
5097 5092 @command('root', [])
5098 5093 def root(ui, repo):
5099 5094 """print the root (top) of the current working directory
5100 5095
5101 5096 Print the root directory of the current repository.
5102 5097
5103 5098 Returns 0 on success.
5104 5099 """
5105 5100 ui.write(repo.root + "\n")
5106 5101
5107 5102 @command('^serve',
5108 5103 [('A', 'accesslog', '', _('name of access log file to write to'),
5109 5104 _('FILE')),
5110 5105 ('d', 'daemon', None, _('run server in background')),
5111 5106 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
5112 5107 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5113 5108 # use string type, then we can check if something was passed
5114 5109 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5115 5110 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5116 5111 _('ADDR')),
5117 5112 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5118 5113 _('PREFIX')),
5119 5114 ('n', 'name', '',
5120 5115 _('name to show in web pages (default: working directory)'), _('NAME')),
5121 5116 ('', 'web-conf', '',
5122 5117 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
5123 5118 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5124 5119 _('FILE')),
5125 5120 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5126 5121 ('', 'stdio', None, _('for remote clients')),
5127 5122 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
5128 5123 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5129 5124 ('', 'style', '', _('template style to use'), _('STYLE')),
5130 5125 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5131 5126 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
5132 5127 _('[OPTION]...'))
5133 5128 def serve(ui, repo, **opts):
5134 5129 """start stand-alone webserver
5135 5130
5136 5131 Start a local HTTP repository browser and pull server. You can use
5137 5132 this for ad-hoc sharing and browsing of repositories. It is
5138 5133 recommended to use a real web server to serve a repository for
5139 5134 longer periods of time.
5140 5135
5141 5136 Please note that the server does not implement access control.
5142 5137 This means that, by default, anybody can read from the server and
5143 5138 nobody can write to it by default. Set the ``web.allow_push``
5144 5139 option to ``*`` to allow everybody to push to the server. You
5145 5140 should use a real web server if you need to authenticate users.
5146 5141
5147 5142 By default, the server logs accesses to stdout and errors to
5148 5143 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5149 5144 files.
5150 5145
5151 5146 To have the server choose a free port number to listen on, specify
5152 5147 a port number of 0; in this case, the server will print the port
5153 5148 number it uses.
5154 5149
5155 5150 Returns 0 on success.
5156 5151 """
5157 5152
5158 5153 if opts["stdio"] and opts["cmdserver"]:
5159 5154 raise util.Abort(_("cannot use --stdio with --cmdserver"))
5160 5155
5161 5156 def checkrepo():
5162 5157 if repo is None:
5163 5158 raise error.RepoError(_("there is no Mercurial repository here"
5164 5159 " (.hg not found)"))
5165 5160
5166 5161 if opts["stdio"]:
5167 5162 checkrepo()
5168 5163 s = sshserver.sshserver(ui, repo)
5169 5164 s.serve_forever()
5170 5165
5171 5166 if opts["cmdserver"]:
5172 5167 s = commandserver.server(ui, repo, opts["cmdserver"])
5173 5168 return s.serve()
5174 5169
5175 5170 # this way we can check if something was given in the command-line
5176 5171 if opts.get('port'):
5177 5172 opts['port'] = util.getport(opts.get('port'))
5178 5173
5179 5174 baseui = repo and repo.baseui or ui
5180 5175 optlist = ("name templates style address port prefix ipv6"
5181 5176 " accesslog errorlog certificate encoding")
5182 5177 for o in optlist.split():
5183 5178 val = opts.get(o, '')
5184 5179 if val in (None, ''): # should check against default options instead
5185 5180 continue
5186 5181 baseui.setconfig("web", o, val)
5187 5182 if repo and repo.ui != baseui:
5188 5183 repo.ui.setconfig("web", o, val)
5189 5184
5190 5185 o = opts.get('web_conf') or opts.get('webdir_conf')
5191 5186 if not o:
5192 5187 if not repo:
5193 5188 raise error.RepoError(_("there is no Mercurial repository"
5194 5189 " here (.hg not found)"))
5195 5190 o = repo
5196 5191
5197 5192 app = hgweb.hgweb(o, baseui=baseui)
5198 5193 service = httpservice(ui, app, opts)
5199 5194 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5200 5195
5201 5196 class httpservice(object):
5202 5197 def __init__(self, ui, app, opts):
5203 5198 self.ui = ui
5204 5199 self.app = app
5205 5200 self.opts = opts
5206 5201
5207 5202 def init(self):
5208 5203 util.setsignalhandler()
5209 5204 self.httpd = hgweb_server.create_server(self.ui, self.app)
5210 5205
5211 5206 if self.opts['port'] and not self.ui.verbose:
5212 5207 return
5213 5208
5214 5209 if self.httpd.prefix:
5215 5210 prefix = self.httpd.prefix.strip('/') + '/'
5216 5211 else:
5217 5212 prefix = ''
5218 5213
5219 5214 port = ':%d' % self.httpd.port
5220 5215 if port == ':80':
5221 5216 port = ''
5222 5217
5223 5218 bindaddr = self.httpd.addr
5224 5219 if bindaddr == '0.0.0.0':
5225 5220 bindaddr = '*'
5226 5221 elif ':' in bindaddr: # IPv6
5227 5222 bindaddr = '[%s]' % bindaddr
5228 5223
5229 5224 fqaddr = self.httpd.fqaddr
5230 5225 if ':' in fqaddr:
5231 5226 fqaddr = '[%s]' % fqaddr
5232 5227 if self.opts['port']:
5233 5228 write = self.ui.status
5234 5229 else:
5235 5230 write = self.ui.write
5236 5231 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5237 5232 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5238 5233
5239 5234 def run(self):
5240 5235 self.httpd.serve_forever()
5241 5236
5242 5237
5243 5238 @command('^status|st',
5244 5239 [('A', 'all', None, _('show status of all files')),
5245 5240 ('m', 'modified', None, _('show only modified files')),
5246 5241 ('a', 'added', None, _('show only added files')),
5247 5242 ('r', 'removed', None, _('show only removed files')),
5248 5243 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5249 5244 ('c', 'clean', None, _('show only files without changes')),
5250 5245 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5251 5246 ('i', 'ignored', None, _('show only ignored files')),
5252 5247 ('n', 'no-status', None, _('hide status prefix')),
5253 5248 ('C', 'copies', None, _('show source of copied files')),
5254 5249 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5255 5250 ('', 'rev', [], _('show difference from revision'), _('REV')),
5256 5251 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5257 5252 ] + walkopts + subrepoopts,
5258 5253 _('[OPTION]... [FILE]...'))
5259 5254 def status(ui, repo, *pats, **opts):
5260 5255 """show changed files in the working directory
5261 5256
5262 5257 Show status of files in the repository. If names are given, only
5263 5258 files that match are shown. Files that are clean or ignored or
5264 5259 the source of a copy/move operation, are not listed unless
5265 5260 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5266 5261 Unless options described with "show only ..." are given, the
5267 5262 options -mardu are used.
5268 5263
5269 5264 Option -q/--quiet hides untracked (unknown and ignored) files
5270 5265 unless explicitly requested with -u/--unknown or -i/--ignored.
5271 5266
5272 5267 .. note::
5273 5268
5274 5269 status may appear to disagree with diff if permissions have
5275 5270 changed or a merge has occurred. The standard diff format does
5276 5271 not report permission changes and diff only reports changes
5277 5272 relative to one merge parent.
5278 5273
5279 5274 If one revision is given, it is used as the base revision.
5280 5275 If two revisions are given, the differences between them are
5281 5276 shown. The --change option can also be used as a shortcut to list
5282 5277 the changed files of a revision from its first parent.
5283 5278
5284 5279 The codes used to show the status of files are::
5285 5280
5286 5281 M = modified
5287 5282 A = added
5288 5283 R = removed
5289 5284 C = clean
5290 5285 ! = missing (deleted by non-hg command, but still tracked)
5291 5286 ? = not tracked
5292 5287 I = ignored
5293 5288 = origin of the previous file (with --copies)
5294 5289
5295 5290 .. container:: verbose
5296 5291
5297 5292 Examples:
5298 5293
5299 5294 - show changes in the working directory relative to a
5300 5295 changeset::
5301 5296
5302 5297 hg status --rev 9353
5303 5298
5304 5299 - show all changes including copies in an existing changeset::
5305 5300
5306 5301 hg status --copies --change 9353
5307 5302
5308 5303 - get a NUL separated list of added files, suitable for xargs::
5309 5304
5310 5305 hg status -an0
5311 5306
5312 5307 Returns 0 on success.
5313 5308 """
5314 5309
5315 5310 revs = opts.get('rev')
5316 5311 change = opts.get('change')
5317 5312
5318 5313 if revs and change:
5319 5314 msg = _('cannot specify --rev and --change at the same time')
5320 5315 raise util.Abort(msg)
5321 5316 elif change:
5322 5317 node2 = scmutil.revsingle(repo, change, None).node()
5323 5318 node1 = repo[node2].p1().node()
5324 5319 else:
5325 5320 node1, node2 = scmutil.revpair(repo, revs)
5326 5321
5327 5322 cwd = (pats and repo.getcwd()) or ''
5328 5323 end = opts.get('print0') and '\0' or '\n'
5329 5324 copy = {}
5330 5325 states = 'modified added removed deleted unknown ignored clean'.split()
5331 5326 show = [k for k in states if opts.get(k)]
5332 5327 if opts.get('all'):
5333 5328 show += ui.quiet and (states[:4] + ['clean']) or states
5334 5329 if not show:
5335 5330 show = ui.quiet and states[:4] or states[:5]
5336 5331
5337 5332 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5338 5333 'ignored' in show, 'clean' in show, 'unknown' in show,
5339 5334 opts.get('subrepos'))
5340 5335 changestates = zip(states, 'MAR!?IC', stat)
5341 5336
5342 5337 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5343 5338 copy = copies.pathcopies(repo[node1], repo[node2])
5344 5339
5345 5340 fm = ui.formatter('status', opts)
5346 5341 fmt = '%s' + end
5347 5342 showchar = not opts.get('no_status')
5348 5343
5349 5344 for state, char, files in changestates:
5350 5345 if state in show:
5351 5346 label = 'status.' + state
5352 5347 for f in files:
5353 5348 fm.startitem()
5354 5349 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5355 5350 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
5356 5351 if f in copy:
5357 5352 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5358 5353 label='status.copied')
5359 5354 fm.end()
5360 5355
5361 5356 @command('^summary|sum',
5362 5357 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5363 5358 def summary(ui, repo, **opts):
5364 5359 """summarize working directory state
5365 5360
5366 5361 This generates a brief summary of the working directory state,
5367 5362 including parents, branch, commit status, and available updates.
5368 5363
5369 5364 With the --remote option, this will check the default paths for
5370 5365 incoming and outgoing changes. This can be time-consuming.
5371 5366
5372 5367 Returns 0 on success.
5373 5368 """
5374 5369
5375 5370 ctx = repo[None]
5376 5371 parents = ctx.parents()
5377 5372 pnode = parents[0].node()
5378 5373 marks = []
5379 5374
5380 5375 for p in parents:
5381 5376 # label with log.changeset (instead of log.parent) since this
5382 5377 # shows a working directory parent *changeset*:
5383 5378 # i18n: column positioning for "hg summary"
5384 5379 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5385 5380 label='log.changeset changeset.%s' % p.phasestr())
5386 5381 ui.write(' '.join(p.tags()), label='log.tag')
5387 5382 if p.bookmarks():
5388 5383 marks.extend(p.bookmarks())
5389 5384 if p.rev() == -1:
5390 5385 if not len(repo):
5391 5386 ui.write(_(' (empty repository)'))
5392 5387 else:
5393 5388 ui.write(_(' (no revision checked out)'))
5394 5389 ui.write('\n')
5395 5390 if p.description():
5396 5391 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5397 5392 label='log.summary')
5398 5393
5399 5394 branch = ctx.branch()
5400 5395 bheads = repo.branchheads(branch)
5401 5396 # i18n: column positioning for "hg summary"
5402 5397 m = _('branch: %s\n') % branch
5403 5398 if branch != 'default':
5404 5399 ui.write(m, label='log.branch')
5405 5400 else:
5406 5401 ui.status(m, label='log.branch')
5407 5402
5408 5403 if marks:
5409 5404 current = repo._bookmarkcurrent
5410 5405 # i18n: column positioning for "hg summary"
5411 5406 ui.write(_('bookmarks:'), label='log.bookmark')
5412 5407 if current is not None:
5413 5408 if current in marks:
5414 5409 ui.write(' *' + current, label='bookmarks.current')
5415 5410 marks.remove(current)
5416 5411 else:
5417 5412 ui.write(' [%s]' % current, label='bookmarks.current')
5418 5413 for m in marks:
5419 5414 ui.write(' ' + m, label='log.bookmark')
5420 5415 ui.write('\n', label='log.bookmark')
5421 5416
5422 5417 st = list(repo.status(unknown=True))[:6]
5423 5418
5424 5419 c = repo.dirstate.copies()
5425 5420 copied, renamed = [], []
5426 5421 for d, s in c.iteritems():
5427 5422 if s in st[2]:
5428 5423 st[2].remove(s)
5429 5424 renamed.append(d)
5430 5425 else:
5431 5426 copied.append(d)
5432 5427 if d in st[1]:
5433 5428 st[1].remove(d)
5434 5429 st.insert(3, renamed)
5435 5430 st.insert(4, copied)
5436 5431
5437 5432 ms = mergemod.mergestate(repo)
5438 5433 st.append([f for f in ms if ms[f] == 'u'])
5439 5434
5440 5435 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5441 5436 st.append(subs)
5442 5437
5443 5438 labels = [ui.label(_('%d modified'), 'status.modified'),
5444 5439 ui.label(_('%d added'), 'status.added'),
5445 5440 ui.label(_('%d removed'), 'status.removed'),
5446 5441 ui.label(_('%d renamed'), 'status.copied'),
5447 5442 ui.label(_('%d copied'), 'status.copied'),
5448 5443 ui.label(_('%d deleted'), 'status.deleted'),
5449 5444 ui.label(_('%d unknown'), 'status.unknown'),
5450 5445 ui.label(_('%d ignored'), 'status.ignored'),
5451 5446 ui.label(_('%d unresolved'), 'resolve.unresolved'),
5452 5447 ui.label(_('%d subrepos'), 'status.modified')]
5453 5448 t = []
5454 5449 for s, l in zip(st, labels):
5455 5450 if s:
5456 5451 t.append(l % len(s))
5457 5452
5458 5453 t = ', '.join(t)
5459 5454 cleanworkdir = False
5460 5455
5461 5456 if repo.vfs.exists('updatestate'):
5462 5457 t += _(' (interrupted update)')
5463 5458 elif len(parents) > 1:
5464 5459 t += _(' (merge)')
5465 5460 elif branch != parents[0].branch():
5466 5461 t += _(' (new branch)')
5467 5462 elif (parents[0].closesbranch() and
5468 5463 pnode in repo.branchheads(branch, closed=True)):
5469 5464 t += _(' (head closed)')
5470 5465 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
5471 5466 t += _(' (clean)')
5472 5467 cleanworkdir = True
5473 5468 elif pnode not in bheads:
5474 5469 t += _(' (new branch head)')
5475 5470
5476 5471 if cleanworkdir:
5477 5472 # i18n: column positioning for "hg summary"
5478 5473 ui.status(_('commit: %s\n') % t.strip())
5479 5474 else:
5480 5475 # i18n: column positioning for "hg summary"
5481 5476 ui.write(_('commit: %s\n') % t.strip())
5482 5477
5483 5478 # all ancestors of branch heads - all ancestors of parent = new csets
5484 5479 new = len(repo.changelog.findmissing([ctx.node() for ctx in parents],
5485 5480 bheads))
5486 5481
5487 5482 if new == 0:
5488 5483 # i18n: column positioning for "hg summary"
5489 5484 ui.status(_('update: (current)\n'))
5490 5485 elif pnode not in bheads:
5491 5486 # i18n: column positioning for "hg summary"
5492 5487 ui.write(_('update: %d new changesets (update)\n') % new)
5493 5488 else:
5494 5489 # i18n: column positioning for "hg summary"
5495 5490 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5496 5491 (new, len(bheads)))
5497 5492
5498 5493 cmdutil.summaryhooks(ui, repo)
5499 5494
5500 5495 if opts.get('remote'):
5501 5496 t = []
5502 5497 source, branches = hg.parseurl(ui.expandpath('default'))
5503 5498 sbranch = branches[0]
5504 5499 other = hg.peer(repo, {}, source)
5505 5500 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5506 5501 if revs:
5507 5502 revs = [other.lookup(rev) for rev in revs]
5508 5503 ui.debug('comparing with %s\n' % util.hidepassword(source))
5509 5504 repo.ui.pushbuffer()
5510 5505 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5511 5506 _common, incoming, _rheads = commoninc
5512 5507 repo.ui.popbuffer()
5513 5508 if incoming:
5514 5509 t.append(_('1 or more incoming'))
5515 5510
5516 5511 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5517 5512 dbranch = branches[0]
5518 5513 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5519 5514 if source != dest:
5520 5515 other = hg.peer(repo, {}, dest)
5521 5516 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5522 5517 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5523 5518 commoninc = None
5524 5519 if revs:
5525 5520 revs = [repo.lookup(rev) for rev in revs]
5526 5521 repo.ui.pushbuffer()
5527 5522 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs,
5528 5523 commoninc=commoninc)
5529 5524 repo.ui.popbuffer()
5530 5525 o = outgoing.missing
5531 5526 if o:
5532 5527 t.append(_('%d outgoing') % len(o))
5533 5528 if 'bookmarks' in other.listkeys('namespaces'):
5534 5529 lmarks = repo.listkeys('bookmarks')
5535 5530 rmarks = other.listkeys('bookmarks')
5536 5531 diff = set(rmarks) - set(lmarks)
5537 5532 if len(diff) > 0:
5538 5533 t.append(_('%d incoming bookmarks') % len(diff))
5539 5534 diff = set(lmarks) - set(rmarks)
5540 5535 if len(diff) > 0:
5541 5536 t.append(_('%d outgoing bookmarks') % len(diff))
5542 5537
5543 5538 if t:
5544 5539 # i18n: column positioning for "hg summary"
5545 5540 ui.write(_('remote: %s\n') % (', '.join(t)))
5546 5541 else:
5547 5542 # i18n: column positioning for "hg summary"
5548 5543 ui.status(_('remote: (synced)\n'))
5549 5544
5550 5545 @command('tag',
5551 5546 [('f', 'force', None, _('force tag')),
5552 5547 ('l', 'local', None, _('make the tag local')),
5553 5548 ('r', 'rev', '', _('revision to tag'), _('REV')),
5554 5549 ('', 'remove', None, _('remove a tag')),
5555 5550 # -l/--local is already there, commitopts cannot be used
5556 5551 ('e', 'edit', None, _('edit commit message')),
5557 5552 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
5558 5553 ] + commitopts2,
5559 5554 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5560 5555 def tag(ui, repo, name1, *names, **opts):
5561 5556 """add one or more tags for the current or given revision
5562 5557
5563 5558 Name a particular revision using <name>.
5564 5559
5565 5560 Tags are used to name particular revisions of the repository and are
5566 5561 very useful to compare different revisions, to go back to significant
5567 5562 earlier versions or to mark branch points as releases, etc. Changing
5568 5563 an existing tag is normally disallowed; use -f/--force to override.
5569 5564
5570 5565 If no revision is given, the parent of the working directory is
5571 5566 used.
5572 5567
5573 5568 To facilitate version control, distribution, and merging of tags,
5574 5569 they are stored as a file named ".hgtags" which is managed similarly
5575 5570 to other project files and can be hand-edited if necessary. This
5576 5571 also means that tagging creates a new commit. The file
5577 5572 ".hg/localtags" is used for local tags (not shared among
5578 5573 repositories).
5579 5574
5580 5575 Tag commits are usually made at the head of a branch. If the parent
5581 5576 of the working directory is not a branch head, :hg:`tag` aborts; use
5582 5577 -f/--force to force the tag commit to be based on a non-head
5583 5578 changeset.
5584 5579
5585 5580 See :hg:`help dates` for a list of formats valid for -d/--date.
5586 5581
5587 5582 Since tag names have priority over branch names during revision
5588 5583 lookup, using an existing branch name as a tag name is discouraged.
5589 5584
5590 5585 Returns 0 on success.
5591 5586 """
5592 5587 wlock = lock = None
5593 5588 try:
5594 5589 wlock = repo.wlock()
5595 5590 lock = repo.lock()
5596 5591 rev_ = "."
5597 5592 names = [t.strip() for t in (name1,) + names]
5598 5593 if len(names) != len(set(names)):
5599 5594 raise util.Abort(_('tag names must be unique'))
5600 5595 for n in names:
5601 5596 scmutil.checknewlabel(repo, n, 'tag')
5602 5597 if not n:
5603 5598 raise util.Abort(_('tag names cannot consist entirely of '
5604 5599 'whitespace'))
5605 5600 if opts.get('rev') and opts.get('remove'):
5606 5601 raise util.Abort(_("--rev and --remove are incompatible"))
5607 5602 if opts.get('rev'):
5608 5603 rev_ = opts['rev']
5609 5604 message = opts.get('message')
5610 5605 if opts.get('remove'):
5611 5606 expectedtype = opts.get('local') and 'local' or 'global'
5612 5607 for n in names:
5613 5608 if not repo.tagtype(n):
5614 5609 raise util.Abort(_("tag '%s' does not exist") % n)
5615 5610 if repo.tagtype(n) != expectedtype:
5616 5611 if expectedtype == 'global':
5617 5612 raise util.Abort(_("tag '%s' is not a global tag") % n)
5618 5613 else:
5619 5614 raise util.Abort(_("tag '%s' is not a local tag") % n)
5620 5615 rev_ = nullid
5621 5616 if not message:
5622 5617 # we don't translate commit messages
5623 5618 message = 'Removed tag %s' % ', '.join(names)
5624 5619 elif not opts.get('force'):
5625 5620 for n in names:
5626 5621 if n in repo.tags():
5627 5622 raise util.Abort(_("tag '%s' already exists "
5628 5623 "(use -f to force)") % n)
5629 5624 if not opts.get('local'):
5630 5625 p1, p2 = repo.dirstate.parents()
5631 5626 if p2 != nullid:
5632 5627 raise util.Abort(_('uncommitted merge'))
5633 5628 bheads = repo.branchheads()
5634 5629 if not opts.get('force') and bheads and p1 not in bheads:
5635 5630 raise util.Abort(_('not at a branch head (use -f to force)'))
5636 5631 r = scmutil.revsingle(repo, rev_).node()
5637 5632
5638 5633 if not message:
5639 5634 # we don't translate commit messages
5640 5635 message = ('Added tag %s for changeset %s' %
5641 5636 (', '.join(names), short(r)))
5642 5637
5643 5638 date = opts.get('date')
5644 5639 if date:
5645 5640 date = util.parsedate(date)
5646 5641
5647 5642 if opts.get('edit'):
5648 5643 message = ui.edit(message, ui.username())
5649 5644
5650 5645 # don't allow tagging the null rev
5651 5646 if (not opts.get('remove') and
5652 5647 scmutil.revsingle(repo, rev_).rev() == nullrev):
5653 5648 raise util.Abort(_("cannot tag null revision"))
5654 5649
5655 5650 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
5656 5651 finally:
5657 5652 release(lock, wlock)
5658 5653
5659 5654 @command('tags', [], '')
5660 5655 def tags(ui, repo, **opts):
5661 5656 """list repository tags
5662 5657
5663 5658 This lists both regular and local tags. When the -v/--verbose
5664 5659 switch is used, a third column "local" is printed for local tags.
5665 5660
5666 5661 Returns 0 on success.
5667 5662 """
5668 5663
5669 5664 fm = ui.formatter('tags', opts)
5670 5665 hexfunc = ui.debugflag and hex or short
5671 5666 tagtype = ""
5672 5667
5673 5668 for t, n in reversed(repo.tagslist()):
5674 5669 hn = hexfunc(n)
5675 5670 label = 'tags.normal'
5676 5671 tagtype = ''
5677 5672 if repo.tagtype(t) == 'local':
5678 5673 label = 'tags.local'
5679 5674 tagtype = 'local'
5680 5675
5681 5676 fm.startitem()
5682 5677 fm.write('tag', '%s', t, label=label)
5683 5678 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5684 5679 fm.condwrite(not ui.quiet, 'rev id', fmt,
5685 5680 repo.changelog.rev(n), hn, label=label)
5686 5681 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5687 5682 tagtype, label=label)
5688 5683 fm.plain('\n')
5689 5684 fm.end()
5690 5685
5691 5686 @command('tip',
5692 5687 [('p', 'patch', None, _('show patch')),
5693 5688 ('g', 'git', None, _('use git extended diff format')),
5694 5689 ] + templateopts,
5695 5690 _('[-p] [-g]'))
5696 5691 def tip(ui, repo, **opts):
5697 5692 """show the tip revision (DEPRECATED)
5698 5693
5699 5694 The tip revision (usually just called the tip) is the changeset
5700 5695 most recently added to the repository (and therefore the most
5701 5696 recently changed head).
5702 5697
5703 5698 If you have just made a commit, that commit will be the tip. If
5704 5699 you have just pulled changes from another repository, the tip of
5705 5700 that repository becomes the current tip. The "tip" tag is special
5706 5701 and cannot be renamed or assigned to a different changeset.
5707 5702
5708 5703 This command is deprecated, please use :hg:`heads` instead.
5709 5704
5710 5705 Returns 0 on success.
5711 5706 """
5712 5707 displayer = cmdutil.show_changeset(ui, repo, opts)
5713 5708 displayer.show(repo['tip'])
5714 5709 displayer.close()
5715 5710
5716 5711 @command('unbundle',
5717 5712 [('u', 'update', None,
5718 5713 _('update to new branch head if changesets were unbundled'))],
5719 5714 _('[-u] FILE...'))
5720 5715 def unbundle(ui, repo, fname1, *fnames, **opts):
5721 5716 """apply one or more changegroup files
5722 5717
5723 5718 Apply one or more compressed changegroup files generated by the
5724 5719 bundle command.
5725 5720
5726 5721 Returns 0 on success, 1 if an update has unresolved files.
5727 5722 """
5728 5723 fnames = (fname1,) + fnames
5729 5724
5730 5725 lock = repo.lock()
5731 5726 wc = repo['.']
5732 5727 try:
5733 5728 for fname in fnames:
5734 5729 f = hg.openpath(ui, fname)
5735 5730 gen = changegroup.readbundle(f, fname)
5736 5731 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
5737 5732 finally:
5738 5733 lock.release()
5739 5734 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5740 5735 return postincoming(ui, repo, modheads, opts.get('update'), None)
5741 5736
5742 5737 @command('^update|up|checkout|co',
5743 5738 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5744 5739 ('c', 'check', None,
5745 5740 _('update across branches if no uncommitted changes')),
5746 5741 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5747 5742 ('r', 'rev', '', _('revision'), _('REV'))],
5748 5743 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5749 5744 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5750 5745 """update working directory (or switch revisions)
5751 5746
5752 5747 Update the repository's working directory to the specified
5753 5748 changeset. If no changeset is specified, update to the tip of the
5754 5749 current named branch and move the current bookmark (see :hg:`help
5755 5750 bookmarks`).
5756 5751
5757 5752 Update sets the working directory's parent revision to the specified
5758 5753 changeset (see :hg:`help parents`).
5759 5754
5760 5755 If the changeset is not a descendant or ancestor of the working
5761 5756 directory's parent, the update is aborted. With the -c/--check
5762 5757 option, the working directory is checked for uncommitted changes; if
5763 5758 none are found, the working directory is updated to the specified
5764 5759 changeset.
5765 5760
5766 5761 .. container:: verbose
5767 5762
5768 5763 The following rules apply when the working directory contains
5769 5764 uncommitted changes:
5770 5765
5771 5766 1. If neither -c/--check nor -C/--clean is specified, and if
5772 5767 the requested changeset is an ancestor or descendant of
5773 5768 the working directory's parent, the uncommitted changes
5774 5769 are merged into the requested changeset and the merged
5775 5770 result is left uncommitted. If the requested changeset is
5776 5771 not an ancestor or descendant (that is, it is on another
5777 5772 branch), the update is aborted and the uncommitted changes
5778 5773 are preserved.
5779 5774
5780 5775 2. With the -c/--check option, the update is aborted and the
5781 5776 uncommitted changes are preserved.
5782 5777
5783 5778 3. With the -C/--clean option, uncommitted changes are discarded and
5784 5779 the working directory is updated to the requested changeset.
5785 5780
5786 5781 To cancel an uncommitted merge (and lose your changes), use
5787 5782 :hg:`update --clean .`.
5788 5783
5789 5784 Use null as the changeset to remove the working directory (like
5790 5785 :hg:`clone -U`).
5791 5786
5792 5787 If you want to revert just one file to an older revision, use
5793 5788 :hg:`revert [-r REV] NAME`.
5794 5789
5795 5790 See :hg:`help dates` for a list of formats valid for -d/--date.
5796 5791
5797 5792 Returns 0 on success, 1 if there are unresolved files.
5798 5793 """
5799 5794 if rev and node:
5800 5795 raise util.Abort(_("please specify just one revision"))
5801 5796
5802 5797 if rev is None or rev == '':
5803 5798 rev = node
5804 5799
5805 5800 cmdutil.clearunfinished(repo)
5806 5801
5807 5802 # with no argument, we also move the current bookmark, if any
5808 5803 rev, movemarkfrom = bookmarks.calculateupdate(ui, repo, rev)
5809 5804
5810 5805 # if we defined a bookmark, we have to remember the original bookmark name
5811 5806 brev = rev
5812 5807 rev = scmutil.revsingle(repo, rev, rev).rev()
5813 5808
5814 5809 if check and clean:
5815 5810 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5816 5811
5817 5812 if date:
5818 5813 if rev is not None:
5819 5814 raise util.Abort(_("you can't specify a revision and a date"))
5820 5815 rev = cmdutil.finddate(ui, repo, date)
5821 5816
5822 5817 if check:
5823 5818 c = repo[None]
5824 5819 if c.dirty(merge=False, branch=False, missing=True):
5825 5820 raise util.Abort(_("uncommitted changes"))
5826 5821 if rev is None:
5827 5822 rev = repo[repo[None].branch()].rev()
5828 5823 mergemod._checkunknown(repo, repo[None], repo[rev])
5829 5824
5830 5825 if clean:
5831 5826 ret = hg.clean(repo, rev)
5832 5827 else:
5833 5828 ret = hg.update(repo, rev)
5834 5829
5835 5830 if not ret and movemarkfrom:
5836 5831 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
5837 5832 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
5838 5833 elif brev in repo._bookmarks:
5839 5834 bookmarks.setcurrent(repo, brev)
5840 5835 elif brev:
5841 5836 bookmarks.unsetcurrent(repo)
5842 5837
5843 5838 return ret
5844 5839
5845 5840 @command('verify', [])
5846 5841 def verify(ui, repo):
5847 5842 """verify the integrity of the repository
5848 5843
5849 5844 Verify the integrity of the current repository.
5850 5845
5851 5846 This will perform an extensive check of the repository's
5852 5847 integrity, validating the hashes and checksums of each entry in
5853 5848 the changelog, manifest, and tracked files, as well as the
5854 5849 integrity of their crosslinks and indices.
5855 5850
5856 5851 Please see http://mercurial.selenic.com/wiki/RepositoryCorruption
5857 5852 for more information about recovery from corruption of the
5858 5853 repository.
5859 5854
5860 5855 Returns 0 on success, 1 if errors are encountered.
5861 5856 """
5862 5857 return hg.verify(repo)
5863 5858
5864 5859 @command('version', [])
5865 5860 def version_(ui):
5866 5861 """output version and copyright information"""
5867 5862 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5868 5863 % util.version())
5869 5864 ui.status(_(
5870 5865 "(see http://mercurial.selenic.com for more information)\n"
5871 5866 "\nCopyright (C) 2005-2014 Matt Mackall and others\n"
5872 5867 "This is free software; see the source for copying conditions. "
5873 5868 "There is NO\nwarranty; "
5874 5869 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5875 5870 ))
5876 5871
5877 5872 norepo = ("clone init version help debugcommands debugcomplete"
5878 5873 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5879 5874 " debugknown debuggetbundle debugbundle")
5880 5875 optionalrepo = ("identify paths serve config showconfig debugancestor debugdag"
5881 5876 " debugdata debugindex debugindexdot debugrevlog")
5882 5877 inferrepo = ("add addremove annotate cat commit diff grep forget log parents"
5883 5878 " remove resolve status debugwalk")
@@ -1,932 +1,932 b''
1 1 # scmutil.py - Mercurial core utility functions
2 2 #
3 3 # Copyright 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 i18n import _
9 9 from mercurial.node import nullrev
10 10 import util, error, osutil, revset, similar, encoding, phases, parsers
11 11 import pathutil
12 12 import match as matchmod
13 13 import os, errno, re, glob
14 14
15 15 if os.name == 'nt':
16 16 import scmwindows as scmplatform
17 17 else:
18 18 import scmposix as scmplatform
19 19
20 20 systemrcpath = scmplatform.systemrcpath
21 21 userrcpath = scmplatform.userrcpath
22 22
23 23 def itersubrepos(ctx1, ctx2):
24 24 """find subrepos in ctx1 or ctx2"""
25 25 # Create a (subpath, ctx) mapping where we prefer subpaths from
26 26 # ctx1. The subpaths from ctx2 are important when the .hgsub file
27 27 # has been modified (in ctx2) but not yet committed (in ctx1).
28 28 subpaths = dict.fromkeys(ctx2.substate, ctx2)
29 29 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
30 30 for subpath, ctx in sorted(subpaths.iteritems()):
31 31 yield subpath, ctx.sub(subpath)
32 32
33 33 def nochangesfound(ui, repo, excluded=None):
34 34 '''Report no changes for push/pull, excluded is None or a list of
35 35 nodes excluded from the push/pull.
36 36 '''
37 37 secretlist = []
38 38 if excluded:
39 39 for n in excluded:
40 40 if n not in repo:
41 41 # discovery should not have included the filtered revision,
42 42 # we have to explicitly exclude it until discovery is cleanup.
43 43 continue
44 44 ctx = repo[n]
45 45 if ctx.phase() >= phases.secret and not ctx.extinct():
46 46 secretlist.append(n)
47 47
48 48 if secretlist:
49 49 ui.status(_("no changes found (ignored %d secret changesets)\n")
50 50 % len(secretlist))
51 51 else:
52 52 ui.status(_("no changes found\n"))
53 53
54 54 def checknewlabel(repo, lbl, kind):
55 55 # Do not use the "kind" parameter in ui output.
56 56 # It makes strings difficult to translate.
57 57 if lbl in ['tip', '.', 'null']:
58 58 raise util.Abort(_("the name '%s' is reserved") % lbl)
59 59 for c in (':', '\0', '\n', '\r'):
60 60 if c in lbl:
61 61 raise util.Abort(_("%r cannot be used in a name") % c)
62 62 try:
63 63 int(lbl)
64 64 raise util.Abort(_("cannot use an integer as a name"))
65 65 except ValueError:
66 66 pass
67 67
68 68 def checkfilename(f):
69 69 '''Check that the filename f is an acceptable filename for a tracked file'''
70 70 if '\r' in f or '\n' in f:
71 71 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
72 72
73 73 def checkportable(ui, f):
74 74 '''Check if filename f is portable and warn or abort depending on config'''
75 75 checkfilename(f)
76 76 abort, warn = checkportabilityalert(ui)
77 77 if abort or warn:
78 78 msg = util.checkwinfilename(f)
79 79 if msg:
80 80 msg = "%s: %r" % (msg, f)
81 81 if abort:
82 82 raise util.Abort(msg)
83 83 ui.warn(_("warning: %s\n") % msg)
84 84
85 85 def checkportabilityalert(ui):
86 86 '''check if the user's config requests nothing, a warning, or abort for
87 87 non-portable filenames'''
88 88 val = ui.config('ui', 'portablefilenames', 'warn')
89 89 lval = val.lower()
90 90 bval = util.parsebool(val)
91 91 abort = os.name == 'nt' or lval == 'abort'
92 92 warn = bval or lval == 'warn'
93 93 if bval is None and not (warn or abort or lval == 'ignore'):
94 94 raise error.ConfigError(
95 95 _("ui.portablefilenames value is invalid ('%s')") % val)
96 96 return abort, warn
97 97
98 98 class casecollisionauditor(object):
99 99 def __init__(self, ui, abort, dirstate):
100 100 self._ui = ui
101 101 self._abort = abort
102 102 allfiles = '\0'.join(dirstate._map)
103 103 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
104 104 self._dirstate = dirstate
105 105 # The purpose of _newfiles is so that we don't complain about
106 106 # case collisions if someone were to call this object with the
107 107 # same filename twice.
108 108 self._newfiles = set()
109 109
110 110 def __call__(self, f):
111 111 if f in self._newfiles:
112 112 return
113 113 fl = encoding.lower(f)
114 114 if fl in self._loweredfiles and f not in self._dirstate:
115 115 msg = _('possible case-folding collision for %s') % f
116 116 if self._abort:
117 117 raise util.Abort(msg)
118 118 self._ui.warn(_("warning: %s\n") % msg)
119 119 self._loweredfiles.add(fl)
120 120 self._newfiles.add(f)
121 121
122 122 class abstractvfs(object):
123 123 """Abstract base class; cannot be instantiated"""
124 124
125 125 def __init__(self, *args, **kwargs):
126 126 '''Prevent instantiation; don't call this from subclasses.'''
127 127 raise NotImplementedError('attempted instantiating ' + str(type(self)))
128 128
129 129 def tryread(self, path):
130 130 '''gracefully return an empty string for missing files'''
131 131 try:
132 132 return self.read(path)
133 133 except IOError, inst:
134 134 if inst.errno != errno.ENOENT:
135 135 raise
136 136 return ""
137 137
138 138 def open(self, path, mode="r", text=False, atomictemp=False):
139 139 self.open = self.__call__
140 140 return self.__call__(path, mode, text, atomictemp)
141 141
142 142 def read(self, path):
143 143 fp = self(path, 'rb')
144 144 try:
145 145 return fp.read()
146 146 finally:
147 147 fp.close()
148 148
149 149 def write(self, path, data):
150 150 fp = self(path, 'wb')
151 151 try:
152 152 return fp.write(data)
153 153 finally:
154 154 fp.close()
155 155
156 156 def append(self, path, data):
157 157 fp = self(path, 'ab')
158 158 try:
159 159 return fp.write(data)
160 160 finally:
161 161 fp.close()
162 162
163 163 def chmod(self, path, mode):
164 164 return os.chmod(self.join(path), mode)
165 165
166 166 def exists(self, path=None):
167 167 return os.path.exists(self.join(path))
168 168
169 169 def fstat(self, fp):
170 170 return util.fstat(fp)
171 171
172 172 def isdir(self, path=None):
173 173 return os.path.isdir(self.join(path))
174 174
175 175 def isfile(self, path=None):
176 176 return os.path.isfile(self.join(path))
177 177
178 178 def islink(self, path=None):
179 179 return os.path.islink(self.join(path))
180 180
181 181 def lstat(self, path=None):
182 182 return os.lstat(self.join(path))
183 183
184 184 def makedir(self, path=None, notindexed=True):
185 185 return util.makedir(self.join(path), notindexed)
186 186
187 187 def makedirs(self, path=None, mode=None):
188 188 return util.makedirs(self.join(path), mode)
189 189
190 190 def makelock(self, info, path):
191 191 return util.makelock(info, self.join(path))
192 192
193 193 def mkdir(self, path=None):
194 194 return os.mkdir(self.join(path))
195 195
196 196 def readdir(self, path=None, stat=None, skip=None):
197 197 return osutil.listdir(self.join(path), stat, skip)
198 198
199 199 def readlock(self, path):
200 200 return util.readlock(self.join(path))
201 201
202 202 def rename(self, src, dst):
203 203 return util.rename(self.join(src), self.join(dst))
204 204
205 205 def readlink(self, path):
206 206 return os.readlink(self.join(path))
207 207
208 208 def setflags(self, path, l, x):
209 209 return util.setflags(self.join(path), l, x)
210 210
211 211 def stat(self, path=None):
212 212 return os.stat(self.join(path))
213 213
214 214 def unlink(self, path=None):
215 215 return util.unlink(self.join(path))
216 216
217 217 def utime(self, path=None, t=None):
218 218 return os.utime(self.join(path), t)
219 219
220 220 class vfs(abstractvfs):
221 221 '''Operate files relative to a base directory
222 222
223 223 This class is used to hide the details of COW semantics and
224 224 remote file access from higher level code.
225 225 '''
226 226 def __init__(self, base, audit=True, expandpath=False, realpath=False):
227 227 if expandpath:
228 228 base = util.expandpath(base)
229 229 if realpath:
230 230 base = os.path.realpath(base)
231 231 self.base = base
232 232 self._setmustaudit(audit)
233 233 self.createmode = None
234 234 self._trustnlink = None
235 235
236 236 def _getmustaudit(self):
237 237 return self._audit
238 238
239 239 def _setmustaudit(self, onoff):
240 240 self._audit = onoff
241 241 if onoff:
242 242 self.audit = pathutil.pathauditor(self.base)
243 243 else:
244 244 self.audit = util.always
245 245
246 246 mustaudit = property(_getmustaudit, _setmustaudit)
247 247
248 248 @util.propertycache
249 249 def _cansymlink(self):
250 250 return util.checklink(self.base)
251 251
252 252 @util.propertycache
253 253 def _chmod(self):
254 254 return util.checkexec(self.base)
255 255
256 256 def _fixfilemode(self, name):
257 257 if self.createmode is None or not self._chmod:
258 258 return
259 259 os.chmod(name, self.createmode & 0666)
260 260
261 261 def __call__(self, path, mode="r", text=False, atomictemp=False):
262 262 if self._audit:
263 263 r = util.checkosfilename(path)
264 264 if r:
265 265 raise util.Abort("%s: %r" % (r, path))
266 266 self.audit(path)
267 267 f = self.join(path)
268 268
269 269 if not text and "b" not in mode:
270 270 mode += "b" # for that other OS
271 271
272 272 nlink = -1
273 273 if mode not in ('r', 'rb'):
274 274 dirname, basename = util.split(f)
275 275 # If basename is empty, then the path is malformed because it points
276 276 # to a directory. Let the posixfile() call below raise IOError.
277 277 if basename:
278 278 if atomictemp:
279 279 util.ensuredirs(dirname, self.createmode)
280 280 return util.atomictempfile(f, mode, self.createmode)
281 281 try:
282 282 if 'w' in mode:
283 283 util.unlink(f)
284 284 nlink = 0
285 285 else:
286 286 # nlinks() may behave differently for files on Windows
287 287 # shares if the file is open.
288 288 fd = util.posixfile(f)
289 289 nlink = util.nlinks(f)
290 290 if nlink < 1:
291 291 nlink = 2 # force mktempcopy (issue1922)
292 292 fd.close()
293 293 except (OSError, IOError), e:
294 294 if e.errno != errno.ENOENT:
295 295 raise
296 296 nlink = 0
297 297 util.ensuredirs(dirname, self.createmode)
298 298 if nlink > 0:
299 299 if self._trustnlink is None:
300 300 self._trustnlink = nlink > 1 or util.checknlink(f)
301 301 if nlink > 1 or not self._trustnlink:
302 302 util.rename(util.mktempcopy(f), f)
303 303 fp = util.posixfile(f, mode)
304 304 if nlink == 0:
305 305 self._fixfilemode(f)
306 306 return fp
307 307
308 308 def symlink(self, src, dst):
309 309 self.audit(dst)
310 310 linkname = self.join(dst)
311 311 try:
312 312 os.unlink(linkname)
313 313 except OSError:
314 314 pass
315 315
316 316 util.ensuredirs(os.path.dirname(linkname), self.createmode)
317 317
318 318 if self._cansymlink:
319 319 try:
320 320 os.symlink(src, linkname)
321 321 except OSError, err:
322 322 raise OSError(err.errno, _('could not symlink to %r: %s') %
323 323 (src, err.strerror), linkname)
324 324 else:
325 325 self.write(dst, src)
326 326
327 327 def join(self, path):
328 328 if path:
329 329 return os.path.join(self.base, path)
330 330 else:
331 331 return self.base
332 332
333 333 opener = vfs
334 334
335 335 class auditvfs(object):
336 336 def __init__(self, vfs):
337 337 self.vfs = vfs
338 338
339 339 def _getmustaudit(self):
340 340 return self.vfs.mustaudit
341 341
342 342 def _setmustaudit(self, onoff):
343 343 self.vfs.mustaudit = onoff
344 344
345 345 mustaudit = property(_getmustaudit, _setmustaudit)
346 346
347 347 class filtervfs(abstractvfs, auditvfs):
348 348 '''Wrapper vfs for filtering filenames with a function.'''
349 349
350 350 def __init__(self, vfs, filter):
351 351 auditvfs.__init__(self, vfs)
352 352 self._filter = filter
353 353
354 354 def __call__(self, path, *args, **kwargs):
355 355 return self.vfs(self._filter(path), *args, **kwargs)
356 356
357 357 def join(self, path):
358 358 if path:
359 359 return self.vfs.join(self._filter(path))
360 360 else:
361 361 return self.vfs.join(path)
362 362
363 363 filteropener = filtervfs
364 364
365 365 class readonlyvfs(abstractvfs, auditvfs):
366 366 '''Wrapper vfs preventing any writing.'''
367 367
368 368 def __init__(self, vfs):
369 369 auditvfs.__init__(self, vfs)
370 370
371 371 def __call__(self, path, mode='r', *args, **kw):
372 372 if mode not in ('r', 'rb'):
373 373 raise util.Abort('this vfs is read only')
374 374 return self.vfs(path, mode, *args, **kw)
375 375
376 376
377 377 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
378 378 '''yield every hg repository under path, always recursively.
379 379 The recurse flag will only control recursion into repo working dirs'''
380 380 def errhandler(err):
381 381 if err.filename == path:
382 382 raise err
383 383 samestat = getattr(os.path, 'samestat', None)
384 384 if followsym and samestat is not None:
385 385 def adddir(dirlst, dirname):
386 386 match = False
387 387 dirstat = os.stat(dirname)
388 388 for lstdirstat in dirlst:
389 389 if samestat(dirstat, lstdirstat):
390 390 match = True
391 391 break
392 392 if not match:
393 393 dirlst.append(dirstat)
394 394 return not match
395 395 else:
396 396 followsym = False
397 397
398 398 if (seen_dirs is None) and followsym:
399 399 seen_dirs = []
400 400 adddir(seen_dirs, path)
401 401 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
402 402 dirs.sort()
403 403 if '.hg' in dirs:
404 404 yield root # found a repository
405 405 qroot = os.path.join(root, '.hg', 'patches')
406 406 if os.path.isdir(os.path.join(qroot, '.hg')):
407 407 yield qroot # we have a patch queue repo here
408 408 if recurse:
409 409 # avoid recursing inside the .hg directory
410 410 dirs.remove('.hg')
411 411 else:
412 412 dirs[:] = [] # don't descend further
413 413 elif followsym:
414 414 newdirs = []
415 415 for d in dirs:
416 416 fname = os.path.join(root, d)
417 417 if adddir(seen_dirs, fname):
418 418 if os.path.islink(fname):
419 419 for hgname in walkrepos(fname, True, seen_dirs):
420 420 yield hgname
421 421 else:
422 422 newdirs.append(d)
423 423 dirs[:] = newdirs
424 424
425 425 def osrcpath():
426 426 '''return default os-specific hgrc search path'''
427 427 path = systemrcpath()
428 428 path.extend(userrcpath())
429 429 path = [os.path.normpath(f) for f in path]
430 430 return path
431 431
432 432 _rcpath = None
433 433
434 434 def rcpath():
435 435 '''return hgrc search path. if env var HGRCPATH is set, use it.
436 436 for each item in path, if directory, use files ending in .rc,
437 437 else use item.
438 438 make HGRCPATH empty to only look in .hg/hgrc of current repo.
439 439 if no HGRCPATH, use default os-specific path.'''
440 440 global _rcpath
441 441 if _rcpath is None:
442 442 if 'HGRCPATH' in os.environ:
443 443 _rcpath = []
444 444 for p in os.environ['HGRCPATH'].split(os.pathsep):
445 445 if not p:
446 446 continue
447 447 p = util.expandpath(p)
448 448 if os.path.isdir(p):
449 449 for f, kind in osutil.listdir(p):
450 450 if f.endswith('.rc'):
451 451 _rcpath.append(os.path.join(p, f))
452 452 else:
453 453 _rcpath.append(p)
454 454 else:
455 455 _rcpath = osrcpath()
456 456 return _rcpath
457 457
458 458 def revsingle(repo, revspec, default='.'):
459 459 if not revspec and revspec != 0:
460 460 return repo[default]
461 461
462 462 l = revrange(repo, [revspec])
463 463 if len(l) < 1:
464 464 raise util.Abort(_('empty revision set'))
465 465 return repo[l[-1]]
466 466
467 467 def revpair(repo, revs):
468 468 if not revs:
469 469 return repo.dirstate.p1(), None
470 470
471 471 l = revrange(repo, revs)
472 472
473 473 if len(l) == 0:
474 474 if revs:
475 475 raise util.Abort(_('empty revision range'))
476 476 return repo.dirstate.p1(), None
477 477
478 478 if len(l) == 1 and len(revs) == 1 and _revrangesep not in revs[0]:
479 479 return repo.lookup(l[0]), None
480 480
481 481 return repo.lookup(l[0]), repo.lookup(l[-1])
482 482
483 483 _revrangesep = ':'
484 484
485 485 def revrange(repo, revs):
486 486 """Yield revision as strings from a list of revision specifications."""
487 487
488 488 def revfix(repo, val, defval):
489 489 if not val and val != 0 and defval is not None:
490 490 return defval
491 491 return repo[val].rev()
492 492
493 493 seen, l = set(), revset.baseset([])
494 494 for spec in revs:
495 495 if l and not seen:
496 496 seen = set(l)
497 497 # attempt to parse old-style ranges first to deal with
498 498 # things like old-tag which contain query metacharacters
499 499 try:
500 500 if isinstance(spec, int):
501 501 seen.add(spec)
502 502 l = l + [spec]
503 503 continue
504 504
505 505 if _revrangesep in spec:
506 506 start, end = spec.split(_revrangesep, 1)
507 507 start = revfix(repo, start, 0)
508 508 end = revfix(repo, end, len(repo) - 1)
509 if end == nullrev and start <= 0:
509 if end == nullrev and start < 0:
510 510 start = nullrev
511 511 rangeiter = repo.changelog.revs(start, end)
512 512 if not seen and not l:
513 513 # by far the most common case: revs = ["-1:0"]
514 514 l = revset.baseset(rangeiter)
515 515 # defer syncing seen until next iteration
516 516 continue
517 517 newrevs = set(rangeiter)
518 518 if seen:
519 519 newrevs.difference_update(seen)
520 520 seen.update(newrevs)
521 521 else:
522 522 seen = newrevs
523 523 l = l + sorted(newrevs, reverse=start > end)
524 524 continue
525 525 elif spec and spec in repo: # single unquoted rev
526 526 rev = revfix(repo, spec, None)
527 527 if rev in seen:
528 528 continue
529 529 seen.add(rev)
530 530 l = l + [rev]
531 531 continue
532 532 except error.RepoLookupError:
533 533 pass
534 534
535 535 # fall through to new-style queries if old-style fails
536 536 m = revset.match(repo.ui, spec)
537 537 if seen or l:
538 538 dl = [r for r in m(repo, revset.spanset(repo)) if r not in seen]
539 539 l = l + dl
540 540 seen.update(dl)
541 541 else:
542 542 l = m(repo, revset.spanset(repo))
543 543
544 544 return l
545 545
546 546 def expandpats(pats):
547 547 if not util.expandglobs:
548 548 return list(pats)
549 549 ret = []
550 550 for p in pats:
551 551 kind, name = matchmod._patsplit(p, None)
552 552 if kind is None:
553 553 try:
554 554 globbed = glob.glob(name)
555 555 except re.error:
556 556 globbed = [name]
557 557 if globbed:
558 558 ret.extend(globbed)
559 559 continue
560 560 ret.append(p)
561 561 return ret
562 562
563 563 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
564 564 if pats == ("",):
565 565 pats = []
566 566 if not globbed and default == 'relpath':
567 567 pats = expandpats(pats or [])
568 568
569 569 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
570 570 default)
571 571 def badfn(f, msg):
572 572 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
573 573 m.bad = badfn
574 574 return m, pats
575 575
576 576 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
577 577 return matchandpats(ctx, pats, opts, globbed, default)[0]
578 578
579 579 def matchall(repo):
580 580 return matchmod.always(repo.root, repo.getcwd())
581 581
582 582 def matchfiles(repo, files):
583 583 return matchmod.exact(repo.root, repo.getcwd(), files)
584 584
585 585 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
586 586 if dry_run is None:
587 587 dry_run = opts.get('dry_run')
588 588 if similarity is None:
589 589 similarity = float(opts.get('similarity') or 0)
590 590 # we'd use status here, except handling of symlinks and ignore is tricky
591 591 m = match(repo[None], pats, opts)
592 592 rejected = []
593 593 m.bad = lambda x, y: rejected.append(x)
594 594
595 595 added, unknown, deleted, removed = _interestingfiles(repo, m)
596 596
597 597 unknownset = set(unknown)
598 598 toprint = unknownset.copy()
599 599 toprint.update(deleted)
600 600 for abs in sorted(toprint):
601 601 if repo.ui.verbose or not m.exact(abs):
602 602 rel = m.rel(abs)
603 603 if abs in unknownset:
604 604 status = _('adding %s\n') % ((pats and rel) or abs)
605 605 else:
606 606 status = _('removing %s\n') % ((pats and rel) or abs)
607 607 repo.ui.status(status)
608 608
609 609 renames = _findrenames(repo, m, added + unknown, removed + deleted,
610 610 similarity)
611 611
612 612 if not dry_run:
613 613 _markchanges(repo, unknown, deleted, renames)
614 614
615 615 for f in rejected:
616 616 if f in m.files():
617 617 return 1
618 618 return 0
619 619
620 620 def marktouched(repo, files, similarity=0.0):
621 621 '''Assert that files have somehow been operated upon. files are relative to
622 622 the repo root.'''
623 623 m = matchfiles(repo, files)
624 624 rejected = []
625 625 m.bad = lambda x, y: rejected.append(x)
626 626
627 627 added, unknown, deleted, removed = _interestingfiles(repo, m)
628 628
629 629 if repo.ui.verbose:
630 630 unknownset = set(unknown)
631 631 toprint = unknownset.copy()
632 632 toprint.update(deleted)
633 633 for abs in sorted(toprint):
634 634 if abs in unknownset:
635 635 status = _('adding %s\n') % abs
636 636 else:
637 637 status = _('removing %s\n') % abs
638 638 repo.ui.status(status)
639 639
640 640 renames = _findrenames(repo, m, added + unknown, removed + deleted,
641 641 similarity)
642 642
643 643 _markchanges(repo, unknown, deleted, renames)
644 644
645 645 for f in rejected:
646 646 if f in m.files():
647 647 return 1
648 648 return 0
649 649
650 650 def _interestingfiles(repo, matcher):
651 651 '''Walk dirstate with matcher, looking for files that addremove would care
652 652 about.
653 653
654 654 This is different from dirstate.status because it doesn't care about
655 655 whether files are modified or clean.'''
656 656 added, unknown, deleted, removed = [], [], [], []
657 657 audit_path = pathutil.pathauditor(repo.root)
658 658
659 659 ctx = repo[None]
660 660 dirstate = repo.dirstate
661 661 walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
662 662 full=False)
663 663 for abs, st in walkresults.iteritems():
664 664 dstate = dirstate[abs]
665 665 if dstate == '?' and audit_path.check(abs):
666 666 unknown.append(abs)
667 667 elif dstate != 'r' and not st:
668 668 deleted.append(abs)
669 669 # for finding renames
670 670 elif dstate == 'r':
671 671 removed.append(abs)
672 672 elif dstate == 'a':
673 673 added.append(abs)
674 674
675 675 return added, unknown, deleted, removed
676 676
677 677 def _findrenames(repo, matcher, added, removed, similarity):
678 678 '''Find renames from removed files to added ones.'''
679 679 renames = {}
680 680 if similarity > 0:
681 681 for old, new, score in similar.findrenames(repo, added, removed,
682 682 similarity):
683 683 if (repo.ui.verbose or not matcher.exact(old)
684 684 or not matcher.exact(new)):
685 685 repo.ui.status(_('recording removal of %s as rename to %s '
686 686 '(%d%% similar)\n') %
687 687 (matcher.rel(old), matcher.rel(new),
688 688 score * 100))
689 689 renames[new] = old
690 690 return renames
691 691
692 692 def _markchanges(repo, unknown, deleted, renames):
693 693 '''Marks the files in unknown as added, the files in deleted as removed,
694 694 and the files in renames as copied.'''
695 695 wctx = repo[None]
696 696 wlock = repo.wlock()
697 697 try:
698 698 wctx.forget(deleted)
699 699 wctx.add(unknown)
700 700 for new, old in renames.iteritems():
701 701 wctx.copy(old, new)
702 702 finally:
703 703 wlock.release()
704 704
705 705 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
706 706 """Update the dirstate to reflect the intent of copying src to dst. For
707 707 different reasons it might not end with dst being marked as copied from src.
708 708 """
709 709 origsrc = repo.dirstate.copied(src) or src
710 710 if dst == origsrc: # copying back a copy?
711 711 if repo.dirstate[dst] not in 'mn' and not dryrun:
712 712 repo.dirstate.normallookup(dst)
713 713 else:
714 714 if repo.dirstate[origsrc] == 'a' and origsrc == src:
715 715 if not ui.quiet:
716 716 ui.warn(_("%s has not been committed yet, so no copy "
717 717 "data will be stored for %s.\n")
718 718 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
719 719 if repo.dirstate[dst] in '?r' and not dryrun:
720 720 wctx.add([dst])
721 721 elif not dryrun:
722 722 wctx.copy(origsrc, dst)
723 723
724 724 def readrequires(opener, supported):
725 725 '''Reads and parses .hg/requires and checks if all entries found
726 726 are in the list of supported features.'''
727 727 requirements = set(opener.read("requires").splitlines())
728 728 missings = []
729 729 for r in requirements:
730 730 if r not in supported:
731 731 if not r or not r[0].isalnum():
732 732 raise error.RequirementError(_(".hg/requires file is corrupt"))
733 733 missings.append(r)
734 734 missings.sort()
735 735 if missings:
736 736 raise error.RequirementError(
737 737 _("unknown repository format: requires features '%s' (upgrade "
738 738 "Mercurial)") % "', '".join(missings))
739 739 return requirements
740 740
741 741 class filecachesubentry(object):
742 742 def __init__(self, path, stat):
743 743 self.path = path
744 744 self.cachestat = None
745 745 self._cacheable = None
746 746
747 747 if stat:
748 748 self.cachestat = filecachesubentry.stat(self.path)
749 749
750 750 if self.cachestat:
751 751 self._cacheable = self.cachestat.cacheable()
752 752 else:
753 753 # None means we don't know yet
754 754 self._cacheable = None
755 755
756 756 def refresh(self):
757 757 if self.cacheable():
758 758 self.cachestat = filecachesubentry.stat(self.path)
759 759
760 760 def cacheable(self):
761 761 if self._cacheable is not None:
762 762 return self._cacheable
763 763
764 764 # we don't know yet, assume it is for now
765 765 return True
766 766
767 767 def changed(self):
768 768 # no point in going further if we can't cache it
769 769 if not self.cacheable():
770 770 return True
771 771
772 772 newstat = filecachesubentry.stat(self.path)
773 773
774 774 # we may not know if it's cacheable yet, check again now
775 775 if newstat and self._cacheable is None:
776 776 self._cacheable = newstat.cacheable()
777 777
778 778 # check again
779 779 if not self._cacheable:
780 780 return True
781 781
782 782 if self.cachestat != newstat:
783 783 self.cachestat = newstat
784 784 return True
785 785 else:
786 786 return False
787 787
788 788 @staticmethod
789 789 def stat(path):
790 790 try:
791 791 return util.cachestat(path)
792 792 except OSError, e:
793 793 if e.errno != errno.ENOENT:
794 794 raise
795 795
796 796 class filecacheentry(object):
797 797 def __init__(self, paths, stat=True):
798 798 self._entries = []
799 799 for path in paths:
800 800 self._entries.append(filecachesubentry(path, stat))
801 801
802 802 def changed(self):
803 803 '''true if any entry has changed'''
804 804 for entry in self._entries:
805 805 if entry.changed():
806 806 return True
807 807 return False
808 808
809 809 def refresh(self):
810 810 for entry in self._entries:
811 811 entry.refresh()
812 812
813 813 class filecache(object):
814 814 '''A property like decorator that tracks files under .hg/ for updates.
815 815
816 816 Records stat info when called in _filecache.
817 817
818 818 On subsequent calls, compares old stat info with new info, and recreates the
819 819 object when any of the files changes, updating the new stat info in
820 820 _filecache.
821 821
822 822 Mercurial either atomic renames or appends for files under .hg,
823 823 so to ensure the cache is reliable we need the filesystem to be able
824 824 to tell us if a file has been replaced. If it can't, we fallback to
825 825 recreating the object on every call (essentially the same behaviour as
826 826 propertycache).
827 827
828 828 '''
829 829 def __init__(self, *paths):
830 830 self.paths = paths
831 831
832 832 def join(self, obj, fname):
833 833 """Used to compute the runtime path of a cached file.
834 834
835 835 Users should subclass filecache and provide their own version of this
836 836 function to call the appropriate join function on 'obj' (an instance
837 837 of the class that its member function was decorated).
838 838 """
839 839 return obj.join(fname)
840 840
841 841 def __call__(self, func):
842 842 self.func = func
843 843 self.name = func.__name__
844 844 return self
845 845
846 846 def __get__(self, obj, type=None):
847 847 # do we need to check if the file changed?
848 848 if self.name in obj.__dict__:
849 849 assert self.name in obj._filecache, self.name
850 850 return obj.__dict__[self.name]
851 851
852 852 entry = obj._filecache.get(self.name)
853 853
854 854 if entry:
855 855 if entry.changed():
856 856 entry.obj = self.func(obj)
857 857 else:
858 858 paths = [self.join(obj, path) for path in self.paths]
859 859
860 860 # We stat -before- creating the object so our cache doesn't lie if
861 861 # a writer modified between the time we read and stat
862 862 entry = filecacheentry(paths, True)
863 863 entry.obj = self.func(obj)
864 864
865 865 obj._filecache[self.name] = entry
866 866
867 867 obj.__dict__[self.name] = entry.obj
868 868 return entry.obj
869 869
870 870 def __set__(self, obj, value):
871 871 if self.name not in obj._filecache:
872 872 # we add an entry for the missing value because X in __dict__
873 873 # implies X in _filecache
874 874 paths = [self.join(obj, path) for path in self.paths]
875 875 ce = filecacheentry(paths, False)
876 876 obj._filecache[self.name] = ce
877 877 else:
878 878 ce = obj._filecache[self.name]
879 879
880 880 ce.obj = value # update cached copy
881 881 obj.__dict__[self.name] = value # update copy returned by obj.x
882 882
883 883 def __delete__(self, obj):
884 884 try:
885 885 del obj.__dict__[self.name]
886 886 except KeyError:
887 887 raise AttributeError(self.name)
888 888
889 889 class dirs(object):
890 890 '''a multiset of directory names from a dirstate or manifest'''
891 891
892 892 def __init__(self, map, skip=None):
893 893 self._dirs = {}
894 894 addpath = self.addpath
895 895 if util.safehasattr(map, 'iteritems') and skip is not None:
896 896 for f, s in map.iteritems():
897 897 if s[0] != skip:
898 898 addpath(f)
899 899 else:
900 900 for f in map:
901 901 addpath(f)
902 902
903 903 def addpath(self, path):
904 904 dirs = self._dirs
905 905 for base in finddirs(path):
906 906 if base in dirs:
907 907 dirs[base] += 1
908 908 return
909 909 dirs[base] = 1
910 910
911 911 def delpath(self, path):
912 912 dirs = self._dirs
913 913 for base in finddirs(path):
914 914 if dirs[base] > 1:
915 915 dirs[base] -= 1
916 916 return
917 917 del dirs[base]
918 918
919 919 def __iter__(self):
920 920 return self._dirs.iterkeys()
921 921
922 922 def __contains__(self, d):
923 923 return d in self._dirs
924 924
925 925 if util.safehasattr(parsers, 'dirs'):
926 926 dirs = parsers.dirs
927 927
928 928 def finddirs(path):
929 929 pos = path.rfind('/')
930 930 while pos != -1:
931 931 yield path[:pos]
932 932 pos = path.rfind('/', 0, pos)
@@ -1,767 +1,778 b''
1 1 $ hg init
2 2
3 3 Setup:
4 4
5 5 $ echo a >> a
6 6 $ hg ci -Am 'base'
7 7 adding a
8 8
9 9 Refuse to amend public csets:
10 10
11 11 $ hg phase -r . -p
12 12 $ hg ci --amend
13 13 abort: cannot amend public changesets
14 14 [255]
15 15 $ hg phase -r . -f -d
16 16
17 17 $ echo a >> a
18 18 $ hg ci -Am 'base1'
19 19
20 20 Nothing to amend:
21 21
22 22 $ hg ci --amend
23 23 nothing changed
24 24 [1]
25 25
26 26 $ cat >> $HGRCPATH <<EOF
27 27 > [hooks]
28 28 > pretxncommit.foo = sh -c "echo \\"pretxncommit \$HG_NODE\\"; hg id -r \$HG_NODE"
29 29 > EOF
30 30
31 31 Amending changeset with changes in working dir:
32 32 (and check that --message does not trigger an editor)
33 33
34 34 $ echo a >> a
35 35 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -m 'amend base1'
36 36 pretxncommit 43f1ba15f28a50abf0aae529cf8a16bfced7b149
37 37 43f1ba15f28a tip
38 38 saved backup bundle to $TESTTMP/.hg/strip-backup/489edb5b847d-amend-backup.hg (glob)
39 39 $ echo 'pretxncommit.foo = ' >> $HGRCPATH
40 40 $ hg diff -c .
41 41 diff -r ad120869acf0 -r 43f1ba15f28a a
42 42 --- a/a Thu Jan 01 00:00:00 1970 +0000
43 43 +++ b/a Thu Jan 01 00:00:00 1970 +0000
44 44 @@ -1,1 +1,3 @@
45 45 a
46 46 +a
47 47 +a
48 48 $ hg log
49 49 changeset: 1:43f1ba15f28a
50 50 tag: tip
51 51 user: test
52 52 date: Thu Jan 01 00:00:00 1970 +0000
53 53 summary: amend base1
54 54
55 55 changeset: 0:ad120869acf0
56 56 user: test
57 57 date: Thu Jan 01 00:00:00 1970 +0000
58 58 summary: base
59 59
60 60
61 61 Check proper abort for empty message
62 62
63 63 $ cat > editor.sh << '__EOF__'
64 64 > #!/bin/sh
65 65 > echo "" > "$1"
66 66 > __EOF__
67 67 $ echo b > b
68 68 $ hg add b
69 69 $ hg summary
70 70 parent: 1:43f1ba15f28a tip
71 71 amend base1
72 72 branch: default
73 73 commit: 1 added, 1 unknown
74 74 update: (current)
75 75 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
76 76 transaction abort!
77 77 rollback completed
78 78 abort: empty commit message
79 79 [255]
80 80 $ hg summary
81 81 parent: 1:43f1ba15f28a tip
82 82 amend base1
83 83 branch: default
84 84 commit: 1 added, 1 unknown
85 85 update: (current)
86 86
87 87 Add new file:
88 88 $ hg ci --amend -m 'amend base1 new file'
89 89 saved backup bundle to $TESTTMP/.hg/strip-backup/43f1ba15f28a-amend-backup.hg (glob)
90 90
91 91 Remove file that was added in amended commit:
92 92 (and test logfile option)
93 93 (and test that logfile option do not trigger an editor)
94 94
95 95 $ hg rm b
96 96 $ echo 'amend base1 remove new file' > ../logfile
97 97 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg ci --amend --logfile ../logfile
98 98 saved backup bundle to $TESTTMP/.hg/strip-backup/b8e3cb2b3882-amend-backup.hg (glob)
99 99
100 100 $ hg cat b
101 101 b: no such file in rev 74609c7f506e
102 102 [1]
103 103
104 104 No changes, just a different message:
105 105
106 106 $ hg ci -v --amend -m 'no changes, new message'
107 107 amending changeset 74609c7f506e
108 108 copying changeset 74609c7f506e to ad120869acf0
109 109 a
110 110 stripping amended changeset 74609c7f506e
111 111 1 changesets found
112 112 saved backup bundle to $TESTTMP/.hg/strip-backup/74609c7f506e-amend-backup.hg (glob)
113 113 1 changesets found
114 114 adding branch
115 115 adding changesets
116 116 adding manifests
117 117 adding file changes
118 118 added 1 changesets with 1 changes to 1 files
119 119 committed changeset 1:1cd866679df8
120 120 $ hg diff -c .
121 121 diff -r ad120869acf0 -r 1cd866679df8 a
122 122 --- a/a Thu Jan 01 00:00:00 1970 +0000
123 123 +++ b/a Thu Jan 01 00:00:00 1970 +0000
124 124 @@ -1,1 +1,3 @@
125 125 a
126 126 +a
127 127 +a
128 128 $ hg log
129 129 changeset: 1:1cd866679df8
130 130 tag: tip
131 131 user: test
132 132 date: Thu Jan 01 00:00:00 1970 +0000
133 133 summary: no changes, new message
134 134
135 135 changeset: 0:ad120869acf0
136 136 user: test
137 137 date: Thu Jan 01 00:00:00 1970 +0000
138 138 summary: base
139 139
140 140
141 141 Disable default date on commit so when -d isn't given, the old date is preserved:
142 142
143 143 $ echo '[defaults]' >> $HGRCPATH
144 144 $ echo 'commit=' >> $HGRCPATH
145 145
146 146 Test -u/-d:
147 147
148 148 $ hg ci --amend -u foo -d '1 0'
149 149 saved backup bundle to $TESTTMP/.hg/strip-backup/1cd866679df8-amend-backup.hg (glob)
150 150 $ echo a >> a
151 151 $ hg ci --amend -u foo -d '1 0'
152 152 saved backup bundle to $TESTTMP/.hg/strip-backup/780e6f23e03d-amend-backup.hg (glob)
153 153 $ hg log -r .
154 154 changeset: 1:5f357c7560ab
155 155 tag: tip
156 156 user: foo
157 157 date: Thu Jan 01 00:00:01 1970 +0000
158 158 summary: no changes, new message
159 159
160 160
161 161 Open editor with old commit message if a message isn't given otherwise:
162 162
163 163 $ cat > editor.sh << '__EOF__'
164 164 > #!/bin/sh
165 165 > cat $1
166 166 > echo "another precious commit message" > "$1"
167 167 > __EOF__
168 168 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
169 169 amending changeset 5f357c7560ab
170 170 copying changeset 5f357c7560ab to ad120869acf0
171 171 no changes, new message
172 172
173 173
174 174 HG: Enter commit message. Lines beginning with 'HG:' are removed.
175 175 HG: Leave message empty to abort commit.
176 176 HG: --
177 177 HG: user: foo
178 178 HG: branch 'default'
179 179 HG: changed a
180 180 a
181 181 stripping amended changeset 5f357c7560ab
182 182 1 changesets found
183 183 saved backup bundle to $TESTTMP/.hg/strip-backup/5f357c7560ab-amend-backup.hg (glob)
184 184 1 changesets found
185 185 adding branch
186 186 adding changesets
187 187 adding manifests
188 188 adding file changes
189 189 added 1 changesets with 1 changes to 1 files
190 190 committed changeset 1:7ab3bf440b54
191 191
192 192 Same, but with changes in working dir (different code path):
193 193
194 194 $ echo a >> a
195 195 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
196 196 amending changeset 7ab3bf440b54
197 197 a
198 198 copying changeset a0ea9b1a4c8c to ad120869acf0
199 199 another precious commit message
200 200
201 201
202 202 HG: Enter commit message. Lines beginning with 'HG:' are removed.
203 203 HG: Leave message empty to abort commit.
204 204 HG: --
205 205 HG: user: foo
206 206 HG: branch 'default'
207 207 HG: changed a
208 208 a
209 209 stripping intermediate changeset a0ea9b1a4c8c
210 210 stripping amended changeset 7ab3bf440b54
211 211 2 changesets found
212 212 saved backup bundle to $TESTTMP/.hg/strip-backup/7ab3bf440b54-amend-backup.hg (glob)
213 213 1 changesets found
214 214 adding branch
215 215 adding changesets
216 216 adding manifests
217 217 adding file changes
218 218 added 1 changesets with 1 changes to 1 files
219 219 committed changeset 1:ea22a388757c
220 220
221 221 $ rm editor.sh
222 222 $ hg log -r .
223 223 changeset: 1:ea22a388757c
224 224 tag: tip
225 225 user: foo
226 226 date: Thu Jan 01 00:00:01 1970 +0000
227 227 summary: another precious commit message
228 228
229 229
230 230 Moving bookmarks, preserve active bookmark:
231 231
232 232 $ hg book book1
233 233 $ hg book book2
234 234 $ hg ci --amend -m 'move bookmarks'
235 235 saved backup bundle to $TESTTMP/.hg/strip-backup/ea22a388757c-amend-backup.hg (glob)
236 236 $ hg book
237 237 book1 1:6cec5aa930e2
238 238 * book2 1:6cec5aa930e2
239 239 $ echo a >> a
240 240 $ hg ci --amend -m 'move bookmarks'
241 241 saved backup bundle to $TESTTMP/.hg/strip-backup/6cec5aa930e2-amend-backup.hg (glob)
242 242 $ hg book
243 243 book1 1:48bb6e53a15f
244 244 * book2 1:48bb6e53a15f
245 245
246 246 abort does not loose bookmarks
247 247
248 248 $ cat > editor.sh << '__EOF__'
249 249 > #!/bin/sh
250 250 > echo "" > "$1"
251 251 > __EOF__
252 252 $ echo a >> a
253 253 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
254 254 transaction abort!
255 255 rollback completed
256 256 abort: empty commit message
257 257 [255]
258 258 $ hg book
259 259 book1 1:48bb6e53a15f
260 260 * book2 1:48bb6e53a15f
261 261 $ hg revert -Caq
262 262 $ rm editor.sh
263 263
264 264 $ echo '[defaults]' >> $HGRCPATH
265 265 $ echo "commit=-d '0 0'" >> $HGRCPATH
266 266
267 267 Moving branches:
268 268
269 269 $ hg branch foo
270 270 marked working directory as branch foo
271 271 (branches are permanent and global, did you want a bookmark?)
272 272 $ echo a >> a
273 273 $ hg ci -m 'branch foo'
274 274 $ hg branch default -f
275 275 marked working directory as branch default
276 276 (branches are permanent and global, did you want a bookmark?)
277 277 $ hg ci --amend -m 'back to default'
278 278 saved backup bundle to $TESTTMP/.hg/strip-backup/8ac881fbf49d-amend-backup.hg (glob)
279 279 $ hg branches
280 280 default 2:ce12b0b57d46
281 281
282 282 Close branch:
283 283
284 284 $ hg up -q 0
285 285 $ echo b >> b
286 286 $ hg branch foo
287 287 marked working directory as branch foo
288 288 (branches are permanent and global, did you want a bookmark?)
289 289 $ hg ci -Am 'fork'
290 290 adding b
291 291 $ echo b >> b
292 292 $ hg ci -mb
293 293 $ hg ci --amend --close-branch -m 'closing branch foo'
294 294 saved backup bundle to $TESTTMP/.hg/strip-backup/c962248fa264-amend-backup.hg (glob)
295 295
296 296 Same thing, different code path:
297 297
298 298 $ echo b >> b
299 299 $ hg ci -m 'reopen branch'
300 300 reopening closed branch head 4
301 301 $ echo b >> b
302 302 $ hg ci --amend --close-branch
303 303 saved backup bundle to $TESTTMP/.hg/strip-backup/027371728205-amend-backup.hg (glob)
304 304 $ hg branches
305 305 default 2:ce12b0b57d46
306 306
307 307 Refuse to amend during a merge:
308 308
309 309 $ hg up -q default
310 310 $ hg merge foo
311 311 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
312 312 (branch merge, don't forget to commit)
313 313 $ hg ci --amend
314 314 abort: cannot amend while merging
315 315 [255]
316 316 $ hg ci -m 'merge'
317 317
318 318 Follow copies/renames:
319 319
320 320 $ hg mv b c
321 321 $ hg ci -m 'b -> c'
322 322 $ hg mv c d
323 323 $ hg ci --amend -m 'b -> d'
324 324 saved backup bundle to $TESTTMP/.hg/strip-backup/b8c6eac7f12e-amend-backup.hg (glob)
325 325 $ hg st --rev '.^' --copies d
326 326 A d
327 327 b
328 328 $ hg cp d e
329 329 $ hg ci -m 'e = d'
330 330 $ hg cp e f
331 331 $ hg ci --amend -m 'f = d'
332 332 saved backup bundle to $TESTTMP/.hg/strip-backup/7f9761d65613-amend-backup.hg (glob)
333 333 $ hg st --rev '.^' --copies f
334 334 A f
335 335 d
336 336
337 337 $ mv f f.orig
338 338 $ hg rm -A f
339 339 $ hg ci -m removef
340 340 $ hg cp a f
341 341 $ mv f.orig f
342 342 $ hg ci --amend -m replacef
343 343 saved backup bundle to $TESTTMP/.hg/strip-backup/9e8c5f7e3d95-amend-backup.hg (glob)
344 344 $ hg st --change . --copies
345 345 $ hg log -r . --template "{file_copies}\n"
346 346
347 347
348 348 Move added file (issue3410):
349 349
350 350 $ echo g >> g
351 351 $ hg ci -Am g
352 352 adding g
353 353 $ hg mv g h
354 354 $ hg ci --amend
355 355 saved backup bundle to $TESTTMP/.hg/strip-backup/24aa8eacce2b-amend-backup.hg (glob)
356 356 $ hg st --change . --copies h
357 357 A h
358 358 $ hg log -r . --template "{file_copies}\n"
359 359
360 360
361 361 Can't rollback an amend:
362 362
363 363 $ hg rollback
364 364 no rollback information available
365 365 [1]
366 366
367 367 Preserve extra dict (issue3430):
368 368
369 369 $ hg branch a
370 370 marked working directory as branch a
371 371 (branches are permanent and global, did you want a bookmark?)
372 372 $ echo a >> a
373 373 $ hg ci -ma
374 374 $ hg ci --amend -m "a'"
375 375 saved backup bundle to $TESTTMP/.hg/strip-backup/3837aa2a2fdb-amend-backup.hg (glob)
376 376 $ hg log -r . --template "{branch}\n"
377 377 a
378 378 $ hg ci --amend -m "a''"
379 379 saved backup bundle to $TESTTMP/.hg/strip-backup/c05c06be7514-amend-backup.hg (glob)
380 380 $ hg log -r . --template "{branch}\n"
381 381 a
382 382
383 383 Also preserve other entries in the dict that are in the old commit,
384 384 first graft something so there's an additional entry:
385 385
386 386 $ hg up 0 -q
387 387 $ echo z > z
388 388 $ hg ci -Am 'fork'
389 389 adding z
390 390 created new head
391 391 $ hg up 11
392 392 5 files updated, 0 files merged, 1 files removed, 0 files unresolved
393 393 $ hg graft 12
394 394 grafting revision 12
395 395 $ hg ci --amend -m 'graft amend'
396 396 saved backup bundle to $TESTTMP/.hg/strip-backup/bd010aea3f39-amend-backup.hg (glob)
397 397 $ hg log -r . --debug | grep extra
398 398 extra: amend_source=bd010aea3f39f3fb2a2f884b9ccb0471cd77398e
399 399 extra: branch=a
400 400 extra: source=2647734878ef0236dda712fae9c1651cf694ea8a
401 401
402 402 Preserve phase
403 403
404 404 $ hg phase '.^::.'
405 405 11: draft
406 406 13: draft
407 407 $ hg phase --secret --force .
408 408 $ hg phase '.^::.'
409 409 11: draft
410 410 13: secret
411 411 $ hg commit --amend -m 'amend for phase' -q
412 412 $ hg phase '.^::.'
413 413 11: draft
414 414 13: secret
415 415
416 416 Test amend with obsolete
417 417 ---------------------------
418 418
419 419 Enable obsolete
420 420
421 421 $ cat > ${TESTTMP}/obs.py << EOF
422 422 > import mercurial.obsolete
423 423 > mercurial.obsolete._enabled = True
424 424 > EOF
425 425 $ echo '[extensions]' >> $HGRCPATH
426 426 $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
427 427
428 428
429 429 Amend with no files changes
430 430
431 431 $ hg id -n
432 432 13
433 433 $ hg ci --amend -m 'babar'
434 434 $ hg id -n
435 435 14
436 436 $ hg log -Gl 3 --style=compact
437 437 @ 14[tip]:11 b650e6ee8614 1970-01-01 00:00 +0000 test
438 438 | babar
439 439 |
440 440 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
441 441 | | fork
442 442 | |
443 443 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
444 444 | | a''
445 445 | |
446 446 $ hg log -Gl 4 --hidden --style=compact
447 447 @ 14[tip]:11 b650e6ee8614 1970-01-01 00:00 +0000 test
448 448 | babar
449 449 |
450 450 | x 13:11 68ff8ff97044 1970-01-01 00:00 +0000 test
451 451 |/ amend for phase
452 452 |
453 453 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
454 454 | | fork
455 455 | |
456 456 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
457 457 | | a''
458 458 | |
459 459
460 460 Amend with files changes
461 461
462 462 (note: the extra commit over 15 is a temporary junk I would be happy to get
463 463 ride of)
464 464
465 465 $ echo 'babar' >> a
466 466 $ hg commit --amend
467 467 $ hg log -Gl 6 --hidden --style=compact
468 468 @ 16[tip]:11 9f9e9bccf56c 1970-01-01 00:00 +0000 test
469 469 | babar
470 470 |
471 471 | x 15 90fef497c56f 1970-01-01 00:00 +0000 test
472 472 | | temporary amend commit for b650e6ee8614
473 473 | |
474 474 | x 14:11 b650e6ee8614 1970-01-01 00:00 +0000 test
475 475 |/ babar
476 476 |
477 477 | x 13:11 68ff8ff97044 1970-01-01 00:00 +0000 test
478 478 |/ amend for phase
479 479 |
480 480 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
481 481 | | fork
482 482 | |
483 483 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
484 484 | | a''
485 485 | |
486 486
487 487
488 488 Test that amend does not make it easy to create obsolescence cycle
489 489 ---------------------------------------------------------------------
490 490
491 491 $ hg id -r 14 --hidden
492 492 b650e6ee8614 (a)
493 493 $ hg revert -ar 14 --hidden
494 494 reverting a
495 495 $ hg commit --amend
496 496 $ hg id
497 497 b99e5df575f7 (a) tip
498 498
499 499 Test that rewriting leaving instability behind is allowed
500 500 ---------------------------------------------------------------------
501 501
502 502 $ hg up '.^'
503 503 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
504 504 $ echo 'b' >> a
505 505 $ hg log --style compact -r 'children(.)'
506 506 18[tip]:11 b99e5df575f7 1970-01-01 00:00 +0000 test
507 507 babar
508 508
509 509 $ hg commit --amend
510 510 $ hg log -r 'unstable()'
511 511 changeset: 18:b99e5df575f7
512 512 branch: a
513 513 parent: 11:3334b7925910
514 514 user: test
515 515 date: Thu Jan 01 00:00:00 1970 +0000
516 516 summary: babar
517 517
518 518
519 519 Amend a merge changeset (with renames and conflicts from the second parent):
520 520
521 521 $ hg up -q default
522 522 $ hg branch -q bar
523 523 $ hg cp a aa
524 524 $ hg mv z zz
525 525 $ echo cc > cc
526 526 $ hg add cc
527 527 $ hg ci -m aazzcc
528 528 $ hg up -q default
529 529 $ echo a >> a
530 530 $ echo dd > cc
531 531 $ hg add cc
532 532 $ hg ci -m aa
533 533 $ hg merge -q bar
534 534 warning: conflicts during merge.
535 535 merging cc incomplete! (edit conflicts, then use 'hg resolve --mark')
536 536 [1]
537 537 $ hg resolve -m cc
538 538 $ hg ci -m 'merge bar'
539 539 $ hg log --config diff.git=1 -pr .
540 540 changeset: 23:d51446492733
541 541 tag: tip
542 542 parent: 22:30d96aeaf27b
543 543 parent: 21:1aa437659d19
544 544 user: test
545 545 date: Thu Jan 01 00:00:00 1970 +0000
546 546 summary: merge bar
547 547
548 548 diff --git a/a b/aa
549 549 copy from a
550 550 copy to aa
551 551 diff --git a/cc b/cc
552 552 --- a/cc
553 553 +++ b/cc
554 554 @@ -1,1 +1,5 @@
555 555 +<<<<<<< local
556 556 dd
557 557 +=======
558 558 +cc
559 559 +>>>>>>> other
560 560 diff --git a/z b/zz
561 561 rename from z
562 562 rename to zz
563 563
564 564 $ hg debugrename aa
565 565 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
566 566 $ hg debugrename zz
567 567 zz renamed from z:69a1b67522704ec122181c0890bd16e9d3e7516a
568 568 $ hg debugrename cc
569 569 cc not renamed
570 570 $ hg ci --amend -m 'merge bar (amend message)'
571 571 $ hg log --config diff.git=1 -pr .
572 572 changeset: 24:59de3dce7a79
573 573 tag: tip
574 574 parent: 22:30d96aeaf27b
575 575 parent: 21:1aa437659d19
576 576 user: test
577 577 date: Thu Jan 01 00:00:00 1970 +0000
578 578 summary: merge bar (amend message)
579 579
580 580 diff --git a/a b/aa
581 581 copy from a
582 582 copy to aa
583 583 diff --git a/cc b/cc
584 584 --- a/cc
585 585 +++ b/cc
586 586 @@ -1,1 +1,5 @@
587 587 +<<<<<<< local
588 588 dd
589 589 +=======
590 590 +cc
591 591 +>>>>>>> other
592 592 diff --git a/z b/zz
593 593 rename from z
594 594 rename to zz
595 595
596 596 $ hg debugrename aa
597 597 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
598 598 $ hg debugrename zz
599 599 zz renamed from z:69a1b67522704ec122181c0890bd16e9d3e7516a
600 600 $ hg debugrename cc
601 601 cc not renamed
602 602 $ hg mv zz z
603 603 $ hg ci --amend -m 'merge bar (undo rename)'
604 604 $ hg log --config diff.git=1 -pr .
605 605 changeset: 26:7fb89c461f81
606 606 tag: tip
607 607 parent: 22:30d96aeaf27b
608 608 parent: 21:1aa437659d19
609 609 user: test
610 610 date: Thu Jan 01 00:00:00 1970 +0000
611 611 summary: merge bar (undo rename)
612 612
613 613 diff --git a/a b/aa
614 614 copy from a
615 615 copy to aa
616 616 diff --git a/cc b/cc
617 617 --- a/cc
618 618 +++ b/cc
619 619 @@ -1,1 +1,5 @@
620 620 +<<<<<<< local
621 621 dd
622 622 +=======
623 623 +cc
624 624 +>>>>>>> other
625 625
626 626 $ hg debugrename z
627 627 z not renamed
628 628
629 629 Amend a merge changeset (with renames during the merge):
630 630
631 631 $ hg up -q bar
632 632 $ echo x > x
633 633 $ hg add x
634 634 $ hg ci -m x
635 635 $ hg up -q default
636 636 $ hg merge -q bar
637 637 $ hg mv aa aaa
638 638 $ echo aa >> aaa
639 639 $ hg ci -m 'merge bar again'
640 640 $ hg log --config diff.git=1 -pr .
641 641 changeset: 28:982d7a34ffee
642 642 tag: tip
643 643 parent: 26:7fb89c461f81
644 644 parent: 27:4c94d5bc65f5
645 645 user: test
646 646 date: Thu Jan 01 00:00:00 1970 +0000
647 647 summary: merge bar again
648 648
649 649 diff --git a/aa b/aa
650 650 deleted file mode 100644
651 651 --- a/aa
652 652 +++ /dev/null
653 653 @@ -1,2 +0,0 @@
654 654 -a
655 655 -a
656 656 diff --git a/aaa b/aaa
657 657 new file mode 100644
658 658 --- /dev/null
659 659 +++ b/aaa
660 660 @@ -0,0 +1,3 @@
661 661 +a
662 662 +a
663 663 +aa
664 664 diff --git a/x b/x
665 665 new file mode 100644
666 666 --- /dev/null
667 667 +++ b/x
668 668 @@ -0,0 +1,1 @@
669 669 +x
670 670
671 671 $ hg debugrename aaa
672 672 aaa renamed from aa:37d9b5d994eab34eda9c16b195ace52c7b129980
673 673 $ hg mv aaa aa
674 674 $ hg ci --amend -m 'merge bar again (undo rename)'
675 675 $ hg log --config diff.git=1 -pr .
676 676 changeset: 30:522688c0e71b
677 677 tag: tip
678 678 parent: 26:7fb89c461f81
679 679 parent: 27:4c94d5bc65f5
680 680 user: test
681 681 date: Thu Jan 01 00:00:00 1970 +0000
682 682 summary: merge bar again (undo rename)
683 683
684 684 diff --git a/aa b/aa
685 685 --- a/aa
686 686 +++ b/aa
687 687 @@ -1,2 +1,3 @@
688 688 a
689 689 a
690 690 +aa
691 691 diff --git a/x b/x
692 692 new file mode 100644
693 693 --- /dev/null
694 694 +++ b/x
695 695 @@ -0,0 +1,1 @@
696 696 +x
697 697
698 698 $ hg debugrename aa
699 699 aa not renamed
700 700 $ hg debugrename -r '.^' aa
701 701 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
702 702
703 703 Amend a merge changeset (with manifest-level conflicts):
704 704
705 705 $ hg up -q bar
706 706 $ hg rm aa
707 707 $ hg ci -m 'rm aa'
708 708 $ hg up -q default
709 709 $ echo aa >> aa
710 710 $ hg ci -m aa
711 711 $ hg merge -q bar
712 712 local changed aa which remote deleted
713 713 use (c)hanged version or (d)elete? c
714 714 $ hg ci -m 'merge bar (with conflicts)'
715 715 $ hg log --config diff.git=1 -pr .
716 716 changeset: 33:5f9904c491b8
717 717 tag: tip
718 718 parent: 32:01780b896f58
719 719 parent: 31:67db8847a540
720 720 user: test
721 721 date: Thu Jan 01 00:00:00 1970 +0000
722 722 summary: merge bar (with conflicts)
723 723
724 724
725 725 $ hg rm aa
726 726 $ hg ci --amend -m 'merge bar (with conflicts, amended)'
727 727 $ hg log --config diff.git=1 -pr .
728 728 changeset: 35:6ce0c89781a3
729 729 tag: tip
730 730 parent: 32:01780b896f58
731 731 parent: 31:67db8847a540
732 732 user: test
733 733 date: Thu Jan 01 00:00:00 1970 +0000
734 734 summary: merge bar (with conflicts, amended)
735 735
736 736 diff --git a/aa b/aa
737 737 deleted file mode 100644
738 738 --- a/aa
739 739 +++ /dev/null
740 740 @@ -1,4 +0,0 @@
741 741 -a
742 742 -a
743 743 -aa
744 744 -aa
745 745
746 746 Issue 3445: amending with --close-branch a commit that created a new head should fail
747 747 This shouldn't be possible:
748 748
749 749 $ hg up -q default
750 750 $ hg branch closewithamend
751 751 marked working directory as branch closewithamend
752 752 (branches are permanent and global, did you want a bookmark?)
753 753 $ hg add obs.py
754 754 $ hg ci -m..
755 755 $ hg ci --amend --close-branch -m 'closing'
756 756 abort: can only close branch heads
757 757 [255]
758 758
759 759 This silliness fails:
760 760
761 761 $ hg branch silliness
762 762 marked working directory as branch silliness
763 763 (branches are permanent and global, did you want a bookmark?)
764 764 $ echo b >> b
765 765 $ hg ci --close-branch -m'open and close'
766 766 abort: can only close branch heads
767 767 [255]
768
769 Test that amend with --secret creates new secret changeset forcibly
770 ---------------------------------------------------------------------
771
772 $ hg phase '.^::.'
773 35: draft
774 36: draft
775 $ hg commit --amend --secret -m 'amend as secret' -q
776 $ hg phase '.^::.'
777 35: draft
778 38: secret
@@ -1,2124 +1,2129 b''
1 1 @ (34) head
2 2 |
3 3 | o (33) head
4 4 | |
5 5 o | (32) expand
6 6 |\ \
7 7 | o \ (31) expand
8 8 | |\ \
9 9 | | o \ (30) expand
10 10 | | |\ \
11 11 | | | o | (29) regular commit
12 12 | | | | |
13 13 | | o | | (28) merge zero known
14 14 | | |\ \ \
15 15 o | | | | | (27) collapse
16 16 |/ / / / /
17 17 | | o---+ (26) merge one known; far right
18 18 | | | | |
19 19 +---o | | (25) merge one known; far left
20 20 | | | | |
21 21 | | o | | (24) merge one known; immediate right
22 22 | | |\| |
23 23 | | o | | (23) merge one known; immediate left
24 24 | |/| | |
25 25 +---o---+ (22) merge two known; one far left, one far right
26 26 | | / /
27 27 o | | | (21) expand
28 28 |\ \ \ \
29 29 | o---+-+ (20) merge two known; two far right
30 30 | / / /
31 31 o | | | (19) expand
32 32 |\ \ \ \
33 33 +---+---o (18) merge two known; two far left
34 34 | | | |
35 35 | o | | (17) expand
36 36 | |\ \ \
37 37 | | o---+ (16) merge two known; one immediate right, one near right
38 38 | | |/ /
39 39 o | | | (15) expand
40 40 |\ \ \ \
41 41 | o-----+ (14) merge two known; one immediate right, one far right
42 42 | |/ / /
43 43 o | | | (13) expand
44 44 |\ \ \ \
45 45 +---o | | (12) merge two known; one immediate right, one far left
46 46 | | |/ /
47 47 | o | | (11) expand
48 48 | |\ \ \
49 49 | | o---+ (10) merge two known; one immediate left, one near right
50 50 | |/ / /
51 51 o | | | (9) expand
52 52 |\ \ \ \
53 53 | o-----+ (8) merge two known; one immediate left, one far right
54 54 |/ / / /
55 55 o | | | (7) expand
56 56 |\ \ \ \
57 57 +---o | | (6) merge two known; one immediate left, one far left
58 58 | |/ / /
59 59 | o | | (5) expand
60 60 | |\ \ \
61 61 | | o | | (4) merge two known; one immediate left, one immediate right
62 62 | |/|/ /
63 63 | o / / (3) collapse
64 64 |/ / /
65 65 o / / (2) collapse
66 66 |/ /
67 67 o / (1) collapse
68 68 |/
69 69 o (0) root
70 70
71 71
72 72 $ commit()
73 73 > {
74 74 > rev=$1
75 75 > msg=$2
76 76 > shift 2
77 77 > if [ "$#" -gt 0 ]; then
78 78 > hg debugsetparents "$@"
79 79 > fi
80 80 > echo $rev > a
81 81 > hg commit -Aqd "$rev 0" -m "($rev) $msg"
82 82 > }
83 83
84 84 $ cat > printrevset.py <<EOF
85 85 > from mercurial import extensions, revset, commands, cmdutil
86 86 >
87 87 > def uisetup(ui):
88 88 > def printrevset(orig, ui, repo, *pats, **opts):
89 89 > if opts.get('print_revset'):
90 90 > expr = cmdutil.getgraphlogrevs(repo, pats, opts)[1]
91 91 > if expr:
92 92 > tree = revset.parse(expr)[0]
93 93 > else:
94 94 > tree = []
95 95 > ui.write('%r\n' % (opts.get('rev', []),))
96 96 > ui.write(revset.prettyformat(tree) + '\n')
97 97 > return 0
98 98 > return orig(ui, repo, *pats, **opts)
99 99 > entry = extensions.wrapcommand(commands.table, 'log', printrevset)
100 100 > entry[1].append(('', 'print-revset', False,
101 101 > 'print generated revset and exit (DEPRECATED)'))
102 102 > EOF
103 103
104 104 $ echo "[extensions]" >> $HGRCPATH
105 105 $ echo "printrevset=`pwd`/printrevset.py" >> $HGRCPATH
106 106
107 107 $ hg init repo
108 108 $ cd repo
109 109
110 110 Empty repo:
111 111
112 112 $ hg log -G
113 113
114 114
115 115 Building DAG:
116 116
117 117 $ commit 0 "root"
118 118 $ commit 1 "collapse" 0
119 119 $ commit 2 "collapse" 1
120 120 $ commit 3 "collapse" 2
121 121 $ commit 4 "merge two known; one immediate left, one immediate right" 1 3
122 122 $ commit 5 "expand" 3 4
123 123 $ commit 6 "merge two known; one immediate left, one far left" 2 5
124 124 $ commit 7 "expand" 2 5
125 125 $ commit 8 "merge two known; one immediate left, one far right" 0 7
126 126 $ commit 9 "expand" 7 8
127 127 $ commit 10 "merge two known; one immediate left, one near right" 0 6
128 128 $ commit 11 "expand" 6 10
129 129 $ commit 12 "merge two known; one immediate right, one far left" 1 9
130 130 $ commit 13 "expand" 9 11
131 131 $ commit 14 "merge two known; one immediate right, one far right" 0 12
132 132 $ commit 15 "expand" 13 14
133 133 $ commit 16 "merge two known; one immediate right, one near right" 0 1
134 134 $ commit 17 "expand" 12 16
135 135 $ commit 18 "merge two known; two far left" 1 15
136 136 $ commit 19 "expand" 15 17
137 137 $ commit 20 "merge two known; two far right" 0 18
138 138 $ commit 21 "expand" 19 20
139 139 $ commit 22 "merge two known; one far left, one far right" 18 21
140 140 $ commit 23 "merge one known; immediate left" 1 22
141 141 $ commit 24 "merge one known; immediate right" 0 23
142 142 $ commit 25 "merge one known; far left" 21 24
143 143 $ commit 26 "merge one known; far right" 18 25
144 144 $ commit 27 "collapse" 21
145 145 $ commit 28 "merge zero known" 1 26
146 146 $ commit 29 "regular commit" 0
147 147 $ commit 30 "expand" 28 29
148 148 $ commit 31 "expand" 21 30
149 149 $ commit 32 "expand" 27 31
150 150 $ commit 33 "head" 18
151 151 $ commit 34 "head" 32
152 152
153 153
154 154 $ hg log -G -q
155 155 @ 34:fea3ac5810e0
156 156 |
157 157 | o 33:68608f5145f9
158 158 | |
159 159 o | 32:d06dffa21a31
160 160 |\ \
161 161 | o \ 31:621d83e11f67
162 162 | |\ \
163 163 | | o \ 30:6e11cd4b648f
164 164 | | |\ \
165 165 | | | o | 29:cd9bb2be7593
166 166 | | | | |
167 167 | | o | | 28:44ecd0b9ae99
168 168 | | |\ \ \
169 169 o | | | | | 27:886ed638191b
170 170 |/ / / / /
171 171 | | o---+ 26:7f25b6c2f0b9
172 172 | | | | |
173 173 +---o | | 25:91da8ed57247
174 174 | | | | |
175 175 | | o | | 24:a9c19a3d96b7
176 176 | | |\| |
177 177 | | o | | 23:a01cddf0766d
178 178 | |/| | |
179 179 +---o---+ 22:e0d9cccacb5d
180 180 | | / /
181 181 o | | | 21:d42a756af44d
182 182 |\ \ \ \
183 183 | o---+-+ 20:d30ed6450e32
184 184 | / / /
185 185 o | | | 19:31ddc2c1573b
186 186 |\ \ \ \
187 187 +---+---o 18:1aa84d96232a
188 188 | | | |
189 189 | o | | 17:44765d7c06e0
190 190 | |\ \ \
191 191 | | o---+ 16:3677d192927d
192 192 | | |/ /
193 193 o | | | 15:1dda3f72782d
194 194 |\ \ \ \
195 195 | o-----+ 14:8eac370358ef
196 196 | |/ / /
197 197 o | | | 13:22d8966a97e3
198 198 |\ \ \ \
199 199 +---o | | 12:86b91144a6e9
200 200 | | |/ /
201 201 | o | | 11:832d76e6bdf2
202 202 | |\ \ \
203 203 | | o---+ 10:74c64d036d72
204 204 | |/ / /
205 205 o | | | 9:7010c0af0a35
206 206 |\ \ \ \
207 207 | o-----+ 8:7a0b11f71937
208 208 |/ / / /
209 209 o | | | 7:b632bb1b1224
210 210 |\ \ \ \
211 211 +---o | | 6:b105a072e251
212 212 | |/ / /
213 213 | o | | 5:4409d547b708
214 214 | |\ \ \
215 215 | | o | | 4:26a8bac39d9f
216 216 | |/|/ /
217 217 | o / / 3:27eef8ed80b4
218 218 |/ / /
219 219 o / / 2:3d9a33b8d1e1
220 220 |/ /
221 221 o / 1:6db2ef61d156
222 222 |/
223 223 o 0:e6eb3150255d
224 224
225 225
226 226 $ hg log -G
227 227 @ changeset: 34:fea3ac5810e0
228 228 | tag: tip
229 229 | parent: 32:d06dffa21a31
230 230 | user: test
231 231 | date: Thu Jan 01 00:00:34 1970 +0000
232 232 | summary: (34) head
233 233 |
234 234 | o changeset: 33:68608f5145f9
235 235 | | parent: 18:1aa84d96232a
236 236 | | user: test
237 237 | | date: Thu Jan 01 00:00:33 1970 +0000
238 238 | | summary: (33) head
239 239 | |
240 240 o | changeset: 32:d06dffa21a31
241 241 |\ \ parent: 27:886ed638191b
242 242 | | | parent: 31:621d83e11f67
243 243 | | | user: test
244 244 | | | date: Thu Jan 01 00:00:32 1970 +0000
245 245 | | | summary: (32) expand
246 246 | | |
247 247 | o | changeset: 31:621d83e11f67
248 248 | |\ \ parent: 21:d42a756af44d
249 249 | | | | parent: 30:6e11cd4b648f
250 250 | | | | user: test
251 251 | | | | date: Thu Jan 01 00:00:31 1970 +0000
252 252 | | | | summary: (31) expand
253 253 | | | |
254 254 | | o | changeset: 30:6e11cd4b648f
255 255 | | |\ \ parent: 28:44ecd0b9ae99
256 256 | | | | | parent: 29:cd9bb2be7593
257 257 | | | | | user: test
258 258 | | | | | date: Thu Jan 01 00:00:30 1970 +0000
259 259 | | | | | summary: (30) expand
260 260 | | | | |
261 261 | | | o | changeset: 29:cd9bb2be7593
262 262 | | | | | parent: 0:e6eb3150255d
263 263 | | | | | user: test
264 264 | | | | | date: Thu Jan 01 00:00:29 1970 +0000
265 265 | | | | | summary: (29) regular commit
266 266 | | | | |
267 267 | | o | | changeset: 28:44ecd0b9ae99
268 268 | | |\ \ \ parent: 1:6db2ef61d156
269 269 | | | | | | parent: 26:7f25b6c2f0b9
270 270 | | | | | | user: test
271 271 | | | | | | date: Thu Jan 01 00:00:28 1970 +0000
272 272 | | | | | | summary: (28) merge zero known
273 273 | | | | | |
274 274 o | | | | | changeset: 27:886ed638191b
275 275 |/ / / / / parent: 21:d42a756af44d
276 276 | | | | | user: test
277 277 | | | | | date: Thu Jan 01 00:00:27 1970 +0000
278 278 | | | | | summary: (27) collapse
279 279 | | | | |
280 280 | | o---+ changeset: 26:7f25b6c2f0b9
281 281 | | | | | parent: 18:1aa84d96232a
282 282 | | | | | parent: 25:91da8ed57247
283 283 | | | | | user: test
284 284 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
285 285 | | | | | summary: (26) merge one known; far right
286 286 | | | | |
287 287 +---o | | changeset: 25:91da8ed57247
288 288 | | | | | parent: 21:d42a756af44d
289 289 | | | | | parent: 24:a9c19a3d96b7
290 290 | | | | | user: test
291 291 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
292 292 | | | | | summary: (25) merge one known; far left
293 293 | | | | |
294 294 | | o | | changeset: 24:a9c19a3d96b7
295 295 | | |\| | parent: 0:e6eb3150255d
296 296 | | | | | parent: 23:a01cddf0766d
297 297 | | | | | user: test
298 298 | | | | | date: Thu Jan 01 00:00:24 1970 +0000
299 299 | | | | | summary: (24) merge one known; immediate right
300 300 | | | | |
301 301 | | o | | changeset: 23:a01cddf0766d
302 302 | |/| | | parent: 1:6db2ef61d156
303 303 | | | | | parent: 22:e0d9cccacb5d
304 304 | | | | | user: test
305 305 | | | | | date: Thu Jan 01 00:00:23 1970 +0000
306 306 | | | | | summary: (23) merge one known; immediate left
307 307 | | | | |
308 308 +---o---+ changeset: 22:e0d9cccacb5d
309 309 | | | | parent: 18:1aa84d96232a
310 310 | | / / parent: 21:d42a756af44d
311 311 | | | | user: test
312 312 | | | | date: Thu Jan 01 00:00:22 1970 +0000
313 313 | | | | summary: (22) merge two known; one far left, one far right
314 314 | | | |
315 315 o | | | changeset: 21:d42a756af44d
316 316 |\ \ \ \ parent: 19:31ddc2c1573b
317 317 | | | | | parent: 20:d30ed6450e32
318 318 | | | | | user: test
319 319 | | | | | date: Thu Jan 01 00:00:21 1970 +0000
320 320 | | | | | summary: (21) expand
321 321 | | | | |
322 322 | o---+-+ changeset: 20:d30ed6450e32
323 323 | | | | parent: 0:e6eb3150255d
324 324 | / / / parent: 18:1aa84d96232a
325 325 | | | | user: test
326 326 | | | | date: Thu Jan 01 00:00:20 1970 +0000
327 327 | | | | summary: (20) merge two known; two far right
328 328 | | | |
329 329 o | | | changeset: 19:31ddc2c1573b
330 330 |\ \ \ \ parent: 15:1dda3f72782d
331 331 | | | | | parent: 17:44765d7c06e0
332 332 | | | | | user: test
333 333 | | | | | date: Thu Jan 01 00:00:19 1970 +0000
334 334 | | | | | summary: (19) expand
335 335 | | | | |
336 336 +---+---o changeset: 18:1aa84d96232a
337 337 | | | | parent: 1:6db2ef61d156
338 338 | | | | parent: 15:1dda3f72782d
339 339 | | | | user: test
340 340 | | | | date: Thu Jan 01 00:00:18 1970 +0000
341 341 | | | | summary: (18) merge two known; two far left
342 342 | | | |
343 343 | o | | changeset: 17:44765d7c06e0
344 344 | |\ \ \ parent: 12:86b91144a6e9
345 345 | | | | | parent: 16:3677d192927d
346 346 | | | | | user: test
347 347 | | | | | date: Thu Jan 01 00:00:17 1970 +0000
348 348 | | | | | summary: (17) expand
349 349 | | | | |
350 350 | | o---+ changeset: 16:3677d192927d
351 351 | | | | | parent: 0:e6eb3150255d
352 352 | | |/ / parent: 1:6db2ef61d156
353 353 | | | | user: test
354 354 | | | | date: Thu Jan 01 00:00:16 1970 +0000
355 355 | | | | summary: (16) merge two known; one immediate right, one near right
356 356 | | | |
357 357 o | | | changeset: 15:1dda3f72782d
358 358 |\ \ \ \ parent: 13:22d8966a97e3
359 359 | | | | | parent: 14:8eac370358ef
360 360 | | | | | user: test
361 361 | | | | | date: Thu Jan 01 00:00:15 1970 +0000
362 362 | | | | | summary: (15) expand
363 363 | | | | |
364 364 | o-----+ changeset: 14:8eac370358ef
365 365 | | | | | parent: 0:e6eb3150255d
366 366 | |/ / / parent: 12:86b91144a6e9
367 367 | | | | user: test
368 368 | | | | date: Thu Jan 01 00:00:14 1970 +0000
369 369 | | | | summary: (14) merge two known; one immediate right, one far right
370 370 | | | |
371 371 o | | | changeset: 13:22d8966a97e3
372 372 |\ \ \ \ parent: 9:7010c0af0a35
373 373 | | | | | parent: 11:832d76e6bdf2
374 374 | | | | | user: test
375 375 | | | | | date: Thu Jan 01 00:00:13 1970 +0000
376 376 | | | | | summary: (13) expand
377 377 | | | | |
378 378 +---o | | changeset: 12:86b91144a6e9
379 379 | | |/ / parent: 1:6db2ef61d156
380 380 | | | | parent: 9:7010c0af0a35
381 381 | | | | user: test
382 382 | | | | date: Thu Jan 01 00:00:12 1970 +0000
383 383 | | | | summary: (12) merge two known; one immediate right, one far left
384 384 | | | |
385 385 | o | | changeset: 11:832d76e6bdf2
386 386 | |\ \ \ parent: 6:b105a072e251
387 387 | | | | | parent: 10:74c64d036d72
388 388 | | | | | user: test
389 389 | | | | | date: Thu Jan 01 00:00:11 1970 +0000
390 390 | | | | | summary: (11) expand
391 391 | | | | |
392 392 | | o---+ changeset: 10:74c64d036d72
393 393 | | | | | parent: 0:e6eb3150255d
394 394 | |/ / / parent: 6:b105a072e251
395 395 | | | | user: test
396 396 | | | | date: Thu Jan 01 00:00:10 1970 +0000
397 397 | | | | summary: (10) merge two known; one immediate left, one near right
398 398 | | | |
399 399 o | | | changeset: 9:7010c0af0a35
400 400 |\ \ \ \ parent: 7:b632bb1b1224
401 401 | | | | | parent: 8:7a0b11f71937
402 402 | | | | | user: test
403 403 | | | | | date: Thu Jan 01 00:00:09 1970 +0000
404 404 | | | | | summary: (9) expand
405 405 | | | | |
406 406 | o-----+ changeset: 8:7a0b11f71937
407 407 | | | | | parent: 0:e6eb3150255d
408 408 |/ / / / parent: 7:b632bb1b1224
409 409 | | | | user: test
410 410 | | | | date: Thu Jan 01 00:00:08 1970 +0000
411 411 | | | | summary: (8) merge two known; one immediate left, one far right
412 412 | | | |
413 413 o | | | changeset: 7:b632bb1b1224
414 414 |\ \ \ \ parent: 2:3d9a33b8d1e1
415 415 | | | | | parent: 5:4409d547b708
416 416 | | | | | user: test
417 417 | | | | | date: Thu Jan 01 00:00:07 1970 +0000
418 418 | | | | | summary: (7) expand
419 419 | | | | |
420 420 +---o | | changeset: 6:b105a072e251
421 421 | |/ / / parent: 2:3d9a33b8d1e1
422 422 | | | | parent: 5:4409d547b708
423 423 | | | | user: test
424 424 | | | | date: Thu Jan 01 00:00:06 1970 +0000
425 425 | | | | summary: (6) merge two known; one immediate left, one far left
426 426 | | | |
427 427 | o | | changeset: 5:4409d547b708
428 428 | |\ \ \ parent: 3:27eef8ed80b4
429 429 | | | | | parent: 4:26a8bac39d9f
430 430 | | | | | user: test
431 431 | | | | | date: Thu Jan 01 00:00:05 1970 +0000
432 432 | | | | | summary: (5) expand
433 433 | | | | |
434 434 | | o | | changeset: 4:26a8bac39d9f
435 435 | |/|/ / parent: 1:6db2ef61d156
436 436 | | | | parent: 3:27eef8ed80b4
437 437 | | | | user: test
438 438 | | | | date: Thu Jan 01 00:00:04 1970 +0000
439 439 | | | | summary: (4) merge two known; one immediate left, one immediate right
440 440 | | | |
441 441 | o | | changeset: 3:27eef8ed80b4
442 442 |/ / / user: test
443 443 | | | date: Thu Jan 01 00:00:03 1970 +0000
444 444 | | | summary: (3) collapse
445 445 | | |
446 446 o | | changeset: 2:3d9a33b8d1e1
447 447 |/ / user: test
448 448 | | date: Thu Jan 01 00:00:02 1970 +0000
449 449 | | summary: (2) collapse
450 450 | |
451 451 o | changeset: 1:6db2ef61d156
452 452 |/ user: test
453 453 | date: Thu Jan 01 00:00:01 1970 +0000
454 454 | summary: (1) collapse
455 455 |
456 456 o changeset: 0:e6eb3150255d
457 457 user: test
458 458 date: Thu Jan 01 00:00:00 1970 +0000
459 459 summary: (0) root
460 460
461 461
462 462 File glog:
463 463 $ hg log -G a
464 464 @ changeset: 34:fea3ac5810e0
465 465 | tag: tip
466 466 | parent: 32:d06dffa21a31
467 467 | user: test
468 468 | date: Thu Jan 01 00:00:34 1970 +0000
469 469 | summary: (34) head
470 470 |
471 471 | o changeset: 33:68608f5145f9
472 472 | | parent: 18:1aa84d96232a
473 473 | | user: test
474 474 | | date: Thu Jan 01 00:00:33 1970 +0000
475 475 | | summary: (33) head
476 476 | |
477 477 o | changeset: 32:d06dffa21a31
478 478 |\ \ parent: 27:886ed638191b
479 479 | | | parent: 31:621d83e11f67
480 480 | | | user: test
481 481 | | | date: Thu Jan 01 00:00:32 1970 +0000
482 482 | | | summary: (32) expand
483 483 | | |
484 484 | o | changeset: 31:621d83e11f67
485 485 | |\ \ parent: 21:d42a756af44d
486 486 | | | | parent: 30:6e11cd4b648f
487 487 | | | | user: test
488 488 | | | | date: Thu Jan 01 00:00:31 1970 +0000
489 489 | | | | summary: (31) expand
490 490 | | | |
491 491 | | o | changeset: 30:6e11cd4b648f
492 492 | | |\ \ parent: 28:44ecd0b9ae99
493 493 | | | | | parent: 29:cd9bb2be7593
494 494 | | | | | user: test
495 495 | | | | | date: Thu Jan 01 00:00:30 1970 +0000
496 496 | | | | | summary: (30) expand
497 497 | | | | |
498 498 | | | o | changeset: 29:cd9bb2be7593
499 499 | | | | | parent: 0:e6eb3150255d
500 500 | | | | | user: test
501 501 | | | | | date: Thu Jan 01 00:00:29 1970 +0000
502 502 | | | | | summary: (29) regular commit
503 503 | | | | |
504 504 | | o | | changeset: 28:44ecd0b9ae99
505 505 | | |\ \ \ parent: 1:6db2ef61d156
506 506 | | | | | | parent: 26:7f25b6c2f0b9
507 507 | | | | | | user: test
508 508 | | | | | | date: Thu Jan 01 00:00:28 1970 +0000
509 509 | | | | | | summary: (28) merge zero known
510 510 | | | | | |
511 511 o | | | | | changeset: 27:886ed638191b
512 512 |/ / / / / parent: 21:d42a756af44d
513 513 | | | | | user: test
514 514 | | | | | date: Thu Jan 01 00:00:27 1970 +0000
515 515 | | | | | summary: (27) collapse
516 516 | | | | |
517 517 | | o---+ changeset: 26:7f25b6c2f0b9
518 518 | | | | | parent: 18:1aa84d96232a
519 519 | | | | | parent: 25:91da8ed57247
520 520 | | | | | user: test
521 521 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
522 522 | | | | | summary: (26) merge one known; far right
523 523 | | | | |
524 524 +---o | | changeset: 25:91da8ed57247
525 525 | | | | | parent: 21:d42a756af44d
526 526 | | | | | parent: 24:a9c19a3d96b7
527 527 | | | | | user: test
528 528 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
529 529 | | | | | summary: (25) merge one known; far left
530 530 | | | | |
531 531 | | o | | changeset: 24:a9c19a3d96b7
532 532 | | |\| | parent: 0:e6eb3150255d
533 533 | | | | | parent: 23:a01cddf0766d
534 534 | | | | | user: test
535 535 | | | | | date: Thu Jan 01 00:00:24 1970 +0000
536 536 | | | | | summary: (24) merge one known; immediate right
537 537 | | | | |
538 538 | | o | | changeset: 23:a01cddf0766d
539 539 | |/| | | parent: 1:6db2ef61d156
540 540 | | | | | parent: 22:e0d9cccacb5d
541 541 | | | | | user: test
542 542 | | | | | date: Thu Jan 01 00:00:23 1970 +0000
543 543 | | | | | summary: (23) merge one known; immediate left
544 544 | | | | |
545 545 +---o---+ changeset: 22:e0d9cccacb5d
546 546 | | | | parent: 18:1aa84d96232a
547 547 | | / / parent: 21:d42a756af44d
548 548 | | | | user: test
549 549 | | | | date: Thu Jan 01 00:00:22 1970 +0000
550 550 | | | | summary: (22) merge two known; one far left, one far right
551 551 | | | |
552 552 o | | | changeset: 21:d42a756af44d
553 553 |\ \ \ \ parent: 19:31ddc2c1573b
554 554 | | | | | parent: 20:d30ed6450e32
555 555 | | | | | user: test
556 556 | | | | | date: Thu Jan 01 00:00:21 1970 +0000
557 557 | | | | | summary: (21) expand
558 558 | | | | |
559 559 | o---+-+ changeset: 20:d30ed6450e32
560 560 | | | | parent: 0:e6eb3150255d
561 561 | / / / parent: 18:1aa84d96232a
562 562 | | | | user: test
563 563 | | | | date: Thu Jan 01 00:00:20 1970 +0000
564 564 | | | | summary: (20) merge two known; two far right
565 565 | | | |
566 566 o | | | changeset: 19:31ddc2c1573b
567 567 |\ \ \ \ parent: 15:1dda3f72782d
568 568 | | | | | parent: 17:44765d7c06e0
569 569 | | | | | user: test
570 570 | | | | | date: Thu Jan 01 00:00:19 1970 +0000
571 571 | | | | | summary: (19) expand
572 572 | | | | |
573 573 +---+---o changeset: 18:1aa84d96232a
574 574 | | | | parent: 1:6db2ef61d156
575 575 | | | | parent: 15:1dda3f72782d
576 576 | | | | user: test
577 577 | | | | date: Thu Jan 01 00:00:18 1970 +0000
578 578 | | | | summary: (18) merge two known; two far left
579 579 | | | |
580 580 | o | | changeset: 17:44765d7c06e0
581 581 | |\ \ \ parent: 12:86b91144a6e9
582 582 | | | | | parent: 16:3677d192927d
583 583 | | | | | user: test
584 584 | | | | | date: Thu Jan 01 00:00:17 1970 +0000
585 585 | | | | | summary: (17) expand
586 586 | | | | |
587 587 | | o---+ changeset: 16:3677d192927d
588 588 | | | | | parent: 0:e6eb3150255d
589 589 | | |/ / parent: 1:6db2ef61d156
590 590 | | | | user: test
591 591 | | | | date: Thu Jan 01 00:00:16 1970 +0000
592 592 | | | | summary: (16) merge two known; one immediate right, one near right
593 593 | | | |
594 594 o | | | changeset: 15:1dda3f72782d
595 595 |\ \ \ \ parent: 13:22d8966a97e3
596 596 | | | | | parent: 14:8eac370358ef
597 597 | | | | | user: test
598 598 | | | | | date: Thu Jan 01 00:00:15 1970 +0000
599 599 | | | | | summary: (15) expand
600 600 | | | | |
601 601 | o-----+ changeset: 14:8eac370358ef
602 602 | | | | | parent: 0:e6eb3150255d
603 603 | |/ / / parent: 12:86b91144a6e9
604 604 | | | | user: test
605 605 | | | | date: Thu Jan 01 00:00:14 1970 +0000
606 606 | | | | summary: (14) merge two known; one immediate right, one far right
607 607 | | | |
608 608 o | | | changeset: 13:22d8966a97e3
609 609 |\ \ \ \ parent: 9:7010c0af0a35
610 610 | | | | | parent: 11:832d76e6bdf2
611 611 | | | | | user: test
612 612 | | | | | date: Thu Jan 01 00:00:13 1970 +0000
613 613 | | | | | summary: (13) expand
614 614 | | | | |
615 615 +---o | | changeset: 12:86b91144a6e9
616 616 | | |/ / parent: 1:6db2ef61d156
617 617 | | | | parent: 9:7010c0af0a35
618 618 | | | | user: test
619 619 | | | | date: Thu Jan 01 00:00:12 1970 +0000
620 620 | | | | summary: (12) merge two known; one immediate right, one far left
621 621 | | | |
622 622 | o | | changeset: 11:832d76e6bdf2
623 623 | |\ \ \ parent: 6:b105a072e251
624 624 | | | | | parent: 10:74c64d036d72
625 625 | | | | | user: test
626 626 | | | | | date: Thu Jan 01 00:00:11 1970 +0000
627 627 | | | | | summary: (11) expand
628 628 | | | | |
629 629 | | o---+ changeset: 10:74c64d036d72
630 630 | | | | | parent: 0:e6eb3150255d
631 631 | |/ / / parent: 6:b105a072e251
632 632 | | | | user: test
633 633 | | | | date: Thu Jan 01 00:00:10 1970 +0000
634 634 | | | | summary: (10) merge two known; one immediate left, one near right
635 635 | | | |
636 636 o | | | changeset: 9:7010c0af0a35
637 637 |\ \ \ \ parent: 7:b632bb1b1224
638 638 | | | | | parent: 8:7a0b11f71937
639 639 | | | | | user: test
640 640 | | | | | date: Thu Jan 01 00:00:09 1970 +0000
641 641 | | | | | summary: (9) expand
642 642 | | | | |
643 643 | o-----+ changeset: 8:7a0b11f71937
644 644 | | | | | parent: 0:e6eb3150255d
645 645 |/ / / / parent: 7:b632bb1b1224
646 646 | | | | user: test
647 647 | | | | date: Thu Jan 01 00:00:08 1970 +0000
648 648 | | | | summary: (8) merge two known; one immediate left, one far right
649 649 | | | |
650 650 o | | | changeset: 7:b632bb1b1224
651 651 |\ \ \ \ parent: 2:3d9a33b8d1e1
652 652 | | | | | parent: 5:4409d547b708
653 653 | | | | | user: test
654 654 | | | | | date: Thu Jan 01 00:00:07 1970 +0000
655 655 | | | | | summary: (7) expand
656 656 | | | | |
657 657 +---o | | changeset: 6:b105a072e251
658 658 | |/ / / parent: 2:3d9a33b8d1e1
659 659 | | | | parent: 5:4409d547b708
660 660 | | | | user: test
661 661 | | | | date: Thu Jan 01 00:00:06 1970 +0000
662 662 | | | | summary: (6) merge two known; one immediate left, one far left
663 663 | | | |
664 664 | o | | changeset: 5:4409d547b708
665 665 | |\ \ \ parent: 3:27eef8ed80b4
666 666 | | | | | parent: 4:26a8bac39d9f
667 667 | | | | | user: test
668 668 | | | | | date: Thu Jan 01 00:00:05 1970 +0000
669 669 | | | | | summary: (5) expand
670 670 | | | | |
671 671 | | o | | changeset: 4:26a8bac39d9f
672 672 | |/|/ / parent: 1:6db2ef61d156
673 673 | | | | parent: 3:27eef8ed80b4
674 674 | | | | user: test
675 675 | | | | date: Thu Jan 01 00:00:04 1970 +0000
676 676 | | | | summary: (4) merge two known; one immediate left, one immediate right
677 677 | | | |
678 678 | o | | changeset: 3:27eef8ed80b4
679 679 |/ / / user: test
680 680 | | | date: Thu Jan 01 00:00:03 1970 +0000
681 681 | | | summary: (3) collapse
682 682 | | |
683 683 o | | changeset: 2:3d9a33b8d1e1
684 684 |/ / user: test
685 685 | | date: Thu Jan 01 00:00:02 1970 +0000
686 686 | | summary: (2) collapse
687 687 | |
688 688 o | changeset: 1:6db2ef61d156
689 689 |/ user: test
690 690 | date: Thu Jan 01 00:00:01 1970 +0000
691 691 | summary: (1) collapse
692 692 |
693 693 o changeset: 0:e6eb3150255d
694 694 user: test
695 695 date: Thu Jan 01 00:00:00 1970 +0000
696 696 summary: (0) root
697 697
698 698
699 699 File glog per revset:
700 700
701 701 $ hg log -G -r 'file("a")'
702 702 @ changeset: 34:fea3ac5810e0
703 703 | tag: tip
704 704 | parent: 32:d06dffa21a31
705 705 | user: test
706 706 | date: Thu Jan 01 00:00:34 1970 +0000
707 707 | summary: (34) head
708 708 |
709 709 | o changeset: 33:68608f5145f9
710 710 | | parent: 18:1aa84d96232a
711 711 | | user: test
712 712 | | date: Thu Jan 01 00:00:33 1970 +0000
713 713 | | summary: (33) head
714 714 | |
715 715 o | changeset: 32:d06dffa21a31
716 716 |\ \ parent: 27:886ed638191b
717 717 | | | parent: 31:621d83e11f67
718 718 | | | user: test
719 719 | | | date: Thu Jan 01 00:00:32 1970 +0000
720 720 | | | summary: (32) expand
721 721 | | |
722 722 | o | changeset: 31:621d83e11f67
723 723 | |\ \ parent: 21:d42a756af44d
724 724 | | | | parent: 30:6e11cd4b648f
725 725 | | | | user: test
726 726 | | | | date: Thu Jan 01 00:00:31 1970 +0000
727 727 | | | | summary: (31) expand
728 728 | | | |
729 729 | | o | changeset: 30:6e11cd4b648f
730 730 | | |\ \ parent: 28:44ecd0b9ae99
731 731 | | | | | parent: 29:cd9bb2be7593
732 732 | | | | | user: test
733 733 | | | | | date: Thu Jan 01 00:00:30 1970 +0000
734 734 | | | | | summary: (30) expand
735 735 | | | | |
736 736 | | | o | changeset: 29:cd9bb2be7593
737 737 | | | | | parent: 0:e6eb3150255d
738 738 | | | | | user: test
739 739 | | | | | date: Thu Jan 01 00:00:29 1970 +0000
740 740 | | | | | summary: (29) regular commit
741 741 | | | | |
742 742 | | o | | changeset: 28:44ecd0b9ae99
743 743 | | |\ \ \ parent: 1:6db2ef61d156
744 744 | | | | | | parent: 26:7f25b6c2f0b9
745 745 | | | | | | user: test
746 746 | | | | | | date: Thu Jan 01 00:00:28 1970 +0000
747 747 | | | | | | summary: (28) merge zero known
748 748 | | | | | |
749 749 o | | | | | changeset: 27:886ed638191b
750 750 |/ / / / / parent: 21:d42a756af44d
751 751 | | | | | user: test
752 752 | | | | | date: Thu Jan 01 00:00:27 1970 +0000
753 753 | | | | | summary: (27) collapse
754 754 | | | | |
755 755 | | o---+ changeset: 26:7f25b6c2f0b9
756 756 | | | | | parent: 18:1aa84d96232a
757 757 | | | | | parent: 25:91da8ed57247
758 758 | | | | | user: test
759 759 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
760 760 | | | | | summary: (26) merge one known; far right
761 761 | | | | |
762 762 +---o | | changeset: 25:91da8ed57247
763 763 | | | | | parent: 21:d42a756af44d
764 764 | | | | | parent: 24:a9c19a3d96b7
765 765 | | | | | user: test
766 766 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
767 767 | | | | | summary: (25) merge one known; far left
768 768 | | | | |
769 769 | | o | | changeset: 24:a9c19a3d96b7
770 770 | | |\| | parent: 0:e6eb3150255d
771 771 | | | | | parent: 23:a01cddf0766d
772 772 | | | | | user: test
773 773 | | | | | date: Thu Jan 01 00:00:24 1970 +0000
774 774 | | | | | summary: (24) merge one known; immediate right
775 775 | | | | |
776 776 | | o | | changeset: 23:a01cddf0766d
777 777 | |/| | | parent: 1:6db2ef61d156
778 778 | | | | | parent: 22:e0d9cccacb5d
779 779 | | | | | user: test
780 780 | | | | | date: Thu Jan 01 00:00:23 1970 +0000
781 781 | | | | | summary: (23) merge one known; immediate left
782 782 | | | | |
783 783 +---o---+ changeset: 22:e0d9cccacb5d
784 784 | | | | parent: 18:1aa84d96232a
785 785 | | / / parent: 21:d42a756af44d
786 786 | | | | user: test
787 787 | | | | date: Thu Jan 01 00:00:22 1970 +0000
788 788 | | | | summary: (22) merge two known; one far left, one far right
789 789 | | | |
790 790 o | | | changeset: 21:d42a756af44d
791 791 |\ \ \ \ parent: 19:31ddc2c1573b
792 792 | | | | | parent: 20:d30ed6450e32
793 793 | | | | | user: test
794 794 | | | | | date: Thu Jan 01 00:00:21 1970 +0000
795 795 | | | | | summary: (21) expand
796 796 | | | | |
797 797 | o---+-+ changeset: 20:d30ed6450e32
798 798 | | | | parent: 0:e6eb3150255d
799 799 | / / / parent: 18:1aa84d96232a
800 800 | | | | user: test
801 801 | | | | date: Thu Jan 01 00:00:20 1970 +0000
802 802 | | | | summary: (20) merge two known; two far right
803 803 | | | |
804 804 o | | | changeset: 19:31ddc2c1573b
805 805 |\ \ \ \ parent: 15:1dda3f72782d
806 806 | | | | | parent: 17:44765d7c06e0
807 807 | | | | | user: test
808 808 | | | | | date: Thu Jan 01 00:00:19 1970 +0000
809 809 | | | | | summary: (19) expand
810 810 | | | | |
811 811 +---+---o changeset: 18:1aa84d96232a
812 812 | | | | parent: 1:6db2ef61d156
813 813 | | | | parent: 15:1dda3f72782d
814 814 | | | | user: test
815 815 | | | | date: Thu Jan 01 00:00:18 1970 +0000
816 816 | | | | summary: (18) merge two known; two far left
817 817 | | | |
818 818 | o | | changeset: 17:44765d7c06e0
819 819 | |\ \ \ parent: 12:86b91144a6e9
820 820 | | | | | parent: 16:3677d192927d
821 821 | | | | | user: test
822 822 | | | | | date: Thu Jan 01 00:00:17 1970 +0000
823 823 | | | | | summary: (17) expand
824 824 | | | | |
825 825 | | o---+ changeset: 16:3677d192927d
826 826 | | | | | parent: 0:e6eb3150255d
827 827 | | |/ / parent: 1:6db2ef61d156
828 828 | | | | user: test
829 829 | | | | date: Thu Jan 01 00:00:16 1970 +0000
830 830 | | | | summary: (16) merge two known; one immediate right, one near right
831 831 | | | |
832 832 o | | | changeset: 15:1dda3f72782d
833 833 |\ \ \ \ parent: 13:22d8966a97e3
834 834 | | | | | parent: 14:8eac370358ef
835 835 | | | | | user: test
836 836 | | | | | date: Thu Jan 01 00:00:15 1970 +0000
837 837 | | | | | summary: (15) expand
838 838 | | | | |
839 839 | o-----+ changeset: 14:8eac370358ef
840 840 | | | | | parent: 0:e6eb3150255d
841 841 | |/ / / parent: 12:86b91144a6e9
842 842 | | | | user: test
843 843 | | | | date: Thu Jan 01 00:00:14 1970 +0000
844 844 | | | | summary: (14) merge two known; one immediate right, one far right
845 845 | | | |
846 846 o | | | changeset: 13:22d8966a97e3
847 847 |\ \ \ \ parent: 9:7010c0af0a35
848 848 | | | | | parent: 11:832d76e6bdf2
849 849 | | | | | user: test
850 850 | | | | | date: Thu Jan 01 00:00:13 1970 +0000
851 851 | | | | | summary: (13) expand
852 852 | | | | |
853 853 +---o | | changeset: 12:86b91144a6e9
854 854 | | |/ / parent: 1:6db2ef61d156
855 855 | | | | parent: 9:7010c0af0a35
856 856 | | | | user: test
857 857 | | | | date: Thu Jan 01 00:00:12 1970 +0000
858 858 | | | | summary: (12) merge two known; one immediate right, one far left
859 859 | | | |
860 860 | o | | changeset: 11:832d76e6bdf2
861 861 | |\ \ \ parent: 6:b105a072e251
862 862 | | | | | parent: 10:74c64d036d72
863 863 | | | | | user: test
864 864 | | | | | date: Thu Jan 01 00:00:11 1970 +0000
865 865 | | | | | summary: (11) expand
866 866 | | | | |
867 867 | | o---+ changeset: 10:74c64d036d72
868 868 | | | | | parent: 0:e6eb3150255d
869 869 | |/ / / parent: 6:b105a072e251
870 870 | | | | user: test
871 871 | | | | date: Thu Jan 01 00:00:10 1970 +0000
872 872 | | | | summary: (10) merge two known; one immediate left, one near right
873 873 | | | |
874 874 o | | | changeset: 9:7010c0af0a35
875 875 |\ \ \ \ parent: 7:b632bb1b1224
876 876 | | | | | parent: 8:7a0b11f71937
877 877 | | | | | user: test
878 878 | | | | | date: Thu Jan 01 00:00:09 1970 +0000
879 879 | | | | | summary: (9) expand
880 880 | | | | |
881 881 | o-----+ changeset: 8:7a0b11f71937
882 882 | | | | | parent: 0:e6eb3150255d
883 883 |/ / / / parent: 7:b632bb1b1224
884 884 | | | | user: test
885 885 | | | | date: Thu Jan 01 00:00:08 1970 +0000
886 886 | | | | summary: (8) merge two known; one immediate left, one far right
887 887 | | | |
888 888 o | | | changeset: 7:b632bb1b1224
889 889 |\ \ \ \ parent: 2:3d9a33b8d1e1
890 890 | | | | | parent: 5:4409d547b708
891 891 | | | | | user: test
892 892 | | | | | date: Thu Jan 01 00:00:07 1970 +0000
893 893 | | | | | summary: (7) expand
894 894 | | | | |
895 895 +---o | | changeset: 6:b105a072e251
896 896 | |/ / / parent: 2:3d9a33b8d1e1
897 897 | | | | parent: 5:4409d547b708
898 898 | | | | user: test
899 899 | | | | date: Thu Jan 01 00:00:06 1970 +0000
900 900 | | | | summary: (6) merge two known; one immediate left, one far left
901 901 | | | |
902 902 | o | | changeset: 5:4409d547b708
903 903 | |\ \ \ parent: 3:27eef8ed80b4
904 904 | | | | | parent: 4:26a8bac39d9f
905 905 | | | | | user: test
906 906 | | | | | date: Thu Jan 01 00:00:05 1970 +0000
907 907 | | | | | summary: (5) expand
908 908 | | | | |
909 909 | | o | | changeset: 4:26a8bac39d9f
910 910 | |/|/ / parent: 1:6db2ef61d156
911 911 | | | | parent: 3:27eef8ed80b4
912 912 | | | | user: test
913 913 | | | | date: Thu Jan 01 00:00:04 1970 +0000
914 914 | | | | summary: (4) merge two known; one immediate left, one immediate right
915 915 | | | |
916 916 | o | | changeset: 3:27eef8ed80b4
917 917 |/ / / user: test
918 918 | | | date: Thu Jan 01 00:00:03 1970 +0000
919 919 | | | summary: (3) collapse
920 920 | | |
921 921 o | | changeset: 2:3d9a33b8d1e1
922 922 |/ / user: test
923 923 | | date: Thu Jan 01 00:00:02 1970 +0000
924 924 | | summary: (2) collapse
925 925 | |
926 926 o | changeset: 1:6db2ef61d156
927 927 |/ user: test
928 928 | date: Thu Jan 01 00:00:01 1970 +0000
929 929 | summary: (1) collapse
930 930 |
931 931 o changeset: 0:e6eb3150255d
932 932 user: test
933 933 date: Thu Jan 01 00:00:00 1970 +0000
934 934 summary: (0) root
935 935
936 936
937 937
938 938 File glog per revset (only merges):
939 939
940 940 $ hg log -G -r 'file("a")' -m
941 941 o changeset: 32:d06dffa21a31
942 942 |\ parent: 27:886ed638191b
943 943 | | parent: 31:621d83e11f67
944 944 | | user: test
945 945 | | date: Thu Jan 01 00:00:32 1970 +0000
946 946 | | summary: (32) expand
947 947 | |
948 948 o | changeset: 31:621d83e11f67
949 949 |\| parent: 21:d42a756af44d
950 950 | | parent: 30:6e11cd4b648f
951 951 | | user: test
952 952 | | date: Thu Jan 01 00:00:31 1970 +0000
953 953 | | summary: (31) expand
954 954 | |
955 955 o | changeset: 30:6e11cd4b648f
956 956 |\ \ parent: 28:44ecd0b9ae99
957 957 | | | parent: 29:cd9bb2be7593
958 958 | | | user: test
959 959 | | | date: Thu Jan 01 00:00:30 1970 +0000
960 960 | | | summary: (30) expand
961 961 | | |
962 962 o | | changeset: 28:44ecd0b9ae99
963 963 |\ \ \ parent: 1:6db2ef61d156
964 964 | | | | parent: 26:7f25b6c2f0b9
965 965 | | | | user: test
966 966 | | | | date: Thu Jan 01 00:00:28 1970 +0000
967 967 | | | | summary: (28) merge zero known
968 968 | | | |
969 969 o | | | changeset: 26:7f25b6c2f0b9
970 970 |\ \ \ \ parent: 18:1aa84d96232a
971 971 | | | | | parent: 25:91da8ed57247
972 972 | | | | | user: test
973 973 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
974 974 | | | | | summary: (26) merge one known; far right
975 975 | | | | |
976 976 | o-----+ changeset: 25:91da8ed57247
977 977 | | | | | parent: 21:d42a756af44d
978 978 | | | | | parent: 24:a9c19a3d96b7
979 979 | | | | | user: test
980 980 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
981 981 | | | | | summary: (25) merge one known; far left
982 982 | | | | |
983 983 | o | | | changeset: 24:a9c19a3d96b7
984 984 | |\ \ \ \ parent: 0:e6eb3150255d
985 985 | | | | | | parent: 23:a01cddf0766d
986 986 | | | | | | user: test
987 987 | | | | | | date: Thu Jan 01 00:00:24 1970 +0000
988 988 | | | | | | summary: (24) merge one known; immediate right
989 989 | | | | | |
990 990 | o---+ | | changeset: 23:a01cddf0766d
991 991 | | | | | | parent: 1:6db2ef61d156
992 992 | | | | | | parent: 22:e0d9cccacb5d
993 993 | | | | | | user: test
994 994 | | | | | | date: Thu Jan 01 00:00:23 1970 +0000
995 995 | | | | | | summary: (23) merge one known; immediate left
996 996 | | | | | |
997 997 | o-------+ changeset: 22:e0d9cccacb5d
998 998 | | | | | | parent: 18:1aa84d96232a
999 999 |/ / / / / parent: 21:d42a756af44d
1000 1000 | | | | | user: test
1001 1001 | | | | | date: Thu Jan 01 00:00:22 1970 +0000
1002 1002 | | | | | summary: (22) merge two known; one far left, one far right
1003 1003 | | | | |
1004 1004 | | | | o changeset: 21:d42a756af44d
1005 1005 | | | | |\ parent: 19:31ddc2c1573b
1006 1006 | | | | | | parent: 20:d30ed6450e32
1007 1007 | | | | | | user: test
1008 1008 | | | | | | date: Thu Jan 01 00:00:21 1970 +0000
1009 1009 | | | | | | summary: (21) expand
1010 1010 | | | | | |
1011 1011 +-+-------o changeset: 20:d30ed6450e32
1012 1012 | | | | | parent: 0:e6eb3150255d
1013 1013 | | | | | parent: 18:1aa84d96232a
1014 1014 | | | | | user: test
1015 1015 | | | | | date: Thu Jan 01 00:00:20 1970 +0000
1016 1016 | | | | | summary: (20) merge two known; two far right
1017 1017 | | | | |
1018 1018 | | | | o changeset: 19:31ddc2c1573b
1019 1019 | | | | |\ parent: 15:1dda3f72782d
1020 1020 | | | | | | parent: 17:44765d7c06e0
1021 1021 | | | | | | user: test
1022 1022 | | | | | | date: Thu Jan 01 00:00:19 1970 +0000
1023 1023 | | | | | | summary: (19) expand
1024 1024 | | | | | |
1025 1025 o---+---+ | changeset: 18:1aa84d96232a
1026 1026 | | | | | parent: 1:6db2ef61d156
1027 1027 / / / / / parent: 15:1dda3f72782d
1028 1028 | | | | | user: test
1029 1029 | | | | | date: Thu Jan 01 00:00:18 1970 +0000
1030 1030 | | | | | summary: (18) merge two known; two far left
1031 1031 | | | | |
1032 1032 | | | | o changeset: 17:44765d7c06e0
1033 1033 | | | | |\ parent: 12:86b91144a6e9
1034 1034 | | | | | | parent: 16:3677d192927d
1035 1035 | | | | | | user: test
1036 1036 | | | | | | date: Thu Jan 01 00:00:17 1970 +0000
1037 1037 | | | | | | summary: (17) expand
1038 1038 | | | | | |
1039 1039 +-+-------o changeset: 16:3677d192927d
1040 1040 | | | | | parent: 0:e6eb3150255d
1041 1041 | | | | | parent: 1:6db2ef61d156
1042 1042 | | | | | user: test
1043 1043 | | | | | date: Thu Jan 01 00:00:16 1970 +0000
1044 1044 | | | | | summary: (16) merge two known; one immediate right, one near right
1045 1045 | | | | |
1046 1046 | | | o | changeset: 15:1dda3f72782d
1047 1047 | | | |\ \ parent: 13:22d8966a97e3
1048 1048 | | | | | | parent: 14:8eac370358ef
1049 1049 | | | | | | user: test
1050 1050 | | | | | | date: Thu Jan 01 00:00:15 1970 +0000
1051 1051 | | | | | | summary: (15) expand
1052 1052 | | | | | |
1053 1053 +-------o | changeset: 14:8eac370358ef
1054 1054 | | | | |/ parent: 0:e6eb3150255d
1055 1055 | | | | | parent: 12:86b91144a6e9
1056 1056 | | | | | user: test
1057 1057 | | | | | date: Thu Jan 01 00:00:14 1970 +0000
1058 1058 | | | | | summary: (14) merge two known; one immediate right, one far right
1059 1059 | | | | |
1060 1060 | | | o | changeset: 13:22d8966a97e3
1061 1061 | | | |\ \ parent: 9:7010c0af0a35
1062 1062 | | | | | | parent: 11:832d76e6bdf2
1063 1063 | | | | | | user: test
1064 1064 | | | | | | date: Thu Jan 01 00:00:13 1970 +0000
1065 1065 | | | | | | summary: (13) expand
1066 1066 | | | | | |
1067 1067 | +---+---o changeset: 12:86b91144a6e9
1068 1068 | | | | | parent: 1:6db2ef61d156
1069 1069 | | | | | parent: 9:7010c0af0a35
1070 1070 | | | | | user: test
1071 1071 | | | | | date: Thu Jan 01 00:00:12 1970 +0000
1072 1072 | | | | | summary: (12) merge two known; one immediate right, one far left
1073 1073 | | | | |
1074 1074 | | | | o changeset: 11:832d76e6bdf2
1075 1075 | | | | |\ parent: 6:b105a072e251
1076 1076 | | | | | | parent: 10:74c64d036d72
1077 1077 | | | | | | user: test
1078 1078 | | | | | | date: Thu Jan 01 00:00:11 1970 +0000
1079 1079 | | | | | | summary: (11) expand
1080 1080 | | | | | |
1081 1081 +---------o changeset: 10:74c64d036d72
1082 1082 | | | | |/ parent: 0:e6eb3150255d
1083 1083 | | | | | parent: 6:b105a072e251
1084 1084 | | | | | user: test
1085 1085 | | | | | date: Thu Jan 01 00:00:10 1970 +0000
1086 1086 | | | | | summary: (10) merge two known; one immediate left, one near right
1087 1087 | | | | |
1088 1088 | | | o | changeset: 9:7010c0af0a35
1089 1089 | | | |\ \ parent: 7:b632bb1b1224
1090 1090 | | | | | | parent: 8:7a0b11f71937
1091 1091 | | | | | | user: test
1092 1092 | | | | | | date: Thu Jan 01 00:00:09 1970 +0000
1093 1093 | | | | | | summary: (9) expand
1094 1094 | | | | | |
1095 1095 +-------o | changeset: 8:7a0b11f71937
1096 1096 | | | |/ / parent: 0:e6eb3150255d
1097 1097 | | | | | parent: 7:b632bb1b1224
1098 1098 | | | | | user: test
1099 1099 | | | | | date: Thu Jan 01 00:00:08 1970 +0000
1100 1100 | | | | | summary: (8) merge two known; one immediate left, one far right
1101 1101 | | | | |
1102 1102 | | | o | changeset: 7:b632bb1b1224
1103 1103 | | | |\ \ parent: 2:3d9a33b8d1e1
1104 1104 | | | | | | parent: 5:4409d547b708
1105 1105 | | | | | | user: test
1106 1106 | | | | | | date: Thu Jan 01 00:00:07 1970 +0000
1107 1107 | | | | | | summary: (7) expand
1108 1108 | | | | | |
1109 1109 | | | +---o changeset: 6:b105a072e251
1110 1110 | | | | |/ parent: 2:3d9a33b8d1e1
1111 1111 | | | | | parent: 5:4409d547b708
1112 1112 | | | | | user: test
1113 1113 | | | | | date: Thu Jan 01 00:00:06 1970 +0000
1114 1114 | | | | | summary: (6) merge two known; one immediate left, one far left
1115 1115 | | | | |
1116 1116 | | | o | changeset: 5:4409d547b708
1117 1117 | | | |\ \ parent: 3:27eef8ed80b4
1118 1118 | | | | | | parent: 4:26a8bac39d9f
1119 1119 | | | | | | user: test
1120 1120 | | | | | | date: Thu Jan 01 00:00:05 1970 +0000
1121 1121 | | | | | | summary: (5) expand
1122 1122 | | | | | |
1123 1123 | +---o | | changeset: 4:26a8bac39d9f
1124 1124 | | | |/ / parent: 1:6db2ef61d156
1125 1125 | | | | | parent: 3:27eef8ed80b4
1126 1126 | | | | | user: test
1127 1127 | | | | | date: Thu Jan 01 00:00:04 1970 +0000
1128 1128 | | | | | summary: (4) merge two known; one immediate left, one immediate right
1129 1129 | | | | |
1130 1130
1131 1131
1132 1132 Empty revision range - display nothing:
1133 1133 $ hg log -G -r 1..0
1134 1134
1135 1135 $ cd ..
1136 1136
1137 1137 #if no-outer-repo
1138 1138
1139 1139 From outer space:
1140 1140 $ hg log -G -l1 repo
1141 1141 @ changeset: 34:fea3ac5810e0
1142 1142 | tag: tip
1143 1143 | parent: 32:d06dffa21a31
1144 1144 | user: test
1145 1145 | date: Thu Jan 01 00:00:34 1970 +0000
1146 1146 | summary: (34) head
1147 1147 |
1148 1148 $ hg log -G -l1 repo/a
1149 1149 @ changeset: 34:fea3ac5810e0
1150 1150 | tag: tip
1151 1151 | parent: 32:d06dffa21a31
1152 1152 | user: test
1153 1153 | date: Thu Jan 01 00:00:34 1970 +0000
1154 1154 | summary: (34) head
1155 1155 |
1156 1156 $ hg log -G -l1 repo/missing
1157 1157
1158 1158 #endif
1159 1159
1160 1160 File log with revs != cset revs:
1161 1161 $ hg init flog
1162 1162 $ cd flog
1163 1163 $ echo one >one
1164 1164 $ hg add one
1165 1165 $ hg commit -mone
1166 1166 $ echo two >two
1167 1167 $ hg add two
1168 1168 $ hg commit -mtwo
1169 1169 $ echo more >two
1170 1170 $ hg commit -mmore
1171 1171 $ hg log -G two
1172 1172 @ changeset: 2:12c28321755b
1173 1173 | tag: tip
1174 1174 | user: test
1175 1175 | date: Thu Jan 01 00:00:00 1970 +0000
1176 1176 | summary: more
1177 1177 |
1178 1178 o changeset: 1:5ac72c0599bf
1179 1179 | user: test
1180 1180 | date: Thu Jan 01 00:00:00 1970 +0000
1181 1181 | summary: two
1182 1182 |
1183 1183
1184 1184 Issue1896: File log with explicit style
1185 1185 $ hg log -G --style=default one
1186 1186 o changeset: 0:3d578b4a1f53
1187 1187 user: test
1188 1188 date: Thu Jan 01 00:00:00 1970 +0000
1189 1189 summary: one
1190 1190
1191 1191 Issue2395: glog --style header and footer
1192 1192 $ hg log -G --style=xml one
1193 1193 <?xml version="1.0"?>
1194 1194 <log>
1195 1195 o <logentry revision="0" node="3d578b4a1f537d5fcf7301bfa9c0b97adfaa6fb1">
1196 1196 <author email="test">test</author>
1197 1197 <date>1970-01-01T00:00:00+00:00</date>
1198 1198 <msg xml:space="preserve">one</msg>
1199 1199 </logentry>
1200 1200 </log>
1201 1201
1202 1202 $ cd ..
1203 1203
1204 1204 Incoming and outgoing:
1205 1205
1206 1206 $ hg clone -U -r31 repo repo2
1207 1207 adding changesets
1208 1208 adding manifests
1209 1209 adding file changes
1210 1210 added 31 changesets with 31 changes to 1 files
1211 1211 $ cd repo2
1212 1212
1213 1213 $ hg incoming --graph ../repo
1214 1214 comparing with ../repo
1215 1215 searching for changes
1216 1216 o changeset: 34:fea3ac5810e0
1217 1217 | tag: tip
1218 1218 | parent: 32:d06dffa21a31
1219 1219 | user: test
1220 1220 | date: Thu Jan 01 00:00:34 1970 +0000
1221 1221 | summary: (34) head
1222 1222 |
1223 1223 | o changeset: 33:68608f5145f9
1224 1224 | parent: 18:1aa84d96232a
1225 1225 | user: test
1226 1226 | date: Thu Jan 01 00:00:33 1970 +0000
1227 1227 | summary: (33) head
1228 1228 |
1229 1229 o changeset: 32:d06dffa21a31
1230 1230 | parent: 27:886ed638191b
1231 1231 | parent: 31:621d83e11f67
1232 1232 | user: test
1233 1233 | date: Thu Jan 01 00:00:32 1970 +0000
1234 1234 | summary: (32) expand
1235 1235 |
1236 1236 o changeset: 27:886ed638191b
1237 1237 parent: 21:d42a756af44d
1238 1238 user: test
1239 1239 date: Thu Jan 01 00:00:27 1970 +0000
1240 1240 summary: (27) collapse
1241 1241
1242 1242 $ cd ..
1243 1243
1244 1244 $ hg -R repo outgoing --graph repo2
1245 1245 comparing with repo2
1246 1246 searching for changes
1247 1247 @ changeset: 34:fea3ac5810e0
1248 1248 | tag: tip
1249 1249 | parent: 32:d06dffa21a31
1250 1250 | user: test
1251 1251 | date: Thu Jan 01 00:00:34 1970 +0000
1252 1252 | summary: (34) head
1253 1253 |
1254 1254 | o changeset: 33:68608f5145f9
1255 1255 | parent: 18:1aa84d96232a
1256 1256 | user: test
1257 1257 | date: Thu Jan 01 00:00:33 1970 +0000
1258 1258 | summary: (33) head
1259 1259 |
1260 1260 o changeset: 32:d06dffa21a31
1261 1261 | parent: 27:886ed638191b
1262 1262 | parent: 31:621d83e11f67
1263 1263 | user: test
1264 1264 | date: Thu Jan 01 00:00:32 1970 +0000
1265 1265 | summary: (32) expand
1266 1266 |
1267 1267 o changeset: 27:886ed638191b
1268 1268 parent: 21:d42a756af44d
1269 1269 user: test
1270 1270 date: Thu Jan 01 00:00:27 1970 +0000
1271 1271 summary: (27) collapse
1272 1272
1273 1273
1274 1274 File + limit with revs != cset revs:
1275 1275 $ cd repo
1276 1276 $ touch b
1277 1277 $ hg ci -Aqm0
1278 1278 $ hg log -G -l2 a
1279 1279 o changeset: 34:fea3ac5810e0
1280 1280 | parent: 32:d06dffa21a31
1281 1281 | user: test
1282 1282 | date: Thu Jan 01 00:00:34 1970 +0000
1283 1283 | summary: (34) head
1284 1284 |
1285 1285 | o changeset: 33:68608f5145f9
1286 1286 | | parent: 18:1aa84d96232a
1287 1287 | | user: test
1288 1288 | | date: Thu Jan 01 00:00:33 1970 +0000
1289 1289 | | summary: (33) head
1290 1290 | |
1291 1291
1292 1292 File + limit + -ra:b, (b - a) < limit:
1293 1293 $ hg log -G -l3000 -r32:tip a
1294 1294 o changeset: 34:fea3ac5810e0
1295 1295 | parent: 32:d06dffa21a31
1296 1296 | user: test
1297 1297 | date: Thu Jan 01 00:00:34 1970 +0000
1298 1298 | summary: (34) head
1299 1299 |
1300 1300 | o changeset: 33:68608f5145f9
1301 1301 | | parent: 18:1aa84d96232a
1302 1302 | | user: test
1303 1303 | | date: Thu Jan 01 00:00:33 1970 +0000
1304 1304 | | summary: (33) head
1305 1305 | |
1306 1306 o | changeset: 32:d06dffa21a31
1307 1307 |\ \ parent: 27:886ed638191b
1308 1308 | | | parent: 31:621d83e11f67
1309 1309 | | | user: test
1310 1310 | | | date: Thu Jan 01 00:00:32 1970 +0000
1311 1311 | | | summary: (32) expand
1312 1312 | | |
1313 1313
1314 1314 Point out a common and an uncommon unshown parent
1315 1315
1316 1316 $ hg log -G -r 'rev(8) or rev(9)'
1317 1317 o changeset: 9:7010c0af0a35
1318 1318 |\ parent: 7:b632bb1b1224
1319 1319 | | parent: 8:7a0b11f71937
1320 1320 | | user: test
1321 1321 | | date: Thu Jan 01 00:00:09 1970 +0000
1322 1322 | | summary: (9) expand
1323 1323 | |
1324 1324 o | changeset: 8:7a0b11f71937
1325 1325 |\| parent: 0:e6eb3150255d
1326 1326 | | parent: 7:b632bb1b1224
1327 1327 | | user: test
1328 1328 | | date: Thu Jan 01 00:00:08 1970 +0000
1329 1329 | | summary: (8) merge two known; one immediate left, one far right
1330 1330 | |
1331 1331
1332 1332 File + limit + -ra:b, b < tip:
1333 1333
1334 1334 $ hg log -G -l1 -r32:34 a
1335 1335 o changeset: 34:fea3ac5810e0
1336 1336 | parent: 32:d06dffa21a31
1337 1337 | user: test
1338 1338 | date: Thu Jan 01 00:00:34 1970 +0000
1339 1339 | summary: (34) head
1340 1340 |
1341 1341
1342 1342 file(File) + limit + -ra:b, b < tip:
1343 1343
1344 1344 $ hg log -G -l1 -r32:34 -r 'file("a")'
1345 1345 o changeset: 34:fea3ac5810e0
1346 1346 | parent: 32:d06dffa21a31
1347 1347 | user: test
1348 1348 | date: Thu Jan 01 00:00:34 1970 +0000
1349 1349 | summary: (34) head
1350 1350 |
1351 1351
1352 1352 limit(file(File) and a::b), b < tip:
1353 1353
1354 1354 $ hg log -G -r 'limit(file("a") and 32::34, 1)'
1355 1355 o changeset: 32:d06dffa21a31
1356 1356 |\ parent: 27:886ed638191b
1357 1357 | | parent: 31:621d83e11f67
1358 1358 | | user: test
1359 1359 | | date: Thu Jan 01 00:00:32 1970 +0000
1360 1360 | | summary: (32) expand
1361 1361 | |
1362 1362
1363 1363 File + limit + -ra:b, b < tip:
1364 1364
1365 1365 $ hg log -G -r 'limit(file("a") and 34::32, 1)'
1366 1366
1367 1367 File + limit + -ra:b, b < tip, (b - a) < limit:
1368 1368
1369 1369 $ hg log -G -l10 -r33:34 a
1370 1370 o changeset: 34:fea3ac5810e0
1371 1371 | parent: 32:d06dffa21a31
1372 1372 | user: test
1373 1373 | date: Thu Jan 01 00:00:34 1970 +0000
1374 1374 | summary: (34) head
1375 1375 |
1376 1376 | o changeset: 33:68608f5145f9
1377 1377 | | parent: 18:1aa84d96232a
1378 1378 | | user: test
1379 1379 | | date: Thu Jan 01 00:00:33 1970 +0000
1380 1380 | | summary: (33) head
1381 1381 | |
1382 1382
1383 1383 Do not crash or produce strange graphs if history is buggy
1384 1384
1385 1385 $ hg branch branch
1386 1386 marked working directory as branch branch
1387 1387 (branches are permanent and global, did you want a bookmark?)
1388 1388 $ commit 36 "buggy merge: identical parents" 35 35
1389 1389 $ hg log -G -l5
1390 1390 @ changeset: 36:08a19a744424
1391 1391 | branch: branch
1392 1392 | tag: tip
1393 1393 | parent: 35:9159c3644c5e
1394 1394 | parent: 35:9159c3644c5e
1395 1395 | user: test
1396 1396 | date: Thu Jan 01 00:00:36 1970 +0000
1397 1397 | summary: (36) buggy merge: identical parents
1398 1398 |
1399 1399 o changeset: 35:9159c3644c5e
1400 1400 | user: test
1401 1401 | date: Thu Jan 01 00:00:00 1970 +0000
1402 1402 | summary: 0
1403 1403 |
1404 1404 o changeset: 34:fea3ac5810e0
1405 1405 | parent: 32:d06dffa21a31
1406 1406 | user: test
1407 1407 | date: Thu Jan 01 00:00:34 1970 +0000
1408 1408 | summary: (34) head
1409 1409 |
1410 1410 | o changeset: 33:68608f5145f9
1411 1411 | | parent: 18:1aa84d96232a
1412 1412 | | user: test
1413 1413 | | date: Thu Jan 01 00:00:33 1970 +0000
1414 1414 | | summary: (33) head
1415 1415 | |
1416 1416 o | changeset: 32:d06dffa21a31
1417 1417 |\ \ parent: 27:886ed638191b
1418 1418 | | | parent: 31:621d83e11f67
1419 1419 | | | user: test
1420 1420 | | | date: Thu Jan 01 00:00:32 1970 +0000
1421 1421 | | | summary: (32) expand
1422 1422 | | |
1423 1423
1424 1424 Test log -G options
1425 1425
1426 1426 $ testlog() {
1427 1427 > hg log -G --print-revset "$@"
1428 1428 > hg log --template 'nodetag {rev}\n' "$@" | grep nodetag \
1429 1429 > | sed 's/.*nodetag/nodetag/' > log.nodes
1430 1430 > hg log -G --template 'nodetag {rev}\n' "$@" | grep nodetag \
1431 1431 > | sed 's/.*nodetag/nodetag/' > glog.nodes
1432 1432 > diff -u log.nodes glog.nodes | grep '^[-+@ ]' || :
1433 1433 > }
1434 1434
1435 1435 glog always reorders nodes which explains the difference with log
1436 1436
1437 1437 $ testlog -r 27 -r 25 -r 21 -r 34 -r 32 -r 31
1438 1438 ['27', '25', '21', '34', '32', '31']
1439 1439 []
1440 1440 --- log.nodes * (glob)
1441 1441 +++ glog.nodes * (glob)
1442 1442 @@ -1,6 +1,6 @@
1443 1443 -nodetag 27
1444 1444 -nodetag 25
1445 1445 -nodetag 21
1446 1446 nodetag 34
1447 1447 nodetag 32
1448 1448 nodetag 31
1449 1449 +nodetag 27
1450 1450 +nodetag 25
1451 1451 +nodetag 21
1452 1452 $ testlog -u test -u not-a-user
1453 1453 []
1454 1454 (group
1455 1455 (group
1456 1456 (or
1457 1457 (func
1458 1458 ('symbol', 'user')
1459 1459 ('string', 'test'))
1460 1460 (func
1461 1461 ('symbol', 'user')
1462 1462 ('string', 'not-a-user')))))
1463 1463 $ testlog -b not-a-branch
1464 1464 abort: unknown revision 'not-a-branch'!
1465 1465 abort: unknown revision 'not-a-branch'!
1466 1466 abort: unknown revision 'not-a-branch'!
1467 1467 $ testlog -b 35 -b 36 --only-branch branch
1468 1468 []
1469 1469 (group
1470 1470 (group
1471 1471 (or
1472 1472 (or
1473 1473 (func
1474 1474 ('symbol', 'branch')
1475 1475 ('string', 'default'))
1476 1476 (func
1477 1477 ('symbol', 'branch')
1478 1478 ('string', 'branch')))
1479 1479 (func
1480 1480 ('symbol', 'branch')
1481 1481 ('string', 'branch')))))
1482 1482 $ testlog -k expand -k merge
1483 1483 []
1484 1484 (group
1485 1485 (group
1486 1486 (or
1487 1487 (func
1488 1488 ('symbol', 'keyword')
1489 1489 ('string', 'expand'))
1490 1490 (func
1491 1491 ('symbol', 'keyword')
1492 1492 ('string', 'merge')))))
1493 1493 $ testlog --only-merges
1494 1494 []
1495 1495 (group
1496 1496 (func
1497 1497 ('symbol', 'merge')
1498 1498 None))
1499 1499 $ testlog --no-merges
1500 1500 []
1501 1501 (group
1502 1502 (not
1503 1503 (func
1504 1504 ('symbol', 'merge')
1505 1505 None)))
1506 1506 $ testlog --date '2 0 to 4 0'
1507 1507 []
1508 1508 (group
1509 1509 (func
1510 1510 ('symbol', 'date')
1511 1511 ('string', '2 0 to 4 0')))
1512 1512 $ hg log -G -d 'brace ) in a date'
1513 1513 abort: invalid date: 'brace ) in a date'
1514 1514 [255]
1515 1515 $ testlog --prune 31 --prune 32
1516 1516 []
1517 1517 (group
1518 1518 (group
1519 1519 (and
1520 1520 (not
1521 1521 (group
1522 1522 (or
1523 1523 ('string', '31')
1524 1524 (func
1525 1525 ('symbol', 'ancestors')
1526 1526 ('string', '31')))))
1527 1527 (not
1528 1528 (group
1529 1529 (or
1530 1530 ('string', '32')
1531 1531 (func
1532 1532 ('symbol', 'ancestors')
1533 1533 ('string', '32'))))))))
1534 1534
1535 1535 Dedicated repo for --follow and paths filtering. The g is crafted to
1536 1536 have 2 filelog topological heads in a linear changeset graph.
1537 1537
1538 1538 $ cd ..
1539 1539 $ hg init follow
1540 1540 $ cd follow
1541 1541 $ testlog --follow
1542 1542 []
1543 1543 []
1544 1544 $ echo a > a
1545 1545 $ echo aa > aa
1546 1546 $ echo f > f
1547 1547 $ hg ci -Am "add a" a aa f
1548 1548 $ hg cp a b
1549 1549 $ hg cp f g
1550 1550 $ hg ci -m "copy a b"
1551 1551 $ mkdir dir
1552 1552 $ hg mv b dir
1553 1553 $ echo g >> g
1554 1554 $ echo f >> f
1555 1555 $ hg ci -m "mv b dir/b"
1556 1556 $ hg mv a b
1557 1557 $ hg cp -f f g
1558 1558 $ echo a > d
1559 1559 $ hg add d
1560 1560 $ hg ci -m "mv a b; add d"
1561 1561 $ hg mv dir/b e
1562 1562 $ hg ci -m "mv dir/b e"
1563 1563 $ hg log -G --template '({rev}) {desc|firstline}\n'
1564 1564 @ (4) mv dir/b e
1565 1565 |
1566 1566 o (3) mv a b; add d
1567 1567 |
1568 1568 o (2) mv b dir/b
1569 1569 |
1570 1570 o (1) copy a b
1571 1571 |
1572 1572 o (0) add a
1573 1573
1574 1574
1575 1575 $ testlog a
1576 1576 []
1577 1577 (group
1578 1578 (group
1579 1579 (func
1580 1580 ('symbol', 'filelog')
1581 1581 ('string', 'a'))))
1582 1582 $ testlog a b
1583 1583 []
1584 1584 (group
1585 1585 (group
1586 1586 (or
1587 1587 (func
1588 1588 ('symbol', 'filelog')
1589 1589 ('string', 'a'))
1590 1590 (func
1591 1591 ('symbol', 'filelog')
1592 1592 ('string', 'b')))))
1593 1593
1594 1594 Test falling back to slow path for non-existing files
1595 1595
1596 1596 $ testlog a c
1597 1597 []
1598 1598 (group
1599 1599 (func
1600 1600 ('symbol', '_matchfiles')
1601 1601 (list
1602 1602 (list
1603 1603 (list
1604 1604 ('string', 'r:')
1605 1605 ('string', 'd:relpath'))
1606 1606 ('string', 'p:a'))
1607 1607 ('string', 'p:c'))))
1608 1608
1609 1609 Test multiple --include/--exclude/paths
1610 1610
1611 1611 $ testlog --include a --include e --exclude b --exclude e a e
1612 1612 []
1613 1613 (group
1614 1614 (func
1615 1615 ('symbol', '_matchfiles')
1616 1616 (list
1617 1617 (list
1618 1618 (list
1619 1619 (list
1620 1620 (list
1621 1621 (list
1622 1622 (list
1623 1623 ('string', 'r:')
1624 1624 ('string', 'd:relpath'))
1625 1625 ('string', 'p:a'))
1626 1626 ('string', 'p:e'))
1627 1627 ('string', 'i:a'))
1628 1628 ('string', 'i:e'))
1629 1629 ('string', 'x:b'))
1630 1630 ('string', 'x:e'))))
1631 1631
1632 1632 Test glob expansion of pats
1633 1633
1634 1634 $ expandglobs=`python -c "import mercurial.util; \
1635 1635 > print mercurial.util.expandglobs and 'true' or 'false'"`
1636 1636 $ if [ $expandglobs = "true" ]; then
1637 1637 > testlog 'a*';
1638 1638 > else
1639 1639 > testlog a*;
1640 1640 > fi;
1641 1641 []
1642 1642 (group
1643 1643 (group
1644 1644 (func
1645 1645 ('symbol', 'filelog')
1646 1646 ('string', 'aa'))))
1647 1647
1648 1648 Test --follow on a directory
1649 1649
1650 1650 $ testlog -f dir
1651 1651 abort: cannot follow file not in parent revision: "dir"
1652 1652 abort: cannot follow file not in parent revision: "dir"
1653 1653 abort: cannot follow file not in parent revision: "dir"
1654 1654
1655 1655 Test --follow on file not in parent revision
1656 1656
1657 1657 $ testlog -f a
1658 1658 abort: cannot follow file not in parent revision: "a"
1659 1659 abort: cannot follow file not in parent revision: "a"
1660 1660 abort: cannot follow file not in parent revision: "a"
1661 1661
1662 1662 Test --follow and patterns
1663 1663
1664 1664 $ testlog -f 'glob:*'
1665 1665 abort: can only follow copies/renames for explicit filenames
1666 1666 abort: can only follow copies/renames for explicit filenames
1667 1667 abort: can only follow copies/renames for explicit filenames
1668 1668
1669 1669 Test --follow on a single rename
1670 1670
1671 1671 $ hg up -q 2
1672 1672 $ testlog -f a
1673 1673 []
1674 1674 (group
1675 1675 (group
1676 1676 (func
1677 1677 ('symbol', 'follow')
1678 1678 ('string', 'a'))))
1679 1679
1680 1680 Test --follow and multiple renames
1681 1681
1682 1682 $ hg up -q tip
1683 1683 $ testlog -f e
1684 1684 []
1685 1685 (group
1686 1686 (group
1687 1687 (func
1688 1688 ('symbol', 'follow')
1689 1689 ('string', 'e'))))
1690 1690
1691 1691 Test --follow and multiple filelog heads
1692 1692
1693 1693 $ hg up -q 2
1694 1694 $ testlog -f g
1695 1695 []
1696 1696 (group
1697 1697 (group
1698 1698 (func
1699 1699 ('symbol', 'follow')
1700 1700 ('string', 'g'))))
1701 1701 $ cat log.nodes
1702 1702 nodetag 2
1703 1703 nodetag 1
1704 1704 nodetag 0
1705 1705 $ hg up -q tip
1706 1706 $ testlog -f g
1707 1707 []
1708 1708 (group
1709 1709 (group
1710 1710 (func
1711 1711 ('symbol', 'follow')
1712 1712 ('string', 'g'))))
1713 1713 $ cat log.nodes
1714 1714 nodetag 3
1715 1715 nodetag 2
1716 1716 nodetag 0
1717 1717
1718 1718 Test --follow and multiple files
1719 1719
1720 1720 $ testlog -f g e
1721 1721 []
1722 1722 (group
1723 1723 (group
1724 1724 (or
1725 1725 (func
1726 1726 ('symbol', 'follow')
1727 1727 ('string', 'g'))
1728 1728 (func
1729 1729 ('symbol', 'follow')
1730 1730 ('string', 'e')))))
1731 1731 $ cat log.nodes
1732 1732 nodetag 4
1733 1733 nodetag 3
1734 1734 nodetag 2
1735 1735 nodetag 1
1736 1736 nodetag 0
1737 1737
1738 1738 Test --follow-first
1739 1739
1740 1740 $ hg up -q 3
1741 1741 $ echo ee > e
1742 1742 $ hg ci -Am "add another e" e
1743 1743 created new head
1744 1744 $ hg merge --tool internal:other 4
1745 1745 0 files updated, 1 files merged, 1 files removed, 0 files unresolved
1746 1746 (branch merge, don't forget to commit)
1747 1747 $ echo merge > e
1748 1748 $ hg ci -m "merge 5 and 4"
1749 1749 $ testlog --follow-first
1750 1750 []
1751 1751 (group
1752 1752 (func
1753 1753 ('symbol', '_firstancestors')
1754 1754 ('symbol', '6')))
1755 1755
1756 1756 Cannot compare with log --follow-first FILE as it never worked
1757 1757
1758 1758 $ hg log -G --print-revset --follow-first e
1759 1759 []
1760 1760 (group
1761 1761 (group
1762 1762 (func
1763 1763 ('symbol', '_followfirst')
1764 1764 ('string', 'e'))))
1765 1765 $ hg log -G --follow-first e --template '{rev} {desc|firstline}\n'
1766 1766 @ 6 merge 5 and 4
1767 1767 |\
1768 1768 o | 5 add another e
1769 1769 | |
1770 1770
1771 1771 Test --copies
1772 1772
1773 1773 $ hg log -G --copies --template "{rev} {desc|firstline} \
1774 1774 > copies: {file_copies_switch}\n"
1775 1775 @ 6 merge 5 and 4 copies:
1776 1776 |\
1777 1777 | o 5 add another e copies:
1778 1778 | |
1779 1779 o | 4 mv dir/b e copies: e (dir/b)
1780 1780 |/
1781 1781 o 3 mv a b; add d copies: b (a)g (f)
1782 1782 |
1783 1783 o 2 mv b dir/b copies: dir/b (b)
1784 1784 |
1785 1785 o 1 copy a b copies: b (a)g (f)
1786 1786 |
1787 1787 o 0 add a copies:
1788 1788
1789 1789 Test "set:..." and parent revision
1790 1790
1791 1791 $ hg up -q 4
1792 1792 $ testlog "set:copied()"
1793 1793 []
1794 1794 (group
1795 1795 (func
1796 1796 ('symbol', '_matchfiles')
1797 1797 (list
1798 1798 (list
1799 1799 ('string', 'r:')
1800 1800 ('string', 'd:relpath'))
1801 1801 ('string', 'p:set:copied()'))))
1802 1802 $ testlog --include "set:copied()"
1803 1803 []
1804 1804 (group
1805 1805 (func
1806 1806 ('symbol', '_matchfiles')
1807 1807 (list
1808 1808 (list
1809 1809 ('string', 'r:')
1810 1810 ('string', 'd:relpath'))
1811 1811 ('string', 'i:set:copied()'))))
1812 1812 $ testlog -r "sort(file('set:copied()'), -rev)"
1813 1813 ["sort(file('set:copied()'), -rev)"]
1814 1814 []
1815 1815
1816 1816 Test --removed
1817 1817
1818 1818 $ testlog --removed
1819 1819 []
1820 1820 []
1821 1821 $ testlog --removed a
1822 1822 []
1823 1823 (group
1824 1824 (func
1825 1825 ('symbol', '_matchfiles')
1826 1826 (list
1827 1827 (list
1828 1828 ('string', 'r:')
1829 1829 ('string', 'd:relpath'))
1830 1830 ('string', 'p:a'))))
1831 1831 $ testlog --removed --follow a
1832 1832 abort: can only follow copies/renames for explicit filenames
1833 1833 abort: can only follow copies/renames for explicit filenames
1834 1834 abort: can only follow copies/renames for explicit filenames
1835 1835
1836 1836 Test --patch and --stat with --follow and --follow-first
1837 1837
1838 1838 $ hg up -q 3
1839 1839 $ hg log -G --git --patch b
1840 1840 o changeset: 1:216d4c92cf98
1841 1841 | user: test
1842 1842 | date: Thu Jan 01 00:00:00 1970 +0000
1843 1843 | summary: copy a b
1844 1844 |
1845 1845 | diff --git a/a b/b
1846 1846 | copy from a
1847 1847 | copy to b
1848 1848 |
1849 1849
1850 1850 $ hg log -G --git --stat b
1851 1851 o changeset: 1:216d4c92cf98
1852 1852 | user: test
1853 1853 | date: Thu Jan 01 00:00:00 1970 +0000
1854 1854 | summary: copy a b
1855 1855 |
1856 1856 | a | 0
1857 1857 | 1 files changed, 0 insertions(+), 0 deletions(-)
1858 1858 |
1859 1859
1860 1860 $ hg log -G --git --patch --follow b
1861 1861 o changeset: 1:216d4c92cf98
1862 1862 | user: test
1863 1863 | date: Thu Jan 01 00:00:00 1970 +0000
1864 1864 | summary: copy a b
1865 1865 |
1866 1866 | diff --git a/a b/b
1867 1867 | copy from a
1868 1868 | copy to b
1869 1869 |
1870 1870 o changeset: 0:f8035bb17114
1871 1871 user: test
1872 1872 date: Thu Jan 01 00:00:00 1970 +0000
1873 1873 summary: add a
1874 1874
1875 1875 diff --git a/a b/a
1876 1876 new file mode 100644
1877 1877 --- /dev/null
1878 1878 +++ b/a
1879 1879 @@ -0,0 +1,1 @@
1880 1880 +a
1881 1881
1882 1882
1883 1883 $ hg log -G --git --stat --follow b
1884 1884 o changeset: 1:216d4c92cf98
1885 1885 | user: test
1886 1886 | date: Thu Jan 01 00:00:00 1970 +0000
1887 1887 | summary: copy a b
1888 1888 |
1889 1889 | a | 0
1890 1890 | 1 files changed, 0 insertions(+), 0 deletions(-)
1891 1891 |
1892 1892 o changeset: 0:f8035bb17114
1893 1893 user: test
1894 1894 date: Thu Jan 01 00:00:00 1970 +0000
1895 1895 summary: add a
1896 1896
1897 1897 a | 1 +
1898 1898 1 files changed, 1 insertions(+), 0 deletions(-)
1899 1899
1900 1900
1901 1901 $ hg up -q 6
1902 1902 $ hg log -G --git --patch --follow-first e
1903 1903 @ changeset: 6:fc281d8ff18d
1904 1904 |\ tag: tip
1905 1905 | | parent: 5:99b31f1c2782
1906 1906 | | parent: 4:17d952250a9d
1907 1907 | | user: test
1908 1908 | | date: Thu Jan 01 00:00:00 1970 +0000
1909 1909 | | summary: merge 5 and 4
1910 1910 | |
1911 1911 | | diff --git a/e b/e
1912 1912 | | --- a/e
1913 1913 | | +++ b/e
1914 1914 | | @@ -1,1 +1,1 @@
1915 1915 | | -ee
1916 1916 | | +merge
1917 1917 | |
1918 1918 o | changeset: 5:99b31f1c2782
1919 1919 | | parent: 3:5918b8d165d1
1920 1920 | | user: test
1921 1921 | | date: Thu Jan 01 00:00:00 1970 +0000
1922 1922 | | summary: add another e
1923 1923 | |
1924 1924 | | diff --git a/e b/e
1925 1925 | | new file mode 100644
1926 1926 | | --- /dev/null
1927 1927 | | +++ b/e
1928 1928 | | @@ -0,0 +1,1 @@
1929 1929 | | +ee
1930 1930 | |
1931 1931
1932 1932 Test old-style --rev
1933 1933
1934 1934 $ hg tag 'foo-bar'
1935 1935 $ testlog -r 'foo-bar'
1936 1936 ['foo-bar']
1937 1937 []
1938 1938
1939 1939 Test --follow and forward --rev
1940 1940
1941 1941 $ hg up -q 6
1942 1942 $ echo g > g
1943 1943 $ hg ci -Am 'add g' g
1944 1944 created new head
1945 1945 $ hg up -q 2
1946 1946 $ hg log -G --template "{rev} {desc|firstline}\n"
1947 1947 o 8 add g
1948 1948 |
1949 1949 | o 7 Added tag foo-bar for changeset fc281d8ff18d
1950 1950 |/
1951 1951 o 6 merge 5 and 4
1952 1952 |\
1953 1953 | o 5 add another e
1954 1954 | |
1955 1955 o | 4 mv dir/b e
1956 1956 |/
1957 1957 o 3 mv a b; add d
1958 1958 |
1959 1959 @ 2 mv b dir/b
1960 1960 |
1961 1961 o 1 copy a b
1962 1962 |
1963 1963 o 0 add a
1964 1964
1965 1965 $ testlog --follow -r6 -r8 -r5 -r7 -r4
1966 1966 ['6', '8', '5', '7', '4']
1967 1967 (group
1968 1968 (func
1969 1969 ('symbol', 'descendants')
1970 1970 ('symbol', '6')))
1971 1971 --- log.nodes * (glob)
1972 1972 +++ glog.nodes * (glob)
1973 1973 @@ -1,3 +1,3 @@
1974 1974 -nodetag 6
1975 1975 nodetag 8
1976 1976 nodetag 7
1977 1977 +nodetag 6
1978 1978
1979 1979 Test --follow-first and forward --rev
1980 1980
1981 1981 $ testlog --follow-first -r6 -r8 -r5 -r7 -r4
1982 1982 ['6', '8', '5', '7', '4']
1983 1983 (group
1984 1984 (func
1985 1985 ('symbol', '_firstdescendants')
1986 1986 ('symbol', '6')))
1987 1987 --- log.nodes * (glob)
1988 1988 +++ glog.nodes * (glob)
1989 1989 @@ -1,3 +1,3 @@
1990 1990 -nodetag 6
1991 1991 nodetag 8
1992 1992 nodetag 7
1993 1993 +nodetag 6
1994 1994
1995 1995 Test --follow and backward --rev
1996 1996
1997 1997 $ testlog --follow -r6 -r5 -r7 -r8 -r4
1998 1998 ['6', '5', '7', '8', '4']
1999 1999 (group
2000 2000 (func
2001 2001 ('symbol', 'ancestors')
2002 2002 ('symbol', '6')))
2003 2003
2004 2004 Test --follow-first and backward --rev
2005 2005
2006 2006 $ testlog --follow-first -r6 -r5 -r7 -r8 -r4
2007 2007 ['6', '5', '7', '8', '4']
2008 2008 (group
2009 2009 (func
2010 2010 ('symbol', '_firstancestors')
2011 2011 ('symbol', '6')))
2012 2012
2013 2013 Test subdir
2014 2014
2015 2015 $ hg up -q 3
2016 2016 $ cd dir
2017 2017 $ testlog .
2018 2018 []
2019 2019 (group
2020 2020 (func
2021 2021 ('symbol', '_matchfiles')
2022 2022 (list
2023 2023 (list
2024 2024 ('string', 'r:')
2025 2025 ('string', 'd:relpath'))
2026 2026 ('string', 'p:.'))))
2027 2027 $ testlog ../b
2028 2028 []
2029 2029 (group
2030 2030 (group
2031 2031 (func
2032 2032 ('symbol', 'filelog')
2033 2033 ('string', '../b'))))
2034 2034 $ testlog -f ../b
2035 2035 []
2036 2036 (group
2037 2037 (group
2038 2038 (func
2039 2039 ('symbol', 'follow')
2040 2040 ('string', 'b'))))
2041 2041 $ cd ..
2042 2042
2043 2043 Test --hidden
2044 2044 (enable obsolete)
2045 2045
2046 2046 $ cat > ${TESTTMP}/obs.py << EOF
2047 2047 > import mercurial.obsolete
2048 2048 > mercurial.obsolete._enabled = True
2049 2049 > EOF
2050 2050 $ echo '[extensions]' >> $HGRCPATH
2051 2051 $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
2052 2052
2053 2053 $ hg debugobsolete `hg id --debug -i -r 8`
2054 2054 $ testlog
2055 2055 []
2056 2056 []
2057 2057 $ testlog --hidden
2058 2058 []
2059 2059 []
2060 2060 $ hg log -G --template '{rev} {desc}\n'
2061 2061 o 7 Added tag foo-bar for changeset fc281d8ff18d
2062 2062 |
2063 2063 o 6 merge 5 and 4
2064 2064 |\
2065 2065 | o 5 add another e
2066 2066 | |
2067 2067 o | 4 mv dir/b e
2068 2068 |/
2069 2069 @ 3 mv a b; add d
2070 2070 |
2071 2071 o 2 mv b dir/b
2072 2072 |
2073 2073 o 1 copy a b
2074 2074 |
2075 2075 o 0 add a
2076 2076
2077 2077
2078 2078 A template without trailing newline should do something sane
2079 2079
2080 2080 $ hg log -G -r ::2 --template '{rev} {desc}'
2081 2081 o 2 mv b dir/b
2082 2082 |
2083 2083 o 1 copy a b
2084 2084 |
2085 2085 o 0 add a
2086 2086
2087 2087
2088 2088 Extra newlines must be preserved
2089 2089
2090 2090 $ hg log -G -r ::2 --template '\n{rev} {desc}\n\n'
2091 2091 o
2092 2092 | 2 mv b dir/b
2093 2093 |
2094 2094 o
2095 2095 | 1 copy a b
2096 2096 |
2097 2097 o
2098 2098 0 add a
2099 2099
2100 2100
2101 2101 The almost-empty template should do something sane too ...
2102 2102
2103 2103 $ hg log -G -r ::2 --template '\n'
2104 2104 o
2105 2105 |
2106 2106 o
2107 2107 |
2108 2108 o
2109 2109
2110 2110
2111 2111 issue3772
2112 2112
2113 2113 $ hg log -G -r :null
2114 o changeset: 0:f8035bb17114
2115 | user: test
2116 | date: Thu Jan 01 00:00:00 1970 +0000
2117 | summary: add a
2118 |
2114 2119 o changeset: -1:000000000000
2115 2120 user:
2116 2121 date: Thu Jan 01 00:00:00 1970 +0000
2117 2122
2118 2123 $ hg log -G -r null:null
2119 2124 o changeset: -1:000000000000
2120 2125 user:
2121 2126 date: Thu Jan 01 00:00:00 1970 +0000
2122 2127
2123 2128
2124 2129 $ cd ..
@@ -1,1369 +1,1375 b''
1 1 Log on empty repository: checking consistency
2 2
3 3 $ hg init empty
4 4 $ cd empty
5 5 $ hg log
6 6 $ hg log -r 1
7 7 abort: unknown revision '1'!
8 8 [255]
9 9 $ hg log -r -1:0
10 10 abort: unknown revision '-1'!
11 11 [255]
12 12 $ hg log -r 'branch(name)'
13 13 abort: unknown revision 'name'!
14 14 [255]
15 15 $ hg log -r null -q
16 16 -1:000000000000
17 17
18 18 The g is crafted to have 2 filelog topological heads in a linear
19 19 changeset graph
20 20
21 21 $ hg init a
22 22 $ cd a
23 23 $ echo a > a
24 24 $ echo f > f
25 25 $ hg ci -Ama -d '1 0'
26 26 adding a
27 27 adding f
28 28
29 29 $ hg cp a b
30 30 $ hg cp f g
31 31 $ hg ci -mb -d '2 0'
32 32
33 33 $ mkdir dir
34 34 $ hg mv b dir
35 35 $ echo g >> g
36 36 $ echo f >> f
37 37 $ hg ci -mc -d '3 0'
38 38
39 39 $ hg mv a b
40 40 $ hg cp -f f g
41 41 $ echo a > d
42 42 $ hg add d
43 43 $ hg ci -md -d '4 0'
44 44
45 45 $ hg mv dir/b e
46 46 $ hg ci -me -d '5 0'
47 47
48 48 $ hg log a
49 49 changeset: 0:9161b9aeaf16
50 50 user: test
51 51 date: Thu Jan 01 00:00:01 1970 +0000
52 52 summary: a
53 53
54 54 log on directory
55 55
56 56 $ hg log dir
57 57 changeset: 4:7e4639b4691b
58 58 tag: tip
59 59 user: test
60 60 date: Thu Jan 01 00:00:05 1970 +0000
61 61 summary: e
62 62
63 63 changeset: 2:f8954cd4dc1f
64 64 user: test
65 65 date: Thu Jan 01 00:00:03 1970 +0000
66 66 summary: c
67 67
68 68 $ hg log somethingthatdoesntexist dir
69 69 changeset: 4:7e4639b4691b
70 70 tag: tip
71 71 user: test
72 72 date: Thu Jan 01 00:00:05 1970 +0000
73 73 summary: e
74 74
75 75 changeset: 2:f8954cd4dc1f
76 76 user: test
77 77 date: Thu Jan 01 00:00:03 1970 +0000
78 78 summary: c
79 79
80 80
81 81 -f, directory
82 82
83 83 $ hg log -f dir
84 84 abort: cannot follow file not in parent revision: "dir"
85 85 [255]
86 86
87 87 -f, a wrong style
88 88
89 89 $ hg log -f -l1 --style something
90 90 abort: style 'something' not found
91 91 (available styles: bisect, changelog, compact, default, phases, xml)
92 92 [255]
93 93
94 94 -f, phases style
95 95
96 96
97 97 $ hg log -f -l1 --style phases
98 98 changeset: 4:7e4639b4691b
99 99 tag: tip
100 100 phase: draft
101 101 user: test
102 102 date: Thu Jan 01 00:00:05 1970 +0000
103 103 summary: e
104 104
105 105
106 106 -f, but no args
107 107
108 108 $ hg log -f
109 109 changeset: 4:7e4639b4691b
110 110 tag: tip
111 111 user: test
112 112 date: Thu Jan 01 00:00:05 1970 +0000
113 113 summary: e
114 114
115 115 changeset: 3:2ca5ba701980
116 116 user: test
117 117 date: Thu Jan 01 00:00:04 1970 +0000
118 118 summary: d
119 119
120 120 changeset: 2:f8954cd4dc1f
121 121 user: test
122 122 date: Thu Jan 01 00:00:03 1970 +0000
123 123 summary: c
124 124
125 125 changeset: 1:d89b0a12d229
126 126 user: test
127 127 date: Thu Jan 01 00:00:02 1970 +0000
128 128 summary: b
129 129
130 130 changeset: 0:9161b9aeaf16
131 131 user: test
132 132 date: Thu Jan 01 00:00:01 1970 +0000
133 133 summary: a
134 134
135 135
136 136 one rename
137 137
138 138 $ hg up -q 2
139 139 $ hg log -vf a
140 140 changeset: 0:9161b9aeaf16
141 141 user: test
142 142 date: Thu Jan 01 00:00:01 1970 +0000
143 143 files: a f
144 144 description:
145 145 a
146 146
147 147
148 148
149 149 many renames
150 150
151 151 $ hg up -q tip
152 152 $ hg log -vf e
153 153 changeset: 4:7e4639b4691b
154 154 tag: tip
155 155 user: test
156 156 date: Thu Jan 01 00:00:05 1970 +0000
157 157 files: dir/b e
158 158 description:
159 159 e
160 160
161 161
162 162 changeset: 2:f8954cd4dc1f
163 163 user: test
164 164 date: Thu Jan 01 00:00:03 1970 +0000
165 165 files: b dir/b f g
166 166 description:
167 167 c
168 168
169 169
170 170 changeset: 1:d89b0a12d229
171 171 user: test
172 172 date: Thu Jan 01 00:00:02 1970 +0000
173 173 files: b g
174 174 description:
175 175 b
176 176
177 177
178 178 changeset: 0:9161b9aeaf16
179 179 user: test
180 180 date: Thu Jan 01 00:00:01 1970 +0000
181 181 files: a f
182 182 description:
183 183 a
184 184
185 185
186 186
187 187
188 188 log -pf dir/b
189 189
190 190 $ hg up -q 3
191 191 $ hg log -pf dir/b
192 192 changeset: 2:f8954cd4dc1f
193 193 user: test
194 194 date: Thu Jan 01 00:00:03 1970 +0000
195 195 summary: c
196 196
197 197 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
198 198 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
199 199 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
200 200 @@ -0,0 +1,1 @@
201 201 +a
202 202
203 203 changeset: 1:d89b0a12d229
204 204 user: test
205 205 date: Thu Jan 01 00:00:02 1970 +0000
206 206 summary: b
207 207
208 208 diff -r 9161b9aeaf16 -r d89b0a12d229 b
209 209 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
210 210 +++ b/b Thu Jan 01 00:00:02 1970 +0000
211 211 @@ -0,0 +1,1 @@
212 212 +a
213 213
214 214 changeset: 0:9161b9aeaf16
215 215 user: test
216 216 date: Thu Jan 01 00:00:01 1970 +0000
217 217 summary: a
218 218
219 219 diff -r 000000000000 -r 9161b9aeaf16 a
220 220 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
221 221 +++ b/a Thu Jan 01 00:00:01 1970 +0000
222 222 @@ -0,0 +1,1 @@
223 223 +a
224 224
225 225
226 226 log -vf dir/b
227 227
228 228 $ hg log -vf dir/b
229 229 changeset: 2:f8954cd4dc1f
230 230 user: test
231 231 date: Thu Jan 01 00:00:03 1970 +0000
232 232 files: b dir/b f g
233 233 description:
234 234 c
235 235
236 236
237 237 changeset: 1:d89b0a12d229
238 238 user: test
239 239 date: Thu Jan 01 00:00:02 1970 +0000
240 240 files: b g
241 241 description:
242 242 b
243 243
244 244
245 245 changeset: 0:9161b9aeaf16
246 246 user: test
247 247 date: Thu Jan 01 00:00:01 1970 +0000
248 248 files: a f
249 249 description:
250 250 a
251 251
252 252
253 253
254 254
255 255 -f and multiple filelog heads
256 256
257 257 $ hg up -q 2
258 258 $ hg log -f g --template '{rev}\n'
259 259 2
260 260 1
261 261 0
262 262 $ hg up -q tip
263 263 $ hg log -f g --template '{rev}\n'
264 264 3
265 265 2
266 266 0
267 267
268 268
269 269 log copies with --copies
270 270
271 271 $ hg log -vC --template '{rev} {file_copies}\n'
272 272 4 e (dir/b)
273 273 3 b (a)g (f)
274 274 2 dir/b (b)
275 275 1 b (a)g (f)
276 276 0
277 277
278 278 log copies switch without --copies, with old filecopy template
279 279
280 280 $ hg log -v --template '{rev} {file_copies_switch%filecopy}\n'
281 281 4
282 282 3
283 283 2
284 284 1
285 285 0
286 286
287 287 log copies switch with --copies
288 288
289 289 $ hg log -vC --template '{rev} {file_copies_switch}\n'
290 290 4 e (dir/b)
291 291 3 b (a)g (f)
292 292 2 dir/b (b)
293 293 1 b (a)g (f)
294 294 0
295 295
296 296
297 297 log copies with hardcoded style and with --style=default
298 298
299 299 $ hg log -vC -r4
300 300 changeset: 4:7e4639b4691b
301 301 tag: tip
302 302 user: test
303 303 date: Thu Jan 01 00:00:05 1970 +0000
304 304 files: dir/b e
305 305 copies: e (dir/b)
306 306 description:
307 307 e
308 308
309 309
310 310 $ hg log -vC -r4 --style=default
311 311 changeset: 4:7e4639b4691b
312 312 tag: tip
313 313 user: test
314 314 date: Thu Jan 01 00:00:05 1970 +0000
315 315 files: dir/b e
316 316 copies: e (dir/b)
317 317 description:
318 318 e
319 319
320 320
321 321
322 322
323 323 log copies, non-linear manifest
324 324
325 325 $ hg up -C 3
326 326 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
327 327 $ hg mv dir/b e
328 328 $ echo foo > foo
329 329 $ hg ci -Ame2 -d '6 0'
330 330 adding foo
331 331 created new head
332 332 $ hg log -v --template '{rev} {file_copies}\n' -r 5
333 333 5 e (dir/b)
334 334
335 335
336 336 log copies, execute bit set
337 337
338 338 #if execbit
339 339 $ chmod +x e
340 340 $ hg ci -me3 -d '7 0'
341 341 $ hg log -v --template '{rev} {file_copies}\n' -r 6
342 342 6
343 343 #endif
344 344
345 345
346 346 log -p d
347 347
348 348 $ hg log -pv d
349 349 changeset: 3:2ca5ba701980
350 350 user: test
351 351 date: Thu Jan 01 00:00:04 1970 +0000
352 352 files: a b d g
353 353 description:
354 354 d
355 355
356 356
357 357 diff -r f8954cd4dc1f -r 2ca5ba701980 d
358 358 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
359 359 +++ b/d Thu Jan 01 00:00:04 1970 +0000
360 360 @@ -0,0 +1,1 @@
361 361 +a
362 362
363 363
364 364
365 365 log --removed file
366 366
367 367 $ hg log --removed -v a
368 368 changeset: 3:2ca5ba701980
369 369 user: test
370 370 date: Thu Jan 01 00:00:04 1970 +0000
371 371 files: a b d g
372 372 description:
373 373 d
374 374
375 375
376 376 changeset: 0:9161b9aeaf16
377 377 user: test
378 378 date: Thu Jan 01 00:00:01 1970 +0000
379 379 files: a f
380 380 description:
381 381 a
382 382
383 383
384 384
385 385 log --removed revrange file
386 386
387 387 $ hg log --removed -v -r0:2 a
388 388 changeset: 0:9161b9aeaf16
389 389 user: test
390 390 date: Thu Jan 01 00:00:01 1970 +0000
391 391 files: a f
392 392 description:
393 393 a
394 394
395 395
396 396 $ cd ..
397 397
398 398 log --follow tests
399 399
400 400 $ hg init follow
401 401 $ cd follow
402 402
403 403 $ echo base > base
404 404 $ hg ci -Ambase -d '1 0'
405 405 adding base
406 406
407 407 $ echo r1 >> base
408 408 $ hg ci -Amr1 -d '1 0'
409 409 $ echo r2 >> base
410 410 $ hg ci -Amr2 -d '1 0'
411 411
412 412 $ hg up -C 1
413 413 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
414 414 $ echo b1 > b1
415 415 $ hg ci -Amb1 -d '1 0'
416 416 adding b1
417 417 created new head
418 418
419 419
420 420 log -f
421 421
422 422 $ hg log -f
423 423 changeset: 3:e62f78d544b4
424 424 tag: tip
425 425 parent: 1:3d5bf5654eda
426 426 user: test
427 427 date: Thu Jan 01 00:00:01 1970 +0000
428 428 summary: b1
429 429
430 430 changeset: 1:3d5bf5654eda
431 431 user: test
432 432 date: Thu Jan 01 00:00:01 1970 +0000
433 433 summary: r1
434 434
435 435 changeset: 0:67e992f2c4f3
436 436 user: test
437 437 date: Thu Jan 01 00:00:01 1970 +0000
438 438 summary: base
439 439
440 440
441 441
442 442 log -f -r 1:tip
443 443
444 444 $ hg up -C 0
445 445 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
446 446 $ echo b2 > b2
447 447 $ hg ci -Amb2 -d '1 0'
448 448 adding b2
449 449 created new head
450 450 $ hg log -f -r 1:tip
451 451 changeset: 1:3d5bf5654eda
452 452 user: test
453 453 date: Thu Jan 01 00:00:01 1970 +0000
454 454 summary: r1
455 455
456 456 changeset: 2:60c670bf5b30
457 457 user: test
458 458 date: Thu Jan 01 00:00:01 1970 +0000
459 459 summary: r2
460 460
461 461 changeset: 3:e62f78d544b4
462 462 parent: 1:3d5bf5654eda
463 463 user: test
464 464 date: Thu Jan 01 00:00:01 1970 +0000
465 465 summary: b1
466 466
467 467
468 468
469 469 log -r . with two parents
470 470
471 471 $ hg up -C 3
472 472 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
473 473 $ hg merge tip
474 474 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
475 475 (branch merge, don't forget to commit)
476 476 $ hg log -r .
477 477 changeset: 3:e62f78d544b4
478 478 parent: 1:3d5bf5654eda
479 479 user: test
480 480 date: Thu Jan 01 00:00:01 1970 +0000
481 481 summary: b1
482 482
483 483
484 484
485 485 log -r . with one parent
486 486
487 487 $ hg ci -mm12 -d '1 0'
488 488 $ hg log -r .
489 489 changeset: 5:302e9dd6890d
490 490 tag: tip
491 491 parent: 3:e62f78d544b4
492 492 parent: 4:ddb82e70d1a1
493 493 user: test
494 494 date: Thu Jan 01 00:00:01 1970 +0000
495 495 summary: m12
496 496
497 497
498 498 $ echo postm >> b1
499 499 $ hg ci -Amb1.1 -d'1 0'
500 500
501 501
502 502 log --follow-first
503 503
504 504 $ hg log --follow-first
505 505 changeset: 6:2404bbcab562
506 506 tag: tip
507 507 user: test
508 508 date: Thu Jan 01 00:00:01 1970 +0000
509 509 summary: b1.1
510 510
511 511 changeset: 5:302e9dd6890d
512 512 parent: 3:e62f78d544b4
513 513 parent: 4:ddb82e70d1a1
514 514 user: test
515 515 date: Thu Jan 01 00:00:01 1970 +0000
516 516 summary: m12
517 517
518 518 changeset: 3:e62f78d544b4
519 519 parent: 1:3d5bf5654eda
520 520 user: test
521 521 date: Thu Jan 01 00:00:01 1970 +0000
522 522 summary: b1
523 523
524 524 changeset: 1:3d5bf5654eda
525 525 user: test
526 526 date: Thu Jan 01 00:00:01 1970 +0000
527 527 summary: r1
528 528
529 529 changeset: 0:67e992f2c4f3
530 530 user: test
531 531 date: Thu Jan 01 00:00:01 1970 +0000
532 532 summary: base
533 533
534 534
535 535
536 536 log -P 2
537 537
538 538 $ hg log -P 2
539 539 changeset: 6:2404bbcab562
540 540 tag: tip
541 541 user: test
542 542 date: Thu Jan 01 00:00:01 1970 +0000
543 543 summary: b1.1
544 544
545 545 changeset: 5:302e9dd6890d
546 546 parent: 3:e62f78d544b4
547 547 parent: 4:ddb82e70d1a1
548 548 user: test
549 549 date: Thu Jan 01 00:00:01 1970 +0000
550 550 summary: m12
551 551
552 552 changeset: 4:ddb82e70d1a1
553 553 parent: 0:67e992f2c4f3
554 554 user: test
555 555 date: Thu Jan 01 00:00:01 1970 +0000
556 556 summary: b2
557 557
558 558 changeset: 3:e62f78d544b4
559 559 parent: 1:3d5bf5654eda
560 560 user: test
561 561 date: Thu Jan 01 00:00:01 1970 +0000
562 562 summary: b1
563 563
564 564
565 565
566 566 log -r tip -p --git
567 567
568 568 $ hg log -r tip -p --git
569 569 changeset: 6:2404bbcab562
570 570 tag: tip
571 571 user: test
572 572 date: Thu Jan 01 00:00:01 1970 +0000
573 573 summary: b1.1
574 574
575 575 diff --git a/b1 b/b1
576 576 --- a/b1
577 577 +++ b/b1
578 578 @@ -1,1 +1,2 @@
579 579 b1
580 580 +postm
581 581
582 582
583 583
584 584 log -r ""
585 585
586 586 $ hg log -r ''
587 587 hg: parse error: empty query
588 588 [255]
589 589
590 590 log -r <some unknown node id>
591 591
592 592 $ hg log -r 1000000000000000000000000000000000000000
593 593 abort: unknown revision '1000000000000000000000000000000000000000'!
594 594 [255]
595 595
596 596 log -k r1
597 597
598 598 $ hg log -k r1
599 599 changeset: 1:3d5bf5654eda
600 600 user: test
601 601 date: Thu Jan 01 00:00:01 1970 +0000
602 602 summary: r1
603 603
604 604 log -p -l2 --color=always
605 605
606 606 $ hg --config extensions.color= --config color.mode=ansi \
607 607 > log -p -l2 --color=always
608 608 \x1b[0;33mchangeset: 6:2404bbcab562\x1b[0m (esc)
609 609 tag: tip
610 610 user: test
611 611 date: Thu Jan 01 00:00:01 1970 +0000
612 612 summary: b1.1
613 613
614 614 \x1b[0;1mdiff -r 302e9dd6890d -r 2404bbcab562 b1\x1b[0m (esc)
615 615 \x1b[0;31;1m--- a/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
616 616 \x1b[0;32;1m+++ b/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
617 617 \x1b[0;35m@@ -1,1 +1,2 @@\x1b[0m (esc)
618 618 b1
619 619 \x1b[0;32m+postm\x1b[0m (esc)
620 620
621 621 \x1b[0;33mchangeset: 5:302e9dd6890d\x1b[0m (esc)
622 622 parent: 3:e62f78d544b4
623 623 parent: 4:ddb82e70d1a1
624 624 user: test
625 625 date: Thu Jan 01 00:00:01 1970 +0000
626 626 summary: m12
627 627
628 628 \x1b[0;1mdiff -r e62f78d544b4 -r 302e9dd6890d b2\x1b[0m (esc)
629 629 \x1b[0;31;1m--- /dev/null Thu Jan 01 00:00:00 1970 +0000\x1b[0m (esc)
630 630 \x1b[0;32;1m+++ b/b2 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
631 631 \x1b[0;35m@@ -0,0 +1,1 @@\x1b[0m (esc)
632 632 \x1b[0;32m+b2\x1b[0m (esc)
633 633
634 634
635 635
636 636 log -r tip --stat
637 637
638 638 $ hg log -r tip --stat
639 639 changeset: 6:2404bbcab562
640 640 tag: tip
641 641 user: test
642 642 date: Thu Jan 01 00:00:01 1970 +0000
643 643 summary: b1.1
644 644
645 645 b1 | 1 +
646 646 1 files changed, 1 insertions(+), 0 deletions(-)
647 647
648 648
649 649 $ cd ..
650 650
651 651
652 652 User
653 653
654 654 $ hg init usertest
655 655 $ cd usertest
656 656
657 657 $ echo a > a
658 658 $ hg ci -A -m "a" -u "User One <user1@example.org>"
659 659 adding a
660 660 $ echo b > b
661 661 $ hg ci -A -m "b" -u "User Two <user2@example.org>"
662 662 adding b
663 663
664 664 $ hg log -u "User One <user1@example.org>"
665 665 changeset: 0:29a4c94f1924
666 666 user: User One <user1@example.org>
667 667 date: Thu Jan 01 00:00:00 1970 +0000
668 668 summary: a
669 669
670 670 $ hg log -u "user1" -u "user2"
671 671 changeset: 1:e834b5e69c0e
672 672 tag: tip
673 673 user: User Two <user2@example.org>
674 674 date: Thu Jan 01 00:00:00 1970 +0000
675 675 summary: b
676 676
677 677 changeset: 0:29a4c94f1924
678 678 user: User One <user1@example.org>
679 679 date: Thu Jan 01 00:00:00 1970 +0000
680 680 summary: a
681 681
682 682 $ hg log -u "user3"
683 683
684 684 $ cd ..
685 685
686 686 $ hg init branches
687 687 $ cd branches
688 688
689 689 $ echo a > a
690 690 $ hg ci -A -m "commit on default"
691 691 adding a
692 692 $ hg branch test
693 693 marked working directory as branch test
694 694 (branches are permanent and global, did you want a bookmark?)
695 695 $ echo b > b
696 696 $ hg ci -A -m "commit on test"
697 697 adding b
698 698
699 699 $ hg up default
700 700 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
701 701 $ echo c > c
702 702 $ hg ci -A -m "commit on default"
703 703 adding c
704 704 $ hg up test
705 705 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
706 706 $ echo c > c
707 707 $ hg ci -A -m "commit on test"
708 708 adding c
709 709
710 710
711 711 log -b default
712 712
713 713 $ hg log -b default
714 714 changeset: 2:c3a4f03cc9a7
715 715 parent: 0:24427303d56f
716 716 user: test
717 717 date: Thu Jan 01 00:00:00 1970 +0000
718 718 summary: commit on default
719 719
720 720 changeset: 0:24427303d56f
721 721 user: test
722 722 date: Thu Jan 01 00:00:00 1970 +0000
723 723 summary: commit on default
724 724
725 725
726 726
727 727 log -b test
728 728
729 729 $ hg log -b test
730 730 changeset: 3:f5d8de11c2e2
731 731 branch: test
732 732 tag: tip
733 733 parent: 1:d32277701ccb
734 734 user: test
735 735 date: Thu Jan 01 00:00:00 1970 +0000
736 736 summary: commit on test
737 737
738 738 changeset: 1:d32277701ccb
739 739 branch: test
740 740 user: test
741 741 date: Thu Jan 01 00:00:00 1970 +0000
742 742 summary: commit on test
743 743
744 744
745 745
746 746 log -b dummy
747 747
748 748 $ hg log -b dummy
749 749 abort: unknown revision 'dummy'!
750 750 [255]
751 751
752 752
753 753 log -b .
754 754
755 755 $ hg log -b .
756 756 changeset: 3:f5d8de11c2e2
757 757 branch: test
758 758 tag: tip
759 759 parent: 1:d32277701ccb
760 760 user: test
761 761 date: Thu Jan 01 00:00:00 1970 +0000
762 762 summary: commit on test
763 763
764 764 changeset: 1:d32277701ccb
765 765 branch: test
766 766 user: test
767 767 date: Thu Jan 01 00:00:00 1970 +0000
768 768 summary: commit on test
769 769
770 770
771 771
772 772 log -b default -b test
773 773
774 774 $ hg log -b default -b test
775 775 changeset: 3:f5d8de11c2e2
776 776 branch: test
777 777 tag: tip
778 778 parent: 1:d32277701ccb
779 779 user: test
780 780 date: Thu Jan 01 00:00:00 1970 +0000
781 781 summary: commit on test
782 782
783 783 changeset: 2:c3a4f03cc9a7
784 784 parent: 0:24427303d56f
785 785 user: test
786 786 date: Thu Jan 01 00:00:00 1970 +0000
787 787 summary: commit on default
788 788
789 789 changeset: 1:d32277701ccb
790 790 branch: test
791 791 user: test
792 792 date: Thu Jan 01 00:00:00 1970 +0000
793 793 summary: commit on test
794 794
795 795 changeset: 0:24427303d56f
796 796 user: test
797 797 date: Thu Jan 01 00:00:00 1970 +0000
798 798 summary: commit on default
799 799
800 800
801 801
802 802 log -b default -b .
803 803
804 804 $ hg log -b default -b .
805 805 changeset: 3:f5d8de11c2e2
806 806 branch: test
807 807 tag: tip
808 808 parent: 1:d32277701ccb
809 809 user: test
810 810 date: Thu Jan 01 00:00:00 1970 +0000
811 811 summary: commit on test
812 812
813 813 changeset: 2:c3a4f03cc9a7
814 814 parent: 0:24427303d56f
815 815 user: test
816 816 date: Thu Jan 01 00:00:00 1970 +0000
817 817 summary: commit on default
818 818
819 819 changeset: 1:d32277701ccb
820 820 branch: test
821 821 user: test
822 822 date: Thu Jan 01 00:00:00 1970 +0000
823 823 summary: commit on test
824 824
825 825 changeset: 0:24427303d56f
826 826 user: test
827 827 date: Thu Jan 01 00:00:00 1970 +0000
828 828 summary: commit on default
829 829
830 830
831 831
832 832 log -b . -b test
833 833
834 834 $ hg log -b . -b test
835 835 changeset: 3:f5d8de11c2e2
836 836 branch: test
837 837 tag: tip
838 838 parent: 1:d32277701ccb
839 839 user: test
840 840 date: Thu Jan 01 00:00:00 1970 +0000
841 841 summary: commit on test
842 842
843 843 changeset: 1:d32277701ccb
844 844 branch: test
845 845 user: test
846 846 date: Thu Jan 01 00:00:00 1970 +0000
847 847 summary: commit on test
848 848
849 849
850 850
851 851 log -b 2
852 852
853 853 $ hg log -b 2
854 854 changeset: 2:c3a4f03cc9a7
855 855 parent: 0:24427303d56f
856 856 user: test
857 857 date: Thu Jan 01 00:00:00 1970 +0000
858 858 summary: commit on default
859 859
860 860 changeset: 0:24427303d56f
861 861 user: test
862 862 date: Thu Jan 01 00:00:00 1970 +0000
863 863 summary: commit on default
864 864
865 865
866 866
867 867 log -p --cwd dir (in subdir)
868 868
869 869 $ mkdir dir
870 870 $ hg log -p --cwd dir
871 871 changeset: 3:f5d8de11c2e2
872 872 branch: test
873 873 tag: tip
874 874 parent: 1:d32277701ccb
875 875 user: test
876 876 date: Thu Jan 01 00:00:00 1970 +0000
877 877 summary: commit on test
878 878
879 879 diff -r d32277701ccb -r f5d8de11c2e2 c
880 880 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
881 881 +++ b/c Thu Jan 01 00:00:00 1970 +0000
882 882 @@ -0,0 +1,1 @@
883 883 +c
884 884
885 885 changeset: 2:c3a4f03cc9a7
886 886 parent: 0:24427303d56f
887 887 user: test
888 888 date: Thu Jan 01 00:00:00 1970 +0000
889 889 summary: commit on default
890 890
891 891 diff -r 24427303d56f -r c3a4f03cc9a7 c
892 892 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
893 893 +++ b/c Thu Jan 01 00:00:00 1970 +0000
894 894 @@ -0,0 +1,1 @@
895 895 +c
896 896
897 897 changeset: 1:d32277701ccb
898 898 branch: test
899 899 user: test
900 900 date: Thu Jan 01 00:00:00 1970 +0000
901 901 summary: commit on test
902 902
903 903 diff -r 24427303d56f -r d32277701ccb b
904 904 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
905 905 +++ b/b Thu Jan 01 00:00:00 1970 +0000
906 906 @@ -0,0 +1,1 @@
907 907 +b
908 908
909 909 changeset: 0:24427303d56f
910 910 user: test
911 911 date: Thu Jan 01 00:00:00 1970 +0000
912 912 summary: commit on default
913 913
914 914 diff -r 000000000000 -r 24427303d56f a
915 915 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
916 916 +++ b/a Thu Jan 01 00:00:00 1970 +0000
917 917 @@ -0,0 +1,1 @@
918 918 +a
919 919
920 920
921 921
922 922 log -p -R repo
923 923
924 924 $ cd dir
925 925 $ hg log -p -R .. ../a
926 926 changeset: 0:24427303d56f
927 927 user: test
928 928 date: Thu Jan 01 00:00:00 1970 +0000
929 929 summary: commit on default
930 930
931 931 diff -r 000000000000 -r 24427303d56f a
932 932 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
933 933 +++ b/a Thu Jan 01 00:00:00 1970 +0000
934 934 @@ -0,0 +1,1 @@
935 935 +a
936 936
937 937
938 938 $ cd ../..
939 939
940 940 $ hg init follow2
941 941 $ cd follow2
942 942
943 943 # Build the following history:
944 944 # tip - o - x - o - x - x
945 945 # \ /
946 946 # o - o - o - x
947 947 # \ /
948 948 # o
949 949 #
950 950 # Where "o" is a revision containing "foo" and
951 951 # "x" is a revision without "foo"
952 952
953 953 $ touch init
954 954 $ hg ci -A -m "init, unrelated"
955 955 adding init
956 956 $ echo 'foo' > init
957 957 $ hg ci -m "change, unrelated"
958 958 $ echo 'foo' > foo
959 959 $ hg ci -A -m "add unrelated old foo"
960 960 adding foo
961 961 $ hg rm foo
962 962 $ hg ci -m "delete foo, unrelated"
963 963 $ echo 'related' > foo
964 964 $ hg ci -A -m "add foo, related"
965 965 adding foo
966 966
967 967 $ hg up 0
968 968 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
969 969 $ touch branch
970 970 $ hg ci -A -m "first branch, unrelated"
971 971 adding branch
972 972 created new head
973 973 $ touch foo
974 974 $ hg ci -A -m "create foo, related"
975 975 adding foo
976 976 $ echo 'change' > foo
977 977 $ hg ci -m "change foo, related"
978 978
979 979 $ hg up 6
980 980 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
981 981 $ echo 'change foo in branch' > foo
982 982 $ hg ci -m "change foo in branch, related"
983 983 created new head
984 984 $ hg merge 7
985 985 merging foo
986 986 warning: conflicts during merge.
987 987 merging foo incomplete! (edit conflicts, then use 'hg resolve --mark')
988 988 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
989 989 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
990 990 [1]
991 991 $ echo 'merge 1' > foo
992 992 $ hg resolve -m foo
993 993 $ hg ci -m "First merge, related"
994 994
995 995 $ hg merge 4
996 996 merging foo
997 997 warning: conflicts during merge.
998 998 merging foo incomplete! (edit conflicts, then use 'hg resolve --mark')
999 999 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
1000 1000 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
1001 1001 [1]
1002 1002 $ echo 'merge 2' > foo
1003 1003 $ hg resolve -m foo
1004 1004 $ hg ci -m "Last merge, related"
1005 1005
1006 1006 $ hg log --graph
1007 1007 @ changeset: 10:4dae8563d2c5
1008 1008 |\ tag: tip
1009 1009 | | parent: 9:7b35701b003e
1010 1010 | | parent: 4:88176d361b69
1011 1011 | | user: test
1012 1012 | | date: Thu Jan 01 00:00:00 1970 +0000
1013 1013 | | summary: Last merge, related
1014 1014 | |
1015 1015 | o changeset: 9:7b35701b003e
1016 1016 | |\ parent: 8:e5416ad8a855
1017 1017 | | | parent: 7:87fe3144dcfa
1018 1018 | | | user: test
1019 1019 | | | date: Thu Jan 01 00:00:00 1970 +0000
1020 1020 | | | summary: First merge, related
1021 1021 | | |
1022 1022 | | o changeset: 8:e5416ad8a855
1023 1023 | | | parent: 6:dc6c325fe5ee
1024 1024 | | | user: test
1025 1025 | | | date: Thu Jan 01 00:00:00 1970 +0000
1026 1026 | | | summary: change foo in branch, related
1027 1027 | | |
1028 1028 | o | changeset: 7:87fe3144dcfa
1029 1029 | |/ user: test
1030 1030 | | date: Thu Jan 01 00:00:00 1970 +0000
1031 1031 | | summary: change foo, related
1032 1032 | |
1033 1033 | o changeset: 6:dc6c325fe5ee
1034 1034 | | user: test
1035 1035 | | date: Thu Jan 01 00:00:00 1970 +0000
1036 1036 | | summary: create foo, related
1037 1037 | |
1038 1038 | o changeset: 5:73db34516eb9
1039 1039 | | parent: 0:e87515fd044a
1040 1040 | | user: test
1041 1041 | | date: Thu Jan 01 00:00:00 1970 +0000
1042 1042 | | summary: first branch, unrelated
1043 1043 | |
1044 1044 o | changeset: 4:88176d361b69
1045 1045 | | user: test
1046 1046 | | date: Thu Jan 01 00:00:00 1970 +0000
1047 1047 | | summary: add foo, related
1048 1048 | |
1049 1049 o | changeset: 3:dd78ae4afb56
1050 1050 | | user: test
1051 1051 | | date: Thu Jan 01 00:00:00 1970 +0000
1052 1052 | | summary: delete foo, unrelated
1053 1053 | |
1054 1054 o | changeset: 2:c4c64aedf0f7
1055 1055 | | user: test
1056 1056 | | date: Thu Jan 01 00:00:00 1970 +0000
1057 1057 | | summary: add unrelated old foo
1058 1058 | |
1059 1059 o | changeset: 1:e5faa7440653
1060 1060 |/ user: test
1061 1061 | date: Thu Jan 01 00:00:00 1970 +0000
1062 1062 | summary: change, unrelated
1063 1063 |
1064 1064 o changeset: 0:e87515fd044a
1065 1065 user: test
1066 1066 date: Thu Jan 01 00:00:00 1970 +0000
1067 1067 summary: init, unrelated
1068 1068
1069 1069
1070 1070 $ hg --traceback log -f foo
1071 1071 changeset: 10:4dae8563d2c5
1072 1072 tag: tip
1073 1073 parent: 9:7b35701b003e
1074 1074 parent: 4:88176d361b69
1075 1075 user: test
1076 1076 date: Thu Jan 01 00:00:00 1970 +0000
1077 1077 summary: Last merge, related
1078 1078
1079 1079 changeset: 9:7b35701b003e
1080 1080 parent: 8:e5416ad8a855
1081 1081 parent: 7:87fe3144dcfa
1082 1082 user: test
1083 1083 date: Thu Jan 01 00:00:00 1970 +0000
1084 1084 summary: First merge, related
1085 1085
1086 1086 changeset: 8:e5416ad8a855
1087 1087 parent: 6:dc6c325fe5ee
1088 1088 user: test
1089 1089 date: Thu Jan 01 00:00:00 1970 +0000
1090 1090 summary: change foo in branch, related
1091 1091
1092 1092 changeset: 7:87fe3144dcfa
1093 1093 user: test
1094 1094 date: Thu Jan 01 00:00:00 1970 +0000
1095 1095 summary: change foo, related
1096 1096
1097 1097 changeset: 6:dc6c325fe5ee
1098 1098 user: test
1099 1099 date: Thu Jan 01 00:00:00 1970 +0000
1100 1100 summary: create foo, related
1101 1101
1102 1102 changeset: 4:88176d361b69
1103 1103 user: test
1104 1104 date: Thu Jan 01 00:00:00 1970 +0000
1105 1105 summary: add foo, related
1106 1106
1107 1107
1108 1108 Also check when maxrev < lastrevfilelog
1109 1109
1110 1110 $ hg --traceback log -f -r4 foo
1111 1111 changeset: 4:88176d361b69
1112 1112 user: test
1113 1113 date: Thu Jan 01 00:00:00 1970 +0000
1114 1114 summary: add foo, related
1115 1115
1116 1116 $ cd ..
1117 1117
1118 1118 Issue2383: hg log showing _less_ differences than hg diff
1119 1119
1120 1120 $ hg init issue2383
1121 1121 $ cd issue2383
1122 1122
1123 1123 Create a test repo:
1124 1124
1125 1125 $ echo a > a
1126 1126 $ hg ci -Am0
1127 1127 adding a
1128 1128 $ echo b > b
1129 1129 $ hg ci -Am1
1130 1130 adding b
1131 1131 $ hg co 0
1132 1132 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1133 1133 $ echo b > a
1134 1134 $ hg ci -m2
1135 1135 created new head
1136 1136
1137 1137 Merge:
1138 1138
1139 1139 $ hg merge
1140 1140 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1141 1141 (branch merge, don't forget to commit)
1142 1142
1143 1143 Make sure there's a file listed in the merge to trigger the bug:
1144 1144
1145 1145 $ echo c > a
1146 1146 $ hg ci -m3
1147 1147
1148 1148 Two files shown here in diff:
1149 1149
1150 1150 $ hg diff --rev 2:3
1151 1151 diff -r b09be438c43a -r 8e07aafe1edc a
1152 1152 --- a/a Thu Jan 01 00:00:00 1970 +0000
1153 1153 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1154 1154 @@ -1,1 +1,1 @@
1155 1155 -b
1156 1156 +c
1157 1157 diff -r b09be438c43a -r 8e07aafe1edc b
1158 1158 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1159 1159 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1160 1160 @@ -0,0 +1,1 @@
1161 1161 +b
1162 1162
1163 1163 Diff here should be the same:
1164 1164
1165 1165 $ hg log -vpr 3
1166 1166 changeset: 3:8e07aafe1edc
1167 1167 tag: tip
1168 1168 parent: 2:b09be438c43a
1169 1169 parent: 1:925d80f479bb
1170 1170 user: test
1171 1171 date: Thu Jan 01 00:00:00 1970 +0000
1172 1172 files: a
1173 1173 description:
1174 1174 3
1175 1175
1176 1176
1177 1177 diff -r b09be438c43a -r 8e07aafe1edc a
1178 1178 --- a/a Thu Jan 01 00:00:00 1970 +0000
1179 1179 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1180 1180 @@ -1,1 +1,1 @@
1181 1181 -b
1182 1182 +c
1183 1183 diff -r b09be438c43a -r 8e07aafe1edc b
1184 1184 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1185 1185 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1186 1186 @@ -0,0 +1,1 @@
1187 1187 +b
1188 1188
1189 1189 $ cd ..
1190 1190
1191 1191 'hg log -r rev fn' when last(filelog(fn)) != rev
1192 1192
1193 1193 $ hg init simplelog
1194 1194 $ cd simplelog
1195 1195 $ echo f > a
1196 1196 $ hg ci -Am'a' -d '0 0'
1197 1197 adding a
1198 1198 $ echo f >> a
1199 1199 $ hg ci -Am'a bis' -d '1 0'
1200 1200
1201 1201 $ hg log -r0 a
1202 1202 changeset: 0:9f758d63dcde
1203 1203 user: test
1204 1204 date: Thu Jan 01 00:00:00 1970 +0000
1205 1205 summary: a
1206 1206
1207 1207 enable obsolete to test hidden feature
1208 1208
1209 1209 $ cat > ${TESTTMP}/obs.py << EOF
1210 1210 > import mercurial.obsolete
1211 1211 > mercurial.obsolete._enabled = True
1212 1212 > EOF
1213 1213 $ echo '[extensions]' >> $HGRCPATH
1214 1214 $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
1215 1215
1216 1216 $ hg log --template='{rev}:{node}\n'
1217 1217 1:a765632148dc55d38c35c4f247c618701886cb2f
1218 1218 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1219 1219 $ hg debugobsolete a765632148dc55d38c35c4f247c618701886cb2f
1220 1220 $ hg up null -q
1221 1221 $ hg log --template='{rev}:{node}\n'
1222 1222 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1223 1223 $ hg log --template='{rev}:{node}\n' --hidden
1224 1224 1:a765632148dc55d38c35c4f247c618701886cb2f
1225 1225 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1226 1226 $ hg log -r a
1227 1227 abort: unknown revision 'a'!
1228 1228 [255]
1229 1229
1230 1230 test that parent prevent a changeset to be hidden
1231 1231
1232 1232 $ hg up 1 -q --hidden
1233 1233 $ hg log --template='{rev}:{node}\n'
1234 1234 1:a765632148dc55d38c35c4f247c618701886cb2f
1235 1235 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1236 1236
1237 1237 test that second parent prevent a changeset to be hidden too
1238 1238
1239 1239 $ hg debugsetparents 0 1 # nothing suitable to merge here
1240 1240 $ hg log --template='{rev}:{node}\n'
1241 1241 1:a765632148dc55d38c35c4f247c618701886cb2f
1242 1242 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1243 1243 $ hg debugsetparents 1
1244 1244 $ hg up -q null
1245 1245
1246 1246 bookmarks prevent a changeset being hidden
1247 1247
1248 1248 $ hg bookmark --hidden -r 1 X
1249 1249 $ hg log --template '{rev}:{node}\n'
1250 1250 1:a765632148dc55d38c35c4f247c618701886cb2f
1251 1251 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1252 1252 $ hg bookmark -d X
1253 1253
1254 1254 divergent bookmarks are not hidden
1255 1255
1256 1256 $ hg bookmark --hidden -r 1 X@foo
1257 1257 $ hg log --template '{rev}:{node}\n'
1258 1258 1:a765632148dc55d38c35c4f247c618701886cb2f
1259 1259 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1260 1260
1261 1261 clear extensions configuration
1262 1262 $ echo '[extensions]' >> $HGRCPATH
1263 1263 $ echo "obs=!" >> $HGRCPATH
1264 1264 $ cd ..
1265 1265
1266 1266 test -u/-k for problematic encoding
1267 1267 # unicode: cp932:
1268 1268 # u30A2 0x83 0x41(= 'A')
1269 1269 # u30C2 0x83 0x61(= 'a')
1270 1270
1271 1271 $ hg init problematicencoding
1272 1272 $ cd problematicencoding
1273 1273
1274 1274 $ python > setup.sh <<EOF
1275 1275 > print u'''
1276 1276 > echo a > text
1277 1277 > hg add text
1278 1278 > hg --encoding utf-8 commit -u '\u30A2' -m none
1279 1279 > echo b > text
1280 1280 > hg --encoding utf-8 commit -u '\u30C2' -m none
1281 1281 > echo c > text
1282 1282 > hg --encoding utf-8 commit -u none -m '\u30A2'
1283 1283 > echo d > text
1284 1284 > hg --encoding utf-8 commit -u none -m '\u30C2'
1285 1285 > '''.encode('utf-8')
1286 1286 > EOF
1287 1287 $ sh < setup.sh
1288 1288
1289 1289 test in problematic encoding
1290 1290 $ python > test.sh <<EOF
1291 1291 > print u'''
1292 1292 > hg --encoding cp932 log --template '{rev}\\n' -u '\u30A2'
1293 1293 > echo ====
1294 1294 > hg --encoding cp932 log --template '{rev}\\n' -u '\u30C2'
1295 1295 > echo ====
1296 1296 > hg --encoding cp932 log --template '{rev}\\n' -k '\u30A2'
1297 1297 > echo ====
1298 1298 > hg --encoding cp932 log --template '{rev}\\n' -k '\u30C2'
1299 1299 > '''.encode('cp932')
1300 1300 > EOF
1301 1301 $ sh < test.sh
1302 1302 0
1303 1303 ====
1304 1304 1
1305 1305 ====
1306 1306 2
1307 1307 0
1308 1308 ====
1309 1309 3
1310 1310 1
1311 1311
1312 1312 $ cd ..
1313 1313
1314 1314 test hg log on non-existent files and on directories
1315 1315 $ hg init issue1340
1316 1316 $ cd issue1340
1317 1317 $ mkdir d1; mkdir D2; mkdir D3.i; mkdir d4.hg; mkdir d5.d; mkdir .d6
1318 1318 $ echo 1 > d1/f1
1319 1319 $ echo 1 > D2/f1
1320 1320 $ echo 1 > D3.i/f1
1321 1321 $ echo 1 > d4.hg/f1
1322 1322 $ echo 1 > d5.d/f1
1323 1323 $ echo 1 > .d6/f1
1324 1324 $ hg -q add .
1325 1325 $ hg commit -m "a bunch of weird directories"
1326 1326 $ hg log -l1 d1/f1 | grep changeset
1327 1327 changeset: 0:65624cd9070a
1328 1328 $ hg log -l1 f1
1329 1329 $ hg log -l1 . | grep changeset
1330 1330 changeset: 0:65624cd9070a
1331 1331 $ hg log -l1 ./ | grep changeset
1332 1332 changeset: 0:65624cd9070a
1333 1333 $ hg log -l1 d1 | grep changeset
1334 1334 changeset: 0:65624cd9070a
1335 1335 $ hg log -l1 D2 | grep changeset
1336 1336 changeset: 0:65624cd9070a
1337 1337 $ hg log -l1 D2/f1 | grep changeset
1338 1338 changeset: 0:65624cd9070a
1339 1339 $ hg log -l1 D3.i | grep changeset
1340 1340 changeset: 0:65624cd9070a
1341 1341 $ hg log -l1 D3.i/f1 | grep changeset
1342 1342 changeset: 0:65624cd9070a
1343 1343 $ hg log -l1 d4.hg | grep changeset
1344 1344 changeset: 0:65624cd9070a
1345 1345 $ hg log -l1 d4.hg/f1 | grep changeset
1346 1346 changeset: 0:65624cd9070a
1347 1347 $ hg log -l1 d5.d | grep changeset
1348 1348 changeset: 0:65624cd9070a
1349 1349 $ hg log -l1 d5.d/f1 | grep changeset
1350 1350 changeset: 0:65624cd9070a
1351 1351 $ hg log -l1 .d6 | grep changeset
1352 1352 changeset: 0:65624cd9070a
1353 1353 $ hg log -l1 .d6/f1 | grep changeset
1354 1354 changeset: 0:65624cd9070a
1355 1355
1356 1356 issue3772: hg log -r :null showing revision 0 as well
1357 1357
1358 1358 $ hg log -r :null
1359 changeset: 0:65624cd9070a
1360 tag: tip
1361 user: test
1362 date: Thu Jan 01 00:00:00 1970 +0000
1363 summary: a bunch of weird directories
1364
1359 1365 changeset: -1:000000000000
1360 1366 user:
1361 1367 date: Thu Jan 01 00:00:00 1970 +0000
1362 1368
1363 1369 $ hg log -r null:null
1364 1370 changeset: -1:000000000000
1365 1371 user:
1366 1372 date: Thu Jan 01 00:00:00 1970 +0000
1367 1373
1368 1374
1369 1375 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now