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