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={