hgext/simple4server.py
branchmercurial-3.9
changeset 2110 f1ffd093ef30
parent 1816 bb665c99562a
parent 2109 90ab79764ce4
child 2111 ec04eb4d2c6e
child 2261 3e339f6717c7
equal deleted inserted replaced
1816:bb665c99562a 2110:f1ffd093ef30
     1 '''enable experimental obsolescence feature of Mercurial
       
     2 
       
     3 OBSOLESCENCE IS AN EXPERIMENTAL FEATURE MAKE SURE YOU UNDERSTOOD THE INVOLVED
       
     4 CONCEPT BEFORE USING IT.
       
     5 
       
     6 /!\ THIS EXTENSION IS INTENDED FOR SERVER SIDE ONLY USAGE /!\
       
     7 
       
     8 For client side usages it is recommended to use the evolve extension for
       
     9 improved user interface.'''
       
    10 
       
    11 testedwith = '3.3 3.4-rc'
       
    12 buglink = 'https://bz.mercurial-scm.org/'
       
    13 
       
    14 import mercurial.obsolete
       
    15 
       
    16 import hashlib
       
    17 import struct
       
    18 from mercurial import error
       
    19 from mercurial import util
       
    20 from mercurial import wireproto
       
    21 from mercurial import extensions
       
    22 from mercurial import obsolete
       
    23 from cStringIO import StringIO
       
    24 from mercurial import node
       
    25 from mercurial.hgweb import hgweb_mod
       
    26 from mercurial import bundle2
       
    27 from mercurial import localrepo
       
    28 from mercurial import exchange
       
    29 from mercurial import node
       
    30 _pack = struct.pack
       
    31 
       
    32 gboptslist = gboptsmap = None
       
    33 try:
       
    34     from mercurial import obsolete
       
    35     from mercurial import wireproto
       
    36     gboptslist = getattr(wireproto, 'gboptslist', None)
       
    37     gboptsmap = getattr(wireproto, 'gboptsmap', None)
       
    38 except (ImportError, AttributeError):
       
    39     raise error.Abort('Your Mercurial is too old for this version of Evolve\n'
       
    40                       'requires version 3.0.1 or above')
       
    41 
       
    42 # Start of simple4server specific content
       
    43 
       
    44 from mercurial import pushkey
       
    45 
       
    46 # specific content also include the wrapping int extsetup
       
    47 def _nslist(orig, repo):
       
    48     rep = orig(repo)
       
    49     if not repo.ui.configbool('__temporary__', 'advertiseobsolete', True):
       
    50         rep.pop('obsolete')
       
    51     return rep
       
    52 
       
    53 # End of simple4server specific content
       
    54 
       
    55 
       
    56 
       
    57 # from evolve extension: 1a23c7c52a43
       
    58 def srv_pushobsmarkers(repo, proto):
       
    59     """That receives a stream of markers and apply then to the repo"""
       
    60     fp = StringIO()
       
    61     proto.redirect()
       
    62     proto.getfile(fp)
       
    63     data = fp.getvalue()
       
    64     fp.close()
       
    65     lock = repo.lock()
       
    66     try:
       
    67         tr = repo.transaction('pushkey: obsolete markers')
       
    68         try:
       
    69             repo.obsstore.mergemarkers(tr, data)
       
    70             tr.close()
       
    71         finally:
       
    72             tr.release()
       
    73     finally:
       
    74         lock.release()
       
    75     repo.hook('evolve_pushobsmarkers')
       
    76     return wireproto.pushres(0)
       
    77 
       
    78 # from evolve extension: 1a23c7c52a43
       
    79 def _getobsmarkersstream(repo, heads=None, common=None):
       
    80     """Get a binary stream for all markers relevant to `::<heads> - ::<common>`
       
    81     """
       
    82     revset = ''
       
    83     args = []
       
    84     repo = repo.unfiltered()
       
    85     if heads is None:
       
    86         revset = 'all()'
       
    87     elif heads:
       
    88         revset += "(::%ln)"
       
    89         args.append(heads)
       
    90     else:
       
    91         assert False, 'pulling no heads?'
       
    92     if common:
       
    93         revset += ' - (::%ln)'
       
    94         args.append(common)
       
    95     nodes = [c.node() for c in repo.set(revset, *args)]
       
    96     markers = repo.obsstore.relevantmarkers(nodes)
       
    97     obsdata = StringIO()
       
    98     for chunk in obsolete.encodemarkers(markers, True):
       
    99         obsdata.write(chunk)
       
   100     obsdata.seek(0)
       
   101     return obsdata
       
   102 
       
   103 if not util.safehasattr(obsolete.obsstore, 'relevantmarkers'):
       
   104     # from evolve extension: 1a23c7c52a43
       
   105     class pruneobsstore(obsolete.obsstore):
       
   106         """And extended obsstore class that read parent information from v1
       
   107         format
       
   108 
       
   109         Evolve extension adds parent information in prune marker.
       
   110         We use it to make markers relevant to pushed changeset."""
       
   111 
       
   112         def __init__(self, *args, **kwargs):
       
   113             self.prunedchildren = {}
       
   114             return super(pruneobsstore, self).__init__(*args, **kwargs)
       
   115 
       
   116         def _load(self, markers):
       
   117             markers = self._prunedetectingmarkers(markers)
       
   118             return super(pruneobsstore, self)._load(markers)
       
   119 
       
   120 
       
   121         def _prunedetectingmarkers(self, markers):
       
   122             for m in markers:
       
   123                 if not m[1]: # no successors
       
   124                     meta = obsolete.decodemeta(m[3])
       
   125                     if 'p1' in meta:
       
   126                         p1 = node.bin(meta['p1'])
       
   127                         self.prunedchildren.setdefault(p1, set()).add(m)
       
   128                     if 'p2' in meta:
       
   129                         p2 = node.bin(meta['p2'])
       
   130                         self.prunedchildren.setdefault(p2, set()).add(m)
       
   131                 yield m
       
   132 
       
   133     # from evolve extension: 1a23c7c52a43
       
   134     def relevantmarkers(self, nodes):
       
   135         """return a set of all obsolescence marker relevant to a set of node.
       
   136 
       
   137         "relevant" to a set of node mean:
       
   138 
       
   139         - marker that use this changeset as successors
       
   140         - prune marker of direct children on this changeset.
       
   141         - recursive application of the two rules on precursors of these markers
       
   142 
       
   143         It is a set so you cannot rely on order"""
       
   144         seennodes = set(nodes)
       
   145         seenmarkers = set()
       
   146         pendingnodes = set(nodes)
       
   147         precursorsmarkers = self.precursors
       
   148         prunedchildren = self.prunedchildren
       
   149         while pendingnodes:
       
   150             direct = set()
       
   151             for current in pendingnodes:
       
   152                 direct.update(precursorsmarkers.get(current, ()))
       
   153                 direct.update(prunedchildren.get(current, ()))
       
   154             direct -= seenmarkers
       
   155             pendingnodes = set([m[0] for m in direct])
       
   156             seenmarkers |= direct
       
   157             pendingnodes -= seennodes
       
   158             seennodes |= pendingnodes
       
   159         return seenmarkers
       
   160 
       
   161 # The wireproto.streamres API changed, handling chunking and compression
       
   162 # directly. Handle either case.
       
   163 if util.safehasattr(wireproto.abstractserverproto, 'groupchunks'):
       
   164     # We need to handle chunking and compression directly
       
   165     def streamres(d, proto):
       
   166         return wireproto.streamres(proto.groupchunks(d))
       
   167 else:
       
   168     # Leave chunking and compression to streamres
       
   169     def streamres(d, proto):
       
   170         return wireproto.streamres(reader=d, v1compressible=True)
       
   171 
       
   172 # from evolve extension: cf35f38d6a10
       
   173 def srv_pullobsmarkers(repo, proto, others):
       
   174     """serves a binary stream of markers.
       
   175 
       
   176     Serves relevant to changeset between heads and common. The stream is prefix
       
   177     by a -string- representation of an integer. This integer is the size of the
       
   178     stream."""
       
   179     opts = wireproto.options('', ['heads', 'common'], others)
       
   180     for k, v in opts.iteritems():
       
   181         if k in ('heads', 'common'):
       
   182             opts[k] = wireproto.decodelist(v)
       
   183     obsdata = _getobsmarkersstream(repo, **opts)
       
   184     finaldata = StringIO()
       
   185     obsdata = obsdata.getvalue()
       
   186     finaldata.write('%20i' % len(obsdata))
       
   187     finaldata.write(obsdata)
       
   188     finaldata.seek(0)
       
   189     return streamres(finaldata, proto)
       
   190 
       
   191 
       
   192 # from evolve extension: 3249814dabd1
       
   193 def _obsrelsethashtreefm0(repo):
       
   194     return _obsrelsethashtree(repo, obsolete._fm0encodeonemarker)
       
   195 
       
   196 # from evolve extension: 3249814dabd1
       
   197 def _obsrelsethashtreefm1(repo):
       
   198     return _obsrelsethashtree(repo, obsolete._fm1encodeonemarker)
       
   199 
       
   200 # from evolve extension: 3249814dabd1
       
   201 def _obsrelsethashtree(repo, encodeonemarker):
       
   202     cache = []
       
   203     unfi = repo.unfiltered()
       
   204     markercache = {}
       
   205     for i in unfi:
       
   206         ctx = unfi[i]
       
   207         entry = 0
       
   208         sha = hashlib.sha1()
       
   209         # add data from p1
       
   210         for p in ctx.parents():
       
   211             p = p.rev()
       
   212             if p < 0:
       
   213                 p = node.nullid
       
   214             else:
       
   215                 p = cache[p][1]
       
   216             if p != node.nullid:
       
   217                 entry += 1
       
   218                 sha.update(p)
       
   219         tmarkers = repo.obsstore.relevantmarkers([ctx.node()])
       
   220         if tmarkers:
       
   221             bmarkers = []
       
   222             for m in tmarkers:
       
   223                 if not m in markercache:
       
   224                     markercache[m] = encodeonemarker(m)
       
   225                 bmarkers.append(markercache[m])
       
   226             bmarkers.sort()
       
   227             for m in bmarkers:
       
   228                 entry += 1
       
   229                 sha.update(m)
       
   230         if entry:
       
   231             cache.append((ctx.node(), sha.digest()))
       
   232         else:
       
   233             cache.append((ctx.node(), node.nullid))
       
   234     return cache
       
   235 
       
   236 # from evolve extension: 3249814dabd1
       
   237 def _obshash(repo, nodes, version=0):
       
   238     if version == 0:
       
   239         hashs = _obsrelsethashtreefm0(repo)
       
   240     elif version ==1:
       
   241         hashs = _obsrelsethashtreefm1(repo)
       
   242     else:
       
   243         assert False
       
   244     nm = repo.changelog.nodemap
       
   245     revs = [nm.get(n) for n in nodes]
       
   246     return [r is None and node.nullid or hashs[r][1] for r in revs]
       
   247 
       
   248 # from evolve extension: 3249814dabd1
       
   249 def srv_obshash(repo, proto, nodes):
       
   250     return wireproto.encodelist(_obshash(repo, wireproto.decodelist(nodes)))
       
   251 
       
   252 # from evolve extension: 3249814dabd1
       
   253 def srv_obshash1(repo, proto, nodes):
       
   254     return wireproto.encodelist(_obshash(repo, wireproto.decodelist(nodes),
       
   255                                 version=1))
       
   256 
       
   257 # from evolve extension: 3249814dabd1
       
   258 def capabilities(orig, repo, proto):
       
   259     """wrapper to advertise new capability"""
       
   260     caps = orig(repo, proto)
       
   261     advertise = repo.ui.configbool('__temporary__', 'advertiseobsolete', True)
       
   262     if obsolete.isenabled(repo, obsolete.exchangeopt) and advertise:
       
   263         caps += ' _evoext_pushobsmarkers_0'
       
   264         caps += ' _evoext_pullobsmarkers_0'
       
   265         caps += ' _evoext_obshash_0'
       
   266         caps += ' _evoext_obshash_1'
       
   267         caps += ' _evoext_getbundle_obscommon'
       
   268     return caps
       
   269 
       
   270 def _getbundleobsmarkerpart(orig, bundler, repo, source, **kwargs):
       
   271     if 'evo_obscommon' not in kwargs:
       
   272         return orig(bundler, repo, source, **kwargs)
       
   273 
       
   274     heads = kwargs.get('heads')
       
   275     if 'evo_obscommon' not in kwargs:
       
   276         return orig(bundler, repo, source, **kwargs)
       
   277 
       
   278     if kwargs.get('obsmarkers', False):
       
   279         if heads is None:
       
   280             heads = repo.heads()
       
   281         obscommon = kwargs.get('evo_obscommon', ())
       
   282         obsset = repo.set('::%ln - ::%ln', heads, obscommon)
       
   283         subset = [c.node() for c in obsset]
       
   284         markers = repo.obsstore.relevantmarkers(subset)
       
   285         exchange.buildobsmarkerspart(bundler, markers)
       
   286 
       
   287 # from evolve extension: 10867a8e27c6
       
   288 # heavily modified
       
   289 def extsetup(ui):
       
   290     localrepo.moderncaps.add('_evoext_b2x_obsmarkers_0')
       
   291     gboptsmap['evo_obscommon'] = 'nodes'
       
   292     if not util.safehasattr(obsolete.obsstore, 'relevantmarkers'):
       
   293         obsolete.obsstore = pruneobsstore
       
   294         obsolete.obsstore.relevantmarkers = relevantmarkers
       
   295     hgweb_mod.perms['evoext_pushobsmarkers_0'] = 'push'
       
   296     hgweb_mod.perms['evoext_pullobsmarkers_0'] = 'pull'
       
   297     hgweb_mod.perms['evoext_obshash'] = 'pull'
       
   298     wireproto.commands['evoext_pushobsmarkers_0'] = (srv_pushobsmarkers, '')
       
   299     wireproto.commands['evoext_pullobsmarkers_0'] = (srv_pullobsmarkers, '*')
       
   300     # wrap module content
       
   301     origfunc = exchange.getbundle2partsmapping['obsmarkers']
       
   302     def newfunc(*args, **kwargs):
       
   303         return _getbundleobsmarkerpart(origfunc, *args, **kwargs)
       
   304     exchange.getbundle2partsmapping['obsmarkers'] = newfunc
       
   305     extensions.wrapfunction(wireproto, 'capabilities', capabilities)
       
   306     # wrap command content
       
   307     oldcap, args = wireproto.commands['capabilities']
       
   308     def newcap(repo, proto):
       
   309         return capabilities(oldcap, repo, proto)
       
   310     wireproto.commands['capabilities'] = (newcap, args)
       
   311     wireproto.commands['evoext_obshash'] = (srv_obshash, 'nodes')
       
   312     wireproto.commands['evoext_obshash1'] = (srv_obshash1, 'nodes')
       
   313     # specific simple4server content
       
   314     extensions.wrapfunction(pushkey, '_nslist', _nslist)
       
   315     pushkey._namespaces['namespaces'] = (lambda *x: False, pushkey._nslist)
       
   316 
       
   317 def reposetup(ui, repo):
       
   318     evolveopts = ui.configlist('experimental', 'evolution')
       
   319     if not evolveopts:
       
   320         evolveopts = 'all'
       
   321         ui.setconfig('experimental', 'evolution', evolveopts)