Mercurial > hg
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, |