##// END OF EJS Templates
context: add __int__ and hex methods
Matt Mackall -
r6763:403682f1 default
parent child Browse files
Show More
@@ -1,742 +1,749
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, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 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 node import nullid, nullrev, short
8 from node import nullid, nullrev, short, hex
9 from i18n import _
9 from i18n import _
10 import ancestor, bdiff, revlog, util, os, errno
10 import ancestor, bdiff, revlog, util, os, errno
11
11
12 class changectx(object):
12 class changectx(object):
13 """A changecontext object makes access to data related to a particular
13 """A changecontext object makes access to data related to a particular
14 changeset convenient."""
14 changeset convenient."""
15 def __init__(self, repo, changeid=''):
15 def __init__(self, repo, changeid=''):
16 """changeid is a revision number, node, or tag"""
16 """changeid is a revision number, node, or tag"""
17 if changeid == '':
17 if changeid == '':
18 changeid = '.'
18 changeid = '.'
19 self._repo = repo
19 self._repo = repo
20 self._node = self._repo.lookup(changeid)
20 self._node = self._repo.lookup(changeid)
21 self._rev = self._repo.changelog.rev(self._node)
21 self._rev = self._repo.changelog.rev(self._node)
22
22
23 def __str__(self):
23 def __str__(self):
24 return short(self.node())
24 return short(self.node())
25
25
26 def __int__(self):
27 return self.rev()
28
26 def __repr__(self):
29 def __repr__(self):
27 return "<changectx %s>" % str(self)
30 return "<changectx %s>" % str(self)
28
31
29 def __hash__(self):
32 def __hash__(self):
30 try:
33 try:
31 return hash(self._rev)
34 return hash(self._rev)
32 except AttributeError:
35 except AttributeError:
33 return id(self)
36 return id(self)
34
37
35 def __eq__(self, other):
38 def __eq__(self, other):
36 try:
39 try:
37 return self._rev == other._rev
40 return self._rev == other._rev
38 except AttributeError:
41 except AttributeError:
39 return False
42 return False
40
43
41 def __ne__(self, other):
44 def __ne__(self, other):
42 return not (self == other)
45 return not (self == other)
43
46
44 def __nonzero__(self):
47 def __nonzero__(self):
45 return self._rev != nullrev
48 return self._rev != nullrev
46
49
47 def __getattr__(self, name):
50 def __getattr__(self, name):
48 if name == '_changeset':
51 if name == '_changeset':
49 self._changeset = self._repo.changelog.read(self.node())
52 self._changeset = self._repo.changelog.read(self.node())
50 return self._changeset
53 return self._changeset
51 elif name == '_manifest':
54 elif name == '_manifest':
52 self._manifest = self._repo.manifest.read(self._changeset[0])
55 self._manifest = self._repo.manifest.read(self._changeset[0])
53 return self._manifest
56 return self._manifest
54 elif name == '_manifestdelta':
57 elif name == '_manifestdelta':
55 md = self._repo.manifest.readdelta(self._changeset[0])
58 md = self._repo.manifest.readdelta(self._changeset[0])
56 self._manifestdelta = md
59 self._manifestdelta = md
57 return self._manifestdelta
60 return self._manifestdelta
58 elif name == '_parents':
61 elif name == '_parents':
59 p = self._repo.changelog.parents(self._node)
62 p = self._repo.changelog.parents(self._node)
60 if p[1] == nullid:
63 if p[1] == nullid:
61 p = p[:-1]
64 p = p[:-1]
62 self._parents = [changectx(self._repo, x) for x in p]
65 self._parents = [changectx(self._repo, x) for x in p]
63 return self._parents
66 return self._parents
64 else:
67 else:
65 raise AttributeError, name
68 raise AttributeError, name
66
69
67 def __contains__(self, key):
70 def __contains__(self, key):
68 return key in self._manifest
71 return key in self._manifest
69
72
70 def __getitem__(self, key):
73 def __getitem__(self, key):
71 return self.filectx(key)
74 return self.filectx(key)
72
75
73 def __iter__(self):
76 def __iter__(self):
74 for f in util.sort(self._manifest):
77 for f in util.sort(self._manifest):
75 yield f
78 yield f
76
79
77 def changeset(self): return self._changeset
80 def changeset(self): return self._changeset
78 def manifest(self): return self._manifest
81 def manifest(self): return self._manifest
79
82
80 def rev(self): return self._rev
83 def rev(self): return self._rev
81 def node(self): return self._node
84 def node(self): return self._node
85 def hex(self): return hex(self._node)
82 def user(self): return self._changeset[1]
86 def user(self): return self._changeset[1]
83 def date(self): return self._changeset[2]
87 def date(self): return self._changeset[2]
84 def files(self): return self._changeset[3]
88 def files(self): return self._changeset[3]
85 def description(self): return self._changeset[4]
89 def description(self): return self._changeset[4]
86 def branch(self): return self._changeset[5].get("branch")
90 def branch(self): return self._changeset[5].get("branch")
87 def extra(self): return self._changeset[5]
91 def extra(self): return self._changeset[5]
88 def tags(self): return self._repo.nodetags(self._node)
92 def tags(self): return self._repo.nodetags(self._node)
89
93
90 def parents(self):
94 def parents(self):
91 """return contexts for each parent changeset"""
95 """return contexts for each parent changeset"""
92 return self._parents
96 return self._parents
93
97
94 def children(self):
98 def children(self):
95 """return contexts for each child changeset"""
99 """return contexts for each child changeset"""
96 c = self._repo.changelog.children(self._node)
100 c = self._repo.changelog.children(self._node)
97 return [changectx(self._repo, x) for x in c]
101 return [changectx(self._repo, x) for x in c]
98
102
99 def _fileinfo(self, path):
103 def _fileinfo(self, path):
100 if '_manifest' in self.__dict__:
104 if '_manifest' in self.__dict__:
101 try:
105 try:
102 return self._manifest[path], self._manifest.flags(path)
106 return self._manifest[path], self._manifest.flags(path)
103 except KeyError:
107 except KeyError:
104 raise revlog.LookupError(self._node, path,
108 raise revlog.LookupError(self._node, path,
105 _('not found in manifest'))
109 _('not found in manifest'))
106 if '_manifestdelta' in self.__dict__ or path in self.files():
110 if '_manifestdelta' in self.__dict__ or path in self.files():
107 if path in self._manifestdelta:
111 if path in self._manifestdelta:
108 return self._manifestdelta[path], self._manifestdelta.flags(path)
112 return self._manifestdelta[path], self._manifestdelta.flags(path)
109 node, flag = self._repo.manifest.find(self._changeset[0], path)
113 node, flag = self._repo.manifest.find(self._changeset[0], path)
110 if not node:
114 if not node:
111 raise revlog.LookupError(self._node, path,
115 raise revlog.LookupError(self._node, path,
112 _('not found in manifest'))
116 _('not found in manifest'))
113
117
114 return node, flag
118 return node, flag
115
119
116 def filenode(self, path):
120 def filenode(self, path):
117 return self._fileinfo(path)[0]
121 return self._fileinfo(path)[0]
118
122
119 def flags(self, path):
123 def flags(self, path):
120 try:
124 try:
121 return self._fileinfo(path)[1]
125 return self._fileinfo(path)[1]
122 except revlog.LookupError:
126 except revlog.LookupError:
123 return ''
127 return ''
124
128
125 def filectx(self, path, fileid=None, filelog=None):
129 def filectx(self, path, fileid=None, filelog=None):
126 """get a file context from this changeset"""
130 """get a file context from this changeset"""
127 if fileid is None:
131 if fileid is None:
128 fileid = self.filenode(path)
132 fileid = self.filenode(path)
129 return filectx(self._repo, path, fileid=fileid,
133 return filectx(self._repo, path, fileid=fileid,
130 changectx=self, filelog=filelog)
134 changectx=self, filelog=filelog)
131
135
132 def filectxs(self):
136 def filectxs(self):
133 """generate a file context for each file in this changeset's
137 """generate a file context for each file in this changeset's
134 manifest"""
138 manifest"""
135 for f in util.sort(mf):
139 for f in util.sort(mf):
136 yield self.filectx(f, fileid=mf[f])
140 yield self.filectx(f, fileid=mf[f])
137
141
138 def ancestor(self, c2):
142 def ancestor(self, c2):
139 """
143 """
140 return the ancestor context of self and c2
144 return the ancestor context of self and c2
141 """
145 """
142 n = self._repo.changelog.ancestor(self._node, c2._node)
146 n = self._repo.changelog.ancestor(self._node, c2._node)
143 return changectx(self._repo, n)
147 return changectx(self._repo, n)
144
148
145 class filectx(object):
149 class filectx(object):
146 """A filecontext object makes access to data related to a particular
150 """A filecontext object makes access to data related to a particular
147 filerevision convenient."""
151 filerevision convenient."""
148 def __init__(self, repo, path, changeid=None, fileid=None,
152 def __init__(self, repo, path, changeid=None, fileid=None,
149 filelog=None, changectx=None):
153 filelog=None, changectx=None):
150 """changeid can be a changeset revision, node, or tag.
154 """changeid can be a changeset revision, node, or tag.
151 fileid can be a file revision or node."""
155 fileid can be a file revision or node."""
152 self._repo = repo
156 self._repo = repo
153 self._path = path
157 self._path = path
154
158
155 assert (changeid is not None
159 assert (changeid is not None
156 or fileid is not None
160 or fileid is not None
157 or changectx is not None)
161 or changectx is not None)
158
162
159 if filelog:
163 if filelog:
160 self._filelog = filelog
164 self._filelog = filelog
161
165
162 if changeid is not None:
166 if changeid is not None:
163 self._changeid = changeid
167 self._changeid = changeid
164 if changectx is not None:
168 if changectx is not None:
165 self._changectx = changectx
169 self._changectx = changectx
166 if fileid is not None:
170 if fileid is not None:
167 self._fileid = fileid
171 self._fileid = fileid
168
172
169 def __getattr__(self, name):
173 def __getattr__(self, name):
170 if name == '_changectx':
174 if name == '_changectx':
171 self._changectx = changectx(self._repo, self._changeid)
175 self._changectx = changectx(self._repo, self._changeid)
172 return self._changectx
176 return self._changectx
173 elif name == '_filelog':
177 elif name == '_filelog':
174 self._filelog = self._repo.file(self._path)
178 self._filelog = self._repo.file(self._path)
175 return self._filelog
179 return self._filelog
176 elif name == '_changeid':
180 elif name == '_changeid':
177 if '_changectx' in self.__dict__:
181 if '_changectx' in self.__dict__:
178 self._changeid = self._changectx.rev()
182 self._changeid = self._changectx.rev()
179 else:
183 else:
180 self._changeid = self._filelog.linkrev(self._filenode)
184 self._changeid = self._filelog.linkrev(self._filenode)
181 return self._changeid
185 return self._changeid
182 elif name == '_filenode':
186 elif name == '_filenode':
183 if '_fileid' in self.__dict__:
187 if '_fileid' in self.__dict__:
184 self._filenode = self._filelog.lookup(self._fileid)
188 self._filenode = self._filelog.lookup(self._fileid)
185 else:
189 else:
186 self._filenode = self._changectx.filenode(self._path)
190 self._filenode = self._changectx.filenode(self._path)
187 return self._filenode
191 return self._filenode
188 elif name == '_filerev':
192 elif name == '_filerev':
189 self._filerev = self._filelog.rev(self._filenode)
193 self._filerev = self._filelog.rev(self._filenode)
190 return self._filerev
194 return self._filerev
191 elif name == '_repopath':
195 elif name == '_repopath':
192 self._repopath = self._path
196 self._repopath = self._path
193 return self._repopath
197 return self._repopath
194 else:
198 else:
195 raise AttributeError, name
199 raise AttributeError, name
196
200
197 def __nonzero__(self):
201 def __nonzero__(self):
198 try:
202 try:
199 n = self._filenode
203 n = self._filenode
200 return True
204 return True
201 except revlog.LookupError:
205 except revlog.LookupError:
202 # file is missing
206 # file is missing
203 return False
207 return False
204
208
205 def __str__(self):
209 def __str__(self):
206 return "%s@%s" % (self.path(), short(self.node()))
210 return "%s@%s" % (self.path(), short(self.node()))
207
211
208 def __repr__(self):
212 def __repr__(self):
209 return "<filectx %s>" % str(self)
213 return "<filectx %s>" % str(self)
210
214
211 def __hash__(self):
215 def __hash__(self):
212 try:
216 try:
213 return hash((self._path, self._fileid))
217 return hash((self._path, self._fileid))
214 except AttributeError:
218 except AttributeError:
215 return id(self)
219 return id(self)
216
220
217 def __eq__(self, other):
221 def __eq__(self, other):
218 try:
222 try:
219 return (self._path == other._path
223 return (self._path == other._path
220 and self._fileid == other._fileid)
224 and self._fileid == other._fileid)
221 except AttributeError:
225 except AttributeError:
222 return False
226 return False
223
227
224 def __ne__(self, other):
228 def __ne__(self, other):
225 return not (self == other)
229 return not (self == other)
226
230
227 def filectx(self, fileid):
231 def filectx(self, fileid):
228 '''opens an arbitrary revision of the file without
232 '''opens an arbitrary revision of the file without
229 opening a new filelog'''
233 opening a new filelog'''
230 return filectx(self._repo, self._path, fileid=fileid,
234 return filectx(self._repo, self._path, fileid=fileid,
231 filelog=self._filelog)
235 filelog=self._filelog)
232
236
233 def filerev(self): return self._filerev
237 def filerev(self): return self._filerev
234 def filenode(self): return self._filenode
238 def filenode(self): return self._filenode
235 def flags(self): return self._changectx.flags(self._path)
239 def flags(self): return self._changectx.flags(self._path)
236 def filelog(self): return self._filelog
240 def filelog(self): return self._filelog
237
241
238 def rev(self):
242 def rev(self):
239 if '_changectx' in self.__dict__:
243 if '_changectx' in self.__dict__:
240 return self._changectx.rev()
244 return self._changectx.rev()
241 if '_changeid' in self.__dict__:
245 if '_changeid' in self.__dict__:
242 return self._changectx.rev()
246 return self._changectx.rev()
243 return self._filelog.linkrev(self._filenode)
247 return self._filelog.linkrev(self._filenode)
244
248
245 def linkrev(self): return self._filelog.linkrev(self._filenode)
249 def linkrev(self): return self._filelog.linkrev(self._filenode)
246 def node(self): return self._changectx.node()
250 def node(self): return self._changectx.node()
247 def user(self): return self._changectx.user()
251 def user(self): return self._changectx.user()
248 def date(self): return self._changectx.date()
252 def date(self): return self._changectx.date()
249 def files(self): return self._changectx.files()
253 def files(self): return self._changectx.files()
250 def description(self): return self._changectx.description()
254 def description(self): return self._changectx.description()
251 def branch(self): return self._changectx.branch()
255 def branch(self): return self._changectx.branch()
252 def manifest(self): return self._changectx.manifest()
256 def manifest(self): return self._changectx.manifest()
253 def changectx(self): return self._changectx
257 def changectx(self): return self._changectx
254
258
255 def data(self): return self._filelog.read(self._filenode)
259 def data(self): return self._filelog.read(self._filenode)
256 def path(self): return self._path
260 def path(self): return self._path
257 def size(self): return self._filelog.size(self._filerev)
261 def size(self): return self._filelog.size(self._filerev)
258
262
259 def cmp(self, text): return self._filelog.cmp(self._filenode, text)
263 def cmp(self, text): return self._filelog.cmp(self._filenode, text)
260
264
261 def renamed(self):
265 def renamed(self):
262 """check if file was actually renamed in this changeset revision
266 """check if file was actually renamed in this changeset revision
263
267
264 If rename logged in file revision, we report copy for changeset only
268 If rename logged in file revision, we report copy for changeset only
265 if file revisions linkrev points back to the changeset in question
269 if file revisions linkrev points back to the changeset in question
266 or both changeset parents contain different file revisions.
270 or both changeset parents contain different file revisions.
267 """
271 """
268
272
269 renamed = self._filelog.renamed(self._filenode)
273 renamed = self._filelog.renamed(self._filenode)
270 if not renamed:
274 if not renamed:
271 return renamed
275 return renamed
272
276
273 if self.rev() == self.linkrev():
277 if self.rev() == self.linkrev():
274 return renamed
278 return renamed
275
279
276 name = self.path()
280 name = self.path()
277 fnode = self._filenode
281 fnode = self._filenode
278 for p in self._changectx.parents():
282 for p in self._changectx.parents():
279 try:
283 try:
280 if fnode == p.filenode(name):
284 if fnode == p.filenode(name):
281 return None
285 return None
282 except revlog.LookupError:
286 except revlog.LookupError:
283 pass
287 pass
284 return renamed
288 return renamed
285
289
286 def parents(self):
290 def parents(self):
287 p = self._path
291 p = self._path
288 fl = self._filelog
292 fl = self._filelog
289 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
293 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
290
294
291 r = self._filelog.renamed(self._filenode)
295 r = self._filelog.renamed(self._filenode)
292 if r:
296 if r:
293 pl[0] = (r[0], r[1], None)
297 pl[0] = (r[0], r[1], None)
294
298
295 return [filectx(self._repo, p, fileid=n, filelog=l)
299 return [filectx(self._repo, p, fileid=n, filelog=l)
296 for p,n,l in pl if n != nullid]
300 for p,n,l in pl if n != nullid]
297
301
298 def children(self):
302 def children(self):
299 # hard for renames
303 # hard for renames
300 c = self._filelog.children(self._filenode)
304 c = self._filelog.children(self._filenode)
301 return [filectx(self._repo, self._path, fileid=x,
305 return [filectx(self._repo, self._path, fileid=x,
302 filelog=self._filelog) for x in c]
306 filelog=self._filelog) for x in c]
303
307
304 def annotate(self, follow=False, linenumber=None):
308 def annotate(self, follow=False, linenumber=None):
305 '''returns a list of tuples of (ctx, line) for each line
309 '''returns a list of tuples of (ctx, line) for each line
306 in the file, where ctx is the filectx of the node where
310 in the file, where ctx is the filectx of the node where
307 that line was last changed.
311 that line was last changed.
308 This returns tuples of ((ctx, linenumber), line) for each line,
312 This returns tuples of ((ctx, linenumber), line) for each line,
309 if "linenumber" parameter is NOT "None".
313 if "linenumber" parameter is NOT "None".
310 In such tuples, linenumber means one at the first appearance
314 In such tuples, linenumber means one at the first appearance
311 in the managed file.
315 in the managed file.
312 To reduce annotation cost,
316 To reduce annotation cost,
313 this returns fixed value(False is used) as linenumber,
317 this returns fixed value(False is used) as linenumber,
314 if "linenumber" parameter is "False".'''
318 if "linenumber" parameter is "False".'''
315
319
316 def decorate_compat(text, rev):
320 def decorate_compat(text, rev):
317 return ([rev] * len(text.splitlines()), text)
321 return ([rev] * len(text.splitlines()), text)
318
322
319 def without_linenumber(text, rev):
323 def without_linenumber(text, rev):
320 return ([(rev, False)] * len(text.splitlines()), text)
324 return ([(rev, False)] * len(text.splitlines()), text)
321
325
322 def with_linenumber(text, rev):
326 def with_linenumber(text, rev):
323 size = len(text.splitlines())
327 size = len(text.splitlines())
324 return ([(rev, i) for i in xrange(1, size + 1)], text)
328 return ([(rev, i) for i in xrange(1, size + 1)], text)
325
329
326 decorate = (((linenumber is None) and decorate_compat) or
330 decorate = (((linenumber is None) and decorate_compat) or
327 (linenumber and with_linenumber) or
331 (linenumber and with_linenumber) or
328 without_linenumber)
332 without_linenumber)
329
333
330 def pair(parent, child):
334 def pair(parent, child):
331 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
335 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
332 child[0][b1:b2] = parent[0][a1:a2]
336 child[0][b1:b2] = parent[0][a1:a2]
333 return child
337 return child
334
338
335 getlog = util.cachefunc(lambda x: self._repo.file(x))
339 getlog = util.cachefunc(lambda x: self._repo.file(x))
336 def getctx(path, fileid):
340 def getctx(path, fileid):
337 log = path == self._path and self._filelog or getlog(path)
341 log = path == self._path and self._filelog or getlog(path)
338 return filectx(self._repo, path, fileid=fileid, filelog=log)
342 return filectx(self._repo, path, fileid=fileid, filelog=log)
339 getctx = util.cachefunc(getctx)
343 getctx = util.cachefunc(getctx)
340
344
341 def parents(f):
345 def parents(f):
342 # we want to reuse filectx objects as much as possible
346 # we want to reuse filectx objects as much as possible
343 p = f._path
347 p = f._path
344 if f._filerev is None: # working dir
348 if f._filerev is None: # working dir
345 pl = [(n.path(), n.filerev()) for n in f.parents()]
349 pl = [(n.path(), n.filerev()) for n in f.parents()]
346 else:
350 else:
347 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
351 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
348
352
349 if follow:
353 if follow:
350 r = f.renamed()
354 r = f.renamed()
351 if r:
355 if r:
352 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
356 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
353
357
354 return [getctx(p, n) for p, n in pl if n != nullrev]
358 return [getctx(p, n) for p, n in pl if n != nullrev]
355
359
356 # use linkrev to find the first changeset where self appeared
360 # use linkrev to find the first changeset where self appeared
357 if self.rev() != self.linkrev():
361 if self.rev() != self.linkrev():
358 base = self.filectx(self.filerev())
362 base = self.filectx(self.filerev())
359 else:
363 else:
360 base = self
364 base = self
361
365
362 # find all ancestors
366 # find all ancestors
363 needed = {base: 1}
367 needed = {base: 1}
364 visit = [base]
368 visit = [base]
365 files = [base._path]
369 files = [base._path]
366 while visit:
370 while visit:
367 f = visit.pop(0)
371 f = visit.pop(0)
368 for p in parents(f):
372 for p in parents(f):
369 if p not in needed:
373 if p not in needed:
370 needed[p] = 1
374 needed[p] = 1
371 visit.append(p)
375 visit.append(p)
372 if p._path not in files:
376 if p._path not in files:
373 files.append(p._path)
377 files.append(p._path)
374 else:
378 else:
375 # count how many times we'll use this
379 # count how many times we'll use this
376 needed[p] += 1
380 needed[p] += 1
377
381
378 # sort by revision (per file) which is a topological order
382 # sort by revision (per file) which is a topological order
379 visit = []
383 visit = []
380 for f in files:
384 for f in files:
381 fn = [(n.rev(), n) for n in needed if n._path == f]
385 fn = [(n.rev(), n) for n in needed if n._path == f]
382 visit.extend(fn)
386 visit.extend(fn)
383
387
384 hist = {}
388 hist = {}
385 for r, f in util.sort(visit):
389 for r, f in util.sort(visit):
386 curr = decorate(f.data(), f)
390 curr = decorate(f.data(), f)
387 for p in parents(f):
391 for p in parents(f):
388 if p != nullid:
392 if p != nullid:
389 curr = pair(hist[p], curr)
393 curr = pair(hist[p], curr)
390 # trim the history of unneeded revs
394 # trim the history of unneeded revs
391 needed[p] -= 1
395 needed[p] -= 1
392 if not needed[p]:
396 if not needed[p]:
393 del hist[p]
397 del hist[p]
394 hist[f] = curr
398 hist[f] = curr
395
399
396 return zip(hist[f][0], hist[f][1].splitlines(1))
400 return zip(hist[f][0], hist[f][1].splitlines(1))
397
401
398 def ancestor(self, fc2):
402 def ancestor(self, fc2):
399 """
403 """
400 find the common ancestor file context, if any, of self, and fc2
404 find the common ancestor file context, if any, of self, and fc2
401 """
405 """
402
406
403 acache = {}
407 acache = {}
404
408
405 # prime the ancestor cache for the working directory
409 # prime the ancestor cache for the working directory
406 for c in (self, fc2):
410 for c in (self, fc2):
407 if c._filerev == None:
411 if c._filerev == None:
408 pl = [(n.path(), n.filenode()) for n in c.parents()]
412 pl = [(n.path(), n.filenode()) for n in c.parents()]
409 acache[(c._path, None)] = pl
413 acache[(c._path, None)] = pl
410
414
411 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
415 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
412 def parents(vertex):
416 def parents(vertex):
413 if vertex in acache:
417 if vertex in acache:
414 return acache[vertex]
418 return acache[vertex]
415 f, n = vertex
419 f, n = vertex
416 if f not in flcache:
420 if f not in flcache:
417 flcache[f] = self._repo.file(f)
421 flcache[f] = self._repo.file(f)
418 fl = flcache[f]
422 fl = flcache[f]
419 pl = [(f, p) for p in fl.parents(n) if p != nullid]
423 pl = [(f, p) for p in fl.parents(n) if p != nullid]
420 re = fl.renamed(n)
424 re = fl.renamed(n)
421 if re:
425 if re:
422 pl.append(re)
426 pl.append(re)
423 acache[vertex] = pl
427 acache[vertex] = pl
424 return pl
428 return pl
425
429
426 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
430 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
427 v = ancestor.ancestor(a, b, parents)
431 v = ancestor.ancestor(a, b, parents)
428 if v:
432 if v:
429 f, n = v
433 f, n = v
430 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
434 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
431
435
432 return None
436 return None
433
437
434 class workingctx(changectx):
438 class workingctx(changectx):
435 """A workingctx object makes access to data related to
439 """A workingctx object makes access to data related to
436 the current working directory convenient.
440 the current working directory convenient.
437 parents - a pair of parent nodeids, or None to use the dirstate.
441 parents - a pair of parent nodeids, or None to use the dirstate.
438 date - any valid date string or (unixtime, offset), or None.
442 date - any valid date string or (unixtime, offset), or None.
439 user - username string, or None.
443 user - username string, or None.
440 extra - a dictionary of extra values, or None.
444 extra - a dictionary of extra values, or None.
441 changes - a list of file lists as returned by localrepo.status()
445 changes - a list of file lists as returned by localrepo.status()
442 or None to use the repository status.
446 or None to use the repository status.
443 """
447 """
444 def __init__(self, repo, parents=None, text="", user=None, date=None,
448 def __init__(self, repo, parents=None, text="", user=None, date=None,
445 extra=None, changes=None):
449 extra=None, changes=None):
446 self._repo = repo
450 self._repo = repo
447 self._rev = None
451 self._rev = None
448 self._node = None
452 self._node = None
449 self._text = text
453 self._text = text
450 if date:
454 if date:
451 self._date = util.parsedate(date)
455 self._date = util.parsedate(date)
452 else:
456 else:
453 self._date = util.makedate()
457 self._date = util.makedate()
454 if user:
458 if user:
455 self._user = user
459 self._user = user
456 else:
460 else:
457 self._user = self._repo.ui.username()
461 self._user = self._repo.ui.username()
458 if parents:
462 if parents:
459 p1, p2 = parents
463 p1, p2 = parents
460 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
464 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
461 if changes:
465 if changes:
462 self._status = list(changes)
466 self._status = list(changes)
463
467
464 self._extra = {}
468 self._extra = {}
465 if extra:
469 if extra:
466 self._extra = extra.copy()
470 self._extra = extra.copy()
467 if 'branch' not in self._extra:
471 if 'branch' not in self._extra:
468 branch = self._repo.dirstate.branch()
472 branch = self._repo.dirstate.branch()
469 try:
473 try:
470 branch = branch.decode('UTF-8').encode('UTF-8')
474 branch = branch.decode('UTF-8').encode('UTF-8')
471 except UnicodeDecodeError:
475 except UnicodeDecodeError:
472 raise util.Abort(_('branch name not in UTF-8!'))
476 raise util.Abort(_('branch name not in UTF-8!'))
473 self._extra['branch'] = branch
477 self._extra['branch'] = branch
474 if self._extra['branch'] == '':
478 if self._extra['branch'] == '':
475 self._extra['branch'] = 'default'
479 self._extra['branch'] = 'default'
476
480
477 def __str__(self):
481 def __str__(self):
478 return str(self._parents[0]) + "+"
482 return str(self._parents[0]) + "+"
479
483
480 def __nonzero__(self):
484 def __nonzero__(self):
481 return True
485 return True
482
486
483 def __getattr__(self, name):
487 def __getattr__(self, name):
484 if name == '_status':
488 if name == '_status':
485 self._status = self._repo.status(unknown=True)
489 self._status = self._repo.status(unknown=True)
486 return self._status
490 return self._status
487 if name == '_manifest':
491 if name == '_manifest':
488 self._buildmanifest()
492 self._buildmanifest()
489 return self._manifest
493 return self._manifest
490 elif name == '_parents':
494 elif name == '_parents':
491 p = self._repo.dirstate.parents()
495 p = self._repo.dirstate.parents()
492 if p[1] == nullid:
496 if p[1] == nullid:
493 p = p[:-1]
497 p = p[:-1]
494 self._parents = [changectx(self._repo, x) for x in p]
498 self._parents = [changectx(self._repo, x) for x in p]
495 return self._parents
499 return self._parents
496 else:
500 else:
497 raise AttributeError, name
501 raise AttributeError, name
498
502
499 def _buildmanifest(self):
503 def _buildmanifest(self):
500 """generate a manifest corresponding to the working directory"""
504 """generate a manifest corresponding to the working directory"""
501
505
502 man = self._parents[0].manifest().copy()
506 man = self._parents[0].manifest().copy()
503 copied = self._repo.dirstate.copies()
507 copied = self._repo.dirstate.copies()
504 cf = lambda x: man.flags(copied.get(x, x))
508 cf = lambda x: man.flags(copied.get(x, x))
505 ff = self._repo.dirstate.flagfunc(cf)
509 ff = self._repo.dirstate.flagfunc(cf)
506 modified, added, removed, deleted, unknown = self._status[:5]
510 modified, added, removed, deleted, unknown = self._status[:5]
507 for i, l in (("a", added), ("m", modified), ("u", unknown)):
511 for i, l in (("a", added), ("m", modified), ("u", unknown)):
508 for f in l:
512 for f in l:
509 man[f] = man.get(copied.get(f, f), nullid) + i
513 man[f] = man.get(copied.get(f, f), nullid) + i
510 try:
514 try:
511 man.set(f, ff(f))
515 man.set(f, ff(f))
512 except OSError:
516 except OSError:
513 pass
517 pass
514
518
515 for f in deleted + removed:
519 for f in deleted + removed:
516 if f in man:
520 if f in man:
517 del man[f]
521 del man[f]
518
522
519 self._manifest = man
523 self._manifest = man
520
524
521 def manifest(self): return self._manifest
525 def manifest(self): return self._manifest
522
526
523 def user(self): return self._user
527 def user(self): return self._user
524 def date(self): return self._date
528 def date(self): return self._date
525 def description(self): return self._text
529 def description(self): return self._text
526 def files(self):
530 def files(self):
527 return util.sort(self._status[0] + self._status[1] + self._status[2])
531 return util.sort(self._status[0] + self._status[1] + self._status[2])
528
532
529 def modified(self): return self._status[0]
533 def modified(self): return self._status[0]
530 def added(self): return self._status[1]
534 def added(self): return self._status[1]
531 def removed(self): return self._status[2]
535 def removed(self): return self._status[2]
532 def deleted(self): return self._status[3]
536 def deleted(self): return self._status[3]
533 def unknown(self): return self._status[4]
537 def unknown(self): return self._status[4]
534 def clean(self): return self._status[5]
538 def clean(self): return self._status[5]
535 def branch(self): return self._extra['branch']
539 def branch(self): return self._extra['branch']
536 def extra(self): return self._extra
540 def extra(self): return self._extra
537
541
538 def tags(self):
542 def tags(self):
539 t = []
543 t = []
540 [t.extend(p.tags()) for p in self.parents()]
544 [t.extend(p.tags()) for p in self.parents()]
541 return t
545 return t
542
546
543 def children(self):
547 def children(self):
544 return []
548 return []
545
549
546 def flags(self, path):
550 def flags(self, path):
547 if '_manifest' in self.__dict__:
551 if '_manifest' in self.__dict__:
548 try:
552 try:
549 return self._manifest.flags(path)
553 return self._manifest.flags(path)
550 except KeyError:
554 except KeyError:
551 return ''
555 return ''
552
556
553 pnode = self._parents[0].changeset()[0]
557 pnode = self._parents[0].changeset()[0]
554 orig = self._repo.dirstate.copies().get(path, path)
558 orig = self._repo.dirstate.copies().get(path, path)
555 node, flag = self._repo.manifest.find(pnode, orig)
559 node, flag = self._repo.manifest.find(pnode, orig)
556 try:
560 try:
557 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
561 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
558 return ff(path)
562 return ff(path)
559 except OSError:
563 except OSError:
560 pass
564 pass
561
565
562 if not node or path in self.deleted() or path in self.removed():
566 if not node or path in self.deleted() or path in self.removed():
563 return ''
567 return ''
564 return flag
568 return flag
565
569
566 def filectx(self, path, filelog=None):
570 def filectx(self, path, filelog=None):
567 """get a file context from the working directory"""
571 """get a file context from the working directory"""
568 return workingfilectx(self._repo, path, workingctx=self,
572 return workingfilectx(self._repo, path, workingctx=self,
569 filelog=filelog)
573 filelog=filelog)
570
574
571 def ancestor(self, c2):
575 def ancestor(self, c2):
572 """return the ancestor context of self and c2"""
576 """return the ancestor context of self and c2"""
573 return self._parents[0].ancestor(c2) # punt on two parents for now
577 return self._parents[0].ancestor(c2) # punt on two parents for now
574
578
575 class workingfilectx(filectx):
579 class workingfilectx(filectx):
576 """A workingfilectx object makes access to data related to a particular
580 """A workingfilectx object makes access to data related to a particular
577 file in the working directory convenient."""
581 file in the working directory convenient."""
578 def __init__(self, repo, path, filelog=None, workingctx=None):
582 def __init__(self, repo, path, filelog=None, workingctx=None):
579 """changeid can be a changeset revision, node, or tag.
583 """changeid can be a changeset revision, node, or tag.
580 fileid can be a file revision or node."""
584 fileid can be a file revision or node."""
581 self._repo = repo
585 self._repo = repo
582 self._path = path
586 self._path = path
583 self._changeid = None
587 self._changeid = None
584 self._filerev = self._filenode = None
588 self._filerev = self._filenode = None
585
589
586 if filelog:
590 if filelog:
587 self._filelog = filelog
591 self._filelog = filelog
588 if workingctx:
592 if workingctx:
589 self._changectx = workingctx
593 self._changectx = workingctx
590
594
591 def __getattr__(self, name):
595 def __getattr__(self, name):
592 if name == '_changectx':
596 if name == '_changectx':
593 self._changectx = workingctx(self._repo)
597 self._changectx = workingctx(self._repo)
594 return self._changectx
598 return self._changectx
595 elif name == '_repopath':
599 elif name == '_repopath':
596 self._repopath = (self._repo.dirstate.copied(self._path)
600 self._repopath = (self._repo.dirstate.copied(self._path)
597 or self._path)
601 or self._path)
598 return self._repopath
602 return self._repopath
599 elif name == '_filelog':
603 elif name == '_filelog':
600 self._filelog = self._repo.file(self._repopath)
604 self._filelog = self._repo.file(self._repopath)
601 return self._filelog
605 return self._filelog
602 else:
606 else:
603 raise AttributeError, name
607 raise AttributeError, name
604
608
605 def __nonzero__(self):
609 def __nonzero__(self):
606 return True
610 return True
607
611
608 def __str__(self):
612 def __str__(self):
609 return "%s@%s" % (self.path(), self._changectx)
613 return "%s@%s" % (self.path(), self._changectx)
610
614
611 def filectx(self, fileid):
615 def filectx(self, fileid):
612 '''opens an arbitrary revision of the file without
616 '''opens an arbitrary revision of the file without
613 opening a new filelog'''
617 opening a new filelog'''
614 return filectx(self._repo, self._repopath, fileid=fileid,
618 return filectx(self._repo, self._repopath, fileid=fileid,
615 filelog=self._filelog)
619 filelog=self._filelog)
616
620
617 def rev(self):
621 def rev(self):
618 if '_changectx' in self.__dict__:
622 if '_changectx' in self.__dict__:
619 return self._changectx.rev()
623 return self._changectx.rev()
620 return self._filelog.linkrev(self._filenode)
624 return self._filelog.linkrev(self._filenode)
621
625
622 def data(self): return self._repo.wread(self._path)
626 def data(self): return self._repo.wread(self._path)
623 def renamed(self):
627 def renamed(self):
624 rp = self._repopath
628 rp = self._repopath
625 if rp == self._path:
629 if rp == self._path:
626 return None
630 return None
627 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
631 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
628
632
629 def parents(self):
633 def parents(self):
630 '''return parent filectxs, following copies if necessary'''
634 '''return parent filectxs, following copies if necessary'''
631 p = self._path
635 p = self._path
632 rp = self._repopath
636 rp = self._repopath
633 pcl = self._changectx._parents
637 pcl = self._changectx._parents
634 fl = self._filelog
638 fl = self._filelog
635 pl = [(rp, pcl[0]._manifest.get(rp, nullid), fl)]
639 pl = [(rp, pcl[0]._manifest.get(rp, nullid), fl)]
636 if len(pcl) > 1:
640 if len(pcl) > 1:
637 if rp != p:
641 if rp != p:
638 fl = None
642 fl = None
639 pl.append((p, pcl[1]._manifest.get(p, nullid), fl))
643 pl.append((p, pcl[1]._manifest.get(p, nullid), fl))
640
644
641 return [filectx(self._repo, p, fileid=n, filelog=l)
645 return [filectx(self._repo, p, fileid=n, filelog=l)
642 for p,n,l in pl if n != nullid]
646 for p,n,l in pl if n != nullid]
643
647
644 def children(self):
648 def children(self):
645 return []
649 return []
646
650
647 def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
651 def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
648 def date(self):
652 def date(self):
649 t, tz = self._changectx.date()
653 t, tz = self._changectx.date()
650 try:
654 try:
651 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
655 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
652 except OSError, err:
656 except OSError, err:
653 if err.errno != errno.ENOENT: raise
657 if err.errno != errno.ENOENT: raise
654 return (t, tz)
658 return (t, tz)
655
659
656 def cmp(self, text): return self._repo.wread(self._path) == text
660 def cmp(self, text): return self._repo.wread(self._path) == text
657
661
658 class memctx(object):
662 class memctx(object):
659 """A memctx is a subset of changectx supposed to be built on memory
663 """A memctx is a subset of changectx supposed to be built on memory
660 and passed to commit functions.
664 and passed to commit functions.
661
665
662 NOTE: this interface and the related memfilectx are experimental and
666 NOTE: this interface and the related memfilectx are experimental and
663 may change without notice.
667 may change without notice.
664
668
665 parents - a pair of parent nodeids.
669 parents - a pair of parent nodeids.
666 filectxfn - a callable taking (repo, memctx, path) arguments and
670 filectxfn - a callable taking (repo, memctx, path) arguments and
667 returning a memctx object.
671 returning a memctx object.
668 date - any valid date string or (unixtime, offset), or None.
672 date - any valid date string or (unixtime, offset), or None.
669 user - username string, or None.
673 user - username string, or None.
670 extra - a dictionary of extra values, or None.
674 extra - a dictionary of extra values, or None.
671 """
675 """
672 def __init__(self, repo, parents, text, files, filectxfn, user=None,
676 def __init__(self, repo, parents, text, files, filectxfn, user=None,
673 date=None, extra=None):
677 date=None, extra=None):
674 self._repo = repo
678 self._repo = repo
675 self._rev = None
679 self._rev = None
676 self._node = None
680 self._node = None
677 self._text = text
681 self._text = text
678 self._date = date and util.parsedate(date) or util.makedate()
682 self._date = date and util.parsedate(date) or util.makedate()
679 self._user = user or self._repo.ui.username()
683 self._user = user or self._repo.ui.username()
680 parents = [(p or nullid) for p in parents]
684 parents = [(p or nullid) for p in parents]
681 p1, p2 = parents
685 p1, p2 = parents
682 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
686 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
683 files = util.sort(list(files))
687 files = util.sort(list(files))
684 self._status = [files, [], [], [], []]
688 self._status = [files, [], [], [], []]
685 self._filectxfn = filectxfn
689 self._filectxfn = filectxfn
686
690
687 self._extra = extra and extra.copy() or {}
691 self._extra = extra and extra.copy() or {}
688 if 'branch' not in self._extra:
692 if 'branch' not in self._extra:
689 self._extra['branch'] = 'default'
693 self._extra['branch'] = 'default'
690 elif self._extra.get('branch') == '':
694 elif self._extra.get('branch') == '':
691 self._extra['branch'] = 'default'
695 self._extra['branch'] = 'default'
692
696
693 def __str__(self):
697 def __str__(self):
694 return str(self._parents[0]) + "+"
698 return str(self._parents[0]) + "+"
695
699
700 def __int__(self):
701 return self._rev
702
696 def __nonzero__(self):
703 def __nonzero__(self):
697 return True
704 return True
698
705
699 def user(self): return self._user
706 def user(self): return self._user
700 def date(self): return self._date
707 def date(self): return self._date
701 def description(self): return self._text
708 def description(self): return self._text
702 def files(self): return self.modified()
709 def files(self): return self.modified()
703 def modified(self): return self._status[0]
710 def modified(self): return self._status[0]
704 def added(self): return self._status[1]
711 def added(self): return self._status[1]
705 def removed(self): return self._status[2]
712 def removed(self): return self._status[2]
706 def deleted(self): return self._status[3]
713 def deleted(self): return self._status[3]
707 def unknown(self): return self._status[4]
714 def unknown(self): return self._status[4]
708 def clean(self): return self._status[5]
715 def clean(self): return self._status[5]
709 def branch(self): return self._extra['branch']
716 def branch(self): return self._extra['branch']
710 def extra(self): return self._extra
717 def extra(self): return self._extra
711 def flags(self, f): return self[f].flags()
718 def flags(self, f): return self[f].flags()
712
719
713 def parents(self):
720 def parents(self):
714 """return contexts for each parent changeset"""
721 """return contexts for each parent changeset"""
715 return self._parents
722 return self._parents
716
723
717 def filectx(self, path, filelog=None):
724 def filectx(self, path, filelog=None):
718 """get a file context from the working directory"""
725 """get a file context from the working directory"""
719 return self._filectxfn(self._repo, self, path)
726 return self._filectxfn(self._repo, self, path)
720
727
721 class memfilectx(object):
728 class memfilectx(object):
722 """A memfilectx is a subset of filectx supposed to be built by client
729 """A memfilectx is a subset of filectx supposed to be built by client
723 code and passed to commit functions.
730 code and passed to commit functions.
724 """
731 """
725 def __init__(self, path, data, islink, isexec, copied):
732 def __init__(self, path, data, islink, isexec, copied):
726 """copied is the source file path, or None."""
733 """copied is the source file path, or None."""
727 self._path = path
734 self._path = path
728 self._data = data
735 self._data = data
729 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
736 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
730 self._copied = None
737 self._copied = None
731 if copied:
738 if copied:
732 self._copied = (copied, nullid)
739 self._copied = (copied, nullid)
733
740
734 def __nonzero__(self): return True
741 def __nonzero__(self): return True
735 def __str__(self): return "%s@%s" % (self.path(), self._changectx)
742 def __str__(self): return "%s@%s" % (self.path(), self._changectx)
736 def path(self): return self._path
743 def path(self): return self._path
737 def data(self): return self._data
744 def data(self): return self._data
738 def flags(self): return self._flags
745 def flags(self): return self._flags
739 def isexec(self): return 'x' in self._flags
746 def isexec(self): return 'x' in self._flags
740 def islink(self): return 'l' in self._flags
747 def islink(self): return 'l' in self._flags
741 def renamed(self): return self._copied
748 def renamed(self): return self._copied
742
749
General Comments 0
You need to be logged in to leave comments. Login now