[PATCH 1 of 3] localrepo: store closed state of each head in the branch cache

Steven Brown stevengbrown at gmail.com
Tue Feb 7 08:48:33 CST 2012


# HG changeset patch
# User Steven Brown <StevenGBrown at gmail.com>
# Date 1328623028 -28800
# Node ID 0a5cacc501b4c680832c5ab9c775321611a6015c
# Parent  8af9e08a094ff43f828085e1102b320995e0c1b2
localrepo: store closed state of each head in the branch cache

This will reduce the time taken to query the branches in a branchy repo.
Profiling shows that although the closed state of a single revision can be
determined very quickly, this is repeated many times and so it accounts for most
of the time taken.

The heads for each branch are still cached at .hg/cache/branchheads. The closed
state of each head is now stored in the first column. When switching to this
cset from an earlier version of Mercurial, or switching back again, the cache
will be rebuilt.

diff --git a/mercurial/discovery.py b/mercurial/discovery.py
--- a/mercurial/discovery.py
+++ b/mercurial/discovery.py
@@ -183,12 +183,12 @@
         # 4. Update newmap with outgoing changes.
         # This will possibly add new heads and remove existing ones.
         ctxgen = (repo[n] for n in outgoing.missing)
-        repo._updatebranchcache(newmap, ctxgen)
+        repo.updatebranchheads(newmap, ctxgen)
 
     else:
         # 1-4b. old servers: Check for new topological heads.
         # Construct {old,new}map with branch = None (topological branch).
-        # (code based on _updatebranchcache)
+        # (code based on _updatebranchheads)
         oldheads = set(h for h in remoteheads if h in cl.nodemap)
         newheads = oldheads.union(outgoing.missing)
         if len(newheads) > 1:
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -500,10 +500,14 @@
         # this private cache holds all heads (not just tips)
         self._branchcache = partial
 
+    def updatebranchheads(self, partialheads, ctxgen):
+        '''update partialheads with the branch heads provided by ctxgen'''
+        self._updatebranchheads(partialheads, ctxgen)
+
     def branchmap(self):
         '''returns a dictionary {branch: [branchheads]}'''
         self.updatebranchcache()
