##// END OF EJS Templates
jsonchangeset: set manifest node to "null" for workingctx...
Yuya Nishihara -
r24603:e74f819e default
parent child Browse files
Show More
@@ -1,3255 +1,3259
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, cStringIO, shutil
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 encoding
17 17 import crecord as crecordmod
18 18 import lock as lockmod
19 19
20 20 def parsealiases(cmd):
21 21 return cmd.lstrip("^").split("|")
22 22
23 23 def setupwrapcolorwrite(ui):
24 24 # wrap ui.write so diff output can be labeled/colorized
25 25 def wrapwrite(orig, *args, **kw):
26 26 label = kw.pop('label', '')
27 27 for chunk, l in patch.difflabel(lambda: args):
28 28 orig(chunk, label=label + l)
29 29
30 30 oldwrite = ui.write
31 31 def wrap(*args, **kwargs):
32 32 return wrapwrite(oldwrite, *args, **kwargs)
33 33 setattr(ui, 'write', wrap)
34 34 return oldwrite
35 35
36 36 def filterchunks(ui, originalhunks, usecurses, testfile):
37 37 if usecurses:
38 38 if testfile:
39 39 recordfn = crecordmod.testdecorator(testfile,
40 40 crecordmod.testchunkselector)
41 41 else:
42 42 recordfn = crecordmod.chunkselector
43 43
44 44 return crecordmod.filterpatch(ui, originalhunks, recordfn)
45 45
46 46 else:
47 47 return patch.filterpatch(ui, originalhunks)
48 48
49 49 def recordfilter(ui, originalhunks):
50 50 usecurses = ui.configbool('experimental', 'crecord', False)
51 51 testfile = ui.config('experimental', 'crecordtest', None)
52 52 oldwrite = setupwrapcolorwrite(ui)
53 53 try:
54 54 newchunks = filterchunks(ui, originalhunks, usecurses, testfile)
55 55 finally:
56 56 ui.write = oldwrite
57 57 return newchunks
58 58
59 59 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
60 60 filterfn, *pats, **opts):
61 61 import merge as mergemod
62 62 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
63 63 ishunk = lambda x: isinstance(x, hunkclasses)
64 64
65 65 if not ui.interactive():
66 66 raise util.Abort(_('running non-interactively, use %s instead') %
67 67 cmdsuggest)
68 68
69 69 # make sure username is set before going interactive
70 70 if not opts.get('user'):
71 71 ui.username() # raise exception, username not provided
72 72
73 73 def recordfunc(ui, repo, message, match, opts):
74 74 """This is generic record driver.
75 75
76 76 Its job is to interactively filter local changes, and
77 77 accordingly prepare working directory into a state in which the
78 78 job can be delegated to a non-interactive commit command such as
79 79 'commit' or 'qrefresh'.
80 80
81 81 After the actual job is done by non-interactive command, the
82 82 working directory is restored to its original state.
83 83
84 84 In the end we'll record interesting changes, and everything else
85 85 will be left in place, so the user can continue working.
86 86 """
87 87
88 88 checkunfinished(repo, commit=True)
89 89 merge = len(repo[None].parents()) > 1
90 90 if merge:
91 91 raise util.Abort(_('cannot partially commit a merge '
92 92 '(use "hg commit" instead)'))
93 93
94 94 status = repo.status(match=match)
95 95 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
96 96 diffopts.nodates = True
97 97 diffopts.git = True
98 98 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
99 99 originalchunks = patch.parsepatch(originaldiff)
100 100
101 101 # 1. filter patch, so we have intending-to apply subset of it
102 102 try:
103 103 chunks = filterfn(ui, originalchunks)
104 104 except patch.PatchError, err:
105 105 raise util.Abort(_('error parsing patch: %s') % err)
106 106
107 107 contenders = set()
108 108 for h in chunks:
109 109 try:
110 110 contenders.update(set(h.files()))
111 111 except AttributeError:
112 112 pass
113 113
114 114 changed = status.modified + status.added + status.removed
115 115 newfiles = [f for f in changed if f in contenders]
116 116 if not newfiles:
117 117 ui.status(_('no changes to record\n'))
118 118 return 0
119 119
120 120 newandmodifiedfiles = set()
121 121 for h in chunks:
122 122 isnew = h.filename() in status.added
123 123 if ishunk(h) and isnew and not h in originalchunks:
124 124 newandmodifiedfiles.add(h.filename())
125 125
126 126 modified = set(status.modified)
127 127
128 128 # 2. backup changed files, so we can restore them in the end
129 129
130 130 if backupall:
131 131 tobackup = changed
132 132 else:
133 133 tobackup = [f for f in newfiles
134 134 if f in modified or f in newandmodifiedfiles]
135 135
136 136 backups = {}
137 137 if tobackup:
138 138 backupdir = repo.join('record-backups')
139 139 try:
140 140 os.mkdir(backupdir)
141 141 except OSError, err:
142 142 if err.errno != errno.EEXIST:
143 143 raise
144 144 try:
145 145 # backup continues
146 146 for f in tobackup:
147 147 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
148 148 dir=backupdir)
149 149 os.close(fd)
150 150 ui.debug('backup %r as %r\n' % (f, tmpname))
151 151 util.copyfile(repo.wjoin(f), tmpname)
152 152 shutil.copystat(repo.wjoin(f), tmpname)
153 153 backups[f] = tmpname
154 154
155 155 fp = cStringIO.StringIO()
156 156 for c in chunks:
157 157 fname = c.filename()
158 158 if fname in backups or fname in newandmodifiedfiles:
159 159 c.write(fp)
160 160 dopatch = fp.tell()
161 161 fp.seek(0)
162 162
163 163 [os.unlink(c) for c in newandmodifiedfiles]
164 164
165 165 # 3a. apply filtered patch to clean repo (clean)
166 166 if backups:
167 167 # Equivalent to hg.revert
168 168 choices = lambda key: key in backups
169 169 mergemod.update(repo, repo.dirstate.p1(),
170 170 False, True, choices)
171 171
172 172 # 3b. (apply)
173 173 if dopatch:
174 174 try:
175 175 ui.debug('applying patch\n')
176 176 ui.debug(fp.getvalue())
177 177 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
178 178 except patch.PatchError, err:
179 179 raise util.Abort(str(err))
180 180 del fp
181 181
182 182 # 4. We prepared working directory according to filtered
183 183 # patch. Now is the time to delegate the job to
184 184 # commit/qrefresh or the like!
185 185
186 186 # Make all of the pathnames absolute.
187 187 newfiles = [repo.wjoin(nf) for nf in newfiles]
188 188 return commitfunc(ui, repo, *newfiles, **opts)
189 189 finally:
190 190 # 5. finally restore backed-up files
191 191 try:
192 192 for realname, tmpname in backups.iteritems():
193 193 ui.debug('restoring %r to %r\n' % (tmpname, realname))
194 194 util.copyfile(tmpname, repo.wjoin(realname))
195 195 # Our calls to copystat() here and above are a
196 196 # hack to trick any editors that have f open that
197 197 # we haven't modified them.
198 198 #
199 199 # Also note that this racy as an editor could
200 200 # notice the file's mtime before we've finished
201 201 # writing it.
202 202 shutil.copystat(tmpname, repo.wjoin(realname))
203 203 os.unlink(tmpname)
204 204 if tobackup:
205 205 os.rmdir(backupdir)
206 206 except OSError:
207 207 pass
208 208
209 209 return commit(ui, repo, recordfunc, pats, opts)
210 210
211 211 def findpossible(cmd, table, strict=False):
212 212 """
213 213 Return cmd -> (aliases, command table entry)
214 214 for each matching command.
215 215 Return debug commands (or their aliases) only if no normal command matches.
216 216 """
217 217 choice = {}
218 218 debugchoice = {}
219 219
220 220 if cmd in table:
221 221 # short-circuit exact matches, "log" alias beats "^log|history"
222 222 keys = [cmd]
223 223 else:
224 224 keys = table.keys()
225 225
226 226 allcmds = []
227 227 for e in keys:
228 228 aliases = parsealiases(e)
229 229 allcmds.extend(aliases)
230 230 found = None
231 231 if cmd in aliases:
232 232 found = cmd
233 233 elif not strict:
234 234 for a in aliases:
235 235 if a.startswith(cmd):
236 236 found = a
237 237 break
238 238 if found is not None:
239 239 if aliases[0].startswith("debug") or found.startswith("debug"):
240 240 debugchoice[found] = (aliases, table[e])
241 241 else:
242 242 choice[found] = (aliases, table[e])
243 243
244 244 if not choice and debugchoice:
245 245 choice = debugchoice
246 246
247 247 return choice, allcmds
248 248
249 249 def findcmd(cmd, table, strict=True):
250 250 """Return (aliases, command table entry) for command string."""
251 251 choice, allcmds = findpossible(cmd, table, strict)
252 252
253 253 if cmd in choice:
254 254 return choice[cmd]
255 255
256 256 if len(choice) > 1:
257 257 clist = choice.keys()
258 258 clist.sort()
259 259 raise error.AmbiguousCommand(cmd, clist)
260 260
261 261 if choice:
262 262 return choice.values()[0]
263 263
264 264 raise error.UnknownCommand(cmd, allcmds)
265 265
266 266 def findrepo(p):
267 267 while not os.path.isdir(os.path.join(p, ".hg")):
268 268 oldp, p = p, os.path.dirname(p)
269 269 if p == oldp:
270 270 return None
271 271
272 272 return p
273 273
274 274 def bailifchanged(repo, merge=True):
275 275 if merge and repo.dirstate.p2() != nullid:
276 276 raise util.Abort(_('outstanding uncommitted merge'))
277 277 modified, added, removed, deleted = repo.status()[:4]
278 278 if modified or added or removed or deleted:
279 279 raise util.Abort(_('uncommitted changes'))
280 280 ctx = repo[None]
281 281 for s in sorted(ctx.substate):
282 282 ctx.sub(s).bailifchanged()
283 283
284 284 def logmessage(ui, opts):
285 285 """ get the log message according to -m and -l option """
286 286 message = opts.get('message')
287 287 logfile = opts.get('logfile')
288 288
289 289 if message and logfile:
290 290 raise util.Abort(_('options --message and --logfile are mutually '
291 291 'exclusive'))
292 292 if not message and logfile:
293 293 try:
294 294 if logfile == '-':
295 295 message = ui.fin.read()
296 296 else:
297 297 message = '\n'.join(util.readfile(logfile).splitlines())
298 298 except IOError, inst:
299 299 raise util.Abort(_("can't read commit message '%s': %s") %
300 300 (logfile, inst.strerror))
301 301 return message
302 302
303 303 def mergeeditform(ctxorbool, baseformname):
304 304 """return appropriate editform name (referencing a committemplate)
305 305
306 306 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
307 307 merging is committed.
308 308
309 309 This returns baseformname with '.merge' appended if it is a merge,
310 310 otherwise '.normal' is appended.
311 311 """
312 312 if isinstance(ctxorbool, bool):
313 313 if ctxorbool:
314 314 return baseformname + ".merge"
315 315 elif 1 < len(ctxorbool.parents()):
316 316 return baseformname + ".merge"
317 317
318 318 return baseformname + ".normal"
319 319
320 320 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
321 321 editform='', **opts):
322 322 """get appropriate commit message editor according to '--edit' option
323 323
324 324 'finishdesc' is a function to be called with edited commit message
325 325 (= 'description' of the new changeset) just after editing, but
326 326 before checking empty-ness. It should return actual text to be
327 327 stored into history. This allows to change description before
328 328 storing.
329 329
330 330 'extramsg' is a extra message to be shown in the editor instead of
331 331 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
332 332 is automatically added.
333 333
334 334 'editform' is a dot-separated list of names, to distinguish
335 335 the purpose of commit text editing.
336 336
337 337 'getcommiteditor' returns 'commitforceeditor' regardless of
338 338 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
339 339 they are specific for usage in MQ.
340 340 """
341 341 if edit or finishdesc or extramsg:
342 342 return lambda r, c, s: commitforceeditor(r, c, s,
343 343 finishdesc=finishdesc,
344 344 extramsg=extramsg,
345 345 editform=editform)
346 346 elif editform:
347 347 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
348 348 else:
349 349 return commiteditor
350 350
351 351 def loglimit(opts):
352 352 """get the log limit according to option -l/--limit"""
353 353 limit = opts.get('limit')
354 354 if limit:
355 355 try:
356 356 limit = int(limit)
357 357 except ValueError:
358 358 raise util.Abort(_('limit must be a positive integer'))
359 359 if limit <= 0:
360 360 raise util.Abort(_('limit must be positive'))
361 361 else:
362 362 limit = None
363 363 return limit
364 364
365 365 def makefilename(repo, pat, node, desc=None,
366 366 total=None, seqno=None, revwidth=None, pathname=None):
367 367 node_expander = {
368 368 'H': lambda: hex(node),
369 369 'R': lambda: str(repo.changelog.rev(node)),
370 370 'h': lambda: short(node),
371 371 'm': lambda: re.sub('[^\w]', '_', str(desc))
372 372 }
373 373 expander = {
374 374 '%': lambda: '%',
375 375 'b': lambda: os.path.basename(repo.root),
376 376 }
377 377
378 378 try:
379 379 if node:
380 380 expander.update(node_expander)
381 381 if node:
382 382 expander['r'] = (lambda:
383 383 str(repo.changelog.rev(node)).zfill(revwidth or 0))
384 384 if total is not None:
385 385 expander['N'] = lambda: str(total)
386 386 if seqno is not None:
387 387 expander['n'] = lambda: str(seqno)
388 388 if total is not None and seqno is not None:
389 389 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
390 390 if pathname is not None:
391 391 expander['s'] = lambda: os.path.basename(pathname)
392 392 expander['d'] = lambda: os.path.dirname(pathname) or '.'
393 393 expander['p'] = lambda: pathname
394 394
395 395 newname = []
396 396 patlen = len(pat)
397 397 i = 0
398 398 while i < patlen:
399 399 c = pat[i]
400 400 if c == '%':
401 401 i += 1
402 402 c = pat[i]
403 403 c = expander[c]()
404 404 newname.append(c)
405 405 i += 1
406 406 return ''.join(newname)
407 407 except KeyError, inst:
408 408 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
409 409 inst.args[0])
410 410
411 411 def makefileobj(repo, pat, node=None, desc=None, total=None,
412 412 seqno=None, revwidth=None, mode='wb', modemap=None,
413 413 pathname=None):
414 414
415 415 writable = mode not in ('r', 'rb')
416 416
417 417 if not pat or pat == '-':
418 418 if writable:
419 419 fp = repo.ui.fout
420 420 else:
421 421 fp = repo.ui.fin
422 422 if util.safehasattr(fp, 'fileno'):
423 423 return os.fdopen(os.dup(fp.fileno()), mode)
424 424 else:
425 425 # if this fp can't be duped properly, return
426 426 # a dummy object that can be closed
427 427 class wrappedfileobj(object):
428 428 noop = lambda x: None
429 429 def __init__(self, f):
430 430 self.f = f
431 431 def __getattr__(self, attr):
432 432 if attr == 'close':
433 433 return self.noop
434 434 else:
435 435 return getattr(self.f, attr)
436 436
437 437 return wrappedfileobj(fp)
438 438 if util.safehasattr(pat, 'write') and writable:
439 439 return pat
440 440 if util.safehasattr(pat, 'read') and 'r' in mode:
441 441 return pat
442 442 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
443 443 if modemap is not None:
444 444 mode = modemap.get(fn, mode)
445 445 if mode == 'wb':
446 446 modemap[fn] = 'ab'
447 447 return open(fn, mode)
448 448
449 449 def openrevlog(repo, cmd, file_, opts):
450 450 """opens the changelog, manifest, a filelog or a given revlog"""
451 451 cl = opts['changelog']
452 452 mf = opts['manifest']
453 453 msg = None
454 454 if cl and mf:
455 455 msg = _('cannot specify --changelog and --manifest at the same time')
456 456 elif cl or mf:
457 457 if file_:
458 458 msg = _('cannot specify filename with --changelog or --manifest')
459 459 elif not repo:
460 460 msg = _('cannot specify --changelog or --manifest '
461 461 'without a repository')
462 462 if msg:
463 463 raise util.Abort(msg)
464 464
465 465 r = None
466 466 if repo:
467 467 if cl:
468 468 r = repo.unfiltered().changelog
469 469 elif mf:
470 470 r = repo.manifest
471 471 elif file_:
472 472 filelog = repo.file(file_)
473 473 if len(filelog):
474 474 r = filelog
475 475 if not r:
476 476 if not file_:
477 477 raise error.CommandError(cmd, _('invalid arguments'))
478 478 if not os.path.isfile(file_):
479 479 raise util.Abort(_("revlog '%s' not found") % file_)
480 480 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
481 481 file_[:-2] + ".i")
482 482 return r
483 483
484 484 def copy(ui, repo, pats, opts, rename=False):
485 485 # called with the repo lock held
486 486 #
487 487 # hgsep => pathname that uses "/" to separate directories
488 488 # ossep => pathname that uses os.sep to separate directories
489 489 cwd = repo.getcwd()
490 490 targets = {}
491 491 after = opts.get("after")
492 492 dryrun = opts.get("dry_run")
493 493 wctx = repo[None]
494 494
495 495 def walkpat(pat):
496 496 srcs = []
497 497 if after:
498 498 badstates = '?'
499 499 else:
500 500 badstates = '?r'
501 501 m = scmutil.match(repo[None], [pat], opts, globbed=True)
502 502 for abs in repo.walk(m):
503 503 state = repo.dirstate[abs]
504 504 rel = m.rel(abs)
505 505 exact = m.exact(abs)
506 506 if state in badstates:
507 507 if exact and state == '?':
508 508 ui.warn(_('%s: not copying - file is not managed\n') % rel)
509 509 if exact and state == 'r':
510 510 ui.warn(_('%s: not copying - file has been marked for'
511 511 ' remove\n') % rel)
512 512 continue
513 513 # abs: hgsep
514 514 # rel: ossep
515 515 srcs.append((abs, rel, exact))
516 516 return srcs
517 517
518 518 # abssrc: hgsep
519 519 # relsrc: ossep
520 520 # otarget: ossep
521 521 def copyfile(abssrc, relsrc, otarget, exact):
522 522 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
523 523 if '/' in abstarget:
524 524 # We cannot normalize abstarget itself, this would prevent
525 525 # case only renames, like a => A.
526 526 abspath, absname = abstarget.rsplit('/', 1)
527 527 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
528 528 reltarget = repo.pathto(abstarget, cwd)
529 529 target = repo.wjoin(abstarget)
530 530 src = repo.wjoin(abssrc)
531 531 state = repo.dirstate[abstarget]
532 532
533 533 scmutil.checkportable(ui, abstarget)
534 534
535 535 # check for collisions
536 536 prevsrc = targets.get(abstarget)
537 537 if prevsrc is not None:
538 538 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
539 539 (reltarget, repo.pathto(abssrc, cwd),
540 540 repo.pathto(prevsrc, cwd)))
541 541 return
542 542
543 543 # check for overwrites
544 544 exists = os.path.lexists(target)
545 545 samefile = False
546 546 if exists and abssrc != abstarget:
547 547 if (repo.dirstate.normalize(abssrc) ==
548 548 repo.dirstate.normalize(abstarget)):
549 549 if not rename:
550 550 ui.warn(_("%s: can't copy - same file\n") % reltarget)
551 551 return
552 552 exists = False
553 553 samefile = True
554 554
555 555 if not after and exists or after and state in 'mn':
556 556 if not opts['force']:
557 557 ui.warn(_('%s: not overwriting - file exists\n') %
558 558 reltarget)
559 559 return
560 560
561 561 if after:
562 562 if not exists:
563 563 if rename:
564 564 ui.warn(_('%s: not recording move - %s does not exist\n') %
565 565 (relsrc, reltarget))
566 566 else:
567 567 ui.warn(_('%s: not recording copy - %s does not exist\n') %
568 568 (relsrc, reltarget))
569 569 return
570 570 elif not dryrun:
571 571 try:
572 572 if exists:
573 573 os.unlink(target)
574 574 targetdir = os.path.dirname(target) or '.'
575 575 if not os.path.isdir(targetdir):
576 576 os.makedirs(targetdir)
577 577 if samefile:
578 578 tmp = target + "~hgrename"
579 579 os.rename(src, tmp)
580 580 os.rename(tmp, target)
581 581 else:
582 582 util.copyfile(src, target)
583 583 srcexists = True
584 584 except IOError, inst:
585 585 if inst.errno == errno.ENOENT:
586 586 ui.warn(_('%s: deleted in working directory\n') % relsrc)
587 587 srcexists = False
588 588 else:
589 589 ui.warn(_('%s: cannot copy - %s\n') %
590 590 (relsrc, inst.strerror))
591 591 return True # report a failure
592 592
593 593 if ui.verbose or not exact:
594 594 if rename:
595 595 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
596 596 else:
597 597 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
598 598
599 599 targets[abstarget] = abssrc
600 600
601 601 # fix up dirstate
602 602 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
603 603 dryrun=dryrun, cwd=cwd)
604 604 if rename and not dryrun:
605 605 if not after and srcexists and not samefile:
606 606 util.unlinkpath(repo.wjoin(abssrc))
607 607 wctx.forget([abssrc])
608 608
609 609 # pat: ossep
610 610 # dest ossep
611 611 # srcs: list of (hgsep, hgsep, ossep, bool)
612 612 # return: function that takes hgsep and returns ossep
613 613 def targetpathfn(pat, dest, srcs):
614 614 if os.path.isdir(pat):
615 615 abspfx = pathutil.canonpath(repo.root, cwd, pat)
616 616 abspfx = util.localpath(abspfx)
617 617 if destdirexists:
618 618 striplen = len(os.path.split(abspfx)[0])
619 619 else:
620 620 striplen = len(abspfx)
621 621 if striplen:
622 622 striplen += len(os.sep)
623 623 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
624 624 elif destdirexists:
625 625 res = lambda p: os.path.join(dest,
626 626 os.path.basename(util.localpath(p)))
627 627 else:
628 628 res = lambda p: dest
629 629 return res
630 630
631 631 # pat: ossep
632 632 # dest ossep
633 633 # srcs: list of (hgsep, hgsep, ossep, bool)
634 634 # return: function that takes hgsep and returns ossep
635 635 def targetpathafterfn(pat, dest, srcs):
636 636 if matchmod.patkind(pat):
637 637 # a mercurial pattern
638 638 res = lambda p: os.path.join(dest,
639 639 os.path.basename(util.localpath(p)))
640 640 else:
641 641 abspfx = pathutil.canonpath(repo.root, cwd, pat)
642 642 if len(abspfx) < len(srcs[0][0]):
643 643 # A directory. Either the target path contains the last
644 644 # component of the source path or it does not.
645 645 def evalpath(striplen):
646 646 score = 0
647 647 for s in srcs:
648 648 t = os.path.join(dest, util.localpath(s[0])[striplen:])
649 649 if os.path.lexists(t):
650 650 score += 1
651 651 return score
652 652
653 653 abspfx = util.localpath(abspfx)
654 654 striplen = len(abspfx)
655 655 if striplen:
656 656 striplen += len(os.sep)
657 657 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
658 658 score = evalpath(striplen)
659 659 striplen1 = len(os.path.split(abspfx)[0])
660 660 if striplen1:
661 661 striplen1 += len(os.sep)
662 662 if evalpath(striplen1) > score:
663 663 striplen = striplen1
664 664 res = lambda p: os.path.join(dest,
665 665 util.localpath(p)[striplen:])
666 666 else:
667 667 # a file
668 668 if destdirexists:
669 669 res = lambda p: os.path.join(dest,
670 670 os.path.basename(util.localpath(p)))
671 671 else:
672 672 res = lambda p: dest
673 673 return res
674 674
675 675 pats = scmutil.expandpats(pats)
676 676 if not pats:
677 677 raise util.Abort(_('no source or destination specified'))
678 678 if len(pats) == 1:
679 679 raise util.Abort(_('no destination specified'))
680 680 dest = pats.pop()
681 681 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
682 682 if not destdirexists:
683 683 if len(pats) > 1 or matchmod.patkind(pats[0]):
684 684 raise util.Abort(_('with multiple sources, destination must be an '
685 685 'existing directory'))
686 686 if util.endswithsep(dest):
687 687 raise util.Abort(_('destination %s is not a directory') % dest)
688 688
689 689 tfn = targetpathfn
690 690 if after:
691 691 tfn = targetpathafterfn
692 692 copylist = []
693 693 for pat in pats:
694 694 srcs = walkpat(pat)
695 695 if not srcs:
696 696 continue
697 697 copylist.append((tfn(pat, dest, srcs), srcs))
698 698 if not copylist:
699 699 raise util.Abort(_('no files to copy'))
700 700
701 701 errors = 0
702 702 for targetpath, srcs in copylist:
703 703 for abssrc, relsrc, exact in srcs:
704 704 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
705 705 errors += 1
706 706
707 707 if errors:
708 708 ui.warn(_('(consider using --after)\n'))
709 709
710 710 return errors != 0
711 711
712 712 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
713 713 runargs=None, appendpid=False):
714 714 '''Run a command as a service.'''
715 715
716 716 def writepid(pid):
717 717 if opts['pid_file']:
718 718 if appendpid:
719 719 mode = 'a'
720 720 else:
721 721 mode = 'w'
722 722 fp = open(opts['pid_file'], mode)
723 723 fp.write(str(pid) + '\n')
724 724 fp.close()
725 725
726 726 if opts['daemon'] and not opts['daemon_pipefds']:
727 727 # Signal child process startup with file removal
728 728 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
729 729 os.close(lockfd)
730 730 try:
731 731 if not runargs:
732 732 runargs = util.hgcmd() + sys.argv[1:]
733 733 runargs.append('--daemon-pipefds=%s' % lockpath)
734 734 # Don't pass --cwd to the child process, because we've already
735 735 # changed directory.
736 736 for i in xrange(1, len(runargs)):
737 737 if runargs[i].startswith('--cwd='):
738 738 del runargs[i]
739 739 break
740 740 elif runargs[i].startswith('--cwd'):
741 741 del runargs[i:i + 2]
742 742 break
743 743 def condfn():
744 744 return not os.path.exists(lockpath)
745 745 pid = util.rundetached(runargs, condfn)
746 746 if pid < 0:
747 747 raise util.Abort(_('child process failed to start'))
748 748 writepid(pid)
749 749 finally:
750 750 try:
751 751 os.unlink(lockpath)
752 752 except OSError, e:
753 753 if e.errno != errno.ENOENT:
754 754 raise
755 755 if parentfn:
756 756 return parentfn(pid)
757 757 else:
758 758 return
759 759
760 760 if initfn:
761 761 initfn()
762 762
763 763 if not opts['daemon']:
764 764 writepid(os.getpid())
765 765
766 766 if opts['daemon_pipefds']:
767 767 lockpath = opts['daemon_pipefds']
768 768 try:
769 769 os.setsid()
770 770 except AttributeError:
771 771 pass
772 772 os.unlink(lockpath)
773 773 util.hidewindow()
774 774 sys.stdout.flush()
775 775 sys.stderr.flush()
776 776
777 777 nullfd = os.open(os.devnull, os.O_RDWR)
778 778 logfilefd = nullfd
779 779 if logfile:
780 780 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
781 781 os.dup2(nullfd, 0)
782 782 os.dup2(logfilefd, 1)
783 783 os.dup2(logfilefd, 2)
784 784 if nullfd not in (0, 1, 2):
785 785 os.close(nullfd)
786 786 if logfile and logfilefd not in (0, 1, 2):
787 787 os.close(logfilefd)
788 788
789 789 if runfn:
790 790 return runfn()
791 791
792 792 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
793 793 """Utility function used by commands.import to import a single patch
794 794
795 795 This function is explicitly defined here to help the evolve extension to
796 796 wrap this part of the import logic.
797 797
798 798 The API is currently a bit ugly because it a simple code translation from
799 799 the import command. Feel free to make it better.
800 800
801 801 :hunk: a patch (as a binary string)
802 802 :parents: nodes that will be parent of the created commit
803 803 :opts: the full dict of option passed to the import command
804 804 :msgs: list to save commit message to.
805 805 (used in case we need to save it when failing)
806 806 :updatefunc: a function that update a repo to a given node
807 807 updatefunc(<repo>, <node>)
808 808 """
809 809 tmpname, message, user, date, branch, nodeid, p1, p2 = \
810 810 patch.extract(ui, hunk)
811 811
812 812 update = not opts.get('bypass')
813 813 strip = opts["strip"]
814 814 prefix = opts["prefix"]
815 815 sim = float(opts.get('similarity') or 0)
816 816 if not tmpname:
817 817 return (None, None, False)
818 818 msg = _('applied to working directory')
819 819
820 820 rejects = False
821 821
822 822 try:
823 823 cmdline_message = logmessage(ui, opts)
824 824 if cmdline_message:
825 825 # pickup the cmdline msg
826 826 message = cmdline_message
827 827 elif message:
828 828 # pickup the patch msg
829 829 message = message.strip()
830 830 else:
831 831 # launch the editor
832 832 message = None
833 833 ui.debug('message:\n%s\n' % message)
834 834
835 835 if len(parents) == 1:
836 836 parents.append(repo[nullid])
837 837 if opts.get('exact'):
838 838 if not nodeid or not p1:
839 839 raise util.Abort(_('not a Mercurial patch'))
840 840 p1 = repo[p1]
841 841 p2 = repo[p2 or nullid]
842 842 elif p2:
843 843 try:
844 844 p1 = repo[p1]
845 845 p2 = repo[p2]
846 846 # Without any options, consider p2 only if the
847 847 # patch is being applied on top of the recorded
848 848 # first parent.
849 849 if p1 != parents[0]:
850 850 p1 = parents[0]
851 851 p2 = repo[nullid]
852 852 except error.RepoError:
853 853 p1, p2 = parents
854 854 if p2.node() == nullid:
855 855 ui.warn(_("warning: import the patch as a normal revision\n"
856 856 "(use --exact to import the patch as a merge)\n"))
857 857 else:
858 858 p1, p2 = parents
859 859
860 860 n = None
861 861 if update:
862 862 repo.dirstate.beginparentchange()
863 863 if p1 != parents[0]:
864 864 updatefunc(repo, p1.node())
865 865 if p2 != parents[1]:
866 866 repo.setparents(p1.node(), p2.node())
867 867
868 868 if opts.get('exact') or opts.get('import_branch'):
869 869 repo.dirstate.setbranch(branch or 'default')
870 870
871 871 partial = opts.get('partial', False)
872 872 files = set()
873 873 try:
874 874 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
875 875 files=files, eolmode=None, similarity=sim / 100.0)
876 876 except patch.PatchError, e:
877 877 if not partial:
878 878 raise util.Abort(str(e))
879 879 if partial:
880 880 rejects = True
881 881
882 882 files = list(files)
883 883 if opts.get('no_commit'):
884 884 if message:
885 885 msgs.append(message)
886 886 else:
887 887 if opts.get('exact') or p2:
888 888 # If you got here, you either use --force and know what
889 889 # you are doing or used --exact or a merge patch while
890 890 # being updated to its first parent.
891 891 m = None
892 892 else:
893 893 m = scmutil.matchfiles(repo, files or [])
894 894 editform = mergeeditform(repo[None], 'import.normal')
895 895 if opts.get('exact'):
896 896 editor = None
897 897 else:
898 898 editor = getcommiteditor(editform=editform, **opts)
899 899 n = repo.commit(message, opts.get('user') or user,
900 900 opts.get('date') or date, match=m,
901 901 editor=editor, force=partial)
902 902 repo.dirstate.endparentchange()
903 903 else:
904 904 if opts.get('exact') or opts.get('import_branch'):
905 905 branch = branch or 'default'
906 906 else:
907 907 branch = p1.branch()
908 908 store = patch.filestore()
909 909 try:
910 910 files = set()
911 911 try:
912 912 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
913 913 files, eolmode=None)
914 914 except patch.PatchError, e:
915 915 raise util.Abort(str(e))
916 916 if opts.get('exact'):
917 917 editor = None
918 918 else:
919 919 editor = getcommiteditor(editform='import.bypass')
920 920 memctx = context.makememctx(repo, (p1.node(), p2.node()),
921 921 message,
922 922 opts.get('user') or user,
923 923 opts.get('date') or date,
924 924 branch, files, store,
925 925 editor=editor)
926 926 n = memctx.commit()
927 927 finally:
928 928 store.close()
929 929 if opts.get('exact') and opts.get('no_commit'):
930 930 # --exact with --no-commit is still useful in that it does merge
931 931 # and branch bits
932 932 ui.warn(_("warning: can't check exact import with --no-commit\n"))
933 933 elif opts.get('exact') and hex(n) != nodeid:
934 934 raise util.Abort(_('patch is damaged or loses information'))
935 935 if n:
936 936 # i18n: refers to a short changeset id
937 937 msg = _('created %s') % short(n)
938 938 return (msg, n, rejects)
939 939 finally:
940 940 os.unlink(tmpname)
941 941
942 942 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
943 943 opts=None):
944 944 '''export changesets as hg patches.'''
945 945
946 946 total = len(revs)
947 947 revwidth = max([len(str(rev)) for rev in revs])
948 948 filemode = {}
949 949
950 950 def single(rev, seqno, fp):
951 951 ctx = repo[rev]
952 952 node = ctx.node()
953 953 parents = [p.node() for p in ctx.parents() if p]
954 954 branch = ctx.branch()
955 955 if switch_parent:
956 956 parents.reverse()
957 957
958 958 if parents:
959 959 prev = parents[0]
960 960 else:
961 961 prev = nullid
962 962
963 963 shouldclose = False
964 964 if not fp and len(template) > 0:
965 965 desc_lines = ctx.description().rstrip().split('\n')
966 966 desc = desc_lines[0] #Commit always has a first line.
967 967 fp = makefileobj(repo, template, node, desc=desc, total=total,
968 968 seqno=seqno, revwidth=revwidth, mode='wb',
969 969 modemap=filemode)
970 970 if fp != template:
971 971 shouldclose = True
972 972 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
973 973 repo.ui.note("%s\n" % fp.name)
974 974
975 975 if not fp:
976 976 write = repo.ui.write
977 977 else:
978 978 def write(s, **kw):
979 979 fp.write(s)
980 980
981 981 write("# HG changeset patch\n")
982 982 write("# User %s\n" % ctx.user())
983 983 write("# Date %d %d\n" % ctx.date())
984 984 write("# %s\n" % util.datestr(ctx.date()))
985 985 if branch and branch != 'default':
986 986 write("# Branch %s\n" % branch)
987 987 write("# Node ID %s\n" % hex(node))
988 988 write("# Parent %s\n" % hex(prev))
989 989 if len(parents) > 1:
990 990 write("# Parent %s\n" % hex(parents[1]))
991 991 write(ctx.description().rstrip())
992 992 write("\n\n")
993 993
994 994 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
995 995 write(chunk, label=label)
996 996
997 997 if shouldclose:
998 998 fp.close()
999 999
1000 1000 for seqno, rev in enumerate(revs):
1001 1001 single(rev, seqno + 1, fp)
1002 1002
1003 1003 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1004 1004 changes=None, stat=False, fp=None, prefix='',
1005 1005 root='', listsubrepos=False):
1006 1006 '''show diff or diffstat.'''
1007 1007 if fp is None:
1008 1008 write = ui.write
1009 1009 else:
1010 1010 def write(s, **kw):
1011 1011 fp.write(s)
1012 1012
1013 1013 if root:
1014 1014 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1015 1015 else:
1016 1016 relroot = ''
1017 1017 if relroot != '':
1018 1018 # XXX relative roots currently don't work if the root is within a
1019 1019 # subrepo
1020 1020 uirelroot = match.uipath(relroot)
1021 1021 relroot += '/'
1022 1022 for matchroot in match.files():
1023 1023 if not matchroot.startswith(relroot):
1024 1024 ui.warn(_('warning: %s not inside relative root %s\n') % (
1025 1025 match.uipath(matchroot), uirelroot))
1026 1026
1027 1027 if stat:
1028 1028 diffopts = diffopts.copy(context=0)
1029 1029 width = 80
1030 1030 if not ui.plain():
1031 1031 width = ui.termwidth()
1032 1032 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1033 1033 prefix=prefix, relroot=relroot)
1034 1034 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1035 1035 width=width,
1036 1036 git=diffopts.git):
1037 1037 write(chunk, label=label)
1038 1038 else:
1039 1039 for chunk, label in patch.diffui(repo, node1, node2, match,
1040 1040 changes, diffopts, prefix=prefix,
1041 1041 relroot=relroot):
1042 1042 write(chunk, label=label)
1043 1043
1044 1044 if listsubrepos:
1045 1045 ctx1 = repo[node1]
1046 1046 ctx2 = repo[node2]
1047 1047 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1048 1048 tempnode2 = node2
1049 1049 try:
1050 1050 if node2 is not None:
1051 1051 tempnode2 = ctx2.substate[subpath][1]
1052 1052 except KeyError:
1053 1053 # A subrepo that existed in node1 was deleted between node1 and
1054 1054 # node2 (inclusive). Thus, ctx2's substate won't contain that
1055 1055 # subpath. The best we can do is to ignore it.
1056 1056 tempnode2 = None
1057 1057 submatch = matchmod.narrowmatcher(subpath, match)
1058 1058 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1059 1059 stat=stat, fp=fp, prefix=prefix)
1060 1060
1061 1061 class changeset_printer(object):
1062 1062 '''show changeset information when templating not requested.'''
1063 1063
1064 1064 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1065 1065 self.ui = ui
1066 1066 self.repo = repo
1067 1067 self.buffered = buffered
1068 1068 self.matchfn = matchfn
1069 1069 self.diffopts = diffopts
1070 1070 self.header = {}
1071 1071 self.hunk = {}
1072 1072 self.lastheader = None
1073 1073 self.footer = None
1074 1074
1075 1075 def flush(self, rev):
1076 1076 if rev in self.header:
1077 1077 h = self.header[rev]
1078 1078 if h != self.lastheader:
1079 1079 self.lastheader = h
1080 1080 self.ui.write(h)
1081 1081 del self.header[rev]
1082 1082 if rev in self.hunk:
1083 1083 self.ui.write(self.hunk[rev])
1084 1084 del self.hunk[rev]
1085 1085 return 1
1086 1086 return 0
1087 1087
1088 1088 def close(self):
1089 1089 if self.footer:
1090 1090 self.ui.write(self.footer)
1091 1091
1092 1092 def show(self, ctx, copies=None, matchfn=None, **props):
1093 1093 if self.buffered:
1094 1094 self.ui.pushbuffer()
1095 1095 self._show(ctx, copies, matchfn, props)
1096 1096 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
1097 1097 else:
1098 1098 self._show(ctx, copies, matchfn, props)
1099 1099
1100 1100 def _show(self, ctx, copies, matchfn, props):
1101 1101 '''show a single changeset or file revision'''
1102 1102 changenode = ctx.node()
1103 1103 rev = ctx.rev()
1104 1104 if self.ui.debugflag:
1105 1105 hexfunc = hex
1106 1106 else:
1107 1107 hexfunc = short
1108 1108 if rev is None:
1109 1109 pctx = ctx.p1()
1110 1110 revnode = (pctx.rev(), hexfunc(pctx.node()) + '+')
1111 1111 else:
1112 1112 revnode = (rev, hexfunc(changenode))
1113 1113
1114 1114 if self.ui.quiet:
1115 1115 self.ui.write("%d:%s\n" % revnode, label='log.node')
1116 1116 return
1117 1117
1118 1118 date = util.datestr(ctx.date())
1119 1119
1120 1120 # i18n: column positioning for "hg log"
1121 1121 self.ui.write(_("changeset: %d:%s\n") % revnode,
1122 1122 label='log.changeset changeset.%s' % ctx.phasestr())
1123 1123
1124 1124 # branches are shown first before any other names due to backwards
1125 1125 # compatibility
1126 1126 branch = ctx.branch()
1127 1127 # don't show the default branch name
1128 1128 if branch != 'default':
1129 1129 # i18n: column positioning for "hg log"
1130 1130 self.ui.write(_("branch: %s\n") % branch,
1131 1131 label='log.branch')
1132 1132
1133 1133 for name, ns in self.repo.names.iteritems():
1134 1134 # branches has special logic already handled above, so here we just
1135 1135 # skip it
1136 1136 if name == 'branches':
1137 1137 continue
1138 1138 # we will use the templatename as the color name since those two
1139 1139 # should be the same
1140 1140 for name in ns.names(self.repo, changenode):
1141 1141 self.ui.write(ns.logfmt % name,
1142 1142 label='log.%s' % ns.colorname)
1143 1143 if self.ui.debugflag:
1144 1144 # i18n: column positioning for "hg log"
1145 1145 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
1146 1146 label='log.phase')
1147 1147 for pctx in self._meaningful_parentrevs(ctx):
1148 1148 label = 'log.parent changeset.%s' % pctx.phasestr()
1149 1149 # i18n: column positioning for "hg log"
1150 1150 self.ui.write(_("parent: %d:%s\n")
1151 1151 % (pctx.rev(), hexfunc(pctx.node())),
1152 1152 label=label)
1153 1153
1154 1154 if self.ui.debugflag and rev is not None:
1155 1155 mnode = ctx.manifestnode()
1156 1156 # i18n: column positioning for "hg log"
1157 1157 self.ui.write(_("manifest: %d:%s\n") %
1158 1158 (self.repo.manifest.rev(mnode), hex(mnode)),
1159 1159 label='ui.debug log.manifest')
1160 1160 # i18n: column positioning for "hg log"
1161 1161 self.ui.write(_("user: %s\n") % ctx.user(),
1162 1162 label='log.user')
1163 1163 # i18n: column positioning for "hg log"
1164 1164 self.ui.write(_("date: %s\n") % date,
1165 1165 label='log.date')
1166 1166
1167 1167 if self.ui.debugflag:
1168 1168 files = ctx.p1().status(ctx)[:3]
1169 1169 for key, value in zip([# i18n: column positioning for "hg log"
1170 1170 _("files:"),
1171 1171 # i18n: column positioning for "hg log"
1172 1172 _("files+:"),
1173 1173 # i18n: column positioning for "hg log"
1174 1174 _("files-:")], files):
1175 1175 if value:
1176 1176 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1177 1177 label='ui.debug log.files')
1178 1178 elif ctx.files() and self.ui.verbose:
1179 1179 # i18n: column positioning for "hg log"
1180 1180 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1181 1181 label='ui.note log.files')
1182 1182 if copies and self.ui.verbose:
1183 1183 copies = ['%s (%s)' % c for c in copies]
1184 1184 # i18n: column positioning for "hg log"
1185 1185 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1186 1186 label='ui.note log.copies')
1187 1187
1188 1188 extra = ctx.extra()
1189 1189 if extra and self.ui.debugflag:
1190 1190 for key, value in sorted(extra.items()):
1191 1191 # i18n: column positioning for "hg log"
1192 1192 self.ui.write(_("extra: %s=%s\n")
1193 1193 % (key, value.encode('string_escape')),
1194 1194 label='ui.debug log.extra')
1195 1195
1196 1196 description = ctx.description().strip()
1197 1197 if description:
1198 1198 if self.ui.verbose:
1199 1199 self.ui.write(_("description:\n"),
1200 1200 label='ui.note log.description')
1201 1201 self.ui.write(description,
1202 1202 label='ui.note log.description')
1203 1203 self.ui.write("\n\n")
1204 1204 else:
1205 1205 # i18n: column positioning for "hg log"
1206 1206 self.ui.write(_("summary: %s\n") %
1207 1207 description.splitlines()[0],
1208 1208 label='log.summary')
1209 1209 self.ui.write("\n")
1210 1210
1211 1211 self.showpatch(changenode, matchfn)
1212 1212
1213 1213 def showpatch(self, node, matchfn):
1214 1214 if not matchfn:
1215 1215 matchfn = self.matchfn
1216 1216 if matchfn:
1217 1217 stat = self.diffopts.get('stat')
1218 1218 diff = self.diffopts.get('patch')
1219 1219 diffopts = patch.diffallopts(self.ui, self.diffopts)
1220 1220 prev = self.repo.changelog.parents(node)[0]
1221 1221 if stat:
1222 1222 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1223 1223 match=matchfn, stat=True)
1224 1224 if diff:
1225 1225 if stat:
1226 1226 self.ui.write("\n")
1227 1227 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1228 1228 match=matchfn, stat=False)
1229 1229 self.ui.write("\n")
1230 1230
1231 1231 def _meaningful_parentrevs(self, ctx):
1232 1232 """Return list of meaningful (or all if debug) parentrevs for rev.
1233 1233
1234 1234 For merges (two non-nullrev revisions) both parents are meaningful.
1235 1235 Otherwise the first parent revision is considered meaningful if it
1236 1236 is not the preceding revision.
1237 1237 """
1238 1238 parents = ctx.parents()
1239 1239 if len(parents) > 1:
1240 1240 return parents
1241 1241 if self.ui.debugflag:
1242 1242 return [parents[0], self.repo['null']]
1243 1243 if parents[0].rev() >= scmutil.intrev(self.repo, ctx.rev()) - 1:
1244 1244 return []
1245 1245 return parents
1246 1246
1247 1247 class jsonchangeset(changeset_printer):
1248 1248 '''format changeset information.'''
1249 1249
1250 1250 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1251 1251 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1252 1252 self.cache = {}
1253 1253 self._first = True
1254 1254
1255 1255 def close(self):
1256 1256 if not self._first:
1257 1257 self.ui.write("\n]\n")
1258 1258 else:
1259 1259 self.ui.write("[]\n")
1260 1260
1261 1261 def _show(self, ctx, copies, matchfn, props):
1262 1262 '''show a single changeset or file revision'''
1263 1263 rev = ctx.rev()
1264 1264 if rev is None:
1265 1265 jrev = jnode = 'null'
1266 1266 else:
1267 1267 jrev = str(rev)
1268 1268 jnode = '"%s"' % hex(ctx.node())
1269 1269 j = encoding.jsonescape
1270 1270
1271 1271 if self._first:
1272 1272 self.ui.write("[\n {")
1273 1273 self._first = False
1274 1274 else:
1275 1275 self.ui.write(",\n {")
1276 1276
1277 1277 if self.ui.quiet:
1278 1278 self.ui.write('\n "rev": %s' % jrev)
1279 1279 self.ui.write(',\n "node": %s' % jnode)
1280 1280 self.ui.write('\n }')
1281 1281 return
1282 1282
1283 1283 self.ui.write('\n "rev": %s' % jrev)
1284 1284 self.ui.write(',\n "node": %s' % jnode)
1285 1285 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1286 1286 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1287 1287 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1288 1288 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1289 1289 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1290 1290
1291 1291 self.ui.write(',\n "bookmarks": [%s]' %
1292 1292 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1293 1293 self.ui.write(',\n "tags": [%s]' %
1294 1294 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1295 1295 self.ui.write(',\n "parents": [%s]' %
1296 1296 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1297 1297
1298 1298 if self.ui.debugflag:
1299 self.ui.write(',\n "manifest": "%s"' % hex(ctx.manifestnode()))
1299 if rev is None:
1300 jmanifestnode = 'null'
1301 else:
1302 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1303 self.ui.write(',\n "manifest": %s' % jmanifestnode)
1300 1304
1301 1305 self.ui.write(',\n "extra": {%s}' %
1302 1306 ", ".join('"%s": "%s"' % (j(k), j(v))
1303 1307 for k, v in ctx.extra().items()))
1304 1308
1305 1309 files = ctx.p1().status(ctx)
1306 1310 self.ui.write(',\n "modified": [%s]' %
1307 1311 ", ".join('"%s"' % j(f) for f in files[0]))
1308 1312 self.ui.write(',\n "added": [%s]' %
1309 1313 ", ".join('"%s"' % j(f) for f in files[1]))
1310 1314 self.ui.write(',\n "removed": [%s]' %
1311 1315 ", ".join('"%s"' % j(f) for f in files[2]))
1312 1316
1313 1317 elif self.ui.verbose:
1314 1318 self.ui.write(',\n "files": [%s]' %
1315 1319 ", ".join('"%s"' % j(f) for f in ctx.files()))
1316 1320
1317 1321 if copies:
1318 1322 self.ui.write(',\n "copies": {%s}' %
1319 1323 ", ".join('"%s": "%s"' % (j(k), j(v))
1320 1324 for k, v in copies))
1321 1325
1322 1326 matchfn = self.matchfn
1323 1327 if matchfn:
1324 1328 stat = self.diffopts.get('stat')
1325 1329 diff = self.diffopts.get('patch')
1326 1330 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1327 1331 node, prev = ctx.node(), ctx.p1().node()
1328 1332 if stat:
1329 1333 self.ui.pushbuffer()
1330 1334 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1331 1335 match=matchfn, stat=True)
1332 1336 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1333 1337 if diff:
1334 1338 self.ui.pushbuffer()
1335 1339 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1336 1340 match=matchfn, stat=False)
1337 1341 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1338 1342
1339 1343 self.ui.write("\n }")
1340 1344
1341 1345 class changeset_templater(changeset_printer):
1342 1346 '''format changeset information.'''
1343 1347
1344 1348 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1345 1349 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1346 1350 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1347 1351 defaulttempl = {
1348 1352 'parent': '{rev}:{node|formatnode} ',
1349 1353 'manifest': '{rev}:{node|formatnode}',
1350 1354 'file_copy': '{name} ({source})',
1351 1355 'extra': '{key}={value|stringescape}'
1352 1356 }
1353 1357 # filecopy is preserved for compatibility reasons
1354 1358 defaulttempl['filecopy'] = defaulttempl['file_copy']
1355 1359 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1356 1360 cache=defaulttempl)
1357 1361 if tmpl:
1358 1362 self.t.cache['changeset'] = tmpl
1359 1363
1360 1364 self.cache = {}
1361 1365
1362 1366 def _show(self, ctx, copies, matchfn, props):
1363 1367 '''show a single changeset or file revision'''
1364 1368
1365 1369 showlist = templatekw.showlist
1366 1370
1367 1371 # showparents() behaviour depends on ui trace level which
1368 1372 # causes unexpected behaviours at templating level and makes
1369 1373 # it harder to extract it in a standalone function. Its
1370 1374 # behaviour cannot be changed so leave it here for now.
1371 1375 def showparents(**args):
1372 1376 ctx = args['ctx']
1373 1377 parents = [[('rev', p.rev()),
1374 1378 ('node', p.hex()),
1375 1379 ('phase', p.phasestr())]
1376 1380 for p in self._meaningful_parentrevs(ctx)]
1377 1381 return showlist('parent', parents, **args)
1378 1382
1379 1383 props = props.copy()
1380 1384 props.update(templatekw.keywords)
1381 1385 props['parents'] = showparents
1382 1386 props['templ'] = self.t
1383 1387 props['ctx'] = ctx
1384 1388 props['repo'] = self.repo
1385 1389 props['revcache'] = {'copies': copies}
1386 1390 props['cache'] = self.cache
1387 1391
1388 1392 # find correct templates for current mode
1389 1393
1390 1394 tmplmodes = [
1391 1395 (True, None),
1392 1396 (self.ui.verbose, 'verbose'),
1393 1397 (self.ui.quiet, 'quiet'),
1394 1398 (self.ui.debugflag, 'debug'),
1395 1399 ]
1396 1400
1397 1401 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1398 1402 for mode, postfix in tmplmodes:
1399 1403 for type in types:
1400 1404 cur = postfix and ('%s_%s' % (type, postfix)) or type
1401 1405 if mode and cur in self.t:
1402 1406 types[type] = cur
1403 1407
1404 1408 try:
1405 1409
1406 1410 # write header
1407 1411 if types['header']:
1408 1412 h = templater.stringify(self.t(types['header'], **props))
1409 1413 if self.buffered:
1410 1414 self.header[ctx.rev()] = h
1411 1415 else:
1412 1416 if self.lastheader != h:
1413 1417 self.lastheader = h
1414 1418 self.ui.write(h)
1415 1419
1416 1420 # write changeset metadata, then patch if requested
1417 1421 key = types['changeset']
1418 1422 self.ui.write(templater.stringify(self.t(key, **props)))
1419 1423 self.showpatch(ctx.node(), matchfn)
1420 1424
1421 1425 if types['footer']:
1422 1426 if not self.footer:
1423 1427 self.footer = templater.stringify(self.t(types['footer'],
1424 1428 **props))
1425 1429
1426 1430 except KeyError, inst:
1427 1431 msg = _("%s: no key named '%s'")
1428 1432 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1429 1433 except SyntaxError, inst:
1430 1434 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1431 1435
1432 1436 def gettemplate(ui, tmpl, style):
1433 1437 """
1434 1438 Find the template matching the given template spec or style.
1435 1439 """
1436 1440
1437 1441 # ui settings
1438 1442 if not tmpl and not style: # template are stronger than style
1439 1443 tmpl = ui.config('ui', 'logtemplate')
1440 1444 if tmpl:
1441 1445 try:
1442 1446 tmpl = templater.parsestring(tmpl)
1443 1447 except SyntaxError:
1444 1448 tmpl = templater.parsestring(tmpl, quoted=False)
1445 1449 return tmpl, None
1446 1450 else:
1447 1451 style = util.expandpath(ui.config('ui', 'style', ''))
1448 1452
1449 1453 if not tmpl and style:
1450 1454 mapfile = style
1451 1455 if not os.path.split(mapfile)[0]:
1452 1456 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1453 1457 or templater.templatepath(mapfile))
1454 1458 if mapname:
1455 1459 mapfile = mapname
1456 1460 return None, mapfile
1457 1461
1458 1462 if not tmpl:
1459 1463 return None, None
1460 1464
1461 1465 # looks like a literal template?
1462 1466 if '{' in tmpl:
1463 1467 return tmpl, None
1464 1468
1465 1469 # perhaps a stock style?
1466 1470 if not os.path.split(tmpl)[0]:
1467 1471 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1468 1472 or templater.templatepath(tmpl))
1469 1473 if mapname and os.path.isfile(mapname):
1470 1474 return None, mapname
1471 1475
1472 1476 # perhaps it's a reference to [templates]
1473 1477 t = ui.config('templates', tmpl)
1474 1478 if t:
1475 1479 try:
1476 1480 tmpl = templater.parsestring(t)
1477 1481 except SyntaxError:
1478 1482 tmpl = templater.parsestring(t, quoted=False)
1479 1483 return tmpl, None
1480 1484
1481 1485 if tmpl == 'list':
1482 1486 ui.write(_("available styles: %s\n") % templater.stylelist())
1483 1487 raise util.Abort(_("specify a template"))
1484 1488
1485 1489 # perhaps it's a path to a map or a template
1486 1490 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1487 1491 # is it a mapfile for a style?
1488 1492 if os.path.basename(tmpl).startswith("map-"):
1489 1493 return None, os.path.realpath(tmpl)
1490 1494 tmpl = open(tmpl).read()
1491 1495 return tmpl, None
1492 1496
1493 1497 # constant string?
1494 1498 return tmpl, None
1495 1499
1496 1500 def show_changeset(ui, repo, opts, buffered=False):
1497 1501 """show one changeset using template or regular display.
1498 1502
1499 1503 Display format will be the first non-empty hit of:
1500 1504 1. option 'template'
1501 1505 2. option 'style'
1502 1506 3. [ui] setting 'logtemplate'
1503 1507 4. [ui] setting 'style'
1504 1508 If all of these values are either the unset or the empty string,
1505 1509 regular display via changeset_printer() is done.
1506 1510 """
1507 1511 # options
1508 1512 matchfn = None
1509 1513 if opts.get('patch') or opts.get('stat'):
1510 1514 matchfn = scmutil.matchall(repo)
1511 1515
1512 1516 if opts.get('template') == 'json':
1513 1517 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1514 1518
1515 1519 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1516 1520
1517 1521 if not tmpl and not mapfile:
1518 1522 return changeset_printer(ui, repo, matchfn, opts, buffered)
1519 1523
1520 1524 try:
1521 1525 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1522 1526 buffered)
1523 1527 except SyntaxError, inst:
1524 1528 raise util.Abort(inst.args[0])
1525 1529 return t
1526 1530
1527 1531 def showmarker(ui, marker):
1528 1532 """utility function to display obsolescence marker in a readable way
1529 1533
1530 1534 To be used by debug function."""
1531 1535 ui.write(hex(marker.precnode()))
1532 1536 for repl in marker.succnodes():
1533 1537 ui.write(' ')
1534 1538 ui.write(hex(repl))
1535 1539 ui.write(' %X ' % marker.flags())
1536 1540 parents = marker.parentnodes()
1537 1541 if parents is not None:
1538 1542 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1539 1543 ui.write('(%s) ' % util.datestr(marker.date()))
1540 1544 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1541 1545 sorted(marker.metadata().items())
1542 1546 if t[0] != 'date')))
1543 1547 ui.write('\n')
1544 1548
1545 1549 def finddate(ui, repo, date):
1546 1550 """Find the tipmost changeset that matches the given date spec"""
1547 1551
1548 1552 df = util.matchdate(date)
1549 1553 m = scmutil.matchall(repo)
1550 1554 results = {}
1551 1555
1552 1556 def prep(ctx, fns):
1553 1557 d = ctx.date()
1554 1558 if df(d[0]):
1555 1559 results[ctx.rev()] = d
1556 1560
1557 1561 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1558 1562 rev = ctx.rev()
1559 1563 if rev in results:
1560 1564 ui.status(_("found revision %s from %s\n") %
1561 1565 (rev, util.datestr(results[rev])))
1562 1566 return str(rev)
1563 1567
1564 1568 raise util.Abort(_("revision matching date not found"))
1565 1569
1566 1570 def increasingwindows(windowsize=8, sizelimit=512):
1567 1571 while True:
1568 1572 yield windowsize
1569 1573 if windowsize < sizelimit:
1570 1574 windowsize *= 2
1571 1575
1572 1576 class FileWalkError(Exception):
1573 1577 pass
1574 1578
1575 1579 def walkfilerevs(repo, match, follow, revs, fncache):
1576 1580 '''Walks the file history for the matched files.
1577 1581
1578 1582 Returns the changeset revs that are involved in the file history.
1579 1583
1580 1584 Throws FileWalkError if the file history can't be walked using
1581 1585 filelogs alone.
1582 1586 '''
1583 1587 wanted = set()
1584 1588 copies = []
1585 1589 minrev, maxrev = min(revs), max(revs)
1586 1590 def filerevgen(filelog, last):
1587 1591 """
1588 1592 Only files, no patterns. Check the history of each file.
1589 1593
1590 1594 Examines filelog entries within minrev, maxrev linkrev range
1591 1595 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1592 1596 tuples in backwards order
1593 1597 """
1594 1598 cl_count = len(repo)
1595 1599 revs = []
1596 1600 for j in xrange(0, last + 1):
1597 1601 linkrev = filelog.linkrev(j)
1598 1602 if linkrev < minrev:
1599 1603 continue
1600 1604 # only yield rev for which we have the changelog, it can
1601 1605 # happen while doing "hg log" during a pull or commit
1602 1606 if linkrev >= cl_count:
1603 1607 break
1604 1608
1605 1609 parentlinkrevs = []
1606 1610 for p in filelog.parentrevs(j):
1607 1611 if p != nullrev:
1608 1612 parentlinkrevs.append(filelog.linkrev(p))
1609 1613 n = filelog.node(j)
1610 1614 revs.append((linkrev, parentlinkrevs,
1611 1615 follow and filelog.renamed(n)))
1612 1616
1613 1617 return reversed(revs)
1614 1618 def iterfiles():
1615 1619 pctx = repo['.']
1616 1620 for filename in match.files():
1617 1621 if follow:
1618 1622 if filename not in pctx:
1619 1623 raise util.Abort(_('cannot follow file not in parent '
1620 1624 'revision: "%s"') % filename)
1621 1625 yield filename, pctx[filename].filenode()
1622 1626 else:
1623 1627 yield filename, None
1624 1628 for filename_node in copies:
1625 1629 yield filename_node
1626 1630
1627 1631 for file_, node in iterfiles():
1628 1632 filelog = repo.file(file_)
1629 1633 if not len(filelog):
1630 1634 if node is None:
1631 1635 # A zero count may be a directory or deleted file, so
1632 1636 # try to find matching entries on the slow path.
1633 1637 if follow:
1634 1638 raise util.Abort(
1635 1639 _('cannot follow nonexistent file: "%s"') % file_)
1636 1640 raise FileWalkError("Cannot walk via filelog")
1637 1641 else:
1638 1642 continue
1639 1643
1640 1644 if node is None:
1641 1645 last = len(filelog) - 1
1642 1646 else:
1643 1647 last = filelog.rev(node)
1644 1648
1645 1649 # keep track of all ancestors of the file
1646 1650 ancestors = set([filelog.linkrev(last)])
1647 1651
1648 1652 # iterate from latest to oldest revision
1649 1653 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1650 1654 if not follow:
1651 1655 if rev > maxrev:
1652 1656 continue
1653 1657 else:
1654 1658 # Note that last might not be the first interesting
1655 1659 # rev to us:
1656 1660 # if the file has been changed after maxrev, we'll
1657 1661 # have linkrev(last) > maxrev, and we still need
1658 1662 # to explore the file graph
1659 1663 if rev not in ancestors:
1660 1664 continue
1661 1665 # XXX insert 1327 fix here
1662 1666 if flparentlinkrevs:
1663 1667 ancestors.update(flparentlinkrevs)
1664 1668
1665 1669 fncache.setdefault(rev, []).append(file_)
1666 1670 wanted.add(rev)
1667 1671 if copied:
1668 1672 copies.append(copied)
1669 1673
1670 1674 return wanted
1671 1675
1672 1676 class _followfilter(object):
1673 1677 def __init__(self, repo, onlyfirst=False):
1674 1678 self.repo = repo
1675 1679 self.startrev = nullrev
1676 1680 self.roots = set()
1677 1681 self.onlyfirst = onlyfirst
1678 1682
1679 1683 def match(self, rev):
1680 1684 def realparents(rev):
1681 1685 if self.onlyfirst:
1682 1686 return self.repo.changelog.parentrevs(rev)[0:1]
1683 1687 else:
1684 1688 return filter(lambda x: x != nullrev,
1685 1689 self.repo.changelog.parentrevs(rev))
1686 1690
1687 1691 if self.startrev == nullrev:
1688 1692 self.startrev = rev
1689 1693 return True
1690 1694
1691 1695 if rev > self.startrev:
1692 1696 # forward: all descendants
1693 1697 if not self.roots:
1694 1698 self.roots.add(self.startrev)
1695 1699 for parent in realparents(rev):
1696 1700 if parent in self.roots:
1697 1701 self.roots.add(rev)
1698 1702 return True
1699 1703 else:
1700 1704 # backwards: all parents
1701 1705 if not self.roots:
1702 1706 self.roots.update(realparents(self.startrev))
1703 1707 if rev in self.roots:
1704 1708 self.roots.remove(rev)
1705 1709 self.roots.update(realparents(rev))
1706 1710 return True
1707 1711
1708 1712 return False
1709 1713
1710 1714 def walkchangerevs(repo, match, opts, prepare):
1711 1715 '''Iterate over files and the revs in which they changed.
1712 1716
1713 1717 Callers most commonly need to iterate backwards over the history
1714 1718 in which they are interested. Doing so has awful (quadratic-looking)
1715 1719 performance, so we use iterators in a "windowed" way.
1716 1720
1717 1721 We walk a window of revisions in the desired order. Within the
1718 1722 window, we first walk forwards to gather data, then in the desired
1719 1723 order (usually backwards) to display it.
1720 1724
1721 1725 This function returns an iterator yielding contexts. Before
1722 1726 yielding each context, the iterator will first call the prepare
1723 1727 function on each context in the window in forward order.'''
1724 1728
1725 1729 follow = opts.get('follow') or opts.get('follow_first')
1726 1730 revs = _logrevs(repo, opts)
1727 1731 if not revs:
1728 1732 return []
1729 1733 wanted = set()
1730 1734 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1731 1735 fncache = {}
1732 1736 change = repo.changectx
1733 1737
1734 1738 # First step is to fill wanted, the set of revisions that we want to yield.
1735 1739 # When it does not induce extra cost, we also fill fncache for revisions in
1736 1740 # wanted: a cache of filenames that were changed (ctx.files()) and that
1737 1741 # match the file filtering conditions.
1738 1742
1739 1743 if match.always():
1740 1744 # No files, no patterns. Display all revs.
1741 1745 wanted = revs
1742 1746
1743 1747 if not slowpath and match.files():
1744 1748 # We only have to read through the filelog to find wanted revisions
1745 1749
1746 1750 try:
1747 1751 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1748 1752 except FileWalkError:
1749 1753 slowpath = True
1750 1754
1751 1755 # We decided to fall back to the slowpath because at least one
1752 1756 # of the paths was not a file. Check to see if at least one of them
1753 1757 # existed in history, otherwise simply return
1754 1758 for path in match.files():
1755 1759 if path == '.' or path in repo.store:
1756 1760 break
1757 1761 else:
1758 1762 return []
1759 1763
1760 1764 if slowpath:
1761 1765 # We have to read the changelog to match filenames against
1762 1766 # changed files
1763 1767
1764 1768 if follow:
1765 1769 raise util.Abort(_('can only follow copies/renames for explicit '
1766 1770 'filenames'))
1767 1771
1768 1772 # The slow path checks files modified in every changeset.
1769 1773 # This is really slow on large repos, so compute the set lazily.
1770 1774 class lazywantedset(object):
1771 1775 def __init__(self):
1772 1776 self.set = set()
1773 1777 self.revs = set(revs)
1774 1778
1775 1779 # No need to worry about locality here because it will be accessed
1776 1780 # in the same order as the increasing window below.
1777 1781 def __contains__(self, value):
1778 1782 if value in self.set:
1779 1783 return True
1780 1784 elif not value in self.revs:
1781 1785 return False
1782 1786 else:
1783 1787 self.revs.discard(value)
1784 1788 ctx = change(value)
1785 1789 matches = filter(match, ctx.files())
1786 1790 if matches:
1787 1791 fncache[value] = matches
1788 1792 self.set.add(value)
1789 1793 return True
1790 1794 return False
1791 1795
1792 1796 def discard(self, value):
1793 1797 self.revs.discard(value)
1794 1798 self.set.discard(value)
1795 1799
1796 1800 wanted = lazywantedset()
1797 1801
1798 1802 # it might be worthwhile to do this in the iterator if the rev range
1799 1803 # is descending and the prune args are all within that range
1800 1804 for rev in opts.get('prune', ()):
1801 1805 rev = repo[rev].rev()
1802 1806 ff = _followfilter(repo)
1803 1807 stop = min(revs[0], revs[-1])
1804 1808 for x in xrange(rev, stop - 1, -1):
1805 1809 if ff.match(x):
1806 1810 wanted = wanted - [x]
1807 1811
1808 1812 # Now that wanted is correctly initialized, we can iterate over the
1809 1813 # revision range, yielding only revisions in wanted.
1810 1814 def iterate():
1811 1815 if follow and not match.files():
1812 1816 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1813 1817 def want(rev):
1814 1818 return ff.match(rev) and rev in wanted
1815 1819 else:
1816 1820 def want(rev):
1817 1821 return rev in wanted
1818 1822
1819 1823 it = iter(revs)
1820 1824 stopiteration = False
1821 1825 for windowsize in increasingwindows():
1822 1826 nrevs = []
1823 1827 for i in xrange(windowsize):
1824 1828 try:
1825 1829 rev = it.next()
1826 1830 if want(rev):
1827 1831 nrevs.append(rev)
1828 1832 except (StopIteration):
1829 1833 stopiteration = True
1830 1834 break
1831 1835 for rev in sorted(nrevs):
1832 1836 fns = fncache.get(rev)
1833 1837 ctx = change(rev)
1834 1838 if not fns:
1835 1839 def fns_generator():
1836 1840 for f in ctx.files():
1837 1841 if match(f):
1838 1842 yield f
1839 1843 fns = fns_generator()
1840 1844 prepare(ctx, fns)
1841 1845 for rev in nrevs:
1842 1846 yield change(rev)
1843 1847
1844 1848 if stopiteration:
1845 1849 break
1846 1850
1847 1851 return iterate()
1848 1852
1849 1853 def _makefollowlogfilematcher(repo, files, followfirst):
1850 1854 # When displaying a revision with --patch --follow FILE, we have
1851 1855 # to know which file of the revision must be diffed. With
1852 1856 # --follow, we want the names of the ancestors of FILE in the
1853 1857 # revision, stored in "fcache". "fcache" is populated by
1854 1858 # reproducing the graph traversal already done by --follow revset
1855 1859 # and relating linkrevs to file names (which is not "correct" but
1856 1860 # good enough).
1857 1861 fcache = {}
1858 1862 fcacheready = [False]
1859 1863 pctx = repo['.']
1860 1864
1861 1865 def populate():
1862 1866 for fn in files:
1863 1867 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1864 1868 for c in i:
1865 1869 fcache.setdefault(c.linkrev(), set()).add(c.path())
1866 1870
1867 1871 def filematcher(rev):
1868 1872 if not fcacheready[0]:
1869 1873 # Lazy initialization
1870 1874 fcacheready[0] = True
1871 1875 populate()
1872 1876 return scmutil.matchfiles(repo, fcache.get(rev, []))
1873 1877
1874 1878 return filematcher
1875 1879
1876 1880 def _makenofollowlogfilematcher(repo, pats, opts):
1877 1881 '''hook for extensions to override the filematcher for non-follow cases'''
1878 1882 return None
1879 1883
1880 1884 def _makelogrevset(repo, pats, opts, revs):
1881 1885 """Return (expr, filematcher) where expr is a revset string built
1882 1886 from log options and file patterns or None. If --stat or --patch
1883 1887 are not passed filematcher is None. Otherwise it is a callable
1884 1888 taking a revision number and returning a match objects filtering
1885 1889 the files to be detailed when displaying the revision.
1886 1890 """
1887 1891 opt2revset = {
1888 1892 'no_merges': ('not merge()', None),
1889 1893 'only_merges': ('merge()', None),
1890 1894 '_ancestors': ('ancestors(%(val)s)', None),
1891 1895 '_fancestors': ('_firstancestors(%(val)s)', None),
1892 1896 '_descendants': ('descendants(%(val)s)', None),
1893 1897 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1894 1898 '_matchfiles': ('_matchfiles(%(val)s)', None),
1895 1899 'date': ('date(%(val)r)', None),
1896 1900 'branch': ('branch(%(val)r)', ' or '),
1897 1901 '_patslog': ('filelog(%(val)r)', ' or '),
1898 1902 '_patsfollow': ('follow(%(val)r)', ' or '),
1899 1903 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1900 1904 'keyword': ('keyword(%(val)r)', ' or '),
1901 1905 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1902 1906 'user': ('user(%(val)r)', ' or '),
1903 1907 }
1904 1908
1905 1909 opts = dict(opts)
1906 1910 # follow or not follow?
1907 1911 follow = opts.get('follow') or opts.get('follow_first')
1908 1912 if opts.get('follow_first'):
1909 1913 followfirst = 1
1910 1914 else:
1911 1915 followfirst = 0
1912 1916 # --follow with FILE behaviour depends on revs...
1913 1917 it = iter(revs)
1914 1918 startrev = it.next()
1915 1919 try:
1916 1920 followdescendants = startrev < it.next()
1917 1921 except (StopIteration):
1918 1922 followdescendants = False
1919 1923
1920 1924 # branch and only_branch are really aliases and must be handled at
1921 1925 # the same time
1922 1926 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1923 1927 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1924 1928 # pats/include/exclude are passed to match.match() directly in
1925 1929 # _matchfiles() revset but walkchangerevs() builds its matcher with
1926 1930 # scmutil.match(). The difference is input pats are globbed on
1927 1931 # platforms without shell expansion (windows).
1928 1932 wctx = repo[None]
1929 1933 match, pats = scmutil.matchandpats(wctx, pats, opts)
1930 1934 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1931 1935 if not slowpath:
1932 1936 for f in match.files():
1933 1937 if follow and f not in wctx:
1934 1938 # If the file exists, it may be a directory, so let it
1935 1939 # take the slow path.
1936 1940 if os.path.exists(repo.wjoin(f)):
1937 1941 slowpath = True
1938 1942 continue
1939 1943 else:
1940 1944 raise util.Abort(_('cannot follow file not in parent '
1941 1945 'revision: "%s"') % f)
1942 1946 filelog = repo.file(f)
1943 1947 if not filelog:
1944 1948 # A zero count may be a directory or deleted file, so
1945 1949 # try to find matching entries on the slow path.
1946 1950 if follow:
1947 1951 raise util.Abort(
1948 1952 _('cannot follow nonexistent file: "%s"') % f)
1949 1953 slowpath = True
1950 1954
1951 1955 # We decided to fall back to the slowpath because at least one
1952 1956 # of the paths was not a file. Check to see if at least one of them
1953 1957 # existed in history - in that case, we'll continue down the
1954 1958 # slowpath; otherwise, we can turn off the slowpath
1955 1959 if slowpath:
1956 1960 for path in match.files():
1957 1961 if path == '.' or path in repo.store:
1958 1962 break
1959 1963 else:
1960 1964 slowpath = False
1961 1965
1962 1966 fpats = ('_patsfollow', '_patsfollowfirst')
1963 1967 fnopats = (('_ancestors', '_fancestors'),
1964 1968 ('_descendants', '_fdescendants'))
1965 1969 if slowpath:
1966 1970 # See walkchangerevs() slow path.
1967 1971 #
1968 1972 # pats/include/exclude cannot be represented as separate
1969 1973 # revset expressions as their filtering logic applies at file
1970 1974 # level. For instance "-I a -X a" matches a revision touching
1971 1975 # "a" and "b" while "file(a) and not file(b)" does
1972 1976 # not. Besides, filesets are evaluated against the working
1973 1977 # directory.
1974 1978 matchargs = ['r:', 'd:relpath']
1975 1979 for p in pats:
1976 1980 matchargs.append('p:' + p)
1977 1981 for p in opts.get('include', []):
1978 1982 matchargs.append('i:' + p)
1979 1983 for p in opts.get('exclude', []):
1980 1984 matchargs.append('x:' + p)
1981 1985 matchargs = ','.join(('%r' % p) for p in matchargs)
1982 1986 opts['_matchfiles'] = matchargs
1983 1987 if follow:
1984 1988 opts[fnopats[0][followfirst]] = '.'
1985 1989 else:
1986 1990 if follow:
1987 1991 if pats:
1988 1992 # follow() revset interprets its file argument as a
1989 1993 # manifest entry, so use match.files(), not pats.
1990 1994 opts[fpats[followfirst]] = list(match.files())
1991 1995 else:
1992 1996 op = fnopats[followdescendants][followfirst]
1993 1997 opts[op] = 'rev(%d)' % startrev
1994 1998 else:
1995 1999 opts['_patslog'] = list(pats)
1996 2000
1997 2001 filematcher = None
1998 2002 if opts.get('patch') or opts.get('stat'):
1999 2003 # When following files, track renames via a special matcher.
2000 2004 # If we're forced to take the slowpath it means we're following
2001 2005 # at least one pattern/directory, so don't bother with rename tracking.
2002 2006 if follow and not match.always() and not slowpath:
2003 2007 # _makefollowlogfilematcher expects its files argument to be
2004 2008 # relative to the repo root, so use match.files(), not pats.
2005 2009 filematcher = _makefollowlogfilematcher(repo, match.files(),
2006 2010 followfirst)
2007 2011 else:
2008 2012 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2009 2013 if filematcher is None:
2010 2014 filematcher = lambda rev: match
2011 2015
2012 2016 expr = []
2013 2017 for op, val in sorted(opts.iteritems()):
2014 2018 if not val:
2015 2019 continue
2016 2020 if op not in opt2revset:
2017 2021 continue
2018 2022 revop, andor = opt2revset[op]
2019 2023 if '%(val)' not in revop:
2020 2024 expr.append(revop)
2021 2025 else:
2022 2026 if not isinstance(val, list):
2023 2027 e = revop % {'val': val}
2024 2028 else:
2025 2029 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2026 2030 expr.append(e)
2027 2031
2028 2032 if expr:
2029 2033 expr = '(' + ' and '.join(expr) + ')'
2030 2034 else:
2031 2035 expr = None
2032 2036 return expr, filematcher
2033 2037
2034 2038 def _logrevs(repo, opts):
2035 2039 # Default --rev value depends on --follow but --follow behaviour
2036 2040 # depends on revisions resolved from --rev...
2037 2041 follow = opts.get('follow') or opts.get('follow_first')
2038 2042 if opts.get('rev'):
2039 2043 revs = scmutil.revrange(repo, opts['rev'])
2040 2044 elif follow and repo.dirstate.p1() == nullid:
2041 2045 revs = revset.baseset()
2042 2046 elif follow:
2043 2047 revs = repo.revs('reverse(:.)')
2044 2048 else:
2045 2049 revs = revset.spanset(repo)
2046 2050 revs.reverse()
2047 2051 return revs
2048 2052
2049 2053 def getgraphlogrevs(repo, pats, opts):
2050 2054 """Return (revs, expr, filematcher) where revs is an iterable of
2051 2055 revision numbers, expr is a revset string built from log options
2052 2056 and file patterns or None, and used to filter 'revs'. If --stat or
2053 2057 --patch are not passed filematcher is None. Otherwise it is a
2054 2058 callable taking a revision number and returning a match objects
2055 2059 filtering the files to be detailed when displaying the revision.
2056 2060 """
2057 2061 limit = loglimit(opts)
2058 2062 revs = _logrevs(repo, opts)
2059 2063 if not revs:
2060 2064 return revset.baseset(), None, None
2061 2065 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2062 2066 if opts.get('rev'):
2063 2067 # User-specified revs might be unsorted, but don't sort before
2064 2068 # _makelogrevset because it might depend on the order of revs
2065 2069 revs.sort(reverse=True)
2066 2070 if expr:
2067 2071 # Revset matchers often operate faster on revisions in changelog
2068 2072 # order, because most filters deal with the changelog.
2069 2073 revs.reverse()
2070 2074 matcher = revset.match(repo.ui, expr)
2071 2075 # Revset matches can reorder revisions. "A or B" typically returns
2072 2076 # returns the revision matching A then the revision matching B. Sort
2073 2077 # again to fix that.
2074 2078 revs = matcher(repo, revs)
2075 2079 revs.sort(reverse=True)
2076 2080 if limit is not None:
2077 2081 limitedrevs = []
2078 2082 for idx, rev in enumerate(revs):
2079 2083 if idx >= limit:
2080 2084 break
2081 2085 limitedrevs.append(rev)
2082 2086 revs = revset.baseset(limitedrevs)
2083 2087
2084 2088 return revs, expr, filematcher
2085 2089
2086 2090 def getlogrevs(repo, pats, opts):
2087 2091 """Return (revs, expr, filematcher) where revs is an iterable of
2088 2092 revision numbers, expr is a revset string built from log options
2089 2093 and file patterns or None, and used to filter 'revs'. If --stat or
2090 2094 --patch are not passed filematcher is None. Otherwise it is a
2091 2095 callable taking a revision number and returning a match objects
2092 2096 filtering the files to be detailed when displaying the revision.
2093 2097 """
2094 2098 limit = loglimit(opts)
2095 2099 revs = _logrevs(repo, opts)
2096 2100 if not revs:
2097 2101 return revset.baseset([]), None, None
2098 2102 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2099 2103 if expr:
2100 2104 # Revset matchers often operate faster on revisions in changelog
2101 2105 # order, because most filters deal with the changelog.
2102 2106 if not opts.get('rev'):
2103 2107 revs.reverse()
2104 2108 matcher = revset.match(repo.ui, expr)
2105 2109 # Revset matches can reorder revisions. "A or B" typically returns
2106 2110 # returns the revision matching A then the revision matching B. Sort
2107 2111 # again to fix that.
2108 2112 revs = matcher(repo, revs)
2109 2113 if not opts.get('rev'):
2110 2114 revs.sort(reverse=True)
2111 2115 if limit is not None:
2112 2116 count = 0
2113 2117 limitedrevs = []
2114 2118 it = iter(revs)
2115 2119 while count < limit:
2116 2120 try:
2117 2121 limitedrevs.append(it.next())
2118 2122 except (StopIteration):
2119 2123 break
2120 2124 count += 1
2121 2125 revs = revset.baseset(limitedrevs)
2122 2126
2123 2127 return revs, expr, filematcher
2124 2128
2125 2129 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
2126 2130 filematcher=None):
2127 2131 seen, state = [], graphmod.asciistate()
2128 2132 for rev, type, ctx, parents in dag:
2129 2133 char = 'o'
2130 2134 if ctx.node() in showparents:
2131 2135 char = '@'
2132 2136 elif ctx.obsolete():
2133 2137 char = 'x'
2134 2138 elif ctx.closesbranch():
2135 2139 char = '_'
2136 2140 copies = None
2137 2141 if getrenamed and ctx.rev():
2138 2142 copies = []
2139 2143 for fn in ctx.files():
2140 2144 rename = getrenamed(fn, ctx.rev())
2141 2145 if rename:
2142 2146 copies.append((fn, rename[0]))
2143 2147 revmatchfn = None
2144 2148 if filematcher is not None:
2145 2149 revmatchfn = filematcher(ctx.rev())
2146 2150 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2147 2151 lines = displayer.hunk.pop(rev).split('\n')
2148 2152 if not lines[-1]:
2149 2153 del lines[-1]
2150 2154 displayer.flush(rev)
2151 2155 edges = edgefn(type, char, lines, seen, rev, parents)
2152 2156 for type, char, lines, coldata in edges:
2153 2157 graphmod.ascii(ui, state, type, char, lines, coldata)
2154 2158 displayer.close()
2155 2159
2156 2160 def graphlog(ui, repo, *pats, **opts):
2157 2161 # Parameters are identical to log command ones
2158 2162 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2159 2163 revdag = graphmod.dagwalker(repo, revs)
2160 2164
2161 2165 getrenamed = None
2162 2166 if opts.get('copies'):
2163 2167 endrev = None
2164 2168 if opts.get('rev'):
2165 2169 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2166 2170 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2167 2171 displayer = show_changeset(ui, repo, opts, buffered=True)
2168 2172 showparents = [ctx.node() for ctx in repo[None].parents()]
2169 2173 displaygraph(ui, revdag, displayer, showparents,
2170 2174 graphmod.asciiedges, getrenamed, filematcher)
2171 2175
2172 2176 def checkunsupportedgraphflags(pats, opts):
2173 2177 for op in ["newest_first"]:
2174 2178 if op in opts and opts[op]:
2175 2179 raise util.Abort(_("-G/--graph option is incompatible with --%s")
2176 2180 % op.replace("_", "-"))
2177 2181
2178 2182 def graphrevs(repo, nodes, opts):
2179 2183 limit = loglimit(opts)
2180 2184 nodes.reverse()
2181 2185 if limit is not None:
2182 2186 nodes = nodes[:limit]
2183 2187 return graphmod.nodes(repo, nodes)
2184 2188
2185 2189 def add(ui, repo, match, prefix, explicitonly, **opts):
2186 2190 join = lambda f: os.path.join(prefix, f)
2187 2191 bad = []
2188 2192 oldbad = match.bad
2189 2193 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2190 2194 names = []
2191 2195 wctx = repo[None]
2192 2196 cca = None
2193 2197 abort, warn = scmutil.checkportabilityalert(ui)
2194 2198 if abort or warn:
2195 2199 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2196 2200 for f in wctx.walk(match):
2197 2201 exact = match.exact(f)
2198 2202 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2199 2203 if cca:
2200 2204 cca(f)
2201 2205 names.append(f)
2202 2206 if ui.verbose or not exact:
2203 2207 ui.status(_('adding %s\n') % match.rel(f))
2204 2208
2205 2209 for subpath in sorted(wctx.substate):
2206 2210 sub = wctx.sub(subpath)
2207 2211 try:
2208 2212 submatch = matchmod.narrowmatcher(subpath, match)
2209 2213 if opts.get('subrepos'):
2210 2214 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2211 2215 else:
2212 2216 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2213 2217 except error.LookupError:
2214 2218 ui.status(_("skipping missing subrepository: %s\n")
2215 2219 % join(subpath))
2216 2220
2217 2221 if not opts.get('dry_run'):
2218 2222 rejected = wctx.add(names, prefix)
2219 2223 bad.extend(f for f in rejected if f in match.files())
2220 2224 return bad
2221 2225
2222 2226 def forget(ui, repo, match, prefix, explicitonly):
2223 2227 join = lambda f: os.path.join(prefix, f)
2224 2228 bad = []
2225 2229 oldbad = match.bad
2226 2230 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2227 2231 wctx = repo[None]
2228 2232 forgot = []
2229 2233 s = repo.status(match=match, clean=True)
2230 2234 forget = sorted(s[0] + s[1] + s[3] + s[6])
2231 2235 if explicitonly:
2232 2236 forget = [f for f in forget if match.exact(f)]
2233 2237
2234 2238 for subpath in sorted(wctx.substate):
2235 2239 sub = wctx.sub(subpath)
2236 2240 try:
2237 2241 submatch = matchmod.narrowmatcher(subpath, match)
2238 2242 subbad, subforgot = sub.forget(submatch, prefix)
2239 2243 bad.extend([subpath + '/' + f for f in subbad])
2240 2244 forgot.extend([subpath + '/' + f for f in subforgot])
2241 2245 except error.LookupError:
2242 2246 ui.status(_("skipping missing subrepository: %s\n")
2243 2247 % join(subpath))
2244 2248
2245 2249 if not explicitonly:
2246 2250 for f in match.files():
2247 2251 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2248 2252 if f not in forgot:
2249 2253 if repo.wvfs.exists(f):
2250 2254 # Don't complain if the exact case match wasn't given.
2251 2255 # But don't do this until after checking 'forgot', so
2252 2256 # that subrepo files aren't normalized, and this op is
2253 2257 # purely from data cached by the status walk above.
2254 2258 if repo.dirstate.normalize(f) in repo.dirstate:
2255 2259 continue
2256 2260 ui.warn(_('not removing %s: '
2257 2261 'file is already untracked\n')
2258 2262 % match.rel(f))
2259 2263 bad.append(f)
2260 2264
2261 2265 for f in forget:
2262 2266 if ui.verbose or not match.exact(f):
2263 2267 ui.status(_('removing %s\n') % match.rel(f))
2264 2268
2265 2269 rejected = wctx.forget(forget, prefix)
2266 2270 bad.extend(f for f in rejected if f in match.files())
2267 2271 forgot.extend(f for f in forget if f not in rejected)
2268 2272 return bad, forgot
2269 2273
2270 2274 def files(ui, ctx, m, fm, fmt, subrepos):
2271 2275 rev = ctx.rev()
2272 2276 ret = 1
2273 2277 ds = ctx.repo().dirstate
2274 2278
2275 2279 for f in ctx.matches(m):
2276 2280 if rev is None and ds[f] == 'r':
2277 2281 continue
2278 2282 fm.startitem()
2279 2283 if ui.verbose:
2280 2284 fc = ctx[f]
2281 2285 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2282 2286 fm.data(abspath=f)
2283 2287 fm.write('path', fmt, m.rel(f))
2284 2288 ret = 0
2285 2289
2286 2290 if subrepos:
2287 2291 for subpath in sorted(ctx.substate):
2288 2292 sub = ctx.sub(subpath)
2289 2293 try:
2290 2294 submatch = matchmod.narrowmatcher(subpath, m)
2291 2295 if sub.printfiles(ui, submatch, fm, fmt) == 0:
2292 2296 ret = 0
2293 2297 except error.LookupError:
2294 2298 ui.status(_("skipping missing subrepository: %s\n")
2295 2299 % m.abs(subpath))
2296 2300
2297 2301 return ret
2298 2302
2299 2303 def remove(ui, repo, m, prefix, after, force, subrepos):
2300 2304 join = lambda f: os.path.join(prefix, f)
2301 2305 ret = 0
2302 2306 s = repo.status(match=m, clean=True)
2303 2307 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2304 2308
2305 2309 wctx = repo[None]
2306 2310
2307 2311 for subpath in sorted(wctx.substate):
2308 2312 def matchessubrepo(matcher, subpath):
2309 2313 if matcher.exact(subpath):
2310 2314 return True
2311 2315 for f in matcher.files():
2312 2316 if f.startswith(subpath):
2313 2317 return True
2314 2318 return False
2315 2319
2316 2320 if subrepos or matchessubrepo(m, subpath):
2317 2321 sub = wctx.sub(subpath)
2318 2322 try:
2319 2323 submatch = matchmod.narrowmatcher(subpath, m)
2320 2324 if sub.removefiles(submatch, prefix, after, force, subrepos):
2321 2325 ret = 1
2322 2326 except error.LookupError:
2323 2327 ui.status(_("skipping missing subrepository: %s\n")
2324 2328 % join(subpath))
2325 2329
2326 2330 # warn about failure to delete explicit files/dirs
2327 2331 deleteddirs = scmutil.dirs(deleted)
2328 2332 for f in m.files():
2329 2333 def insubrepo():
2330 2334 for subpath in wctx.substate:
2331 2335 if f.startswith(subpath):
2332 2336 return True
2333 2337 return False
2334 2338
2335 2339 isdir = f in deleteddirs or f in wctx.dirs()
2336 2340 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2337 2341 continue
2338 2342
2339 2343 if repo.wvfs.exists(f):
2340 2344 if repo.wvfs.isdir(f):
2341 2345 ui.warn(_('not removing %s: no tracked files\n')
2342 2346 % m.rel(f))
2343 2347 else:
2344 2348 ui.warn(_('not removing %s: file is untracked\n')
2345 2349 % m.rel(f))
2346 2350 # missing files will generate a warning elsewhere
2347 2351 ret = 1
2348 2352
2349 2353 if force:
2350 2354 list = modified + deleted + clean + added
2351 2355 elif after:
2352 2356 list = deleted
2353 2357 for f in modified + added + clean:
2354 2358 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2355 2359 ret = 1
2356 2360 else:
2357 2361 list = deleted + clean
2358 2362 for f in modified:
2359 2363 ui.warn(_('not removing %s: file is modified (use -f'
2360 2364 ' to force removal)\n') % m.rel(f))
2361 2365 ret = 1
2362 2366 for f in added:
2363 2367 ui.warn(_('not removing %s: file has been marked for add'
2364 2368 ' (use forget to undo)\n') % m.rel(f))
2365 2369 ret = 1
2366 2370
2367 2371 for f in sorted(list):
2368 2372 if ui.verbose or not m.exact(f):
2369 2373 ui.status(_('removing %s\n') % m.rel(f))
2370 2374
2371 2375 wlock = repo.wlock()
2372 2376 try:
2373 2377 if not after:
2374 2378 for f in list:
2375 2379 if f in added:
2376 2380 continue # we never unlink added files on remove
2377 2381 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2378 2382 repo[None].forget(list)
2379 2383 finally:
2380 2384 wlock.release()
2381 2385
2382 2386 return ret
2383 2387
2384 2388 def cat(ui, repo, ctx, matcher, prefix, **opts):
2385 2389 err = 1
2386 2390
2387 2391 def write(path):
2388 2392 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2389 2393 pathname=os.path.join(prefix, path))
2390 2394 data = ctx[path].data()
2391 2395 if opts.get('decode'):
2392 2396 data = repo.wwritedata(path, data)
2393 2397 fp.write(data)
2394 2398 fp.close()
2395 2399
2396 2400 # Automation often uses hg cat on single files, so special case it
2397 2401 # for performance to avoid the cost of parsing the manifest.
2398 2402 if len(matcher.files()) == 1 and not matcher.anypats():
2399 2403 file = matcher.files()[0]
2400 2404 mf = repo.manifest
2401 2405 mfnode = ctx._changeset[0]
2402 2406 if mf.find(mfnode, file)[0]:
2403 2407 write(file)
2404 2408 return 0
2405 2409
2406 2410 # Don't warn about "missing" files that are really in subrepos
2407 2411 bad = matcher.bad
2408 2412
2409 2413 def badfn(path, msg):
2410 2414 for subpath in ctx.substate:
2411 2415 if path.startswith(subpath):
2412 2416 return
2413 2417 bad(path, msg)
2414 2418
2415 2419 matcher.bad = badfn
2416 2420
2417 2421 for abs in ctx.walk(matcher):
2418 2422 write(abs)
2419 2423 err = 0
2420 2424
2421 2425 matcher.bad = bad
2422 2426
2423 2427 for subpath in sorted(ctx.substate):
2424 2428 sub = ctx.sub(subpath)
2425 2429 try:
2426 2430 submatch = matchmod.narrowmatcher(subpath, matcher)
2427 2431
2428 2432 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2429 2433 **opts):
2430 2434 err = 0
2431 2435 except error.RepoLookupError:
2432 2436 ui.status(_("skipping missing subrepository: %s\n")
2433 2437 % os.path.join(prefix, subpath))
2434 2438
2435 2439 return err
2436 2440
2437 2441 def commit(ui, repo, commitfunc, pats, opts):
2438 2442 '''commit the specified files or all outstanding changes'''
2439 2443 date = opts.get('date')
2440 2444 if date:
2441 2445 opts['date'] = util.parsedate(date)
2442 2446 message = logmessage(ui, opts)
2443 2447 matcher = scmutil.match(repo[None], pats, opts)
2444 2448
2445 2449 # extract addremove carefully -- this function can be called from a command
2446 2450 # that doesn't support addremove
2447 2451 if opts.get('addremove'):
2448 2452 if scmutil.addremove(repo, matcher, "", opts) != 0:
2449 2453 raise util.Abort(
2450 2454 _("failed to mark all new/missing files as added/removed"))
2451 2455
2452 2456 return commitfunc(ui, repo, message, matcher, opts)
2453 2457
2454 2458 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2455 2459 # amend will reuse the existing user if not specified, but the obsolete
2456 2460 # marker creation requires that the current user's name is specified.
2457 2461 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2458 2462 ui.username() # raise exception if username not set
2459 2463
2460 2464 ui.note(_('amending changeset %s\n') % old)
2461 2465 base = old.p1()
2462 2466
2463 2467 wlock = lock = newid = None
2464 2468 try:
2465 2469 wlock = repo.wlock()
2466 2470 lock = repo.lock()
2467 2471 tr = repo.transaction('amend')
2468 2472 try:
2469 2473 # See if we got a message from -m or -l, if not, open the editor
2470 2474 # with the message of the changeset to amend
2471 2475 message = logmessage(ui, opts)
2472 2476 # ensure logfile does not conflict with later enforcement of the
2473 2477 # message. potential logfile content has been processed by
2474 2478 # `logmessage` anyway.
2475 2479 opts.pop('logfile')
2476 2480 # First, do a regular commit to record all changes in the working
2477 2481 # directory (if there are any)
2478 2482 ui.callhooks = False
2479 2483 currentbookmark = repo._bookmarkcurrent
2480 2484 try:
2481 2485 repo._bookmarkcurrent = None
2482 2486 opts['message'] = 'temporary amend commit for %s' % old
2483 2487 node = commit(ui, repo, commitfunc, pats, opts)
2484 2488 finally:
2485 2489 repo._bookmarkcurrent = currentbookmark
2486 2490 ui.callhooks = True
2487 2491 ctx = repo[node]
2488 2492
2489 2493 # Participating changesets:
2490 2494 #
2491 2495 # node/ctx o - new (intermediate) commit that contains changes
2492 2496 # | from working dir to go into amending commit
2493 2497 # | (or a workingctx if there were no changes)
2494 2498 # |
2495 2499 # old o - changeset to amend
2496 2500 # |
2497 2501 # base o - parent of amending changeset
2498 2502
2499 2503 # Update extra dict from amended commit (e.g. to preserve graft
2500 2504 # source)
2501 2505 extra.update(old.extra())
2502 2506
2503 2507 # Also update it from the intermediate commit or from the wctx
2504 2508 extra.update(ctx.extra())
2505 2509
2506 2510 if len(old.parents()) > 1:
2507 2511 # ctx.files() isn't reliable for merges, so fall back to the
2508 2512 # slower repo.status() method
2509 2513 files = set([fn for st in repo.status(base, old)[:3]
2510 2514 for fn in st])
2511 2515 else:
2512 2516 files = set(old.files())
2513 2517
2514 2518 # Second, we use either the commit we just did, or if there were no
2515 2519 # changes the parent of the working directory as the version of the
2516 2520 # files in the final amend commit
2517 2521 if node:
2518 2522 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2519 2523
2520 2524 user = ctx.user()
2521 2525 date = ctx.date()
2522 2526 # Recompute copies (avoid recording a -> b -> a)
2523 2527 copied = copies.pathcopies(base, ctx)
2524 2528 if old.p2:
2525 2529 copied.update(copies.pathcopies(old.p2(), ctx))
2526 2530
2527 2531 # Prune files which were reverted by the updates: if old
2528 2532 # introduced file X and our intermediate commit, node,
2529 2533 # renamed that file, then those two files are the same and
2530 2534 # we can discard X from our list of files. Likewise if X
2531 2535 # was deleted, it's no longer relevant
2532 2536 files.update(ctx.files())
2533 2537
2534 2538 def samefile(f):
2535 2539 if f in ctx.manifest():
2536 2540 a = ctx.filectx(f)
2537 2541 if f in base.manifest():
2538 2542 b = base.filectx(f)
2539 2543 return (not a.cmp(b)
2540 2544 and a.flags() == b.flags())
2541 2545 else:
2542 2546 return False
2543 2547 else:
2544 2548 return f not in base.manifest()
2545 2549 files = [f for f in files if not samefile(f)]
2546 2550
2547 2551 def filectxfn(repo, ctx_, path):
2548 2552 try:
2549 2553 fctx = ctx[path]
2550 2554 flags = fctx.flags()
2551 2555 mctx = context.memfilectx(repo,
2552 2556 fctx.path(), fctx.data(),
2553 2557 islink='l' in flags,
2554 2558 isexec='x' in flags,
2555 2559 copied=copied.get(path))
2556 2560 return mctx
2557 2561 except KeyError:
2558 2562 return None
2559 2563 else:
2560 2564 ui.note(_('copying changeset %s to %s\n') % (old, base))
2561 2565
2562 2566 # Use version of files as in the old cset
2563 2567 def filectxfn(repo, ctx_, path):
2564 2568 try:
2565 2569 return old.filectx(path)
2566 2570 except KeyError:
2567 2571 return None
2568 2572
2569 2573 user = opts.get('user') or old.user()
2570 2574 date = opts.get('date') or old.date()
2571 2575 editform = mergeeditform(old, 'commit.amend')
2572 2576 editor = getcommiteditor(editform=editform, **opts)
2573 2577 if not message:
2574 2578 editor = getcommiteditor(edit=True, editform=editform)
2575 2579 message = old.description()
2576 2580
2577 2581 pureextra = extra.copy()
2578 2582 extra['amend_source'] = old.hex()
2579 2583
2580 2584 new = context.memctx(repo,
2581 2585 parents=[base.node(), old.p2().node()],
2582 2586 text=message,
2583 2587 files=files,
2584 2588 filectxfn=filectxfn,
2585 2589 user=user,
2586 2590 date=date,
2587 2591 extra=extra,
2588 2592 editor=editor)
2589 2593
2590 2594 newdesc = changelog.stripdesc(new.description())
2591 2595 if ((not node)
2592 2596 and newdesc == old.description()
2593 2597 and user == old.user()
2594 2598 and date == old.date()
2595 2599 and pureextra == old.extra()):
2596 2600 # nothing changed. continuing here would create a new node
2597 2601 # anyway because of the amend_source noise.
2598 2602 #
2599 2603 # This not what we expect from amend.
2600 2604 return old.node()
2601 2605
2602 2606 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2603 2607 try:
2604 2608 if opts.get('secret'):
2605 2609 commitphase = 'secret'
2606 2610 else:
2607 2611 commitphase = old.phase()
2608 2612 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2609 2613 newid = repo.commitctx(new)
2610 2614 finally:
2611 2615 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2612 2616 if newid != old.node():
2613 2617 # Reroute the working copy parent to the new changeset
2614 2618 repo.setparents(newid, nullid)
2615 2619
2616 2620 # Move bookmarks from old parent to amend commit
2617 2621 bms = repo.nodebookmarks(old.node())
2618 2622 if bms:
2619 2623 marks = repo._bookmarks
2620 2624 for bm in bms:
2621 2625 marks[bm] = newid
2622 2626 marks.write()
2623 2627 #commit the whole amend process
2624 2628 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2625 2629 if createmarkers and newid != old.node():
2626 2630 # mark the new changeset as successor of the rewritten one
2627 2631 new = repo[newid]
2628 2632 obs = [(old, (new,))]
2629 2633 if node:
2630 2634 obs.append((ctx, ()))
2631 2635
2632 2636 obsolete.createmarkers(repo, obs)
2633 2637 tr.close()
2634 2638 finally:
2635 2639 tr.release()
2636 2640 if not createmarkers and newid != old.node():
2637 2641 # Strip the intermediate commit (if there was one) and the amended
2638 2642 # commit
2639 2643 if node:
2640 2644 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2641 2645 ui.note(_('stripping amended changeset %s\n') % old)
2642 2646 repair.strip(ui, repo, old.node(), topic='amend-backup')
2643 2647 finally:
2644 2648 if newid is None:
2645 2649 repo.dirstate.invalidate()
2646 2650 lockmod.release(lock, wlock)
2647 2651 return newid
2648 2652
2649 2653 def commiteditor(repo, ctx, subs, editform=''):
2650 2654 if ctx.description():
2651 2655 return ctx.description()
2652 2656 return commitforceeditor(repo, ctx, subs, editform=editform)
2653 2657
2654 2658 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2655 2659 editform=''):
2656 2660 if not extramsg:
2657 2661 extramsg = _("Leave message empty to abort commit.")
2658 2662
2659 2663 forms = [e for e in editform.split('.') if e]
2660 2664 forms.insert(0, 'changeset')
2661 2665 while forms:
2662 2666 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2663 2667 if tmpl:
2664 2668 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2665 2669 break
2666 2670 forms.pop()
2667 2671 else:
2668 2672 committext = buildcommittext(repo, ctx, subs, extramsg)
2669 2673
2670 2674 # run editor in the repository root
2671 2675 olddir = os.getcwd()
2672 2676 os.chdir(repo.root)
2673 2677 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2674 2678 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2675 2679 os.chdir(olddir)
2676 2680
2677 2681 if finishdesc:
2678 2682 text = finishdesc(text)
2679 2683 if not text.strip():
2680 2684 raise util.Abort(_("empty commit message"))
2681 2685
2682 2686 return text
2683 2687
2684 2688 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2685 2689 ui = repo.ui
2686 2690 tmpl, mapfile = gettemplate(ui, tmpl, None)
2687 2691
2688 2692 try:
2689 2693 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2690 2694 except SyntaxError, inst:
2691 2695 raise util.Abort(inst.args[0])
2692 2696
2693 2697 for k, v in repo.ui.configitems('committemplate'):
2694 2698 if k != 'changeset':
2695 2699 t.t.cache[k] = v
2696 2700
2697 2701 if not extramsg:
2698 2702 extramsg = '' # ensure that extramsg is string
2699 2703
2700 2704 ui.pushbuffer()
2701 2705 t.show(ctx, extramsg=extramsg)
2702 2706 return ui.popbuffer()
2703 2707
2704 2708 def buildcommittext(repo, ctx, subs, extramsg):
2705 2709 edittext = []
2706 2710 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2707 2711 if ctx.description():
2708 2712 edittext.append(ctx.description())
2709 2713 edittext.append("")
2710 2714 edittext.append("") # Empty line between message and comments.
2711 2715 edittext.append(_("HG: Enter commit message."
2712 2716 " Lines beginning with 'HG:' are removed."))
2713 2717 edittext.append("HG: %s" % extramsg)
2714 2718 edittext.append("HG: --")
2715 2719 edittext.append(_("HG: user: %s") % ctx.user())
2716 2720 if ctx.p2():
2717 2721 edittext.append(_("HG: branch merge"))
2718 2722 if ctx.branch():
2719 2723 edittext.append(_("HG: branch '%s'") % ctx.branch())
2720 2724 if bookmarks.iscurrent(repo):
2721 2725 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2722 2726 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2723 2727 edittext.extend([_("HG: added %s") % f for f in added])
2724 2728 edittext.extend([_("HG: changed %s") % f for f in modified])
2725 2729 edittext.extend([_("HG: removed %s") % f for f in removed])
2726 2730 if not added and not modified and not removed:
2727 2731 edittext.append(_("HG: no files changed"))
2728 2732 edittext.append("")
2729 2733
2730 2734 return "\n".join(edittext)
2731 2735
2732 2736 def commitstatus(repo, node, branch, bheads=None, opts={}):
2733 2737 ctx = repo[node]
2734 2738 parents = ctx.parents()
2735 2739
2736 2740 if (not opts.get('amend') and bheads and node not in bheads and not
2737 2741 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2738 2742 repo.ui.status(_('created new head\n'))
2739 2743 # The message is not printed for initial roots. For the other
2740 2744 # changesets, it is printed in the following situations:
2741 2745 #
2742 2746 # Par column: for the 2 parents with ...
2743 2747 # N: null or no parent
2744 2748 # B: parent is on another named branch
2745 2749 # C: parent is a regular non head changeset
2746 2750 # H: parent was a branch head of the current branch
2747 2751 # Msg column: whether we print "created new head" message
2748 2752 # In the following, it is assumed that there already exists some
2749 2753 # initial branch heads of the current branch, otherwise nothing is
2750 2754 # printed anyway.
2751 2755 #
2752 2756 # Par Msg Comment
2753 2757 # N N y additional topo root
2754 2758 #
2755 2759 # B N y additional branch root
2756 2760 # C N y additional topo head
2757 2761 # H N n usual case
2758 2762 #
2759 2763 # B B y weird additional branch root
2760 2764 # C B y branch merge
2761 2765 # H B n merge with named branch
2762 2766 #
2763 2767 # C C y additional head from merge
2764 2768 # C H n merge with a head
2765 2769 #
2766 2770 # H H n head merge: head count decreases
2767 2771
2768 2772 if not opts.get('close_branch'):
2769 2773 for r in parents:
2770 2774 if r.closesbranch() and r.branch() == branch:
2771 2775 repo.ui.status(_('reopening closed branch head %d\n') % r)
2772 2776
2773 2777 if repo.ui.debugflag:
2774 2778 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2775 2779 elif repo.ui.verbose:
2776 2780 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2777 2781
2778 2782 def revert(ui, repo, ctx, parents, *pats, **opts):
2779 2783 parent, p2 = parents
2780 2784 node = ctx.node()
2781 2785
2782 2786 mf = ctx.manifest()
2783 2787 if node == p2:
2784 2788 parent = p2
2785 2789 if node == parent:
2786 2790 pmf = mf
2787 2791 else:
2788 2792 pmf = None
2789 2793
2790 2794 # need all matching names in dirstate and manifest of target rev,
2791 2795 # so have to walk both. do not print errors if files exist in one
2792 2796 # but not other. in both cases, filesets should be evaluated against
2793 2797 # workingctx to get consistent result (issue4497). this means 'set:**'
2794 2798 # cannot be used to select missing files from target rev.
2795 2799
2796 2800 # `names` is a mapping for all elements in working copy and target revision
2797 2801 # The mapping is in the form:
2798 2802 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2799 2803 names = {}
2800 2804
2801 2805 wlock = repo.wlock()
2802 2806 try:
2803 2807 ## filling of the `names` mapping
2804 2808 # walk dirstate to fill `names`
2805 2809
2806 2810 interactive = opts.get('interactive', False)
2807 2811 wctx = repo[None]
2808 2812 m = scmutil.match(wctx, pats, opts)
2809 2813
2810 2814 # we'll need this later
2811 2815 targetsubs = sorted(s for s in wctx.substate if m(s))
2812 2816
2813 2817 if not m.always():
2814 2818 m.bad = lambda x, y: False
2815 2819 for abs in repo.walk(m):
2816 2820 names[abs] = m.rel(abs), m.exact(abs)
2817 2821
2818 2822 # walk target manifest to fill `names`
2819 2823
2820 2824 def badfn(path, msg):
2821 2825 if path in names:
2822 2826 return
2823 2827 if path in ctx.substate:
2824 2828 return
2825 2829 path_ = path + '/'
2826 2830 for f in names:
2827 2831 if f.startswith(path_):
2828 2832 return
2829 2833 ui.warn("%s: %s\n" % (m.rel(path), msg))
2830 2834
2831 2835 m.bad = badfn
2832 2836 for abs in ctx.walk(m):
2833 2837 if abs not in names:
2834 2838 names[abs] = m.rel(abs), m.exact(abs)
2835 2839
2836 2840 # Find status of all file in `names`.
2837 2841 m = scmutil.matchfiles(repo, names)
2838 2842
2839 2843 changes = repo.status(node1=node, match=m,
2840 2844 unknown=True, ignored=True, clean=True)
2841 2845 else:
2842 2846 changes = repo.status(node1=node, match=m)
2843 2847 for kind in changes:
2844 2848 for abs in kind:
2845 2849 names[abs] = m.rel(abs), m.exact(abs)
2846 2850
2847 2851 m = scmutil.matchfiles(repo, names)
2848 2852
2849 2853 modified = set(changes.modified)
2850 2854 added = set(changes.added)
2851 2855 removed = set(changes.removed)
2852 2856 _deleted = set(changes.deleted)
2853 2857 unknown = set(changes.unknown)
2854 2858 unknown.update(changes.ignored)
2855 2859 clean = set(changes.clean)
2856 2860 modadded = set()
2857 2861
2858 2862 # split between files known in target manifest and the others
2859 2863 smf = set(mf)
2860 2864
2861 2865 # determine the exact nature of the deleted changesets
2862 2866 deladded = _deleted - smf
2863 2867 deleted = _deleted - deladded
2864 2868
2865 2869 # We need to account for the state of the file in the dirstate,
2866 2870 # even when we revert against something else than parent. This will
2867 2871 # slightly alter the behavior of revert (doing back up or not, delete
2868 2872 # or just forget etc).
2869 2873 if parent == node:
2870 2874 dsmodified = modified
2871 2875 dsadded = added
2872 2876 dsremoved = removed
2873 2877 # store all local modifications, useful later for rename detection
2874 2878 localchanges = dsmodified | dsadded
2875 2879 modified, added, removed = set(), set(), set()
2876 2880 else:
2877 2881 changes = repo.status(node1=parent, match=m)
2878 2882 dsmodified = set(changes.modified)
2879 2883 dsadded = set(changes.added)
2880 2884 dsremoved = set(changes.removed)
2881 2885 # store all local modifications, useful later for rename detection
2882 2886 localchanges = dsmodified | dsadded
2883 2887
2884 2888 # only take into account for removes between wc and target
2885 2889 clean |= dsremoved - removed
2886 2890 dsremoved &= removed
2887 2891 # distinct between dirstate remove and other
2888 2892 removed -= dsremoved
2889 2893
2890 2894 modadded = added & dsmodified
2891 2895 added -= modadded
2892 2896
2893 2897 # tell newly modified apart.
2894 2898 dsmodified &= modified
2895 2899 dsmodified |= modified & dsadded # dirstate added may needs backup
2896 2900 modified -= dsmodified
2897 2901
2898 2902 # We need to wait for some post-processing to update this set
2899 2903 # before making the distinction. The dirstate will be used for
2900 2904 # that purpose.
2901 2905 dsadded = added
2902 2906
2903 2907 # in case of merge, files that are actually added can be reported as
2904 2908 # modified, we need to post process the result
2905 2909 if p2 != nullid:
2906 2910 if pmf is None:
2907 2911 # only need parent manifest in the merge case,
2908 2912 # so do not read by default
2909 2913 pmf = repo[parent].manifest()
2910 2914 mergeadd = dsmodified - set(pmf)
2911 2915 dsadded |= mergeadd
2912 2916 dsmodified -= mergeadd
2913 2917
2914 2918 # if f is a rename, update `names` to also revert the source
2915 2919 cwd = repo.getcwd()
2916 2920 for f in localchanges:
2917 2921 src = repo.dirstate.copied(f)
2918 2922 # XXX should we check for rename down to target node?
2919 2923 if src and src not in names and repo.dirstate[src] == 'r':
2920 2924 dsremoved.add(src)
2921 2925 names[src] = (repo.pathto(src, cwd), True)
2922 2926
2923 2927 # distinguish between file to forget and the other
2924 2928 added = set()
2925 2929 for abs in dsadded:
2926 2930 if repo.dirstate[abs] != 'a':
2927 2931 added.add(abs)
2928 2932 dsadded -= added
2929 2933
2930 2934 for abs in deladded:
2931 2935 if repo.dirstate[abs] == 'a':
2932 2936 dsadded.add(abs)
2933 2937 deladded -= dsadded
2934 2938
2935 2939 # For files marked as removed, we check if an unknown file is present at
2936 2940 # the same path. If a such file exists it may need to be backed up.
2937 2941 # Making the distinction at this stage helps have simpler backup
2938 2942 # logic.
2939 2943 removunk = set()
2940 2944 for abs in removed:
2941 2945 target = repo.wjoin(abs)
2942 2946 if os.path.lexists(target):
2943 2947 removunk.add(abs)
2944 2948 removed -= removunk
2945 2949
2946 2950 dsremovunk = set()
2947 2951 for abs in dsremoved:
2948 2952 target = repo.wjoin(abs)
2949 2953 if os.path.lexists(target):
2950 2954 dsremovunk.add(abs)
2951 2955 dsremoved -= dsremovunk
2952 2956
2953 2957 # action to be actually performed by revert
2954 2958 # (<list of file>, message>) tuple
2955 2959 actions = {'revert': ([], _('reverting %s\n')),
2956 2960 'add': ([], _('adding %s\n')),
2957 2961 'remove': ([], _('removing %s\n')),
2958 2962 'drop': ([], _('removing %s\n')),
2959 2963 'forget': ([], _('forgetting %s\n')),
2960 2964 'undelete': ([], _('undeleting %s\n')),
2961 2965 'noop': (None, _('no changes needed to %s\n')),
2962 2966 'unknown': (None, _('file not managed: %s\n')),
2963 2967 }
2964 2968
2965 2969 # "constant" that convey the backup strategy.
2966 2970 # All set to `discard` if `no-backup` is set do avoid checking
2967 2971 # no_backup lower in the code.
2968 2972 # These values are ordered for comparison purposes
2969 2973 backup = 2 # unconditionally do backup
2970 2974 check = 1 # check if the existing file differs from target
2971 2975 discard = 0 # never do backup
2972 2976 if opts.get('no_backup'):
2973 2977 backup = check = discard
2974 2978
2975 2979 backupanddel = actions['remove']
2976 2980 if not opts.get('no_backup'):
2977 2981 backupanddel = actions['drop']
2978 2982
2979 2983 disptable = (
2980 2984 # dispatch table:
2981 2985 # file state
2982 2986 # action
2983 2987 # make backup
2984 2988
2985 2989 ## Sets that results that will change file on disk
2986 2990 # Modified compared to target, no local change
2987 2991 (modified, actions['revert'], discard),
2988 2992 # Modified compared to target, but local file is deleted
2989 2993 (deleted, actions['revert'], discard),
2990 2994 # Modified compared to target, local change
2991 2995 (dsmodified, actions['revert'], backup),
2992 2996 # Added since target
2993 2997 (added, actions['remove'], discard),
2994 2998 # Added in working directory
2995 2999 (dsadded, actions['forget'], discard),
2996 3000 # Added since target, have local modification
2997 3001 (modadded, backupanddel, backup),
2998 3002 # Added since target but file is missing in working directory
2999 3003 (deladded, actions['drop'], discard),
3000 3004 # Removed since target, before working copy parent
3001 3005 (removed, actions['add'], discard),
3002 3006 # Same as `removed` but an unknown file exists at the same path
3003 3007 (removunk, actions['add'], check),
3004 3008 # Removed since targe, marked as such in working copy parent
3005 3009 (dsremoved, actions['undelete'], discard),
3006 3010 # Same as `dsremoved` but an unknown file exists at the same path
3007 3011 (dsremovunk, actions['undelete'], check),
3008 3012 ## the following sets does not result in any file changes
3009 3013 # File with no modification
3010 3014 (clean, actions['noop'], discard),
3011 3015 # Existing file, not tracked anywhere
3012 3016 (unknown, actions['unknown'], discard),
3013 3017 )
3014 3018
3015 3019 for abs, (rel, exact) in sorted(names.items()):
3016 3020 # target file to be touch on disk (relative to cwd)
3017 3021 target = repo.wjoin(abs)
3018 3022 # search the entry in the dispatch table.
3019 3023 # if the file is in any of these sets, it was touched in the working
3020 3024 # directory parent and we are sure it needs to be reverted.
3021 3025 for table, (xlist, msg), dobackup in disptable:
3022 3026 if abs not in table:
3023 3027 continue
3024 3028 if xlist is not None:
3025 3029 xlist.append(abs)
3026 3030 if dobackup and (backup <= dobackup
3027 3031 or wctx[abs].cmp(ctx[abs])):
3028 3032 bakname = "%s.orig" % rel
3029 3033 ui.note(_('saving current version of %s as %s\n') %
3030 3034 (rel, bakname))
3031 3035 if not opts.get('dry_run'):
3032 3036 if interactive:
3033 3037 util.copyfile(target, bakname)
3034 3038 else:
3035 3039 util.rename(target, bakname)
3036 3040 if ui.verbose or not exact:
3037 3041 if not isinstance(msg, basestring):
3038 3042 msg = msg(abs)
3039 3043 ui.status(msg % rel)
3040 3044 elif exact:
3041 3045 ui.warn(msg % rel)
3042 3046 break
3043 3047
3044 3048 if not opts.get('dry_run'):
3045 3049 needdata = ('revert', 'add', 'undelete')
3046 3050 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3047 3051 _performrevert(repo, parents, ctx, actions, interactive)
3048 3052
3049 3053 if targetsubs:
3050 3054 # Revert the subrepos on the revert list
3051 3055 for sub in targetsubs:
3052 3056 try:
3053 3057 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3054 3058 except KeyError:
3055 3059 raise util.Abort("subrepository '%s' does not exist in %s!"
3056 3060 % (sub, short(ctx.node())))
3057 3061 finally:
3058 3062 wlock.release()
3059 3063
3060 3064 def _revertprefetch(repo, ctx, *files):
3061 3065 """Let extension changing the storage layer prefetch content"""
3062 3066 pass
3063 3067
3064 3068 def _performrevert(repo, parents, ctx, actions, interactive=False):
3065 3069 """function that actually perform all the actions computed for revert
3066 3070
3067 3071 This is an independent function to let extension to plug in and react to
3068 3072 the imminent revert.
3069 3073
3070 3074 Make sure you have the working directory locked when calling this function.
3071 3075 """
3072 3076 parent, p2 = parents
3073 3077 node = ctx.node()
3074 3078 def checkout(f):
3075 3079 fc = ctx[f]
3076 3080 repo.wwrite(f, fc.data(), fc.flags())
3077 3081
3078 3082 audit_path = pathutil.pathauditor(repo.root)
3079 3083 for f in actions['forget'][0]:
3080 3084 repo.dirstate.drop(f)
3081 3085 for f in actions['remove'][0]:
3082 3086 audit_path(f)
3083 3087 util.unlinkpath(repo.wjoin(f))
3084 3088 repo.dirstate.remove(f)
3085 3089 for f in actions['drop'][0]:
3086 3090 audit_path(f)
3087 3091 repo.dirstate.remove(f)
3088 3092
3089 3093 normal = None
3090 3094 if node == parent:
3091 3095 # We're reverting to our parent. If possible, we'd like status
3092 3096 # to report the file as clean. We have to use normallookup for
3093 3097 # merges to avoid losing information about merged/dirty files.
3094 3098 if p2 != nullid:
3095 3099 normal = repo.dirstate.normallookup
3096 3100 else:
3097 3101 normal = repo.dirstate.normal
3098 3102
3099 3103 if interactive:
3100 3104 # Prompt the user for changes to revert
3101 3105 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3102 3106 m = scmutil.match(ctx, torevert, {})
3103 3107 diff = patch.diff(repo, None, ctx.node(), m)
3104 3108 originalchunks = patch.parsepatch(diff)
3105 3109 try:
3106 3110 chunks = recordfilter(repo.ui, originalchunks)
3107 3111 except patch.PatchError, err:
3108 3112 raise util.Abort(_('error parsing patch: %s') % err)
3109 3113
3110 3114 # Apply changes
3111 3115 fp = cStringIO.StringIO()
3112 3116 for c in chunks:
3113 3117 c.write(fp)
3114 3118 dopatch = fp.tell()
3115 3119 fp.seek(0)
3116 3120 if dopatch:
3117 3121 try:
3118 3122 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3119 3123 except patch.PatchError, err:
3120 3124 raise util.Abort(str(err))
3121 3125 del fp
3122 3126
3123 3127 for f in actions['revert'][0]:
3124 3128 if normal:
3125 3129 normal(f)
3126 3130
3127 3131 else:
3128 3132 for f in actions['revert'][0]:
3129 3133 checkout(f)
3130 3134 if normal:
3131 3135 normal(f)
3132 3136
3133 3137 for f in actions['add'][0]:
3134 3138 checkout(f)
3135 3139 repo.dirstate.add(f)
3136 3140
3137 3141 normal = repo.dirstate.normallookup
3138 3142 if node == parent and p2 == nullid:
3139 3143 normal = repo.dirstate.normal
3140 3144 for f in actions['undelete'][0]:
3141 3145 checkout(f)
3142 3146 normal(f)
3143 3147
3144 3148 copied = copies.pathcopies(repo[parent], ctx)
3145 3149
3146 3150 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3147 3151 if f in copied:
3148 3152 repo.dirstate.copy(copied[f], f)
3149 3153
3150 3154 def command(table):
3151 3155 """Returns a function object to be used as a decorator for making commands.
3152 3156
3153 3157 This function receives a command table as its argument. The table should
3154 3158 be a dict.
3155 3159
3156 3160 The returned function can be used as a decorator for adding commands
3157 3161 to that command table. This function accepts multiple arguments to define
3158 3162 a command.
3159 3163
3160 3164 The first argument is the command name.
3161 3165
3162 3166 The options argument is an iterable of tuples defining command arguments.
3163 3167 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3164 3168
3165 3169 The synopsis argument defines a short, one line summary of how to use the
3166 3170 command. This shows up in the help output.
3167 3171
3168 3172 The norepo argument defines whether the command does not require a
3169 3173 local repository. Most commands operate against a repository, thus the
3170 3174 default is False.
3171 3175
3172 3176 The optionalrepo argument defines whether the command optionally requires
3173 3177 a local repository.
3174 3178
3175 3179 The inferrepo argument defines whether to try to find a repository from the
3176 3180 command line arguments. If True, arguments will be examined for potential
3177 3181 repository locations. See ``findrepo()``. If a repository is found, it
3178 3182 will be used.
3179 3183 """
3180 3184 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3181 3185 inferrepo=False):
3182 3186 def decorator(func):
3183 3187 if synopsis:
3184 3188 table[name] = func, list(options), synopsis
3185 3189 else:
3186 3190 table[name] = func, list(options)
3187 3191
3188 3192 if norepo:
3189 3193 # Avoid import cycle.
3190 3194 import commands
3191 3195 commands.norepo += ' %s' % ' '.join(parsealiases(name))
3192 3196
3193 3197 if optionalrepo:
3194 3198 import commands
3195 3199 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
3196 3200
3197 3201 if inferrepo:
3198 3202 import commands
3199 3203 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
3200 3204
3201 3205 return func
3202 3206 return decorator
3203 3207
3204 3208 return cmd
3205 3209
3206 3210 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3207 3211 # commands.outgoing. "missing" is "missing" of the result of
3208 3212 # "findcommonoutgoing()"
3209 3213 outgoinghooks = util.hooks()
3210 3214
3211 3215 # a list of (ui, repo) functions called by commands.summary
3212 3216 summaryhooks = util.hooks()
3213 3217
3214 3218 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3215 3219 #
3216 3220 # functions should return tuple of booleans below, if 'changes' is None:
3217 3221 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3218 3222 #
3219 3223 # otherwise, 'changes' is a tuple of tuples below:
3220 3224 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3221 3225 # - (desturl, destbranch, destpeer, outgoing)
3222 3226 summaryremotehooks = util.hooks()
3223 3227
3224 3228 # A list of state files kept by multistep operations like graft.
3225 3229 # Since graft cannot be aborted, it is considered 'clearable' by update.
3226 3230 # note: bisect is intentionally excluded
3227 3231 # (state file, clearable, allowcommit, error, hint)
3228 3232 unfinishedstates = [
3229 3233 ('graftstate', True, False, _('graft in progress'),
3230 3234 _("use 'hg graft --continue' or 'hg update' to abort")),
3231 3235 ('updatestate', True, False, _('last update was interrupted'),
3232 3236 _("use 'hg update' to get a consistent checkout"))
3233 3237 ]
3234 3238
3235 3239 def checkunfinished(repo, commit=False):
3236 3240 '''Look for an unfinished multistep operation, like graft, and abort
3237 3241 if found. It's probably good to check this right before
3238 3242 bailifchanged().
3239 3243 '''
3240 3244 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3241 3245 if commit and allowcommit:
3242 3246 continue
3243 3247 if repo.vfs.exists(f):
3244 3248 raise util.Abort(msg, hint=hint)
3245 3249
3246 3250 def clearunfinished(repo):
3247 3251 '''Check for unfinished operations (as above), and clear the ones
3248 3252 that are clearable.
3249 3253 '''
3250 3254 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3251 3255 if not clearable and repo.vfs.exists(f):
3252 3256 raise util.Abort(msg, hint=hint)
3253 3257 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3254 3258 if clearable and repo.vfs.exists(f):
3255 3259 util.unlink(repo.join(f))
@@ -1,2031 +1,2052
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 Make sure largefiles doesn't interfere with logging a regular file
49 49 $ hg --debug log a -T '{rev}: {desc}\n' --config extensions.largefiles=
50 50 updated patterns: ['.hglf/a', 'a']
51 51 0: a
52 52 $ hg log a
53 53 changeset: 0:9161b9aeaf16
54 54 user: test
55 55 date: Thu Jan 01 00:00:01 1970 +0000
56 56 summary: a
57 57
58 58 $ hg log glob:a*
59 59 changeset: 3:2ca5ba701980
60 60 user: test
61 61 date: Thu Jan 01 00:00:04 1970 +0000
62 62 summary: d
63 63
64 64 changeset: 0:9161b9aeaf16
65 65 user: test
66 66 date: Thu Jan 01 00:00:01 1970 +0000
67 67 summary: a
68 68
69 69 $ hg --debug log glob:a* -T '{rev}: {desc}\n' --config extensions.largefiles=
70 70 updated patterns: ['glob:.hglf/a*', 'glob:a*']
71 71 3: d
72 72 0: a
73 73
74 74 log on directory
75 75
76 76 $ hg log dir
77 77 changeset: 4:7e4639b4691b
78 78 tag: tip
79 79 user: test
80 80 date: Thu Jan 01 00:00:05 1970 +0000
81 81 summary: e
82 82
83 83 changeset: 2:f8954cd4dc1f
84 84 user: test
85 85 date: Thu Jan 01 00:00:03 1970 +0000
86 86 summary: c
87 87
88 88 $ hg log somethingthatdoesntexist dir
89 89 changeset: 4:7e4639b4691b
90 90 tag: tip
91 91 user: test
92 92 date: Thu Jan 01 00:00:05 1970 +0000
93 93 summary: e
94 94
95 95 changeset: 2:f8954cd4dc1f
96 96 user: test
97 97 date: Thu Jan 01 00:00:03 1970 +0000
98 98 summary: c
99 99
100 100
101 101 -f, non-existent directory
102 102
103 103 $ hg log -f dir
104 104 abort: cannot follow file not in parent revision: "dir"
105 105 [255]
106 106
107 107 -f, directory
108 108
109 109 $ hg up -q 3
110 110 $ hg log -f dir
111 111 changeset: 2:f8954cd4dc1f
112 112 user: test
113 113 date: Thu Jan 01 00:00:03 1970 +0000
114 114 summary: c
115 115
116 116 -f, directory with --patch
117 117
118 118 $ hg log -f dir -p
119 119 changeset: 2:f8954cd4dc1f
120 120 user: test
121 121 date: Thu Jan 01 00:00:03 1970 +0000
122 122 summary: c
123 123
124 124 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
125 125 --- /dev/null* (glob)
126 126 +++ b/dir/b* (glob)
127 127 @@ -0,0 +1,1 @@
128 128 +a
129 129
130 130
131 131 -f, pattern
132 132
133 133 $ hg log -f -I 'dir**' -p
134 134 changeset: 2:f8954cd4dc1f
135 135 user: test
136 136 date: Thu Jan 01 00:00:03 1970 +0000
137 137 summary: c
138 138
139 139 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
140 140 --- /dev/null* (glob)
141 141 +++ b/dir/b* (glob)
142 142 @@ -0,0 +1,1 @@
143 143 +a
144 144
145 145 $ hg up -q 4
146 146
147 147 -f, a wrong style
148 148
149 149 $ hg log -f -l1 --style something
150 150 abort: style 'something' not found
151 151 (available styles: bisect, changelog, compact, default, phases, xml)
152 152 [255]
153 153
154 154 -f, phases style
155 155
156 156
157 157 $ hg log -f -l1 --style phases
158 158 changeset: 4:7e4639b4691b
159 159 tag: tip
160 160 phase: draft
161 161 user: test
162 162 date: Thu Jan 01 00:00:05 1970 +0000
163 163 summary: e
164 164
165 165
166 166 $ hg log -f -l1 --style phases -q
167 167 4:7e4639b4691b
168 168
169 169 -f, but no args
170 170
171 171 $ hg log -f
172 172 changeset: 4:7e4639b4691b
173 173 tag: tip
174 174 user: test
175 175 date: Thu Jan 01 00:00:05 1970 +0000
176 176 summary: e
177 177
178 178 changeset: 3:2ca5ba701980
179 179 user: test
180 180 date: Thu Jan 01 00:00:04 1970 +0000
181 181 summary: d
182 182
183 183 changeset: 2:f8954cd4dc1f
184 184 user: test
185 185 date: Thu Jan 01 00:00:03 1970 +0000
186 186 summary: c
187 187
188 188 changeset: 1:d89b0a12d229
189 189 user: test
190 190 date: Thu Jan 01 00:00:02 1970 +0000
191 191 summary: b
192 192
193 193 changeset: 0:9161b9aeaf16
194 194 user: test
195 195 date: Thu Jan 01 00:00:01 1970 +0000
196 196 summary: a
197 197
198 198
199 199 one rename
200 200
201 201 $ hg up -q 2
202 202 $ hg log -vf a
203 203 changeset: 0:9161b9aeaf16
204 204 user: test
205 205 date: Thu Jan 01 00:00:01 1970 +0000
206 206 files: a f
207 207 description:
208 208 a
209 209
210 210
211 211
212 212 many renames
213 213
214 214 $ hg up -q tip
215 215 $ hg log -vf e
216 216 changeset: 4:7e4639b4691b
217 217 tag: tip
218 218 user: test
219 219 date: Thu Jan 01 00:00:05 1970 +0000
220 220 files: dir/b e
221 221 description:
222 222 e
223 223
224 224
225 225 changeset: 2:f8954cd4dc1f
226 226 user: test
227 227 date: Thu Jan 01 00:00:03 1970 +0000
228 228 files: b dir/b f g
229 229 description:
230 230 c
231 231
232 232
233 233 changeset: 1:d89b0a12d229
234 234 user: test
235 235 date: Thu Jan 01 00:00:02 1970 +0000
236 236 files: b g
237 237 description:
238 238 b
239 239
240 240
241 241 changeset: 0:9161b9aeaf16
242 242 user: test
243 243 date: Thu Jan 01 00:00:01 1970 +0000
244 244 files: a f
245 245 description:
246 246 a
247 247
248 248
249 249
250 250
251 251 log -pf dir/b
252 252
253 253 $ hg up -q 3
254 254 $ hg log -pf dir/b
255 255 changeset: 2:f8954cd4dc1f
256 256 user: test
257 257 date: Thu Jan 01 00:00:03 1970 +0000
258 258 summary: c
259 259
260 260 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
261 261 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
262 262 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
263 263 @@ -0,0 +1,1 @@
264 264 +a
265 265
266 266 changeset: 1:d89b0a12d229
267 267 user: test
268 268 date: Thu Jan 01 00:00:02 1970 +0000
269 269 summary: b
270 270
271 271 diff -r 9161b9aeaf16 -r d89b0a12d229 b
272 272 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
273 273 +++ b/b Thu Jan 01 00:00:02 1970 +0000
274 274 @@ -0,0 +1,1 @@
275 275 +a
276 276
277 277 changeset: 0:9161b9aeaf16
278 278 user: test
279 279 date: Thu Jan 01 00:00:01 1970 +0000
280 280 summary: a
281 281
282 282 diff -r 000000000000 -r 9161b9aeaf16 a
283 283 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
284 284 +++ b/a Thu Jan 01 00:00:01 1970 +0000
285 285 @@ -0,0 +1,1 @@
286 286 +a
287 287
288 288
289 289 log -pf b inside dir
290 290
291 291 $ hg --cwd=dir log -pf b
292 292 changeset: 2:f8954cd4dc1f
293 293 user: test
294 294 date: Thu Jan 01 00:00:03 1970 +0000
295 295 summary: c
296 296
297 297 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
298 298 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
299 299 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
300 300 @@ -0,0 +1,1 @@
301 301 +a
302 302
303 303 changeset: 1:d89b0a12d229
304 304 user: test
305 305 date: Thu Jan 01 00:00:02 1970 +0000
306 306 summary: b
307 307
308 308 diff -r 9161b9aeaf16 -r d89b0a12d229 b
309 309 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
310 310 +++ b/b Thu Jan 01 00:00:02 1970 +0000
311 311 @@ -0,0 +1,1 @@
312 312 +a
313 313
314 314 changeset: 0:9161b9aeaf16
315 315 user: test
316 316 date: Thu Jan 01 00:00:01 1970 +0000
317 317 summary: a
318 318
319 319 diff -r 000000000000 -r 9161b9aeaf16 a
320 320 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
321 321 +++ b/a Thu Jan 01 00:00:01 1970 +0000
322 322 @@ -0,0 +1,1 @@
323 323 +a
324 324
325 325
326 326 log -pf, but no args
327 327
328 328 $ hg log -pf
329 329 changeset: 3:2ca5ba701980
330 330 user: test
331 331 date: Thu Jan 01 00:00:04 1970 +0000
332 332 summary: d
333 333
334 334 diff -r f8954cd4dc1f -r 2ca5ba701980 a
335 335 --- a/a Thu Jan 01 00:00:03 1970 +0000
336 336 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
337 337 @@ -1,1 +0,0 @@
338 338 -a
339 339 diff -r f8954cd4dc1f -r 2ca5ba701980 b
340 340 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
341 341 +++ b/b Thu Jan 01 00:00:04 1970 +0000
342 342 @@ -0,0 +1,1 @@
343 343 +a
344 344 diff -r f8954cd4dc1f -r 2ca5ba701980 d
345 345 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
346 346 +++ b/d Thu Jan 01 00:00:04 1970 +0000
347 347 @@ -0,0 +1,1 @@
348 348 +a
349 349 diff -r f8954cd4dc1f -r 2ca5ba701980 g
350 350 --- a/g Thu Jan 01 00:00:03 1970 +0000
351 351 +++ b/g Thu Jan 01 00:00:04 1970 +0000
352 352 @@ -1,2 +1,2 @@
353 353 f
354 354 -g
355 355 +f
356 356
357 357 changeset: 2:f8954cd4dc1f
358 358 user: test
359 359 date: Thu Jan 01 00:00:03 1970 +0000
360 360 summary: c
361 361
362 362 diff -r d89b0a12d229 -r f8954cd4dc1f b
363 363 --- a/b Thu Jan 01 00:00:02 1970 +0000
364 364 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
365 365 @@ -1,1 +0,0 @@
366 366 -a
367 367 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
368 368 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
369 369 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
370 370 @@ -0,0 +1,1 @@
371 371 +a
372 372 diff -r d89b0a12d229 -r f8954cd4dc1f f
373 373 --- a/f Thu Jan 01 00:00:02 1970 +0000
374 374 +++ b/f Thu Jan 01 00:00:03 1970 +0000
375 375 @@ -1,1 +1,2 @@
376 376 f
377 377 +f
378 378 diff -r d89b0a12d229 -r f8954cd4dc1f g
379 379 --- a/g Thu Jan 01 00:00:02 1970 +0000
380 380 +++ b/g Thu Jan 01 00:00:03 1970 +0000
381 381 @@ -1,1 +1,2 @@
382 382 f
383 383 +g
384 384
385 385 changeset: 1:d89b0a12d229
386 386 user: test
387 387 date: Thu Jan 01 00:00:02 1970 +0000
388 388 summary: b
389 389
390 390 diff -r 9161b9aeaf16 -r d89b0a12d229 b
391 391 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
392 392 +++ b/b Thu Jan 01 00:00:02 1970 +0000
393 393 @@ -0,0 +1,1 @@
394 394 +a
395 395 diff -r 9161b9aeaf16 -r d89b0a12d229 g
396 396 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
397 397 +++ b/g Thu Jan 01 00:00:02 1970 +0000
398 398 @@ -0,0 +1,1 @@
399 399 +f
400 400
401 401 changeset: 0:9161b9aeaf16
402 402 user: test
403 403 date: Thu Jan 01 00:00:01 1970 +0000
404 404 summary: a
405 405
406 406 diff -r 000000000000 -r 9161b9aeaf16 a
407 407 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
408 408 +++ b/a Thu Jan 01 00:00:01 1970 +0000
409 409 @@ -0,0 +1,1 @@
410 410 +a
411 411 diff -r 000000000000 -r 9161b9aeaf16 f
412 412 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
413 413 +++ b/f Thu Jan 01 00:00:01 1970 +0000
414 414 @@ -0,0 +1,1 @@
415 415 +f
416 416
417 417
418 418 log -vf dir/b
419 419
420 420 $ hg log -vf dir/b
421 421 changeset: 2:f8954cd4dc1f
422 422 user: test
423 423 date: Thu Jan 01 00:00:03 1970 +0000
424 424 files: b dir/b f g
425 425 description:
426 426 c
427 427
428 428
429 429 changeset: 1:d89b0a12d229
430 430 user: test
431 431 date: Thu Jan 01 00:00:02 1970 +0000
432 432 files: b g
433 433 description:
434 434 b
435 435
436 436
437 437 changeset: 0:9161b9aeaf16
438 438 user: test
439 439 date: Thu Jan 01 00:00:01 1970 +0000
440 440 files: a f
441 441 description:
442 442 a
443 443
444 444
445 445
446 446
447 447 -f and multiple filelog heads
448 448
449 449 $ hg up -q 2
450 450 $ hg log -f g --template '{rev}\n'
451 451 2
452 452 1
453 453 0
454 454 $ hg up -q tip
455 455 $ hg log -f g --template '{rev}\n'
456 456 3
457 457 2
458 458 0
459 459
460 460
461 461 log copies with --copies
462 462
463 463 $ hg log -vC --template '{rev} {file_copies}\n'
464 464 4 e (dir/b)
465 465 3 b (a)g (f)
466 466 2 dir/b (b)
467 467 1 b (a)g (f)
468 468 0
469 469
470 470 log copies switch without --copies, with old filecopy template
471 471
472 472 $ hg log -v --template '{rev} {file_copies_switch%filecopy}\n'
473 473 4
474 474 3
475 475 2
476 476 1
477 477 0
478 478
479 479 log copies switch with --copies
480 480
481 481 $ hg log -vC --template '{rev} {file_copies_switch}\n'
482 482 4 e (dir/b)
483 483 3 b (a)g (f)
484 484 2 dir/b (b)
485 485 1 b (a)g (f)
486 486 0
487 487
488 488
489 489 log copies with hardcoded style and with --style=default
490 490
491 491 $ hg log -vC -r4
492 492 changeset: 4:7e4639b4691b
493 493 tag: tip
494 494 user: test
495 495 date: Thu Jan 01 00:00:05 1970 +0000
496 496 files: dir/b e
497 497 copies: e (dir/b)
498 498 description:
499 499 e
500 500
501 501
502 502 $ hg log -vC -r4 --style=default
503 503 changeset: 4:7e4639b4691b
504 504 tag: tip
505 505 user: test
506 506 date: Thu Jan 01 00:00:05 1970 +0000
507 507 files: dir/b e
508 508 copies: e (dir/b)
509 509 description:
510 510 e
511 511
512 512
513 513 $ hg log -vC -r4 -Tjson
514 514 [
515 515 {
516 516 "rev": 4,
517 517 "node": "7e4639b4691b9f84b81036a8d4fb218ce3c5e3a3",
518 518 "branch": "default",
519 519 "phase": "draft",
520 520 "user": "test",
521 521 "date": [5, 0],
522 522 "desc": "e",
523 523 "bookmarks": [],
524 524 "tags": ["tip"],
525 525 "parents": ["2ca5ba7019804f1f597249caddf22a64d34df0ba"],
526 526 "files": ["dir/b", "e"],
527 527 "copies": {"e": "dir/b"}
528 528 }
529 529 ]
530 530
531 531 log copies, non-linear manifest
532 532
533 533 $ hg up -C 3
534 534 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
535 535 $ hg mv dir/b e
536 536 $ echo foo > foo
537 537 $ hg ci -Ame2 -d '6 0'
538 538 adding foo
539 539 created new head
540 540 $ hg log -v --template '{rev} {file_copies}\n' -r 5
541 541 5 e (dir/b)
542 542
543 543
544 544 log copies, execute bit set
545 545
546 546 #if execbit
547 547 $ chmod +x e
548 548 $ hg ci -me3 -d '7 0'
549 549 $ hg log -v --template '{rev} {file_copies}\n' -r 6
550 550 6
551 551 #endif
552 552
553 553
554 554 log -p d
555 555
556 556 $ hg log -pv d
557 557 changeset: 3:2ca5ba701980
558 558 user: test
559 559 date: Thu Jan 01 00:00:04 1970 +0000
560 560 files: a b d g
561 561 description:
562 562 d
563 563
564 564
565 565 diff -r f8954cd4dc1f -r 2ca5ba701980 d
566 566 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
567 567 +++ b/d Thu Jan 01 00:00:04 1970 +0000
568 568 @@ -0,0 +1,1 @@
569 569 +a
570 570
571 571
572 572
573 573 log --removed file
574 574
575 575 $ hg log --removed -v a
576 576 changeset: 3:2ca5ba701980
577 577 user: test
578 578 date: Thu Jan 01 00:00:04 1970 +0000
579 579 files: a b d g
580 580 description:
581 581 d
582 582
583 583
584 584 changeset: 0:9161b9aeaf16
585 585 user: test
586 586 date: Thu Jan 01 00:00:01 1970 +0000
587 587 files: a f
588 588 description:
589 589 a
590 590
591 591
592 592
593 593 log --removed revrange file
594 594
595 595 $ hg log --removed -v -r0:2 a
596 596 changeset: 0:9161b9aeaf16
597 597 user: test
598 598 date: Thu Jan 01 00:00:01 1970 +0000
599 599 files: a f
600 600 description:
601 601 a
602 602
603 603
604 604 $ cd ..
605 605
606 606 log --follow tests
607 607
608 608 $ hg init follow
609 609 $ cd follow
610 610
611 611 $ echo base > base
612 612 $ hg ci -Ambase -d '1 0'
613 613 adding base
614 614
615 615 $ echo r1 >> base
616 616 $ hg ci -Amr1 -d '1 0'
617 617 $ echo r2 >> base
618 618 $ hg ci -Amr2 -d '1 0'
619 619
620 620 $ hg up -C 1
621 621 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
622 622 $ echo b1 > b1
623 623 $ hg ci -Amb1 -d '1 0'
624 624 adding b1
625 625 created new head
626 626
627 627
628 628 log -f
629 629
630 630 $ hg log -f
631 631 changeset: 3:e62f78d544b4
632 632 tag: tip
633 633 parent: 1:3d5bf5654eda
634 634 user: test
635 635 date: Thu Jan 01 00:00:01 1970 +0000
636 636 summary: b1
637 637
638 638 changeset: 1:3d5bf5654eda
639 639 user: test
640 640 date: Thu Jan 01 00:00:01 1970 +0000
641 641 summary: r1
642 642
643 643 changeset: 0:67e992f2c4f3
644 644 user: test
645 645 date: Thu Jan 01 00:00:01 1970 +0000
646 646 summary: base
647 647
648 648
649 649
650 650 log -f -r '1 + 4'
651 651
652 652 $ hg up -C 0
653 653 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
654 654 $ echo b2 > b2
655 655 $ hg ci -Amb2 -d '1 0'
656 656 adding b2
657 657 created new head
658 658 $ hg log -f -r '1 + 4'
659 659 changeset: 4:ddb82e70d1a1
660 660 tag: tip
661 661 parent: 0:67e992f2c4f3
662 662 user: test
663 663 date: Thu Jan 01 00:00:01 1970 +0000
664 664 summary: b2
665 665
666 666 changeset: 1:3d5bf5654eda
667 667 user: test
668 668 date: Thu Jan 01 00:00:01 1970 +0000
669 669 summary: r1
670 670
671 671 changeset: 0:67e992f2c4f3
672 672 user: test
673 673 date: Thu Jan 01 00:00:01 1970 +0000
674 674 summary: base
675 675
676 676 log -f -r null
677 677
678 678 $ hg log -f -r null
679 679 changeset: -1:000000000000
680 680 user:
681 681 date: Thu Jan 01 00:00:00 1970 +0000
682 682
683 683 $ hg log -f -r null -G
684 684 o changeset: -1:000000000000
685 685 user:
686 686 date: Thu Jan 01 00:00:00 1970 +0000
687 687
688 688
689 689
690 690 log -f with null parent
691 691
692 692 $ hg up -C null
693 693 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
694 694 $ hg log -f
695 695
696 696
697 697 log -r . with two parents
698 698
699 699 $ hg up -C 3
700 700 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
701 701 $ hg merge tip
702 702 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
703 703 (branch merge, don't forget to commit)
704 704 $ hg log -r .
705 705 changeset: 3:e62f78d544b4
706 706 parent: 1:3d5bf5654eda
707 707 user: test
708 708 date: Thu Jan 01 00:00:01 1970 +0000
709 709 summary: b1
710 710
711 711
712 712
713 713 log -r . with one parent
714 714
715 715 $ hg ci -mm12 -d '1 0'
716 716 $ hg log -r .
717 717 changeset: 5:302e9dd6890d
718 718 tag: tip
719 719 parent: 3:e62f78d544b4
720 720 parent: 4:ddb82e70d1a1
721 721 user: test
722 722 date: Thu Jan 01 00:00:01 1970 +0000
723 723 summary: m12
724 724
725 725
726 726 $ echo postm >> b1
727 727 $ hg ci -Amb1.1 -d'1 0'
728 728
729 729
730 730 log --follow-first
731 731
732 732 $ hg log --follow-first
733 733 changeset: 6:2404bbcab562
734 734 tag: tip
735 735 user: test
736 736 date: Thu Jan 01 00:00:01 1970 +0000
737 737 summary: b1.1
738 738
739 739 changeset: 5:302e9dd6890d
740 740 parent: 3:e62f78d544b4
741 741 parent: 4:ddb82e70d1a1
742 742 user: test
743 743 date: Thu Jan 01 00:00:01 1970 +0000
744 744 summary: m12
745 745
746 746 changeset: 3:e62f78d544b4
747 747 parent: 1:3d5bf5654eda
748 748 user: test
749 749 date: Thu Jan 01 00:00:01 1970 +0000
750 750 summary: b1
751 751
752 752 changeset: 1:3d5bf5654eda
753 753 user: test
754 754 date: Thu Jan 01 00:00:01 1970 +0000
755 755 summary: r1
756 756
757 757 changeset: 0:67e992f2c4f3
758 758 user: test
759 759 date: Thu Jan 01 00:00:01 1970 +0000
760 760 summary: base
761 761
762 762
763 763
764 764 log -P 2
765 765
766 766 $ hg log -P 2
767 767 changeset: 6:2404bbcab562
768 768 tag: tip
769 769 user: test
770 770 date: Thu Jan 01 00:00:01 1970 +0000
771 771 summary: b1.1
772 772
773 773 changeset: 5:302e9dd6890d
774 774 parent: 3:e62f78d544b4
775 775 parent: 4:ddb82e70d1a1
776 776 user: test
777 777 date: Thu Jan 01 00:00:01 1970 +0000
778 778 summary: m12
779 779
780 780 changeset: 4:ddb82e70d1a1
781 781 parent: 0:67e992f2c4f3
782 782 user: test
783 783 date: Thu Jan 01 00:00:01 1970 +0000
784 784 summary: b2
785 785
786 786 changeset: 3:e62f78d544b4
787 787 parent: 1:3d5bf5654eda
788 788 user: test
789 789 date: Thu Jan 01 00:00:01 1970 +0000
790 790 summary: b1
791 791
792 792
793 793
794 794 log -r tip -p --git
795 795
796 796 $ hg log -r tip -p --git
797 797 changeset: 6:2404bbcab562
798 798 tag: tip
799 799 user: test
800 800 date: Thu Jan 01 00:00:01 1970 +0000
801 801 summary: b1.1
802 802
803 803 diff --git a/b1 b/b1
804 804 --- a/b1
805 805 +++ b/b1
806 806 @@ -1,1 +1,2 @@
807 807 b1
808 808 +postm
809 809
810 810
811 811
812 812 log -r ""
813 813
814 814 $ hg log -r ''
815 815 hg: parse error: empty query
816 816 [255]
817 817
818 818 log -r <some unknown node id>
819 819
820 820 $ hg log -r 1000000000000000000000000000000000000000
821 821 abort: unknown revision '1000000000000000000000000000000000000000'!
822 822 [255]
823 823
824 824 log -k r1
825 825
826 826 $ hg log -k r1
827 827 changeset: 1:3d5bf5654eda
828 828 user: test
829 829 date: Thu Jan 01 00:00:01 1970 +0000
830 830 summary: r1
831 831
832 832 log -p -l2 --color=always
833 833
834 834 $ hg --config extensions.color= --config color.mode=ansi \
835 835 > log -p -l2 --color=always
836 836 \x1b[0;33mchangeset: 6:2404bbcab562\x1b[0m (esc)
837 837 tag: tip
838 838 user: test
839 839 date: Thu Jan 01 00:00:01 1970 +0000
840 840 summary: b1.1
841 841
842 842 \x1b[0;1mdiff -r 302e9dd6890d -r 2404bbcab562 b1\x1b[0m (esc)
843 843 \x1b[0;31;1m--- a/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
844 844 \x1b[0;32;1m+++ b/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
845 845 \x1b[0;35m@@ -1,1 +1,2 @@\x1b[0m (esc)
846 846 b1
847 847 \x1b[0;32m+postm\x1b[0m (esc)
848 848
849 849 \x1b[0;33mchangeset: 5:302e9dd6890d\x1b[0m (esc)
850 850 parent: 3:e62f78d544b4
851 851 parent: 4:ddb82e70d1a1
852 852 user: test
853 853 date: Thu Jan 01 00:00:01 1970 +0000
854 854 summary: m12
855 855
856 856 \x1b[0;1mdiff -r e62f78d544b4 -r 302e9dd6890d b2\x1b[0m (esc)
857 857 \x1b[0;31;1m--- /dev/null Thu Jan 01 00:00:00 1970 +0000\x1b[0m (esc)
858 858 \x1b[0;32;1m+++ b/b2 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
859 859 \x1b[0;35m@@ -0,0 +1,1 @@\x1b[0m (esc)
860 860 \x1b[0;32m+b2\x1b[0m (esc)
861 861
862 862
863 863
864 864 log -r tip --stat
865 865
866 866 $ hg log -r tip --stat
867 867 changeset: 6:2404bbcab562
868 868 tag: tip
869 869 user: test
870 870 date: Thu Jan 01 00:00:01 1970 +0000
871 871 summary: b1.1
872 872
873 873 b1 | 1 +
874 874 1 files changed, 1 insertions(+), 0 deletions(-)
875 875
876 876
877 877 $ cd ..
878 878
879 879
880 880 User
881 881
882 882 $ hg init usertest
883 883 $ cd usertest
884 884
885 885 $ echo a > a
886 886 $ hg ci -A -m "a" -u "User One <user1@example.org>"
887 887 adding a
888 888 $ echo b > b
889 889 $ hg ci -A -m "b" -u "User Two <user2@example.org>"
890 890 adding b
891 891
892 892 $ hg log -u "User One <user1@example.org>"
893 893 changeset: 0:29a4c94f1924
894 894 user: User One <user1@example.org>
895 895 date: Thu Jan 01 00:00:00 1970 +0000
896 896 summary: a
897 897
898 898 $ hg log -u "user1" -u "user2"
899 899 changeset: 1:e834b5e69c0e
900 900 tag: tip
901 901 user: User Two <user2@example.org>
902 902 date: Thu Jan 01 00:00:00 1970 +0000
903 903 summary: b
904 904
905 905 changeset: 0:29a4c94f1924
906 906 user: User One <user1@example.org>
907 907 date: Thu Jan 01 00:00:00 1970 +0000
908 908 summary: a
909 909
910 910 $ hg log -u "user3"
911 911
912 912 $ cd ..
913 913
914 914 $ hg init branches
915 915 $ cd branches
916 916
917 917 $ echo a > a
918 918 $ hg ci -A -m "commit on default"
919 919 adding a
920 920 $ hg branch test
921 921 marked working directory as branch test
922 922 (branches are permanent and global, did you want a bookmark?)
923 923 $ echo b > b
924 924 $ hg ci -A -m "commit on test"
925 925 adding b
926 926
927 927 $ hg up default
928 928 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
929 929 $ echo c > c
930 930 $ hg ci -A -m "commit on default"
931 931 adding c
932 932 $ hg up test
933 933 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
934 934 $ echo c > c
935 935 $ hg ci -A -m "commit on test"
936 936 adding c
937 937
938 938
939 939 log -b default
940 940
941 941 $ hg log -b default
942 942 changeset: 2:c3a4f03cc9a7
943 943 parent: 0:24427303d56f
944 944 user: test
945 945 date: Thu Jan 01 00:00:00 1970 +0000
946 946 summary: commit on default
947 947
948 948 changeset: 0:24427303d56f
949 949 user: test
950 950 date: Thu Jan 01 00:00:00 1970 +0000
951 951 summary: commit on default
952 952
953 953
954 954
955 955 log -b test
956 956
957 957 $ hg log -b test
958 958 changeset: 3:f5d8de11c2e2
959 959 branch: test
960 960 tag: tip
961 961 parent: 1:d32277701ccb
962 962 user: test
963 963 date: Thu Jan 01 00:00:00 1970 +0000
964 964 summary: commit on test
965 965
966 966 changeset: 1:d32277701ccb
967 967 branch: test
968 968 user: test
969 969 date: Thu Jan 01 00:00:00 1970 +0000
970 970 summary: commit on test
971 971
972 972
973 973
974 974 log -b dummy
975 975
976 976 $ hg log -b dummy
977 977 abort: unknown revision 'dummy'!
978 978 [255]
979 979
980 980
981 981 log -b .
982 982
983 983 $ hg log -b .
984 984 changeset: 3:f5d8de11c2e2
985 985 branch: test
986 986 tag: tip
987 987 parent: 1:d32277701ccb
988 988 user: test
989 989 date: Thu Jan 01 00:00:00 1970 +0000
990 990 summary: commit on test
991 991
992 992 changeset: 1:d32277701ccb
993 993 branch: test
994 994 user: test
995 995 date: Thu Jan 01 00:00:00 1970 +0000
996 996 summary: commit on test
997 997
998 998
999 999
1000 1000 log -b default -b test
1001 1001
1002 1002 $ hg log -b default -b test
1003 1003 changeset: 3:f5d8de11c2e2
1004 1004 branch: test
1005 1005 tag: tip
1006 1006 parent: 1:d32277701ccb
1007 1007 user: test
1008 1008 date: Thu Jan 01 00:00:00 1970 +0000
1009 1009 summary: commit on test
1010 1010
1011 1011 changeset: 2:c3a4f03cc9a7
1012 1012 parent: 0:24427303d56f
1013 1013 user: test
1014 1014 date: Thu Jan 01 00:00:00 1970 +0000
1015 1015 summary: commit on default
1016 1016
1017 1017 changeset: 1:d32277701ccb
1018 1018 branch: test
1019 1019 user: test
1020 1020 date: Thu Jan 01 00:00:00 1970 +0000
1021 1021 summary: commit on test
1022 1022
1023 1023 changeset: 0:24427303d56f
1024 1024 user: test
1025 1025 date: Thu Jan 01 00:00:00 1970 +0000
1026 1026 summary: commit on default
1027 1027
1028 1028
1029 1029
1030 1030 log -b default -b .
1031 1031
1032 1032 $ hg log -b default -b .
1033 1033 changeset: 3:f5d8de11c2e2
1034 1034 branch: test
1035 1035 tag: tip
1036 1036 parent: 1:d32277701ccb
1037 1037 user: test
1038 1038 date: Thu Jan 01 00:00:00 1970 +0000
1039 1039 summary: commit on test
1040 1040
1041 1041 changeset: 2:c3a4f03cc9a7
1042 1042 parent: 0:24427303d56f
1043 1043 user: test
1044 1044 date: Thu Jan 01 00:00:00 1970 +0000
1045 1045 summary: commit on default
1046 1046
1047 1047 changeset: 1:d32277701ccb
1048 1048 branch: test
1049 1049 user: test
1050 1050 date: Thu Jan 01 00:00:00 1970 +0000
1051 1051 summary: commit on test
1052 1052
1053 1053 changeset: 0:24427303d56f
1054 1054 user: test
1055 1055 date: Thu Jan 01 00:00:00 1970 +0000
1056 1056 summary: commit on default
1057 1057
1058 1058
1059 1059
1060 1060 log -b . -b test
1061 1061
1062 1062 $ hg log -b . -b test
1063 1063 changeset: 3:f5d8de11c2e2
1064 1064 branch: test
1065 1065 tag: tip
1066 1066 parent: 1:d32277701ccb
1067 1067 user: test
1068 1068 date: Thu Jan 01 00:00:00 1970 +0000
1069 1069 summary: commit on test
1070 1070
1071 1071 changeset: 1:d32277701ccb
1072 1072 branch: test
1073 1073 user: test
1074 1074 date: Thu Jan 01 00:00:00 1970 +0000
1075 1075 summary: commit on test
1076 1076
1077 1077
1078 1078
1079 1079 log -b 2
1080 1080
1081 1081 $ hg log -b 2
1082 1082 changeset: 2:c3a4f03cc9a7
1083 1083 parent: 0:24427303d56f
1084 1084 user: test
1085 1085 date: Thu Jan 01 00:00:00 1970 +0000
1086 1086 summary: commit on default
1087 1087
1088 1088 changeset: 0:24427303d56f
1089 1089 user: test
1090 1090 date: Thu Jan 01 00:00:00 1970 +0000
1091 1091 summary: commit on default
1092 1092
1093 1093 #if gettext
1094 1094
1095 1095 Test that all log names are translated (e.g. branches, bookmarks, tags):
1096 1096
1097 1097 $ hg bookmark babar -r tip
1098 1098
1099 1099 $ HGENCODING=UTF-8 LANGUAGE=de hg log -r tip
1100 1100 \xc3\x84nderung: 3:f5d8de11c2e2 (esc)
1101 1101 Zweig: test
1102 1102 Lesezeichen: babar
1103 1103 Marke: tip
1104 1104 Vorg\xc3\xa4nger: 1:d32277701ccb (esc)
1105 1105 Nutzer: test
1106 1106 Datum: Thu Jan 01 00:00:00 1970 +0000
1107 1107 Zusammenfassung: commit on test
1108 1108
1109 1109 $ hg bookmark -d babar
1110 1110
1111 1111 #endif
1112 1112
1113 1113 log -p --cwd dir (in subdir)
1114 1114
1115 1115 $ mkdir dir
1116 1116 $ hg log -p --cwd dir
1117 1117 changeset: 3:f5d8de11c2e2
1118 1118 branch: test
1119 1119 tag: tip
1120 1120 parent: 1:d32277701ccb
1121 1121 user: test
1122 1122 date: Thu Jan 01 00:00:00 1970 +0000
1123 1123 summary: commit on test
1124 1124
1125 1125 diff -r d32277701ccb -r f5d8de11c2e2 c
1126 1126 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1127 1127 +++ b/c Thu Jan 01 00:00:00 1970 +0000
1128 1128 @@ -0,0 +1,1 @@
1129 1129 +c
1130 1130
1131 1131 changeset: 2:c3a4f03cc9a7
1132 1132 parent: 0:24427303d56f
1133 1133 user: test
1134 1134 date: Thu Jan 01 00:00:00 1970 +0000
1135 1135 summary: commit on default
1136 1136
1137 1137 diff -r 24427303d56f -r c3a4f03cc9a7 c
1138 1138 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1139 1139 +++ b/c Thu Jan 01 00:00:00 1970 +0000
1140 1140 @@ -0,0 +1,1 @@
1141 1141 +c
1142 1142
1143 1143 changeset: 1:d32277701ccb
1144 1144 branch: test
1145 1145 user: test
1146 1146 date: Thu Jan 01 00:00:00 1970 +0000
1147 1147 summary: commit on test
1148 1148
1149 1149 diff -r 24427303d56f -r d32277701ccb b
1150 1150 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1151 1151 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1152 1152 @@ -0,0 +1,1 @@
1153 1153 +b
1154 1154
1155 1155 changeset: 0:24427303d56f
1156 1156 user: test
1157 1157 date: Thu Jan 01 00:00:00 1970 +0000
1158 1158 summary: commit on default
1159 1159
1160 1160 diff -r 000000000000 -r 24427303d56f a
1161 1161 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1162 1162 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1163 1163 @@ -0,0 +1,1 @@
1164 1164 +a
1165 1165
1166 1166
1167 1167
1168 1168 log -p -R repo
1169 1169
1170 1170 $ cd dir
1171 1171 $ hg log -p -R .. ../a
1172 1172 changeset: 0:24427303d56f
1173 1173 user: test
1174 1174 date: Thu Jan 01 00:00:00 1970 +0000
1175 1175 summary: commit on default
1176 1176
1177 1177 diff -r 000000000000 -r 24427303d56f a
1178 1178 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1179 1179 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1180 1180 @@ -0,0 +1,1 @@
1181 1181 +a
1182 1182
1183 1183
1184 1184 $ cd ../..
1185 1185
1186 1186 $ hg init follow2
1187 1187 $ cd follow2
1188 1188
1189 1189 # Build the following history:
1190 1190 # tip - o - x - o - x - x
1191 1191 # \ /
1192 1192 # o - o - o - x
1193 1193 # \ /
1194 1194 # o
1195 1195 #
1196 1196 # Where "o" is a revision containing "foo" and
1197 1197 # "x" is a revision without "foo"
1198 1198
1199 1199 $ touch init
1200 1200 $ hg ci -A -m "init, unrelated"
1201 1201 adding init
1202 1202 $ echo 'foo' > init
1203 1203 $ hg ci -m "change, unrelated"
1204 1204 $ echo 'foo' > foo
1205 1205 $ hg ci -A -m "add unrelated old foo"
1206 1206 adding foo
1207 1207 $ hg rm foo
1208 1208 $ hg ci -m "delete foo, unrelated"
1209 1209 $ echo 'related' > foo
1210 1210 $ hg ci -A -m "add foo, related"
1211 1211 adding foo
1212 1212
1213 1213 $ hg up 0
1214 1214 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
1215 1215 $ touch branch
1216 1216 $ hg ci -A -m "first branch, unrelated"
1217 1217 adding branch
1218 1218 created new head
1219 1219 $ touch foo
1220 1220 $ hg ci -A -m "create foo, related"
1221 1221 adding foo
1222 1222 $ echo 'change' > foo
1223 1223 $ hg ci -m "change foo, related"
1224 1224
1225 1225 $ hg up 6
1226 1226 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1227 1227 $ echo 'change foo in branch' > foo
1228 1228 $ hg ci -m "change foo in branch, related"
1229 1229 created new head
1230 1230 $ hg merge 7
1231 1231 merging foo
1232 1232 warning: conflicts during merge.
1233 1233 merging foo incomplete! (edit conflicts, then use 'hg resolve --mark')
1234 1234 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1235 1235 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
1236 1236 [1]
1237 1237 $ echo 'merge 1' > foo
1238 1238 $ hg resolve -m foo
1239 1239 (no more unresolved files)
1240 1240 $ hg ci -m "First merge, related"
1241 1241
1242 1242 $ hg merge 4
1243 1243 merging foo
1244 1244 warning: conflicts during merge.
1245 1245 merging foo incomplete! (edit conflicts, then use 'hg resolve --mark')
1246 1246 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
1247 1247 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
1248 1248 [1]
1249 1249 $ echo 'merge 2' > foo
1250 1250 $ hg resolve -m foo
1251 1251 (no more unresolved files)
1252 1252 $ hg ci -m "Last merge, related"
1253 1253
1254 1254 $ hg log --graph
1255 1255 @ changeset: 10:4dae8563d2c5
1256 1256 |\ tag: tip
1257 1257 | | parent: 9:7b35701b003e
1258 1258 | | parent: 4:88176d361b69
1259 1259 | | user: test
1260 1260 | | date: Thu Jan 01 00:00:00 1970 +0000
1261 1261 | | summary: Last merge, related
1262 1262 | |
1263 1263 | o changeset: 9:7b35701b003e
1264 1264 | |\ parent: 8:e5416ad8a855
1265 1265 | | | parent: 7:87fe3144dcfa
1266 1266 | | | user: test
1267 1267 | | | date: Thu Jan 01 00:00:00 1970 +0000
1268 1268 | | | summary: First merge, related
1269 1269 | | |
1270 1270 | | o changeset: 8:e5416ad8a855
1271 1271 | | | parent: 6:dc6c325fe5ee
1272 1272 | | | user: test
1273 1273 | | | date: Thu Jan 01 00:00:00 1970 +0000
1274 1274 | | | summary: change foo in branch, related
1275 1275 | | |
1276 1276 | o | changeset: 7:87fe3144dcfa
1277 1277 | |/ user: test
1278 1278 | | date: Thu Jan 01 00:00:00 1970 +0000
1279 1279 | | summary: change foo, related
1280 1280 | |
1281 1281 | o changeset: 6:dc6c325fe5ee
1282 1282 | | user: test
1283 1283 | | date: Thu Jan 01 00:00:00 1970 +0000
1284 1284 | | summary: create foo, related
1285 1285 | |
1286 1286 | o changeset: 5:73db34516eb9
1287 1287 | | parent: 0:e87515fd044a
1288 1288 | | user: test
1289 1289 | | date: Thu Jan 01 00:00:00 1970 +0000
1290 1290 | | summary: first branch, unrelated
1291 1291 | |
1292 1292 o | changeset: 4:88176d361b69
1293 1293 | | user: test
1294 1294 | | date: Thu Jan 01 00:00:00 1970 +0000
1295 1295 | | summary: add foo, related
1296 1296 | |
1297 1297 o | changeset: 3:dd78ae4afb56
1298 1298 | | user: test
1299 1299 | | date: Thu Jan 01 00:00:00 1970 +0000
1300 1300 | | summary: delete foo, unrelated
1301 1301 | |
1302 1302 o | changeset: 2:c4c64aedf0f7
1303 1303 | | user: test
1304 1304 | | date: Thu Jan 01 00:00:00 1970 +0000
1305 1305 | | summary: add unrelated old foo
1306 1306 | |
1307 1307 o | changeset: 1:e5faa7440653
1308 1308 |/ user: test
1309 1309 | date: Thu Jan 01 00:00:00 1970 +0000
1310 1310 | summary: change, unrelated
1311 1311 |
1312 1312 o changeset: 0:e87515fd044a
1313 1313 user: test
1314 1314 date: Thu Jan 01 00:00:00 1970 +0000
1315 1315 summary: init, unrelated
1316 1316
1317 1317
1318 1318 $ hg --traceback log -f foo
1319 1319 changeset: 10:4dae8563d2c5
1320 1320 tag: tip
1321 1321 parent: 9:7b35701b003e
1322 1322 parent: 4:88176d361b69
1323 1323 user: test
1324 1324 date: Thu Jan 01 00:00:00 1970 +0000
1325 1325 summary: Last merge, related
1326 1326
1327 1327 changeset: 9:7b35701b003e
1328 1328 parent: 8:e5416ad8a855
1329 1329 parent: 7:87fe3144dcfa
1330 1330 user: test
1331 1331 date: Thu Jan 01 00:00:00 1970 +0000
1332 1332 summary: First merge, related
1333 1333
1334 1334 changeset: 8:e5416ad8a855
1335 1335 parent: 6:dc6c325fe5ee
1336 1336 user: test
1337 1337 date: Thu Jan 01 00:00:00 1970 +0000
1338 1338 summary: change foo in branch, related
1339 1339
1340 1340 changeset: 7:87fe3144dcfa
1341 1341 user: test
1342 1342 date: Thu Jan 01 00:00:00 1970 +0000
1343 1343 summary: change foo, related
1344 1344
1345 1345 changeset: 6:dc6c325fe5ee
1346 1346 user: test
1347 1347 date: Thu Jan 01 00:00:00 1970 +0000
1348 1348 summary: create foo, related
1349 1349
1350 1350 changeset: 4:88176d361b69
1351 1351 user: test
1352 1352 date: Thu Jan 01 00:00:00 1970 +0000
1353 1353 summary: add foo, related
1354 1354
1355 1355
1356 1356 Also check when maxrev < lastrevfilelog
1357 1357
1358 1358 $ hg --traceback log -f -r4 foo
1359 1359 changeset: 4:88176d361b69
1360 1360 user: test
1361 1361 date: Thu Jan 01 00:00:00 1970 +0000
1362 1362 summary: add foo, related
1363 1363
1364 1364 changeset: 2:c4c64aedf0f7
1365 1365 user: test
1366 1366 date: Thu Jan 01 00:00:00 1970 +0000
1367 1367 summary: add unrelated old foo
1368 1368
1369 1369 $ cd ..
1370 1370
1371 1371 Issue2383: hg log showing _less_ differences than hg diff
1372 1372
1373 1373 $ hg init issue2383
1374 1374 $ cd issue2383
1375 1375
1376 1376 Create a test repo:
1377 1377
1378 1378 $ echo a > a
1379 1379 $ hg ci -Am0
1380 1380 adding a
1381 1381 $ echo b > b
1382 1382 $ hg ci -Am1
1383 1383 adding b
1384 1384 $ hg co 0
1385 1385 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1386 1386 $ echo b > a
1387 1387 $ hg ci -m2
1388 1388 created new head
1389 1389
1390 1390 Merge:
1391 1391
1392 1392 $ hg merge
1393 1393 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1394 1394 (branch merge, don't forget to commit)
1395 1395
1396 1396 Make sure there's a file listed in the merge to trigger the bug:
1397 1397
1398 1398 $ echo c > a
1399 1399 $ hg ci -m3
1400 1400
1401 1401 Two files shown here in diff:
1402 1402
1403 1403 $ hg diff --rev 2:3
1404 1404 diff -r b09be438c43a -r 8e07aafe1edc a
1405 1405 --- a/a Thu Jan 01 00:00:00 1970 +0000
1406 1406 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1407 1407 @@ -1,1 +1,1 @@
1408 1408 -b
1409 1409 +c
1410 1410 diff -r b09be438c43a -r 8e07aafe1edc b
1411 1411 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1412 1412 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1413 1413 @@ -0,0 +1,1 @@
1414 1414 +b
1415 1415
1416 1416 Diff here should be the same:
1417 1417
1418 1418 $ hg log -vpr 3
1419 1419 changeset: 3:8e07aafe1edc
1420 1420 tag: tip
1421 1421 parent: 2:b09be438c43a
1422 1422 parent: 1:925d80f479bb
1423 1423 user: test
1424 1424 date: Thu Jan 01 00:00:00 1970 +0000
1425 1425 files: a
1426 1426 description:
1427 1427 3
1428 1428
1429 1429
1430 1430 diff -r b09be438c43a -r 8e07aafe1edc a
1431 1431 --- a/a Thu Jan 01 00:00:00 1970 +0000
1432 1432 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1433 1433 @@ -1,1 +1,1 @@
1434 1434 -b
1435 1435 +c
1436 1436 diff -r b09be438c43a -r 8e07aafe1edc b
1437 1437 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1438 1438 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1439 1439 @@ -0,0 +1,1 @@
1440 1440 +b
1441 1441
1442 1442 $ cd ..
1443 1443
1444 1444 'hg log -r rev fn' when last(filelog(fn)) != rev
1445 1445
1446 1446 $ hg init simplelog
1447 1447 $ cd simplelog
1448 1448 $ echo f > a
1449 1449 $ hg ci -Am'a' -d '0 0'
1450 1450 adding a
1451 1451 $ echo f >> a
1452 1452 $ hg ci -Am'a bis' -d '1 0'
1453 1453
1454 1454 $ hg log -r0 a
1455 1455 changeset: 0:9f758d63dcde
1456 1456 user: test
1457 1457 date: Thu Jan 01 00:00:00 1970 +0000
1458 1458 summary: a
1459 1459
1460 1460 enable obsolete to test hidden feature
1461 1461
1462 1462 $ cat >> $HGRCPATH << EOF
1463 1463 > [experimental]
1464 1464 > evolution=createmarkers
1465 1465 > EOF
1466 1466
1467 1467 $ hg log --template='{rev}:{node}\n'
1468 1468 1:a765632148dc55d38c35c4f247c618701886cb2f
1469 1469 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1470 1470 $ hg debugobsolete a765632148dc55d38c35c4f247c618701886cb2f
1471 1471 $ hg up null -q
1472 1472 $ hg log --template='{rev}:{node}\n'
1473 1473 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1474 1474 $ hg log --template='{rev}:{node}\n' --hidden
1475 1475 1:a765632148dc55d38c35c4f247c618701886cb2f
1476 1476 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1477 1477 $ hg log -r a
1478 1478 abort: hidden revision 'a'!
1479 1479 (use --hidden to access hidden revisions)
1480 1480 [255]
1481 1481
1482 1482 test that parent prevent a changeset to be hidden
1483 1483
1484 1484 $ hg up 1 -q --hidden
1485 1485 $ hg log --template='{rev}:{node}\n'
1486 1486 1:a765632148dc55d38c35c4f247c618701886cb2f
1487 1487 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1488 1488
1489 1489 test that second parent prevent a changeset to be hidden too
1490 1490
1491 1491 $ hg debugsetparents 0 1 # nothing suitable to merge here
1492 1492 $ hg log --template='{rev}:{node}\n'
1493 1493 1:a765632148dc55d38c35c4f247c618701886cb2f
1494 1494 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1495 1495 $ hg debugsetparents 1
1496 1496 $ hg up -q null
1497 1497
1498 1498 bookmarks prevent a changeset being hidden
1499 1499
1500 1500 $ hg bookmark --hidden -r 1 X
1501 1501 $ hg log --template '{rev}:{node}\n'
1502 1502 1:a765632148dc55d38c35c4f247c618701886cb2f
1503 1503 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1504 1504 $ hg bookmark -d X
1505 1505
1506 1506 divergent bookmarks are not hidden
1507 1507
1508 1508 $ hg bookmark --hidden -r 1 X@foo
1509 1509 $ hg log --template '{rev}:{node}\n'
1510 1510 1:a765632148dc55d38c35c4f247c618701886cb2f
1511 1511 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1512 1512
1513 1513 clear extensions configuration
1514 1514 $ echo '[extensions]' >> $HGRCPATH
1515 1515 $ echo "obs=!" >> $HGRCPATH
1516 1516 $ cd ..
1517 1517
1518 1518 test -u/-k for problematic encoding
1519 1519 # unicode: cp932:
1520 1520 # u30A2 0x83 0x41(= 'A')
1521 1521 # u30C2 0x83 0x61(= 'a')
1522 1522
1523 1523 $ hg init problematicencoding
1524 1524 $ cd problematicencoding
1525 1525
1526 1526 $ python > setup.sh <<EOF
1527 1527 > print u'''
1528 1528 > echo a > text
1529 1529 > hg add text
1530 1530 > hg --encoding utf-8 commit -u '\u30A2' -m none
1531 1531 > echo b > text
1532 1532 > hg --encoding utf-8 commit -u '\u30C2' -m none
1533 1533 > echo c > text
1534 1534 > hg --encoding utf-8 commit -u none -m '\u30A2'
1535 1535 > echo d > text
1536 1536 > hg --encoding utf-8 commit -u none -m '\u30C2'
1537 1537 > '''.encode('utf-8')
1538 1538 > EOF
1539 1539 $ sh < setup.sh
1540 1540
1541 1541 test in problematic encoding
1542 1542 $ python > test.sh <<EOF
1543 1543 > print u'''
1544 1544 > hg --encoding cp932 log --template '{rev}\\n' -u '\u30A2'
1545 1545 > echo ====
1546 1546 > hg --encoding cp932 log --template '{rev}\\n' -u '\u30C2'
1547 1547 > echo ====
1548 1548 > hg --encoding cp932 log --template '{rev}\\n' -k '\u30A2'
1549 1549 > echo ====
1550 1550 > hg --encoding cp932 log --template '{rev}\\n' -k '\u30C2'
1551 1551 > '''.encode('cp932')
1552 1552 > EOF
1553 1553 $ sh < test.sh
1554 1554 0
1555 1555 ====
1556 1556 1
1557 1557 ====
1558 1558 2
1559 1559 0
1560 1560 ====
1561 1561 3
1562 1562 1
1563 1563
1564 1564 $ cd ..
1565 1565
1566 1566 test hg log on non-existent files and on directories
1567 1567 $ hg init issue1340
1568 1568 $ cd issue1340
1569 1569 $ mkdir d1; mkdir D2; mkdir D3.i; mkdir d4.hg; mkdir d5.d; mkdir .d6
1570 1570 $ echo 1 > d1/f1
1571 1571 $ echo 1 > D2/f1
1572 1572 $ echo 1 > D3.i/f1
1573 1573 $ echo 1 > d4.hg/f1
1574 1574 $ echo 1 > d5.d/f1
1575 1575 $ echo 1 > .d6/f1
1576 1576 $ hg -q add .
1577 1577 $ hg commit -m "a bunch of weird directories"
1578 1578 $ hg log -l1 d1/f1 | grep changeset
1579 1579 changeset: 0:65624cd9070a
1580 1580 $ hg log -l1 f1
1581 1581 $ hg log -l1 . | grep changeset
1582 1582 changeset: 0:65624cd9070a
1583 1583 $ hg log -l1 ./ | grep changeset
1584 1584 changeset: 0:65624cd9070a
1585 1585 $ hg log -l1 d1 | grep changeset
1586 1586 changeset: 0:65624cd9070a
1587 1587 $ hg log -l1 D2 | grep changeset
1588 1588 changeset: 0:65624cd9070a
1589 1589 $ hg log -l1 D2/f1 | grep changeset
1590 1590 changeset: 0:65624cd9070a
1591 1591 $ hg log -l1 D3.i | grep changeset
1592 1592 changeset: 0:65624cd9070a
1593 1593 $ hg log -l1 D3.i/f1 | grep changeset
1594 1594 changeset: 0:65624cd9070a
1595 1595 $ hg log -l1 d4.hg | grep changeset
1596 1596 changeset: 0:65624cd9070a
1597 1597 $ hg log -l1 d4.hg/f1 | grep changeset
1598 1598 changeset: 0:65624cd9070a
1599 1599 $ hg log -l1 d5.d | grep changeset
1600 1600 changeset: 0:65624cd9070a
1601 1601 $ hg log -l1 d5.d/f1 | grep changeset
1602 1602 changeset: 0:65624cd9070a
1603 1603 $ hg log -l1 .d6 | grep changeset
1604 1604 changeset: 0:65624cd9070a
1605 1605 $ hg log -l1 .d6/f1 | grep changeset
1606 1606 changeset: 0:65624cd9070a
1607 1607
1608 1608 issue3772: hg log -r :null showing revision 0 as well
1609 1609
1610 1610 $ hg log -r :null
1611 1611 changeset: 0:65624cd9070a
1612 1612 tag: tip
1613 1613 user: test
1614 1614 date: Thu Jan 01 00:00:00 1970 +0000
1615 1615 summary: a bunch of weird directories
1616 1616
1617 1617 changeset: -1:000000000000
1618 1618 user:
1619 1619 date: Thu Jan 01 00:00:00 1970 +0000
1620 1620
1621 1621 $ hg log -r null:null
1622 1622 changeset: -1:000000000000
1623 1623 user:
1624 1624 date: Thu Jan 01 00:00:00 1970 +0000
1625 1625
1626 1626 working-directory revision requires special treatment
1627 1627
1628 1628 $ hg log -r 'wdir()'
1629 1629 changeset: 0:65624cd9070a+
1630 1630 user: test
1631 1631 date: [A-Za-z0-9:+ ]+ (re)
1632 1632
1633 1633 $ hg log -r 'wdir()' -q
1634 1634 0:65624cd9070a+
1635 1635
1636 1636 $ hg log -r 'wdir()' --debug
1637 1637 changeset: 0:65624cd9070a035fa7191a54f2b8af39f16b0c08+
1638 1638 phase: draft
1639 1639 parent: 0:65624cd9070a035fa7191a54f2b8af39f16b0c08
1640 1640 parent: -1:0000000000000000000000000000000000000000
1641 1641 user: test
1642 1642 date: [A-Za-z0-9:+ ]+ (re)
1643 1643 extra: branch=default
1644 1644
1645 1645 $ hg log -r 'wdir()' -Tjson
1646 1646 [
1647 1647 {
1648 1648 "rev": null,
1649 1649 "node": null,
1650 1650 "branch": "default",
1651 1651 "phase": "draft",
1652 1652 "user": "test",
1653 1653 "date": [*, 0], (glob)
1654 1654 "desc": "",
1655 1655 "bookmarks": [],
1656 1656 "tags": ["tip"],
1657 1657 "parents": ["65624cd9070a035fa7191a54f2b8af39f16b0c08"]
1658 1658 }
1659 1659 ]
1660 1660
1661 1661 $ hg log -r 'wdir()' -Tjson -q
1662 1662 [
1663 1663 {
1664 1664 "rev": null,
1665 1665 "node": null
1666 1666 }
1667 1667 ]
1668 1668
1669 $ hg log -r 'wdir()' -Tjson --debug
1670 [
1671 {
1672 "rev": null,
1673 "node": null,
1674 "branch": "default",
1675 "phase": "draft",
1676 "user": "test",
1677 "date": [*, 0], (glob)
1678 "desc": "",
1679 "bookmarks": [],
1680 "tags": ["tip"],
1681 "parents": ["65624cd9070a035fa7191a54f2b8af39f16b0c08"],
1682 "manifest": null,
1683 "extra": {"branch": "default"},
1684 "modified": [],
1685 "added": [],
1686 "removed": []
1687 }
1688 ]
1689
1669 1690 Check that adding an arbitrary name shows up in log automatically
1670 1691
1671 1692 $ cat > ../names.py <<EOF
1672 1693 > """A small extension to test adding arbitrary names to a repo"""
1673 1694 > from mercurial.namespaces import namespace
1674 1695 >
1675 1696 > def reposetup(ui, repo):
1676 1697 > foo = {'foo': repo[0].node()}
1677 1698 > names = lambda r: foo.keys()
1678 1699 > namemap = lambda r, name: foo.get(name)
1679 1700 > nodemap = lambda r, node: [name for name, n in foo.iteritems()
1680 1701 > if n == node]
1681 1702 > ns = namespace("bars", templatename="bar", logname="barlog",
1682 1703 > colorname="barcolor", listnames=names, namemap=namemap,
1683 1704 > nodemap=nodemap)
1684 1705 >
1685 1706 > repo.names.addnamespace(ns)
1686 1707 > EOF
1687 1708
1688 1709 $ hg --config extensions.names=../names.py log -r 0
1689 1710 changeset: 0:65624cd9070a
1690 1711 tag: tip
1691 1712 barlog: foo
1692 1713 user: test
1693 1714 date: Thu Jan 01 00:00:00 1970 +0000
1694 1715 summary: a bunch of weird directories
1695 1716
1696 1717 $ hg --config extensions.names=../names.py \
1697 1718 > --config extensions.color= --config color.log.barcolor=red \
1698 1719 > --color=always log -r 0
1699 1720 \x1b[0;33mchangeset: 0:65624cd9070a\x1b[0m (esc)
1700 1721 tag: tip
1701 1722 \x1b[0;31mbarlog: foo\x1b[0m (esc)
1702 1723 user: test
1703 1724 date: Thu Jan 01 00:00:00 1970 +0000
1704 1725 summary: a bunch of weird directories
1705 1726
1706 1727 $ hg --config extensions.names=../names.py log -r 0 --template '{bars}\n'
1707 1728 foo
1708 1729
1709 1730 $ cd ..
1710 1731
1711 1732 hg log -f dir across branches
1712 1733
1713 1734 $ hg init acrossbranches
1714 1735 $ cd acrossbranches
1715 1736 $ mkdir d
1716 1737 $ echo a > d/a && hg ci -Aqm a
1717 1738 $ echo b > d/a && hg ci -Aqm b
1718 1739 $ hg up -q 0
1719 1740 $ echo b > d/a && hg ci -Aqm c
1720 1741 $ hg log -f d -T '{desc}' -G
1721 1742 @ c
1722 1743 |
1723 1744 o a
1724 1745
1725 1746 Ensure that largefiles doesn't interfere with following a normal file
1726 1747 $ hg --config extensions.largefiles= log -f d -T '{desc}' -G
1727 1748 @ c
1728 1749 |
1729 1750 o a
1730 1751
1731 1752 $ hg log -f d/a -T '{desc}' -G
1732 1753 @ c
1733 1754 |
1734 1755 o a
1735 1756
1736 1757 $ cd ..
1737 1758
1738 1759 hg log -f with linkrev pointing to another branch
1739 1760 -------------------------------------------------
1740 1761
1741 1762 create history with a filerev whose linkrev points to another branch
1742 1763
1743 1764 $ hg init branchedlinkrev
1744 1765 $ cd branchedlinkrev
1745 1766 $ echo 1 > a
1746 1767 $ hg commit -Am 'content1'
1747 1768 adding a
1748 1769 $ echo 2 > a
1749 1770 $ hg commit -m 'content2'
1750 1771 $ hg up --rev 'desc(content1)'
1751 1772 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1752 1773 $ echo unrelated > unrelated
1753 1774 $ hg commit -Am 'unrelated'
1754 1775 adding unrelated
1755 1776 created new head
1756 1777 $ hg graft -r 'desc(content2)'
1757 1778 grafting 1:2294ae80ad84 "content2"
1758 1779 $ echo 3 > a
1759 1780 $ hg commit -m 'content3'
1760 1781 $ hg log -G
1761 1782 @ changeset: 4:50b9b36e9c5d
1762 1783 | tag: tip
1763 1784 | user: test
1764 1785 | date: Thu Jan 01 00:00:00 1970 +0000
1765 1786 | summary: content3
1766 1787 |
1767 1788 o changeset: 3:15b2327059e5
1768 1789 | user: test
1769 1790 | date: Thu Jan 01 00:00:00 1970 +0000
1770 1791 | summary: content2
1771 1792 |
1772 1793 o changeset: 2:2029acd1168c
1773 1794 | parent: 0:ae0a3c9f9e95
1774 1795 | user: test
1775 1796 | date: Thu Jan 01 00:00:00 1970 +0000
1776 1797 | summary: unrelated
1777 1798 |
1778 1799 | o changeset: 1:2294ae80ad84
1779 1800 |/ user: test
1780 1801 | date: Thu Jan 01 00:00:00 1970 +0000
1781 1802 | summary: content2
1782 1803 |
1783 1804 o changeset: 0:ae0a3c9f9e95
1784 1805 user: test
1785 1806 date: Thu Jan 01 00:00:00 1970 +0000
1786 1807 summary: content1
1787 1808
1788 1809
1789 1810 log -f on the file should list the graft result.
1790 1811
1791 1812 $ hg log -Gf a
1792 1813 @ changeset: 4:50b9b36e9c5d
1793 1814 | tag: tip
1794 1815 | user: test
1795 1816 | date: Thu Jan 01 00:00:00 1970 +0000
1796 1817 | summary: content3
1797 1818 |
1798 1819 o changeset: 3:15b2327059e5
1799 1820 | user: test
1800 1821 | date: Thu Jan 01 00:00:00 1970 +0000
1801 1822 | summary: content2
1802 1823 |
1803 1824 o changeset: 0:ae0a3c9f9e95
1804 1825 user: test
1805 1826 date: Thu Jan 01 00:00:00 1970 +0000
1806 1827 summary: content1
1807 1828
1808 1829
1809 1830 plain log lists the original version
1810 1831 (XXX we should probably list both)
1811 1832
1812 1833 $ hg log -G a
1813 1834 @ changeset: 4:50b9b36e9c5d
1814 1835 | tag: tip
1815 1836 | user: test
1816 1837 | date: Thu Jan 01 00:00:00 1970 +0000
1817 1838 | summary: content3
1818 1839 |
1819 1840 | o changeset: 1:2294ae80ad84
1820 1841 |/ user: test
1821 1842 | date: Thu Jan 01 00:00:00 1970 +0000
1822 1843 | summary: content2
1823 1844 |
1824 1845 o changeset: 0:ae0a3c9f9e95
1825 1846 user: test
1826 1847 date: Thu Jan 01 00:00:00 1970 +0000
1827 1848 summary: content1
1828 1849
1829 1850
1830 1851 hg log -f from the grafted changeset
1831 1852 (The bootstrap should properly take the topology in account)
1832 1853
1833 1854 $ hg up 'desc(content3)^'
1834 1855 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1835 1856 $ hg log -Gf a
1836 1857 @ changeset: 3:15b2327059e5
1837 1858 | user: test
1838 1859 | date: Thu Jan 01 00:00:00 1970 +0000
1839 1860 | summary: content2
1840 1861 |
1841 1862 o changeset: 0:ae0a3c9f9e95
1842 1863 user: test
1843 1864 date: Thu Jan 01 00:00:00 1970 +0000
1844 1865 summary: content1
1845 1866
1846 1867
1847 1868 Test that we use the first non-hidden changeset in that case.
1848 1869
1849 1870 (hide the changeset)
1850 1871
1851 1872 $ hg log -T '{node}\n' -r 1
1852 1873 2294ae80ad8447bc78383182eeac50cb049df623
1853 1874 $ hg debugobsolete 2294ae80ad8447bc78383182eeac50cb049df623
1854 1875 $ hg log -G
1855 1876 o changeset: 4:50b9b36e9c5d
1856 1877 | tag: tip
1857 1878 | user: test
1858 1879 | date: Thu Jan 01 00:00:00 1970 +0000
1859 1880 | summary: content3
1860 1881 |
1861 1882 @ changeset: 3:15b2327059e5
1862 1883 | user: test
1863 1884 | date: Thu Jan 01 00:00:00 1970 +0000
1864 1885 | summary: content2
1865 1886 |
1866 1887 o changeset: 2:2029acd1168c
1867 1888 | parent: 0:ae0a3c9f9e95
1868 1889 | user: test
1869 1890 | date: Thu Jan 01 00:00:00 1970 +0000
1870 1891 | summary: unrelated
1871 1892 |
1872 1893 o changeset: 0:ae0a3c9f9e95
1873 1894 user: test
1874 1895 date: Thu Jan 01 00:00:00 1970 +0000
1875 1896 summary: content1
1876 1897
1877 1898
1878 1899 Check that log on the file does not drop the file revision.
1879 1900
1880 1901 $ hg log -G a
1881 1902 o changeset: 4:50b9b36e9c5d
1882 1903 | tag: tip
1883 1904 | user: test
1884 1905 | date: Thu Jan 01 00:00:00 1970 +0000
1885 1906 | summary: content3
1886 1907 |
1887 1908 @ changeset: 3:15b2327059e5
1888 1909 | user: test
1889 1910 | date: Thu Jan 01 00:00:00 1970 +0000
1890 1911 | summary: content2
1891 1912 |
1892 1913 o changeset: 0:ae0a3c9f9e95
1893 1914 user: test
1894 1915 date: Thu Jan 01 00:00:00 1970 +0000
1895 1916 summary: content1
1896 1917
1897 1918
1898 1919 Even when a head revision is linkrev-shadowed.
1899 1920
1900 1921 $ hg log -T '{node}\n' -r 4
1901 1922 50b9b36e9c5df2c6fc6dcefa8ad0da929e84aed2
1902 1923 $ hg debugobsolete 50b9b36e9c5df2c6fc6dcefa8ad0da929e84aed2
1903 1924 $ hg log -G a
1904 1925 @ changeset: 3:15b2327059e5
1905 1926 | tag: tip
1906 1927 | user: test
1907 1928 | date: Thu Jan 01 00:00:00 1970 +0000
1908 1929 | summary: content2
1909 1930 |
1910 1931 o changeset: 0:ae0a3c9f9e95
1911 1932 user: test
1912 1933 date: Thu Jan 01 00:00:00 1970 +0000
1913 1934 summary: content1
1914 1935
1915 1936
1916 1937 $ cd ..
1917 1938
1918 1939 Even when the file revision is missing from some head:
1919 1940
1920 1941 $ hg init issue4490
1921 1942 $ cd issue4490
1922 1943 $ echo '[experimental]' >> .hg/hgrc
1923 1944 $ echo 'evolution=createmarkers' >> .hg/hgrc
1924 1945 $ echo a > a
1925 1946 $ hg ci -Am0
1926 1947 adding a
1927 1948 $ echo b > b
1928 1949 $ hg ci -Am1
1929 1950 adding b
1930 1951 $ echo B > b
1931 1952 $ hg ci --amend -m 1
1932 1953 $ hg up 0
1933 1954 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1934 1955 $ echo c > c
1935 1956 $ hg ci -Am2
1936 1957 adding c
1937 1958 created new head
1938 1959 $ hg up 'head() and not .'
1939 1960 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
1940 1961 $ hg log -G
1941 1962 o changeset: 4:db815d6d32e6
1942 1963 | tag: tip
1943 1964 | parent: 0:f7b1eb17ad24
1944 1965 | user: test
1945 1966 | date: Thu Jan 01 00:00:00 1970 +0000
1946 1967 | summary: 2
1947 1968 |
1948 1969 | @ changeset: 3:9bc8ce7f9356
1949 1970 |/ parent: 0:f7b1eb17ad24
1950 1971 | user: test
1951 1972 | date: Thu Jan 01 00:00:00 1970 +0000
1952 1973 | summary: 1
1953 1974 |
1954 1975 o changeset: 0:f7b1eb17ad24
1955 1976 user: test
1956 1977 date: Thu Jan 01 00:00:00 1970 +0000
1957 1978 summary: 0
1958 1979
1959 1980 $ hg log -f -G b
1960 1981 @ changeset: 3:9bc8ce7f9356
1961 1982 | parent: 0:f7b1eb17ad24
1962 1983 | user: test
1963 1984 | date: Thu Jan 01 00:00:00 1970 +0000
1964 1985 | summary: 1
1965 1986 |
1966 1987 $ hg log -G b
1967 1988 @ changeset: 3:9bc8ce7f9356
1968 1989 | parent: 0:f7b1eb17ad24
1969 1990 | user: test
1970 1991 | date: Thu Jan 01 00:00:00 1970 +0000
1971 1992 | summary: 1
1972 1993 |
1973 1994 $ cd ..
1974 1995
1975 1996 Check proper report when the manifest changes but not the file issue4499
1976 1997 ------------------------------------------------------------------------
1977 1998
1978 1999 $ hg init issue4499
1979 2000 $ cd issue4499
1980 2001 $ for f in A B C D F E G H I J K L M N O P Q R S T U; do
1981 2002 > echo 1 > $f;
1982 2003 > hg add $f;
1983 2004 > done
1984 2005 $ hg commit -m 'A1B1C1'
1985 2006 $ echo 2 > A
1986 2007 $ echo 2 > B
1987 2008 $ echo 2 > C
1988 2009 $ hg commit -m 'A2B2C2'
1989 2010 $ hg up 0
1990 2011 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1991 2012 $ echo 3 > A
1992 2013 $ echo 2 > B
1993 2014 $ echo 2 > C
1994 2015 $ hg commit -m 'A3B2C2'
1995 2016 created new head
1996 2017
1997 2018 $ hg log -G
1998 2019 @ changeset: 2:fe5fc3d0eb17
1999 2020 | tag: tip
2000 2021 | parent: 0:abf4f0e38563
2001 2022 | user: test
2002 2023 | date: Thu Jan 01 00:00:00 1970 +0000
2003 2024 | summary: A3B2C2
2004 2025 |
2005 2026 | o changeset: 1:07dcc6b312c0
2006 2027 |/ user: test
2007 2028 | date: Thu Jan 01 00:00:00 1970 +0000
2008 2029 | summary: A2B2C2
2009 2030 |
2010 2031 o changeset: 0:abf4f0e38563
2011 2032 user: test
2012 2033 date: Thu Jan 01 00:00:00 1970 +0000
2013 2034 summary: A1B1C1
2014 2035
2015 2036
2016 2037 Log -f on B should reports current changesets
2017 2038
2018 2039 $ hg log -fG B
2019 2040 @ changeset: 2:fe5fc3d0eb17
2020 2041 | tag: tip
2021 2042 | parent: 0:abf4f0e38563
2022 2043 | user: test
2023 2044 | date: Thu Jan 01 00:00:00 1970 +0000
2024 2045 | summary: A3B2C2
2025 2046 |
2026 2047 o changeset: 0:abf4f0e38563
2027 2048 user: test
2028 2049 date: Thu Jan 01 00:00:00 1970 +0000
2029 2050 summary: A1B1C1
2030 2051
2031 2052 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now