##// END OF EJS Templates
manifest: use `read_delta_parents` when adjusting linkrev in remotefile...
marmoute -
r52675:4704c4c7 default
parent child Browse files
Show More
@@ -1,527 +1,533
1 # remotefilectx.py - filectx/workingfilectx implementations for remotefilelog
1 # remotefilectx.py - filectx/workingfilectx implementations for remotefilelog
2 #
2 #
3 # Copyright 2013 Facebook, Inc.
3 # Copyright 2013 Facebook, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import collections
8 import collections
9 import time
9 import time
10
10
11 from mercurial.node import bin, hex, nullrev
11 from mercurial.node import bin, hex, nullrev
12 from mercurial import (
12 from mercurial import (
13 ancestor,
13 ancestor,
14 context,
14 context,
15 error,
15 error,
16 phases,
16 phases,
17 util,
17 util,
18 )
18 )
19 from . import shallowutil
19 from . import shallowutil
20
20
21 propertycache = util.propertycache
21 propertycache = util.propertycache
22 FASTLOG_TIMEOUT_IN_SECS = 0.5
22 FASTLOG_TIMEOUT_IN_SECS = 0.5
23
23
24
24
25 class remotefilectx(context.filectx):
25 class remotefilectx(context.filectx):
26 def __init__(
26 def __init__(
27 self,
27 self,
28 repo,
28 repo,
29 path,
29 path,
30 changeid=None,
30 changeid=None,
31 fileid=None,
31 fileid=None,
32 filelog=None,
32 filelog=None,
33 changectx=None,
33 changectx=None,
34 ancestormap=None,
34 ancestormap=None,
35 ):
35 ):
36 if fileid == nullrev:
36 if fileid == nullrev:
37 fileid = repo.nullid
37 fileid = repo.nullid
38 if fileid and len(fileid) == 40:
38 if fileid and len(fileid) == 40:
39 fileid = bin(fileid)
39 fileid = bin(fileid)
40 super(remotefilectx, self).__init__(
40 super(remotefilectx, self).__init__(
41 repo, path, changeid, fileid, filelog, changectx
41 repo, path, changeid, fileid, filelog, changectx
42 )
42 )
43 self._ancestormap = ancestormap
43 self._ancestormap = ancestormap
44
44
45 def size(self):
45 def size(self):
46 return self._filelog.size(self._filenode)
46 return self._filelog.size(self._filenode)
47
47
48 @propertycache
48 @propertycache
49 def _changeid(self):
49 def _changeid(self):
50 if '_changeid' in self.__dict__:
50 if '_changeid' in self.__dict__:
51 return self._changeid
51 return self._changeid
52 elif '_changectx' in self.__dict__:
52 elif '_changectx' in self.__dict__:
53 return self._changectx.rev()
53 return self._changectx.rev()
54 elif '_descendantrev' in self.__dict__:
54 elif '_descendantrev' in self.__dict__:
55 # this file context was created from a revision with a known
55 # this file context was created from a revision with a known
56 # descendant, we can (lazily) correct for linkrev aliases
56 # descendant, we can (lazily) correct for linkrev aliases
57 linknode = self._adjustlinknode(
57 linknode = self._adjustlinknode(
58 self._path, self._filelog, self._filenode, self._descendantrev
58 self._path, self._filelog, self._filenode, self._descendantrev
59 )
59 )
60 return self._repo.unfiltered().changelog.rev(linknode)
60 return self._repo.unfiltered().changelog.rev(linknode)
61 else:
61 else:
62 return self.linkrev()
62 return self.linkrev()
63
63
64 def filectx(self, fileid, changeid=None):
64 def filectx(self, fileid, changeid=None):
65 """opens an arbitrary revision of the file without
65 """opens an arbitrary revision of the file without
66 opening a new filelog"""
66 opening a new filelog"""
67 return remotefilectx(
67 return remotefilectx(
68 self._repo,
68 self._repo,
69 self._path,
69 self._path,
70 fileid=fileid,
70 fileid=fileid,
71 filelog=self._filelog,
71 filelog=self._filelog,
72 changeid=changeid,
72 changeid=changeid,
73 )
73 )
74
74
75 def linkrev(self):
75 def linkrev(self):
76 return self._linkrev
76 return self._linkrev
77
77
78 @propertycache
78 @propertycache
79 def _linkrev(self):
79 def _linkrev(self):
80 if self._filenode == self._repo.nullid:
80 if self._filenode == self._repo.nullid:
81 return nullrev
81 return nullrev
82
82
83 ancestormap = self.ancestormap()
83 ancestormap = self.ancestormap()
84 p1, p2, linknode, copyfrom = ancestormap[self._filenode]
84 p1, p2, linknode, copyfrom = ancestormap[self._filenode]
85 rev = self._repo.changelog.index.get_rev(linknode)
85 rev = self._repo.changelog.index.get_rev(linknode)
86 if rev is not None:
86 if rev is not None:
87 return rev
87 return rev
88
88
89 # Search all commits for the appropriate linkrev (slow, but uncommon)
89 # Search all commits for the appropriate linkrev (slow, but uncommon)
90 path = self._path
90 path = self._path
91 fileid = self._filenode
91 fileid = self._filenode
92 cl = self._repo.unfiltered().changelog
92 cl = self._repo.unfiltered().changelog
93 mfl = self._repo.manifestlog
93 mfl = self._repo.manifestlog
94
94
95 for rev in range(len(cl) - 1, 0, -1):
95 for rev in range(len(cl) - 1, 0, -1):
96 node = cl.node(rev)
96 node = cl.node(rev)
97 data = cl.read(
97 data = cl.read(
98 node
98 node
99 ) # get changeset data (we avoid object creation)
99 ) # get changeset data (we avoid object creation)
100 if path in data[3]: # checking the 'files' field.
100 if path in data[3]: # checking the 'files' field.
101 # The file has been touched, check if the hash is what we're
101 # The file has been touched, check if the hash is what we're
102 # looking for.
102 # looking for.
103 if fileid == mfl[data[0]].readfast().get(path):
103 #
104 # The change has to be against a parent, otherwise we might be
105 # missing linkrev worthy changes.
106 m = mfl[data[0]].read_delta_parents(exact=False)
107 if fileid == m.get(path):
104 return rev
108 return rev
105
109
106 # Couldn't find the linkrev. This should generally not happen, and will
110 # Couldn't find the linkrev. This should generally not happen, and will
107 # likely cause a crash.
111 # likely cause a crash.
108 return None
112 return None
109
113
110 def introrev(self):
114 def introrev(self):
111 """return the rev of the changeset which introduced this file revision
115 """return the rev of the changeset which introduced this file revision
112
116
113 This method is different from linkrev because it take into account the
117 This method is different from linkrev because it take into account the
114 changeset the filectx was created from. It ensures the returned
118 changeset the filectx was created from. It ensures the returned
115 revision is one of its ancestors. This prevents bugs from
119 revision is one of its ancestors. This prevents bugs from
116 'linkrev-shadowing' when a file revision is used by multiple
120 'linkrev-shadowing' when a file revision is used by multiple
117 changesets.
121 changesets.
118 """
122 """
119 lkr = self.linkrev()
123 lkr = self.linkrev()
120 attrs = vars(self)
124 attrs = vars(self)
121 noctx = not ('_changeid' in attrs or r'_changectx' in attrs)
125 noctx = not ('_changeid' in attrs or r'_changectx' in attrs)
122 if noctx or self.rev() == lkr:
126 if noctx or self.rev() == lkr:
123 return lkr
127 return lkr
124 linknode = self._adjustlinknode(
128 linknode = self._adjustlinknode(
125 self._path,
129 self._path,
126 self._filelog,
130 self._filelog,
127 self._filenode,
131 self._filenode,
128 self.rev(),
132 self.rev(),
129 inclusive=True,
133 inclusive=True,
130 )
134 )
131 return self._repo.changelog.rev(linknode)
135 return self._repo.changelog.rev(linknode)
132
136
133 def renamed(self):
137 def renamed(self):
134 """check if file was actually renamed in this changeset revision
138 """check if file was actually renamed in this changeset revision
135
139
136 If rename logged in file revision, we report copy for changeset only
140 If rename logged in file revision, we report copy for changeset only
137 if file revisions linkrev points back to the changeset in question
141 if file revisions linkrev points back to the changeset in question
138 or both changeset parents contain different file revisions.
142 or both changeset parents contain different file revisions.
139 """
143 """
140 ancestormap = self.ancestormap()
144 ancestormap = self.ancestormap()
141
145
142 p1, p2, linknode, copyfrom = ancestormap[self._filenode]
146 p1, p2, linknode, copyfrom = ancestormap[self._filenode]
143 if not copyfrom:
147 if not copyfrom:
144 return None
148 return None
145
149
146 renamed = (copyfrom, p1)
150 renamed = (copyfrom, p1)
147 if self.rev() == self.linkrev():
151 if self.rev() == self.linkrev():
148 return renamed
152 return renamed
149
153
150 name = self.path()
154 name = self.path()
151 fnode = self._filenode
155 fnode = self._filenode
152 for p in self._changectx.parents():
156 for p in self._changectx.parents():
153 try:
157 try:
154 if fnode == p.filenode(name):
158 if fnode == p.filenode(name):
155 return None
159 return None
156 except error.LookupError:
160 except error.LookupError:
157 pass
161 pass
158 return renamed
162 return renamed
159
163
160 def copysource(self):
164 def copysource(self):
161 copy = self.renamed()
165 copy = self.renamed()
162 return copy and copy[0]
166 return copy and copy[0]
163
167
164 def ancestormap(self):
168 def ancestormap(self):
165 if not self._ancestormap:
169 if not self._ancestormap:
166 self._ancestormap = self.filelog().ancestormap(self._filenode)
170 self._ancestormap = self.filelog().ancestormap(self._filenode)
167
171
168 return self._ancestormap
172 return self._ancestormap
169
173
170 def parents(self):
174 def parents(self):
171 repo = self._repo
175 repo = self._repo
172 ancestormap = self.ancestormap()
176 ancestormap = self.ancestormap()
173
177
174 p1, p2, linknode, copyfrom = ancestormap[self._filenode]
178 p1, p2, linknode, copyfrom = ancestormap[self._filenode]
175 results = []
179 results = []
176 if p1 != repo.nullid:
180 if p1 != repo.nullid:
177 path = copyfrom or self._path
181 path = copyfrom or self._path
178 flog = repo.file(path)
182 flog = repo.file(path)
179 p1ctx = remotefilectx(
183 p1ctx = remotefilectx(
180 repo, path, fileid=p1, filelog=flog, ancestormap=ancestormap
184 repo, path, fileid=p1, filelog=flog, ancestormap=ancestormap
181 )
185 )
182 p1ctx._descendantrev = self.rev()
186 p1ctx._descendantrev = self.rev()
183 results.append(p1ctx)
187 results.append(p1ctx)
184
188
185 if p2 != repo.nullid:
189 if p2 != repo.nullid:
186 path = self._path
190 path = self._path
187 flog = repo.file(path)
191 flog = repo.file(path)
188 p2ctx = remotefilectx(
192 p2ctx = remotefilectx(
189 repo, path, fileid=p2, filelog=flog, ancestormap=ancestormap
193 repo, path, fileid=p2, filelog=flog, ancestormap=ancestormap
190 )
194 )
191 p2ctx._descendantrev = self.rev()
195 p2ctx._descendantrev = self.rev()
192 results.append(p2ctx)
196 results.append(p2ctx)
193
197
194 return results
198 return results
195
199
196 def _nodefromancrev(self, ancrev, cl, mfl, path, fnode):
200 def _nodefromancrev(self, ancrev, cl, mfl, path, fnode):
197 """returns the node for <path> in <ancrev> if content matches <fnode>"""
201 """returns the node for <path> in <ancrev> if content matches <fnode>"""
198 ancctx = cl.read(ancrev) # This avoids object creation.
202 ancctx = cl.read(ancrev) # This avoids object creation.
199 manifestnode, files = ancctx[0], ancctx[3]
203 manifestnode, files = ancctx[0], ancctx[3]
200 # If the file was touched in this ancestor, and the content is similar
204 # If the file was touched in this ancestor, and the content is similar
201 # to the one we are searching for.
205 # to the one we are searching for.
202 if path in files and fnode == mfl[manifestnode].readfast().get(path):
206 if path in files:
203 return cl.node(ancrev)
207 m = mfl[manifestnode].read_delta_parents(exact=False)
208 if fnode == m.get(path):
209 return cl.node(ancrev)
204 return None
210 return None
205
211
206 def _adjustlinknode(self, path, filelog, fnode, srcrev, inclusive=False):
212 def _adjustlinknode(self, path, filelog, fnode, srcrev, inclusive=False):
207 """return the first ancestor of <srcrev> introducing <fnode>
213 """return the first ancestor of <srcrev> introducing <fnode>
208
214
209 If the linkrev of the file revision does not point to an ancestor of
215 If the linkrev of the file revision does not point to an ancestor of
210 srcrev, we'll walk down the ancestors until we find one introducing
216 srcrev, we'll walk down the ancestors until we find one introducing
211 this file revision.
217 this file revision.
212
218
213 :repo: a localrepository object (used to access changelog and manifest)
219 :repo: a localrepository object (used to access changelog and manifest)
214 :path: the file path
220 :path: the file path
215 :fnode: the nodeid of the file revision
221 :fnode: the nodeid of the file revision
216 :filelog: the filelog of this path
222 :filelog: the filelog of this path
217 :srcrev: the changeset revision we search ancestors from
223 :srcrev: the changeset revision we search ancestors from
218 :inclusive: if true, the src revision will also be checked
224 :inclusive: if true, the src revision will also be checked
219
225
220 Note: This is based on adjustlinkrev in core, but it's quite different.
226 Note: This is based on adjustlinkrev in core, but it's quite different.
221
227
222 adjustlinkrev depends on the fact that the linkrev is the bottom most
228 adjustlinkrev depends on the fact that the linkrev is the bottom most
223 node, and uses that as a stopping point for the ancestor traversal. We
229 node, and uses that as a stopping point for the ancestor traversal. We
224 can't do that here because the linknode is not guaranteed to be the
230 can't do that here because the linknode is not guaranteed to be the
225 bottom most one.
231 bottom most one.
226
232
227 In our code here, we actually know what a bunch of potential ancestor
233 In our code here, we actually know what a bunch of potential ancestor
228 linknodes are, so instead of stopping the cheap-ancestor-traversal when
234 linknodes are, so instead of stopping the cheap-ancestor-traversal when
229 we get to a linkrev, we stop when we see any of the known linknodes.
235 we get to a linkrev, we stop when we see any of the known linknodes.
230 """
236 """
231 repo = self._repo
237 repo = self._repo
232 cl = repo.unfiltered().changelog
238 cl = repo.unfiltered().changelog
233 mfl = repo.manifestlog
239 mfl = repo.manifestlog
234 ancestormap = self.ancestormap()
240 ancestormap = self.ancestormap()
235 linknode = ancestormap[fnode][2]
241 linknode = ancestormap[fnode][2]
236
242
237 if srcrev is None:
243 if srcrev is None:
238 # wctx case, used by workingfilectx during mergecopy
244 # wctx case, used by workingfilectx during mergecopy
239 revs = [p.rev() for p in self._repo[None].parents()]
245 revs = [p.rev() for p in self._repo[None].parents()]
240 inclusive = True # we skipped the real (revless) source
246 inclusive = True # we skipped the real (revless) source
241 else:
247 else:
242 revs = [srcrev]
248 revs = [srcrev]
243
249
244 if self._verifylinknode(revs, linknode):
250 if self._verifylinknode(revs, linknode):
245 return linknode
251 return linknode
246
252
247 commonlogkwargs = {
253 commonlogkwargs = {
248 'revs': b' '.join([hex(cl.node(rev)) for rev in revs]),
254 'revs': b' '.join([hex(cl.node(rev)) for rev in revs]),
249 'fnode': hex(fnode),
255 'fnode': hex(fnode),
250 'filepath': path,
256 'filepath': path,
251 'user': shallowutil.getusername(repo.ui),
257 'user': shallowutil.getusername(repo.ui),
252 'reponame': shallowutil.getreponame(repo.ui),
258 'reponame': shallowutil.getreponame(repo.ui),
253 }
259 }
254
260
255 repo.ui.log(b'linkrevfixup', b'adjusting linknode\n', **commonlogkwargs)
261 repo.ui.log(b'linkrevfixup', b'adjusting linknode\n', **commonlogkwargs)
256
262
257 pc = repo._phasecache
263 pc = repo._phasecache
258 seenpublic = False
264 seenpublic = False
259 iteranc = cl.ancestors(revs, inclusive=inclusive)
265 iteranc = cl.ancestors(revs, inclusive=inclusive)
260 for ancrev in iteranc:
266 for ancrev in iteranc:
261 # First, check locally-available history.
267 # First, check locally-available history.
262 lnode = self._nodefromancrev(ancrev, cl, mfl, path, fnode)
268 lnode = self._nodefromancrev(ancrev, cl, mfl, path, fnode)
263 if lnode is not None:
269 if lnode is not None:
264 return lnode
270 return lnode
265
271
266 # adjusting linknode can be super-slow. To mitigate the issue
272 # adjusting linknode can be super-slow. To mitigate the issue
267 # we use two heuristics: calling fastlog and forcing remotefilelog
273 # we use two heuristics: calling fastlog and forcing remotefilelog
268 # prefetch
274 # prefetch
269 if not seenpublic and pc.phase(repo, ancrev) == phases.public:
275 if not seenpublic and pc.phase(repo, ancrev) == phases.public:
270 # TODO: there used to be a codepath to fetch linknodes
276 # TODO: there used to be a codepath to fetch linknodes
271 # from a server as a fast path, but it appeared to
277 # from a server as a fast path, but it appeared to
272 # depend on an API FB added to their phabricator.
278 # depend on an API FB added to their phabricator.
273 lnode = self._forceprefetch(
279 lnode = self._forceprefetch(
274 repo, path, fnode, revs, commonlogkwargs
280 repo, path, fnode, revs, commonlogkwargs
275 )
281 )
276 if lnode:
282 if lnode:
277 return lnode
283 return lnode
278 seenpublic = True
284 seenpublic = True
279
285
280 return linknode
286 return linknode
281
287
282 def _forceprefetch(self, repo, path, fnode, revs, commonlogkwargs):
288 def _forceprefetch(self, repo, path, fnode, revs, commonlogkwargs):
283 # This next part is super non-obvious, so big comment block time!
289 # This next part is super non-obvious, so big comment block time!
284 #
290 #
285 # It is possible to get extremely bad performance here when a fairly
291 # It is possible to get extremely bad performance here when a fairly
286 # common set of circumstances occur when this extension is combined
292 # common set of circumstances occur when this extension is combined
287 # with a server-side commit rewriting extension like pushrebase.
293 # with a server-side commit rewriting extension like pushrebase.
288 #
294 #
289 # First, an engineer creates Commit A and pushes it to the server.
295 # First, an engineer creates Commit A and pushes it to the server.
290 # While the server's data structure will have the correct linkrev
296 # While the server's data structure will have the correct linkrev
291 # for the files touched in Commit A, the client will have the
297 # for the files touched in Commit A, the client will have the
292 # linkrev of the local commit, which is "invalid" because it's not
298 # linkrev of the local commit, which is "invalid" because it's not
293 # an ancestor of the main line of development.
299 # an ancestor of the main line of development.
294 #
300 #
295 # The client will never download the remotefilelog with the correct
301 # The client will never download the remotefilelog with the correct
296 # linkrev as long as nobody else touches that file, since the file
302 # linkrev as long as nobody else touches that file, since the file
297 # data and history hasn't changed since Commit A.
303 # data and history hasn't changed since Commit A.
298 #
304 #
299 # After a long time (or a short time in a heavily used repo), if the
305 # After a long time (or a short time in a heavily used repo), if the
300 # same engineer returns to change the same file, some commands --
306 # same engineer returns to change the same file, some commands --
301 # such as amends of commits with file moves, logs, diffs, etc --
307 # such as amends of commits with file moves, logs, diffs, etc --
302 # can trigger this _adjustlinknode code. In those cases, finding
308 # can trigger this _adjustlinknode code. In those cases, finding
303 # the correct rev can become quite expensive, as the correct
309 # the correct rev can become quite expensive, as the correct
304 # revision is far back in history and we need to walk back through
310 # revision is far back in history and we need to walk back through
305 # history to find it.
311 # history to find it.
306 #
312 #
307 # In order to improve this situation, we force a prefetch of the
313 # In order to improve this situation, we force a prefetch of the
308 # remotefilelog data blob for the file we were called on. We do this
314 # remotefilelog data blob for the file we were called on. We do this
309 # at most once, when we first see a public commit in the history we
315 # at most once, when we first see a public commit in the history we
310 # are traversing.
316 # are traversing.
311 #
317 #
312 # Forcing the prefetch means we will download the remote blob even
318 # Forcing the prefetch means we will download the remote blob even
313 # if we have the "correct" blob in the local store. Since the union
319 # if we have the "correct" blob in the local store. Since the union
314 # store checks the remote store first, this means we are much more
320 # store checks the remote store first, this means we are much more
315 # likely to get the correct linkrev at this point.
321 # likely to get the correct linkrev at this point.
316 #
322 #
317 # In rare circumstances (such as the server having a suboptimal
323 # In rare circumstances (such as the server having a suboptimal
318 # linkrev for our use case), we will fall back to the old slow path.
324 # linkrev for our use case), we will fall back to the old slow path.
319 #
325 #
320 # We may want to add additional heuristics here in the future if
326 # We may want to add additional heuristics here in the future if
321 # the slow path is used too much. One promising possibility is using
327 # the slow path is used too much. One promising possibility is using
322 # obsolescence markers to find a more-likely-correct linkrev.
328 # obsolescence markers to find a more-likely-correct linkrev.
323
329
324 logmsg = b''
330 logmsg = b''
325 start = time.time()
331 start = time.time()
326 try:
332 try:
327 repo.fileservice.prefetch([(path, hex(fnode))], force=True)
333 repo.fileservice.prefetch([(path, hex(fnode))], force=True)
328
334
329 # Now that we've downloaded a new blob from the server,
335 # Now that we've downloaded a new blob from the server,
330 # we need to rebuild the ancestor map to recompute the
336 # we need to rebuild the ancestor map to recompute the
331 # linknodes.
337 # linknodes.
332 self._ancestormap = None
338 self._ancestormap = None
333 linknode = self.ancestormap()[fnode][2] # 2 is linknode
339 linknode = self.ancestormap()[fnode][2] # 2 is linknode
334 if self._verifylinknode(revs, linknode):
340 if self._verifylinknode(revs, linknode):
335 logmsg = b'remotefilelog prefetching succeeded'
341 logmsg = b'remotefilelog prefetching succeeded'
336 return linknode
342 return linknode
337 logmsg = b'remotefilelog prefetching not found'
343 logmsg = b'remotefilelog prefetching not found'
338 return None
344 return None
339 except Exception as e:
345 except Exception as e:
340 logmsg = b'remotefilelog prefetching failed (%s)' % e
346 logmsg = b'remotefilelog prefetching failed (%s)' % e
341 return None
347 return None
342 finally:
348 finally:
343 elapsed = time.time() - start
349 elapsed = time.time() - start
344 repo.ui.log(
350 repo.ui.log(
345 b'linkrevfixup',
351 b'linkrevfixup',
346 logmsg + b'\n',
352 logmsg + b'\n',
347 elapsed=elapsed * 1000,
353 elapsed=elapsed * 1000,
348 **commonlogkwargs
354 **commonlogkwargs
349 )
355 )
350
356
351 def _verifylinknode(self, revs, linknode):
357 def _verifylinknode(self, revs, linknode):
352 """
358 """
353 Check if a linknode is correct one for the current history.
359 Check if a linknode is correct one for the current history.
354
360
355 That is, return True if the linkrev is the ancestor of any of the
361 That is, return True if the linkrev is the ancestor of any of the
356 passed in revs, otherwise return False.
362 passed in revs, otherwise return False.
357
363
358 `revs` is a list that usually has one element -- usually the wdir parent
364 `revs` is a list that usually has one element -- usually the wdir parent
359 or the user-passed rev we're looking back from. It may contain two revs
365 or the user-passed rev we're looking back from. It may contain two revs
360 when there is a merge going on, or zero revs when a root node with no
366 when there is a merge going on, or zero revs when a root node with no
361 parents is being created.
367 parents is being created.
362 """
368 """
363 if not revs:
369 if not revs:
364 return False
370 return False
365 try:
371 try:
366 # Use the C fastpath to check if the given linknode is correct.
372 # Use the C fastpath to check if the given linknode is correct.
367 cl = self._repo.unfiltered().changelog
373 cl = self._repo.unfiltered().changelog
368 return any(cl.isancestor(linknode, cl.node(r)) for r in revs)
374 return any(cl.isancestor(linknode, cl.node(r)) for r in revs)
369 except error.LookupError:
375 except error.LookupError:
370 # The linknode read from the blob may have been stripped or
376 # The linknode read from the blob may have been stripped or
371 # otherwise not present in the repository anymore. Do not fail hard
377 # otherwise not present in the repository anymore. Do not fail hard
372 # in this case. Instead, return false and continue the search for
378 # in this case. Instead, return false and continue the search for
373 # the correct linknode.
379 # the correct linknode.
374 return False
380 return False
375
381
376 def ancestors(self, followfirst=False):
382 def ancestors(self, followfirst=False):
377 ancestors = []
383 ancestors = []
378 queue = collections.deque((self,))
384 queue = collections.deque((self,))
379 seen = set()
385 seen = set()
380 while queue:
386 while queue:
381 current = queue.pop()
387 current = queue.pop()
382 if current.filenode() in seen:
388 if current.filenode() in seen:
383 continue
389 continue
384 seen.add(current.filenode())
390 seen.add(current.filenode())
385
391
386 ancestors.append(current)
392 ancestors.append(current)
387
393
388 parents = current.parents()
394 parents = current.parents()
389 first = True
395 first = True
390 for p in parents:
396 for p in parents:
391 if first or not followfirst:
397 if first or not followfirst:
392 queue.append(p)
398 queue.append(p)
393 first = False
399 first = False
394
400
395 # Remove self
401 # Remove self
396 ancestors.pop(0)
402 ancestors.pop(0)
397
403
398 # Sort by linkrev
404 # Sort by linkrev
399 # The copy tracing algorithm depends on these coming out in order
405 # The copy tracing algorithm depends on these coming out in order
400 ancestors = sorted(ancestors, reverse=True, key=lambda x: x.linkrev())
406 ancestors = sorted(ancestors, reverse=True, key=lambda x: x.linkrev())
401
407
402 for ancestor in ancestors:
408 for ancestor in ancestors:
403 yield ancestor
409 yield ancestor
404
410
405 def ancestor(self, fc2, actx):
411 def ancestor(self, fc2, actx):
406 # the easy case: no (relevant) renames
412 # the easy case: no (relevant) renames
407 if fc2.path() == self.path() and self.path() in actx:
413 if fc2.path() == self.path() and self.path() in actx:
408 return actx[self.path()]
414 return actx[self.path()]
409
415
410 # the next easiest cases: unambiguous predecessor (name trumps
416 # the next easiest cases: unambiguous predecessor (name trumps
411 # history)
417 # history)
412 if self.path() in actx and fc2.path() not in actx:
418 if self.path() in actx and fc2.path() not in actx:
413 return actx[self.path()]
419 return actx[self.path()]
414 if fc2.path() in actx and self.path() not in actx:
420 if fc2.path() in actx and self.path() not in actx:
415 return actx[fc2.path()]
421 return actx[fc2.path()]
416
422
417 # do a full traversal
423 # do a full traversal
418 amap = self.ancestormap()
424 amap = self.ancestormap()
419 bmap = fc2.ancestormap()
425 bmap = fc2.ancestormap()
420
426
421 def parents(x):
427 def parents(x):
422 f, n = x
428 f, n = x
423 p = amap.get(n) or bmap.get(n)
429 p = amap.get(n) or bmap.get(n)
424 if not p:
430 if not p:
425 return []
431 return []
426
432
427 return [(p[3] or f, p[0]), (f, p[1])]
433 return [(p[3] or f, p[0]), (f, p[1])]
428
434
429 a = (self.path(), self.filenode())
435 a = (self.path(), self.filenode())
430 b = (fc2.path(), fc2.filenode())
436 b = (fc2.path(), fc2.filenode())
431 result = ancestor.genericancestor(a, b, parents)
437 result = ancestor.genericancestor(a, b, parents)
432 if result:
438 if result:
433 f, n = result
439 f, n = result
434 r = remotefilectx(self._repo, f, fileid=n, ancestormap=amap)
440 r = remotefilectx(self._repo, f, fileid=n, ancestormap=amap)
435 return r
441 return r
436
442
437 return None
443 return None
438
444
439 def annotate(self, *args, **kwargs):
445 def annotate(self, *args, **kwargs):
440 introctx = self
446 introctx = self
441 prefetchskip = kwargs.pop('prefetchskip', None)
447 prefetchskip = kwargs.pop('prefetchskip', None)
442 if prefetchskip:
448 if prefetchskip:
443 # use introrev so prefetchskip can be accurately tested
449 # use introrev so prefetchskip can be accurately tested
444 introrev = self.introrev()
450 introrev = self.introrev()
445 if self.rev() != introrev:
451 if self.rev() != introrev:
446 introctx = remotefilectx(
452 introctx = remotefilectx(
447 self._repo,
453 self._repo,
448 self._path,
454 self._path,
449 changeid=introrev,
455 changeid=introrev,
450 fileid=self._filenode,
456 fileid=self._filenode,
451 filelog=self._filelog,
457 filelog=self._filelog,
452 ancestormap=self._ancestormap,
458 ancestormap=self._ancestormap,
453 )
459 )
454
460
455 # like self.ancestors, but append to "fetch" and skip visiting parents
461 # like self.ancestors, but append to "fetch" and skip visiting parents
456 # of nodes in "prefetchskip".
462 # of nodes in "prefetchskip".
457 fetch = []
463 fetch = []
458 seen = set()
464 seen = set()
459 queue = collections.deque((introctx,))
465 queue = collections.deque((introctx,))
460 seen.add(introctx.node())
466 seen.add(introctx.node())
461 while queue:
467 while queue:
462 current = queue.pop()
468 current = queue.pop()
463 if current.filenode() != self.filenode():
469 if current.filenode() != self.filenode():
464 # this is a "joint point". fastannotate needs contents of
470 # this is a "joint point". fastannotate needs contents of
465 # "joint point"s to calculate diffs for side branches.
471 # "joint point"s to calculate diffs for side branches.
466 fetch.append((current.path(), hex(current.filenode())))
472 fetch.append((current.path(), hex(current.filenode())))
467 if prefetchskip and current in prefetchskip:
473 if prefetchskip and current in prefetchskip:
468 continue
474 continue
469 for parent in current.parents():
475 for parent in current.parents():
470 if parent.node() not in seen:
476 if parent.node() not in seen:
471 seen.add(parent.node())
477 seen.add(parent.node())
472 queue.append(parent)
478 queue.append(parent)
473
479
474 self._repo.ui.debug(
480 self._repo.ui.debug(
475 b'remotefilelog: prefetching %d files '
481 b'remotefilelog: prefetching %d files '
476 b'for annotate\n' % len(fetch)
482 b'for annotate\n' % len(fetch)
477 )
483 )
478 if fetch:
484 if fetch:
479 self._repo.fileservice.prefetch(fetch)
485 self._repo.fileservice.prefetch(fetch)
480 return super(remotefilectx, self).annotate(*args, **kwargs)
486 return super(remotefilectx, self).annotate(*args, **kwargs)
481
487
482 # Return empty set so that the hg serve and thg don't stack trace
488 # Return empty set so that the hg serve and thg don't stack trace
483 def children(self):
489 def children(self):
484 return []
490 return []
485
491
486
492
487 class remoteworkingfilectx(context.workingfilectx, remotefilectx):
493 class remoteworkingfilectx(context.workingfilectx, remotefilectx):
488 def __init__(self, repo, path, filelog=None, workingctx=None):
494 def __init__(self, repo, path, filelog=None, workingctx=None):
489 self._ancestormap = None
495 self._ancestormap = None
490 super(remoteworkingfilectx, self).__init__(
496 super(remoteworkingfilectx, self).__init__(
491 repo, path, filelog, workingctx
497 repo, path, filelog, workingctx
492 )
498 )
493
499
494 def parents(self):
500 def parents(self):
495 return remotefilectx.parents(self)
501 return remotefilectx.parents(self)
496
502
497 def ancestormap(self):
503 def ancestormap(self):
498 if not self._ancestormap:
504 if not self._ancestormap:
499 path = self._path
505 path = self._path
500 pcl = self._changectx._parents
506 pcl = self._changectx._parents
501 renamed = self.renamed()
507 renamed = self.renamed()
502
508
503 if renamed:
509 if renamed:
504 p1 = renamed
510 p1 = renamed
505 else:
511 else:
506 p1 = (path, pcl[0]._manifest.get(path, self._repo.nullid))
512 p1 = (path, pcl[0]._manifest.get(path, self._repo.nullid))
507
513
508 p2 = (path, self._repo.nullid)
514 p2 = (path, self._repo.nullid)
509 if len(pcl) > 1:
515 if len(pcl) > 1:
510 p2 = (path, pcl[1]._manifest.get(path, self._repo.nullid))
516 p2 = (path, pcl[1]._manifest.get(path, self._repo.nullid))
511
517
512 m = {}
518 m = {}
513 if p1[1] != self._repo.nullid:
519 if p1[1] != self._repo.nullid:
514 p1ctx = self._repo.filectx(p1[0], fileid=p1[1])
520 p1ctx = self._repo.filectx(p1[0], fileid=p1[1])
515 m.update(p1ctx.filelog().ancestormap(p1[1]))
521 m.update(p1ctx.filelog().ancestormap(p1[1]))
516
522
517 if p2[1] != self._repo.nullid:
523 if p2[1] != self._repo.nullid:
518 p2ctx = self._repo.filectx(p2[0], fileid=p2[1])
524 p2ctx = self._repo.filectx(p2[0], fileid=p2[1])
519 m.update(p2ctx.filelog().ancestormap(p2[1]))
525 m.update(p2ctx.filelog().ancestormap(p2[1]))
520
526
521 copyfrom = b''
527 copyfrom = b''
522 if renamed:
528 if renamed:
523 copyfrom = renamed[0]
529 copyfrom = renamed[0]
524 m[None] = (p1[1], p2[1], self._repo.nullid, copyfrom)
530 m[None] = (p1[1], p2[1], self._repo.nullid, copyfrom)
525 self._ancestormap = m
531 self._ancestormap = m
526
532
527 return self._ancestormap
533 return self._ancestormap
General Comments 0
You need to be logged in to leave comments. Login now