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