|
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 |