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