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