##// END OF EJS Templates
convert: make contents of "extra" dict available from sources, for sinks....
Bryan O'Sullivan -
r5439:d0c67b52 default
parent child Browse files
Show More
@@ -1,179 +1,181 b''
1 # common code for the convert extension
1 # common code for the convert extension
2 import base64
2 import base64
3 import cPickle as pickle
3 import cPickle as pickle
4
4
5 def encodeargs(args):
5 def encodeargs(args):
6 def encodearg(s):
6 def encodearg(s):
7 lines = base64.encodestring(s)
7 lines = base64.encodestring(s)
8 lines = [l.splitlines()[0] for l in lines]
8 lines = [l.splitlines()[0] for l in lines]
9 return ''.join(lines)
9 return ''.join(lines)
10
10
11 s = pickle.dumps(args)
11 s = pickle.dumps(args)
12 return encodearg(s)
12 return encodearg(s)
13
13
14 def decodeargs(s):
14 def decodeargs(s):
15 s = base64.decodestring(s)
15 s = base64.decodestring(s)
16 return pickle.loads(s)
16 return pickle.loads(s)
17
17
18 class NoRepo(Exception): pass
18 class NoRepo(Exception): pass
19
19
20 SKIPREV = 'SKIP'
20 SKIPREV = 'SKIP'
21
21
22 class commit(object):
22 class commit(object):
23 def __init__(self, author, date, desc, parents, branch=None, rev=None):
23 def __init__(self, author, date, desc, parents, branch=None, rev=None,
24 extra={}):
24 self.author = author
25 self.author = author
25 self.date = date
26 self.date = date
26 self.desc = desc
27 self.desc = desc
27 self.parents = parents
28 self.parents = parents
28 self.branch = branch
29 self.branch = branch
29 self.rev = rev
30 self.rev = rev
31 self.extra = extra
30
32
31 class converter_source(object):
33 class converter_source(object):
32 """Conversion source interface"""
34 """Conversion source interface"""
33
35
34 def __init__(self, ui, path, rev=None):
36 def __init__(self, ui, path, rev=None):
35 """Initialize conversion source (or raise NoRepo("message")
37 """Initialize conversion source (or raise NoRepo("message")
36 exception if path is not a valid repository)"""
38 exception if path is not a valid repository)"""
37 self.ui = ui
39 self.ui = ui
38 self.path = path
40 self.path = path
39 self.rev = rev
41 self.rev = rev
40
42
41 self.encoding = 'utf-8'
43 self.encoding = 'utf-8'
42
44
43 def before(self):
45 def before(self):
44 pass
46 pass
45
47
46 def after(self):
48 def after(self):
47 pass
49 pass
48
50
49 def setrevmap(self, revmap, order):
51 def setrevmap(self, revmap, order):
50 """set the map of already-converted revisions
52 """set the map of already-converted revisions
51
53
52 order is a list with the keys from revmap in the order they
54 order is a list with the keys from revmap in the order they
53 appear in the revision map file."""
55 appear in the revision map file."""
54 pass
56 pass
55
57
56 def getheads(self):
58 def getheads(self):
57 """Return a list of this repository's heads"""
59 """Return a list of this repository's heads"""
58 raise NotImplementedError()
60 raise NotImplementedError()
59
61
60 def getfile(self, name, rev):
62 def getfile(self, name, rev):
61 """Return file contents as a string"""
63 """Return file contents as a string"""
62 raise NotImplementedError()
64 raise NotImplementedError()
63
65
64 def getmode(self, name, rev):
66 def getmode(self, name, rev):
65 """Return file mode, eg. '', 'x', or 'l'"""
67 """Return file mode, eg. '', 'x', or 'l'"""
66 raise NotImplementedError()
68 raise NotImplementedError()
67
69
68 def getchanges(self, version):
70 def getchanges(self, version):
69 """Returns a tuple of (files, copies)
71 """Returns a tuple of (files, copies)
70 Files is a sorted list of (filename, id) tuples for all files changed
72 Files is a sorted list of (filename, id) tuples for all files changed
71 in version, where id is the source revision id of the file.
73 in version, where id is the source revision id of the file.
72
74
73 copies is a dictionary of dest: source
75 copies is a dictionary of dest: source
74 """
76 """
75 raise NotImplementedError()
77 raise NotImplementedError()
76
78
77 def getcommit(self, version):
79 def getcommit(self, version):
78 """Return the commit object for version"""
80 """Return the commit object for version"""
79 raise NotImplementedError()
81 raise NotImplementedError()
80
82
81 def gettags(self):
83 def gettags(self):
82 """Return the tags as a dictionary of name: revision"""
84 """Return the tags as a dictionary of name: revision"""
83 raise NotImplementedError()
85 raise NotImplementedError()
84
86
85 def recode(self, s, encoding=None):
87 def recode(self, s, encoding=None):
86 if not encoding:
88 if not encoding:
87 encoding = self.encoding or 'utf-8'
89 encoding = self.encoding or 'utf-8'
88
90
89 if isinstance(s, unicode):
91 if isinstance(s, unicode):
90 return s.encode("utf-8")
92 return s.encode("utf-8")
91 try:
93 try:
92 return s.decode(encoding).encode("utf-8")
94 return s.decode(encoding).encode("utf-8")
93 except:
95 except:
94 try:
96 try:
95 return s.decode("latin-1").encode("utf-8")
97 return s.decode("latin-1").encode("utf-8")
96 except:
98 except:
97 return s.decode(encoding, "replace").encode("utf-8")
99 return s.decode(encoding, "replace").encode("utf-8")
98
100
99 def getchangedfiles(self, rev, i):
101 def getchangedfiles(self, rev, i):
100 """Return the files changed by rev compared to parent[i].
102 """Return the files changed by rev compared to parent[i].
101
103
102 i is an index selecting one of the parents of rev. The return
104 i is an index selecting one of the parents of rev. The return
103 value should be the list of files that are different in rev and
105 value should be the list of files that are different in rev and
104 this parent.
106 this parent.
105
107
106 If rev has no parents, i is None.
108 If rev has no parents, i is None.
107
109
108 This function is only needed to support --filemap
110 This function is only needed to support --filemap
109 """
111 """
110 raise NotImplementedError()
112 raise NotImplementedError()
111
113
112 class converter_sink(object):
114 class converter_sink(object):
113 """Conversion sink (target) interface"""
115 """Conversion sink (target) interface"""
114
116
115 def __init__(self, ui, path):
117 def __init__(self, ui, path):
116 """Initialize conversion sink (or raise NoRepo("message")
118 """Initialize conversion sink (or raise NoRepo("message")
117 exception if path is not a valid repository)"""
119 exception if path is not a valid repository)"""
118 raise NotImplementedError()
120 raise NotImplementedError()
119
121
120 def getheads(self):
122 def getheads(self):
121 """Return a list of this repository's heads"""
123 """Return a list of this repository's heads"""
122 raise NotImplementedError()
124 raise NotImplementedError()
123
125
124 def revmapfile(self):
126 def revmapfile(self):
125 """Path to a file that will contain lines
127 """Path to a file that will contain lines
126 source_rev_id sink_rev_id
128 source_rev_id sink_rev_id
127 mapping equivalent revision identifiers for each system."""
129 mapping equivalent revision identifiers for each system."""
128 raise NotImplementedError()
130 raise NotImplementedError()
129
131
130 def authorfile(self):
132 def authorfile(self):
131 """Path to a file that will contain lines
133 """Path to a file that will contain lines
132 srcauthor=dstauthor
134 srcauthor=dstauthor
133 mapping equivalent authors identifiers for each system."""
135 mapping equivalent authors identifiers for each system."""
134 return None
136 return None
135
137
136 def putfile(self, f, e, data):
138 def putfile(self, f, e, data):
137 """Put file for next putcommit().
139 """Put file for next putcommit().
138 f: path to file
140 f: path to file
139 e: '', 'x', or 'l' (regular file, executable, or symlink)
141 e: '', 'x', or 'l' (regular file, executable, or symlink)
140 data: file contents"""
142 data: file contents"""
141 raise NotImplementedError()
143 raise NotImplementedError()
142
144
143 def delfile(self, f):
145 def delfile(self, f):
144 """Delete file for next putcommit().
146 """Delete file for next putcommit().
145 f: path to file"""
147 f: path to file"""
146 raise NotImplementedError()
148 raise NotImplementedError()
147
149
148 def putcommit(self, files, parents, commit):
150 def putcommit(self, files, parents, commit):
149 """Create a revision with all changed files listed in 'files'
151 """Create a revision with all changed files listed in 'files'
150 and having listed parents. 'commit' is a commit object containing
152 and having listed parents. 'commit' is a commit object containing
151 at a minimum the author, date, and message for this changeset.
153 at a minimum the author, date, and message for this changeset.
152 Called after putfile() and delfile() calls. Note that the sink
154 Called after putfile() and delfile() calls. Note that the sink
153 repository is not told to update itself to a particular revision
155 repository is not told to update itself to a particular revision
154 (or even what that revision would be) before it receives the
156 (or even what that revision would be) before it receives the
155 file data."""
157 file data."""
156 raise NotImplementedError()
158 raise NotImplementedError()
157
159
158 def puttags(self, tags):
160 def puttags(self, tags):
159 """Put tags into sink.
161 """Put tags into sink.
160 tags: {tagname: sink_rev_id, ...}"""
162 tags: {tagname: sink_rev_id, ...}"""
161 raise NotImplementedError()
163 raise NotImplementedError()
162
164
163 def setbranch(self, branch, pbranch, parents):
165 def setbranch(self, branch, pbranch, parents):
164 """Set the current branch name. Called before the first putfile
166 """Set the current branch name. Called before the first putfile
165 on the branch.
167 on the branch.
166 branch: branch name for subsequent commits
168 branch: branch name for subsequent commits
167 pbranch: branch name of parent commit
169 pbranch: branch name of parent commit
168 parents: destination revisions of parent"""
170 parents: destination revisions of parent"""
169 pass
171 pass
170
172
171 def setfilemapmode(self, active):
173 def setfilemapmode(self, active):
172 """Tell the destination that we're using a filemap
174 """Tell the destination that we're using a filemap
173
175
174 Some converter_sources (svn in particular) can claim that a file
176 Some converter_sources (svn in particular) can claim that a file
175 was changed in a revision, even if there was no change. This method
177 was changed in a revision, even if there was no change. This method
176 tells the destination that we're using a filemap and that it should
178 tells the destination that we're using a filemap and that it should
177 filter empty revisions.
179 filter empty revisions.
178 """
180 """
179 pass
181 pass
@@ -1,248 +1,248 b''
1 # hg backend for convert extension
1 # hg backend for convert extension
2
2
3 # Note for hg->hg conversion: Old versions of Mercurial didn't trim
3 # Note for hg->hg conversion: Old versions of Mercurial didn't trim
4 # the whitespace from the ends of commit messages, but new versions
4 # the whitespace from the ends of commit messages, but new versions
5 # do. Changesets created by those older versions, then converted, may
5 # do. Changesets created by those older versions, then converted, may
6 # thus have different hashes for changesets that are otherwise
6 # thus have different hashes for changesets that are otherwise
7 # identical.
7 # identical.
8
8
9
9
10 import os, time
10 import os, time
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12 from mercurial.node import *
12 from mercurial.node import *
13 from mercurial import hg, lock, revlog, util
13 from mercurial import hg, lock, revlog, util
14
14
15 from common import NoRepo, commit, converter_source, converter_sink
15 from common import NoRepo, commit, converter_source, converter_sink
16
16
17 class mercurial_sink(converter_sink):
17 class mercurial_sink(converter_sink):
18 def __init__(self, ui, path):
18 def __init__(self, ui, path):
19 self.path = path
19 self.path = path
20 self.ui = ui
20 self.ui = ui
21 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
21 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
22 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
22 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
23 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
23 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
24 self.lastbranch = None
24 self.lastbranch = None
25 try:
25 try:
26 self.repo = hg.repository(self.ui, path)
26 self.repo = hg.repository(self.ui, path)
27 except:
27 except:
28 raise NoRepo("could not open hg repo %s as sink" % path)
28 raise NoRepo("could not open hg repo %s as sink" % path)
29 self.lock = None
29 self.lock = None
30 self.wlock = None
30 self.wlock = None
31 self.filemapmode = False
31 self.filemapmode = False
32
32
33 def before(self):
33 def before(self):
34 self.wlock = self.repo.wlock()
34 self.wlock = self.repo.wlock()
35 self.lock = self.repo.lock()
35 self.lock = self.repo.lock()
36 self.repo.dirstate.clear()
36 self.repo.dirstate.clear()
37
37
38 def after(self):
38 def after(self):
39 self.repo.dirstate.invalidate()
39 self.repo.dirstate.invalidate()
40 self.lock = None
40 self.lock = None
41 self.wlock = None
41 self.wlock = None
42
42
43 def revmapfile(self):
43 def revmapfile(self):
44 return os.path.join(self.path, ".hg", "shamap")
44 return os.path.join(self.path, ".hg", "shamap")
45
45
46 def authorfile(self):
46 def authorfile(self):
47 return os.path.join(self.path, ".hg", "authormap")
47 return os.path.join(self.path, ".hg", "authormap")
48
48
49 def getheads(self):
49 def getheads(self):
50 h = self.repo.changelog.heads()
50 h = self.repo.changelog.heads()
51 return [ hex(x) for x in h ]
51 return [ hex(x) for x in h ]
52
52
53 def putfile(self, f, e, data):
53 def putfile(self, f, e, data):
54 self.repo.wwrite(f, data, e)
54 self.repo.wwrite(f, data, e)
55 if f not in self.repo.dirstate:
55 if f not in self.repo.dirstate:
56 self.repo.dirstate.normallookup(f)
56 self.repo.dirstate.normallookup(f)
57
57
58 def copyfile(self, source, dest):
58 def copyfile(self, source, dest):
59 self.repo.copy(source, dest)
59 self.repo.copy(source, dest)
60
60
61 def delfile(self, f):
61 def delfile(self, f):
62 try:
62 try:
63 util.unlink(self.repo.wjoin(f))
63 util.unlink(self.repo.wjoin(f))
64 #self.repo.remove([f])
64 #self.repo.remove([f])
65 except OSError:
65 except OSError:
66 pass
66 pass
67
67
68 def setbranch(self, branch, pbranch, parents):
68 def setbranch(self, branch, pbranch, parents):
69 if (not self.clonebranches) or (branch == self.lastbranch):
69 if (not self.clonebranches) or (branch == self.lastbranch):
70 return
70 return
71
71
72 self.lastbranch = branch
72 self.lastbranch = branch
73 self.after()
73 self.after()
74 if not branch:
74 if not branch:
75 branch = 'default'
75 branch = 'default'
76 if not pbranch:
76 if not pbranch:
77 pbranch = 'default'
77 pbranch = 'default'
78
78
79 branchpath = os.path.join(self.path, branch)
79 branchpath = os.path.join(self.path, branch)
80 try:
80 try:
81 self.repo = hg.repository(self.ui, branchpath)
81 self.repo = hg.repository(self.ui, branchpath)
82 except:
82 except:
83 if not parents:
83 if not parents:
84 self.repo = hg.repository(self.ui, branchpath, create=True)
84 self.repo = hg.repository(self.ui, branchpath, create=True)
85 else:
85 else:
86 self.ui.note(_('cloning branch %s to %s\n') % (pbranch, branch))
86 self.ui.note(_('cloning branch %s to %s\n') % (pbranch, branch))
87 hg.clone(self.ui, os.path.join(self.path, pbranch),
87 hg.clone(self.ui, os.path.join(self.path, pbranch),
88 branchpath, rev=parents, update=False,
88 branchpath, rev=parents, update=False,
89 stream=True)
89 stream=True)
90 self.repo = hg.repository(self.ui, branchpath)
90 self.repo = hg.repository(self.ui, branchpath)
91 self.before()
91 self.before()
92
92
93 def putcommit(self, files, parents, commit):
93 def putcommit(self, files, parents, commit):
94 seen = {}
94 seen = {}
95 pl = []
95 pl = []
96 for p in parents:
96 for p in parents:
97 if p not in seen:
97 if p not in seen:
98 pl.append(p)
98 pl.append(p)
99 seen[p] = 1
99 seen[p] = 1
100 parents = pl
100 parents = pl
101 nparents = len(parents)
101 nparents = len(parents)
102 if self.filemapmode and nparents == 1:
102 if self.filemapmode and nparents == 1:
103 m1node = self.repo.changelog.read(bin(parents[0]))[0]
103 m1node = self.repo.changelog.read(bin(parents[0]))[0]
104 parent = parents[0]
104 parent = parents[0]
105
105
106 if len(parents) < 2: parents.append("0" * 40)
106 if len(parents) < 2: parents.append("0" * 40)
107 if len(parents) < 2: parents.append("0" * 40)
107 if len(parents) < 2: parents.append("0" * 40)
108 p2 = parents.pop(0)
108 p2 = parents.pop(0)
109
109
110 text = commit.desc
110 text = commit.desc
111 extra = {}
111 extra = commit.extra.copy()
112 if self.branchnames and commit.branch:
112 if self.branchnames and commit.branch:
113 extra['branch'] = commit.branch
113 extra['branch'] = commit.branch
114 if commit.rev:
114 if commit.rev:
115 extra['convert_revision'] = commit.rev
115 extra['convert_revision'] = commit.rev
116
116
117 while parents:
117 while parents:
118 p1 = p2
118 p1 = p2
119 p2 = parents.pop(0)
119 p2 = parents.pop(0)
120 a = self.repo.rawcommit(files, text, commit.author, commit.date,
120 a = self.repo.rawcommit(files, text, commit.author, commit.date,
121 bin(p1), bin(p2), extra=extra)
121 bin(p1), bin(p2), extra=extra)
122 self.repo.dirstate.clear()
122 self.repo.dirstate.clear()
123 text = "(octopus merge fixup)\n"
123 text = "(octopus merge fixup)\n"
124 p2 = hg.hex(self.repo.changelog.tip())
124 p2 = hg.hex(self.repo.changelog.tip())
125
125
126 if self.filemapmode and nparents == 1:
126 if self.filemapmode and nparents == 1:
127 man = self.repo.manifest
127 man = self.repo.manifest
128 mnode = self.repo.changelog.read(bin(p2))[0]
128 mnode = self.repo.changelog.read(bin(p2))[0]
129 if not man.cmp(m1node, man.revision(mnode)):
129 if not man.cmp(m1node, man.revision(mnode)):
130 self.repo.rollback()
130 self.repo.rollback()
131 self.repo.dirstate.clear()
131 self.repo.dirstate.clear()
132 return parent
132 return parent
133 return p2
133 return p2
134
134
135 def puttags(self, tags):
135 def puttags(self, tags):
136 try:
136 try:
137 old = self.repo.wfile(".hgtags").read()
137 old = self.repo.wfile(".hgtags").read()
138 oldlines = old.splitlines(1)
138 oldlines = old.splitlines(1)
139 oldlines.sort()
139 oldlines.sort()
140 except:
140 except:
141 oldlines = []
141 oldlines = []
142
142
143 k = tags.keys()
143 k = tags.keys()
144 k.sort()
144 k.sort()
145 newlines = []
145 newlines = []
146 for tag in k:
146 for tag in k:
147 newlines.append("%s %s\n" % (tags[tag], tag))
147 newlines.append("%s %s\n" % (tags[tag], tag))
148
148
149 newlines.sort()
149 newlines.sort()
150
150
151 if newlines != oldlines:
151 if newlines != oldlines:
152 self.ui.status("updating tags\n")
152 self.ui.status("updating tags\n")
153 f = self.repo.wfile(".hgtags", "w")
153 f = self.repo.wfile(".hgtags", "w")
154 f.write("".join(newlines))
154 f.write("".join(newlines))
155 f.close()
155 f.close()
156 if not oldlines: self.repo.add([".hgtags"])
156 if not oldlines: self.repo.add([".hgtags"])
157 date = "%s 0" % int(time.mktime(time.gmtime()))
157 date = "%s 0" % int(time.mktime(time.gmtime()))
158 extra = {}
158 extra = {}
159 if self.tagsbranch != 'default':
159 if self.tagsbranch != 'default':
160 extra['branch'] = self.tagsbranch
160 extra['branch'] = self.tagsbranch
161 try:
161 try:
162 tagparent = self.repo.changectx(self.tagsbranch).node()
162 tagparent = self.repo.changectx(self.tagsbranch).node()
163 except hg.RepoError, inst:
163 except hg.RepoError, inst:
164 tagparent = nullid
164 tagparent = nullid
165 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
165 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
166 date, tagparent, nullid)
166 date, tagparent, nullid)
167 return hex(self.repo.changelog.tip())
167 return hex(self.repo.changelog.tip())
168
168
169 def setfilemapmode(self, active):
169 def setfilemapmode(self, active):
170 self.filemapmode = active
170 self.filemapmode = active
171
171
172 class mercurial_source(converter_source):
172 class mercurial_source(converter_source):
173 def __init__(self, ui, path, rev=None):
173 def __init__(self, ui, path, rev=None):
174 converter_source.__init__(self, ui, path, rev)
174 converter_source.__init__(self, ui, path, rev)
175 try:
175 try:
176 self.repo = hg.repository(self.ui, path)
176 self.repo = hg.repository(self.ui, path)
177 # try to provoke an exception if this isn't really a hg
177 # try to provoke an exception if this isn't really a hg
178 # repo, but some other bogus compatible-looking url
178 # repo, but some other bogus compatible-looking url
179 self.repo.heads()
179 self.repo.heads()
180 except hg.RepoError:
180 except hg.RepoError:
181 ui.print_exc()
181 ui.print_exc()
182 raise NoRepo("could not open hg repo %s as source" % path)
182 raise NoRepo("could not open hg repo %s as source" % path)
183 self.lastrev = None
183 self.lastrev = None
184 self.lastctx = None
184 self.lastctx = None
185 self._changescache = None
185 self._changescache = None
186
186
187 def changectx(self, rev):
187 def changectx(self, rev):
188 if self.lastrev != rev:
188 if self.lastrev != rev:
189 self.lastctx = self.repo.changectx(rev)
189 self.lastctx = self.repo.changectx(rev)
190 self.lastrev = rev
190 self.lastrev = rev
191 return self.lastctx
191 return self.lastctx
192
192
193 def getheads(self):
193 def getheads(self):
194 if self.rev:
194 if self.rev:
195 return [hex(self.repo.changectx(self.rev).node())]
195 return [hex(self.repo.changectx(self.rev).node())]
196 else:
196 else:
197 return [hex(node) for node in self.repo.heads()]
197 return [hex(node) for node in self.repo.heads()]
198
198
199 def getfile(self, name, rev):
199 def getfile(self, name, rev):
200 try:
200 try:
201 return self.changectx(rev).filectx(name).data()
201 return self.changectx(rev).filectx(name).data()
202 except revlog.LookupError, err:
202 except revlog.LookupError, err:
203 raise IOError(err)
203 raise IOError(err)
204
204
205 def getmode(self, name, rev):
205 def getmode(self, name, rev):
206 m = self.changectx(rev).manifest()
206 m = self.changectx(rev).manifest()
207 return (m.execf(name) and 'x' or '') + (m.linkf(name) and 'l' or '')
207 return (m.execf(name) and 'x' or '') + (m.linkf(name) and 'l' or '')
208
208
209 def getchanges(self, rev):
209 def getchanges(self, rev):
210 ctx = self.changectx(rev)
210 ctx = self.changectx(rev)
211 if self._changescache and self._changescache[0] == rev:
211 if self._changescache and self._changescache[0] == rev:
212 m, a, r = self._changescache[1]
212 m, a, r = self._changescache[1]
213 else:
213 else:
214 m, a, r = self.repo.status(ctx.parents()[0].node(), ctx.node())[:3]
214 m, a, r = self.repo.status(ctx.parents()[0].node(), ctx.node())[:3]
215 changes = [(name, rev) for name in m + a + r]
215 changes = [(name, rev) for name in m + a + r]
216 changes.sort()
216 changes.sort()
217 return (changes, self.getcopies(ctx, m + a))
217 return (changes, self.getcopies(ctx, m + a))
218
218
219 def getcopies(self, ctx, files):
219 def getcopies(self, ctx, files):
220 copies = {}
220 copies = {}
221 for name in files:
221 for name in files:
222 try:
222 try:
223 copies[name] = ctx.filectx(name).renamed()[0]
223 copies[name] = ctx.filectx(name).renamed()[0]
224 except TypeError:
224 except TypeError:
225 pass
225 pass
226 return copies
226 return copies
227
227
228 def getcommit(self, rev):
228 def getcommit(self, rev):
229 ctx = self.changectx(rev)
229 ctx = self.changectx(rev)
230 parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid]
230 parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid]
231 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
231 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
232 desc=ctx.description(), parents=parents,
232 desc=ctx.description(), parents=parents,
233 branch=ctx.branch())
233 branch=ctx.branch(), extra=ctx.extra())
234
234
235 def gettags(self):
235 def gettags(self):
236 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
236 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
237 return dict([(name, hex(node)) for name, node in tags])
237 return dict([(name, hex(node)) for name, node in tags])
238
238
239 def getchangedfiles(self, rev, i):
239 def getchangedfiles(self, rev, i):
240 ctx = self.changectx(rev)
240 ctx = self.changectx(rev)
241 i = i or 0
241 i = i or 0
242 changes = self.repo.status(ctx.parents()[i].node(), ctx.node())[:3]
242 changes = self.repo.status(ctx.parents()[i].node(), ctx.node())[:3]
243
243
244 if i == 0:
244 if i == 0:
245 self._changescache = (rev, changes)
245 self._changescache = (rev, changes)
246
246
247 return changes[0] + changes[1] + changes[2]
247 return changes[0] + changes[1] + changes[2]
248
248
@@ -1,590 +1,591 b''
1 # context.py - changeset and file context objects for mercurial
1 # context.py - changeset and file context objects for mercurial
2 #
2 #
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 *
8 from node import *
9 from i18n import _
9 from i18n import _
10 import ancestor, bdiff, repo, revlog, util, os, errno
10 import ancestor, bdiff, repo, 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=None):
16 """changeid is a revision number, node, or tag"""
16 """changeid is a revision number, node, or tag"""
17 self._repo = repo
17 self._repo = repo
18
18
19 if not changeid and changeid != 0:
19 if not changeid and changeid != 0:
20 p1, p2 = self._repo.dirstate.parents()
20 p1, p2 = self._repo.dirstate.parents()
21 self._rev = self._repo.changelog.rev(p1)
21 self._rev = self._repo.changelog.rev(p1)
22 if self._rev == -1:
22 if self._rev == -1:
23 changeid = 'tip'
23 changeid = 'tip'
24 else:
24 else:
25 self._node = p1
25 self._node = p1
26 return
26 return
27
27
28 self._node = self._repo.lookup(changeid)
28 self._node = self._repo.lookup(changeid)
29 self._rev = self._repo.changelog.rev(self._node)
29 self._rev = self._repo.changelog.rev(self._node)
30
30
31 def __str__(self):
31 def __str__(self):
32 return short(self.node())
32 return short(self.node())
33
33
34 def __repr__(self):
34 def __repr__(self):
35 return "<changectx %s>" % str(self)
35 return "<changectx %s>" % str(self)
36
36
37 def __eq__(self, other):
37 def __eq__(self, other):
38 try:
38 try:
39 return self._rev == other._rev
39 return self._rev == other._rev
40 except AttributeError:
40 except AttributeError:
41 return False
41 return False
42
42
43 def __ne__(self, other):
43 def __ne__(self, other):
44 return not (self == other)
44 return not (self == other)
45
45
46 def __nonzero__(self):
46 def __nonzero__(self):
47 return self._rev != nullrev
47 return self._rev != nullrev
48
48
49 def __getattr__(self, name):
49 def __getattr__(self, name):
50 if name == '_changeset':
50 if name == '_changeset':
51 self._changeset = self._repo.changelog.read(self.node())
51 self._changeset = self._repo.changelog.read(self.node())
52 return self._changeset
52 return self._changeset
53 elif name == '_manifest':
53 elif name == '_manifest':
54 self._manifest = self._repo.manifest.read(self._changeset[0])
54 self._manifest = self._repo.manifest.read(self._changeset[0])
55 return self._manifest
55 return self._manifest
56 elif name == '_manifestdelta':
56 elif name == '_manifestdelta':
57 md = self._repo.manifest.readdelta(self._changeset[0])
57 md = self._repo.manifest.readdelta(self._changeset[0])
58 self._manifestdelta = md
58 self._manifestdelta = md
59 return self._manifestdelta
59 return self._manifestdelta
60 else:
60 else:
61 raise AttributeError, name
61 raise AttributeError, name
62
62
63 def __contains__(self, key):
63 def __contains__(self, key):
64 return key in self._manifest
64 return key in self._manifest
65
65
66 def __getitem__(self, key):
66 def __getitem__(self, key):
67 return self.filectx(key)
67 return self.filectx(key)
68
68
69 def __iter__(self):
69 def __iter__(self):
70 a = self._manifest.keys()
70 a = self._manifest.keys()
71 a.sort()
71 a.sort()
72 for f in a:
72 for f in a:
73 return f
73 return f
74
74
75 def changeset(self): return self._changeset
75 def changeset(self): return self._changeset
76 def manifest(self): return self._manifest
76 def manifest(self): return self._manifest
77
77
78 def rev(self): return self._rev
78 def rev(self): return self._rev
79 def node(self): return self._node
79 def node(self): return self._node
80 def user(self): return self._changeset[1]
80 def user(self): return self._changeset[1]
81 def date(self): return self._changeset[2]
81 def date(self): return self._changeset[2]
82 def files(self): return self._changeset[3]
82 def files(self): return self._changeset[3]
83 def description(self): return self._changeset[4]
83 def description(self): return self._changeset[4]
84 def branch(self): return self._changeset[5].get("branch")
84 def branch(self): return self._changeset[5].get("branch")
85 def extra(self): return self._changeset[5]
85 def tags(self): return self._repo.nodetags(self._node)
86 def tags(self): return self._repo.nodetags(self._node)
86
87
87 def parents(self):
88 def parents(self):
88 """return contexts for each parent changeset"""
89 """return contexts for each parent changeset"""
89 p = self._repo.changelog.parents(self._node)
90 p = self._repo.changelog.parents(self._node)
90 return [changectx(self._repo, x) for x in p]
91 return [changectx(self._repo, x) for x in p]
91
92
92 def children(self):
93 def children(self):
93 """return contexts for each child changeset"""
94 """return contexts for each child changeset"""
94 c = self._repo.changelog.children(self._node)
95 c = self._repo.changelog.children(self._node)
95 return [changectx(self._repo, x) for x in c]
96 return [changectx(self._repo, x) for x in c]
96
97
97 def _fileinfo(self, path):
98 def _fileinfo(self, path):
98 if '_manifest' in self.__dict__:
99 if '_manifest' in self.__dict__:
99 try:
100 try:
100 return self._manifest[path], self._manifest.flags(path)
101 return self._manifest[path], self._manifest.flags(path)
101 except KeyError:
102 except KeyError:
102 raise revlog.LookupError(_("'%s' not found in manifest") % path)
103 raise revlog.LookupError(_("'%s' not found in manifest") % path)
103 if '_manifestdelta' in self.__dict__ or path in self.files():
104 if '_manifestdelta' in self.__dict__ or path in self.files():
104 if path in self._manifestdelta:
105 if path in self._manifestdelta:
105 return self._manifestdelta[path], self._manifestdelta.flags(path)
106 return self._manifestdelta[path], self._manifestdelta.flags(path)
106 node, flag = self._repo.manifest.find(self._changeset[0], path)
107 node, flag = self._repo.manifest.find(self._changeset[0], path)
107 if not node:
108 if not node:
108 raise revlog.LookupError(_("'%s' not found in manifest") % path)
109 raise revlog.LookupError(_("'%s' not found in manifest") % path)
109
110
110 return node, flag
111 return node, flag
111
112
112 def filenode(self, path):
113 def filenode(self, path):
113 return self._fileinfo(path)[0]
114 return self._fileinfo(path)[0]
114
115
115 def fileflags(self, path):
116 def fileflags(self, path):
116 try:
117 try:
117 return self._fileinfo(path)[1]
118 return self._fileinfo(path)[1]
118 except revlog.LookupError:
119 except revlog.LookupError:
119 return ''
120 return ''
120
121
121 def filectx(self, path, fileid=None, filelog=None):
122 def filectx(self, path, fileid=None, filelog=None):
122 """get a file context from this changeset"""
123 """get a file context from this changeset"""
123 if fileid is None:
124 if fileid is None:
124 fileid = self.filenode(path)
125 fileid = self.filenode(path)
125 return filectx(self._repo, path, fileid=fileid,
126 return filectx(self._repo, path, fileid=fileid,
126 changectx=self, filelog=filelog)
127 changectx=self, filelog=filelog)
127
128
128 def filectxs(self):
129 def filectxs(self):
129 """generate a file context for each file in this changeset's
130 """generate a file context for each file in this changeset's
130 manifest"""
131 manifest"""
131 mf = self.manifest()
132 mf = self.manifest()
132 m = mf.keys()
133 m = mf.keys()
133 m.sort()
134 m.sort()
134 for f in m:
135 for f in m:
135 yield self.filectx(f, fileid=mf[f])
136 yield self.filectx(f, fileid=mf[f])
136
137
137 def ancestor(self, c2):
138 def ancestor(self, c2):
138 """
139 """
139 return the ancestor context of self and c2
140 return the ancestor context of self and c2
140 """
141 """
141 n = self._repo.changelog.ancestor(self._node, c2._node)
142 n = self._repo.changelog.ancestor(self._node, c2._node)
142 return changectx(self._repo, n)
143 return changectx(self._repo, n)
143
144
144 class filectx(object):
145 class filectx(object):
145 """A filecontext object makes access to data related to a particular
146 """A filecontext object makes access to data related to a particular
146 filerevision convenient."""
147 filerevision convenient."""
147 def __init__(self, repo, path, changeid=None, fileid=None,
148 def __init__(self, repo, path, changeid=None, fileid=None,
148 filelog=None, changectx=None):
149 filelog=None, changectx=None):
149 """changeid can be a changeset revision, node, or tag.
150 """changeid can be a changeset revision, node, or tag.
150 fileid can be a file revision or node."""
151 fileid can be a file revision or node."""
151 self._repo = repo
152 self._repo = repo
152 self._path = path
153 self._path = path
153
154
154 assert (changeid is not None
155 assert (changeid is not None
155 or fileid is not None
156 or fileid is not None
156 or changectx is not None)
157 or changectx is not None)
157
158
158 if filelog:
159 if filelog:
159 self._filelog = filelog
160 self._filelog = filelog
160
161
161 if fileid is None:
162 if fileid is None:
162 if changectx is None:
163 if changectx is None:
163 self._changeid = changeid
164 self._changeid = changeid
164 else:
165 else:
165 self._changectx = changectx
166 self._changectx = changectx
166 else:
167 else:
167 self._fileid = fileid
168 self._fileid = fileid
168
169
169 def __getattr__(self, name):
170 def __getattr__(self, name):
170 if name == '_changectx':
171 if name == '_changectx':
171 self._changectx = changectx(self._repo, self._changeid)
172 self._changectx = changectx(self._repo, self._changeid)
172 return self._changectx
173 return self._changectx
173 elif name == '_filelog':
174 elif name == '_filelog':
174 self._filelog = self._repo.file(self._path)
175 self._filelog = self._repo.file(self._path)
175 return self._filelog
176 return self._filelog
176 elif name == '_changeid':
177 elif name == '_changeid':
177 self._changeid = self._filelog.linkrev(self._filenode)
178 self._changeid = self._filelog.linkrev(self._filenode)
178 return self._changeid
179 return self._changeid
179 elif name == '_filenode':
180 elif name == '_filenode':
180 if '_fileid' in self.__dict__:
181 if '_fileid' in self.__dict__:
181 self._filenode = self._filelog.lookup(self._fileid)
182 self._filenode = self._filelog.lookup(self._fileid)
182 else:
183 else:
183 self._filenode = self._changectx.filenode(self._path)
184 self._filenode = self._changectx.filenode(self._path)
184 return self._filenode
185 return self._filenode
185 elif name == '_filerev':
186 elif name == '_filerev':
186 self._filerev = self._filelog.rev(self._filenode)
187 self._filerev = self._filelog.rev(self._filenode)
187 return self._filerev
188 return self._filerev
188 else:
189 else:
189 raise AttributeError, name
190 raise AttributeError, name
190
191
191 def __nonzero__(self):
192 def __nonzero__(self):
192 try:
193 try:
193 n = self._filenode
194 n = self._filenode
194 return True
195 return True
195 except revlog.LookupError:
196 except revlog.LookupError:
196 # file is missing
197 # file is missing
197 return False
198 return False
198
199
199 def __str__(self):
200 def __str__(self):
200 return "%s@%s" % (self.path(), short(self.node()))
201 return "%s@%s" % (self.path(), short(self.node()))
201
202
202 def __repr__(self):
203 def __repr__(self):
203 return "<filectx %s>" % str(self)
204 return "<filectx %s>" % str(self)
204
205
205 def __eq__(self, other):
206 def __eq__(self, other):
206 try:
207 try:
207 return (self._path == other._path
208 return (self._path == other._path
208 and self._fileid == other._fileid)
209 and self._fileid == other._fileid)
209 except AttributeError:
210 except AttributeError:
210 return False
211 return False
211
212
212 def __ne__(self, other):
213 def __ne__(self, other):
213 return not (self == other)
214 return not (self == other)
214
215
215 def filectx(self, fileid):
216 def filectx(self, fileid):
216 '''opens an arbitrary revision of the file without
217 '''opens an arbitrary revision of the file without
217 opening a new filelog'''
218 opening a new filelog'''
218 return filectx(self._repo, self._path, fileid=fileid,
219 return filectx(self._repo, self._path, fileid=fileid,
219 filelog=self._filelog)
220 filelog=self._filelog)
220
221
221 def filerev(self): return self._filerev
222 def filerev(self): return self._filerev
222 def filenode(self): return self._filenode
223 def filenode(self): return self._filenode
223 def fileflags(self): return self._changectx.fileflags(self._path)
224 def fileflags(self): return self._changectx.fileflags(self._path)
224 def isexec(self): return 'x' in self.fileflags()
225 def isexec(self): return 'x' in self.fileflags()
225 def islink(self): return 'l' in self.fileflags()
226 def islink(self): return 'l' in self.fileflags()
226 def filelog(self): return self._filelog
227 def filelog(self): return self._filelog
227
228
228 def rev(self):
229 def rev(self):
229 if '_changectx' in self.__dict__:
230 if '_changectx' in self.__dict__:
230 return self._changectx.rev()
231 return self._changectx.rev()
231 return self._filelog.linkrev(self._filenode)
232 return self._filelog.linkrev(self._filenode)
232
233
233 def node(self): return self._changectx.node()
234 def node(self): return self._changectx.node()
234 def user(self): return self._changectx.user()
235 def user(self): return self._changectx.user()
235 def date(self): return self._changectx.date()
236 def date(self): return self._changectx.date()
236 def files(self): return self._changectx.files()
237 def files(self): return self._changectx.files()
237 def description(self): return self._changectx.description()
238 def description(self): return self._changectx.description()
238 def branch(self): return self._changectx.branch()
239 def branch(self): return self._changectx.branch()
239 def manifest(self): return self._changectx.manifest()
240 def manifest(self): return self._changectx.manifest()
240 def changectx(self): return self._changectx
241 def changectx(self): return self._changectx
241
242
242 def data(self): return self._filelog.read(self._filenode)
243 def data(self): return self._filelog.read(self._filenode)
243 def renamed(self): return self._filelog.renamed(self._filenode)
244 def renamed(self): return self._filelog.renamed(self._filenode)
244 def path(self): return self._path
245 def path(self): return self._path
245 def size(self): return self._filelog.size(self._filerev)
246 def size(self): return self._filelog.size(self._filerev)
246
247
247 def cmp(self, text): return self._filelog.cmp(self._filenode, text)
248 def cmp(self, text): return self._filelog.cmp(self._filenode, text)
248
249
249 def parents(self):
250 def parents(self):
250 p = self._path
251 p = self._path
251 fl = self._filelog
252 fl = self._filelog
252 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
253 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
253
254
254 r = self.renamed()
255 r = self.renamed()
255 if r:
256 if r:
256 pl[0] = (r[0], r[1], None)
257 pl[0] = (r[0], r[1], None)
257
258
258 return [filectx(self._repo, p, fileid=n, filelog=l)
259 return [filectx(self._repo, p, fileid=n, filelog=l)
259 for p,n,l in pl if n != nullid]
260 for p,n,l in pl if n != nullid]
260
261
261 def children(self):
262 def children(self):
262 # hard for renames
263 # hard for renames
263 c = self._filelog.children(self._filenode)
264 c = self._filelog.children(self._filenode)
264 return [filectx(self._repo, self._path, fileid=x,
265 return [filectx(self._repo, self._path, fileid=x,
265 filelog=self._filelog) for x in c]
266 filelog=self._filelog) for x in c]
266
267
267 def annotate(self, follow=False, linenumber=None):
268 def annotate(self, follow=False, linenumber=None):
268 '''returns a list of tuples of (ctx, line) for each line
269 '''returns a list of tuples of (ctx, line) for each line
269 in the file, where ctx is the filectx of the node where
270 in the file, where ctx is the filectx of the node where
270 that line was last changed.
271 that line was last changed.
271 This returns tuples of ((ctx, linenumber), line) for each line,
272 This returns tuples of ((ctx, linenumber), line) for each line,
272 if "linenumber" parameter is NOT "None".
273 if "linenumber" parameter is NOT "None".
273 In such tuples, linenumber means one at the first appearance
274 In such tuples, linenumber means one at the first appearance
274 in the managed file.
275 in the managed file.
275 To reduce annotation cost,
276 To reduce annotation cost,
276 this returns fixed value(False is used) as linenumber,
277 this returns fixed value(False is used) as linenumber,
277 if "linenumber" parameter is "False".'''
278 if "linenumber" parameter is "False".'''
278
279
279 def decorate_compat(text, rev):
280 def decorate_compat(text, rev):
280 return ([rev] * len(text.splitlines()), text)
281 return ([rev] * len(text.splitlines()), text)
281
282
282 def without_linenumber(text, rev):
283 def without_linenumber(text, rev):
283 return ([(rev, False)] * len(text.splitlines()), text)
284 return ([(rev, False)] * len(text.splitlines()), text)
284
285
285 def with_linenumber(text, rev):
286 def with_linenumber(text, rev):
286 size = len(text.splitlines())
287 size = len(text.splitlines())
287 return ([(rev, i) for i in xrange(1, size + 1)], text)
288 return ([(rev, i) for i in xrange(1, size + 1)], text)
288
289
289 decorate = (((linenumber is None) and decorate_compat) or
290 decorate = (((linenumber is None) and decorate_compat) or
290 (linenumber and with_linenumber) or
291 (linenumber and with_linenumber) or
291 without_linenumber)
292 without_linenumber)
292
293
293 def pair(parent, child):
294 def pair(parent, child):
294 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
295 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
295 child[0][b1:b2] = parent[0][a1:a2]
296 child[0][b1:b2] = parent[0][a1:a2]
296 return child
297 return child
297
298
298 getlog = util.cachefunc(lambda x: self._repo.file(x))
299 getlog = util.cachefunc(lambda x: self._repo.file(x))
299 def getctx(path, fileid):
300 def getctx(path, fileid):
300 log = path == self._path and self._filelog or getlog(path)
301 log = path == self._path and self._filelog or getlog(path)
301 return filectx(self._repo, path, fileid=fileid, filelog=log)
302 return filectx(self._repo, path, fileid=fileid, filelog=log)
302 getctx = util.cachefunc(getctx)
303 getctx = util.cachefunc(getctx)
303
304
304 def parents(f):
305 def parents(f):
305 # we want to reuse filectx objects as much as possible
306 # we want to reuse filectx objects as much as possible
306 p = f._path
307 p = f._path
307 if f._filerev is None: # working dir
308 if f._filerev is None: # working dir
308 pl = [(n.path(), n.filerev()) for n in f.parents()]
309 pl = [(n.path(), n.filerev()) for n in f.parents()]
309 else:
310 else:
310 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
311 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
311
312
312 if follow:
313 if follow:
313 r = f.renamed()
314 r = f.renamed()
314 if r:
315 if r:
315 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
316 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
316
317
317 return [getctx(p, n) for p, n in pl if n != nullrev]
318 return [getctx(p, n) for p, n in pl if n != nullrev]
318
319
319 # use linkrev to find the first changeset where self appeared
320 # use linkrev to find the first changeset where self appeared
320 if self.rev() != self._filelog.linkrev(self._filenode):
321 if self.rev() != self._filelog.linkrev(self._filenode):
321 base = self.filectx(self.filerev())
322 base = self.filectx(self.filerev())
322 else:
323 else:
323 base = self
324 base = self
324
325
325 # find all ancestors
326 # find all ancestors
326 needed = {base: 1}
327 needed = {base: 1}
327 visit = [base]
328 visit = [base]
328 files = [base._path]
329 files = [base._path]
329 while visit:
330 while visit:
330 f = visit.pop(0)
331 f = visit.pop(0)
331 for p in parents(f):
332 for p in parents(f):
332 if p not in needed:
333 if p not in needed:
333 needed[p] = 1
334 needed[p] = 1
334 visit.append(p)
335 visit.append(p)
335 if p._path not in files:
336 if p._path not in files:
336 files.append(p._path)
337 files.append(p._path)
337 else:
338 else:
338 # count how many times we'll use this
339 # count how many times we'll use this
339 needed[p] += 1
340 needed[p] += 1
340
341
341 # sort by revision (per file) which is a topological order
342 # sort by revision (per file) which is a topological order
342 visit = []
343 visit = []
343 for f in files:
344 for f in files:
344 fn = [(n.rev(), n) for n in needed.keys() if n._path == f]
345 fn = [(n.rev(), n) for n in needed.keys() if n._path == f]
345 visit.extend(fn)
346 visit.extend(fn)
346 visit.sort()
347 visit.sort()
347 hist = {}
348 hist = {}
348
349
349 for r, f in visit:
350 for r, f in visit:
350 curr = decorate(f.data(), f)
351 curr = decorate(f.data(), f)
351 for p in parents(f):
352 for p in parents(f):
352 if p != nullid:
353 if p != nullid:
353 curr = pair(hist[p], curr)
354 curr = pair(hist[p], curr)
354 # trim the history of unneeded revs
355 # trim the history of unneeded revs
355 needed[p] -= 1
356 needed[p] -= 1
356 if not needed[p]:
357 if not needed[p]:
357 del hist[p]
358 del hist[p]
358 hist[f] = curr
359 hist[f] = curr
359
360
360 return zip(hist[f][0], hist[f][1].splitlines(1))
361 return zip(hist[f][0], hist[f][1].splitlines(1))
361
362
362 def ancestor(self, fc2):
363 def ancestor(self, fc2):
363 """
364 """
364 find the common ancestor file context, if any, of self, and fc2
365 find the common ancestor file context, if any, of self, and fc2
365 """
366 """
366
367
367 acache = {}
368 acache = {}
368
369
369 # prime the ancestor cache for the working directory
370 # prime the ancestor cache for the working directory
370 for c in (self, fc2):
371 for c in (self, fc2):
371 if c._filerev == None:
372 if c._filerev == None:
372 pl = [(n.path(), n.filenode()) for n in c.parents()]
373 pl = [(n.path(), n.filenode()) for n in c.parents()]
373 acache[(c._path, None)] = pl
374 acache[(c._path, None)] = pl
374
375
375 flcache = {self._path:self._filelog, fc2._path:fc2._filelog}
376 flcache = {self._path:self._filelog, fc2._path:fc2._filelog}
376 def parents(vertex):
377 def parents(vertex):
377 if vertex in acache:
378 if vertex in acache:
378 return acache[vertex]
379 return acache[vertex]
379 f, n = vertex
380 f, n = vertex
380 if f not in flcache:
381 if f not in flcache:
381 flcache[f] = self._repo.file(f)
382 flcache[f] = self._repo.file(f)
382 fl = flcache[f]
383 fl = flcache[f]
383 pl = [(f, p) for p in fl.parents(n) if p != nullid]
384 pl = [(f, p) for p in fl.parents(n) if p != nullid]
384 re = fl.renamed(n)
385 re = fl.renamed(n)
385 if re:
386 if re:
386 pl.append(re)
387 pl.append(re)
387 acache[vertex] = pl
388 acache[vertex] = pl
388 return pl
389 return pl
389
390
390 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
391 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
391 v = ancestor.ancestor(a, b, parents)
392 v = ancestor.ancestor(a, b, parents)
392 if v:
393 if v:
393 f, n = v
394 f, n = v
394 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
395 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
395
396
396 return None
397 return None
397
398
398 class workingctx(changectx):
399 class workingctx(changectx):
399 """A workingctx object makes access to data related to
400 """A workingctx object makes access to data related to
400 the current working directory convenient."""
401 the current working directory convenient."""
401 def __init__(self, repo):
402 def __init__(self, repo):
402 self._repo = repo
403 self._repo = repo
403 self._rev = None
404 self._rev = None
404 self._node = None
405 self._node = None
405
406
406 def __str__(self):
407 def __str__(self):
407 return str(self._parents[0]) + "+"
408 return str(self._parents[0]) + "+"
408
409
409 def __nonzero__(self):
410 def __nonzero__(self):
410 return True
411 return True
411
412
412 def __getattr__(self, name):
413 def __getattr__(self, name):
413 if name == '_parents':
414 if name == '_parents':
414 self._parents = self._repo.parents()
415 self._parents = self._repo.parents()
415 return self._parents
416 return self._parents
416 if name == '_status':
417 if name == '_status':
417 self._status = self._repo.status()
418 self._status = self._repo.status()
418 return self._status
419 return self._status
419 if name == '_manifest':
420 if name == '_manifest':
420 self._buildmanifest()
421 self._buildmanifest()
421 return self._manifest
422 return self._manifest
422 else:
423 else:
423 raise AttributeError, name
424 raise AttributeError, name
424
425
425 def _buildmanifest(self):
426 def _buildmanifest(self):
426 """generate a manifest corresponding to the working directory"""
427 """generate a manifest corresponding to the working directory"""
427
428
428 man = self._parents[0].manifest().copy()
429 man = self._parents[0].manifest().copy()
429 copied = self._repo.dirstate.copies()
430 copied = self._repo.dirstate.copies()
430 is_exec = util.execfunc(self._repo.root,
431 is_exec = util.execfunc(self._repo.root,
431 lambda p: man.execf(copied.get(p,p)))
432 lambda p: man.execf(copied.get(p,p)))
432 is_link = util.linkfunc(self._repo.root,
433 is_link = util.linkfunc(self._repo.root,
433 lambda p: man.linkf(copied.get(p,p)))
434 lambda p: man.linkf(copied.get(p,p)))
434 modified, added, removed, deleted, unknown = self._status[:5]
435 modified, added, removed, deleted, unknown = self._status[:5]
435 for i, l in (("a", added), ("m", modified), ("u", unknown)):
436 for i, l in (("a", added), ("m", modified), ("u", unknown)):
436 for f in l:
437 for f in l:
437 man[f] = man.get(copied.get(f, f), nullid) + i
438 man[f] = man.get(copied.get(f, f), nullid) + i
438 try:
439 try:
439 man.set(f, is_exec(f), is_link(f))
440 man.set(f, is_exec(f), is_link(f))
440 except OSError:
441 except OSError:
441 pass
442 pass
442
443
443 for f in deleted + removed:
444 for f in deleted + removed:
444 if f in man:
445 if f in man:
445 del man[f]
446 del man[f]
446
447
447 self._manifest = man
448 self._manifest = man
448
449
449 def manifest(self): return self._manifest
450 def manifest(self): return self._manifest
450
451
451 def user(self): return self._repo.ui.username()
452 def user(self): return self._repo.ui.username()
452 def date(self): return util.makedate()
453 def date(self): return util.makedate()
453 def description(self): return ""
454 def description(self): return ""
454 def files(self):
455 def files(self):
455 f = self.modified() + self.added() + self.removed()
456 f = self.modified() + self.added() + self.removed()
456 f.sort()
457 f.sort()
457 return f
458 return f
458
459
459 def modified(self): return self._status[0]
460 def modified(self): return self._status[0]
460 def added(self): return self._status[1]
461 def added(self): return self._status[1]
461 def removed(self): return self._status[2]
462 def removed(self): return self._status[2]
462 def deleted(self): return self._status[3]
463 def deleted(self): return self._status[3]
463 def unknown(self): return self._status[4]
464 def unknown(self): return self._status[4]
464 def clean(self): return self._status[5]
465 def clean(self): return self._status[5]
465 def branch(self): return self._repo.dirstate.branch()
466 def branch(self): return self._repo.dirstate.branch()
466
467
467 def tags(self):
468 def tags(self):
468 t = []
469 t = []
469 [t.extend(p.tags()) for p in self.parents()]
470 [t.extend(p.tags()) for p in self.parents()]
470 return t
471 return t
471
472
472 def parents(self):
473 def parents(self):
473 """return contexts for each parent changeset"""
474 """return contexts for each parent changeset"""
474 return self._parents
475 return self._parents
475
476
476 def children(self):
477 def children(self):
477 return []
478 return []
478
479
479 def fileflags(self, path):
480 def fileflags(self, path):
480 if '_manifest' in self.__dict__:
481 if '_manifest' in self.__dict__:
481 try:
482 try:
482 return self._manifest.flags(path)
483 return self._manifest.flags(path)
483 except KeyError:
484 except KeyError:
484 return ''
485 return ''
485
486
486 pnode = self._parents[0].changeset()[0]
487 pnode = self._parents[0].changeset()[0]
487 orig = self._repo.dirstate.copies().get(path, path)
488 orig = self._repo.dirstate.copies().get(path, path)
488 node, flag = self._repo.manifest.find(pnode, orig)
489 node, flag = self._repo.manifest.find(pnode, orig)
489 is_link = util.linkfunc(self._repo.root, lambda p: 'l' in flag)
490 is_link = util.linkfunc(self._repo.root, lambda p: 'l' in flag)
490 is_exec = util.execfunc(self._repo.root, lambda p: 'x' in flag)
491 is_exec = util.execfunc(self._repo.root, lambda p: 'x' in flag)
491 try:
492 try:
492 return (is_link(path) and 'l' or '') + (is_exec(path) and 'e' or '')
493 return (is_link(path) and 'l' or '') + (is_exec(path) and 'e' or '')
493 except OSError:
494 except OSError:
494 pass
495 pass
495
496
496 if not node or path in self.deleted() or path in self.removed():
497 if not node or path in self.deleted() or path in self.removed():
497 return ''
498 return ''
498 return flag
499 return flag
499
500
500 def filectx(self, path, filelog=None):
501 def filectx(self, path, filelog=None):
501 """get a file context from the working directory"""
502 """get a file context from the working directory"""
502 return workingfilectx(self._repo, path, workingctx=self,
503 return workingfilectx(self._repo, path, workingctx=self,
503 filelog=filelog)
504 filelog=filelog)
504
505
505 def ancestor(self, c2):
506 def ancestor(self, c2):
506 """return the ancestor context of self and c2"""
507 """return the ancestor context of self and c2"""
507 return self._parents[0].ancestor(c2) # punt on two parents for now
508 return self._parents[0].ancestor(c2) # punt on two parents for now
508
509
509 class workingfilectx(filectx):
510 class workingfilectx(filectx):
510 """A workingfilectx object makes access to data related to a particular
511 """A workingfilectx object makes access to data related to a particular
511 file in the working directory convenient."""
512 file in the working directory convenient."""
512 def __init__(self, repo, path, filelog=None, workingctx=None):
513 def __init__(self, repo, path, filelog=None, workingctx=None):
513 """changeid can be a changeset revision, node, or tag.
514 """changeid can be a changeset revision, node, or tag.
514 fileid can be a file revision or node."""
515 fileid can be a file revision or node."""
515 self._repo = repo
516 self._repo = repo
516 self._path = path
517 self._path = path
517 self._changeid = None
518 self._changeid = None
518 self._filerev = self._filenode = None
519 self._filerev = self._filenode = None
519
520
520 if filelog:
521 if filelog:
521 self._filelog = filelog
522 self._filelog = filelog
522 if workingctx:
523 if workingctx:
523 self._changectx = workingctx
524 self._changectx = workingctx
524
525
525 def __getattr__(self, name):
526 def __getattr__(self, name):
526 if name == '_changectx':
527 if name == '_changectx':
527 self._changectx = workingctx(self._repo)
528 self._changectx = workingctx(self._repo)
528 return self._changectx
529 return self._changectx
529 elif name == '_repopath':
530 elif name == '_repopath':
530 self._repopath = (self._repo.dirstate.copied(self._path)
531 self._repopath = (self._repo.dirstate.copied(self._path)
531 or self._path)
532 or self._path)
532 return self._repopath
533 return self._repopath
533 elif name == '_filelog':
534 elif name == '_filelog':
534 self._filelog = self._repo.file(self._repopath)
535 self._filelog = self._repo.file(self._repopath)
535 return self._filelog
536 return self._filelog
536 else:
537 else:
537 raise AttributeError, name
538 raise AttributeError, name
538
539
539 def __nonzero__(self):
540 def __nonzero__(self):
540 return True
541 return True
541
542
542 def __str__(self):
543 def __str__(self):
543 return "%s@%s" % (self.path(), self._changectx)
544 return "%s@%s" % (self.path(), self._changectx)
544
545
545 def filectx(self, fileid):
546 def filectx(self, fileid):
546 '''opens an arbitrary revision of the file without
547 '''opens an arbitrary revision of the file without
547 opening a new filelog'''
548 opening a new filelog'''
548 return filectx(self._repo, self._repopath, fileid=fileid,
549 return filectx(self._repo, self._repopath, fileid=fileid,
549 filelog=self._filelog)
550 filelog=self._filelog)
550
551
551 def rev(self):
552 def rev(self):
552 if '_changectx' in self.__dict__:
553 if '_changectx' in self.__dict__:
553 return self._changectx.rev()
554 return self._changectx.rev()
554 return self._filelog.linkrev(self._filenode)
555 return self._filelog.linkrev(self._filenode)
555
556
556 def data(self): return self._repo.wread(self._path)
557 def data(self): return self._repo.wread(self._path)
557 def renamed(self):
558 def renamed(self):
558 rp = self._repopath
559 rp = self._repopath
559 if rp == self._path:
560 if rp == self._path:
560 return None
561 return None
561 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
562 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
562
563
563 def parents(self):
564 def parents(self):
564 '''return parent filectxs, following copies if necessary'''
565 '''return parent filectxs, following copies if necessary'''
565 p = self._path
566 p = self._path
566 rp = self._repopath
567 rp = self._repopath
567 pcl = self._changectx._parents
568 pcl = self._changectx._parents
568 fl = self._filelog
569 fl = self._filelog
569 pl = [(rp, pcl[0]._manifest.get(rp, nullid), fl)]
570 pl = [(rp, pcl[0]._manifest.get(rp, nullid), fl)]
570 if len(pcl) > 1:
571 if len(pcl) > 1:
571 if rp != p:
572 if rp != p:
572 fl = None
573 fl = None
573 pl.append((p, pcl[1]._manifest.get(p, nullid), fl))
574 pl.append((p, pcl[1]._manifest.get(p, nullid), fl))
574
575
575 return [filectx(self._repo, p, fileid=n, filelog=l)
576 return [filectx(self._repo, p, fileid=n, filelog=l)
576 for p,n,l in pl if n != nullid]
577 for p,n,l in pl if n != nullid]
577
578
578 def children(self):
579 def children(self):
579 return []
580 return []
580
581
581 def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
582 def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
582 def date(self):
583 def date(self):
583 t, tz = self._changectx.date()
584 t, tz = self._changectx.date()
584 try:
585 try:
585 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
586 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
586 except OSError, err:
587 except OSError, err:
587 if err.errno != errno.ENOENT: raise
588 if err.errno != errno.ENOENT: raise
588 return (t, tz)
589 return (t, tz)
589
590
590 def cmp(self, text): return self._repo.wread(self._path) == text
591 def cmp(self, text): return self._repo.wread(self._path) == text
General Comments 0
You need to be logged in to leave comments. Login now