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

Steven Brown stevengbrown at gmail.com
Wed Feb 8 11:17:18 UTC 2012


On 8 February 2012 03:09, Matt Mackall <mpm at selenic.com> wrote:
> On Tue, 2012-02-07 at 22:48 +0800, Steven Brown wrote:
>> # 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.
>
> What happens on a repo shared on NFS by users with two different
> versions? Our usual solution here is to move to a new cache name when we
> tweak the format.

OK. 'heads' seems like a good name.

>> 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)
>
> Why are we changing the name of everything?

_updatebranchcache still updates the branch cache, but this now
includes the closed state of the heads. The discovery module doesn't
want this information, so it calls updatebranchheads to get the heads
only.

> Why is this no longer marked as an internal method?

It is called from discovery, so it isn't internal to localrepo. Have I
misunderstood the naming convention?

>>      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)
>
> Doesn't match.
>
>>          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)
>> +
>
> This seems redundant?
>
>>      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:
>>
>> _______________________________________________
>> Mercurial-devel mailing list
>> Mercurial-devel at selenic.com
>> http://selenic.com/mailman/listinfo/mercurial-devel
>
>
> --
> Mathematics is the supreme nostalgia of our time.
>
>



More information about the Mercurial-devel mailing list