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