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