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