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