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