##// END OF EJS Templates
bundlerepo: properly handle hidden linkrev in manifestlog (issue4945)...
Pierre-Yves David -
r28221:7a8c4484 stable
parent child Browse files
Show More
@@ -1,530 +1,531 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 pathutil,
35 pathutil,
36 phases,
36 phases,
37 revlog,
37 revlog,
38 scmutil,
38 scmutil,
39 util,
39 util,
40 )
40 )
41
41
42 class bundlerevlog(revlog.revlog):
42 class bundlerevlog(revlog.revlog):
43 def __init__(self, opener, indexfile, bundle, linkmapper):
43 def __init__(self, opener, indexfile, bundle, linkmapper):
44 # How it works:
44 # How it works:
45 # To retrieve a revision, we need to know the offset of the revision in
45 # To retrieve a revision, we need to know the offset of the revision in
46 # the bundle (an unbundle object). We store this offset in the index
46 # the bundle (an unbundle object). We store this offset in the index
47 # (start). The base of the delta is stored in the base field.
47 # (start). The base of the delta is stored in the base field.
48 #
48 #
49 # To differentiate a rev in the bundle from a rev in the revlog, we
49 # To differentiate a rev in the bundle from a rev in the revlog, we
50 # check revision against repotiprev.
50 # check revision against repotiprev.
51 opener = scmutil.readonlyvfs(opener)
51 opener = scmutil.readonlyvfs(opener)
52 revlog.revlog.__init__(self, opener, indexfile)
52 revlog.revlog.__init__(self, opener, indexfile)
53 self.bundle = bundle
53 self.bundle = bundle
54 n = len(self)
54 n = len(self)
55 self.repotiprev = n - 1
55 self.repotiprev = n - 1
56 chain = None
56 chain = None
57 self.bundlerevs = set() # used by 'bundle()' revset expression
57 self.bundlerevs = set() # used by 'bundle()' revset expression
58 while True:
58 while True:
59 chunkdata = bundle.deltachunk(chain)
59 chunkdata = bundle.deltachunk(chain)
60 if not chunkdata:
60 if not chunkdata:
61 break
61 break
62 node = chunkdata['node']
62 node = chunkdata['node']
63 p1 = chunkdata['p1']
63 p1 = chunkdata['p1']
64 p2 = chunkdata['p2']
64 p2 = chunkdata['p2']
65 cs = chunkdata['cs']
65 cs = chunkdata['cs']
66 deltabase = chunkdata['deltabase']
66 deltabase = chunkdata['deltabase']
67 delta = chunkdata['delta']
67 delta = chunkdata['delta']
68
68
69 size = len(delta)
69 size = len(delta)
70 start = bundle.tell() - size
70 start = bundle.tell() - size
71
71
72 link = linkmapper(cs)
72 link = linkmapper(cs)
73 if node in self.nodemap:
73 if node in self.nodemap:
74 # this can happen if two branches make the same change
74 # this can happen if two branches make the same change
75 chain = node
75 chain = node
76 self.bundlerevs.add(self.nodemap[node])
76 self.bundlerevs.add(self.nodemap[node])
77 continue
77 continue
78
78
79 for p in (p1, p2):
79 for p in (p1, p2):
80 if p not in self.nodemap:
80 if p not in self.nodemap:
81 raise error.LookupError(p, self.indexfile,
81 raise error.LookupError(p, self.indexfile,
82 _("unknown parent"))
82 _("unknown parent"))
83
83
84 if deltabase not in self.nodemap:
84 if deltabase not in self.nodemap:
85 raise LookupError(deltabase, self.indexfile,
85 raise LookupError(deltabase, self.indexfile,
86 _('unknown delta base'))
86 _('unknown delta base'))
87
87
88 baserev = self.rev(deltabase)
88 baserev = self.rev(deltabase)
89 # start, size, full unc. size, base (unused), link, p1, p2, node
89 # start, size, full unc. size, base (unused), link, p1, p2, node
90 e = (revlog.offset_type(start, 0), size, -1, baserev, link,
90 e = (revlog.offset_type(start, 0), size, -1, baserev, link,
91 self.rev(p1), self.rev(p2), node)
91 self.rev(p1), self.rev(p2), node)
92 self.index.insert(-1, e)
92 self.index.insert(-1, e)
93 self.nodemap[node] = n
93 self.nodemap[node] = n
94 self.bundlerevs.add(n)
94 self.bundlerevs.add(n)
95 chain = node
95 chain = node
96 n += 1
96 n += 1
97
97
98 def _chunk(self, rev):
98 def _chunk(self, rev):
99 # Warning: in case of bundle, the diff is against what we stored as
99 # Warning: in case of bundle, the diff is against what we stored as
100 # delta base, not against rev - 1
100 # delta base, not against rev - 1
101 # XXX: could use some caching
101 # XXX: could use some caching
102 if rev <= self.repotiprev:
102 if rev <= self.repotiprev:
103 return revlog.revlog._chunk(self, rev)
103 return revlog.revlog._chunk(self, rev)
104 self.bundle.seek(self.start(rev))
104 self.bundle.seek(self.start(rev))
105 return self.bundle.read(self.length(rev))
105 return self.bundle.read(self.length(rev))
106
106
107 def revdiff(self, rev1, rev2):
107 def revdiff(self, rev1, rev2):
108 """return or calculate a delta between two revisions"""
108 """return or calculate a delta between two revisions"""
109 if rev1 > self.repotiprev and rev2 > self.repotiprev:
109 if rev1 > self.repotiprev and rev2 > self.repotiprev:
110 # hot path for bundle
110 # hot path for bundle
111 revb = self.index[rev2][3]
111 revb = self.index[rev2][3]
112 if revb == rev1:
112 if revb == rev1:
113 return self._chunk(rev2)
113 return self._chunk(rev2)
114 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
114 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
115 return revlog.revlog.revdiff(self, rev1, rev2)
115 return revlog.revlog.revdiff(self, rev1, rev2)
116
116
117 return mdiff.textdiff(self.revision(self.node(rev1)),
117 return mdiff.textdiff(self.revision(self.node(rev1)),
118 self.revision(self.node(rev2)))
118 self.revision(self.node(rev2)))
119
119
120 def revision(self, nodeorrev):
120 def revision(self, nodeorrev):
121 """return an uncompressed revision of a given node or revision
121 """return an uncompressed revision of a given node or revision
122 number.
122 number.
123 """
123 """
124 if isinstance(nodeorrev, int):
124 if isinstance(nodeorrev, int):
125 rev = nodeorrev
125 rev = nodeorrev
126 node = self.node(rev)
126 node = self.node(rev)
127 else:
127 else:
128 node = nodeorrev
128 node = nodeorrev
129 rev = self.rev(node)
129 rev = self.rev(node)
130
130
131 if node == nullid:
131 if node == nullid:
132 return ""
132 return ""
133
133
134 text = None
134 text = None
135 chain = []
135 chain = []
136 iterrev = rev
136 iterrev = rev
137 # reconstruct the revision if it is from a changegroup
137 # reconstruct the revision if it is from a changegroup
138 while iterrev > self.repotiprev:
138 while iterrev > self.repotiprev:
139 if self._cache and self._cache[1] == iterrev:
139 if self._cache and self._cache[1] == iterrev:
140 text = self._cache[2]
140 text = self._cache[2]
141 break
141 break
142 chain.append(iterrev)
142 chain.append(iterrev)
143 iterrev = self.index[iterrev][3]
143 iterrev = self.index[iterrev][3]
144 if text is None:
144 if text is None:
145 text = self.baserevision(iterrev)
145 text = self.baserevision(iterrev)
146
146
147 while chain:
147 while chain:
148 delta = self._chunk(chain.pop())
148 delta = self._chunk(chain.pop())
149 text = mdiff.patches(text, [delta])
149 text = mdiff.patches(text, [delta])
150
150
151 self._checkhash(text, node, rev)
151 self._checkhash(text, node, rev)
152 self._cache = (node, rev, text)
152 self._cache = (node, rev, text)
153 return text
153 return text
154
154
155 def baserevision(self, nodeorrev):
155 def baserevision(self, nodeorrev):
156 # Revlog subclasses may override 'revision' method to modify format of
156 # Revlog subclasses may override 'revision' method to modify format of
157 # content retrieved from revlog. To use bundlerevlog with such class one
157 # content retrieved from revlog. To use bundlerevlog with such class one
158 # needs to override 'baserevision' and make more specific call here.
158 # needs to override 'baserevision' and make more specific call here.
159 return revlog.revlog.revision(self, nodeorrev)
159 return revlog.revlog.revision(self, nodeorrev)
160
160
161 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
161 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
162 raise NotImplementedError
162 raise NotImplementedError
163 def addgroup(self, revs, linkmapper, transaction):
163 def addgroup(self, revs, linkmapper, transaction):
164 raise NotImplementedError
164 raise NotImplementedError
165 def strip(self, rev, minlink):
165 def strip(self, rev, minlink):
166 raise NotImplementedError
166 raise NotImplementedError
167 def checksize(self):
167 def checksize(self):
168 raise NotImplementedError
168 raise NotImplementedError
169
169
170 class bundlechangelog(bundlerevlog, changelog.changelog):
170 class bundlechangelog(bundlerevlog, changelog.changelog):
171 def __init__(self, opener, bundle):
171 def __init__(self, opener, bundle):
172 changelog.changelog.__init__(self, opener)
172 changelog.changelog.__init__(self, opener)
173 linkmapper = lambda x: x
173 linkmapper = lambda x: x
174 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
174 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
175 linkmapper)
175 linkmapper)
176
176
177 def baserevision(self, nodeorrev):
177 def baserevision(self, nodeorrev):
178 # Although changelog doesn't override 'revision' method, some extensions
178 # Although changelog doesn't override 'revision' method, some extensions
179 # may replace this class with another that does. Same story with
179 # may replace this class with another that does. Same story with
180 # manifest and filelog classes.
180 # manifest and filelog classes.
181
181
182 # This bypasses filtering on changelog.node() and rev() because we need
182 # This bypasses filtering on changelog.node() and rev() because we need
183 # revision text of the bundle base even if it is hidden.
183 # revision text of the bundle base even if it is hidden.
184 oldfilter = self.filteredrevs
184 oldfilter = self.filteredrevs
185 try:
185 try:
186 self.filteredrevs = ()
186 self.filteredrevs = ()
187 return changelog.changelog.revision(self, nodeorrev)
187 return changelog.changelog.revision(self, nodeorrev)
188 finally:
188 finally:
189 self.filteredrevs = oldfilter
189 self.filteredrevs = oldfilter
190
190
191 class bundlemanifest(bundlerevlog, manifest.manifest):
191 class bundlemanifest(bundlerevlog, manifest.manifest):
192 def __init__(self, opener, bundle, linkmapper):
192 def __init__(self, opener, bundle, linkmapper):
193 manifest.manifest.__init__(self, opener)
193 manifest.manifest.__init__(self, opener)
194 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
194 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
195 linkmapper)
195 linkmapper)
196
196
197 def baserevision(self, nodeorrev):
197 def baserevision(self, nodeorrev):
198 node = nodeorrev
198 node = nodeorrev
199 if isinstance(node, int):
199 if isinstance(node, int):
200 node = self.node(node)
200 node = self.node(node)
201
201
202 if node in self._mancache:
202 if node in self._mancache:
203 result = self._mancache[node][0].text()
203 result = self._mancache[node][0].text()
204 else:
204 else:
205 result = manifest.manifest.revision(self, nodeorrev)
205 result = manifest.manifest.revision(self, nodeorrev)
206 return result
206 return result
207
207
208 class bundlefilelog(bundlerevlog, filelog.filelog):
208 class bundlefilelog(bundlerevlog, filelog.filelog):
209 def __init__(self, opener, path, bundle, linkmapper):
209 def __init__(self, opener, path, bundle, linkmapper):
210 filelog.filelog.__init__(self, opener, path)
210 filelog.filelog.__init__(self, opener, path)
211 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
211 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
212 linkmapper)
212 linkmapper)
213
213
214 def baserevision(self, nodeorrev):
214 def baserevision(self, nodeorrev):
215 return filelog.filelog.revision(self, nodeorrev)
215 return filelog.filelog.revision(self, nodeorrev)
216
216
217 class bundlepeer(localrepo.localpeer):
217 class bundlepeer(localrepo.localpeer):
218 def canpush(self):
218 def canpush(self):
219 return False
219 return False
220
220
221 class bundlephasecache(phases.phasecache):
221 class bundlephasecache(phases.phasecache):
222 def __init__(self, *args, **kwargs):
222 def __init__(self, *args, **kwargs):
223 super(bundlephasecache, self).__init__(*args, **kwargs)
223 super(bundlephasecache, self).__init__(*args, **kwargs)
224 if util.safehasattr(self, 'opener'):
224 if util.safehasattr(self, 'opener'):
225 self.opener = scmutil.readonlyvfs(self.opener)
225 self.opener = scmutil.readonlyvfs(self.opener)
226
226
227 def write(self):
227 def write(self):
228 raise NotImplementedError
228 raise NotImplementedError
229
229
230 def _write(self, fp):
230 def _write(self, fp):
231 raise NotImplementedError
231 raise NotImplementedError
232
232
233 def _updateroots(self, phase, newroots, tr):
233 def _updateroots(self, phase, newroots, tr):
234 self.phaseroots[phase] = newroots
234 self.phaseroots[phase] = newroots
235 self.invalidate()
235 self.invalidate()
236 self.dirty = True
236 self.dirty = True
237
237
238 class bundlerepository(localrepo.localrepository):
238 class bundlerepository(localrepo.localrepository):
239 def __init__(self, ui, path, bundlename):
239 def __init__(self, ui, path, bundlename):
240 def _writetempbundle(read, suffix, header=''):
240 def _writetempbundle(read, suffix, header=''):
241 """Write a temporary file to disk
241 """Write a temporary file to disk
242
242
243 This is closure because we need to make sure this tracked by
243 This is closure because we need to make sure this tracked by
244 self.tempfile for cleanup purposes."""
244 self.tempfile for cleanup purposes."""
245 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
245 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
246 suffix=".hg10un")
246 suffix=".hg10un")
247 self.tempfile = temp
247 self.tempfile = temp
248
248
249 with os.fdopen(fdtemp, 'wb') as fptemp:
249 with os.fdopen(fdtemp, 'wb') as fptemp:
250 fptemp.write(header)
250 fptemp.write(header)
251 while True:
251 while True:
252 chunk = read(2**18)
252 chunk = read(2**18)
253 if not chunk:
253 if not chunk:
254 break
254 break
255 fptemp.write(chunk)
255 fptemp.write(chunk)
256
256
257 return self.vfs.open(self.tempfile, mode="rb")
257 return self.vfs.open(self.tempfile, mode="rb")
258 self._tempparent = None
258 self._tempparent = None
259 try:
259 try:
260 localrepo.localrepository.__init__(self, ui, path)
260 localrepo.localrepository.__init__(self, ui, path)
261 except error.RepoError:
261 except error.RepoError:
262 self._tempparent = tempfile.mkdtemp()
262 self._tempparent = tempfile.mkdtemp()
263 localrepo.instance(ui, self._tempparent, 1)
263 localrepo.instance(ui, self._tempparent, 1)
264 localrepo.localrepository.__init__(self, ui, self._tempparent)
264 localrepo.localrepository.__init__(self, ui, self._tempparent)
265 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
265 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
266
266
267 if path:
267 if path:
268 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
268 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
269 else:
269 else:
270 self._url = 'bundle:' + bundlename
270 self._url = 'bundle:' + bundlename
271
271
272 self.tempfile = None
272 self.tempfile = None
273 f = util.posixfile(bundlename, "rb")
273 f = util.posixfile(bundlename, "rb")
274 self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
274 self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
275
275
276 if isinstance(self.bundle, bundle2.unbundle20):
276 if isinstance(self.bundle, bundle2.unbundle20):
277 cgstream = None
277 cgstream = None
278 for part in self.bundle.iterparts():
278 for part in self.bundle.iterparts():
279 if part.type == 'changegroup':
279 if part.type == 'changegroup':
280 if cgstream is not None:
280 if cgstream is not None:
281 raise NotImplementedError("can't process "
281 raise NotImplementedError("can't process "
282 "multiple changegroups")
282 "multiple changegroups")
283 cgstream = part
283 cgstream = part
284 version = part.params.get('version', '01')
284 version = part.params.get('version', '01')
285 if version not in changegroup.allsupportedversions(ui):
285 if version not in changegroup.allsupportedversions(ui):
286 msg = _('Unsupported changegroup version: %s')
286 msg = _('Unsupported changegroup version: %s')
287 raise error.Abort(msg % version)
287 raise error.Abort(msg % version)
288 if self.bundle.compressed():
288 if self.bundle.compressed():
289 cgstream = _writetempbundle(part.read,
289 cgstream = _writetempbundle(part.read,
290 ".cg%sun" % version)
290 ".cg%sun" % version)
291
291
292 if cgstream is None:
292 if cgstream is None:
293 raise error.Abort('No changegroups found')
293 raise error.Abort('No changegroups found')
294 cgstream.seek(0)
294 cgstream.seek(0)
295
295
296 self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
296 self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
297
297
298 elif self.bundle.compressed():
298 elif self.bundle.compressed():
299 f = _writetempbundle(self.bundle.read, '.hg10un', header='HG10UN')
299 f = _writetempbundle(self.bundle.read, '.hg10un', header='HG10UN')
300 self.bundlefile = self.bundle = exchange.readbundle(ui, f,
300 self.bundlefile = self.bundle = exchange.readbundle(ui, f,
301 bundlename,
301 bundlename,
302 self.vfs)
302 self.vfs)
303
303
304 # dict with the mapping 'filename' -> position in the bundle
304 # dict with the mapping 'filename' -> position in the bundle
305 self.bundlefilespos = {}
305 self.bundlefilespos = {}
306
306
307 self.firstnewrev = self.changelog.repotiprev + 1
307 self.firstnewrev = self.changelog.repotiprev + 1
308 phases.retractboundary(self, None, phases.draft,
308 phases.retractboundary(self, None, phases.draft,
309 [ctx.node() for ctx in self[self.firstnewrev:]])
309 [ctx.node() for ctx in self[self.firstnewrev:]])
310
310
311 @localrepo.unfilteredpropertycache
311 @localrepo.unfilteredpropertycache
312 def _phasecache(self):
312 def _phasecache(self):
313 return bundlephasecache(self, self._phasedefaults)
313 return bundlephasecache(self, self._phasedefaults)
314
314
315 @localrepo.unfilteredpropertycache
315 @localrepo.unfilteredpropertycache
316 def changelog(self):
316 def changelog(self):
317 # consume the header if it exists
317 # consume the header if it exists
318 self.bundle.changelogheader()
318 self.bundle.changelogheader()
319 c = bundlechangelog(self.svfs, self.bundle)
319 c = bundlechangelog(self.svfs, self.bundle)
320 self.manstart = self.bundle.tell()
320 self.manstart = self.bundle.tell()
321 return c
321 return c
322
322
323 @localrepo.unfilteredpropertycache
323 @localrepo.unfilteredpropertycache
324 def manifest(self):
324 def manifest(self):
325 self.bundle.seek(self.manstart)
325 self.bundle.seek(self.manstart)
326 # consume the header if it exists
326 # consume the header if it exists
327 self.bundle.manifestheader()
327 self.bundle.manifestheader()
328 m = bundlemanifest(self.svfs, self.bundle, self.changelog.rev)
328 linkmapper = self.unfiltered().changelog.rev
329 m = bundlemanifest(self.svfs, self.bundle, linkmapper)
329 # XXX: hack to work with changegroup3, but we still don't handle
330 # XXX: hack to work with changegroup3, but we still don't handle
330 # tree manifests correctly
331 # tree manifests correctly
331 if self.bundle.version == "03":
332 if self.bundle.version == "03":
332 self.bundle.filelogheader()
333 self.bundle.filelogheader()
333 self.filestart = self.bundle.tell()
334 self.filestart = self.bundle.tell()
334 return m
335 return m
335
336
336 @localrepo.unfilteredpropertycache
337 @localrepo.unfilteredpropertycache
337 def manstart(self):
338 def manstart(self):
338 self.changelog
339 self.changelog
339 return self.manstart
340 return self.manstart
340
341
341 @localrepo.unfilteredpropertycache
342 @localrepo.unfilteredpropertycache
342 def filestart(self):
343 def filestart(self):
343 self.manifest
344 self.manifest
344 return self.filestart
345 return self.filestart
345
346
346 def url(self):
347 def url(self):
347 return self._url
348 return self._url
348
349
349 def file(self, f):
350 def file(self, f):
350 if not self.bundlefilespos:
351 if not self.bundlefilespos:
351 self.bundle.seek(self.filestart)
352 self.bundle.seek(self.filestart)
352 while True:
353 while True:
353 chunkdata = self.bundle.filelogheader()
354 chunkdata = self.bundle.filelogheader()
354 if not chunkdata:
355 if not chunkdata:
355 break
356 break
356 fname = chunkdata['filename']
357 fname = chunkdata['filename']
357 self.bundlefilespos[fname] = self.bundle.tell()
358 self.bundlefilespos[fname] = self.bundle.tell()
358 while True:
359 while True:
359 c = self.bundle.deltachunk(None)
360 c = self.bundle.deltachunk(None)
360 if not c:
361 if not c:
361 break
362 break
362
363
363 if f in self.bundlefilespos:
364 if f in self.bundlefilespos:
364 self.bundle.seek(self.bundlefilespos[f])
365 self.bundle.seek(self.bundlefilespos[f])
365 linkmapper = self.unfiltered().changelog.rev
366 linkmapper = self.unfiltered().changelog.rev
366 return bundlefilelog(self.svfs, f, self.bundle, linkmapper)
367 return bundlefilelog(self.svfs, f, self.bundle, linkmapper)
367 else:
368 else:
368 return filelog.filelog(self.svfs, f)
369 return filelog.filelog(self.svfs, f)
369
370
370 def close(self):
371 def close(self):
371 """Close assigned bundle file immediately."""
372 """Close assigned bundle file immediately."""
372 self.bundlefile.close()
373 self.bundlefile.close()
373 if self.tempfile is not None:
374 if self.tempfile is not None:
374 self.vfs.unlink(self.tempfile)
375 self.vfs.unlink(self.tempfile)
375 if self._tempparent:
376 if self._tempparent:
376 shutil.rmtree(self._tempparent, True)
377 shutil.rmtree(self._tempparent, True)
377
378
378 def cancopy(self):
379 def cancopy(self):
379 return False
380 return False
380
381
381 def peer(self):
382 def peer(self):
382 return bundlepeer(self)
383 return bundlepeer(self)
383
384
384 def getcwd(self):
385 def getcwd(self):
385 return os.getcwd() # always outside the repo
386 return os.getcwd() # always outside the repo
386
387
387
388
388 def instance(ui, path, create):
389 def instance(ui, path, create):
389 if create:
390 if create:
390 raise error.Abort(_('cannot create new bundle repository'))
391 raise error.Abort(_('cannot create new bundle repository'))
391 # internal config: bundle.mainreporoot
392 # internal config: bundle.mainreporoot
392 parentpath = ui.config("bundle", "mainreporoot", "")
393 parentpath = ui.config("bundle", "mainreporoot", "")
393 if not parentpath:
394 if not parentpath:
394 # try to find the correct path to the working directory repo
395 # try to find the correct path to the working directory repo
395 parentpath = cmdutil.findrepo(os.getcwd())
396 parentpath = cmdutil.findrepo(os.getcwd())
396 if parentpath is None:
397 if parentpath is None:
397 parentpath = ''
398 parentpath = ''
398 if parentpath:
399 if parentpath:
399 # Try to make the full path relative so we get a nice, short URL.
400 # Try to make the full path relative so we get a nice, short URL.
400 # In particular, we don't want temp dir names in test outputs.
401 # In particular, we don't want temp dir names in test outputs.
401 cwd = os.getcwd()
402 cwd = os.getcwd()
402 if parentpath == cwd:
403 if parentpath == cwd:
403 parentpath = ''
404 parentpath = ''
404 else:
405 else:
405 cwd = pathutil.normasprefix(cwd)
406 cwd = pathutil.normasprefix(cwd)
406 if parentpath.startswith(cwd):
407 if parentpath.startswith(cwd):
407 parentpath = parentpath[len(cwd):]
408 parentpath = parentpath[len(cwd):]
408 u = util.url(path)
409 u = util.url(path)
409 path = u.localpath()
410 path = u.localpath()
410 if u.scheme == 'bundle':
411 if u.scheme == 'bundle':
411 s = path.split("+", 1)
412 s = path.split("+", 1)
412 if len(s) == 1:
413 if len(s) == 1:
413 repopath, bundlename = parentpath, s[0]
414 repopath, bundlename = parentpath, s[0]
414 else:
415 else:
415 repopath, bundlename = s
416 repopath, bundlename = s
416 else:
417 else:
417 repopath, bundlename = parentpath, path
418 repopath, bundlename = parentpath, path
418 return bundlerepository(ui, repopath, bundlename)
419 return bundlerepository(ui, repopath, bundlename)
419
420
420 class bundletransactionmanager(object):
421 class bundletransactionmanager(object):
421 def transaction(self):
422 def transaction(self):
422 return None
423 return None
423
424
424 def close(self):
425 def close(self):
425 raise NotImplementedError
426 raise NotImplementedError
426
427
427 def release(self):
428 def release(self):
428 raise NotImplementedError
429 raise NotImplementedError
429
430
430 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
431 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
431 force=False):
432 force=False):
432 '''obtains a bundle of changes incoming from other
433 '''obtains a bundle of changes incoming from other
433
434
434 "onlyheads" restricts the returned changes to those reachable from the
435 "onlyheads" restricts the returned changes to those reachable from the
435 specified heads.
436 specified heads.
436 "bundlename", if given, stores the bundle to this file path permanently;
437 "bundlename", if given, stores the bundle to this file path permanently;
437 otherwise it's stored to a temp file and gets deleted again when you call
438 otherwise it's stored to a temp file and gets deleted again when you call
438 the returned "cleanupfn".
439 the returned "cleanupfn".
439 "force" indicates whether to proceed on unrelated repos.
440 "force" indicates whether to proceed on unrelated repos.
440
441
441 Returns a tuple (local, csets, cleanupfn):
442 Returns a tuple (local, csets, cleanupfn):
442
443
443 "local" is a local repo from which to obtain the actual incoming
444 "local" is a local repo from which to obtain the actual incoming
444 changesets; it is a bundlerepo for the obtained bundle when the
445 changesets; it is a bundlerepo for the obtained bundle when the
445 original "other" is remote.
446 original "other" is remote.
446 "csets" lists the incoming changeset node ids.
447 "csets" lists the incoming changeset node ids.
447 "cleanupfn" must be called without arguments when you're done processing
448 "cleanupfn" must be called without arguments when you're done processing
448 the changes; it closes both the original "other" and the one returned
449 the changes; it closes both the original "other" and the one returned
449 here.
450 here.
450 '''
451 '''
451 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
452 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
452 force=force)
453 force=force)
453 common, incoming, rheads = tmp
454 common, incoming, rheads = tmp
454 if not incoming:
455 if not incoming:
455 try:
456 try:
456 if bundlename:
457 if bundlename:
457 os.unlink(bundlename)
458 os.unlink(bundlename)
458 except OSError:
459 except OSError:
459 pass
460 pass
460 return repo, [], other.close
461 return repo, [], other.close
461
462
462 commonset = set(common)
463 commonset = set(common)
463 rheads = [x for x in rheads if x not in commonset]
464 rheads = [x for x in rheads if x not in commonset]
464
465
465 bundle = None
466 bundle = None
466 bundlerepo = None
467 bundlerepo = None
467 localrepo = other.local()
468 localrepo = other.local()
468 if bundlename or not localrepo:
469 if bundlename or not localrepo:
469 # create a bundle (uncompressed if other repo is not local)
470 # create a bundle (uncompressed if other repo is not local)
470
471
471 canbundle2 = (ui.configbool('experimental', 'bundle2-exp', True)
472 canbundle2 = (ui.configbool('experimental', 'bundle2-exp', True)
472 and other.capable('getbundle')
473 and other.capable('getbundle')
473 and other.capable('bundle2'))
474 and other.capable('bundle2'))
474 if canbundle2:
475 if canbundle2:
475 kwargs = {}
476 kwargs = {}
476 kwargs['common'] = common
477 kwargs['common'] = common
477 kwargs['heads'] = rheads
478 kwargs['heads'] = rheads
478 kwargs['bundlecaps'] = exchange.caps20to10(repo)
479 kwargs['bundlecaps'] = exchange.caps20to10(repo)
479 kwargs['cg'] = True
480 kwargs['cg'] = True
480 b2 = other.getbundle('incoming', **kwargs)
481 b2 = other.getbundle('incoming', **kwargs)
481 fname = bundle = changegroup.writechunks(ui, b2._forwardchunks(),
482 fname = bundle = changegroup.writechunks(ui, b2._forwardchunks(),
482 bundlename)
483 bundlename)
483 else:
484 else:
484 if other.capable('getbundle'):
485 if other.capable('getbundle'):
485 cg = other.getbundle('incoming', common=common, heads=rheads)
486 cg = other.getbundle('incoming', common=common, heads=rheads)
486 elif onlyheads is None and not other.capable('changegroupsubset'):
487 elif onlyheads is None and not other.capable('changegroupsubset'):
487 # compat with older servers when pulling all remote heads
488 # compat with older servers when pulling all remote heads
488 cg = other.changegroup(incoming, "incoming")
489 cg = other.changegroup(incoming, "incoming")
489 rheads = None
490 rheads = None
490 else:
491 else:
491 cg = other.changegroupsubset(incoming, rheads, 'incoming')
492 cg = other.changegroupsubset(incoming, rheads, 'incoming')
492 if localrepo:
493 if localrepo:
493 bundletype = "HG10BZ"
494 bundletype = "HG10BZ"
494 else:
495 else:
495 bundletype = "HG10UN"
496 bundletype = "HG10UN"
496 fname = bundle = changegroup.writebundle(ui, cg, bundlename,
497 fname = bundle = changegroup.writebundle(ui, cg, bundlename,
497 bundletype)
498 bundletype)
498 # keep written bundle?
499 # keep written bundle?
499 if bundlename:
500 if bundlename:
500 bundle = None
501 bundle = None
501 if not localrepo:
502 if not localrepo:
502 # use the created uncompressed bundlerepo
503 # use the created uncompressed bundlerepo
503 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
504 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
504 fname)
505 fname)
505 # this repo contains local and other now, so filter out local again
506 # this repo contains local and other now, so filter out local again
506 common = repo.heads()
507 common = repo.heads()
507 if localrepo:
508 if localrepo:
508 # Part of common may be remotely filtered
509 # Part of common may be remotely filtered
509 # So use an unfiltered version
510 # So use an unfiltered version
510 # The discovery process probably need cleanup to avoid that
511 # The discovery process probably need cleanup to avoid that
511 localrepo = localrepo.unfiltered()
512 localrepo = localrepo.unfiltered()
512
513
513 csets = localrepo.changelog.findmissing(common, rheads)
514 csets = localrepo.changelog.findmissing(common, rheads)
514
515
515 if bundlerepo:
516 if bundlerepo:
516 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
517 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
517 remotephases = other.listkeys('phases')
518 remotephases = other.listkeys('phases')
518
519
519 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
520 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
520 pullop.trmanager = bundletransactionmanager()
521 pullop.trmanager = bundletransactionmanager()
521 exchange._pullapplyphases(pullop, remotephases)
522 exchange._pullapplyphases(pullop, remotephases)
522
523
523 def cleanup():
524 def cleanup():
524 if bundlerepo:
525 if bundlerepo:
525 bundlerepo.close()
526 bundlerepo.close()
526 if bundle:
527 if bundle:
527 os.unlink(bundle)
528 os.unlink(bundle)
528 other.close()
529 other.close()
529
530
530 return (localrepo, csets, cleanup)
531 return (localrepo, csets, cleanup)
General Comments 0
You need to be logged in to leave comments. Login now