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