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