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