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