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