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