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) |
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, |
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() |
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 |