Mercurial > public > mercurial-scm > hg
comparison mercurial/wireprotov2server.py @ 40923:3ed77780f4a6
wireprotov2: send linknodes to emitfilerevisions()
Previously, linknodes were calculated within emitfilerevisions() by
using filectx.introrev(), which would always use the linkrev/linknode
as recorded by storage. This is wrong for cases where the receiver
doesn't have the changeset the linknode refers to.
This commit changes the logic for linknode emission so the mapping
of filenode to linknode is computed by the caller and passed into
emitfilerevisions().
As part of the change, linknodes for "filesdata" in the
haveparents=False case are now correct: the existing code performed a
manifest walk and it was trivial to plug in the correct linknode.
However, behavior for the haveparents=True case is still wrong
because it relies on filtering linkrevs against the outgoing set in
order to determine what to send. This will be fixed in a subsequent
commit.
The change test test-wireproto-exchangev2-shallow.t is a bit wonky.
The test repo has 6 revisions. The changed test is performing a shallow
clone with depth=1. So, only file data for revision 5 is present
locally. So, the new behavior of associating the linknode with
revision 5 for every file revision seems correct. Of course, when
backfilling old revisions, we'll want to update the linknode. But
this problem requires wire protocol support and we'll cross that
bridge later.
Differential Revision: https://phab.mercurial-scm.org/D5405
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Mon, 10 Dec 2018 18:04:12 +0000 |
parents | f83cea7f54d7 |
children | 08cfa77d7288 |
comparison
equal
deleted
inserted
replaced
40922:ca6372b7e566 | 40923:3ed77780f4a6 |
---|---|
982 if not len(fl): | 982 if not len(fl): |
983 raise FileAccessError(path, 'unknown file: %s', (path,)) | 983 raise FileAccessError(path, 'unknown file: %s', (path,)) |
984 | 984 |
985 return fl | 985 return fl |
986 | 986 |
987 def emitfilerevisions(repo, path, revisions, fields): | 987 def emitfilerevisions(repo, path, revisions, linknodes, fields): |
988 clnode = repo.changelog.node | |
989 | |
990 for revision in revisions: | 988 for revision in revisions: |
991 d = { | 989 d = { |
992 b'node': revision.node, | 990 b'node': revision.node, |
993 } | 991 } |
994 | 992 |
995 if b'parents' in fields: | 993 if b'parents' in fields: |
996 d[b'parents'] = [revision.p1node, revision.p2node] | 994 d[b'parents'] = [revision.p1node, revision.p2node] |
997 | 995 |
998 if b'linknode' in fields: | 996 if b'linknode' in fields: |
999 # TODO by creating the filectx against a specific file revision | 997 d[b'linknode'] = linknodes[revision.node] |
1000 # instead of changeset, linkrev() is always used. This is wrong for | |
1001 # cases where linkrev() may refer to a hidden changeset. We need an | |
1002 # API for performing linkrev adjustment that takes this into | |
1003 # account. | |
1004 fctx = repo.filectx(path, fileid=revision.node) | |
1005 d[b'linknode'] = clnode(fctx.introrev()) | |
1006 | 998 |
1007 followingmeta = [] | 999 followingmeta = [] |
1008 followingdata = [] | 1000 followingdata = [] |
1009 | 1001 |
1010 if b'revision' in fields: | 1002 if b'revision' in fields: |
1084 # Extensions may wish to access the protocol handler. | 1076 # Extensions may wish to access the protocol handler. |
1085 store = getfilestore(repo, proto, path) | 1077 store = getfilestore(repo, proto, path) |
1086 except FileAccessError as e: | 1078 except FileAccessError as e: |
1087 raise error.WireprotoCommandError(e.msg, e.args) | 1079 raise error.WireprotoCommandError(e.msg, e.args) |
1088 | 1080 |
1081 clnode = repo.changelog.node | |
1082 linknodes = {} | |
1083 | |
1089 # Validate requested nodes. | 1084 # Validate requested nodes. |
1090 for node in nodes: | 1085 for node in nodes: |
1091 try: | 1086 try: |
1092 store.rev(node) | 1087 store.rev(node) |
1093 except error.LookupError: | 1088 except error.LookupError: |
1094 raise error.WireprotoCommandError('unknown file node: %s', | 1089 raise error.WireprotoCommandError('unknown file node: %s', |
1095 (hex(node),)) | 1090 (hex(node),)) |
1096 | 1091 |
1092 # TODO by creating the filectx against a specific file revision | |
1093 # instead of changeset, linkrev() is always used. This is wrong for | |
1094 # cases where linkrev() may refer to a hidden changeset. But since this | |
1095 # API doesn't know anything about changesets, we're not sure how to | |
1096 # disambiguate the linknode. Perhaps we should delete this API? | |
1097 fctx = repo.filectx(path, fileid=node) | |
1098 linknodes[node] = clnode(fctx.introrev()) | |
1099 | |
1097 revisions = store.emitrevisions(nodes, | 1100 revisions = store.emitrevisions(nodes, |
1098 revisiondata=b'revision' in fields, | 1101 revisiondata=b'revision' in fields, |
1099 assumehaveparentrevisions=haveparents) | 1102 assumehaveparentrevisions=haveparents) |
1100 | 1103 |
1101 yield { | 1104 yield { |
1102 b'totalitems': len(nodes), | 1105 b'totalitems': len(nodes), |
1103 } | 1106 } |
1104 | 1107 |
1105 for o in emitfilerevisions(repo, path, revisions, fields): | 1108 for o in emitfilerevisions(repo, path, revisions, linknodes, fields): |
1106 yield o | 1109 yield o |
1107 | 1110 |
1108 def filesdatacapabilities(repo, proto): | 1111 def filesdatacapabilities(repo, proto): |
1109 batchsize = repo.ui.configint( | 1112 batchsize = repo.ui.configint( |
1110 b'experimental', b'server.filesdata.recommended-batch-size') | 1113 b'experimental', b'server.filesdata.recommended-batch-size') |
1152 # another client fetching files data for that changeset. If a client has a | 1155 # another client fetching files data for that changeset. If a client has a |
1153 # changeset, it should probably be allowed to access files data for that | 1156 # changeset, it should probably be allowed to access files data for that |
1154 # changeset. | 1157 # changeset. |
1155 | 1158 |
1156 cl = repo.changelog | 1159 cl = repo.changelog |
1160 clnode = cl.node | |
1157 outgoing = resolvenodes(repo, revisions) | 1161 outgoing = resolvenodes(repo, revisions) |
1158 filematcher = makefilematcher(repo, pathfilter) | 1162 filematcher = makefilematcher(repo, pathfilter) |
1159 | 1163 |
1160 # Figure out what needs to be emitted. | 1164 # Figure out what needs to be emitted. |
1161 changedpaths = set() | 1165 changedpaths = set() |
1162 fnodes = collections.defaultdict(set) | 1166 # path -> {fnode: linknode} |
1167 fnodes = collections.defaultdict(dict) | |
1163 | 1168 |
1164 for node in outgoing: | 1169 for node in outgoing: |
1165 ctx = repo[node] | 1170 ctx = repo[node] |
1166 changedpaths.update(ctx.files()) | 1171 changedpaths.update(ctx.files()) |
1167 | 1172 |
1180 | 1185 |
1181 for rev in store: | 1186 for rev in store: |
1182 linkrev = store.linkrev(rev) | 1187 linkrev = store.linkrev(rev) |
1183 | 1188 |
1184 if linkrev in outgoingclrevs: | 1189 if linkrev in outgoingclrevs: |
1185 fnodes[path].add(store.node(rev)) | 1190 fnodes[path].setdefault(store.node(rev), clnode(linkrev)) |
1186 | 1191 |
1187 # If ancestors aren't known, we walk the manifests and send all | 1192 # If ancestors aren't known, we walk the manifests and send all |
1188 # encountered file revisions. | 1193 # encountered file revisions. |
1189 else: | 1194 else: |
1190 for node in outgoing: | 1195 for node in outgoing: |
1191 mctx = repo[node].manifestctx() | 1196 mctx = repo[node].manifestctx() |
1192 | 1197 |
1193 for path, fnode in mctx.read().items(): | 1198 for path, fnode in mctx.read().items(): |
1194 if filematcher(path): | 1199 if filematcher(path): |
1195 fnodes[path].add(fnode) | 1200 fnodes[path].setdefault(fnode, node) |
1196 | 1201 |
1197 yield { | 1202 yield { |
1198 b'totalpaths': len(fnodes), | 1203 b'totalpaths': len(fnodes), |
1199 b'totalitems': sum(len(v) for v in fnodes.values()) | 1204 b'totalitems': sum(len(v) for v in fnodes.values()) |
1200 } | 1205 } |
1208 yield { | 1213 yield { |
1209 b'path': path, | 1214 b'path': path, |
1210 b'totalitems': len(filenodes), | 1215 b'totalitems': len(filenodes), |
1211 } | 1216 } |
1212 | 1217 |
1213 revisions = store.emitrevisions(filenodes, | 1218 revisions = store.emitrevisions(filenodes.keys(), |
1214 revisiondata=b'revision' in fields, | 1219 revisiondata=b'revision' in fields, |
1215 assumehaveparentrevisions=haveparents) | 1220 assumehaveparentrevisions=haveparents) |
1216 | 1221 |
1217 for o in emitfilerevisions(repo, path, revisions, fields): | 1222 for o in emitfilerevisions(repo, path, revisions, filenodes, fields): |
1218 yield o | 1223 yield o |
1219 | 1224 |
1220 @wireprotocommand( | 1225 @wireprotocommand( |
1221 'heads', | 1226 'heads', |
1222 args={ | 1227 args={ |