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