##// END OF EJS Templates
basefilectx: move isbinary from filectx
Sean Farley -
r19603:a92302f4 default
parent child Browse files
Show More
@@ -1,1409 +1,1409
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 426 @propertycache
427 427 def _changeid(self):
428 428 if '_changeid' in self.__dict__:
429 429 return self._changeid
430 430 elif '_changectx' in self.__dict__:
431 431 return self._changectx.rev()
432 432 else:
433 433 return self._filelog.linkrev(self._filerev)
434 434
435 435 @propertycache
436 436 def _filenode(self):
437 437 if '_fileid' in self.__dict__:
438 438 return self._filelog.lookup(self._fileid)
439 439 else:
440 440 return self._changectx.filenode(self._path)
441 441
442 442 @propertycache
443 443 def _filerev(self):
444 444 return self._filelog.rev(self._filenode)
445 445
446 446 @propertycache
447 447 def _repopath(self):
448 448 return self._path
449 449
450 450 def __nonzero__(self):
451 451 try:
452 452 self._filenode
453 453 return True
454 454 except error.LookupError:
455 455 # file is missing
456 456 return False
457 457
458 458 def __str__(self):
459 459 return "%s@%s" % (self.path(), short(self.node()))
460 460
461 461 def __repr__(self):
462 462 return "<%s %s>" % (type(self).__name__, str(self))
463 463
464 464 def __hash__(self):
465 465 try:
466 466 return hash((self._path, self._filenode))
467 467 except AttributeError:
468 468 return id(self)
469 469
470 470 def __eq__(self, other):
471 471 try:
472 472 return (type(self) == type(other) and self._path == other._path
473 473 and self._filenode == other._filenode)
474 474 except AttributeError:
475 475 return False
476 476
477 477 def __ne__(self, other):
478 478 return not (self == other)
479 479
480 480 def filerev(self):
481 481 return self._filerev
482 482 def filenode(self):
483 483 return self._filenode
484 484 def flags(self):
485 485 return self._changectx.flags(self._path)
486 486 def filelog(self):
487 487 return self._filelog
488 488 def rev(self):
489 489 return self._changeid
490 490 def linkrev(self):
491 491 return self._filelog.linkrev(self._filerev)
492 492 def node(self):
493 493 return self._changectx.node()
494 494 def hex(self):
495 495 return self._changectx.hex()
496 496 def user(self):
497 497 return self._changectx.user()
498 498 def date(self):
499 499 return self._changectx.date()
500 500 def files(self):
501 501 return self._changectx.files()
502 502 def description(self):
503 503 return self._changectx.description()
504 504 def branch(self):
505 505 return self._changectx.branch()
506 506 def extra(self):
507 507 return self._changectx.extra()
508 508 def phase(self):
509 509 return self._changectx.phase()
510 510 def phasestr(self):
511 511 return self._changectx.phasestr()
512 512 def manifest(self):
513 513 return self._changectx.manifest()
514 514 def changectx(self):
515 515 return self._changectx
516 516
517 517 def path(self):
518 518 return self._path
519 519
520 def isbinary(self):
521 try:
522 return util.binary(self.data())
523 except IOError:
524 return False
525
520 526 class filectx(basefilectx):
521 527 """A filecontext object makes access to data related to a particular
522 528 filerevision convenient."""
523 529 def __init__(self, repo, path, changeid=None, fileid=None,
524 530 filelog=None, changectx=None):
525 531 """changeid can be a changeset revision, node, or tag.
526 532 fileid can be a file revision or node."""
527 533 self._repo = repo
528 534 self._path = path
529 535
530 536 assert (changeid is not None
531 537 or fileid is not None
532 538 or changectx is not None), \
533 539 ("bad args: changeid=%r, fileid=%r, changectx=%r"
534 540 % (changeid, fileid, changectx))
535 541
536 542 if filelog is not None:
537 543 self._filelog = filelog
538 544
539 545 if changeid is not None:
540 546 self._changeid = changeid
541 547 if changectx is not None:
542 548 self._changectx = changectx
543 549 if fileid is not None:
544 550 self._fileid = fileid
545 551
546 552 @propertycache
547 553 def _changectx(self):
548 554 try:
549 555 return changectx(self._repo, self._changeid)
550 556 except error.RepoLookupError:
551 557 # Linkrev may point to any revision in the repository. When the
552 558 # repository is filtered this may lead to `filectx` trying to build
553 559 # `changectx` for filtered revision. In such case we fallback to
554 560 # creating `changectx` on the unfiltered version of the reposition.
555 561 # This fallback should not be an issue because `changectx` from
556 562 # `filectx` are not used in complex operations that care about
557 563 # filtering.
558 564 #
559 565 # This fallback is a cheap and dirty fix that prevent several
560 566 # crashes. It does not ensure the behavior is correct. However the
561 567 # behavior was not correct before filtering either and "incorrect
562 568 # behavior" is seen as better as "crash"
563 569 #
564 570 # Linkrevs have several serious troubles with filtering that are
565 571 # complicated to solve. Proper handling of the issue here should be
566 572 # considered when solving linkrev issue are on the table.
567 573 return changectx(self._repo.unfiltered(), self._changeid)
568 574
569 575 def filectx(self, fileid):
570 576 '''opens an arbitrary revision of the file without
571 577 opening a new filelog'''
572 578 return filectx(self._repo, self._path, fileid=fileid,
573 579 filelog=self._filelog)
574 580
575 581 def data(self):
576 582 return self._filelog.read(self._filenode)
577 583 def size(self):
578 584 return self._filelog.size(self._filerev)
579 585
580 def isbinary(self):
581 try:
582 return util.binary(self.data())
583 except IOError:
584 return False
585
586 586 def cmp(self, fctx):
587 587 """compare with other file context
588 588
589 589 returns True if different than fctx.
590 590 """
591 591 if (fctx._filerev is None
592 592 and (self._repo._encodefilterpats
593 593 # if file data starts with '\1\n', empty metadata block is
594 594 # prepended, which adds 4 bytes to filelog.size().
595 595 or self.size() - 4 == fctx.size())
596 596 or self.size() == fctx.size()):
597 597 return self._filelog.cmp(self._filenode, fctx.data())
598 598
599 599 return True
600 600
601 601 def renamed(self):
602 602 """check if file was actually renamed in this changeset revision
603 603
604 604 If rename logged in file revision, we report copy for changeset only
605 605 if file revisions linkrev points back to the changeset in question
606 606 or both changeset parents contain different file revisions.
607 607 """
608 608
609 609 renamed = self._filelog.renamed(self._filenode)
610 610 if not renamed:
611 611 return renamed
612 612
613 613 if self.rev() == self.linkrev():
614 614 return renamed
615 615
616 616 name = self.path()
617 617 fnode = self._filenode
618 618 for p in self._changectx.parents():
619 619 try:
620 620 if fnode == p.filenode(name):
621 621 return None
622 622 except error.LookupError:
623 623 pass
624 624 return renamed
625 625
626 626 def parents(self):
627 627 p = self._path
628 628 fl = self._filelog
629 629 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
630 630
631 631 r = self._filelog.renamed(self._filenode)
632 632 if r:
633 633 pl[0] = (r[0], r[1], None)
634 634
635 635 return [filectx(self._repo, p, fileid=n, filelog=l)
636 636 for p, n, l in pl if n != nullid]
637 637
638 638 def p1(self):
639 639 return self.parents()[0]
640 640
641 641 def p2(self):
642 642 p = self.parents()
643 643 if len(p) == 2:
644 644 return p[1]
645 645 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
646 646
647 647 def children(self):
648 648 # hard for renames
649 649 c = self._filelog.children(self._filenode)
650 650 return [filectx(self._repo, self._path, fileid=x,
651 651 filelog=self._filelog) for x in c]
652 652
653 653 def annotate(self, follow=False, linenumber=None, diffopts=None):
654 654 '''returns a list of tuples of (ctx, line) for each line
655 655 in the file, where ctx is the filectx of the node where
656 656 that line was last changed.
657 657 This returns tuples of ((ctx, linenumber), line) for each line,
658 658 if "linenumber" parameter is NOT "None".
659 659 In such tuples, linenumber means one at the first appearance
660 660 in the managed file.
661 661 To reduce annotation cost,
662 662 this returns fixed value(False is used) as linenumber,
663 663 if "linenumber" parameter is "False".'''
664 664
665 665 def decorate_compat(text, rev):
666 666 return ([rev] * len(text.splitlines()), text)
667 667
668 668 def without_linenumber(text, rev):
669 669 return ([(rev, False)] * len(text.splitlines()), text)
670 670
671 671 def with_linenumber(text, rev):
672 672 size = len(text.splitlines())
673 673 return ([(rev, i) for i in xrange(1, size + 1)], text)
674 674
675 675 decorate = (((linenumber is None) and decorate_compat) or
676 676 (linenumber and with_linenumber) or
677 677 without_linenumber)
678 678
679 679 def pair(parent, child):
680 680 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
681 681 refine=True)
682 682 for (a1, a2, b1, b2), t in blocks:
683 683 # Changed blocks ('!') or blocks made only of blank lines ('~')
684 684 # belong to the child.
685 685 if t == '=':
686 686 child[0][b1:b2] = parent[0][a1:a2]
687 687 return child
688 688
689 689 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
690 690
691 691 def parents(f):
692 692 pl = f.parents()
693 693
694 694 # Don't return renamed parents if we aren't following.
695 695 if not follow:
696 696 pl = [p for p in pl if p.path() == f.path()]
697 697
698 698 # renamed filectx won't have a filelog yet, so set it
699 699 # from the cache to save time
700 700 for p in pl:
701 701 if not '_filelog' in p.__dict__:
702 702 p._filelog = getlog(p.path())
703 703
704 704 return pl
705 705
706 706 # use linkrev to find the first changeset where self appeared
707 707 if self.rev() != self.linkrev():
708 708 base = self.filectx(self.filenode())
709 709 else:
710 710 base = self
711 711
712 712 # This algorithm would prefer to be recursive, but Python is a
713 713 # bit recursion-hostile. Instead we do an iterative
714 714 # depth-first search.
715 715
716 716 visit = [base]
717 717 hist = {}
718 718 pcache = {}
719 719 needed = {base: 1}
720 720 while visit:
721 721 f = visit[-1]
722 722 pcached = f in pcache
723 723 if not pcached:
724 724 pcache[f] = parents(f)
725 725
726 726 ready = True
727 727 pl = pcache[f]
728 728 for p in pl:
729 729 if p not in hist:
730 730 ready = False
731 731 visit.append(p)
732 732 if not pcached:
733 733 needed[p] = needed.get(p, 0) + 1
734 734 if ready:
735 735 visit.pop()
736 736 reusable = f in hist
737 737 if reusable:
738 738 curr = hist[f]
739 739 else:
740 740 curr = decorate(f.data(), f)
741 741 for p in pl:
742 742 if not reusable:
743 743 curr = pair(hist[p], curr)
744 744 if needed[p] == 1:
745 745 del hist[p]
746 746 del needed[p]
747 747 else:
748 748 needed[p] -= 1
749 749
750 750 hist[f] = curr
751 751 pcache[f] = []
752 752
753 753 return zip(hist[base][0], hist[base][1].splitlines(True))
754 754
755 755 def ancestor(self, fc2, actx):
756 756 """
757 757 find the common ancestor file context, if any, of self, and fc2
758 758
759 759 actx must be the changectx of the common ancestor
760 760 of self's and fc2's respective changesets.
761 761 """
762 762
763 763 # the easy case: no (relevant) renames
764 764 if fc2.path() == self.path() and self.path() in actx:
765 765 return actx[self.path()]
766 766
767 767 # the next easiest cases: unambiguous predecessor (name trumps
768 768 # history)
769 769 if self.path() in actx and fc2.path() not in actx:
770 770 return actx[self.path()]
771 771 if fc2.path() in actx and self.path() not in actx:
772 772 return actx[fc2.path()]
773 773
774 774 # prime the ancestor cache for the working directory
775 775 acache = {}
776 776 for c in (self, fc2):
777 777 if c.filenode() is None:
778 778 pl = [(n.path(), n.filenode()) for n in c.parents()]
779 779 acache[(c._path, None)] = pl
780 780
781 781 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
782 782 def parents(vertex):
783 783 if vertex in acache:
784 784 return acache[vertex]
785 785 f, n = vertex
786 786 if f not in flcache:
787 787 flcache[f] = self._repo.file(f)
788 788 fl = flcache[f]
789 789 pl = [(f, p) for p in fl.parents(n) if p != nullid]
790 790 re = fl.renamed(n)
791 791 if re:
792 792 pl.append(re)
793 793 acache[vertex] = pl
794 794 return pl
795 795
796 796 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
797 797 v = ancestor.genericancestor(a, b, parents)
798 798 if v:
799 799 f, n = v
800 800 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
801 801
802 802 return None
803 803
804 804 def ancestors(self, followfirst=False):
805 805 visit = {}
806 806 c = self
807 807 cut = followfirst and 1 or None
808 808 while True:
809 809 for parent in c.parents()[:cut]:
810 810 visit[(parent.rev(), parent.node())] = parent
811 811 if not visit:
812 812 break
813 813 c = visit.pop(max(visit))
814 814 yield c
815 815
816 816 def copies(self, c2):
817 817 if not util.safehasattr(self, "_copycache"):
818 818 self._copycache = {}
819 819 sc2 = str(c2)
820 820 if sc2 not in self._copycache:
821 821 self._copycache[sc2] = copies.pathcopies(c2)
822 822 return self._copycache[sc2]
823 823
824 824 class workingctx(basectx):
825 825 """A workingctx object makes access to data related to
826 826 the current working directory convenient.
827 827 date - any valid date string or (unixtime, offset), or None.
828 828 user - username string, or None.
829 829 extra - a dictionary of extra values, or None.
830 830 changes - a list of file lists as returned by localrepo.status()
831 831 or None to use the repository status.
832 832 """
833 833 def __init__(self, repo, text="", user=None, date=None, extra=None,
834 834 changes=None):
835 835 self._repo = repo
836 836 self._rev = None
837 837 self._node = None
838 838 self._text = text
839 839 if date:
840 840 self._date = util.parsedate(date)
841 841 if user:
842 842 self._user = user
843 843 if changes:
844 844 self._status = list(changes[:4])
845 845 self._unknown = changes[4]
846 846 self._ignored = changes[5]
847 847 self._clean = changes[6]
848 848 else:
849 849 self._unknown = None
850 850 self._ignored = None
851 851 self._clean = None
852 852
853 853 self._extra = {}
854 854 if extra:
855 855 self._extra = extra.copy()
856 856 if 'branch' not in self._extra:
857 857 try:
858 858 branch = encoding.fromlocal(self._repo.dirstate.branch())
859 859 except UnicodeDecodeError:
860 860 raise util.Abort(_('branch name not in UTF-8!'))
861 861 self._extra['branch'] = branch
862 862 if self._extra['branch'] == '':
863 863 self._extra['branch'] = 'default'
864 864
865 865 def __str__(self):
866 866 return str(self._parents[0]) + "+"
867 867
868 868 def __repr__(self):
869 869 return "<workingctx %s>" % str(self)
870 870
871 871 def __nonzero__(self):
872 872 return True
873 873
874 874 def __contains__(self, key):
875 875 return self._repo.dirstate[key] not in "?r"
876 876
877 877 def _buildflagfunc(self):
878 878 # Create a fallback function for getting file flags when the
879 879 # filesystem doesn't support them
880 880
881 881 copiesget = self._repo.dirstate.copies().get
882 882
883 883 if len(self._parents) < 2:
884 884 # when we have one parent, it's easy: copy from parent
885 885 man = self._parents[0].manifest()
886 886 def func(f):
887 887 f = copiesget(f, f)
888 888 return man.flags(f)
889 889 else:
890 890 # merges are tricky: we try to reconstruct the unstored
891 891 # result from the merge (issue1802)
892 892 p1, p2 = self._parents
893 893 pa = p1.ancestor(p2)
894 894 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
895 895
896 896 def func(f):
897 897 f = copiesget(f, f) # may be wrong for merges with copies
898 898 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
899 899 if fl1 == fl2:
900 900 return fl1
901 901 if fl1 == fla:
902 902 return fl2
903 903 if fl2 == fla:
904 904 return fl1
905 905 return '' # punt for conflicts
906 906
907 907 return func
908 908
909 909 @propertycache
910 910 def _flagfunc(self):
911 911 return self._repo.dirstate.flagfunc(self._buildflagfunc)
912 912
913 913 @propertycache
914 914 def _manifest(self):
915 915 """generate a manifest corresponding to the working directory"""
916 916
917 917 man = self._parents[0].manifest().copy()
918 918 if len(self._parents) > 1:
919 919 man2 = self.p2().manifest()
920 920 def getman(f):
921 921 if f in man:
922 922 return man
923 923 return man2
924 924 else:
925 925 getman = lambda f: man
926 926
927 927 copied = self._repo.dirstate.copies()
928 928 ff = self._flagfunc
929 929 modified, added, removed, deleted = self._status
930 930 for i, l in (("a", added), ("m", modified)):
931 931 for f in l:
932 932 orig = copied.get(f, f)
933 933 man[f] = getman(orig).get(orig, nullid) + i
934 934 try:
935 935 man.set(f, ff(f))
936 936 except OSError:
937 937 pass
938 938
939 939 for f in deleted + removed:
940 940 if f in man:
941 941 del man[f]
942 942
943 943 return man
944 944
945 945 def __iter__(self):
946 946 d = self._repo.dirstate
947 947 for f in d:
948 948 if d[f] != 'r':
949 949 yield f
950 950
951 951 @propertycache
952 952 def _status(self):
953 953 return self._repo.status()[:4]
954 954
955 955 @propertycache
956 956 def _user(self):
957 957 return self._repo.ui.username()
958 958
959 959 @propertycache
960 960 def _date(self):
961 961 return util.makedate()
962 962
963 963 @propertycache
964 964 def _parents(self):
965 965 p = self._repo.dirstate.parents()
966 966 if p[1] == nullid:
967 967 p = p[:-1]
968 968 return [changectx(self._repo, x) for x in p]
969 969
970 970 def status(self, ignored=False, clean=False, unknown=False):
971 971 """Explicit status query
972 972 Unless this method is used to query the working copy status, the
973 973 _status property will implicitly read the status using its default
974 974 arguments."""
975 975 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
976 976 self._unknown = self._ignored = self._clean = None
977 977 if unknown:
978 978 self._unknown = stat[4]
979 979 if ignored:
980 980 self._ignored = stat[5]
981 981 if clean:
982 982 self._clean = stat[6]
983 983 self._status = stat[:4]
984 984 return stat
985 985
986 986 def manifest(self):
987 987 return self._manifest
988 988 def user(self):
989 989 return self._user or self._repo.ui.username()
990 990 def date(self):
991 991 return self._date
992 992 def description(self):
993 993 return self._text
994 994 def files(self):
995 995 return sorted(self._status[0] + self._status[1] + self._status[2])
996 996
997 997 def modified(self):
998 998 return self._status[0]
999 999 def added(self):
1000 1000 return self._status[1]
1001 1001 def removed(self):
1002 1002 return self._status[2]
1003 1003 def deleted(self):
1004 1004 return self._status[3]
1005 1005 def unknown(self):
1006 1006 assert self._unknown is not None # must call status first
1007 1007 return self._unknown
1008 1008 def ignored(self):
1009 1009 assert self._ignored is not None # must call status first
1010 1010 return self._ignored
1011 1011 def clean(self):
1012 1012 assert self._clean is not None # must call status first
1013 1013 return self._clean
1014 1014 def branch(self):
1015 1015 return encoding.tolocal(self._extra['branch'])
1016 1016 def closesbranch(self):
1017 1017 return 'close' in self._extra
1018 1018 def extra(self):
1019 1019 return self._extra
1020 1020
1021 1021 def tags(self):
1022 1022 t = []
1023 1023 for p in self.parents():
1024 1024 t.extend(p.tags())
1025 1025 return t
1026 1026
1027 1027 def bookmarks(self):
1028 1028 b = []
1029 1029 for p in self.parents():
1030 1030 b.extend(p.bookmarks())
1031 1031 return b
1032 1032
1033 1033 def phase(self):
1034 1034 phase = phases.draft # default phase to draft
1035 1035 for p in self.parents():
1036 1036 phase = max(phase, p.phase())
1037 1037 return phase
1038 1038
1039 1039 def hidden(self):
1040 1040 return False
1041 1041
1042 1042 def children(self):
1043 1043 return []
1044 1044
1045 1045 def flags(self, path):
1046 1046 if '_manifest' in self.__dict__:
1047 1047 try:
1048 1048 return self._manifest.flags(path)
1049 1049 except KeyError:
1050 1050 return ''
1051 1051
1052 1052 try:
1053 1053 return self._flagfunc(path)
1054 1054 except OSError:
1055 1055 return ''
1056 1056
1057 1057 def filectx(self, path, filelog=None):
1058 1058 """get a file context from the working directory"""
1059 1059 return workingfilectx(self._repo, path, workingctx=self,
1060 1060 filelog=filelog)
1061 1061
1062 1062 def ancestor(self, c2):
1063 1063 """return the ancestor context of self and c2"""
1064 1064 return self._parents[0].ancestor(c2) # punt on two parents for now
1065 1065
1066 1066 def walk(self, match):
1067 1067 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1068 1068 True, False))
1069 1069
1070 1070 def dirty(self, missing=False, merge=True, branch=True):
1071 1071 "check whether a working directory is modified"
1072 1072 # check subrepos first
1073 1073 for s in sorted(self.substate):
1074 1074 if self.sub(s).dirty():
1075 1075 return True
1076 1076 # check current working dir
1077 1077 return ((merge and self.p2()) or
1078 1078 (branch and self.branch() != self.p1().branch()) or
1079 1079 self.modified() or self.added() or self.removed() or
1080 1080 (missing and self.deleted()))
1081 1081
1082 1082 def add(self, list, prefix=""):
1083 1083 join = lambda f: os.path.join(prefix, f)
1084 1084 wlock = self._repo.wlock()
1085 1085 ui, ds = self._repo.ui, self._repo.dirstate
1086 1086 try:
1087 1087 rejected = []
1088 1088 for f in list:
1089 1089 scmutil.checkportable(ui, join(f))
1090 1090 p = self._repo.wjoin(f)
1091 1091 try:
1092 1092 st = os.lstat(p)
1093 1093 except OSError:
1094 1094 ui.warn(_("%s does not exist!\n") % join(f))
1095 1095 rejected.append(f)
1096 1096 continue
1097 1097 if st.st_size > 10000000:
1098 1098 ui.warn(_("%s: up to %d MB of RAM may be required "
1099 1099 "to manage this file\n"
1100 1100 "(use 'hg revert %s' to cancel the "
1101 1101 "pending addition)\n")
1102 1102 % (f, 3 * st.st_size // 1000000, join(f)))
1103 1103 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1104 1104 ui.warn(_("%s not added: only files and symlinks "
1105 1105 "supported currently\n") % join(f))
1106 1106 rejected.append(p)
1107 1107 elif ds[f] in 'amn':
1108 1108 ui.warn(_("%s already tracked!\n") % join(f))
1109 1109 elif ds[f] == 'r':
1110 1110 ds.normallookup(f)
1111 1111 else:
1112 1112 ds.add(f)
1113 1113 return rejected
1114 1114 finally:
1115 1115 wlock.release()
1116 1116
1117 1117 def forget(self, files, prefix=""):
1118 1118 join = lambda f: os.path.join(prefix, f)
1119 1119 wlock = self._repo.wlock()
1120 1120 try:
1121 1121 rejected = []
1122 1122 for f in files:
1123 1123 if f not in self._repo.dirstate:
1124 1124 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1125 1125 rejected.append(f)
1126 1126 elif self._repo.dirstate[f] != 'a':
1127 1127 self._repo.dirstate.remove(f)
1128 1128 else:
1129 1129 self._repo.dirstate.drop(f)
1130 1130 return rejected
1131 1131 finally:
1132 1132 wlock.release()
1133 1133
1134 1134 def ancestors(self):
1135 1135 for a in self._repo.changelog.ancestors(
1136 1136 [p.rev() for p in self._parents]):
1137 1137 yield changectx(self._repo, a)
1138 1138
1139 1139 def undelete(self, list):
1140 1140 pctxs = self.parents()
1141 1141 wlock = self._repo.wlock()
1142 1142 try:
1143 1143 for f in list:
1144 1144 if self._repo.dirstate[f] != 'r':
1145 1145 self._repo.ui.warn(_("%s not removed!\n") % f)
1146 1146 else:
1147 1147 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1148 1148 t = fctx.data()
1149 1149 self._repo.wwrite(f, t, fctx.flags())
1150 1150 self._repo.dirstate.normal(f)
1151 1151 finally:
1152 1152 wlock.release()
1153 1153
1154 1154 def copy(self, source, dest):
1155 1155 p = self._repo.wjoin(dest)
1156 1156 if not os.path.lexists(p):
1157 1157 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1158 1158 elif not (os.path.isfile(p) or os.path.islink(p)):
1159 1159 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1160 1160 "symbolic link\n") % dest)
1161 1161 else:
1162 1162 wlock = self._repo.wlock()
1163 1163 try:
1164 1164 if self._repo.dirstate[dest] in '?r':
1165 1165 self._repo.dirstate.add(dest)
1166 1166 self._repo.dirstate.copy(source, dest)
1167 1167 finally:
1168 1168 wlock.release()
1169 1169
1170 1170 def markcommitted(self, node):
1171 1171 """Perform post-commit cleanup necessary after committing this ctx
1172 1172
1173 1173 Specifically, this updates backing stores this working context
1174 1174 wraps to reflect the fact that the changes reflected by this
1175 1175 workingctx have been committed. For example, it marks
1176 1176 modified and added files as normal in the dirstate.
1177 1177
1178 1178 """
1179 1179
1180 1180 for f in self.modified() + self.added():
1181 1181 self._repo.dirstate.normal(f)
1182 1182 for f in self.removed():
1183 1183 self._repo.dirstate.drop(f)
1184 1184 self._repo.dirstate.setparents(node)
1185 1185
1186 1186 def dirs(self):
1187 1187 return self._repo.dirstate.dirs()
1188 1188
1189 1189 class workingfilectx(filectx):
1190 1190 """A workingfilectx object makes access to data related to a particular
1191 1191 file in the working directory convenient."""
1192 1192 def __init__(self, repo, path, filelog=None, workingctx=None):
1193 1193 """changeid can be a changeset revision, node, or tag.
1194 1194 fileid can be a file revision or node."""
1195 1195 self._repo = repo
1196 1196 self._path = path
1197 1197 self._changeid = None
1198 1198 self._filerev = self._filenode = None
1199 1199
1200 1200 if filelog is not None:
1201 1201 self._filelog = filelog
1202 1202 if workingctx:
1203 1203 self._changectx = workingctx
1204 1204
1205 1205 @propertycache
1206 1206 def _changectx(self):
1207 1207 return workingctx(self._repo)
1208 1208
1209 1209 def __nonzero__(self):
1210 1210 return True
1211 1211
1212 1212 def __str__(self):
1213 1213 return "%s@%s" % (self.path(), self._changectx)
1214 1214
1215 1215 def __repr__(self):
1216 1216 return "<workingfilectx %s>" % str(self)
1217 1217
1218 1218 def data(self):
1219 1219 return self._repo.wread(self._path)
1220 1220 def renamed(self):
1221 1221 rp = self._repo.dirstate.copied(self._path)
1222 1222 if not rp:
1223 1223 return None
1224 1224 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1225 1225
1226 1226 def parents(self):
1227 1227 '''return parent filectxs, following copies if necessary'''
1228 1228 def filenode(ctx, path):
1229 1229 return ctx._manifest.get(path, nullid)
1230 1230
1231 1231 path = self._path
1232 1232 fl = self._filelog
1233 1233 pcl = self._changectx._parents
1234 1234 renamed = self.renamed()
1235 1235
1236 1236 if renamed:
1237 1237 pl = [renamed + (None,)]
1238 1238 else:
1239 1239 pl = [(path, filenode(pcl[0], path), fl)]
1240 1240
1241 1241 for pc in pcl[1:]:
1242 1242 pl.append((path, filenode(pc, path), fl))
1243 1243
1244 1244 return [filectx(self._repo, p, fileid=n, filelog=l)
1245 1245 for p, n, l in pl if n != nullid]
1246 1246
1247 1247 def children(self):
1248 1248 return []
1249 1249
1250 1250 def size(self):
1251 1251 return os.lstat(self._repo.wjoin(self._path)).st_size
1252 1252 def date(self):
1253 1253 t, tz = self._changectx.date()
1254 1254 try:
1255 1255 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1256 1256 except OSError, err:
1257 1257 if err.errno != errno.ENOENT:
1258 1258 raise
1259 1259 return (t, tz)
1260 1260
1261 1261 def cmp(self, fctx):
1262 1262 """compare with other file context
1263 1263
1264 1264 returns True if different than fctx.
1265 1265 """
1266 1266 # fctx should be a filectx (not a workingfilectx)
1267 1267 # invert comparison to reuse the same code path
1268 1268 return fctx.cmp(self)
1269 1269
1270 1270 class memctx(object):
1271 1271 """Use memctx to perform in-memory commits via localrepo.commitctx().
1272 1272
1273 1273 Revision information is supplied at initialization time while
1274 1274 related files data and is made available through a callback
1275 1275 mechanism. 'repo' is the current localrepo, 'parents' is a
1276 1276 sequence of two parent revisions identifiers (pass None for every
1277 1277 missing parent), 'text' is the commit message and 'files' lists
1278 1278 names of files touched by the revision (normalized and relative to
1279 1279 repository root).
1280 1280
1281 1281 filectxfn(repo, memctx, path) is a callable receiving the
1282 1282 repository, the current memctx object and the normalized path of
1283 1283 requested file, relative to repository root. It is fired by the
1284 1284 commit function for every file in 'files', but calls order is
1285 1285 undefined. If the file is available in the revision being
1286 1286 committed (updated or added), filectxfn returns a memfilectx
1287 1287 object. If the file was removed, filectxfn raises an
1288 1288 IOError. Moved files are represented by marking the source file
1289 1289 removed and the new file added with copy information (see
1290 1290 memfilectx).
1291 1291
1292 1292 user receives the committer name and defaults to current
1293 1293 repository username, date is the commit date in any format
1294 1294 supported by util.parsedate() and defaults to current date, extra
1295 1295 is a dictionary of metadata or is left empty.
1296 1296 """
1297 1297 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1298 1298 date=None, extra=None):
1299 1299 self._repo = repo
1300 1300 self._rev = None
1301 1301 self._node = None
1302 1302 self._text = text
1303 1303 self._date = date and util.parsedate(date) or util.makedate()
1304 1304 self._user = user
1305 1305 parents = [(p or nullid) for p in parents]
1306 1306 p1, p2 = parents
1307 1307 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1308 1308 files = sorted(set(files))
1309 1309 self._status = [files, [], [], [], []]
1310 1310 self._filectxfn = filectxfn
1311 1311
1312 1312 self._extra = extra and extra.copy() or {}
1313 1313 if self._extra.get('branch', '') == '':
1314 1314 self._extra['branch'] = 'default'
1315 1315
1316 1316 def __str__(self):
1317 1317 return str(self._parents[0]) + "+"
1318 1318
1319 1319 def __int__(self):
1320 1320 return self._rev
1321 1321
1322 1322 def __nonzero__(self):
1323 1323 return True
1324 1324
1325 1325 def __getitem__(self, key):
1326 1326 return self.filectx(key)
1327 1327
1328 1328 def p1(self):
1329 1329 return self._parents[0]
1330 1330 def p2(self):
1331 1331 return self._parents[1]
1332 1332
1333 1333 def user(self):
1334 1334 return self._user or self._repo.ui.username()
1335 1335 def date(self):
1336 1336 return self._date
1337 1337 def description(self):
1338 1338 return self._text
1339 1339 def files(self):
1340 1340 return self.modified()
1341 1341 def modified(self):
1342 1342 return self._status[0]
1343 1343 def added(self):
1344 1344 return self._status[1]
1345 1345 def removed(self):
1346 1346 return self._status[2]
1347 1347 def deleted(self):
1348 1348 return self._status[3]
1349 1349 def unknown(self):
1350 1350 return self._status[4]
1351 1351 def ignored(self):
1352 1352 return self._status[5]
1353 1353 def clean(self):
1354 1354 return self._status[6]
1355 1355 def branch(self):
1356 1356 return encoding.tolocal(self._extra['branch'])
1357 1357 def extra(self):
1358 1358 return self._extra
1359 1359 def flags(self, f):
1360 1360 return self[f].flags()
1361 1361
1362 1362 def parents(self):
1363 1363 """return contexts for each parent changeset"""
1364 1364 return self._parents
1365 1365
1366 1366 def filectx(self, path, filelog=None):
1367 1367 """get a file context from the working directory"""
1368 1368 return self._filectxfn(self._repo, self, path)
1369 1369
1370 1370 def commit(self):
1371 1371 """commit context to the repo"""
1372 1372 return self._repo.commitctx(self)
1373 1373
1374 1374 class memfilectx(object):
1375 1375 """memfilectx represents an in-memory file to commit.
1376 1376
1377 1377 See memctx for more details.
1378 1378 """
1379 1379 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1380 1380 """
1381 1381 path is the normalized file path relative to repository root.
1382 1382 data is the file content as a string.
1383 1383 islink is True if the file is a symbolic link.
1384 1384 isexec is True if the file is executable.
1385 1385 copied is the source file path if current file was copied in the
1386 1386 revision being committed, or None."""
1387 1387 self._path = path
1388 1388 self._data = data
1389 1389 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1390 1390 self._copied = None
1391 1391 if copied:
1392 1392 self._copied = (copied, nullid)
1393 1393
1394 1394 def __nonzero__(self):
1395 1395 return True
1396 1396 def __str__(self):
1397 1397 return "%s@%s" % (self.path(), self._changectx)
1398 1398 def path(self):
1399 1399 return self._path
1400 1400 def data(self):
1401 1401 return self._data
1402 1402 def flags(self):
1403 1403 return self._flags
1404 1404 def isexec(self):
1405 1405 return 'x' in self._flags
1406 1406 def islink(self):
1407 1407 return 'l' in self._flags
1408 1408 def renamed(self):
1409 1409 return self._copied
General Comments 0
You need to be logged in to leave comments. Login now