##// END OF EJS Templates
context: be even more careful about result order in ancestors() (issue2642)...
Matt Mackall -
r13481:4eb1e9d6 stable
parent child Browse files
Show More
@@ -1,1116 +1,1112
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 or any later version.
6 # GNU General Public License version 2 or any later version.
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, subrepo, patch, encoding
10 import ancestor, bdiff, error, util, subrepo, patch, encoding
11 import os, errno, stat
11 import os, errno, stat
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 @propertycache
76 @propertycache
77 def substate(self):
77 def substate(self):
78 return subrepo.state(self, self._repo.ui)
78 return subrepo.state(self, self._repo.ui)
79
79
80 def __contains__(self, key):
80 def __contains__(self, key):
81 return key in self._manifest
81 return key in self._manifest
82
82
83 def __getitem__(self, key):
83 def __getitem__(self, key):
84 return self.filectx(key)
84 return self.filectx(key)
85
85
86 def __iter__(self):
86 def __iter__(self):
87 for f in sorted(self._manifest):
87 for f in sorted(self._manifest):
88 yield f
88 yield f
89
89
90 def changeset(self):
90 def changeset(self):
91 return self._changeset
91 return self._changeset
92 def manifest(self):
92 def manifest(self):
93 return self._manifest
93 return self._manifest
94 def manifestnode(self):
94 def manifestnode(self):
95 return self._changeset[0]
95 return self._changeset[0]
96
96
97 def rev(self):
97 def rev(self):
98 return self._rev
98 return self._rev
99 def node(self):
99 def node(self):
100 return self._node
100 return self._node
101 def hex(self):
101 def hex(self):
102 return hex(self._node)
102 return hex(self._node)
103 def user(self):
103 def user(self):
104 return self._changeset[1]
104 return self._changeset[1]
105 def date(self):
105 def date(self):
106 return self._changeset[2]
106 return self._changeset[2]
107 def files(self):
107 def files(self):
108 return self._changeset[3]
108 return self._changeset[3]
109 def description(self):
109 def description(self):
110 return self._changeset[4]
110 return self._changeset[4]
111 def branch(self):
111 def branch(self):
112 return encoding.tolocal(self._changeset[5].get("branch"))
112 return encoding.tolocal(self._changeset[5].get("branch"))
113 def extra(self):
113 def extra(self):
114 return self._changeset[5]
114 return self._changeset[5]
115 def tags(self):
115 def tags(self):
116 return self._repo.nodetags(self._node)
116 return self._repo.nodetags(self._node)
117 def bookmarks(self):
117 def bookmarks(self):
118 return self._repo.nodebookmarks(self._node)
118 return self._repo.nodebookmarks(self._node)
119
119
120 def parents(self):
120 def parents(self):
121 """return contexts for each parent changeset"""
121 """return contexts for each parent changeset"""
122 return self._parents
122 return self._parents
123
123
124 def p1(self):
124 def p1(self):
125 return self._parents[0]
125 return self._parents[0]
126
126
127 def p2(self):
127 def p2(self):
128 if len(self._parents) == 2:
128 if len(self._parents) == 2:
129 return self._parents[1]
129 return self._parents[1]
130 return changectx(self._repo, -1)
130 return changectx(self._repo, -1)
131
131
132 def children(self):
132 def children(self):
133 """return contexts for each child changeset"""
133 """return contexts for each child changeset"""
134 c = self._repo.changelog.children(self._node)
134 c = self._repo.changelog.children(self._node)
135 return [changectx(self._repo, x) for x in c]
135 return [changectx(self._repo, x) for x in c]
136
136
137 def ancestors(self):
137 def ancestors(self):
138 for a in self._repo.changelog.ancestors(self._rev):
138 for a in self._repo.changelog.ancestors(self._rev):
139 yield changectx(self._repo, a)
139 yield changectx(self._repo, a)
140
140
141 def descendants(self):
141 def descendants(self):
142 for d in self._repo.changelog.descendants(self._rev):
142 for d in self._repo.changelog.descendants(self._rev):
143 yield changectx(self._repo, d)
143 yield changectx(self._repo, d)
144
144
145 def _fileinfo(self, path):
145 def _fileinfo(self, path):
146 if '_manifest' in self.__dict__:
146 if '_manifest' in self.__dict__:
147 try:
147 try:
148 return self._manifest[path], self._manifest.flags(path)
148 return self._manifest[path], self._manifest.flags(path)
149 except KeyError:
149 except KeyError:
150 raise error.LookupError(self._node, path,
150 raise error.LookupError(self._node, path,
151 _('not found in manifest'))
151 _('not found in manifest'))
152 if '_manifestdelta' in self.__dict__ or path in self.files():
152 if '_manifestdelta' in self.__dict__ or path in self.files():
153 if path in self._manifestdelta:
153 if path in self._manifestdelta:
154 return self._manifestdelta[path], self._manifestdelta.flags(path)
154 return self._manifestdelta[path], self._manifestdelta.flags(path)
155 node, flag = self._repo.manifest.find(self._changeset[0], path)
155 node, flag = self._repo.manifest.find(self._changeset[0], path)
156 if not node:
156 if not node:
157 raise error.LookupError(self._node, path,
157 raise error.LookupError(self._node, path,
158 _('not found in manifest'))
158 _('not found in manifest'))
159
159
160 return node, flag
160 return node, flag
161
161
162 def filenode(self, path):
162 def filenode(self, path):
163 return self._fileinfo(path)[0]
163 return self._fileinfo(path)[0]
164
164
165 def flags(self, path):
165 def flags(self, path):
166 try:
166 try:
167 return self._fileinfo(path)[1]
167 return self._fileinfo(path)[1]
168 except error.LookupError:
168 except error.LookupError:
169 return ''
169 return ''
170
170
171 def filectx(self, path, fileid=None, filelog=None):
171 def filectx(self, path, fileid=None, filelog=None):
172 """get a file context from this changeset"""
172 """get a file context from this changeset"""
173 if fileid is None:
173 if fileid is None:
174 fileid = self.filenode(path)
174 fileid = self.filenode(path)
175 return filectx(self._repo, path, fileid=fileid,
175 return filectx(self._repo, path, fileid=fileid,
176 changectx=self, filelog=filelog)
176 changectx=self, filelog=filelog)
177
177
178 def ancestor(self, c2):
178 def ancestor(self, c2):
179 """
179 """
180 return the ancestor context of self and c2
180 return the ancestor context of self and c2
181 """
181 """
182 # deal with workingctxs
182 # deal with workingctxs
183 n2 = c2._node
183 n2 = c2._node
184 if n2 is None:
184 if n2 is None:
185 n2 = c2._parents[0]._node
185 n2 = c2._parents[0]._node
186 n = self._repo.changelog.ancestor(self._node, n2)
186 n = self._repo.changelog.ancestor(self._node, n2)
187 return changectx(self._repo, n)
187 return changectx(self._repo, n)
188
188
189 def walk(self, match):
189 def walk(self, match):
190 fset = set(match.files())
190 fset = set(match.files())
191 # for dirstate.walk, files=['.'] means "walk the whole tree".
191 # for dirstate.walk, files=['.'] means "walk the whole tree".
192 # follow that here, too
192 # follow that here, too
193 fset.discard('.')
193 fset.discard('.')
194 for fn in self:
194 for fn in self:
195 for ffn in fset:
195 for ffn in fset:
196 # match if the file is the exact name or a directory
196 # match if the file is the exact name or a directory
197 if ffn == fn or fn.startswith("%s/" % ffn):
197 if ffn == fn or fn.startswith("%s/" % ffn):
198 fset.remove(ffn)
198 fset.remove(ffn)
199 break
199 break
200 if match(fn):
200 if match(fn):
201 yield fn
201 yield fn
202 for fn in sorted(fset):
202 for fn in sorted(fset):
203 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
203 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
204 yield fn
204 yield fn
205
205
206 def sub(self, path):
206 def sub(self, path):
207 return subrepo.subrepo(self, path)
207 return subrepo.subrepo(self, path)
208
208
209 def diff(self, ctx2=None, match=None, **opts):
209 def diff(self, ctx2=None, match=None, **opts):
210 """Returns a diff generator for the given contexts and matcher"""
210 """Returns a diff generator for the given contexts and matcher"""
211 if ctx2 is None:
211 if ctx2 is None:
212 ctx2 = self.p1()
212 ctx2 = self.p1()
213 if ctx2 is not None and not isinstance(ctx2, changectx):
213 if ctx2 is not None and not isinstance(ctx2, changectx):
214 ctx2 = self._repo[ctx2]
214 ctx2 = self._repo[ctx2]
215 diffopts = patch.diffopts(self._repo.ui, opts)
215 diffopts = patch.diffopts(self._repo.ui, opts)
216 return patch.diff(self._repo, ctx2.node(), self.node(),
216 return patch.diff(self._repo, ctx2.node(), self.node(),
217 match=match, opts=diffopts)
217 match=match, opts=diffopts)
218
218
219 class filectx(object):
219 class filectx(object):
220 """A filecontext object makes access to data related to a particular
220 """A filecontext object makes access to data related to a particular
221 filerevision convenient."""
221 filerevision convenient."""
222 def __init__(self, repo, path, changeid=None, fileid=None,
222 def __init__(self, repo, path, changeid=None, fileid=None,
223 filelog=None, changectx=None):
223 filelog=None, changectx=None):
224 """changeid can be a changeset revision, node, or tag.
224 """changeid can be a changeset revision, node, or tag.
225 fileid can be a file revision or node."""
225 fileid can be a file revision or node."""
226 self._repo = repo
226 self._repo = repo
227 self._path = path
227 self._path = path
228
228
229 assert (changeid is not None
229 assert (changeid is not None
230 or fileid is not None
230 or fileid is not None
231 or changectx is not None), \
231 or changectx is not None), \
232 ("bad args: changeid=%r, fileid=%r, changectx=%r"
232 ("bad args: changeid=%r, fileid=%r, changectx=%r"
233 % (changeid, fileid, changectx))
233 % (changeid, fileid, changectx))
234
234
235 if filelog:
235 if filelog:
236 self._filelog = filelog
236 self._filelog = filelog
237
237
238 if changeid is not None:
238 if changeid is not None:
239 self._changeid = changeid
239 self._changeid = changeid
240 if changectx is not None:
240 if changectx is not None:
241 self._changectx = changectx
241 self._changectx = changectx
242 if fileid is not None:
242 if fileid is not None:
243 self._fileid = fileid
243 self._fileid = fileid
244
244
245 @propertycache
245 @propertycache
246 def _changectx(self):
246 def _changectx(self):
247 return changectx(self._repo, self._changeid)
247 return changectx(self._repo, self._changeid)
248
248
249 @propertycache
249 @propertycache
250 def _filelog(self):
250 def _filelog(self):
251 return self._repo.file(self._path)
251 return self._repo.file(self._path)
252
252
253 @propertycache
253 @propertycache
254 def _changeid(self):
254 def _changeid(self):
255 if '_changectx' in self.__dict__:
255 if '_changectx' in self.__dict__:
256 return self._changectx.rev()
256 return self._changectx.rev()
257 else:
257 else:
258 return self._filelog.linkrev(self._filerev)
258 return self._filelog.linkrev(self._filerev)
259
259
260 @propertycache
260 @propertycache
261 def _filenode(self):
261 def _filenode(self):
262 if '_fileid' in self.__dict__:
262 if '_fileid' in self.__dict__:
263 return self._filelog.lookup(self._fileid)
263 return self._filelog.lookup(self._fileid)
264 else:
264 else:
265 return self._changectx.filenode(self._path)
265 return self._changectx.filenode(self._path)
266
266
267 @propertycache
267 @propertycache
268 def _filerev(self):
268 def _filerev(self):
269 return self._filelog.rev(self._filenode)
269 return self._filelog.rev(self._filenode)
270
270
271 @propertycache
271 @propertycache
272 def _repopath(self):
272 def _repopath(self):
273 return self._path
273 return self._path
274
274
275 def __nonzero__(self):
275 def __nonzero__(self):
276 try:
276 try:
277 self._filenode
277 self._filenode
278 return True
278 return True
279 except error.LookupError:
279 except error.LookupError:
280 # file is missing
280 # file is missing
281 return False
281 return False
282
282
283 def __str__(self):
283 def __str__(self):
284 return "%s@%s" % (self.path(), short(self.node()))
284 return "%s@%s" % (self.path(), short(self.node()))
285
285
286 def __repr__(self):
286 def __repr__(self):
287 return "<filectx %s>" % str(self)
287 return "<filectx %s>" % str(self)
288
288
289 def __hash__(self):
289 def __hash__(self):
290 try:
290 try:
291 return hash((self._path, self._filenode))
291 return hash((self._path, self._filenode))
292 except AttributeError:
292 except AttributeError:
293 return id(self)
293 return id(self)
294
294
295 def __eq__(self, other):
295 def __eq__(self, other):
296 try:
296 try:
297 return (self._path == other._path
297 return (self._path == other._path
298 and self._filenode == other._filenode)
298 and self._filenode == other._filenode)
299 except AttributeError:
299 except AttributeError:
300 return False
300 return False
301
301
302 def __ne__(self, other):
302 def __ne__(self, other):
303 return not (self == other)
303 return not (self == other)
304
304
305 def filectx(self, fileid):
305 def filectx(self, fileid):
306 '''opens an arbitrary revision of the file without
306 '''opens an arbitrary revision of the file without
307 opening a new filelog'''
307 opening a new filelog'''
308 return filectx(self._repo, self._path, fileid=fileid,
308 return filectx(self._repo, self._path, fileid=fileid,
309 filelog=self._filelog)
309 filelog=self._filelog)
310
310
311 def filerev(self):
311 def filerev(self):
312 return self._filerev
312 return self._filerev
313 def filenode(self):
313 def filenode(self):
314 return self._filenode
314 return self._filenode
315 def flags(self):
315 def flags(self):
316 return self._changectx.flags(self._path)
316 return self._changectx.flags(self._path)
317 def filelog(self):
317 def filelog(self):
318 return self._filelog
318 return self._filelog
319
319
320 def rev(self):
320 def rev(self):
321 if '_changectx' in self.__dict__:
321 if '_changectx' in self.__dict__:
322 return self._changectx.rev()
322 return self._changectx.rev()
323 if '_changeid' in self.__dict__:
323 if '_changeid' in self.__dict__:
324 return self._changectx.rev()
324 return self._changectx.rev()
325 return self._filelog.linkrev(self._filerev)
325 return self._filelog.linkrev(self._filerev)
326
326
327 def linkrev(self):
327 def linkrev(self):
328 return self._filelog.linkrev(self._filerev)
328 return self._filelog.linkrev(self._filerev)
329 def node(self):
329 def node(self):
330 return self._changectx.node()
330 return self._changectx.node()
331 def hex(self):
331 def hex(self):
332 return hex(self.node())
332 return hex(self.node())
333 def user(self):
333 def user(self):
334 return self._changectx.user()
334 return self._changectx.user()
335 def date(self):
335 def date(self):
336 return self._changectx.date()
336 return self._changectx.date()
337 def files(self):
337 def files(self):
338 return self._changectx.files()
338 return self._changectx.files()
339 def description(self):
339 def description(self):
340 return self._changectx.description()
340 return self._changectx.description()
341 def branch(self):
341 def branch(self):
342 return self._changectx.branch()
342 return self._changectx.branch()
343 def extra(self):
343 def extra(self):
344 return self._changectx.extra()
344 return self._changectx.extra()
345 def manifest(self):
345 def manifest(self):
346 return self._changectx.manifest()
346 return self._changectx.manifest()
347 def changectx(self):
347 def changectx(self):
348 return self._changectx
348 return self._changectx
349
349
350 def data(self):
350 def data(self):
351 return self._filelog.read(self._filenode)
351 return self._filelog.read(self._filenode)
352 def path(self):
352 def path(self):
353 return self._path
353 return self._path
354 def size(self):
354 def size(self):
355 return self._filelog.size(self._filerev)
355 return self._filelog.size(self._filerev)
356
356
357 def cmp(self, fctx):
357 def cmp(self, fctx):
358 """compare with other file context
358 """compare with other file context
359
359
360 returns True if different than fctx.
360 returns True if different than fctx.
361 """
361 """
362 if (fctx._filerev is None and self._repo._encodefilterpats
362 if (fctx._filerev is None and self._repo._encodefilterpats
363 or self.size() == fctx.size()):
363 or self.size() == fctx.size()):
364 return self._filelog.cmp(self._filenode, fctx.data())
364 return self._filelog.cmp(self._filenode, fctx.data())
365
365
366 return True
366 return True
367
367
368 def renamed(self):
368 def renamed(self):
369 """check if file was actually renamed in this changeset revision
369 """check if file was actually renamed in this changeset revision
370
370
371 If rename logged in file revision, we report copy for changeset only
371 If rename logged in file revision, we report copy for changeset only
372 if file revisions linkrev points back to the changeset in question
372 if file revisions linkrev points back to the changeset in question
373 or both changeset parents contain different file revisions.
373 or both changeset parents contain different file revisions.
374 """
374 """
375
375
376 renamed = self._filelog.renamed(self._filenode)
376 renamed = self._filelog.renamed(self._filenode)
377 if not renamed:
377 if not renamed:
378 return renamed
378 return renamed
379
379
380 if self.rev() == self.linkrev():
380 if self.rev() == self.linkrev():
381 return renamed
381 return renamed
382
382
383 name = self.path()
383 name = self.path()
384 fnode = self._filenode
384 fnode = self._filenode
385 for p in self._changectx.parents():
385 for p in self._changectx.parents():
386 try:
386 try:
387 if fnode == p.filenode(name):
387 if fnode == p.filenode(name):
388 return None
388 return None
389 except error.LookupError:
389 except error.LookupError:
390 pass
390 pass
391 return renamed
391 return renamed
392
392
393 def parents(self):
393 def parents(self):
394 p = self._path
394 p = self._path
395 fl = self._filelog
395 fl = self._filelog
396 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
396 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
397
397
398 r = self._filelog.renamed(self._filenode)
398 r = self._filelog.renamed(self._filenode)
399 if r:
399 if r:
400 pl[0] = (r[0], r[1], None)
400 pl[0] = (r[0], r[1], None)
401
401
402 return [filectx(self._repo, p, fileid=n, filelog=l)
402 return [filectx(self._repo, p, fileid=n, filelog=l)
403 for p, n, l in pl if n != nullid]
403 for p, n, l in pl if n != nullid]
404
404
405 def children(self):
405 def children(self):
406 # hard for renames
406 # hard for renames
407 c = self._filelog.children(self._filenode)
407 c = self._filelog.children(self._filenode)
408 return [filectx(self._repo, self._path, fileid=x,
408 return [filectx(self._repo, self._path, fileid=x,
409 filelog=self._filelog) for x in c]
409 filelog=self._filelog) for x in c]
410
410
411 def annotate(self, follow=False, linenumber=None):
411 def annotate(self, follow=False, linenumber=None):
412 '''returns a list of tuples of (ctx, line) for each line
412 '''returns a list of tuples of (ctx, line) for each line
413 in the file, where ctx is the filectx of the node where
413 in the file, where ctx is the filectx of the node where
414 that line was last changed.
414 that line was last changed.
415 This returns tuples of ((ctx, linenumber), line) for each line,
415 This returns tuples of ((ctx, linenumber), line) for each line,
416 if "linenumber" parameter is NOT "None".
416 if "linenumber" parameter is NOT "None".
417 In such tuples, linenumber means one at the first appearance
417 In such tuples, linenumber means one at the first appearance
418 in the managed file.
418 in the managed file.
419 To reduce annotation cost,
419 To reduce annotation cost,
420 this returns fixed value(False is used) as linenumber,
420 this returns fixed value(False is used) as linenumber,
421 if "linenumber" parameter is "False".'''
421 if "linenumber" parameter is "False".'''
422
422
423 def decorate_compat(text, rev):
423 def decorate_compat(text, rev):
424 return ([rev] * len(text.splitlines()), text)
424 return ([rev] * len(text.splitlines()), text)
425
425
426 def without_linenumber(text, rev):
426 def without_linenumber(text, rev):
427 return ([(rev, False)] * len(text.splitlines()), text)
427 return ([(rev, False)] * len(text.splitlines()), text)
428
428
429 def with_linenumber(text, rev):
429 def with_linenumber(text, rev):
430 size = len(text.splitlines())
430 size = len(text.splitlines())
431 return ([(rev, i) for i in xrange(1, size + 1)], text)
431 return ([(rev, i) for i in xrange(1, size + 1)], text)
432
432
433 decorate = (((linenumber is None) and decorate_compat) or
433 decorate = (((linenumber is None) and decorate_compat) or
434 (linenumber and with_linenumber) or
434 (linenumber and with_linenumber) or
435 without_linenumber)
435 without_linenumber)
436
436
437 def pair(parent, child):
437 def pair(parent, child):
438 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
438 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
439 child[0][b1:b2] = parent[0][a1:a2]
439 child[0][b1:b2] = parent[0][a1:a2]
440 return child
440 return child
441
441
442 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
442 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
443 def getctx(path, fileid):
443 def getctx(path, fileid):
444 log = path == self._path and self._filelog or getlog(path)
444 log = path == self._path and self._filelog or getlog(path)
445 return filectx(self._repo, path, fileid=fileid, filelog=log)
445 return filectx(self._repo, path, fileid=fileid, filelog=log)
446 getctx = util.lrucachefunc(getctx)
446 getctx = util.lrucachefunc(getctx)
447
447
448 def parents(f):
448 def parents(f):
449 # we want to reuse filectx objects as much as possible
449 # we want to reuse filectx objects as much as possible
450 p = f._path
450 p = f._path
451 if f._filerev is None: # working dir
451 if f._filerev is None: # working dir
452 pl = [(n.path(), n.filerev()) for n in f.parents()]
452 pl = [(n.path(), n.filerev()) for n in f.parents()]
453 else:
453 else:
454 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
454 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
455
455
456 if follow:
456 if follow:
457 r = f.renamed()
457 r = f.renamed()
458 if r:
458 if r:
459 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
459 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
460
460
461 return [getctx(p, n) for p, n in pl if n != nullrev]
461 return [getctx(p, n) for p, n in pl if n != nullrev]
462
462
463 # use linkrev to find the first changeset where self appeared
463 # use linkrev to find the first changeset where self appeared
464 if self.rev() != self.linkrev():
464 if self.rev() != self.linkrev():
465 base = self.filectx(self.filerev())
465 base = self.filectx(self.filerev())
466 else:
466 else:
467 base = self
467 base = self
468
468
469 # find all ancestors
469 # find all ancestors
470 needed = {base: 1}
470 needed = {base: 1}
471 visit = [base]
471 visit = [base]
472 files = [base._path]
472 files = [base._path]
473 while visit:
473 while visit:
474 f = visit.pop(0)
474 f = visit.pop(0)
475 for p in parents(f):
475 for p in parents(f):
476 if p not in needed:
476 if p not in needed:
477 needed[p] = 1
477 needed[p] = 1
478 visit.append(p)
478 visit.append(p)
479 if p._path not in files:
479 if p._path not in files:
480 files.append(p._path)
480 files.append(p._path)
481 else:
481 else:
482 # count how many times we'll use this
482 # count how many times we'll use this
483 needed[p] += 1
483 needed[p] += 1
484
484
485 # sort by revision (per file) which is a topological order
485 # sort by revision (per file) which is a topological order
486 visit = []
486 visit = []
487 for f in files:
487 for f in files:
488 visit.extend(n for n in needed if n._path == f)
488 visit.extend(n for n in needed if n._path == f)
489
489
490 hist = {}
490 hist = {}
491 for f in sorted(visit, key=lambda x: x.rev()):
491 for f in sorted(visit, key=lambda x: x.rev()):
492 curr = decorate(f.data(), f)
492 curr = decorate(f.data(), f)
493 for p in parents(f):
493 for p in parents(f):
494 curr = pair(hist[p], curr)
494 curr = pair(hist[p], curr)
495 # trim the history of unneeded revs
495 # trim the history of unneeded revs
496 needed[p] -= 1
496 needed[p] -= 1
497 if not needed[p]:
497 if not needed[p]:
498 del hist[p]
498 del hist[p]
499 hist[f] = curr
499 hist[f] = curr
500
500
501 return zip(hist[f][0], hist[f][1].splitlines(True))
501 return zip(hist[f][0], hist[f][1].splitlines(True))
502
502
503 def ancestor(self, fc2, actx=None):
503 def ancestor(self, fc2, actx=None):
504 """
504 """
505 find the common ancestor file context, if any, of self, and fc2
505 find the common ancestor file context, if any, of self, and fc2
506
506
507 If actx is given, it must be the changectx of the common ancestor
507 If actx is given, it must be the changectx of the common ancestor
508 of self's and fc2's respective changesets.
508 of self's and fc2's respective changesets.
509 """
509 """
510
510
511 if actx is None:
511 if actx is None:
512 actx = self.changectx().ancestor(fc2.changectx())
512 actx = self.changectx().ancestor(fc2.changectx())
513
513
514 # the trivial case: changesets are unrelated, files must be too
514 # the trivial case: changesets are unrelated, files must be too
515 if not actx:
515 if not actx:
516 return None
516 return None
517
517
518 # the easy case: no (relevant) renames
518 # the easy case: no (relevant) renames
519 if fc2.path() == self.path() and self.path() in actx:
519 if fc2.path() == self.path() and self.path() in actx:
520 return actx[self.path()]
520 return actx[self.path()]
521 acache = {}
521 acache = {}
522
522
523 # prime the ancestor cache for the working directory
523 # prime the ancestor cache for the working directory
524 for c in (self, fc2):
524 for c in (self, fc2):
525 if c._filerev is None:
525 if c._filerev is None:
526 pl = [(n.path(), n.filenode()) for n in c.parents()]
526 pl = [(n.path(), n.filenode()) for n in c.parents()]
527 acache[(c._path, None)] = pl
527 acache[(c._path, None)] = pl
528
528
529 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
529 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
530 def parents(vertex):
530 def parents(vertex):
531 if vertex in acache:
531 if vertex in acache:
532 return acache[vertex]
532 return acache[vertex]
533 f, n = vertex
533 f, n = vertex
534 if f not in flcache:
534 if f not in flcache:
535 flcache[f] = self._repo.file(f)
535 flcache[f] = self._repo.file(f)
536 fl = flcache[f]
536 fl = flcache[f]
537 pl = [(f, p) for p in fl.parents(n) if p != nullid]
537 pl = [(f, p) for p in fl.parents(n) if p != nullid]
538 re = fl.renamed(n)
538 re = fl.renamed(n)
539 if re:
539 if re:
540 pl.append(re)
540 pl.append(re)
541 acache[vertex] = pl
541 acache[vertex] = pl
542 return pl
542 return pl
543
543
544 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
544 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
545 v = ancestor.ancestor(a, b, parents)
545 v = ancestor.ancestor(a, b, parents)
546 if v:
546 if v:
547 f, n = v
547 f, n = v
548 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
548 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
549
549
550 return None
550 return None
551
551
552 def ancestors(self):
552 def ancestors(self):
553 seen = set()
553 visit = {}
554 visit = [self]
554 c = self
555 while visit:
555 while True:
556 parents = visit.pop(0).parents()
556 for parent in c.parents():
557 if len(parents) > 1 and parents[1].rev() > parents[0].rev():
557 visit[(parent.rev(), parent.node())] = parent
558 # make sure we return ancestors in reverse revision order
558 if not visit:
559 parents = reversed(parents)
559 break
560 for parent in parents:
560 c = visit.pop(max(visit))
561 s = str(parent)
561 yield c
562 if s not in seen:
563 visit.append(parent)
564 seen.add(s)
565 yield parent
566
562
567 class workingctx(changectx):
563 class workingctx(changectx):
568 """A workingctx object makes access to data related to
564 """A workingctx object makes access to data related to
569 the current working directory convenient.
565 the current working directory convenient.
570 date - any valid date string or (unixtime, offset), or None.
566 date - any valid date string or (unixtime, offset), or None.
571 user - username string, or None.
567 user - username string, or None.
572 extra - a dictionary of extra values, or None.
568 extra - a dictionary of extra values, or None.
573 changes - a list of file lists as returned by localrepo.status()
569 changes - a list of file lists as returned by localrepo.status()
574 or None to use the repository status.
570 or None to use the repository status.
575 """
571 """
576 def __init__(self, repo, text="", user=None, date=None, extra=None,
572 def __init__(self, repo, text="", user=None, date=None, extra=None,
577 changes=None):
573 changes=None):
578 self._repo = repo
574 self._repo = repo
579 self._rev = None
575 self._rev = None
580 self._node = None
576 self._node = None
581 self._text = text
577 self._text = text
582 if date:
578 if date:
583 self._date = util.parsedate(date)
579 self._date = util.parsedate(date)
584 if user:
580 if user:
585 self._user = user
581 self._user = user
586 if changes:
582 if changes:
587 self._status = list(changes[:4])
583 self._status = list(changes[:4])
588 self._unknown = changes[4]
584 self._unknown = changes[4]
589 self._ignored = changes[5]
585 self._ignored = changes[5]
590 self._clean = changes[6]
586 self._clean = changes[6]
591 else:
587 else:
592 self._unknown = None
588 self._unknown = None
593 self._ignored = None
589 self._ignored = None
594 self._clean = None
590 self._clean = None
595
591
596 self._extra = {}
592 self._extra = {}
597 if extra:
593 if extra:
598 self._extra = extra.copy()
594 self._extra = extra.copy()
599 if 'branch' not in self._extra:
595 if 'branch' not in self._extra:
600 try:
596 try:
601 branch = encoding.fromlocal(self._repo.dirstate.branch())
597 branch = encoding.fromlocal(self._repo.dirstate.branch())
602 except UnicodeDecodeError:
598 except UnicodeDecodeError:
603 raise util.Abort(_('branch name not in UTF-8!'))
599 raise util.Abort(_('branch name not in UTF-8!'))
604 self._extra['branch'] = branch
600 self._extra['branch'] = branch
605 if self._extra['branch'] == '':
601 if self._extra['branch'] == '':
606 self._extra['branch'] = 'default'
602 self._extra['branch'] = 'default'
607
603
608 def __str__(self):
604 def __str__(self):
609 return str(self._parents[0]) + "+"
605 return str(self._parents[0]) + "+"
610
606
611 def __repr__(self):
607 def __repr__(self):
612 return "<workingctx %s>" % str(self)
608 return "<workingctx %s>" % str(self)
613
609
614 def __nonzero__(self):
610 def __nonzero__(self):
615 return True
611 return True
616
612
617 def __contains__(self, key):
613 def __contains__(self, key):
618 return self._repo.dirstate[key] not in "?r"
614 return self._repo.dirstate[key] not in "?r"
619
615
620 @propertycache
616 @propertycache
621 def _manifest(self):
617 def _manifest(self):
622 """generate a manifest corresponding to the working directory"""
618 """generate a manifest corresponding to the working directory"""
623
619
624 if self._unknown is None:
620 if self._unknown is None:
625 self.status(unknown=True)
621 self.status(unknown=True)
626
622
627 man = self._parents[0].manifest().copy()
623 man = self._parents[0].manifest().copy()
628 copied = self._repo.dirstate.copies()
624 copied = self._repo.dirstate.copies()
629 if len(self._parents) > 1:
625 if len(self._parents) > 1:
630 man2 = self.p2().manifest()
626 man2 = self.p2().manifest()
631 def getman(f):
627 def getman(f):
632 if f in man:
628 if f in man:
633 return man
629 return man
634 return man2
630 return man2
635 else:
631 else:
636 getman = lambda f: man
632 getman = lambda f: man
637 def cf(f):
633 def cf(f):
638 f = copied.get(f, f)
634 f = copied.get(f, f)
639 return getman(f).flags(f)
635 return getman(f).flags(f)
640 ff = self._repo.dirstate.flagfunc(cf)
636 ff = self._repo.dirstate.flagfunc(cf)
641 modified, added, removed, deleted = self._status
637 modified, added, removed, deleted = self._status
642 unknown = self._unknown
638 unknown = self._unknown
643 for i, l in (("a", added), ("m", modified), ("u", unknown)):
639 for i, l in (("a", added), ("m", modified), ("u", unknown)):
644 for f in l:
640 for f in l:
645 orig = copied.get(f, f)
641 orig = copied.get(f, f)
646 man[f] = getman(orig).get(orig, nullid) + i
642 man[f] = getman(orig).get(orig, nullid) + i
647 try:
643 try:
648 man.set(f, ff(f))
644 man.set(f, ff(f))
649 except OSError:
645 except OSError:
650 pass
646 pass
651
647
652 for f in deleted + removed:
648 for f in deleted + removed:
653 if f in man:
649 if f in man:
654 del man[f]
650 del man[f]
655
651
656 return man
652 return man
657
653
658 @propertycache
654 @propertycache
659 def _status(self):
655 def _status(self):
660 return self._repo.status()[:4]
656 return self._repo.status()[:4]
661
657
662 @propertycache
658 @propertycache
663 def _user(self):
659 def _user(self):
664 return self._repo.ui.username()
660 return self._repo.ui.username()
665
661
666 @propertycache
662 @propertycache
667 def _date(self):
663 def _date(self):
668 return util.makedate()
664 return util.makedate()
669
665
670 @propertycache
666 @propertycache
671 def _parents(self):
667 def _parents(self):
672 p = self._repo.dirstate.parents()
668 p = self._repo.dirstate.parents()
673 if p[1] == nullid:
669 if p[1] == nullid:
674 p = p[:-1]
670 p = p[:-1]
675 self._parents = [changectx(self._repo, x) for x in p]
671 self._parents = [changectx(self._repo, x) for x in p]
676 return self._parents
672 return self._parents
677
673
678 def status(self, ignored=False, clean=False, unknown=False):
674 def status(self, ignored=False, clean=False, unknown=False):
679 """Explicit status query
675 """Explicit status query
680 Unless this method is used to query the working copy status, the
676 Unless this method is used to query the working copy status, the
681 _status property will implicitly read the status using its default
677 _status property will implicitly read the status using its default
682 arguments."""
678 arguments."""
683 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
679 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
684 self._unknown = self._ignored = self._clean = None
680 self._unknown = self._ignored = self._clean = None
685 if unknown:
681 if unknown:
686 self._unknown = stat[4]
682 self._unknown = stat[4]
687 if ignored:
683 if ignored:
688 self._ignored = stat[5]
684 self._ignored = stat[5]
689 if clean:
685 if clean:
690 self._clean = stat[6]
686 self._clean = stat[6]
691 self._status = stat[:4]
687 self._status = stat[:4]
692 return stat
688 return stat
693
689
694 def manifest(self):
690 def manifest(self):
695 return self._manifest
691 return self._manifest
696 def user(self):
692 def user(self):
697 return self._user or self._repo.ui.username()
693 return self._user or self._repo.ui.username()
698 def date(self):
694 def date(self):
699 return self._date
695 return self._date
700 def description(self):
696 def description(self):
701 return self._text
697 return self._text
702 def files(self):
698 def files(self):
703 return sorted(self._status[0] + self._status[1] + self._status[2])
699 return sorted(self._status[0] + self._status[1] + self._status[2])
704
700
705 def modified(self):
701 def modified(self):
706 return self._status[0]
702 return self._status[0]
707 def added(self):
703 def added(self):
708 return self._status[1]
704 return self._status[1]
709 def removed(self):
705 def removed(self):
710 return self._status[2]
706 return self._status[2]
711 def deleted(self):
707 def deleted(self):
712 return self._status[3]
708 return self._status[3]
713 def unknown(self):
709 def unknown(self):
714 assert self._unknown is not None # must call status first
710 assert self._unknown is not None # must call status first
715 return self._unknown
711 return self._unknown
716 def ignored(self):
712 def ignored(self):
717 assert self._ignored is not None # must call status first
713 assert self._ignored is not None # must call status first
718 return self._ignored
714 return self._ignored
719 def clean(self):
715 def clean(self):
720 assert self._clean is not None # must call status first
716 assert self._clean is not None # must call status first
721 return self._clean
717 return self._clean
722 def branch(self):
718 def branch(self):
723 return encoding.tolocal(self._extra['branch'])
719 return encoding.tolocal(self._extra['branch'])
724 def extra(self):
720 def extra(self):
725 return self._extra
721 return self._extra
726
722
727 def tags(self):
723 def tags(self):
728 t = []
724 t = []
729 for p in self.parents():
725 for p in self.parents():
730 t.extend(p.tags())
726 t.extend(p.tags())
731 return t
727 return t
732
728
733 def bookmarks(self):
729 def bookmarks(self):
734 b = []
730 b = []
735 for p in self.parents():
731 for p in self.parents():
736 b.extend(p.bookmarks())
732 b.extend(p.bookmarks())
737 return b
733 return b
738
734
739 def children(self):
735 def children(self):
740 return []
736 return []
741
737
742 def flags(self, path):
738 def flags(self, path):
743 if '_manifest' in self.__dict__:
739 if '_manifest' in self.__dict__:
744 try:
740 try:
745 return self._manifest.flags(path)
741 return self._manifest.flags(path)
746 except KeyError:
742 except KeyError:
747 return ''
743 return ''
748
744
749 orig = self._repo.dirstate.copies().get(path, path)
745 orig = self._repo.dirstate.copies().get(path, path)
750
746
751 def findflag(ctx):
747 def findflag(ctx):
752 mnode = ctx.changeset()[0]
748 mnode = ctx.changeset()[0]
753 node, flag = self._repo.manifest.find(mnode, orig)
749 node, flag = self._repo.manifest.find(mnode, orig)
754 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
750 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
755 try:
751 try:
756 return ff(path)
752 return ff(path)
757 except OSError:
753 except OSError:
758 pass
754 pass
759
755
760 flag = findflag(self._parents[0])
756 flag = findflag(self._parents[0])
761 if flag is None and len(self.parents()) > 1:
757 if flag is None and len(self.parents()) > 1:
762 flag = findflag(self._parents[1])
758 flag = findflag(self._parents[1])
763 if flag is None or self._repo.dirstate[path] == 'r':
759 if flag is None or self._repo.dirstate[path] == 'r':
764 return ''
760 return ''
765 return flag
761 return flag
766
762
767 def filectx(self, path, filelog=None):
763 def filectx(self, path, filelog=None):
768 """get a file context from the working directory"""
764 """get a file context from the working directory"""
769 return workingfilectx(self._repo, path, workingctx=self,
765 return workingfilectx(self._repo, path, workingctx=self,
770 filelog=filelog)
766 filelog=filelog)
771
767
772 def ancestor(self, c2):
768 def ancestor(self, c2):
773 """return the ancestor context of self and c2"""
769 """return the ancestor context of self and c2"""
774 return self._parents[0].ancestor(c2) # punt on two parents for now
770 return self._parents[0].ancestor(c2) # punt on two parents for now
775
771
776 def walk(self, match):
772 def walk(self, match):
777 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
773 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
778 True, False))
774 True, False))
779
775
780 def dirty(self, missing=False):
776 def dirty(self, missing=False):
781 "check whether a working directory is modified"
777 "check whether a working directory is modified"
782 # check subrepos first
778 # check subrepos first
783 for s in self.substate:
779 for s in self.substate:
784 if self.sub(s).dirty():
780 if self.sub(s).dirty():
785 return True
781 return True
786 # check current working dir
782 # check current working dir
787 return (self.p2() or self.branch() != self.p1().branch() or
783 return (self.p2() or self.branch() != self.p1().branch() or
788 self.modified() or self.added() or self.removed() or
784 self.modified() or self.added() or self.removed() or
789 (missing and self.deleted()))
785 (missing and self.deleted()))
790
786
791 def add(self, list, prefix=""):
787 def add(self, list, prefix=""):
792 join = lambda f: os.path.join(prefix, f)
788 join = lambda f: os.path.join(prefix, f)
793 wlock = self._repo.wlock()
789 wlock = self._repo.wlock()
794 ui, ds = self._repo.ui, self._repo.dirstate
790 ui, ds = self._repo.ui, self._repo.dirstate
795 try:
791 try:
796 rejected = []
792 rejected = []
797 for f in list:
793 for f in list:
798 p = self._repo.wjoin(f)
794 p = self._repo.wjoin(f)
799 try:
795 try:
800 st = os.lstat(p)
796 st = os.lstat(p)
801 except:
797 except:
802 ui.warn(_("%s does not exist!\n") % join(f))
798 ui.warn(_("%s does not exist!\n") % join(f))
803 rejected.append(f)
799 rejected.append(f)
804 continue
800 continue
805 if st.st_size > 10000000:
801 if st.st_size > 10000000:
806 ui.warn(_("%s: up to %d MB of RAM may be required "
802 ui.warn(_("%s: up to %d MB of RAM may be required "
807 "to manage this file\n"
803 "to manage this file\n"
808 "(use 'hg revert %s' to cancel the "
804 "(use 'hg revert %s' to cancel the "
809 "pending addition)\n")
805 "pending addition)\n")
810 % (f, 3 * st.st_size // 1000000, join(f)))
806 % (f, 3 * st.st_size // 1000000, join(f)))
811 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
807 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
812 ui.warn(_("%s not added: only files and symlinks "
808 ui.warn(_("%s not added: only files and symlinks "
813 "supported currently\n") % join(f))
809 "supported currently\n") % join(f))
814 rejected.append(p)
810 rejected.append(p)
815 elif ds[f] in 'amn':
811 elif ds[f] in 'amn':
816 ui.warn(_("%s already tracked!\n") % join(f))
812 ui.warn(_("%s already tracked!\n") % join(f))
817 elif ds[f] == 'r':
813 elif ds[f] == 'r':
818 ds.normallookup(f)
814 ds.normallookup(f)
819 else:
815 else:
820 ds.add(f)
816 ds.add(f)
821 return rejected
817 return rejected
822 finally:
818 finally:
823 wlock.release()
819 wlock.release()
824
820
825 def forget(self, list):
821 def forget(self, list):
826 wlock = self._repo.wlock()
822 wlock = self._repo.wlock()
827 try:
823 try:
828 for f in list:
824 for f in list:
829 if self._repo.dirstate[f] != 'a':
825 if self._repo.dirstate[f] != 'a':
830 self._repo.ui.warn(_("%s not added!\n") % f)
826 self._repo.ui.warn(_("%s not added!\n") % f)
831 else:
827 else:
832 self._repo.dirstate.forget(f)
828 self._repo.dirstate.forget(f)
833 finally:
829 finally:
834 wlock.release()
830 wlock.release()
835
831
836 def ancestors(self):
832 def ancestors(self):
837 for a in self._repo.changelog.ancestors(
833 for a in self._repo.changelog.ancestors(
838 *[p.rev() for p in self._parents]):
834 *[p.rev() for p in self._parents]):
839 yield changectx(self._repo, a)
835 yield changectx(self._repo, a)
840
836
841 def remove(self, list, unlink=False):
837 def remove(self, list, unlink=False):
842 if unlink:
838 if unlink:
843 for f in list:
839 for f in list:
844 try:
840 try:
845 util.unlinkpath(self._repo.wjoin(f))
841 util.unlinkpath(self._repo.wjoin(f))
846 except OSError, inst:
842 except OSError, inst:
847 if inst.errno != errno.ENOENT:
843 if inst.errno != errno.ENOENT:
848 raise
844 raise
849 wlock = self._repo.wlock()
845 wlock = self._repo.wlock()
850 try:
846 try:
851 for f in list:
847 for f in list:
852 if unlink and os.path.lexists(self._repo.wjoin(f)):
848 if unlink and os.path.lexists(self._repo.wjoin(f)):
853 self._repo.ui.warn(_("%s still exists!\n") % f)
849 self._repo.ui.warn(_("%s still exists!\n") % f)
854 elif self._repo.dirstate[f] == 'a':
850 elif self._repo.dirstate[f] == 'a':
855 self._repo.dirstate.forget(f)
851 self._repo.dirstate.forget(f)
856 elif f not in self._repo.dirstate:
852 elif f not in self._repo.dirstate:
857 self._repo.ui.warn(_("%s not tracked!\n") % f)
853 self._repo.ui.warn(_("%s not tracked!\n") % f)
858 else:
854 else:
859 self._repo.dirstate.remove(f)
855 self._repo.dirstate.remove(f)
860 finally:
856 finally:
861 wlock.release()
857 wlock.release()
862
858
863 def undelete(self, list):
859 def undelete(self, list):
864 pctxs = self.parents()
860 pctxs = self.parents()
865 wlock = self._repo.wlock()
861 wlock = self._repo.wlock()
866 try:
862 try:
867 for f in list:
863 for f in list:
868 if self._repo.dirstate[f] != 'r':
864 if self._repo.dirstate[f] != 'r':
869 self._repo.ui.warn(_("%s not removed!\n") % f)
865 self._repo.ui.warn(_("%s not removed!\n") % f)
870 else:
866 else:
871 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
867 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
872 t = fctx.data()
868 t = fctx.data()
873 self._repo.wwrite(f, t, fctx.flags())
869 self._repo.wwrite(f, t, fctx.flags())
874 self._repo.dirstate.normal(f)
870 self._repo.dirstate.normal(f)
875 finally:
871 finally:
876 wlock.release()
872 wlock.release()
877
873
878 def copy(self, source, dest):
874 def copy(self, source, dest):
879 p = self._repo.wjoin(dest)
875 p = self._repo.wjoin(dest)
880 if not os.path.lexists(p):
876 if not os.path.lexists(p):
881 self._repo.ui.warn(_("%s does not exist!\n") % dest)
877 self._repo.ui.warn(_("%s does not exist!\n") % dest)
882 elif not (os.path.isfile(p) or os.path.islink(p)):
878 elif not (os.path.isfile(p) or os.path.islink(p)):
883 self._repo.ui.warn(_("copy failed: %s is not a file or a "
879 self._repo.ui.warn(_("copy failed: %s is not a file or a "
884 "symbolic link\n") % dest)
880 "symbolic link\n") % dest)
885 else:
881 else:
886 wlock = self._repo.wlock()
882 wlock = self._repo.wlock()
887 try:
883 try:
888 if self._repo.dirstate[dest] in '?r':
884 if self._repo.dirstate[dest] in '?r':
889 self._repo.dirstate.add(dest)
885 self._repo.dirstate.add(dest)
890 self._repo.dirstate.copy(source, dest)
886 self._repo.dirstate.copy(source, dest)
891 finally:
887 finally:
892 wlock.release()
888 wlock.release()
893
889
894 class workingfilectx(filectx):
890 class workingfilectx(filectx):
895 """A workingfilectx object makes access to data related to a particular
891 """A workingfilectx object makes access to data related to a particular
896 file in the working directory convenient."""
892 file in the working directory convenient."""
897 def __init__(self, repo, path, filelog=None, workingctx=None):
893 def __init__(self, repo, path, filelog=None, workingctx=None):
898 """changeid can be a changeset revision, node, or tag.
894 """changeid can be a changeset revision, node, or tag.
899 fileid can be a file revision or node."""
895 fileid can be a file revision or node."""
900 self._repo = repo
896 self._repo = repo
901 self._path = path
897 self._path = path
902 self._changeid = None
898 self._changeid = None
903 self._filerev = self._filenode = None
899 self._filerev = self._filenode = None
904
900
905 if filelog:
901 if filelog:
906 self._filelog = filelog
902 self._filelog = filelog
907 if workingctx:
903 if workingctx:
908 self._changectx = workingctx
904 self._changectx = workingctx
909
905
910 @propertycache
906 @propertycache
911 def _changectx(self):
907 def _changectx(self):
912 return workingctx(self._repo)
908 return workingctx(self._repo)
913
909
914 def __nonzero__(self):
910 def __nonzero__(self):
915 return True
911 return True
916
912
917 def __str__(self):
913 def __str__(self):
918 return "%s@%s" % (self.path(), self._changectx)
914 return "%s@%s" % (self.path(), self._changectx)
919
915
920 def __repr__(self):
916 def __repr__(self):
921 return "<workingfilectx %s>" % str(self)
917 return "<workingfilectx %s>" % str(self)
922
918
923 def data(self):
919 def data(self):
924 return self._repo.wread(self._path)
920 return self._repo.wread(self._path)
925 def renamed(self):
921 def renamed(self):
926 rp = self._repo.dirstate.copied(self._path)
922 rp = self._repo.dirstate.copied(self._path)
927 if not rp:
923 if not rp:
928 return None
924 return None
929 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
925 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
930
926
931 def parents(self):
927 def parents(self):
932 '''return parent filectxs, following copies if necessary'''
928 '''return parent filectxs, following copies if necessary'''
933 def filenode(ctx, path):
929 def filenode(ctx, path):
934 return ctx._manifest.get(path, nullid)
930 return ctx._manifest.get(path, nullid)
935
931
936 path = self._path
932 path = self._path
937 fl = self._filelog
933 fl = self._filelog
938 pcl = self._changectx._parents
934 pcl = self._changectx._parents
939 renamed = self.renamed()
935 renamed = self.renamed()
940
936
941 if renamed:
937 if renamed:
942 pl = [renamed + (None,)]
938 pl = [renamed + (None,)]
943 else:
939 else:
944 pl = [(path, filenode(pcl[0], path), fl)]
940 pl = [(path, filenode(pcl[0], path), fl)]
945
941
946 for pc in pcl[1:]:
942 for pc in pcl[1:]:
947 pl.append((path, filenode(pc, path), fl))
943 pl.append((path, filenode(pc, path), fl))
948
944
949 return [filectx(self._repo, p, fileid=n, filelog=l)
945 return [filectx(self._repo, p, fileid=n, filelog=l)
950 for p, n, l in pl if n != nullid]
946 for p, n, l in pl if n != nullid]
951
947
952 def children(self):
948 def children(self):
953 return []
949 return []
954
950
955 def size(self):
951 def size(self):
956 return os.lstat(self._repo.wjoin(self._path)).st_size
952 return os.lstat(self._repo.wjoin(self._path)).st_size
957 def date(self):
953 def date(self):
958 t, tz = self._changectx.date()
954 t, tz = self._changectx.date()
959 try:
955 try:
960 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
956 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
961 except OSError, err:
957 except OSError, err:
962 if err.errno != errno.ENOENT:
958 if err.errno != errno.ENOENT:
963 raise
959 raise
964 return (t, tz)
960 return (t, tz)
965
961
966 def cmp(self, fctx):
962 def cmp(self, fctx):
967 """compare with other file context
963 """compare with other file context
968
964
969 returns True if different than fctx.
965 returns True if different than fctx.
970 """
966 """
971 # fctx should be a filectx (not a wfctx)
967 # fctx should be a filectx (not a wfctx)
972 # invert comparison to reuse the same code path
968 # invert comparison to reuse the same code path
973 return fctx.cmp(self)
969 return fctx.cmp(self)
974
970
975 class memctx(object):
971 class memctx(object):
976 """Use memctx to perform in-memory commits via localrepo.commitctx().
972 """Use memctx to perform in-memory commits via localrepo.commitctx().
977
973
978 Revision information is supplied at initialization time while
974 Revision information is supplied at initialization time while
979 related files data and is made available through a callback
975 related files data and is made available through a callback
980 mechanism. 'repo' is the current localrepo, 'parents' is a
976 mechanism. 'repo' is the current localrepo, 'parents' is a
981 sequence of two parent revisions identifiers (pass None for every
977 sequence of two parent revisions identifiers (pass None for every
982 missing parent), 'text' is the commit message and 'files' lists
978 missing parent), 'text' is the commit message and 'files' lists
983 names of files touched by the revision (normalized and relative to
979 names of files touched by the revision (normalized and relative to
984 repository root).
980 repository root).
985
981
986 filectxfn(repo, memctx, path) is a callable receiving the
982 filectxfn(repo, memctx, path) is a callable receiving the
987 repository, the current memctx object and the normalized path of
983 repository, the current memctx object and the normalized path of
988 requested file, relative to repository root. It is fired by the
984 requested file, relative to repository root. It is fired by the
989 commit function for every file in 'files', but calls order is
985 commit function for every file in 'files', but calls order is
990 undefined. If the file is available in the revision being
986 undefined. If the file is available in the revision being
991 committed (updated or added), filectxfn returns a memfilectx
987 committed (updated or added), filectxfn returns a memfilectx
992 object. If the file was removed, filectxfn raises an
988 object. If the file was removed, filectxfn raises an
993 IOError. Moved files are represented by marking the source file
989 IOError. Moved files are represented by marking the source file
994 removed and the new file added with copy information (see
990 removed and the new file added with copy information (see
995 memfilectx).
991 memfilectx).
996
992
997 user receives the committer name and defaults to current
993 user receives the committer name and defaults to current
998 repository username, date is the commit date in any format
994 repository username, date is the commit date in any format
999 supported by util.parsedate() and defaults to current date, extra
995 supported by util.parsedate() and defaults to current date, extra
1000 is a dictionary of metadata or is left empty.
996 is a dictionary of metadata or is left empty.
1001 """
997 """
1002 def __init__(self, repo, parents, text, files, filectxfn, user=None,
998 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1003 date=None, extra=None):
999 date=None, extra=None):
1004 self._repo = repo
1000 self._repo = repo
1005 self._rev = None
1001 self._rev = None
1006 self._node = None
1002 self._node = None
1007 self._text = text
1003 self._text = text
1008 self._date = date and util.parsedate(date) or util.makedate()
1004 self._date = date and util.parsedate(date) or util.makedate()
1009 self._user = user
1005 self._user = user
1010 parents = [(p or nullid) for p in parents]
1006 parents = [(p or nullid) for p in parents]
1011 p1, p2 = parents
1007 p1, p2 = parents
1012 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1008 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1013 files = sorted(set(files))
1009 files = sorted(set(files))
1014 self._status = [files, [], [], [], []]
1010 self._status = [files, [], [], [], []]
1015 self._filectxfn = filectxfn
1011 self._filectxfn = filectxfn
1016
1012
1017 self._extra = extra and extra.copy() or {}
1013 self._extra = extra and extra.copy() or {}
1018 if 'branch' not in self._extra:
1014 if 'branch' not in self._extra:
1019 self._extra['branch'] = 'default'
1015 self._extra['branch'] = 'default'
1020 elif self._extra.get('branch') == '':
1016 elif self._extra.get('branch') == '':
1021 self._extra['branch'] = 'default'
1017 self._extra['branch'] = 'default'
1022
1018
1023 def __str__(self):
1019 def __str__(self):
1024 return str(self._parents[0]) + "+"
1020 return str(self._parents[0]) + "+"
1025
1021
1026 def __int__(self):
1022 def __int__(self):
1027 return self._rev
1023 return self._rev
1028
1024
1029 def __nonzero__(self):
1025 def __nonzero__(self):
1030 return True
1026 return True
1031
1027
1032 def __getitem__(self, key):
1028 def __getitem__(self, key):
1033 return self.filectx(key)
1029 return self.filectx(key)
1034
1030
1035 def p1(self):
1031 def p1(self):
1036 return self._parents[0]
1032 return self._parents[0]
1037 def p2(self):
1033 def p2(self):
1038 return self._parents[1]
1034 return self._parents[1]
1039
1035
1040 def user(self):
1036 def user(self):
1041 return self._user or self._repo.ui.username()
1037 return self._user or self._repo.ui.username()
1042 def date(self):
1038 def date(self):
1043 return self._date
1039 return self._date
1044 def description(self):
1040 def description(self):
1045 return self._text
1041 return self._text
1046 def files(self):
1042 def files(self):
1047 return self.modified()
1043 return self.modified()
1048 def modified(self):
1044 def modified(self):
1049 return self._status[0]
1045 return self._status[0]
1050 def added(self):
1046 def added(self):
1051 return self._status[1]
1047 return self._status[1]
1052 def removed(self):
1048 def removed(self):
1053 return self._status[2]
1049 return self._status[2]
1054 def deleted(self):
1050 def deleted(self):
1055 return self._status[3]
1051 return self._status[3]
1056 def unknown(self):
1052 def unknown(self):
1057 return self._status[4]
1053 return self._status[4]
1058 def ignored(self):
1054 def ignored(self):
1059 return self._status[5]
1055 return self._status[5]
1060 def clean(self):
1056 def clean(self):
1061 return self._status[6]
1057 return self._status[6]
1062 def branch(self):
1058 def branch(self):
1063 return encoding.tolocal(self._extra['branch'])
1059 return encoding.tolocal(self._extra['branch'])
1064 def extra(self):
1060 def extra(self):
1065 return self._extra
1061 return self._extra
1066 def flags(self, f):
1062 def flags(self, f):
1067 return self[f].flags()
1063 return self[f].flags()
1068
1064
1069 def parents(self):
1065 def parents(self):
1070 """return contexts for each parent changeset"""
1066 """return contexts for each parent changeset"""
1071 return self._parents
1067 return self._parents
1072
1068
1073 def filectx(self, path, filelog=None):
1069 def filectx(self, path, filelog=None):
1074 """get a file context from the working directory"""
1070 """get a file context from the working directory"""
1075 return self._filectxfn(self._repo, self, path)
1071 return self._filectxfn(self._repo, self, path)
1076
1072
1077 def commit(self):
1073 def commit(self):
1078 """commit context to the repo"""
1074 """commit context to the repo"""
1079 return self._repo.commitctx(self)
1075 return self._repo.commitctx(self)
1080
1076
1081 class memfilectx(object):
1077 class memfilectx(object):
1082 """memfilectx represents an in-memory file to commit.
1078 """memfilectx represents an in-memory file to commit.
1083
1079
1084 See memctx for more details.
1080 See memctx for more details.
1085 """
1081 """
1086 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1082 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1087 """
1083 """
1088 path is the normalized file path relative to repository root.
1084 path is the normalized file path relative to repository root.
1089 data is the file content as a string.
1085 data is the file content as a string.
1090 islink is True if the file is a symbolic link.
1086 islink is True if the file is a symbolic link.
1091 isexec is True if the file is executable.
1087 isexec is True if the file is executable.
1092 copied is the source file path if current file was copied in the
1088 copied is the source file path if current file was copied in the
1093 revision being committed, or None."""
1089 revision being committed, or None."""
1094 self._path = path
1090 self._path = path
1095 self._data = data
1091 self._data = data
1096 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1092 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1097 self._copied = None
1093 self._copied = None
1098 if copied:
1094 if copied:
1099 self._copied = (copied, nullid)
1095 self._copied = (copied, nullid)
1100
1096
1101 def __nonzero__(self):
1097 def __nonzero__(self):
1102 return True
1098 return True
1103 def __str__(self):
1099 def __str__(self):
1104 return "%s@%s" % (self.path(), self._changectx)
1100 return "%s@%s" % (self.path(), self._changectx)
1105 def path(self):
1101 def path(self):
1106 return self._path
1102 return self._path
1107 def data(self):
1103 def data(self):
1108 return self._data
1104 return self._data
1109 def flags(self):
1105 def flags(self):
1110 return self._flags
1106 return self._flags
1111 def isexec(self):
1107 def isexec(self):
1112 return 'x' in self._flags
1108 return 'x' in self._flags
1113 def islink(self):
1109 def islink(self):
1114 return 'l' in self._flags
1110 return 'l' in self._flags
1115 def renamed(self):
1111 def renamed(self):
1116 return self._copied
1112 return self._copied
General Comments 0
You need to be logged in to leave comments. Login now