# HG changeset patch # User Brodie Rao # Date 1379318909 25200 # Node ID 7d421951282328b9ec8b1884465452f92c8ae976 # Parent a14d93b2fb1bd23d4084d7e7c1a76a3f41efe8cf 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). diff -r a14d93b2fb1b -r 7d4219512823 mercurial/branchmap.py --- 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: [optional filtered repo hex hash] - - + + ... 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) diff -r a14d93b2fb1b -r 7d4219512823 mercurial/changelog.py --- 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 diff -r a14d93b2fb1b -r 7d4219512823 mercurial/hg.py --- 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) diff -r a14d93b2fb1b -r 7d4219512823 tests/test-fncache.t --- 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 diff -r a14d93b2fb1b -r 7d4219512823 tests/test-hardlinks.t --- 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 diff -r a14d93b2fb1b -r 7d4219512823 tests/test-inherit-mode.t --- 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 diff -r a14d93b2fb1b -r 7d4219512823 tests/test-newbranch.t --- 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: diff -r a14d93b2fb1b -r 7d4219512823 tests/test-phases.t --- 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. diff -r a14d93b2fb1b -r 7d4219512823 tests/test-rebase-collapse.t --- 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