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