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