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