##// 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 phases,
31 phases,
32 pycompat,
32 pycompat,
33 repository,
33 repository,
34 revlog,
35 util,
34 util,
36 )
35 )
37
36
@@ -512,19 +511,6 b' class revisiondeltarequest(object):'
512 basenode = attr.ib()
511 basenode = attr.ib()
513 ellipsis = attr.ib(default=False)
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 def _revisiondeltatochunks(delta, headerfn):
514 def _revisiondeltatochunks(delta, headerfn):
529 """Serialize a revisiondelta to changegroup chunks."""
515 """Serialize a revisiondelta to changegroup chunks."""
530
516
@@ -583,77 +569,6 b' def _sortnodesellipsis(store, nodes, cl,'
583 key = lambda n: cl.rev(lookup(n))
569 key = lambda n: cl.rev(lookup(n))
584 return [store.rev(n) for n in sorted(nodes, key=key)]
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 def _makenarrowdeltarequest(cl, store, ischangelog, rev, node, linkrev,
572 def _makenarrowdeltarequest(cl, store, ischangelog, rev, node, linkrev,
658 linknode, clrevtolocalrev, fullclnodes,
573 linknode, clrevtolocalrev, fullclnodes,
659 precomputedellipsis):
574 precomputedellipsis):
@@ -832,17 +747,12 b' def deltagroup(repo, store, nodes, ischa'
832 progress = repo.ui.makeprogress(_('bundling'), unit=units,
747 progress = repo.ui.makeprogress(_('bundling'), unit=units,
833 total=len(requests))
748 total=len(requests))
834
749
835 prevnode = store.node(revs[0])
750 for i, delta in enumerate(store.emitrevisiondeltas(requests)):
836 for i, request in enumerate(requests):
837 if progress:
751 if progress:
838 progress.update(i + 1)
752 progress.update(i + 1)
839
753
840 delta = _handlerevisiondeltarequest(store, request, prevnode)
841
842 yield delta
754 yield delta
843
755
844 prevnode = request.node
845
846 if progress:
756 if progress:
847 progress.complete()
757 progress.complete()
848
758
@@ -95,6 +95,9 b' class filelog(object):'
95 def revdiff(self, rev1, rev2):
95 def revdiff(self, rev1, rev2):
96 return self._revlog.revdiff(rev1, rev2)
96 return self._revlog.revdiff(rev1, rev2)
97
97
98 def emitrevisiondeltas(self, requests):
99 return self._revlog.emitrevisiondeltas(requests)
100
98 def addrevision(self, revisiondata, transaction, linkrev, p1, p2,
101 def addrevision(self, revisiondata, transaction, linkrev, p1, p2,
99 node=None, flags=revlog.REVIDX_DEFAULT_FLAGS,
102 node=None, flags=revlog.REVIDX_DEFAULT_FLAGS,
100 cachedelta=None):
103 cachedelta=None):
@@ -621,6 +621,30 b' class ifiledata(interfaceutil.Interface)'
621 revision data.
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 class ifilemutation(interfaceutil.Interface):
648 class ifilemutation(interfaceutil.Interface):
625 """Storage interface for mutation events of a tracked file."""
649 """Storage interface for mutation events of a tracked file."""
626
650
@@ -45,10 +45,12 b' from . import ('
45 mdiff,
45 mdiff,
46 policy,
46 policy,
47 pycompat,
47 pycompat,
48 repository,
48 templatefilters,
49 templatefilters,
49 util,
50 util,
50 )
51 )
51 from .utils import (
52 from .utils import (
53 interfaceutil,
52 stringutil,
54 stringutil,
53 )
55 )
54
56
@@ -821,6 +823,19 b' class _revisioninfo(object):'
821 cachedelta = attr.ib()
823 cachedelta = attr.ib()
822 flags = attr.ib()
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 # index v0:
839 # index v0:
825 # 4 bytes: offset
840 # 4 bytes: offset
826 # 4 bytes: compressed length
841 # 4 bytes: compressed length
@@ -2950,6 +2965,87 b' class revlog(object):'
2950 res.append(self.datafile)
2965 res.append(self.datafile)
2951 return res
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 DELTAREUSEALWAYS = 'always'
3049 DELTAREUSEALWAYS = 'always'
2954 DELTAREUSESAMEREVS = 'samerevs'
3050 DELTAREUSESAMEREVS = 'samerevs'
2955 DELTAREUSENEVER = 'never'
3051 DELTAREUSENEVER = 'never'
@@ -22,6 +22,7 b' from mercurial.node import ('
22 nullrev,
22 nullrev,
23 )
23 )
24 from mercurial.thirdparty import (
24 from mercurial.thirdparty import (
25 attr,
25 cbor,
26 cbor,
26 )
27 )
27 from mercurial import (
28 from mercurial import (
@@ -60,6 +61,19 b' def validaterev(rev):'
60 if not isinstance(rev, int):
61 if not isinstance(rev, int):
61 raise ValueError('expected int')
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 @interfaceutil.implementer(repository.ifilestorage)
77 @interfaceutil.implementer(repository.ifilestorage)
64 class filestorage(object):
78 class filestorage(object):
65 """Implements storage for a tracked path.
79 """Implements storage for a tracked path.
@@ -500,6 +514,54 b' class filestorage(object):'
500 return mdiff.textdiff(self.revision(node1, raw=True),
514 return mdiff.textdiff(self.revision(node1, raw=True),
501 self.revision(node2, raw=True))
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 def headrevs(self):
565 def headrevs(self):
504 # Assume all revisions are heads by default.
566 # Assume all revisions are heads by default.
505 revishead = {rev: True for rev in self._indexbyrev}
567 revishead = {rev: True for rev in self._indexbyrev}
@@ -29,6 +29,7 b' from mercurial import ('
29 manifest,
29 manifest,
30 pycompat,
30 pycompat,
31 repository,
31 repository,
32 revlog,
32 sshpeer,
33 sshpeer,
33 statichttprepo,
34 statichttprepo,
34 ui as uimod,
35 ui as uimod,
@@ -198,11 +199,11 b' def main():'
198 checkzobject(mctx.read())
199 checkzobject(mctx.read())
199
200
200 ziverify.verifyClass(repository.irevisiondelta,
201 ziverify.verifyClass(repository.irevisiondelta,
201 changegroup.revisiondelta)
202 revlog.revlogrevisiondelta)
202 ziverify.verifyClass(repository.irevisiondeltarequest,
203 ziverify.verifyClass(repository.irevisiondeltarequest,
203 changegroup.revisiondeltarequest)
204 changegroup.revisiondeltarequest)
204
205
205 rd = changegroup.revisiondelta(
206 rd = revlog.revlogrevisiondelta(
206 node=b'',
207 node=b'',
207 p1node=b'',
208 p1node=b'',
208 p2node=b'',
209 p2node=b'',
General Comments 0
You need to be logged in to leave comments. Login now