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