Mercurial > hg-stable
view mercurial/branchmap.py @ 18132:db25bf1dc828
branchmap: move validity logic in the object itself
In several place, We check if a branchcache is still valid regarding the current
state of the repository. This changeset puts this logic in a method of the object
that can be reused when necessary.
A branch map is considered valid whenever it is up to date or a strict subset of
the repository state.
The change will help making branchcache aware of filtered revision.
The change in keyword is expected. the branch cache is actually invalid after
the amend. The previous check did not detected it.
author | Pierre-Yves David <pierre-yves.david@ens-lyon.org> |
---|---|
date | Mon, 24 Dec 2012 02:49:59 +0100 |
parents | f0eeb9b3444a |
children | 3a2e810dd3d8 |
line wrap: on
line source
# branchmap.py - logic to computes, maintain and stores branchmap for local repo # # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. from node import bin, hex, nullid, nullrev import encoding def read(repo): try: f = repo.opener("cache/branchheads") lines = f.read().split('\n') f.close() except (IOError, OSError): return branchcache() try: last, lrev = lines.pop(0).split(" ", 1) last, lrev = bin(last), int(lrev) partial = branchcache(tipnode=last, tiprev=lrev) if not partial.validfor(repo): # invalidate the cache raise ValueError('invalidating branch cache (tip differs)') for l in lines: if not l: continue node, label = l.split(" ", 1) label = encoding.tolocal(label.strip()) if not node in repo: raise ValueError('invalidating branch cache because node '+ '%s does not exist' % node) partial.setdefault(label, []).append(bin(node)) except KeyboardInterrupt: raise except Exception, inst: if repo.ui.debugflag: repo.ui.warn(str(inst), '\n') partial = branchcache() return partial def updatecache(repo): repo = repo.unfiltered() # Until we get a smarter cache management cl = repo.changelog partial = repo._branchcache if partial is None or not partial.validfor(repo): partial = read(repo) catip = repo._cacheabletip() # if partial.tiprev == catip: cache is already up to date # if partial.tiprev > catip: we have uncachable element in `partial` can't # write on disk if partial.tiprev < catip: ctxgen = (repo[r] for r in cl.revs(partial.tiprev + 1, catip)) partial.update(repo, ctxgen) partial.write(repo) # If cacheable tip were lower than actual tip, we need to update the # cache up to tip. This update (from cacheable to actual tip) is not # written to disk since it's not cacheable. tiprev = len(repo) - 1 if partial.tiprev < tiprev: ctxgen = (repo[r] for r in cl.revs(partial.tiprev + 1, tiprev)) partial.update(repo, ctxgen) repo._branchcache = partial class branchcache(dict): """A dict like object that hold branches heads cache""" def __init__(self, entries=(), tipnode=nullid, tiprev=nullrev): super(branchcache, self).__init__(entries) self.tipnode = tipnode self.tiprev = tiprev def validfor(self, repo): """Is the cache content valide regarding a repo - False when cached tipnode are unknown or if we detect a strip. - True when cache is up to date or a subset of current repo.""" try: return self.tipnode == repo.changelog.node(self.tiprev) except IndexError: return False def write(self, repo): try: f = repo.opener("cache/branchheads", "w", atomictemp=True) f.write("%s %s\n" % (hex(self.tipnode), self.tiprev)) for label, nodes in self.iteritems(): for node in nodes: f.write("%s %s\n" % (hex(node), encoding.fromlocal(label))) f.close() except (IOError, OSError): pass def update(self, repo, ctxgen): """Given a branchhead cache, self, that may have extra nodes or be missing heads, and a generator of nodes that are at least a superset of heads missing, this function updates self to be correct. """ cl = repo.changelog # collect new branch entries newbranches = {} for c in ctxgen: newbranches.setdefault(c.branch(), []).append(c.node()) # if older branchheads are reachable from new ones, they aren't # really branchheads. Note checking parents is insufficient: # 1 (branch a) -> 2 (branch b) -> 3 (branch a) for branch, newnodes in newbranches.iteritems(): bheads = self.setdefault(branch, []) # Remove candidate heads that no longer are in the repo (e.g., as # the result of a strip that just happened). Avoid using 'node in # self' here because that dives down into branchcache code somewhat # recursively. bheadrevs = [cl.rev(node) for node in bheads if cl.hasnode(node)] newheadrevs = [cl.rev(node) for node in newnodes if cl.hasnode(node)] ctxisnew = bheadrevs and min(newheadrevs) > max(bheadrevs) # Remove duplicates - nodes that are in newheadrevs and are already # in bheadrevs. This can happen if you strip a node whose parent # was already a head (because they're on different branches). bheadrevs = sorted(set(bheadrevs).union(newheadrevs)) # Starting from tip means fewer passes over reachable. If we know # the new candidates are not ancestors of existing heads, we don't # have to examine ancestors of existing heads if ctxisnew: iterrevs = sorted(newheadrevs) else: iterrevs = list(bheadrevs) # This loop prunes out two kinds of heads - heads that are # superseded by a head in newheadrevs, and newheadrevs that are not # heads because an existing head is their descendant. while iterrevs: latest = iterrevs.pop() if latest not in bheadrevs: continue ancestors = set(cl.ancestors([latest], bheadrevs[0])) if ancestors: bheadrevs = [b for b in bheadrevs if b not in ancestors] self[branch] = [cl.node(rev) for rev in bheadrevs] tiprev = max(bheadrevs) if tiprev > self.tiprev: self.tipnode = cl.node(tiprev) self.tiprev = tiprev # There may be branches that cease to exist when the last commit in the # branch was stripped. This code filters them out. Note that the # branch that ceased to exist may not be in newbranches because # newbranches is the set of candidate heads, which when you strip the # last commit in a branch will be the parent branch. droppednodes = [] for branch in self.keys(): nodes = [head for head in self[branch] if cl.hasnode(head)] if not nodes: droppednodes.extend(nodes) del self[branch] if ((not self.validfor(repo)) or (self.tipnode in droppednodes)): # cache key are not valid anymore self.tipnode = nullid self.tiprev = nullrev for heads in self.values(): tiprev = max(cl.rev(node) for node in heads) if tiprev > self.tiprev: self.tipnode = cl.node(tiprev) self.tiprev = tiprev