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