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