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