##// END OF EJS Templates
Make changectx.filenode raise repo.LookupError on failure
Brendan Cully -
r3242:1539f788 default
parent child Browse files
Show More
@@ -1,459 +1,465 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 node import *
9 9 from i18n import gettext as _
10 10 from demandload import demandload
11 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>" % str(self)
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 __getattr__(self, name):
45 45 if name == '_changeset':
46 46 self._changeset = self._repo.changelog.read(self.node())
47 47 return self._changeset
48 48 elif name == '_manifest':
49 49 self._manifest = self._repo.manifest.read(self._changeset[0])
50 50 return self._manifest
51 51 else:
52 52 raise AttributeError, name
53 53
54 54 def changeset(self): return self._changeset
55 55 def manifest(self): return self._manifest
56 56
57 57 def rev(self): return self._rev
58 58 def node(self): return self._node
59 59 def user(self): return self._changeset[1]
60 60 def date(self): return self._changeset[2]
61 61 def files(self): return self._changeset[3]
62 62 def description(self): return self._changeset[4]
63 63
64 64 def parents(self):
65 65 """return contexts for each parent changeset"""
66 66 p = self._repo.changelog.parents(self._node)
67 67 return [ changectx(self._repo, x) for x in p ]
68 68
69 69 def children(self):
70 70 """return contexts for each child changeset"""
71 71 c = self._repo.changelog.children(self._node)
72 72 return [ changectx(self._repo, x) for x in c ]
73 73
74 74 def filenode(self, path):
75 75 if hasattr(self, "_manifest"):
76 return self._manifest[path]
76 try:
77 return self._manifest[path]
78 except KeyError:
79 raise repo.LookupError(_("'%s' not found in manifest") % path)
77 80 node, flag = self._repo.manifest.find(self._changeset[0], path)
81 if not node:
82 raise repo.LookupError(_("'%s' not found in manifest") % path)
83
78 84 return node
79 85
80 86 def filectx(self, path, fileid=None):
81 87 """get a file context from this changeset"""
82 88 if fileid is None:
83 89 fileid = self.filenode(path)
84 90 return filectx(self._repo, path, fileid=fileid, changectx=self)
85 91
86 92 def filectxs(self):
87 93 """generate a file context for each file in this changeset's
88 94 manifest"""
89 95 mf = self.manifest()
90 96 m = mf.keys()
91 97 m.sort()
92 98 for f in m:
93 99 yield self.filectx(f, fileid=mf[f])
94 100
95 101 def ancestor(self, c2):
96 102 """
97 103 return the ancestor context of self and c2
98 104 """
99 105 n = self._repo.changelog.ancestor(self._node, c2._node)
100 106 return changectx(self._repo, n)
101 107
102 108 class filectx(object):
103 109 """A filecontext object makes access to data related to a particular
104 110 filerevision convenient."""
105 111 def __init__(self, repo, path, changeid=None, fileid=None,
106 112 filelog=None, changectx=None):
107 113 """changeid can be a changeset revision, node, or tag.
108 114 fileid can be a file revision or node."""
109 115 self._repo = repo
110 116 self._path = path
111 117
112 118 assert changeid is not None or fileid is not None
113 119
114 120 if filelog:
115 121 self._filelog = filelog
116 122 if changectx:
117 123 self._changectx = changectx
118 124 self._changeid = changectx.node()
119 125
120 126 if fileid is None:
121 127 self._changeid = changeid
122 128 else:
123 129 self._fileid = fileid
124 130
125 131 def __getattr__(self, name):
126 132 if name == '_changectx':
127 133 self._changectx = changectx(self._repo, self._changeid)
128 134 return self._changectx
129 135 elif name == '_filelog':
130 136 self._filelog = self._repo.file(self._path)
131 137 return self._filelog
132 138 elif name == '_changeid':
133 139 self._changeid = self._filelog.linkrev(self._filenode)
134 140 return self._changeid
135 141 elif name == '_filenode':
136 142 try:
137 143 if hasattr(self, "_fileid"):
138 144 self._filenode = self._filelog.lookup(self._fileid)
139 145 else:
140 146 self._filenode = self._changectx.filenode(self._path)
141 147 except revlog.RevlogError, inst:
142 148 raise repo.LookupError(str(inst))
143 149 return self._filenode
144 150 elif name == '_filerev':
145 151 self._filerev = self._filelog.rev(self._filenode)
146 152 return self._filerev
147 153 else:
148 154 raise AttributeError, name
149 155
150 156 def __nonzero__(self):
151 157 return self._filerev != nullid
152 158
153 159 def __str__(self):
154 160 return "%s@%s" % (self.path(), short(self.node()))
155 161
156 162 def __repr__(self):
157 163 return "<filectx %s>" % str(self)
158 164
159 165 def __eq__(self, other):
160 166 return self._path == other._path and self._changeid == other._changeid
161 167
162 168 def filectx(self, fileid):
163 169 '''opens an arbitrary revision of the file without
164 170 opening a new filelog'''
165 171 return filectx(self._repo, self._path, fileid=fileid,
166 172 filelog=self._filelog)
167 173
168 174 def filerev(self): return self._filerev
169 175 def filenode(self): return self._filenode
170 176 def filelog(self): return self._filelog
171 177
172 178 def rev(self):
173 179 if hasattr(self, "_changectx"):
174 180 return self._changectx.rev()
175 181 return self._filelog.linkrev(self._filenode)
176 182
177 183 def node(self): return self._changectx.node()
178 184 def user(self): return self._changectx.user()
179 185 def date(self): return self._changectx.date()
180 186 def files(self): return self._changectx.files()
181 187 def description(self): return self._changectx.description()
182 188 def manifest(self): return self._changectx.manifest()
183 189 def changectx(self): return self._changectx
184 190
185 191 def data(self): return self._filelog.read(self._filenode)
186 192 def renamed(self): return self._filelog.renamed(self._filenode)
187 193 def path(self): return self._path
188 194
189 195 def parents(self):
190 196 p = self._path
191 197 fl = self._filelog
192 198 pl = [ (p, n, fl) for n in self._filelog.parents(self._filenode) ]
193 199
194 200 r = self.renamed()
195 201 if r:
196 202 pl[0] = (r[0], r[1], None)
197 203
198 204 return [ filectx(self._repo, p, fileid=n, filelog=l)
199 205 for p,n,l in pl if n != nullid ]
200 206
201 207 def children(self):
202 208 # hard for renames
203 209 c = self._filelog.children(self._filenode)
204 210 return [ filectx(self._repo, self._path, fileid=x,
205 211 filelog=self._filelog) for x in c ]
206 212
207 213 def annotate(self, follow=False):
208 214 '''returns a list of tuples of (ctx, line) for each line
209 215 in the file, where ctx is the filectx of the node where
210 216 that line was last changed'''
211 217
212 218 def decorate(text, rev):
213 219 return ([rev] * len(text.splitlines()), text)
214 220
215 221 def pair(parent, child):
216 222 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
217 223 child[0][b1:b2] = parent[0][a1:a2]
218 224 return child
219 225
220 226 getlog = util.cachefunc(lambda x: self._repo.file(x))
221 227 def getctx(path, fileid):
222 228 log = path == self._path and self._filelog or getlog(path)
223 229 return filectx(self._repo, path, fileid=fileid, filelog=log)
224 230 getctx = util.cachefunc(getctx)
225 231
226 232 def parents(f):
227 233 # we want to reuse filectx objects as much as possible
228 234 p = f._path
229 235 if f._filerev is None: # working dir
230 236 pl = [ (n.path(), n.filerev()) for n in f.parents() ]
231 237 else:
232 238 pl = [ (p, n) for n in f._filelog.parentrevs(f._filerev) ]
233 239
234 240 if follow:
235 241 r = f.renamed()
236 242 if r:
237 243 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
238 244
239 245 return [ getctx(p, n) for p, n in pl if n != -1 ]
240 246
241 247 # find all ancestors
242 248 needed = {self: 1}
243 249 visit = [self]
244 250 files = [self._path]
245 251 while visit:
246 252 f = visit.pop(0)
247 253 for p in parents(f):
248 254 if p not in needed:
249 255 needed[p] = 1
250 256 visit.append(p)
251 257 if p._path not in files:
252 258 files.append(p._path)
253 259 else:
254 260 # count how many times we'll use this
255 261 needed[p] += 1
256 262
257 263 # sort by revision (per file) which is a topological order
258 264 visit = []
259 265 files.reverse()
260 266 for f in files:
261 267 fn = [(n._filerev, n) for n in needed.keys() if n._path == f]
262 268 fn.sort()
263 269 visit.extend(fn)
264 270 hist = {}
265 271
266 272 for r, f in visit:
267 273 curr = decorate(f.data(), f)
268 274 for p in parents(f):
269 275 if p != nullid:
270 276 curr = pair(hist[p], curr)
271 277 # trim the history of unneeded revs
272 278 needed[p] -= 1
273 279 if not needed[p]:
274 280 del hist[p]
275 281 hist[f] = curr
276 282
277 283 return zip(hist[f][0], hist[f][1].splitlines(1))
278 284
279 285 def ancestor(self, fc2):
280 286 """
281 287 find the common ancestor file context, if any, of self, and fc2
282 288 """
283 289
284 290 acache = {}
285 291
286 292 # prime the ancestor cache for the working directory
287 293 for c in (self, fc2):
288 294 if c._filerev == None:
289 295 pl = [ (n.path(), n.filenode()) for n in c.parents() ]
290 296 acache[(c._path, None)] = pl
291 297
292 298 flcache = {self._path:self._filelog, fc2._path:fc2._filelog}
293 299 def parents(vertex):
294 300 if vertex in acache:
295 301 return acache[vertex]
296 302 f, n = vertex
297 303 if f not in flcache:
298 304 flcache[f] = self._repo.file(f)
299 305 fl = flcache[f]
300 306 pl = [ (f,p) for p in fl.parents(n) if p != nullid ]
301 307 re = fl.renamed(n)
302 308 if re:
303 309 pl.append(re)
304 310 acache[vertex]=pl
305 311 return pl
306 312
307 313 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
308 314 v = ancestor.ancestor(a, b, parents)
309 315 if v:
310 316 f,n = v
311 317 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
312 318
313 319 return None
314 320
315 321 class workingctx(changectx):
316 322 """A workingctx object makes access to data related to
317 323 the current working directory convenient."""
318 324 def __init__(self, repo):
319 325 self._repo = repo
320 326 self._rev = None
321 327 self._node = None
322 328
323 329 def __str__(self):
324 330 return "."
325 331
326 332 def __nonzero__(self):
327 333 return True
328 334
329 335 def __getattr__(self, name):
330 336 if name == '_parents':
331 337 self._parents = self._repo.parents()
332 338 return self._parents
333 339 if name == '_status':
334 340 self._status = self._repo.status()
335 341 return self._status
336 342 if name == '_manifest':
337 343 self._buildmanifest()
338 344 return self._manifest
339 345 else:
340 346 raise AttributeError, name
341 347
342 348 def _buildmanifest(self):
343 349 """generate a manifest corresponding to the working directory"""
344 350
345 351 man = self._parents[0].manifest().copy()
346 352 copied = self._repo.dirstate.copies()
347 353 modified, added, removed, deleted, unknown = self._status[:5]
348 354 for i,l in (("a", added), ("m", modified), ("u", unknown)):
349 355 for f in l:
350 356 man[f] = man.get(copied.get(f, f), nullid) + i
351 357 man.set(f, util.is_exec(self._repo.wjoin(f), man.execf(f)))
352 358
353 359 for f in deleted + removed:
354 360 del man[f]
355 361
356 362 self._manifest = man
357 363
358 364 def manifest(self): return self._manifest
359 365
360 366 def user(self): return self._repo.ui.username()
361 367 def date(self): return util.makedate()
362 368 def description(self): return ""
363 369 def files(self):
364 370 f = self.modified() + self.added() + self.removed()
365 371 f.sort()
366 372 return f
367 373
368 374 def modified(self): return self._status[0]
369 375 def added(self): return self._status[1]
370 376 def removed(self): return self._status[2]
371 377 def deleted(self): return self._status[3]
372 378 def unknown(self): return self._status[4]
373 379 def clean(self): return self._status[5]
374 380
375 381 def parents(self):
376 382 """return contexts for each parent changeset"""
377 383 return self._parents
378 384
379 385 def children(self):
380 386 return []
381 387
382 388 def filectx(self, path):
383 389 """get a file context from the working directory"""
384 390 return workingfilectx(self._repo, path, workingctx=self)
385 391
386 392 def ancestor(self, c2):
387 393 """return the ancestor context of self and c2"""
388 394 return self._parents[0].ancestor(c2) # punt on two parents for now
389 395
390 396 class workingfilectx(filectx):
391 397 """A workingfilectx object makes access to data related to a particular
392 398 file in the working directory convenient."""
393 399 def __init__(self, repo, path, filelog=None, workingctx=None):
394 400 """changeid can be a changeset revision, node, or tag.
395 401 fileid can be a file revision or node."""
396 402 self._repo = repo
397 403 self._path = path
398 404 self._changeid = None
399 405 self._filerev = self._filenode = None
400 406
401 407 if filelog:
402 408 self._filelog = filelog
403 409 if workingctx:
404 410 self._changectx = workingctx
405 411
406 412 def __getattr__(self, name):
407 413 if name == '_changectx':
408 414 self._changectx = workingctx(repo)
409 415 return self._changectx
410 416 elif name == '_repopath':
411 417 self._repopath = self._repo.dirstate.copied(p) or self._path
412 418 elif name == '_filelog':
413 419 self._filelog = self._repo.file(self._repopath)
414 420 return self._filelog
415 421 else:
416 422 raise AttributeError, name
417 423
418 424 def __nonzero__(self):
419 425 return True
420 426
421 427 def __str__(self):
422 428 return "%s@." % self.path()
423 429
424 430 def filectx(self, fileid):
425 431 '''opens an arbitrary revision of the file without
426 432 opening a new filelog'''
427 433 return filectx(self._repo, self._repopath, fileid=fileid,
428 434 filelog=self._filelog)
429 435
430 436 def rev(self):
431 437 if hasattr(self, "_changectx"):
432 438 return self._changectx.rev()
433 439 return self._filelog.linkrev(self._filenode)
434 440
435 441 def data(self): return self._repo.wread(self._path)
436 442 def renamed(self):
437 443 rp = self._repopath
438 444 if rp == self._path:
439 445 return None
440 446 return rp, self._workingctx._parents._manifest.get(rp, nullid)
441 447
442 448 def parents(self):
443 449 '''return parent filectxs, following copies if necessary'''
444 450 p = self._path
445 451 rp = self._repopath
446 452 pcl = self._workingctx._parents
447 453 fl = self._filelog
448 454 pl = [ (rp, pcl[0]._manifest.get(rp, nullid), fl) ]
449 455 if len(pcl) > 1:
450 456 if rp != p:
451 457 fl = None
452 458 pl.append((p, pcl[1]._manifest.get(p, nullid), fl))
453 459
454 460 return [ filectx(self._repo, p, fileid=n, filelog=l)
455 461 for p,n,l in pl if n != nullid ]
456 462
457 463 def children(self):
458 464 return []
459 465
General Comments 0
You need to be logged in to leave comments. Login now