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