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