##// END OF EJS Templates
context: add a dirty method to detect modified contexts
Matt Mackall -
r8717:e8de5957 default
parent child Browse files
Show More
@@ -1,801 +1,808 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 def dirty(self, missing=False):
622 "check whether a working directory is modified"
623
624 return (self.p2() or self.branch() != self.p1().branch() or
625 self.modified() or self.added() or self.removed() or
626 (missing and self.deleted()))
627
621 class workingfilectx(filectx):
628 class workingfilectx(filectx):
622 """A workingfilectx object makes access to data related to a particular
629 """A workingfilectx object makes access to data related to a particular
623 file in the working directory convenient."""
630 file in the working directory convenient."""
624 def __init__(self, repo, path, filelog=None, workingctx=None):
631 def __init__(self, repo, path, filelog=None, workingctx=None):
625 """changeid can be a changeset revision, node, or tag.
632 """changeid can be a changeset revision, node, or tag.
626 fileid can be a file revision or node."""
633 fileid can be a file revision or node."""
627 self._repo = repo
634 self._repo = repo
628 self._path = path
635 self._path = path
629 self._changeid = None
636 self._changeid = None
630 self._filerev = self._filenode = None
637 self._filerev = self._filenode = None
631
638
632 if filelog:
639 if filelog:
633 self._filelog = filelog
640 self._filelog = filelog
634 if workingctx:
641 if workingctx:
635 self._changectx = workingctx
642 self._changectx = workingctx
636
643
637 @propertycache
644 @propertycache
638 def _changectx(self):
645 def _changectx(self):
639 return workingctx(self._repo)
646 return workingctx(self._repo)
640
647
641 def __nonzero__(self):
648 def __nonzero__(self):
642 return True
649 return True
643
650
644 def __str__(self):
651 def __str__(self):
645 return "%s@%s" % (self.path(), self._changectx)
652 return "%s@%s" % (self.path(), self._changectx)
646
653
647 def data(self): return self._repo.wread(self._path)
654 def data(self): return self._repo.wread(self._path)
648 def renamed(self):
655 def renamed(self):
649 rp = self._repo.dirstate.copied(self._path)
656 rp = self._repo.dirstate.copied(self._path)
650 if not rp:
657 if not rp:
651 return None
658 return None
652 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
659 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
653
660
654 def parents(self):
661 def parents(self):
655 '''return parent filectxs, following copies if necessary'''
662 '''return parent filectxs, following copies if necessary'''
656 def filenode(ctx, path):
663 def filenode(ctx, path):
657 return ctx._manifest.get(path, nullid)
664 return ctx._manifest.get(path, nullid)
658
665
659 path = self._path
666 path = self._path
660 fl = self._filelog
667 fl = self._filelog
661 pcl = self._changectx._parents
668 pcl = self._changectx._parents
662 renamed = self.renamed()
669 renamed = self.renamed()
663
670
664 if renamed:
671 if renamed:
665 pl = [renamed + (None,)]
672 pl = [renamed + (None,)]
666 else:
673 else:
667 pl = [(path, filenode(pcl[0], path), fl)]
674 pl = [(path, filenode(pcl[0], path), fl)]
668
675
669 for pc in pcl[1:]:
676 for pc in pcl[1:]:
670 pl.append((path, filenode(pc, path), fl))
677 pl.append((path, filenode(pc, path), fl))
671
678
672 return [filectx(self._repo, p, fileid=n, filelog=l)
679 return [filectx(self._repo, p, fileid=n, filelog=l)
673 for p,n,l in pl if n != nullid]
680 for p,n,l in pl if n != nullid]
674
681
675 def children(self):
682 def children(self):
676 return []
683 return []
677
684
678 def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
685 def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
679 def date(self):
686 def date(self):
680 t, tz = self._changectx.date()
687 t, tz = self._changectx.date()
681 try:
688 try:
682 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
689 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
683 except OSError, err:
690 except OSError, err:
684 if err.errno != errno.ENOENT: raise
691 if err.errno != errno.ENOENT: raise
685 return (t, tz)
692 return (t, tz)
686
693
687 def cmp(self, text): return self._repo.wread(self._path) == text
694 def cmp(self, text): return self._repo.wread(self._path) == text
688
695
689 class memctx(object):
696 class memctx(object):
690 """Use memctx to perform in-memory commits via localrepo.commitctx().
697 """Use memctx to perform in-memory commits via localrepo.commitctx().
691
698
692 Revision information is supplied at initialization time while
699 Revision information is supplied at initialization time while
693 related files data and is made available through a callback
700 related files data and is made available through a callback
694 mechanism. 'repo' is the current localrepo, 'parents' is a
701 mechanism. 'repo' is the current localrepo, 'parents' is a
695 sequence of two parent revisions identifiers (pass None for every
702 sequence of two parent revisions identifiers (pass None for every
696 missing parent), 'text' is the commit message and 'files' lists
703 missing parent), 'text' is the commit message and 'files' lists
697 names of files touched by the revision (normalized and relative to
704 names of files touched by the revision (normalized and relative to
698 repository root).
705 repository root).
699
706
700 filectxfn(repo, memctx, path) is a callable receiving the
707 filectxfn(repo, memctx, path) is a callable receiving the
701 repository, the current memctx object and the normalized path of
708 repository, the current memctx object and the normalized path of
702 requested file, relative to repository root. It is fired by the
709 requested file, relative to repository root. It is fired by the
703 commit function for every file in 'files', but calls order is
710 commit function for every file in 'files', but calls order is
704 undefined. If the file is available in the revision being
711 undefined. If the file is available in the revision being
705 committed (updated or added), filectxfn returns a memfilectx
712 committed (updated or added), filectxfn returns a memfilectx
706 object. If the file was removed, filectxfn raises an
713 object. If the file was removed, filectxfn raises an
707 IOError. Moved files are represented by marking the source file
714 IOError. Moved files are represented by marking the source file
708 removed and the new file added with copy information (see
715 removed and the new file added with copy information (see
709 memfilectx).
716 memfilectx).
710
717
711 user receives the committer name and defaults to current
718 user receives the committer name and defaults to current
712 repository username, date is the commit date in any format
719 repository username, date is the commit date in any format
713 supported by util.parsedate() and defaults to current date, extra
720 supported by util.parsedate() and defaults to current date, extra
714 is a dictionary of metadata or is left empty.
721 is a dictionary of metadata or is left empty.
715 """
722 """
716 def __init__(self, repo, parents, text, files, filectxfn, user=None,
723 def __init__(self, repo, parents, text, files, filectxfn, user=None,
717 date=None, extra=None):
724 date=None, extra=None):
718 self._repo = repo
725 self._repo = repo
719 self._rev = None
726 self._rev = None
720 self._node = None
727 self._node = None
721 self._text = text
728 self._text = text
722 self._date = date and util.parsedate(date) or util.makedate()
729 self._date = date and util.parsedate(date) or util.makedate()
723 self._user = user
730 self._user = user
724 parents = [(p or nullid) for p in parents]
731 parents = [(p or nullid) for p in parents]
725 p1, p2 = parents
732 p1, p2 = parents
726 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
733 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
727 files = sorted(set(files))
734 files = sorted(set(files))
728 self._status = [files, [], [], [], []]
735 self._status = [files, [], [], [], []]
729 self._filectxfn = filectxfn
736 self._filectxfn = filectxfn
730
737
731 self._extra = extra and extra.copy() or {}
738 self._extra = extra and extra.copy() or {}
732 if 'branch' not in self._extra:
739 if 'branch' not in self._extra:
733 self._extra['branch'] = 'default'
740 self._extra['branch'] = 'default'
734 elif self._extra.get('branch') == '':
741 elif self._extra.get('branch') == '':
735 self._extra['branch'] = 'default'
742 self._extra['branch'] = 'default'
736
743
737 def __str__(self):
744 def __str__(self):
738 return str(self._parents[0]) + "+"
745 return str(self._parents[0]) + "+"
739
746
740 def __int__(self):
747 def __int__(self):
741 return self._rev
748 return self._rev
742
749
743 def __nonzero__(self):
750 def __nonzero__(self):
744 return True
751 return True
745
752
746 def __getitem__(self, key):
753 def __getitem__(self, key):
747 return self.filectx(key)
754 return self.filectx(key)
748
755
749 def p1(self): return self._parents[0]
756 def p1(self): return self._parents[0]
750 def p2(self): return self._parents[1]
757 def p2(self): return self._parents[1]
751
758
752 def user(self): return self._user or self._repo.ui.username()
759 def user(self): return self._user or self._repo.ui.username()
753 def date(self): return self._date
760 def date(self): return self._date
754 def description(self): return self._text
761 def description(self): return self._text
755 def files(self): return self.modified()
762 def files(self): return self.modified()
756 def modified(self): return self._status[0]
763 def modified(self): return self._status[0]
757 def added(self): return self._status[1]
764 def added(self): return self._status[1]
758 def removed(self): return self._status[2]
765 def removed(self): return self._status[2]
759 def deleted(self): return self._status[3]
766 def deleted(self): return self._status[3]
760 def unknown(self): return self._status[4]
767 def unknown(self): return self._status[4]
761 def clean(self): return self._status[5]
768 def clean(self): return self._status[5]
762 def branch(self): return self._extra['branch']
769 def branch(self): return self._extra['branch']
763 def extra(self): return self._extra
770 def extra(self): return self._extra
764 def flags(self, f): return self[f].flags()
771 def flags(self, f): return self[f].flags()
765
772
766 def parents(self):
773 def parents(self):
767 """return contexts for each parent changeset"""
774 """return contexts for each parent changeset"""
768 return self._parents
775 return self._parents
769
776
770 def filectx(self, path, filelog=None):
777 def filectx(self, path, filelog=None):
771 """get a file context from the working directory"""
778 """get a file context from the working directory"""
772 return self._filectxfn(self._repo, self, path)
779 return self._filectxfn(self._repo, self, path)
773
780
774 class memfilectx(object):
781 class memfilectx(object):
775 """memfilectx represents an in-memory file to commit.
782 """memfilectx represents an in-memory file to commit.
776
783
777 See memctx for more details.
784 See memctx for more details.
778 """
785 """
779 def __init__(self, path, data, islink, isexec, copied):
786 def __init__(self, path, data, islink, isexec, copied):
780 """
787 """
781 path is the normalized file path relative to repository root.
788 path is the normalized file path relative to repository root.
782 data is the file content as a string.
789 data is the file content as a string.
783 islink is True if the file is a symbolic link.
790 islink is True if the file is a symbolic link.
784 isexec is True if the file is executable.
791 isexec is True if the file is executable.
785 copied is the source file path if current file was copied in the
792 copied is the source file path if current file was copied in the
786 revision being committed, or None."""
793 revision being committed, or None."""
787 self._path = path
794 self._path = path
788 self._data = data
795 self._data = data
789 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
796 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
790 self._copied = None
797 self._copied = None
791 if copied:
798 if copied:
792 self._copied = (copied, nullid)
799 self._copied = (copied, nullid)
793
800
794 def __nonzero__(self): return True
801 def __nonzero__(self): return True
795 def __str__(self): return "%s@%s" % (self.path(), self._changectx)
802 def __str__(self): return "%s@%s" % (self.path(), self._changectx)
796 def path(self): return self._path
803 def path(self): return self._path
797 def data(self): return self._data
804 def data(self): return self._data
798 def flags(self): return self._flags
805 def flags(self): return self._flags
799 def isexec(self): return 'x' in self._flags
806 def isexec(self): return 'x' in self._flags
800 def islink(self): return 'l' in self._flags
807 def islink(self): return 'l' in self._flags
801 def renamed(self): return self._copied
808 def renamed(self): return self._copied
General Comments 0
You need to be logged in to leave comments. Login now