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