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