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