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