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