hgext3rd/topic/topicmap.py
changeset 1901 85390446f8c1
parent 1890 e846b8f402d0
child 1927 cf33ba7fbf4b
equal deleted inserted replaced
1899:b65f39791f92 1901:85390446f8c1
       
     1 from mercurial import branchmap
       
     2 from mercurial import encoding
       
     3 from mercurial import error
       
     4 from mercurial import scmutil
       
     5 from mercurial import util
       
     6 from mercurial.node import hex, bin, nullid
       
     7 
       
     8 def _filename(repo):
       
     9     """name of a branchcache file for a given repo or repoview"""
       
    10     filename = "cache/topicmap"
       
    11     if repo.filtername:
       
    12         filename = '%s-%s' % (filename, repo.filtername)
       
    13     return filename
       
    14 
       
    15 oldbranchcache = branchmap.branchcache
       
    16 
       
    17 def _phaseshash(repo, maxrev):
       
    18     revs = set()
       
    19     cl = repo.changelog
       
    20     fr = cl.filteredrevs
       
    21     nm = cl.nodemap
       
    22     for roots in repo._phasecache.phaseroots[1:]:
       
    23         for n in roots:
       
    24             r = nm.get(n)
       
    25             if r not in fr and r < maxrev:
       
    26                 revs.add(r)
       
    27     key = nullid
       
    28     revs = sorted(revs)
       
    29     if revs:
       
    30         s = util.sha1()
       
    31         for rev in revs:
       
    32             s.update('%s;' % rev)
       
    33         key = s.digest()
       
    34     return key
       
    35 
       
    36 class topiccache(oldbranchcache):
       
    37 
       
    38     def __init__(self, *args, **kwargs):
       
    39         otherbranchcache = branchmap.branchcache
       
    40         try:
       
    41             # super() call may fail otherwise
       
    42             branchmap.branchcache = oldbranchcache
       
    43             super(topiccache, self).__init__(*args, **kwargs)
       
    44             if self.filteredhash is None:
       
    45                 self.filteredhash = nullid
       
    46             self.phaseshash = nullid
       
    47         finally:
       
    48             branchmap.branchcache = otherbranchcache
       
    49 
       
    50     def copy(self):
       
    51         """return an deep copy of the branchcache object"""
       
    52         new =  topiccache(self, self.tipnode, self.tiprev, self.filteredhash,
       
    53                           self._closednodes)
       
    54         if self.filteredhash is None:
       
    55             self.filteredhash = nullid
       
    56         new.phaseshash = self.phaseshash
       
    57         return new
       
    58 
       
    59     def branchtip(self, branch, topic=''):
       
    60         '''Return the tipmost open head on branch head, otherwise return the
       
    61         tipmost closed head on branch.
       
    62         Raise KeyError for unknown branch.'''
       
    63         if topic:
       
    64             branch = '%s:%s' % (branch, topic)
       
    65         return super(topiccache, self).branchtip(branch)
       
    66 
       
    67     def branchheads(self, branch, closed=False, topic=''):
       
    68         if topic:
       
    69             branch = '%s:%s' % (branch, topic)
       
    70         return super(topiccache, self).branchheads(branch, closed=closed)
       
    71 
       
    72     def validfor(self, repo):
       
    73         """Is the cache content valid regarding a repo
       
    74 
       
    75         - False when cached tipnode is unknown or if we detect a strip.
       
    76         - True when cache is up to date or a subset of current repo."""
       
    77         # This is copy paste of mercurial.branchmap.branchcache.validfor in
       
    78         # 69077c65919d With a small changes to the cache key handling to
       
    79         # include phase information that impact the topic cache.
       
    80         #
       
    81         # All code changes should be flagged on site.
       
    82         try:
       
    83             if (self.tipnode == repo.changelog.node(self.tiprev)):
       
    84                 fh = scmutil.filteredhash(repo, self.tiprev)
       
    85                 if fh is None:
       
    86                     fh = nullid
       
    87                 if ((self.filteredhash == fh)
       
    88                      and (self.phaseshash == _phaseshash(repo, self.tiprev))):
       
    89                     return True
       
    90             return False
       
    91         except IndexError:
       
    92             return False
       
    93 
       
    94     def write(self, repo):
       
    95         # This is copy paste of mercurial.branchmap.branchcache.write in
       
    96         # 69077c65919d With a small changes to the cache key handling to
       
    97         # include phase information that impact the topic cache.
       
    98         #
       
    99         # All code changes should be flagged on site.
       
   100         try:
       
   101             f = repo.vfs(_filename(repo), "w", atomictemp=True)
       
   102             cachekey = [hex(self.tipnode), str(self.tiprev)]
       
   103             # [CHANGE] we need a hash in all cases
       
   104             assert self.filteredhash is not None
       
   105             cachekey.append(hex(self.filteredhash))
       
   106             cachekey.append(hex(self.phaseshash))
       
   107             f.write(" ".join(cachekey) + '\n')
       
   108             nodecount = 0
       
   109             for label, nodes in sorted(self.iteritems()):
       
   110                 for node in nodes:
       
   111                     nodecount += 1
       
   112                     if node in self._closednodes:
       
   113                         state = 'c'
       
   114                     else:
       
   115                         state = 'o'
       
   116                     f.write("%s %s %s\n" % (hex(node), state,
       
   117                                             encoding.fromlocal(label)))
       
   118             f.close()
       
   119             repo.ui.log('branchcache',
       
   120                         'wrote %s branch cache with %d labels and %d nodes\n',
       
   121                         repo.filtername, len(self), nodecount)
       
   122         except (IOError, OSError, error.Abort) as inst:
       
   123             repo.ui.debug("couldn't write branch cache: %s\n" % inst)
       
   124             # Abort may be raise by read only opener
       
   125             pass
       
   126 
       
   127     def update(self, repo, revgen):
       
   128         """Given a branchhead cache, self, that may have extra nodes or be
       
   129         missing heads, and a generator of nodes that are strictly a superset of
       
   130         heads missing, this function updates self to be correct.
       
   131         """
       
   132         oldgetbranchinfo = repo.revbranchcache().branchinfo
       
   133         try:
       
   134             def branchinfo(r):
       
   135                 info = oldgetbranchinfo(r)
       
   136                 topic = ''
       
   137                 ctx = repo[r]
       
   138                 if ctx.mutable():
       
   139                     topic = ctx.topic()
       
   140                 branch = info[0]
       
   141                 if topic:
       
   142                     branch = '%s:%s' % (branch, topic)
       
   143                 return (branch, info[1])
       
   144             repo.revbranchcache().branchinfo = branchinfo
       
   145             super(topiccache, self).update(repo, revgen)
       
   146             if self.filteredhash is None:
       
   147                 self.filteredhash = nullid
       
   148             self.phaseshash = _phaseshash(repo, self.tiprev)
       
   149         finally:
       
   150             repo.revbranchcache().branchinfo = oldgetbranchinfo
       
   151 
       
   152 def readtopicmap(repo):
       
   153     # This is copy paste of mercurial.branchmap.read in 69077c65919d
       
   154     # With a small changes to the cache key handling to include phase
       
   155     # information that impact the topic cache.
       
   156     #
       
   157     # All code changes should be flagged on site.
       
   158     try:
       
   159         f = repo.vfs(_filename(repo))
       
   160         lines = f.read().split('\n')
       
   161         f.close()
       
   162     except (IOError, OSError):
       
   163         return None
       
   164 
       
   165     try:
       
   166         cachekey = lines.pop(0).split(" ", 2)
       
   167         last, lrev = cachekey[:2]
       
   168         last, lrev = bin(last), int(lrev)
       
   169         filteredhash = bin(cachekey[2]) # [CHANGE] unconditional filteredhash
       
   170         partial = branchcache(tipnode=last, tiprev=lrev,
       
   171                               filteredhash=filteredhash)
       
   172         partial.phaseshash = bin(cachekey[3]) # [CHANGE] read phaseshash
       
   173         if not partial.validfor(repo):
       
   174             # invalidate the cache
       
   175             raise ValueError('tip differs')
       
   176         cl = repo.changelog
       
   177         for l in lines:
       
   178             if not l:
       
   179                 continue
       
   180             node, state, label = l.split(" ", 2)
       
   181             if state not in 'oc':
       
   182                 raise ValueError('invalid branch state')
       
   183             label = encoding.tolocal(label.strip())
       
   184             node = bin(node)
       
   185             if not cl.hasnode(node):
       
   186                 raise ValueError('node %s does not exist' % hex(node))
       
   187             partial.setdefault(label, []).append(node)
       
   188             if state == 'c':
       
   189                 partial._closednodes.add(node)
       
   190     except KeyboardInterrupt:
       
   191         raise
       
   192     except Exception as inst:
       
   193         if repo.ui.debugflag:
       
   194             msg = 'invalid branchheads cache'
       
   195             if repo.filtername is not None:
       
   196                 msg += ' (%s)' % repo.filtername
       
   197             msg += ': %s\n'
       
   198             repo.ui.debug(msg % inst)
       
   199         partial = None
       
   200     return partial