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