##// END OF EJS Templates
annotate: discard refcount of discarded annotation for memory efficiency...
FUJIWARA Katsunori -
r19061:36067f5b default
parent child Browse files
Show More
@@ -1,1378 +1,1379 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 node import nullid, nullrev, short, hex, bin
9 9 from i18n import _
10 10 import ancestor, mdiff, error, util, scmutil, subrepo, patch, encoding, phases
11 11 import copies
12 12 import match as matchmod
13 13 import os, errno, stat
14 14 import obsolete as obsmod
15 15 import repoview
16 16
17 17 propertycache = util.propertycache
18 18
19 19 class changectx(object):
20 20 """A changecontext object makes access to data related to a particular
21 21 changeset convenient."""
22 22 def __init__(self, repo, changeid=''):
23 23 """changeid is a revision number, node, or tag"""
24 24 if changeid == '':
25 25 changeid = '.'
26 26 self._repo = repo
27 27
28 28 if isinstance(changeid, int):
29 29 try:
30 30 self._node = repo.changelog.node(changeid)
31 31 except IndexError:
32 32 raise error.RepoLookupError(
33 33 _("unknown revision '%s'") % changeid)
34 34 self._rev = changeid
35 35 return
36 36 if isinstance(changeid, long):
37 37 changeid = str(changeid)
38 38 if changeid == '.':
39 39 self._node = repo.dirstate.p1()
40 40 self._rev = repo.changelog.rev(self._node)
41 41 return
42 42 if changeid == 'null':
43 43 self._node = nullid
44 44 self._rev = nullrev
45 45 return
46 46 if changeid == 'tip':
47 47 self._node = repo.changelog.tip()
48 48 self._rev = repo.changelog.rev(self._node)
49 49 return
50 50 if len(changeid) == 20:
51 51 try:
52 52 self._node = changeid
53 53 self._rev = repo.changelog.rev(changeid)
54 54 return
55 55 except LookupError:
56 56 pass
57 57
58 58 try:
59 59 r = int(changeid)
60 60 if str(r) != changeid:
61 61 raise ValueError
62 62 l = len(repo.changelog)
63 63 if r < 0:
64 64 r += l
65 65 if r < 0 or r >= l:
66 66 raise ValueError
67 67 self._rev = r
68 68 self._node = repo.changelog.node(r)
69 69 return
70 70 except (ValueError, OverflowError, IndexError):
71 71 pass
72 72
73 73 if len(changeid) == 40:
74 74 try:
75 75 self._node = bin(changeid)
76 76 self._rev = repo.changelog.rev(self._node)
77 77 return
78 78 except (TypeError, LookupError):
79 79 pass
80 80
81 81 if changeid in repo._bookmarks:
82 82 self._node = repo._bookmarks[changeid]
83 83 self._rev = repo.changelog.rev(self._node)
84 84 return
85 85 if changeid in repo._tagscache.tags:
86 86 self._node = repo._tagscache.tags[changeid]
87 87 self._rev = repo.changelog.rev(self._node)
88 88 return
89 89 try:
90 90 self._node = repo.branchtip(changeid)
91 91 self._rev = repo.changelog.rev(self._node)
92 92 return
93 93 except error.RepoLookupError:
94 94 pass
95 95
96 96 self._node = repo.changelog._partialmatch(changeid)
97 97 if self._node is not None:
98 98 self._rev = repo.changelog.rev(self._node)
99 99 return
100 100
101 101 # lookup failed
102 102 # check if it might have come from damaged dirstate
103 103 #
104 104 # XXX we could avoid the unfiltered if we had a recognizable exception
105 105 # for filtered changeset access
106 106 if changeid in repo.unfiltered().dirstate.parents():
107 107 raise error.Abort(_("working directory has unknown parent '%s'!")
108 108 % short(changeid))
109 109 try:
110 110 if len(changeid) == 20:
111 111 changeid = hex(changeid)
112 112 except TypeError:
113 113 pass
114 114 raise error.RepoLookupError(
115 115 _("unknown revision '%s'") % changeid)
116 116
117 117 def __str__(self):
118 118 return short(self.node())
119 119
120 120 def __int__(self):
121 121 return self.rev()
122 122
123 123 def __repr__(self):
124 124 return "<changectx %s>" % str(self)
125 125
126 126 def __hash__(self):
127 127 try:
128 128 return hash(self._rev)
129 129 except AttributeError:
130 130 return id(self)
131 131
132 132 def __eq__(self, other):
133 133 try:
134 134 return self._rev == other._rev
135 135 except AttributeError:
136 136 return False
137 137
138 138 def __ne__(self, other):
139 139 return not (self == other)
140 140
141 141 def __nonzero__(self):
142 142 return self._rev != nullrev
143 143
144 144 @propertycache
145 145 def _changeset(self):
146 146 return self._repo.changelog.read(self.rev())
147 147
148 148 @propertycache
149 149 def _manifest(self):
150 150 return self._repo.manifest.read(self._changeset[0])
151 151
152 152 @propertycache
153 153 def _manifestdelta(self):
154 154 return self._repo.manifest.readdelta(self._changeset[0])
155 155
156 156 @propertycache
157 157 def _parents(self):
158 158 p = self._repo.changelog.parentrevs(self._rev)
159 159 if p[1] == nullrev:
160 160 p = p[:-1]
161 161 return [changectx(self._repo, x) for x in p]
162 162
163 163 @propertycache
164 164 def substate(self):
165 165 return subrepo.state(self, self._repo.ui)
166 166
167 167 def __contains__(self, key):
168 168 return key in self._manifest
169 169
170 170 def __getitem__(self, key):
171 171 return self.filectx(key)
172 172
173 173 def __iter__(self):
174 174 for f in sorted(self._manifest):
175 175 yield f
176 176
177 177 def changeset(self):
178 178 return self._changeset
179 179 def manifest(self):
180 180 return self._manifest
181 181 def manifestnode(self):
182 182 return self._changeset[0]
183 183
184 184 def rev(self):
185 185 return self._rev
186 186 def node(self):
187 187 return self._node
188 188 def hex(self):
189 189 return hex(self._node)
190 190 def user(self):
191 191 return self._changeset[1]
192 192 def date(self):
193 193 return self._changeset[2]
194 194 def files(self):
195 195 return self._changeset[3]
196 196 def description(self):
197 197 return self._changeset[4]
198 198 def branch(self):
199 199 return encoding.tolocal(self._changeset[5].get("branch"))
200 200 def closesbranch(self):
201 201 return 'close' in self._changeset[5]
202 202 def extra(self):
203 203 return self._changeset[5]
204 204 def tags(self):
205 205 return self._repo.nodetags(self._node)
206 206 def bookmarks(self):
207 207 return self._repo.nodebookmarks(self._node)
208 208 def phase(self):
209 209 return self._repo._phasecache.phase(self._repo, self._rev)
210 210 def phasestr(self):
211 211 return phases.phasenames[self.phase()]
212 212 def mutable(self):
213 213 return self.phase() > phases.public
214 214 def hidden(self):
215 215 return self._rev in repoview.filterrevs(self._repo, 'visible')
216 216
217 217 def parents(self):
218 218 """return contexts for each parent changeset"""
219 219 return self._parents
220 220
221 221 def p1(self):
222 222 return self._parents[0]
223 223
224 224 def p2(self):
225 225 if len(self._parents) == 2:
226 226 return self._parents[1]
227 227 return changectx(self._repo, -1)
228 228
229 229 def children(self):
230 230 """return contexts for each child changeset"""
231 231 c = self._repo.changelog.children(self._node)
232 232 return [changectx(self._repo, x) for x in c]
233 233
234 234 def ancestors(self):
235 235 for a in self._repo.changelog.ancestors([self._rev]):
236 236 yield changectx(self._repo, a)
237 237
238 238 def descendants(self):
239 239 for d in self._repo.changelog.descendants([self._rev]):
240 240 yield changectx(self._repo, d)
241 241
242 242 def obsolete(self):
243 243 """True if the changeset is obsolete"""
244 244 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
245 245
246 246 def extinct(self):
247 247 """True if the changeset is extinct"""
248 248 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
249 249
250 250 def unstable(self):
251 251 """True if the changeset is not obsolete but it's ancestor are"""
252 252 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
253 253
254 254 def bumped(self):
255 255 """True if the changeset try to be a successor of a public changeset
256 256
257 257 Only non-public and non-obsolete changesets may be bumped.
258 258 """
259 259 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
260 260
261 261 def divergent(self):
262 262 """Is a successors of a changeset with multiple possible successors set
263 263
264 264 Only non-public and non-obsolete changesets may be divergent.
265 265 """
266 266 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
267 267
268 268 def troubled(self):
269 269 """True if the changeset is either unstable, bumped or divergent"""
270 270 return self.unstable() or self.bumped() or self.divergent()
271 271
272 272 def troubles(self):
273 273 """return the list of troubles affecting this changesets.
274 274
275 275 Troubles are returned as strings. possible values are:
276 276 - unstable,
277 277 - bumped,
278 278 - divergent.
279 279 """
280 280 troubles = []
281 281 if self.unstable():
282 282 troubles.append('unstable')
283 283 if self.bumped():
284 284 troubles.append('bumped')
285 285 if self.divergent():
286 286 troubles.append('divergent')
287 287 return troubles
288 288
289 289 def _fileinfo(self, path):
290 290 if '_manifest' in self.__dict__:
291 291 try:
292 292 return self._manifest[path], self._manifest.flags(path)
293 293 except KeyError:
294 294 raise error.ManifestLookupError(self._node, path,
295 295 _('not found in manifest'))
296 296 if '_manifestdelta' in self.__dict__ or path in self.files():
297 297 if path in self._manifestdelta:
298 298 return (self._manifestdelta[path],
299 299 self._manifestdelta.flags(path))
300 300 node, flag = self._repo.manifest.find(self._changeset[0], path)
301 301 if not node:
302 302 raise error.ManifestLookupError(self._node, path,
303 303 _('not found in manifest'))
304 304
305 305 return node, flag
306 306
307 307 def filenode(self, path):
308 308 return self._fileinfo(path)[0]
309 309
310 310 def flags(self, path):
311 311 try:
312 312 return self._fileinfo(path)[1]
313 313 except error.LookupError:
314 314 return ''
315 315
316 316 def filectx(self, path, fileid=None, filelog=None):
317 317 """get a file context from this changeset"""
318 318 if fileid is None:
319 319 fileid = self.filenode(path)
320 320 return filectx(self._repo, path, fileid=fileid,
321 321 changectx=self, filelog=filelog)
322 322
323 323 def ancestor(self, c2):
324 324 """
325 325 return the ancestor context of self and c2
326 326 """
327 327 # deal with workingctxs
328 328 n2 = c2._node
329 329 if n2 is None:
330 330 n2 = c2._parents[0]._node
331 331 n = self._repo.changelog.ancestor(self._node, n2)
332 332 return changectx(self._repo, n)
333 333
334 334 def descendant(self, other):
335 335 """True if other is descendant of this changeset"""
336 336 return self._repo.changelog.descendant(self._rev, other._rev)
337 337
338 338 def walk(self, match):
339 339 fset = set(match.files())
340 340 # for dirstate.walk, files=['.'] means "walk the whole tree".
341 341 # follow that here, too
342 342 fset.discard('.')
343 343 for fn in self:
344 344 if fn in fset:
345 345 # specified pattern is the exact name
346 346 fset.remove(fn)
347 347 if match(fn):
348 348 yield fn
349 349 for fn in sorted(fset):
350 350 if fn in self._dirs:
351 351 # specified pattern is a directory
352 352 continue
353 353 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
354 354 yield fn
355 355
356 356 def sub(self, path):
357 357 return subrepo.subrepo(self, path)
358 358
359 359 def match(self, pats=[], include=None, exclude=None, default='glob'):
360 360 r = self._repo
361 361 return matchmod.match(r.root, r.getcwd(), pats,
362 362 include, exclude, default,
363 363 auditor=r.auditor, ctx=self)
364 364
365 365 def diff(self, ctx2=None, match=None, **opts):
366 366 """Returns a diff generator for the given contexts and matcher"""
367 367 if ctx2 is None:
368 368 ctx2 = self.p1()
369 369 if ctx2 is not None and not isinstance(ctx2, changectx):
370 370 ctx2 = self._repo[ctx2]
371 371 diffopts = patch.diffopts(self._repo.ui, opts)
372 372 return patch.diff(self._repo, ctx2.node(), self.node(),
373 373 match=match, opts=diffopts)
374 374
375 375 @propertycache
376 376 def _dirs(self):
377 377 return scmutil.dirs(self._manifest)
378 378
379 379 def dirs(self):
380 380 return self._dirs
381 381
382 382 def dirty(self):
383 383 return False
384 384
385 385 class filectx(object):
386 386 """A filecontext object makes access to data related to a particular
387 387 filerevision convenient."""
388 388 def __init__(self, repo, path, changeid=None, fileid=None,
389 389 filelog=None, changectx=None):
390 390 """changeid can be a changeset revision, node, or tag.
391 391 fileid can be a file revision or node."""
392 392 self._repo = repo
393 393 self._path = path
394 394
395 395 assert (changeid is not None
396 396 or fileid is not None
397 397 or changectx is not None), \
398 398 ("bad args: changeid=%r, fileid=%r, changectx=%r"
399 399 % (changeid, fileid, changectx))
400 400
401 401 if filelog:
402 402 self._filelog = filelog
403 403
404 404 if changeid is not None:
405 405 self._changeid = changeid
406 406 if changectx is not None:
407 407 self._changectx = changectx
408 408 if fileid is not None:
409 409 self._fileid = fileid
410 410
411 411 @propertycache
412 412 def _changectx(self):
413 413 try:
414 414 return changectx(self._repo, self._changeid)
415 415 except error.RepoLookupError:
416 416 # Linkrev may point to any revision in the repository. When the
417 417 # repository is filtered this may lead to `filectx` trying to build
418 418 # `changectx` for filtered revision. In such case we fallback to
419 419 # creating `changectx` on the unfiltered version of the reposition.
420 420 # This fallback should not be an issue because `changectx` from
421 421 # `filectx` are not used in complex operations that care about
422 422 # filtering.
423 423 #
424 424 # This fallback is a cheap and dirty fix that prevent several
425 425 # crashes. It does not ensure the behavior is correct. However the
426 426 # behavior was not correct before filtering either and "incorrect
427 427 # behavior" is seen as better as "crash"
428 428 #
429 429 # Linkrevs have several serious troubles with filtering that are
430 430 # complicated to solve. Proper handling of the issue here should be
431 431 # considered when solving linkrev issue are on the table.
432 432 return changectx(self._repo.unfiltered(), self._changeid)
433 433
434 434 @propertycache
435 435 def _filelog(self):
436 436 return self._repo.file(self._path)
437 437
438 438 @propertycache
439 439 def _changeid(self):
440 440 if '_changectx' in self.__dict__:
441 441 return self._changectx.rev()
442 442 else:
443 443 return self._filelog.linkrev(self._filerev)
444 444
445 445 @propertycache
446 446 def _filenode(self):
447 447 if '_fileid' in self.__dict__:
448 448 return self._filelog.lookup(self._fileid)
449 449 else:
450 450 return self._changectx.filenode(self._path)
451 451
452 452 @propertycache
453 453 def _filerev(self):
454 454 return self._filelog.rev(self._filenode)
455 455
456 456 @propertycache
457 457 def _repopath(self):
458 458 return self._path
459 459
460 460 def __nonzero__(self):
461 461 try:
462 462 self._filenode
463 463 return True
464 464 except error.LookupError:
465 465 # file is missing
466 466 return False
467 467
468 468 def __str__(self):
469 469 return "%s@%s" % (self.path(), short(self.node()))
470 470
471 471 def __repr__(self):
472 472 return "<filectx %s>" % str(self)
473 473
474 474 def __hash__(self):
475 475 try:
476 476 return hash((self._path, self._filenode))
477 477 except AttributeError:
478 478 return id(self)
479 479
480 480 def __eq__(self, other):
481 481 try:
482 482 return (self._path == other._path
483 483 and self._filenode == other._filenode)
484 484 except AttributeError:
485 485 return False
486 486
487 487 def __ne__(self, other):
488 488 return not (self == other)
489 489
490 490 def filectx(self, fileid):
491 491 '''opens an arbitrary revision of the file without
492 492 opening a new filelog'''
493 493 return filectx(self._repo, self._path, fileid=fileid,
494 494 filelog=self._filelog)
495 495
496 496 def filerev(self):
497 497 return self._filerev
498 498 def filenode(self):
499 499 return self._filenode
500 500 def flags(self):
501 501 return self._changectx.flags(self._path)
502 502 def filelog(self):
503 503 return self._filelog
504 504
505 505 def rev(self):
506 506 if '_changectx' in self.__dict__:
507 507 return self._changectx.rev()
508 508 if '_changeid' in self.__dict__:
509 509 return self._changectx.rev()
510 510 return self._filelog.linkrev(self._filerev)
511 511
512 512 def linkrev(self):
513 513 return self._filelog.linkrev(self._filerev)
514 514 def node(self):
515 515 return self._changectx.node()
516 516 def hex(self):
517 517 return hex(self.node())
518 518 def user(self):
519 519 return self._changectx.user()
520 520 def date(self):
521 521 return self._changectx.date()
522 522 def files(self):
523 523 return self._changectx.files()
524 524 def description(self):
525 525 return self._changectx.description()
526 526 def branch(self):
527 527 return self._changectx.branch()
528 528 def extra(self):
529 529 return self._changectx.extra()
530 530 def phase(self):
531 531 return self._changectx.phase()
532 532 def phasestr(self):
533 533 return self._changectx.phasestr()
534 534 def manifest(self):
535 535 return self._changectx.manifest()
536 536 def changectx(self):
537 537 return self._changectx
538 538
539 539 def data(self):
540 540 return self._filelog.read(self._filenode)
541 541 def path(self):
542 542 return self._path
543 543 def size(self):
544 544 return self._filelog.size(self._filerev)
545 545
546 546 def isbinary(self):
547 547 try:
548 548 return util.binary(self.data())
549 549 except IOError:
550 550 return False
551 551
552 552 def cmp(self, fctx):
553 553 """compare with other file context
554 554
555 555 returns True if different than fctx.
556 556 """
557 557 if (fctx._filerev is None
558 558 and (self._repo._encodefilterpats
559 559 # if file data starts with '\1\n', empty metadata block is
560 560 # prepended, which adds 4 bytes to filelog.size().
561 561 or self.size() - 4 == fctx.size())
562 562 or self.size() == fctx.size()):
563 563 return self._filelog.cmp(self._filenode, fctx.data())
564 564
565 565 return True
566 566
567 567 def renamed(self):
568 568 """check if file was actually renamed in this changeset revision
569 569
570 570 If rename logged in file revision, we report copy for changeset only
571 571 if file revisions linkrev points back to the changeset in question
572 572 or both changeset parents contain different file revisions.
573 573 """
574 574
575 575 renamed = self._filelog.renamed(self._filenode)
576 576 if not renamed:
577 577 return renamed
578 578
579 579 if self.rev() == self.linkrev():
580 580 return renamed
581 581
582 582 name = self.path()
583 583 fnode = self._filenode
584 584 for p in self._changectx.parents():
585 585 try:
586 586 if fnode == p.filenode(name):
587 587 return None
588 588 except error.LookupError:
589 589 pass
590 590 return renamed
591 591
592 592 def parents(self):
593 593 p = self._path
594 594 fl = self._filelog
595 595 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
596 596
597 597 r = self._filelog.renamed(self._filenode)
598 598 if r:
599 599 pl[0] = (r[0], r[1], None)
600 600
601 601 return [filectx(self._repo, p, fileid=n, filelog=l)
602 602 for p, n, l in pl if n != nullid]
603 603
604 604 def p1(self):
605 605 return self.parents()[0]
606 606
607 607 def p2(self):
608 608 p = self.parents()
609 609 if len(p) == 2:
610 610 return p[1]
611 611 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
612 612
613 613 def children(self):
614 614 # hard for renames
615 615 c = self._filelog.children(self._filenode)
616 616 return [filectx(self._repo, self._path, fileid=x,
617 617 filelog=self._filelog) for x in c]
618 618
619 619 def annotate(self, follow=False, linenumber=None, diffopts=None):
620 620 '''returns a list of tuples of (ctx, line) for each line
621 621 in the file, where ctx is the filectx of the node where
622 622 that line was last changed.
623 623 This returns tuples of ((ctx, linenumber), line) for each line,
624 624 if "linenumber" parameter is NOT "None".
625 625 In such tuples, linenumber means one at the first appearance
626 626 in the managed file.
627 627 To reduce annotation cost,
628 628 this returns fixed value(False is used) as linenumber,
629 629 if "linenumber" parameter is "False".'''
630 630
631 631 def decorate_compat(text, rev):
632 632 return ([rev] * len(text.splitlines()), text)
633 633
634 634 def without_linenumber(text, rev):
635 635 return ([(rev, False)] * len(text.splitlines()), text)
636 636
637 637 def with_linenumber(text, rev):
638 638 size = len(text.splitlines())
639 639 return ([(rev, i) for i in xrange(1, size + 1)], text)
640 640
641 641 decorate = (((linenumber is None) and decorate_compat) or
642 642 (linenumber and with_linenumber) or
643 643 without_linenumber)
644 644
645 645 def pair(parent, child):
646 646 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
647 647 refine=True)
648 648 for (a1, a2, b1, b2), t in blocks:
649 649 # Changed blocks ('!') or blocks made only of blank lines ('~')
650 650 # belong to the child.
651 651 if t == '=':
652 652 child[0][b1:b2] = parent[0][a1:a2]
653 653 return child
654 654
655 655 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
656 656 def getctx(path, fileid):
657 657 log = path == self._path and self._filelog or getlog(path)
658 658 return filectx(self._repo, path, fileid=fileid, filelog=log)
659 659 getctx = util.lrucachefunc(getctx)
660 660
661 661 def parents(f):
662 662 # we want to reuse filectx objects as much as possible
663 663 p = f._path
664 664 if f._filerev is None: # working dir
665 665 pl = [(n.path(), n.filerev()) for n in f.parents()]
666 666 else:
667 667 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
668 668
669 669 if follow:
670 670 r = f.renamed()
671 671 if r:
672 672 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
673 673
674 674 return [getctx(p, n) for p, n in pl if n != nullrev]
675 675
676 676 # use linkrev to find the first changeset where self appeared
677 677 if self.rev() != self.linkrev():
678 678 base = self.filectx(self.filerev())
679 679 else:
680 680 base = self
681 681
682 682 # This algorithm would prefer to be recursive, but Python is a
683 683 # bit recursion-hostile. Instead we do an iterative
684 684 # depth-first search.
685 685
686 686 visit = [base]
687 687 hist = {}
688 688 pcache = {}
689 689 needed = {base: 1}
690 690 while visit:
691 691 f = visit[-1]
692 692 pcached = f in pcache
693 693 if not pcached:
694 694 pcache[f] = parents(f)
695 695
696 696 ready = True
697 697 pl = pcache[f]
698 698 for p in pl:
699 699 if p not in hist:
700 700 ready = False
701 701 visit.append(p)
702 702 if not pcached:
703 703 needed[p] = needed.get(p, 0) + 1
704 704 if ready:
705 705 visit.pop()
706 706 reusable = f in hist
707 707 if reusable:
708 708 curr = hist[f]
709 709 else:
710 710 curr = decorate(f.data(), f)
711 711 for p in pl:
712 712 if not reusable:
713 713 curr = pair(hist[p], curr)
714 714 if needed[p] == 1:
715 715 del hist[p]
716 del needed[p]
716 717 else:
717 718 needed[p] -= 1
718 719
719 720 hist[f] = curr
720 721 pcache[f] = []
721 722
722 723 return zip(hist[base][0], hist[base][1].splitlines(True))
723 724
724 725 def ancestor(self, fc2, actx):
725 726 """
726 727 find the common ancestor file context, if any, of self, and fc2
727 728
728 729 actx must be the changectx of the common ancestor
729 730 of self's and fc2's respective changesets.
730 731 """
731 732
732 733 # the easy case: no (relevant) renames
733 734 if fc2.path() == self.path() and self.path() in actx:
734 735 return actx[self.path()]
735 736
736 737 # the next easiest cases: unambiguous predecessor (name trumps
737 738 # history)
738 739 if self.path() in actx and fc2.path() not in actx:
739 740 return actx[self.path()]
740 741 if fc2.path() in actx and self.path() not in actx:
741 742 return actx[fc2.path()]
742 743
743 744 # prime the ancestor cache for the working directory
744 745 acache = {}
745 746 for c in (self, fc2):
746 747 if c._filerev is None:
747 748 pl = [(n.path(), n.filenode()) for n in c.parents()]
748 749 acache[(c._path, None)] = pl
749 750
750 751 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
751 752 def parents(vertex):
752 753 if vertex in acache:
753 754 return acache[vertex]
754 755 f, n = vertex
755 756 if f not in flcache:
756 757 flcache[f] = self._repo.file(f)
757 758 fl = flcache[f]
758 759 pl = [(f, p) for p in fl.parents(n) if p != nullid]
759 760 re = fl.renamed(n)
760 761 if re:
761 762 pl.append(re)
762 763 acache[vertex] = pl
763 764 return pl
764 765
765 766 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
766 767 v = ancestor.genericancestor(a, b, parents)
767 768 if v:
768 769 f, n = v
769 770 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
770 771
771 772 return None
772 773
773 774 def ancestors(self, followfirst=False):
774 775 visit = {}
775 776 c = self
776 777 cut = followfirst and 1 or None
777 778 while True:
778 779 for parent in c.parents()[:cut]:
779 780 visit[(parent.rev(), parent.node())] = parent
780 781 if not visit:
781 782 break
782 783 c = visit.pop(max(visit))
783 784 yield c
784 785
785 786 def copies(self, c2):
786 787 if not util.safehasattr(self, "_copycache"):
787 788 self._copycache = {}
788 789 sc2 = str(c2)
789 790 if sc2 not in self._copycache:
790 791 self._copycache[sc2] = copies.pathcopies(c2)
791 792 return self._copycache[sc2]
792 793
793 794 class workingctx(changectx):
794 795 """A workingctx object makes access to data related to
795 796 the current working directory convenient.
796 797 date - any valid date string or (unixtime, offset), or None.
797 798 user - username string, or None.
798 799 extra - a dictionary of extra values, or None.
799 800 changes - a list of file lists as returned by localrepo.status()
800 801 or None to use the repository status.
801 802 """
802 803 def __init__(self, repo, text="", user=None, date=None, extra=None,
803 804 changes=None):
804 805 self._repo = repo
805 806 self._rev = None
806 807 self._node = None
807 808 self._text = text
808 809 if date:
809 810 self._date = util.parsedate(date)
810 811 if user:
811 812 self._user = user
812 813 if changes:
813 814 self._status = list(changes[:4])
814 815 self._unknown = changes[4]
815 816 self._ignored = changes[5]
816 817 self._clean = changes[6]
817 818 else:
818 819 self._unknown = None
819 820 self._ignored = None
820 821 self._clean = None
821 822
822 823 self._extra = {}
823 824 if extra:
824 825 self._extra = extra.copy()
825 826 if 'branch' not in self._extra:
826 827 try:
827 828 branch = encoding.fromlocal(self._repo.dirstate.branch())
828 829 except UnicodeDecodeError:
829 830 raise util.Abort(_('branch name not in UTF-8!'))
830 831 self._extra['branch'] = branch
831 832 if self._extra['branch'] == '':
832 833 self._extra['branch'] = 'default'
833 834
834 835 def __str__(self):
835 836 return str(self._parents[0]) + "+"
836 837
837 838 def __repr__(self):
838 839 return "<workingctx %s>" % str(self)
839 840
840 841 def __nonzero__(self):
841 842 return True
842 843
843 844 def __contains__(self, key):
844 845 return self._repo.dirstate[key] not in "?r"
845 846
846 847 def _buildflagfunc(self):
847 848 # Create a fallback function for getting file flags when the
848 849 # filesystem doesn't support them
849 850
850 851 copiesget = self._repo.dirstate.copies().get
851 852
852 853 if len(self._parents) < 2:
853 854 # when we have one parent, it's easy: copy from parent
854 855 man = self._parents[0].manifest()
855 856 def func(f):
856 857 f = copiesget(f, f)
857 858 return man.flags(f)
858 859 else:
859 860 # merges are tricky: we try to reconstruct the unstored
860 861 # result from the merge (issue1802)
861 862 p1, p2 = self._parents
862 863 pa = p1.ancestor(p2)
863 864 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
864 865
865 866 def func(f):
866 867 f = copiesget(f, f) # may be wrong for merges with copies
867 868 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
868 869 if fl1 == fl2:
869 870 return fl1
870 871 if fl1 == fla:
871 872 return fl2
872 873 if fl2 == fla:
873 874 return fl1
874 875 return '' # punt for conflicts
875 876
876 877 return func
877 878
878 879 @propertycache
879 880 def _flagfunc(self):
880 881 return self._repo.dirstate.flagfunc(self._buildflagfunc)
881 882
882 883 @propertycache
883 884 def _manifest(self):
884 885 """generate a manifest corresponding to the working directory"""
885 886
886 887 man = self._parents[0].manifest().copy()
887 888 if len(self._parents) > 1:
888 889 man2 = self.p2().manifest()
889 890 def getman(f):
890 891 if f in man:
891 892 return man
892 893 return man2
893 894 else:
894 895 getman = lambda f: man
895 896
896 897 copied = self._repo.dirstate.copies()
897 898 ff = self._flagfunc
898 899 modified, added, removed, deleted = self._status
899 900 for i, l in (("a", added), ("m", modified)):
900 901 for f in l:
901 902 orig = copied.get(f, f)
902 903 man[f] = getman(orig).get(orig, nullid) + i
903 904 try:
904 905 man.set(f, ff(f))
905 906 except OSError:
906 907 pass
907 908
908 909 for f in deleted + removed:
909 910 if f in man:
910 911 del man[f]
911 912
912 913 return man
913 914
914 915 def __iter__(self):
915 916 d = self._repo.dirstate
916 917 for f in d:
917 918 if d[f] != 'r':
918 919 yield f
919 920
920 921 @propertycache
921 922 def _status(self):
922 923 return self._repo.status()[:4]
923 924
924 925 @propertycache
925 926 def _user(self):
926 927 return self._repo.ui.username()
927 928
928 929 @propertycache
929 930 def _date(self):
930 931 return util.makedate()
931 932
932 933 @propertycache
933 934 def _parents(self):
934 935 p = self._repo.dirstate.parents()
935 936 if p[1] == nullid:
936 937 p = p[:-1]
937 938 return [changectx(self._repo, x) for x in p]
938 939
939 940 def status(self, ignored=False, clean=False, unknown=False):
940 941 """Explicit status query
941 942 Unless this method is used to query the working copy status, the
942 943 _status property will implicitly read the status using its default
943 944 arguments."""
944 945 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
945 946 self._unknown = self._ignored = self._clean = None
946 947 if unknown:
947 948 self._unknown = stat[4]
948 949 if ignored:
949 950 self._ignored = stat[5]
950 951 if clean:
951 952 self._clean = stat[6]
952 953 self._status = stat[:4]
953 954 return stat
954 955
955 956 def manifest(self):
956 957 return self._manifest
957 958 def user(self):
958 959 return self._user or self._repo.ui.username()
959 960 def date(self):
960 961 return self._date
961 962 def description(self):
962 963 return self._text
963 964 def files(self):
964 965 return sorted(self._status[0] + self._status[1] + self._status[2])
965 966
966 967 def modified(self):
967 968 return self._status[0]
968 969 def added(self):
969 970 return self._status[1]
970 971 def removed(self):
971 972 return self._status[2]
972 973 def deleted(self):
973 974 return self._status[3]
974 975 def unknown(self):
975 976 assert self._unknown is not None # must call status first
976 977 return self._unknown
977 978 def ignored(self):
978 979 assert self._ignored is not None # must call status first
979 980 return self._ignored
980 981 def clean(self):
981 982 assert self._clean is not None # must call status first
982 983 return self._clean
983 984 def branch(self):
984 985 return encoding.tolocal(self._extra['branch'])
985 986 def closesbranch(self):
986 987 return 'close' in self._extra
987 988 def extra(self):
988 989 return self._extra
989 990
990 991 def tags(self):
991 992 t = []
992 993 for p in self.parents():
993 994 t.extend(p.tags())
994 995 return t
995 996
996 997 def bookmarks(self):
997 998 b = []
998 999 for p in self.parents():
999 1000 b.extend(p.bookmarks())
1000 1001 return b
1001 1002
1002 1003 def phase(self):
1003 1004 phase = phases.draft # default phase to draft
1004 1005 for p in self.parents():
1005 1006 phase = max(phase, p.phase())
1006 1007 return phase
1007 1008
1008 1009 def hidden(self):
1009 1010 return False
1010 1011
1011 1012 def children(self):
1012 1013 return []
1013 1014
1014 1015 def flags(self, path):
1015 1016 if '_manifest' in self.__dict__:
1016 1017 try:
1017 1018 return self._manifest.flags(path)
1018 1019 except KeyError:
1019 1020 return ''
1020 1021
1021 1022 try:
1022 1023 return self._flagfunc(path)
1023 1024 except OSError:
1024 1025 return ''
1025 1026
1026 1027 def filectx(self, path, filelog=None):
1027 1028 """get a file context from the working directory"""
1028 1029 return workingfilectx(self._repo, path, workingctx=self,
1029 1030 filelog=filelog)
1030 1031
1031 1032 def ancestor(self, c2):
1032 1033 """return the ancestor context of self and c2"""
1033 1034 return self._parents[0].ancestor(c2) # punt on two parents for now
1034 1035
1035 1036 def walk(self, match):
1036 1037 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1037 1038 True, False))
1038 1039
1039 1040 def dirty(self, missing=False, merge=True, branch=True):
1040 1041 "check whether a working directory is modified"
1041 1042 # check subrepos first
1042 1043 for s in sorted(self.substate):
1043 1044 if self.sub(s).dirty():
1044 1045 return True
1045 1046 # check current working dir
1046 1047 return ((merge and self.p2()) or
1047 1048 (branch and self.branch() != self.p1().branch()) or
1048 1049 self.modified() or self.added() or self.removed() or
1049 1050 (missing and self.deleted()))
1050 1051
1051 1052 def add(self, list, prefix=""):
1052 1053 join = lambda f: os.path.join(prefix, f)
1053 1054 wlock = self._repo.wlock()
1054 1055 ui, ds = self._repo.ui, self._repo.dirstate
1055 1056 try:
1056 1057 rejected = []
1057 1058 for f in list:
1058 1059 scmutil.checkportable(ui, join(f))
1059 1060 p = self._repo.wjoin(f)
1060 1061 try:
1061 1062 st = os.lstat(p)
1062 1063 except OSError:
1063 1064 ui.warn(_("%s does not exist!\n") % join(f))
1064 1065 rejected.append(f)
1065 1066 continue
1066 1067 if st.st_size > 10000000:
1067 1068 ui.warn(_("%s: up to %d MB of RAM may be required "
1068 1069 "to manage this file\n"
1069 1070 "(use 'hg revert %s' to cancel the "
1070 1071 "pending addition)\n")
1071 1072 % (f, 3 * st.st_size // 1000000, join(f)))
1072 1073 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1073 1074 ui.warn(_("%s not added: only files and symlinks "
1074 1075 "supported currently\n") % join(f))
1075 1076 rejected.append(p)
1076 1077 elif ds[f] in 'amn':
1077 1078 ui.warn(_("%s already tracked!\n") % join(f))
1078 1079 elif ds[f] == 'r':
1079 1080 ds.normallookup(f)
1080 1081 else:
1081 1082 ds.add(f)
1082 1083 return rejected
1083 1084 finally:
1084 1085 wlock.release()
1085 1086
1086 1087 def forget(self, files, prefix=""):
1087 1088 join = lambda f: os.path.join(prefix, f)
1088 1089 wlock = self._repo.wlock()
1089 1090 try:
1090 1091 rejected = []
1091 1092 for f in files:
1092 1093 if f not in self._repo.dirstate:
1093 1094 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1094 1095 rejected.append(f)
1095 1096 elif self._repo.dirstate[f] != 'a':
1096 1097 self._repo.dirstate.remove(f)
1097 1098 else:
1098 1099 self._repo.dirstate.drop(f)
1099 1100 return rejected
1100 1101 finally:
1101 1102 wlock.release()
1102 1103
1103 1104 def ancestors(self):
1104 1105 for a in self._repo.changelog.ancestors(
1105 1106 [p.rev() for p in self._parents]):
1106 1107 yield changectx(self._repo, a)
1107 1108
1108 1109 def undelete(self, list):
1109 1110 pctxs = self.parents()
1110 1111 wlock = self._repo.wlock()
1111 1112 try:
1112 1113 for f in list:
1113 1114 if self._repo.dirstate[f] != 'r':
1114 1115 self._repo.ui.warn(_("%s not removed!\n") % f)
1115 1116 else:
1116 1117 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1117 1118 t = fctx.data()
1118 1119 self._repo.wwrite(f, t, fctx.flags())
1119 1120 self._repo.dirstate.normal(f)
1120 1121 finally:
1121 1122 wlock.release()
1122 1123
1123 1124 def copy(self, source, dest):
1124 1125 p = self._repo.wjoin(dest)
1125 1126 if not os.path.lexists(p):
1126 1127 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1127 1128 elif not (os.path.isfile(p) or os.path.islink(p)):
1128 1129 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1129 1130 "symbolic link\n") % dest)
1130 1131 else:
1131 1132 wlock = self._repo.wlock()
1132 1133 try:
1133 1134 if self._repo.dirstate[dest] in '?r':
1134 1135 self._repo.dirstate.add(dest)
1135 1136 self._repo.dirstate.copy(source, dest)
1136 1137 finally:
1137 1138 wlock.release()
1138 1139
1139 1140 def markcommitted(self, node):
1140 1141 """Perform post-commit cleanup necessary after committing this ctx
1141 1142
1142 1143 Specifically, this updates backing stores this working context
1143 1144 wraps to reflect the fact that the changes reflected by this
1144 1145 workingctx have been committed. For example, it marks
1145 1146 modified and added files as normal in the dirstate.
1146 1147
1147 1148 """
1148 1149
1149 1150 for f in self.modified() + self.added():
1150 1151 self._repo.dirstate.normal(f)
1151 1152 for f in self.removed():
1152 1153 self._repo.dirstate.drop(f)
1153 1154 self._repo.dirstate.setparents(node)
1154 1155
1155 1156 def dirs(self):
1156 1157 return self._repo.dirstate.dirs()
1157 1158
1158 1159 class workingfilectx(filectx):
1159 1160 """A workingfilectx object makes access to data related to a particular
1160 1161 file in the working directory convenient."""
1161 1162 def __init__(self, repo, path, filelog=None, workingctx=None):
1162 1163 """changeid can be a changeset revision, node, or tag.
1163 1164 fileid can be a file revision or node."""
1164 1165 self._repo = repo
1165 1166 self._path = path
1166 1167 self._changeid = None
1167 1168 self._filerev = self._filenode = None
1168 1169
1169 1170 if filelog:
1170 1171 self._filelog = filelog
1171 1172 if workingctx:
1172 1173 self._changectx = workingctx
1173 1174
1174 1175 @propertycache
1175 1176 def _changectx(self):
1176 1177 return workingctx(self._repo)
1177 1178
1178 1179 def __nonzero__(self):
1179 1180 return True
1180 1181
1181 1182 def __str__(self):
1182 1183 return "%s@%s" % (self.path(), self._changectx)
1183 1184
1184 1185 def __repr__(self):
1185 1186 return "<workingfilectx %s>" % str(self)
1186 1187
1187 1188 def data(self):
1188 1189 return self._repo.wread(self._path)
1189 1190 def renamed(self):
1190 1191 rp = self._repo.dirstate.copied(self._path)
1191 1192 if not rp:
1192 1193 return None
1193 1194 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1194 1195
1195 1196 def parents(self):
1196 1197 '''return parent filectxs, following copies if necessary'''
1197 1198 def filenode(ctx, path):
1198 1199 return ctx._manifest.get(path, nullid)
1199 1200
1200 1201 path = self._path
1201 1202 fl = self._filelog
1202 1203 pcl = self._changectx._parents
1203 1204 renamed = self.renamed()
1204 1205
1205 1206 if renamed:
1206 1207 pl = [renamed + (None,)]
1207 1208 else:
1208 1209 pl = [(path, filenode(pcl[0], path), fl)]
1209 1210
1210 1211 for pc in pcl[1:]:
1211 1212 pl.append((path, filenode(pc, path), fl))
1212 1213
1213 1214 return [filectx(self._repo, p, fileid=n, filelog=l)
1214 1215 for p, n, l in pl if n != nullid]
1215 1216
1216 1217 def children(self):
1217 1218 return []
1218 1219
1219 1220 def size(self):
1220 1221 return os.lstat(self._repo.wjoin(self._path)).st_size
1221 1222 def date(self):
1222 1223 t, tz = self._changectx.date()
1223 1224 try:
1224 1225 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1225 1226 except OSError, err:
1226 1227 if err.errno != errno.ENOENT:
1227 1228 raise
1228 1229 return (t, tz)
1229 1230
1230 1231 def cmp(self, fctx):
1231 1232 """compare with other file context
1232 1233
1233 1234 returns True if different than fctx.
1234 1235 """
1235 1236 # fctx should be a filectx (not a workingfilectx)
1236 1237 # invert comparison to reuse the same code path
1237 1238 return fctx.cmp(self)
1238 1239
1239 1240 class memctx(object):
1240 1241 """Use memctx to perform in-memory commits via localrepo.commitctx().
1241 1242
1242 1243 Revision information is supplied at initialization time while
1243 1244 related files data and is made available through a callback
1244 1245 mechanism. 'repo' is the current localrepo, 'parents' is a
1245 1246 sequence of two parent revisions identifiers (pass None for every
1246 1247 missing parent), 'text' is the commit message and 'files' lists
1247 1248 names of files touched by the revision (normalized and relative to
1248 1249 repository root).
1249 1250
1250 1251 filectxfn(repo, memctx, path) is a callable receiving the
1251 1252 repository, the current memctx object and the normalized path of
1252 1253 requested file, relative to repository root. It is fired by the
1253 1254 commit function for every file in 'files', but calls order is
1254 1255 undefined. If the file is available in the revision being
1255 1256 committed (updated or added), filectxfn returns a memfilectx
1256 1257 object. If the file was removed, filectxfn raises an
1257 1258 IOError. Moved files are represented by marking the source file
1258 1259 removed and the new file added with copy information (see
1259 1260 memfilectx).
1260 1261
1261 1262 user receives the committer name and defaults to current
1262 1263 repository username, date is the commit date in any format
1263 1264 supported by util.parsedate() and defaults to current date, extra
1264 1265 is a dictionary of metadata or is left empty.
1265 1266 """
1266 1267 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1267 1268 date=None, extra=None):
1268 1269 self._repo = repo
1269 1270 self._rev = None
1270 1271 self._node = None
1271 1272 self._text = text
1272 1273 self._date = date and util.parsedate(date) or util.makedate()
1273 1274 self._user = user
1274 1275 parents = [(p or nullid) for p in parents]
1275 1276 p1, p2 = parents
1276 1277 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1277 1278 files = sorted(set(files))
1278 1279 self._status = [files, [], [], [], []]
1279 1280 self._filectxfn = filectxfn
1280 1281
1281 1282 self._extra = extra and extra.copy() or {}
1282 1283 if self._extra.get('branch', '') == '':
1283 1284 self._extra['branch'] = 'default'
1284 1285
1285 1286 def __str__(self):
1286 1287 return str(self._parents[0]) + "+"
1287 1288
1288 1289 def __int__(self):
1289 1290 return self._rev
1290 1291
1291 1292 def __nonzero__(self):
1292 1293 return True
1293 1294
1294 1295 def __getitem__(self, key):
1295 1296 return self.filectx(key)
1296 1297
1297 1298 def p1(self):
1298 1299 return self._parents[0]
1299 1300 def p2(self):
1300 1301 return self._parents[1]
1301 1302
1302 1303 def user(self):
1303 1304 return self._user or self._repo.ui.username()
1304 1305 def date(self):
1305 1306 return self._date
1306 1307 def description(self):
1307 1308 return self._text
1308 1309 def files(self):
1309 1310 return self.modified()
1310 1311 def modified(self):
1311 1312 return self._status[0]
1312 1313 def added(self):
1313 1314 return self._status[1]
1314 1315 def removed(self):
1315 1316 return self._status[2]
1316 1317 def deleted(self):
1317 1318 return self._status[3]
1318 1319 def unknown(self):
1319 1320 return self._status[4]
1320 1321 def ignored(self):
1321 1322 return self._status[5]
1322 1323 def clean(self):
1323 1324 return self._status[6]
1324 1325 def branch(self):
1325 1326 return encoding.tolocal(self._extra['branch'])
1326 1327 def extra(self):
1327 1328 return self._extra
1328 1329 def flags(self, f):
1329 1330 return self[f].flags()
1330 1331
1331 1332 def parents(self):
1332 1333 """return contexts for each parent changeset"""
1333 1334 return self._parents
1334 1335
1335 1336 def filectx(self, path, filelog=None):
1336 1337 """get a file context from the working directory"""
1337 1338 return self._filectxfn(self._repo, self, path)
1338 1339
1339 1340 def commit(self):
1340 1341 """commit context to the repo"""
1341 1342 return self._repo.commitctx(self)
1342 1343
1343 1344 class memfilectx(object):
1344 1345 """memfilectx represents an in-memory file to commit.
1345 1346
1346 1347 See memctx for more details.
1347 1348 """
1348 1349 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1349 1350 """
1350 1351 path is the normalized file path relative to repository root.
1351 1352 data is the file content as a string.
1352 1353 islink is True if the file is a symbolic link.
1353 1354 isexec is True if the file is executable.
1354 1355 copied is the source file path if current file was copied in the
1355 1356 revision being committed, or None."""
1356 1357 self._path = path
1357 1358 self._data = data
1358 1359 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1359 1360 self._copied = None
1360 1361 if copied:
1361 1362 self._copied = (copied, nullid)
1362 1363
1363 1364 def __nonzero__(self):
1364 1365 return True
1365 1366 def __str__(self):
1366 1367 return "%s@%s" % (self.path(), self._changectx)
1367 1368 def path(self):
1368 1369 return self._path
1369 1370 def data(self):
1370 1371 return self._data
1371 1372 def flags(self):
1372 1373 return self._flags
1373 1374 def isexec(self):
1374 1375 return 'x' in self._flags
1375 1376 def islink(self):
1376 1377 return 'l' in self._flags
1377 1378 def renamed(self):
1378 1379 return self._copied
General Comments 0
You need to be logged in to leave comments. Login now