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