##// END OF EJS Templates
filelog: wrap revlog instead of inheriting it (API)...
Gregory Szorc -
r37515:1541e1a8 default
parent child Browse files
Show More
@@ -1,598 +1,598 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 __future__ import absolute_import
15 15
16 16 import os
17 17 import shutil
18 18 import tempfile
19 19
20 20 from .i18n import _
21 21 from .node import nullid
22 22
23 23 from . import (
24 24 bundle2,
25 25 changegroup,
26 26 changelog,
27 27 cmdutil,
28 28 discovery,
29 29 error,
30 30 exchange,
31 31 filelog,
32 32 localrepo,
33 33 manifest,
34 34 mdiff,
35 35 node as nodemod,
36 36 pathutil,
37 37 phases,
38 38 pycompat,
39 39 revlog,
40 40 util,
41 41 vfs as vfsmod,
42 42 )
43 43
44 44 class bundlerevlog(revlog.revlog):
45 45 def __init__(self, opener, indexfile, cgunpacker, linkmapper):
46 46 # How it works:
47 47 # To retrieve a revision, we need to know the offset of the revision in
48 48 # the bundle (an unbundle object). We store this offset in the index
49 49 # (start). The base of the delta is stored in the base field.
50 50 #
51 51 # To differentiate a rev in the bundle from a rev in the revlog, we
52 52 # check revision against repotiprev.
53 53 opener = vfsmod.readonlyvfs(opener)
54 54 revlog.revlog.__init__(self, opener, indexfile)
55 55 self.bundle = cgunpacker
56 56 n = len(self)
57 57 self.repotiprev = n - 1
58 58 self.bundlerevs = set() # used by 'bundle()' revset expression
59 59 for deltadata in cgunpacker.deltaiter():
60 60 node, p1, p2, cs, deltabase, delta, flags = deltadata
61 61
62 62 size = len(delta)
63 63 start = cgunpacker.tell() - size
64 64
65 65 link = linkmapper(cs)
66 66 if node in self.nodemap:
67 67 # this can happen if two branches make the same change
68 68 self.bundlerevs.add(self.nodemap[node])
69 69 continue
70 70
71 71 for p in (p1, p2):
72 72 if p not in self.nodemap:
73 73 raise error.LookupError(p, self.indexfile,
74 74 _("unknown parent"))
75 75
76 76 if deltabase not in self.nodemap:
77 77 raise LookupError(deltabase, self.indexfile,
78 78 _('unknown delta base'))
79 79
80 80 baserev = self.rev(deltabase)
81 81 # start, size, full unc. size, base (unused), link, p1, p2, node
82 82 e = (revlog.offset_type(start, flags), size, -1, baserev, link,
83 83 self.rev(p1), self.rev(p2), node)
84 84 self.index.insert(-1, e)
85 85 self.nodemap[node] = n
86 86 self.bundlerevs.add(n)
87 87 n += 1
88 88
89 89 def _chunk(self, rev, df=None):
90 90 # Warning: in case of bundle, the diff is against what we stored as
91 91 # delta base, not against rev - 1
92 92 # XXX: could use some caching
93 93 if rev <= self.repotiprev:
94 94 return revlog.revlog._chunk(self, rev)
95 95 self.bundle.seek(self.start(rev))
96 96 return self.bundle.read(self.length(rev))
97 97
98 98 def revdiff(self, rev1, rev2):
99 99 """return or calculate a delta between two revisions"""
100 100 if rev1 > self.repotiprev and rev2 > self.repotiprev:
101 101 # hot path for bundle
102 102 revb = self.index[rev2][3]
103 103 if revb == rev1:
104 104 return self._chunk(rev2)
105 105 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
106 106 return revlog.revlog.revdiff(self, rev1, rev2)
107 107
108 108 return mdiff.textdiff(self.revision(rev1, raw=True),
109 109 self.revision(rev2, raw=True))
110 110
111 111 def revision(self, nodeorrev, _df=None, raw=False):
112 112 """return an uncompressed revision of a given node or revision
113 113 number.
114 114 """
115 115 if isinstance(nodeorrev, int):
116 116 rev = nodeorrev
117 117 node = self.node(rev)
118 118 else:
119 119 node = nodeorrev
120 120 rev = self.rev(node)
121 121
122 122 if node == nullid:
123 123 return ""
124 124
125 125 rawtext = None
126 126 chain = []
127 127 iterrev = rev
128 128 # reconstruct the revision if it is from a changegroup
129 129 while iterrev > self.repotiprev:
130 130 if self._cache and self._cache[1] == iterrev:
131 131 rawtext = self._cache[2]
132 132 break
133 133 chain.append(iterrev)
134 134 iterrev = self.index[iterrev][3]
135 135 if rawtext is None:
136 136 rawtext = self.baserevision(iterrev)
137 137
138 138 while chain:
139 139 delta = self._chunk(chain.pop())
140 140 rawtext = mdiff.patches(rawtext, [delta])
141 141
142 142 text, validatehash = self._processflags(rawtext, self.flags(rev),
143 143 'read', raw=raw)
144 144 if validatehash:
145 145 self.checkhash(text, node, rev=rev)
146 146 self._cache = (node, rev, rawtext)
147 147 return text
148 148
149 149 def baserevision(self, nodeorrev):
150 150 # Revlog subclasses may override 'revision' method to modify format of
151 151 # content retrieved from revlog. To use bundlerevlog with such class one
152 152 # needs to override 'baserevision' and make more specific call here.
153 153 return revlog.revlog.revision(self, nodeorrev, raw=True)
154 154
155 155 def addrevision(self, *args, **kwargs):
156 156 raise NotImplementedError
157 157
158 158 def addgroup(self, *args, **kwargs):
159 159 raise NotImplementedError
160 160
161 161 def strip(self, *args, **kwargs):
162 162 raise NotImplementedError
163 163
164 164 def checksize(self):
165 165 raise NotImplementedError
166 166
167 167 class bundlechangelog(bundlerevlog, changelog.changelog):
168 168 def __init__(self, opener, cgunpacker):
169 169 changelog.changelog.__init__(self, opener)
170 170 linkmapper = lambda x: x
171 171 bundlerevlog.__init__(self, opener, self.indexfile, cgunpacker,
172 172 linkmapper)
173 173
174 174 def baserevision(self, nodeorrev):
175 175 # Although changelog doesn't override 'revision' method, some extensions
176 176 # may replace this class with another that does. Same story with
177 177 # manifest and filelog classes.
178 178
179 179 # This bypasses filtering on changelog.node() and rev() because we need
180 180 # revision text of the bundle base even if it is hidden.
181 181 oldfilter = self.filteredrevs
182 182 try:
183 183 self.filteredrevs = ()
184 184 return changelog.changelog.revision(self, nodeorrev, raw=True)
185 185 finally:
186 186 self.filteredrevs = oldfilter
187 187
188 188 class bundlemanifest(bundlerevlog, manifest.manifestrevlog):
189 189 def __init__(self, opener, cgunpacker, linkmapper, dirlogstarts=None,
190 190 dir=''):
191 191 manifest.manifestrevlog.__init__(self, opener, dir=dir)
192 192 bundlerevlog.__init__(self, opener, self.indexfile, cgunpacker,
193 193 linkmapper)
194 194 if dirlogstarts is None:
195 195 dirlogstarts = {}
196 196 if self.bundle.version == "03":
197 197 dirlogstarts = _getfilestarts(self.bundle)
198 198 self._dirlogstarts = dirlogstarts
199 199 self._linkmapper = linkmapper
200 200
201 201 def baserevision(self, nodeorrev):
202 202 node = nodeorrev
203 203 if isinstance(node, int):
204 204 node = self.node(node)
205 205
206 206 if node in self.fulltextcache:
207 207 result = '%s' % self.fulltextcache[node]
208 208 else:
209 209 result = manifest.manifestrevlog.revision(self, nodeorrev, raw=True)
210 210 return result
211 211
212 212 def dirlog(self, d):
213 213 if d in self._dirlogstarts:
214 214 self.bundle.seek(self._dirlogstarts[d])
215 215 return bundlemanifest(
216 216 self.opener, self.bundle, self._linkmapper,
217 217 self._dirlogstarts, dir=d)
218 218 return super(bundlemanifest, self).dirlog(d)
219 219
220 class bundlefilelog(bundlerevlog, filelog.filelog):
220 class bundlefilelog(filelog.filelog):
221 221 def __init__(self, opener, path, cgunpacker, linkmapper):
222 222 filelog.filelog.__init__(self, opener, path)
223 bundlerevlog.__init__(self, opener, self.indexfile, cgunpacker,
224 linkmapper)
223 self._revlog = bundlerevlog(opener, self.indexfile,
224 cgunpacker, linkmapper)
225 225
226 226 def baserevision(self, nodeorrev):
227 227 return filelog.filelog.revision(self, nodeorrev, raw=True)
228 228
229 229 class bundlepeer(localrepo.localpeer):
230 230 def canpush(self):
231 231 return False
232 232
233 233 class bundlephasecache(phases.phasecache):
234 234 def __init__(self, *args, **kwargs):
235 235 super(bundlephasecache, self).__init__(*args, **kwargs)
236 236 if util.safehasattr(self, 'opener'):
237 237 self.opener = vfsmod.readonlyvfs(self.opener)
238 238
239 239 def write(self):
240 240 raise NotImplementedError
241 241
242 242 def _write(self, fp):
243 243 raise NotImplementedError
244 244
245 245 def _updateroots(self, phase, newroots, tr):
246 246 self.phaseroots[phase] = newroots
247 247 self.invalidate()
248 248 self.dirty = True
249 249
250 250 def _getfilestarts(cgunpacker):
251 251 filespos = {}
252 252 for chunkdata in iter(cgunpacker.filelogheader, {}):
253 253 fname = chunkdata['filename']
254 254 filespos[fname] = cgunpacker.tell()
255 255 for chunk in iter(lambda: cgunpacker.deltachunk(None), {}):
256 256 pass
257 257 return filespos
258 258
259 259 class bundlerepository(localrepo.localrepository):
260 260 """A repository instance that is a union of a local repo and a bundle.
261 261
262 262 Instances represent a read-only repository composed of a local repository
263 263 with the contents of a bundle file applied. The repository instance is
264 264 conceptually similar to the state of a repository after an
265 265 ``hg unbundle`` operation. However, the contents of the bundle are never
266 266 applied to the actual base repository.
267 267 """
268 268 def __init__(self, ui, repopath, bundlepath):
269 269 self._tempparent = None
270 270 try:
271 271 localrepo.localrepository.__init__(self, ui, repopath)
272 272 except error.RepoError:
273 273 self._tempparent = tempfile.mkdtemp()
274 274 localrepo.instance(ui, self._tempparent, 1)
275 275 localrepo.localrepository.__init__(self, ui, self._tempparent)
276 276 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
277 277
278 278 if repopath:
279 279 self._url = 'bundle:' + util.expandpath(repopath) + '+' + bundlepath
280 280 else:
281 281 self._url = 'bundle:' + bundlepath
282 282
283 283 self.tempfile = None
284 284 f = util.posixfile(bundlepath, "rb")
285 285 bundle = exchange.readbundle(ui, f, bundlepath)
286 286
287 287 if isinstance(bundle, bundle2.unbundle20):
288 288 self._bundlefile = bundle
289 289 self._cgunpacker = None
290 290
291 291 cgpart = None
292 292 for part in bundle.iterparts(seekable=True):
293 293 if part.type == 'changegroup':
294 294 if cgpart:
295 295 raise NotImplementedError("can't process "
296 296 "multiple changegroups")
297 297 cgpart = part
298 298
299 299 self._handlebundle2part(bundle, part)
300 300
301 301 if not cgpart:
302 302 raise error.Abort(_("No changegroups found"))
303 303
304 304 # This is required to placate a later consumer, which expects
305 305 # the payload offset to be at the beginning of the changegroup.
306 306 # We need to do this after the iterparts() generator advances
307 307 # because iterparts() will seek to end of payload after the
308 308 # generator returns control to iterparts().
309 309 cgpart.seek(0, os.SEEK_SET)
310 310
311 311 elif isinstance(bundle, changegroup.cg1unpacker):
312 312 if bundle.compressed():
313 313 f = self._writetempbundle(bundle.read, '.hg10un',
314 314 header='HG10UN')
315 315 bundle = exchange.readbundle(ui, f, bundlepath, self.vfs)
316 316
317 317 self._bundlefile = bundle
318 318 self._cgunpacker = bundle
319 319 else:
320 320 raise error.Abort(_('bundle type %s cannot be read') %
321 321 type(bundle))
322 322
323 323 # dict with the mapping 'filename' -> position in the changegroup.
324 324 self._cgfilespos = {}
325 325
326 326 self.firstnewrev = self.changelog.repotiprev + 1
327 327 phases.retractboundary(self, None, phases.draft,
328 328 [ctx.node() for ctx in self[self.firstnewrev:]])
329 329
330 330 def _handlebundle2part(self, bundle, part):
331 331 if part.type != 'changegroup':
332 332 return
333 333
334 334 cgstream = part
335 335 version = part.params.get('version', '01')
336 336 legalcgvers = changegroup.supportedincomingversions(self)
337 337 if version not in legalcgvers:
338 338 msg = _('Unsupported changegroup version: %s')
339 339 raise error.Abort(msg % version)
340 340 if bundle.compressed():
341 341 cgstream = self._writetempbundle(part.read, '.cg%sun' % version)
342 342
343 343 self._cgunpacker = changegroup.getunbundler(version, cgstream, 'UN')
344 344
345 345 def _writetempbundle(self, readfn, suffix, header=''):
346 346 """Write a temporary file to disk
347 347 """
348 348 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
349 349 suffix=suffix)
350 350 self.tempfile = temp
351 351
352 352 with os.fdopen(fdtemp, r'wb') as fptemp:
353 353 fptemp.write(header)
354 354 while True:
355 355 chunk = readfn(2**18)
356 356 if not chunk:
357 357 break
358 358 fptemp.write(chunk)
359 359
360 360 return self.vfs.open(self.tempfile, mode="rb")
361 361
362 362 @localrepo.unfilteredpropertycache
363 363 def _phasecache(self):
364 364 return bundlephasecache(self, self._phasedefaults)
365 365
366 366 @localrepo.unfilteredpropertycache
367 367 def changelog(self):
368 368 # consume the header if it exists
369 369 self._cgunpacker.changelogheader()
370 370 c = bundlechangelog(self.svfs, self._cgunpacker)
371 371 self.manstart = self._cgunpacker.tell()
372 372 return c
373 373
374 374 def _constructmanifest(self):
375 375 self._cgunpacker.seek(self.manstart)
376 376 # consume the header if it exists
377 377 self._cgunpacker.manifestheader()
378 378 linkmapper = self.unfiltered().changelog.rev
379 379 m = bundlemanifest(self.svfs, self._cgunpacker, linkmapper)
380 380 self.filestart = self._cgunpacker.tell()
381 381 return m
382 382
383 383 def _consumemanifest(self):
384 384 """Consumes the manifest portion of the bundle, setting filestart so the
385 385 file portion can be read."""
386 386 self._cgunpacker.seek(self.manstart)
387 387 self._cgunpacker.manifestheader()
388 388 for delta in self._cgunpacker.deltaiter():
389 389 pass
390 390 self.filestart = self._cgunpacker.tell()
391 391
392 392 @localrepo.unfilteredpropertycache
393 393 def manstart(self):
394 394 self.changelog
395 395 return self.manstart
396 396
397 397 @localrepo.unfilteredpropertycache
398 398 def filestart(self):
399 399 self.manifestlog
400 400
401 401 # If filestart was not set by self.manifestlog, that means the
402 402 # manifestlog implementation did not consume the manifests from the
403 403 # changegroup (ex: it might be consuming trees from a separate bundle2
404 404 # part instead). So we need to manually consume it.
405 405 if r'filestart' not in self.__dict__:
406 406 self._consumemanifest()
407 407
408 408 return self.filestart
409 409
410 410 def url(self):
411 411 return self._url
412 412
413 413 def file(self, f):
414 414 if not self._cgfilespos:
415 415 self._cgunpacker.seek(self.filestart)
416 416 self._cgfilespos = _getfilestarts(self._cgunpacker)
417 417
418 418 if f in self._cgfilespos:
419 419 self._cgunpacker.seek(self._cgfilespos[f])
420 420 linkmapper = self.unfiltered().changelog.rev
421 421 return bundlefilelog(self.svfs, f, self._cgunpacker, linkmapper)
422 422 else:
423 423 return super(bundlerepository, self).file(f)
424 424
425 425 def close(self):
426 426 """Close assigned bundle file immediately."""
427 427 self._bundlefile.close()
428 428 if self.tempfile is not None:
429 429 self.vfs.unlink(self.tempfile)
430 430 if self._tempparent:
431 431 shutil.rmtree(self._tempparent, True)
432 432
433 433 def cancopy(self):
434 434 return False
435 435
436 436 def peer(self):
437 437 return bundlepeer(self)
438 438
439 439 def getcwd(self):
440 440 return pycompat.getcwd() # always outside the repo
441 441
442 442 # Check if parents exist in localrepo before setting
443 443 def setparents(self, p1, p2=nullid):
444 444 p1rev = self.changelog.rev(p1)
445 445 p2rev = self.changelog.rev(p2)
446 446 msg = _("setting parent to node %s that only exists in the bundle\n")
447 447 if self.changelog.repotiprev < p1rev:
448 448 self.ui.warn(msg % nodemod.hex(p1))
449 449 if self.changelog.repotiprev < p2rev:
450 450 self.ui.warn(msg % nodemod.hex(p2))
451 451 return super(bundlerepository, self).setparents(p1, p2)
452 452
453 453 def instance(ui, path, create):
454 454 if create:
455 455 raise error.Abort(_('cannot create new bundle repository'))
456 456 # internal config: bundle.mainreporoot
457 457 parentpath = ui.config("bundle", "mainreporoot")
458 458 if not parentpath:
459 459 # try to find the correct path to the working directory repo
460 460 parentpath = cmdutil.findrepo(pycompat.getcwd())
461 461 if parentpath is None:
462 462 parentpath = ''
463 463 if parentpath:
464 464 # Try to make the full path relative so we get a nice, short URL.
465 465 # In particular, we don't want temp dir names in test outputs.
466 466 cwd = pycompat.getcwd()
467 467 if parentpath == cwd:
468 468 parentpath = ''
469 469 else:
470 470 cwd = pathutil.normasprefix(cwd)
471 471 if parentpath.startswith(cwd):
472 472 parentpath = parentpath[len(cwd):]
473 473 u = util.url(path)
474 474 path = u.localpath()
475 475 if u.scheme == 'bundle':
476 476 s = path.split("+", 1)
477 477 if len(s) == 1:
478 478 repopath, bundlename = parentpath, s[0]
479 479 else:
480 480 repopath, bundlename = s
481 481 else:
482 482 repopath, bundlename = parentpath, path
483 483 return bundlerepository(ui, repopath, bundlename)
484 484
485 485 class bundletransactionmanager(object):
486 486 def transaction(self):
487 487 return None
488 488
489 489 def close(self):
490 490 raise NotImplementedError
491 491
492 492 def release(self):
493 493 raise NotImplementedError
494 494
495 495 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
496 496 force=False):
497 497 '''obtains a bundle of changes incoming from other
498 498
499 499 "onlyheads" restricts the returned changes to those reachable from the
500 500 specified heads.
501 501 "bundlename", if given, stores the bundle to this file path permanently;
502 502 otherwise it's stored to a temp file and gets deleted again when you call
503 503 the returned "cleanupfn".
504 504 "force" indicates whether to proceed on unrelated repos.
505 505
506 506 Returns a tuple (local, csets, cleanupfn):
507 507
508 508 "local" is a local repo from which to obtain the actual incoming
509 509 changesets; it is a bundlerepo for the obtained bundle when the
510 510 original "other" is remote.
511 511 "csets" lists the incoming changeset node ids.
512 512 "cleanupfn" must be called without arguments when you're done processing
513 513 the changes; it closes both the original "other" and the one returned
514 514 here.
515 515 '''
516 516 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
517 517 force=force)
518 518 common, incoming, rheads = tmp
519 519 if not incoming:
520 520 try:
521 521 if bundlename:
522 522 os.unlink(bundlename)
523 523 except OSError:
524 524 pass
525 525 return repo, [], other.close
526 526
527 527 commonset = set(common)
528 528 rheads = [x for x in rheads if x not in commonset]
529 529
530 530 bundle = None
531 531 bundlerepo = None
532 532 localrepo = other.local()
533 533 if bundlename or not localrepo:
534 534 # create a bundle (uncompressed if other repo is not local)
535 535
536 536 # developer config: devel.legacy.exchange
537 537 legexc = ui.configlist('devel', 'legacy.exchange')
538 538 forcebundle1 = 'bundle2' not in legexc and 'bundle1' in legexc
539 539 canbundle2 = (not forcebundle1
540 540 and other.capable('getbundle')
541 541 and other.capable('bundle2'))
542 542 if canbundle2:
543 543 kwargs = {}
544 544 kwargs[r'common'] = common
545 545 kwargs[r'heads'] = rheads
546 546 kwargs[r'bundlecaps'] = exchange.caps20to10(repo, role='client')
547 547 kwargs[r'cg'] = True
548 548 b2 = other.getbundle('incoming', **kwargs)
549 549 fname = bundle = changegroup.writechunks(ui, b2._forwardchunks(),
550 550 bundlename)
551 551 else:
552 552 if other.capable('getbundle'):
553 553 cg = other.getbundle('incoming', common=common, heads=rheads)
554 554 elif onlyheads is None and not other.capable('changegroupsubset'):
555 555 # compat with older servers when pulling all remote heads
556 556 cg = other.changegroup(incoming, "incoming")
557 557 rheads = None
558 558 else:
559 559 cg = other.changegroupsubset(incoming, rheads, 'incoming')
560 560 if localrepo:
561 561 bundletype = "HG10BZ"
562 562 else:
563 563 bundletype = "HG10UN"
564 564 fname = bundle = bundle2.writebundle(ui, cg, bundlename,
565 565 bundletype)
566 566 # keep written bundle?
567 567 if bundlename:
568 568 bundle = None
569 569 if not localrepo:
570 570 # use the created uncompressed bundlerepo
571 571 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
572 572 fname)
573 573 # this repo contains local and other now, so filter out local again
574 574 common = repo.heads()
575 575 if localrepo:
576 576 # Part of common may be remotely filtered
577 577 # So use an unfiltered version
578 578 # The discovery process probably need cleanup to avoid that
579 579 localrepo = localrepo.unfiltered()
580 580
581 581 csets = localrepo.changelog.findmissing(common, rheads)
582 582
583 583 if bundlerepo:
584 584 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
585 585 remotephases = other.listkeys('phases')
586 586
587 587 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
588 588 pullop.trmanager = bundletransactionmanager()
589 589 exchange._pullapplyphases(pullop, remotephases)
590 590
591 591 def cleanup():
592 592 if bundlerepo:
593 593 bundlerepo.close()
594 594 if bundle:
595 595 os.unlink(bundle)
596 596 other.close()
597 597
598 598 return (localrepo, csets, cleanup)
@@ -1,85 +1,267 b''
1 1 # filelog.py - file history class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.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 from __future__ import absolute_import
9 9
10 10 from .thirdparty.zope import (
11 11 interface as zi,
12 12 )
13 13 from . import (
14 error,
14 15 repository,
15 16 revlog,
16 17 )
17 18
18 19 @zi.implementer(repository.ifilestorage)
19 class filelog(revlog.revlog):
20 class filelog(object):
20 21 def __init__(self, opener, path):
21 super(filelog, self).__init__(opener,
22 "/".join(("data", path + ".i")),
22 self._revlog = revlog.revlog(opener,
23 '/'.join(('data', path + '.i')),
23 24 censorable=True)
24 25 # full name of the user visible file, relative to the repository root
25 26 self.filename = path
27 self.index = self._revlog.index
28 self.version = self._revlog.version
29 self.storedeltachains = self._revlog.storedeltachains
30 self._generaldelta = self._revlog._generaldelta
31
32 def __len__(self):
33 return len(self._revlog)
34
35 def __iter__(self):
36 return self._revlog.__iter__()
37
38 def revs(self, start=0, stop=None):
39 return self._revlog.revs(start=start, stop=stop)
40
41 def parents(self, node):
42 return self._revlog.parents(node)
43
44 def parentrevs(self, rev):
45 return self._revlog.parentrevs(rev)
46
47 def rev(self, node):
48 return self._revlog.rev(node)
49
50 def node(self, rev):
51 return self._revlog.node(rev)
52
53 def lookup(self, node):
54 return self._revlog.lookup(node)
55
56 def linkrev(self, rev):
57 return self._revlog.linkrev(rev)
58
59 def flags(self, rev):
60 return self._revlog.flags(rev)
61
62 def commonancestorsheads(self, node1, node2):
63 return self._revlog.commonancestorsheads(node1, node2)
64
65 def descendants(self, revs):
66 return self._revlog.descendants(revs)
67
68 def headrevs(self):
69 return self._revlog.headrevs()
70
71 def heads(self, start=None, stop=None):
72 return self._revlog.heads(start, stop)
73
74 def children(self, node):
75 return self._revlog.children(node)
76
77 def deltaparent(self, rev):
78 return self._revlog.deltaparent(rev)
79
80 def candelta(self, baserev, rev):
81 return self._revlog.candelta(baserev, rev)
82
83 def iscensored(self, rev):
84 return self._revlog.iscensored(rev)
85
86 def rawsize(self, rev):
87 return self._revlog.rawsize(rev)
88
89 def checkhash(self, text, node, p1=None, p2=None, rev=None):
90 return self._revlog.checkhash(text, node, p1=p1, p2=p2, rev=rev)
91
92 def revision(self, node, _df=None, raw=False):
93 return self._revlog.revision(node, _df=_df, raw=raw)
94
95 def revdiff(self, rev1, rev2):
96 return self._revlog.revdiff(rev1, rev2)
97
98 def addrevision(self, revisiondata, transaction, linkrev, p1, p2,
99 node=None, flags=revlog.REVIDX_DEFAULT_FLAGS,
100 cachedelta=None):
101 return self._revlog.addrevision(revisiondata, transaction, linkrev,
102 p1, p2, node=node, flags=flags,
103 cachedelta=cachedelta)
104
105 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
106 return self._revlog.addgroup(deltas, linkmapper, transaction,
107 addrevisioncb=addrevisioncb)
108
109 def getstrippoint(self, minlink):
110 return self._revlog.getstrippoint(minlink)
111
112 def strip(self, minlink, transaction):
113 return self._revlog.strip(minlink, transaction)
114
115 def files(self):
116 return self._revlog.files()
117
118 def checksize(self):
119 return self._revlog.checksize()
26 120
27 121 def read(self, node):
28 122 t = self.revision(node)
29 123 if not t.startswith('\1\n'):
30 124 return t
31 125 s = t.index('\1\n', 2)
32 126 return t[s + 2:]
33 127
34 128 def add(self, text, meta, transaction, link, p1=None, p2=None):
35 129 if meta or text.startswith('\1\n'):
36 130 text = revlog.packmeta(meta, text)
37 131 return self.addrevision(text, transaction, link, p1, p2)
38 132
39 133 def renamed(self, node):
40 134 if self.parents(node)[0] != revlog.nullid:
41 135 return False
42 136 t = self.revision(node)
43 137 m = revlog.parsemeta(t)[0]
44 138 if m and "copy" in m:
45 139 return (m["copy"], revlog.bin(m["copyrev"]))
46 140 return False
47 141
48 142 def size(self, rev):
49 143 """return the size of a given revision"""
50 144
51 145 # for revisions with renames, we have to go the slow way
52 146 node = self.node(rev)
53 147 if self.renamed(node):
54 148 return len(self.read(node))
55 149 if self.iscensored(rev):
56 150 return 0
57 151
58 152 # XXX if self.read(node).startswith("\1\n"), this returns (size+4)
59 return super(filelog, self).size(rev)
153 return self._revlog.size(rev)
60 154
61 155 def cmp(self, node, text):
62 156 """compare text with a given file revision
63 157
64 158 returns True if text is different than what is stored.
65 159 """
66 160
67 161 t = text
68 162 if text.startswith('\1\n'):
69 163 t = '\1\n\1\n' + text
70 164
71 samehashes = not super(filelog, self).cmp(node, t)
165 samehashes = not self._revlog.cmp(node, t)
72 166 if samehashes:
73 167 return False
74 168
75 169 # censored files compare against the empty file
76 170 if self.iscensored(self.rev(node)):
77 171 return text != ''
78 172
79 173 # renaming a file produces a different hash, even if the data
80 174 # remains unchanged. Check if it's the case (slow):
81 175 if self.renamed(node):
82 176 t2 = self.read(node)
83 177 return t2 != text
84 178
85 179 return True
180
181 @property
182 def filename(self):
183 return self._revlog.filename
184
185 @filename.setter
186 def filename(self, value):
187 self._revlog.filename = value
188
189 # TODO these aren't part of the interface and aren't internal methods.
190 # Callers should be fixed to not use them.
191 @property
192 def indexfile(self):
193 return self._revlog.indexfile
194
195 @indexfile.setter
196 def indexfile(self, value):
197 self._revlog.indexfile = value
198
199 @property
200 def datafile(self):
201 return self._revlog.datafile
202
203 @property
204 def opener(self):
205 return self._revlog.opener
206
207 @property
208 def _lazydeltabase(self):
209 return self._revlog._lazydeltabase
210
211 @_lazydeltabase.setter
212 def _lazydeltabase(self, value):
213 self._revlog._lazydeltabase = value
214
215 @property
216 def _aggressivemergedeltas(self):
217 return self._revlog._aggressivemergedeltas
218
219 @_aggressivemergedeltas.setter
220 def _aggressivemergedeltas(self, value):
221 self._revlog._aggressivemergedeltas = value
222
223 @property
224 def _inline(self):
225 return self._revlog._inline
226
227 @property
228 def _withsparseread(self):
229 return getattr(self._revlog, '_withsparseread', False)
230
231 @property
232 def _srmingapsize(self):
233 return self._revlog._srmingapsize
234
235 @property
236 def _srdensitythreshold(self):
237 return self._revlog._srdensitythreshold
238
239 def _deltachain(self, rev, stoprev=None):
240 return self._revlog._deltachain(rev, stoprev)
241
242 def chainbase(self, rev):
243 return self._revlog.chainbase(rev)
244
245 def chainlen(self, rev):
246 return self._revlog.chainlen(rev)
247
248 def clone(self, tr, destrevlog, **kwargs):
249 if not isinstance(destrevlog, filelog):
250 raise error.ProgrammingError('expected filelog to clone()')
251
252 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
253
254 def start(self, rev):
255 return self._revlog.start(rev)
256
257 def end(self, rev):
258 return self._revlog.end(rev)
259
260 def length(self, rev):
261 return self._revlog.length(rev)
262
263 def compress(self, data):
264 return self._revlog.compress(data)
265
266 def _addrevision(self, *args, **kwargs):
267 return self._revlog._addrevision(*args, **kwargs)
@@ -1,259 +1,261 b''
1 1 # unionrepo.py - repository class for viewing union of repository changesets
2 2 #
3 3 # Derived from bundlerepo.py
4 4 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
5 5 # Copyright 2013 Unity Technologies, Mads Kiilerich <madski@unity3d.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Repository class for "in-memory pull" of one local repository to another,
11 11 allowing operations like diff and log with revsets.
12 12 """
13 13
14 14 from __future__ import absolute_import
15 15
16 16 from .i18n import _
17 17 from .node import nullid
18 18
19 19 from . import (
20 20 changelog,
21 21 cmdutil,
22 22 error,
23 23 filelog,
24 24 localrepo,
25 25 manifest,
26 26 mdiff,
27 27 pathutil,
28 28 pycompat,
29 29 revlog,
30 30 util,
31 31 vfs as vfsmod,
32 32 )
33 33
34 34 class unionrevlog(revlog.revlog):
35 35 def __init__(self, opener, indexfile, revlog2, linkmapper):
36 36 # How it works:
37 37 # To retrieve a revision, we just need to know the node id so we can
38 38 # look it up in revlog2.
39 39 #
40 40 # To differentiate a rev in the second revlog from a rev in the revlog,
41 41 # we check revision against repotiprev.
42 42 opener = vfsmod.readonlyvfs(opener)
43 43 revlog.revlog.__init__(self, opener, indexfile)
44 44 self.revlog2 = revlog2
45 45
46 46 n = len(self)
47 47 self.repotiprev = n - 1
48 48 self.bundlerevs = set() # used by 'bundle()' revset expression
49 49 for rev2 in self.revlog2:
50 50 rev = self.revlog2.index[rev2]
51 51 # rev numbers - in revlog2, very different from self.rev
52 52 _start, _csize, _rsize, base, linkrev, p1rev, p2rev, node = rev
53 53 flags = _start & 0xFFFF
54 54
55 55 if linkmapper is None: # link is to same revlog
56 56 assert linkrev == rev2 # we never link back
57 57 link = n
58 58 else: # rev must be mapped from repo2 cl to unified cl by linkmapper
59 59 link = linkmapper(linkrev)
60 60
61 61 if linkmapper is not None: # link is to same revlog
62 62 base = linkmapper(base)
63 63
64 64 if node in self.nodemap:
65 65 # this happens for the common revlog revisions
66 66 self.bundlerevs.add(self.nodemap[node])
67 67 continue
68 68
69 69 p1node = self.revlog2.node(p1rev)
70 70 p2node = self.revlog2.node(p2rev)
71 71
72 72 e = (flags, None, None, base,
73 73 link, self.rev(p1node), self.rev(p2node), node)
74 74 self.index.insert(-1, e)
75 75 self.nodemap[node] = n
76 76 self.bundlerevs.add(n)
77 77 n += 1
78 78
79 79 def _chunk(self, rev):
80 80 if rev <= self.repotiprev:
81 81 return revlog.revlog._chunk(self, rev)
82 82 return self.revlog2._chunk(self.node(rev))
83 83
84 84 def revdiff(self, rev1, rev2):
85 85 """return or calculate a delta between two revisions"""
86 86 if rev1 > self.repotiprev and rev2 > self.repotiprev:
87 87 return self.revlog2.revdiff(
88 88 self.revlog2.rev(self.node(rev1)),
89 89 self.revlog2.rev(self.node(rev2)))
90 90 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
91 91 return self.baserevdiff(rev1, rev2)
92 92
93 93 return mdiff.textdiff(self.revision(rev1), self.revision(rev2))
94 94
95 def revision(self, nodeorrev, raw=False):
95 def revision(self, nodeorrev, _df=None, raw=False):
96 96 """return an uncompressed revision of a given node or revision
97 97 number.
98 98 """
99 99 if isinstance(nodeorrev, int):
100 100 rev = nodeorrev
101 101 node = self.node(rev)
102 102 else:
103 103 node = nodeorrev
104 104 rev = self.rev(node)
105 105
106 106 if node == nullid:
107 107 return ""
108 108
109 109 if rev > self.repotiprev:
110 110 text = self.revlog2.revision(node)
111 111 self._cache = (node, rev, text)
112 112 else:
113 113 text = self.baserevision(rev)
114 114 # already cached
115 115 return text
116 116
117 117 def baserevision(self, nodeorrev):
118 118 # Revlog subclasses may override 'revision' method to modify format of
119 119 # content retrieved from revlog. To use unionrevlog with such class one
120 120 # needs to override 'baserevision' and make more specific call here.
121 121 return revlog.revlog.revision(self, nodeorrev)
122 122
123 123 def baserevdiff(self, rev1, rev2):
124 124 # Exists for the same purpose as baserevision.
125 125 return revlog.revlog.revdiff(self, rev1, rev2)
126 126
127 127 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
128 128 raise NotImplementedError
129 129 def addgroup(self, deltas, transaction, addrevisioncb=None):
130 130 raise NotImplementedError
131 131 def strip(self, rev, minlink):
132 132 raise NotImplementedError
133 133 def checksize(self):
134 134 raise NotImplementedError
135 135
136 136 class unionchangelog(unionrevlog, changelog.changelog):
137 137 def __init__(self, opener, opener2):
138 138 changelog.changelog.__init__(self, opener)
139 139 linkmapper = None
140 140 changelog2 = changelog.changelog(opener2)
141 141 unionrevlog.__init__(self, opener, self.indexfile, changelog2,
142 142 linkmapper)
143 143
144 144 def baserevision(self, nodeorrev):
145 145 # Although changelog doesn't override 'revision' method, some extensions
146 146 # may replace this class with another that does. Same story with
147 147 # manifest and filelog classes.
148 148 return changelog.changelog.revision(self, nodeorrev)
149 149
150 150 def baserevdiff(self, rev1, rev2):
151 151 return changelog.changelog.revdiff(self, rev1, rev2)
152 152
153 153 class unionmanifest(unionrevlog, manifest.manifestrevlog):
154 154 def __init__(self, opener, opener2, linkmapper):
155 155 manifest.manifestrevlog.__init__(self, opener)
156 156 manifest2 = manifest.manifestrevlog(opener2)
157 157 unionrevlog.__init__(self, opener, self.indexfile, manifest2,
158 158 linkmapper)
159 159
160 160 def baserevision(self, nodeorrev):
161 161 return manifest.manifestrevlog.revision(self, nodeorrev)
162 162
163 163 def baserevdiff(self, rev1, rev2):
164 164 return manifest.manifestrevlog.revdiff(self, rev1, rev2)
165 165
166 class unionfilelog(unionrevlog, filelog.filelog):
166 class unionfilelog(filelog.filelog):
167 167 def __init__(self, opener, path, opener2, linkmapper, repo):
168 168 filelog.filelog.__init__(self, opener, path)
169 169 filelog2 = filelog.filelog(opener2, path)
170 unionrevlog.__init__(self, opener, self.indexfile, filelog2,
171 linkmapper)
170 self._revlog = unionrevlog(opener, self.indexfile,
171 filelog2._revlog, linkmapper)
172 172 self._repo = repo
173 self.repotiprev = self._revlog.repotiprev
174 self.revlog2 = self._revlog.revlog2
173 175
174 176 def baserevision(self, nodeorrev):
175 177 return filelog.filelog.revision(self, nodeorrev)
176 178
177 179 def baserevdiff(self, rev1, rev2):
178 180 return filelog.filelog.revdiff(self, rev1, rev2)
179 181
180 182 def iscensored(self, rev):
181 183 """Check if a revision is censored."""
182 184 if rev <= self.repotiprev:
183 185 return filelog.filelog.iscensored(self, rev)
184 186 node = self.node(rev)
185 187 return self.revlog2.iscensored(self.revlog2.rev(node))
186 188
187 189 class unionpeer(localrepo.localpeer):
188 190 def canpush(self):
189 191 return False
190 192
191 193 class unionrepository(localrepo.localrepository):
192 194 def __init__(self, ui, path, path2):
193 195 localrepo.localrepository.__init__(self, ui, path)
194 196 self.ui.setconfig('phases', 'publish', False, 'unionrepo')
195 197
196 198 self._url = 'union:%s+%s' % (util.expandpath(path),
197 199 util.expandpath(path2))
198 200 self.repo2 = localrepo.localrepository(ui, path2)
199 201
200 202 @localrepo.unfilteredpropertycache
201 203 def changelog(self):
202 204 return unionchangelog(self.svfs, self.repo2.svfs)
203 205
204 206 def _clrev(self, rev2):
205 207 """map from repo2 changelog rev to temporary rev in self.changelog"""
206 208 node = self.repo2.changelog.node(rev2)
207 209 return self.changelog.rev(node)
208 210
209 211 def _constructmanifest(self):
210 212 return unionmanifest(self.svfs, self.repo2.svfs,
211 213 self.unfiltered()._clrev)
212 214
213 215 def url(self):
214 216 return self._url
215 217
216 218 def file(self, f):
217 219 return unionfilelog(self.svfs, f, self.repo2.svfs,
218 220 self.unfiltered()._clrev, self)
219 221
220 222 def close(self):
221 223 self.repo2.close()
222 224
223 225 def cancopy(self):
224 226 return False
225 227
226 228 def peer(self):
227 229 return unionpeer(self)
228 230
229 231 def getcwd(self):
230 232 return pycompat.getcwd() # always outside the repo
231 233
232 234 def instance(ui, path, create):
233 235 if create:
234 236 raise error.Abort(_('cannot create new union repository'))
235 237 parentpath = ui.config("bundle", "mainreporoot")
236 238 if not parentpath:
237 239 # try to find the correct path to the working directory repo
238 240 parentpath = cmdutil.findrepo(pycompat.getcwd())
239 241 if parentpath is None:
240 242 parentpath = ''
241 243 if parentpath:
242 244 # Try to make the full path relative so we get a nice, short URL.
243 245 # In particular, we don't want temp dir names in test outputs.
244 246 cwd = pycompat.getcwd()
245 247 if parentpath == cwd:
246 248 parentpath = ''
247 249 else:
248 250 cwd = pathutil.normasprefix(cwd)
249 251 if parentpath.startswith(cwd):
250 252 parentpath = parentpath[len(cwd):]
251 253 if path.startswith('union:'):
252 254 s = path.split(":", 1)[1].split("+", 1)
253 255 if len(s) == 1:
254 256 repopath, repopath2 = parentpath, s[0]
255 257 else:
256 258 repopath, repopath2 = s
257 259 else:
258 260 repopath, repopath2 = parentpath, path
259 261 return unionrepository(ui, repopath, repopath2)
General Comments 0
You need to be logged in to leave comments. Login now