##// END OF EJS Templates
cmdutil: fix missing default 'extra' template
Patrick Mezard -
r9961:0aa1a632 default
parent child Browse files
Show More
@@ -1,1293 +1,1294
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, incorporated herein by reference.
7 7
8 8 from node import hex, nullid, nullrev, short
9 9 from i18n import _
10 10 import os, sys, errno, re, glob
11 11 import mdiff, bdiff, util, templater, patch, error, encoding
12 12 import match as _match
13 13
14 14 revrangesep = ':'
15 15
16 16 def findpossible(cmd, table, strict=False):
17 17 """
18 18 Return cmd -> (aliases, command table entry)
19 19 for each matching command.
20 20 Return debug commands (or their aliases) only if no normal command matches.
21 21 """
22 22 choice = {}
23 23 debugchoice = {}
24 24 for e in table.keys():
25 25 aliases = e.lstrip("^").split("|")
26 26 found = None
27 27 if cmd in aliases:
28 28 found = cmd
29 29 elif not strict:
30 30 for a in aliases:
31 31 if a.startswith(cmd):
32 32 found = a
33 33 break
34 34 if found is not None:
35 35 if aliases[0].startswith("debug") or found.startswith("debug"):
36 36 debugchoice[found] = (aliases, table[e])
37 37 else:
38 38 choice[found] = (aliases, table[e])
39 39
40 40 if not choice and debugchoice:
41 41 choice = debugchoice
42 42
43 43 return choice
44 44
45 45 def findcmd(cmd, table, strict=True):
46 46 """Return (aliases, command table entry) for command string."""
47 47 choice = findpossible(cmd, table, strict)
48 48
49 49 if cmd in choice:
50 50 return choice[cmd]
51 51
52 52 if len(choice) > 1:
53 53 clist = choice.keys()
54 54 clist.sort()
55 55 raise error.AmbiguousCommand(cmd, clist)
56 56
57 57 if choice:
58 58 return choice.values()[0]
59 59
60 60 raise error.UnknownCommand(cmd)
61 61
62 62 def bail_if_changed(repo):
63 63 if repo.dirstate.parents()[1] != nullid:
64 64 raise util.Abort(_('outstanding uncommitted merge'))
65 65 modified, added, removed, deleted = repo.status()[:4]
66 66 if modified or added or removed or deleted:
67 67 raise util.Abort(_("outstanding uncommitted changes"))
68 68
69 69 def logmessage(opts):
70 70 """ get the log message according to -m and -l option """
71 71 message = opts.get('message')
72 72 logfile = opts.get('logfile')
73 73
74 74 if message and logfile:
75 75 raise util.Abort(_('options --message and --logfile are mutually '
76 76 'exclusive'))
77 77 if not message and logfile:
78 78 try:
79 79 if logfile == '-':
80 80 message = sys.stdin.read()
81 81 else:
82 82 message = open(logfile).read()
83 83 except IOError, inst:
84 84 raise util.Abort(_("can't read commit message '%s': %s") %
85 85 (logfile, inst.strerror))
86 86 return message
87 87
88 88 def loglimit(opts):
89 89 """get the log limit according to option -l/--limit"""
90 90 limit = opts.get('limit')
91 91 if limit:
92 92 try:
93 93 limit = int(limit)
94 94 except ValueError:
95 95 raise util.Abort(_('limit must be a positive integer'))
96 96 if limit <= 0: raise util.Abort(_('limit must be positive'))
97 97 else:
98 98 limit = sys.maxint
99 99 return limit
100 100
101 101 def remoteui(src, opts):
102 102 'build a remote ui from ui or repo and opts'
103 103 if hasattr(src, 'baseui'): # looks like a repository
104 104 dst = src.baseui.copy() # drop repo-specific config
105 105 src = src.ui # copy target options from repo
106 106 else: # assume it's a global ui object
107 107 dst = src.copy() # keep all global options
108 108
109 109 # copy ssh-specific options
110 110 for o in 'ssh', 'remotecmd':
111 111 v = opts.get(o) or src.config('ui', o)
112 112 if v:
113 113 dst.setconfig("ui", o, v)
114 114 # copy bundle-specific options
115 115 r = src.config('bundle', 'mainreporoot')
116 116 if r:
117 117 dst.setconfig('bundle', 'mainreporoot', r)
118 118
119 119 return dst
120 120
121 121 def revpair(repo, revs):
122 122 '''return pair of nodes, given list of revisions. second item can
123 123 be None, meaning use working dir.'''
124 124
125 125 def revfix(repo, val, defval):
126 126 if not val and val != 0 and defval is not None:
127 127 val = defval
128 128 return repo.lookup(val)
129 129
130 130 if not revs:
131 131 return repo.dirstate.parents()[0], None
132 132 end = None
133 133 if len(revs) == 1:
134 134 if revrangesep in revs[0]:
135 135 start, end = revs[0].split(revrangesep, 1)
136 136 start = revfix(repo, start, 0)
137 137 end = revfix(repo, end, len(repo) - 1)
138 138 else:
139 139 start = revfix(repo, revs[0], None)
140 140 elif len(revs) == 2:
141 141 if revrangesep in revs[0] or revrangesep in revs[1]:
142 142 raise util.Abort(_('too many revisions specified'))
143 143 start = revfix(repo, revs[0], None)
144 144 end = revfix(repo, revs[1], None)
145 145 else:
146 146 raise util.Abort(_('too many revisions specified'))
147 147 return start, end
148 148
149 149 def revrange(repo, revs):
150 150 """Yield revision as strings from a list of revision specifications."""
151 151
152 152 def revfix(repo, val, defval):
153 153 if not val and val != 0 and defval is not None:
154 154 return defval
155 155 return repo.changelog.rev(repo.lookup(val))
156 156
157 157 seen, l = set(), []
158 158 for spec in revs:
159 159 if revrangesep in spec:
160 160 start, end = spec.split(revrangesep, 1)
161 161 start = revfix(repo, start, 0)
162 162 end = revfix(repo, end, len(repo) - 1)
163 163 step = start > end and -1 or 1
164 164 for rev in xrange(start, end+step, step):
165 165 if rev in seen:
166 166 continue
167 167 seen.add(rev)
168 168 l.append(rev)
169 169 else:
170 170 rev = revfix(repo, spec, None)
171 171 if rev in seen:
172 172 continue
173 173 seen.add(rev)
174 174 l.append(rev)
175 175
176 176 return l
177 177
178 178 def make_filename(repo, pat, node,
179 179 total=None, seqno=None, revwidth=None, pathname=None):
180 180 node_expander = {
181 181 'H': lambda: hex(node),
182 182 'R': lambda: str(repo.changelog.rev(node)),
183 183 'h': lambda: short(node),
184 184 }
185 185 expander = {
186 186 '%': lambda: '%',
187 187 'b': lambda: os.path.basename(repo.root),
188 188 }
189 189
190 190 try:
191 191 if node:
192 192 expander.update(node_expander)
193 193 if node:
194 194 expander['r'] = (lambda:
195 195 str(repo.changelog.rev(node)).zfill(revwidth or 0))
196 196 if total is not None:
197 197 expander['N'] = lambda: str(total)
198 198 if seqno is not None:
199 199 expander['n'] = lambda: str(seqno)
200 200 if total is not None and seqno is not None:
201 201 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
202 202 if pathname is not None:
203 203 expander['s'] = lambda: os.path.basename(pathname)
204 204 expander['d'] = lambda: os.path.dirname(pathname) or '.'
205 205 expander['p'] = lambda: pathname
206 206
207 207 newname = []
208 208 patlen = len(pat)
209 209 i = 0
210 210 while i < patlen:
211 211 c = pat[i]
212 212 if c == '%':
213 213 i += 1
214 214 c = pat[i]
215 215 c = expander[c]()
216 216 newname.append(c)
217 217 i += 1
218 218 return ''.join(newname)
219 219 except KeyError, inst:
220 220 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
221 221 inst.args[0])
222 222
223 223 def make_file(repo, pat, node=None,
224 224 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
225 225
226 226 writable = 'w' in mode or 'a' in mode
227 227
228 228 if not pat or pat == '-':
229 229 return writable and sys.stdout or sys.stdin
230 230 if hasattr(pat, 'write') and writable:
231 231 return pat
232 232 if hasattr(pat, 'read') and 'r' in mode:
233 233 return pat
234 234 return open(make_filename(repo, pat, node, total, seqno, revwidth,
235 235 pathname),
236 236 mode)
237 237
238 238 def expandpats(pats):
239 239 if not util.expandglobs:
240 240 return list(pats)
241 241 ret = []
242 242 for p in pats:
243 243 kind, name = _match._patsplit(p, None)
244 244 if kind is None:
245 245 try:
246 246 globbed = glob.glob(name)
247 247 except re.error:
248 248 globbed = [name]
249 249 if globbed:
250 250 ret.extend(globbed)
251 251 continue
252 252 ret.append(p)
253 253 return ret
254 254
255 255 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
256 256 if not globbed and default == 'relpath':
257 257 pats = expandpats(pats or [])
258 258 m = _match.match(repo.root, repo.getcwd(), pats,
259 259 opts.get('include'), opts.get('exclude'), default)
260 260 def badfn(f, msg):
261 261 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
262 262 m.bad = badfn
263 263 return m
264 264
265 265 def matchall(repo):
266 266 return _match.always(repo.root, repo.getcwd())
267 267
268 268 def matchfiles(repo, files):
269 269 return _match.exact(repo.root, repo.getcwd(), files)
270 270
271 271 def findrenames(repo, added, removed, threshold):
272 272 '''find renamed files -- yields (before, after, score) tuples'''
273 273 copies = {}
274 274 ctx = repo['.']
275 275 for r in removed:
276 276 if r not in ctx:
277 277 continue
278 278 fctx = ctx.filectx(r)
279 279
280 280 def score(text):
281 281 if not len(text):
282 282 return 0.0
283 283 if not fctx.cmp(text):
284 284 return 1.0
285 285 if threshold == 1.0:
286 286 return 0.0
287 287 orig = fctx.data()
288 288 # bdiff.blocks() returns blocks of matching lines
289 289 # count the number of bytes in each
290 290 equal = 0
291 291 alines = mdiff.splitnewlines(text)
292 292 matches = bdiff.blocks(text, orig)
293 293 for x1, x2, y1, y2 in matches:
294 294 for line in alines[x1:x2]:
295 295 equal += len(line)
296 296
297 297 lengths = len(text) + len(orig)
298 298 return equal * 2.0 / lengths
299 299
300 300 for a in added:
301 301 bestscore = copies.get(a, (None, threshold))[1]
302 302 myscore = score(repo.wread(a))
303 303 if myscore >= bestscore:
304 304 copies[a] = (r, myscore)
305 305
306 306 for dest, v in copies.iteritems():
307 307 source, score = v
308 308 yield source, dest, score
309 309
310 310 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
311 311 if dry_run is None:
312 312 dry_run = opts.get('dry_run')
313 313 if similarity is None:
314 314 similarity = float(opts.get('similarity') or 0)
315 315 # we'd use status here, except handling of symlinks and ignore is tricky
316 316 added, unknown, deleted, removed = [], [], [], []
317 317 audit_path = util.path_auditor(repo.root)
318 318 m = match(repo, pats, opts)
319 319 for abs in repo.walk(m):
320 320 target = repo.wjoin(abs)
321 321 good = True
322 322 try:
323 323 audit_path(abs)
324 324 except:
325 325 good = False
326 326 rel = m.rel(abs)
327 327 exact = m.exact(abs)
328 328 if good and abs not in repo.dirstate:
329 329 unknown.append(abs)
330 330 if repo.ui.verbose or not exact:
331 331 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
332 332 elif repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
333 333 or (os.path.isdir(target) and not os.path.islink(target))):
334 334 deleted.append(abs)
335 335 if repo.ui.verbose or not exact:
336 336 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
337 337 # for finding renames
338 338 elif repo.dirstate[abs] == 'r':
339 339 removed.append(abs)
340 340 elif repo.dirstate[abs] == 'a':
341 341 added.append(abs)
342 342 if not dry_run:
343 343 repo.remove(deleted)
344 344 repo.add(unknown)
345 345 if similarity > 0:
346 346 for old, new, score in findrenames(repo, added + unknown,
347 347 removed + deleted, similarity):
348 348 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
349 349 repo.ui.status(_('recording removal of %s as rename to %s '
350 350 '(%d%% similar)\n') %
351 351 (m.rel(old), m.rel(new), score * 100))
352 352 if not dry_run:
353 353 repo.copy(old, new)
354 354
355 355 def copy(ui, repo, pats, opts, rename=False):
356 356 # called with the repo lock held
357 357 #
358 358 # hgsep => pathname that uses "/" to separate directories
359 359 # ossep => pathname that uses os.sep to separate directories
360 360 cwd = repo.getcwd()
361 361 targets = {}
362 362 after = opts.get("after")
363 363 dryrun = opts.get("dry_run")
364 364
365 365 def walkpat(pat):
366 366 srcs = []
367 367 m = match(repo, [pat], opts, globbed=True)
368 368 for abs in repo.walk(m):
369 369 state = repo.dirstate[abs]
370 370 rel = m.rel(abs)
371 371 exact = m.exact(abs)
372 372 if state in '?r':
373 373 if exact and state == '?':
374 374 ui.warn(_('%s: not copying - file is not managed\n') % rel)
375 375 if exact and state == 'r':
376 376 ui.warn(_('%s: not copying - file has been marked for'
377 377 ' remove\n') % rel)
378 378 continue
379 379 # abs: hgsep
380 380 # rel: ossep
381 381 srcs.append((abs, rel, exact))
382 382 return srcs
383 383
384 384 # abssrc: hgsep
385 385 # relsrc: ossep
386 386 # otarget: ossep
387 387 def copyfile(abssrc, relsrc, otarget, exact):
388 388 abstarget = util.canonpath(repo.root, cwd, otarget)
389 389 reltarget = repo.pathto(abstarget, cwd)
390 390 target = repo.wjoin(abstarget)
391 391 src = repo.wjoin(abssrc)
392 392 state = repo.dirstate[abstarget]
393 393
394 394 # check for collisions
395 395 prevsrc = targets.get(abstarget)
396 396 if prevsrc is not None:
397 397 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
398 398 (reltarget, repo.pathto(abssrc, cwd),
399 399 repo.pathto(prevsrc, cwd)))
400 400 return
401 401
402 402 # check for overwrites
403 403 exists = os.path.exists(target)
404 404 if not after and exists or after and state in 'mn':
405 405 if not opts['force']:
406 406 ui.warn(_('%s: not overwriting - file exists\n') %
407 407 reltarget)
408 408 return
409 409
410 410 if after:
411 411 if not exists:
412 412 return
413 413 elif not dryrun:
414 414 try:
415 415 if exists:
416 416 os.unlink(target)
417 417 targetdir = os.path.dirname(target) or '.'
418 418 if not os.path.isdir(targetdir):
419 419 os.makedirs(targetdir)
420 420 util.copyfile(src, target)
421 421 except IOError, inst:
422 422 if inst.errno == errno.ENOENT:
423 423 ui.warn(_('%s: deleted in working copy\n') % relsrc)
424 424 else:
425 425 ui.warn(_('%s: cannot copy - %s\n') %
426 426 (relsrc, inst.strerror))
427 427 return True # report a failure
428 428
429 429 if ui.verbose or not exact:
430 430 if rename:
431 431 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
432 432 else:
433 433 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
434 434
435 435 targets[abstarget] = abssrc
436 436
437 437 # fix up dirstate
438 438 origsrc = repo.dirstate.copied(abssrc) or abssrc
439 439 if abstarget == origsrc: # copying back a copy?
440 440 if state not in 'mn' and not dryrun:
441 441 repo.dirstate.normallookup(abstarget)
442 442 else:
443 443 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
444 444 if not ui.quiet:
445 445 ui.warn(_("%s has not been committed yet, so no copy "
446 446 "data will be stored for %s.\n")
447 447 % (repo.pathto(origsrc, cwd), reltarget))
448 448 if repo.dirstate[abstarget] in '?r' and not dryrun:
449 449 repo.add([abstarget])
450 450 elif not dryrun:
451 451 repo.copy(origsrc, abstarget)
452 452
453 453 if rename and not dryrun:
454 454 repo.remove([abssrc], not after)
455 455
456 456 # pat: ossep
457 457 # dest ossep
458 458 # srcs: list of (hgsep, hgsep, ossep, bool)
459 459 # return: function that takes hgsep and returns ossep
460 460 def targetpathfn(pat, dest, srcs):
461 461 if os.path.isdir(pat):
462 462 abspfx = util.canonpath(repo.root, cwd, pat)
463 463 abspfx = util.localpath(abspfx)
464 464 if destdirexists:
465 465 striplen = len(os.path.split(abspfx)[0])
466 466 else:
467 467 striplen = len(abspfx)
468 468 if striplen:
469 469 striplen += len(os.sep)
470 470 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
471 471 elif destdirexists:
472 472 res = lambda p: os.path.join(dest,
473 473 os.path.basename(util.localpath(p)))
474 474 else:
475 475 res = lambda p: dest
476 476 return res
477 477
478 478 # pat: ossep
479 479 # dest ossep
480 480 # srcs: list of (hgsep, hgsep, ossep, bool)
481 481 # return: function that takes hgsep and returns ossep
482 482 def targetpathafterfn(pat, dest, srcs):
483 483 if _match.patkind(pat):
484 484 # a mercurial pattern
485 485 res = lambda p: os.path.join(dest,
486 486 os.path.basename(util.localpath(p)))
487 487 else:
488 488 abspfx = util.canonpath(repo.root, cwd, pat)
489 489 if len(abspfx) < len(srcs[0][0]):
490 490 # A directory. Either the target path contains the last
491 491 # component of the source path or it does not.
492 492 def evalpath(striplen):
493 493 score = 0
494 494 for s in srcs:
495 495 t = os.path.join(dest, util.localpath(s[0])[striplen:])
496 496 if os.path.exists(t):
497 497 score += 1
498 498 return score
499 499
500 500 abspfx = util.localpath(abspfx)
501 501 striplen = len(abspfx)
502 502 if striplen:
503 503 striplen += len(os.sep)
504 504 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
505 505 score = evalpath(striplen)
506 506 striplen1 = len(os.path.split(abspfx)[0])
507 507 if striplen1:
508 508 striplen1 += len(os.sep)
509 509 if evalpath(striplen1) > score:
510 510 striplen = striplen1
511 511 res = lambda p: os.path.join(dest,
512 512 util.localpath(p)[striplen:])
513 513 else:
514 514 # a file
515 515 if destdirexists:
516 516 res = lambda p: os.path.join(dest,
517 517 os.path.basename(util.localpath(p)))
518 518 else:
519 519 res = lambda p: dest
520 520 return res
521 521
522 522
523 523 pats = expandpats(pats)
524 524 if not pats:
525 525 raise util.Abort(_('no source or destination specified'))
526 526 if len(pats) == 1:
527 527 raise util.Abort(_('no destination specified'))
528 528 dest = pats.pop()
529 529 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
530 530 if not destdirexists:
531 531 if len(pats) > 1 or _match.patkind(pats[0]):
532 532 raise util.Abort(_('with multiple sources, destination must be an '
533 533 'existing directory'))
534 534 if util.endswithsep(dest):
535 535 raise util.Abort(_('destination %s is not a directory') % dest)
536 536
537 537 tfn = targetpathfn
538 538 if after:
539 539 tfn = targetpathafterfn
540 540 copylist = []
541 541 for pat in pats:
542 542 srcs = walkpat(pat)
543 543 if not srcs:
544 544 continue
545 545 copylist.append((tfn(pat, dest, srcs), srcs))
546 546 if not copylist:
547 547 raise util.Abort(_('no files to copy'))
548 548
549 549 errors = 0
550 550 for targetpath, srcs in copylist:
551 551 for abssrc, relsrc, exact in srcs:
552 552 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
553 553 errors += 1
554 554
555 555 if errors:
556 556 ui.warn(_('(consider using --after)\n'))
557 557
558 558 return errors
559 559
560 560 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
561 561 runargs=None):
562 562 '''Run a command as a service.'''
563 563
564 564 if opts['daemon'] and not opts['daemon_pipefds']:
565 565 rfd, wfd = os.pipe()
566 566 if not runargs:
567 567 runargs = sys.argv[:]
568 568 runargs.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
569 569 # Don't pass --cwd to the child process, because we've already
570 570 # changed directory.
571 571 for i in xrange(1,len(runargs)):
572 572 if runargs[i].startswith('--cwd='):
573 573 del runargs[i]
574 574 break
575 575 elif runargs[i].startswith('--cwd'):
576 576 del runargs[i:i+2]
577 577 break
578 578 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
579 579 runargs[0], runargs)
580 580 os.close(wfd)
581 581 os.read(rfd, 1)
582 582 if parentfn:
583 583 return parentfn(pid)
584 584 else:
585 585 return
586 586
587 587 if initfn:
588 588 initfn()
589 589
590 590 if opts['pid_file']:
591 591 fp = open(opts['pid_file'], 'w')
592 592 fp.write(str(os.getpid()) + '\n')
593 593 fp.close()
594 594
595 595 if opts['daemon_pipefds']:
596 596 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
597 597 os.close(rfd)
598 598 try:
599 599 os.setsid()
600 600 except AttributeError:
601 601 pass
602 602 os.write(wfd, 'y')
603 603 os.close(wfd)
604 604 sys.stdout.flush()
605 605 sys.stderr.flush()
606 606
607 607 nullfd = os.open(util.nulldev, os.O_RDWR)
608 608 logfilefd = nullfd
609 609 if logfile:
610 610 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
611 611 os.dup2(nullfd, 0)
612 612 os.dup2(logfilefd, 1)
613 613 os.dup2(logfilefd, 2)
614 614 if nullfd not in (0, 1, 2):
615 615 os.close(nullfd)
616 616 if logfile and logfilefd not in (0, 1, 2):
617 617 os.close(logfilefd)
618 618
619 619 if runfn:
620 620 return runfn()
621 621
622 622 class changeset_printer(object):
623 623 '''show changeset information when templating not requested.'''
624 624
625 625 def __init__(self, ui, repo, patch, diffopts, buffered):
626 626 self.ui = ui
627 627 self.repo = repo
628 628 self.buffered = buffered
629 629 self.patch = patch
630 630 self.diffopts = diffopts
631 631 self.header = {}
632 632 self.hunk = {}
633 633 self.lastheader = None
634 634
635 635 def flush(self, rev):
636 636 if rev in self.header:
637 637 h = self.header[rev]
638 638 if h != self.lastheader:
639 639 self.lastheader = h
640 640 self.ui.write(h)
641 641 del self.header[rev]
642 642 if rev in self.hunk:
643 643 self.ui.write(self.hunk[rev])
644 644 del self.hunk[rev]
645 645 return 1
646 646 return 0
647 647
648 648 def show(self, ctx, copies=(), **props):
649 649 if self.buffered:
650 650 self.ui.pushbuffer()
651 651 self._show(ctx, copies, props)
652 652 self.hunk[ctx.rev()] = self.ui.popbuffer()
653 653 else:
654 654 self._show(ctx, copies, props)
655 655
656 656 def _show(self, ctx, copies, props):
657 657 '''show a single changeset or file revision'''
658 658 changenode = ctx.node()
659 659 rev = ctx.rev()
660 660
661 661 if self.ui.quiet:
662 662 self.ui.write("%d:%s\n" % (rev, short(changenode)))
663 663 return
664 664
665 665 log = self.repo.changelog
666 666 date = util.datestr(ctx.date())
667 667
668 668 hexfunc = self.ui.debugflag and hex or short
669 669
670 670 parents = [(p, hexfunc(log.node(p)))
671 671 for p in self._meaningful_parentrevs(log, rev)]
672 672
673 673 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
674 674
675 675 branch = ctx.branch()
676 676 # don't show the default branch name
677 677 if branch != 'default':
678 678 branch = encoding.tolocal(branch)
679 679 self.ui.write(_("branch: %s\n") % branch)
680 680 for tag in self.repo.nodetags(changenode):
681 681 self.ui.write(_("tag: %s\n") % tag)
682 682 for parent in parents:
683 683 self.ui.write(_("parent: %d:%s\n") % parent)
684 684
685 685 if self.ui.debugflag:
686 686 mnode = ctx.manifestnode()
687 687 self.ui.write(_("manifest: %d:%s\n") %
688 688 (self.repo.manifest.rev(mnode), hex(mnode)))
689 689 self.ui.write(_("user: %s\n") % ctx.user())
690 690 self.ui.write(_("date: %s\n") % date)
691 691
692 692 if self.ui.debugflag:
693 693 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
694 694 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
695 695 files):
696 696 if value:
697 697 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
698 698 elif ctx.files() and self.ui.verbose:
699 699 self.ui.write(_("files: %s\n") % " ".join(ctx.files()))
700 700 if copies and self.ui.verbose:
701 701 copies = ['%s (%s)' % c for c in copies]
702 702 self.ui.write(_("copies: %s\n") % ' '.join(copies))
703 703
704 704 extra = ctx.extra()
705 705 if extra and self.ui.debugflag:
706 706 for key, value in sorted(extra.items()):
707 707 self.ui.write(_("extra: %s=%s\n")
708 708 % (key, value.encode('string_escape')))
709 709
710 710 description = ctx.description().strip()
711 711 if description:
712 712 if self.ui.verbose:
713 713 self.ui.write(_("description:\n"))
714 714 self.ui.write(description)
715 715 self.ui.write("\n\n")
716 716 else:
717 717 self.ui.write(_("summary: %s\n") %
718 718 description.splitlines()[0])
719 719 self.ui.write("\n")
720 720
721 721 self.showpatch(changenode)
722 722
723 723 def showpatch(self, node):
724 724 if self.patch:
725 725 prev = self.repo.changelog.parents(node)[0]
726 726 chunks = patch.diff(self.repo, prev, node, match=self.patch,
727 727 opts=patch.diffopts(self.ui, self.diffopts))
728 728 for chunk in chunks:
729 729 self.ui.write(chunk)
730 730 self.ui.write("\n")
731 731
732 732 def _meaningful_parentrevs(self, log, rev):
733 733 """Return list of meaningful (or all if debug) parentrevs for rev.
734 734
735 735 For merges (two non-nullrev revisions) both parents are meaningful.
736 736 Otherwise the first parent revision is considered meaningful if it
737 737 is not the preceding revision.
738 738 """
739 739 parents = log.parentrevs(rev)
740 740 if not self.ui.debugflag and parents[1] == nullrev:
741 741 if parents[0] >= rev - 1:
742 742 parents = []
743 743 else:
744 744 parents = [parents[0]]
745 745 return parents
746 746
747 747
748 748 class changeset_templater(changeset_printer):
749 749 '''format changeset information.'''
750 750
751 751 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
752 752 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
753 753 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
754 754 self.t = templater.templater(mapfile, {'formatnode': formatnode},
755 755 cache={
756 756 'parent': '{rev}:{node|formatnode} ',
757 757 'manifest': '{rev}:{node|formatnode}',
758 'filecopy': '{name} ({source})'})
758 'filecopy': '{name} ({source})',
759 'extra': '{key}={value|stringescape}'})
759 760 # Cache mapping from rev to a tuple with tag date, tag
760 761 # distance and tag name
761 762 self._latesttagcache = {-1: (0, 0, 'null')}
762 763
763 764 def use_template(self, t):
764 765 '''set template string to use'''
765 766 self.t.cache['changeset'] = t
766 767
767 768 def _meaningful_parentrevs(self, ctx):
768 769 """Return list of meaningful (or all if debug) parentrevs for rev.
769 770 """
770 771 parents = ctx.parents()
771 772 if len(parents) > 1:
772 773 return parents
773 774 if self.ui.debugflag:
774 775 return [parents[0], self.repo['null']]
775 776 if parents[0].rev() >= ctx.rev() - 1:
776 777 return []
777 778 return parents
778 779
779 780 def _latesttaginfo(self, rev):
780 781 '''return date, distance and name for the latest tag of rev'''
781 782 todo = [rev]
782 783 while todo:
783 784 rev = todo.pop()
784 785 if rev in self._latesttagcache:
785 786 continue
786 787 ctx = self.repo[rev]
787 788 tags = [t for t in ctx.tags() if self.repo.tagtype(t) == 'global']
788 789 if tags:
789 790 self._latesttagcache[rev] = ctx.date()[0], 0, ':'.join(sorted(tags))
790 791 continue
791 792 try:
792 793 # The tuples are laid out so the right one can be found by comparison.
793 794 pdate, pdist, ptag = max(
794 795 self._latesttagcache[p.rev()] for p in ctx.parents())
795 796 except KeyError:
796 797 # Cache miss - recurse
797 798 todo.append(rev)
798 799 todo.extend(p.rev() for p in ctx.parents())
799 800 continue
800 801 self._latesttagcache[rev] = pdate, pdist + 1, ptag
801 802 return self._latesttagcache[rev]
802 803
803 804 def _show(self, ctx, copies, props):
804 805 '''show a single changeset or file revision'''
805 806
806 807 def showlist(name, values, plural=None, **args):
807 808 '''expand set of values.
808 809 name is name of key in template map.
809 810 values is list of strings or dicts.
810 811 plural is plural of name, if not simply name + 's'.
811 812
812 813 expansion works like this, given name 'foo'.
813 814
814 815 if values is empty, expand 'no_foos'.
815 816
816 817 if 'foo' not in template map, return values as a string,
817 818 joined by space.
818 819
819 820 expand 'start_foos'.
820 821
821 822 for each value, expand 'foo'. if 'last_foo' in template
822 823 map, expand it instead of 'foo' for last key.
823 824
824 825 expand 'end_foos'.
825 826 '''
826 827 if plural: names = plural
827 828 else: names = name + 's'
828 829 if not values:
829 830 noname = 'no_' + names
830 831 if noname in self.t:
831 832 yield self.t(noname, **args)
832 833 return
833 834 if name not in self.t:
834 835 if isinstance(values[0], str):
835 836 yield ' '.join(values)
836 837 else:
837 838 for v in values:
838 839 yield dict(v, **args)
839 840 return
840 841 startname = 'start_' + names
841 842 if startname in self.t:
842 843 yield self.t(startname, **args)
843 844 vargs = args.copy()
844 845 def one(v, tag=name):
845 846 try:
846 847 vargs.update(v)
847 848 except (AttributeError, ValueError):
848 849 try:
849 850 for a, b in v:
850 851 vargs[a] = b
851 852 except ValueError:
852 853 vargs[name] = v
853 854 return self.t(tag, **vargs)
854 855 lastname = 'last_' + name
855 856 if lastname in self.t:
856 857 last = values.pop()
857 858 else:
858 859 last = None
859 860 for v in values:
860 861 yield one(v)
861 862 if last is not None:
862 863 yield one(last, tag=lastname)
863 864 endname = 'end_' + names
864 865 if endname in self.t:
865 866 yield self.t(endname, **args)
866 867
867 868 def showbranches(**args):
868 869 branch = ctx.branch()
869 870 if branch != 'default':
870 871 branch = encoding.tolocal(branch)
871 872 return showlist('branch', [branch], plural='branches', **args)
872 873
873 874 def showparents(**args):
874 875 parents = [[('rev', p.rev()), ('node', p.hex())]
875 876 for p in self._meaningful_parentrevs(ctx)]
876 877 return showlist('parent', parents, **args)
877 878
878 879 def showtags(**args):
879 880 return showlist('tag', ctx.tags(), **args)
880 881
881 882 def showextras(**args):
882 883 for key, value in sorted(ctx.extra().items()):
883 884 args = args.copy()
884 885 args.update(dict(key=key, value=value))
885 886 yield self.t('extra', **args)
886 887
887 888 def showcopies(**args):
888 889 c = [{'name': x[0], 'source': x[1]} for x in copies]
889 890 return showlist('file_copy', c, plural='file_copies', **args)
890 891
891 892 files = []
892 893 def getfiles():
893 894 if not files:
894 895 files[:] = self.repo.status(ctx.parents()[0].node(),
895 896 ctx.node())[:3]
896 897 return files
897 898 def showfiles(**args):
898 899 return showlist('file', ctx.files(), **args)
899 900 def showmods(**args):
900 901 return showlist('file_mod', getfiles()[0], **args)
901 902 def showadds(**args):
902 903 return showlist('file_add', getfiles()[1], **args)
903 904 def showdels(**args):
904 905 return showlist('file_del', getfiles()[2], **args)
905 906 def showmanifest(**args):
906 907 args = args.copy()
907 908 args.update(dict(rev=self.repo.manifest.rev(ctx.changeset()[0]),
908 909 node=hex(ctx.changeset()[0])))
909 910 return self.t('manifest', **args)
910 911
911 912 def showdiffstat(**args):
912 913 diff = patch.diff(self.repo, ctx.parents()[0].node(), ctx.node())
913 914 files, adds, removes = 0, 0, 0
914 915 for i in patch.diffstatdata(util.iterlines(diff)):
915 916 files += 1
916 917 adds += i[1]
917 918 removes += i[2]
918 919 return '%s: +%s/-%s' % (files, adds, removes)
919 920
920 921 def showlatesttag(**args):
921 922 return self._latesttaginfo(ctx.rev())[2]
922 923 def showlatesttagdistance(**args):
923 924 return self._latesttaginfo(ctx.rev())[1]
924 925
925 926 defprops = {
926 927 'author': ctx.user(),
927 928 'branches': showbranches,
928 929 'date': ctx.date(),
929 930 'desc': ctx.description().strip(),
930 931 'file_adds': showadds,
931 932 'file_dels': showdels,
932 933 'file_mods': showmods,
933 934 'files': showfiles,
934 935 'file_copies': showcopies,
935 936 'manifest': showmanifest,
936 937 'node': ctx.hex(),
937 938 'parents': showparents,
938 939 'rev': ctx.rev(),
939 940 'tags': showtags,
940 941 'extras': showextras,
941 942 'diffstat': showdiffstat,
942 943 'latesttag': showlatesttag,
943 944 'latesttagdistance': showlatesttagdistance,
944 945 }
945 946 props = props.copy()
946 947 props.update(defprops)
947 948
948 949 # find correct templates for current mode
949 950
950 951 tmplmodes = [
951 952 (True, None),
952 953 (self.ui.verbose, 'verbose'),
953 954 (self.ui.quiet, 'quiet'),
954 955 (self.ui.debugflag, 'debug'),
955 956 ]
956 957
957 958 types = {'header': '', 'changeset': 'changeset'}
958 959 for mode, postfix in tmplmodes:
959 960 for type in types:
960 961 cur = postfix and ('%s_%s' % (type, postfix)) or type
961 962 if mode and cur in self.t:
962 963 types[type] = cur
963 964
964 965 try:
965 966
966 967 # write header
967 968 if types['header']:
968 969 h = templater.stringify(self.t(types['header'], **props))
969 970 if self.buffered:
970 971 self.header[ctx.rev()] = h
971 972 else:
972 973 self.ui.write(h)
973 974
974 975 # write changeset metadata, then patch if requested
975 976 key = types['changeset']
976 977 self.ui.write(templater.stringify(self.t(key, **props)))
977 978 self.showpatch(ctx.node())
978 979
979 980 except KeyError, inst:
980 981 msg = _("%s: no key named '%s'")
981 982 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
982 983 except SyntaxError, inst:
983 984 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
984 985
985 986 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
986 987 """show one changeset using template or regular display.
987 988
988 989 Display format will be the first non-empty hit of:
989 990 1. option 'template'
990 991 2. option 'style'
991 992 3. [ui] setting 'logtemplate'
992 993 4. [ui] setting 'style'
993 994 If all of these values are either the unset or the empty string,
994 995 regular display via changeset_printer() is done.
995 996 """
996 997 # options
997 998 patch = False
998 999 if opts.get('patch'):
999 1000 patch = matchfn or matchall(repo)
1000 1001
1001 1002 tmpl = opts.get('template')
1002 1003 style = None
1003 1004 if tmpl:
1004 1005 tmpl = templater.parsestring(tmpl, quoted=False)
1005 1006 else:
1006 1007 style = opts.get('style')
1007 1008
1008 1009 # ui settings
1009 1010 if not (tmpl or style):
1010 1011 tmpl = ui.config('ui', 'logtemplate')
1011 1012 if tmpl:
1012 1013 tmpl = templater.parsestring(tmpl)
1013 1014 else:
1014 1015 style = ui.config('ui', 'style')
1015 1016
1016 1017 if not (tmpl or style):
1017 1018 return changeset_printer(ui, repo, patch, opts, buffered)
1018 1019
1019 1020 mapfile = None
1020 1021 if style and not tmpl:
1021 1022 mapfile = style
1022 1023 if not os.path.split(mapfile)[0]:
1023 1024 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1024 1025 or templater.templatepath(mapfile))
1025 1026 if mapname: mapfile = mapname
1026 1027
1027 1028 try:
1028 1029 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1029 1030 except SyntaxError, inst:
1030 1031 raise util.Abort(inst.args[0])
1031 1032 if tmpl: t.use_template(tmpl)
1032 1033 return t
1033 1034
1034 1035 def finddate(ui, repo, date):
1035 1036 """Find the tipmost changeset that matches the given date spec"""
1036 1037
1037 1038 df = util.matchdate(date)
1038 1039 m = matchall(repo)
1039 1040 results = {}
1040 1041
1041 1042 def prep(ctx, fns):
1042 1043 d = ctx.date()
1043 1044 if df(d[0]):
1044 1045 results[ctx.rev()] = d
1045 1046
1046 1047 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1047 1048 rev = ctx.rev()
1048 1049 if rev in results:
1049 1050 ui.status(_("Found revision %s from %s\n") %
1050 1051 (rev, util.datestr(results[rev])))
1051 1052 return str(rev)
1052 1053
1053 1054 raise util.Abort(_("revision matching date not found"))
1054 1055
1055 1056 def walkchangerevs(repo, match, opts, prepare):
1056 1057 '''Iterate over files and the revs in which they changed.
1057 1058
1058 1059 Callers most commonly need to iterate backwards over the history
1059 1060 in which they are interested. Doing so has awful (quadratic-looking)
1060 1061 performance, so we use iterators in a "windowed" way.
1061 1062
1062 1063 We walk a window of revisions in the desired order. Within the
1063 1064 window, we first walk forwards to gather data, then in the desired
1064 1065 order (usually backwards) to display it.
1065 1066
1066 1067 This function returns an iterator yielding contexts. Before
1067 1068 yielding each context, the iterator will first call the prepare
1068 1069 function on each context in the window in forward order.'''
1069 1070
1070 1071 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1071 1072 if start < end:
1072 1073 while start < end:
1073 1074 yield start, min(windowsize, end-start)
1074 1075 start += windowsize
1075 1076 if windowsize < sizelimit:
1076 1077 windowsize *= 2
1077 1078 else:
1078 1079 while start > end:
1079 1080 yield start, min(windowsize, start-end-1)
1080 1081 start -= windowsize
1081 1082 if windowsize < sizelimit:
1082 1083 windowsize *= 2
1083 1084
1084 1085 follow = opts.get('follow') or opts.get('follow_first')
1085 1086
1086 1087 if not len(repo):
1087 1088 return []
1088 1089
1089 1090 if follow:
1090 1091 defrange = '%s:0' % repo['.'].rev()
1091 1092 else:
1092 1093 defrange = '-1:0'
1093 1094 revs = revrange(repo, opts['rev'] or [defrange])
1094 1095 wanted = set()
1095 1096 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1096 1097 fncache = {}
1097 1098 change = util.cachefunc(repo.changectx)
1098 1099
1099 1100 if not slowpath and not match.files():
1100 1101 # No files, no patterns. Display all revs.
1101 1102 wanted = set(revs)
1102 1103 copies = []
1103 1104
1104 1105 if not slowpath:
1105 1106 # Only files, no patterns. Check the history of each file.
1106 1107 def filerevgen(filelog, node):
1107 1108 cl_count = len(repo)
1108 1109 if node is None:
1109 1110 last = len(filelog) - 1
1110 1111 else:
1111 1112 last = filelog.rev(node)
1112 1113 for i, window in increasing_windows(last, nullrev):
1113 1114 revs = []
1114 1115 for j in xrange(i - window, i + 1):
1115 1116 n = filelog.node(j)
1116 1117 revs.append((filelog.linkrev(j),
1117 1118 follow and filelog.renamed(n)))
1118 1119 for rev in reversed(revs):
1119 1120 # only yield rev for which we have the changelog, it can
1120 1121 # happen while doing "hg log" during a pull or commit
1121 1122 if rev[0] < cl_count:
1122 1123 yield rev
1123 1124 def iterfiles():
1124 1125 for filename in match.files():
1125 1126 yield filename, None
1126 1127 for filename_node in copies:
1127 1128 yield filename_node
1128 1129 minrev, maxrev = min(revs), max(revs)
1129 1130 for file_, node in iterfiles():
1130 1131 filelog = repo.file(file_)
1131 1132 if not len(filelog):
1132 1133 if node is None:
1133 1134 # A zero count may be a directory or deleted file, so
1134 1135 # try to find matching entries on the slow path.
1135 1136 if follow:
1136 1137 raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_)
1137 1138 slowpath = True
1138 1139 break
1139 1140 else:
1140 1141 continue
1141 1142 for rev, copied in filerevgen(filelog, node):
1142 1143 if rev <= maxrev:
1143 1144 if rev < minrev:
1144 1145 break
1145 1146 fncache.setdefault(rev, [])
1146 1147 fncache[rev].append(file_)
1147 1148 wanted.add(rev)
1148 1149 if follow and copied:
1149 1150 copies.append(copied)
1150 1151 if slowpath:
1151 1152 if follow:
1152 1153 raise util.Abort(_('can only follow copies/renames for explicit '
1153 1154 'filenames'))
1154 1155
1155 1156 # The slow path checks files modified in every changeset.
1156 1157 def changerevgen():
1157 1158 for i, window in increasing_windows(len(repo) - 1, nullrev):
1158 1159 for j in xrange(i - window, i + 1):
1159 1160 yield change(j)
1160 1161
1161 1162 for ctx in changerevgen():
1162 1163 matches = filter(match, ctx.files())
1163 1164 if matches:
1164 1165 fncache[ctx.rev()] = matches
1165 1166 wanted.add(ctx.rev())
1166 1167
1167 1168 class followfilter(object):
1168 1169 def __init__(self, onlyfirst=False):
1169 1170 self.startrev = nullrev
1170 1171 self.roots = []
1171 1172 self.onlyfirst = onlyfirst
1172 1173
1173 1174 def match(self, rev):
1174 1175 def realparents(rev):
1175 1176 if self.onlyfirst:
1176 1177 return repo.changelog.parentrevs(rev)[0:1]
1177 1178 else:
1178 1179 return filter(lambda x: x != nullrev,
1179 1180 repo.changelog.parentrevs(rev))
1180 1181
1181 1182 if self.startrev == nullrev:
1182 1183 self.startrev = rev
1183 1184 return True
1184 1185
1185 1186 if rev > self.startrev:
1186 1187 # forward: all descendants
1187 1188 if not self.roots:
1188 1189 self.roots.append(self.startrev)
1189 1190 for parent in realparents(rev):
1190 1191 if parent in self.roots:
1191 1192 self.roots.append(rev)
1192 1193 return True
1193 1194 else:
1194 1195 # backwards: all parents
1195 1196 if not self.roots:
1196 1197 self.roots.extend(realparents(self.startrev))
1197 1198 if rev in self.roots:
1198 1199 self.roots.remove(rev)
1199 1200 self.roots.extend(realparents(rev))
1200 1201 return True
1201 1202
1202 1203 return False
1203 1204
1204 1205 # it might be worthwhile to do this in the iterator if the rev range
1205 1206 # is descending and the prune args are all within that range
1206 1207 for rev in opts.get('prune', ()):
1207 1208 rev = repo.changelog.rev(repo.lookup(rev))
1208 1209 ff = followfilter()
1209 1210 stop = min(revs[0], revs[-1])
1210 1211 for x in xrange(rev, stop-1, -1):
1211 1212 if ff.match(x):
1212 1213 wanted.discard(x)
1213 1214
1214 1215 def iterate():
1215 1216 if follow and not match.files():
1216 1217 ff = followfilter(onlyfirst=opts.get('follow_first'))
1217 1218 def want(rev):
1218 1219 return ff.match(rev) and rev in wanted
1219 1220 else:
1220 1221 def want(rev):
1221 1222 return rev in wanted
1222 1223
1223 1224 for i, window in increasing_windows(0, len(revs)):
1224 1225 change = util.cachefunc(repo.changectx)
1225 1226 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1226 1227 for rev in sorted(nrevs):
1227 1228 fns = fncache.get(rev)
1228 1229 ctx = change(rev)
1229 1230 if not fns:
1230 1231 def fns_generator():
1231 1232 for f in ctx.files():
1232 1233 if match(f):
1233 1234 yield f
1234 1235 fns = fns_generator()
1235 1236 prepare(ctx, fns)
1236 1237 for rev in nrevs:
1237 1238 yield change(rev)
1238 1239 return iterate()
1239 1240
1240 1241 def commit(ui, repo, commitfunc, pats, opts):
1241 1242 '''commit the specified files or all outstanding changes'''
1242 1243 date = opts.get('date')
1243 1244 if date:
1244 1245 opts['date'] = util.parsedate(date)
1245 1246 message = logmessage(opts)
1246 1247
1247 1248 # extract addremove carefully -- this function can be called from a command
1248 1249 # that doesn't support addremove
1249 1250 if opts.get('addremove'):
1250 1251 addremove(repo, pats, opts)
1251 1252
1252 1253 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1253 1254
1254 1255 def commiteditor(repo, ctx, subs):
1255 1256 if ctx.description():
1256 1257 return ctx.description()
1257 1258 return commitforceeditor(repo, ctx, subs)
1258 1259
1259 1260 def commitforceeditor(repo, ctx, subs):
1260 1261 edittext = []
1261 1262 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1262 1263 if ctx.description():
1263 1264 edittext.append(ctx.description())
1264 1265 edittext.append("")
1265 1266 edittext.append("") # Empty line between message and comments.
1266 1267 edittext.append(_("HG: Enter commit message."
1267 1268 " Lines beginning with 'HG:' are removed."))
1268 1269 edittext.append(_("HG: Leave message empty to abort commit."))
1269 1270 edittext.append("HG: --")
1270 1271 edittext.append(_("HG: user: %s") % ctx.user())
1271 1272 if ctx.p2():
1272 1273 edittext.append(_("HG: branch merge"))
1273 1274 if ctx.branch():
1274 1275 edittext.append(_("HG: branch '%s'")
1275 1276 % encoding.tolocal(ctx.branch()))
1276 1277 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1277 1278 edittext.extend([_("HG: added %s") % f for f in added])
1278 1279 edittext.extend([_("HG: changed %s") % f for f in modified])
1279 1280 edittext.extend([_("HG: removed %s") % f for f in removed])
1280 1281 if not added and not modified and not removed:
1281 1282 edittext.append(_("HG: no files changed"))
1282 1283 edittext.append("")
1283 1284 # run editor in the repository root
1284 1285 olddir = os.getcwd()
1285 1286 os.chdir(repo.root)
1286 1287 text = repo.ui.edit("\n".join(edittext), ctx.user())
1287 1288 text = re.sub("(?m)^HG:.*\n", "", text)
1288 1289 os.chdir(olddir)
1289 1290
1290 1291 if not text.strip():
1291 1292 raise util.Abort(_("empty commit message"))
1292 1293
1293 1294 return text
@@ -1,175 +1,175
1 1 #!/bin/sh
2 2
3 3 hg init a
4 4 cd a
5 5 echo a > a
6 6 hg add a
7 7 echo line 1 > b
8 8 echo line 2 >> b
9 9 hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
10 10 hg add b
11 11 echo other 1 > c
12 12 echo other 2 >> c
13 13 echo >> c
14 14 echo other 3 >> c
15 15 hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
16 16 hg add c
17 17 hg commit -m 'no person' -d '1200000 0' -u 'other@place'
18 18 echo c >> c
19 19 hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
20 20 echo foo > .hg/branch
21 21 hg commit -m 'new branch' -d '1400000 0' -u 'person'
22 22 hg co -q 3
23 23 echo other 4 >> d
24 24 hg add d
25 25 hg commit -m 'new head' -d '1500000 0' -u 'person'
26 26 hg merge -q foo
27 27 hg commit -m 'merge' -d '1500001 0' -u 'person'
28 28 # second branch starting at nullrev
29 29 hg update null
30 30 echo second > second
31 31 hg add second
32 32 hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
33 33 echo third > third
34 34 hg add third
35 35 hg commit -m third -d "2020-01-01 10:01"
36 36
37 37 # make sure user/global hgrc does not affect tests
38 38 echo '[ui]' > .hg/hgrc
39 39 echo 'logtemplate =' >> .hg/hgrc
40 40 echo 'style =' >> .hg/hgrc
41 41
42 42 echo '# default style is like normal output'
43 43 echo '# normal'
44 44 hg log > log.out
45 45 hg log --style default > style.out
46 46 cmp log.out style.out || diff -u log.out style.out
47 47 echo '# verbose'
48 48 hg log -v > log.out
49 49 hg log -v --style default > style.out
50 50 cmp log.out style.out || diff -u log.out style.out
51 51 echo '# debug'
52 52 hg log --debug > log.out
53 53 hg log --debug --style default > style.out
54 54 cmp log.out style.out || diff -u log.out style.out
55 55
56 56 echo '# revision with no copies (used to print a traceback)'
57 57 hg tip -v --template '\n'
58 58
59 59 echo '# compact style works'
60 60 hg log --style compact
61 61 hg log -v --style compact
62 62 hg log --debug --style compact
63 63
64 64 echo '# error if style not readable'
65 65 touch q
66 66 chmod 0 q
67 67 hg log --style ./q
68 68
69 69 echo '# error if no style'
70 70 hg log --style notexist
71 71
72 72 echo '# error if style missing key'
73 73 echo 'q = q' > t
74 74 hg log --style ./t
75 75
76 76 echo '# error if include fails'
77 77 echo 'changeset = q' >> t
78 78 hg log --style ./t
79 79
80 80 echo '# include works'
81 81 rm q
82 82 echo '{rev}' > q
83 83 hg log --style ./t
84 84
85 85 echo '# ui.style works'
86 86 echo '[ui]' > .hg/hgrc
87 87 echo 'style = t' >> .hg/hgrc
88 88 hg log
89 89
90 90 echo '# issue338'
91 91 hg log --style=changelog > changelog
92 92 cat changelog
93 93
94 94 echo "# keys work"
95 95 for key in author branches date desc file_adds file_dels file_mods \
96 files manifest node parents rev tags diffstat; do
96 files manifest node parents rev tags diffstat extras; do
97 97 for mode in '' --verbose --debug; do
98 98 hg log $mode --template "$key$mode: {$key}\n"
99 99 done
100 100 done
101 101
102 102 echo '# filters work'
103 103 hg log --template '{author|domain}\n'
104 104 hg log --template '{author|person}\n'
105 105 hg log --template '{author|user}\n'
106 106 hg log --template '{date|age}\n' > /dev/null || exit 1
107 107 hg log -l1 --template '{date|age}\n'
108 108 hg log --template '{date|date}\n'
109 109 hg log --template '{date|isodate}\n'
110 110 hg log --template '{date|isodatesec}\n'
111 111 hg log --template '{date|rfc822date}\n'
112 112 hg log --template '{desc|firstline}\n'
113 113 hg log --template '{node|short}\n'
114 114 hg log --template '<changeset author="{author|xmlescape}"/>\n'
115 115
116 116 echo '# formatnode filter works'
117 117 echo '# quiet'
118 118 hg -q log -r 0 --template '{node|formatnode}\n'
119 119 echo '# normal'
120 120 hg log -r 0 --template '{node|formatnode}\n'
121 121 echo '# verbose'
122 122 hg -v log -r 0 --template '{node|formatnode}\n'
123 123 echo '# debug'
124 124 hg --debug log -r 0 --template '{node|formatnode}\n'
125 125
126 126 echo '# error on syntax'
127 127 echo 'x = "f' >> t
128 128 hg log
129 129
130 130 cd ..
131 131
132 132 echo '# latesttag'
133 133 hg init latesttag
134 134 cd latesttag
135 135
136 136 echo a > file
137 137 hg ci -Am a -d '0 0'
138 138
139 139 echo b >> file
140 140 hg ci -m b -d '1 0'
141 141
142 142 echo c >> head1
143 143 hg ci -Am h1c -d '2 0'
144 144
145 145 hg update -q 1
146 146 echo d >> head2
147 147 hg ci -Am h2d -d '3 0'
148 148
149 149 echo e >> head2
150 150 hg ci -m h2e -d '4 0'
151 151
152 152 hg merge -q
153 153 hg ci -m merge -d '5 0'
154 154
155 155 echo '# No tag set'
156 156 hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
157 157
158 158 echo '# one common tag: longuest path wins'
159 159 hg tag -r 1 -m t1 -d '6 0' t1
160 160 hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
161 161
162 162 echo '# one ancestor tag: more recent wins'
163 163 hg tag -r 2 -m t2 -d '7 0' t2
164 164 hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
165 165
166 166 echo '# two branch tags: more recent wins'
167 167 hg tag -r 3 -m t3 -d '8 0' t3
168 168 hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
169 169
170 170 echo '# merged tag overrides'
171 171 hg tag -r 5 -m t5 -d '9 0' t5
172 172 hg tag -r 3 -m at3 -d '10 0' at3
173 173 hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
174 174
175 175 echo '# done'
@@ -1,726 +1,753
1 1 created new head
2 2 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
3 3 created new head
4 4 # default style is like normal output
5 5 # normal
6 6 # verbose
7 7 # debug
8 8 # revision with no copies (used to print a traceback)
9 9
10 10 # compact style works
11 11 8[tip] 946e2bd9c565 2020-01-01 10:01 +0000 test
12 12 third
13 13
14 14 7:-1 29114dbae42b 1970-01-12 13:46 +0000 user
15 15 second
16 16
17 17 6:5,4 c7b487c6c50e 1970-01-18 08:40 +0000 person
18 18 merge
19 19
20 20 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
21 21 new head
22 22
23 23 4 32a18f097fcc 1970-01-17 04:53 +0000 person
24 24 new branch
25 25
26 26 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
27 27 no user, no domain
28 28
29 29 2 97054abb4ab8 1970-01-14 21:20 +0000 other
30 30 no person
31 31
32 32 1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
33 33 other 1
34 34
35 35 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
36 36 line 1
37 37
38 38 8[tip] 946e2bd9c565 2020-01-01 10:01 +0000 test
39 39 third
40 40
41 41 7:-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
42 42 second
43 43
44 44 6:5,4 c7b487c6c50e 1970-01-18 08:40 +0000 person
45 45 merge
46 46
47 47 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
48 48 new head
49 49
50 50 4 32a18f097fcc 1970-01-17 04:53 +0000 person
51 51 new branch
52 52
53 53 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
54 54 no user, no domain
55 55
56 56 2 97054abb4ab8 1970-01-14 21:20 +0000 other@place
57 57 no person
58 58
59 59 1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
60 60 other 1
61 61 other 2
62 62
63 63 other 3
64 64
65 65 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
66 66 line 1
67 67 line 2
68 68
69 69 8[tip]:7,-1 946e2bd9c565 2020-01-01 10:01 +0000 test
70 70 third
71 71
72 72 7:-1,-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
73 73 second
74 74
75 75 6:5,4 c7b487c6c50e 1970-01-18 08:40 +0000 person
76 76 merge
77 77
78 78 5:3,-1 13207e5a10d9 1970-01-18 08:40 +0000 person
79 79 new head
80 80
81 81 4:3,-1 32a18f097fcc 1970-01-17 04:53 +0000 person
82 82 new branch
83 83
84 84 3:2,-1 10e46f2dcbf4 1970-01-16 01:06 +0000 person
85 85 no user, no domain
86 86
87 87 2:1,-1 97054abb4ab8 1970-01-14 21:20 +0000 other@place
88 88 no person
89 89
90 90 1:0,-1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
91 91 other 1
92 92 other 2
93 93
94 94 other 3
95 95
96 96 0:-1,-1 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
97 97 line 1
98 98 line 2
99 99
100 100 # error if style not readable
101 101 abort: Permission denied: ./q
102 102 # error if no style
103 103 abort: style not found: notexist
104 104 # error if style missing key
105 105 abort: ./t: no key named 'changeset'
106 106 # error if include fails
107 107 abort: template file ./q: Permission denied
108 108 # include works
109 109 8
110 110 7
111 111 6
112 112 5
113 113 4
114 114 3
115 115 2
116 116 1
117 117 0
118 118 # ui.style works
119 119 8
120 120 7
121 121 6
122 122 5
123 123 4
124 124 3
125 125 2
126 126 1
127 127 0
128 128 # issue338
129 129 2020-01-01 test <test>
130 130
131 131 * third:
132 132 third
133 133 [946e2bd9c565] [tip]
134 134
135 135 1970-01-12 User Name <user@hostname>
136 136
137 137 * second:
138 138 second
139 139 [29114dbae42b]
140 140
141 141 1970-01-18 person <person>
142 142
143 143 * merge
144 144 [c7b487c6c50e]
145 145
146 146 * d:
147 147 new head
148 148 [13207e5a10d9]
149 149
150 150 1970-01-17 person <person>
151 151
152 152 * new branch
153 153 [32a18f097fcc] <foo>
154 154
155 155 1970-01-16 person <person>
156 156
157 157 * c:
158 158 no user, no domain
159 159 [10e46f2dcbf4]
160 160
161 161 1970-01-14 other <other@place>
162 162
163 163 * c:
164 164 no person
165 165 [97054abb4ab8]
166 166
167 167 1970-01-13 A. N. Other <other@place>
168 168
169 169 * b:
170 170 other 1 other 2
171 171
172 172 other 3
173 173 [b608e9d1a3f0]
174 174
175 175 1970-01-12 User Name <user@hostname>
176 176
177 177 * a:
178 178 line 1 line 2
179 179 [1e4e1b8f71e0]
180 180
181 181 # keys work
182 182 author: test
183 183 author: User Name <user@hostname>
184 184 author: person
185 185 author: person
186 186 author: person
187 187 author: person
188 188 author: other@place
189 189 author: A. N. Other <other@place>
190 190 author: User Name <user@hostname>
191 191 author--verbose: test
192 192 author--verbose: User Name <user@hostname>
193 193 author--verbose: person
194 194 author--verbose: person
195 195 author--verbose: person
196 196 author--verbose: person
197 197 author--verbose: other@place
198 198 author--verbose: A. N. Other <other@place>
199 199 author--verbose: User Name <user@hostname>
200 200 author--debug: test
201 201 author--debug: User Name <user@hostname>
202 202 author--debug: person
203 203 author--debug: person
204 204 author--debug: person
205 205 author--debug: person
206 206 author--debug: other@place
207 207 author--debug: A. N. Other <other@place>
208 208 author--debug: User Name <user@hostname>
209 209 branches:
210 210 branches:
211 211 branches:
212 212 branches:
213 213 branches: foo
214 214 branches:
215 215 branches:
216 216 branches:
217 217 branches:
218 218 branches--verbose:
219 219 branches--verbose:
220 220 branches--verbose:
221 221 branches--verbose:
222 222 branches--verbose: foo
223 223 branches--verbose:
224 224 branches--verbose:
225 225 branches--verbose:
226 226 branches--verbose:
227 227 branches--debug:
228 228 branches--debug:
229 229 branches--debug:
230 230 branches--debug:
231 231 branches--debug: foo
232 232 branches--debug:
233 233 branches--debug:
234 234 branches--debug:
235 235 branches--debug:
236 236 date: 1577872860.00
237 237 date: 1000000.00
238 238 date: 1500001.00
239 239 date: 1500000.00
240 240 date: 1400000.00
241 241 date: 1300000.00
242 242 date: 1200000.00
243 243 date: 1100000.00
244 244 date: 1000000.00
245 245 date--verbose: 1577872860.00
246 246 date--verbose: 1000000.00
247 247 date--verbose: 1500001.00
248 248 date--verbose: 1500000.00
249 249 date--verbose: 1400000.00
250 250 date--verbose: 1300000.00
251 251 date--verbose: 1200000.00
252 252 date--verbose: 1100000.00
253 253 date--verbose: 1000000.00
254 254 date--debug: 1577872860.00
255 255 date--debug: 1000000.00
256 256 date--debug: 1500001.00
257 257 date--debug: 1500000.00
258 258 date--debug: 1400000.00
259 259 date--debug: 1300000.00
260 260 date--debug: 1200000.00
261 261 date--debug: 1100000.00
262 262 date--debug: 1000000.00
263 263 desc: third
264 264 desc: second
265 265 desc: merge
266 266 desc: new head
267 267 desc: new branch
268 268 desc: no user, no domain
269 269 desc: no person
270 270 desc: other 1
271 271 other 2
272 272
273 273 other 3
274 274 desc: line 1
275 275 line 2
276 276 desc--verbose: third
277 277 desc--verbose: second
278 278 desc--verbose: merge
279 279 desc--verbose: new head
280 280 desc--verbose: new branch
281 281 desc--verbose: no user, no domain
282 282 desc--verbose: no person
283 283 desc--verbose: other 1
284 284 other 2
285 285
286 286 other 3
287 287 desc--verbose: line 1
288 288 line 2
289 289 desc--debug: third
290 290 desc--debug: second
291 291 desc--debug: merge
292 292 desc--debug: new head
293 293 desc--debug: new branch
294 294 desc--debug: no user, no domain
295 295 desc--debug: no person
296 296 desc--debug: other 1
297 297 other 2
298 298
299 299 other 3
300 300 desc--debug: line 1
301 301 line 2
302 302 file_adds: third
303 303 file_adds: second
304 304 file_adds:
305 305 file_adds: d
306 306 file_adds:
307 307 file_adds:
308 308 file_adds: c
309 309 file_adds: b
310 310 file_adds: a
311 311 file_adds--verbose: third
312 312 file_adds--verbose: second
313 313 file_adds--verbose:
314 314 file_adds--verbose: d
315 315 file_adds--verbose:
316 316 file_adds--verbose:
317 317 file_adds--verbose: c
318 318 file_adds--verbose: b
319 319 file_adds--verbose: a
320 320 file_adds--debug: third
321 321 file_adds--debug: second
322 322 file_adds--debug:
323 323 file_adds--debug: d
324 324 file_adds--debug:
325 325 file_adds--debug:
326 326 file_adds--debug: c
327 327 file_adds--debug: b
328 328 file_adds--debug: a
329 329 file_dels:
330 330 file_dels:
331 331 file_dels:
332 332 file_dels:
333 333 file_dels:
334 334 file_dels:
335 335 file_dels:
336 336 file_dels:
337 337 file_dels:
338 338 file_dels--verbose:
339 339 file_dels--verbose:
340 340 file_dels--verbose:
341 341 file_dels--verbose:
342 342 file_dels--verbose:
343 343 file_dels--verbose:
344 344 file_dels--verbose:
345 345 file_dels--verbose:
346 346 file_dels--verbose:
347 347 file_dels--debug:
348 348 file_dels--debug:
349 349 file_dels--debug:
350 350 file_dels--debug:
351 351 file_dels--debug:
352 352 file_dels--debug:
353 353 file_dels--debug:
354 354 file_dels--debug:
355 355 file_dels--debug:
356 356 file_mods:
357 357 file_mods:
358 358 file_mods:
359 359 file_mods:
360 360 file_mods:
361 361 file_mods: c
362 362 file_mods:
363 363 file_mods:
364 364 file_mods:
365 365 file_mods--verbose:
366 366 file_mods--verbose:
367 367 file_mods--verbose:
368 368 file_mods--verbose:
369 369 file_mods--verbose:
370 370 file_mods--verbose: c
371 371 file_mods--verbose:
372 372 file_mods--verbose:
373 373 file_mods--verbose:
374 374 file_mods--debug:
375 375 file_mods--debug:
376 376 file_mods--debug:
377 377 file_mods--debug:
378 378 file_mods--debug:
379 379 file_mods--debug: c
380 380 file_mods--debug:
381 381 file_mods--debug:
382 382 file_mods--debug:
383 383 files: third
384 384 files: second
385 385 files:
386 386 files: d
387 387 files:
388 388 files: c
389 389 files: c
390 390 files: b
391 391 files: a
392 392 files--verbose: third
393 393 files--verbose: second
394 394 files--verbose:
395 395 files--verbose: d
396 396 files--verbose:
397 397 files--verbose: c
398 398 files--verbose: c
399 399 files--verbose: b
400 400 files--verbose: a
401 401 files--debug: third
402 402 files--debug: second
403 403 files--debug:
404 404 files--debug: d
405 405 files--debug:
406 406 files--debug: c
407 407 files--debug: c
408 408 files--debug: b
409 409 files--debug: a
410 410 manifest: 8:8a0d8faab8b2
411 411 manifest: 7:f2dbc354b94e
412 412 manifest: 6:91015e9dbdd7
413 413 manifest: 5:4dc3def4f9b4
414 414 manifest: 4:90ae8dda64e1
415 415 manifest: 3:cb5a1327723b
416 416 manifest: 2:6e0e82995c35
417 417 manifest: 1:4e8d705b1e53
418 418 manifest: 0:a0c8bcbbb45c
419 419 manifest--verbose: 8:8a0d8faab8b2
420 420 manifest--verbose: 7:f2dbc354b94e
421 421 manifest--verbose: 6:91015e9dbdd7
422 422 manifest--verbose: 5:4dc3def4f9b4
423 423 manifest--verbose: 4:90ae8dda64e1
424 424 manifest--verbose: 3:cb5a1327723b
425 425 manifest--verbose: 2:6e0e82995c35
426 426 manifest--verbose: 1:4e8d705b1e53
427 427 manifest--verbose: 0:a0c8bcbbb45c
428 428 manifest--debug: 8:8a0d8faab8b2eee97dcfccabbcb18f413c9d097b
429 429 manifest--debug: 7:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf
430 430 manifest--debug: 6:91015e9dbdd76a6791085d12b0a0ec7fcd22ffbf
431 431 manifest--debug: 5:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
432 432 manifest--debug: 4:90ae8dda64e1a876c792bccb9af66284f6018363
433 433 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
434 434 manifest--debug: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
435 435 manifest--debug: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
436 436 manifest--debug: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
437 437 node: 946e2bd9c565394777d74d9669045b39e856e3ea
438 438 node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
439 439 node: c7b487c6c50ef1cf464cafdc4f4f5e615fc5999f
440 440 node: 13207e5a10d9fd28ec424934298e176197f2c67f
441 441 node: 32a18f097fcccf76ef282f62f8a85b3adf8d13c4
442 442 node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
443 443 node: 97054abb4ab824450e9164180baf491ae0078465
444 444 node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
445 445 node: 1e4e1b8f71e05681d422154f5421e385fec3454f
446 446 node--verbose: 946e2bd9c565394777d74d9669045b39e856e3ea
447 447 node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
448 448 node--verbose: c7b487c6c50ef1cf464cafdc4f4f5e615fc5999f
449 449 node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
450 450 node--verbose: 32a18f097fcccf76ef282f62f8a85b3adf8d13c4
451 451 node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
452 452 node--verbose: 97054abb4ab824450e9164180baf491ae0078465
453 453 node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
454 454 node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
455 455 node--debug: 946e2bd9c565394777d74d9669045b39e856e3ea
456 456 node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
457 457 node--debug: c7b487c6c50ef1cf464cafdc4f4f5e615fc5999f
458 458 node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
459 459 node--debug: 32a18f097fcccf76ef282f62f8a85b3adf8d13c4
460 460 node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
461 461 node--debug: 97054abb4ab824450e9164180baf491ae0078465
462 462 node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
463 463 node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
464 464 parents:
465 465 parents: -1:000000000000
466 466 parents: 5:13207e5a10d9 4:32a18f097fcc
467 467 parents: 3:10e46f2dcbf4
468 468 parents:
469 469 parents:
470 470 parents:
471 471 parents:
472 472 parents:
473 473 parents--verbose:
474 474 parents--verbose: -1:000000000000
475 475 parents--verbose: 5:13207e5a10d9 4:32a18f097fcc
476 476 parents--verbose: 3:10e46f2dcbf4
477 477 parents--verbose:
478 478 parents--verbose:
479 479 parents--verbose:
480 480 parents--verbose:
481 481 parents--verbose:
482 482 parents--debug: 7:29114dbae42b9f078cf2714dbe3a86bba8ec7453 -1:0000000000000000000000000000000000000000
483 483 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
484 484 parents--debug: 5:13207e5a10d9fd28ec424934298e176197f2c67f 4:32a18f097fcccf76ef282f62f8a85b3adf8d13c4
485 485 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
486 486 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
487 487 parents--debug: 2:97054abb4ab824450e9164180baf491ae0078465 -1:0000000000000000000000000000000000000000
488 488 parents--debug: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 -1:0000000000000000000000000000000000000000
489 489 parents--debug: 0:1e4e1b8f71e05681d422154f5421e385fec3454f -1:0000000000000000000000000000000000000000
490 490 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
491 491 rev: 8
492 492 rev: 7
493 493 rev: 6
494 494 rev: 5
495 495 rev: 4
496 496 rev: 3
497 497 rev: 2
498 498 rev: 1
499 499 rev: 0
500 500 rev--verbose: 8
501 501 rev--verbose: 7
502 502 rev--verbose: 6
503 503 rev--verbose: 5
504 504 rev--verbose: 4
505 505 rev--verbose: 3
506 506 rev--verbose: 2
507 507 rev--verbose: 1
508 508 rev--verbose: 0
509 509 rev--debug: 8
510 510 rev--debug: 7
511 511 rev--debug: 6
512 512 rev--debug: 5
513 513 rev--debug: 4
514 514 rev--debug: 3
515 515 rev--debug: 2
516 516 rev--debug: 1
517 517 rev--debug: 0
518 518 tags: tip
519 519 tags:
520 520 tags:
521 521 tags:
522 522 tags:
523 523 tags:
524 524 tags:
525 525 tags:
526 526 tags:
527 527 tags--verbose: tip
528 528 tags--verbose:
529 529 tags--verbose:
530 530 tags--verbose:
531 531 tags--verbose:
532 532 tags--verbose:
533 533 tags--verbose:
534 534 tags--verbose:
535 535 tags--verbose:
536 536 tags--debug: tip
537 537 tags--debug:
538 538 tags--debug:
539 539 tags--debug:
540 540 tags--debug:
541 541 tags--debug:
542 542 tags--debug:
543 543 tags--debug:
544 544 tags--debug:
545 545 diffstat: 1: +1/-0
546 546 diffstat: 1: +1/-0
547 547 diffstat: 0: +0/-0
548 548 diffstat: 1: +1/-0
549 549 diffstat: 0: +0/-0
550 550 diffstat: 1: +1/-0
551 551 diffstat: 1: +4/-0
552 552 diffstat: 1: +2/-0
553 553 diffstat: 1: +1/-0
554 554 diffstat--verbose: 1: +1/-0
555 555 diffstat--verbose: 1: +1/-0
556 556 diffstat--verbose: 0: +0/-0
557 557 diffstat--verbose: 1: +1/-0
558 558 diffstat--verbose: 0: +0/-0
559 559 diffstat--verbose: 1: +1/-0
560 560 diffstat--verbose: 1: +4/-0
561 561 diffstat--verbose: 1: +2/-0
562 562 diffstat--verbose: 1: +1/-0
563 563 diffstat--debug: 1: +1/-0
564 564 diffstat--debug: 1: +1/-0
565 565 diffstat--debug: 0: +0/-0
566 566 diffstat--debug: 1: +1/-0
567 567 diffstat--debug: 0: +0/-0
568 568 diffstat--debug: 1: +1/-0
569 569 diffstat--debug: 1: +4/-0
570 570 diffstat--debug: 1: +2/-0
571 571 diffstat--debug: 1: +1/-0
572 extras: branch=default
573 extras: branch=default
574 extras: branch=default
575 extras: branch=default
576 extras: branch=foo
577 extras: branch=default
578 extras: branch=default
579 extras: branch=default
580 extras: branch=default
581 extras--verbose: branch=default
582 extras--verbose: branch=default
583 extras--verbose: branch=default
584 extras--verbose: branch=default
585 extras--verbose: branch=foo
586 extras--verbose: branch=default
587 extras--verbose: branch=default
588 extras--verbose: branch=default
589 extras--verbose: branch=default
590 extras--debug: branch=default
591 extras--debug: branch=default
592 extras--debug: branch=default
593 extras--debug: branch=default
594 extras--debug: branch=foo
595 extras--debug: branch=default
596 extras--debug: branch=default
597 extras--debug: branch=default
598 extras--debug: branch=default
572 599 # filters work
573 600
574 601 hostname
575 602
576 603
577 604
578 605
579 606 place
580 607 place
581 608 hostname
582 609 test
583 610 User Name
584 611 person
585 612 person
586 613 person
587 614 person
588 615 other
589 616 A. N. Other
590 617 User Name
591 618 test
592 619 user
593 620 person
594 621 person
595 622 person
596 623 person
597 624 other
598 625 other
599 626 user
600 627 in the future
601 628 Wed Jan 01 10:01:00 2020 +0000
602 629 Mon Jan 12 13:46:40 1970 +0000
603 630 Sun Jan 18 08:40:01 1970 +0000
604 631 Sun Jan 18 08:40:00 1970 +0000
605 632 Sat Jan 17 04:53:20 1970 +0000
606 633 Fri Jan 16 01:06:40 1970 +0000
607 634 Wed Jan 14 21:20:00 1970 +0000
608 635 Tue Jan 13 17:33:20 1970 +0000
609 636 Mon Jan 12 13:46:40 1970 +0000
610 637 2020-01-01 10:01 +0000
611 638 1970-01-12 13:46 +0000
612 639 1970-01-18 08:40 +0000
613 640 1970-01-18 08:40 +0000
614 641 1970-01-17 04:53 +0000
615 642 1970-01-16 01:06 +0000
616 643 1970-01-14 21:20 +0000
617 644 1970-01-13 17:33 +0000
618 645 1970-01-12 13:46 +0000
619 646 2020-01-01 10:01:00 +0000
620 647 1970-01-12 13:46:40 +0000
621 648 1970-01-18 08:40:01 +0000
622 649 1970-01-18 08:40:00 +0000
623 650 1970-01-17 04:53:20 +0000
624 651 1970-01-16 01:06:40 +0000
625 652 1970-01-14 21:20:00 +0000
626 653 1970-01-13 17:33:20 +0000
627 654 1970-01-12 13:46:40 +0000
628 655 Wed, 01 Jan 2020 10:01:00 +0000
629 656 Mon, 12 Jan 1970 13:46:40 +0000
630 657 Sun, 18 Jan 1970 08:40:01 +0000
631 658 Sun, 18 Jan 1970 08:40:00 +0000
632 659 Sat, 17 Jan 1970 04:53:20 +0000
633 660 Fri, 16 Jan 1970 01:06:40 +0000
634 661 Wed, 14 Jan 1970 21:20:00 +0000
635 662 Tue, 13 Jan 1970 17:33:20 +0000
636 663 Mon, 12 Jan 1970 13:46:40 +0000
637 664 third
638 665 second
639 666 merge
640 667 new head
641 668 new branch
642 669 no user, no domain
643 670 no person
644 671 other 1
645 672 line 1
646 673 946e2bd9c565
647 674 29114dbae42b
648 675 c7b487c6c50e
649 676 13207e5a10d9
650 677 32a18f097fcc
651 678 10e46f2dcbf4
652 679 97054abb4ab8
653 680 b608e9d1a3f0
654 681 1e4e1b8f71e0
655 682 <changeset author="test"/>
656 683 <changeset author="User Name &lt;user@hostname&gt;"/>
657 684 <changeset author="person"/>
658 685 <changeset author="person"/>
659 686 <changeset author="person"/>
660 687 <changeset author="person"/>
661 688 <changeset author="other@place"/>
662 689 <changeset author="A. N. Other &lt;other@place&gt;"/>
663 690 <changeset author="User Name &lt;user@hostname&gt;"/>
664 691 # formatnode filter works
665 692 # quiet
666 693 1e4e1b8f71e0
667 694 # normal
668 695 1e4e1b8f71e0
669 696 # verbose
670 697 1e4e1b8f71e0
671 698 # debug
672 699 1e4e1b8f71e05681d422154f5421e385fec3454f
673 700 # error on syntax
674 701 abort: t:3: unmatched quotes
675 702 # latesttag
676 703 adding file
677 704 adding head1
678 705 adding head2
679 706 created new head
680 707 # No tag set
681 708 5: null+5
682 709 4: null+4
683 710 3: null+3
684 711 2: null+3
685 712 1: null+2
686 713 0: null+1
687 714 # one common tag: longuest path wins
688 715 6: t1+4
689 716 5: t1+3
690 717 4: t1+2
691 718 3: t1+1
692 719 2: t1+1
693 720 1: t1+0
694 721 0: null+1
695 722 # one ancestor tag: more recent wins
696 723 7: t2+3
697 724 6: t2+2
698 725 5: t2+1
699 726 4: t1+2
700 727 3: t1+1
701 728 2: t2+0
702 729 1: t1+0
703 730 0: null+1
704 731 # two branch tags: more recent wins
705 732 8: t3+5
706 733 7: t3+4
707 734 6: t3+3
708 735 5: t3+2
709 736 4: t3+1
710 737 3: t3+0
711 738 2: t2+0
712 739 1: t1+0
713 740 0: null+1
714 741 # merged tag overrides
715 742 10: t5+5
716 743 9: t5+4
717 744 8: t5+3
718 745 7: t5+2
719 746 6: t5+1
720 747 5: t5+0
721 748 4: at3:t3+1
722 749 3: at3:t3+0
723 750 2: t2+0
724 751 1: t1+0
725 752 0: null+1
726 753 # done
General Comments 0
You need to be logged in to leave comments. Login now