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