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