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