##// END OF EJS Templates
context: change workingctx str() from . to <node>+
Matt Mackall -
r3313:6c68bc1e default
parent child Browse files
Show More
@@ -1,473 +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 196 def cmp(self, text): return self._filelog.cmp(self._filenode, text)
197 197
198 198 def parents(self):
199 199 p = self._path
200 200 fl = self._filelog
201 201 pl = [ (p, n, fl) for n in self._filelog.parents(self._filenode) ]
202 202
203 203 r = self.renamed()
204 204 if r:
205 205 pl[0] = (r[0], r[1], None)
206 206
207 207 return [ filectx(self._repo, p, fileid=n, filelog=l)
208 208 for p,n,l in pl if n != nullid ]
209 209
210 210 def children(self):
211 211 # hard for renames
212 212 c = self._filelog.children(self._filenode)
213 213 return [ filectx(self._repo, self._path, fileid=x,
214 214 filelog=self._filelog) for x in c ]
215 215
216 216 def annotate(self, follow=False):
217 217 '''returns a list of tuples of (ctx, line) for each line
218 218 in the file, where ctx is the filectx of the node where
219 219 that line was last changed'''
220 220
221 221 def decorate(text, rev):
222 222 return ([rev] * len(text.splitlines()), text)
223 223
224 224 def pair(parent, child):
225 225 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
226 226 child[0][b1:b2] = parent[0][a1:a2]
227 227 return child
228 228
229 229 getlog = util.cachefunc(lambda x: self._repo.file(x))
230 230 def getctx(path, fileid):
231 231 log = path == self._path and self._filelog or getlog(path)
232 232 return filectx(self._repo, path, fileid=fileid, filelog=log)
233 233 getctx = util.cachefunc(getctx)
234 234
235 235 def parents(f):
236 236 # we want to reuse filectx objects as much as possible
237 237 p = f._path
238 238 if f._filerev is None: # working dir
239 239 pl = [ (n.path(), n.filerev()) for n in f.parents() ]
240 240 else:
241 241 pl = [ (p, n) for n in f._filelog.parentrevs(f._filerev) ]
242 242
243 243 if follow:
244 244 r = f.renamed()
245 245 if r:
246 246 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
247 247
248 248 return [ getctx(p, n) for p, n in pl if n != -1 ]
249 249
250 250 # find all ancestors
251 251 needed = {self: 1}
252 252 visit = [self]
253 253 files = [self._path]
254 254 while visit:
255 255 f = visit.pop(0)
256 256 for p in parents(f):
257 257 if p not in needed:
258 258 needed[p] = 1
259 259 visit.append(p)
260 260 if p._path not in files:
261 261 files.append(p._path)
262 262 else:
263 263 # count how many times we'll use this
264 264 needed[p] += 1
265 265
266 266 # sort by revision (per file) which is a topological order
267 267 visit = []
268 268 files.reverse()
269 269 for f in files:
270 270 fn = [(n._filerev, n) for n in needed.keys() if n._path == f]
271 271 fn.sort()
272 272 visit.extend(fn)
273 273 hist = {}
274 274
275 275 for r, f in visit:
276 276 curr = decorate(f.data(), f)
277 277 for p in parents(f):
278 278 if p != nullid:
279 279 curr = pair(hist[p], curr)
280 280 # trim the history of unneeded revs
281 281 needed[p] -= 1
282 282 if not needed[p]:
283 283 del hist[p]
284 284 hist[f] = curr
285 285
286 286 return zip(hist[f][0], hist[f][1].splitlines(1))
287 287
288 288 def ancestor(self, fc2):
289 289 """
290 290 find the common ancestor file context, if any, of self, and fc2
291 291 """
292 292
293 293 acache = {}
294 294
295 295 # prime the ancestor cache for the working directory
296 296 for c in (self, fc2):
297 297 if c._filerev == None:
298 298 pl = [ (n.path(), n.filenode()) for n in c.parents() ]
299 299 acache[(c._path, None)] = pl
300 300
301 301 flcache = {self._path:self._filelog, fc2._path:fc2._filelog}
302 302 def parents(vertex):
303 303 if vertex in acache:
304 304 return acache[vertex]
305 305 f, n = vertex
306 306 if f not in flcache:
307 307 flcache[f] = self._repo.file(f)
308 308 fl = flcache[f]
309 309 pl = [ (f,p) for p in fl.parents(n) if p != nullid ]
310 310 re = fl.renamed(n)
311 311 if re:
312 312 pl.append(re)
313 313 acache[vertex]=pl
314 314 return pl
315 315
316 316 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
317 317 v = ancestor.ancestor(a, b, parents)
318 318 if v:
319 319 f,n = v
320 320 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
321 321
322 322 return None
323 323
324 324 class workingctx(changectx):
325 325 """A workingctx object makes access to data related to
326 326 the current working directory convenient."""
327 327 def __init__(self, repo):
328 328 self._repo = repo
329 329 self._rev = None
330 330 self._node = None
331 331
332 332 def __str__(self):
333 return "."
333 return str(self._parents[0]) + "+"
334 334
335 335 def __nonzero__(self):
336 336 return True
337 337
338 338 def __getattr__(self, name):
339 339 if name == '_parents':
340 340 self._parents = self._repo.parents()
341 341 return self._parents
342 342 if name == '_status':
343 343 self._status = self._repo.status()
344 344 return self._status
345 345 if name == '_manifest':
346 346 self._buildmanifest()
347 347 return self._manifest
348 348 else:
349 349 raise AttributeError, name
350 350
351 351 def _buildmanifest(self):
352 352 """generate a manifest corresponding to the working directory"""
353 353
354 354 man = self._parents[0].manifest().copy()
355 355 copied = self._repo.dirstate.copies()
356 356 modified, added, removed, deleted, unknown = self._status[:5]
357 357 for i,l in (("a", added), ("m", modified), ("u", unknown)):
358 358 for f in l:
359 359 man[f] = man.get(copied.get(f, f), nullid) + i
360 360 man.set(f, util.is_exec(self._repo.wjoin(f), man.execf(f)))
361 361
362 362 for f in deleted + removed:
363 363 del man[f]
364 364
365 365 self._manifest = man
366 366
367 367 def manifest(self): return self._manifest
368 368
369 369 def user(self): return self._repo.ui.username()
370 370 def date(self): return util.makedate()
371 371 def description(self): return ""
372 372 def files(self):
373 373 f = self.modified() + self.added() + self.removed()
374 374 f.sort()
375 375 return f
376 376
377 377 def modified(self): return self._status[0]
378 378 def added(self): return self._status[1]
379 379 def removed(self): return self._status[2]
380 380 def deleted(self): return self._status[3]
381 381 def unknown(self): return self._status[4]
382 382 def clean(self): return self._status[5]
383 383
384 384 def parents(self):
385 385 """return contexts for each parent changeset"""
386 386 return self._parents
387 387
388 388 def children(self):
389 389 return []
390 390
391 391 def filectx(self, path):
392 392 """get a file context from the working directory"""
393 393 return workingfilectx(self._repo, path, workingctx=self)
394 394
395 395 def ancestor(self, c2):
396 396 """return the ancestor context of self and c2"""
397 397 return self._parents[0].ancestor(c2) # punt on two parents for now
398 398
399 399 class workingfilectx(filectx):
400 400 """A workingfilectx object makes access to data related to a particular
401 401 file in the working directory convenient."""
402 402 def __init__(self, repo, path, filelog=None, workingctx=None):
403 403 """changeid can be a changeset revision, node, or tag.
404 404 fileid can be a file revision or node."""
405 405 self._repo = repo
406 406 self._path = path
407 407 self._changeid = None
408 408 self._filerev = self._filenode = None
409 409
410 410 if filelog:
411 411 self._filelog = filelog
412 412 if workingctx:
413 413 self._changectx = workingctx
414 414
415 415 def __getattr__(self, name):
416 416 if name == '_changectx':
417 417 self._changectx = workingctx(repo)
418 418 return self._changectx
419 419 elif name == '_repopath':
420 420 self._repopath = (self._repo.dirstate.copied(self._path)
421 421 or self._path)
422 422 return self._repopath
423 423 elif name == '_filelog':
424 424 self._filelog = self._repo.file(self._repopath)
425 425 return self._filelog
426 426 else:
427 427 raise AttributeError, name
428 428
429 429 def __nonzero__(self):
430 430 return True
431 431
432 432 def __str__(self):
433 return "%s@." % self.path()
433 return "%s@%s" % (self.path(), self._changectx)
434 434
435 435 def filectx(self, fileid):
436 436 '''opens an arbitrary revision of the file without
437 437 opening a new filelog'''
438 438 return filectx(self._repo, self._repopath, fileid=fileid,
439 439 filelog=self._filelog)
440 440
441 441 def rev(self):
442 442 if hasattr(self, "_changectx"):
443 443 return self._changectx.rev()
444 444 return self._filelog.linkrev(self._filenode)
445 445
446 446 def data(self): return self._repo.wread(self._path)
447 447 def renamed(self):
448 448 rp = self._repopath
449 449 if rp == self._path:
450 450 return None
451 451 return rp, self._workingctx._parents._manifest.get(rp, nullid)
452 452
453 453 def parents(self):
454 454 '''return parent filectxs, following copies if necessary'''
455 455 p = self._path
456 456 rp = self._repopath
457 457 pcl = self._changectx._parents
458 458 fl = self._filelog
459 459 pl = [ (rp, pcl[0]._manifest.get(rp, nullid), fl) ]
460 460 if len(pcl) > 1:
461 461 if rp != p:
462 462 fl = None
463 463 pl.append((p, pcl[1]._manifest.get(p, nullid), fl))
464 464
465 465 return [ filectx(self._repo, p, fileid=n, filelog=l)
466 466 for p,n,l in pl if n != nullid ]
467 467
468 468 def children(self):
469 469 return []
470 470
471 471 def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
472 472
473 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