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