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