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