##// END OF EJS Templates
Add lookup method to filectx
Brendan Cully -
r3207:0790dce2 default
parent child Browse files
Show More
@@ -1,268 +1,274 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 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 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 demandload import *
8 from demandload import *
9 from node import *
9 from node import *
10 demandload(globals(), 'bdiff')
10 demandload(globals(), 'bdiff')
11
11
12 from node import *
12 from node import *
13 from demandload import demandload
13 from demandload import demandload
14 demandload(globals(), "ancestor util")
14 demandload(globals(), "ancestor util")
15
15
16 class changectx(object):
16 class changectx(object):
17 """A changecontext object makes access to data related to a particular
17 """A changecontext object makes access to data related to a particular
18 changeset convenient."""
18 changeset convenient."""
19 def __init__(self, repo, changeid=None):
19 def __init__(self, repo, changeid=None):
20 """changeid is a revision number, node, or tag"""
20 """changeid is a revision number, node, or tag"""
21 self._repo = repo
21 self._repo = repo
22
22
23 if not changeid and changeid != 0:
23 if not changeid and changeid != 0:
24 p1, p2 = self._repo.dirstate.parents()
24 p1, p2 = self._repo.dirstate.parents()
25 self._rev = self._repo.changelog.rev(p1)
25 self._rev = self._repo.changelog.rev(p1)
26 if self._rev == -1:
26 if self._rev == -1:
27 changeid = 'tip'
27 changeid = 'tip'
28 else:
28 else:
29 self._node = p1
29 self._node = p1
30 return
30 return
31
31
32 self._node = self._repo.lookup(changeid)
32 self._node = self._repo.lookup(changeid)
33 self._rev = self._repo.changelog.rev(self._node)
33 self._rev = self._repo.changelog.rev(self._node)
34
34
35 def __repr__(self):
35 def __repr__(self):
36 return "<changectx %s>" % short(self.node())
36 return "<changectx %s>" % short(self.node())
37
37
38 def changeset(self):
38 def changeset(self):
39 try:
39 try:
40 return self._changeset
40 return self._changeset
41 except AttributeError:
41 except AttributeError:
42 self._changeset = self._repo.changelog.read(self.node())
42 self._changeset = self._repo.changelog.read(self.node())
43 return self._changeset
43 return self._changeset
44
44
45 def manifest(self):
45 def manifest(self):
46 try:
46 try:
47 return self._manifest
47 return self._manifest
48 except AttributeError:
48 except AttributeError:
49 self._manifest = self._repo.manifest.read(self.changeset()[0])
49 self._manifest = self._repo.manifest.read(self.changeset()[0])
50 return self._manifest
50 return self._manifest
51
51
52 def rev(self): return self._rev
52 def rev(self): return self._rev
53 def node(self): return self._node
53 def node(self): return self._node
54 def user(self): return self.changeset()[1]
54 def user(self): return self.changeset()[1]
55 def date(self): return self.changeset()[2]
55 def date(self): return self.changeset()[2]
56 def files(self): return self.changeset()[3]
56 def files(self): return self.changeset()[3]
57 def description(self): return self.changeset()[4]
57 def description(self): return self.changeset()[4]
58
58
59 def parents(self):
59 def parents(self):
60 """return contexts for each parent changeset"""
60 """return contexts for each parent changeset"""
61 p = self._repo.changelog.parents(self._node)
61 p = self._repo.changelog.parents(self._node)
62 return [ changectx(self._repo, x) for x in p ]
62 return [ changectx(self._repo, x) for x in p ]
63
63
64 def children(self):
64 def children(self):
65 """return contexts for each child changeset"""
65 """return contexts for each child changeset"""
66 c = self._repo.changelog.children(self._node)
66 c = self._repo.changelog.children(self._node)
67 return [ changectx(self._repo, x) for x in c ]
67 return [ changectx(self._repo, x) for x in c ]
68
68
69 def filenode(self, path):
69 def filenode(self, path):
70 node, flag = self._repo.manifest.find(self.changeset()[0], path)
70 node, flag = self._repo.manifest.find(self.changeset()[0], path)
71 return node
71 return node
72
72
73 def filectx(self, path, fileid=None):
73 def filectx(self, path, fileid=None):
74 """get a file context from this changeset"""
74 """get a file context from this changeset"""
75 if fileid is None:
75 if fileid is None:
76 fileid = self.filenode(path)
76 fileid = self.filenode(path)
77 return filectx(self._repo, path, fileid=fileid)
77 return filectx(self._repo, path, fileid=fileid)
78
78
79 def filectxs(self):
79 def filectxs(self):
80 """generate a file context for each file in this changeset's
80 """generate a file context for each file in this changeset's
81 manifest"""
81 manifest"""
82 mf = self.manifest()
82 mf = self.manifest()
83 m = mf.keys()
83 m = mf.keys()
84 m.sort()
84 m.sort()
85 for f in m:
85 for f in m:
86 yield self.filectx(f, fileid=mf[f])
86 yield self.filectx(f, fileid=mf[f])
87
87
88 def ancestor(self, c2):
88 def ancestor(self, c2):
89 """
89 """
90 return the ancestor context of self and c2
90 return the ancestor context of self and c2
91 """
91 """
92 n = self._repo.changelog.ancestor(self._node, c2._node)
92 n = self._repo.changelog.ancestor(self._node, c2._node)
93 return changectx(self._repo, n)
93 return changectx(self._repo, n)
94
94
95 class filectx(object):
95 class filectx(object):
96 """A filecontext object makes access to data related to a particular
96 """A filecontext object makes access to data related to a particular
97 filerevision convenient."""
97 filerevision convenient."""
98 def __init__(self, repo, path, changeid=None, fileid=None, filelog=None):
98 def __init__(self, repo, path, changeid=None, fileid=None, filelog=None):
99 """changeid can be a changeset revision, node, or tag.
99 """changeid can be a changeset revision, node, or tag.
100 fileid can be a file revision or node."""
100 fileid can be a file revision or node."""
101 self._repo = repo
101 self._repo = repo
102 self._path = path
102 self._path = path
103
103
104 assert changeid is not None or fileid is not None
104 assert changeid is not None or fileid is not None
105
105
106 if filelog:
106 if filelog:
107 self._filelog = filelog
107 self._filelog = filelog
108 else:
108 else:
109 self._filelog = self._repo.file(self._path)
109 self._filelog = self._repo.file(self._path)
110
110
111 if fileid is None:
111 if fileid is None:
112 self._changeid = changeid
112 self._changeid = changeid
113 else:
113 else:
114 self._filenode = self._filelog.lookup(fileid)
114 self._filenode = self._filelog.lookup(fileid)
115 self._changeid = self._filelog.linkrev(self._filenode)
115 self._changeid = self._filelog.linkrev(self._filenode)
116
116
117 def __getattr__(self, name):
117 def __getattr__(self, name):
118 if name == '_changectx':
118 if name == '_changectx':
119 self._changectx = changectx(self._repo, self._changeid)
119 self._changectx = changectx(self._repo, self._changeid)
120 return self._changectx
120 return self._changectx
121 elif name == '_filenode':
121 elif name == '_filenode':
122 self._filenode = self._changectx.filenode(self._path)
122 self._filenode = self._changectx.filenode(self._path)
123 return self._filenode
123 return self._filenode
124 elif name == '_filerev':
124 elif name == '_filerev':
125 self._filerev = self._filelog.rev(self._filenode)
125 self._filerev = self._filelog.rev(self._filenode)
126 return self._filerev
126 return self._filerev
127 else:
127 else:
128 raise AttributeError, name
128 raise AttributeError, name
129
129
130 def __repr__(self):
130 def __repr__(self):
131 return "<filectx %s@%s>" % (self.path(), short(self.node()))
131 return "<filectx %s@%s>" % (self.path(), short(self.node()))
132
132
133 def filectx(self, fileid):
134 '''opens an arbitrary revision of the file without
135 opening a new filelog'''
136 return filectx(self._repo, self._path, fileid=fileid,
137 filelog=self._filelog)
138
133 def filerev(self): return self._filerev
139 def filerev(self): return self._filerev
134 def filenode(self): return self._filenode
140 def filenode(self): return self._filenode
135 def filelog(self): return self._filelog
141 def filelog(self): return self._filelog
136
142
137 def rev(self):
143 def rev(self):
138 if hasattr(self, "_changectx"):
144 if hasattr(self, "_changectx"):
139 return self._changectx.rev()
145 return self._changectx.rev()
140 return self._filelog.linkrev(self._filenode)
146 return self._filelog.linkrev(self._filenode)
141
147
142 def node(self): return self._changectx.node()
148 def node(self): return self._changectx.node()
143 def user(self): return self._changectx.user()
149 def user(self): return self._changectx.user()
144 def date(self): return self._changectx.date()
150 def date(self): return self._changectx.date()
145 def files(self): return self._changectx.files()
151 def files(self): return self._changectx.files()
146 def description(self): return self._changectx.description()
152 def description(self): return self._changectx.description()
147 def manifest(self): return self._changectx.manifest()
153 def manifest(self): return self._changectx.manifest()
148 def changectx(self): return self._changectx
154 def changectx(self): return self._changectx
149
155
150 def data(self): return self._filelog.read(self._filenode)
156 def data(self): return self._filelog.read(self._filenode)
151 def renamed(self): return self._filelog.renamed(self._filenode)
157 def renamed(self): return self._filelog.renamed(self._filenode)
152 def path(self): return self._path
158 def path(self): return self._path
153
159
154 def parents(self):
160 def parents(self):
155 p = self._path
161 p = self._path
156 fl = self._filelog
162 fl = self._filelog
157 pl = [ (p, n, fl) for n in self._filelog.parents(self._filenode) ]
163 pl = [ (p, n, fl) for n in self._filelog.parents(self._filenode) ]
158
164
159 r = self.renamed()
165 r = self.renamed()
160 if r:
166 if r:
161 pl[0] = (r[0], r[1], None)
167 pl[0] = (r[0], r[1], None)
162
168
163 return [ filectx(self._repo, p, fileid=n, filelog=l)
169 return [ filectx(self._repo, p, fileid=n, filelog=l)
164 for p,n,l in pl if n != nullid ]
170 for p,n,l in pl if n != nullid ]
165
171
166 def children(self):
172 def children(self):
167 # hard for renames
173 # hard for renames
168 c = self._filelog.children(self._filenode)
174 c = self._filelog.children(self._filenode)
169 return [ filectx(self._repo, self._path, fileid=x,
175 return [ filectx(self._repo, self._path, fileid=x,
170 filelog=self._filelog) for x in c ]
176 filelog=self._filelog) for x in c ]
171
177
172 def annotate(self, follow=False):
178 def annotate(self, follow=False):
173 '''returns a list of tuples of (ctx, line) for each line
179 '''returns a list of tuples of (ctx, line) for each line
174 in the file, where ctx is the filectx of the node where
180 in the file, where ctx is the filectx of the node where
175 that line was last changed'''
181 that line was last changed'''
176
182
177 def decorate(text, rev):
183 def decorate(text, rev):
178 return ([rev] * len(text.splitlines()), text)
184 return ([rev] * len(text.splitlines()), text)
179
185
180 def pair(parent, child):
186 def pair(parent, child):
181 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
187 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
182 child[0][b1:b2] = parent[0][a1:a2]
188 child[0][b1:b2] = parent[0][a1:a2]
183 return child
189 return child
184
190
185 getlog = util.cachefunc(lambda x: self._repo.file(x))
191 getlog = util.cachefunc(lambda x: self._repo.file(x))
186 def getctx(path, fileid):
192 def getctx(path, fileid):
187 log = path == self._path and self._filelog or getlog(path)
193 log = path == self._path and self._filelog or getlog(path)
188 return filectx(self._repo, path, fileid=fileid, filelog=log)
194 return filectx(self._repo, path, fileid=fileid, filelog=log)
189 getctx = util.cachefunc(getctx)
195 getctx = util.cachefunc(getctx)
190
196
191 def parents(f):
197 def parents(f):
192 # we want to reuse filectx objects as much as possible
198 # we want to reuse filectx objects as much as possible
193 p = f._path
199 p = f._path
194 pl = [ (p, f._filelog.rev(n)) for n in f._filelog.parents(f._filenode) ]
200 pl = [ (p, f._filelog.rev(n)) for n in f._filelog.parents(f._filenode) ]
195
201
196 if follow:
202 if follow:
197 r = f.renamed()
203 r = f.renamed()
198 if r:
204 if r:
199 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
205 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
200
206
201 return [ getctx(p, n) for p, n in pl if n != -1 ]
207 return [ getctx(p, n) for p, n in pl if n != -1 ]
202
208
203 # find all ancestors
209 # find all ancestors
204 needed = {self: 1}
210 needed = {self: 1}
205 visit = [self]
211 visit = [self]
206 files = [self._path]
212 files = [self._path]
207 while visit:
213 while visit:
208 f = visit.pop(0)
214 f = visit.pop(0)
209 for p in parents(f):
215 for p in parents(f):
210 if p not in needed:
216 if p not in needed:
211 needed[p] = 1
217 needed[p] = 1
212 visit.append(p)
218 visit.append(p)
213 if p._path not in files:
219 if p._path not in files:
214 files.append(p._path)
220 files.append(p._path)
215 else:
221 else:
216 # count how many times we'll use this
222 # count how many times we'll use this
217 needed[p] += 1
223 needed[p] += 1
218
224
219 # sort by revision (per file) which is a topological order
225 # sort by revision (per file) which is a topological order
220 visit = []
226 visit = []
221 files.reverse()
227 files.reverse()
222 for f in files:
228 for f in files:
223 fn = [(n._filerev, n) for n in needed.keys() if n._path == f]
229 fn = [(n._filerev, n) for n in needed.keys() if n._path == f]
224 fn.sort()
230 fn.sort()
225 visit.extend(fn)
231 visit.extend(fn)
226 hist = {}
232 hist = {}
227
233
228 for r, f in visit:
234 for r, f in visit:
229 curr = decorate(f.data(), f)
235 curr = decorate(f.data(), f)
230 for p in parents(f):
236 for p in parents(f):
231 if p != nullid:
237 if p != nullid:
232 curr = pair(hist[p], curr)
238 curr = pair(hist[p], curr)
233 # trim the history of unneeded revs
239 # trim the history of unneeded revs
234 needed[p] -= 1
240 needed[p] -= 1
235 if not needed[p]:
241 if not needed[p]:
236 del hist[p]
242 del hist[p]
237 hist[f] = curr
243 hist[f] = curr
238
244
239 return zip(hist[f][0], hist[f][1].splitlines(1))
245 return zip(hist[f][0], hist[f][1].splitlines(1))
240
246
241 def ancestor(self, fc2):
247 def ancestor(self, fc2):
242 """
248 """
243 find the common ancestor file context, if any, of self, and fc2
249 find the common ancestor file context, if any, of self, and fc2
244 """
250 """
245
251
246 acache = {}
252 acache = {}
247 flcache = {self._path:self._filelog, fc2._path:fc2._filelog}
253 flcache = {self._path:self._filelog, fc2._path:fc2._filelog}
248 def parents(vertex):
254 def parents(vertex):
249 if vertex in acache:
255 if vertex in acache:
250 return acache[vertex]
256 return acache[vertex]
251 f, n = vertex
257 f, n = vertex
252 if f not in flcache:
258 if f not in flcache:
253 flcache[f] = self._repo.file(f)
259 flcache[f] = self._repo.file(f)
254 fl = flcache[f]
260 fl = flcache[f]
255 pl = [ (f,p) for p in fl.parents(n) if p != nullid ]
261 pl = [ (f,p) for p in fl.parents(n) if p != nullid ]
256 re = fl.renamed(n)
262 re = fl.renamed(n)
257 if re:
263 if re:
258 pl.append(re)
264 pl.append(re)
259 acache[vertex]=pl
265 acache[vertex]=pl
260 return pl
266 return pl
261
267
262 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
268 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
263 v = ancestor.ancestor(a, b, parents)
269 v = ancestor.ancestor(a, b, parents)
264 if v:
270 if v:
265 f,n = v
271 f,n = v
266 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
272 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
267
273
268 return None
274 return None
General Comments 0
You need to be logged in to leave comments. Login now