comparison mercurial/context.py @ 6876:077f1e637cd8

Merge with stable Simplify the copy search algorithm
author Matt Mackall <mpm@selenic.com>
date Sun, 10 Aug 2008 18:38:43 -0500
parents 54b7c79575fa
children 8fee8ff13d37
comparison
equal deleted inserted replaced
6875:0d714a48ab53 6876:077f1e637cd8
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com> 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 # 4 #
5 # This software may be used and distributed according to the terms 5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference. 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 from node import nullid, nullrev, short 8 from node import nullid, nullrev, short, hex
9 from i18n import _ 9 from i18n import _
10 import ancestor, bdiff, revlog, util, os, errno 10 import ancestor, bdiff, revlog, util, os, errno
11 11
12 class changectx(object): 12 class changectx(object):
13 """A changecontext object makes access to data related to a particular 13 """A changecontext object makes access to data related to a particular
14 changeset convenient.""" 14 changeset convenient."""
15 def __init__(self, repo, changeid=None): 15 def __init__(self, repo, changeid=''):
16 """changeid is a revision number, node, or tag""" 16 """changeid is a revision number, node, or tag"""
17 if changeid == '':
18 changeid = '.'
17 self._repo = repo 19 self._repo = repo
18
19 if not changeid and changeid != 0:
20 p1, p2 = self._repo.dirstate.parents()
21 self._rev = self._repo.changelog.rev(p1)
22 if self._rev == -1:
23 changeid = 'tip'
24 else:
25 self._node = p1
26 return
27
28 self._node = self._repo.lookup(changeid) 20 self._node = self._repo.lookup(changeid)
29 self._rev = self._repo.changelog.rev(self._node) 21 self._rev = self._repo.changelog.rev(self._node)
30 22
31 def __str__(self): 23 def __str__(self):
32 return short(self.node()) 24 return short(self.node())
33 25
26 def __int__(self):
27 return self.rev()
28
34 def __repr__(self): 29 def __repr__(self):
35 return "<changectx %s>" % str(self) 30 return "<changectx %s>" % str(self)
31
32 def __hash__(self):
33 try:
34 return hash(self._rev)
35 except AttributeError:
36 return id(self)
36 37
37 def __eq__(self, other): 38 def __eq__(self, other):
38 try: 39 try:
39 return self._rev == other._rev 40 return self._rev == other._rev
40 except AttributeError: 41 except AttributeError:
55 return self._manifest 56 return self._manifest
56 elif name == '_manifestdelta': 57 elif name == '_manifestdelta':
57 md = self._repo.manifest.readdelta(self._changeset[0]) 58 md = self._repo.manifest.readdelta(self._changeset[0])
58 self._manifestdelta = md 59 self._manifestdelta = md
59 return self._manifestdelta 60 return self._manifestdelta
61 elif name == '_parents':
62 p = self._repo.changelog.parents(self._node)
63 if p[1] == nullid:
64 p = p[:-1]
65 self._parents = [changectx(self._repo, x) for x in p]
66 return self._parents
60 else: 67 else:
61 raise AttributeError, name 68 raise AttributeError, name
62 69
63 def __contains__(self, key): 70 def __contains__(self, key):
64 return key in self._manifest 71 return key in self._manifest
65 72
66 def __getitem__(self, key): 73 def __getitem__(self, key):
67 return self.filectx(key) 74 return self.filectx(key)
68 75
69 def __iter__(self): 76 def __iter__(self):
70 a = self._manifest.keys() 77 for f in util.sort(self._manifest):
71 a.sort()
72 for f in a:
73 yield f 78 yield f
74 79
75 def changeset(self): return self._changeset 80 def changeset(self): return self._changeset
76 def manifest(self): return self._manifest 81 def manifest(self): return self._manifest
77 82
78 def rev(self): return self._rev 83 def rev(self): return self._rev
79 def node(self): return self._node 84 def node(self): return self._node
85 def hex(self): return hex(self._node)
80 def user(self): return self._changeset[1] 86 def user(self): return self._changeset[1]
81 def date(self): return self._changeset[2] 87 def date(self): return self._changeset[2]
82 def files(self): return self._changeset[3] 88 def files(self): return self._changeset[3]
83 def description(self): return self._changeset[4] 89 def description(self): return self._changeset[4]
84 def branch(self): return self._changeset[5].get("branch") 90 def branch(self): return self._changeset[5].get("branch")
85 def extra(self): return self._changeset[5] 91 def extra(self): return self._changeset[5]
86 def tags(self): return self._repo.nodetags(self._node) 92 def tags(self): return self._repo.nodetags(self._node)
87 93
88 def parents(self): 94 def parents(self):
89 """return contexts for each parent changeset""" 95 """return contexts for each parent changeset"""
90 p = self._repo.changelog.parents(self._node) 96 return self._parents
91 return [changectx(self._repo, x) for x in p]
92 97
93 def children(self): 98 def children(self):
94 """return contexts for each child changeset""" 99 """return contexts for each child changeset"""
95 c = self._repo.changelog.children(self._node) 100 c = self._repo.changelog.children(self._node)
96 return [changectx(self._repo, x) for x in c] 101 return [changectx(self._repo, x) for x in c]
102
103 def ancestors(self):
104 for a in self._repo.changelog.ancestors(self._rev):
105 yield changectx(self._repo, a)
106
107 def descendants(self):
108 for d in self._repo.changelog.descendants(self._rev):
109 yield changectx(self._repo, d)
97 110
98 def _fileinfo(self, path): 111 def _fileinfo(self, path):
99 if '_manifest' in self.__dict__: 112 if '_manifest' in self.__dict__:
100 try: 113 try:
101 return self._manifest[path], self._manifest.flags(path) 114 return self._manifest[path], self._manifest.flags(path)
113 return node, flag 126 return node, flag
114 127
115 def filenode(self, path): 128 def filenode(self, path):
116 return self._fileinfo(path)[0] 129 return self._fileinfo(path)[0]
117 130
118 def fileflags(self, path): 131 def flags(self, path):
119 try: 132 try:
120 return self._fileinfo(path)[1] 133 return self._fileinfo(path)[1]
121 except revlog.LookupError: 134 except revlog.LookupError:
122 return '' 135 return ''
123 136
126 if fileid is None: 139 if fileid is None:
127 fileid = self.filenode(path) 140 fileid = self.filenode(path)
128 return filectx(self._repo, path, fileid=fileid, 141 return filectx(self._repo, path, fileid=fileid,
129 changectx=self, filelog=filelog) 142 changectx=self, filelog=filelog)
130 143
131 def filectxs(self):
132 """generate a file context for each file in this changeset's
133 manifest"""
134 mf = self.manifest()
135 m = mf.keys()
136 m.sort()
137 for f in m:
138 yield self.filectx(f, fileid=mf[f])
139
140 def ancestor(self, c2): 144 def ancestor(self, c2):
141 """ 145 """
142 return the ancestor context of self and c2 146 return the ancestor context of self and c2
143 """ 147 """
144 n = self._repo.changelog.ancestor(self._node, c2._node) 148 n = self._repo.changelog.ancestor(self._node, c2._node)
145 return changectx(self._repo, n) 149 return changectx(self._repo, n)
150
151 def walk(self, match):
152 fdict = dict.fromkeys(match.files())
153 # for dirstate.walk, files=['.'] means "walk the whole tree".
154 # follow that here, too
155 fdict.pop('.', None)
156 for fn in self:
157 for ffn in fdict:
158 # match if the file is the exact name or a directory
159 if ffn == fn or fn.startswith("%s/" % ffn):
160 del fdict[ffn]
161 break
162 if match(fn):
163 yield fn
164 for fn in util.sort(fdict):
165 if match.bad(fn, 'No such file in rev ' + str(self)) and match(fn):
166 yield fn
146 167
147 class filectx(object): 168 class filectx(object):
148 """A filecontext object makes access to data related to a particular 169 """A filecontext object makes access to data related to a particular
149 filerevision convenient.""" 170 filerevision convenient."""
150 def __init__(self, repo, path, changeid=None, fileid=None, 171 def __init__(self, repo, path, changeid=None, fileid=None,
208 return "%s@%s" % (self.path(), short(self.node())) 229 return "%s@%s" % (self.path(), short(self.node()))
209 230
210 def __repr__(self): 231 def __repr__(self):
211 return "<filectx %s>" % str(self) 232 return "<filectx %s>" % str(self)
212 233
234 def __hash__(self):
235 try:
236 return hash((self._path, self._fileid))
237 except AttributeError:
238 return id(self)
239
213 def __eq__(self, other): 240 def __eq__(self, other):
214 try: 241 try:
215 return (self._path == other._path 242 return (self._path == other._path
216 and self._fileid == other._fileid) 243 and self._fileid == other._fileid)
217 except AttributeError: 244 except AttributeError:
226 return filectx(self._repo, self._path, fileid=fileid, 253 return filectx(self._repo, self._path, fileid=fileid,
227 filelog=self._filelog) 254 filelog=self._filelog)
228 255
229 def filerev(self): return self._filerev 256 def filerev(self): return self._filerev
230 def filenode(self): return self._filenode 257 def filenode(self): return self._filenode
231 def fileflags(self): return self._changectx.fileflags(self._path) 258 def flags(self): return self._changectx.flags(self._path)
232 def isexec(self): return 'x' in self.fileflags()
233 def islink(self): return 'l' in self.fileflags()
234 def filelog(self): return self._filelog 259 def filelog(self): return self._filelog
235 260
236 def rev(self): 261 def rev(self):
237 if '_changectx' in self.__dict__: 262 if '_changectx' in self.__dict__:
238 return self._changectx.rev() 263 return self._changectx.rev()
374 needed[p] += 1 399 needed[p] += 1
375 400
376 # sort by revision (per file) which is a topological order 401 # sort by revision (per file) which is a topological order
377 visit = [] 402 visit = []
378 for f in files: 403 for f in files:
379 fn = [(n.rev(), n) for n in needed.keys() if n._path == f] 404 fn = [(n.rev(), n) for n in needed if n._path == f]
380 visit.extend(fn) 405 visit.extend(fn)
381 visit.sort() 406
382 hist = {} 407 hist = {}
383 408 for r, f in util.sort(visit):
384 for r, f in visit:
385 curr = decorate(f.data(), f) 409 curr = decorate(f.data(), f)
386 for p in parents(f): 410 for p in parents(f):
387 if p != nullid: 411 if p != nullid:
388 curr = pair(hist[p], curr) 412 curr = pair(hist[p], curr)
389 # trim the history of unneeded revs 413 # trim the history of unneeded revs
430 454
431 return None 455 return None
432 456
433 class workingctx(changectx): 457 class workingctx(changectx):
434 """A workingctx object makes access to data related to 458 """A workingctx object makes access to data related to
435 the current working directory convenient.""" 459 the current working directory convenient.
436 def __init__(self, repo): 460 parents - a pair of parent nodeids, or None to use the dirstate.
461 date - any valid date string or (unixtime, offset), or None.
462 user - username string, or None.
463 extra - a dictionary of extra values, or None.
464 changes - a list of file lists as returned by localrepo.status()
465 or None to use the repository status.
466 """
467 def __init__(self, repo, parents=None, text="", user=None, date=None,
468 extra=None, changes=None):
437 self._repo = repo 469 self._repo = repo
438 self._rev = None 470 self._rev = None
439 self._node = None 471 self._node = None
472 self._text = text
473 if date:
474 self._date = util.parsedate(date)
475 if user:
476 self._user = user
477 if parents:
478 self._parents = [changectx(self._repo, p) for p in parents]
479 if changes:
480 self._status = list(changes)
481
482 self._extra = {}
483 if extra:
484 self._extra = extra.copy()
485 if 'branch' not in self._extra:
486 branch = self._repo.dirstate.branch()
487 try:
488 branch = branch.decode('UTF-8').encode('UTF-8')
489 except UnicodeDecodeError:
490 raise util.Abort(_('branch name not in UTF-8!'))
491 self._extra['branch'] = branch
492 if self._extra['branch'] == '':
493 self._extra['branch'] = 'default'
440 494
441 def __str__(self): 495 def __str__(self):
442 return str(self._parents[0]) + "+" 496 return str(self._parents[0]) + "+"
443 497
444 def __nonzero__(self): 498 def __nonzero__(self):
445 return True 499 return True
446 500
501 def __contains__(self, key):
502 return self._dirstate[key] not in "?r"
503
447 def __getattr__(self, name): 504 def __getattr__(self, name):
448 if name == '_parents':
449 self._parents = self._repo.parents()
450 return self._parents
451 if name == '_status': 505 if name == '_status':
452 self._status = self._repo.status() 506 self._status = self._repo.status(unknown=True)
453 return self._status 507 return self._status
508 elif name == '_user':
509 self._user = self._repo.ui.username()
510 return self._user
511 elif name == '_date':
512 self._date = util.makedate()
513 return self._date
454 if name == '_manifest': 514 if name == '_manifest':
455 self._buildmanifest() 515 self._buildmanifest()
456 return self._manifest 516 return self._manifest
517 elif name == '_parents':
518 p = self._repo.dirstate.parents()
519 if p[1] == nullid:
520 p = p[:-1]
521 self._parents = [changectx(self._repo, x) for x in p]
522 return self._parents
457 else: 523 else:
458 raise AttributeError, name 524 raise AttributeError, name
459 525
460 def _buildmanifest(self): 526 def _buildmanifest(self):
461 """generate a manifest corresponding to the working directory""" 527 """generate a manifest corresponding to the working directory"""
462 528
463 man = self._parents[0].manifest().copy() 529 man = self._parents[0].manifest().copy()
464 copied = self._repo.dirstate.copies() 530 copied = self._repo.dirstate.copies()
465 is_exec = util.execfunc(self._repo.root, 531 cf = lambda x: man.flags(copied.get(x, x))
466 lambda p: man.execf(copied.get(p,p))) 532 ff = self._repo.dirstate.flagfunc(cf)
467 is_link = util.linkfunc(self._repo.root,
468 lambda p: man.linkf(copied.get(p,p)))
469 modified, added, removed, deleted, unknown = self._status[:5] 533 modified, added, removed, deleted, unknown = self._status[:5]
470 for i, l in (("a", added), ("m", modified), ("u", unknown)): 534 for i, l in (("a", added), ("m", modified), ("u", unknown)):
471 for f in l: 535 for f in l:
472 man[f] = man.get(copied.get(f, f), nullid) + i 536 man[f] = man.get(copied.get(f, f), nullid) + i
473 try: 537 try:
474 man.set(f, is_exec(f), is_link(f)) 538 man.set(f, ff(f))
475 except OSError: 539 except OSError:
476 pass 540 pass
477 541
478 for f in deleted + removed: 542 for f in deleted + removed:
479 if f in man: 543 if f in man:
481 545
482 self._manifest = man 546 self._manifest = man
483 547
484 def manifest(self): return self._manifest 548 def manifest(self): return self._manifest
485 549
486 def user(self): return self._repo.ui.username() 550 def user(self): return self._user or self._repo.ui.username()
487 def date(self): return util.makedate() 551 def date(self): return self._date
488 def description(self): return "" 552 def description(self): return self._text
489 def files(self): 553 def files(self):
490 f = self.modified() + self.added() + self.removed() 554 return util.sort(self._status[0] + self._status[1] + self._status[2])
491 f.sort()
492 return f
493 555
494 def modified(self): return self._status[0] 556 def modified(self): return self._status[0]
495 def added(self): return self._status[1] 557 def added(self): return self._status[1]
496 def removed(self): return self._status[2] 558 def removed(self): return self._status[2]
497 def deleted(self): return self._status[3] 559 def deleted(self): return self._status[3]
498 def unknown(self): return self._status[4] 560 def unknown(self): return self._status[4]
499 def clean(self): return self._status[5] 561 def clean(self): return self._status[5]
500 def branch(self): return self._repo.dirstate.branch() 562 def branch(self): return self._extra['branch']
563 def extra(self): return self._extra
501 564
502 def tags(self): 565 def tags(self):
503 t = [] 566 t = []
504 [t.extend(p.tags()) for p in self.parents()] 567 [t.extend(p.tags()) for p in self.parents()]
505 return t 568 return t
506 569
507 def parents(self):
508 """return contexts for each parent changeset"""
509 return self._parents
510
511 def children(self): 570 def children(self):
512 return [] 571 return []
513 572
514 def fileflags(self, path): 573 def flags(self, path):
515 if '_manifest' in self.__dict__: 574 if '_manifest' in self.__dict__:
516 try: 575 try:
517 return self._manifest.flags(path) 576 return self._manifest.flags(path)
518 except KeyError: 577 except KeyError:
519 return '' 578 return ''
520 579
521 pnode = self._parents[0].changeset()[0] 580 pnode = self._parents[0].changeset()[0]
522 orig = self._repo.dirstate.copies().get(path, path) 581 orig = self._repo.dirstate.copies().get(path, path)
523 node, flag = self._repo.manifest.find(pnode, orig) 582 node, flag = self._repo.manifest.find(pnode, orig)
524 is_link = util.linkfunc(self._repo.root,
525 lambda p: flag and 'l' in flag)
526 is_exec = util.execfunc(self._repo.root,
527 lambda p: flag and 'x' in flag)
528 try: 583 try:
529 return (is_link(path) and 'l' or '') + (is_exec(path) and 'x' or '') 584 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
585 return ff(path)
530 except OSError: 586 except OSError:
531 pass 587 pass
532 588
533 if not node or path in self.deleted() or path in self.removed(): 589 if not node or path in self.deleted() or path in self.removed():
534 return '' 590 return ''
540 filelog=filelog) 596 filelog=filelog)
541 597
542 def ancestor(self, c2): 598 def ancestor(self, c2):
543 """return the ancestor context of self and c2""" 599 """return the ancestor context of self and c2"""
544 return self._parents[0].ancestor(c2) # punt on two parents for now 600 return self._parents[0].ancestor(c2) # punt on two parents for now
601
602 def walk(self, match):
603 return util.sort(self._repo.dirstate.walk(match, True, False).keys())
545 604
546 class workingfilectx(filectx): 605 class workingfilectx(filectx):
547 """A workingfilectx object makes access to data related to a particular 606 """A workingfilectx object makes access to data related to a particular
548 file in the working directory convenient.""" 607 file in the working directory convenient."""
549 def __init__(self, repo, path, filelog=None, workingctx=None): 608 def __init__(self, repo, path, filelog=None, workingctx=None):
623 except OSError, err: 682 except OSError, err:
624 if err.errno != errno.ENOENT: raise 683 if err.errno != errno.ENOENT: raise
625 return (t, tz) 684 return (t, tz)
626 685
627 def cmp(self, text): return self._repo.wread(self._path) == text 686 def cmp(self, text): return self._repo.wread(self._path) == text
687
688 class memctx(object):
689 """A memctx is a subset of changectx supposed to be built on memory
690 and passed to commit functions.
691
692 NOTE: this interface and the related memfilectx are experimental and
693 may change without notice.
694
695 parents - a pair of parent nodeids.
696 filectxfn - a callable taking (repo, memctx, path) arguments and
697 returning a memctx object.
698 date - any valid date string or (unixtime, offset), or None.
699 user - username string, or None.
700 extra - a dictionary of extra values, or None.
701 """
702 def __init__(self, repo, parents, text, files, filectxfn, user=None,
703 date=None, extra=None):
704 self._repo = repo
705 self._rev = None
706 self._node = None
707 self._text = text
708 self._date = date and util.parsedate(date) or util.makedate()
709 self._user = user
710 parents = [(p or nullid) for p in parents]
711 p1, p2 = parents
712 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
713 files = util.sort(list(files))
714 self._status = [files, [], [], [], []]
715 self._filectxfn = filectxfn
716
717 self._extra = extra and extra.copy() or {}
718 if 'branch' not in self._extra:
719 self._extra['branch'] = 'default'
720 elif self._extra.get('branch') == '':
721 self._extra['branch'] = 'default'
722
723 def __str__(self):
724 return str(self._parents[0]) + "+"
725
726 def __int__(self):
727 return self._rev
728
729 def __nonzero__(self):
730 return True
731
732 def user(self): return self._user or self._repo.ui.username()
733 def date(self): return self._date
734 def description(self): return self._text
735 def files(self): return self.modified()
736 def modified(self): return self._status[0]
737 def added(self): return self._status[1]
738 def removed(self): return self._status[2]
739 def deleted(self): return self._status[3]
740 def unknown(self): return self._status[4]
741 def clean(self): return self._status[5]
742 def branch(self): return self._extra['branch']
743 def extra(self): return self._extra
744 def flags(self, f): return self[f].flags()
745
746 def parents(self):
747 """return contexts for each parent changeset"""
748 return self._parents
749
750 def filectx(self, path, filelog=None):
751 """get a file context from the working directory"""
752 return self._filectxfn(self._repo, self, path)
753
754 class memfilectx(object):
755 """A memfilectx is a subset of filectx supposed to be built by client
756 code and passed to commit functions.
757 """
758 def __init__(self, path, data, islink, isexec, copied):
759 """copied is the source file path, or None."""
760 self._path = path
761 self._data = data
762 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
763 self._copied = None
764 if copied:
765 self._copied = (copied, nullid)
766
767 def __nonzero__(self): return True
768 def __str__(self): return "%s@%s" % (self.path(), self._changectx)
769 def path(self): return self._path
770 def data(self): return self._data
771 def flags(self): return self._flags
772 def isexec(self): return 'x' in self._flags
773 def islink(self): return 'l' in self._flags
774 def renamed(self): return self._copied
775