comparison mercurial/changegroup.py @ 39017:ef3d3a2f9aa5

changegroup: refactor delta parent code We had recently abstracted the delta parent functions to facilitate extracting code from cgpacker. Now that we're in a better place, it is time to revisit the design. Changegroup version 1 requires that the previous node be used as the delta parent. Later versions allow any available node to be used as the base. In the case where an arbitrary parent can be used, the choice of a delta parent is best left in the hands of the storage backend. So it makes sense for the delta parent selection to be hidden away in the storage layer. This means deferring the choice of the delta parent selection function to as close to delta generation time as possible. This commit moves the delta selection logic to essentially just before delta generation. However, because changegroup version 1 limits what we can do, we have retained the ability to force a delta against the previous revision. As part of this, I realized that the ellipsis parent function was unused! That's because ellipsis mode always sends full revisions and not deltas. Differential Revision: https://phab.mercurial-scm.org/D4214
author Gregory Szorc <gregory.szorc@gmail.com>
date Wed, 08 Aug 2018 20:17:48 -0700
parents 39b8277e2115
children e793e11e1462
comparison
equal deleted inserted replaced
39016:39b8277e2115 39017:ef3d3a2f9aa5
585 # changelog, so what we do is we sort the non-changelog histories 585 # changelog, so what we do is we sort the non-changelog histories
586 # by the order in which they are used by the changelog. 586 # by the order in which they are used by the changelog.
587 key = lambda n: cl.rev(lookup(n)) 587 key = lambda n: cl.rev(lookup(n))
588 return [store.rev(n) for n in sorted(nodes, key=key)] 588 return [store.rev(n) for n in sorted(nodes, key=key)]
589 589
590 def _revisiondeltanormal(store, rev, prev, linknode, deltaparentfn): 590 def _revisiondeltanormal(store, rev, prev, linknode, forcedeltaparentprev):
591 """Construct a revision delta for non-ellipses changegroup generation.""" 591 """Construct a revision delta for non-ellipses changegroup generation."""
592 node = store.node(rev) 592 node = store.node(rev)
593 p1, p2 = store.parentrevs(rev) 593 p1, p2 = store.parentrevs(rev)
594 base = deltaparentfn(store, rev, p1, p2, prev) 594
595 if forcedeltaparentprev:
596 base = prev
597 else:
598 dp = store.deltaparent(rev)
599
600 if dp == nullrev and store.storedeltachains:
601 # Avoid sending full revisions when delta parent is null. Pick prev
602 # in that case. It's tempting to pick p1 in this case, as p1 will
603 # be smaller in the common case. However, computing a delta against
604 # p1 may require resolving the raw text of p1, which could be
605 # expensive. The revlog caches should have prev cached, meaning
606 # less CPU for changegroup generation. There is likely room to add
607 # a flag and/or config option to control this behavior.
608 base = prev
609 elif dp == nullrev:
610 # revlog is configured to use full snapshot for a reason,
611 # stick to full snapshot.
612 base = nullrev
613 elif dp not in (p1, p2, prev):
614 # Pick prev when we can't be sure remote has the base revision.
615 base = prev
616 else:
617 base = dp
618
619 if base != nullrev and not store.candelta(base, rev):
620 base = nullrev
595 621
596 revision = None 622 revision = None
597 delta = None 623 delta = None
598 baserevisionsize = None 624 baserevisionsize = None
599 625
717 baserevisionsize=None, 743 baserevisionsize=None,
718 revision=store.revision(n), 744 revision=store.revision(n),
719 delta=None, 745 delta=None,
720 ) 746 )
721 747
722 def deltagroup(repo, revs, store, ischangelog, lookup, deltaparentfn, 748 def deltagroup(repo, revs, store, ischangelog, lookup, forcedeltaparentprev,
723 units=None, 749 units=None,
724 ellipses=False, clrevtolocalrev=None, fullclnodes=None, 750 ellipses=False, clrevtolocalrev=None, fullclnodes=None,
725 precomputedellipsis=None): 751 precomputedellipsis=None):
726 """Calculate deltas for a set of revisions. 752 """Calculate deltas for a set of revisions.
727 753
759 785
760 # This is a node to send in full, because the changeset it 786 # This is a node to send in full, because the changeset it
761 # corresponds to was a full changeset. 787 # corresponds to was a full changeset.
762 if linknode in fullclnodes: 788 if linknode in fullclnodes:
763 delta = _revisiondeltanormal(store, curr, prev, linknode, 789 delta = _revisiondeltanormal(store, curr, prev, linknode,
764 deltaparentfn) 790 forcedeltaparentprev)
765 elif linkrev not in precomputedellipsis: 791 elif linkrev not in precomputedellipsis:
766 delta = None 792 delta = None
767 else: 793 else:
768 delta = _revisiondeltanarrow( 794 delta = _revisiondeltanarrow(
769 cl, store, ischangelog, curr, linkrev, linknode, 795 cl, store, ischangelog, curr, linkrev, linknode,
770 clrevtolocalrev, fullclnodes, 796 clrevtolocalrev, fullclnodes,
771 precomputedellipsis) 797 precomputedellipsis)
772 else: 798 else:
773 delta = _revisiondeltanormal(store, curr, prev, linknode, 799 delta = _revisiondeltanormal(store, curr, prev, linknode,
774 deltaparentfn) 800 forcedeltaparentprev)
775 801
776 if delta: 802 if delta:
777 yield delta 803 yield delta
778 804
779 if progress: 805 if progress:
780 progress.complete() 806 progress.complete()
781 807
782 class cgpacker(object): 808 class cgpacker(object):
783 def __init__(self, repo, filematcher, version, allowreorder, 809 def __init__(self, repo, filematcher, version, allowreorder,
784 deltaparentfn, builddeltaheader, manifestsend, 810 builddeltaheader, manifestsend,
811 forcedeltaparentprev=False,
785 bundlecaps=None, ellipses=False, 812 bundlecaps=None, ellipses=False,
786 shallow=False, ellipsisroots=None, fullnodes=None): 813 shallow=False, ellipsisroots=None, fullnodes=None):
787 """Given a source repo, construct a bundler. 814 """Given a source repo, construct a bundler.
788 815
789 filematcher is a matcher that matches on files to include in the 816 filematcher is a matcher that matches on files to include in the
791 818
792 allowreorder controls whether reordering of revisions is allowed. 819 allowreorder controls whether reordering of revisions is allowed.
793 This value is used when ``bundle.reorder`` is ``auto`` or isn't 820 This value is used when ``bundle.reorder`` is ``auto`` or isn't
794 set. 821 set.
795 822
796 deltaparentfn is a callable that resolves the delta parent for 823 forcedeltaparentprev indicates whether delta parents must be against
797 a specific revision. 824 the previous revision in a delta group. This should only be used for
825 compatibility with changegroup version 1.
798 826
799 builddeltaheader is a callable that constructs the header for a group 827 builddeltaheader is a callable that constructs the header for a group
800 delta. 828 delta.
801 829
802 manifestsend is a chunk to send after manifests have been fully emitted. 830 manifestsend is a chunk to send after manifests have been fully emitted.
818 """ 846 """
819 assert filematcher 847 assert filematcher
820 self._filematcher = filematcher 848 self._filematcher = filematcher
821 849
822 self.version = version 850 self.version = version
823 self._deltaparentfn = deltaparentfn 851 self._forcedeltaparentprev = forcedeltaparentprev
824 self._builddeltaheader = builddeltaheader 852 self._builddeltaheader = builddeltaheader
825 self._manifestsend = manifestsend 853 self._manifestsend = manifestsend
826 self._ellipses = ellipses 854 self._ellipses = ellipses
827 855
828 # Set of capabilities we can use to build the bundle. 856 # Set of capabilities we can use to build the bundle.
1023 'clrevtomanifestrev': clrevtomanifestrev, 1051 'clrevtomanifestrev': clrevtomanifestrev,
1024 } 1052 }
1025 1053
1026 gen = deltagroup( 1054 gen = deltagroup(
1027 self._repo, revs, cl, True, lookupcl, 1055 self._repo, revs, cl, True, lookupcl,
1028 self._deltaparentfn, 1056 self._forcedeltaparentprev,
1029 ellipses=self._ellipses, 1057 ellipses=self._ellipses,
1030 units=_('changesets'), 1058 units=_('changesets'),
1031 clrevtolocalrev={}, 1059 clrevtolocalrev={},
1032 fullclnodes=self._fullclnodes, 1060 fullclnodes=self._fullclnodes,
1033 precomputedellipsis=self._precomputedellipsis) 1061 precomputedellipsis=self._precomputedellipsis)
1112 revs = _sortnodesnormal(store, prunednodes, 1140 revs = _sortnodesnormal(store, prunednodes,
1113 self._reorder) 1141 self._reorder)
1114 1142
1115 deltas = deltagroup( 1143 deltas = deltagroup(
1116 self._repo, revs, store, False, lookupfn, 1144 self._repo, revs, store, False, lookupfn,
1117 self._deltaparentfn, 1145 self._forcedeltaparentprev,
1118 ellipses=self._ellipses, 1146 ellipses=self._ellipses,
1119 units=_('manifests'), 1147 units=_('manifests'),
1120 clrevtolocalrev=clrevtolocalrev, 1148 clrevtolocalrev=clrevtolocalrev,
1121 fullclnodes=self._fullclnodes, 1149 fullclnodes=self._fullclnodes,
1122 precomputedellipsis=self._precomputedellipsis) 1150 precomputedellipsis=self._precomputedellipsis)
1204 1232
1205 progress.update(i + 1, item=fname) 1233 progress.update(i + 1, item=fname)
1206 1234
1207 deltas = deltagroup( 1235 deltas = deltagroup(
1208 self._repo, revs, filerevlog, False, lookupfilelog, 1236 self._repo, revs, filerevlog, False, lookupfilelog,
1209 self._deltaparentfn, 1237 self._forcedeltaparentprev,
1210 ellipses=self._ellipses, 1238 ellipses=self._ellipses,
1211 clrevtolocalrev=clrevtolocalrev, 1239 clrevtolocalrev=clrevtolocalrev,
1212 fullclnodes=self._fullclnodes, 1240 fullclnodes=self._fullclnodes,
1213 precomputedellipsis=self._precomputedellipsis) 1241 precomputedellipsis=self._precomputedellipsis)
1214 1242
1215 yield fname, deltas 1243 yield fname, deltas
1216 1244
1217 progress.complete() 1245 progress.complete()
1218 1246
1219 def _deltaparentprev(store, rev, p1, p2, prev):
1220 """Resolve a delta parent to the previous revision.
1221
1222 Used for version 1 changegroups, which don't support generaldelta.
1223 """
1224 return prev
1225
1226 def _deltaparentgeneraldelta(store, rev, p1, p2, prev):
1227 """Resolve a delta parent when general deltas are supported."""
1228 dp = store.deltaparent(rev)
1229 if dp == nullrev and store.storedeltachains:
1230 # Avoid sending full revisions when delta parent is null. Pick prev
1231 # in that case. It's tempting to pick p1 in this case, as p1 will
1232 # be smaller in the common case. However, computing a delta against
1233 # p1 may require resolving the raw text of p1, which could be
1234 # expensive. The revlog caches should have prev cached, meaning
1235 # less CPU for changegroup generation. There is likely room to add
1236 # a flag and/or config option to control this behavior.
1237 base = prev
1238 elif dp == nullrev:
1239 # revlog is configured to use full snapshot for a reason,
1240 # stick to full snapshot.
1241 base = nullrev
1242 elif dp not in (p1, p2, prev):
1243 # Pick prev when we can't be sure remote has the base revision.
1244 return prev
1245 else:
1246 base = dp
1247
1248 if base != nullrev and not store.candelta(base, rev):
1249 base = nullrev
1250
1251 return base
1252
1253 def _deltaparentellipses(store, rev, p1, p2, prev):
1254 """Resolve a delta parent when in ellipses mode."""
1255 # TODO: send better deltas when in narrow mode.
1256 #
1257 # changegroup.group() loops over revisions to send,
1258 # including revisions we'll skip. What this means is that
1259 # `prev` will be a potentially useless delta base for all
1260 # ellipsis nodes, as the client likely won't have it. In
1261 # the future we should do bookkeeping about which nodes
1262 # have been sent to the client, and try to be
1263 # significantly smarter about delta bases. This is
1264 # slightly tricky because this same code has to work for
1265 # all revlogs, and we don't have the linkrev/linknode here.
1266 return p1
1267
1268 def _makecg1packer(repo, filematcher, bundlecaps, ellipses=False, 1247 def _makecg1packer(repo, filematcher, bundlecaps, ellipses=False,
1269 shallow=False, ellipsisroots=None, fullnodes=None): 1248 shallow=False, ellipsisroots=None, fullnodes=None):
1270 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack( 1249 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1271 d.node, d.p1node, d.p2node, d.linknode) 1250 d.node, d.p1node, d.p2node, d.linknode)
1272 1251
1273 return cgpacker(repo, filematcher, b'01', 1252 return cgpacker(repo, filematcher, b'01',
1274 deltaparentfn=_deltaparentprev,
1275 allowreorder=None, 1253 allowreorder=None,
1276 builddeltaheader=builddeltaheader, 1254 builddeltaheader=builddeltaheader,
1277 manifestsend=b'', 1255 manifestsend=b'',
1256 forcedeltaparentprev=True,
1278 bundlecaps=bundlecaps, 1257 bundlecaps=bundlecaps,
1279 ellipses=ellipses, 1258 ellipses=ellipses,
1280 shallow=shallow, 1259 shallow=shallow,
1281 ellipsisroots=ellipsisroots, 1260 ellipsisroots=ellipsisroots,
1282 fullnodes=fullnodes) 1261 fullnodes=fullnodes)
1288 1267
1289 # Since generaldelta is directly supported by cg2, reordering 1268 # Since generaldelta is directly supported by cg2, reordering
1290 # generally doesn't help, so we disable it by default (treating 1269 # generally doesn't help, so we disable it by default (treating
1291 # bundle.reorder=auto just like bundle.reorder=False). 1270 # bundle.reorder=auto just like bundle.reorder=False).
1292 return cgpacker(repo, filematcher, b'02', 1271 return cgpacker(repo, filematcher, b'02',
1293 deltaparentfn=_deltaparentgeneraldelta,
1294 allowreorder=False, 1272 allowreorder=False,
1295 builddeltaheader=builddeltaheader, 1273 builddeltaheader=builddeltaheader,
1296 manifestsend=b'', 1274 manifestsend=b'',
1297 bundlecaps=bundlecaps, 1275 bundlecaps=bundlecaps,
1298 ellipses=ellipses, 1276 ellipses=ellipses,
1303 def _makecg3packer(repo, filematcher, bundlecaps, ellipses=False, 1281 def _makecg3packer(repo, filematcher, bundlecaps, ellipses=False,
1304 shallow=False, ellipsisroots=None, fullnodes=None): 1282 shallow=False, ellipsisroots=None, fullnodes=None):
1305 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack( 1283 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1306 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags) 1284 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags)
1307 1285
1308 deltaparentfn = (_deltaparentellipses if ellipses
1309 else _deltaparentgeneraldelta)
1310
1311 return cgpacker(repo, filematcher, b'03', 1286 return cgpacker(repo, filematcher, b'03',
1312 deltaparentfn=deltaparentfn,
1313 allowreorder=False, 1287 allowreorder=False,
1314 builddeltaheader=builddeltaheader, 1288 builddeltaheader=builddeltaheader,
1315 manifestsend=closechunk(), 1289 manifestsend=closechunk(),
1316 bundlecaps=bundlecaps, 1290 bundlecaps=bundlecaps,
1317 ellipses=ellipses, 1291 ellipses=ellipses,