##// END OF EJS Templates
bundlerepo: rename "bundle" arguments to "cgunpacker"...
Gregory Szorc -
r35075:90609be1 default
parent child Browse files
Show More
@@ -1,589 +1,590 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 import tempfile
18 import tempfile
19
19
20 from .i18n import _
20 from .i18n import _
21 from .node import nullid
21 from .node import nullid
22
22
23 from . import (
23 from . import (
24 bundle2,
24 bundle2,
25 changegroup,
25 changegroup,
26 changelog,
26 changelog,
27 cmdutil,
27 cmdutil,
28 discovery,
28 discovery,
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, bundle, 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 = bundle
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 bundle.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 = bundle.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.insert(-1, e)
84 self.index.insert(-1, 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._cache and self._cache[1] == iterrev:
130 if self._cache and self._cache[1] == iterrev:
131 rawtext = self._cache[2]
131 rawtext = self._cache[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._cache = (node, rev, rawtext)
146 self._cache = (node, rev, rawtext)
147 return text
147 return text
148
148
149 def baserevision(self, nodeorrev):
149 def baserevision(self, nodeorrev):
150 # Revlog subclasses may override 'revision' method to modify format of
150 # Revlog subclasses may override 'revision' method to modify format of
151 # content retrieved from revlog. To use bundlerevlog with such class one
151 # content retrieved from revlog. To use bundlerevlog with such class one
152 # needs to override 'baserevision' and make more specific call here.
152 # needs to override 'baserevision' and make more specific call here.
153 return revlog.revlog.revision(self, nodeorrev, raw=True)
153 return revlog.revlog.revision(self, nodeorrev, raw=True)
154
154
155 def addrevision(self, *args, **kwargs):
155 def addrevision(self, *args, **kwargs):
156 raise NotImplementedError
156 raise NotImplementedError
157
157
158 def addgroup(self, *args, **kwargs):
158 def addgroup(self, *args, **kwargs):
159 raise NotImplementedError
159 raise NotImplementedError
160
160
161 def strip(self, *args, **kwargs):
161 def strip(self, *args, **kwargs):
162 raise NotImplementedError
162 raise NotImplementedError
163
163
164 def checksize(self):
164 def checksize(self):
165 raise NotImplementedError
165 raise NotImplementedError
166
166
167 class bundlechangelog(bundlerevlog, changelog.changelog):
167 class bundlechangelog(bundlerevlog, changelog.changelog):
168 def __init__(self, opener, bundle):
168 def __init__(self, opener, cgunpacker):
169 changelog.changelog.__init__(self, opener)
169 changelog.changelog.__init__(self, opener)
170 linkmapper = lambda x: x
170 linkmapper = lambda x: x
171 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
171 bundlerevlog.__init__(self, opener, self.indexfile, cgunpacker,
172 linkmapper)
172 linkmapper)
173
173
174 def baserevision(self, nodeorrev):
174 def baserevision(self, nodeorrev):
175 # Although changelog doesn't override 'revision' method, some extensions
175 # Although changelog doesn't override 'revision' method, some extensions
176 # may replace this class with another that does. Same story with
176 # may replace this class with another that does. Same story with
177 # manifest and filelog classes.
177 # manifest and filelog classes.
178
178
179 # This bypasses filtering on changelog.node() and rev() because we need
179 # This bypasses filtering on changelog.node() and rev() because we need
180 # revision text of the bundle base even if it is hidden.
180 # revision text of the bundle base even if it is hidden.
181 oldfilter = self.filteredrevs
181 oldfilter = self.filteredrevs
182 try:
182 try:
183 self.filteredrevs = ()
183 self.filteredrevs = ()
184 return changelog.changelog.revision(self, nodeorrev, raw=True)
184 return changelog.changelog.revision(self, nodeorrev, raw=True)
185 finally:
185 finally:
186 self.filteredrevs = oldfilter
186 self.filteredrevs = oldfilter
187
187
188 class bundlemanifest(bundlerevlog, manifest.manifestrevlog):
188 class bundlemanifest(bundlerevlog, manifest.manifestrevlog):
189 def __init__(self, opener, bundle, linkmapper, dirlogstarts=None, dir=''):
189 def __init__(self, opener, cgunpacker, linkmapper, dirlogstarts=None,
190 dir=''):
190 manifest.manifestrevlog.__init__(self, opener, dir=dir)
191 manifest.manifestrevlog.__init__(self, opener, dir=dir)
191 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
192 bundlerevlog.__init__(self, opener, self.indexfile, cgunpacker,
192 linkmapper)
193 linkmapper)
193 if dirlogstarts is None:
194 if dirlogstarts is None:
194 dirlogstarts = {}
195 dirlogstarts = {}
195 if self.bundle.version == "03":
196 if self.bundle.version == "03":
196 dirlogstarts = _getfilestarts(self.bundle)
197 dirlogstarts = _getfilestarts(self.bundle)
197 self._dirlogstarts = dirlogstarts
198 self._dirlogstarts = dirlogstarts
198 self._linkmapper = linkmapper
199 self._linkmapper = linkmapper
199
200
200 def baserevision(self, nodeorrev):
201 def baserevision(self, nodeorrev):
201 node = nodeorrev
202 node = nodeorrev
202 if isinstance(node, int):
203 if isinstance(node, int):
203 node = self.node(node)
204 node = self.node(node)
204
205
205 if node in self.fulltextcache:
206 if node in self.fulltextcache:
206 result = '%s' % self.fulltextcache[node]
207 result = '%s' % self.fulltextcache[node]
207 else:
208 else:
208 result = manifest.manifestrevlog.revision(self, nodeorrev, raw=True)
209 result = manifest.manifestrevlog.revision(self, nodeorrev, raw=True)
209 return result
210 return result
210
211
211 def dirlog(self, d):
212 def dirlog(self, d):
212 if d in self._dirlogstarts:
213 if d in self._dirlogstarts:
213 self.bundle.seek(self._dirlogstarts[d])
214 self.bundle.seek(self._dirlogstarts[d])
214 return bundlemanifest(
215 return bundlemanifest(
215 self.opener, self.bundle, self._linkmapper,
216 self.opener, self.bundle, self._linkmapper,
216 self._dirlogstarts, dir=d)
217 self._dirlogstarts, dir=d)
217 return super(bundlemanifest, self).dirlog(d)
218 return super(bundlemanifest, self).dirlog(d)
218
219
219 class bundlefilelog(bundlerevlog, filelog.filelog):
220 class bundlefilelog(bundlerevlog, filelog.filelog):
220 def __init__(self, opener, path, bundle, linkmapper):
221 def __init__(self, opener, path, cgunpacker, linkmapper):
221 filelog.filelog.__init__(self, opener, path)
222 filelog.filelog.__init__(self, opener, path)
222 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
223 bundlerevlog.__init__(self, opener, self.indexfile, cgunpacker,
223 linkmapper)
224 linkmapper)
224
225
225 def baserevision(self, nodeorrev):
226 def baserevision(self, nodeorrev):
226 return filelog.filelog.revision(self, nodeorrev, raw=True)
227 return filelog.filelog.revision(self, nodeorrev, raw=True)
227
228
228 class bundlepeer(localrepo.localpeer):
229 class bundlepeer(localrepo.localpeer):
229 def canpush(self):
230 def canpush(self):
230 return False
231 return False
231
232
232 class bundlephasecache(phases.phasecache):
233 class bundlephasecache(phases.phasecache):
233 def __init__(self, *args, **kwargs):
234 def __init__(self, *args, **kwargs):
234 super(bundlephasecache, self).__init__(*args, **kwargs)
235 super(bundlephasecache, self).__init__(*args, **kwargs)
235 if util.safehasattr(self, 'opener'):
236 if util.safehasattr(self, 'opener'):
236 self.opener = vfsmod.readonlyvfs(self.opener)
237 self.opener = vfsmod.readonlyvfs(self.opener)
237
238
238 def write(self):
239 def write(self):
239 raise NotImplementedError
240 raise NotImplementedError
240
241
241 def _write(self, fp):
242 def _write(self, fp):
242 raise NotImplementedError
243 raise NotImplementedError
243
244
244 def _updateroots(self, phase, newroots, tr):
245 def _updateroots(self, phase, newroots, tr):
245 self.phaseroots[phase] = newroots
246 self.phaseroots[phase] = newroots
246 self.invalidate()
247 self.invalidate()
247 self.dirty = True
248 self.dirty = True
248
249
249 def _getfilestarts(bundle):
250 bundlefilespos = {}
250 bundlefilespos = {}
251 for chunkdata in iter(bundle.filelogheader, {}):
251 def _getfilestarts(cgunpacker):
252 for chunkdata in iter(cgunpacker.filelogheader, {}):
252 fname = chunkdata['filename']
253 fname = chunkdata['filename']
253 bundlefilespos[fname] = bundle.tell()
254 bundlefilespos[fname] = bundle.tell()
254 for chunk in iter(lambda: bundle.deltachunk(None), {}):
255 for chunk in iter(lambda: cgunpacker.deltachunk(None), {}):
255 pass
256 pass
256 return bundlefilespos
257 return bundlefilespos
257
258
258 class bundlerepository(localrepo.localrepository):
259 class bundlerepository(localrepo.localrepository):
259 """A repository instance that is a union of a local repo and a bundle.
260 """A repository instance that is a union of a local repo and a bundle.
260
261
261 Instances represent a read-only repository composed of a local repository
262 Instances represent a read-only repository composed of a local repository
262 with the contents of a bundle file applied. The repository instance is
263 with the contents of a bundle file applied. The repository instance is
263 conceptually similar to the state of a repository after an
264 conceptually similar to the state of a repository after an
264 ``hg unbundle`` operation. However, the contents of the bundle are never
265 ``hg unbundle`` operation. However, the contents of the bundle are never
265 applied to the actual base repository.
266 applied to the actual base repository.
266 """
267 """
267 def __init__(self, ui, repopath, bundlepath):
268 def __init__(self, ui, repopath, bundlepath):
268 self._tempparent = None
269 self._tempparent = None
269 try:
270 try:
270 localrepo.localrepository.__init__(self, ui, repopath)
271 localrepo.localrepository.__init__(self, ui, repopath)
271 except error.RepoError:
272 except error.RepoError:
272 self._tempparent = tempfile.mkdtemp()
273 self._tempparent = tempfile.mkdtemp()
273 localrepo.instance(ui, self._tempparent, 1)
274 localrepo.instance(ui, self._tempparent, 1)
274 localrepo.localrepository.__init__(self, ui, self._tempparent)
275 localrepo.localrepository.__init__(self, ui, self._tempparent)
275 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
276 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
276
277
277 if repopath:
278 if repopath:
278 self._url = 'bundle:' + util.expandpath(repopath) + '+' + bundlepath
279 self._url = 'bundle:' + util.expandpath(repopath) + '+' + bundlepath
279 else:
280 else:
280 self._url = 'bundle:' + bundlepath
281 self._url = 'bundle:' + bundlepath
281
282
282 self.tempfile = None
283 self.tempfile = None
283 f = util.posixfile(bundlepath, "rb")
284 f = util.posixfile(bundlepath, "rb")
284 bundle = exchange.readbundle(ui, f, bundlepath)
285 bundle = exchange.readbundle(ui, f, bundlepath)
285
286
286 if isinstance(bundle, bundle2.unbundle20):
287 if isinstance(bundle, bundle2.unbundle20):
287 self._bundlefile = bundle
288 self._bundlefile = bundle
288 self._cgunpacker = None
289 self._cgunpacker = None
289
290
290 hadchangegroup = False
291 hadchangegroup = False
291 for part in bundle.iterparts():
292 for part in bundle.iterparts():
292 if part.type == 'changegroup':
293 if part.type == 'changegroup':
293 if hadchangegroup:
294 if hadchangegroup:
294 raise NotImplementedError("can't process "
295 raise NotImplementedError("can't process "
295 "multiple changegroups")
296 "multiple changegroups")
296 hadchangegroup = True
297 hadchangegroup = True
297
298
298 self._handlebundle2part(bundle, part)
299 self._handlebundle2part(bundle, part)
299
300
300 if not hadchangegroup:
301 if not hadchangegroup:
301 raise error.Abort(_("No changegroups found"))
302 raise error.Abort(_("No changegroups found"))
302 elif isinstance(bundle, changegroup.cg1unpacker):
303 elif isinstance(bundle, changegroup.cg1unpacker):
303 if bundle.compressed():
304 if bundle.compressed():
304 f = self._writetempbundle(bundle.read, '.hg10un',
305 f = self._writetempbundle(bundle.read, '.hg10un',
305 header='HG10UN')
306 header='HG10UN')
306 bundle = exchange.readbundle(ui, f, bundlepath, self.vfs)
307 bundle = exchange.readbundle(ui, f, bundlepath, self.vfs)
307
308
308 self._bundlefile = bundle
309 self._bundlefile = bundle
309 self._cgunpacker = bundle
310 self._cgunpacker = bundle
310 else:
311 else:
311 raise error.Abort(_('bundle type %s cannot be read') %
312 raise error.Abort(_('bundle type %s cannot be read') %
312 type(bundle))
313 type(bundle))
313
314
314 # dict with the mapping 'filename' -> position in the bundle
315 # dict with the mapping 'filename' -> position in the bundle
315 self.bundlefilespos = {}
316 self.bundlefilespos = {}
316
317
317 self.firstnewrev = self.changelog.repotiprev + 1
318 self.firstnewrev = self.changelog.repotiprev + 1
318 phases.retractboundary(self, None, phases.draft,
319 phases.retractboundary(self, None, phases.draft,
319 [ctx.node() for ctx in self[self.firstnewrev:]])
320 [ctx.node() for ctx in self[self.firstnewrev:]])
320
321
321 def _handlebundle2part(self, bundle, part):
322 def _handlebundle2part(self, bundle, part):
322 if part.type != 'changegroup':
323 if part.type != 'changegroup':
323 return
324 return
324
325
325 cgstream = part
326 cgstream = part
326 version = part.params.get('version', '01')
327 version = part.params.get('version', '01')
327 legalcgvers = changegroup.supportedincomingversions(self)
328 legalcgvers = changegroup.supportedincomingversions(self)
328 if version not in legalcgvers:
329 if version not in legalcgvers:
329 msg = _('Unsupported changegroup version: %s')
330 msg = _('Unsupported changegroup version: %s')
330 raise error.Abort(msg % version)
331 raise error.Abort(msg % version)
331 if bundle.compressed():
332 if bundle.compressed():
332 cgstream = self._writetempbundle(part.read, '.cg%sun' % version)
333 cgstream = self._writetempbundle(part.read, '.cg%sun' % version)
333
334
334 self._cgunpacker = changegroup.getunbundler(version, cgstream, 'UN')
335 self._cgunpacker = changegroup.getunbundler(version, cgstream, 'UN')
335
336
336 def _writetempbundle(self, readfn, suffix, header=''):
337 def _writetempbundle(self, readfn, suffix, header=''):
337 """Write a temporary file to disk
338 """Write a temporary file to disk
338 """
339 """
339 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
340 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
340 suffix=suffix)
341 suffix=suffix)
341 self.tempfile = temp
342 self.tempfile = temp
342
343
343 with os.fdopen(fdtemp, pycompat.sysstr('wb')) as fptemp:
344 with os.fdopen(fdtemp, pycompat.sysstr('wb')) as fptemp:
344 fptemp.write(header)
345 fptemp.write(header)
345 while True:
346 while True:
346 chunk = readfn(2**18)
347 chunk = readfn(2**18)
347 if not chunk:
348 if not chunk:
348 break
349 break
349 fptemp.write(chunk)
350 fptemp.write(chunk)
350
351
351 return self.vfs.open(self.tempfile, mode="rb")
352 return self.vfs.open(self.tempfile, mode="rb")
352
353
353 @localrepo.unfilteredpropertycache
354 @localrepo.unfilteredpropertycache
354 def _phasecache(self):
355 def _phasecache(self):
355 return bundlephasecache(self, self._phasedefaults)
356 return bundlephasecache(self, self._phasedefaults)
356
357
357 @localrepo.unfilteredpropertycache
358 @localrepo.unfilteredpropertycache
358 def changelog(self):
359 def changelog(self):
359 # consume the header if it exists
360 # consume the header if it exists
360 self._cgunpacker.changelogheader()
361 self._cgunpacker.changelogheader()
361 c = bundlechangelog(self.svfs, self._cgunpacker)
362 c = bundlechangelog(self.svfs, self._cgunpacker)
362 self.manstart = self._cgunpacker.tell()
363 self.manstart = self._cgunpacker.tell()
363 return c
364 return c
364
365
365 def _constructmanifest(self):
366 def _constructmanifest(self):
366 self._cgunpacker.seek(self.manstart)
367 self._cgunpacker.seek(self.manstart)
367 # consume the header if it exists
368 # consume the header if it exists
368 self._cgunpacker.manifestheader()
369 self._cgunpacker.manifestheader()
369 linkmapper = self.unfiltered().changelog.rev
370 linkmapper = self.unfiltered().changelog.rev
370 m = bundlemanifest(self.svfs, self._cgunpacker, linkmapper)
371 m = bundlemanifest(self.svfs, self._cgunpacker, linkmapper)
371 self.filestart = self._cgunpacker.tell()
372 self.filestart = self._cgunpacker.tell()
372 return m
373 return m
373
374
374 def _consumemanifest(self):
375 def _consumemanifest(self):
375 """Consumes the manifest portion of the bundle, setting filestart so the
376 """Consumes the manifest portion of the bundle, setting filestart so the
376 file portion can be read."""
377 file portion can be read."""
377 self._cgunpacker.seek(self.manstart)
378 self._cgunpacker.seek(self.manstart)
378 self._cgunpacker.manifestheader()
379 self._cgunpacker.manifestheader()
379 for delta in self._cgunpacker.deltaiter():
380 for delta in self._cgunpacker.deltaiter():
380 pass
381 pass
381 self.filestart = self._cgunpacker.tell()
382 self.filestart = self._cgunpacker.tell()
382
383
383 @localrepo.unfilteredpropertycache
384 @localrepo.unfilteredpropertycache
384 def manstart(self):
385 def manstart(self):
385 self.changelog
386 self.changelog
386 return self.manstart
387 return self.manstart
387
388
388 @localrepo.unfilteredpropertycache
389 @localrepo.unfilteredpropertycache
389 def filestart(self):
390 def filestart(self):
390 self.manifestlog
391 self.manifestlog
391
392
392 # If filestart was not set by self.manifestlog, that means the
393 # If filestart was not set by self.manifestlog, that means the
393 # manifestlog implementation did not consume the manifests from the
394 # manifestlog implementation did not consume the manifests from the
394 # changegroup (ex: it might be consuming trees from a separate bundle2
395 # changegroup (ex: it might be consuming trees from a separate bundle2
395 # part instead). So we need to manually consume it.
396 # part instead). So we need to manually consume it.
396 if 'filestart' not in self.__dict__:
397 if 'filestart' not in self.__dict__:
397 self._consumemanifest()
398 self._consumemanifest()
398
399
399 return self.filestart
400 return self.filestart
400
401
401 def url(self):
402 def url(self):
402 return self._url
403 return self._url
403
404
404 def file(self, f):
405 def file(self, f):
405 if not self.bundlefilespos:
406 if not self.bundlefilespos:
406 self._cgunpacker.seek(self.filestart)
407 self._cgunpacker.seek(self.filestart)
407 self.bundlefilespos = _getfilestarts(self._cgunpacker)
408 self.bundlefilespos = _getfilestarts(self._cgunpacker)
408
409
409 if f in self.bundlefilespos:
410 if f in self.bundlefilespos:
410 self._cgunpacker.seek(self.bundlefilespos[f])
411 self._cgunpacker.seek(self.bundlefilespos[f])
411 linkmapper = self.unfiltered().changelog.rev
412 linkmapper = self.unfiltered().changelog.rev
412 return bundlefilelog(self.svfs, f, self._cgunpacker, linkmapper)
413 return bundlefilelog(self.svfs, f, self._cgunpacker, linkmapper)
413 else:
414 else:
414 return filelog.filelog(self.svfs, f)
415 return filelog.filelog(self.svfs, f)
415
416
416 def close(self):
417 def close(self):
417 """Close assigned bundle file immediately."""
418 """Close assigned bundle file immediately."""
418 self._bundlefile.close()
419 self._bundlefile.close()
419 if self.tempfile is not None:
420 if self.tempfile is not None:
420 self.vfs.unlink(self.tempfile)
421 self.vfs.unlink(self.tempfile)
421 if self._tempparent:
422 if self._tempparent:
422 shutil.rmtree(self._tempparent, True)
423 shutil.rmtree(self._tempparent, True)
423
424
424 def cancopy(self):
425 def cancopy(self):
425 return False
426 return False
426
427
427 def peer(self):
428 def peer(self):
428 return bundlepeer(self)
429 return bundlepeer(self)
429
430
430 def getcwd(self):
431 def getcwd(self):
431 return pycompat.getcwd() # always outside the repo
432 return pycompat.getcwd() # always outside the repo
432
433
433 # Check if parents exist in localrepo before setting
434 # Check if parents exist in localrepo before setting
434 def setparents(self, p1, p2=nullid):
435 def setparents(self, p1, p2=nullid):
435 p1rev = self.changelog.rev(p1)
436 p1rev = self.changelog.rev(p1)
436 p2rev = self.changelog.rev(p2)
437 p2rev = self.changelog.rev(p2)
437 msg = _("setting parent to node %s that only exists in the bundle\n")
438 msg = _("setting parent to node %s that only exists in the bundle\n")
438 if self.changelog.repotiprev < p1rev:
439 if self.changelog.repotiprev < p1rev:
439 self.ui.warn(msg % nodemod.hex(p1))
440 self.ui.warn(msg % nodemod.hex(p1))
440 if self.changelog.repotiprev < p2rev:
441 if self.changelog.repotiprev < p2rev:
441 self.ui.warn(msg % nodemod.hex(p2))
442 self.ui.warn(msg % nodemod.hex(p2))
442 return super(bundlerepository, self).setparents(p1, p2)
443 return super(bundlerepository, self).setparents(p1, p2)
443
444
444 def instance(ui, path, create):
445 def instance(ui, path, create):
445 if create:
446 if create:
446 raise error.Abort(_('cannot create new bundle repository'))
447 raise error.Abort(_('cannot create new bundle repository'))
447 # internal config: bundle.mainreporoot
448 # internal config: bundle.mainreporoot
448 parentpath = ui.config("bundle", "mainreporoot")
449 parentpath = ui.config("bundle", "mainreporoot")
449 if not parentpath:
450 if not parentpath:
450 # try to find the correct path to the working directory repo
451 # try to find the correct path to the working directory repo
451 parentpath = cmdutil.findrepo(pycompat.getcwd())
452 parentpath = cmdutil.findrepo(pycompat.getcwd())
452 if parentpath is None:
453 if parentpath is None:
453 parentpath = ''
454 parentpath = ''
454 if parentpath:
455 if parentpath:
455 # Try to make the full path relative so we get a nice, short URL.
456 # Try to make the full path relative so we get a nice, short URL.
456 # In particular, we don't want temp dir names in test outputs.
457 # In particular, we don't want temp dir names in test outputs.
457 cwd = pycompat.getcwd()
458 cwd = pycompat.getcwd()
458 if parentpath == cwd:
459 if parentpath == cwd:
459 parentpath = ''
460 parentpath = ''
460 else:
461 else:
461 cwd = pathutil.normasprefix(cwd)
462 cwd = pathutil.normasprefix(cwd)
462 if parentpath.startswith(cwd):
463 if parentpath.startswith(cwd):
463 parentpath = parentpath[len(cwd):]
464 parentpath = parentpath[len(cwd):]
464 u = util.url(path)
465 u = util.url(path)
465 path = u.localpath()
466 path = u.localpath()
466 if u.scheme == 'bundle':
467 if u.scheme == 'bundle':
467 s = path.split("+", 1)
468 s = path.split("+", 1)
468 if len(s) == 1:
469 if len(s) == 1:
469 repopath, bundlename = parentpath, s[0]
470 repopath, bundlename = parentpath, s[0]
470 else:
471 else:
471 repopath, bundlename = s
472 repopath, bundlename = s
472 else:
473 else:
473 repopath, bundlename = parentpath, path
474 repopath, bundlename = parentpath, path
474 return bundlerepository(ui, repopath, bundlename)
475 return bundlerepository(ui, repopath, bundlename)
475
476
476 class bundletransactionmanager(object):
477 class bundletransactionmanager(object):
477 def transaction(self):
478 def transaction(self):
478 return None
479 return None
479
480
480 def close(self):
481 def close(self):
481 raise NotImplementedError
482 raise NotImplementedError
482
483
483 def release(self):
484 def release(self):
484 raise NotImplementedError
485 raise NotImplementedError
485
486
486 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
487 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
487 force=False):
488 force=False):
488 '''obtains a bundle of changes incoming from other
489 '''obtains a bundle of changes incoming from other
489
490
490 "onlyheads" restricts the returned changes to those reachable from the
491 "onlyheads" restricts the returned changes to those reachable from the
491 specified heads.
492 specified heads.
492 "bundlename", if given, stores the bundle to this file path permanently;
493 "bundlename", if given, stores the bundle to this file path permanently;
493 otherwise it's stored to a temp file and gets deleted again when you call
494 otherwise it's stored to a temp file and gets deleted again when you call
494 the returned "cleanupfn".
495 the returned "cleanupfn".
495 "force" indicates whether to proceed on unrelated repos.
496 "force" indicates whether to proceed on unrelated repos.
496
497
497 Returns a tuple (local, csets, cleanupfn):
498 Returns a tuple (local, csets, cleanupfn):
498
499
499 "local" is a local repo from which to obtain the actual incoming
500 "local" is a local repo from which to obtain the actual incoming
500 changesets; it is a bundlerepo for the obtained bundle when the
501 changesets; it is a bundlerepo for the obtained bundle when the
501 original "other" is remote.
502 original "other" is remote.
502 "csets" lists the incoming changeset node ids.
503 "csets" lists the incoming changeset node ids.
503 "cleanupfn" must be called without arguments when you're done processing
504 "cleanupfn" must be called without arguments when you're done processing
504 the changes; it closes both the original "other" and the one returned
505 the changes; it closes both the original "other" and the one returned
505 here.
506 here.
506 '''
507 '''
507 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
508 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
508 force=force)
509 force=force)
509 common, incoming, rheads = tmp
510 common, incoming, rheads = tmp
510 if not incoming:
511 if not incoming:
511 try:
512 try:
512 if bundlename:
513 if bundlename:
513 os.unlink(bundlename)
514 os.unlink(bundlename)
514 except OSError:
515 except OSError:
515 pass
516 pass
516 return repo, [], other.close
517 return repo, [], other.close
517
518
518 commonset = set(common)
519 commonset = set(common)
519 rheads = [x for x in rheads if x not in commonset]
520 rheads = [x for x in rheads if x not in commonset]
520
521
521 bundle = None
522 bundle = None
522 bundlerepo = None
523 bundlerepo = None
523 localrepo = other.local()
524 localrepo = other.local()
524 if bundlename or not localrepo:
525 if bundlename or not localrepo:
525 # create a bundle (uncompressed if other repo is not local)
526 # create a bundle (uncompressed if other repo is not local)
526
527
527 # developer config: devel.legacy.exchange
528 # developer config: devel.legacy.exchange
528 legexc = ui.configlist('devel', 'legacy.exchange')
529 legexc = ui.configlist('devel', 'legacy.exchange')
529 forcebundle1 = 'bundle2' not in legexc and 'bundle1' in legexc
530 forcebundle1 = 'bundle2' not in legexc and 'bundle1' in legexc
530 canbundle2 = (not forcebundle1
531 canbundle2 = (not forcebundle1
531 and other.capable('getbundle')
532 and other.capable('getbundle')
532 and other.capable('bundle2'))
533 and other.capable('bundle2'))
533 if canbundle2:
534 if canbundle2:
534 kwargs = {}
535 kwargs = {}
535 kwargs['common'] = common
536 kwargs['common'] = common
536 kwargs['heads'] = rheads
537 kwargs['heads'] = rheads
537 kwargs['bundlecaps'] = exchange.caps20to10(repo)
538 kwargs['bundlecaps'] = exchange.caps20to10(repo)
538 kwargs['cg'] = True
539 kwargs['cg'] = True
539 b2 = other.getbundle('incoming', **kwargs)
540 b2 = other.getbundle('incoming', **kwargs)
540 fname = bundle = changegroup.writechunks(ui, b2._forwardchunks(),
541 fname = bundle = changegroup.writechunks(ui, b2._forwardchunks(),
541 bundlename)
542 bundlename)
542 else:
543 else:
543 if other.capable('getbundle'):
544 if other.capable('getbundle'):
544 cg = other.getbundle('incoming', common=common, heads=rheads)
545 cg = other.getbundle('incoming', common=common, heads=rheads)
545 elif onlyheads is None and not other.capable('changegroupsubset'):
546 elif onlyheads is None and not other.capable('changegroupsubset'):
546 # compat with older servers when pulling all remote heads
547 # compat with older servers when pulling all remote heads
547 cg = other.changegroup(incoming, "incoming")
548 cg = other.changegroup(incoming, "incoming")
548 rheads = None
549 rheads = None
549 else:
550 else:
550 cg = other.changegroupsubset(incoming, rheads, 'incoming')
551 cg = other.changegroupsubset(incoming, rheads, 'incoming')
551 if localrepo:
552 if localrepo:
552 bundletype = "HG10BZ"
553 bundletype = "HG10BZ"
553 else:
554 else:
554 bundletype = "HG10UN"
555 bundletype = "HG10UN"
555 fname = bundle = bundle2.writebundle(ui, cg, bundlename,
556 fname = bundle = bundle2.writebundle(ui, cg, bundlename,
556 bundletype)
557 bundletype)
557 # keep written bundle?
558 # keep written bundle?
558 if bundlename:
559 if bundlename:
559 bundle = None
560 bundle = None
560 if not localrepo:
561 if not localrepo:
561 # use the created uncompressed bundlerepo
562 # use the created uncompressed bundlerepo
562 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
563 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
563 fname)
564 fname)
564 # this repo contains local and other now, so filter out local again
565 # this repo contains local and other now, so filter out local again
565 common = repo.heads()
566 common = repo.heads()
566 if localrepo:
567 if localrepo:
567 # Part of common may be remotely filtered
568 # Part of common may be remotely filtered
568 # So use an unfiltered version
569 # So use an unfiltered version
569 # The discovery process probably need cleanup to avoid that
570 # The discovery process probably need cleanup to avoid that
570 localrepo = localrepo.unfiltered()
571 localrepo = localrepo.unfiltered()
571
572
572 csets = localrepo.changelog.findmissing(common, rheads)
573 csets = localrepo.changelog.findmissing(common, rheads)
573
574
574 if bundlerepo:
575 if bundlerepo:
575 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
576 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
576 remotephases = other.listkeys('phases')
577 remotephases = other.listkeys('phases')
577
578
578 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
579 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
579 pullop.trmanager = bundletransactionmanager()
580 pullop.trmanager = bundletransactionmanager()
580 exchange._pullapplyphases(pullop, remotephases)
581 exchange._pullapplyphases(pullop, remotephases)
581
582
582 def cleanup():
583 def cleanup():
583 if bundlerepo:
584 if bundlerepo:
584 bundlerepo.close()
585 bundlerepo.close()
585 if bundle:
586 if bundle:
586 os.unlink(bundle)
587 os.unlink(bundle)
587 other.close()
588 other.close()
588
589
589 return (localrepo, csets, cleanup)
590 return (localrepo, csets, cleanup)
General Comments 0
You need to be logged in to leave comments. Login now