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