Mercurial > public > mercurial-scm > hg
diff mercurial/branchmap.py @ 20185:7d4219512823
branchmap: cache open/closed branch head information
This lets us determine the open/closed state of a branch without
reading from the changelog (which can be costly over NFS and/or with
many branches).
author | Brodie Rao <brodie@sf.io> |
---|---|
date | Mon, 16 Sep 2013 01:08:29 -0700 |
parents | b9515fb9e72a |
children | f5b461a4bc55 |
line wrap: on
line diff
--- a/mercurial/branchmap.py Mon Nov 11 21:16:54 2013 +1100 +++ b/mercurial/branchmap.py Mon Sep 16 01:08:29 2013 -0700 @@ -11,7 +11,7 @@ def _filename(repo): """name of a branchcache file for a given repo or repoview""" - filename = "cache/branchheads" + filename = "cache/branch2" if repo.filtername: filename = '%s-%s' % (filename, repo.filtername) return filename @@ -39,11 +39,16 @@ for l in lines: if not l: continue - node, label = l.split(" ", 1) + node, state, label = l.split(" ", 2) + if state not in 'oc': + raise ValueError('invalid branch state') label = encoding.tolocal(label.strip()) if not node in repo: raise ValueError('node %s does not exist' % node) - partial.setdefault(label, []).append(bin(node)) + node = bin(node) + partial.setdefault(label, []).append(node) + if state == 'c': + partial._closednodes.add(node) except KeyboardInterrupt: raise except Exception, inst: @@ -102,21 +107,32 @@ The cache is serialized on disk in the following format: <tip hex node> <tip rev number> [optional filtered repo hex hash] - <branch head hex node> <branch name> - <branch head hex node> <branch name> + <branch head hex node> <open/closed state> <branch name> + <branch head hex node> <open/closed state> <branch name> ... The first line is used to check if the cache is still valid. If the branch cache is for a filtered repo view, an optional third hash is included that hashes the hashes of all filtered revisions. + + The open/closed state is represented by a single letter 'o' or 'c'. + This field can be used to avoid changelog reads when determining if a + branch head closes a branch or not. """ def __init__(self, entries=(), tipnode=nullid, tiprev=nullrev, - filteredhash=None): + filteredhash=None, closednodes=None): super(branchcache, self).__init__(entries) self.tipnode = tipnode self.tiprev = tiprev self.filteredhash = filteredhash + # closednodes is a set of nodes that close their branch. If the branch + # cache has been updated, it may contain nodes that are no longer + # heads. + if closednodes is None: + self._closednodes = set() + else: + self._closednodes = closednodes def _hashfiltered(self, repo): """build hash of revision filtered in the current cache @@ -152,7 +168,8 @@ def copy(self): """return an deep copy of the branchcache object""" - return branchcache(self, self.tipnode, self.tiprev, self.filteredhash) + return branchcache(self, self.tipnode, self.tiprev, self.filteredhash, + self._closednodes) def write(self, repo): try: @@ -163,7 +180,12 @@ f.write(" ".join(cachekey) + '\n') for label, nodes in sorted(self.iteritems()): for node in nodes: - f.write("%s %s\n" % (hex(node), encoding.fromlocal(label))) + if node in self._closednodes: + state = 'c' + else: + state = 'o' + f.write("%s %s %s\n" % (hex(node), state, + encoding.fromlocal(label))) f.close() except (IOError, OSError, util.Abort): # Abort may be raise by read only opener @@ -177,9 +199,13 @@ cl = repo.changelog # collect new branch entries newbranches = {} - getbranch = cl.branch + getbranchinfo = cl.branchinfo for r in revgen: - newbranches.setdefault(getbranch(r), []).append(cl.node(r)) + branch, closesbranch = getbranchinfo(r) + node = cl.node(r) + newbranches.setdefault(branch, []).append(node) + if closesbranch: + self._closednodes.add(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)