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