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