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