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