hgfastobs.py
changeset 801 f49d4774b999
parent 800 ad2060da7ffa
equal deleted inserted replaced
790:5af309865040 801:f49d4774b999
       
     1 """Extension to try and speed up transfer of obsolete markers.
       
     2 
       
     3 Mercurial 2.6 transfers obsolete markers in the dumbest way possible:
       
     4 it simply transfers all of them to the server on every
       
     5 operation. While this /works/, it's not ideal because it's a large
       
     6 amount of extra data for users to pull down (1.9M for the 17k obsolete
       
     7 markers in hg-crew as of this writing in late July 2013). It's also
       
     8 frustrating because this transfer takes a nontrivial amount of time.
       
     9 
       
    10 You can specify a strategy with the config knob
       
    11 obsolete.syncstrategy. Current strategies are "stock" and
       
    12 "boxfill". Default strategy is presently boxfill.
       
    13 
       
    14 :stock: use the default strategy of mercurial explaned above
       
    15 
       
    16 :boxfill: transmit obsolete markers which list any of transmitted changesets as
       
    17           a successor (transitively), as well as any kill markers for dead
       
    18           nodes descended from any of the precursors of outgoing.missing.
       
    19 
       
    20 TODO(durin42): consider better names for sync strategies.
       
    21 """
       
    22 import sys
       
    23 
       
    24 from mercurial import base85
       
    25 from mercurial import commands
       
    26 from mercurial import extensions
       
    27 from mercurial import node
       
    28 from mercurial import obsolete
       
    29 from mercurial import exchange
       
    30 from mercurial import revset
       
    31 from mercurial.i18n import _
       
    32 
       
    33 _strategies = {
       
    34     'stock': exchange._pushobsolete,
       
    35     }
       
    36 
       
    37 def _strategy(name, default=False):
       
    38     def inner(func):
       
    39         _strategies[name] = func
       
    40         if default:
       
    41             _strategies[None] = func
       
    42         return func
       
    43     return inner
       
    44 
       
    45 def _pushobsoletewrapper(orig, pushop):
       
    46     stratfn = _strategies[pushop.repo.ui.config('obsolete', 'syncstrategy')]
       
    47     return stratfn(pushop)
       
    48 
       
    49 extensions.wrapfunction(exchange, '_pushobsolete', _pushobsoletewrapper)
       
    50 
       
    51 def _precursors(repo, s):
       
    52     """Precursor of a changeset"""
       
    53     cs = set()
       
    54     nm = repo.changelog.nodemap
       
    55     markerbysubj = repo.obsstore.precursors
       
    56     for r in s:
       
    57         for p in markerbysubj.get(repo[r].node(), ()):
       
    58             pr = nm.get(p[0])
       
    59             if pr is not None:
       
    60                 cs.add(pr)
       
    61     return cs
       
    62 
       
    63 def _revsetprecursors(repo, subset, x):
       
    64     s = revset.getset(repo, revset.baseset(range(len(repo))), x)
       
    65     cs = _precursors(repo, s)
       
    66     return revset.baseset([r for r in subset if r in cs])
       
    67 
       
    68 revset.symbols['_fastobs_precursors'] = _revsetprecursors
       
    69 
       
    70 
       
    71 @_strategy('boxfill', default=True)
       
    72 def boxfill(pushop):
       
    73     """The "fill in the box" strategy from the 2.6 sprint.
       
    74 
       
    75     See the notes[0] from the 2.6 sprint for what "fill in the box"
       
    76     means here. It's a fairly subtle algorithm, which may have
       
    77     surprising behavior at times, but was the least-bad option
       
    78     proposed at the sprint.
       
    79 
       
    80     [0]: https://bitbucket.org/durin42/2.6sprint-notes/src/tip/mercurial26-obsstore-rev.1398.txt
       
    81     """
       
    82     repo = pushop.repo
       
    83     remote = pushop.remote
       
    84     outgoing = pushop.outgoing
       
    85     urepo = pushop.repo.unfiltered()
       
    86     # need to collect obsolete markers which list any of
       
    87     # outgoing.missing as a successor (transitively), as well as any
       
    88     # kill markers for dead nodes descended from any of the precursors
       
    89     # of outgoing.missing.
       
    90     boxedges = urepo.revs(
       
    91         '(descendants(_fastobs_precursors(%ln)) or '
       
    92         ' descendants(%ln)) and hidden()',
       
    93         outgoing.missing, outgoing.missing)
       
    94     transmit = []
       
    95     for node in outgoing.missing:
       
    96         transmit.extend(obsolete.precursormarkers(urepo[node]))
       
    97     for node in boxedges:
       
    98         transmit.extend(obsolete.successormarkers(urepo[node]))
       
    99     transmit = list(set(transmit))
       
   100     xmit, total = len(transmit), len(repo.obsstore._all)
       
   101     repo.ui.status(
       
   102         'boxpush: about to transmit %d obsolete markers (%d markers total)\n'
       
   103         % (xmit, total))
       
   104     parts, size, chunk = [], 0, 0
       
   105     def transmitmarks():
       
   106             repo.ui.note(
       
   107                 'boxpush: sending a chunk of obsolete markers\n')
       
   108             data = ''.join([obsolete._pack('>B', obsolete._fmversion)] + parts)
       
   109             remote.pushkey('obsolete', 'dump-%d' % chunk, '',
       
   110                            base85.b85encode(data))
       
   111 
       
   112     for marker in transmit:
       
   113         enc = obsolete._encodeonemarker(_markertuple(marker))
       
   114         parts.append(enc)
       
   115         size += len(enc)
       
   116         if size > obsolete._maxpayload:
       
   117             transmitmarks()
       
   118             parts, size = [], 0
       
   119             chunk += 1
       
   120     if parts:
       
   121         transmitmarks()
       
   122 
       
   123 def _markertuple(marker):
       
   124     return marker._data