##// END OF EJS Templates
bundlerepo: fix closing and docstring of getremotechanges...
Peter Arrenbrecht -
r14190:8aab5a82 default
parent child Browse files
Show More
@@ -1,354 +1,355 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 node import nullid
14 from node import nullid
15 from i18n import _
15 from i18n import _
16 import os, tempfile, shutil
16 import os, tempfile, shutil
17 import changegroup, util, mdiff, discovery
17 import changegroup, util, mdiff, discovery
18 import localrepo, changelog, manifest, filelog, revlog, error
18 import localrepo, changelog, manifest, filelog, revlog, error
19
19
20 class bundlerevlog(revlog.revlog):
20 class bundlerevlog(revlog.revlog):
21 def __init__(self, opener, indexfile, bundle, linkmapper):
21 def __init__(self, opener, indexfile, bundle, linkmapper):
22 # How it works:
22 # How it works:
23 # to retrieve a revision, we need to know the offset of
23 # to retrieve a revision, we need to know the offset of
24 # the revision in the bundle (an unbundle object).
24 # the revision in the bundle (an unbundle object).
25 #
25 #
26 # We store this offset in the index (start), to differentiate a
26 # We store this offset in the index (start), to differentiate a
27 # rev in the bundle and from a rev in the revlog, we check
27 # rev in the bundle and from a rev in the revlog, we check
28 # len(index[r]). If the tuple is bigger than 7, it is a bundle
28 # len(index[r]). If the tuple is bigger than 7, it is a bundle
29 # (it is bigger since we store the node to which the delta is)
29 # (it is bigger since we store the node to which the delta is)
30 #
30 #
31 revlog.revlog.__init__(self, opener, indexfile)
31 revlog.revlog.__init__(self, opener, indexfile)
32 self.bundle = bundle
32 self.bundle = bundle
33 self.basemap = {}
33 self.basemap = {}
34 n = len(self)
34 n = len(self)
35 chain = None
35 chain = None
36 while 1:
36 while 1:
37 chunkdata = bundle.deltachunk(chain)
37 chunkdata = bundle.deltachunk(chain)
38 if not chunkdata:
38 if not chunkdata:
39 break
39 break
40 node = chunkdata['node']
40 node = chunkdata['node']
41 p1 = chunkdata['p1']
41 p1 = chunkdata['p1']
42 p2 = chunkdata['p2']
42 p2 = chunkdata['p2']
43 cs = chunkdata['cs']
43 cs = chunkdata['cs']
44 deltabase = chunkdata['deltabase']
44 deltabase = chunkdata['deltabase']
45 delta = chunkdata['delta']
45 delta = chunkdata['delta']
46
46
47 size = len(delta)
47 size = len(delta)
48 start = bundle.tell() - size
48 start = bundle.tell() - size
49
49
50 link = linkmapper(cs)
50 link = linkmapper(cs)
51 if node in self.nodemap:
51 if node in self.nodemap:
52 # this can happen if two branches make the same change
52 # this can happen if two branches make the same change
53 chain = node
53 chain = node
54 continue
54 continue
55
55
56 for p in (p1, p2):
56 for p in (p1, p2):
57 if not p in self.nodemap:
57 if not p in self.nodemap:
58 raise error.LookupError(p, self.indexfile,
58 raise error.LookupError(p, self.indexfile,
59 _("unknown parent"))
59 _("unknown parent"))
60 # start, size, full unc. size, base (unused), link, p1, p2, node
60 # start, size, full unc. size, base (unused), link, p1, p2, node
61 e = (revlog.offset_type(start, 0), size, -1, -1, link,
61 e = (revlog.offset_type(start, 0), size, -1, -1, link,
62 self.rev(p1), self.rev(p2), node)
62 self.rev(p1), self.rev(p2), node)
63 self.basemap[n] = deltabase
63 self.basemap[n] = deltabase
64 self.index.insert(-1, e)
64 self.index.insert(-1, e)
65 self.nodemap[node] = n
65 self.nodemap[node] = n
66 chain = node
66 chain = node
67 n += 1
67 n += 1
68
68
69 def inbundle(self, rev):
69 def inbundle(self, rev):
70 """is rev from the bundle"""
70 """is rev from the bundle"""
71 if rev < 0:
71 if rev < 0:
72 return False
72 return False
73 return rev in self.basemap
73 return rev in self.basemap
74 def bundlebase(self, rev):
74 def bundlebase(self, rev):
75 return self.basemap[rev]
75 return self.basemap[rev]
76 def _chunk(self, rev):
76 def _chunk(self, rev):
77 # Warning: in case of bundle, the diff is against bundlebase,
77 # Warning: in case of bundle, the diff is against bundlebase,
78 # not against rev - 1
78 # not against rev - 1
79 # XXX: could use some caching
79 # XXX: could use some caching
80 if not self.inbundle(rev):
80 if not self.inbundle(rev):
81 return revlog.revlog._chunk(self, rev)
81 return revlog.revlog._chunk(self, rev)
82 self.bundle.seek(self.start(rev))
82 self.bundle.seek(self.start(rev))
83 return self.bundle.read(self.length(rev))
83 return self.bundle.read(self.length(rev))
84
84
85 def revdiff(self, rev1, rev2):
85 def revdiff(self, rev1, rev2):
86 """return or calculate a delta between two revisions"""
86 """return or calculate a delta between two revisions"""
87 if self.inbundle(rev1) and self.inbundle(rev2):
87 if self.inbundle(rev1) and self.inbundle(rev2):
88 # hot path for bundle
88 # hot path for bundle
89 revb = self.rev(self.bundlebase(rev2))
89 revb = self.rev(self.bundlebase(rev2))
90 if revb == rev1:
90 if revb == rev1:
91 return self._chunk(rev2)
91 return self._chunk(rev2)
92 elif not self.inbundle(rev1) and not self.inbundle(rev2):
92 elif not self.inbundle(rev1) and not self.inbundle(rev2):
93 return revlog.revlog.revdiff(self, rev1, rev2)
93 return revlog.revlog.revdiff(self, rev1, rev2)
94
94
95 return mdiff.textdiff(self.revision(self.node(rev1)),
95 return mdiff.textdiff(self.revision(self.node(rev1)),
96 self.revision(self.node(rev2)))
96 self.revision(self.node(rev2)))
97
97
98 def revision(self, node):
98 def revision(self, node):
99 """return an uncompressed revision of a given"""
99 """return an uncompressed revision of a given"""
100 if node == nullid:
100 if node == nullid:
101 return ""
101 return ""
102
102
103 text = None
103 text = None
104 chain = []
104 chain = []
105 iter_node = node
105 iter_node = node
106 rev = self.rev(iter_node)
106 rev = self.rev(iter_node)
107 # reconstruct the revision if it is from a changegroup
107 # reconstruct the revision if it is from a changegroup
108 while self.inbundle(rev):
108 while self.inbundle(rev):
109 if self._cache and self._cache[0] == iter_node:
109 if self._cache and self._cache[0] == iter_node:
110 text = self._cache[2]
110 text = self._cache[2]
111 break
111 break
112 chain.append(rev)
112 chain.append(rev)
113 iter_node = self.bundlebase(rev)
113 iter_node = self.bundlebase(rev)
114 rev = self.rev(iter_node)
114 rev = self.rev(iter_node)
115 if text is None:
115 if text is None:
116 text = revlog.revlog.revision(self, iter_node)
116 text = revlog.revlog.revision(self, iter_node)
117
117
118 while chain:
118 while chain:
119 delta = self._chunk(chain.pop())
119 delta = self._chunk(chain.pop())
120 text = mdiff.patches(text, [delta])
120 text = mdiff.patches(text, [delta])
121
121
122 p1, p2 = self.parents(node)
122 p1, p2 = self.parents(node)
123 if node != revlog.hash(text, p1, p2):
123 if node != revlog.hash(text, p1, p2):
124 raise error.RevlogError(_("integrity check failed on %s:%d")
124 raise error.RevlogError(_("integrity check failed on %s:%d")
125 % (self.datafile, self.rev(node)))
125 % (self.datafile, self.rev(node)))
126
126
127 self._cache = (node, self.rev(node), text)
127 self._cache = (node, self.rev(node), text)
128 return text
128 return text
129
129
130 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
130 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
131 raise NotImplementedError
131 raise NotImplementedError
132 def addgroup(self, revs, linkmapper, transaction):
132 def addgroup(self, revs, linkmapper, transaction):
133 raise NotImplementedError
133 raise NotImplementedError
134 def strip(self, rev, minlink):
134 def strip(self, rev, minlink):
135 raise NotImplementedError
135 raise NotImplementedError
136 def checksize(self):
136 def checksize(self):
137 raise NotImplementedError
137 raise NotImplementedError
138
138
139 class bundlechangelog(bundlerevlog, changelog.changelog):
139 class bundlechangelog(bundlerevlog, changelog.changelog):
140 def __init__(self, opener, bundle):
140 def __init__(self, opener, bundle):
141 changelog.changelog.__init__(self, opener)
141 changelog.changelog.__init__(self, opener)
142 linkmapper = lambda x: x
142 linkmapper = lambda x: x
143 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
143 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
144 linkmapper)
144 linkmapper)
145
145
146 class bundlemanifest(bundlerevlog, manifest.manifest):
146 class bundlemanifest(bundlerevlog, manifest.manifest):
147 def __init__(self, opener, bundle, linkmapper):
147 def __init__(self, opener, bundle, linkmapper):
148 manifest.manifest.__init__(self, opener)
148 manifest.manifest.__init__(self, opener)
149 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
149 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
150 linkmapper)
150 linkmapper)
151
151
152 class bundlefilelog(bundlerevlog, filelog.filelog):
152 class bundlefilelog(bundlerevlog, filelog.filelog):
153 def __init__(self, opener, path, bundle, linkmapper):
153 def __init__(self, opener, path, bundle, linkmapper):
154 filelog.filelog.__init__(self, opener, path)
154 filelog.filelog.__init__(self, opener, path)
155 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
155 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
156 linkmapper)
156 linkmapper)
157
157
158 class bundlerepository(localrepo.localrepository):
158 class bundlerepository(localrepo.localrepository):
159 def __init__(self, ui, path, bundlename):
159 def __init__(self, ui, path, bundlename):
160 self._tempparent = None
160 self._tempparent = None
161 try:
161 try:
162 localrepo.localrepository.__init__(self, ui, path)
162 localrepo.localrepository.__init__(self, ui, path)
163 except error.RepoError:
163 except error.RepoError:
164 self._tempparent = tempfile.mkdtemp()
164 self._tempparent = tempfile.mkdtemp()
165 localrepo.instance(ui, self._tempparent, 1)
165 localrepo.instance(ui, self._tempparent, 1)
166 localrepo.localrepository.__init__(self, ui, self._tempparent)
166 localrepo.localrepository.__init__(self, ui, self._tempparent)
167
167
168 if path:
168 if path:
169 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
169 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
170 else:
170 else:
171 self._url = 'bundle:' + bundlename
171 self._url = 'bundle:' + bundlename
172
172
173 self.tempfile = None
173 self.tempfile = None
174 f = util.posixfile(bundlename, "rb")
174 f = util.posixfile(bundlename, "rb")
175 self.bundle = changegroup.readbundle(f, bundlename)
175 self.bundle = changegroup.readbundle(f, bundlename)
176 if self.bundle.compressed():
176 if self.bundle.compressed():
177 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
177 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
178 suffix=".hg10un", dir=self.path)
178 suffix=".hg10un", dir=self.path)
179 self.tempfile = temp
179 self.tempfile = temp
180 fptemp = os.fdopen(fdtemp, 'wb')
180 fptemp = os.fdopen(fdtemp, 'wb')
181
181
182 try:
182 try:
183 fptemp.write("HG10UN")
183 fptemp.write("HG10UN")
184 while 1:
184 while 1:
185 chunk = self.bundle.read(2**18)
185 chunk = self.bundle.read(2**18)
186 if not chunk:
186 if not chunk:
187 break
187 break
188 fptemp.write(chunk)
188 fptemp.write(chunk)
189 finally:
189 finally:
190 fptemp.close()
190 fptemp.close()
191
191
192 f = util.posixfile(self.tempfile, "rb")
192 f = util.posixfile(self.tempfile, "rb")
193 self.bundle = changegroup.readbundle(f, bundlename)
193 self.bundle = changegroup.readbundle(f, bundlename)
194
194
195 # dict with the mapping 'filename' -> position in the bundle
195 # dict with the mapping 'filename' -> position in the bundle
196 self.bundlefilespos = {}
196 self.bundlefilespos = {}
197
197
198 @util.propertycache
198 @util.propertycache
199 def changelog(self):
199 def changelog(self):
200 # consume the header if it exists
200 # consume the header if it exists
201 self.bundle.changelogheader()
201 self.bundle.changelogheader()
202 c = bundlechangelog(self.sopener, self.bundle)
202 c = bundlechangelog(self.sopener, self.bundle)
203 self.manstart = self.bundle.tell()
203 self.manstart = self.bundle.tell()
204 return c
204 return c
205
205
206 @util.propertycache
206 @util.propertycache
207 def manifest(self):
207 def manifest(self):
208 self.bundle.seek(self.manstart)
208 self.bundle.seek(self.manstart)
209 # consume the header if it exists
209 # consume the header if it exists
210 self.bundle.manifestheader()
210 self.bundle.manifestheader()
211 m = bundlemanifest(self.sopener, self.bundle, self.changelog.rev)
211 m = bundlemanifest(self.sopener, self.bundle, self.changelog.rev)
212 self.filestart = self.bundle.tell()
212 self.filestart = self.bundle.tell()
213 return m
213 return m
214
214
215 @util.propertycache
215 @util.propertycache
216 def manstart(self):
216 def manstart(self):
217 self.changelog
217 self.changelog
218 return self.manstart
218 return self.manstart
219
219
220 @util.propertycache
220 @util.propertycache
221 def filestart(self):
221 def filestart(self):
222 self.manifest
222 self.manifest
223 return self.filestart
223 return self.filestart
224
224
225 def url(self):
225 def url(self):
226 return self._url
226 return self._url
227
227
228 def file(self, f):
228 def file(self, f):
229 if not self.bundlefilespos:
229 if not self.bundlefilespos:
230 self.bundle.seek(self.filestart)
230 self.bundle.seek(self.filestart)
231 while 1:
231 while 1:
232 chunkdata = self.bundle.filelogheader()
232 chunkdata = self.bundle.filelogheader()
233 if not chunkdata:
233 if not chunkdata:
234 break
234 break
235 fname = chunkdata['filename']
235 fname = chunkdata['filename']
236 self.bundlefilespos[fname] = self.bundle.tell()
236 self.bundlefilespos[fname] = self.bundle.tell()
237 while 1:
237 while 1:
238 c = self.bundle.deltachunk(None)
238 c = self.bundle.deltachunk(None)
239 if not c:
239 if not c:
240 break
240 break
241
241
242 if f[0] == '/':
242 if f[0] == '/':
243 f = f[1:]
243 f = f[1:]
244 if f in self.bundlefilespos:
244 if f in self.bundlefilespos:
245 self.bundle.seek(self.bundlefilespos[f])
245 self.bundle.seek(self.bundlefilespos[f])
246 return bundlefilelog(self.sopener, f, self.bundle,
246 return bundlefilelog(self.sopener, f, self.bundle,
247 self.changelog.rev)
247 self.changelog.rev)
248 else:
248 else:
249 return filelog.filelog(self.sopener, f)
249 return filelog.filelog(self.sopener, f)
250
250
251 def close(self):
251 def close(self):
252 """Close assigned bundle file immediately."""
252 """Close assigned bundle file immediately."""
253 self.bundle.close()
253 self.bundle.close()
254 if self.tempfile is not None:
254 if self.tempfile is not None:
255 os.unlink(self.tempfile)
255 os.unlink(self.tempfile)
256 if self._tempparent:
256 if self._tempparent:
257 shutil.rmtree(self._tempparent, True)
257 shutil.rmtree(self._tempparent, True)
258
258
259 def cancopy(self):
259 def cancopy(self):
260 return False
260 return False
261
261
262 def getcwd(self):
262 def getcwd(self):
263 return os.getcwd() # always outside the repo
263 return os.getcwd() # always outside the repo
264
264
265 def instance(ui, path, create):
265 def instance(ui, path, create):
266 if create:
266 if create:
267 raise util.Abort(_('cannot create new bundle repository'))
267 raise util.Abort(_('cannot create new bundle repository'))
268 parentpath = ui.config("bundle", "mainreporoot", "")
268 parentpath = ui.config("bundle", "mainreporoot", "")
269 if parentpath:
269 if parentpath:
270 # Try to make the full path relative so we get a nice, short URL.
270 # Try to make the full path relative so we get a nice, short URL.
271 # In particular, we don't want temp dir names in test outputs.
271 # In particular, we don't want temp dir names in test outputs.
272 cwd = os.getcwd()
272 cwd = os.getcwd()
273 if parentpath == cwd:
273 if parentpath == cwd:
274 parentpath = ''
274 parentpath = ''
275 else:
275 else:
276 cwd = os.path.join(cwd,'')
276 cwd = os.path.join(cwd,'')
277 if parentpath.startswith(cwd):
277 if parentpath.startswith(cwd):
278 parentpath = parentpath[len(cwd):]
278 parentpath = parentpath[len(cwd):]
279 u = util.url(path)
279 u = util.url(path)
280 path = u.localpath()
280 path = u.localpath()
281 if u.scheme == 'bundle':
281 if u.scheme == 'bundle':
282 s = path.split("+", 1)
282 s = path.split("+", 1)
283 if len(s) == 1:
283 if len(s) == 1:
284 repopath, bundlename = parentpath, s[0]
284 repopath, bundlename = parentpath, s[0]
285 else:
285 else:
286 repopath, bundlename = s
286 repopath, bundlename = s
287 else:
287 else:
288 repopath, bundlename = parentpath, path
288 repopath, bundlename = parentpath, path
289 return bundlerepository(ui, repopath, bundlename)
289 return bundlerepository(ui, repopath, bundlename)
290
290
291 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
291 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
292 force=False):
292 force=False):
293 '''obtains a bundle of changes incoming from other
293 '''obtains a bundle of changes incoming from other
294
294
295 "onlyheads" restricts the returned changes to those reachable from the
295 "onlyheads" restricts the returned changes to those reachable from the
296 specified heads.
296 specified heads.
297 "bundlename", if given, stores the bundle to this file path permanently;
297 "bundlename", if given, stores the bundle to this file path permanently;
298 the returned "bundle" will be None.
298 otherwise it's stored to a temp file and gets deleted again when you call
299 the returned "cleanupfn".
299 "force" indicates whether to proceed on unrelated repos.
300 "force" indicates whether to proceed on unrelated repos.
300
301
301 Returns a tuple (local, csets, cleanupfn):
302 Returns a tuple (local, csets, cleanupfn):
302
303
303 "local" is a local repo from which to obtain the actual incoming changesets; it
304 "local" is a local repo from which to obtain the actual incoming changesets; it
304 is a bundlerepo for the obtained bundle when the original "other" is remote.
305 is a bundlerepo for the obtained bundle when the original "other" is remote.
305 "csets" lists the incoming changeset node ids.
306 "csets" lists the incoming changeset node ids.
306 "cleanupfn" must be called without arguments when you're done processing the
307 "cleanupfn" must be called without arguments when you're done processing the
307 changes; it closes both the original "other" and the one returned here.
308 changes; it closes both the original "other" and the one returned here.
308 '''
309 '''
309 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads, force=force)
310 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads, force=force)
310 common, incoming, rheads = tmp
311 common, incoming, rheads = tmp
311 if not incoming:
312 if not incoming:
312 try:
313 try:
313 os.unlink(bundlename)
314 os.unlink(bundlename)
314 except OSError:
315 except OSError:
315 pass
316 pass
316 return other, [], other.close
317 return other, [], other.close
317
318
318 bundle = None
319 bundle = None
319 bundlerepo = None
320 bundlerepo = None
320 localrepo = other
321 localrepo = other
321 if bundlename or not other.local():
322 if bundlename or not other.local():
322 # create a bundle (uncompressed if other repo is not local)
323 # create a bundle (uncompressed if other repo is not local)
323
324
324 if onlyheads is None and other.capable('changegroupsubset'):
325 if onlyheads is None and other.capable('changegroupsubset'):
325 onlyheads = rheads
326 onlyheads = rheads
326
327
327 if other.capable('getbundle'):
328 if other.capable('getbundle'):
328 cg = other.getbundle('incoming', common=common, heads=onlyheads)
329 cg = other.getbundle('incoming', common=common, heads=onlyheads)
329 elif onlyheads is None:
330 elif onlyheads is None:
330 cg = other.changegroup(incoming, "incoming")
331 cg = other.changegroup(incoming, "incoming")
331 else:
332 else:
332 cg = other.changegroupsubset(incoming, onlyheads, 'incoming')
333 cg = other.changegroupsubset(incoming, onlyheads, 'incoming')
333 bundletype = other.local() and "HG10BZ" or "HG10UN"
334 bundletype = other.local() and "HG10BZ" or "HG10UN"
334 fname = bundle = changegroup.writebundle(cg, bundlename, bundletype)
335 fname = bundle = changegroup.writebundle(cg, bundlename, bundletype)
335 # keep written bundle?
336 # keep written bundle?
336 if bundlename:
337 if bundlename:
337 bundle = None
338 bundle = None
338 if not other.local():
339 if not other.local():
339 # use the created uncompressed bundlerepo
340 # use the created uncompressed bundlerepo
340 localrepo = bundlerepo = bundlerepository(ui, repo.root, fname)
341 localrepo = bundlerepo = bundlerepository(ui, repo.root, fname)
341 # this repo contains local and other now, so filter out local again
342 # this repo contains local and other now, so filter out local again
342 common = repo.heads()
343 common = repo.heads()
343
344
344 csets = localrepo.changelog.findmissing(common, onlyheads)
345 csets = localrepo.changelog.findmissing(common, onlyheads)
345
346
346 def cleanup():
347 def cleanup():
347 if bundlerepo:
348 if bundlerepo:
348 bundlerepo.close()
349 bundlerepo.close()
349 if bundle:
350 if bundle:
350 os.unlink(bundle)
351 os.unlink(bundle)
351 localrepo.close()
352 other.close()
352
353
353 return (localrepo, csets, cleanup)
354 return (localrepo, csets, cleanup)
354
355
General Comments 0
You need to be logged in to leave comments. Login now