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