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