##// END OF EJS Templates
context: add specialized way of getting copy source file only...
Martin von Zweigbergk -
r41934:041d8295 default
parent child Browse files
Show More
@@ -1,490 +1,494 b''
1 1 # remotefilectx.py - filectx/workingfilectx implementations for remotefilelog
2 2 #
3 3 # Copyright 2013 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 from __future__ import absolute_import
8 8
9 9 import collections
10 10 import time
11 11
12 12 from mercurial.node import bin, hex, nullid, nullrev
13 13 from mercurial import (
14 14 ancestor,
15 15 context,
16 16 error,
17 17 phases,
18 18 util,
19 19 )
20 20 from . import shallowutil
21 21
22 22 propertycache = util.propertycache
23 23 FASTLOG_TIMEOUT_IN_SECS = 0.5
24 24
25 25 class remotefilectx(context.filectx):
26 26 def __init__(self, repo, path, changeid=None, fileid=None,
27 27 filelog=None, changectx=None, ancestormap=None):
28 28 if fileid == nullrev:
29 29 fileid = nullid
30 30 if fileid and len(fileid) == 40:
31 31 fileid = bin(fileid)
32 32 super(remotefilectx, self).__init__(repo, path, changeid,
33 33 fileid, filelog, changectx)
34 34 self._ancestormap = ancestormap
35 35
36 36 def size(self):
37 37 return self._filelog.size(self._filenode)
38 38
39 39 @propertycache
40 40 def _changeid(self):
41 41 if r'_changeid' in self.__dict__:
42 42 return self._changeid
43 43 elif r'_changectx' in self.__dict__:
44 44 return self._changectx.rev()
45 45 elif r'_descendantrev' in self.__dict__:
46 46 # this file context was created from a revision with a known
47 47 # descendant, we can (lazily) correct for linkrev aliases
48 48 linknode = self._adjustlinknode(self._path, self._filelog,
49 49 self._filenode, self._descendantrev)
50 50 return self._repo.unfiltered().changelog.rev(linknode)
51 51 else:
52 52 return self.linkrev()
53 53
54 54 def filectx(self, fileid, changeid=None):
55 55 '''opens an arbitrary revision of the file without
56 56 opening a new filelog'''
57 57 return remotefilectx(self._repo, self._path, fileid=fileid,
58 58 filelog=self._filelog, changeid=changeid)
59 59
60 60 def linkrev(self):
61 61 return self._linkrev
62 62
63 63 @propertycache
64 64 def _linkrev(self):
65 65 if self._filenode == nullid:
66 66 return nullrev
67 67
68 68 ancestormap = self.ancestormap()
69 69 p1, p2, linknode, copyfrom = ancestormap[self._filenode]
70 70 rev = self._repo.changelog.nodemap.get(linknode)
71 71 if rev is not None:
72 72 return rev
73 73
74 74 # Search all commits for the appropriate linkrev (slow, but uncommon)
75 75 path = self._path
76 76 fileid = self._filenode
77 77 cl = self._repo.unfiltered().changelog
78 78 mfl = self._repo.manifestlog
79 79
80 80 for rev in range(len(cl) - 1, 0, -1):
81 81 node = cl.node(rev)
82 82 data = cl.read(node) # get changeset data (we avoid object creation)
83 83 if path in data[3]: # checking the 'files' field.
84 84 # The file has been touched, check if the hash is what we're
85 85 # looking for.
86 86 if fileid == mfl[data[0]].readfast().get(path):
87 87 return rev
88 88
89 89 # Couldn't find the linkrev. This should generally not happen, and will
90 90 # likely cause a crash.
91 91 return None
92 92
93 93 def introrev(self):
94 94 """return the rev of the changeset which introduced this file revision
95 95
96 96 This method is different from linkrev because it take into account the
97 97 changeset the filectx was created from. It ensures the returned
98 98 revision is one of its ancestors. This prevents bugs from
99 99 'linkrev-shadowing' when a file revision is used by multiple
100 100 changesets.
101 101 """
102 102 lkr = self.linkrev()
103 103 attrs = vars(self)
104 104 noctx = not (r'_changeid' in attrs or r'_changectx' in attrs)
105 105 if noctx or self.rev() == lkr:
106 106 return lkr
107 107 linknode = self._adjustlinknode(self._path, self._filelog,
108 108 self._filenode, self.rev(),
109 109 inclusive=True)
110 110 return self._repo.changelog.rev(linknode)
111 111
112 112 def renamed(self):
113 113 """check if file was actually renamed in this changeset revision
114 114
115 115 If rename logged in file revision, we report copy for changeset only
116 116 if file revisions linkrev points back to the changeset in question
117 117 or both changeset parents contain different file revisions.
118 118 """
119 119 ancestormap = self.ancestormap()
120 120
121 121 p1, p2, linknode, copyfrom = ancestormap[self._filenode]
122 122 if not copyfrom:
123 123 return None
124 124
125 125 renamed = (copyfrom, p1)
126 126 if self.rev() == self.linkrev():
127 127 return renamed
128 128
129 129 name = self.path()
130 130 fnode = self._filenode
131 131 for p in self._changectx.parents():
132 132 try:
133 133 if fnode == p.filenode(name):
134 134 return None
135 135 except error.LookupError:
136 136 pass
137 137 return renamed
138 138
139 def copysource(self):
140 copy = self.renamed()
141 return copy and copy[0]
142
139 143 def ancestormap(self):
140 144 if not self._ancestormap:
141 145 self._ancestormap = self.filelog().ancestormap(self._filenode)
142 146
143 147 return self._ancestormap
144 148
145 149 def parents(self):
146 150 repo = self._repo
147 151 ancestormap = self.ancestormap()
148 152
149 153 p1, p2, linknode, copyfrom = ancestormap[self._filenode]
150 154 results = []
151 155 if p1 != nullid:
152 156 path = copyfrom or self._path
153 157 flog = repo.file(path)
154 158 p1ctx = remotefilectx(repo, path, fileid=p1, filelog=flog,
155 159 ancestormap=ancestormap)
156 160 p1ctx._descendantrev = self.rev()
157 161 results.append(p1ctx)
158 162
159 163 if p2 != nullid:
160 164 path = self._path
161 165 flog = repo.file(path)
162 166 p2ctx = remotefilectx(repo, path, fileid=p2, filelog=flog,
163 167 ancestormap=ancestormap)
164 168 p2ctx._descendantrev = self.rev()
165 169 results.append(p2ctx)
166 170
167 171 return results
168 172
169 173 def _nodefromancrev(self, ancrev, cl, mfl, path, fnode):
170 174 """returns the node for <path> in <ancrev> if content matches <fnode>"""
171 175 ancctx = cl.read(ancrev) # This avoids object creation.
172 176 manifestnode, files = ancctx[0], ancctx[3]
173 177 # If the file was touched in this ancestor, and the content is similar
174 178 # to the one we are searching for.
175 179 if path in files and fnode == mfl[manifestnode].readfast().get(path):
176 180 return cl.node(ancrev)
177 181 return None
178 182
179 183 def _adjustlinknode(self, path, filelog, fnode, srcrev, inclusive=False):
180 184 """return the first ancestor of <srcrev> introducing <fnode>
181 185
182 186 If the linkrev of the file revision does not point to an ancestor of
183 187 srcrev, we'll walk down the ancestors until we find one introducing
184 188 this file revision.
185 189
186 190 :repo: a localrepository object (used to access changelog and manifest)
187 191 :path: the file path
188 192 :fnode: the nodeid of the file revision
189 193 :filelog: the filelog of this path
190 194 :srcrev: the changeset revision we search ancestors from
191 195 :inclusive: if true, the src revision will also be checked
192 196
193 197 Note: This is based on adjustlinkrev in core, but it's quite different.
194 198
195 199 adjustlinkrev depends on the fact that the linkrev is the bottom most
196 200 node, and uses that as a stopping point for the ancestor traversal. We
197 201 can't do that here because the linknode is not guaranteed to be the
198 202 bottom most one.
199 203
200 204 In our code here, we actually know what a bunch of potential ancestor
201 205 linknodes are, so instead of stopping the cheap-ancestor-traversal when
202 206 we get to a linkrev, we stop when we see any of the known linknodes.
203 207 """
204 208 repo = self._repo
205 209 cl = repo.unfiltered().changelog
206 210 mfl = repo.manifestlog
207 211 ancestormap = self.ancestormap()
208 212 linknode = ancestormap[fnode][2]
209 213
210 214 if srcrev is None:
211 215 # wctx case, used by workingfilectx during mergecopy
212 216 revs = [p.rev() for p in self._repo[None].parents()]
213 217 inclusive = True # we skipped the real (revless) source
214 218 else:
215 219 revs = [srcrev]
216 220
217 221 if self._verifylinknode(revs, linknode):
218 222 return linknode
219 223
220 224 commonlogkwargs = {
221 225 r'revs': ' '.join([hex(cl.node(rev)) for rev in revs]),
222 226 r'fnode': hex(fnode),
223 227 r'filepath': path,
224 228 r'user': shallowutil.getusername(repo.ui),
225 229 r'reponame': shallowutil.getreponame(repo.ui),
226 230 }
227 231
228 232 repo.ui.log('linkrevfixup', 'adjusting linknode\n', **commonlogkwargs)
229 233
230 234 pc = repo._phasecache
231 235 seenpublic = False
232 236 iteranc = cl.ancestors(revs, inclusive=inclusive)
233 237 for ancrev in iteranc:
234 238 # First, check locally-available history.
235 239 lnode = self._nodefromancrev(ancrev, cl, mfl, path, fnode)
236 240 if lnode is not None:
237 241 return lnode
238 242
239 243 # adjusting linknode can be super-slow. To mitigate the issue
240 244 # we use two heuristics: calling fastlog and forcing remotefilelog
241 245 # prefetch
242 246 if not seenpublic and pc.phase(repo, ancrev) == phases.public:
243 247 # TODO: there used to be a codepath to fetch linknodes
244 248 # from a server as a fast path, but it appeared to
245 249 # depend on an API FB added to their phabricator.
246 250 lnode = self._forceprefetch(repo, path, fnode, revs,
247 251 commonlogkwargs)
248 252 if lnode:
249 253 return lnode
250 254 seenpublic = True
251 255
252 256 return linknode
253 257
254 258 def _forceprefetch(self, repo, path, fnode, revs,
255 259 commonlogkwargs):
256 260 # This next part is super non-obvious, so big comment block time!
257 261 #
258 262 # It is possible to get extremely bad performance here when a fairly
259 263 # common set of circumstances occur when this extension is combined
260 264 # with a server-side commit rewriting extension like pushrebase.
261 265 #
262 266 # First, an engineer creates Commit A and pushes it to the server.
263 267 # While the server's data structure will have the correct linkrev
264 268 # for the files touched in Commit A, the client will have the
265 269 # linkrev of the local commit, which is "invalid" because it's not
266 270 # an ancestor of the main line of development.
267 271 #
268 272 # The client will never download the remotefilelog with the correct
269 273 # linkrev as long as nobody else touches that file, since the file
270 274 # data and history hasn't changed since Commit A.
271 275 #
272 276 # After a long time (or a short time in a heavily used repo), if the
273 277 # same engineer returns to change the same file, some commands --
274 278 # such as amends of commits with file moves, logs, diffs, etc --
275 279 # can trigger this _adjustlinknode code. In those cases, finding
276 280 # the correct rev can become quite expensive, as the correct
277 281 # revision is far back in history and we need to walk back through
278 282 # history to find it.
279 283 #
280 284 # In order to improve this situation, we force a prefetch of the
281 285 # remotefilelog data blob for the file we were called on. We do this
282 286 # at most once, when we first see a public commit in the history we
283 287 # are traversing.
284 288 #
285 289 # Forcing the prefetch means we will download the remote blob even
286 290 # if we have the "correct" blob in the local store. Since the union
287 291 # store checks the remote store first, this means we are much more
288 292 # likely to get the correct linkrev at this point.
289 293 #
290 294 # In rare circumstances (such as the server having a suboptimal
291 295 # linkrev for our use case), we will fall back to the old slow path.
292 296 #
293 297 # We may want to add additional heuristics here in the future if
294 298 # the slow path is used too much. One promising possibility is using
295 299 # obsolescence markers to find a more-likely-correct linkrev.
296 300
297 301 logmsg = ''
298 302 start = time.time()
299 303 try:
300 304 repo.fileservice.prefetch([(path, hex(fnode))], force=True)
301 305
302 306 # Now that we've downloaded a new blob from the server,
303 307 # we need to rebuild the ancestor map to recompute the
304 308 # linknodes.
305 309 self._ancestormap = None
306 310 linknode = self.ancestormap()[fnode][2] # 2 is linknode
307 311 if self._verifylinknode(revs, linknode):
308 312 logmsg = 'remotefilelog prefetching succeeded'
309 313 return linknode
310 314 logmsg = 'remotefilelog prefetching not found'
311 315 return None
312 316 except Exception as e:
313 317 logmsg = 'remotefilelog prefetching failed (%s)' % e
314 318 return None
315 319 finally:
316 320 elapsed = time.time() - start
317 321 repo.ui.log('linkrevfixup', logmsg + '\n', elapsed=elapsed * 1000,
318 322 **commonlogkwargs)
319 323
320 324 def _verifylinknode(self, revs, linknode):
321 325 """
322 326 Check if a linknode is correct one for the current history.
323 327
324 328 That is, return True if the linkrev is the ancestor of any of the
325 329 passed in revs, otherwise return False.
326 330
327 331 `revs` is a list that usually has one element -- usually the wdir parent
328 332 or the user-passed rev we're looking back from. It may contain two revs
329 333 when there is a merge going on, or zero revs when a root node with no
330 334 parents is being created.
331 335 """
332 336 if not revs:
333 337 return False
334 338 try:
335 339 # Use the C fastpath to check if the given linknode is correct.
336 340 cl = self._repo.unfiltered().changelog
337 341 return any(cl.isancestor(linknode, cl.node(r)) for r in revs)
338 342 except error.LookupError:
339 343 # The linknode read from the blob may have been stripped or
340 344 # otherwise not present in the repository anymore. Do not fail hard
341 345 # in this case. Instead, return false and continue the search for
342 346 # the correct linknode.
343 347 return False
344 348
345 349 def ancestors(self, followfirst=False):
346 350 ancestors = []
347 351 queue = collections.deque((self,))
348 352 seen = set()
349 353 while queue:
350 354 current = queue.pop()
351 355 if current.filenode() in seen:
352 356 continue
353 357 seen.add(current.filenode())
354 358
355 359 ancestors.append(current)
356 360
357 361 parents = current.parents()
358 362 first = True
359 363 for p in parents:
360 364 if first or not followfirst:
361 365 queue.append(p)
362 366 first = False
363 367
364 368 # Remove self
365 369 ancestors.pop(0)
366 370
367 371 # Sort by linkrev
368 372 # The copy tracing algorithm depends on these coming out in order
369 373 ancestors = sorted(ancestors, reverse=True, key=lambda x:x.linkrev())
370 374
371 375 for ancestor in ancestors:
372 376 yield ancestor
373 377
374 378 def ancestor(self, fc2, actx):
375 379 # the easy case: no (relevant) renames
376 380 if fc2.path() == self.path() and self.path() in actx:
377 381 return actx[self.path()]
378 382
379 383 # the next easiest cases: unambiguous predecessor (name trumps
380 384 # history)
381 385 if self.path() in actx and fc2.path() not in actx:
382 386 return actx[self.path()]
383 387 if fc2.path() in actx and self.path() not in actx:
384 388 return actx[fc2.path()]
385 389
386 390 # do a full traversal
387 391 amap = self.ancestormap()
388 392 bmap = fc2.ancestormap()
389 393
390 394 def parents(x):
391 395 f, n = x
392 396 p = amap.get(n) or bmap.get(n)
393 397 if not p:
394 398 return []
395 399
396 400 return [(p[3] or f, p[0]), (f, p[1])]
397 401
398 402 a = (self.path(), self.filenode())
399 403 b = (fc2.path(), fc2.filenode())
400 404 result = ancestor.genericancestor(a, b, parents)
401 405 if result:
402 406 f, n = result
403 407 r = remotefilectx(self._repo, f, fileid=n,
404 408 ancestormap=amap)
405 409 return r
406 410
407 411 return None
408 412
409 413 def annotate(self, *args, **kwargs):
410 414 introctx = self
411 415 prefetchskip = kwargs.pop(r'prefetchskip', None)
412 416 if prefetchskip:
413 417 # use introrev so prefetchskip can be accurately tested
414 418 introrev = self.introrev()
415 419 if self.rev() != introrev:
416 420 introctx = remotefilectx(self._repo, self._path,
417 421 changeid=introrev,
418 422 fileid=self._filenode,
419 423 filelog=self._filelog,
420 424 ancestormap=self._ancestormap)
421 425
422 426 # like self.ancestors, but append to "fetch" and skip visiting parents
423 427 # of nodes in "prefetchskip".
424 428 fetch = []
425 429 seen = set()
426 430 queue = collections.deque((introctx,))
427 431 seen.add(introctx.node())
428 432 while queue:
429 433 current = queue.pop()
430 434 if current.filenode() != self.filenode():
431 435 # this is a "joint point". fastannotate needs contents of
432 436 # "joint point"s to calculate diffs for side branches.
433 437 fetch.append((current.path(), hex(current.filenode())))
434 438 if prefetchskip and current in prefetchskip:
435 439 continue
436 440 for parent in current.parents():
437 441 if parent.node() not in seen:
438 442 seen.add(parent.node())
439 443 queue.append(parent)
440 444
441 445 self._repo.ui.debug('remotefilelog: prefetching %d files '
442 446 'for annotate\n' % len(fetch))
443 447 if fetch:
444 448 self._repo.fileservice.prefetch(fetch)
445 449 return super(remotefilectx, self).annotate(*args, **kwargs)
446 450
447 451 # Return empty set so that the hg serve and thg don't stack trace
448 452 def children(self):
449 453 return []
450 454
451 455 class remoteworkingfilectx(context.workingfilectx, remotefilectx):
452 456 def __init__(self, repo, path, filelog=None, workingctx=None):
453 457 self._ancestormap = None
454 458 super(remoteworkingfilectx, self).__init__(repo, path, filelog,
455 459 workingctx)
456 460
457 461 def parents(self):
458 462 return remotefilectx.parents(self)
459 463
460 464 def ancestormap(self):
461 465 if not self._ancestormap:
462 466 path = self._path
463 467 pcl = self._changectx._parents
464 468 renamed = self.renamed()
465 469
466 470 if renamed:
467 471 p1 = renamed
468 472 else:
469 473 p1 = (path, pcl[0]._manifest.get(path, nullid))
470 474
471 475 p2 = (path, nullid)
472 476 if len(pcl) > 1:
473 477 p2 = (path, pcl[1]._manifest.get(path, nullid))
474 478
475 479 m = {}
476 480 if p1[1] != nullid:
477 481 p1ctx = self._repo.filectx(p1[0], fileid=p1[1])
478 482 m.update(p1ctx.filelog().ancestormap(p1[1]))
479 483
480 484 if p2[1] != nullid:
481 485 p2ctx = self._repo.filectx(p2[0], fileid=p2[1])
482 486 m.update(p2ctx.filelog().ancestormap(p2[1]))
483 487
484 488 copyfrom = ''
485 489 if renamed:
486 490 copyfrom = renamed[0]
487 491 m[None] = (p1[1], p2[1], nullid, copyfrom)
488 492 self._ancestormap = m
489 493
490 494 return self._ancestormap
@@ -1,2551 +1,2558 b''
1 1 # context.py - changeset and file context objects for mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import filecmp
12 12 import os
13 13 import stat
14 14
15 15 from .i18n import _
16 16 from .node import (
17 17 addednodeid,
18 18 hex,
19 19 modifiednodeid,
20 20 nullid,
21 21 nullrev,
22 22 short,
23 23 wdirfilenodeids,
24 24 wdirid,
25 25 )
26 26 from . import (
27 27 dagop,
28 28 encoding,
29 29 error,
30 30 fileset,
31 31 match as matchmod,
32 32 obsolete as obsmod,
33 33 patch,
34 34 pathutil,
35 35 phases,
36 36 pycompat,
37 37 repoview,
38 38 scmutil,
39 39 sparse,
40 40 subrepo,
41 41 subrepoutil,
42 42 util,
43 43 )
44 44 from .utils import (
45 45 dateutil,
46 46 stringutil,
47 47 )
48 48
49 49 propertycache = util.propertycache
50 50
51 51 class basectx(object):
52 52 """A basectx object represents the common logic for its children:
53 53 changectx: read-only context that is already present in the repo,
54 54 workingctx: a context that represents the working directory and can
55 55 be committed,
56 56 memctx: a context that represents changes in-memory and can also
57 57 be committed."""
58 58
59 59 def __init__(self, repo):
60 60 self._repo = repo
61 61
62 62 def __bytes__(self):
63 63 return short(self.node())
64 64
65 65 __str__ = encoding.strmethod(__bytes__)
66 66
67 67 def __repr__(self):
68 68 return r"<%s %s>" % (type(self).__name__, str(self))
69 69
70 70 def __eq__(self, other):
71 71 try:
72 72 return type(self) == type(other) and self._rev == other._rev
73 73 except AttributeError:
74 74 return False
75 75
76 76 def __ne__(self, other):
77 77 return not (self == other)
78 78
79 79 def __contains__(self, key):
80 80 return key in self._manifest
81 81
82 82 def __getitem__(self, key):
83 83 return self.filectx(key)
84 84
85 85 def __iter__(self):
86 86 return iter(self._manifest)
87 87
88 88 def _buildstatusmanifest(self, status):
89 89 """Builds a manifest that includes the given status results, if this is
90 90 a working copy context. For non-working copy contexts, it just returns
91 91 the normal manifest."""
92 92 return self.manifest()
93 93
94 94 def _matchstatus(self, other, match):
95 95 """This internal method provides a way for child objects to override the
96 96 match operator.
97 97 """
98 98 return match
99 99
100 100 def _buildstatus(self, other, s, match, listignored, listclean,
101 101 listunknown):
102 102 """build a status with respect to another context"""
103 103 # Load earliest manifest first for caching reasons. More specifically,
104 104 # if you have revisions 1000 and 1001, 1001 is probably stored as a
105 105 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
106 106 # 1000 and cache it so that when you read 1001, we just need to apply a
107 107 # delta to what's in the cache. So that's one full reconstruction + one
108 108 # delta application.
109 109 mf2 = None
110 110 if self.rev() is not None and self.rev() < other.rev():
111 111 mf2 = self._buildstatusmanifest(s)
112 112 mf1 = other._buildstatusmanifest(s)
113 113 if mf2 is None:
114 114 mf2 = self._buildstatusmanifest(s)
115 115
116 116 modified, added = [], []
117 117 removed = []
118 118 clean = []
119 119 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
120 120 deletedset = set(deleted)
121 121 d = mf1.diff(mf2, match=match, clean=listclean)
122 122 for fn, value in d.iteritems():
123 123 if fn in deletedset:
124 124 continue
125 125 if value is None:
126 126 clean.append(fn)
127 127 continue
128 128 (node1, flag1), (node2, flag2) = value
129 129 if node1 is None:
130 130 added.append(fn)
131 131 elif node2 is None:
132 132 removed.append(fn)
133 133 elif flag1 != flag2:
134 134 modified.append(fn)
135 135 elif node2 not in wdirfilenodeids:
136 136 # When comparing files between two commits, we save time by
137 137 # not comparing the file contents when the nodeids differ.
138 138 # Note that this means we incorrectly report a reverted change
139 139 # to a file as a modification.
140 140 modified.append(fn)
141 141 elif self[fn].cmp(other[fn]):
142 142 modified.append(fn)
143 143 else:
144 144 clean.append(fn)
145 145
146 146 if removed:
147 147 # need to filter files if they are already reported as removed
148 148 unknown = [fn for fn in unknown if fn not in mf1 and
149 149 (not match or match(fn))]
150 150 ignored = [fn for fn in ignored if fn not in mf1 and
151 151 (not match or match(fn))]
152 152 # if they're deleted, don't report them as removed
153 153 removed = [fn for fn in removed if fn not in deletedset]
154 154
155 155 return scmutil.status(modified, added, removed, deleted, unknown,
156 156 ignored, clean)
157 157
158 158 @propertycache
159 159 def substate(self):
160 160 return subrepoutil.state(self, self._repo.ui)
161 161
162 162 def subrev(self, subpath):
163 163 return self.substate[subpath][1]
164 164
165 165 def rev(self):
166 166 return self._rev
167 167 def node(self):
168 168 return self._node
169 169 def hex(self):
170 170 return hex(self.node())
171 171 def manifest(self):
172 172 return self._manifest
173 173 def manifestctx(self):
174 174 return self._manifestctx
175 175 def repo(self):
176 176 return self._repo
177 177 def phasestr(self):
178 178 return phases.phasenames[self.phase()]
179 179 def mutable(self):
180 180 return self.phase() > phases.public
181 181
182 182 def matchfileset(self, expr, badfn=None):
183 183 return fileset.match(self, expr, badfn=badfn)
184 184
185 185 def obsolete(self):
186 186 """True if the changeset is obsolete"""
187 187 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
188 188
189 189 def extinct(self):
190 190 """True if the changeset is extinct"""
191 191 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
192 192
193 193 def orphan(self):
194 194 """True if the changeset is not obsolete, but its ancestor is"""
195 195 return self.rev() in obsmod.getrevs(self._repo, 'orphan')
196 196
197 197 def phasedivergent(self):
198 198 """True if the changeset tries to be a successor of a public changeset
199 199
200 200 Only non-public and non-obsolete changesets may be phase-divergent.
201 201 """
202 202 return self.rev() in obsmod.getrevs(self._repo, 'phasedivergent')
203 203
204 204 def contentdivergent(self):
205 205 """Is a successor of a changeset with multiple possible successor sets
206 206
207 207 Only non-public and non-obsolete changesets may be content-divergent.
208 208 """
209 209 return self.rev() in obsmod.getrevs(self._repo, 'contentdivergent')
210 210
211 211 def isunstable(self):
212 212 """True if the changeset is either orphan, phase-divergent or
213 213 content-divergent"""
214 214 return self.orphan() or self.phasedivergent() or self.contentdivergent()
215 215
216 216 def instabilities(self):
217 217 """return the list of instabilities affecting this changeset.
218 218
219 219 Instabilities are returned as strings. possible values are:
220 220 - orphan,
221 221 - phase-divergent,
222 222 - content-divergent.
223 223 """
224 224 instabilities = []
225 225 if self.orphan():
226 226 instabilities.append('orphan')
227 227 if self.phasedivergent():
228 228 instabilities.append('phase-divergent')
229 229 if self.contentdivergent():
230 230 instabilities.append('content-divergent')
231 231 return instabilities
232 232
233 233 def parents(self):
234 234 """return contexts for each parent changeset"""
235 235 return self._parents
236 236
237 237 def p1(self):
238 238 return self._parents[0]
239 239
240 240 def p2(self):
241 241 parents = self._parents
242 242 if len(parents) == 2:
243 243 return parents[1]
244 244 return self._repo[nullrev]
245 245
246 246 def _fileinfo(self, path):
247 247 if r'_manifest' in self.__dict__:
248 248 try:
249 249 return self._manifest[path], self._manifest.flags(path)
250 250 except KeyError:
251 251 raise error.ManifestLookupError(self._node, path,
252 252 _('not found in manifest'))
253 253 if r'_manifestdelta' in self.__dict__ or path in self.files():
254 254 if path in self._manifestdelta:
255 255 return (self._manifestdelta[path],
256 256 self._manifestdelta.flags(path))
257 257 mfl = self._repo.manifestlog
258 258 try:
259 259 node, flag = mfl[self._changeset.manifest].find(path)
260 260 except KeyError:
261 261 raise error.ManifestLookupError(self._node, path,
262 262 _('not found in manifest'))
263 263
264 264 return node, flag
265 265
266 266 def filenode(self, path):
267 267 return self._fileinfo(path)[0]
268 268
269 269 def flags(self, path):
270 270 try:
271 271 return self._fileinfo(path)[1]
272 272 except error.LookupError:
273 273 return ''
274 274
275 275 def sub(self, path, allowcreate=True):
276 276 '''return a subrepo for the stored revision of path, never wdir()'''
277 277 return subrepo.subrepo(self, path, allowcreate=allowcreate)
278 278
279 279 def nullsub(self, path, pctx):
280 280 return subrepo.nullsubrepo(self, path, pctx)
281 281
282 282 def workingsub(self, path):
283 283 '''return a subrepo for the stored revision, or wdir if this is a wdir
284 284 context.
285 285 '''
286 286 return subrepo.subrepo(self, path, allowwdir=True)
287 287
288 288 def match(self, pats=None, include=None, exclude=None, default='glob',
289 289 listsubrepos=False, badfn=None):
290 290 r = self._repo
291 291 return matchmod.match(r.root, r.getcwd(), pats,
292 292 include, exclude, default,
293 293 auditor=r.nofsauditor, ctx=self,
294 294 listsubrepos=listsubrepos, badfn=badfn)
295 295
296 296 def diff(self, ctx2=None, match=None, changes=None, opts=None,
297 297 losedatafn=None, pathfn=None, copy=None,
298 298 copysourcematch=None, hunksfilterfn=None):
299 299 """Returns a diff generator for the given contexts and matcher"""
300 300 if ctx2 is None:
301 301 ctx2 = self.p1()
302 302 if ctx2 is not None:
303 303 ctx2 = self._repo[ctx2]
304 304 return patch.diff(self._repo, ctx2, self, match=match, changes=changes,
305 305 opts=opts, losedatafn=losedatafn, pathfn=pathfn,
306 306 copy=copy, copysourcematch=copysourcematch,
307 307 hunksfilterfn=hunksfilterfn)
308 308
309 309 def dirs(self):
310 310 return self._manifest.dirs()
311 311
312 312 def hasdir(self, dir):
313 313 return self._manifest.hasdir(dir)
314 314
315 315 def status(self, other=None, match=None, listignored=False,
316 316 listclean=False, listunknown=False, listsubrepos=False):
317 317 """return status of files between two nodes or node and working
318 318 directory.
319 319
320 320 If other is None, compare this node with working directory.
321 321
322 322 returns (modified, added, removed, deleted, unknown, ignored, clean)
323 323 """
324 324
325 325 ctx1 = self
326 326 ctx2 = self._repo[other]
327 327
328 328 # This next code block is, admittedly, fragile logic that tests for
329 329 # reversing the contexts and wouldn't need to exist if it weren't for
330 330 # the fast (and common) code path of comparing the working directory
331 331 # with its first parent.
332 332 #
333 333 # What we're aiming for here is the ability to call:
334 334 #
335 335 # workingctx.status(parentctx)
336 336 #
337 337 # If we always built the manifest for each context and compared those,
338 338 # then we'd be done. But the special case of the above call means we
339 339 # just copy the manifest of the parent.
340 340 reversed = False
341 341 if (not isinstance(ctx1, changectx)
342 342 and isinstance(ctx2, changectx)):
343 343 reversed = True
344 344 ctx1, ctx2 = ctx2, ctx1
345 345
346 346 match = self._repo.narrowmatch(match)
347 347 match = ctx2._matchstatus(ctx1, match)
348 348 r = scmutil.status([], [], [], [], [], [], [])
349 349 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
350 350 listunknown)
351 351
352 352 if reversed:
353 353 # Reverse added and removed. Clear deleted, unknown and ignored as
354 354 # these make no sense to reverse.
355 355 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
356 356 r.clean)
357 357
358 358 if listsubrepos:
359 359 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
360 360 try:
361 361 rev2 = ctx2.subrev(subpath)
362 362 except KeyError:
363 363 # A subrepo that existed in node1 was deleted between
364 364 # node1 and node2 (inclusive). Thus, ctx2's substate
365 365 # won't contain that subpath. The best we can do ignore it.
366 366 rev2 = None
367 367 submatch = matchmod.subdirmatcher(subpath, match)
368 368 s = sub.status(rev2, match=submatch, ignored=listignored,
369 369 clean=listclean, unknown=listunknown,
370 370 listsubrepos=True)
371 371 for rfiles, sfiles in zip(r, s):
372 372 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
373 373
374 374 for l in r:
375 375 l.sort()
376 376
377 377 return r
378 378
379 379 class changectx(basectx):
380 380 """A changecontext object makes access to data related to a particular
381 381 changeset convenient. It represents a read-only context already present in
382 382 the repo."""
383 383 def __init__(self, repo, rev, node):
384 384 super(changectx, self).__init__(repo)
385 385 self._rev = rev
386 386 self._node = node
387 387
388 388 def __hash__(self):
389 389 try:
390 390 return hash(self._rev)
391 391 except AttributeError:
392 392 return id(self)
393 393
394 394 def __nonzero__(self):
395 395 return self._rev != nullrev
396 396
397 397 __bool__ = __nonzero__
398 398
399 399 @propertycache
400 400 def _changeset(self):
401 401 return self._repo.changelog.changelogrevision(self.rev())
402 402
403 403 @propertycache
404 404 def _manifest(self):
405 405 return self._manifestctx.read()
406 406
407 407 @property
408 408 def _manifestctx(self):
409 409 return self._repo.manifestlog[self._changeset.manifest]
410 410
411 411 @propertycache
412 412 def _manifestdelta(self):
413 413 return self._manifestctx.readdelta()
414 414
415 415 @propertycache
416 416 def _parents(self):
417 417 repo = self._repo
418 418 p1, p2 = repo.changelog.parentrevs(self._rev)
419 419 if p2 == nullrev:
420 420 return [repo[p1]]
421 421 return [repo[p1], repo[p2]]
422 422
423 423 def changeset(self):
424 424 c = self._changeset
425 425 return (
426 426 c.manifest,
427 427 c.user,
428 428 c.date,
429 429 c.files,
430 430 c.description,
431 431 c.extra,
432 432 )
433 433 def manifestnode(self):
434 434 return self._changeset.manifest
435 435
436 436 def user(self):
437 437 return self._changeset.user
438 438 def date(self):
439 439 return self._changeset.date
440 440 def files(self):
441 441 return self._changeset.files
442 442 @propertycache
443 443 def _copies(self):
444 444 p1copies = {}
445 445 p2copies = {}
446 446 p1 = self.p1()
447 447 p2 = self.p2()
448 448 narrowmatch = self._repo.narrowmatch()
449 449 for dst in self.files():
450 450 if not narrowmatch(dst) or dst not in self:
451 451 continue
452 452 copied = self[dst].renamed()
453 453 if not copied:
454 454 continue
455 455 src, srcnode = copied
456 456 if src in p1 and p1[src].filenode() == srcnode:
457 457 p1copies[dst] = src
458 458 elif src in p2 and p2[src].filenode() == srcnode:
459 459 p2copies[dst] = src
460 460 return p1copies, p2copies
461 461 def p1copies(self):
462 462 return self._copies[0]
463 463 def p2copies(self):
464 464 return self._copies[1]
465 465 def description(self):
466 466 return self._changeset.description
467 467 def branch(self):
468 468 return encoding.tolocal(self._changeset.extra.get("branch"))
469 469 def closesbranch(self):
470 470 return 'close' in self._changeset.extra
471 471 def extra(self):
472 472 """Return a dict of extra information."""
473 473 return self._changeset.extra
474 474 def tags(self):
475 475 """Return a list of byte tag names"""
476 476 return self._repo.nodetags(self._node)
477 477 def bookmarks(self):
478 478 """Return a list of byte bookmark names."""
479 479 return self._repo.nodebookmarks(self._node)
480 480 def phase(self):
481 481 return self._repo._phasecache.phase(self._repo, self._rev)
482 482 def hidden(self):
483 483 return self._rev in repoview.filterrevs(self._repo, 'visible')
484 484
485 485 def isinmemory(self):
486 486 return False
487 487
488 488 def children(self):
489 489 """return list of changectx contexts for each child changeset.
490 490
491 491 This returns only the immediate child changesets. Use descendants() to
492 492 recursively walk children.
493 493 """
494 494 c = self._repo.changelog.children(self._node)
495 495 return [self._repo[x] for x in c]
496 496
497 497 def ancestors(self):
498 498 for a in self._repo.changelog.ancestors([self._rev]):
499 499 yield self._repo[a]
500 500
501 501 def descendants(self):
502 502 """Recursively yield all children of the changeset.
503 503
504 504 For just the immediate children, use children()
505 505 """
506 506 for d in self._repo.changelog.descendants([self._rev]):
507 507 yield self._repo[d]
508 508
509 509 def filectx(self, path, fileid=None, filelog=None):
510 510 """get a file context from this changeset"""
511 511 if fileid is None:
512 512 fileid = self.filenode(path)
513 513 return filectx(self._repo, path, fileid=fileid,
514 514 changectx=self, filelog=filelog)
515 515
516 516 def ancestor(self, c2, warn=False):
517 517 """return the "best" ancestor context of self and c2
518 518
519 519 If there are multiple candidates, it will show a message and check
520 520 merge.preferancestor configuration before falling back to the
521 521 revlog ancestor."""
522 522 # deal with workingctxs
523 523 n2 = c2._node
524 524 if n2 is None:
525 525 n2 = c2._parents[0]._node
526 526 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
527 527 if not cahs:
528 528 anc = nullid
529 529 elif len(cahs) == 1:
530 530 anc = cahs[0]
531 531 else:
532 532 # experimental config: merge.preferancestor
533 533 for r in self._repo.ui.configlist('merge', 'preferancestor'):
534 534 try:
535 535 ctx = scmutil.revsymbol(self._repo, r)
536 536 except error.RepoLookupError:
537 537 continue
538 538 anc = ctx.node()
539 539 if anc in cahs:
540 540 break
541 541 else:
542 542 anc = self._repo.changelog.ancestor(self._node, n2)
543 543 if warn:
544 544 self._repo.ui.status(
545 545 (_("note: using %s as ancestor of %s and %s\n") %
546 546 (short(anc), short(self._node), short(n2))) +
547 547 ''.join(_(" alternatively, use --config "
548 548 "merge.preferancestor=%s\n") %
549 549 short(n) for n in sorted(cahs) if n != anc))
550 550 return self._repo[anc]
551 551
552 552 def isancestorof(self, other):
553 553 """True if this changeset is an ancestor of other"""
554 554 return self._repo.changelog.isancestorrev(self._rev, other._rev)
555 555
556 556 def walk(self, match):
557 557 '''Generates matching file names.'''
558 558
559 559 # Wrap match.bad method to have message with nodeid
560 560 def bad(fn, msg):
561 561 # The manifest doesn't know about subrepos, so don't complain about
562 562 # paths into valid subrepos.
563 563 if any(fn == s or fn.startswith(s + '/')
564 564 for s in self.substate):
565 565 return
566 566 match.bad(fn, _('no such file in rev %s') % self)
567 567
568 568 m = matchmod.badmatch(self._repo.narrowmatch(match), bad)
569 569 return self._manifest.walk(m)
570 570
571 571 def matches(self, match):
572 572 return self.walk(match)
573 573
574 574 class basefilectx(object):
575 575 """A filecontext object represents the common logic for its children:
576 576 filectx: read-only access to a filerevision that is already present
577 577 in the repo,
578 578 workingfilectx: a filecontext that represents files from the working
579 579 directory,
580 580 memfilectx: a filecontext that represents files in-memory,
581 581 """
582 582 @propertycache
583 583 def _filelog(self):
584 584 return self._repo.file(self._path)
585 585
586 586 @propertycache
587 587 def _changeid(self):
588 588 if r'_changectx' in self.__dict__:
589 589 return self._changectx.rev()
590 590 elif r'_descendantrev' in self.__dict__:
591 591 # this file context was created from a revision with a known
592 592 # descendant, we can (lazily) correct for linkrev aliases
593 593 return self._adjustlinkrev(self._descendantrev)
594 594 else:
595 595 return self._filelog.linkrev(self._filerev)
596 596
597 597 @propertycache
598 598 def _filenode(self):
599 599 if r'_fileid' in self.__dict__:
600 600 return self._filelog.lookup(self._fileid)
601 601 else:
602 602 return self._changectx.filenode(self._path)
603 603
604 604 @propertycache
605 605 def _filerev(self):
606 606 return self._filelog.rev(self._filenode)
607 607
608 608 @propertycache
609 609 def _repopath(self):
610 610 return self._path
611 611
612 612 def __nonzero__(self):
613 613 try:
614 614 self._filenode
615 615 return True
616 616 except error.LookupError:
617 617 # file is missing
618 618 return False
619 619
620 620 __bool__ = __nonzero__
621 621
622 622 def __bytes__(self):
623 623 try:
624 624 return "%s@%s" % (self.path(), self._changectx)
625 625 except error.LookupError:
626 626 return "%s@???" % self.path()
627 627
628 628 __str__ = encoding.strmethod(__bytes__)
629 629
630 630 def __repr__(self):
631 631 return r"<%s %s>" % (type(self).__name__, str(self))
632 632
633 633 def __hash__(self):
634 634 try:
635 635 return hash((self._path, self._filenode))
636 636 except AttributeError:
637 637 return id(self)
638 638
639 639 def __eq__(self, other):
640 640 try:
641 641 return (type(self) == type(other) and self._path == other._path
642 642 and self._filenode == other._filenode)
643 643 except AttributeError:
644 644 return False
645 645
646 646 def __ne__(self, other):
647 647 return not (self == other)
648 648
649 649 def filerev(self):
650 650 return self._filerev
651 651 def filenode(self):
652 652 return self._filenode
653 653 @propertycache
654 654 def _flags(self):
655 655 return self._changectx.flags(self._path)
656 656 def flags(self):
657 657 return self._flags
658 658 def filelog(self):
659 659 return self._filelog
660 660 def rev(self):
661 661 return self._changeid
662 662 def linkrev(self):
663 663 return self._filelog.linkrev(self._filerev)
664 664 def node(self):
665 665 return self._changectx.node()
666 666 def hex(self):
667 667 return self._changectx.hex()
668 668 def user(self):
669 669 return self._changectx.user()
670 670 def date(self):
671 671 return self._changectx.date()
672 672 def files(self):
673 673 return self._changectx.files()
674 674 def description(self):
675 675 return self._changectx.description()
676 676 def branch(self):
677 677 return self._changectx.branch()
678 678 def extra(self):
679 679 return self._changectx.extra()
680 680 def phase(self):
681 681 return self._changectx.phase()
682 682 def phasestr(self):
683 683 return self._changectx.phasestr()
684 684 def obsolete(self):
685 685 return self._changectx.obsolete()
686 686 def instabilities(self):
687 687 return self._changectx.instabilities()
688 688 def manifest(self):
689 689 return self._changectx.manifest()
690 690 def changectx(self):
691 691 return self._changectx
692 692 def renamed(self):
693 693 return self._copied
694 def copysource(self):
695 return self._copied and self._copied[0]
694 696 def repo(self):
695 697 return self._repo
696 698 def size(self):
697 699 return len(self.data())
698 700
699 701 def path(self):
700 702 return self._path
701 703
702 704 def isbinary(self):
703 705 try:
704 706 return stringutil.binary(self.data())
705 707 except IOError:
706 708 return False
707 709 def isexec(self):
708 710 return 'x' in self.flags()
709 711 def islink(self):
710 712 return 'l' in self.flags()
711 713
712 714 def isabsent(self):
713 715 """whether this filectx represents a file not in self._changectx
714 716
715 717 This is mainly for merge code to detect change/delete conflicts. This is
716 718 expected to be True for all subclasses of basectx."""
717 719 return False
718 720
719 721 _customcmp = False
720 722 def cmp(self, fctx):
721 723 """compare with other file context
722 724
723 725 returns True if different than fctx.
724 726 """
725 727 if fctx._customcmp:
726 728 return fctx.cmp(self)
727 729
728 730 if self._filenode is None:
729 731 raise error.ProgrammingError(
730 732 'filectx.cmp() must be reimplemented if not backed by revlog')
731 733
732 734 if fctx._filenode is None:
733 735 if self._repo._encodefilterpats:
734 736 # can't rely on size() because wdir content may be decoded
735 737 return self._filelog.cmp(self._filenode, fctx.data())
736 738 if self.size() - 4 == fctx.size():
737 739 # size() can match:
738 740 # if file data starts with '\1\n', empty metadata block is
739 741 # prepended, which adds 4 bytes to filelog.size().
740 742 return self._filelog.cmp(self._filenode, fctx.data())
741 743 if self.size() == fctx.size():
742 744 # size() matches: need to compare content
743 745 return self._filelog.cmp(self._filenode, fctx.data())
744 746
745 747 # size() differs
746 748 return True
747 749
748 750 def _adjustlinkrev(self, srcrev, inclusive=False, stoprev=None):
749 751 """return the first ancestor of <srcrev> introducing <fnode>
750 752
751 753 If the linkrev of the file revision does not point to an ancestor of
752 754 srcrev, we'll walk down the ancestors until we find one introducing
753 755 this file revision.
754 756
755 757 :srcrev: the changeset revision we search ancestors from
756 758 :inclusive: if true, the src revision will also be checked
757 759 :stoprev: an optional revision to stop the walk at. If no introduction
758 760 of this file content could be found before this floor
759 761 revision, the function will returns "None" and stops its
760 762 iteration.
761 763 """
762 764 repo = self._repo
763 765 cl = repo.unfiltered().changelog
764 766 mfl = repo.manifestlog
765 767 # fetch the linkrev
766 768 lkr = self.linkrev()
767 769 if srcrev == lkr:
768 770 return lkr
769 771 # hack to reuse ancestor computation when searching for renames
770 772 memberanc = getattr(self, '_ancestrycontext', None)
771 773 iteranc = None
772 774 if srcrev is None:
773 775 # wctx case, used by workingfilectx during mergecopy
774 776 revs = [p.rev() for p in self._repo[None].parents()]
775 777 inclusive = True # we skipped the real (revless) source
776 778 else:
777 779 revs = [srcrev]
778 780 if memberanc is None:
779 781 memberanc = iteranc = cl.ancestors(revs, lkr,
780 782 inclusive=inclusive)
781 783 # check if this linkrev is an ancestor of srcrev
782 784 if lkr not in memberanc:
783 785 if iteranc is None:
784 786 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
785 787 fnode = self._filenode
786 788 path = self._path
787 789 for a in iteranc:
788 790 if stoprev is not None and a < stoprev:
789 791 return None
790 792 ac = cl.read(a) # get changeset data (we avoid object creation)
791 793 if path in ac[3]: # checking the 'files' field.
792 794 # The file has been touched, check if the content is
793 795 # similar to the one we search for.
794 796 if fnode == mfl[ac[0]].readfast().get(path):
795 797 return a
796 798 # In theory, we should never get out of that loop without a result.
797 799 # But if manifest uses a buggy file revision (not children of the
798 800 # one it replaces) we could. Such a buggy situation will likely
799 801 # result is crash somewhere else at to some point.
800 802 return lkr
801 803
802 804 def isintroducedafter(self, changelogrev):
803 805 """True if a filectx has been introduced after a given floor revision
804 806 """
805 807 if self.linkrev() >= changelogrev:
806 808 return True
807 809 introrev = self._introrev(stoprev=changelogrev)
808 810 if introrev is None:
809 811 return False
810 812 return introrev >= changelogrev
811 813
812 814 def introrev(self):
813 815 """return the rev of the changeset which introduced this file revision
814 816
815 817 This method is different from linkrev because it take into account the
816 818 changeset the filectx was created from. It ensures the returned
817 819 revision is one of its ancestors. This prevents bugs from
818 820 'linkrev-shadowing' when a file revision is used by multiple
819 821 changesets.
820 822 """
821 823 return self._introrev()
822 824
823 825 def _introrev(self, stoprev=None):
824 826 """
825 827 Same as `introrev` but, with an extra argument to limit changelog
826 828 iteration range in some internal usecase.
827 829
828 830 If `stoprev` is set, the `introrev` will not be searched past that
829 831 `stoprev` revision and "None" might be returned. This is useful to
830 832 limit the iteration range.
831 833 """
832 834 toprev = None
833 835 attrs = vars(self)
834 836 if r'_changeid' in attrs:
835 837 # We have a cached value already
836 838 toprev = self._changeid
837 839 elif r'_changectx' in attrs:
838 840 # We know which changelog entry we are coming from
839 841 toprev = self._changectx.rev()
840 842
841 843 if toprev is not None:
842 844 return self._adjustlinkrev(toprev, inclusive=True, stoprev=stoprev)
843 845 elif r'_descendantrev' in attrs:
844 846 introrev = self._adjustlinkrev(self._descendantrev, stoprev=stoprev)
845 847 # be nice and cache the result of the computation
846 848 if introrev is not None:
847 849 self._changeid = introrev
848 850 return introrev
849 851 else:
850 852 return self.linkrev()
851 853
852 854 def introfilectx(self):
853 855 """Return filectx having identical contents, but pointing to the
854 856 changeset revision where this filectx was introduced"""
855 857 introrev = self.introrev()
856 858 if self.rev() == introrev:
857 859 return self
858 860 return self.filectx(self.filenode(), changeid=introrev)
859 861
860 862 def _parentfilectx(self, path, fileid, filelog):
861 863 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
862 864 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
863 865 if r'_changeid' in vars(self) or r'_changectx' in vars(self):
864 866 # If self is associated with a changeset (probably explicitly
865 867 # fed), ensure the created filectx is associated with a
866 868 # changeset that is an ancestor of self.changectx.
867 869 # This lets us later use _adjustlinkrev to get a correct link.
868 870 fctx._descendantrev = self.rev()
869 871 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
870 872 elif r'_descendantrev' in vars(self):
871 873 # Otherwise propagate _descendantrev if we have one associated.
872 874 fctx._descendantrev = self._descendantrev
873 875 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
874 876 return fctx
875 877
876 878 def parents(self):
877 879 _path = self._path
878 880 fl = self._filelog
879 881 parents = self._filelog.parents(self._filenode)
880 882 pl = [(_path, node, fl) for node in parents if node != nullid]
881 883
882 884 r = fl.renamed(self._filenode)
883 885 if r:
884 886 # - In the simple rename case, both parent are nullid, pl is empty.
885 887 # - In case of merge, only one of the parent is null id and should
886 888 # be replaced with the rename information. This parent is -always-
887 889 # the first one.
888 890 #
889 891 # As null id have always been filtered out in the previous list
890 892 # comprehension, inserting to 0 will always result in "replacing
891 893 # first nullid parent with rename information.
892 894 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
893 895
894 896 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
895 897
896 898 def p1(self):
897 899 return self.parents()[0]
898 900
899 901 def p2(self):
900 902 p = self.parents()
901 903 if len(p) == 2:
902 904 return p[1]
903 905 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
904 906
905 907 def annotate(self, follow=False, skiprevs=None, diffopts=None):
906 908 """Returns a list of annotateline objects for each line in the file
907 909
908 910 - line.fctx is the filectx of the node where that line was last changed
909 911 - line.lineno is the line number at the first appearance in the managed
910 912 file
911 913 - line.text is the data on that line (including newline character)
912 914 """
913 915 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
914 916
915 917 def parents(f):
916 918 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
917 919 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
918 920 # from the topmost introrev (= srcrev) down to p.linkrev() if it
919 921 # isn't an ancestor of the srcrev.
920 922 f._changeid
921 923 pl = f.parents()
922 924
923 925 # Don't return renamed parents if we aren't following.
924 926 if not follow:
925 927 pl = [p for p in pl if p.path() == f.path()]
926 928
927 929 # renamed filectx won't have a filelog yet, so set it
928 930 # from the cache to save time
929 931 for p in pl:
930 932 if not r'_filelog' in p.__dict__:
931 933 p._filelog = getlog(p.path())
932 934
933 935 return pl
934 936
935 937 # use linkrev to find the first changeset where self appeared
936 938 base = self.introfilectx()
937 939 if getattr(base, '_ancestrycontext', None) is None:
938 940 cl = self._repo.changelog
939 941 if base.rev() is None:
940 942 # wctx is not inclusive, but works because _ancestrycontext
941 943 # is used to test filelog revisions
942 944 ac = cl.ancestors([p.rev() for p in base.parents()],
943 945 inclusive=True)
944 946 else:
945 947 ac = cl.ancestors([base.rev()], inclusive=True)
946 948 base._ancestrycontext = ac
947 949
948 950 return dagop.annotate(base, parents, skiprevs=skiprevs,
949 951 diffopts=diffopts)
950 952
951 953 def ancestors(self, followfirst=False):
952 954 visit = {}
953 955 c = self
954 956 if followfirst:
955 957 cut = 1
956 958 else:
957 959 cut = None
958 960
959 961 while True:
960 962 for parent in c.parents()[:cut]:
961 963 visit[(parent.linkrev(), parent.filenode())] = parent
962 964 if not visit:
963 965 break
964 966 c = visit.pop(max(visit))
965 967 yield c
966 968
967 969 def decodeddata(self):
968 970 """Returns `data()` after running repository decoding filters.
969 971
970 972 This is often equivalent to how the data would be expressed on disk.
971 973 """
972 974 return self._repo.wwritedata(self.path(), self.data())
973 975
974 976 class filectx(basefilectx):
975 977 """A filecontext object makes access to data related to a particular
976 978 filerevision convenient."""
977 979 def __init__(self, repo, path, changeid=None, fileid=None,
978 980 filelog=None, changectx=None):
979 981 """changeid must be a revision number, if specified.
980 982 fileid can be a file revision or node."""
981 983 self._repo = repo
982 984 self._path = path
983 985
984 986 assert (changeid is not None
985 987 or fileid is not None
986 988 or changectx is not None), (
987 989 "bad args: changeid=%r, fileid=%r, changectx=%r"
988 990 % (changeid, fileid, changectx))
989 991
990 992 if filelog is not None:
991 993 self._filelog = filelog
992 994
993 995 if changeid is not None:
994 996 self._changeid = changeid
995 997 if changectx is not None:
996 998 self._changectx = changectx
997 999 if fileid is not None:
998 1000 self._fileid = fileid
999 1001
1000 1002 @propertycache
1001 1003 def _changectx(self):
1002 1004 try:
1003 1005 return self._repo[self._changeid]
1004 1006 except error.FilteredRepoLookupError:
1005 1007 # Linkrev may point to any revision in the repository. When the
1006 1008 # repository is filtered this may lead to `filectx` trying to build
1007 1009 # `changectx` for filtered revision. In such case we fallback to
1008 1010 # creating `changectx` on the unfiltered version of the reposition.
1009 1011 # This fallback should not be an issue because `changectx` from
1010 1012 # `filectx` are not used in complex operations that care about
1011 1013 # filtering.
1012 1014 #
1013 1015 # This fallback is a cheap and dirty fix that prevent several
1014 1016 # crashes. It does not ensure the behavior is correct. However the
1015 1017 # behavior was not correct before filtering either and "incorrect
1016 1018 # behavior" is seen as better as "crash"
1017 1019 #
1018 1020 # Linkrevs have several serious troubles with filtering that are
1019 1021 # complicated to solve. Proper handling of the issue here should be
1020 1022 # considered when solving linkrev issue are on the table.
1021 1023 return self._repo.unfiltered()[self._changeid]
1022 1024
1023 1025 def filectx(self, fileid, changeid=None):
1024 1026 '''opens an arbitrary revision of the file without
1025 1027 opening a new filelog'''
1026 1028 return filectx(self._repo, self._path, fileid=fileid,
1027 1029 filelog=self._filelog, changeid=changeid)
1028 1030
1029 1031 def rawdata(self):
1030 1032 return self._filelog.revision(self._filenode, raw=True)
1031 1033
1032 1034 def rawflags(self):
1033 1035 """low-level revlog flags"""
1034 1036 return self._filelog.flags(self._filerev)
1035 1037
1036 1038 def data(self):
1037 1039 try:
1038 1040 return self._filelog.read(self._filenode)
1039 1041 except error.CensoredNodeError:
1040 1042 if self._repo.ui.config("censor", "policy") == "ignore":
1041 1043 return ""
1042 1044 raise error.Abort(_("censored node: %s") % short(self._filenode),
1043 1045 hint=_("set censor.policy to ignore errors"))
1044 1046
1045 1047 def size(self):
1046 1048 return self._filelog.size(self._filerev)
1047 1049
1048 1050 @propertycache
1049 1051 def _copied(self):
1050 1052 """check if file was actually renamed in this changeset revision
1051 1053
1052 1054 If rename logged in file revision, we report copy for changeset only
1053 1055 if file revisions linkrev points back to the changeset in question
1054 1056 or both changeset parents contain different file revisions.
1055 1057 """
1056 1058
1057 1059 renamed = self._filelog.renamed(self._filenode)
1058 1060 if not renamed:
1059 1061 return None
1060 1062
1061 1063 if self.rev() == self.linkrev():
1062 1064 return renamed
1063 1065
1064 1066 name = self.path()
1065 1067 fnode = self._filenode
1066 1068 for p in self._changectx.parents():
1067 1069 try:
1068 1070 if fnode == p.filenode(name):
1069 1071 return None
1070 1072 except error.LookupError:
1071 1073 pass
1072 1074 return renamed
1073 1075
1074 1076 def children(self):
1075 1077 # hard for renames
1076 1078 c = self._filelog.children(self._filenode)
1077 1079 return [filectx(self._repo, self._path, fileid=x,
1078 1080 filelog=self._filelog) for x in c]
1079 1081
1080 1082 class committablectx(basectx):
1081 1083 """A committablectx object provides common functionality for a context that
1082 1084 wants the ability to commit, e.g. workingctx or memctx."""
1083 1085 def __init__(self, repo, text="", user=None, date=None, extra=None,
1084 1086 changes=None):
1085 1087 super(committablectx, self).__init__(repo)
1086 1088 self._rev = None
1087 1089 self._node = None
1088 1090 self._text = text
1089 1091 if date:
1090 1092 self._date = dateutil.parsedate(date)
1091 1093 if user:
1092 1094 self._user = user
1093 1095 if changes:
1094 1096 self._status = changes
1095 1097
1096 1098 self._extra = {}
1097 1099 if extra:
1098 1100 self._extra = extra.copy()
1099 1101 if 'branch' not in self._extra:
1100 1102 try:
1101 1103 branch = encoding.fromlocal(self._repo.dirstate.branch())
1102 1104 except UnicodeDecodeError:
1103 1105 raise error.Abort(_('branch name not in UTF-8!'))
1104 1106 self._extra['branch'] = branch
1105 1107 if self._extra['branch'] == '':
1106 1108 self._extra['branch'] = 'default'
1107 1109
1108 1110 def __bytes__(self):
1109 1111 return bytes(self._parents[0]) + "+"
1110 1112
1111 1113 __str__ = encoding.strmethod(__bytes__)
1112 1114
1113 1115 def __nonzero__(self):
1114 1116 return True
1115 1117
1116 1118 __bool__ = __nonzero__
1117 1119
1118 1120 def _buildflagfunc(self):
1119 1121 # Create a fallback function for getting file flags when the
1120 1122 # filesystem doesn't support them
1121 1123
1122 1124 copiesget = self._repo.dirstate.copies().get
1123 1125 parents = self.parents()
1124 1126 if len(parents) < 2:
1125 1127 # when we have one parent, it's easy: copy from parent
1126 1128 man = parents[0].manifest()
1127 1129 def func(f):
1128 1130 f = copiesget(f, f)
1129 1131 return man.flags(f)
1130 1132 else:
1131 1133 # merges are tricky: we try to reconstruct the unstored
1132 1134 # result from the merge (issue1802)
1133 1135 p1, p2 = parents
1134 1136 pa = p1.ancestor(p2)
1135 1137 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1136 1138
1137 1139 def func(f):
1138 1140 f = copiesget(f, f) # may be wrong for merges with copies
1139 1141 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1140 1142 if fl1 == fl2:
1141 1143 return fl1
1142 1144 if fl1 == fla:
1143 1145 return fl2
1144 1146 if fl2 == fla:
1145 1147 return fl1
1146 1148 return '' # punt for conflicts
1147 1149
1148 1150 return func
1149 1151
1150 1152 @propertycache
1151 1153 def _flagfunc(self):
1152 1154 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1153 1155
1154 1156 @propertycache
1155 1157 def _status(self):
1156 1158 return self._repo.status()
1157 1159
1158 1160 @propertycache
1159 1161 def _user(self):
1160 1162 return self._repo.ui.username()
1161 1163
1162 1164 @propertycache
1163 1165 def _date(self):
1164 1166 ui = self._repo.ui
1165 1167 date = ui.configdate('devel', 'default-date')
1166 1168 if date is None:
1167 1169 date = dateutil.makedate()
1168 1170 return date
1169 1171
1170 1172 def subrev(self, subpath):
1171 1173 return None
1172 1174
1173 1175 def manifestnode(self):
1174 1176 return None
1175 1177 def user(self):
1176 1178 return self._user or self._repo.ui.username()
1177 1179 def date(self):
1178 1180 return self._date
1179 1181 def description(self):
1180 1182 return self._text
1181 1183 def files(self):
1182 1184 return sorted(self._status.modified + self._status.added +
1183 1185 self._status.removed)
1184 1186 @propertycache
1185 1187 def _copies(self):
1186 1188 p1copies = {}
1187 1189 p2copies = {}
1188 1190 parents = self._repo.dirstate.parents()
1189 1191 p1manifest = self._repo[parents[0]].manifest()
1190 1192 p2manifest = self._repo[parents[1]].manifest()
1191 1193 narrowmatch = self._repo.narrowmatch()
1192 1194 for dst, src in self._repo.dirstate.copies().items():
1193 1195 if not narrowmatch(dst):
1194 1196 continue
1195 1197 if src in p1manifest:
1196 1198 p1copies[dst] = src
1197 1199 elif src in p2manifest:
1198 1200 p2copies[dst] = src
1199 1201 return p1copies, p2copies
1200 1202 def p1copies(self):
1201 1203 return self._copies[0]
1202 1204 def p2copies(self):
1203 1205 return self._copies[1]
1204 1206 def modified(self):
1205 1207 return self._status.modified
1206 1208 def added(self):
1207 1209 return self._status.added
1208 1210 def removed(self):
1209 1211 return self._status.removed
1210 1212 def deleted(self):
1211 1213 return self._status.deleted
1212 1214 def branch(self):
1213 1215 return encoding.tolocal(self._extra['branch'])
1214 1216 def closesbranch(self):
1215 1217 return 'close' in self._extra
1216 1218 def extra(self):
1217 1219 return self._extra
1218 1220
1219 1221 def isinmemory(self):
1220 1222 return False
1221 1223
1222 1224 def tags(self):
1223 1225 return []
1224 1226
1225 1227 def bookmarks(self):
1226 1228 b = []
1227 1229 for p in self.parents():
1228 1230 b.extend(p.bookmarks())
1229 1231 return b
1230 1232
1231 1233 def phase(self):
1232 1234 phase = phases.draft # default phase to draft
1233 1235 for p in self.parents():
1234 1236 phase = max(phase, p.phase())
1235 1237 return phase
1236 1238
1237 1239 def hidden(self):
1238 1240 return False
1239 1241
1240 1242 def children(self):
1241 1243 return []
1242 1244
1243 1245 def flags(self, path):
1244 1246 if r'_manifest' in self.__dict__:
1245 1247 try:
1246 1248 return self._manifest.flags(path)
1247 1249 except KeyError:
1248 1250 return ''
1249 1251
1250 1252 try:
1251 1253 return self._flagfunc(path)
1252 1254 except OSError:
1253 1255 return ''
1254 1256
1255 1257 def ancestor(self, c2):
1256 1258 """return the "best" ancestor context of self and c2"""
1257 1259 return self._parents[0].ancestor(c2) # punt on two parents for now
1258 1260
1259 1261 def walk(self, match):
1260 1262 '''Generates matching file names.'''
1261 1263 return sorted(self._repo.dirstate.walk(self._repo.narrowmatch(match),
1262 1264 subrepos=sorted(self.substate),
1263 1265 unknown=True, ignored=False))
1264 1266
1265 1267 def matches(self, match):
1266 1268 match = self._repo.narrowmatch(match)
1267 1269 ds = self._repo.dirstate
1268 1270 return sorted(f for f in ds.matches(match) if ds[f] != 'r')
1269 1271
1270 1272 def ancestors(self):
1271 1273 for p in self._parents:
1272 1274 yield p
1273 1275 for a in self._repo.changelog.ancestors(
1274 1276 [p.rev() for p in self._parents]):
1275 1277 yield self._repo[a]
1276 1278
1277 1279 def markcommitted(self, node):
1278 1280 """Perform post-commit cleanup necessary after committing this ctx
1279 1281
1280 1282 Specifically, this updates backing stores this working context
1281 1283 wraps to reflect the fact that the changes reflected by this
1282 1284 workingctx have been committed. For example, it marks
1283 1285 modified and added files as normal in the dirstate.
1284 1286
1285 1287 """
1286 1288
1287 1289 with self._repo.dirstate.parentchange():
1288 1290 for f in self.modified() + self.added():
1289 1291 self._repo.dirstate.normal(f)
1290 1292 for f in self.removed():
1291 1293 self._repo.dirstate.drop(f)
1292 1294 self._repo.dirstate.setparents(node)
1293 1295
1294 1296 # write changes out explicitly, because nesting wlock at
1295 1297 # runtime may prevent 'wlock.release()' in 'repo.commit()'
1296 1298 # from immediately doing so for subsequent changing files
1297 1299 self._repo.dirstate.write(self._repo.currenttransaction())
1298 1300
1299 1301 def dirty(self, missing=False, merge=True, branch=True):
1300 1302 return False
1301 1303
1302 1304 class workingctx(committablectx):
1303 1305 """A workingctx object makes access to data related to
1304 1306 the current working directory convenient.
1305 1307 date - any valid date string or (unixtime, offset), or None.
1306 1308 user - username string, or None.
1307 1309 extra - a dictionary of extra values, or None.
1308 1310 changes - a list of file lists as returned by localrepo.status()
1309 1311 or None to use the repository status.
1310 1312 """
1311 1313 def __init__(self, repo, text="", user=None, date=None, extra=None,
1312 1314 changes=None):
1313 1315 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1314 1316
1315 1317 def __iter__(self):
1316 1318 d = self._repo.dirstate
1317 1319 for f in d:
1318 1320 if d[f] != 'r':
1319 1321 yield f
1320 1322
1321 1323 def __contains__(self, key):
1322 1324 return self._repo.dirstate[key] not in "?r"
1323 1325
1324 1326 def hex(self):
1325 1327 return hex(wdirid)
1326 1328
1327 1329 @propertycache
1328 1330 def _parents(self):
1329 1331 p = self._repo.dirstate.parents()
1330 1332 if p[1] == nullid:
1331 1333 p = p[:-1]
1332 1334 # use unfiltered repo to delay/avoid loading obsmarkers
1333 1335 unfi = self._repo.unfiltered()
1334 1336 return [changectx(self._repo, unfi.changelog.rev(n), n) for n in p]
1335 1337
1336 1338 def _fileinfo(self, path):
1337 1339 # populate __dict__['_manifest'] as workingctx has no _manifestdelta
1338 1340 self._manifest
1339 1341 return super(workingctx, self)._fileinfo(path)
1340 1342
1341 1343 def filectx(self, path, filelog=None):
1342 1344 """get a file context from the working directory"""
1343 1345 return workingfilectx(self._repo, path, workingctx=self,
1344 1346 filelog=filelog)
1345 1347
1346 1348 def dirty(self, missing=False, merge=True, branch=True):
1347 1349 "check whether a working directory is modified"
1348 1350 # check subrepos first
1349 1351 for s in sorted(self.substate):
1350 1352 if self.sub(s).dirty(missing=missing):
1351 1353 return True
1352 1354 # check current working dir
1353 1355 return ((merge and self.p2()) or
1354 1356 (branch and self.branch() != self.p1().branch()) or
1355 1357 self.modified() or self.added() or self.removed() or
1356 1358 (missing and self.deleted()))
1357 1359
1358 1360 def add(self, list, prefix=""):
1359 1361 with self._repo.wlock():
1360 1362 ui, ds = self._repo.ui, self._repo.dirstate
1361 1363 uipath = lambda f: ds.pathto(pathutil.join(prefix, f))
1362 1364 rejected = []
1363 1365 lstat = self._repo.wvfs.lstat
1364 1366 for f in list:
1365 1367 # ds.pathto() returns an absolute file when this is invoked from
1366 1368 # the keyword extension. That gets flagged as non-portable on
1367 1369 # Windows, since it contains the drive letter and colon.
1368 1370 scmutil.checkportable(ui, os.path.join(prefix, f))
1369 1371 try:
1370 1372 st = lstat(f)
1371 1373 except OSError:
1372 1374 ui.warn(_("%s does not exist!\n") % uipath(f))
1373 1375 rejected.append(f)
1374 1376 continue
1375 1377 limit = ui.configbytes('ui', 'large-file-limit')
1376 1378 if limit != 0 and st.st_size > limit:
1377 1379 ui.warn(_("%s: up to %d MB of RAM may be required "
1378 1380 "to manage this file\n"
1379 1381 "(use 'hg revert %s' to cancel the "
1380 1382 "pending addition)\n")
1381 1383 % (f, 3 * st.st_size // 1000000, uipath(f)))
1382 1384 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1383 1385 ui.warn(_("%s not added: only files and symlinks "
1384 1386 "supported currently\n") % uipath(f))
1385 1387 rejected.append(f)
1386 1388 elif ds[f] in 'amn':
1387 1389 ui.warn(_("%s already tracked!\n") % uipath(f))
1388 1390 elif ds[f] == 'r':
1389 1391 ds.normallookup(f)
1390 1392 else:
1391 1393 ds.add(f)
1392 1394 return rejected
1393 1395
1394 1396 def forget(self, files, prefix=""):
1395 1397 with self._repo.wlock():
1396 1398 ds = self._repo.dirstate
1397 1399 uipath = lambda f: ds.pathto(pathutil.join(prefix, f))
1398 1400 rejected = []
1399 1401 for f in files:
1400 1402 if f not in ds:
1401 1403 self._repo.ui.warn(_("%s not tracked!\n") % uipath(f))
1402 1404 rejected.append(f)
1403 1405 elif ds[f] != 'a':
1404 1406 ds.remove(f)
1405 1407 else:
1406 1408 ds.drop(f)
1407 1409 return rejected
1408 1410
1409 1411 def copy(self, source, dest):
1410 1412 try:
1411 1413 st = self._repo.wvfs.lstat(dest)
1412 1414 except OSError as err:
1413 1415 if err.errno != errno.ENOENT:
1414 1416 raise
1415 1417 self._repo.ui.warn(_("%s does not exist!\n")
1416 1418 % self._repo.dirstate.pathto(dest))
1417 1419 return
1418 1420 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1419 1421 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1420 1422 "symbolic link\n")
1421 1423 % self._repo.dirstate.pathto(dest))
1422 1424 else:
1423 1425 with self._repo.wlock():
1424 1426 ds = self._repo.dirstate
1425 1427 if ds[dest] in '?':
1426 1428 ds.add(dest)
1427 1429 elif ds[dest] in 'r':
1428 1430 ds.normallookup(dest)
1429 1431 ds.copy(source, dest)
1430 1432
1431 1433 def match(self, pats=None, include=None, exclude=None, default='glob',
1432 1434 listsubrepos=False, badfn=None):
1433 1435 r = self._repo
1434 1436
1435 1437 # Only a case insensitive filesystem needs magic to translate user input
1436 1438 # to actual case in the filesystem.
1437 1439 icasefs = not util.fscasesensitive(r.root)
1438 1440 return matchmod.match(r.root, r.getcwd(), pats, include, exclude,
1439 1441 default, auditor=r.auditor, ctx=self,
1440 1442 listsubrepos=listsubrepos, badfn=badfn,
1441 1443 icasefs=icasefs)
1442 1444
1443 1445 def _filtersuspectsymlink(self, files):
1444 1446 if not files or self._repo.dirstate._checklink:
1445 1447 return files
1446 1448
1447 1449 # Symlink placeholders may get non-symlink-like contents
1448 1450 # via user error or dereferencing by NFS or Samba servers,
1449 1451 # so we filter out any placeholders that don't look like a
1450 1452 # symlink
1451 1453 sane = []
1452 1454 for f in files:
1453 1455 if self.flags(f) == 'l':
1454 1456 d = self[f].data()
1455 1457 if (d == '' or len(d) >= 1024 or '\n' in d
1456 1458 or stringutil.binary(d)):
1457 1459 self._repo.ui.debug('ignoring suspect symlink placeholder'
1458 1460 ' "%s"\n' % f)
1459 1461 continue
1460 1462 sane.append(f)
1461 1463 return sane
1462 1464
1463 1465 def _checklookup(self, files):
1464 1466 # check for any possibly clean files
1465 1467 if not files:
1466 1468 return [], [], []
1467 1469
1468 1470 modified = []
1469 1471 deleted = []
1470 1472 fixup = []
1471 1473 pctx = self._parents[0]
1472 1474 # do a full compare of any files that might have changed
1473 1475 for f in sorted(files):
1474 1476 try:
1475 1477 # This will return True for a file that got replaced by a
1476 1478 # directory in the interim, but fixing that is pretty hard.
1477 1479 if (f not in pctx or self.flags(f) != pctx.flags(f)
1478 1480 or pctx[f].cmp(self[f])):
1479 1481 modified.append(f)
1480 1482 else:
1481 1483 fixup.append(f)
1482 1484 except (IOError, OSError):
1483 1485 # A file become inaccessible in between? Mark it as deleted,
1484 1486 # matching dirstate behavior (issue5584).
1485 1487 # The dirstate has more complex behavior around whether a
1486 1488 # missing file matches a directory, etc, but we don't need to
1487 1489 # bother with that: if f has made it to this point, we're sure
1488 1490 # it's in the dirstate.
1489 1491 deleted.append(f)
1490 1492
1491 1493 return modified, deleted, fixup
1492 1494
1493 1495 def _poststatusfixup(self, status, fixup):
1494 1496 """update dirstate for files that are actually clean"""
1495 1497 poststatus = self._repo.postdsstatus()
1496 1498 if fixup or poststatus:
1497 1499 try:
1498 1500 oldid = self._repo.dirstate.identity()
1499 1501
1500 1502 # updating the dirstate is optional
1501 1503 # so we don't wait on the lock
1502 1504 # wlock can invalidate the dirstate, so cache normal _after_
1503 1505 # taking the lock
1504 1506 with self._repo.wlock(False):
1505 1507 if self._repo.dirstate.identity() == oldid:
1506 1508 if fixup:
1507 1509 normal = self._repo.dirstate.normal
1508 1510 for f in fixup:
1509 1511 normal(f)
1510 1512 # write changes out explicitly, because nesting
1511 1513 # wlock at runtime may prevent 'wlock.release()'
1512 1514 # after this block from doing so for subsequent
1513 1515 # changing files
1514 1516 tr = self._repo.currenttransaction()
1515 1517 self._repo.dirstate.write(tr)
1516 1518
1517 1519 if poststatus:
1518 1520 for ps in poststatus:
1519 1521 ps(self, status)
1520 1522 else:
1521 1523 # in this case, writing changes out breaks
1522 1524 # consistency, because .hg/dirstate was
1523 1525 # already changed simultaneously after last
1524 1526 # caching (see also issue5584 for detail)
1525 1527 self._repo.ui.debug('skip updating dirstate: '
1526 1528 'identity mismatch\n')
1527 1529 except error.LockError:
1528 1530 pass
1529 1531 finally:
1530 1532 # Even if the wlock couldn't be grabbed, clear out the list.
1531 1533 self._repo.clearpostdsstatus()
1532 1534
1533 1535 def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
1534 1536 '''Gets the status from the dirstate -- internal use only.'''
1535 1537 subrepos = []
1536 1538 if '.hgsub' in self:
1537 1539 subrepos = sorted(self.substate)
1538 1540 cmp, s = self._repo.dirstate.status(match, subrepos, ignored=ignored,
1539 1541 clean=clean, unknown=unknown)
1540 1542
1541 1543 # check for any possibly clean files
1542 1544 fixup = []
1543 1545 if cmp:
1544 1546 modified2, deleted2, fixup = self._checklookup(cmp)
1545 1547 s.modified.extend(modified2)
1546 1548 s.deleted.extend(deleted2)
1547 1549
1548 1550 if fixup and clean:
1549 1551 s.clean.extend(fixup)
1550 1552
1551 1553 self._poststatusfixup(s, fixup)
1552 1554
1553 1555 if match.always():
1554 1556 # cache for performance
1555 1557 if s.unknown or s.ignored or s.clean:
1556 1558 # "_status" is cached with list*=False in the normal route
1557 1559 self._status = scmutil.status(s.modified, s.added, s.removed,
1558 1560 s.deleted, [], [], [])
1559 1561 else:
1560 1562 self._status = s
1561 1563
1562 1564 return s
1563 1565
1564 1566 @propertycache
1565 1567 def _manifest(self):
1566 1568 """generate a manifest corresponding to the values in self._status
1567 1569
1568 1570 This reuse the file nodeid from parent, but we use special node
1569 1571 identifiers for added and modified files. This is used by manifests
1570 1572 merge to see that files are different and by update logic to avoid
1571 1573 deleting newly added files.
1572 1574 """
1573 1575 return self._buildstatusmanifest(self._status)
1574 1576
1575 1577 def _buildstatusmanifest(self, status):
1576 1578 """Builds a manifest that includes the given status results."""
1577 1579 parents = self.parents()
1578 1580
1579 1581 man = parents[0].manifest().copy()
1580 1582
1581 1583 ff = self._flagfunc
1582 1584 for i, l in ((addednodeid, status.added),
1583 1585 (modifiednodeid, status.modified)):
1584 1586 for f in l:
1585 1587 man[f] = i
1586 1588 try:
1587 1589 man.setflag(f, ff(f))
1588 1590 except OSError:
1589 1591 pass
1590 1592
1591 1593 for f in status.deleted + status.removed:
1592 1594 if f in man:
1593 1595 del man[f]
1594 1596
1595 1597 return man
1596 1598
1597 1599 def _buildstatus(self, other, s, match, listignored, listclean,
1598 1600 listunknown):
1599 1601 """build a status with respect to another context
1600 1602
1601 1603 This includes logic for maintaining the fast path of status when
1602 1604 comparing the working directory against its parent, which is to skip
1603 1605 building a new manifest if self (working directory) is not comparing
1604 1606 against its parent (repo['.']).
1605 1607 """
1606 1608 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1607 1609 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1608 1610 # might have accidentally ended up with the entire contents of the file
1609 1611 # they are supposed to be linking to.
1610 1612 s.modified[:] = self._filtersuspectsymlink(s.modified)
1611 1613 if other != self._repo['.']:
1612 1614 s = super(workingctx, self)._buildstatus(other, s, match,
1613 1615 listignored, listclean,
1614 1616 listunknown)
1615 1617 return s
1616 1618
1617 1619 def _matchstatus(self, other, match):
1618 1620 """override the match method with a filter for directory patterns
1619 1621
1620 1622 We use inheritance to customize the match.bad method only in cases of
1621 1623 workingctx since it belongs only to the working directory when
1622 1624 comparing against the parent changeset.
1623 1625
1624 1626 If we aren't comparing against the working directory's parent, then we
1625 1627 just use the default match object sent to us.
1626 1628 """
1627 1629 if other != self._repo['.']:
1628 1630 def bad(f, msg):
1629 1631 # 'f' may be a directory pattern from 'match.files()',
1630 1632 # so 'f not in ctx1' is not enough
1631 1633 if f not in other and not other.hasdir(f):
1632 1634 self._repo.ui.warn('%s: %s\n' %
1633 1635 (self._repo.dirstate.pathto(f), msg))
1634 1636 match.bad = bad
1635 1637 return match
1636 1638
1637 1639 def markcommitted(self, node):
1638 1640 super(workingctx, self).markcommitted(node)
1639 1641
1640 1642 sparse.aftercommit(self._repo, node)
1641 1643
1642 1644 class committablefilectx(basefilectx):
1643 1645 """A committablefilectx provides common functionality for a file context
1644 1646 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1645 1647 def __init__(self, repo, path, filelog=None, ctx=None):
1646 1648 self._repo = repo
1647 1649 self._path = path
1648 1650 self._changeid = None
1649 1651 self._filerev = self._filenode = None
1650 1652
1651 1653 if filelog is not None:
1652 1654 self._filelog = filelog
1653 1655 if ctx:
1654 1656 self._changectx = ctx
1655 1657
1656 1658 def __nonzero__(self):
1657 1659 return True
1658 1660
1659 1661 __bool__ = __nonzero__
1660 1662
1661 1663 def linkrev(self):
1662 1664 # linked to self._changectx no matter if file is modified or not
1663 1665 return self.rev()
1664 1666
1665 1667 def parents(self):
1666 1668 '''return parent filectxs, following copies if necessary'''
1667 1669 def filenode(ctx, path):
1668 1670 return ctx._manifest.get(path, nullid)
1669 1671
1670 1672 path = self._path
1671 1673 fl = self._filelog
1672 1674 pcl = self._changectx._parents
1673 1675 renamed = self.renamed()
1674 1676
1675 1677 if renamed:
1676 1678 pl = [renamed + (None,)]
1677 1679 else:
1678 1680 pl = [(path, filenode(pcl[0], path), fl)]
1679 1681
1680 1682 for pc in pcl[1:]:
1681 1683 pl.append((path, filenode(pc, path), fl))
1682 1684
1683 1685 return [self._parentfilectx(p, fileid=n, filelog=l)
1684 1686 for p, n, l in pl if n != nullid]
1685 1687
1686 1688 def children(self):
1687 1689 return []
1688 1690
1689 1691 class workingfilectx(committablefilectx):
1690 1692 """A workingfilectx object makes access to data related to a particular
1691 1693 file in the working directory convenient."""
1692 1694 def __init__(self, repo, path, filelog=None, workingctx=None):
1693 1695 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1694 1696
1695 1697 @propertycache
1696 1698 def _changectx(self):
1697 1699 return workingctx(self._repo)
1698 1700
1699 1701 def data(self):
1700 1702 return self._repo.wread(self._path)
1701 1703 def renamed(self):
1702 1704 rp = self._repo.dirstate.copied(self._path)
1703 1705 if not rp:
1704 1706 return None
1705 1707 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1708 def copysource(self):
1709 return self._repo.dirstate.copied(self._path)
1706 1710
1707 1711 def size(self):
1708 1712 return self._repo.wvfs.lstat(self._path).st_size
1709 1713 def date(self):
1710 1714 t, tz = self._changectx.date()
1711 1715 try:
1712 1716 return (self._repo.wvfs.lstat(self._path)[stat.ST_MTIME], tz)
1713 1717 except OSError as err:
1714 1718 if err.errno != errno.ENOENT:
1715 1719 raise
1716 1720 return (t, tz)
1717 1721
1718 1722 def exists(self):
1719 1723 return self._repo.wvfs.exists(self._path)
1720 1724
1721 1725 def lexists(self):
1722 1726 return self._repo.wvfs.lexists(self._path)
1723 1727
1724 1728 def audit(self):
1725 1729 return self._repo.wvfs.audit(self._path)
1726 1730
1727 1731 def cmp(self, fctx):
1728 1732 """compare with other file context
1729 1733
1730 1734 returns True if different than fctx.
1731 1735 """
1732 1736 # fctx should be a filectx (not a workingfilectx)
1733 1737 # invert comparison to reuse the same code path
1734 1738 return fctx.cmp(self)
1735 1739
1736 1740 def remove(self, ignoremissing=False):
1737 1741 """wraps unlink for a repo's working directory"""
1738 1742 rmdir = self._repo.ui.configbool('experimental', 'removeemptydirs')
1739 1743 self._repo.wvfs.unlinkpath(self._path, ignoremissing=ignoremissing,
1740 1744 rmdir=rmdir)
1741 1745
1742 1746 def write(self, data, flags, backgroundclose=False, **kwargs):
1743 1747 """wraps repo.wwrite"""
1744 1748 self._repo.wwrite(self._path, data, flags,
1745 1749 backgroundclose=backgroundclose,
1746 1750 **kwargs)
1747 1751
1748 1752 def markcopied(self, src):
1749 1753 """marks this file a copy of `src`"""
1750 1754 if self._repo.dirstate[self._path] in "nma":
1751 1755 self._repo.dirstate.copy(src, self._path)
1752 1756
1753 1757 def clearunknown(self):
1754 1758 """Removes conflicting items in the working directory so that
1755 1759 ``write()`` can be called successfully.
1756 1760 """
1757 1761 wvfs = self._repo.wvfs
1758 1762 f = self._path
1759 1763 wvfs.audit(f)
1760 1764 if self._repo.ui.configbool('experimental', 'merge.checkpathconflicts'):
1761 1765 # remove files under the directory as they should already be
1762 1766 # warned and backed up
1763 1767 if wvfs.isdir(f) and not wvfs.islink(f):
1764 1768 wvfs.rmtree(f, forcibly=True)
1765 1769 for p in reversed(list(util.finddirs(f))):
1766 1770 if wvfs.isfileorlink(p):
1767 1771 wvfs.unlink(p)
1768 1772 break
1769 1773 else:
1770 1774 # don't remove files if path conflicts are not processed
1771 1775 if wvfs.isdir(f) and not wvfs.islink(f):
1772 1776 wvfs.removedirs(f)
1773 1777
1774 1778 def setflags(self, l, x):
1775 1779 self._repo.wvfs.setflags(self._path, l, x)
1776 1780
1777 1781 class overlayworkingctx(committablectx):
1778 1782 """Wraps another mutable context with a write-back cache that can be
1779 1783 converted into a commit context.
1780 1784
1781 1785 self._cache[path] maps to a dict with keys: {
1782 1786 'exists': bool?
1783 1787 'date': date?
1784 1788 'data': str?
1785 1789 'flags': str?
1786 1790 'copied': str? (path or None)
1787 1791 }
1788 1792 If `exists` is True, `flags` must be non-None and 'date' is non-None. If it
1789 1793 is `False`, the file was deleted.
1790 1794 """
1791 1795
1792 1796 def __init__(self, repo):
1793 1797 super(overlayworkingctx, self).__init__(repo)
1794 1798 self.clean()
1795 1799
1796 1800 def setbase(self, wrappedctx):
1797 1801 self._wrappedctx = wrappedctx
1798 1802 self._parents = [wrappedctx]
1799 1803 # Drop old manifest cache as it is now out of date.
1800 1804 # This is necessary when, e.g., rebasing several nodes with one
1801 1805 # ``overlayworkingctx`` (e.g. with --collapse).
1802 1806 util.clearcachedproperty(self, '_manifest')
1803 1807
1804 1808 def data(self, path):
1805 1809 if self.isdirty(path):
1806 1810 if self._cache[path]['exists']:
1807 1811 if self._cache[path]['data']:
1808 1812 return self._cache[path]['data']
1809 1813 else:
1810 1814 # Must fallback here, too, because we only set flags.
1811 1815 return self._wrappedctx[path].data()
1812 1816 else:
1813 1817 raise error.ProgrammingError("No such file or directory: %s" %
1814 1818 path)
1815 1819 else:
1816 1820 return self._wrappedctx[path].data()
1817 1821
1818 1822 @propertycache
1819 1823 def _manifest(self):
1820 1824 parents = self.parents()
1821 1825 man = parents[0].manifest().copy()
1822 1826
1823 1827 flag = self._flagfunc
1824 1828 for path in self.added():
1825 1829 man[path] = addednodeid
1826 1830 man.setflag(path, flag(path))
1827 1831 for path in self.modified():
1828 1832 man[path] = modifiednodeid
1829 1833 man.setflag(path, flag(path))
1830 1834 for path in self.removed():
1831 1835 del man[path]
1832 1836 return man
1833 1837
1834 1838 @propertycache
1835 1839 def _flagfunc(self):
1836 1840 def f(path):
1837 1841 return self._cache[path]['flags']
1838 1842 return f
1839 1843
1840 1844 def files(self):
1841 1845 return sorted(self.added() + self.modified() + self.removed())
1842 1846
1843 1847 def modified(self):
1844 1848 return [f for f in self._cache.keys() if self._cache[f]['exists'] and
1845 1849 self._existsinparent(f)]
1846 1850
1847 1851 def added(self):
1848 1852 return [f for f in self._cache.keys() if self._cache[f]['exists'] and
1849 1853 not self._existsinparent(f)]
1850 1854
1851 1855 def removed(self):
1852 1856 return [f for f in self._cache.keys() if
1853 1857 not self._cache[f]['exists'] and self._existsinparent(f)]
1854 1858
1855 1859 def p1copies(self):
1856 1860 copies = self._repo._wrappedctx.p1copies().copy()
1857 1861 narrowmatch = self._repo.narrowmatch()
1858 1862 for f in self._cache.keys():
1859 1863 if not narrowmatch(f):
1860 1864 continue
1861 1865 copies.pop(f, None) # delete if it exists
1862 1866 source = self._cache[f]['copied']
1863 1867 if source:
1864 1868 copies[f] = source
1865 1869 return copies
1866 1870
1867 1871 def p2copies(self):
1868 1872 copies = self._repo._wrappedctx.p2copies().copy()
1869 1873 narrowmatch = self._repo.narrowmatch()
1870 1874 for f in self._cache.keys():
1871 1875 if not narrowmatch(f):
1872 1876 continue
1873 1877 copies.pop(f, None) # delete if it exists
1874 1878 source = self._cache[f]['copied']
1875 1879 if source:
1876 1880 copies[f] = source
1877 1881 return copies
1878 1882
1879 1883 def isinmemory(self):
1880 1884 return True
1881 1885
1882 1886 def filedate(self, path):
1883 1887 if self.isdirty(path):
1884 1888 return self._cache[path]['date']
1885 1889 else:
1886 1890 return self._wrappedctx[path].date()
1887 1891
1888 1892 def markcopied(self, path, origin):
1889 1893 if self.isdirty(path):
1890 1894 self._cache[path]['copied'] = origin
1891 1895 else:
1892 1896 raise error.ProgrammingError('markcopied() called on clean context')
1893 1897
1894 1898 def copydata(self, path):
1895 1899 if self.isdirty(path):
1896 1900 return self._cache[path]['copied']
1897 1901 else:
1898 1902 raise error.ProgrammingError('copydata() called on clean context')
1899 1903
1900 1904 def flags(self, path):
1901 1905 if self.isdirty(path):
1902 1906 if self._cache[path]['exists']:
1903 1907 return self._cache[path]['flags']
1904 1908 else:
1905 1909 raise error.ProgrammingError("No such file or directory: %s" %
1906 1910 self._path)
1907 1911 else:
1908 1912 return self._wrappedctx[path].flags()
1909 1913
1910 1914 def __contains__(self, key):
1911 1915 if key in self._cache:
1912 1916 return self._cache[key]['exists']
1913 1917 return key in self.p1()
1914 1918
1915 1919 def _existsinparent(self, path):
1916 1920 try:
1917 1921 # ``commitctx` raises a ``ManifestLookupError`` if a path does not
1918 1922 # exist, unlike ``workingctx``, which returns a ``workingfilectx``
1919 1923 # with an ``exists()`` function.
1920 1924 self._wrappedctx[path]
1921 1925 return True
1922 1926 except error.ManifestLookupError:
1923 1927 return False
1924 1928
1925 1929 def _auditconflicts(self, path):
1926 1930 """Replicates conflict checks done by wvfs.write().
1927 1931
1928 1932 Since we never write to the filesystem and never call `applyupdates` in
1929 1933 IMM, we'll never check that a path is actually writable -- e.g., because
1930 1934 it adds `a/foo`, but `a` is actually a file in the other commit.
1931 1935 """
1932 1936 def fail(path, component):
1933 1937 # p1() is the base and we're receiving "writes" for p2()'s
1934 1938 # files.
1935 1939 if 'l' in self.p1()[component].flags():
1936 1940 raise error.Abort("error: %s conflicts with symlink %s "
1937 1941 "in %d." % (path, component,
1938 1942 self.p1().rev()))
1939 1943 else:
1940 1944 raise error.Abort("error: '%s' conflicts with file '%s' in "
1941 1945 "%d." % (path, component,
1942 1946 self.p1().rev()))
1943 1947
1944 1948 # Test that each new directory to be created to write this path from p2
1945 1949 # is not a file in p1.
1946 1950 components = path.split('/')
1947 1951 for i in pycompat.xrange(len(components)):
1948 1952 component = "/".join(components[0:i])
1949 1953 if component in self:
1950 1954 fail(path, component)
1951 1955
1952 1956 # Test the other direction -- that this path from p2 isn't a directory
1953 1957 # in p1 (test that p1 doesn't have any paths matching `path/*`).
1954 1958 match = self.match(include=[path + '/'], default=b'path')
1955 1959 matches = self.p1().manifest().matches(match)
1956 1960 mfiles = matches.keys()
1957 1961 if len(mfiles) > 0:
1958 1962 if len(mfiles) == 1 and mfiles[0] == path:
1959 1963 return
1960 1964 # omit the files which are deleted in current IMM wctx
1961 1965 mfiles = [m for m in mfiles if m in self]
1962 1966 if not mfiles:
1963 1967 return
1964 1968 raise error.Abort("error: file '%s' cannot be written because "
1965 1969 " '%s/' is a folder in %s (containing %d "
1966 1970 "entries: %s)"
1967 1971 % (path, path, self.p1(), len(mfiles),
1968 1972 ', '.join(mfiles)))
1969 1973
1970 1974 def write(self, path, data, flags='', **kwargs):
1971 1975 if data is None:
1972 1976 raise error.ProgrammingError("data must be non-None")
1973 1977 self._auditconflicts(path)
1974 1978 self._markdirty(path, exists=True, data=data, date=dateutil.makedate(),
1975 1979 flags=flags)
1976 1980
1977 1981 def setflags(self, path, l, x):
1978 1982 flag = ''
1979 1983 if l:
1980 1984 flag = 'l'
1981 1985 elif x:
1982 1986 flag = 'x'
1983 1987 self._markdirty(path, exists=True, date=dateutil.makedate(),
1984 1988 flags=flag)
1985 1989
1986 1990 def remove(self, path):
1987 1991 self._markdirty(path, exists=False)
1988 1992
1989 1993 def exists(self, path):
1990 1994 """exists behaves like `lexists`, but needs to follow symlinks and
1991 1995 return False if they are broken.
1992 1996 """
1993 1997 if self.isdirty(path):
1994 1998 # If this path exists and is a symlink, "follow" it by calling
1995 1999 # exists on the destination path.
1996 2000 if (self._cache[path]['exists'] and
1997 2001 'l' in self._cache[path]['flags']):
1998 2002 return self.exists(self._cache[path]['data'].strip())
1999 2003 else:
2000 2004 return self._cache[path]['exists']
2001 2005
2002 2006 return self._existsinparent(path)
2003 2007
2004 2008 def lexists(self, path):
2005 2009 """lexists returns True if the path exists"""
2006 2010 if self.isdirty(path):
2007 2011 return self._cache[path]['exists']
2008 2012
2009 2013 return self._existsinparent(path)
2010 2014
2011 2015 def size(self, path):
2012 2016 if self.isdirty(path):
2013 2017 if self._cache[path]['exists']:
2014 2018 return len(self._cache[path]['data'])
2015 2019 else:
2016 2020 raise error.ProgrammingError("No such file or directory: %s" %
2017 2021 self._path)
2018 2022 return self._wrappedctx[path].size()
2019 2023
2020 2024 def tomemctx(self, text, branch=None, extra=None, date=None, parents=None,
2021 2025 user=None, editor=None):
2022 2026 """Converts this ``overlayworkingctx`` into a ``memctx`` ready to be
2023 2027 committed.
2024 2028
2025 2029 ``text`` is the commit message.
2026 2030 ``parents`` (optional) are rev numbers.
2027 2031 """
2028 2032 # Default parents to the wrapped contexts' if not passed.
2029 2033 if parents is None:
2030 2034 parents = self._wrappedctx.parents()
2031 2035 if len(parents) == 1:
2032 2036 parents = (parents[0], None)
2033 2037
2034 2038 # ``parents`` is passed as rev numbers; convert to ``commitctxs``.
2035 2039 if parents[1] is None:
2036 2040 parents = (self._repo[parents[0]], None)
2037 2041 else:
2038 2042 parents = (self._repo[parents[0]], self._repo[parents[1]])
2039 2043
2040 2044 files = self._cache.keys()
2041 2045 def getfile(repo, memctx, path):
2042 2046 if self._cache[path]['exists']:
2043 2047 return memfilectx(repo, memctx, path,
2044 2048 self._cache[path]['data'],
2045 2049 'l' in self._cache[path]['flags'],
2046 2050 'x' in self._cache[path]['flags'],
2047 2051 self._cache[path]['copied'])
2048 2052 else:
2049 2053 # Returning None, but including the path in `files`, is
2050 2054 # necessary for memctx to register a deletion.
2051 2055 return None
2052 2056 return memctx(self._repo, parents, text, files, getfile, date=date,
2053 2057 extra=extra, user=user, branch=branch, editor=editor)
2054 2058
2055 2059 def isdirty(self, path):
2056 2060 return path in self._cache
2057 2061
2058 2062 def isempty(self):
2059 2063 # We need to discard any keys that are actually clean before the empty
2060 2064 # commit check.
2061 2065 self._compact()
2062 2066 return len(self._cache) == 0
2063 2067
2064 2068 def clean(self):
2065 2069 self._cache = {}
2066 2070
2067 2071 def _compact(self):
2068 2072 """Removes keys from the cache that are actually clean, by comparing
2069 2073 them with the underlying context.
2070 2074
2071 2075 This can occur during the merge process, e.g. by passing --tool :local
2072 2076 to resolve a conflict.
2073 2077 """
2074 2078 keys = []
2075 2079 # This won't be perfect, but can help performance significantly when
2076 2080 # using things like remotefilelog.
2077 2081 scmutil.prefetchfiles(
2078 2082 self.repo(), [self.p1().rev()],
2079 2083 scmutil.matchfiles(self.repo(), self._cache.keys()))
2080 2084
2081 2085 for path in self._cache.keys():
2082 2086 cache = self._cache[path]
2083 2087 try:
2084 2088 underlying = self._wrappedctx[path]
2085 2089 if (underlying.data() == cache['data'] and
2086 2090 underlying.flags() == cache['flags']):
2087 2091 keys.append(path)
2088 2092 except error.ManifestLookupError:
2089 2093 # Path not in the underlying manifest (created).
2090 2094 continue
2091 2095
2092 2096 for path in keys:
2093 2097 del self._cache[path]
2094 2098 return keys
2095 2099
2096 2100 def _markdirty(self, path, exists, data=None, date=None, flags=''):
2097 2101 # data not provided, let's see if we already have some; if not, let's
2098 2102 # grab it from our underlying context, so that we always have data if
2099 2103 # the file is marked as existing.
2100 2104 if exists and data is None:
2101 2105 oldentry = self._cache.get(path) or {}
2102 2106 data = oldentry.get('data') or self._wrappedctx[path].data()
2103 2107
2104 2108 self._cache[path] = {
2105 2109 'exists': exists,
2106 2110 'data': data,
2107 2111 'date': date,
2108 2112 'flags': flags,
2109 2113 'copied': None,
2110 2114 }
2111 2115
2112 2116 def filectx(self, path, filelog=None):
2113 2117 return overlayworkingfilectx(self._repo, path, parent=self,
2114 2118 filelog=filelog)
2115 2119
2116 2120 class overlayworkingfilectx(committablefilectx):
2117 2121 """Wrap a ``workingfilectx`` but intercepts all writes into an in-memory
2118 2122 cache, which can be flushed through later by calling ``flush()``."""
2119 2123
2120 2124 def __init__(self, repo, path, filelog=None, parent=None):
2121 2125 super(overlayworkingfilectx, self).__init__(repo, path, filelog,
2122 2126 parent)
2123 2127 self._repo = repo
2124 2128 self._parent = parent
2125 2129 self._path = path
2126 2130
2127 2131 def cmp(self, fctx):
2128 2132 return self.data() != fctx.data()
2129 2133
2130 2134 def changectx(self):
2131 2135 return self._parent
2132 2136
2133 2137 def data(self):
2134 2138 return self._parent.data(self._path)
2135 2139
2136 2140 def date(self):
2137 2141 return self._parent.filedate(self._path)
2138 2142
2139 2143 def exists(self):
2140 2144 return self.lexists()
2141 2145
2142 2146 def lexists(self):
2143 2147 return self._parent.exists(self._path)
2144 2148
2145 2149 def renamed(self):
2146 2150 path = self._parent.copydata(self._path)
2147 2151 if not path:
2148 2152 return None
2149 2153 return path, self._changectx._parents[0]._manifest.get(path, nullid)
2150 2154
2155 def copysource(self):
2156 return self._parent.copydata(self._path)
2157
2151 2158 def size(self):
2152 2159 return self._parent.size(self._path)
2153 2160
2154 2161 def markcopied(self, origin):
2155 2162 self._parent.markcopied(self._path, origin)
2156 2163
2157 2164 def audit(self):
2158 2165 pass
2159 2166
2160 2167 def flags(self):
2161 2168 return self._parent.flags(self._path)
2162 2169
2163 2170 def setflags(self, islink, isexec):
2164 2171 return self._parent.setflags(self._path, islink, isexec)
2165 2172
2166 2173 def write(self, data, flags, backgroundclose=False, **kwargs):
2167 2174 return self._parent.write(self._path, data, flags, **kwargs)
2168 2175
2169 2176 def remove(self, ignoremissing=False):
2170 2177 return self._parent.remove(self._path)
2171 2178
2172 2179 def clearunknown(self):
2173 2180 pass
2174 2181
2175 2182 class workingcommitctx(workingctx):
2176 2183 """A workingcommitctx object makes access to data related to
2177 2184 the revision being committed convenient.
2178 2185
2179 2186 This hides changes in the working directory, if they aren't
2180 2187 committed in this context.
2181 2188 """
2182 2189 def __init__(self, repo, changes,
2183 2190 text="", user=None, date=None, extra=None):
2184 2191 super(workingcommitctx, self).__init__(repo, text, user, date, extra,
2185 2192 changes)
2186 2193
2187 2194 def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
2188 2195 """Return matched files only in ``self._status``
2189 2196
2190 2197 Uncommitted files appear "clean" via this context, even if
2191 2198 they aren't actually so in the working directory.
2192 2199 """
2193 2200 if clean:
2194 2201 clean = [f for f in self._manifest if f not in self._changedset]
2195 2202 else:
2196 2203 clean = []
2197 2204 return scmutil.status([f for f in self._status.modified if match(f)],
2198 2205 [f for f in self._status.added if match(f)],
2199 2206 [f for f in self._status.removed if match(f)],
2200 2207 [], [], [], clean)
2201 2208
2202 2209 @propertycache
2203 2210 def _changedset(self):
2204 2211 """Return the set of files changed in this context
2205 2212 """
2206 2213 changed = set(self._status.modified)
2207 2214 changed.update(self._status.added)
2208 2215 changed.update(self._status.removed)
2209 2216 return changed
2210 2217
2211 2218 def makecachingfilectxfn(func):
2212 2219 """Create a filectxfn that caches based on the path.
2213 2220
2214 2221 We can't use util.cachefunc because it uses all arguments as the cache
2215 2222 key and this creates a cycle since the arguments include the repo and
2216 2223 memctx.
2217 2224 """
2218 2225 cache = {}
2219 2226
2220 2227 def getfilectx(repo, memctx, path):
2221 2228 if path not in cache:
2222 2229 cache[path] = func(repo, memctx, path)
2223 2230 return cache[path]
2224 2231
2225 2232 return getfilectx
2226 2233
2227 2234 def memfilefromctx(ctx):
2228 2235 """Given a context return a memfilectx for ctx[path]
2229 2236
2230 2237 This is a convenience method for building a memctx based on another
2231 2238 context.
2232 2239 """
2233 2240 def getfilectx(repo, memctx, path):
2234 2241 fctx = ctx[path]
2235 2242 copied = fctx.renamed()
2236 2243 if copied:
2237 2244 copied = copied[0]
2238 2245 return memfilectx(repo, memctx, path, fctx.data(),
2239 2246 islink=fctx.islink(), isexec=fctx.isexec(),
2240 2247 copied=copied)
2241 2248
2242 2249 return getfilectx
2243 2250
2244 2251 def memfilefrompatch(patchstore):
2245 2252 """Given a patch (e.g. patchstore object) return a memfilectx
2246 2253
2247 2254 This is a convenience method for building a memctx based on a patchstore.
2248 2255 """
2249 2256 def getfilectx(repo, memctx, path):
2250 2257 data, mode, copied = patchstore.getfile(path)
2251 2258 if data is None:
2252 2259 return None
2253 2260 islink, isexec = mode
2254 2261 return memfilectx(repo, memctx, path, data, islink=islink,
2255 2262 isexec=isexec, copied=copied)
2256 2263
2257 2264 return getfilectx
2258 2265
2259 2266 class memctx(committablectx):
2260 2267 """Use memctx to perform in-memory commits via localrepo.commitctx().
2261 2268
2262 2269 Revision information is supplied at initialization time while
2263 2270 related files data and is made available through a callback
2264 2271 mechanism. 'repo' is the current localrepo, 'parents' is a
2265 2272 sequence of two parent revisions identifiers (pass None for every
2266 2273 missing parent), 'text' is the commit message and 'files' lists
2267 2274 names of files touched by the revision (normalized and relative to
2268 2275 repository root).
2269 2276
2270 2277 filectxfn(repo, memctx, path) is a callable receiving the
2271 2278 repository, the current memctx object and the normalized path of
2272 2279 requested file, relative to repository root. It is fired by the
2273 2280 commit function for every file in 'files', but calls order is
2274 2281 undefined. If the file is available in the revision being
2275 2282 committed (updated or added), filectxfn returns a memfilectx
2276 2283 object. If the file was removed, filectxfn return None for recent
2277 2284 Mercurial. Moved files are represented by marking the source file
2278 2285 removed and the new file added with copy information (see
2279 2286 memfilectx).
2280 2287
2281 2288 user receives the committer name and defaults to current
2282 2289 repository username, date is the commit date in any format
2283 2290 supported by dateutil.parsedate() and defaults to current date, extra
2284 2291 is a dictionary of metadata or is left empty.
2285 2292 """
2286 2293
2287 2294 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
2288 2295 # Extensions that need to retain compatibility across Mercurial 3.1 can use
2289 2296 # this field to determine what to do in filectxfn.
2290 2297 _returnnoneformissingfiles = True
2291 2298
2292 2299 def __init__(self, repo, parents, text, files, filectxfn, user=None,
2293 2300 date=None, extra=None, branch=None, editor=False):
2294 2301 super(memctx, self).__init__(repo, text, user, date, extra)
2295 2302 self._rev = None
2296 2303 self._node = None
2297 2304 parents = [(p or nullid) for p in parents]
2298 2305 p1, p2 = parents
2299 2306 self._parents = [self._repo[p] for p in (p1, p2)]
2300 2307 files = sorted(set(files))
2301 2308 self._files = files
2302 2309 if branch is not None:
2303 2310 self._extra['branch'] = encoding.fromlocal(branch)
2304 2311 self.substate = {}
2305 2312
2306 2313 if isinstance(filectxfn, patch.filestore):
2307 2314 filectxfn = memfilefrompatch(filectxfn)
2308 2315 elif not callable(filectxfn):
2309 2316 # if store is not callable, wrap it in a function
2310 2317 filectxfn = memfilefromctx(filectxfn)
2311 2318
2312 2319 # memoizing increases performance for e.g. vcs convert scenarios.
2313 2320 self._filectxfn = makecachingfilectxfn(filectxfn)
2314 2321
2315 2322 if editor:
2316 2323 self._text = editor(self._repo, self, [])
2317 2324 self._repo.savecommitmessage(self._text)
2318 2325
2319 2326 def filectx(self, path, filelog=None):
2320 2327 """get a file context from the working directory
2321 2328
2322 2329 Returns None if file doesn't exist and should be removed."""
2323 2330 return self._filectxfn(self._repo, self, path)
2324 2331
2325 2332 def commit(self):
2326 2333 """commit context to the repo"""
2327 2334 return self._repo.commitctx(self)
2328 2335
2329 2336 @propertycache
2330 2337 def _manifest(self):
2331 2338 """generate a manifest based on the return values of filectxfn"""
2332 2339
2333 2340 # keep this simple for now; just worry about p1
2334 2341 pctx = self._parents[0]
2335 2342 man = pctx.manifest().copy()
2336 2343
2337 2344 for f in self._status.modified:
2338 2345 man[f] = modifiednodeid
2339 2346
2340 2347 for f in self._status.added:
2341 2348 man[f] = addednodeid
2342 2349
2343 2350 for f in self._status.removed:
2344 2351 if f in man:
2345 2352 del man[f]
2346 2353
2347 2354 return man
2348 2355
2349 2356 @propertycache
2350 2357 def _status(self):
2351 2358 """Calculate exact status from ``files`` specified at construction
2352 2359 """
2353 2360 man1 = self.p1().manifest()
2354 2361 p2 = self._parents[1]
2355 2362 # "1 < len(self._parents)" can't be used for checking
2356 2363 # existence of the 2nd parent, because "memctx._parents" is
2357 2364 # explicitly initialized by the list, of which length is 2.
2358 2365 if p2.node() != nullid:
2359 2366 man2 = p2.manifest()
2360 2367 managing = lambda f: f in man1 or f in man2
2361 2368 else:
2362 2369 managing = lambda f: f in man1
2363 2370
2364 2371 modified, added, removed = [], [], []
2365 2372 for f in self._files:
2366 2373 if not managing(f):
2367 2374 added.append(f)
2368 2375 elif self[f]:
2369 2376 modified.append(f)
2370 2377 else:
2371 2378 removed.append(f)
2372 2379
2373 2380 return scmutil.status(modified, added, removed, [], [], [], [])
2374 2381
2375 2382 class memfilectx(committablefilectx):
2376 2383 """memfilectx represents an in-memory file to commit.
2377 2384
2378 2385 See memctx and committablefilectx for more details.
2379 2386 """
2380 2387 def __init__(self, repo, changectx, path, data, islink=False,
2381 2388 isexec=False, copied=None):
2382 2389 """
2383 2390 path is the normalized file path relative to repository root.
2384 2391 data is the file content as a string.
2385 2392 islink is True if the file is a symbolic link.
2386 2393 isexec is True if the file is executable.
2387 2394 copied is the source file path if current file was copied in the
2388 2395 revision being committed, or None."""
2389 2396 super(memfilectx, self).__init__(repo, path, None, changectx)
2390 2397 self._data = data
2391 2398 if islink:
2392 2399 self._flags = 'l'
2393 2400 elif isexec:
2394 2401 self._flags = 'x'
2395 2402 else:
2396 2403 self._flags = ''
2397 2404 self._copied = None
2398 2405 if copied:
2399 2406 self._copied = (copied, nullid)
2400 2407
2401 2408 def cmp(self, fctx):
2402 2409 return self.data() != fctx.data()
2403 2410
2404 2411 def data(self):
2405 2412 return self._data
2406 2413
2407 2414 def remove(self, ignoremissing=False):
2408 2415 """wraps unlink for a repo's working directory"""
2409 2416 # need to figure out what to do here
2410 2417 del self._changectx[self._path]
2411 2418
2412 2419 def write(self, data, flags, **kwargs):
2413 2420 """wraps repo.wwrite"""
2414 2421 self._data = data
2415 2422
2416 2423
2417 2424 class metadataonlyctx(committablectx):
2418 2425 """Like memctx but it's reusing the manifest of different commit.
2419 2426 Intended to be used by lightweight operations that are creating
2420 2427 metadata-only changes.
2421 2428
2422 2429 Revision information is supplied at initialization time. 'repo' is the
2423 2430 current localrepo, 'ctx' is original revision which manifest we're reuisng
2424 2431 'parents' is a sequence of two parent revisions identifiers (pass None for
2425 2432 every missing parent), 'text' is the commit.
2426 2433
2427 2434 user receives the committer name and defaults to current repository
2428 2435 username, date is the commit date in any format supported by
2429 2436 dateutil.parsedate() and defaults to current date, extra is a dictionary of
2430 2437 metadata or is left empty.
2431 2438 """
2432 2439 def __init__(self, repo, originalctx, parents=None, text=None, user=None,
2433 2440 date=None, extra=None, editor=False):
2434 2441 if text is None:
2435 2442 text = originalctx.description()
2436 2443 super(metadataonlyctx, self).__init__(repo, text, user, date, extra)
2437 2444 self._rev = None
2438 2445 self._node = None
2439 2446 self._originalctx = originalctx
2440 2447 self._manifestnode = originalctx.manifestnode()
2441 2448 if parents is None:
2442 2449 parents = originalctx.parents()
2443 2450 else:
2444 2451 parents = [repo[p] for p in parents if p is not None]
2445 2452 parents = parents[:]
2446 2453 while len(parents) < 2:
2447 2454 parents.append(repo[nullid])
2448 2455 p1, p2 = self._parents = parents
2449 2456
2450 2457 # sanity check to ensure that the reused manifest parents are
2451 2458 # manifests of our commit parents
2452 2459 mp1, mp2 = self.manifestctx().parents
2453 2460 if p1 != nullid and p1.manifestnode() != mp1:
2454 2461 raise RuntimeError(r"can't reuse the manifest: its p1 "
2455 2462 r"doesn't match the new ctx p1")
2456 2463 if p2 != nullid and p2.manifestnode() != mp2:
2457 2464 raise RuntimeError(r"can't reuse the manifest: "
2458 2465 r"its p2 doesn't match the new ctx p2")
2459 2466
2460 2467 self._files = originalctx.files()
2461 2468 self.substate = {}
2462 2469
2463 2470 if editor:
2464 2471 self._text = editor(self._repo, self, [])
2465 2472 self._repo.savecommitmessage(self._text)
2466 2473
2467 2474 def manifestnode(self):
2468 2475 return self._manifestnode
2469 2476
2470 2477 @property
2471 2478 def _manifestctx(self):
2472 2479 return self._repo.manifestlog[self._manifestnode]
2473 2480
2474 2481 def filectx(self, path, filelog=None):
2475 2482 return self._originalctx.filectx(path, filelog=filelog)
2476 2483
2477 2484 def commit(self):
2478 2485 """commit context to the repo"""
2479 2486 return self._repo.commitctx(self)
2480 2487
2481 2488 @property
2482 2489 def _manifest(self):
2483 2490 return self._originalctx.manifest()
2484 2491
2485 2492 @propertycache
2486 2493 def _status(self):
2487 2494 """Calculate exact status from ``files`` specified in the ``origctx``
2488 2495 and parents manifests.
2489 2496 """
2490 2497 man1 = self.p1().manifest()
2491 2498 p2 = self._parents[1]
2492 2499 # "1 < len(self._parents)" can't be used for checking
2493 2500 # existence of the 2nd parent, because "metadataonlyctx._parents" is
2494 2501 # explicitly initialized by the list, of which length is 2.
2495 2502 if p2.node() != nullid:
2496 2503 man2 = p2.manifest()
2497 2504 managing = lambda f: f in man1 or f in man2
2498 2505 else:
2499 2506 managing = lambda f: f in man1
2500 2507
2501 2508 modified, added, removed = [], [], []
2502 2509 for f in self._files:
2503 2510 if not managing(f):
2504 2511 added.append(f)
2505 2512 elif f in self:
2506 2513 modified.append(f)
2507 2514 else:
2508 2515 removed.append(f)
2509 2516
2510 2517 return scmutil.status(modified, added, removed, [], [], [], [])
2511 2518
2512 2519 class arbitraryfilectx(object):
2513 2520 """Allows you to use filectx-like functions on a file in an arbitrary
2514 2521 location on disk, possibly not in the working directory.
2515 2522 """
2516 2523 def __init__(self, path, repo=None):
2517 2524 # Repo is optional because contrib/simplemerge uses this class.
2518 2525 self._repo = repo
2519 2526 self._path = path
2520 2527
2521 2528 def cmp(self, fctx):
2522 2529 # filecmp follows symlinks whereas `cmp` should not, so skip the fast
2523 2530 # path if either side is a symlink.
2524 2531 symlinks = ('l' in self.flags() or 'l' in fctx.flags())
2525 2532 if not symlinks and isinstance(fctx, workingfilectx) and self._repo:
2526 2533 # Add a fast-path for merge if both sides are disk-backed.
2527 2534 # Note that filecmp uses the opposite return values (True if same)
2528 2535 # from our cmp functions (True if different).
2529 2536 return not filecmp.cmp(self.path(), self._repo.wjoin(fctx.path()))
2530 2537 return self.data() != fctx.data()
2531 2538
2532 2539 def path(self):
2533 2540 return self._path
2534 2541
2535 2542 def flags(self):
2536 2543 return ''
2537 2544
2538 2545 def data(self):
2539 2546 return util.readfile(self._path)
2540 2547
2541 2548 def decodeddata(self):
2542 2549 with open(self._path, "rb") as f:
2543 2550 return f.read()
2544 2551
2545 2552 def remove(self):
2546 2553 util.unlink(self._path)
2547 2554
2548 2555 def write(self, data, flags, **kwargs):
2549 2556 assert not flags
2550 2557 with open(self._path, "wb") as f:
2551 2558 f.write(data)
General Comments 0
You need to be logged in to leave comments. Login now