##// END OF EJS Templates
command line templates: add formatnode filter...
Alexis S. L. Carvalho -
r4352:051fb8c2 default
parent child Browse files
Show More
@@ -1,776 +1,780 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 from node import *
10 10 from i18n import gettext as _
11 11 demandload(globals(), 'os sys')
12 12 demandload(globals(), 'mdiff util templater patch')
13 13
14 14 revrangesep = ':'
15 15
16 16 def revpair(repo, revs):
17 17 '''return pair of nodes, given list of revisions. second item can
18 18 be None, meaning use working dir.'''
19 19
20 20 def revfix(repo, val, defval):
21 21 if not val and val != 0 and defval is not None:
22 22 val = defval
23 23 return repo.lookup(val)
24 24
25 25 if not revs:
26 26 return repo.dirstate.parents()[0], None
27 27 end = None
28 28 if len(revs) == 1:
29 29 if revrangesep in revs[0]:
30 30 start, end = revs[0].split(revrangesep, 1)
31 31 start = revfix(repo, start, 0)
32 32 end = revfix(repo, end, repo.changelog.count() - 1)
33 33 else:
34 34 start = revfix(repo, revs[0], None)
35 35 elif len(revs) == 2:
36 36 if revrangesep in revs[0] or revrangesep in revs[1]:
37 37 raise util.Abort(_('too many revisions specified'))
38 38 start = revfix(repo, revs[0], None)
39 39 end = revfix(repo, revs[1], None)
40 40 else:
41 41 raise util.Abort(_('too many revisions specified'))
42 42 return start, end
43 43
44 44 def revrange(repo, revs):
45 45 """Yield revision as strings from a list of revision specifications."""
46 46
47 47 def revfix(repo, val, defval):
48 48 if not val and val != 0 and defval is not None:
49 49 return defval
50 50 return repo.changelog.rev(repo.lookup(val))
51 51
52 52 seen, l = {}, []
53 53 for spec in revs:
54 54 if revrangesep in spec:
55 55 start, end = spec.split(revrangesep, 1)
56 56 start = revfix(repo, start, 0)
57 57 end = revfix(repo, end, repo.changelog.count() - 1)
58 58 step = start > end and -1 or 1
59 59 for rev in xrange(start, end+step, step):
60 60 if rev in seen:
61 61 continue
62 62 seen[rev] = 1
63 63 l.append(rev)
64 64 else:
65 65 rev = revfix(repo, spec, None)
66 66 if rev in seen:
67 67 continue
68 68 seen[rev] = 1
69 69 l.append(rev)
70 70
71 71 return l
72 72
73 73 def make_filename(repo, pat, node,
74 74 total=None, seqno=None, revwidth=None, pathname=None):
75 75 node_expander = {
76 76 'H': lambda: hex(node),
77 77 'R': lambda: str(repo.changelog.rev(node)),
78 78 'h': lambda: short(node),
79 79 }
80 80 expander = {
81 81 '%': lambda: '%',
82 82 'b': lambda: os.path.basename(repo.root),
83 83 }
84 84
85 85 try:
86 86 if node:
87 87 expander.update(node_expander)
88 88 if node and revwidth is not None:
89 89 expander['r'] = (lambda:
90 90 str(repo.changelog.rev(node)).zfill(revwidth))
91 91 if total is not None:
92 92 expander['N'] = lambda: str(total)
93 93 if seqno is not None:
94 94 expander['n'] = lambda: str(seqno)
95 95 if total is not None and seqno is not None:
96 96 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
97 97 if pathname is not None:
98 98 expander['s'] = lambda: os.path.basename(pathname)
99 99 expander['d'] = lambda: os.path.dirname(pathname) or '.'
100 100 expander['p'] = lambda: pathname
101 101
102 102 newname = []
103 103 patlen = len(pat)
104 104 i = 0
105 105 while i < patlen:
106 106 c = pat[i]
107 107 if c == '%':
108 108 i += 1
109 109 c = pat[i]
110 110 c = expander[c]()
111 111 newname.append(c)
112 112 i += 1
113 113 return ''.join(newname)
114 114 except KeyError, inst:
115 115 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
116 116 inst.args[0])
117 117
118 118 def make_file(repo, pat, node=None,
119 119 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
120 120 if not pat or pat == '-':
121 121 return 'w' in mode and sys.stdout or sys.stdin
122 122 if hasattr(pat, 'write') and 'w' in mode:
123 123 return pat
124 124 if hasattr(pat, 'read') and 'r' in mode:
125 125 return pat
126 126 return open(make_filename(repo, pat, node, total, seqno, revwidth,
127 127 pathname),
128 128 mode)
129 129
130 130 def matchpats(repo, pats=[], opts={}, head='', globbed=False):
131 131 cwd = repo.getcwd()
132 132 if not pats and cwd:
133 133 opts['include'] = [os.path.join(cwd, i)
134 134 for i in opts.get('include', [])]
135 135 opts['exclude'] = [os.path.join(cwd, x)
136 136 for x in opts.get('exclude', [])]
137 137 cwd = ''
138 138 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
139 139 opts.get('exclude'), head, globbed=globbed)
140 140
141 141 def walk(repo, pats=[], opts={}, node=None, head='', badmatch=None,
142 142 globbed=False):
143 143 files, matchfn, anypats = matchpats(repo, pats, opts, head,
144 144 globbed=globbed)
145 145 exact = dict.fromkeys(files)
146 146 for src, fn in repo.walk(node=node, files=files, match=matchfn,
147 147 badmatch=badmatch):
148 148 yield src, fn, util.pathto(repo.root, repo.getcwd(), fn), fn in exact
149 149
150 150 def findrenames(repo, added=None, removed=None, threshold=0.5):
151 151 if added is None or removed is None:
152 152 added, removed = repo.status()[1:3]
153 153 changes = repo.changelog.read(repo.dirstate.parents()[0])
154 154 mf = repo.manifest.read(changes[0])
155 155 for a in added:
156 156 aa = repo.wread(a)
157 157 bestscore, bestname = None, None
158 158 for r in removed:
159 159 rr = repo.file(r).read(mf[r])
160 160 delta = mdiff.textdiff(aa, rr)
161 161 if len(delta) < len(aa):
162 162 myscore = 1.0 - (float(len(delta)) / len(aa))
163 163 if bestscore is None or myscore > bestscore:
164 164 bestscore, bestname = myscore, r
165 165 if bestname and bestscore >= threshold:
166 166 yield bestname, a, bestscore
167 167
168 168 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
169 169 similarity=None):
170 170 if dry_run is None:
171 171 dry_run = opts.get('dry_run')
172 172 if similarity is None:
173 173 similarity = float(opts.get('similarity') or 0)
174 174 add, remove = [], []
175 175 mapping = {}
176 176 for src, abs, rel, exact in walk(repo, pats, opts):
177 177 if src == 'f' and repo.dirstate.state(abs) == '?':
178 178 add.append(abs)
179 179 mapping[abs] = rel, exact
180 180 if repo.ui.verbose or not exact:
181 181 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
182 182 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
183 183 remove.append(abs)
184 184 mapping[abs] = rel, exact
185 185 if repo.ui.verbose or not exact:
186 186 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
187 187 if not dry_run:
188 188 repo.add(add, wlock=wlock)
189 189 repo.remove(remove, wlock=wlock)
190 190 if similarity > 0:
191 191 for old, new, score in findrenames(repo, add, remove, similarity):
192 192 oldrel, oldexact = mapping[old]
193 193 newrel, newexact = mapping[new]
194 194 if repo.ui.verbose or not oldexact or not newexact:
195 195 repo.ui.status(_('recording removal of %s as rename to %s '
196 196 '(%d%% similar)\n') %
197 197 (oldrel, newrel, score * 100))
198 198 if not dry_run:
199 199 repo.copy(old, new, wlock=wlock)
200 200
201 201 class changeset_printer(object):
202 202 '''show changeset information when templating not requested.'''
203 203
204 204 def __init__(self, ui, repo, patch, brinfo, buffered):
205 205 self.ui = ui
206 206 self.repo = repo
207 207 self.buffered = buffered
208 208 self.patch = patch
209 209 self.brinfo = brinfo
210 210 self.header = {}
211 211 self.hunk = {}
212 212 self.lastheader = None
213 213
214 214 def flush(self, rev):
215 215 if rev in self.header:
216 216 h = self.header[rev]
217 217 if h != self.lastheader:
218 218 self.lastheader = h
219 219 self.ui.write(h)
220 220 del self.header[rev]
221 221 if rev in self.hunk:
222 222 self.ui.write(self.hunk[rev])
223 223 del self.hunk[rev]
224 224 return 1
225 225 return 0
226 226
227 227 def show(self, rev=0, changenode=None, copies=(), **props):
228 228 if self.buffered:
229 229 self.ui.pushbuffer()
230 230 self._show(rev, changenode, copies, props)
231 231 self.hunk[rev] = self.ui.popbuffer()
232 232 else:
233 233 self._show(rev, changenode, copies, props)
234 234
235 235 def _show(self, rev, changenode, copies, props):
236 236 '''show a single changeset or file revision'''
237 237 log = self.repo.changelog
238 238 if changenode is None:
239 239 changenode = log.node(rev)
240 240 elif not rev:
241 241 rev = log.rev(changenode)
242 242
243 243 if self.ui.quiet:
244 244 self.ui.write("%d:%s\n" % (rev, short(changenode)))
245 245 return
246 246
247 247 changes = log.read(changenode)
248 248 date = util.datestr(changes[2])
249 249 extra = changes[5]
250 250 branch = extra.get("branch")
251 251
252 252 hexfunc = self.ui.debugflag and hex or short
253 253
254 254 parents = log.parentrevs(rev)
255 255 if not self.ui.debugflag:
256 256 if parents[1] == nullrev:
257 257 if parents[0] >= rev - 1:
258 258 parents = []
259 259 else:
260 260 parents = [parents[0]]
261 261 parents = [(p, hexfunc(log.node(p))) for p in parents]
262 262
263 263 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
264 264
265 265 # don't show the default branch name
266 266 if branch != 'default':
267 267 branch = util.tolocal(branch)
268 268 self.ui.write(_("branch: %s\n") % branch)
269 269 for tag in self.repo.nodetags(changenode):
270 270 self.ui.write(_("tag: %s\n") % tag)
271 271 for parent in parents:
272 272 self.ui.write(_("parent: %d:%s\n") % parent)
273 273
274 274 if self.brinfo:
275 275 br = self.repo.branchlookup([changenode])
276 276 if br:
277 277 self.ui.write(_("branch: %s\n") % " ".join(br[changenode]))
278 278
279 279 if self.ui.debugflag:
280 280 self.ui.write(_("manifest: %d:%s\n") %
281 281 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
282 282 self.ui.write(_("user: %s\n") % changes[1])
283 283 self.ui.write(_("date: %s\n") % date)
284 284
285 285 if self.ui.debugflag:
286 286 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
287 287 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
288 288 files):
289 289 if value:
290 290 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
291 291 elif changes[3] and self.ui.verbose:
292 292 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
293 293 if copies and self.ui.verbose:
294 294 copies = ['%s (%s)' % c for c in copies]
295 295 self.ui.write(_("copies: %s\n") % ' '.join(copies))
296 296
297 297 if extra and self.ui.debugflag:
298 298 extraitems = extra.items()
299 299 extraitems.sort()
300 300 for key, value in extraitems:
301 301 self.ui.write(_("extra: %s=%s\n")
302 302 % (key, value.encode('string_escape')))
303 303
304 304 description = changes[4].strip()
305 305 if description:
306 306 if self.ui.verbose:
307 307 self.ui.write(_("description:\n"))
308 308 self.ui.write(description)
309 309 self.ui.write("\n\n")
310 310 else:
311 311 self.ui.write(_("summary: %s\n") %
312 312 description.splitlines()[0])
313 313 self.ui.write("\n")
314 314
315 315 self.showpatch(changenode)
316 316
317 317 def showpatch(self, node):
318 318 if self.patch:
319 319 prev = self.repo.changelog.parents(node)[0]
320 320 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui)
321 321 self.ui.write("\n")
322 322
323 323 class changeset_templater(changeset_printer):
324 324 '''format changeset information.'''
325 325
326 326 def __init__(self, ui, repo, patch, brinfo, mapfile, buffered):
327 327 changeset_printer.__init__(self, ui, repo, patch, brinfo, buffered)
328 self.t = templater.templater(mapfile, templater.common_filters,
329 cache={'parent': '{rev}:{node|short} ',
330 'manifest': '{rev}:{node|short}',
331 'filecopy': '{name} ({source})'})
328 filters = templater.common_filters.copy()
329 filters['formatnode'] = (ui.debugflag and (lambda x: x)
330 or (lambda x: x[:12]))
331 self.t = templater.templater(mapfile, filters,
332 cache={
333 'parent': '{rev}:{node|formatnode} ',
334 'manifest': '{rev}:{node|formatnode}',
335 'filecopy': '{name} ({source})'})
332 336
333 337 def use_template(self, t):
334 338 '''set template string to use'''
335 339 self.t.cache['changeset'] = t
336 340
337 341 def _show(self, rev, changenode, copies, props):
338 342 '''show a single changeset or file revision'''
339 343 log = self.repo.changelog
340 344 if changenode is None:
341 345 changenode = log.node(rev)
342 346 elif not rev:
343 347 rev = log.rev(changenode)
344 348
345 349 changes = log.read(changenode)
346 350
347 351 def showlist(name, values, plural=None, **args):
348 352 '''expand set of values.
349 353 name is name of key in template map.
350 354 values is list of strings or dicts.
351 355 plural is plural of name, if not simply name + 's'.
352 356
353 357 expansion works like this, given name 'foo'.
354 358
355 359 if values is empty, expand 'no_foos'.
356 360
357 361 if 'foo' not in template map, return values as a string,
358 362 joined by space.
359 363
360 364 expand 'start_foos'.
361 365
362 366 for each value, expand 'foo'. if 'last_foo' in template
363 367 map, expand it instead of 'foo' for last key.
364 368
365 369 expand 'end_foos'.
366 370 '''
367 371 if plural: names = plural
368 372 else: names = name + 's'
369 373 if not values:
370 374 noname = 'no_' + names
371 375 if noname in self.t:
372 376 yield self.t(noname, **args)
373 377 return
374 378 if name not in self.t:
375 379 if isinstance(values[0], str):
376 380 yield ' '.join(values)
377 381 else:
378 382 for v in values:
379 383 yield dict(v, **args)
380 384 return
381 385 startname = 'start_' + names
382 386 if startname in self.t:
383 387 yield self.t(startname, **args)
384 388 vargs = args.copy()
385 389 def one(v, tag=name):
386 390 try:
387 391 vargs.update(v)
388 392 except (AttributeError, ValueError):
389 393 try:
390 394 for a, b in v:
391 395 vargs[a] = b
392 396 except ValueError:
393 397 vargs[name] = v
394 398 return self.t(tag, **vargs)
395 399 lastname = 'last_' + name
396 400 if lastname in self.t:
397 401 last = values.pop()
398 402 else:
399 403 last = None
400 404 for v in values:
401 405 yield one(v)
402 406 if last is not None:
403 407 yield one(last, tag=lastname)
404 408 endname = 'end_' + names
405 409 if endname in self.t:
406 410 yield self.t(endname, **args)
407 411
408 412 def showbranches(**args):
409 413 branch = changes[5].get("branch")
410 414 if branch != 'default':
411 415 branch = util.tolocal(branch)
412 416 return showlist('branch', [branch], plural='branches', **args)
413 417 # add old style branches if requested
414 418 if self.brinfo:
415 419 br = self.repo.branchlookup([changenode])
416 420 if changenode in br:
417 421 return showlist('branch', br[changenode],
418 422 plural='branches', **args)
419 423
420 424 def showparents(**args):
421 425 parents = [[('rev', log.rev(p)), ('node', hex(p))]
422 426 for p in log.parents(changenode)
423 427 if self.ui.debugflag or p != nullid]
424 428 if (not self.ui.debugflag and len(parents) == 1 and
425 429 parents[0][0][1] == rev - 1):
426 430 return
427 431 return showlist('parent', parents, **args)
428 432
429 433 def showtags(**args):
430 434 return showlist('tag', self.repo.nodetags(changenode), **args)
431 435
432 436 def showextras(**args):
433 437 extras = changes[5].items()
434 438 extras.sort()
435 439 for key, value in extras:
436 440 args = args.copy()
437 441 args.update(dict(key=key, value=value))
438 442 yield self.t('extra', **args)
439 443
440 444 def showcopies(**args):
441 445 c = [{'name': x[0], 'source': x[1]} for x in copies]
442 446 return showlist('file_copy', c, plural='file_copies', **args)
443 447
444 448 if self.ui.debugflag:
445 449 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
446 450 def showfiles(**args):
447 451 return showlist('file', files[0], **args)
448 452 def showadds(**args):
449 453 return showlist('file_add', files[1], **args)
450 454 def showdels(**args):
451 455 return showlist('file_del', files[2], **args)
452 456 def showmanifest(**args):
453 457 args = args.copy()
454 458 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
455 459 node=hex(changes[0])))
456 460 return self.t('manifest', **args)
457 461 else:
458 462 def showfiles(**args):
459 463 return showlist('file', changes[3], **args)
460 464 showadds = ''
461 465 showdels = ''
462 466 showmanifest = ''
463 467
464 468 defprops = {
465 469 'author': changes[1],
466 470 'branches': showbranches,
467 471 'date': changes[2],
468 472 'desc': changes[4],
469 473 'file_adds': showadds,
470 474 'file_dels': showdels,
471 475 'files': showfiles,
472 476 'file_copies': showcopies,
473 477 'manifest': showmanifest,
474 478 'node': hex(changenode),
475 479 'parents': showparents,
476 480 'rev': rev,
477 481 'tags': showtags,
478 482 'extras': showextras,
479 483 }
480 484 props = props.copy()
481 485 props.update(defprops)
482 486
483 487 try:
484 488 if self.ui.debugflag and 'header_debug' in self.t:
485 489 key = 'header_debug'
486 490 elif self.ui.quiet and 'header_quiet' in self.t:
487 491 key = 'header_quiet'
488 492 elif self.ui.verbose and 'header_verbose' in self.t:
489 493 key = 'header_verbose'
490 494 elif 'header' in self.t:
491 495 key = 'header'
492 496 else:
493 497 key = ''
494 498 if key:
495 499 h = templater.stringify(self.t(key, **props))
496 500 if self.buffered:
497 501 self.header[rev] = h
498 502 else:
499 503 self.ui.write(h)
500 504 if self.ui.debugflag and 'changeset_debug' in self.t:
501 505 key = 'changeset_debug'
502 506 elif self.ui.quiet and 'changeset_quiet' in self.t:
503 507 key = 'changeset_quiet'
504 508 elif self.ui.verbose and 'changeset_verbose' in self.t:
505 509 key = 'changeset_verbose'
506 510 else:
507 511 key = 'changeset'
508 512 self.ui.write(templater.stringify(self.t(key, **props)))
509 513 self.showpatch(changenode)
510 514 except KeyError, inst:
511 515 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
512 516 inst.args[0]))
513 517 except SyntaxError, inst:
514 518 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
515 519
516 520 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
517 521 """show one changeset using template or regular display.
518 522
519 523 Display format will be the first non-empty hit of:
520 524 1. option 'template'
521 525 2. option 'style'
522 526 3. [ui] setting 'logtemplate'
523 527 4. [ui] setting 'style'
524 528 If all of these values are either the unset or the empty string,
525 529 regular display via changeset_printer() is done.
526 530 """
527 531 # options
528 532 patch = False
529 533 if opts.get('patch'):
530 534 patch = matchfn or util.always
531 535
532 536 br = None
533 537 if opts.get('branches'):
534 538 ui.warn(_("the --branches option is deprecated, "
535 539 "please use 'hg branches' instead\n"))
536 540 br = True
537 541 tmpl = opts.get('template')
538 542 mapfile = None
539 543 if tmpl:
540 544 tmpl = templater.parsestring(tmpl, quoted=False)
541 545 else:
542 546 mapfile = opts.get('style')
543 547 # ui settings
544 548 if not mapfile:
545 549 tmpl = ui.config('ui', 'logtemplate')
546 550 if tmpl:
547 551 tmpl = templater.parsestring(tmpl)
548 552 else:
549 553 mapfile = ui.config('ui', 'style')
550 554
551 555 if tmpl or mapfile:
552 556 if mapfile:
553 557 if not os.path.split(mapfile)[0]:
554 558 mapname = (templater.templatepath('map-cmdline.' + mapfile)
555 559 or templater.templatepath(mapfile))
556 560 if mapname: mapfile = mapname
557 561 try:
558 562 t = changeset_templater(ui, repo, patch, br, mapfile, buffered)
559 563 except SyntaxError, inst:
560 564 raise util.Abort(inst.args[0])
561 565 if tmpl: t.use_template(tmpl)
562 566 return t
563 567 return changeset_printer(ui, repo, patch, br, buffered)
564 568
565 569 def finddate(ui, repo, date):
566 570 """Find the tipmost changeset that matches the given date spec"""
567 571 df = util.matchdate(date + " to " + date)
568 572 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
569 573 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
570 574 results = {}
571 575 for st, rev, fns in changeiter:
572 576 if st == 'add':
573 577 d = get(rev)[2]
574 578 if df(d[0]):
575 579 results[rev] = d
576 580 elif st == 'iter':
577 581 if rev in results:
578 582 ui.status("Found revision %s from %s\n" %
579 583 (rev, util.datestr(results[rev])))
580 584 return str(rev)
581 585
582 586 raise util.Abort(_("revision matching date not found"))
583 587
584 588 def walkchangerevs(ui, repo, pats, change, opts):
585 589 '''Iterate over files and the revs they changed in.
586 590
587 591 Callers most commonly need to iterate backwards over the history
588 592 it is interested in. Doing so has awful (quadratic-looking)
589 593 performance, so we use iterators in a "windowed" way.
590 594
591 595 We walk a window of revisions in the desired order. Within the
592 596 window, we first walk forwards to gather data, then in the desired
593 597 order (usually backwards) to display it.
594 598
595 599 This function returns an (iterator, matchfn) tuple. The iterator
596 600 yields 3-tuples. They will be of one of the following forms:
597 601
598 602 "window", incrementing, lastrev: stepping through a window,
599 603 positive if walking forwards through revs, last rev in the
600 604 sequence iterated over - use to reset state for the current window
601 605
602 606 "add", rev, fns: out-of-order traversal of the given file names
603 607 fns, which changed during revision rev - use to gather data for
604 608 possible display
605 609
606 610 "iter", rev, None: in-order traversal of the revs earlier iterated
607 611 over with "add" - use to display data'''
608 612
609 613 def increasing_windows(start, end, windowsize=8, sizelimit=512):
610 614 if start < end:
611 615 while start < end:
612 616 yield start, min(windowsize, end-start)
613 617 start += windowsize
614 618 if windowsize < sizelimit:
615 619 windowsize *= 2
616 620 else:
617 621 while start > end:
618 622 yield start, min(windowsize, start-end-1)
619 623 start -= windowsize
620 624 if windowsize < sizelimit:
621 625 windowsize *= 2
622 626
623 627 files, matchfn, anypats = matchpats(repo, pats, opts)
624 628 follow = opts.get('follow') or opts.get('follow_first')
625 629
626 630 if repo.changelog.count() == 0:
627 631 return [], matchfn
628 632
629 633 if follow:
630 634 defrange = '%s:0' % repo.changectx().rev()
631 635 else:
632 636 defrange = 'tip:0'
633 637 revs = revrange(repo, opts['rev'] or [defrange])
634 638 wanted = {}
635 639 slowpath = anypats or opts.get('removed')
636 640 fncache = {}
637 641
638 642 if not slowpath and not files:
639 643 # No files, no patterns. Display all revs.
640 644 wanted = dict.fromkeys(revs)
641 645 copies = []
642 646 if not slowpath:
643 647 # Only files, no patterns. Check the history of each file.
644 648 def filerevgen(filelog, node):
645 649 cl_count = repo.changelog.count()
646 650 if node is None:
647 651 last = filelog.count() - 1
648 652 else:
649 653 last = filelog.rev(node)
650 654 for i, window in increasing_windows(last, nullrev):
651 655 revs = []
652 656 for j in xrange(i - window, i + 1):
653 657 n = filelog.node(j)
654 658 revs.append((filelog.linkrev(n),
655 659 follow and filelog.renamed(n)))
656 660 revs.reverse()
657 661 for rev in revs:
658 662 # only yield rev for which we have the changelog, it can
659 663 # happen while doing "hg log" during a pull or commit
660 664 if rev[0] < cl_count:
661 665 yield rev
662 666 def iterfiles():
663 667 for filename in files:
664 668 yield filename, None
665 669 for filename_node in copies:
666 670 yield filename_node
667 671 minrev, maxrev = min(revs), max(revs)
668 672 for file_, node in iterfiles():
669 673 filelog = repo.file(file_)
670 674 # A zero count may be a directory or deleted file, so
671 675 # try to find matching entries on the slow path.
672 676 if filelog.count() == 0:
673 677 slowpath = True
674 678 break
675 679 for rev, copied in filerevgen(filelog, node):
676 680 if rev <= maxrev:
677 681 if rev < minrev:
678 682 break
679 683 fncache.setdefault(rev, [])
680 684 fncache[rev].append(file_)
681 685 wanted[rev] = 1
682 686 if follow and copied:
683 687 copies.append(copied)
684 688 if slowpath:
685 689 if follow:
686 690 raise util.Abort(_('can only follow copies/renames for explicit '
687 691 'file names'))
688 692
689 693 # The slow path checks files modified in every changeset.
690 694 def changerevgen():
691 695 for i, window in increasing_windows(repo.changelog.count()-1,
692 696 nullrev):
693 697 for j in xrange(i - window, i + 1):
694 698 yield j, change(j)[3]
695 699
696 700 for rev, changefiles in changerevgen():
697 701 matches = filter(matchfn, changefiles)
698 702 if matches:
699 703 fncache[rev] = matches
700 704 wanted[rev] = 1
701 705
702 706 class followfilter:
703 707 def __init__(self, onlyfirst=False):
704 708 self.startrev = nullrev
705 709 self.roots = []
706 710 self.onlyfirst = onlyfirst
707 711
708 712 def match(self, rev):
709 713 def realparents(rev):
710 714 if self.onlyfirst:
711 715 return repo.changelog.parentrevs(rev)[0:1]
712 716 else:
713 717 return filter(lambda x: x != nullrev,
714 718 repo.changelog.parentrevs(rev))
715 719
716 720 if self.startrev == nullrev:
717 721 self.startrev = rev
718 722 return True
719 723
720 724 if rev > self.startrev:
721 725 # forward: all descendants
722 726 if not self.roots:
723 727 self.roots.append(self.startrev)
724 728 for parent in realparents(rev):
725 729 if parent in self.roots:
726 730 self.roots.append(rev)
727 731 return True
728 732 else:
729 733 # backwards: all parents
730 734 if not self.roots:
731 735 self.roots.extend(realparents(self.startrev))
732 736 if rev in self.roots:
733 737 self.roots.remove(rev)
734 738 self.roots.extend(realparents(rev))
735 739 return True
736 740
737 741 return False
738 742
739 743 # it might be worthwhile to do this in the iterator if the rev range
740 744 # is descending and the prune args are all within that range
741 745 for rev in opts.get('prune', ()):
742 746 rev = repo.changelog.rev(repo.lookup(rev))
743 747 ff = followfilter()
744 748 stop = min(revs[0], revs[-1])
745 749 for x in xrange(rev, stop-1, -1):
746 750 if ff.match(x) and x in wanted:
747 751 del wanted[x]
748 752
749 753 def iterate():
750 754 if follow and not files:
751 755 ff = followfilter(onlyfirst=opts.get('follow_first'))
752 756 def want(rev):
753 757 if ff.match(rev) and rev in wanted:
754 758 return True
755 759 return False
756 760 else:
757 761 def want(rev):
758 762 return rev in wanted
759 763
760 764 for i, window in increasing_windows(0, len(revs)):
761 765 yield 'window', revs[0] < revs[-1], revs[-1]
762 766 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
763 767 srevs = list(nrevs)
764 768 srevs.sort()
765 769 for rev in srevs:
766 770 fns = fncache.get(rev)
767 771 if not fns:
768 772 def fns_generator():
769 773 for f in change(rev)[3]:
770 774 if matchfn(f):
771 775 yield f
772 776 fns = fns_generator()
773 777 yield 'add', rev, fns
774 778 for rev in nrevs:
775 779 yield 'iter', rev, None
776 780 return iterate(), matchfn
@@ -1,22 +1,21 b''
1 changeset = 'changeset: {rev}:{node|short}\n{branches}{tags}{short_parents}user: {author}\ndate: {date|date}\nsummary: {desc|firstline}\n\n'
1 changeset = 'changeset: {rev}:{node|short}\n{branches}{tags}{parents}user: {author}\ndate: {date|date}\nsummary: {desc|firstline}\n\n'
2 2 changeset_quiet = '{rev}:{node|short}\n'
3 3 changeset_verbose = 'changeset: {rev}:{node|short}\n{branches}{tags}{parents}{manifest}user: {author}\ndate: {date|date}\n{files}{file_adds}{file_dels}{file_copies}description:\n{desc|strip}\n\n\n'
4 4 changeset_debug = 'changeset: {rev}:{node}\n{branches}{tags}{parents}{manifest}user: {author}\ndate: {date|date}\n{files}{file_adds}{file_dels}{file_copies}{extras}description:\n{desc|strip}\n\n\n'
5 5 start_files = 'files: '
6 6 file = ' {file}'
7 7 end_files = '\n'
8 8 start_file_adds = 'files+: '
9 9 file_add = ' {file_add}'
10 10 end_file_adds = '\n'
11 11 start_file_dels = 'files-: '
12 12 file_del = ' {file_del}'
13 13 end_file_dels = '\n'
14 14 start_file_copies = 'copies: '
15 15 file_copy = ' {name} ({source})'
16 16 end_file_copies = '\n'
17 short_parent = 'parent: {rev}:{node|short}\n'
18 parent = 'parent: {rev}:{node}\n'
17 parent = 'parent: {rev}:{node|formatnode}\n'
19 18 manifest = 'manifest: {rev}:{node}\n'
20 19 branch = 'branch: {branch}\n'
21 20 tag = 'tag: {tag}\n'
22 21 extra = 'extra: {key}={value|stringescape}\n'
@@ -1,103 +1,113 b''
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
23 23 # make sure user/global hgrc does not affect tests
24 24 echo '[ui]' > .hg/hgrc
25 25 echo 'logtemplate =' >> .hg/hgrc
26 26 echo 'style =' >> .hg/hgrc
27 27
28 28 echo '# default style is like normal output'
29 29 echo '# normal'
30 30 hg log > log.out
31 31 hg log --style default > style.out
32 32 diff log.out style.out
33 33 echo '# verbose'
34 34 hg log -v > log.out
35 35 hg log -v --style default > style.out
36 36 diff log.out style.out
37 37 echo '# debug'
38 38 hg log --debug > log.out
39 39 hg log --debug --style default > style.out
40 40 diff log.out style.out
41 41
42 42 echo '# revision with no copies (used to print a traceback)'
43 43 hg tip -v --template '\n'
44 44
45 45 echo '# compact style works'
46 46 hg log --style compact
47 47 hg log -v --style compact
48 48 hg log --debug --style compact
49 49
50 50 echo '# error if style not readable'
51 51 touch q
52 52 chmod 0 q
53 53 hg log --style ./q
54 54
55 55 echo '# error if no style'
56 56 hg log --style notexist
57 57
58 58 echo '# error if style missing key'
59 59 echo 'q = q' > t
60 60 hg log --style ./t
61 61
62 62 echo '# error if include fails'
63 63 echo 'changeset = q' >> t
64 64 hg log --style ./t
65 65
66 66 echo '# include works'
67 67 rm q
68 68 echo '{rev}' > q
69 69 hg log --style ./t
70 70
71 71 echo '# ui.style works'
72 72 echo '[ui]' > .hg/hgrc
73 73 echo 'style = t' >> .hg/hgrc
74 74 hg log
75 75
76 76 echo '# issue338'
77 77 hg log --style=changelog > changelog
78 78 cat changelog
79 79
80 80 echo "# keys work"
81 81 for key in author branches date desc file_adds file_dels files \
82 82 manifest node parents rev tags; do
83 83 for mode in '' --verbose --debug; do
84 84 hg log $mode --template "$key$mode: {$key}\n"
85 85 done
86 86 done
87 87
88 88 echo '# filters work'
89 89 hg log --template '{author|domain}\n'
90 90 hg log --template '{author|person}\n'
91 91 hg log --template '{author|user}\n'
92 92 hg log --template '{date|age}\n' > /dev/null || exit 1
93 93 hg log --template '{date|date}\n'
94 94 hg log --template '{date|isodate}\n'
95 95 hg log --template '{date|rfc822date}\n'
96 96 hg log --template '{desc|firstline}\n'
97 97 hg log --template '{node|short}\n'
98 98
99 echo '# formatnode filter works'
100 echo '# quiet'
101 hg -q log -r 0 --template '#node|formatnode#\n'
102 echo '# normal'
103 hg log -r 0 --template '#node|formatnode#\n'
104 echo '# verbose'
105 hg -v log -r 0 --template '#node|formatnode#\n'
106 echo '# debug'
107 hg --debug log -r 0 --template '#node|formatnode#\n'
108
99 109 echo '# error on syntax'
100 110 echo 'x = "f' >> t
101 111 hg log
102 112
103 113 echo '# done'
@@ -1,341 +1,350 b''
1 1 # default style is like normal output
2 2 # normal
3 3 # verbose
4 4 # debug
5 5 # revision with no copies (used to print a traceback)
6 6
7 7 # compact style works
8 8 4[tip] 32a18f097fcc 1970-01-17 04:53 +0000 person
9 9 new branch
10 10
11 11 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
12 12 no user, no domain
13 13
14 14 2 97054abb4ab8 1970-01-14 21:20 +0000 other
15 15 no person
16 16
17 17 1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
18 18 other 1
19 19
20 20 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
21 21 line 1
22 22
23 23 4[tip] 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 4[tip]:3,-1 32a18f097fcc 1970-01-17 04:53 +0000 person
39 39 new branch
40 40
41 41 3:2,-1 10e46f2dcbf4 1970-01-16 01:06 +0000 person
42 42 no user, no domain
43 43
44 44 2:1,-1 97054abb4ab8 1970-01-14 21:20 +0000 other
45 45 no person
46 46
47 47 1:0,-1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
48 48 other 1
49 49
50 50 0:-1,-1 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
51 51 line 1
52 52
53 53 # error if style not readable
54 54 abort: Permission denied: ./q
55 55 # error if no style
56 56 abort: No such file or directory: notexist
57 57 # error if style missing key
58 58 abort: ./t: no key named 'changeset'
59 59 # error if include fails
60 60 abort: template file ./q: Permission denied
61 61 # include works
62 62 4
63 63 3
64 64 2
65 65 1
66 66 0
67 67 # ui.style works
68 68 4
69 69 3
70 70 2
71 71 1
72 72 0
73 73 # issue338
74 74 1970-01-17 person <person>
75 75
76 76 * new branch
77 77 [32a18f097fcc] [tip]
78 78
79 79 1970-01-16 person <person>
80 80
81 81 * c:
82 82 no user, no domain
83 83 [10e46f2dcbf4]
84 84
85 85 1970-01-14 other <other@place>
86 86
87 87 * c:
88 88 no person
89 89 [97054abb4ab8]
90 90
91 91 1970-01-13 A. N. Other <other@place>
92 92
93 93 * b:
94 94 other 1 other 2
95 95
96 96 other 3
97 97 [b608e9d1a3f0]
98 98
99 99 1970-01-12 User Name <user@hostname>
100 100
101 101 * a:
102 102 line 1 line 2
103 103 [1e4e1b8f71e0]
104 104
105 105 # keys work
106 106 author: person
107 107 author: person
108 108 author: other@place
109 109 author: A. N. Other <other@place>
110 110 author: User Name <user@hostname>
111 111 author--verbose: person
112 112 author--verbose: person
113 113 author--verbose: other@place
114 114 author--verbose: A. N. Other <other@place>
115 115 author--verbose: User Name <user@hostname>
116 116 author--debug: person
117 117 author--debug: person
118 118 author--debug: other@place
119 119 author--debug: A. N. Other <other@place>
120 120 author--debug: User Name <user@hostname>
121 121 branches: foo
122 122 branches:
123 123 branches:
124 124 branches:
125 125 branches:
126 126 branches--verbose: foo
127 127 branches--verbose:
128 128 branches--verbose:
129 129 branches--verbose:
130 130 branches--verbose:
131 131 branches--debug: foo
132 132 branches--debug:
133 133 branches--debug:
134 134 branches--debug:
135 135 branches--debug:
136 136 date: 1400000.00
137 137 date: 1300000.00
138 138 date: 1200000.00
139 139 date: 1100000.00
140 140 date: 1000000.00
141 141 date--verbose: 1400000.00
142 142 date--verbose: 1300000.00
143 143 date--verbose: 1200000.00
144 144 date--verbose: 1100000.00
145 145 date--verbose: 1000000.00
146 146 date--debug: 1400000.00
147 147 date--debug: 1300000.00
148 148 date--debug: 1200000.00
149 149 date--debug: 1100000.00
150 150 date--debug: 1000000.00
151 151 desc: new branch
152 152 desc: no user, no domain
153 153 desc: no person
154 154 desc: other 1
155 155 other 2
156 156
157 157 other 3
158 158 desc: line 1
159 159 line 2
160 160 desc--verbose: new branch
161 161 desc--verbose: no user, no domain
162 162 desc--verbose: no person
163 163 desc--verbose: other 1
164 164 other 2
165 165
166 166 other 3
167 167 desc--verbose: line 1
168 168 line 2
169 169 desc--debug: new branch
170 170 desc--debug: no user, no domain
171 171 desc--debug: no person
172 172 desc--debug: other 1
173 173 other 2
174 174
175 175 other 3
176 176 desc--debug: line 1
177 177 line 2
178 178 file_adds:
179 179 file_adds:
180 180 file_adds:
181 181 file_adds:
182 182 file_adds:
183 183 file_adds--verbose:
184 184 file_adds--verbose:
185 185 file_adds--verbose:
186 186 file_adds--verbose:
187 187 file_adds--verbose:
188 188 file_adds--debug:
189 189 file_adds--debug:
190 190 file_adds--debug: c
191 191 file_adds--debug: b
192 192 file_adds--debug: a
193 193 file_dels:
194 194 file_dels:
195 195 file_dels:
196 196 file_dels:
197 197 file_dels:
198 198 file_dels--verbose:
199 199 file_dels--verbose:
200 200 file_dels--verbose:
201 201 file_dels--verbose:
202 202 file_dels--verbose:
203 203 file_dels--debug:
204 204 file_dels--debug:
205 205 file_dels--debug:
206 206 file_dels--debug:
207 207 file_dels--debug:
208 208 files:
209 209 files: c
210 210 files: c
211 211 files: b
212 212 files: a
213 213 files--verbose:
214 214 files--verbose: c
215 215 files--verbose: c
216 216 files--verbose: b
217 217 files--verbose: a
218 218 files--debug:
219 219 files--debug: c
220 220 files--debug:
221 221 files--debug:
222 222 files--debug:
223 223 manifest:
224 224 manifest:
225 225 manifest:
226 226 manifest:
227 227 manifest:
228 228 manifest--verbose:
229 229 manifest--verbose:
230 230 manifest--verbose:
231 231 manifest--verbose:
232 232 manifest--verbose:
233 manifest--debug: 4:90ae8dda64e1
234 manifest--debug: 3:cb5a1327723b
235 manifest--debug: 2:6e0e82995c35
236 manifest--debug: 1:4e8d705b1e53
237 manifest--debug: 0:a0c8bcbbb45c
233 manifest--debug: 4:90ae8dda64e1a876c792bccb9af66284f6018363
234 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
235 manifest--debug: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
236 manifest--debug: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
237 manifest--debug: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
238 238 node: 32a18f097fcccf76ef282f62f8a85b3adf8d13c4
239 239 node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
240 240 node: 97054abb4ab824450e9164180baf491ae0078465
241 241 node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
242 242 node: 1e4e1b8f71e05681d422154f5421e385fec3454f
243 243 node--verbose: 32a18f097fcccf76ef282f62f8a85b3adf8d13c4
244 244 node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
245 245 node--verbose: 97054abb4ab824450e9164180baf491ae0078465
246 246 node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
247 247 node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
248 248 node--debug: 32a18f097fcccf76ef282f62f8a85b3adf8d13c4
249 249 node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
250 250 node--debug: 97054abb4ab824450e9164180baf491ae0078465
251 251 node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
252 252 node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
253 253 parents:
254 254 parents:
255 255 parents:
256 256 parents:
257 257 parents:
258 258 parents--verbose:
259 259 parents--verbose:
260 260 parents--verbose:
261 261 parents--verbose:
262 262 parents--verbose:
263 parents--debug: 3:10e46f2dcbf4 -1:000000000000
264 parents--debug: 2:97054abb4ab8 -1:000000000000
265 parents--debug: 1:b608e9d1a3f0 -1:000000000000
266 parents--debug: 0:1e4e1b8f71e0 -1:000000000000
267 parents--debug: -1:000000000000 -1:000000000000
263 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
264 parents--debug: 2:97054abb4ab824450e9164180baf491ae0078465 -1:0000000000000000000000000000000000000000
265 parents--debug: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 -1:0000000000000000000000000000000000000000
266 parents--debug: 0:1e4e1b8f71e05681d422154f5421e385fec3454f -1:0000000000000000000000000000000000000000
267 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
268 268 rev: 4
269 269 rev: 3
270 270 rev: 2
271 271 rev: 1
272 272 rev: 0
273 273 rev--verbose: 4
274 274 rev--verbose: 3
275 275 rev--verbose: 2
276 276 rev--verbose: 1
277 277 rev--verbose: 0
278 278 rev--debug: 4
279 279 rev--debug: 3
280 280 rev--debug: 2
281 281 rev--debug: 1
282 282 rev--debug: 0
283 283 tags: tip
284 284 tags:
285 285 tags:
286 286 tags:
287 287 tags:
288 288 tags--verbose: tip
289 289 tags--verbose:
290 290 tags--verbose:
291 291 tags--verbose:
292 292 tags--verbose:
293 293 tags--debug: tip
294 294 tags--debug:
295 295 tags--debug:
296 296 tags--debug:
297 297 tags--debug:
298 298 # filters work
299 299
300 300
301 301 place
302 302 place
303 303 hostname
304 304 person
305 305 person
306 306 other
307 307 A. N. Other
308 308 User Name
309 309 person
310 310 person
311 311 other
312 312 other
313 313 user
314 314 Sat Jan 17 04:53:20 1970 +0000
315 315 Fri Jan 16 01:06:40 1970 +0000
316 316 Wed Jan 14 21:20:00 1970 +0000
317 317 Tue Jan 13 17:33:20 1970 +0000
318 318 Mon Jan 12 13:46:40 1970 +0000
319 319 1970-01-17 04:53 +0000
320 320 1970-01-16 01:06 +0000
321 321 1970-01-14 21:20 +0000
322 322 1970-01-13 17:33 +0000
323 323 1970-01-12 13:46 +0000
324 324 Sat, 17 Jan 1970 04:53:20 +0000
325 325 Fri, 16 Jan 1970 01:06:40 +0000
326 326 Wed, 14 Jan 1970 21:20:00 +0000
327 327 Tue, 13 Jan 1970 17:33:20 +0000
328 328 Mon, 12 Jan 1970 13:46:40 +0000
329 329 new branch
330 330 no user, no domain
331 331 no person
332 332 other 1
333 333 line 1
334 334 32a18f097fcc
335 335 10e46f2dcbf4
336 336 97054abb4ab8
337 337 b608e9d1a3f0
338 338 1e4e1b8f71e0
339 # formatnode filter works
340 # quiet
341 1e4e1b8f71e0
342 # normal
343 1e4e1b8f71e0
344 # verbose
345 1e4e1b8f71e0
346 # debug
347 1e4e1b8f71e05681d422154f5421e385fec3454f
339 348 # error on syntax
340 349 abort: t:3: unmatched quotes
341 350 # done
General Comments 0
You need to be logged in to leave comments. Login now