##// END OF EJS Templates
Use util.always instead of creating a new lambda function in show_changeset...
Thomas Arendsen Hein -
r3838:dec4eba7 default
parent child Browse files
Show More
@@ -1,775 +1,773 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=''):
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)
140 140
141 141 def walk(repo, pats=[], opts={}, node=None, head='', badmatch=None):
142 142 files, matchfn, anypats = matchpats(repo, pats, opts, head)
143 143 exact = dict.fromkeys(files)
144 144 for src, fn in repo.walk(node=node, files=files, match=matchfn,
145 145 badmatch=badmatch):
146 146 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
147 147
148 148 def findrenames(repo, added=None, removed=None, threshold=0.5):
149 149 if added is None or removed is None:
150 150 added, removed = repo.status()[1:3]
151 151 changes = repo.changelog.read(repo.dirstate.parents()[0])
152 152 mf = repo.manifest.read(changes[0])
153 153 for a in added:
154 154 aa = repo.wread(a)
155 155 bestscore, bestname = None, None
156 156 for r in removed:
157 157 rr = repo.file(r).read(mf[r])
158 158 delta = mdiff.textdiff(aa, rr)
159 159 if len(delta) < len(aa):
160 160 myscore = 1.0 - (float(len(delta)) / len(aa))
161 161 if bestscore is None or myscore > bestscore:
162 162 bestscore, bestname = myscore, r
163 163 if bestname and bestscore >= threshold:
164 164 yield bestname, a, bestscore
165 165
166 166 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
167 167 similarity=None):
168 168 if dry_run is None:
169 169 dry_run = opts.get('dry_run')
170 170 if similarity is None:
171 171 similarity = float(opts.get('similarity') or 0)
172 172 add, remove = [], []
173 173 mapping = {}
174 174 for src, abs, rel, exact in walk(repo, pats, opts):
175 175 if src == 'f' and repo.dirstate.state(abs) == '?':
176 176 add.append(abs)
177 177 mapping[abs] = rel, exact
178 178 if repo.ui.verbose or not exact:
179 179 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
180 180 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
181 181 remove.append(abs)
182 182 mapping[abs] = rel, exact
183 183 if repo.ui.verbose or not exact:
184 184 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
185 185 if not dry_run:
186 186 repo.add(add, wlock=wlock)
187 187 repo.remove(remove, wlock=wlock)
188 188 if similarity > 0:
189 189 for old, new, score in findrenames(repo, add, remove, similarity):
190 190 oldrel, oldexact = mapping[old]
191 191 newrel, newexact = mapping[new]
192 192 if repo.ui.verbose or not oldexact or not newexact:
193 193 repo.ui.status(_('recording removal of %s as rename to %s '
194 194 '(%d%% similar)\n') %
195 195 (oldrel, newrel, score * 100))
196 196 if not dry_run:
197 197 repo.copy(old, new, wlock=wlock)
198 198
199 199 class changeset_printer(object):
200 200 '''show changeset information when templating not requested.'''
201 201
202 202 def __init__(self, ui, repo, patch, brinfo, buffered):
203 203 self.ui = ui
204 204 self.repo = repo
205 205 self.buffered = buffered
206 206 self.patch = patch
207 207 self.brinfo = brinfo
208 208 self.header = {}
209 209 self.hunk = {}
210 210 self.lastheader = None
211 211
212 212 def flush(self, rev):
213 213 if rev in self.header:
214 214 h = self.header[rev]
215 215 if h != self.lastheader:
216 216 self.lastheader = h
217 217 self.ui.write(h)
218 218 del self.header[rev]
219 219 if rev in self.hunk:
220 220 self.ui.write(self.hunk[rev])
221 221 del self.hunk[rev]
222 222 return 1
223 223 return 0
224 224
225 225 def show(self, rev=0, changenode=None, copies=None, **props):
226 226 if self.buffered:
227 227 self.ui.pushbuffer()
228 228 self._show(rev, changenode, copies, props)
229 229 self.hunk[rev] = self.ui.popbuffer()
230 230 else:
231 231 self._show(rev, changenode, copies, props)
232 232
233 233 def _show(self, rev, changenode, copies, props):
234 234 '''show a single changeset or file revision'''
235 235 log = self.repo.changelog
236 236 if changenode is None:
237 237 changenode = log.node(rev)
238 238 elif not rev:
239 239 rev = log.rev(changenode)
240 240
241 241 if self.ui.quiet:
242 242 self.ui.write("%d:%s\n" % (rev, short(changenode)))
243 243 return
244 244
245 245 changes = log.read(changenode)
246 246 date = util.datestr(changes[2])
247 247 extra = changes[5]
248 248 branch = extra.get("branch")
249 249
250 250 hexfunc = self.ui.debugflag and hex or short
251 251
252 252 parents = log.parentrevs(rev)
253 253 if not self.ui.debugflag:
254 254 if parents[1] == nullrev:
255 255 if parents[0] >= rev - 1:
256 256 parents = []
257 257 else:
258 258 parents = [parents[0]]
259 259 parents = [(p, hexfunc(log.node(p))) for p in parents]
260 260
261 261 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
262 262
263 263 if branch:
264 264 branch = util.tolocal(branch)
265 265 self.ui.write(_("branch: %s\n") % branch)
266 266 for tag in self.repo.nodetags(changenode):
267 267 self.ui.write(_("tag: %s\n") % tag)
268 268 for parent in parents:
269 269 self.ui.write(_("parent: %d:%s\n") % parent)
270 270
271 271 if self.brinfo:
272 272 br = self.repo.branchlookup([changenode])
273 273 if br:
274 274 self.ui.write(_("branch: %s\n") % " ".join(br[changenode]))
275 275
276 276 if self.ui.debugflag:
277 277 self.ui.write(_("manifest: %d:%s\n") %
278 278 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
279 279 self.ui.write(_("user: %s\n") % changes[1])
280 280 self.ui.write(_("date: %s\n") % date)
281 281
282 282 if self.ui.debugflag:
283 283 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
284 284 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
285 285 files):
286 286 if value:
287 287 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
288 288 elif changes[3] and self.ui.verbose:
289 289 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
290 290 if copies and self.ui.verbose:
291 291 copies = ['%s (%s)' % c for c in copies]
292 292 self.ui.write(_("copies: %s\n") % ' '.join(copies))
293 293
294 294 if extra and self.ui.debugflag:
295 295 extraitems = extra.items()
296 296 extraitems.sort()
297 297 for key, value in extraitems:
298 298 self.ui.write(_("extra: %s=%s\n")
299 299 % (key, value.encode('string_escape')))
300 300
301 301 description = changes[4].strip()
302 302 if description:
303 303 if self.ui.verbose:
304 304 self.ui.write(_("description:\n"))
305 305 self.ui.write(description)
306 306 self.ui.write("\n\n")
307 307 else:
308 308 self.ui.write(_("summary: %s\n") %
309 309 description.splitlines()[0])
310 310 self.ui.write("\n")
311 311
312 312 self.showpatch(changenode)
313 313
314 314 def showpatch(self, node):
315 315 if self.patch:
316 316 prev = self.repo.changelog.parents(node)[0]
317 317 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui)
318 318 self.ui.write("\n")
319 319
320 320 class changeset_templater(changeset_printer):
321 321 '''format changeset information.'''
322 322
323 323 def __init__(self, ui, repo, patch, brinfo, mapfile, buffered):
324 324 changeset_printer.__init__(self, ui, repo, patch, brinfo, buffered)
325 325 self.t = templater.templater(mapfile, templater.common_filters,
326 326 cache={'parent': '{rev}:{node|short} ',
327 327 'manifest': '{rev}:{node|short}',
328 328 'filecopy': '{name} ({source})'})
329 329
330 330 def use_template(self, t):
331 331 '''set template string to use'''
332 332 self.t.cache['changeset'] = t
333 333
334 334 def _show(self, rev, changenode, copies, props):
335 335 '''show a single changeset or file revision'''
336 336 log = self.repo.changelog
337 337 if changenode is None:
338 338 changenode = log.node(rev)
339 339 elif not rev:
340 340 rev = log.rev(changenode)
341 341
342 342 changes = log.read(changenode)
343 343
344 344 def showlist(name, values, plural=None, **args):
345 345 '''expand set of values.
346 346 name is name of key in template map.
347 347 values is list of strings or dicts.
348 348 plural is plural of name, if not simply name + 's'.
349 349
350 350 expansion works like this, given name 'foo'.
351 351
352 352 if values is empty, expand 'no_foos'.
353 353
354 354 if 'foo' not in template map, return values as a string,
355 355 joined by space.
356 356
357 357 expand 'start_foos'.
358 358
359 359 for each value, expand 'foo'. if 'last_foo' in template
360 360 map, expand it instead of 'foo' for last key.
361 361
362 362 expand 'end_foos'.
363 363 '''
364 364 if plural: names = plural
365 365 else: names = name + 's'
366 366 if not values:
367 367 noname = 'no_' + names
368 368 if noname in self.t:
369 369 yield self.t(noname, **args)
370 370 return
371 371 if name not in self.t:
372 372 if isinstance(values[0], str):
373 373 yield ' '.join(values)
374 374 else:
375 375 for v in values:
376 376 yield dict(v, **args)
377 377 return
378 378 startname = 'start_' + names
379 379 if startname in self.t:
380 380 yield self.t(startname, **args)
381 381 vargs = args.copy()
382 382 def one(v, tag=name):
383 383 try:
384 384 vargs.update(v)
385 385 except (AttributeError, ValueError):
386 386 try:
387 387 for a, b in v:
388 388 vargs[a] = b
389 389 except ValueError:
390 390 vargs[name] = v
391 391 return self.t(tag, **vargs)
392 392 lastname = 'last_' + name
393 393 if lastname in self.t:
394 394 last = values.pop()
395 395 else:
396 396 last = None
397 397 for v in values:
398 398 yield one(v)
399 399 if last is not None:
400 400 yield one(last, tag=lastname)
401 401 endname = 'end_' + names
402 402 if endname in self.t:
403 403 yield self.t(endname, **args)
404 404
405 405 def showbranches(**args):
406 406 branch = changes[5].get("branch")
407 407 if branch:
408 408 branch = util.tolocal(branch)
409 409 return showlist('branch', [branch], plural='branches', **args)
410 410 # add old style branches if requested
411 411 if self.brinfo:
412 412 br = self.repo.branchlookup([changenode])
413 413 if changenode in br:
414 414 return showlist('branch', br[changenode],
415 415 plural='branches', **args)
416 416
417 417 def showparents(**args):
418 418 parents = [[('rev', log.rev(p)), ('node', hex(p))]
419 419 for p in log.parents(changenode)
420 420 if self.ui.debugflag or p != nullid]
421 421 if (not self.ui.debugflag and len(parents) == 1 and
422 422 parents[0][0][1] == rev - 1):
423 423 return
424 424 return showlist('parent', parents, **args)
425 425
426 426 def showtags(**args):
427 427 return showlist('tag', self.repo.nodetags(changenode), **args)
428 428
429 429 def showextras(**args):
430 430 extras = changes[5].items()
431 431 extras.sort()
432 432 for key, value in extras:
433 433 args = args.copy()
434 434 args.update(dict(key=key, value=value))
435 435 yield self.t('extra', **args)
436 436
437 437 def showcopies(**args):
438 438 c = [{'name': x[0], 'source': x[1]} for x in copies]
439 439 return showlist('file_copy', c, plural='file_copies', **args)
440 440
441 441 if self.ui.debugflag:
442 442 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
443 443 def showfiles(**args):
444 444 return showlist('file', files[0], **args)
445 445 def showadds(**args):
446 446 return showlist('file_add', files[1], **args)
447 447 def showdels(**args):
448 448 return showlist('file_del', files[2], **args)
449 449 def showmanifest(**args):
450 450 args = args.copy()
451 451 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
452 452 node=hex(changes[0])))
453 453 return self.t('manifest', **args)
454 454 else:
455 455 def showfiles(**args):
456 456 return showlist('file', changes[3], **args)
457 457 showadds = ''
458 458 showdels = ''
459 459 showmanifest = ''
460 460
461 461 defprops = {
462 462 'author': changes[1],
463 463 'branches': showbranches,
464 464 'date': changes[2],
465 465 'desc': changes[4],
466 466 'file_adds': showadds,
467 467 'file_dels': showdels,
468 468 'files': showfiles,
469 469 'file_copies': showcopies,
470 470 'manifest': showmanifest,
471 471 'node': hex(changenode),
472 472 'parents': showparents,
473 473 'rev': rev,
474 474 'tags': showtags,
475 475 'extras': showextras,
476 476 }
477 477 props = props.copy()
478 478 props.update(defprops)
479 479
480 480 try:
481 481 if self.ui.debugflag and 'header_debug' in self.t:
482 482 key = 'header_debug'
483 483 elif self.ui.quiet and 'header_quiet' in self.t:
484 484 key = 'header_quiet'
485 485 elif self.ui.verbose and 'header_verbose' in self.t:
486 486 key = 'header_verbose'
487 487 elif 'header' in self.t:
488 488 key = 'header'
489 489 else:
490 490 key = ''
491 491 if key:
492 492 h = templater.stringify(self.t(key, **props))
493 493 if self.buffered:
494 494 self.header[rev] = h
495 495 else:
496 496 self.ui.write(h)
497 497 if self.ui.debugflag and 'changeset_debug' in self.t:
498 498 key = 'changeset_debug'
499 499 elif self.ui.quiet and 'changeset_quiet' in self.t:
500 500 key = 'changeset_quiet'
501 501 elif self.ui.verbose and 'changeset_verbose' in self.t:
502 502 key = 'changeset_verbose'
503 503 else:
504 504 key = 'changeset'
505 505 self.ui.write(templater.stringify(self.t(key, **props)))
506 506 self.showpatch(changenode)
507 507 except KeyError, inst:
508 508 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
509 509 inst.args[0]))
510 510 except SyntaxError, inst:
511 511 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
512 512
513 513 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
514 514 """show one changeset using template or regular display.
515 515
516 516 Display format will be the first non-empty hit of:
517 517 1. option 'template'
518 518 2. option 'style'
519 519 3. [ui] setting 'logtemplate'
520 520 4. [ui] setting 'style'
521 521 If all of these values are either the unset or the empty string,
522 522 regular display via changeset_printer() is done.
523 523 """
524 524 # options
525 525 patch = False
526 526 if opts.get('patch'):
527 patch = lambda x: True
528 if matchfn:
529 patch = matchfn
527 patch = matchfn or util.always
530 528
531 529 br = None
532 530 if opts.get('branches'):
533 531 ui.warn(_("the --branches option is deprecated, "
534 532 "please use 'hg branches' instead\n"))
535 533 br = True
536 534 tmpl = opts.get('template')
537 535 mapfile = None
538 536 if tmpl:
539 537 tmpl = templater.parsestring(tmpl, quoted=False)
540 538 else:
541 539 mapfile = opts.get('style')
542 540 # ui settings
543 541 if not mapfile:
544 542 tmpl = ui.config('ui', 'logtemplate')
545 543 if tmpl:
546 544 tmpl = templater.parsestring(tmpl)
547 545 else:
548 546 mapfile = ui.config('ui', 'style')
549 547
550 548 if tmpl or mapfile:
551 549 if mapfile:
552 550 if not os.path.split(mapfile)[0]:
553 551 mapname = (templater.templatepath('map-cmdline.' + mapfile)
554 552 or templater.templatepath(mapfile))
555 553 if mapname: mapfile = mapname
556 554 try:
557 555 t = changeset_templater(ui, repo, patch, br, mapfile, buffered)
558 556 except SyntaxError, inst:
559 557 raise util.Abort(inst.args[0])
560 558 if tmpl: t.use_template(tmpl)
561 559 return t
562 560 return changeset_printer(ui, repo, patch, br, buffered)
563 561
564 562 def finddate(ui, repo, date):
565 563 """Find the tipmost changeset that matches the given date spec"""
566 564 df = util.matchdate(date + " to " + date)
567 565 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
568 566 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
569 567 results = {}
570 568 for st, rev, fns in changeiter:
571 569 if st == 'add':
572 570 d = get(rev)[2]
573 571 if df(d[0]):
574 572 results[rev] = d
575 573 elif st == 'iter':
576 574 if rev in results:
577 575 ui.status("Found revision %s from %s\n" %
578 576 (rev, util.datestr(results[rev])))
579 577 return str(rev)
580 578
581 579 raise util.Abort(_("revision matching date not found"))
582 580
583 581 def walkchangerevs(ui, repo, pats, change, opts):
584 582 '''Iterate over files and the revs they changed in.
585 583
586 584 Callers most commonly need to iterate backwards over the history
587 585 it is interested in. Doing so has awful (quadratic-looking)
588 586 performance, so we use iterators in a "windowed" way.
589 587
590 588 We walk a window of revisions in the desired order. Within the
591 589 window, we first walk forwards to gather data, then in the desired
592 590 order (usually backwards) to display it.
593 591
594 592 This function returns an (iterator, matchfn) tuple. The iterator
595 593 yields 3-tuples. They will be of one of the following forms:
596 594
597 595 "window", incrementing, lastrev: stepping through a window,
598 596 positive if walking forwards through revs, last rev in the
599 597 sequence iterated over - use to reset state for the current window
600 598
601 599 "add", rev, fns: out-of-order traversal of the given file names
602 600 fns, which changed during revision rev - use to gather data for
603 601 possible display
604 602
605 603 "iter", rev, None: in-order traversal of the revs earlier iterated
606 604 over with "add" - use to display data'''
607 605
608 606 def increasing_windows(start, end, windowsize=8, sizelimit=512):
609 607 if start < end:
610 608 while start < end:
611 609 yield start, min(windowsize, end-start)
612 610 start += windowsize
613 611 if windowsize < sizelimit:
614 612 windowsize *= 2
615 613 else:
616 614 while start > end:
617 615 yield start, min(windowsize, start-end-1)
618 616 start -= windowsize
619 617 if windowsize < sizelimit:
620 618 windowsize *= 2
621 619
622 620 files, matchfn, anypats = matchpats(repo, pats, opts)
623 621 follow = opts.get('follow') or opts.get('follow_first')
624 622
625 623 if repo.changelog.count() == 0:
626 624 return [], matchfn
627 625
628 626 if follow:
629 627 defrange = '%s:0' % repo.changectx().rev()
630 628 else:
631 629 defrange = 'tip:0'
632 630 revs = revrange(repo, opts['rev'] or [defrange])
633 631 wanted = {}
634 632 slowpath = anypats or opts.get('removed')
635 633 fncache = {}
636 634
637 635 if not slowpath and not files:
638 636 # No files, no patterns. Display all revs.
639 637 wanted = dict.fromkeys(revs)
640 638 copies = []
641 639 if not slowpath:
642 640 # Only files, no patterns. Check the history of each file.
643 641 def filerevgen(filelog, node):
644 642 cl_count = repo.changelog.count()
645 643 if node is None:
646 644 last = filelog.count() - 1
647 645 else:
648 646 last = filelog.rev(node)
649 647 for i, window in increasing_windows(last, nullrev):
650 648 revs = []
651 649 for j in xrange(i - window, i + 1):
652 650 n = filelog.node(j)
653 651 revs.append((filelog.linkrev(n),
654 652 follow and filelog.renamed(n)))
655 653 revs.reverse()
656 654 for rev in revs:
657 655 # only yield rev for which we have the changelog, it can
658 656 # happen while doing "hg log" during a pull or commit
659 657 if rev[0] < cl_count:
660 658 yield rev
661 659 def iterfiles():
662 660 for filename in files:
663 661 yield filename, None
664 662 for filename_node in copies:
665 663 yield filename_node
666 664 minrev, maxrev = min(revs), max(revs)
667 665 for file_, node in iterfiles():
668 666 filelog = repo.file(file_)
669 667 # A zero count may be a directory or deleted file, so
670 668 # try to find matching entries on the slow path.
671 669 if filelog.count() == 0:
672 670 slowpath = True
673 671 break
674 672 for rev, copied in filerevgen(filelog, node):
675 673 if rev <= maxrev:
676 674 if rev < minrev:
677 675 break
678 676 fncache.setdefault(rev, [])
679 677 fncache[rev].append(file_)
680 678 wanted[rev] = 1
681 679 if follow and copied:
682 680 copies.append(copied)
683 681 if slowpath:
684 682 if follow:
685 683 raise util.Abort(_('can only follow copies/renames for explicit '
686 684 'file names'))
687 685
688 686 # The slow path checks files modified in every changeset.
689 687 def changerevgen():
690 688 for i, window in increasing_windows(repo.changelog.count()-1,
691 689 nullrev):
692 690 for j in xrange(i - window, i + 1):
693 691 yield j, change(j)[3]
694 692
695 693 for rev, changefiles in changerevgen():
696 694 matches = filter(matchfn, changefiles)
697 695 if matches:
698 696 fncache[rev] = matches
699 697 wanted[rev] = 1
700 698
701 699 class followfilter:
702 700 def __init__(self, onlyfirst=False):
703 701 self.startrev = nullrev
704 702 self.roots = []
705 703 self.onlyfirst = onlyfirst
706 704
707 705 def match(self, rev):
708 706 def realparents(rev):
709 707 if self.onlyfirst:
710 708 return repo.changelog.parentrevs(rev)[0:1]
711 709 else:
712 710 return filter(lambda x: x != nullrev,
713 711 repo.changelog.parentrevs(rev))
714 712
715 713 if self.startrev == nullrev:
716 714 self.startrev = rev
717 715 return True
718 716
719 717 if rev > self.startrev:
720 718 # forward: all descendants
721 719 if not self.roots:
722 720 self.roots.append(self.startrev)
723 721 for parent in realparents(rev):
724 722 if parent in self.roots:
725 723 self.roots.append(rev)
726 724 return True
727 725 else:
728 726 # backwards: all parents
729 727 if not self.roots:
730 728 self.roots.extend(realparents(self.startrev))
731 729 if rev in self.roots:
732 730 self.roots.remove(rev)
733 731 self.roots.extend(realparents(rev))
734 732 return True
735 733
736 734 return False
737 735
738 736 # it might be worthwhile to do this in the iterator if the rev range
739 737 # is descending and the prune args are all within that range
740 738 for rev in opts.get('prune', ()):
741 739 rev = repo.changelog.rev(repo.lookup(rev))
742 740 ff = followfilter()
743 741 stop = min(revs[0], revs[-1])
744 742 for x in xrange(rev, stop-1, -1):
745 743 if ff.match(x) and x in wanted:
746 744 del wanted[x]
747 745
748 746 def iterate():
749 747 if follow and not files:
750 748 ff = followfilter(onlyfirst=opts.get('follow_first'))
751 749 def want(rev):
752 750 if ff.match(rev) and rev in wanted:
753 751 return True
754 752 return False
755 753 else:
756 754 def want(rev):
757 755 return rev in wanted
758 756
759 757 for i, window in increasing_windows(0, len(revs)):
760 758 yield 'window', revs[0] < revs[-1], revs[-1]
761 759 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
762 760 srevs = list(nrevs)
763 761 srevs.sort()
764 762 for rev in srevs:
765 763 fns = fncache.get(rev)
766 764 if not fns:
767 765 def fns_generator():
768 766 for f in change(rev)[3]:
769 767 if matchfn(f):
770 768 yield f
771 769 fns = fns_generator()
772 770 yield 'add', rev, fns
773 771 for rev in nrevs:
774 772 yield 'iter', rev, None
775 773 return iterate(), matchfn
General Comments 0
You need to be logged in to leave comments. Login now