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