##// END OF EJS Templates
context: make filectx remember changectx in changectx.filectx
Matt Mackall -
r3214:696c6562 default
parent child Browse files
Show More
@@ -1,298 +1,302 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>" % short(self.node())
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 changeset(self):
48 48 try:
49 49 return self._changeset
50 50 except AttributeError:
51 51 self._changeset = self._repo.changelog.read(self.node())
52 52 return self._changeset
53 53
54 54 def manifest(self):
55 55 try:
56 56 return self._manifest
57 57 except AttributeError:
58 58 self._manifest = self._repo.manifest.read(self.changeset()[0])
59 59 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 67
68 68 def parents(self):
69 69 """return contexts for each parent changeset"""
70 70 p = self._repo.changelog.parents(self._node)
71 71 return [ changectx(self._repo, x) for x in p ]
72 72
73 73 def children(self):
74 74 """return contexts for each child changeset"""
75 75 c = self._repo.changelog.children(self._node)
76 76 return [ changectx(self._repo, x) for x in c ]
77 77
78 78 def filenode(self, path):
79 79 node, flag = self._repo.manifest.find(self.changeset()[0], path)
80 80 return node
81 81
82 82 def filectx(self, path, fileid=None):
83 83 """get a file context from this changeset"""
84 84 if fileid is None:
85 85 fileid = self.filenode(path)
86 return filectx(self._repo, path, fileid=fileid)
86 return filectx(self._repo, path, fileid=fileid, changectx=self)
87 87
88 88 def filectxs(self):
89 89 """generate a file context for each file in this changeset's
90 90 manifest"""
91 91 mf = self.manifest()
92 92 m = mf.keys()
93 93 m.sort()
94 94 for f in m:
95 95 yield self.filectx(f, fileid=mf[f])
96 96
97 97 def ancestor(self, c2):
98 98 """
99 99 return the ancestor context of self and c2
100 100 """
101 101 n = self._repo.changelog.ancestor(self._node, c2._node)
102 102 return changectx(self._repo, n)
103 103
104 104 class filectx(object):
105 105 """A filecontext object makes access to data related to a particular
106 106 filerevision convenient."""
107 def __init__(self, repo, path, changeid=None, fileid=None, filelog=None):
107 def __init__(self, repo, path, changeid=None, fileid=None,
108 filelog=None, changectx=None):
108 109 """changeid can be a changeset revision, node, or tag.
109 110 fileid can be a file revision or node."""
110 111 self._repo = repo
111 112 self._path = path
112 113
113 114 assert changeid is not None or fileid is not None
114 115
115 116 if filelog:
116 117 self._filelog = filelog
118 if changectx:
119 self._changectx = changectx
120 self._changeid = changectx.node()
117 121
118 122 if fileid is None:
119 123 self._changeid = changeid
120 124 else:
121 125 self._fileid = fileid
122 126
123 127 def __getattr__(self, name):
124 128 if name == '_changectx':
125 129 self._changectx = changectx(self._repo, self._changeid)
126 130 return self._changectx
127 131 elif name == '_filelog':
128 132 self._filelog = self._repo.file(self._path)
129 133 return self._filelog
130 134 elif name == '_changeid':
131 135 self._changeid = self._filelog.linkrev(self._filenode)
132 136 return self._changeid
133 137 elif name == '_filenode':
134 138 if hasattr(self, "_fileid"):
135 139 self._filenode = self._filelog.lookup(self._fileid)
136 140 else:
137 141 self._filenode = self._changectx.filenode(self._path)
138 142 return self._filenode
139 143 elif name == '_filerev':
140 144 self._filerev = self._filelog.rev(self._filenode)
141 145 return self._filerev
142 146 else:
143 147 raise AttributeError, name
144 148
145 149 def __nonzero__(self):
146 150 return self._filerev != nullid
147 151
148 152 def __str__(self):
149 153 return "%s@%s" % (self.path(), short(self.node()))
150 154
151 155 def __repr__(self):
152 156 return "<filectx %s@%s>" % (self.path(), short(self.node()))
153 157
154 158 def __eq__(self, other):
155 159 return self._path == other._path and self._changeid == other._changeid
156 160
157 161 def filectx(self, fileid):
158 162 '''opens an arbitrary revision of the file without
159 163 opening a new filelog'''
160 164 return filectx(self._repo, self._path, fileid=fileid,
161 165 filelog=self._filelog)
162 166
163 167 def filerev(self): return self._filerev
164 168 def filenode(self): return self._filenode
165 169 def filelog(self): return self._filelog
166 170
167 171 def rev(self):
168 172 if hasattr(self, "_changectx"):
169 173 return self._changectx.rev()
170 174 return self._filelog.linkrev(self._filenode)
171 175
172 176 def node(self): return self._changectx.node()
173 177 def user(self): return self._changectx.user()
174 178 def date(self): return self._changectx.date()
175 179 def files(self): return self._changectx.files()
176 180 def description(self): return self._changectx.description()
177 181 def manifest(self): return self._changectx.manifest()
178 182 def changectx(self): return self._changectx
179 183
180 184 def data(self): return self._filelog.read(self._filenode)
181 185 def renamed(self): return self._filelog.renamed(self._filenode)
182 186 def path(self): return self._path
183 187
184 188 def parents(self):
185 189 p = self._path
186 190 fl = self._filelog
187 191 pl = [ (p, n, fl) for n in self._filelog.parents(self._filenode) ]
188 192
189 193 r = self.renamed()
190 194 if r:
191 195 pl[0] = (r[0], r[1], None)
192 196
193 197 return [ filectx(self._repo, p, fileid=n, filelog=l)
194 198 for p,n,l in pl if n != nullid ]
195 199
196 200 def children(self):
197 201 # hard for renames
198 202 c = self._filelog.children(self._filenode)
199 203 return [ filectx(self._repo, self._path, fileid=x,
200 204 filelog=self._filelog) for x in c ]
201 205
202 206 def annotate(self, follow=False):
203 207 '''returns a list of tuples of (ctx, line) for each line
204 208 in the file, where ctx is the filectx of the node where
205 209 that line was last changed'''
206 210
207 211 def decorate(text, rev):
208 212 return ([rev] * len(text.splitlines()), text)
209 213
210 214 def pair(parent, child):
211 215 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
212 216 child[0][b1:b2] = parent[0][a1:a2]
213 217 return child
214 218
215 219 getlog = util.cachefunc(lambda x: self._repo.file(x))
216 220 def getctx(path, fileid):
217 221 log = path == self._path and self._filelog or getlog(path)
218 222 return filectx(self._repo, path, fileid=fileid, filelog=log)
219 223 getctx = util.cachefunc(getctx)
220 224
221 225 def parents(f):
222 226 # we want to reuse filectx objects as much as possible
223 227 p = f._path
224 228 pl = [ (p, f._filelog.rev(n)) for n in f._filelog.parents(f._filenode) ]
225 229
226 230 if follow:
227 231 r = f.renamed()
228 232 if r:
229 233 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
230 234
231 235 return [ getctx(p, n) for p, n in pl if n != -1 ]
232 236
233 237 # find all ancestors
234 238 needed = {self: 1}
235 239 visit = [self]
236 240 files = [self._path]
237 241 while visit:
238 242 f = visit.pop(0)
239 243 for p in parents(f):
240 244 if p not in needed:
241 245 needed[p] = 1
242 246 visit.append(p)
243 247 if p._path not in files:
244 248 files.append(p._path)
245 249 else:
246 250 # count how many times we'll use this
247 251 needed[p] += 1
248 252
249 253 # sort by revision (per file) which is a topological order
250 254 visit = []
251 255 files.reverse()
252 256 for f in files:
253 257 fn = [(n._filerev, n) for n in needed.keys() if n._path == f]
254 258 fn.sort()
255 259 visit.extend(fn)
256 260 hist = {}
257 261
258 262 for r, f in visit:
259 263 curr = decorate(f.data(), f)
260 264 for p in parents(f):
261 265 if p != nullid:
262 266 curr = pair(hist[p], curr)
263 267 # trim the history of unneeded revs
264 268 needed[p] -= 1
265 269 if not needed[p]:
266 270 del hist[p]
267 271 hist[f] = curr
268 272
269 273 return zip(hist[f][0], hist[f][1].splitlines(1))
270 274
271 275 def ancestor(self, fc2):
272 276 """
273 277 find the common ancestor file context, if any, of self, and fc2
274 278 """
275 279
276 280 acache = {}
277 281 flcache = {self._path:self._filelog, fc2._path:fc2._filelog}
278 282 def parents(vertex):
279 283 if vertex in acache:
280 284 return acache[vertex]
281 285 f, n = vertex
282 286 if f not in flcache:
283 287 flcache[f] = self._repo.file(f)
284 288 fl = flcache[f]
285 289 pl = [ (f,p) for p in fl.parents(n) if p != nullid ]
286 290 re = fl.renamed(n)
287 291 if re:
288 292 pl.append(re)
289 293 acache[vertex]=pl
290 294 return pl
291 295
292 296 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
293 297 v = ancestor.ancestor(a, b, parents)
294 298 if v:
295 299 f,n = v
296 300 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
297 301
298 302 return None
General Comments 0
You need to be logged in to leave comments. Login now