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