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