-        return self._branchcache
+        return self._branchcache.heads
 
     def branchtags(self):
         '''return a dict where branch names map to the tipmost head of
@@ -519,13 +523,13 @@
         return bt
 
     def _readbranchcache(self):
-        partial = {}
+        partial = branchcache()
         try:
             f = self.opener("cache/branchheads")
             lines = f.read().split('\n')
             f.close()
         except (IOError, OSError):
-            return {}, nullid, nullrev
+            return partial, nullid, nullrev
 
         try:
             last, lrev = lines.pop(0).split(" ", 1)
@@ -536,29 +540,45 @@
             for l in lines:
                 if not l:
                     continue
-                node, label = l.split(" ", 1)
-                label = encoding.tolocal(label.strip())
-                partial.setdefault(label, []).append(bin(node))
+                closed = l[:1]
+                node = bin(l[2:42])
+                label = encoding.tolocal(l[43:].strip())
+                partial.heads.setdefault(label, []).append(node)
+                openheads = partial.openheads.setdefault(label, [])
+                if closed != 'c':
+                    openheads.append(node)
         except KeyboardInterrupt:
             raise
         except Exception, inst:
             if self.ui.debugflag:
                 self.ui.warn(str(inst), '\n')
-            partial, last, lrev = {}, nullid, nullrev
+            partial, last, lrev = branchcache(), nullid, nullrev
         return partial, last, lrev
 
     def _writebranchcache(self, branches, tip, tiprev):
         try:
             f = self.opener("cache/branchheads", "w", atomictemp=True)
             f.write("%s %s\n" % (hex(tip), tiprev))
-            for label, nodes in branches.iteritems():
+            for label, nodes in branches.heads.iteritems():
+                openheads = branches.openheads[label]
                 for node in nodes:
-                    f.write("%s %s\n" % (hex(node), encoding.fromlocal(label)))
+                    closed = '_'
+                    if node not in openheads:
+                        closed = 'c'
+                    enclabel = encoding.fromlocal(label)
+                    f.write("%s %s %s\n" % (closed, hex(node), enclabel))
             f.close()
         except (IOError, OSError):
             pass
 
     def _updatebranchcache(self, partial, ctxgen):
+        updated = self._updatebranchheads(partial.heads, ctxgen)
+        for branch in updated:
+            headsctx = [self[h] for h in partial.heads[branch]]
+            partial.openheads[branch] = \
+                [h.node() for h in headsctx if 'close' not in h.extra()]
+
+    def _updatebranchheads(self, partialheads, ctxgen):
         # collect new branch entries
         newbranches = {}
         for c in ctxgen:
@@ -567,7 +587,7 @@
         # really branchheads. Note checking parents is insufficient:
         # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
         for branch, newnodes in newbranches.iteritems():
-            bheads = partial.setdefault(branch, [])
+            bheads = partialheads.setdefault(branch, [])
             bheads.extend(newnodes)
             if len(bheads) <= 1:
                 continue
@@ -582,7 +602,8 @@
                 reachable.remove(latest)
                 if reachable:
                     bheads = [b for b in bheads if b not in reachable]
-            partial[branch] = bheads
+            partialheads[branch] = bheads
+        return set(newbranches.keys())
 
     def lookup(self, key):
         if isinstance(key, int):
@@ -2308,3 +2329,8 @@
 
 def islocal(path):
     return True
+
+class branchcache(object):
+    def __init__(self):
+        self.heads = {}
+        self.openheads = {}
diff --git a/tests/test-mq-caches.t b/tests/test-mq-caches.t
--- a/tests/test-mq-caches.t
+++ b/tests/test-mq-caches.t
@@ -8,7 +8,8 @@
   >     hg log -r does-not-exist 2> /dev/null
   >     hg log -r tip --template 'tip: {rev}\n'
   >     if [ -f $branches ]; then
-  >       sort $branches
+  >       head -n1 $branches
+  >       tail -n+2 $branches | sort
   >     else
   >       echo No branch cache
   >     fi
@@ -30,7 +31,7 @@
   $ show_branch_cache
   tip: 0
   d986d5caac23a7d44a46efc0ddaf5eb9665844cf 0
-  d986d5caac23a7d44a46efc0ddaf5eb9665844cf default
+  _ d986d5caac23a7d44a46efc0ddaf5eb9665844cf default
 
   $ echo > pfile
   $ hg add pfile
@@ -38,7 +39,7 @@
   $ show_branch_cache
   tip: 0
   a7977e38ed2c2942fa6c278030badfef3d180979 0
-  a7977e38ed2c2942fa6c278030badfef3d180979 default
+  _ a7977e38ed2c2942fa6c278030badfef3d180979 default
 
 some regular revisions
 
@@ -57,8 +58,8 @@
   $ show_branch_cache
   tip: 1
   c229711f16da3d7591f89b1b8d963b79bda22714 1
-  c229711f16da3d7591f89b1b8d963b79bda22714 bar
-  dc25e3827021582e979f600811852e36cbe57341 foo
+  _ c229711f16da3d7591f89b1b8d963b79bda22714 bar
+  _ dc25e3827021582e979f600811852e36cbe57341 foo
 
 add some mq patches
 
@@ -68,8 +69,8 @@
   $ show_branch_cache
   tip: 2
   982611f6955f9c48d3365decea203217c945ef0d 2
-  982611f6955f9c48d3365decea203217c945ef0d bar
-  dc25e3827021582e979f600811852e36cbe57341 foo
+  _ 982611f6955f9c48d3365decea203217c945ef0d bar
+  _ dc25e3827021582e979f600811852e36cbe57341 foo
 
   $ hg qnew -d '0 0' p2
   $ echo foo > .hg/branch
@@ -78,8 +79,8 @@
   $ show_branch_cache 1
   tip: 3
   982611f6955f9c48d3365decea203217c945ef0d 2
-  982611f6955f9c48d3365decea203217c945ef0d bar
-  dc25e3827021582e979f600811852e36cbe57341 foo
+  _ 982611f6955f9c48d3365decea203217c945ef0d bar
+  _ dc25e3827021582e979f600811852e36cbe57341 foo
   branch foo: 3
   branch bar: 2
 
@@ -89,8 +90,8 @@
   $ show_branch_cache 1
   tip: 3
   c229711f16da3d7591f89b1b8d963b79bda22714 1
-  c229711f16da3d7591f89b1b8d963b79bda22714 bar
-  dc25e3827021582e979f600811852e36cbe57341 foo
+  _ c229711f16da3d7591f89b1b8d963b79bda22714 bar
+  _ dc25e3827021582e979f600811852e36cbe57341 foo
   branch foo: 3
   branch bar: 2
 
@@ -100,8 +101,8 @@
   $ show_branch_cache 1
   tip: 3
   c229711f16da3d7591f89b1b8d963b79bda22714 1
-  c229711f16da3d7591f89b1b8d963b79bda22714 bar
-  dc25e3827021582e979f600811852e36cbe57341 foo
+  _ c229711f16da3d7591f89b1b8d963b79bda22714 bar
+  _ dc25e3827021582e979f600811852e36cbe57341 foo
   branch foo: 3
   branch bar: 2
   $ hg log -r qbase --template 'qbase: {rev}\n'
@@ -122,5 +123,5 @@
   $ show_branch_cache
   tip: 3
   3fe2e3b237359b5c55cec6ed172ac41d3850fade 1
-  3fe2e3b237359b5c55cec6ed172ac41d3850fade foo
+  _ 3fe2e3b237359b5c55cec6ed172ac41d3850fade foo
 
diff --git a/tests/test-newbranch.t b/tests/test-newbranch.t
--- a/tests/test-newbranch.t
+++ b/tests/test-newbranch.t
@@ -150,9 +150,9 @@
 
   $ cat $branchcache
   adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 4
-  1c28f494dae69a2f8fc815059d257eccf3fcfe75 default
-  adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 foo
-  c21617b13b220988e7a2e26290fbe4325ffa7139 bar
+  _ 1c28f494dae69a2f8fc815059d257eccf3fcfe75 default
+  _ adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 foo
+  _ c21617b13b220988e7a2e26290fbe4325ffa7139 bar
 
 Push should update the branch cache:
 
@@ -164,7 +164,7 @@
 
   $ cat ../target/$branchcache
   db01e8ea3388fd3c7c94e1436ea2bd6a53d581c5 0
-  db01e8ea3388fd3c7c94e1436ea2bd6a53d581c5 default
+  _ db01e8ea3388fd3c7c94e1436ea2bd6a53d581c5 default
 
 Pushing everything:
 
@@ -172,9 +172,37 @@
 
   $ cat ../target/$branchcache
   adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 4
-  1c28f494dae69a2f8fc815059d257eccf3fcfe75 default
-  adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 foo
-  c21617b13b220988e7a2e26290fbe4325ffa7139 bar
+  _ 1c28f494dae69a2f8fc815059d257eccf3fcfe75 default
+  _ adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 foo
+  _ c21617b13b220988e7a2e26290fbe4325ffa7139 bar
+
+Closed branch in the cache:
+
+  $ hg -R ../target update bar
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg -R ../target ci -m "close" --close-branch
+
+  $ cat ../target/$branchcache
+  7f3676b7004d4616a72ccf9ecccbc5c38e128882 5
+  _ 1c28f494dae69a2f8fc815059d257eccf3fcfe75 default
+  _ adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 foo
+  c 7f3676b7004d4616a72ccf9ecccbc5c38e128882 bar
+
+Cache created with Mercurial 2.1 or earlier will be rebuilt:
+
+  $ echo '7f3676b7004d4616a72ccf9ecccbc5c38e128882 5' > ../target/$branchcache
+  $ echo '1c28f494dae69a2f8fc815059d257eccf3fcfe75 default' >> ../target/$branchcache
+  $ echo 'adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 foo' >> ../target/$branchcache
+  $ echo '7f3676b7004d4616a72ccf9ecccbc5c38e128882 bar' >> ../target/$branchcache
+  $ hg -R ../target branches
+  foo                            4:adf1a74a7f7b
+  default                        3:1c28f494dae6
+
+  $ cat ../target/$branchcache
+  7f3676b7004d4616a72ccf9ecccbc5c38e128882 5
+  _ 1c28f494dae69a2f8fc815059d257eccf3fcfe75 default
+  _ adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 foo
+  c 7f3676b7004d4616a72ccf9ecccbc5c38e128882 bar
 
 Update with no arguments: tipmost revision of the current branch:
 


More information about the Mercurial-devel mailing list