branchmap: cache open/closed branch head information
authorBrodie Rao <brodie@sf.io>
Mon, 16 Sep 2013 01:08:29 -0700
changeset 20185 7d4219512823
parent 20184 a14d93b2fb1b
child 20186 f5b461a4bc55
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).
mercurial/branchmap.py
mercurial/changelog.py
mercurial/hg.py
tests/test-fncache.t
tests/test-hardlinks.t
tests/test-inherit-mode.t
tests/test-newbranch.t
tests/test-phases.t
tests/test-rebase-collapse.t
--- 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)
--- a/mercurial/changelog.py	Mon Nov 11 21:16:54 2013 +1100
+++ b/mercurial/changelog.py	Mon Sep 16 01:08:29 2013 -0700
@@ -342,9 +342,10 @@
         text = "\n".join(l)
         return self.addrevision(text, transaction, len(self), p1, p2)
 
-    def branch(self, rev):
-        """return the branch of a revision
+    def branchinfo(self, rev):
+        """return the branch name and open/close state of a revision
 
         This function exists because creating a changectx object
         just to access this is costly."""
-        return encoding.tolocal(self.read(rev)[5].get("branch"))
+        extra = self.read(rev)[5]
+        return encoding.tolocal(extra.get("branch")), 'close' in extra
--- a/mercurial/hg.py	Mon Nov 11 21:16:54 2013 +1100
+++ b/mercurial/hg.py	Mon Sep 16 01:08:29 2013 -0700
@@ -338,8 +338,8 @@
             # Recomputing branch cache might be slow on big repos,
             # so just copy it
             dstcachedir = os.path.join(destpath, 'cache')
-            srcbranchcache = srcrepo.sjoin('cache/branchheads')
-            dstbranchcache = os.path.join(dstcachedir, 'branchheads')
+            srcbranchcache = srcrepo.sjoin('cache/branch2')
+            dstbranchcache = os.path.join(dstcachedir, 'branch2')
             if os.path.exists(srcbranchcache):
                 if not os.path.exists(dstcachedir):
                     os.mkdir(dstcachedir)
--- a/tests/test-fncache.t	Mon Nov 11 21:16:54 2013 +1100
+++ b/tests/test-fncache.t	Mon Sep 16 01:08:29 2013 -0700
@@ -70,7 +70,7 @@
   .hg/00changelog.i
   .hg/00manifest.i
   .hg/cache
-  .hg/cache/branchheads-served
+  .hg/cache/branch2-served
   .hg/data
   .hg/data/tst.d.hg
   .hg/data/tst.d.hg/foo.i
@@ -98,7 +98,7 @@
   .hg
   .hg/00changelog.i
   .hg/cache
-  .hg/cache/branchheads-served
+  .hg/cache/branch2-served
   .hg/dirstate
   .hg/last-message.txt
   .hg/requires
--- a/tests/test-hardlinks.t	Mon Nov 11 21:16:54 2013 +1100
+++ b/tests/test-hardlinks.t	Mon Sep 16 01:08:29 2013 -0700
@@ -196,7 +196,7 @@
   $ nlinksdir r4
   2 r4/.hg/00changelog.i
   2 r4/.hg/branch
-  2 r4/.hg/cache/branchheads-served
+  2 r4/.hg/cache/branch2-served
   2 r4/.hg/dirstate
   2 r4/.hg/hgrc
   2 r4/.hg/last-message.txt
@@ -226,7 +226,7 @@
   $ nlinksdir r4
   2 r4/.hg/00changelog.i
   1 r4/.hg/branch
-  2 r4/.hg/cache/branchheads-served
+  2 r4/.hg/cache/branch2-served
   1 r4/.hg/dirstate
   2 r4/.hg/hgrc
   2 r4/.hg/last-message.txt
--- a/tests/test-inherit-mode.t	Mon Nov 11 21:16:54 2013 +1100
+++ b/tests/test-inherit-mode.t	Mon Sep 16 01:08:29 2013 -0700
@@ -66,7 +66,7 @@
   00700 ./.hg/
   00600 ./.hg/00changelog.i
   00770 ./.hg/cache/
-  00660 ./.hg/cache/branchheads-served
+  00660 ./.hg/cache/branch2-served
   00660 ./.hg/dirstate
   00660 ./.hg/last-message.txt
   00600 ./.hg/requires
@@ -111,7 +111,7 @@
   00770 ../push/.hg/
   00660 ../push/.hg/00changelog.i
   00770 ../push/.hg/cache/
-  00660 ../push/.hg/cache/branchheads-base
+  00660 ../push/.hg/cache/branch2-base
   00660 ../push/.hg/requires
   00770 ../push/.hg/store/
   00660 ../push/.hg/store/00changelog.i
--- a/tests/test-newbranch.t	Mon Nov 11 21:16:54 2013 +1100
+++ b/tests/test-newbranch.t	Mon Sep 16 01:08:29 2013 -0700
@@ -1,13 +1,13 @@
-  $ branchcache=.hg/cache/branchheads
+  $ branchcache=.hg/cache/branch2
 
   $ listbranchcaches() {
-  >    for f in .hg/cache/branchheads*;
+  >    for f in .hg/cache/branch2*;
   >       do echo === $f ===;
   >       cat $f;
   >     done;
   > }
   $ purgebranchcaches() {
-  >     rm .hg/cache/branchheads*
+  >     rm .hg/cache/branch2*
   > }
 
   $ hg init t
@@ -158,13 +158,13 @@
   4:adf1a74a7f7b
 
   $ listbranchcaches
-  === .hg/cache/branchheads ===
+  === .hg/cache/branch2 ===
   corrupted
-  === .hg/cache/branchheads-served ===
+  === .hg/cache/branch2-served ===
   adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 4
-  c21617b13b220988e7a2e26290fbe4325ffa7139 bar
-  1c28f494dae69a2f8fc815059d257eccf3fcfe75 default
-  adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 foo
+  c21617b13b220988e7a2e26290fbe4325ffa7139 o bar
+  1c28f494dae69a2f8fc815059d257eccf3fcfe75 o default
+  adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 o foo
 
 Push should update the branch cache:
 
@@ -175,20 +175,20 @@
   $ hg push -qr 0 ../target
 
   $ (cd ../target/; listbranchcaches)
-  === .hg/cache/branchheads-base ===
+  === .hg/cache/branch2-base ===
   db01e8ea3388fd3c7c94e1436ea2bd6a53d581c5 0
-  db01e8ea3388fd3c7c94e1436ea2bd6a53d581c5 default
+  db01e8ea3388fd3c7c94e1436ea2bd6a53d581c5 o default
 
 Pushing everything:
 
   $ hg push -qf ../target
 
   $ (cd ../target/; listbranchcaches)
-  === .hg/cache/branchheads-base ===
+  === .hg/cache/branch2-base ===
   adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 4
-  c21617b13b220988e7a2e26290fbe4325ffa7139 bar
-  1c28f494dae69a2f8fc815059d257eccf3fcfe75 default
-  adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 foo
+  c21617b13b220988e7a2e26290fbe4325ffa7139 o bar
+  1c28f494dae69a2f8fc815059d257eccf3fcfe75 o default
+  adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 o foo
 
 Update with no arguments: tipmost revision of the current branch:
 
--- a/tests/test-phases.t	Mon Nov 11 21:16:54 2013 +1100
+++ b/tests/test-phases.t	Mon Sep 16 01:08:29 2013 -0700
@@ -175,28 +175,28 @@
 
 check that branch cache with "served" filter are properly computed and stored
 
-  $ ls ../push-dest/.hg/cache/branchheads*
-  ../push-dest/.hg/cache/branchheads-served
-  $ cat ../push-dest/.hg/cache/branchheads-served
+  $ ls ../push-dest/.hg/cache/branch2*
+  ../push-dest/.hg/cache/branch2-served
+  $ cat ../push-dest/.hg/cache/branch2-served
   6d6770faffce199f1fddd1cf87f6f026138cf061 6 465891ffab3c47a3c23792f7dc84156e19a90722
-  b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e default
-  6d6770faffce199f1fddd1cf87f6f026138cf061 default
+  b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
+  6d6770faffce199f1fddd1cf87f6f026138cf061 o default
   $ hg heads -R ../push-dest --template '{rev}:{node} {phase}\n'  #update visible cache too
   6:6d6770faffce199f1fddd1cf87f6f026138cf061 draft
   5:2713879da13d6eea1ff22b442a5a87cb31a7ce6a secret
   3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e draft
-  $ ls ../push-dest/.hg/cache/branchheads*
-  ../push-dest/.hg/cache/branchheads-served
-  ../push-dest/.hg/cache/branchheads-visible
-  $ cat ../push-dest/.hg/cache/branchheads-served
+  $ ls ../push-dest/.hg/cache/branch2*
+  ../push-dest/.hg/cache/branch2-served
+  ../push-dest/.hg/cache/branch2-visible
+  $ cat ../push-dest/.hg/cache/branch2-served
   6d6770faffce199f1fddd1cf87f6f026138cf061 6 465891ffab3c47a3c23792f7dc84156e19a90722
-  b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e default
-  6d6770faffce199f1fddd1cf87f6f026138cf061 default
-  $ cat ../push-dest/.hg/cache/branchheads-visible
+  b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
+  6d6770faffce199f1fddd1cf87f6f026138cf061 o default
+  $ cat ../push-dest/.hg/cache/branch2-visible
   6d6770faffce199f1fddd1cf87f6f026138cf061 6
-  b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e default
-  2713879da13d6eea1ff22b442a5a87cb31a7ce6a default
-  6d6770faffce199f1fddd1cf87f6f026138cf061 default
+  b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
+  2713879da13d6eea1ff22b442a5a87cb31a7ce6a o default
+  6d6770faffce199f1fddd1cf87f6f026138cf061 o default
 
 
 Restore condition prior extra insertion.
--- a/tests/test-rebase-collapse.t	Mon Nov 11 21:16:54 2013 +1100
+++ b/tests/test-rebase-collapse.t	Mon Sep 16 01:08:29 2013 -0700
@@ -274,18 +274,18 @@
   7:c65502d4178782309ce0574c5ae6ee9485a9bafa default
   6:c772a8b2dc17629cec88a19d09c926c4814b12c7 default
 
-  $ cat $TESTTMP/b2/.hg/cache/branchheads-served
+  $ cat $TESTTMP/b2/.hg/cache/branch2-served
   c65502d4178782309ce0574c5ae6ee9485a9bafa 7
-  c772a8b2dc17629cec88a19d09c926c4814b12c7 default
-  c65502d4178782309ce0574c5ae6ee9485a9bafa default
+  c772a8b2dc17629cec88a19d09c926c4814b12c7 o default
+  c65502d4178782309ce0574c5ae6ee9485a9bafa o default
 
   $ hg strip 4
   saved backup bundle to $TESTTMP/b2/.hg/strip-backup/8a5212ebc852-backup.hg (glob)
 
-  $ cat $TESTTMP/b2/.hg/cache/branchheads-served
+  $ cat $TESTTMP/b2/.hg/cache/branch2-served
   c65502d4178782309ce0574c5ae6ee9485a9bafa 4
-  2870ad076e541e714f3c2bc32826b5c6a6e5b040 default
-  c65502d4178782309ce0574c5ae6ee9485a9bafa default
+  2870ad076e541e714f3c2bc32826b5c6a6e5b040 o default
+  c65502d4178782309ce0574c5ae6ee9485a9bafa o default
 
   $ hg heads --template="{rev}:{node} {branch}\n"
   4:c65502d4178782309ce0574c5ae6ee9485a9bafa default