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