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