##// END OF EJS Templates
repository: establish API for emitting revision deltas...
Gregory Szorc -
r39267:b41d023a default
parent child Browse files
Show More
@@ -31,7 +31,6 b' from . import ('
31 31 phases,
32 32 pycompat,
33 33 repository,
34 revlog,
35 34 util,
36 35 )
37 36
@@ -512,19 +511,6 b' class revisiondeltarequest(object):'
512 511 basenode = attr.ib()
513 512 ellipsis = attr.ib(default=False)
514 513
515 @interfaceutil.implementer(repository.irevisiondelta)
516 @attr.s(slots=True, frozen=True)
517 class revisiondelta(object):
518 node = attr.ib()
519 p1node = attr.ib()
520 p2node = attr.ib()
521 basenode = attr.ib()
522 linknode = attr.ib()
523 flags = attr.ib()
524 baserevisionsize = attr.ib()
525 revision = attr.ib()
526 delta = attr.ib()
527
528 514 def _revisiondeltatochunks(delta, headerfn):
529 515 """Serialize a revisiondelta to changegroup chunks."""
530 516
@@ -583,77 +569,6 b' def _sortnodesellipsis(store, nodes, cl,'
583 569 key = lambda n: cl.rev(lookup(n))
584 570 return [store.rev(n) for n in sorted(nodes, key=key)]
585 571
586 def _handlerevisiondeltarequest(store, request, prevnode):
587 """Obtain a revisiondelta from a revisiondeltarequest"""
588
589 node = request.node
590 rev = store.rev(node)
591
592 # Requesting a full revision.
593 if request.basenode == nullid:
594 baserev = nullrev
595 # Requesting an explicit revision.
596 elif request.basenode is not None:
597 baserev = store.rev(request.basenode)
598 # Allowing us to choose.
599 else:
600 p1, p2 = store.parentrevs(rev)
601 dp = store.deltaparent(rev)
602
603 if dp == nullrev and store.storedeltachains:
604 # Avoid sending full revisions when delta parent is null. Pick prev
605 # in that case. It's tempting to pick p1 in this case, as p1 will
606 # be smaller in the common case. However, computing a delta against
607 # p1 may require resolving the raw text of p1, which could be
608 # expensive. The revlog caches should have prev cached, meaning
609 # less CPU for changegroup generation. There is likely room to add
610 # a flag and/or config option to control this behavior.
611 baserev = store.rev(prevnode)
612 elif dp == nullrev:
613 # revlog is configured to use full snapshot for a reason,
614 # stick to full snapshot.
615 baserev = nullrev
616 elif dp not in (p1, p2, store.rev(prevnode)):
617 # Pick prev when we can't be sure remote has the base revision.
618 baserev = store.rev(prevnode)
619 else:
620 baserev = dp
621
622 if baserev != nullrev and not store.candelta(baserev, rev):
623 baserev = nullrev
624
625 revision = None
626 delta = None
627 baserevisionsize = None
628
629 if store.iscensored(baserev) or store.iscensored(rev):
630 try:
631 revision = store.revision(node, raw=True)
632 except error.CensoredNodeError as e:
633 revision = e.tombstone
634
635 if baserev != nullrev:
636 baserevisionsize = store.rawsize(baserev)
637
638 elif baserev == nullrev:
639 revision = store.revision(node, raw=True)
640 else:
641 delta = store.revdiff(baserev, rev)
642
643 extraflags = revlog.REVIDX_ELLIPSIS if request.ellipsis else 0
644
645 return revisiondelta(
646 node=node,
647 p1node=request.p1node,
648 p2node=request.p2node,
649 linknode=request.linknode,
650 basenode=store.node(baserev),
651 flags=store.flags(rev) | extraflags,
652 baserevisionsize=baserevisionsize,
653 revision=revision,
654 delta=delta,
655 )
656
657 572 def _makenarrowdeltarequest(cl, store, ischangelog, rev, node, linkrev,
658 573 linknode, clrevtolocalrev, fullclnodes,
659 574 precomputedellipsis):
@@ -832,17 +747,12 b' def deltagroup(repo, store, nodes, ischa'
832 747 progress = repo.ui.makeprogress(_('bundling'), unit=units,
833 748 total=len(requests))
834 749
835 prevnode = store.node(revs[0])
836 for i, request in enumerate(requests):
750 for i, delta in enumerate(store.emitrevisiondeltas(requests)):
837 751 if progress:
838 752 progress.update(i + 1)
839 753
840 delta = _handlerevisiondeltarequest(store, request, prevnode)
841
842 754 yield delta
843 755
844 prevnode = request.node
845
846 756 if progress:
847 757 progress.complete()
848 758
@@ -95,6 +95,9 b' class filelog(object):'
95 95 def revdiff(self, rev1, rev2):
96 96 return self._revlog.revdiff(rev1, rev2)
97 97
98 def emitrevisiondeltas(self, requests):
99 return self._revlog.emitrevisiondeltas(requests)
100
98 101 def addrevision(self, revisiondata, transaction, linkrev, p1, p2,
99 102 node=None, flags=revlog.REVIDX_DEFAULT_FLAGS,
100 103 cachedelta=None):
@@ -621,6 +621,30 b' class ifiledata(interfaceutil.Interface)'
621 621 revision data.
622 622 """
623 623
624 def emitrevisiondeltas(requests):
625 """Produce ``irevisiondelta`` from ``irevisiondeltarequest``s.
626
627 Given an iterable of objects conforming to the ``irevisiondeltarequest``
628 interface, emits objects conforming to the ``irevisiondelta``
629 interface.
630
631 This method is a generator.
632
633 ``irevisiondelta`` should be emitted in the same order of
634 ``irevisiondeltarequest`` that was passed in.
635
636 The emitted objects MUST conform by the results of
637 ``irevisiondeltarequest``. Namely, they must respect any requests
638 for building a delta from a specific ``basenode`` if defined.
639
640 When sending deltas, implementations must take into account whether
641 the client has the base delta before encoding a delta against that
642 revision. A revision encountered previously in ``requests`` is
643 always a suitable base revision. An example of a bad delta is a delta
644 against a non-ancestor revision. Another example of a bad delta is a
645 delta against a censored revision.
646 """
647
624 648 class ifilemutation(interfaceutil.Interface):
625 649 """Storage interface for mutation events of a tracked file."""
626 650
@@ -45,10 +45,12 b' from . import ('
45 45 mdiff,
46 46 policy,
47 47 pycompat,
48 repository,
48 49 templatefilters,
49 50 util,
50 51 )
51 52 from .utils import (
53 interfaceutil,
52 54 stringutil,
53 55 )
54 56
@@ -821,6 +823,19 b' class _revisioninfo(object):'
821 823 cachedelta = attr.ib()
822 824 flags = attr.ib()
823 825
826 @interfaceutil.implementer(repository.irevisiondelta)
827 @attr.s(slots=True, frozen=True)
828 class revlogrevisiondelta(object):
829 node = attr.ib()
830 p1node = attr.ib()
831 p2node = attr.ib()
832 basenode = attr.ib()
833 linknode = attr.ib()
834 flags = attr.ib()
835 baserevisionsize = attr.ib()
836 revision = attr.ib()
837 delta = attr.ib()
838
824 839 # index v0:
825 840 # 4 bytes: offset
826 841 # 4 bytes: compressed length
@@ -2950,6 +2965,87 b' class revlog(object):'
2950 2965 res.append(self.datafile)
2951 2966 return res
2952 2967
2968 def emitrevisiondeltas(self, requests):
2969 frev = self.rev
2970
2971 prevrev = None
2972 for request in requests:
2973 node = request.node
2974 rev = frev(node)
2975
2976 if prevrev is None:
2977 prevrev = self.index[rev][5]
2978
2979 # Requesting a full revision.
2980 if request.basenode == nullid:
2981 baserev = nullrev
2982 # Requesting an explicit revision.
2983 elif request.basenode is not None:
2984 baserev = frev(request.basenode)
2985 # Allowing us to choose.
2986 else:
2987 p1rev, p2rev = self.parentrevs(rev)
2988 deltaparentrev = self.deltaparent(rev)
2989
2990 # Avoid sending full revisions when delta parent is null. Pick
2991 # prev in that case. It's tempting to pick p1 in this case, as
2992 # p1 will be smaller in the common case. However, computing a
2993 # delta against p1 may require resolving the raw text of p1,
2994 # which could be expensive. The revlog caches should have prev
2995 # cached, meaning less CPU for delta generation. There is
2996 # likely room to add a flag and/or config option to control this
2997 # behavior.
2998 if deltaparentrev == nullrev and self.storedeltachains:
2999 baserev = prevrev
3000
3001 # Revlog is configured to use full snapshot for a reason.
3002 # Stick to full snapshot.
3003 elif deltaparentrev == nullrev:
3004 baserev = nullrev
3005
3006 # Pick previous when we can't be sure the base is available
3007 # on consumer.
3008 elif deltaparentrev not in (p1rev, p2rev, prevrev):
3009 baserev = prevrev
3010 else:
3011 baserev = deltaparentrev
3012
3013 if baserev != nullrev and not self.candelta(baserev, rev):
3014 baserev = nullrev
3015
3016 revision = None
3017 delta = None
3018 baserevisionsize = None
3019
3020 if self.iscensored(baserev) or self.iscensored(rev):
3021 try:
3022 revision = self.revision(node, raw=True)
3023 except error.CensoredNodeError as e:
3024 revision = e.tombstone
3025
3026 if baserev != nullrev:
3027 baserevisionsize = self.rawsize(baserev)
3028
3029 elif baserev == nullrev:
3030 revision = self.revision(node, raw=True)
3031 else:
3032 delta = self.revdiff(baserev, rev)
3033
3034 extraflags = REVIDX_ELLIPSIS if request.ellipsis else 0
3035
3036 yield revlogrevisiondelta(
3037 node=node,
3038 p1node=request.p1node,
3039 p2node=request.p2node,
3040 linknode=request.linknode,
3041 basenode=self.node(baserev),
3042 flags=self.flags(rev) | extraflags,
3043 baserevisionsize=baserevisionsize,
3044 revision=revision,
3045 delta=delta)
3046
3047 prevrev = rev
3048
2953 3049 DELTAREUSEALWAYS = 'always'
2954 3050 DELTAREUSESAMEREVS = 'samerevs'
2955 3051 DELTAREUSENEVER = 'never'
@@ -22,6 +22,7 b' from mercurial.node import ('
22 22 nullrev,
23 23 )
24 24 from mercurial.thirdparty import (
25 attr,
25 26 cbor,
26 27 )
27 28 from mercurial import (
@@ -60,6 +61,19 b' def validaterev(rev):'
60 61 if not isinstance(rev, int):
61 62 raise ValueError('expected int')
62 63
64 @interfaceutil.implementer(repository.irevisiondelta)
65 @attr.s(slots=True, frozen=True)
66 class simplestorerevisiondelta(object):
67 node = attr.ib()
68 p1node = attr.ib()
69 p2node = attr.ib()
70 basenode = attr.ib()
71 linknode = attr.ib()
72 flags = attr.ib()
73 baserevisionsize = attr.ib()
74 revision = attr.ib()
75 delta = attr.ib()
76
63 77 @interfaceutil.implementer(repository.ifilestorage)
64 78 class filestorage(object):
65 79 """Implements storage for a tracked path.
@@ -500,6 +514,54 b' class filestorage(object):'
500 514 return mdiff.textdiff(self.revision(node1, raw=True),
501 515 self.revision(node2, raw=True))
502 516
517 def emitrevisiondeltas(self, requests):
518 for request in requests:
519 node = request.node
520 rev = self.rev(node)
521
522 if request.basenode == nullid:
523 baserev = nullrev
524 elif request.basenode is not None:
525 baserev = self.rev(request.basenode)
526 else:
527 # This is a test extension and we can do simple things
528 # for choosing a delta parent.
529 baserev = self.deltaparent(rev)
530
531 if baserev != nullrev and not self.candelta(baserev, rev):
532 baserev = nullrev
533
534 revision = None
535 delta = None
536 baserevisionsize = None
537
538 if self.iscensored(baserev) or self.iscensored(rev):
539 try:
540 revision = self.revision(node, raw=True)
541 except error.CensoredNodeError as e:
542 revision = e.tombstone
543
544 if baserev != nullrev:
545 baserevisionsize = self.rawsize(baserev)
546
547 elif baserev == nullrev:
548 revision = self.revision(node, raw=True)
549 else:
550 delta = self.revdiff(baserev, rev)
551
552 extraflags = revlog.REVIDX_ELLIPSIS if request.ellipsis else 0
553
554 yield simplestorerevisiondelta(
555 node=node,
556 p1node=request.p1node,
557 p2node=request.p2node,
558 linknode=request.linknode,
559 basenode=self.node(baserev),
560 flags=self.flags(rev) | extraflags,
561 baserevisionsize=baserevisionsize,
562 revision=revision,
563 delta=delta)
564
503 565 def headrevs(self):
504 566 # Assume all revisions are heads by default.
505 567 revishead = {rev: True for rev in self._indexbyrev}
@@ -29,6 +29,7 b' from mercurial import ('
29 29 manifest,
30 30 pycompat,
31 31 repository,
32 revlog,
32 33 sshpeer,
33 34 statichttprepo,
34 35 ui as uimod,
@@ -198,11 +199,11 b' def main():'
198 199 checkzobject(mctx.read())
199 200
200 201 ziverify.verifyClass(repository.irevisiondelta,
201 changegroup.revisiondelta)
202 revlog.revlogrevisiondelta)
202 203 ziverify.verifyClass(repository.irevisiondeltarequest,
203 204 changegroup.revisiondeltarequest)
204 205
205 rd = changegroup.revisiondelta(
206 rd = revlog.revlogrevisiondelta(
206 207 node=b'',
207 208 p1node=b'',
208 209 p2node=b'',
General Comments 0
You need to be logged in to leave comments. Login now