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