Mercurial > public > mercurial-scm > hg
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 |