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