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