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