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