hgext3rd/serverminitopic.py
changeset 3206 3ccde4699cf0
child 3207 35c79686a635
equal deleted inserted replaced
3205:0b85da2e8e2a 3206:3ccde4699cf0
       
     1 """enable a minimal verison of topic for server
       
     2 
       
     3 Non publishing repository will see topic as "branch:topic" in the branch field.
       
     4 
       
     5 In addition to adding the extensions, the feature must be manually enabled in the config:
       
     6 
       
     7     [experimental]
       
     8     server-mini-topic = yes
       
     9 """
       
    10 import hashlib
       
    11 import contextlib
       
    12 
       
    13 from mercurial import (
       
    14     branchmap,
       
    15     context,
       
    16     encoding,
       
    17     extensions,
       
    18     node,
       
    19     registrar,
       
    20     util,
       
    21     wireproto,
       
    22 )
       
    23 
       
    24 if util.safehasattr(registrar, 'configitem'):
       
    25 
       
    26     configtable = {}
       
    27     configitem = registrar.configitem(configtable)
       
    28     configitem('experimental', 'server-mini-topic',
       
    29                default=False,
       
    30     )
       
    31 
       
    32 def hasminitopic(repo):
       
    33     """true if minitopic is enabled on the repository
       
    34 
       
    35     (The value is cached on the repository)
       
    36     """
       
    37     enabled = getattr(repo, '_hasminitopic', None)
       
    38     if enabled is None:
       
    39         enabled = (repo.ui.configbool('experimental', 'server-mini-topic')
       
    40                    and not repo.publishing())
       
    41         repo._hasminitopic = enabled
       
    42     return enabled
       
    43 
       
    44 ### make topic visible though "ctx.branch()"
       
    45 
       
    46 class topicchangectx(context.changectx):
       
    47     """a sunclass of changectx that add topic to the branch name"""
       
    48 
       
    49     def branch(self):
       
    50         branch = super(topicchangectx, self).branch()
       
    51         if hasminitopic(self._repo) and self.phase():
       
    52             topic = self._changeset.extra.get('topic')
       
    53             if topic is not None:
       
    54                 topic = encoding.tolocal(topic)
       
    55                 branch = '%s:%s' % (branch, topic)
       
    56         return branch
       
    57 
       
    58 ### avoid caching topic data in rev-branch-cache
       
    59 
       
    60 class revbranchcacheoverlay(object):
       
    61     """revbranch mixin that don't use the cache for non public changeset"""
       
    62 
       
    63     def _init__(self, *args, **kwargs):
       
    64         super(revbranchcacheoverlay, self).__init__(*args, **kwargs)
       
    65         if 'branchinfo' in vars(self):
       
    66             del self.branchinfo
       
    67 
       
    68     def branchinfo(self, rev):
       
    69         """return branch name and close flag for rev, using and updating
       
    70         persistent cache."""
       
    71         phase = self._repo._phasecache.phase(self, rev)
       
    72         if phase:
       
    73             ctx = self._repo[rev]
       
    74             return ctx.branch(), ctx.closesbranch()
       
    75         return super(revbranchcacheoverlay, self).branchinfo(rev)
       
    76 
       
    77 def reposetup(ui, repo):
       
    78     """install a repo class with a special revbranchcache"""
       
    79 
       
    80     if hasminitopic(repo):
       
    81         repo = repo.unfiltered()
       
    82 
       
    83         class minitopicrepo(repo.__class__):
       
    84             """repository subclass that install the modified cache"""
       
    85 
       
    86             def revbranchcache(self):
       
    87                 if self._revbranchcache is None:
       
    88                     cache = super(minitopicrepo, self).revbranchcache()
       
    89 
       
    90                     class topicawarerbc(revbranchcacheoverlay, cache.__class__):
       
    91                         pass
       
    92                     cache.__class__ = topicawarerbc
       
    93                     if 'branchinfo' in vars(cache):
       
    94                         del cache.branchinfo
       
    95                     self._revbranchcache = cache
       
    96                 return self._revbranchcache
       
    97 
       
    98         repo.__class__ = minitopicrepo
       
    99 
       
   100 ### topic aware branch head cache
       
   101 
       
   102 def _phaseshash(repo, maxrev):
       
   103     """uniq ID for a phase matching a set of rev"""
       
   104     revs = set()
       
   105     cl = repo.changelog
       
   106     fr = cl.filteredrevs
       
   107     nm = cl.nodemap
       
   108     for roots in repo._phasecache.phaseroots[1:]:
       
   109         for n in roots:
       
   110             r = nm.get(n)
       
   111             if r not in fr and r < maxrev:
       
   112                 revs.add(r)
       
   113     key = node.nullid
       
   114     revs = sorted(revs)
       
   115     if revs:
       
   116         s = hashlib.sha1()
       
   117         for rev in revs:
       
   118             s.update('%s;' % rev)
       
   119         key = s.digest()
       
   120     return key
       
   121 
       
   122 # needed to prevent reference used for 'super()' call using in branchmap.py to
       
   123 # no go into cycle. (yes, URG)
       
   124 _oldbranchmap = branchmap.branchcache
       
   125 
       
   126 @contextlib.contextmanager
       
   127 def oldbranchmap():
       
   128     previous = branchmap.branchcache
       
   129     try:
       
   130         branchmap.branchcache = _oldbranchmap
       
   131         yield
       
   132     finally:
       
   133         branchmap.branchcache = previous
       
   134 
       
   135 _publiconly = set([
       
   136     'base',
       
   137     'immutable',
       
   138 ])
       
   139 
       
   140 def mighttopic(repo):
       
   141     return hasminitopic(repo) and repo.filtername not in _publiconly
       
   142 
       
   143 class _topiccache(branchmap.branchcache): # combine me with branchmap.branchcache
       
   144 
       
   145     def __init__(self, *args, **kwargs):
       
   146         # super() call may fail otherwise
       
   147         with oldbranchmap():
       
   148             super(_topiccache, self).__init__(*args, **kwargs)
       
   149         self.phaseshash = None
       
   150 
       
   151     def copy(self):
       
   152         """return an deep copy of the branchcache object"""
       
   153         new = self.__class__(self, self.tipnode, self.tiprev, self.filteredhash,
       
   154                              self._closednodes)
       
   155         new.phaseshash = self.phaseshash
       
   156         return new
       
   157 
       
   158     def validfor(self, repo):
       
   159         """Is the cache content valid regarding a repo
       
   160 
       
   161         - False when cached tipnode is unknown or if we detect a strip.
       
   162         - True when cache is up to date or a subset of current repo."""
       
   163         valid = super(_topiccache, self).validfor(repo)
       
   164         if not valid:
       
   165             return False
       
   166         elif not mighttopic(repo) and self.phaseshash is None:
       
   167             # phasehash at None means this is a branchmap
       
   168             # coming from a public only set
       
   169             return True
       
   170         else:
       
   171             try:
       
   172                 valid = self.phaseshash == _phaseshash(repo, self.tiprev)
       
   173                 return valid
       
   174             except IndexError:
       
   175                 return False
       
   176 
       
   177     def write(self, repo):
       
   178         # we expect (hope) mutable set to be small enough to be that computing
       
   179         # it all the time will be fast enough
       
   180         if not mighttopic(repo):
       
   181             super(_topiccache, self).write(repo)
       
   182 
       
   183     def update(self, repo, revgen):
       
   184         """Given a branchhead cache, self, that may have extra nodes or be
       
   185         missing heads, and a generator of nodes that are strictly a superset of
       
   186         heads missing, this function updates self to be correct.
       
   187         """
       
   188         super(_topiccache, self).update(repo, revgen)
       
   189         if mighttopic(repo):
       
   190             self.phaseshash = _phaseshash(repo, self.tiprev)
       
   191 
       
   192 # advertise topic capabilities
       
   193 
       
   194 def wireprotocaps(orig, repo, proto):
       
   195     caps = orig(repo, proto)
       
   196     if hasminitopic(repo):
       
   197         caps.append('topics')
       
   198     return caps
       
   199 
       
   200 # wrap the necessary bit
       
   201 
       
   202 def wrapclass(container, oldname, new):
       
   203     old = getattr(container, oldname)
       
   204     if not issubclass(old, new):
       
   205         targetclass = new
       
   206         # check if someone else already wrapped the class and handle that
       
   207         if not issubclass(new, old):
       
   208             class targetclass(new, old):
       
   209                 pass
       
   210         setattr(container, oldname, targetclass)
       
   211     current = getattr(container, oldname)
       
   212     assert issubclass(current, new), (current, new, targetclass)
       
   213 
       
   214 def uisetup(ui):
       
   215     wrapclass(context, 'changectx', topicchangectx)
       
   216     wrapclass(branchmap, 'branchcache', _topiccache)
       
   217     extensions.wrapfunction(wireproto, '_capabilities', wireprotocaps)