mercurial/cmdutil.py
changeset 3657 731e739b8659
parent 3656 e50891e461e4
child 3664 4d988b7412f2
equal deleted inserted replaced
3656:e50891e461e4 3657:731e739b8659
   590             raise util.Abort(inst.args[0])
   590             raise util.Abort(inst.args[0])
   591         if tmpl: t.use_template(tmpl)
   591         if tmpl: t.use_template(tmpl)
   592         return t
   592         return t
   593     return changeset_printer(ui, repo, patch, br, buffered)
   593     return changeset_printer(ui, repo, patch, br, buffered)
   594 
   594 
       
   595 def walkchangerevs(ui, repo, pats, change, opts):
       
   596     '''Iterate over files and the revs they changed in.
       
   597 
       
   598     Callers most commonly need to iterate backwards over the history
       
   599     it is interested in.  Doing so has awful (quadratic-looking)
       
   600     performance, so we use iterators in a "windowed" way.
       
   601 
       
   602     We walk a window of revisions in the desired order.  Within the
       
   603     window, we first walk forwards to gather data, then in the desired
       
   604     order (usually backwards) to display it.
       
   605 
       
   606     This function returns an (iterator, matchfn) tuple. The iterator
       
   607     yields 3-tuples. They will be of one of the following forms:
       
   608 
       
   609     "window", incrementing, lastrev: stepping through a window,
       
   610     positive if walking forwards through revs, last rev in the
       
   611     sequence iterated over - use to reset state for the current window
       
   612 
       
   613     "add", rev, fns: out-of-order traversal of the given file names
       
   614     fns, which changed during revision rev - use to gather data for
       
   615     possible display
       
   616 
       
   617     "iter", rev, None: in-order traversal of the revs earlier iterated
       
   618     over with "add" - use to display data'''
       
   619 
       
   620     def increasing_windows(start, end, windowsize=8, sizelimit=512):
       
   621         if start < end:
       
   622             while start < end:
       
   623                 yield start, min(windowsize, end-start)
       
   624                 start += windowsize
       
   625                 if windowsize < sizelimit:
       
   626                     windowsize *= 2
       
   627         else:
       
   628             while start > end:
       
   629                 yield start, min(windowsize, start-end-1)
       
   630                 start -= windowsize
       
   631                 if windowsize < sizelimit:
       
   632                     windowsize *= 2
       
   633 
       
   634     files, matchfn, anypats = matchpats(repo, pats, opts)
       
   635     follow = opts.get('follow') or opts.get('follow_first')
       
   636 
       
   637     if repo.changelog.count() == 0:
       
   638         return [], matchfn
       
   639 
       
   640     if follow:
       
   641         defrange = '%s:0' % repo.changectx().rev()
       
   642     else:
       
   643         defrange = 'tip:0'
       
   644     revs = revrange(ui, repo, opts['rev'] or [defrange])
       
   645     wanted = {}
       
   646     slowpath = anypats
       
   647     fncache = {}
       
   648 
       
   649     if not slowpath and not files:
       
   650         # No files, no patterns.  Display all revs.
       
   651         wanted = dict.fromkeys(revs)
       
   652     copies = []
       
   653     if not slowpath:
       
   654         # Only files, no patterns.  Check the history of each file.
       
   655         def filerevgen(filelog, node):
       
   656             cl_count = repo.changelog.count()
       
   657             if node is None:
       
   658                 last = filelog.count() - 1
       
   659             else:
       
   660                 last = filelog.rev(node)
       
   661             for i, window in increasing_windows(last, nullrev):
       
   662                 revs = []
       
   663                 for j in xrange(i - window, i + 1):
       
   664                     n = filelog.node(j)
       
   665                     revs.append((filelog.linkrev(n),
       
   666                                  follow and filelog.renamed(n)))
       
   667                 revs.reverse()
       
   668                 for rev in revs:
       
   669                     # only yield rev for which we have the changelog, it can
       
   670                     # happen while doing "hg log" during a pull or commit
       
   671                     if rev[0] < cl_count:
       
   672                         yield rev
       
   673         def iterfiles():
       
   674             for filename in files:
       
   675                 yield filename, None
       
   676             for filename_node in copies:
       
   677                 yield filename_node
       
   678         minrev, maxrev = min(revs), max(revs)
       
   679         for file_, node in iterfiles():
       
   680             filelog = repo.file(file_)
       
   681             # A zero count may be a directory or deleted file, so
       
   682             # try to find matching entries on the slow path.
       
   683             if filelog.count() == 0:
       
   684                 slowpath = True
       
   685                 break
       
   686             for rev, copied in filerevgen(filelog, node):
       
   687                 if rev <= maxrev:
       
   688                     if rev < minrev:
       
   689                         break
       
   690                     fncache.setdefault(rev, [])
       
   691                     fncache[rev].append(file_)
       
   692                     wanted[rev] = 1
       
   693                     if follow and copied:
       
   694                         copies.append(copied)
       
   695     if slowpath:
       
   696         if follow:
       
   697             raise util.Abort(_('can only follow copies/renames for explicit '
       
   698                                'file names'))
       
   699 
       
   700         # The slow path checks files modified in every changeset.
       
   701         def changerevgen():
       
   702             for i, window in increasing_windows(repo.changelog.count()-1,
       
   703                                                 nullrev):
       
   704                 for j in xrange(i - window, i + 1):
       
   705                     yield j, change(j)[3]
       
   706 
       
   707         for rev, changefiles in changerevgen():
       
   708             matches = filter(matchfn, changefiles)
       
   709             if matches:
       
   710                 fncache[rev] = matches
       
   711                 wanted[rev] = 1
       
   712 
       
   713     class followfilter:
       
   714         def __init__(self, onlyfirst=False):
       
   715             self.startrev = nullrev
       
   716             self.roots = []
       
   717             self.onlyfirst = onlyfirst
       
   718 
       
   719         def match(self, rev):
       
   720             def realparents(rev):
       
   721                 if self.onlyfirst:
       
   722                     return repo.changelog.parentrevs(rev)[0:1]
       
   723                 else:
       
   724                     return filter(lambda x: x != nullrev,
       
   725                                   repo.changelog.parentrevs(rev))
       
   726 
       
   727             if self.startrev == nullrev:
       
   728                 self.startrev = rev
       
   729                 return True
       
   730 
       
   731             if rev > self.startrev:
       
   732                 # forward: all descendants
       
   733                 if not self.roots:
       
   734                     self.roots.append(self.startrev)
       
   735                 for parent in realparents(rev):
       
   736                     if parent in self.roots:
       
   737                         self.roots.append(rev)
       
   738                         return True
       
   739             else:
       
   740                 # backwards: all parents
       
   741                 if not self.roots:
       
   742                     self.roots.extend(realparents(self.startrev))
       
   743                 if rev in self.roots:
       
   744                     self.roots.remove(rev)
       
   745                     self.roots.extend(realparents(rev))
       
   746                     return True
       
   747 
       
   748             return False
       
   749 
       
   750     # it might be worthwhile to do this in the iterator if the rev range
       
   751     # is descending and the prune args are all within that range
       
   752     for rev in opts.get('prune', ()):
       
   753         rev = repo.changelog.rev(repo.lookup(rev))
       
   754         ff = followfilter()
       
   755         stop = min(revs[0], revs[-1])
       
   756         for x in xrange(rev, stop-1, -1):
       
   757             if ff.match(x) and x in wanted:
       
   758                 del wanted[x]
       
   759 
       
   760     def iterate():
       
   761         if follow and not files:
       
   762             ff = followfilter(onlyfirst=opts.get('follow_first'))
       
   763             def want(rev):
       
   764                 if ff.match(rev) and rev in wanted:
       
   765                     return True
       
   766                 return False
       
   767         else:
       
   768             def want(rev):
       
   769                 return rev in wanted
       
   770 
       
   771         for i, window in increasing_windows(0, len(revs)):
       
   772             yield 'window', revs[0] < revs[-1], revs[-1]
       
   773             nrevs = [rev for rev in revs[i:i+window] if want(rev)]
       
   774             srevs = list(nrevs)
       
   775             srevs.sort()
       
   776             for rev in srevs:
       
   777                 fns = fncache.get(rev)
       
   778                 if not fns:
       
   779                     def fns_generator():
       
   780                         for f in change(rev)[3]:
       
   781                             if matchfn(f):
       
   782                                 yield f
       
   783                     fns = fns_generator()
       
   784                 yield 'add', rev, fns
       
   785             for rev in nrevs:
       
   786                 yield 'iter', rev, None
       
   787     return iterate(), matchfn