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