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