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