##// END OF EJS Templates
revlog: adapt the `reading` check for `bundlerepo`...
marmoute -
r51905:3470a39f default
parent child Browse files
Show More
@@ -1,742 +1,751 b''
1 # bundlerepo.py - repository class for viewing uncompressed bundles
1 # bundlerepo.py - repository class for viewing uncompressed bundles
2 #
2 #
3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Repository class for viewing uncompressed bundles.
8 """Repository class for viewing uncompressed bundles.
9
9
10 This provides a read-only repository interface to bundles as if they
10 This provides a read-only repository interface to bundles as if they
11 were part of the actual repository.
11 were part of the actual repository.
12 """
12 """
13
13
14
14
15 import contextlib
15 import os
16 import os
16 import shutil
17 import shutil
17
18
18 from .i18n import _
19 from .i18n import _
19 from .node import (
20 from .node import (
20 hex,
21 hex,
21 nullrev,
22 nullrev,
22 )
23 )
23
24
24 from . import (
25 from . import (
25 bundle2,
26 bundle2,
26 changegroup,
27 changegroup,
27 changelog,
28 changelog,
28 cmdutil,
29 cmdutil,
29 discovery,
30 discovery,
30 encoding,
31 encoding,
31 error,
32 error,
32 exchange,
33 exchange,
33 filelog,
34 filelog,
34 localrepo,
35 localrepo,
35 manifest,
36 manifest,
36 mdiff,
37 mdiff,
37 pathutil,
38 pathutil,
38 phases,
39 phases,
39 pycompat,
40 pycompat,
40 revlog,
41 revlog,
41 revlogutils,
42 revlogutils,
42 util,
43 util,
43 vfs as vfsmod,
44 vfs as vfsmod,
44 )
45 )
45 from .utils import (
46 from .utils import (
46 urlutil,
47 urlutil,
47 )
48 )
48
49
49 from .revlogutils import (
50 from .revlogutils import (
50 constants as revlog_constants,
51 constants as revlog_constants,
51 )
52 )
52
53
53
54
54 class bundlerevlog(revlog.revlog):
55 class bundlerevlog(revlog.revlog):
55 def __init__(self, opener, target, radix, cgunpacker, linkmapper):
56 def __init__(self, opener, target, radix, cgunpacker, linkmapper):
56 # How it works:
57 # How it works:
57 # To retrieve a revision, we need to know the offset of the revision in
58 # To retrieve a revision, we need to know the offset of the revision in
58 # the bundle (an unbundle object). We store this offset in the index
59 # the bundle (an unbundle object). We store this offset in the index
59 # (start). The base of the delta is stored in the base field.
60 # (start). The base of the delta is stored in the base field.
60 #
61 #
61 # To differentiate a rev in the bundle from a rev in the revlog, we
62 # To differentiate a rev in the bundle from a rev in the revlog, we
62 # check revision against repotiprev.
63 # check revision against repotiprev.
63 opener = vfsmod.readonlyvfs(opener)
64 opener = vfsmod.readonlyvfs(opener)
64 revlog.revlog.__init__(self, opener, target=target, radix=radix)
65 revlog.revlog.__init__(self, opener, target=target, radix=radix)
65 self.bundle = cgunpacker
66 self.bundle = cgunpacker
66 n = len(self)
67 n = len(self)
67 self.repotiprev = n - 1
68 self.repotiprev = n - 1
68 self.bundlerevs = set() # used by 'bundle()' revset expression
69 self.bundlerevs = set() # used by 'bundle()' revset expression
69 for deltadata in cgunpacker.deltaiter():
70 for deltadata in cgunpacker.deltaiter():
70 node, p1, p2, cs, deltabase, delta, flags, sidedata = deltadata
71 node, p1, p2, cs, deltabase, delta, flags, sidedata = deltadata
71
72
72 size = len(delta)
73 size = len(delta)
73 start = cgunpacker.tell() - size
74 start = cgunpacker.tell() - size
74
75
75 if self.index.has_node(node):
76 if self.index.has_node(node):
76 # this can happen if two branches make the same change
77 # this can happen if two branches make the same change
77 self.bundlerevs.add(self.index.rev(node))
78 self.bundlerevs.add(self.index.rev(node))
78 continue
79 continue
79 if cs == node:
80 if cs == node:
80 linkrev = nullrev
81 linkrev = nullrev
81 else:
82 else:
82 linkrev = linkmapper(cs)
83 linkrev = linkmapper(cs)
83
84
84 for p in (p1, p2):
85 for p in (p1, p2):
85 if not self.index.has_node(p):
86 if not self.index.has_node(p):
86 raise error.LookupError(
87 raise error.LookupError(
87 p, self.display_id, _(b"unknown parent")
88 p, self.display_id, _(b"unknown parent")
88 )
89 )
89
90
90 if not self.index.has_node(deltabase):
91 if not self.index.has_node(deltabase):
91 raise error.LookupError(
92 raise error.LookupError(
92 deltabase, self.display_id, _(b'unknown delta base')
93 deltabase, self.display_id, _(b'unknown delta base')
93 )
94 )
94
95
95 baserev = self.rev(deltabase)
96 baserev = self.rev(deltabase)
96 # start, size, full unc. size, base (unused), link, p1, p2, node, sidedata_offset (unused), sidedata_size (unused)
97 # start, size, full unc. size, base (unused), link, p1, p2, node, sidedata_offset (unused), sidedata_size (unused)
97 e = revlogutils.entry(
98 e = revlogutils.entry(
98 flags=flags,
99 flags=flags,
99 data_offset=start,
100 data_offset=start,
100 data_compressed_length=size,
101 data_compressed_length=size,
101 data_delta_base=baserev,
102 data_delta_base=baserev,
102 link_rev=linkrev,
103 link_rev=linkrev,
103 parent_rev_1=self.rev(p1),
104 parent_rev_1=self.rev(p1),
104 parent_rev_2=self.rev(p2),
105 parent_rev_2=self.rev(p2),
105 node_id=node,
106 node_id=node,
106 )
107 )
107 self.index.append(e)
108 self.index.append(e)
108 self.bundlerevs.add(n)
109 self.bundlerevs.add(n)
109 n += 1
110 n += 1
110
111
112 @contextlib.contextmanager
113 def reading(self):
114 if self.repotiprev < 0:
115 yield
116 else:
117 with super().reading() as x:
118 yield x
119
111 def _chunk(self, rev, df=None):
120 def _chunk(self, rev, df=None):
112 # Warning: in case of bundle, the diff is against what we stored as
121 # Warning: in case of bundle, the diff is against what we stored as
113 # delta base, not against rev - 1
122 # delta base, not against rev - 1
114 # XXX: could use some caching
123 # XXX: could use some caching
115 if rev <= self.repotiprev:
124 if rev <= self.repotiprev:
116 return revlog.revlog._chunk(self, rev)
125 return revlog.revlog._chunk(self, rev)
117 self.bundle.seek(self.start(rev))
126 self.bundle.seek(self.start(rev))
118 return self.bundle.read(self.length(rev))
127 return self.bundle.read(self.length(rev))
119
128
120 def revdiff(self, rev1, rev2):
129 def revdiff(self, rev1, rev2):
121 """return or calculate a delta between two revisions"""
130 """return or calculate a delta between two revisions"""
122 if rev1 > self.repotiprev and rev2 > self.repotiprev:
131 if rev1 > self.repotiprev and rev2 > self.repotiprev:
123 # hot path for bundle
132 # hot path for bundle
124 revb = self.index[rev2][3]
133 revb = self.index[rev2][3]
125 if revb == rev1:
134 if revb == rev1:
126 return self._chunk(rev2)
135 return self._chunk(rev2)
127 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
136 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
128 return revlog.revlog.revdiff(self, rev1, rev2)
137 return revlog.revlog.revdiff(self, rev1, rev2)
129
138
130 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
139 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
131
140
132 def _rawtext(self, node, rev, _df=None):
141 def _rawtext(self, node, rev, _df=None):
133 if rev is None:
142 if rev is None:
134 rev = self.rev(node)
143 rev = self.rev(node)
135 validated = False
144 validated = False
136 rawtext = None
145 rawtext = None
137 chain = []
146 chain = []
138 iterrev = rev
147 iterrev = rev
139 # reconstruct the revision if it is from a changegroup
148 # reconstruct the revision if it is from a changegroup
140 while iterrev > self.repotiprev:
149 while iterrev > self.repotiprev:
141 if self._revisioncache and self._revisioncache[1] == iterrev:
150 if self._revisioncache and self._revisioncache[1] == iterrev:
142 rawtext = self._revisioncache[2]
151 rawtext = self._revisioncache[2]
143 break
152 break
144 chain.append(iterrev)
153 chain.append(iterrev)
145 iterrev = self.index[iterrev][3]
154 iterrev = self.index[iterrev][3]
146 if iterrev == nullrev:
155 if iterrev == nullrev:
147 rawtext = b''
156 rawtext = b''
148 elif rawtext is None:
157 elif rawtext is None:
149 r = super(bundlerevlog, self)._rawtext(
158 r = super(bundlerevlog, self)._rawtext(
150 self.node(iterrev), iterrev, _df=_df
159 self.node(iterrev), iterrev, _df=_df
151 )
160 )
152 __, rawtext, validated = r
161 __, rawtext, validated = r
153 if chain:
162 if chain:
154 validated = False
163 validated = False
155 while chain:
164 while chain:
156 delta = self._chunk(chain.pop())
165 delta = self._chunk(chain.pop())
157 rawtext = mdiff.patches(rawtext, [delta])
166 rawtext = mdiff.patches(rawtext, [delta])
158 return rev, rawtext, validated
167 return rev, rawtext, validated
159
168
160 def addrevision(self, *args, **kwargs):
169 def addrevision(self, *args, **kwargs):
161 raise NotImplementedError
170 raise NotImplementedError
162
171
163 def addgroup(self, *args, **kwargs):
172 def addgroup(self, *args, **kwargs):
164 raise NotImplementedError
173 raise NotImplementedError
165
174
166 def strip(self, *args, **kwargs):
175 def strip(self, *args, **kwargs):
167 raise NotImplementedError
176 raise NotImplementedError
168
177
169 def checksize(self):
178 def checksize(self):
170 raise NotImplementedError
179 raise NotImplementedError
171
180
172
181
173 class bundlechangelog(bundlerevlog, changelog.changelog):
182 class bundlechangelog(bundlerevlog, changelog.changelog):
174 def __init__(self, opener, cgunpacker):
183 def __init__(self, opener, cgunpacker):
175 changelog.changelog.__init__(self, opener)
184 changelog.changelog.__init__(self, opener)
176 linkmapper = lambda x: x
185 linkmapper = lambda x: x
177 bundlerevlog.__init__(
186 bundlerevlog.__init__(
178 self,
187 self,
179 opener,
188 opener,
180 (revlog_constants.KIND_CHANGELOG, None),
189 (revlog_constants.KIND_CHANGELOG, None),
181 self.radix,
190 self.radix,
182 cgunpacker,
191 cgunpacker,
183 linkmapper,
192 linkmapper,
184 )
193 )
185
194
186
195
187 class bundlemanifest(bundlerevlog, manifest.manifestrevlog):
196 class bundlemanifest(bundlerevlog, manifest.manifestrevlog):
188 def __init__(
197 def __init__(
189 self,
198 self,
190 nodeconstants,
199 nodeconstants,
191 opener,
200 opener,
192 cgunpacker,
201 cgunpacker,
193 linkmapper,
202 linkmapper,
194 dirlogstarts=None,
203 dirlogstarts=None,
195 dir=b'',
204 dir=b'',
196 ):
205 ):
197 manifest.manifestrevlog.__init__(self, nodeconstants, opener, tree=dir)
206 manifest.manifestrevlog.__init__(self, nodeconstants, opener, tree=dir)
198 bundlerevlog.__init__(
207 bundlerevlog.__init__(
199 self,
208 self,
200 opener,
209 opener,
201 (revlog_constants.KIND_MANIFESTLOG, dir),
210 (revlog_constants.KIND_MANIFESTLOG, dir),
202 self._revlog.radix,
211 self._revlog.radix,
203 cgunpacker,
212 cgunpacker,
204 linkmapper,
213 linkmapper,
205 )
214 )
206 if dirlogstarts is None:
215 if dirlogstarts is None:
207 dirlogstarts = {}
216 dirlogstarts = {}
208 if self.bundle.version == b"03":
217 if self.bundle.version == b"03":
209 dirlogstarts = _getfilestarts(self.bundle)
218 dirlogstarts = _getfilestarts(self.bundle)
210 self._dirlogstarts = dirlogstarts
219 self._dirlogstarts = dirlogstarts
211 self._linkmapper = linkmapper
220 self._linkmapper = linkmapper
212
221
213 def dirlog(self, d):
222 def dirlog(self, d):
214 if d in self._dirlogstarts:
223 if d in self._dirlogstarts:
215 self.bundle.seek(self._dirlogstarts[d])
224 self.bundle.seek(self._dirlogstarts[d])
216 return bundlemanifest(
225 return bundlemanifest(
217 self.nodeconstants,
226 self.nodeconstants,
218 self.opener,
227 self.opener,
219 self.bundle,
228 self.bundle,
220 self._linkmapper,
229 self._linkmapper,
221 self._dirlogstarts,
230 self._dirlogstarts,
222 dir=d,
231 dir=d,
223 )
232 )
224 return super(bundlemanifest, self).dirlog(d)
233 return super(bundlemanifest, self).dirlog(d)
225
234
226
235
227 class bundlefilelog(filelog.filelog):
236 class bundlefilelog(filelog.filelog):
228 def __init__(self, opener, path, cgunpacker, linkmapper):
237 def __init__(self, opener, path, cgunpacker, linkmapper):
229 filelog.filelog.__init__(self, opener, path)
238 filelog.filelog.__init__(self, opener, path)
230 self._revlog = bundlerevlog(
239 self._revlog = bundlerevlog(
231 opener,
240 opener,
232 # XXX should use the unencoded path
241 # XXX should use the unencoded path
233 target=(revlog_constants.KIND_FILELOG, path),
242 target=(revlog_constants.KIND_FILELOG, path),
234 radix=self._revlog.radix,
243 radix=self._revlog.radix,
235 cgunpacker=cgunpacker,
244 cgunpacker=cgunpacker,
236 linkmapper=linkmapper,
245 linkmapper=linkmapper,
237 )
246 )
238
247
239
248
240 class bundlepeer(localrepo.localpeer):
249 class bundlepeer(localrepo.localpeer):
241 def canpush(self):
250 def canpush(self):
242 return False
251 return False
243
252
244
253
245 class bundlephasecache(phases.phasecache):
254 class bundlephasecache(phases.phasecache):
246 def __init__(self, *args, **kwargs):
255 def __init__(self, *args, **kwargs):
247 super(bundlephasecache, self).__init__(*args, **kwargs)
256 super(bundlephasecache, self).__init__(*args, **kwargs)
248 if hasattr(self, 'opener'):
257 if hasattr(self, 'opener'):
249 self.opener = vfsmod.readonlyvfs(self.opener)
258 self.opener = vfsmod.readonlyvfs(self.opener)
250
259
251 def write(self):
260 def write(self):
252 raise NotImplementedError
261 raise NotImplementedError
253
262
254 def _write(self, fp):
263 def _write(self, fp):
255 raise NotImplementedError
264 raise NotImplementedError
256
265
257 def _updateroots(self, phase, newroots, tr):
266 def _updateroots(self, phase, newroots, tr):
258 self.phaseroots[phase] = newroots
267 self.phaseroots[phase] = newroots
259 self.invalidate()
268 self.invalidate()
260 self.dirty = True
269 self.dirty = True
261
270
262
271
263 def _getfilestarts(cgunpacker):
272 def _getfilestarts(cgunpacker):
264 filespos = {}
273 filespos = {}
265 for chunkdata in iter(cgunpacker.filelogheader, {}):
274 for chunkdata in iter(cgunpacker.filelogheader, {}):
266 fname = chunkdata[b'filename']
275 fname = chunkdata[b'filename']
267 filespos[fname] = cgunpacker.tell()
276 filespos[fname] = cgunpacker.tell()
268 for chunk in iter(lambda: cgunpacker.deltachunk(None), {}):
277 for chunk in iter(lambda: cgunpacker.deltachunk(None), {}):
269 pass
278 pass
270 return filespos
279 return filespos
271
280
272
281
273 class bundlerepository:
282 class bundlerepository:
274 """A repository instance that is a union of a local repo and a bundle.
283 """A repository instance that is a union of a local repo and a bundle.
275
284
276 Instances represent a read-only repository composed of a local repository
285 Instances represent a read-only repository composed of a local repository
277 with the contents of a bundle file applied. The repository instance is
286 with the contents of a bundle file applied. The repository instance is
278 conceptually similar to the state of a repository after an
287 conceptually similar to the state of a repository after an
279 ``hg unbundle`` operation. However, the contents of the bundle are never
288 ``hg unbundle`` operation. However, the contents of the bundle are never
280 applied to the actual base repository.
289 applied to the actual base repository.
281
290
282 Instances constructed directly are not usable as repository objects.
291 Instances constructed directly are not usable as repository objects.
283 Use instance() or makebundlerepository() to create instances.
292 Use instance() or makebundlerepository() to create instances.
284 """
293 """
285
294
286 def __init__(self, bundlepath, url, tempparent):
295 def __init__(self, bundlepath, url, tempparent):
287 self._tempparent = tempparent
296 self._tempparent = tempparent
288 self._url = url
297 self._url = url
289
298
290 self.ui.setconfig(b'phases', b'publish', False, b'bundlerepo')
299 self.ui.setconfig(b'phases', b'publish', False, b'bundlerepo')
291
300
292 # dict with the mapping 'filename' -> position in the changegroup.
301 # dict with the mapping 'filename' -> position in the changegroup.
293 self._cgfilespos = {}
302 self._cgfilespos = {}
294 self._bundlefile = None
303 self._bundlefile = None
295 self._cgunpacker = None
304 self._cgunpacker = None
296 self.tempfile = None
305 self.tempfile = None
297 f = util.posixfile(bundlepath, b"rb")
306 f = util.posixfile(bundlepath, b"rb")
298 bundle = exchange.readbundle(self.ui, f, bundlepath)
307 bundle = exchange.readbundle(self.ui, f, bundlepath)
299
308
300 if isinstance(bundle, bundle2.unbundle20):
309 if isinstance(bundle, bundle2.unbundle20):
301 self._bundlefile = bundle
310 self._bundlefile = bundle
302
311
303 cgpart = None
312 cgpart = None
304 for part in bundle.iterparts(seekable=True):
313 for part in bundle.iterparts(seekable=True):
305 if part.type == b'phase-heads':
314 if part.type == b'phase-heads':
306 self._handle_bundle2_phase_part(bundle, part)
315 self._handle_bundle2_phase_part(bundle, part)
307 elif part.type == b'changegroup':
316 elif part.type == b'changegroup':
308 if cgpart:
317 if cgpart:
309 raise NotImplementedError(
318 raise NotImplementedError(
310 b"can't process multiple changegroups"
319 b"can't process multiple changegroups"
311 )
320 )
312 cgpart = part
321 cgpart = part
313 self._handle_bundle2_cg_part(bundle, part)
322 self._handle_bundle2_cg_part(bundle, part)
314
323
315 if not cgpart:
324 if not cgpart:
316 raise error.Abort(_(b"No changegroups found"))
325 raise error.Abort(_(b"No changegroups found"))
317
326
318 # This is required to placate a later consumer, which expects
327 # This is required to placate a later consumer, which expects
319 # the payload offset to be at the beginning of the changegroup.
328 # the payload offset to be at the beginning of the changegroup.
320 # We need to do this after the iterparts() generator advances
329 # We need to do this after the iterparts() generator advances
321 # because iterparts() will seek to end of payload after the
330 # because iterparts() will seek to end of payload after the
322 # generator returns control to iterparts().
331 # generator returns control to iterparts().
323 cgpart.seek(0, os.SEEK_SET)
332 cgpart.seek(0, os.SEEK_SET)
324
333
325 elif isinstance(bundle, changegroup.cg1unpacker):
334 elif isinstance(bundle, changegroup.cg1unpacker):
326 self._handle_bundle1(bundle, bundlepath)
335 self._handle_bundle1(bundle, bundlepath)
327 else:
336 else:
328 raise error.Abort(
337 raise error.Abort(
329 _(b'bundle type %r cannot be read') % type(bundle)
338 _(b'bundle type %r cannot be read') % type(bundle)
330 )
339 )
331
340
332 def _handle_bundle1(self, bundle, bundlepath):
341 def _handle_bundle1(self, bundle, bundlepath):
333 if bundle.compressed():
342 if bundle.compressed():
334 f = self._writetempbundle(bundle.read, b'.hg10un', header=b'HG10UN')
343 f = self._writetempbundle(bundle.read, b'.hg10un', header=b'HG10UN')
335 bundle = exchange.readbundle(self.ui, f, bundlepath, self.vfs)
344 bundle = exchange.readbundle(self.ui, f, bundlepath, self.vfs)
336
345
337 self._bundlefile = bundle
346 self._bundlefile = bundle
338 self._cgunpacker = bundle
347 self._cgunpacker = bundle
339
348
340 self.firstnewrev = self.changelog.repotiprev + 1
349 self.firstnewrev = self.changelog.repotiprev + 1
341 phases.retractboundary(
350 phases.retractboundary(
342 self,
351 self,
343 None,
352 None,
344 phases.draft,
353 phases.draft,
345 [ctx.node() for ctx in self[self.firstnewrev :]],
354 [ctx.node() for ctx in self[self.firstnewrev :]],
346 )
355 )
347
356
348 def _handle_bundle2_cg_part(self, bundle, part):
357 def _handle_bundle2_cg_part(self, bundle, part):
349 assert part.type == b'changegroup'
358 assert part.type == b'changegroup'
350 cgstream = part
359 cgstream = part
351 targetphase = part.params.get(b'targetphase')
360 targetphase = part.params.get(b'targetphase')
352 try:
361 try:
353 targetphase = int(targetphase)
362 targetphase = int(targetphase)
354 except TypeError:
363 except TypeError:
355 pass
364 pass
356 if targetphase is None:
365 if targetphase is None:
357 targetphase = phases.draft
366 targetphase = phases.draft
358 if targetphase not in phases.allphases:
367 if targetphase not in phases.allphases:
359 m = _(b'unsupported targetphase: %d')
368 m = _(b'unsupported targetphase: %d')
360 m %= targetphase
369 m %= targetphase
361 raise error.Abort(m)
370 raise error.Abort(m)
362 version = part.params.get(b'version', b'01')
371 version = part.params.get(b'version', b'01')
363 legalcgvers = changegroup.supportedincomingversions(self)
372 legalcgvers = changegroup.supportedincomingversions(self)
364 if version not in legalcgvers:
373 if version not in legalcgvers:
365 msg = _(b'Unsupported changegroup version: %s')
374 msg = _(b'Unsupported changegroup version: %s')
366 raise error.Abort(msg % version)
375 raise error.Abort(msg % version)
367 if bundle.compressed():
376 if bundle.compressed():
368 cgstream = self._writetempbundle(part.read, b'.cg%sun' % version)
377 cgstream = self._writetempbundle(part.read, b'.cg%sun' % version)
369
378
370 self._cgunpacker = changegroup.getunbundler(version, cgstream, b'UN')
379 self._cgunpacker = changegroup.getunbundler(version, cgstream, b'UN')
371
380
372 self.firstnewrev = self.changelog.repotiprev + 1
381 self.firstnewrev = self.changelog.repotiprev + 1
373 phases.retractboundary(
382 phases.retractboundary(
374 self,
383 self,
375 None,
384 None,
376 targetphase,
385 targetphase,
377 [ctx.node() for ctx in self[self.firstnewrev :]],
386 [ctx.node() for ctx in self[self.firstnewrev :]],
378 )
387 )
379
388
380 def _handle_bundle2_phase_part(self, bundle, part):
389 def _handle_bundle2_phase_part(self, bundle, part):
381 assert part.type == b'phase-heads'
390 assert part.type == b'phase-heads'
382
391
383 unfi = self.unfiltered()
392 unfi = self.unfiltered()
384 headsbyphase = phases.binarydecode(part)
393 headsbyphase = phases.binarydecode(part)
385 phases.updatephases(unfi, lambda: None, headsbyphase)
394 phases.updatephases(unfi, lambda: None, headsbyphase)
386
395
387 def _writetempbundle(self, readfn, suffix, header=b''):
396 def _writetempbundle(self, readfn, suffix, header=b''):
388 """Write a temporary file to disk"""
397 """Write a temporary file to disk"""
389 fdtemp, temp = self.vfs.mkstemp(prefix=b"hg-bundle-", suffix=suffix)
398 fdtemp, temp = self.vfs.mkstemp(prefix=b"hg-bundle-", suffix=suffix)
390 self.tempfile = temp
399 self.tempfile = temp
391
400
392 with os.fdopen(fdtemp, 'wb') as fptemp:
401 with os.fdopen(fdtemp, 'wb') as fptemp:
393 fptemp.write(header)
402 fptemp.write(header)
394 while True:
403 while True:
395 chunk = readfn(2 ** 18)
404 chunk = readfn(2 ** 18)
396 if not chunk:
405 if not chunk:
397 break
406 break
398 fptemp.write(chunk)
407 fptemp.write(chunk)
399
408
400 return self.vfs.open(self.tempfile, mode=b"rb")
409 return self.vfs.open(self.tempfile, mode=b"rb")
401
410
402 @localrepo.unfilteredpropertycache
411 @localrepo.unfilteredpropertycache
403 def _phasecache(self):
412 def _phasecache(self):
404 return bundlephasecache(self, self._phasedefaults)
413 return bundlephasecache(self, self._phasedefaults)
405
414
406 @localrepo.unfilteredpropertycache
415 @localrepo.unfilteredpropertycache
407 def changelog(self):
416 def changelog(self):
408 # consume the header if it exists
417 # consume the header if it exists
409 self._cgunpacker.changelogheader()
418 self._cgunpacker.changelogheader()
410 c = bundlechangelog(self.svfs, self._cgunpacker)
419 c = bundlechangelog(self.svfs, self._cgunpacker)
411 self.manstart = self._cgunpacker.tell()
420 self.manstart = self._cgunpacker.tell()
412 return c
421 return c
413
422
414 def _refreshchangelog(self):
423 def _refreshchangelog(self):
415 # changelog for bundle repo are not filecache, this method is not
424 # changelog for bundle repo are not filecache, this method is not
416 # applicable.
425 # applicable.
417 pass
426 pass
418
427
419 @localrepo.unfilteredpropertycache
428 @localrepo.unfilteredpropertycache
420 def manifestlog(self):
429 def manifestlog(self):
421 self._cgunpacker.seek(self.manstart)
430 self._cgunpacker.seek(self.manstart)
422 # consume the header if it exists
431 # consume the header if it exists
423 self._cgunpacker.manifestheader()
432 self._cgunpacker.manifestheader()
424 linkmapper = self.unfiltered().changelog.rev
433 linkmapper = self.unfiltered().changelog.rev
425 rootstore = bundlemanifest(
434 rootstore = bundlemanifest(
426 self.nodeconstants, self.svfs, self._cgunpacker, linkmapper
435 self.nodeconstants, self.svfs, self._cgunpacker, linkmapper
427 )
436 )
428 self.filestart = self._cgunpacker.tell()
437 self.filestart = self._cgunpacker.tell()
429
438
430 return manifest.manifestlog(
439 return manifest.manifestlog(
431 self.svfs, self, rootstore, self.narrowmatch()
440 self.svfs, self, rootstore, self.narrowmatch()
432 )
441 )
433
442
434 def _consumemanifest(self):
443 def _consumemanifest(self):
435 """Consumes the manifest portion of the bundle, setting filestart so the
444 """Consumes the manifest portion of the bundle, setting filestart so the
436 file portion can be read."""
445 file portion can be read."""
437 self._cgunpacker.seek(self.manstart)
446 self._cgunpacker.seek(self.manstart)
438 self._cgunpacker.manifestheader()
447 self._cgunpacker.manifestheader()
439 for delta in self._cgunpacker.deltaiter():
448 for delta in self._cgunpacker.deltaiter():
440 pass
449 pass
441 self.filestart = self._cgunpacker.tell()
450 self.filestart = self._cgunpacker.tell()
442
451
443 @localrepo.unfilteredpropertycache
452 @localrepo.unfilteredpropertycache
444 def manstart(self):
453 def manstart(self):
445 self.changelog
454 self.changelog
446 return self.manstart
455 return self.manstart
447
456
448 @localrepo.unfilteredpropertycache
457 @localrepo.unfilteredpropertycache
449 def filestart(self):
458 def filestart(self):
450 self.manifestlog
459 self.manifestlog
451
460
452 # If filestart was not set by self.manifestlog, that means the
461 # If filestart was not set by self.manifestlog, that means the
453 # manifestlog implementation did not consume the manifests from the
462 # manifestlog implementation did not consume the manifests from the
454 # changegroup (ex: it might be consuming trees from a separate bundle2
463 # changegroup (ex: it might be consuming trees from a separate bundle2
455 # part instead). So we need to manually consume it.
464 # part instead). So we need to manually consume it.
456 if 'filestart' not in self.__dict__:
465 if 'filestart' not in self.__dict__:
457 self._consumemanifest()
466 self._consumemanifest()
458
467
459 return self.filestart
468 return self.filestart
460
469
461 def url(self):
470 def url(self):
462 return self._url
471 return self._url
463
472
464 def file(self, f):
473 def file(self, f):
465 if not self._cgfilespos:
474 if not self._cgfilespos:
466 self._cgunpacker.seek(self.filestart)
475 self._cgunpacker.seek(self.filestart)
467 self._cgfilespos = _getfilestarts(self._cgunpacker)
476 self._cgfilespos = _getfilestarts(self._cgunpacker)
468
477
469 if f in self._cgfilespos:
478 if f in self._cgfilespos:
470 self._cgunpacker.seek(self._cgfilespos[f])
479 self._cgunpacker.seek(self._cgfilespos[f])
471 linkmapper = self.unfiltered().changelog.rev
480 linkmapper = self.unfiltered().changelog.rev
472 return bundlefilelog(self.svfs, f, self._cgunpacker, linkmapper)
481 return bundlefilelog(self.svfs, f, self._cgunpacker, linkmapper)
473 else:
482 else:
474 return super(bundlerepository, self).file(f)
483 return super(bundlerepository, self).file(f)
475
484
476 def close(self):
485 def close(self):
477 """Close assigned bundle file immediately."""
486 """Close assigned bundle file immediately."""
478 self._bundlefile.close()
487 self._bundlefile.close()
479 if self.tempfile is not None:
488 if self.tempfile is not None:
480 self.vfs.unlink(self.tempfile)
489 self.vfs.unlink(self.tempfile)
481 if self._tempparent:
490 if self._tempparent:
482 shutil.rmtree(self._tempparent, True)
491 shutil.rmtree(self._tempparent, True)
483
492
484 def cancopy(self):
493 def cancopy(self):
485 return False
494 return False
486
495
487 def peer(self, path=None, remotehidden=False):
496 def peer(self, path=None, remotehidden=False):
488 return bundlepeer(self, path=path, remotehidden=remotehidden)
497 return bundlepeer(self, path=path, remotehidden=remotehidden)
489
498
490 def getcwd(self):
499 def getcwd(self):
491 return encoding.getcwd() # always outside the repo
500 return encoding.getcwd() # always outside the repo
492
501
493 # Check if parents exist in localrepo before setting
502 # Check if parents exist in localrepo before setting
494 def setparents(self, p1, p2=None):
503 def setparents(self, p1, p2=None):
495 if p2 is None:
504 if p2 is None:
496 p2 = self.nullid
505 p2 = self.nullid
497 p1rev = self.changelog.rev(p1)
506 p1rev = self.changelog.rev(p1)
498 p2rev = self.changelog.rev(p2)
507 p2rev = self.changelog.rev(p2)
499 msg = _(b"setting parent to node %s that only exists in the bundle\n")
508 msg = _(b"setting parent to node %s that only exists in the bundle\n")
500 if self.changelog.repotiprev < p1rev:
509 if self.changelog.repotiprev < p1rev:
501 self.ui.warn(msg % hex(p1))
510 self.ui.warn(msg % hex(p1))
502 if self.changelog.repotiprev < p2rev:
511 if self.changelog.repotiprev < p2rev:
503 self.ui.warn(msg % hex(p2))
512 self.ui.warn(msg % hex(p2))
504 return super(bundlerepository, self).setparents(p1, p2)
513 return super(bundlerepository, self).setparents(p1, p2)
505
514
506
515
507 def instance(ui, path, create, intents=None, createopts=None):
516 def instance(ui, path, create, intents=None, createopts=None):
508 if create:
517 if create:
509 raise error.Abort(_(b'cannot create new bundle repository'))
518 raise error.Abort(_(b'cannot create new bundle repository'))
510 # internal config: bundle.mainreporoot
519 # internal config: bundle.mainreporoot
511 parentpath = ui.config(b"bundle", b"mainreporoot")
520 parentpath = ui.config(b"bundle", b"mainreporoot")
512 if not parentpath:
521 if not parentpath:
513 # try to find the correct path to the working directory repo
522 # try to find the correct path to the working directory repo
514 parentpath = cmdutil.findrepo(encoding.getcwd())
523 parentpath = cmdutil.findrepo(encoding.getcwd())
515 if parentpath is None:
524 if parentpath is None:
516 parentpath = b''
525 parentpath = b''
517 if parentpath:
526 if parentpath:
518 # Try to make the full path relative so we get a nice, short URL.
527 # Try to make the full path relative so we get a nice, short URL.
519 # In particular, we don't want temp dir names in test outputs.
528 # In particular, we don't want temp dir names in test outputs.
520 cwd = encoding.getcwd()
529 cwd = encoding.getcwd()
521 if parentpath == cwd:
530 if parentpath == cwd:
522 parentpath = b''
531 parentpath = b''
523 else:
532 else:
524 cwd = pathutil.normasprefix(cwd)
533 cwd = pathutil.normasprefix(cwd)
525 if parentpath.startswith(cwd):
534 if parentpath.startswith(cwd):
526 parentpath = parentpath[len(cwd) :]
535 parentpath = parentpath[len(cwd) :]
527 u = urlutil.url(path)
536 u = urlutil.url(path)
528 path = u.localpath()
537 path = u.localpath()
529 if u.scheme == b'bundle':
538 if u.scheme == b'bundle':
530 s = path.split(b"+", 1)
539 s = path.split(b"+", 1)
531 if len(s) == 1:
540 if len(s) == 1:
532 repopath, bundlename = parentpath, s[0]
541 repopath, bundlename = parentpath, s[0]
533 else:
542 else:
534 repopath, bundlename = s
543 repopath, bundlename = s
535 else:
544 else:
536 repopath, bundlename = parentpath, path
545 repopath, bundlename = parentpath, path
537
546
538 return makebundlerepository(ui, repopath, bundlename)
547 return makebundlerepository(ui, repopath, bundlename)
539
548
540
549
541 def makebundlerepository(ui, repopath, bundlepath):
550 def makebundlerepository(ui, repopath, bundlepath):
542 """Make a bundle repository object based on repo and bundle paths."""
551 """Make a bundle repository object based on repo and bundle paths."""
543 if repopath:
552 if repopath:
544 url = b'bundle:%s+%s' % (util.expandpath(repopath), bundlepath)
553 url = b'bundle:%s+%s' % (util.expandpath(repopath), bundlepath)
545 else:
554 else:
546 url = b'bundle:%s' % bundlepath
555 url = b'bundle:%s' % bundlepath
547
556
548 # Because we can't make any guarantees about the type of the base
557 # Because we can't make any guarantees about the type of the base
549 # repository, we can't have a static class representing the bundle
558 # repository, we can't have a static class representing the bundle
550 # repository. We also can't make any guarantees about how to even
559 # repository. We also can't make any guarantees about how to even
551 # call the base repository's constructor!
560 # call the base repository's constructor!
552 #
561 #
553 # So, our strategy is to go through ``localrepo.instance()`` to construct
562 # So, our strategy is to go through ``localrepo.instance()`` to construct
554 # a repo instance. Then, we dynamically create a new type derived from
563 # a repo instance. Then, we dynamically create a new type derived from
555 # both it and our ``bundlerepository`` class which overrides some
564 # both it and our ``bundlerepository`` class which overrides some
556 # functionality. We then change the type of the constructed repository
565 # functionality. We then change the type of the constructed repository
557 # to this new type and initialize the bundle-specific bits of it.
566 # to this new type and initialize the bundle-specific bits of it.
558
567
559 try:
568 try:
560 repo = localrepo.instance(ui, repopath, create=False)
569 repo = localrepo.instance(ui, repopath, create=False)
561 tempparent = None
570 tempparent = None
562 except error.RequirementError:
571 except error.RequirementError:
563 raise # no fallback if the backing repo is unsupported
572 raise # no fallback if the backing repo is unsupported
564 except error.RepoError:
573 except error.RepoError:
565 tempparent = pycompat.mkdtemp()
574 tempparent = pycompat.mkdtemp()
566 try:
575 try:
567 repo = localrepo.instance(ui, tempparent, create=True)
576 repo = localrepo.instance(ui, tempparent, create=True)
568 except Exception:
577 except Exception:
569 shutil.rmtree(tempparent)
578 shutil.rmtree(tempparent)
570 raise
579 raise
571
580
572 class derivedbundlerepository(bundlerepository, repo.__class__):
581 class derivedbundlerepository(bundlerepository, repo.__class__):
573 pass
582 pass
574
583
575 repo.__class__ = derivedbundlerepository
584 repo.__class__ = derivedbundlerepository
576 bundlerepository.__init__(repo, bundlepath, url, tempparent)
585 bundlerepository.__init__(repo, bundlepath, url, tempparent)
577
586
578 return repo
587 return repo
579
588
580
589
581 class bundletransactionmanager:
590 class bundletransactionmanager:
582 def transaction(self):
591 def transaction(self):
583 return None
592 return None
584
593
585 def close(self):
594 def close(self):
586 raise NotImplementedError
595 raise NotImplementedError
587
596
588 def release(self):
597 def release(self):
589 raise NotImplementedError
598 raise NotImplementedError
590
599
591
600
592 def getremotechanges(
601 def getremotechanges(
593 ui, repo, peer, onlyheads=None, bundlename=None, force=False
602 ui, repo, peer, onlyheads=None, bundlename=None, force=False
594 ):
603 ):
595 """obtains a bundle of changes incoming from peer
604 """obtains a bundle of changes incoming from peer
596
605
597 "onlyheads" restricts the returned changes to those reachable from the
606 "onlyheads" restricts the returned changes to those reachable from the
598 specified heads.
607 specified heads.
599 "bundlename", if given, stores the bundle to this file path permanently;
608 "bundlename", if given, stores the bundle to this file path permanently;
600 otherwise it's stored to a temp file and gets deleted again when you call
609 otherwise it's stored to a temp file and gets deleted again when you call
601 the returned "cleanupfn".
610 the returned "cleanupfn".
602 "force" indicates whether to proceed on unrelated repos.
611 "force" indicates whether to proceed on unrelated repos.
603
612
604 Returns a tuple (local, csets, cleanupfn):
613 Returns a tuple (local, csets, cleanupfn):
605
614
606 "local" is a local repo from which to obtain the actual incoming
615 "local" is a local repo from which to obtain the actual incoming
607 changesets; it is a bundlerepo for the obtained bundle when the
616 changesets; it is a bundlerepo for the obtained bundle when the
608 original "peer" is remote.
617 original "peer" is remote.
609 "csets" lists the incoming changeset node ids.
618 "csets" lists the incoming changeset node ids.
610 "cleanupfn" must be called without arguments when you're done processing
619 "cleanupfn" must be called without arguments when you're done processing
611 the changes; it closes both the original "peer" and the one returned
620 the changes; it closes both the original "peer" and the one returned
612 here.
621 here.
613 """
622 """
614 tmp = discovery.findcommonincoming(repo, peer, heads=onlyheads, force=force)
623 tmp = discovery.findcommonincoming(repo, peer, heads=onlyheads, force=force)
615 common, incoming, rheads = tmp
624 common, incoming, rheads = tmp
616 if not incoming:
625 if not incoming:
617 try:
626 try:
618 if bundlename:
627 if bundlename:
619 os.unlink(bundlename)
628 os.unlink(bundlename)
620 except OSError:
629 except OSError:
621 pass
630 pass
622 return repo, [], peer.close
631 return repo, [], peer.close
623
632
624 commonset = set(common)
633 commonset = set(common)
625 rheads = [x for x in rheads if x not in commonset]
634 rheads = [x for x in rheads if x not in commonset]
626
635
627 bundle = None
636 bundle = None
628 bundlerepo = None
637 bundlerepo = None
629 localrepo = peer.local()
638 localrepo = peer.local()
630 if bundlename or not localrepo:
639 if bundlename or not localrepo:
631 # create a bundle (uncompressed if peer repo is not local)
640 # create a bundle (uncompressed if peer repo is not local)
632
641
633 # developer config: devel.legacy.exchange
642 # developer config: devel.legacy.exchange
634 legexc = ui.configlist(b'devel', b'legacy.exchange')
643 legexc = ui.configlist(b'devel', b'legacy.exchange')
635 forcebundle1 = b'bundle2' not in legexc and b'bundle1' in legexc
644 forcebundle1 = b'bundle2' not in legexc and b'bundle1' in legexc
636 canbundle2 = (
645 canbundle2 = (
637 not forcebundle1
646 not forcebundle1
638 and peer.capable(b'getbundle')
647 and peer.capable(b'getbundle')
639 and peer.capable(b'bundle2')
648 and peer.capable(b'bundle2')
640 )
649 )
641 if canbundle2:
650 if canbundle2:
642 with peer.commandexecutor() as e:
651 with peer.commandexecutor() as e:
643 b2 = e.callcommand(
652 b2 = e.callcommand(
644 b'getbundle',
653 b'getbundle',
645 {
654 {
646 b'source': b'incoming',
655 b'source': b'incoming',
647 b'common': common,
656 b'common': common,
648 b'heads': rheads,
657 b'heads': rheads,
649 b'bundlecaps': exchange.caps20to10(
658 b'bundlecaps': exchange.caps20to10(
650 repo, role=b'client'
659 repo, role=b'client'
651 ),
660 ),
652 b'cg': True,
661 b'cg': True,
653 },
662 },
654 ).result()
663 ).result()
655
664
656 fname = bundle = changegroup.writechunks(
665 fname = bundle = changegroup.writechunks(
657 ui, b2._forwardchunks(), bundlename
666 ui, b2._forwardchunks(), bundlename
658 )
667 )
659 else:
668 else:
660 if peer.capable(b'getbundle'):
669 if peer.capable(b'getbundle'):
661 with peer.commandexecutor() as e:
670 with peer.commandexecutor() as e:
662 cg = e.callcommand(
671 cg = e.callcommand(
663 b'getbundle',
672 b'getbundle',
664 {
673 {
665 b'source': b'incoming',
674 b'source': b'incoming',
666 b'common': common,
675 b'common': common,
667 b'heads': rheads,
676 b'heads': rheads,
668 },
677 },
669 ).result()
678 ).result()
670 elif onlyheads is None and not peer.capable(b'changegroupsubset'):
679 elif onlyheads is None and not peer.capable(b'changegroupsubset'):
671 # compat with older servers when pulling all remote heads
680 # compat with older servers when pulling all remote heads
672
681
673 with peer.commandexecutor() as e:
682 with peer.commandexecutor() as e:
674 cg = e.callcommand(
683 cg = e.callcommand(
675 b'changegroup',
684 b'changegroup',
676 {
685 {
677 b'nodes': incoming,
686 b'nodes': incoming,
678 b'source': b'incoming',
687 b'source': b'incoming',
679 },
688 },
680 ).result()
689 ).result()
681
690
682 rheads = None
691 rheads = None
683 else:
692 else:
684 with peer.commandexecutor() as e:
693 with peer.commandexecutor() as e:
685 cg = e.callcommand(
694 cg = e.callcommand(
686 b'changegroupsubset',
695 b'changegroupsubset',
687 {
696 {
688 b'bases': incoming,
697 b'bases': incoming,
689 b'heads': rheads,
698 b'heads': rheads,
690 b'source': b'incoming',
699 b'source': b'incoming',
691 },
700 },
692 ).result()
701 ).result()
693
702
694 if localrepo:
703 if localrepo:
695 bundletype = b"HG10BZ"
704 bundletype = b"HG10BZ"
696 else:
705 else:
697 bundletype = b"HG10UN"
706 bundletype = b"HG10UN"
698 fname = bundle = bundle2.writebundle(ui, cg, bundlename, bundletype)
707 fname = bundle = bundle2.writebundle(ui, cg, bundlename, bundletype)
699 # keep written bundle?
708 # keep written bundle?
700 if bundlename:
709 if bundlename:
701 bundle = None
710 bundle = None
702 if not localrepo:
711 if not localrepo:
703 # use the created uncompressed bundlerepo
712 # use the created uncompressed bundlerepo
704 localrepo = bundlerepo = makebundlerepository(
713 localrepo = bundlerepo = makebundlerepository(
705 repo.baseui, repo.root, fname
714 repo.baseui, repo.root, fname
706 )
715 )
707
716
708 # this repo contains local and peer now, so filter out local again
717 # this repo contains local and peer now, so filter out local again
709 common = repo.heads()
718 common = repo.heads()
710 if localrepo:
719 if localrepo:
711 # Part of common may be remotely filtered
720 # Part of common may be remotely filtered
712 # So use an unfiltered version
721 # So use an unfiltered version
713 # The discovery process probably need cleanup to avoid that
722 # The discovery process probably need cleanup to avoid that
714 localrepo = localrepo.unfiltered()
723 localrepo = localrepo.unfiltered()
715
724
716 csets = localrepo.changelog.findmissing(common, rheads)
725 csets = localrepo.changelog.findmissing(common, rheads)
717
726
718 if bundlerepo:
727 if bundlerepo:
719 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev :]]
728 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev :]]
720
729
721 with peer.commandexecutor() as e:
730 with peer.commandexecutor() as e:
722 remotephases = e.callcommand(
731 remotephases = e.callcommand(
723 b'listkeys',
732 b'listkeys',
724 {
733 {
725 b'namespace': b'phases',
734 b'namespace': b'phases',
726 },
735 },
727 ).result()
736 ).result()
728
737
729 pullop = exchange.pulloperation(
738 pullop = exchange.pulloperation(
730 bundlerepo, peer, path=None, heads=reponodes
739 bundlerepo, peer, path=None, heads=reponodes
731 )
740 )
732 pullop.trmanager = bundletransactionmanager()
741 pullop.trmanager = bundletransactionmanager()
733 exchange._pullapplyphases(pullop, remotephases)
742 exchange._pullapplyphases(pullop, remotephases)
734
743
735 def cleanup():
744 def cleanup():
736 if bundlerepo:
745 if bundlerepo:
737 bundlerepo.close()
746 bundlerepo.close()
738 if bundle:
747 if bundle:
739 os.unlink(bundle)
748 os.unlink(bundle)
740 peer.close()
749 peer.close()
741
750
742 return (localrepo, csets, cleanup)
751 return (localrepo, csets, cleanup)
General Comments 0
You need to be logged in to leave comments. Login now