##// END OF EJS Templates
bundlerepo: move temp-bundle writing logic into a closure...
Pierre-Yves David -
r26800:7cac6ee4 default
parent child Browse files
Show More
@@ -1,517 +1,523 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=''):
241 """Write a temporary file to disk
242
243 This is closure because we need to make sure this tracked by
244 self.tempfile for cleanup purposes."""
245 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
246 suffix=".hg10un")
247 self.tempfile = temp
248 fptemp = os.fdopen(fdtemp, 'wb')
249
250 try:
251 fptemp.write(header)
252 while True:
253 chunk = read(2**18)
254 if not chunk:
255 break
256 fptemp.write(chunk)
257 finally:
258 fptemp.close()
259
260 return self.vfs.open(self.tempfile, mode="rb")
240 self._tempparent = None
261 self._tempparent = None
241 try:
262 try:
242 localrepo.localrepository.__init__(self, ui, path)
263 localrepo.localrepository.__init__(self, ui, path)
243 except error.RepoError:
264 except error.RepoError:
244 self._tempparent = tempfile.mkdtemp()
265 self._tempparent = tempfile.mkdtemp()
245 localrepo.instance(ui, self._tempparent, 1)
266 localrepo.instance(ui, self._tempparent, 1)
246 localrepo.localrepository.__init__(self, ui, self._tempparent)
267 localrepo.localrepository.__init__(self, ui, self._tempparent)
247 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
268 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
248
269
249 if path:
270 if path:
250 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
271 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
251 else:
272 else:
252 self._url = 'bundle:' + bundlename
273 self._url = 'bundle:' + bundlename
253
274
254 self.tempfile = None
275 self.tempfile = None
255 f = util.posixfile(bundlename, "rb")
276 f = util.posixfile(bundlename, "rb")
256 self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
277 self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
257 if self.bundle.compressed():
278 if self.bundle.compressed():
258 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
279 f = _writetempbundle(self.bundle.read, '.hg10un', header='HG10UN')
259 suffix=".hg10un")
260 self.tempfile = temp
261 fptemp = os.fdopen(fdtemp, 'wb')
262
263 try:
264 fptemp.write("HG10UN")
265 while True:
266 chunk = self.bundle.read(2**18)
267 if not chunk:
268 break
269 fptemp.write(chunk)
270 finally:
271 fptemp.close()
272
273 f = self.vfs.open(self.tempfile, mode="rb")
274 self.bundlefile = self.bundle = exchange.readbundle(ui, f,
280 self.bundlefile = self.bundle = exchange.readbundle(ui, f,
275 bundlename,
281 bundlename,
276 self.vfs)
282 self.vfs)
277
283
278 if isinstance(self.bundle, bundle2.unbundle20):
284 if isinstance(self.bundle, bundle2.unbundle20):
279 cgparts = [part for part in self.bundle.iterparts()
285 cgparts = [part for part in self.bundle.iterparts()
280 if (part.type == 'changegroup')
286 if (part.type == 'changegroup')
281 and (part.params.get('version', '01')
287 and (part.params.get('version', '01')
282 in changegroup.packermap)]
288 in changegroup.packermap)]
283
289
284 if not cgparts:
290 if not cgparts:
285 raise error.Abort('No changegroups found')
291 raise error.Abort('No changegroups found')
286 version = cgparts[0].params.get('version', '01')
292 version = cgparts[0].params.get('version', '01')
287 cgparts = [p for p in cgparts
293 cgparts = [p for p in cgparts
288 if p.params.get('version', '01') == version]
294 if p.params.get('version', '01') == version]
289 if len(cgparts) > 1:
295 if len(cgparts) > 1:
290 raise NotImplementedError("Can't process multiple changegroups")
296 raise NotImplementedError("Can't process multiple changegroups")
291 part = cgparts[0]
297 part = cgparts[0]
292
298
293 part.seek(0)
299 part.seek(0)
294 self.bundle = changegroup.packermap[version][1](part, 'UN')
300 self.bundle = changegroup.packermap[version][1](part, 'UN')
295
301
296 # dict with the mapping 'filename' -> position in the bundle
302 # dict with the mapping 'filename' -> position in the bundle
297 self.bundlefilespos = {}
303 self.bundlefilespos = {}
298
304
299 self.firstnewrev = self.changelog.repotiprev + 1
305 self.firstnewrev = self.changelog.repotiprev + 1
300 phases.retractboundary(self, None, phases.draft,
306 phases.retractboundary(self, None, phases.draft,
301 [ctx.node() for ctx in self[self.firstnewrev:]])
307 [ctx.node() for ctx in self[self.firstnewrev:]])
302
308
303 @localrepo.unfilteredpropertycache
309 @localrepo.unfilteredpropertycache
304 def _phasecache(self):
310 def _phasecache(self):
305 return bundlephasecache(self, self._phasedefaults)
311 return bundlephasecache(self, self._phasedefaults)
306
312
307 @localrepo.unfilteredpropertycache
313 @localrepo.unfilteredpropertycache
308 def changelog(self):
314 def changelog(self):
309 # consume the header if it exists
315 # consume the header if it exists
310 self.bundle.changelogheader()
316 self.bundle.changelogheader()
311 c = bundlechangelog(self.svfs, self.bundle)
317 c = bundlechangelog(self.svfs, self.bundle)
312 self.manstart = self.bundle.tell()
318 self.manstart = self.bundle.tell()
313 return c
319 return c
314
320
315 @localrepo.unfilteredpropertycache
321 @localrepo.unfilteredpropertycache
316 def manifest(self):
322 def manifest(self):
317 self.bundle.seek(self.manstart)
323 self.bundle.seek(self.manstart)
318 # consume the header if it exists
324 # consume the header if it exists
319 self.bundle.manifestheader()
325 self.bundle.manifestheader()
320 m = bundlemanifest(self.svfs, self.bundle, self.changelog.rev)
326 m = bundlemanifest(self.svfs, self.bundle, self.changelog.rev)
321 self.filestart = self.bundle.tell()
327 self.filestart = self.bundle.tell()
322 return m
328 return m
323
329
324 @localrepo.unfilteredpropertycache
330 @localrepo.unfilteredpropertycache
325 def manstart(self):
331 def manstart(self):
326 self.changelog
332 self.changelog
327 return self.manstart
333 return self.manstart
328
334
329 @localrepo.unfilteredpropertycache
335 @localrepo.unfilteredpropertycache
330 def filestart(self):
336 def filestart(self):
331 self.manifest
337 self.manifest
332 return self.filestart
338 return self.filestart
333
339
334 def url(self):
340 def url(self):
335 return self._url
341 return self._url
336
342
337 def file(self, f):
343 def file(self, f):
338 if not self.bundlefilespos:
344 if not self.bundlefilespos:
339 self.bundle.seek(self.filestart)
345 self.bundle.seek(self.filestart)
340 while True:
346 while True:
341 chunkdata = self.bundle.filelogheader()
347 chunkdata = self.bundle.filelogheader()
342 if not chunkdata:
348 if not chunkdata:
343 break
349 break
344 fname = chunkdata['filename']
350 fname = chunkdata['filename']
345 self.bundlefilespos[fname] = self.bundle.tell()
351 self.bundlefilespos[fname] = self.bundle.tell()
346 while True:
352 while True:
347 c = self.bundle.deltachunk(None)
353 c = self.bundle.deltachunk(None)
348 if not c:
354 if not c:
349 break
355 break
350
356
351 if f in self.bundlefilespos:
357 if f in self.bundlefilespos:
352 self.bundle.seek(self.bundlefilespos[f])
358 self.bundle.seek(self.bundlefilespos[f])
353 return bundlefilelog(self.svfs, f, self.bundle, self.changelog.rev)
359 return bundlefilelog(self.svfs, f, self.bundle, self.changelog.rev)
354 else:
360 else:
355 return filelog.filelog(self.svfs, f)
361 return filelog.filelog(self.svfs, f)
356
362
357 def close(self):
363 def close(self):
358 """Close assigned bundle file immediately."""
364 """Close assigned bundle file immediately."""
359 self.bundlefile.close()
365 self.bundlefile.close()
360 if self.tempfile is not None:
366 if self.tempfile is not None:
361 self.vfs.unlink(self.tempfile)
367 self.vfs.unlink(self.tempfile)
362 if self._tempparent:
368 if self._tempparent:
363 shutil.rmtree(self._tempparent, True)
369 shutil.rmtree(self._tempparent, True)
364
370
365 def cancopy(self):
371 def cancopy(self):
366 return False
372 return False
367
373
368 def peer(self):
374 def peer(self):
369 return bundlepeer(self)
375 return bundlepeer(self)
370
376
371 def getcwd(self):
377 def getcwd(self):
372 return os.getcwd() # always outside the repo
378 return os.getcwd() # always outside the repo
373
379
374
380
375 def instance(ui, path, create):
381 def instance(ui, path, create):
376 if create:
382 if create:
377 raise error.Abort(_('cannot create new bundle repository'))
383 raise error.Abort(_('cannot create new bundle repository'))
378 # internal config: bundle.mainreporoot
384 # internal config: bundle.mainreporoot
379 parentpath = ui.config("bundle", "mainreporoot", "")
385 parentpath = ui.config("bundle", "mainreporoot", "")
380 if not parentpath:
386 if not parentpath:
381 # try to find the correct path to the working directory repo
387 # try to find the correct path to the working directory repo
382 parentpath = cmdutil.findrepo(os.getcwd())
388 parentpath = cmdutil.findrepo(os.getcwd())
383 if parentpath is None:
389 if parentpath is None:
384 parentpath = ''
390 parentpath = ''
385 if parentpath:
391 if parentpath:
386 # Try to make the full path relative so we get a nice, short URL.
392 # Try to make the full path relative so we get a nice, short URL.
387 # In particular, we don't want temp dir names in test outputs.
393 # In particular, we don't want temp dir names in test outputs.
388 cwd = os.getcwd()
394 cwd = os.getcwd()
389 if parentpath == cwd:
395 if parentpath == cwd:
390 parentpath = ''
396 parentpath = ''
391 else:
397 else:
392 cwd = pathutil.normasprefix(cwd)
398 cwd = pathutil.normasprefix(cwd)
393 if parentpath.startswith(cwd):
399 if parentpath.startswith(cwd):
394 parentpath = parentpath[len(cwd):]
400 parentpath = parentpath[len(cwd):]
395 u = util.url(path)
401 u = util.url(path)
396 path = u.localpath()
402 path = u.localpath()
397 if u.scheme == 'bundle':
403 if u.scheme == 'bundle':
398 s = path.split("+", 1)
404 s = path.split("+", 1)
399 if len(s) == 1:
405 if len(s) == 1:
400 repopath, bundlename = parentpath, s[0]
406 repopath, bundlename = parentpath, s[0]
401 else:
407 else:
402 repopath, bundlename = s
408 repopath, bundlename = s
403 else:
409 else:
404 repopath, bundlename = parentpath, path
410 repopath, bundlename = parentpath, path
405 return bundlerepository(ui, repopath, bundlename)
411 return bundlerepository(ui, repopath, bundlename)
406
412
407 class bundletransactionmanager(object):
413 class bundletransactionmanager(object):
408 def transaction(self):
414 def transaction(self):
409 return None
415 return None
410
416
411 def close(self):
417 def close(self):
412 raise NotImplementedError
418 raise NotImplementedError
413
419
414 def release(self):
420 def release(self):
415 raise NotImplementedError
421 raise NotImplementedError
416
422
417 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
423 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
418 force=False):
424 force=False):
419 '''obtains a bundle of changes incoming from other
425 '''obtains a bundle of changes incoming from other
420
426
421 "onlyheads" restricts the returned changes to those reachable from the
427 "onlyheads" restricts the returned changes to those reachable from the
422 specified heads.
428 specified heads.
423 "bundlename", if given, stores the bundle to this file path permanently;
429 "bundlename", if given, stores the bundle to this file path permanently;
424 otherwise it's stored to a temp file and gets deleted again when you call
430 otherwise it's stored to a temp file and gets deleted again when you call
425 the returned "cleanupfn".
431 the returned "cleanupfn".
426 "force" indicates whether to proceed on unrelated repos.
432 "force" indicates whether to proceed on unrelated repos.
427
433
428 Returns a tuple (local, csets, cleanupfn):
434 Returns a tuple (local, csets, cleanupfn):
429
435
430 "local" is a local repo from which to obtain the actual incoming
436 "local" is a local repo from which to obtain the actual incoming
431 changesets; it is a bundlerepo for the obtained bundle when the
437 changesets; it is a bundlerepo for the obtained bundle when the
432 original "other" is remote.
438 original "other" is remote.
433 "csets" lists the incoming changeset node ids.
439 "csets" lists the incoming changeset node ids.
434 "cleanupfn" must be called without arguments when you're done processing
440 "cleanupfn" must be called without arguments when you're done processing
435 the changes; it closes both the original "other" and the one returned
441 the changes; it closes both the original "other" and the one returned
436 here.
442 here.
437 '''
443 '''
438 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
444 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
439 force=force)
445 force=force)
440 common, incoming, rheads = tmp
446 common, incoming, rheads = tmp
441 if not incoming:
447 if not incoming:
442 try:
448 try:
443 if bundlename:
449 if bundlename:
444 os.unlink(bundlename)
450 os.unlink(bundlename)
445 except OSError:
451 except OSError:
446 pass
452 pass
447 return repo, [], other.close
453 return repo, [], other.close
448
454
449 commonset = set(common)
455 commonset = set(common)
450 rheads = [x for x in rheads if x not in commonset]
456 rheads = [x for x in rheads if x not in commonset]
451
457
452 bundle = None
458 bundle = None
453 bundlerepo = None
459 bundlerepo = None
454 localrepo = other.local()
460 localrepo = other.local()
455 if bundlename or not localrepo:
461 if bundlename or not localrepo:
456 # create a bundle (uncompressed if other repo is not local)
462 # create a bundle (uncompressed if other repo is not local)
457
463
458 canbundle2 = (ui.configbool('experimental', 'bundle2-exp', True)
464 canbundle2 = (ui.configbool('experimental', 'bundle2-exp', True)
459 and other.capable('getbundle')
465 and other.capable('getbundle')
460 and other.capable('bundle2'))
466 and other.capable('bundle2'))
461 if canbundle2:
467 if canbundle2:
462 kwargs = {}
468 kwargs = {}
463 kwargs['common'] = common
469 kwargs['common'] = common
464 kwargs['heads'] = rheads
470 kwargs['heads'] = rheads
465 kwargs['bundlecaps'] = exchange.caps20to10(repo)
471 kwargs['bundlecaps'] = exchange.caps20to10(repo)
466 kwargs['cg'] = True
472 kwargs['cg'] = True
467 b2 = other.getbundle('incoming', **kwargs)
473 b2 = other.getbundle('incoming', **kwargs)
468 fname = bundle = changegroup.writechunks(ui, b2._forwardchunks(),
474 fname = bundle = changegroup.writechunks(ui, b2._forwardchunks(),
469 bundlename)
475 bundlename)
470 else:
476 else:
471 if other.capable('getbundle'):
477 if other.capable('getbundle'):
472 cg = other.getbundle('incoming', common=common, heads=rheads)
478 cg = other.getbundle('incoming', common=common, heads=rheads)
473 elif onlyheads is None and not other.capable('changegroupsubset'):
479 elif onlyheads is None and not other.capable('changegroupsubset'):
474 # compat with older servers when pulling all remote heads
480 # compat with older servers when pulling all remote heads
475 cg = other.changegroup(incoming, "incoming")
481 cg = other.changegroup(incoming, "incoming")
476 rheads = None
482 rheads = None
477 else:
483 else:
478 cg = other.changegroupsubset(incoming, rheads, 'incoming')
484 cg = other.changegroupsubset(incoming, rheads, 'incoming')
479 if localrepo:
485 if localrepo:
480 bundletype = "HG10BZ"
486 bundletype = "HG10BZ"
481 else:
487 else:
482 bundletype = "HG10UN"
488 bundletype = "HG10UN"
483 fname = bundle = changegroup.writebundle(ui, cg, bundlename,
489 fname = bundle = changegroup.writebundle(ui, cg, bundlename,
484 bundletype)
490 bundletype)
485 # keep written bundle?
491 # keep written bundle?
486 if bundlename:
492 if bundlename:
487 bundle = None
493 bundle = None
488 if not localrepo:
494 if not localrepo:
489 # use the created uncompressed bundlerepo
495 # use the created uncompressed bundlerepo
490 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
496 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
491 fname)
497 fname)
492 # this repo contains local and other now, so filter out local again
498 # this repo contains local and other now, so filter out local again
493 common = repo.heads()
499 common = repo.heads()
494 if localrepo:
500 if localrepo:
495 # Part of common may be remotely filtered
501 # Part of common may be remotely filtered
496 # So use an unfiltered version
502 # So use an unfiltered version
497 # The discovery process probably need cleanup to avoid that
503 # The discovery process probably need cleanup to avoid that
498 localrepo = localrepo.unfiltered()
504 localrepo = localrepo.unfiltered()
499
505
500 csets = localrepo.changelog.findmissing(common, rheads)
506 csets = localrepo.changelog.findmissing(common, rheads)
501
507
502 if bundlerepo:
508 if bundlerepo:
503 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
509 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
504 remotephases = other.listkeys('phases')
510 remotephases = other.listkeys('phases')
505
511
506 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
512 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
507 pullop.trmanager = bundletransactionmanager()
513 pullop.trmanager = bundletransactionmanager()
508 exchange._pullapplyphases(pullop, remotephases)
514 exchange._pullapplyphases(pullop, remotephases)
509
515
510 def cleanup():
516 def cleanup():
511 if bundlerepo:
517 if bundlerepo:
512 bundlerepo.close()
518 bundlerepo.close()
513 if bundle:
519 if bundle:
514 os.unlink(bundle)
520 os.unlink(bundle)
515 other.close()
521 other.close()
516
522
517 return (localrepo, csets, cleanup)
523 return (localrepo, csets, cleanup)
General Comments 0
You need to be logged in to leave comments. Login now