##// END OF EJS Templates
log: fix log -rREV FILE when REV isnt the last filerev (issue2492)...
Nicolas Dumazet -
r12972:7916a84c stable
parent child Browse files
Show More
@@ -1,1365 +1,1374
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, glob, tempfile
11 11 import util, templater, patch, error, encoding, templatekw
12 12 import match as matchmod
13 13 import similar, revset, subrepo
14 14
15 15 revrangesep = ':'
16 16
17 17 def parsealiases(cmd):
18 18 return cmd.lstrip("^").split("|")
19 19
20 20 def findpossible(cmd, table, strict=False):
21 21 """
22 22 Return cmd -> (aliases, command table entry)
23 23 for each matching command.
24 24 Return debug commands (or their aliases) only if no normal command matches.
25 25 """
26 26 choice = {}
27 27 debugchoice = {}
28 28 for e in table.keys():
29 29 aliases = parsealiases(e)
30 30 found = None
31 31 if cmd in aliases:
32 32 found = cmd
33 33 elif not strict:
34 34 for a in aliases:
35 35 if a.startswith(cmd):
36 36 found = a
37 37 break
38 38 if found is not None:
39 39 if aliases[0].startswith("debug") or found.startswith("debug"):
40 40 debugchoice[found] = (aliases, table[e])
41 41 else:
42 42 choice[found] = (aliases, table[e])
43 43
44 44 if not choice and debugchoice:
45 45 choice = debugchoice
46 46
47 47 return choice
48 48
49 49 def findcmd(cmd, table, strict=True):
50 50 """Return (aliases, command table entry) for command string."""
51 51 choice = findpossible(cmd, table, strict)
52 52
53 53 if cmd in choice:
54 54 return choice[cmd]
55 55
56 56 if len(choice) > 1:
57 57 clist = choice.keys()
58 58 clist.sort()
59 59 raise error.AmbiguousCommand(cmd, clist)
60 60
61 61 if choice:
62 62 return choice.values()[0]
63 63
64 64 raise error.UnknownCommand(cmd)
65 65
66 66 def findrepo(p):
67 67 while not os.path.isdir(os.path.join(p, ".hg")):
68 68 oldp, p = p, os.path.dirname(p)
69 69 if p == oldp:
70 70 return None
71 71
72 72 return p
73 73
74 74 def bail_if_changed(repo):
75 75 if repo.dirstate.parents()[1] != nullid:
76 76 raise util.Abort(_('outstanding uncommitted merge'))
77 77 modified, added, removed, deleted = repo.status()[:4]
78 78 if modified or added or removed or deleted:
79 79 raise util.Abort(_("outstanding uncommitted changes"))
80 80
81 81 def logmessage(opts):
82 82 """ get the log message according to -m and -l option """
83 83 message = opts.get('message')
84 84 logfile = opts.get('logfile')
85 85
86 86 if message and logfile:
87 87 raise util.Abort(_('options --message and --logfile are mutually '
88 88 'exclusive'))
89 89 if not message and logfile:
90 90 try:
91 91 if logfile == '-':
92 92 message = sys.stdin.read()
93 93 else:
94 94 message = open(logfile).read()
95 95 except IOError, inst:
96 96 raise util.Abort(_("can't read commit message '%s': %s") %
97 97 (logfile, inst.strerror))
98 98 return message
99 99
100 100 def loglimit(opts):
101 101 """get the log limit according to option -l/--limit"""
102 102 limit = opts.get('limit')
103 103 if limit:
104 104 try:
105 105 limit = int(limit)
106 106 except ValueError:
107 107 raise util.Abort(_('limit must be a positive integer'))
108 108 if limit <= 0:
109 109 raise util.Abort(_('limit must be positive'))
110 110 else:
111 111 limit = None
112 112 return limit
113 113
114 114 def revsingle(repo, revspec, default='.'):
115 115 if not revspec:
116 116 return repo[default]
117 117
118 118 l = revrange(repo, [revspec])
119 119 if len(l) < 1:
120 120 raise util.Abort(_('empty revision set'))
121 121 return repo[l[-1]]
122 122
123 123 def revpair(repo, revs):
124 124 if not revs:
125 125 return repo.dirstate.parents()[0], None
126 126
127 127 l = revrange(repo, revs)
128 128
129 129 if len(l) == 0:
130 130 return repo.dirstate.parents()[0], None
131 131
132 132 if len(l) == 1:
133 133 return repo.lookup(l[0]), None
134 134
135 135 return repo.lookup(l[0]), repo.lookup(l[-1])
136 136
137 137 def revrange(repo, revs):
138 138 """Yield revision as strings from a list of revision specifications."""
139 139
140 140 def revfix(repo, val, defval):
141 141 if not val and val != 0 and defval is not None:
142 142 return defval
143 143 return repo.changelog.rev(repo.lookup(val))
144 144
145 145 seen, l = set(), []
146 146 for spec in revs:
147 147 # attempt to parse old-style ranges first to deal with
148 148 # things like old-tag which contain query metacharacters
149 149 try:
150 150 if revrangesep in spec:
151 151 start, end = spec.split(revrangesep, 1)
152 152 start = revfix(repo, start, 0)
153 153 end = revfix(repo, end, len(repo) - 1)
154 154 step = start > end and -1 or 1
155 155 for rev in xrange(start, end + step, step):
156 156 if rev in seen:
157 157 continue
158 158 seen.add(rev)
159 159 l.append(rev)
160 160 continue
161 161 elif spec and spec in repo: # single unquoted rev
162 162 rev = revfix(repo, spec, None)
163 163 if rev in seen:
164 164 continue
165 165 seen.add(rev)
166 166 l.append(rev)
167 167 continue
168 168 except error.RepoLookupError:
169 169 pass
170 170
171 171 # fall through to new-style queries if old-style fails
172 172 m = revset.match(spec)
173 173 for r in m(repo, range(len(repo))):
174 174 if r not in seen:
175 175 l.append(r)
176 176 seen.update(l)
177 177
178 178 return l
179 179
180 180 def make_filename(repo, pat, node,
181 181 total=None, seqno=None, revwidth=None, pathname=None):
182 182 node_expander = {
183 183 'H': lambda: hex(node),
184 184 'R': lambda: str(repo.changelog.rev(node)),
185 185 'h': lambda: short(node),
186 186 }
187 187 expander = {
188 188 '%': lambda: '%',
189 189 'b': lambda: os.path.basename(repo.root),
190 190 }
191 191
192 192 try:
193 193 if node:
194 194 expander.update(node_expander)
195 195 if node:
196 196 expander['r'] = (lambda:
197 197 str(repo.changelog.rev(node)).zfill(revwidth or 0))
198 198 if total is not None:
199 199 expander['N'] = lambda: str(total)
200 200 if seqno is not None:
201 201 expander['n'] = lambda: str(seqno)
202 202 if total is not None and seqno is not None:
203 203 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
204 204 if pathname is not None:
205 205 expander['s'] = lambda: os.path.basename(pathname)
206 206 expander['d'] = lambda: os.path.dirname(pathname) or '.'
207 207 expander['p'] = lambda: pathname
208 208
209 209 newname = []
210 210 patlen = len(pat)
211 211 i = 0
212 212 while i < patlen:
213 213 c = pat[i]
214 214 if c == '%':
215 215 i += 1
216 216 c = pat[i]
217 217 c = expander[c]()
218 218 newname.append(c)
219 219 i += 1
220 220 return ''.join(newname)
221 221 except KeyError, inst:
222 222 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
223 223 inst.args[0])
224 224
225 225 def make_file(repo, pat, node=None,
226 226 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
227 227
228 228 writable = 'w' in mode or 'a' in mode
229 229
230 230 if not pat or pat == '-':
231 231 return writable and sys.stdout or sys.stdin
232 232 if hasattr(pat, 'write') and writable:
233 233 return pat
234 234 if hasattr(pat, 'read') and 'r' in mode:
235 235 return pat
236 236 return open(make_filename(repo, pat, node, total, seqno, revwidth,
237 237 pathname),
238 238 mode)
239 239
240 240 def expandpats(pats):
241 241 if not util.expandglobs:
242 242 return list(pats)
243 243 ret = []
244 244 for p in pats:
245 245 kind, name = matchmod._patsplit(p, None)
246 246 if kind is None:
247 247 try:
248 248 globbed = glob.glob(name)
249 249 except re.error:
250 250 globbed = [name]
251 251 if globbed:
252 252 ret.extend(globbed)
253 253 continue
254 254 ret.append(p)
255 255 return ret
256 256
257 257 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
258 258 if not globbed and default == 'relpath':
259 259 pats = expandpats(pats or [])
260 260 m = matchmod.match(repo.root, repo.getcwd(), pats,
261 261 opts.get('include'), opts.get('exclude'), default,
262 262 auditor=repo.auditor)
263 263 def badfn(f, msg):
264 264 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
265 265 m.bad = badfn
266 266 return m
267 267
268 268 def matchall(repo):
269 269 return matchmod.always(repo.root, repo.getcwd())
270 270
271 271 def matchfiles(repo, files):
272 272 return matchmod.exact(repo.root, repo.getcwd(), files)
273 273
274 274 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
275 275 if dry_run is None:
276 276 dry_run = opts.get('dry_run')
277 277 if similarity is None:
278 278 similarity = float(opts.get('similarity') or 0)
279 279 # we'd use status here, except handling of symlinks and ignore is tricky
280 280 added, unknown, deleted, removed = [], [], [], []
281 281 audit_path = util.path_auditor(repo.root)
282 282 m = match(repo, pats, opts)
283 283 for abs in repo.walk(m):
284 284 target = repo.wjoin(abs)
285 285 good = True
286 286 try:
287 287 audit_path(abs)
288 288 except:
289 289 good = False
290 290 rel = m.rel(abs)
291 291 exact = m.exact(abs)
292 292 if good and abs not in repo.dirstate:
293 293 unknown.append(abs)
294 294 if repo.ui.verbose or not exact:
295 295 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
296 296 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
297 297 or (os.path.isdir(target) and not os.path.islink(target))):
298 298 deleted.append(abs)
299 299 if repo.ui.verbose or not exact:
300 300 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
301 301 # for finding renames
302 302 elif repo.dirstate[abs] == 'r':
303 303 removed.append(abs)
304 304 elif repo.dirstate[abs] == 'a':
305 305 added.append(abs)
306 306 copies = {}
307 307 if similarity > 0:
308 308 for old, new, score in similar.findrenames(repo,
309 309 added + unknown, removed + deleted, similarity):
310 310 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
311 311 repo.ui.status(_('recording removal of %s as rename to %s '
312 312 '(%d%% similar)\n') %
313 313 (m.rel(old), m.rel(new), score * 100))
314 314 copies[new] = old
315 315
316 316 if not dry_run:
317 317 wctx = repo[None]
318 318 wlock = repo.wlock()
319 319 try:
320 320 wctx.remove(deleted)
321 321 wctx.add(unknown)
322 322 for new, old in copies.iteritems():
323 323 wctx.copy(old, new)
324 324 finally:
325 325 wlock.release()
326 326
327 327 def updatedir(ui, repo, patches, similarity=0):
328 328 '''Update dirstate after patch application according to metadata'''
329 329 if not patches:
330 330 return
331 331 copies = []
332 332 removes = set()
333 333 cfiles = patches.keys()
334 334 cwd = repo.getcwd()
335 335 if cwd:
336 336 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
337 337 for f in patches:
338 338 gp = patches[f]
339 339 if not gp:
340 340 continue
341 341 if gp.op == 'RENAME':
342 342 copies.append((gp.oldpath, gp.path))
343 343 removes.add(gp.oldpath)
344 344 elif gp.op == 'COPY':
345 345 copies.append((gp.oldpath, gp.path))
346 346 elif gp.op == 'DELETE':
347 347 removes.add(gp.path)
348 348
349 349 wctx = repo[None]
350 350 for src, dst in copies:
351 351 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
352 352 if (not similarity) and removes:
353 353 wctx.remove(sorted(removes), True)
354 354
355 355 for f in patches:
356 356 gp = patches[f]
357 357 if gp and gp.mode:
358 358 islink, isexec = gp.mode
359 359 dst = repo.wjoin(gp.path)
360 360 # patch won't create empty files
361 361 if gp.op == 'ADD' and not os.path.lexists(dst):
362 362 flags = (isexec and 'x' or '') + (islink and 'l' or '')
363 363 repo.wwrite(gp.path, '', flags)
364 364 util.set_flags(dst, islink, isexec)
365 365 addremove(repo, cfiles, similarity=similarity)
366 366 files = patches.keys()
367 367 files.extend([r for r in removes if r not in files])
368 368 return sorted(files)
369 369
370 370 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
371 371 """Update the dirstate to reflect the intent of copying src to dst. For
372 372 different reasons it might not end with dst being marked as copied from src.
373 373 """
374 374 origsrc = repo.dirstate.copied(src) or src
375 375 if dst == origsrc: # copying back a copy?
376 376 if repo.dirstate[dst] not in 'mn' and not dryrun:
377 377 repo.dirstate.normallookup(dst)
378 378 else:
379 379 if repo.dirstate[origsrc] == 'a' and origsrc == src:
380 380 if not ui.quiet:
381 381 ui.warn(_("%s has not been committed yet, so no copy "
382 382 "data will be stored for %s.\n")
383 383 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
384 384 if repo.dirstate[dst] in '?r' and not dryrun:
385 385 wctx.add([dst])
386 386 elif not dryrun:
387 387 wctx.copy(origsrc, dst)
388 388
389 389 def copy(ui, repo, pats, opts, rename=False):
390 390 # called with the repo lock held
391 391 #
392 392 # hgsep => pathname that uses "/" to separate directories
393 393 # ossep => pathname that uses os.sep to separate directories
394 394 cwd = repo.getcwd()
395 395 targets = {}
396 396 after = opts.get("after")
397 397 dryrun = opts.get("dry_run")
398 398 wctx = repo[None]
399 399
400 400 def walkpat(pat):
401 401 srcs = []
402 402 badstates = after and '?' or '?r'
403 403 m = match(repo, [pat], opts, globbed=True)
404 404 for abs in repo.walk(m):
405 405 state = repo.dirstate[abs]
406 406 rel = m.rel(abs)
407 407 exact = m.exact(abs)
408 408 if state in badstates:
409 409 if exact and state == '?':
410 410 ui.warn(_('%s: not copying - file is not managed\n') % rel)
411 411 if exact and state == 'r':
412 412 ui.warn(_('%s: not copying - file has been marked for'
413 413 ' remove\n') % rel)
414 414 continue
415 415 # abs: hgsep
416 416 # rel: ossep
417 417 srcs.append((abs, rel, exact))
418 418 return srcs
419 419
420 420 # abssrc: hgsep
421 421 # relsrc: ossep
422 422 # otarget: ossep
423 423 def copyfile(abssrc, relsrc, otarget, exact):
424 424 abstarget = util.canonpath(repo.root, cwd, otarget)
425 425 reltarget = repo.pathto(abstarget, cwd)
426 426 target = repo.wjoin(abstarget)
427 427 src = repo.wjoin(abssrc)
428 428 state = repo.dirstate[abstarget]
429 429
430 430 # check for collisions
431 431 prevsrc = targets.get(abstarget)
432 432 if prevsrc is not None:
433 433 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
434 434 (reltarget, repo.pathto(abssrc, cwd),
435 435 repo.pathto(prevsrc, cwd)))
436 436 return
437 437
438 438 # check for overwrites
439 439 exists = os.path.lexists(target)
440 440 if not after and exists or after and state in 'mn':
441 441 if not opts['force']:
442 442 ui.warn(_('%s: not overwriting - file exists\n') %
443 443 reltarget)
444 444 return
445 445
446 446 if after:
447 447 if not exists:
448 448 if rename:
449 449 ui.warn(_('%s: not recording move - %s does not exist\n') %
450 450 (relsrc, reltarget))
451 451 else:
452 452 ui.warn(_('%s: not recording copy - %s does not exist\n') %
453 453 (relsrc, reltarget))
454 454 return
455 455 elif not dryrun:
456 456 try:
457 457 if exists:
458 458 os.unlink(target)
459 459 targetdir = os.path.dirname(target) or '.'
460 460 if not os.path.isdir(targetdir):
461 461 os.makedirs(targetdir)
462 462 util.copyfile(src, target)
463 463 except IOError, inst:
464 464 if inst.errno == errno.ENOENT:
465 465 ui.warn(_('%s: deleted in working copy\n') % relsrc)
466 466 else:
467 467 ui.warn(_('%s: cannot copy - %s\n') %
468 468 (relsrc, inst.strerror))
469 469 return True # report a failure
470 470
471 471 if ui.verbose or not exact:
472 472 if rename:
473 473 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
474 474 else:
475 475 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
476 476
477 477 targets[abstarget] = abssrc
478 478
479 479 # fix up dirstate
480 480 dirstatecopy(ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd)
481 481 if rename and not dryrun:
482 482 wctx.remove([abssrc], not after)
483 483
484 484 # pat: ossep
485 485 # dest ossep
486 486 # srcs: list of (hgsep, hgsep, ossep, bool)
487 487 # return: function that takes hgsep and returns ossep
488 488 def targetpathfn(pat, dest, srcs):
489 489 if os.path.isdir(pat):
490 490 abspfx = util.canonpath(repo.root, cwd, pat)
491 491 abspfx = util.localpath(abspfx)
492 492 if destdirexists:
493 493 striplen = len(os.path.split(abspfx)[0])
494 494 else:
495 495 striplen = len(abspfx)
496 496 if striplen:
497 497 striplen += len(os.sep)
498 498 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
499 499 elif destdirexists:
500 500 res = lambda p: os.path.join(dest,
501 501 os.path.basename(util.localpath(p)))
502 502 else:
503 503 res = lambda p: dest
504 504 return res
505 505
506 506 # pat: ossep
507 507 # dest ossep
508 508 # srcs: list of (hgsep, hgsep, ossep, bool)
509 509 # return: function that takes hgsep and returns ossep
510 510 def targetpathafterfn(pat, dest, srcs):
511 511 if matchmod.patkind(pat):
512 512 # a mercurial pattern
513 513 res = lambda p: os.path.join(dest,
514 514 os.path.basename(util.localpath(p)))
515 515 else:
516 516 abspfx = util.canonpath(repo.root, cwd, pat)
517 517 if len(abspfx) < len(srcs[0][0]):
518 518 # A directory. Either the target path contains the last
519 519 # component of the source path or it does not.
520 520 def evalpath(striplen):
521 521 score = 0
522 522 for s in srcs:
523 523 t = os.path.join(dest, util.localpath(s[0])[striplen:])
524 524 if os.path.lexists(t):
525 525 score += 1
526 526 return score
527 527
528 528 abspfx = util.localpath(abspfx)
529 529 striplen = len(abspfx)
530 530 if striplen:
531 531 striplen += len(os.sep)
532 532 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
533 533 score = evalpath(striplen)
534 534 striplen1 = len(os.path.split(abspfx)[0])
535 535 if striplen1:
536 536 striplen1 += len(os.sep)
537 537 if evalpath(striplen1) > score:
538 538 striplen = striplen1
539 539 res = lambda p: os.path.join(dest,
540 540 util.localpath(p)[striplen:])
541 541 else:
542 542 # a file
543 543 if destdirexists:
544 544 res = lambda p: os.path.join(dest,
545 545 os.path.basename(util.localpath(p)))
546 546 else:
547 547 res = lambda p: dest
548 548 return res
549 549
550 550
551 551 pats = expandpats(pats)
552 552 if not pats:
553 553 raise util.Abort(_('no source or destination specified'))
554 554 if len(pats) == 1:
555 555 raise util.Abort(_('no destination specified'))
556 556 dest = pats.pop()
557 557 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
558 558 if not destdirexists:
559 559 if len(pats) > 1 or matchmod.patkind(pats[0]):
560 560 raise util.Abort(_('with multiple sources, destination must be an '
561 561 'existing directory'))
562 562 if util.endswithsep(dest):
563 563 raise util.Abort(_('destination %s is not a directory') % dest)
564 564
565 565 tfn = targetpathfn
566 566 if after:
567 567 tfn = targetpathafterfn
568 568 copylist = []
569 569 for pat in pats:
570 570 srcs = walkpat(pat)
571 571 if not srcs:
572 572 continue
573 573 copylist.append((tfn(pat, dest, srcs), srcs))
574 574 if not copylist:
575 575 raise util.Abort(_('no files to copy'))
576 576
577 577 errors = 0
578 578 for targetpath, srcs in copylist:
579 579 for abssrc, relsrc, exact in srcs:
580 580 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
581 581 errors += 1
582 582
583 583 if errors:
584 584 ui.warn(_('(consider using --after)\n'))
585 585
586 586 return errors != 0
587 587
588 588 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
589 589 runargs=None, appendpid=False):
590 590 '''Run a command as a service.'''
591 591
592 592 if opts['daemon'] and not opts['daemon_pipefds']:
593 593 # Signal child process startup with file removal
594 594 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
595 595 os.close(lockfd)
596 596 try:
597 597 if not runargs:
598 598 runargs = util.hgcmd() + sys.argv[1:]
599 599 runargs.append('--daemon-pipefds=%s' % lockpath)
600 600 # Don't pass --cwd to the child process, because we've already
601 601 # changed directory.
602 602 for i in xrange(1, len(runargs)):
603 603 if runargs[i].startswith('--cwd='):
604 604 del runargs[i]
605 605 break
606 606 elif runargs[i].startswith('--cwd'):
607 607 del runargs[i:i + 2]
608 608 break
609 609 def condfn():
610 610 return not os.path.exists(lockpath)
611 611 pid = util.rundetached(runargs, condfn)
612 612 if pid < 0:
613 613 raise util.Abort(_('child process failed to start'))
614 614 finally:
615 615 try:
616 616 os.unlink(lockpath)
617 617 except OSError, e:
618 618 if e.errno != errno.ENOENT:
619 619 raise
620 620 if parentfn:
621 621 return parentfn(pid)
622 622 else:
623 623 return
624 624
625 625 if initfn:
626 626 initfn()
627 627
628 628 if opts['pid_file']:
629 629 mode = appendpid and 'a' or 'w'
630 630 fp = open(opts['pid_file'], mode)
631 631 fp.write(str(os.getpid()) + '\n')
632 632 fp.close()
633 633
634 634 if opts['daemon_pipefds']:
635 635 lockpath = opts['daemon_pipefds']
636 636 try:
637 637 os.setsid()
638 638 except AttributeError:
639 639 pass
640 640 os.unlink(lockpath)
641 641 util.hidewindow()
642 642 sys.stdout.flush()
643 643 sys.stderr.flush()
644 644
645 645 nullfd = os.open(util.nulldev, os.O_RDWR)
646 646 logfilefd = nullfd
647 647 if logfile:
648 648 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
649 649 os.dup2(nullfd, 0)
650 650 os.dup2(logfilefd, 1)
651 651 os.dup2(logfilefd, 2)
652 652 if nullfd not in (0, 1, 2):
653 653 os.close(nullfd)
654 654 if logfile and logfilefd not in (0, 1, 2):
655 655 os.close(logfilefd)
656 656
657 657 if runfn:
658 658 return runfn()
659 659
660 660 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
661 661 opts=None):
662 662 '''export changesets as hg patches.'''
663 663
664 664 total = len(revs)
665 665 revwidth = max([len(str(rev)) for rev in revs])
666 666
667 667 def single(rev, seqno, fp):
668 668 ctx = repo[rev]
669 669 node = ctx.node()
670 670 parents = [p.node() for p in ctx.parents() if p]
671 671 branch = ctx.branch()
672 672 if switch_parent:
673 673 parents.reverse()
674 674 prev = (parents and parents[0]) or nullid
675 675
676 676 if not fp:
677 677 fp = make_file(repo, template, node, total=total, seqno=seqno,
678 678 revwidth=revwidth, mode='ab')
679 679 if fp != sys.stdout and hasattr(fp, 'name'):
680 680 repo.ui.note("%s\n" % fp.name)
681 681
682 682 fp.write("# HG changeset patch\n")
683 683 fp.write("# User %s\n" % ctx.user())
684 684 fp.write("# Date %d %d\n" % ctx.date())
685 685 if branch and branch != 'default':
686 686 fp.write("# Branch %s\n" % branch)
687 687 fp.write("# Node ID %s\n" % hex(node))
688 688 fp.write("# Parent %s\n" % hex(prev))
689 689 if len(parents) > 1:
690 690 fp.write("# Parent %s\n" % hex(parents[1]))
691 691 fp.write(ctx.description().rstrip())
692 692 fp.write("\n\n")
693 693
694 694 for chunk in patch.diff(repo, prev, node, opts=opts):
695 695 fp.write(chunk)
696 696
697 697 for seqno, rev in enumerate(revs):
698 698 single(rev, seqno + 1, fp)
699 699
700 700 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
701 701 changes=None, stat=False, fp=None, prefix='',
702 702 listsubrepos=False):
703 703 '''show diff or diffstat.'''
704 704 if fp is None:
705 705 write = ui.write
706 706 else:
707 707 def write(s, **kw):
708 708 fp.write(s)
709 709
710 710 if stat:
711 711 diffopts = diffopts.copy(context=0)
712 712 width = 80
713 713 if not ui.plain():
714 714 width = ui.termwidth()
715 715 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
716 716 prefix=prefix)
717 717 for chunk, label in patch.diffstatui(util.iterlines(chunks),
718 718 width=width,
719 719 git=diffopts.git):
720 720 write(chunk, label=label)
721 721 else:
722 722 for chunk, label in patch.diffui(repo, node1, node2, match,
723 723 changes, diffopts, prefix=prefix):
724 724 write(chunk, label=label)
725 725
726 726 if listsubrepos:
727 727 ctx1 = repo[node1]
728 728 ctx2 = repo[node2]
729 729 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
730 730 if node2 is not None:
731 731 node2 = ctx2.substate[subpath][1]
732 732 submatch = matchmod.narrowmatcher(subpath, match)
733 733 sub.diff(diffopts, node2, submatch, changes=changes,
734 734 stat=stat, fp=fp, prefix=prefix)
735 735
736 736 class changeset_printer(object):
737 737 '''show changeset information when templating not requested.'''
738 738
739 739 def __init__(self, ui, repo, patch, diffopts, buffered):
740 740 self.ui = ui
741 741 self.repo = repo
742 742 self.buffered = buffered
743 743 self.patch = patch
744 744 self.diffopts = diffopts
745 745 self.header = {}
746 746 self.hunk = {}
747 747 self.lastheader = None
748 748 self.footer = None
749 749
750 750 def flush(self, rev):
751 751 if rev in self.header:
752 752 h = self.header[rev]
753 753 if h != self.lastheader:
754 754 self.lastheader = h
755 755 self.ui.write(h)
756 756 del self.header[rev]
757 757 if rev in self.hunk:
758 758 self.ui.write(self.hunk[rev])
759 759 del self.hunk[rev]
760 760 return 1
761 761 return 0
762 762
763 763 def close(self):
764 764 if self.footer:
765 765 self.ui.write(self.footer)
766 766
767 767 def show(self, ctx, copies=None, matchfn=None, **props):
768 768 if self.buffered:
769 769 self.ui.pushbuffer()
770 770 self._show(ctx, copies, matchfn, props)
771 771 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
772 772 else:
773 773 self._show(ctx, copies, matchfn, props)
774 774
775 775 def _show(self, ctx, copies, matchfn, props):
776 776 '''show a single changeset or file revision'''
777 777 changenode = ctx.node()
778 778 rev = ctx.rev()
779 779
780 780 if self.ui.quiet:
781 781 self.ui.write("%d:%s\n" % (rev, short(changenode)),
782 782 label='log.node')
783 783 return
784 784
785 785 log = self.repo.changelog
786 786 date = util.datestr(ctx.date())
787 787
788 788 hexfunc = self.ui.debugflag and hex or short
789 789
790 790 parents = [(p, hexfunc(log.node(p)))
791 791 for p in self._meaningful_parentrevs(log, rev)]
792 792
793 793 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
794 794 label='log.changeset')
795 795
796 796 branch = ctx.branch()
797 797 # don't show the default branch name
798 798 if branch != 'default':
799 799 branch = encoding.tolocal(branch)
800 800 self.ui.write(_("branch: %s\n") % branch,
801 801 label='log.branch')
802 802 for tag in self.repo.nodetags(changenode):
803 803 self.ui.write(_("tag: %s\n") % tag,
804 804 label='log.tag')
805 805 for parent in parents:
806 806 self.ui.write(_("parent: %d:%s\n") % parent,
807 807 label='log.parent')
808 808
809 809 if self.ui.debugflag:
810 810 mnode = ctx.manifestnode()
811 811 self.ui.write(_("manifest: %d:%s\n") %
812 812 (self.repo.manifest.rev(mnode), hex(mnode)),
813 813 label='ui.debug log.manifest')
814 814 self.ui.write(_("user: %s\n") % ctx.user(),
815 815 label='log.user')
816 816 self.ui.write(_("date: %s\n") % date,
817 817 label='log.date')
818 818
819 819 if self.ui.debugflag:
820 820 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
821 821 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
822 822 files):
823 823 if value:
824 824 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
825 825 label='ui.debug log.files')
826 826 elif ctx.files() and self.ui.verbose:
827 827 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
828 828 label='ui.note log.files')
829 829 if copies and self.ui.verbose:
830 830 copies = ['%s (%s)' % c for c in copies]
831 831 self.ui.write(_("copies: %s\n") % ' '.join(copies),
832 832 label='ui.note log.copies')
833 833
834 834 extra = ctx.extra()
835 835 if extra and self.ui.debugflag:
836 836 for key, value in sorted(extra.items()):
837 837 self.ui.write(_("extra: %s=%s\n")
838 838 % (key, value.encode('string_escape')),
839 839 label='ui.debug log.extra')
840 840
841 841 description = ctx.description().strip()
842 842 if description:
843 843 if self.ui.verbose:
844 844 self.ui.write(_("description:\n"),
845 845 label='ui.note log.description')
846 846 self.ui.write(description,
847 847 label='ui.note log.description')
848 848 self.ui.write("\n\n")
849 849 else:
850 850 self.ui.write(_("summary: %s\n") %
851 851 description.splitlines()[0],
852 852 label='log.summary')
853 853 self.ui.write("\n")
854 854
855 855 self.showpatch(changenode, matchfn)
856 856
857 857 def showpatch(self, node, matchfn):
858 858 if not matchfn:
859 859 matchfn = self.patch
860 860 if matchfn:
861 861 stat = self.diffopts.get('stat')
862 862 diff = self.diffopts.get('patch')
863 863 diffopts = patch.diffopts(self.ui, self.diffopts)
864 864 prev = self.repo.changelog.parents(node)[0]
865 865 if stat:
866 866 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
867 867 match=matchfn, stat=True)
868 868 if diff:
869 869 if stat:
870 870 self.ui.write("\n")
871 871 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
872 872 match=matchfn, stat=False)
873 873 self.ui.write("\n")
874 874
875 875 def _meaningful_parentrevs(self, log, rev):
876 876 """Return list of meaningful (or all if debug) parentrevs for rev.
877 877
878 878 For merges (two non-nullrev revisions) both parents are meaningful.
879 879 Otherwise the first parent revision is considered meaningful if it
880 880 is not the preceding revision.
881 881 """
882 882 parents = log.parentrevs(rev)
883 883 if not self.ui.debugflag and parents[1] == nullrev:
884 884 if parents[0] >= rev - 1:
885 885 parents = []
886 886 else:
887 887 parents = [parents[0]]
888 888 return parents
889 889
890 890
891 891 class changeset_templater(changeset_printer):
892 892 '''format changeset information.'''
893 893
894 894 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
895 895 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
896 896 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
897 897 defaulttempl = {
898 898 'parent': '{rev}:{node|formatnode} ',
899 899 'manifest': '{rev}:{node|formatnode}',
900 900 'file_copy': '{name} ({source})',
901 901 'extra': '{key}={value|stringescape}'
902 902 }
903 903 # filecopy is preserved for compatibility reasons
904 904 defaulttempl['filecopy'] = defaulttempl['file_copy']
905 905 self.t = templater.templater(mapfile, {'formatnode': formatnode},
906 906 cache=defaulttempl)
907 907 self.cache = {}
908 908
909 909 def use_template(self, t):
910 910 '''set template string to use'''
911 911 self.t.cache['changeset'] = t
912 912
913 913 def _meaningful_parentrevs(self, ctx):
914 914 """Return list of meaningful (or all if debug) parentrevs for rev.
915 915 """
916 916 parents = ctx.parents()
917 917 if len(parents) > 1:
918 918 return parents
919 919 if self.ui.debugflag:
920 920 return [parents[0], self.repo['null']]
921 921 if parents[0].rev() >= ctx.rev() - 1:
922 922 return []
923 923 return parents
924 924
925 925 def _show(self, ctx, copies, matchfn, props):
926 926 '''show a single changeset or file revision'''
927 927
928 928 showlist = templatekw.showlist
929 929
930 930 # showparents() behaviour depends on ui trace level which
931 931 # causes unexpected behaviours at templating level and makes
932 932 # it harder to extract it in a standalone function. Its
933 933 # behaviour cannot be changed so leave it here for now.
934 934 def showparents(**args):
935 935 ctx = args['ctx']
936 936 parents = [[('rev', p.rev()), ('node', p.hex())]
937 937 for p in self._meaningful_parentrevs(ctx)]
938 938 return showlist('parent', parents, **args)
939 939
940 940 props = props.copy()
941 941 props.update(templatekw.keywords)
942 942 props['parents'] = showparents
943 943 props['templ'] = self.t
944 944 props['ctx'] = ctx
945 945 props['repo'] = self.repo
946 946 props['revcache'] = {'copies': copies}
947 947 props['cache'] = self.cache
948 948
949 949 # find correct templates for current mode
950 950
951 951 tmplmodes = [
952 952 (True, None),
953 953 (self.ui.verbose, 'verbose'),
954 954 (self.ui.quiet, 'quiet'),
955 955 (self.ui.debugflag, 'debug'),
956 956 ]
957 957
958 958 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
959 959 for mode, postfix in tmplmodes:
960 960 for type in types:
961 961 cur = postfix and ('%s_%s' % (type, postfix)) or type
962 962 if mode and cur in self.t:
963 963 types[type] = cur
964 964
965 965 try:
966 966
967 967 # write header
968 968 if types['header']:
969 969 h = templater.stringify(self.t(types['header'], **props))
970 970 if self.buffered:
971 971 self.header[ctx.rev()] = h
972 972 else:
973 973 if self.lastheader != h:
974 974 self.lastheader = h
975 975 self.ui.write(h)
976 976
977 977 # write changeset metadata, then patch if requested
978 978 key = types['changeset']
979 979 self.ui.write(templater.stringify(self.t(key, **props)))
980 980 self.showpatch(ctx.node(), matchfn)
981 981
982 982 if types['footer']:
983 983 if not self.footer:
984 984 self.footer = templater.stringify(self.t(types['footer'],
985 985 **props))
986 986
987 987 except KeyError, inst:
988 988 msg = _("%s: no key named '%s'")
989 989 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
990 990 except SyntaxError, inst:
991 991 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
992 992
993 993 def show_changeset(ui, repo, opts, buffered=False):
994 994 """show one changeset using template or regular display.
995 995
996 996 Display format will be the first non-empty hit of:
997 997 1. option 'template'
998 998 2. option 'style'
999 999 3. [ui] setting 'logtemplate'
1000 1000 4. [ui] setting 'style'
1001 1001 If all of these values are either the unset or the empty string,
1002 1002 regular display via changeset_printer() is done.
1003 1003 """
1004 1004 # options
1005 1005 patch = False
1006 1006 if opts.get('patch') or opts.get('stat'):
1007 1007 patch = matchall(repo)
1008 1008
1009 1009 tmpl = opts.get('template')
1010 1010 style = None
1011 1011 if tmpl:
1012 1012 tmpl = templater.parsestring(tmpl, quoted=False)
1013 1013 else:
1014 1014 style = opts.get('style')
1015 1015
1016 1016 # ui settings
1017 1017 if not (tmpl or style):
1018 1018 tmpl = ui.config('ui', 'logtemplate')
1019 1019 if tmpl:
1020 1020 tmpl = templater.parsestring(tmpl)
1021 1021 else:
1022 1022 style = util.expandpath(ui.config('ui', 'style', ''))
1023 1023
1024 1024 if not (tmpl or style):
1025 1025 return changeset_printer(ui, repo, patch, opts, buffered)
1026 1026
1027 1027 mapfile = None
1028 1028 if style and not tmpl:
1029 1029 mapfile = style
1030 1030 if not os.path.split(mapfile)[0]:
1031 1031 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1032 1032 or templater.templatepath(mapfile))
1033 1033 if mapname:
1034 1034 mapfile = mapname
1035 1035
1036 1036 try:
1037 1037 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1038 1038 except SyntaxError, inst:
1039 1039 raise util.Abort(inst.args[0])
1040 1040 if tmpl:
1041 1041 t.use_template(tmpl)
1042 1042 return t
1043 1043
1044 1044 def finddate(ui, repo, date):
1045 1045 """Find the tipmost changeset that matches the given date spec"""
1046 1046
1047 1047 df = util.matchdate(date)
1048 1048 m = matchall(repo)
1049 1049 results = {}
1050 1050
1051 1051 def prep(ctx, fns):
1052 1052 d = ctx.date()
1053 1053 if df(d[0]):
1054 1054 results[ctx.rev()] = d
1055 1055
1056 1056 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1057 1057 rev = ctx.rev()
1058 1058 if rev in results:
1059 1059 ui.status(_("Found revision %s from %s\n") %
1060 1060 (rev, util.datestr(results[rev])))
1061 1061 return str(rev)
1062 1062
1063 1063 raise util.Abort(_("revision matching date not found"))
1064 1064
1065 1065 def walkchangerevs(repo, match, opts, prepare):
1066 1066 '''Iterate over files and the revs in which they changed.
1067 1067
1068 1068 Callers most commonly need to iterate backwards over the history
1069 1069 in which they are interested. Doing so has awful (quadratic-looking)
1070 1070 performance, so we use iterators in a "windowed" way.
1071 1071
1072 1072 We walk a window of revisions in the desired order. Within the
1073 1073 window, we first walk forwards to gather data, then in the desired
1074 1074 order (usually backwards) to display it.
1075 1075
1076 1076 This function returns an iterator yielding contexts. Before
1077 1077 yielding each context, the iterator will first call the prepare
1078 1078 function on each context in the window in forward order.'''
1079 1079
1080 1080 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1081 1081 if start < end:
1082 1082 while start < end:
1083 1083 yield start, min(windowsize, end - start)
1084 1084 start += windowsize
1085 1085 if windowsize < sizelimit:
1086 1086 windowsize *= 2
1087 1087 else:
1088 1088 while start > end:
1089 1089 yield start, min(windowsize, start - end - 1)
1090 1090 start -= windowsize
1091 1091 if windowsize < sizelimit:
1092 1092 windowsize *= 2
1093 1093
1094 1094 follow = opts.get('follow') or opts.get('follow_first')
1095 1095
1096 1096 if not len(repo):
1097 1097 return []
1098 1098
1099 1099 if follow:
1100 1100 defrange = '%s:0' % repo['.'].rev()
1101 1101 else:
1102 1102 defrange = '-1:0'
1103 1103 revs = revrange(repo, opts['rev'] or [defrange])
1104 1104 if not revs:
1105 1105 return []
1106 1106 wanted = set()
1107 1107 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1108 1108 fncache = {}
1109 1109 change = util.cachefunc(repo.changectx)
1110 1110
1111 1111 # First step is to fill wanted, the set of revisions that we want to yield.
1112 1112 # When it does not induce extra cost, we also fill fncache for revisions in
1113 1113 # wanted: a cache of filenames that were changed (ctx.files()) and that
1114 1114 # match the file filtering conditions.
1115 1115
1116 1116 if not slowpath and not match.files():
1117 1117 # No files, no patterns. Display all revs.
1118 1118 wanted = set(revs)
1119 1119 copies = []
1120 1120
1121 1121 if not slowpath:
1122 1122 # We only have to read through the filelog to find wanted revisions
1123 1123
1124 1124 minrev, maxrev = min(revs), max(revs)
1125 1125 def filerevgen(filelog, last):
1126 1126 """
1127 1127 Only files, no patterns. Check the history of each file.
1128 1128
1129 1129 Examines filelog entries within minrev, maxrev linkrev range
1130 1130 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1131 1131 tuples in backwards order
1132 1132 """
1133 1133 cl_count = len(repo)
1134 1134 revs = []
1135 1135 for j in xrange(0, last + 1):
1136 1136 linkrev = filelog.linkrev(j)
1137 1137 if linkrev < minrev:
1138 1138 continue
1139 1139 # only yield rev for which we have the changelog, it can
1140 1140 # happen while doing "hg log" during a pull or commit
1141 1141 if linkrev >= cl_count:
1142 1142 break
1143 1143
1144 1144 parentlinkrevs = []
1145 1145 for p in filelog.parentrevs(j):
1146 1146 if p != nullrev:
1147 1147 parentlinkrevs.append(filelog.linkrev(p))
1148 1148 n = filelog.node(j)
1149 1149 revs.append((linkrev, parentlinkrevs,
1150 1150 follow and filelog.renamed(n)))
1151 1151
1152 1152 return reversed(revs)
1153 1153 def iterfiles():
1154 1154 for filename in match.files():
1155 1155 yield filename, None
1156 1156 for filename_node in copies:
1157 1157 yield filename_node
1158 1158 for file_, node in iterfiles():
1159 1159 filelog = repo.file(file_)
1160 1160 if not len(filelog):
1161 1161 if node is None:
1162 1162 # A zero count may be a directory or deleted file, so
1163 1163 # try to find matching entries on the slow path.
1164 1164 if follow:
1165 1165 raise util.Abort(
1166 1166 _('cannot follow nonexistent file: "%s"') % file_)
1167 1167 slowpath = True
1168 1168 break
1169 1169 else:
1170 1170 continue
1171 1171
1172 1172 if node is None:
1173 1173 last = len(filelog) - 1
1174 1174 else:
1175 1175 last = filelog.rev(node)
1176 1176
1177 1177
1178 1178 # keep track of all ancestors of the file
1179 1179 ancestors = set([filelog.linkrev(last)])
1180 1180
1181 1181 # iterate from latest to oldest revision
1182 1182 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1183 if rev > maxrev or rev not in ancestors:
1183 if not follow:
1184 if rev > maxrev:
1185 continue
1186 else:
1187 # Note that last might not be the first interesting
1188 # rev to us:
1189 # if the file has been changed after maxrev, we'll
1190 # have linkrev(last) > maxrev, and we still need
1191 # to explore the file graph
1192 if rev not in ancestors:
1184 1193 continue
1185 1194 # XXX insert 1327 fix here
1186 1195 if flparentlinkrevs:
1187 1196 ancestors.update(flparentlinkrevs)
1188 1197
1189 1198 fncache.setdefault(rev, []).append(file_)
1190 1199 wanted.add(rev)
1191 1200 if copied:
1192 1201 copies.append(copied)
1193 1202 if slowpath:
1194 1203 # We have to read the changelog to match filenames against
1195 1204 # changed files
1196 1205
1197 1206 if follow:
1198 1207 raise util.Abort(_('can only follow copies/renames for explicit '
1199 1208 'filenames'))
1200 1209
1201 1210 # The slow path checks files modified in every changeset.
1202 1211 for i in sorted(revs):
1203 1212 ctx = change(i)
1204 1213 matches = filter(match, ctx.files())
1205 1214 if matches:
1206 1215 fncache[i] = matches
1207 1216 wanted.add(i)
1208 1217
1209 1218 class followfilter(object):
1210 1219 def __init__(self, onlyfirst=False):
1211 1220 self.startrev = nullrev
1212 1221 self.roots = set()
1213 1222 self.onlyfirst = onlyfirst
1214 1223
1215 1224 def match(self, rev):
1216 1225 def realparents(rev):
1217 1226 if self.onlyfirst:
1218 1227 return repo.changelog.parentrevs(rev)[0:1]
1219 1228 else:
1220 1229 return filter(lambda x: x != nullrev,
1221 1230 repo.changelog.parentrevs(rev))
1222 1231
1223 1232 if self.startrev == nullrev:
1224 1233 self.startrev = rev
1225 1234 return True
1226 1235
1227 1236 if rev > self.startrev:
1228 1237 # forward: all descendants
1229 1238 if not self.roots:
1230 1239 self.roots.add(self.startrev)
1231 1240 for parent in realparents(rev):
1232 1241 if parent in self.roots:
1233 1242 self.roots.add(rev)
1234 1243 return True
1235 1244 else:
1236 1245 # backwards: all parents
1237 1246 if not self.roots:
1238 1247 self.roots.update(realparents(self.startrev))
1239 1248 if rev in self.roots:
1240 1249 self.roots.remove(rev)
1241 1250 self.roots.update(realparents(rev))
1242 1251 return True
1243 1252
1244 1253 return False
1245 1254
1246 1255 # it might be worthwhile to do this in the iterator if the rev range
1247 1256 # is descending and the prune args are all within that range
1248 1257 for rev in opts.get('prune', ()):
1249 1258 rev = repo.changelog.rev(repo.lookup(rev))
1250 1259 ff = followfilter()
1251 1260 stop = min(revs[0], revs[-1])
1252 1261 for x in xrange(rev, stop - 1, -1):
1253 1262 if ff.match(x):
1254 1263 wanted.discard(x)
1255 1264
1256 1265 # Now that wanted is correctly initialized, we can iterate over the
1257 1266 # revision range, yielding only revisions in wanted.
1258 1267 def iterate():
1259 1268 if follow and not match.files():
1260 1269 ff = followfilter(onlyfirst=opts.get('follow_first'))
1261 1270 def want(rev):
1262 1271 return ff.match(rev) and rev in wanted
1263 1272 else:
1264 1273 def want(rev):
1265 1274 return rev in wanted
1266 1275
1267 1276 for i, window in increasing_windows(0, len(revs)):
1268 1277 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1269 1278 for rev in sorted(nrevs):
1270 1279 fns = fncache.get(rev)
1271 1280 ctx = change(rev)
1272 1281 if not fns:
1273 1282 def fns_generator():
1274 1283 for f in ctx.files():
1275 1284 if match(f):
1276 1285 yield f
1277 1286 fns = fns_generator()
1278 1287 prepare(ctx, fns)
1279 1288 for rev in nrevs:
1280 1289 yield change(rev)
1281 1290 return iterate()
1282 1291
1283 1292 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1284 1293 join = lambda f: os.path.join(prefix, f)
1285 1294 bad = []
1286 1295 oldbad = match.bad
1287 1296 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1288 1297 names = []
1289 1298 wctx = repo[None]
1290 1299 for f in repo.walk(match):
1291 1300 exact = match.exact(f)
1292 1301 if exact or f not in repo.dirstate:
1293 1302 names.append(f)
1294 1303 if ui.verbose or not exact:
1295 1304 ui.status(_('adding %s\n') % match.rel(join(f)))
1296 1305
1297 1306 if listsubrepos:
1298 1307 for subpath in wctx.substate:
1299 1308 sub = wctx.sub(subpath)
1300 1309 try:
1301 1310 submatch = matchmod.narrowmatcher(subpath, match)
1302 1311 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1303 1312 except error.LookupError:
1304 1313 ui.status(_("skipping missing subrepository: %s\n")
1305 1314 % join(subpath))
1306 1315
1307 1316 if not dryrun:
1308 1317 rejected = wctx.add(names, prefix)
1309 1318 bad.extend(f for f in rejected if f in match.files())
1310 1319 return bad
1311 1320
1312 1321 def commit(ui, repo, commitfunc, pats, opts):
1313 1322 '''commit the specified files or all outstanding changes'''
1314 1323 date = opts.get('date')
1315 1324 if date:
1316 1325 opts['date'] = util.parsedate(date)
1317 1326 message = logmessage(opts)
1318 1327
1319 1328 # extract addremove carefully -- this function can be called from a command
1320 1329 # that doesn't support addremove
1321 1330 if opts.get('addremove'):
1322 1331 addremove(repo, pats, opts)
1323 1332
1324 1333 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1325 1334
1326 1335 def commiteditor(repo, ctx, subs):
1327 1336 if ctx.description():
1328 1337 return ctx.description()
1329 1338 return commitforceeditor(repo, ctx, subs)
1330 1339
1331 1340 def commitforceeditor(repo, ctx, subs):
1332 1341 edittext = []
1333 1342 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1334 1343 if ctx.description():
1335 1344 edittext.append(ctx.description())
1336 1345 edittext.append("")
1337 1346 edittext.append("") # Empty line between message and comments.
1338 1347 edittext.append(_("HG: Enter commit message."
1339 1348 " Lines beginning with 'HG:' are removed."))
1340 1349 edittext.append(_("HG: Leave message empty to abort commit."))
1341 1350 edittext.append("HG: --")
1342 1351 edittext.append(_("HG: user: %s") % ctx.user())
1343 1352 if ctx.p2():
1344 1353 edittext.append(_("HG: branch merge"))
1345 1354 if ctx.branch():
1346 1355 edittext.append(_("HG: branch '%s'")
1347 1356 % encoding.tolocal(ctx.branch()))
1348 1357 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1349 1358 edittext.extend([_("HG: added %s") % f for f in added])
1350 1359 edittext.extend([_("HG: changed %s") % f for f in modified])
1351 1360 edittext.extend([_("HG: removed %s") % f for f in removed])
1352 1361 if not added and not modified and not removed:
1353 1362 edittext.append(_("HG: no files changed"))
1354 1363 edittext.append("")
1355 1364 # run editor in the repository root
1356 1365 olddir = os.getcwd()
1357 1366 os.chdir(repo.root)
1358 1367 text = repo.ui.edit("\n".join(edittext), ctx.user())
1359 1368 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1360 1369 os.chdir(olddir)
1361 1370
1362 1371 if not text.strip():
1363 1372 raise util.Abort(_("empty commit message"))
1364 1373
1365 1374 return text
@@ -1,1094 +1,1119
1 1 $ hg init a
2 2
3 3 $ cd a
4 4 $ echo a > a
5 5 $ hg ci -Ama -d '1 0'
6 6 adding a
7 7
8 8 $ hg cp a b
9 9 $ hg ci -mb -d '2 0'
10 10
11 11 $ mkdir dir
12 12 $ hg mv b dir
13 13 $ hg ci -mc -d '3 0'
14 14
15 15 $ hg mv a b
16 16 $ echo a > d
17 17 $ hg add d
18 18 $ hg ci -md -d '4 0'
19 19
20 20 $ hg mv dir/b e
21 21 $ hg ci -me -d '5 0'
22 22
23 23 $ hg log a
24 24 changeset: 0:8580ff50825a
25 25 user: test
26 26 date: Thu Jan 01 00:00:01 1970 +0000
27 27 summary: a
28 28
29 29
30 30 -f, directory
31 31
32 32 $ hg log -f dir
33 33 abort: cannot follow nonexistent file: "dir"
34 34 [255]
35 35
36 36 -f, but no args
37 37
38 38 $ hg log -f
39 39 changeset: 4:66c1345dc4f9
40 40 tag: tip
41 41 user: test
42 42 date: Thu Jan 01 00:00:05 1970 +0000
43 43 summary: e
44 44
45 45 changeset: 3:7c6c671bb7cc
46 46 user: test
47 47 date: Thu Jan 01 00:00:04 1970 +0000
48 48 summary: d
49 49
50 50 changeset: 2:41dd4284081e
51 51 user: test
52 52 date: Thu Jan 01 00:00:03 1970 +0000
53 53 summary: c
54 54
55 55 changeset: 1:784de7cef101
56 56 user: test
57 57 date: Thu Jan 01 00:00:02 1970 +0000
58 58 summary: b
59 59
60 60 changeset: 0:8580ff50825a
61 61 user: test
62 62 date: Thu Jan 01 00:00:01 1970 +0000
63 63 summary: a
64 64
65 65
66 66 one rename
67 67
68 68 $ hg log -vf a
69 69 changeset: 0:8580ff50825a
70 70 user: test
71 71 date: Thu Jan 01 00:00:01 1970 +0000
72 72 files: a
73 73 description:
74 74 a
75 75
76 76
77 77
78 78 many renames
79 79
80 80 $ hg log -vf e
81 81 changeset: 4:66c1345dc4f9
82 82 tag: tip
83 83 user: test
84 84 date: Thu Jan 01 00:00:05 1970 +0000
85 85 files: dir/b e
86 86 description:
87 87 e
88 88
89 89
90 90 changeset: 2:41dd4284081e
91 91 user: test
92 92 date: Thu Jan 01 00:00:03 1970 +0000
93 93 files: b dir/b
94 94 description:
95 95 c
96 96
97 97
98 98 changeset: 1:784de7cef101
99 99 user: test
100 100 date: Thu Jan 01 00:00:02 1970 +0000
101 101 files: b
102 102 description:
103 103 b
104 104
105 105
106 106 changeset: 0:8580ff50825a
107 107 user: test
108 108 date: Thu Jan 01 00:00:01 1970 +0000
109 109 files: a
110 110 description:
111 111 a
112 112
113 113
114 114
115 115
116 116 log -pf dir/b
117 117
118 118 $ hg log -pf dir/b
119 119 changeset: 2:41dd4284081e
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 784de7cef101 -r 41dd4284081e dir/b
125 125 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
126 126 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
127 127 @@ -0,0 +1,1 @@
128 128 +a
129 129
130 130 changeset: 1:784de7cef101
131 131 user: test
132 132 date: Thu Jan 01 00:00:02 1970 +0000
133 133 summary: b
134 134
135 135 diff -r 8580ff50825a -r 784de7cef101 b
136 136 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
137 137 +++ b/b Thu Jan 01 00:00:02 1970 +0000
138 138 @@ -0,0 +1,1 @@
139 139 +a
140 140
141 141 changeset: 0:8580ff50825a
142 142 user: test
143 143 date: Thu Jan 01 00:00:01 1970 +0000
144 144 summary: a
145 145
146 146 diff -r 000000000000 -r 8580ff50825a a
147 147 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
148 148 +++ b/a Thu Jan 01 00:00:01 1970 +0000
149 149 @@ -0,0 +1,1 @@
150 150 +a
151 151
152 152
153 153 log -vf dir/b
154 154
155 155 $ hg log -vf dir/b
156 156 changeset: 2:41dd4284081e
157 157 user: test
158 158 date: Thu Jan 01 00:00:03 1970 +0000
159 159 files: b dir/b
160 160 description:
161 161 c
162 162
163 163
164 164 changeset: 1:784de7cef101
165 165 user: test
166 166 date: Thu Jan 01 00:00:02 1970 +0000
167 167 files: b
168 168 description:
169 169 b
170 170
171 171
172 172 changeset: 0:8580ff50825a
173 173 user: test
174 174 date: Thu Jan 01 00:00:01 1970 +0000
175 175 files: a
176 176 description:
177 177 a
178 178
179 179
180 180
181 181
182 182 log copies with --copies
183 183
184 184 $ hg log -vC --template '{rev} {file_copies}\n'
185 185 4 e (dir/b)
186 186 3 b (a)
187 187 2 dir/b (b)
188 188 1 b (a)
189 189 0
190 190
191 191 log copies switch without --copies, with old filecopy template
192 192
193 193 $ hg log -v --template '{rev} {file_copies_switch%filecopy}\n'
194 194 4
195 195 3
196 196 2
197 197 1
198 198 0
199 199
200 200 log copies switch with --copies
201 201
202 202 $ hg log -vC --template '{rev} {file_copies_switch}\n'
203 203 4 e (dir/b)
204 204 3 b (a)
205 205 2 dir/b (b)
206 206 1 b (a)
207 207 0
208 208
209 209
210 210 log copies with hardcoded style and with --style=default
211 211
212 212 $ hg log -vC -r4
213 213 changeset: 4:66c1345dc4f9
214 214 tag: tip
215 215 user: test
216 216 date: Thu Jan 01 00:00:05 1970 +0000
217 217 files: dir/b e
218 218 copies: e (dir/b)
219 219 description:
220 220 e
221 221
222 222
223 223 $ hg log -vC -r4 --style=default
224 224 changeset: 4:66c1345dc4f9
225 225 tag: tip
226 226 user: test
227 227 date: Thu Jan 01 00:00:05 1970 +0000
228 228 files: dir/b e
229 229 copies: e (dir/b)
230 230 description:
231 231 e
232 232
233 233
234 234
235 235
236 236 log copies, non-linear manifest
237 237
238 238 $ hg up -C 3
239 239 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
240 240 $ hg mv dir/b e
241 241 $ echo foo > foo
242 242 $ hg ci -Ame2 -d '6 0'
243 243 adding foo
244 244 created new head
245 245 $ hg log -v --template '{rev} {file_copies}\n' -r 5
246 246 5 e (dir/b)
247 247
248 248
249 249 log copies, execute bit set
250 250
251 251 $ chmod +x e
252 252 $ hg ci -me3 -d '7 0'
253 253 $ hg log -v --template '{rev} {file_copies}\n' -r 6
254 254 6
255 255
256 256
257 257 log -p d
258 258
259 259 $ hg log -pv d
260 260 changeset: 3:7c6c671bb7cc
261 261 user: test
262 262 date: Thu Jan 01 00:00:04 1970 +0000
263 263 files: a b d
264 264 description:
265 265 d
266 266
267 267
268 268 diff -r 41dd4284081e -r 7c6c671bb7cc d
269 269 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
270 270 +++ b/d Thu Jan 01 00:00:04 1970 +0000
271 271 @@ -0,0 +1,1 @@
272 272 +a
273 273
274 274
275 275
276 276 log --removed file
277 277
278 278 $ hg log --removed -v a
279 279 changeset: 3:7c6c671bb7cc
280 280 user: test
281 281 date: Thu Jan 01 00:00:04 1970 +0000
282 282 files: a b d
283 283 description:
284 284 d
285 285
286 286
287 287 changeset: 0:8580ff50825a
288 288 user: test
289 289 date: Thu Jan 01 00:00:01 1970 +0000
290 290 files: a
291 291 description:
292 292 a
293 293
294 294
295 295
296 296 log --removed revrange file
297 297
298 298 $ hg log --removed -v -r0:2 a
299 299 changeset: 0:8580ff50825a
300 300 user: test
301 301 date: Thu Jan 01 00:00:01 1970 +0000
302 302 files: a
303 303 description:
304 304 a
305 305
306 306
307 307
308 308
309 309 log --follow tests
310 310
311 311 $ hg init ../follow
312 312 $ cd ../follow
313 313
314 314 $ echo base > base
315 315 $ hg ci -Ambase -d '1 0'
316 316 adding base
317 317
318 318 $ echo r1 >> base
319 319 $ hg ci -Amr1 -d '1 0'
320 320 $ echo r2 >> base
321 321 $ hg ci -Amr2 -d '1 0'
322 322
323 323 $ hg up -C 1
324 324 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
325 325 $ echo b1 > b1
326 326 $ hg ci -Amb1 -d '1 0'
327 327 adding b1
328 328 created new head
329 329
330 330
331 331 log -f
332 332
333 333 $ hg log -f
334 334 changeset: 3:e62f78d544b4
335 335 tag: tip
336 336 parent: 1:3d5bf5654eda
337 337 user: test
338 338 date: Thu Jan 01 00:00:01 1970 +0000
339 339 summary: b1
340 340
341 341 changeset: 1:3d5bf5654eda
342 342 user: test
343 343 date: Thu Jan 01 00:00:01 1970 +0000
344 344 summary: r1
345 345
346 346 changeset: 0:67e992f2c4f3
347 347 user: test
348 348 date: Thu Jan 01 00:00:01 1970 +0000
349 349 summary: base
350 350
351 351
352 352
353 353 log -f -r 1:tip
354 354
355 355 $ hg up -C 0
356 356 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
357 357 $ echo b2 > b2
358 358 $ hg ci -Amb2 -d '1 0'
359 359 adding b2
360 360 created new head
361 361 $ hg log -f -r 1:tip
362 362 changeset: 1:3d5bf5654eda
363 363 user: test
364 364 date: Thu Jan 01 00:00:01 1970 +0000
365 365 summary: r1
366 366
367 367 changeset: 2:60c670bf5b30
368 368 user: test
369 369 date: Thu Jan 01 00:00:01 1970 +0000
370 370 summary: r2
371 371
372 372 changeset: 3:e62f78d544b4
373 373 parent: 1:3d5bf5654eda
374 374 user: test
375 375 date: Thu Jan 01 00:00:01 1970 +0000
376 376 summary: b1
377 377
378 378
379 379
380 380 log -r . with two parents
381 381
382 382 $ hg up -C 3
383 383 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
384 384 $ hg merge tip
385 385 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
386 386 (branch merge, don't forget to commit)
387 387 $ hg log -r .
388 388 changeset: 3:e62f78d544b4
389 389 parent: 1:3d5bf5654eda
390 390 user: test
391 391 date: Thu Jan 01 00:00:01 1970 +0000
392 392 summary: b1
393 393
394 394
395 395
396 396 log -r . with one parent
397 397
398 398 $ hg ci -mm12 -d '1 0'
399 399 $ hg log -r .
400 400 changeset: 5:302e9dd6890d
401 401 tag: tip
402 402 parent: 3:e62f78d544b4
403 403 parent: 4:ddb82e70d1a1
404 404 user: test
405 405 date: Thu Jan 01 00:00:01 1970 +0000
406 406 summary: m12
407 407
408 408
409 409 $ echo postm >> b1
410 410 $ hg ci -Amb1.1 -d'1 0'
411 411
412 412
413 413 log --follow-first
414 414
415 415 $ hg log --follow-first
416 416 changeset: 6:2404bbcab562
417 417 tag: tip
418 418 user: test
419 419 date: Thu Jan 01 00:00:01 1970 +0000
420 420 summary: b1.1
421 421
422 422 changeset: 5:302e9dd6890d
423 423 parent: 3:e62f78d544b4
424 424 parent: 4:ddb82e70d1a1
425 425 user: test
426 426 date: Thu Jan 01 00:00:01 1970 +0000
427 427 summary: m12
428 428
429 429 changeset: 3:e62f78d544b4
430 430 parent: 1:3d5bf5654eda
431 431 user: test
432 432 date: Thu Jan 01 00:00:01 1970 +0000
433 433 summary: b1
434 434
435 435 changeset: 1:3d5bf5654eda
436 436 user: test
437 437 date: Thu Jan 01 00:00:01 1970 +0000
438 438 summary: r1
439 439
440 440 changeset: 0:67e992f2c4f3
441 441 user: test
442 442 date: Thu Jan 01 00:00:01 1970 +0000
443 443 summary: base
444 444
445 445
446 446
447 447 log -P 2
448 448
449 449 $ hg log -P 2
450 450 changeset: 6:2404bbcab562
451 451 tag: tip
452 452 user: test
453 453 date: Thu Jan 01 00:00:01 1970 +0000
454 454 summary: b1.1
455 455
456 456 changeset: 5:302e9dd6890d
457 457 parent: 3:e62f78d544b4
458 458 parent: 4:ddb82e70d1a1
459 459 user: test
460 460 date: Thu Jan 01 00:00:01 1970 +0000
461 461 summary: m12
462 462
463 463 changeset: 4:ddb82e70d1a1
464 464 parent: 0:67e992f2c4f3
465 465 user: test
466 466 date: Thu Jan 01 00:00:01 1970 +0000
467 467 summary: b2
468 468
469 469 changeset: 3:e62f78d544b4
470 470 parent: 1:3d5bf5654eda
471 471 user: test
472 472 date: Thu Jan 01 00:00:01 1970 +0000
473 473 summary: b1
474 474
475 475
476 476
477 477 log -r tip -p --git
478 478
479 479 $ hg log -r tip -p --git
480 480 changeset: 6:2404bbcab562
481 481 tag: tip
482 482 user: test
483 483 date: Thu Jan 01 00:00:01 1970 +0000
484 484 summary: b1.1
485 485
486 486 diff --git a/b1 b/b1
487 487 --- a/b1
488 488 +++ b/b1
489 489 @@ -1,1 +1,2 @@
490 490 b1
491 491 +postm
492 492
493 493
494 494
495 495 log -r ""
496 496
497 497 $ hg log -r ''
498 498 hg: parse error: empty query
499 499 [255]
500 500
501 501 log -r <some unknown node id>
502 502
503 503 $ hg log -r 1000000000000000000000000000000000000000
504 504 abort: unknown revision '1000000000000000000000000000000000000000'!
505 505 [255]
506 506
507 507 log -k r1
508 508
509 509 $ hg log -k r1
510 510 changeset: 1:3d5bf5654eda
511 511 user: test
512 512 date: Thu Jan 01 00:00:01 1970 +0000
513 513 summary: r1
514 514
515 515
516 516
517 517 log -d -1
518 518
519 519 $ hg log -d -1
520 520
521 521
522 522 log -p -l2 --color=always
523 523
524 524 $ hg --config extensions.color= --config color.mode=ansi \
525 525 > log -p -l2 --color=always
526 526 \x1b[0;33mchangeset: 6:2404bbcab562\x1b[0m (esc)
527 527 tag: tip
528 528 user: test
529 529 date: Thu Jan 01 00:00:01 1970 +0000
530 530 summary: b1.1
531 531
532 532 \x1b[0;1mdiff -r 302e9dd6890d -r 2404bbcab562 b1\x1b[0m (esc)
533 533 \x1b[0;31;1m--- a/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
534 534 \x1b[0;32;1m+++ b/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
535 535 \x1b[0;35m@@ -1,1 +1,2 @@\x1b[0m (esc)
536 536 b1
537 537 \x1b[0;32m+postm\x1b[0m (esc)
538 538
539 539 \x1b[0;33mchangeset: 5:302e9dd6890d\x1b[0m (esc)
540 540 parent: 3:e62f78d544b4
541 541 parent: 4:ddb82e70d1a1
542 542 user: test
543 543 date: Thu Jan 01 00:00:01 1970 +0000
544 544 summary: m12
545 545
546 546 \x1b[0;1mdiff -r e62f78d544b4 -r 302e9dd6890d b2\x1b[0m (esc)
547 547 \x1b[0;31;1m--- /dev/null Thu Jan 01 00:00:00 1970 +0000\x1b[0m (esc)
548 548 \x1b[0;32;1m+++ b/b2 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
549 549 \x1b[0;35m@@ -0,0 +1,1 @@\x1b[0m (esc)
550 550 \x1b[0;32m+b2\x1b[0m (esc)
551 551
552 552
553 553
554 554 log -r tip --stat
555 555
556 556 $ hg log -r tip --stat
557 557 changeset: 6:2404bbcab562
558 558 tag: tip
559 559 user: test
560 560 date: Thu Jan 01 00:00:01 1970 +0000
561 561 summary: b1.1
562 562
563 563 b1 | 1 +
564 564 1 files changed, 1 insertions(+), 0 deletions(-)
565 565
566 566
567 567 $ cd ..
568 568
569 569 $ hg init usertest
570 570 $ cd usertest
571 571
572 572 $ echo a > a
573 573 $ hg ci -A -m "a" -u "User One <user1@example.org>"
574 574 adding a
575 575 $ echo b > b
576 576 $ hg ci -A -m "b" -u "User Two <user2@example.org>"
577 577 adding b
578 578
579 579 $ hg log -u "User One <user1@example.org>"
580 580 changeset: 0:29a4c94f1924
581 581 user: User One <user1@example.org>
582 582 date: Thu Jan 01 00:00:00 1970 +0000
583 583 summary: a
584 584
585 585 $ hg log -u "user1" -u "user2"
586 586 changeset: 1:e834b5e69c0e
587 587 tag: tip
588 588 user: User Two <user2@example.org>
589 589 date: Thu Jan 01 00:00:00 1970 +0000
590 590 summary: b
591 591
592 592 changeset: 0:29a4c94f1924
593 593 user: User One <user1@example.org>
594 594 date: Thu Jan 01 00:00:00 1970 +0000
595 595 summary: a
596 596
597 597 $ hg log -u "user3"
598 598
599 599 $ cd ..
600 600
601 601 $ hg init branches
602 602 $ cd branches
603 603
604 604 $ echo a > a
605 605 $ hg ci -A -m "commit on default"
606 606 adding a
607 607 $ hg branch test
608 608 marked working directory as branch test
609 609 $ echo b > b
610 610 $ hg ci -A -m "commit on test"
611 611 adding b
612 612
613 613 $ hg up default
614 614 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
615 615 $ echo c > c
616 616 $ hg ci -A -m "commit on default"
617 617 adding c
618 618 $ hg up test
619 619 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
620 620 $ echo c > c
621 621 $ hg ci -A -m "commit on test"
622 622 adding c
623 623
624 624
625 625 log -b default
626 626
627 627 $ hg log -b default
628 628 changeset: 2:c3a4f03cc9a7
629 629 parent: 0:24427303d56f
630 630 user: test
631 631 date: Thu Jan 01 00:00:00 1970 +0000
632 632 summary: commit on default
633 633
634 634 changeset: 0:24427303d56f
635 635 user: test
636 636 date: Thu Jan 01 00:00:00 1970 +0000
637 637 summary: commit on default
638 638
639 639
640 640
641 641 log -b test
642 642
643 643 $ hg log -b test
644 644 changeset: 3:f5d8de11c2e2
645 645 branch: test
646 646 tag: tip
647 647 parent: 1:d32277701ccb
648 648 user: test
649 649 date: Thu Jan 01 00:00:00 1970 +0000
650 650 summary: commit on test
651 651
652 652 changeset: 1:d32277701ccb
653 653 branch: test
654 654 user: test
655 655 date: Thu Jan 01 00:00:00 1970 +0000
656 656 summary: commit on test
657 657
658 658
659 659
660 660 log -b dummy
661 661
662 662 $ hg log -b dummy
663 663 abort: unknown revision 'dummy'!
664 664 [255]
665 665
666 666
667 667 log -b .
668 668
669 669 $ hg log -b .
670 670 changeset: 3:f5d8de11c2e2
671 671 branch: test
672 672 tag: tip
673 673 parent: 1:d32277701ccb
674 674 user: test
675 675 date: Thu Jan 01 00:00:00 1970 +0000
676 676 summary: commit on test
677 677
678 678 changeset: 1:d32277701ccb
679 679 branch: test
680 680 user: test
681 681 date: Thu Jan 01 00:00:00 1970 +0000
682 682 summary: commit on test
683 683
684 684
685 685
686 686 log -b default -b test
687 687
688 688 $ hg log -b default -b test
689 689 changeset: 3:f5d8de11c2e2
690 690 branch: test
691 691 tag: tip
692 692 parent: 1:d32277701ccb
693 693 user: test
694 694 date: Thu Jan 01 00:00:00 1970 +0000
695 695 summary: commit on test
696 696
697 697 changeset: 2:c3a4f03cc9a7
698 698 parent: 0:24427303d56f
699 699 user: test
700 700 date: Thu Jan 01 00:00:00 1970 +0000
701 701 summary: commit on default
702 702
703 703 changeset: 1:d32277701ccb
704 704 branch: test
705 705 user: test
706 706 date: Thu Jan 01 00:00:00 1970 +0000
707 707 summary: commit on test
708 708
709 709 changeset: 0:24427303d56f
710 710 user: test
711 711 date: Thu Jan 01 00:00:00 1970 +0000
712 712 summary: commit on default
713 713
714 714
715 715
716 716 log -b default -b .
717 717
718 718 $ hg log -b default -b .
719 719 changeset: 3:f5d8de11c2e2
720 720 branch: test
721 721 tag: tip
722 722 parent: 1:d32277701ccb
723 723 user: test
724 724 date: Thu Jan 01 00:00:00 1970 +0000
725 725 summary: commit on test
726 726
727 727 changeset: 2:c3a4f03cc9a7
728 728 parent: 0:24427303d56f
729 729 user: test
730 730 date: Thu Jan 01 00:00:00 1970 +0000
731 731 summary: commit on default
732 732
733 733 changeset: 1:d32277701ccb
734 734 branch: test
735 735 user: test
736 736 date: Thu Jan 01 00:00:00 1970 +0000
737 737 summary: commit on test
738 738
739 739 changeset: 0:24427303d56f
740 740 user: test
741 741 date: Thu Jan 01 00:00:00 1970 +0000
742 742 summary: commit on default
743 743
744 744
745 745
746 746 log -b . -b test
747 747
748 748 $ hg log -b . -b test
749 749 changeset: 3:f5d8de11c2e2
750 750 branch: test
751 751 tag: tip
752 752 parent: 1:d32277701ccb
753 753 user: test
754 754 date: Thu Jan 01 00:00:00 1970 +0000
755 755 summary: commit on test
756 756
757 757 changeset: 1:d32277701ccb
758 758 branch: test
759 759 user: test
760 760 date: Thu Jan 01 00:00:00 1970 +0000
761 761 summary: commit on test
762 762
763 763
764 764
765 765 log -b 2
766 766
767 767 $ hg log -b 2
768 768 changeset: 2:c3a4f03cc9a7
769 769 parent: 0:24427303d56f
770 770 user: test
771 771 date: Thu Jan 01 00:00:00 1970 +0000
772 772 summary: commit on default
773 773
774 774 changeset: 0:24427303d56f
775 775 user: test
776 776 date: Thu Jan 01 00:00:00 1970 +0000
777 777 summary: commit on default
778 778
779 779
780 780
781 781 log -p --cwd dir (in subdir)
782 782
783 783 $ mkdir dir
784 784 $ hg log -p --cwd dir
785 785 changeset: 3:f5d8de11c2e2
786 786 branch: test
787 787 tag: tip
788 788 parent: 1:d32277701ccb
789 789 user: test
790 790 date: Thu Jan 01 00:00:00 1970 +0000
791 791 summary: commit on test
792 792
793 793 diff -r d32277701ccb -r f5d8de11c2e2 c
794 794 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
795 795 +++ b/c Thu Jan 01 00:00:00 1970 +0000
796 796 @@ -0,0 +1,1 @@
797 797 +c
798 798
799 799 changeset: 2:c3a4f03cc9a7
800 800 parent: 0:24427303d56f
801 801 user: test
802 802 date: Thu Jan 01 00:00:00 1970 +0000
803 803 summary: commit on default
804 804
805 805 diff -r 24427303d56f -r c3a4f03cc9a7 c
806 806 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
807 807 +++ b/c Thu Jan 01 00:00:00 1970 +0000
808 808 @@ -0,0 +1,1 @@
809 809 +c
810 810
811 811 changeset: 1:d32277701ccb
812 812 branch: test
813 813 user: test
814 814 date: Thu Jan 01 00:00:00 1970 +0000
815 815 summary: commit on test
816 816
817 817 diff -r 24427303d56f -r d32277701ccb b
818 818 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
819 819 +++ b/b Thu Jan 01 00:00:00 1970 +0000
820 820 @@ -0,0 +1,1 @@
821 821 +b
822 822
823 823 changeset: 0:24427303d56f
824 824 user: test
825 825 date: Thu Jan 01 00:00:00 1970 +0000
826 826 summary: commit on default
827 827
828 828 diff -r 000000000000 -r 24427303d56f a
829 829 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
830 830 +++ b/a Thu Jan 01 00:00:00 1970 +0000
831 831 @@ -0,0 +1,1 @@
832 832 +a
833 833
834 834
835 835
836 836 log -p -R repo
837 837
838 838 $ cd dir
839 839 $ hg log -p -R .. ../a
840 840 changeset: 0:24427303d56f
841 841 user: test
842 842 date: Thu Jan 01 00:00:00 1970 +0000
843 843 summary: commit on default
844 844
845 845 diff -r 000000000000 -r 24427303d56f a
846 846 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
847 847 +++ b/a Thu Jan 01 00:00:00 1970 +0000
848 848 @@ -0,0 +1,1 @@
849 849 +a
850 850
851 851
852 852
853 853 $ cd ..
854 854 $ hg init follow2
855 855 $ cd follow2
856 856
857 857
858 858 # Build the following history:
859 859 # tip - o - x - o - x - x
860 860 # \ /
861 861 # o - o - o - x
862 862 # \ /
863 863 # o
864 864 #
865 865 # Where "o" is a revision containing "foo" and
866 866 # "x" is a revision without "foo"
867 867
868 868 $ touch init
869 869 $ hg ci -A -m "init, unrelated"
870 870 adding init
871 871 $ echo 'foo' > init
872 872 $ hg ci -m "change, unrelated"
873 873 $ echo 'foo' > foo
874 874 $ hg ci -A -m "add unrelated old foo"
875 875 adding foo
876 876 $ hg rm foo
877 877 $ hg ci -m "delete foo, unrelated"
878 878 $ echo 'related' > foo
879 879 $ hg ci -A -m "add foo, related"
880 880 adding foo
881 881
882 882 $ hg up 0
883 883 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
884 884 $ touch branch
885 885 $ hg ci -A -m "first branch, unrelated"
886 886 adding branch
887 887 created new head
888 888 $ touch foo
889 889 $ hg ci -A -m "create foo, related"
890 890 adding foo
891 891 $ echo 'change' > foo
892 892 $ hg ci -m "change foo, related"
893 893
894 894 $ hg up 6
895 895 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
896 896 $ echo 'change foo in branch' > foo
897 897 $ hg ci -m "change foo in branch, related"
898 898 created new head
899 899 $ hg merge 7
900 900 merging foo
901 901 warning: conflicts during merge.
902 902 merging foo failed!
903 903 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
904 904 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
905 905 [1]
906 906 $ echo 'merge 1' > foo
907 907 $ hg resolve -m foo
908 908 $ hg ci -m "First merge, related"
909 909
910 910 $ hg merge 4
911 911 merging foo
912 912 warning: conflicts during merge.
913 913 merging foo failed!
914 914 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
915 915 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
916 916 [1]
917 917 $ echo 'merge 2' > foo
918 918 $ hg resolve -m foo
919 919 $ hg ci -m "Last merge, related"
920 920
921 921 $ hg --config "extensions.graphlog=" glog
922 922 @ changeset: 10:4dae8563d2c5
923 923 |\ tag: tip
924 924 | | parent: 9:7b35701b003e
925 925 | | parent: 4:88176d361b69
926 926 | | user: test
927 927 | | date: Thu Jan 01 00:00:00 1970 +0000
928 928 | | summary: Last merge, related
929 929 | |
930 930 | o changeset: 9:7b35701b003e
931 931 | |\ parent: 8:e5416ad8a855
932 932 | | | parent: 7:87fe3144dcfa
933 933 | | | user: test
934 934 | | | date: Thu Jan 01 00:00:00 1970 +0000
935 935 | | | summary: First merge, related
936 936 | | |
937 937 | | o changeset: 8:e5416ad8a855
938 938 | | | parent: 6:dc6c325fe5ee
939 939 | | | user: test
940 940 | | | date: Thu Jan 01 00:00:00 1970 +0000
941 941 | | | summary: change foo in branch, related
942 942 | | |
943 943 | o | changeset: 7:87fe3144dcfa
944 944 | |/ user: test
945 945 | | date: Thu Jan 01 00:00:00 1970 +0000
946 946 | | summary: change foo, related
947 947 | |
948 948 | o changeset: 6:dc6c325fe5ee
949 949 | | user: test
950 950 | | date: Thu Jan 01 00:00:00 1970 +0000
951 951 | | summary: create foo, related
952 952 | |
953 953 | o changeset: 5:73db34516eb9
954 954 | | parent: 0:e87515fd044a
955 955 | | user: test
956 956 | | date: Thu Jan 01 00:00:00 1970 +0000
957 957 | | summary: first branch, unrelated
958 958 | |
959 959 o | changeset: 4:88176d361b69
960 960 | | user: test
961 961 | | date: Thu Jan 01 00:00:00 1970 +0000
962 962 | | summary: add foo, related
963 963 | |
964 964 o | changeset: 3:dd78ae4afb56
965 965 | | user: test
966 966 | | date: Thu Jan 01 00:00:00 1970 +0000
967 967 | | summary: delete foo, unrelated
968 968 | |
969 969 o | changeset: 2:c4c64aedf0f7
970 970 | | user: test
971 971 | | date: Thu Jan 01 00:00:00 1970 +0000
972 972 | | summary: add unrelated old foo
973 973 | |
974 974 o | changeset: 1:e5faa7440653
975 975 |/ user: test
976 976 | date: Thu Jan 01 00:00:00 1970 +0000
977 977 | summary: change, unrelated
978 978 |
979 979 o changeset: 0:e87515fd044a
980 980 user: test
981 981 date: Thu Jan 01 00:00:00 1970 +0000
982 982 summary: init, unrelated
983 983
984 984
985 985 $ hg --traceback log -f foo
986 986 changeset: 10:4dae8563d2c5
987 987 tag: tip
988 988 parent: 9:7b35701b003e
989 989 parent: 4:88176d361b69
990 990 user: test
991 991 date: Thu Jan 01 00:00:00 1970 +0000
992 992 summary: Last merge, related
993 993
994 994 changeset: 9:7b35701b003e
995 995 parent: 8:e5416ad8a855
996 996 parent: 7:87fe3144dcfa
997 997 user: test
998 998 date: Thu Jan 01 00:00:00 1970 +0000
999 999 summary: First merge, related
1000 1000
1001 1001 changeset: 8:e5416ad8a855
1002 1002 parent: 6:dc6c325fe5ee
1003 1003 user: test
1004 1004 date: Thu Jan 01 00:00:00 1970 +0000
1005 1005 summary: change foo in branch, related
1006 1006
1007 1007 changeset: 7:87fe3144dcfa
1008 1008 user: test
1009 1009 date: Thu Jan 01 00:00:00 1970 +0000
1010 1010 summary: change foo, related
1011 1011
1012 1012 changeset: 6:dc6c325fe5ee
1013 1013 user: test
1014 1014 date: Thu Jan 01 00:00:00 1970 +0000
1015 1015 summary: create foo, related
1016 1016
1017 1017 changeset: 4:88176d361b69
1018 1018 user: test
1019 1019 date: Thu Jan 01 00:00:00 1970 +0000
1020 1020 summary: add foo, related
1021 1021
1022 1022
1023 Also check when maxrev < lastrevfilelog
1024
1025 $ hg --traceback log -f -r4 foo
1026 changeset: 4:88176d361b69
1027 user: test
1028 date: Thu Jan 01 00:00:00 1970 +0000
1029 summary: add foo, related
1030
1031
1023 1032 Issue2383: hg log showing _less_ differences than hg diff
1024 1033
1025 1034 $ hg init issue2383
1026 1035 $ cd issue2383
1027 1036
1028 1037 Create a test repo:
1029 1038
1030 1039 $ echo a > a
1031 1040 $ hg ci -Am0
1032 1041 adding a
1033 1042 $ echo b > b
1034 1043 $ hg ci -Am1
1035 1044 adding b
1036 1045 $ hg co 0
1037 1046 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1038 1047 $ echo b > a
1039 1048 $ hg ci -m2
1040 1049 created new head
1041 1050
1042 1051 Merge:
1043 1052
1044 1053 $ hg merge
1045 1054 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1046 1055 (branch merge, don't forget to commit)
1047 1056
1048 1057 Make sure there's a file listed in the merge to trigger the bug:
1049 1058
1050 1059 $ echo c > a
1051 1060 $ hg ci -m3
1052 1061
1053 1062 Two files shown here in diff:
1054 1063
1055 1064 $ hg diff --rev 2:3
1056 1065 diff -r b09be438c43a -r 8e07aafe1edc a
1057 1066 --- a/a Thu Jan 01 00:00:00 1970 +0000
1058 1067 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1059 1068 @@ -1,1 +1,1 @@
1060 1069 -b
1061 1070 +c
1062 1071 diff -r b09be438c43a -r 8e07aafe1edc b
1063 1072 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1064 1073 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1065 1074 @@ -0,0 +1,1 @@
1066 1075 +b
1067 1076
1068 1077 Diff here should be the same:
1069 1078
1070 1079 $ hg log -vpr 3
1071 1080 changeset: 3:8e07aafe1edc
1072 1081 tag: tip
1073 1082 parent: 2:b09be438c43a
1074 1083 parent: 1:925d80f479bb
1075 1084 user: test
1076 1085 date: Thu Jan 01 00:00:00 1970 +0000
1077 1086 files: a
1078 1087 description:
1079 1088 3
1080 1089
1081 1090
1082 1091 diff -r b09be438c43a -r 8e07aafe1edc a
1083 1092 --- a/a Thu Jan 01 00:00:00 1970 +0000
1084 1093 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1085 1094 @@ -1,1 +1,1 @@
1086 1095 -b
1087 1096 +c
1088 1097 diff -r b09be438c43a -r 8e07aafe1edc b
1089 1098 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1090 1099 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1091 1100 @@ -0,0 +1,1 @@
1092 1101 +b
1093 1102
1094 1103 $ cd ..
1104
1105 'hg log -r rev fn' when last(filelog(fn)) != rev
1106
1107 $ hg init simplelog; cd simplelog
1108 $ echo f > a
1109 $ hg ci -Am'a' -d '0 0'
1110 adding a
1111 $ echo f >> a
1112 $ hg ci -Am'a bis' -d '1 0'
1113
1114 $ hg log -r0 a
1115 changeset: 0:9f758d63dcde
1116 user: test
1117 date: Thu Jan 01 00:00:00 1970 +0000
1118 summary: a
1119
General Comments 0
You need to be logged in to leave comments. Login now