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