##// END OF EJS Templates
icasefs: ignore removed files at building "dirstate._foldmap" up on icasefs...
FUJIWARA Katsunori -
r19103:0176d0db stable
parent child Browse files
Show More
@@ -1,814 +1,815 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 import errno
7 import errno
8
8
9 from node import nullid
9 from node import nullid
10 from i18n import _
10 from i18n import _
11 import scmutil, util, ignore, osutil, parsers, encoding
11 import scmutil, util, ignore, osutil, parsers, encoding
12 import os, stat, errno, gc
12 import os, stat, errno, gc
13
13
14 propertycache = util.propertycache
14 propertycache = util.propertycache
15 filecache = scmutil.filecache
15 filecache = scmutil.filecache
16 _rangemask = 0x7fffffff
16 _rangemask = 0x7fffffff
17
17
18 class repocache(filecache):
18 class repocache(filecache):
19 """filecache for files in .hg/"""
19 """filecache for files in .hg/"""
20 def join(self, obj, fname):
20 def join(self, obj, fname):
21 return obj._opener.join(fname)
21 return obj._opener.join(fname)
22
22
23 class rootcache(filecache):
23 class rootcache(filecache):
24 """filecache for files in the repository root"""
24 """filecache for files in the repository root"""
25 def join(self, obj, fname):
25 def join(self, obj, fname):
26 return obj._join(fname)
26 return obj._join(fname)
27
27
28 class dirstate(object):
28 class dirstate(object):
29
29
30 def __init__(self, opener, ui, root, validate):
30 def __init__(self, opener, ui, root, validate):
31 '''Create a new dirstate object.
31 '''Create a new dirstate object.
32
32
33 opener is an open()-like callable that can be used to open the
33 opener is an open()-like callable that can be used to open the
34 dirstate file; root is the root of the directory tracked by
34 dirstate file; root is the root of the directory tracked by
35 the dirstate.
35 the dirstate.
36 '''
36 '''
37 self._opener = opener
37 self._opener = opener
38 self._validate = validate
38 self._validate = validate
39 self._root = root
39 self._root = root
40 self._rootdir = os.path.join(root, '')
40 self._rootdir = os.path.join(root, '')
41 self._dirty = False
41 self._dirty = False
42 self._dirtypl = False
42 self._dirtypl = False
43 self._lastnormaltime = 0
43 self._lastnormaltime = 0
44 self._ui = ui
44 self._ui = ui
45 self._filecache = {}
45 self._filecache = {}
46
46
47 @propertycache
47 @propertycache
48 def _map(self):
48 def _map(self):
49 '''Return the dirstate contents as a map from filename to
49 '''Return the dirstate contents as a map from filename to
50 (state, mode, size, time).'''
50 (state, mode, size, time).'''
51 self._read()
51 self._read()
52 return self._map
52 return self._map
53
53
54 @propertycache
54 @propertycache
55 def _copymap(self):
55 def _copymap(self):
56 self._read()
56 self._read()
57 return self._copymap
57 return self._copymap
58
58
59 @propertycache
59 @propertycache
60 def _foldmap(self):
60 def _foldmap(self):
61 f = {}
61 f = {}
62 for name in self._map:
62 for name, s in self._map.iteritems():
63 f[util.normcase(name)] = name
63 if s[0] != 'r':
64 f[util.normcase(name)] = name
64 for name in self._dirs:
65 for name in self._dirs:
65 f[util.normcase(name)] = name
66 f[util.normcase(name)] = name
66 f['.'] = '.' # prevents useless util.fspath() invocation
67 f['.'] = '.' # prevents useless util.fspath() invocation
67 return f
68 return f
68
69
69 @repocache('branch')
70 @repocache('branch')
70 def _branch(self):
71 def _branch(self):
71 try:
72 try:
72 return self._opener.read("branch").strip() or "default"
73 return self._opener.read("branch").strip() or "default"
73 except IOError, inst:
74 except IOError, inst:
74 if inst.errno != errno.ENOENT:
75 if inst.errno != errno.ENOENT:
75 raise
76 raise
76 return "default"
77 return "default"
77
78
78 @propertycache
79 @propertycache
79 def _pl(self):
80 def _pl(self):
80 try:
81 try:
81 fp = self._opener("dirstate")
82 fp = self._opener("dirstate")
82 st = fp.read(40)
83 st = fp.read(40)
83 fp.close()
84 fp.close()
84 l = len(st)
85 l = len(st)
85 if l == 40:
86 if l == 40:
86 return st[:20], st[20:40]
87 return st[:20], st[20:40]
87 elif l > 0 and l < 40:
88 elif l > 0 and l < 40:
88 raise util.Abort(_('working directory state appears damaged!'))
89 raise util.Abort(_('working directory state appears damaged!'))
89 except IOError, err:
90 except IOError, err:
90 if err.errno != errno.ENOENT:
91 if err.errno != errno.ENOENT:
91 raise
92 raise
92 return [nullid, nullid]
93 return [nullid, nullid]
93
94
94 @propertycache
95 @propertycache
95 def _dirs(self):
96 def _dirs(self):
96 return scmutil.dirs(self._map, 'r')
97 return scmutil.dirs(self._map, 'r')
97
98
98 def dirs(self):
99 def dirs(self):
99 return self._dirs
100 return self._dirs
100
101
101 @rootcache('.hgignore')
102 @rootcache('.hgignore')
102 def _ignore(self):
103 def _ignore(self):
103 files = [self._join('.hgignore')]
104 files = [self._join('.hgignore')]
104 for name, path in self._ui.configitems("ui"):
105 for name, path in self._ui.configitems("ui"):
105 if name == 'ignore' or name.startswith('ignore.'):
106 if name == 'ignore' or name.startswith('ignore.'):
106 files.append(util.expandpath(path))
107 files.append(util.expandpath(path))
107 return ignore.ignore(self._root, files, self._ui.warn)
108 return ignore.ignore(self._root, files, self._ui.warn)
108
109
109 @propertycache
110 @propertycache
110 def _slash(self):
111 def _slash(self):
111 return self._ui.configbool('ui', 'slash') and os.sep != '/'
112 return self._ui.configbool('ui', 'slash') and os.sep != '/'
112
113
113 @propertycache
114 @propertycache
114 def _checklink(self):
115 def _checklink(self):
115 return util.checklink(self._root)
116 return util.checklink(self._root)
116
117
117 @propertycache
118 @propertycache
118 def _checkexec(self):
119 def _checkexec(self):
119 return util.checkexec(self._root)
120 return util.checkexec(self._root)
120
121
121 @propertycache
122 @propertycache
122 def _checkcase(self):
123 def _checkcase(self):
123 return not util.checkcase(self._join('.hg'))
124 return not util.checkcase(self._join('.hg'))
124
125
125 def _join(self, f):
126 def _join(self, f):
126 # much faster than os.path.join()
127 # much faster than os.path.join()
127 # it's safe because f is always a relative path
128 # it's safe because f is always a relative path
128 return self._rootdir + f
129 return self._rootdir + f
129
130
130 def flagfunc(self, buildfallback):
131 def flagfunc(self, buildfallback):
131 if self._checklink and self._checkexec:
132 if self._checklink and self._checkexec:
132 def f(x):
133 def f(x):
133 try:
134 try:
134 st = os.lstat(self._join(x))
135 st = os.lstat(self._join(x))
135 if util.statislink(st):
136 if util.statislink(st):
136 return 'l'
137 return 'l'
137 if util.statisexec(st):
138 if util.statisexec(st):
138 return 'x'
139 return 'x'
139 except OSError:
140 except OSError:
140 pass
141 pass
141 return ''
142 return ''
142 return f
143 return f
143
144
144 fallback = buildfallback()
145 fallback = buildfallback()
145 if self._checklink:
146 if self._checklink:
146 def f(x):
147 def f(x):
147 if os.path.islink(self._join(x)):
148 if os.path.islink(self._join(x)):
148 return 'l'
149 return 'l'
149 if 'x' in fallback(x):
150 if 'x' in fallback(x):
150 return 'x'
151 return 'x'
151 return ''
152 return ''
152 return f
153 return f
153 if self._checkexec:
154 if self._checkexec:
154 def f(x):
155 def f(x):
155 if 'l' in fallback(x):
156 if 'l' in fallback(x):
156 return 'l'
157 return 'l'
157 if util.isexec(self._join(x)):
158 if util.isexec(self._join(x)):
158 return 'x'
159 return 'x'
159 return ''
160 return ''
160 return f
161 return f
161 else:
162 else:
162 return fallback
163 return fallback
163
164
164 def getcwd(self):
165 def getcwd(self):
165 cwd = os.getcwd()
166 cwd = os.getcwd()
166 if cwd == self._root:
167 if cwd == self._root:
167 return ''
168 return ''
168 # 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:\'
169 rootsep = self._root
170 rootsep = self._root
170 if not util.endswithsep(rootsep):
171 if not util.endswithsep(rootsep):
171 rootsep += os.sep
172 rootsep += os.sep
172 if cwd.startswith(rootsep):
173 if cwd.startswith(rootsep):
173 return cwd[len(rootsep):]
174 return cwd[len(rootsep):]
174 else:
175 else:
175 # we're outside the repo. return an absolute path.
176 # we're outside the repo. return an absolute path.
176 return cwd
177 return cwd
177
178
178 def pathto(self, f, cwd=None):
179 def pathto(self, f, cwd=None):
179 if cwd is None:
180 if cwd is None:
180 cwd = self.getcwd()
181 cwd = self.getcwd()
181 path = util.pathto(self._root, cwd, f)
182 path = util.pathto(self._root, cwd, f)
182 if self._slash:
183 if self._slash:
183 return util.normpath(path)
184 return util.normpath(path)
184 return path
185 return path
185
186
186 def __getitem__(self, key):
187 def __getitem__(self, key):
187 '''Return the current state of key (a filename) in the dirstate.
188 '''Return the current state of key (a filename) in the dirstate.
188
189
189 States are:
190 States are:
190 n normal
191 n normal
191 m needs merging
192 m needs merging
192 r marked for removal
193 r marked for removal
193 a marked for addition
194 a marked for addition
194 ? not tracked
195 ? not tracked
195 '''
196 '''
196 return self._map.get(key, ("?",))[0]
197 return self._map.get(key, ("?",))[0]
197
198
198 def __contains__(self, key):
199 def __contains__(self, key):
199 return key in self._map
200 return key in self._map
200
201
201 def __iter__(self):
202 def __iter__(self):
202 for x in sorted(self._map):
203 for x in sorted(self._map):
203 yield x
204 yield x
204
205
205 def iteritems(self):
206 def iteritems(self):
206 return self._map.iteritems()
207 return self._map.iteritems()
207
208
208 def parents(self):
209 def parents(self):
209 return [self._validate(p) for p in self._pl]
210 return [self._validate(p) for p in self._pl]
210
211
211 def p1(self):
212 def p1(self):
212 return self._validate(self._pl[0])
213 return self._validate(self._pl[0])
213
214
214 def p2(self):
215 def p2(self):
215 return self._validate(self._pl[1])
216 return self._validate(self._pl[1])
216
217
217 def branch(self):
218 def branch(self):
218 return encoding.tolocal(self._branch)
219 return encoding.tolocal(self._branch)
219
220
220 def setparents(self, p1, p2=nullid):
221 def setparents(self, p1, p2=nullid):
221 """Set dirstate parents to p1 and p2.
222 """Set dirstate parents to p1 and p2.
222
223
223 When moving from two parents to one, 'm' merged entries a
224 When moving from two parents to one, 'm' merged entries a
224 adjusted to normal and previous copy records discarded and
225 adjusted to normal and previous copy records discarded and
225 returned by the call.
226 returned by the call.
226
227
227 See localrepo.setparents()
228 See localrepo.setparents()
228 """
229 """
229 self._dirty = self._dirtypl = True
230 self._dirty = self._dirtypl = True
230 oldp2 = self._pl[1]
231 oldp2 = self._pl[1]
231 self._pl = p1, p2
232 self._pl = p1, p2
232 copies = {}
233 copies = {}
233 if oldp2 != nullid and p2 == nullid:
234 if oldp2 != nullid and p2 == nullid:
234 # Discard 'm' markers when moving away from a merge state
235 # Discard 'm' markers when moving away from a merge state
235 for f, s in self._map.iteritems():
236 for f, s in self._map.iteritems():
236 if s[0] == 'm':
237 if s[0] == 'm':
237 if f in self._copymap:
238 if f in self._copymap:
238 copies[f] = self._copymap[f]
239 copies[f] = self._copymap[f]
239 self.normallookup(f)
240 self.normallookup(f)
240 return copies
241 return copies
241
242
242 def setbranch(self, branch):
243 def setbranch(self, branch):
243 self._branch = encoding.fromlocal(branch)
244 self._branch = encoding.fromlocal(branch)
244 f = self._opener('branch', 'w', atomictemp=True)
245 f = self._opener('branch', 'w', atomictemp=True)
245 try:
246 try:
246 f.write(self._branch + '\n')
247 f.write(self._branch + '\n')
247 f.close()
248 f.close()
248
249
249 # make sure filecache has the correct stat info for _branch after
250 # make sure filecache has the correct stat info for _branch after
250 # replacing the underlying file
251 # replacing the underlying file
251 ce = self._filecache['_branch']
252 ce = self._filecache['_branch']
252 if ce:
253 if ce:
253 ce.refresh()
254 ce.refresh()
254 except: # re-raises
255 except: # re-raises
255 f.discard()
256 f.discard()
256 raise
257 raise
257
258
258 def _read(self):
259 def _read(self):
259 self._map = {}
260 self._map = {}
260 self._copymap = {}
261 self._copymap = {}
261 try:
262 try:
262 st = self._opener.read("dirstate")
263 st = self._opener.read("dirstate")
263 except IOError, err:
264 except IOError, err:
264 if err.errno != errno.ENOENT:
265 if err.errno != errno.ENOENT:
265 raise
266 raise
266 return
267 return
267 if not st:
268 if not st:
268 return
269 return
269
270
270 # Python's garbage collector triggers a GC each time a certain number
271 # Python's garbage collector triggers a GC each time a certain number
271 # of container objects (the number being defined by
272 # of container objects (the number being defined by
272 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
273 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
273 # for each file in the dirstate. The C version then immediately marks
274 # for each file in the dirstate. The C version then immediately marks
274 # them as not to be tracked by the collector. However, this has no
275 # them as not to be tracked by the collector. However, this has no
275 # effect on when GCs are triggered, only on what objects the GC looks
276 # effect on when GCs are triggered, only on what objects the GC looks
276 # into. This means that O(number of files) GCs are unavoidable.
277 # into. This means that O(number of files) GCs are unavoidable.
277 # Depending on when in the process's lifetime the dirstate is parsed,
278 # Depending on when in the process's lifetime the dirstate is parsed,
278 # this can get very expensive. As a workaround, disable GC while
279 # this can get very expensive. As a workaround, disable GC while
279 # parsing the dirstate.
280 # parsing the dirstate.
280 gcenabled = gc.isenabled()
281 gcenabled = gc.isenabled()
281 gc.disable()
282 gc.disable()
282 try:
283 try:
283 p = parsers.parse_dirstate(self._map, self._copymap, st)
284 p = parsers.parse_dirstate(self._map, self._copymap, st)
284 finally:
285 finally:
285 if gcenabled:
286 if gcenabled:
286 gc.enable()
287 gc.enable()
287 if not self._dirtypl:
288 if not self._dirtypl:
288 self._pl = p
289 self._pl = p
289
290
290 def invalidate(self):
291 def invalidate(self):
291 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
292 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
292 "_ignore"):
293 "_ignore"):
293 if a in self.__dict__:
294 if a in self.__dict__:
294 delattr(self, a)
295 delattr(self, a)
295 self._lastnormaltime = 0
296 self._lastnormaltime = 0
296 self._dirty = False
297 self._dirty = False
297
298
298 def copy(self, source, dest):
299 def copy(self, source, dest):
299 """Mark dest as a copy of source. Unmark dest if source is None."""
300 """Mark dest as a copy of source. Unmark dest if source is None."""
300 if source == dest:
301 if source == dest:
301 return
302 return
302 self._dirty = True
303 self._dirty = True
303 if source is not None:
304 if source is not None:
304 self._copymap[dest] = source
305 self._copymap[dest] = source
305 elif dest in self._copymap:
306 elif dest in self._copymap:
306 del self._copymap[dest]
307 del self._copymap[dest]
307
308
308 def copied(self, file):
309 def copied(self, file):
309 return self._copymap.get(file, None)
310 return self._copymap.get(file, None)
310
311
311 def copies(self):
312 def copies(self):
312 return self._copymap
313 return self._copymap
313
314
314 def _droppath(self, f):
315 def _droppath(self, f):
315 if self[f] not in "?r" and "_dirs" in self.__dict__:
316 if self[f] not in "?r" and "_dirs" in self.__dict__:
316 self._dirs.delpath(f)
317 self._dirs.delpath(f)
317
318
318 def _addpath(self, f, state, mode, size, mtime):
319 def _addpath(self, f, state, mode, size, mtime):
319 oldstate = self[f]
320 oldstate = self[f]
320 if state == 'a' or oldstate == 'r':
321 if state == 'a' or oldstate == 'r':
321 scmutil.checkfilename(f)
322 scmutil.checkfilename(f)
322 if f in self._dirs:
323 if f in self._dirs:
323 raise util.Abort(_('directory %r already in dirstate') % f)
324 raise util.Abort(_('directory %r already in dirstate') % f)
324 # shadows
325 # shadows
325 for d in scmutil.finddirs(f):
326 for d in scmutil.finddirs(f):
326 if d in self._dirs:
327 if d in self._dirs:
327 break
328 break
328 if d in self._map and self[d] != 'r':
329 if d in self._map and self[d] != 'r':
329 raise util.Abort(
330 raise util.Abort(
330 _('file %r in dirstate clashes with %r') % (d, f))
331 _('file %r in dirstate clashes with %r') % (d, f))
331 if oldstate in "?r" and "_dirs" in self.__dict__:
332 if oldstate in "?r" and "_dirs" in self.__dict__:
332 self._dirs.addpath(f)
333 self._dirs.addpath(f)
333 self._dirty = True
334 self._dirty = True
334 self._map[f] = (state, mode, size, mtime)
335 self._map[f] = (state, mode, size, mtime)
335
336
336 def normal(self, f):
337 def normal(self, f):
337 '''Mark a file normal and clean.'''
338 '''Mark a file normal and clean.'''
338 s = os.lstat(self._join(f))
339 s = os.lstat(self._join(f))
339 mtime = int(s.st_mtime)
340 mtime = int(s.st_mtime)
340 self._addpath(f, 'n', s.st_mode,
341 self._addpath(f, 'n', s.st_mode,
341 s.st_size & _rangemask, mtime & _rangemask)
342 s.st_size & _rangemask, mtime & _rangemask)
342 if f in self._copymap:
343 if f in self._copymap:
343 del self._copymap[f]
344 del self._copymap[f]
344 if mtime > self._lastnormaltime:
345 if mtime > self._lastnormaltime:
345 # Remember the most recent modification timeslot for status(),
346 # Remember the most recent modification timeslot for status(),
346 # to make sure we won't miss future size-preserving file content
347 # to make sure we won't miss future size-preserving file content
347 # modifications that happen within the same timeslot.
348 # modifications that happen within the same timeslot.
348 self._lastnormaltime = mtime
349 self._lastnormaltime = mtime
349
350
350 def normallookup(self, f):
351 def normallookup(self, f):
351 '''Mark a file normal, but possibly dirty.'''
352 '''Mark a file normal, but possibly dirty.'''
352 if self._pl[1] != nullid and f in self._map:
353 if self._pl[1] != nullid and f in self._map:
353 # if there is a merge going on and the file was either
354 # if there is a merge going on and the file was either
354 # in state 'm' (-1) or coming from other parent (-2) before
355 # in state 'm' (-1) or coming from other parent (-2) before
355 # being removed, restore that state.
356 # being removed, restore that state.
356 entry = self._map[f]
357 entry = self._map[f]
357 if entry[0] == 'r' and entry[2] in (-1, -2):
358 if entry[0] == 'r' and entry[2] in (-1, -2):
358 source = self._copymap.get(f)
359 source = self._copymap.get(f)
359 if entry[2] == -1:
360 if entry[2] == -1:
360 self.merge(f)
361 self.merge(f)
361 elif entry[2] == -2:
362 elif entry[2] == -2:
362 self.otherparent(f)
363 self.otherparent(f)
363 if source:
364 if source:
364 self.copy(source, f)
365 self.copy(source, f)
365 return
366 return
366 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
367 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
367 return
368 return
368 self._addpath(f, 'n', 0, -1, -1)
369 self._addpath(f, 'n', 0, -1, -1)
369 if f in self._copymap:
370 if f in self._copymap:
370 del self._copymap[f]
371 del self._copymap[f]
371
372
372 def otherparent(self, f):
373 def otherparent(self, f):
373 '''Mark as coming from the other parent, always dirty.'''
374 '''Mark as coming from the other parent, always dirty.'''
374 if self._pl[1] == nullid:
375 if self._pl[1] == nullid:
375 raise util.Abort(_("setting %r to other parent "
376 raise util.Abort(_("setting %r to other parent "
376 "only allowed in merges") % f)
377 "only allowed in merges") % f)
377 self._addpath(f, 'n', 0, -2, -1)
378 self._addpath(f, 'n', 0, -2, -1)
378 if f in self._copymap:
379 if f in self._copymap:
379 del self._copymap[f]
380 del self._copymap[f]
380
381
381 def add(self, f):
382 def add(self, f):
382 '''Mark a file added.'''
383 '''Mark a file added.'''
383 self._addpath(f, 'a', 0, -1, -1)
384 self._addpath(f, 'a', 0, -1, -1)
384 if f in self._copymap:
385 if f in self._copymap:
385 del self._copymap[f]
386 del self._copymap[f]
386
387
387 def remove(self, f):
388 def remove(self, f):
388 '''Mark a file removed.'''
389 '''Mark a file removed.'''
389 self._dirty = True
390 self._dirty = True
390 self._droppath(f)
391 self._droppath(f)
391 size = 0
392 size = 0
392 if self._pl[1] != nullid and f in self._map:
393 if self._pl[1] != nullid and f in self._map:
393 # backup the previous state
394 # backup the previous state
394 entry = self._map[f]
395 entry = self._map[f]
395 if entry[0] == 'm': # merge
396 if entry[0] == 'm': # merge
396 size = -1
397 size = -1
397 elif entry[0] == 'n' and entry[2] == -2: # other parent
398 elif entry[0] == 'n' and entry[2] == -2: # other parent
398 size = -2
399 size = -2
399 self._map[f] = ('r', 0, size, 0)
400 self._map[f] = ('r', 0, size, 0)
400 if size == 0 and f in self._copymap:
401 if size == 0 and f in self._copymap:
401 del self._copymap[f]
402 del self._copymap[f]
402
403
403 def merge(self, f):
404 def merge(self, f):
404 '''Mark a file merged.'''
405 '''Mark a file merged.'''
405 if self._pl[1] == nullid:
406 if self._pl[1] == nullid:
406 return self.normallookup(f)
407 return self.normallookup(f)
407 s = os.lstat(self._join(f))
408 s = os.lstat(self._join(f))
408 self._addpath(f, 'm', s.st_mode,
409 self._addpath(f, 'm', s.st_mode,
409 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
410 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
410 if f in self._copymap:
411 if f in self._copymap:
411 del self._copymap[f]
412 del self._copymap[f]
412
413
413 def drop(self, f):
414 def drop(self, f):
414 '''Drop a file from the dirstate'''
415 '''Drop a file from the dirstate'''
415 if f in self._map:
416 if f in self._map:
416 self._dirty = True
417 self._dirty = True
417 self._droppath(f)
418 self._droppath(f)
418 del self._map[f]
419 del self._map[f]
419
420
420 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
421 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
421 normed = util.normcase(path)
422 normed = util.normcase(path)
422 folded = self._foldmap.get(normed, None)
423 folded = self._foldmap.get(normed, None)
423 if folded is None:
424 if folded is None:
424 if isknown:
425 if isknown:
425 folded = path
426 folded = path
426 else:
427 else:
427 if exists is None:
428 if exists is None:
428 exists = os.path.lexists(os.path.join(self._root, path))
429 exists = os.path.lexists(os.path.join(self._root, path))
429 if not exists:
430 if not exists:
430 # Maybe a path component exists
431 # Maybe a path component exists
431 if not ignoremissing and '/' in path:
432 if not ignoremissing and '/' in path:
432 d, f = path.rsplit('/', 1)
433 d, f = path.rsplit('/', 1)
433 d = self._normalize(d, isknown, ignoremissing, None)
434 d = self._normalize(d, isknown, ignoremissing, None)
434 folded = d + "/" + f
435 folded = d + "/" + f
435 else:
436 else:
436 # No path components, preserve original case
437 # No path components, preserve original case
437 folded = path
438 folded = path
438 else:
439 else:
439 # recursively normalize leading directory components
440 # recursively normalize leading directory components
440 # against dirstate
441 # against dirstate
441 if '/' in normed:
442 if '/' in normed:
442 d, f = normed.rsplit('/', 1)
443 d, f = normed.rsplit('/', 1)
443 d = self._normalize(d, isknown, ignoremissing, True)
444 d = self._normalize(d, isknown, ignoremissing, True)
444 r = self._root + "/" + d
445 r = self._root + "/" + d
445 folded = d + "/" + util.fspath(f, r)
446 folded = d + "/" + util.fspath(f, r)
446 else:
447 else:
447 folded = util.fspath(normed, self._root)
448 folded = util.fspath(normed, self._root)
448 self._foldmap[normed] = folded
449 self._foldmap[normed] = folded
449
450
450 return folded
451 return folded
451
452
452 def normalize(self, path, isknown=False, ignoremissing=False):
453 def normalize(self, path, isknown=False, ignoremissing=False):
453 '''
454 '''
454 normalize the case of a pathname when on a casefolding filesystem
455 normalize the case of a pathname when on a casefolding filesystem
455
456
456 isknown specifies whether the filename came from walking the
457 isknown specifies whether the filename came from walking the
457 disk, to avoid extra filesystem access.
458 disk, to avoid extra filesystem access.
458
459
459 If ignoremissing is True, missing path are returned
460 If ignoremissing is True, missing path are returned
460 unchanged. Otherwise, we try harder to normalize possibly
461 unchanged. Otherwise, we try harder to normalize possibly
461 existing path components.
462 existing path components.
462
463
463 The normalized case is determined based on the following precedence:
464 The normalized case is determined based on the following precedence:
464
465
465 - version of name already stored in the dirstate
466 - version of name already stored in the dirstate
466 - version of name stored on disk
467 - version of name stored on disk
467 - version provided via command arguments
468 - version provided via command arguments
468 '''
469 '''
469
470
470 if self._checkcase:
471 if self._checkcase:
471 return self._normalize(path, isknown, ignoremissing)
472 return self._normalize(path, isknown, ignoremissing)
472 return path
473 return path
473
474
474 def clear(self):
475 def clear(self):
475 self._map = {}
476 self._map = {}
476 if "_dirs" in self.__dict__:
477 if "_dirs" in self.__dict__:
477 delattr(self, "_dirs")
478 delattr(self, "_dirs")
478 self._copymap = {}
479 self._copymap = {}
479 self._pl = [nullid, nullid]
480 self._pl = [nullid, nullid]
480 self._lastnormaltime = 0
481 self._lastnormaltime = 0
481 self._dirty = True
482 self._dirty = True
482
483
483 def rebuild(self, parent, allfiles, changedfiles=None):
484 def rebuild(self, parent, allfiles, changedfiles=None):
484 changedfiles = changedfiles or allfiles
485 changedfiles = changedfiles or allfiles
485 oldmap = self._map
486 oldmap = self._map
486 self.clear()
487 self.clear()
487 for f in allfiles:
488 for f in allfiles:
488 if f not in changedfiles:
489 if f not in changedfiles:
489 self._map[f] = oldmap[f]
490 self._map[f] = oldmap[f]
490 else:
491 else:
491 if 'x' in allfiles.flags(f):
492 if 'x' in allfiles.flags(f):
492 self._map[f] = ('n', 0777, -1, 0)
493 self._map[f] = ('n', 0777, -1, 0)
493 else:
494 else:
494 self._map[f] = ('n', 0666, -1, 0)
495 self._map[f] = ('n', 0666, -1, 0)
495 self._pl = (parent, nullid)
496 self._pl = (parent, nullid)
496 self._dirty = True
497 self._dirty = True
497
498
498 def write(self):
499 def write(self):
499 if not self._dirty:
500 if not self._dirty:
500 return
501 return
501 st = self._opener("dirstate", "w", atomictemp=True)
502 st = self._opener("dirstate", "w", atomictemp=True)
502
503
503 def finish(s):
504 def finish(s):
504 st.write(s)
505 st.write(s)
505 st.close()
506 st.close()
506 self._lastnormaltime = 0
507 self._lastnormaltime = 0
507 self._dirty = self._dirtypl = False
508 self._dirty = self._dirtypl = False
508
509
509 # use the modification time of the newly created temporary file as the
510 # use the modification time of the newly created temporary file as the
510 # filesystem's notion of 'now'
511 # filesystem's notion of 'now'
511 now = util.fstat(st).st_mtime
512 now = util.fstat(st).st_mtime
512 finish(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
513 finish(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
513
514
514 def _dirignore(self, f):
515 def _dirignore(self, f):
515 if f == '.':
516 if f == '.':
516 return False
517 return False
517 if self._ignore(f):
518 if self._ignore(f):
518 return True
519 return True
519 for p in scmutil.finddirs(f):
520 for p in scmutil.finddirs(f):
520 if self._ignore(p):
521 if self._ignore(p):
521 return True
522 return True
522 return False
523 return False
523
524
524 def walk(self, match, subrepos, unknown, ignored):
525 def walk(self, match, subrepos, unknown, ignored):
525 '''
526 '''
526 Walk recursively through the directory tree, finding all files
527 Walk recursively through the directory tree, finding all files
527 matched by match.
528 matched by match.
528
529
529 Return a dict mapping filename to stat-like object (either
530 Return a dict mapping filename to stat-like object (either
530 mercurial.osutil.stat instance or return value of os.stat()).
531 mercurial.osutil.stat instance or return value of os.stat()).
531 '''
532 '''
532
533
533 def fwarn(f, msg):
534 def fwarn(f, msg):
534 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
535 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
535 return False
536 return False
536
537
537 def badtype(mode):
538 def badtype(mode):
538 kind = _('unknown')
539 kind = _('unknown')
539 if stat.S_ISCHR(mode):
540 if stat.S_ISCHR(mode):
540 kind = _('character device')
541 kind = _('character device')
541 elif stat.S_ISBLK(mode):
542 elif stat.S_ISBLK(mode):
542 kind = _('block device')
543 kind = _('block device')
543 elif stat.S_ISFIFO(mode):
544 elif stat.S_ISFIFO(mode):
544 kind = _('fifo')
545 kind = _('fifo')
545 elif stat.S_ISSOCK(mode):
546 elif stat.S_ISSOCK(mode):
546 kind = _('socket')
547 kind = _('socket')
547 elif stat.S_ISDIR(mode):
548 elif stat.S_ISDIR(mode):
548 kind = _('directory')
549 kind = _('directory')
549 return _('unsupported file type (type is %s)') % kind
550 return _('unsupported file type (type is %s)') % kind
550
551
551 ignore = self._ignore
552 ignore = self._ignore
552 dirignore = self._dirignore
553 dirignore = self._dirignore
553 if ignored:
554 if ignored:
554 ignore = util.never
555 ignore = util.never
555 dirignore = util.never
556 dirignore = util.never
556 elif not unknown:
557 elif not unknown:
557 # if unknown and ignored are False, skip step 2
558 # if unknown and ignored are False, skip step 2
558 ignore = util.always
559 ignore = util.always
559 dirignore = util.always
560 dirignore = util.always
560
561
561 matchfn = match.matchfn
562 matchfn = match.matchfn
562 matchalways = match.always()
563 matchalways = match.always()
563 badfn = match.bad
564 badfn = match.bad
564 dmap = self._map
565 dmap = self._map
565 normpath = util.normpath
566 normpath = util.normpath
566 listdir = osutil.listdir
567 listdir = osutil.listdir
567 lstat = os.lstat
568 lstat = os.lstat
568 getkind = stat.S_IFMT
569 getkind = stat.S_IFMT
569 dirkind = stat.S_IFDIR
570 dirkind = stat.S_IFDIR
570 regkind = stat.S_IFREG
571 regkind = stat.S_IFREG
571 lnkkind = stat.S_IFLNK
572 lnkkind = stat.S_IFLNK
572 join = self._join
573 join = self._join
573 work = []
574 work = []
574 wadd = work.append
575 wadd = work.append
575
576
576 exact = skipstep3 = False
577 exact = skipstep3 = False
577 if matchfn == match.exact: # match.exact
578 if matchfn == match.exact: # match.exact
578 exact = True
579 exact = True
579 dirignore = util.always # skip step 2
580 dirignore = util.always # skip step 2
580 elif match.files() and not match.anypats(): # match.match, no patterns
581 elif match.files() and not match.anypats(): # match.match, no patterns
581 skipstep3 = True
582 skipstep3 = True
582
583
583 if not exact and self._checkcase:
584 if not exact and self._checkcase:
584 normalize = self._normalize
585 normalize = self._normalize
585 skipstep3 = False
586 skipstep3 = False
586 else:
587 else:
587 normalize = None
588 normalize = None
588
589
589 files = sorted(match.files())
590 files = sorted(match.files())
590 subrepos.sort()
591 subrepos.sort()
591 i, j = 0, 0
592 i, j = 0, 0
592 while i < len(files) and j < len(subrepos):
593 while i < len(files) and j < len(subrepos):
593 subpath = subrepos[j] + "/"
594 subpath = subrepos[j] + "/"
594 if files[i] < subpath:
595 if files[i] < subpath:
595 i += 1
596 i += 1
596 continue
597 continue
597 while i < len(files) and files[i].startswith(subpath):
598 while i < len(files) and files[i].startswith(subpath):
598 del files[i]
599 del files[i]
599 j += 1
600 j += 1
600
601
601 if not files or '.' in files:
602 if not files or '.' in files:
602 files = ['']
603 files = ['']
603 results = dict.fromkeys(subrepos)
604 results = dict.fromkeys(subrepos)
604 results['.hg'] = None
605 results['.hg'] = None
605
606
606 # step 1: find all explicit files
607 # step 1: find all explicit files
607 for ff in files:
608 for ff in files:
608 if normalize:
609 if normalize:
609 nf = normalize(normpath(ff), False, True)
610 nf = normalize(normpath(ff), False, True)
610 else:
611 else:
611 nf = normpath(ff)
612 nf = normpath(ff)
612 if nf in results:
613 if nf in results:
613 continue
614 continue
614
615
615 try:
616 try:
616 st = lstat(join(nf))
617 st = lstat(join(nf))
617 kind = getkind(st.st_mode)
618 kind = getkind(st.st_mode)
618 if kind == dirkind:
619 if kind == dirkind:
619 skipstep3 = False
620 skipstep3 = False
620 if nf in dmap:
621 if nf in dmap:
621 #file deleted on disk but still in dirstate
622 #file deleted on disk but still in dirstate
622 results[nf] = None
623 results[nf] = None
623 match.dir(nf)
624 match.dir(nf)
624 if not dirignore(nf):
625 if not dirignore(nf):
625 wadd(nf)
626 wadd(nf)
626 elif kind == regkind or kind == lnkkind:
627 elif kind == regkind or kind == lnkkind:
627 results[nf] = st
628 results[nf] = st
628 else:
629 else:
629 badfn(ff, badtype(kind))
630 badfn(ff, badtype(kind))
630 if nf in dmap:
631 if nf in dmap:
631 results[nf] = None
632 results[nf] = None
632 except OSError, inst:
633 except OSError, inst:
633 if nf in dmap: # does it exactly match a file?
634 if nf in dmap: # does it exactly match a file?
634 results[nf] = None
635 results[nf] = None
635 else: # does it match a directory?
636 else: # does it match a directory?
636 prefix = nf + "/"
637 prefix = nf + "/"
637 for fn in dmap:
638 for fn in dmap:
638 if fn.startswith(prefix):
639 if fn.startswith(prefix):
639 match.dir(nf)
640 match.dir(nf)
640 skipstep3 = False
641 skipstep3 = False
641 break
642 break
642 else:
643 else:
643 badfn(ff, inst.strerror)
644 badfn(ff, inst.strerror)
644
645
645 # step 2: visit subdirectories
646 # step 2: visit subdirectories
646 while work:
647 while work:
647 nd = work.pop()
648 nd = work.pop()
648 skip = None
649 skip = None
649 if nd == '.':
650 if nd == '.':
650 nd = ''
651 nd = ''
651 else:
652 else:
652 skip = '.hg'
653 skip = '.hg'
653 try:
654 try:
654 entries = listdir(join(nd), stat=True, skip=skip)
655 entries = listdir(join(nd), stat=True, skip=skip)
655 except OSError, inst:
656 except OSError, inst:
656 if inst.errno in (errno.EACCES, errno.ENOENT):
657 if inst.errno in (errno.EACCES, errno.ENOENT):
657 fwarn(nd, inst.strerror)
658 fwarn(nd, inst.strerror)
658 continue
659 continue
659 raise
660 raise
660 for f, kind, st in entries:
661 for f, kind, st in entries:
661 if normalize:
662 if normalize:
662 nf = normalize(nd and (nd + "/" + f) or f, True, True)
663 nf = normalize(nd and (nd + "/" + f) or f, True, True)
663 else:
664 else:
664 nf = nd and (nd + "/" + f) or f
665 nf = nd and (nd + "/" + f) or f
665 if nf not in results:
666 if nf not in results:
666 if kind == dirkind:
667 if kind == dirkind:
667 if not ignore(nf):
668 if not ignore(nf):
668 match.dir(nf)
669 match.dir(nf)
669 wadd(nf)
670 wadd(nf)
670 if nf in dmap and (matchalways or matchfn(nf)):
671 if nf in dmap and (matchalways or matchfn(nf)):
671 results[nf] = None
672 results[nf] = None
672 elif kind == regkind or kind == lnkkind:
673 elif kind == regkind or kind == lnkkind:
673 if nf in dmap:
674 if nf in dmap:
674 if matchalways or matchfn(nf):
675 if matchalways or matchfn(nf):
675 results[nf] = st
676 results[nf] = st
676 elif (matchalways or matchfn(nf)) and not ignore(nf):
677 elif (matchalways or matchfn(nf)) and not ignore(nf):
677 results[nf] = st
678 results[nf] = st
678 elif nf in dmap and (matchalways or matchfn(nf)):
679 elif nf in dmap and (matchalways or matchfn(nf)):
679 results[nf] = None
680 results[nf] = None
680
681
681 for s in subrepos:
682 for s in subrepos:
682 del results[s]
683 del results[s]
683 del results['.hg']
684 del results['.hg']
684
685
685 # step 3: report unseen items in the dmap hash
686 # step 3: report unseen items in the dmap hash
686 if not skipstep3 and not exact:
687 if not skipstep3 and not exact:
687 if not results and matchalways:
688 if not results and matchalways:
688 visit = dmap.keys()
689 visit = dmap.keys()
689 else:
690 else:
690 visit = [f for f in dmap if f not in results and matchfn(f)]
691 visit = [f for f in dmap if f not in results and matchfn(f)]
691 visit.sort()
692 visit.sort()
692
693
693 if unknown:
694 if unknown:
694 # unknown == True means we walked the full directory tree above.
695 # unknown == True means we walked the full directory tree above.
695 # So if a file is not seen it was either a) not matching matchfn
696 # So if a file is not seen it was either a) not matching matchfn
696 # b) ignored, c) missing, or d) under a symlink directory.
697 # b) ignored, c) missing, or d) under a symlink directory.
697 audit_path = scmutil.pathauditor(self._root)
698 audit_path = scmutil.pathauditor(self._root)
698
699
699 for nf in iter(visit):
700 for nf in iter(visit):
700 # Report ignored items in the dmap as long as they are not
701 # Report ignored items in the dmap as long as they are not
701 # under a symlink directory.
702 # under a symlink directory.
702 if ignore(nf) and audit_path.check(nf):
703 if ignore(nf) and audit_path.check(nf):
703 try:
704 try:
704 results[nf] = lstat(join(nf))
705 results[nf] = lstat(join(nf))
705 except OSError:
706 except OSError:
706 # file doesn't exist
707 # file doesn't exist
707 results[nf] = None
708 results[nf] = None
708 else:
709 else:
709 # It's either missing or under a symlink directory
710 # It's either missing or under a symlink directory
710 results[nf] = None
711 results[nf] = None
711 else:
712 else:
712 # We may not have walked the full directory tree above,
713 # We may not have walked the full directory tree above,
713 # so stat everything we missed.
714 # so stat everything we missed.
714 nf = iter(visit).next
715 nf = iter(visit).next
715 for st in util.statfiles([join(i) for i in visit]):
716 for st in util.statfiles([join(i) for i in visit]):
716 results[nf()] = st
717 results[nf()] = st
717 return results
718 return results
718
719
719 def status(self, match, subrepos, ignored, clean, unknown):
720 def status(self, match, subrepos, ignored, clean, unknown):
720 '''Determine the status of the working copy relative to the
721 '''Determine the status of the working copy relative to the
721 dirstate and return a tuple of lists (unsure, modified, added,
722 dirstate and return a tuple of lists (unsure, modified, added,
722 removed, deleted, unknown, ignored, clean), where:
723 removed, deleted, unknown, ignored, clean), where:
723
724
724 unsure:
725 unsure:
725 files that might have been modified since the dirstate was
726 files that might have been modified since the dirstate was
726 written, but need to be read to be sure (size is the same
727 written, but need to be read to be sure (size is the same
727 but mtime differs)
728 but mtime differs)
728 modified:
729 modified:
729 files that have definitely been modified since the dirstate
730 files that have definitely been modified since the dirstate
730 was written (different size or mode)
731 was written (different size or mode)
731 added:
732 added:
732 files that have been explicitly added with hg add
733 files that have been explicitly added with hg add
733 removed:
734 removed:
734 files that have been explicitly removed with hg remove
735 files that have been explicitly removed with hg remove
735 deleted:
736 deleted:
736 files that have been deleted through other means ("missing")
737 files that have been deleted through other means ("missing")
737 unknown:
738 unknown:
738 files not in the dirstate that are not ignored
739 files not in the dirstate that are not ignored
739 ignored:
740 ignored:
740 files not in the dirstate that are ignored
741 files not in the dirstate that are ignored
741 (by _dirignore())
742 (by _dirignore())
742 clean:
743 clean:
743 files that have definitely not been modified since the
744 files that have definitely not been modified since the
744 dirstate was written
745 dirstate was written
745 '''
746 '''
746 listignored, listclean, listunknown = ignored, clean, unknown
747 listignored, listclean, listunknown = ignored, clean, unknown
747 lookup, modified, added, unknown, ignored = [], [], [], [], []
748 lookup, modified, added, unknown, ignored = [], [], [], [], []
748 removed, deleted, clean = [], [], []
749 removed, deleted, clean = [], [], []
749
750
750 dmap = self._map
751 dmap = self._map
751 ladd = lookup.append # aka "unsure"
752 ladd = lookup.append # aka "unsure"
752 madd = modified.append
753 madd = modified.append
753 aadd = added.append
754 aadd = added.append
754 uadd = unknown.append
755 uadd = unknown.append
755 iadd = ignored.append
756 iadd = ignored.append
756 radd = removed.append
757 radd = removed.append
757 dadd = deleted.append
758 dadd = deleted.append
758 cadd = clean.append
759 cadd = clean.append
759 mexact = match.exact
760 mexact = match.exact
760 dirignore = self._dirignore
761 dirignore = self._dirignore
761 checkexec = self._checkexec
762 checkexec = self._checkexec
762 checklink = self._checklink
763 checklink = self._checklink
763 copymap = self._copymap
764 copymap = self._copymap
764 lastnormaltime = self._lastnormaltime
765 lastnormaltime = self._lastnormaltime
765
766
766 lnkkind = stat.S_IFLNK
767 lnkkind = stat.S_IFLNK
767
768
768 for fn, st in self.walk(match, subrepos, listunknown,
769 for fn, st in self.walk(match, subrepos, listunknown,
769 listignored).iteritems():
770 listignored).iteritems():
770 if fn not in dmap:
771 if fn not in dmap:
771 if (listignored or mexact(fn)) and dirignore(fn):
772 if (listignored or mexact(fn)) and dirignore(fn):
772 if listignored:
773 if listignored:
773 iadd(fn)
774 iadd(fn)
774 elif listunknown:
775 elif listunknown:
775 uadd(fn)
776 uadd(fn)
776 continue
777 continue
777
778
778 state, mode, size, time = dmap[fn]
779 state, mode, size, time = dmap[fn]
779
780
780 if not st and state in "nma":
781 if not st and state in "nma":
781 dadd(fn)
782 dadd(fn)
782 elif state == 'n':
783 elif state == 'n':
783 # The "mode & lnkkind != lnkkind or self._checklink"
784 # The "mode & lnkkind != lnkkind or self._checklink"
784 # lines are an expansion of "islink => checklink"
785 # lines are an expansion of "islink => checklink"
785 # where islink means "is this a link?" and checklink
786 # where islink means "is this a link?" and checklink
786 # means "can we check links?".
787 # means "can we check links?".
787 mtime = int(st.st_mtime)
788 mtime = int(st.st_mtime)
788 if (size >= 0 and
789 if (size >= 0 and
789 ((size != st.st_size and size != st.st_size & _rangemask)
790 ((size != st.st_size and size != st.st_size & _rangemask)
790 or ((mode ^ st.st_mode) & 0100 and checkexec))
791 or ((mode ^ st.st_mode) & 0100 and checkexec))
791 and (mode & lnkkind != lnkkind or checklink)
792 and (mode & lnkkind != lnkkind or checklink)
792 or size == -2 # other parent
793 or size == -2 # other parent
793 or fn in copymap):
794 or fn in copymap):
794 madd(fn)
795 madd(fn)
795 elif ((time != mtime and time != mtime & _rangemask)
796 elif ((time != mtime and time != mtime & _rangemask)
796 and (mode & lnkkind != lnkkind or checklink)):
797 and (mode & lnkkind != lnkkind or checklink)):
797 ladd(fn)
798 ladd(fn)
798 elif mtime == lastnormaltime:
799 elif mtime == lastnormaltime:
799 # fn may have been changed in the same timeslot without
800 # fn may have been changed in the same timeslot without
800 # changing its size. This can happen if we quickly do
801 # changing its size. This can happen if we quickly do
801 # multiple commits in a single transaction.
802 # multiple commits in a single transaction.
802 # Force lookup, so we don't miss such a racy file change.
803 # Force lookup, so we don't miss such a racy file change.
803 ladd(fn)
804 ladd(fn)
804 elif listclean:
805 elif listclean:
805 cadd(fn)
806 cadd(fn)
806 elif state == 'm':
807 elif state == 'm':
807 madd(fn)
808 madd(fn)
808 elif state == 'a':
809 elif state == 'a':
809 aadd(fn)
810 aadd(fn)
810 elif state == 'r':
811 elif state == 'r':
811 radd(fn)
812 radd(fn)
812
813
813 return (lookup, modified, added, removed, deleted, unknown, ignored,
814 return (lookup, modified, added, removed, deleted, unknown, ignored,
814 clean)
815 clean)
General Comments 0
You need to be logged in to leave comments. Login now