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