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