##// END OF EJS Templates
dirstate: fix in memory dirstate entries for 1-second race...
Benoit Boissinot -
r10836:e5aaa454 stable
parent child Browse files
Show More
@@ -1,657 +1,657 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 util, ignore, osutil, parsers
10 import util, ignore, osutil, parsers
11 import struct, os, stat, errno
11 import struct, os, stat, errno
12 import cStringIO
12 import cStringIO
13
13
14 _unknown = ('?', 0, 0, 0)
14 _unknown = ('?', 0, 0, 0)
15 _format = ">cllll"
15 _format = ">cllll"
16 propertycache = util.propertycache
16 propertycache = util.propertycache
17
17
18 def _finddirs(path):
18 def _finddirs(path):
19 pos = path.rfind('/')
19 pos = path.rfind('/')
20 while pos != -1:
20 while pos != -1:
21 yield path[:pos]
21 yield path[:pos]
22 pos = path.rfind('/', 0, pos)
22 pos = path.rfind('/', 0, pos)
23
23
24 def _incdirs(dirs, path):
24 def _incdirs(dirs, path):
25 for base in _finddirs(path):
25 for base in _finddirs(path):
26 if base in dirs:
26 if base in dirs:
27 dirs[base] += 1
27 dirs[base] += 1
28 return
28 return
29 dirs[base] = 1
29 dirs[base] = 1
30
30
31 def _decdirs(dirs, path):
31 def _decdirs(dirs, path):
32 for base in _finddirs(path):
32 for base in _finddirs(path):
33 if dirs[base] > 1:
33 if dirs[base] > 1:
34 dirs[base] -= 1
34 dirs[base] -= 1
35 return
35 return
36 del dirs[base]
36 del dirs[base]
37
37
38 class dirstate(object):
38 class dirstate(object):
39
39
40 def __init__(self, opener, ui, root):
40 def __init__(self, opener, ui, root):
41 '''Create a new dirstate object.
41 '''Create a new dirstate object.
42
42
43 opener is an open()-like callable that can be used to open the
43 opener is an open()-like callable that can be used to open the
44 dirstate file; root is the root of the directory tracked by
44 dirstate file; root is the root of the directory tracked by
45 the dirstate.
45 the dirstate.
46 '''
46 '''
47 self._opener = opener
47 self._opener = opener
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._ui = ui
52 self._ui = ui
53
53
54 @propertycache
54 @propertycache
55 def _map(self):
55 def _map(self):
56 '''Return the dirstate contents as a map from filename to
56 '''Return the dirstate contents as a map from filename to
57 (state, mode, size, time).'''
57 (state, mode, size, time).'''
58 self._read()
58 self._read()
59 return self._map
59 return self._map
60
60
61 @propertycache
61 @propertycache
62 def _copymap(self):
62 def _copymap(self):
63 self._read()
63 self._read()
64 return self._copymap
64 return self._copymap
65
65
66 @propertycache
66 @propertycache
67 def _foldmap(self):
67 def _foldmap(self):
68 f = {}
68 f = {}
69 for name in self._map:
69 for name in self._map:
70 f[os.path.normcase(name)] = name
70 f[os.path.normcase(name)] = name
71 return f
71 return f
72
72
73 @propertycache
73 @propertycache
74 def _branch(self):
74 def _branch(self):
75 try:
75 try:
76 return self._opener("branch").read().strip() or "default"
76 return self._opener("branch").read().strip() or "default"
77 except IOError:
77 except IOError:
78 return "default"
78 return "default"
79
79
80 @propertycache
80 @propertycache
81 def _pl(self):
81 def _pl(self):
82 try:
82 try:
83 st = self._opener("dirstate").read(40)
83 st = self._opener("dirstate").read(40)
84 l = len(st)
84 l = len(st)
85 if l == 40:
85 if l == 40:
86 return st[:20], st[20:40]
86 return st[:20], st[20:40]
87 elif l > 0 and l < 40:
87 elif l > 0 and l < 40:
88 raise util.Abort(_('working directory state appears damaged!'))
88 raise util.Abort(_('working directory state appears damaged!'))
89 except IOError, err:
89 except IOError, err:
90 if err.errno != errno.ENOENT:
90 if err.errno != errno.ENOENT:
91 raise
91 raise
92 return [nullid, nullid]
92 return [nullid, nullid]
93
93
94 @propertycache
94 @propertycache
95 def _dirs(self):
95 def _dirs(self):
96 dirs = {}
96 dirs = {}
97 for f, s in self._map.iteritems():
97 for f, s in self._map.iteritems():
98 if s[0] != 'r':
98 if s[0] != 'r':
99 _incdirs(dirs, f)
99 _incdirs(dirs, f)
100 return dirs
100 return dirs
101
101
102 @propertycache
102 @propertycache
103 def _ignore(self):
103 def _ignore(self):
104 files = [self._join('.hgignore')]
104 files = [self._join('.hgignore')]
105 for name, path in self._ui.configitems("ui"):
105 for name, path in self._ui.configitems("ui"):
106 if name == 'ignore' or name.startswith('ignore.'):
106 if name == 'ignore' or name.startswith('ignore.'):
107 files.append(util.expandpath(path))
107 files.append(util.expandpath(path))
108 return ignore.ignore(self._root, files, self._ui.warn)
108 return ignore.ignore(self._root, files, self._ui.warn)
109
109
110 @propertycache
110 @propertycache
111 def _slash(self):
111 def _slash(self):
112 return self._ui.configbool('ui', 'slash') and os.sep != '/'
112 return self._ui.configbool('ui', 'slash') and os.sep != '/'
113
113
114 @propertycache
114 @propertycache
115 def _checklink(self):
115 def _checklink(self):
116 return util.checklink(self._root)
116 return util.checklink(self._root)
117
117
118 @propertycache
118 @propertycache
119 def _checkexec(self):
119 def _checkexec(self):
120 return util.checkexec(self._root)
120 return util.checkexec(self._root)
121
121
122 @propertycache
122 @propertycache
123 def _checkcase(self):
123 def _checkcase(self):
124 return not util.checkcase(self._join('.hg'))
124 return not util.checkcase(self._join('.hg'))
125
125
126 def _join(self, f):
126 def _join(self, f):
127 # much faster than os.path.join()
127 # much faster than os.path.join()
128 # it's safe because f is always a relative path
128 # it's safe because f is always a relative path
129 return self._rootdir + f
129 return self._rootdir + f
130
130
131 def flagfunc(self, fallback):
131 def flagfunc(self, fallback):
132 if self._checklink:
132 if self._checklink:
133 if self._checkexec:
133 if self._checkexec:
134 def f(x):
134 def f(x):
135 p = self._join(x)
135 p = self._join(x)
136 if os.path.islink(p):
136 if os.path.islink(p):
137 return 'l'
137 return 'l'
138 if util.is_exec(p):
138 if util.is_exec(p):
139 return 'x'
139 return 'x'
140 return ''
140 return ''
141 return f
141 return f
142 def f(x):
142 def f(x):
143 if os.path.islink(self._join(x)):
143 if os.path.islink(self._join(x)):
144 return 'l'
144 return 'l'
145 if 'x' in fallback(x):
145 if 'x' in fallback(x):
146 return 'x'
146 return 'x'
147 return ''
147 return ''
148 return f
148 return f
149 if self._checkexec:
149 if self._checkexec:
150 def f(x):
150 def f(x):
151 if 'l' in fallback(x):
151 if 'l' in fallback(x):
152 return 'l'
152 return 'l'
153 if util.is_exec(self._join(x)):
153 if util.is_exec(self._join(x)):
154 return 'x'
154 return 'x'
155 return ''
155 return ''
156 return f
156 return f
157 return fallback
157 return fallback
158
158
159 def getcwd(self):
159 def getcwd(self):
160 cwd = os.getcwd()
160 cwd = os.getcwd()
161 if cwd == self._root:
161 if cwd == self._root:
162 return ''
162 return ''
163 # self._root ends with a path separator if self._root is '/' or 'C:\'
163 # self._root ends with a path separator if self._root is '/' or 'C:\'
164 rootsep = self._root
164 rootsep = self._root
165 if not util.endswithsep(rootsep):
165 if not util.endswithsep(rootsep):
166 rootsep += os.sep
166 rootsep += os.sep
167 if cwd.startswith(rootsep):
167 if cwd.startswith(rootsep):
168 return cwd[len(rootsep):]
168 return cwd[len(rootsep):]
169 else:
169 else:
170 # we're outside the repo. return an absolute path.
170 # we're outside the repo. return an absolute path.
171 return cwd
171 return cwd
172
172
173 def pathto(self, f, cwd=None):
173 def pathto(self, f, cwd=None):
174 if cwd is None:
174 if cwd is None:
175 cwd = self.getcwd()
175 cwd = self.getcwd()
176 path = util.pathto(self._root, cwd, f)
176 path = util.pathto(self._root, cwd, f)
177 if self._slash:
177 if self._slash:
178 return util.normpath(path)
178 return util.normpath(path)
179 return path
179 return path
180
180
181 def __getitem__(self, key):
181 def __getitem__(self, key):
182 '''Return the current state of key (a filename) in the dirstate.
182 '''Return the current state of key (a filename) in the dirstate.
183
183
184 States are:
184 States are:
185 n normal
185 n normal
186 m needs merging
186 m needs merging
187 r marked for removal
187 r marked for removal
188 a marked for addition
188 a marked for addition
189 ? not tracked
189 ? not tracked
190 '''
190 '''
191 return self._map.get(key, ("?",))[0]
191 return self._map.get(key, ("?",))[0]
192
192
193 def __contains__(self, key):
193 def __contains__(self, key):
194 return key in self._map
194 return key in self._map
195
195
196 def __iter__(self):
196 def __iter__(self):
197 for x in sorted(self._map):
197 for x in sorted(self._map):
198 yield x
198 yield x
199
199
200 def parents(self):
200 def parents(self):
201 return self._pl
201 return self._pl
202
202
203 def branch(self):
203 def branch(self):
204 return self._branch
204 return self._branch
205
205
206 def setparents(self, p1, p2=nullid):
206 def setparents(self, p1, p2=nullid):
207 self._dirty = self._dirtypl = True
207 self._dirty = self._dirtypl = True
208 self._pl = p1, p2
208 self._pl = p1, p2
209
209
210 def setbranch(self, branch):
210 def setbranch(self, branch):
211 if branch in ['tip', '.', 'null']:
211 if branch in ['tip', '.', 'null']:
212 raise util.Abort(_('the name \'%s\' is reserved') % branch)
212 raise util.Abort(_('the name \'%s\' is reserved') % branch)
213 self._branch = branch
213 self._branch = branch
214 self._opener("branch", "w").write(branch + '\n')
214 self._opener("branch", "w").write(branch + '\n')
215
215
216 def _read(self):
216 def _read(self):
217 self._map = {}
217 self._map = {}
218 self._copymap = {}
218 self._copymap = {}
219 try:
219 try:
220 st = self._opener("dirstate").read()
220 st = self._opener("dirstate").read()
221 except IOError, err:
221 except IOError, err:
222 if err.errno != errno.ENOENT:
222 if err.errno != errno.ENOENT:
223 raise
223 raise
224 return
224 return
225 if not st:
225 if not st:
226 return
226 return
227
227
228 p = parsers.parse_dirstate(self._map, self._copymap, st)
228 p = parsers.parse_dirstate(self._map, self._copymap, st)
229 if not self._dirtypl:
229 if not self._dirtypl:
230 self._pl = p
230 self._pl = p
231
231
232 def invalidate(self):
232 def invalidate(self):
233 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
233 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
234 if a in self.__dict__:
234 if a in self.__dict__:
235 delattr(self, a)
235 delattr(self, a)
236 self._dirty = False
236 self._dirty = False
237
237
238 def copy(self, source, dest):
238 def copy(self, source, dest):
239 """Mark dest as a copy of source. Unmark dest if source is None."""
239 """Mark dest as a copy of source. Unmark dest if source is None."""
240 if source == dest:
240 if source == dest:
241 return
241 return
242 self._dirty = True
242 self._dirty = True
243 if source is not None:
243 if source is not None:
244 self._copymap[dest] = source
244 self._copymap[dest] = source
245 elif dest in self._copymap:
245 elif dest in self._copymap:
246 del self._copymap[dest]
246 del self._copymap[dest]
247
247
248 def copied(self, file):
248 def copied(self, file):
249 return self._copymap.get(file, None)
249 return self._copymap.get(file, None)
250
250
251 def copies(self):
251 def copies(self):
252 return self._copymap
252 return self._copymap
253
253
254 def _droppath(self, f):
254 def _droppath(self, f):
255 if self[f] not in "?r" and "_dirs" in self.__dict__:
255 if self[f] not in "?r" and "_dirs" in self.__dict__:
256 _decdirs(self._dirs, f)
256 _decdirs(self._dirs, f)
257
257
258 def _addpath(self, f, check=False):
258 def _addpath(self, f, check=False):
259 oldstate = self[f]
259 oldstate = self[f]
260 if check or oldstate == "r":
260 if check or oldstate == "r":
261 if '\r' in f or '\n' in f:
261 if '\r' in f or '\n' in f:
262 raise util.Abort(
262 raise util.Abort(
263 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
263 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
264 if f in self._dirs:
264 if f in self._dirs:
265 raise util.Abort(_('directory %r already in dirstate') % f)
265 raise util.Abort(_('directory %r already in dirstate') % f)
266 # shadows
266 # shadows
267 for d in _finddirs(f):
267 for d in _finddirs(f):
268 if d in self._dirs:
268 if d in self._dirs:
269 break
269 break
270 if d in self._map and self[d] != 'r':
270 if d in self._map and self[d] != 'r':
271 raise util.Abort(
271 raise util.Abort(
272 _('file %r in dirstate clashes with %r') % (d, f))
272 _('file %r in dirstate clashes with %r') % (d, f))
273 if oldstate in "?r" and "_dirs" in self.__dict__:
273 if oldstate in "?r" and "_dirs" in self.__dict__:
274 _incdirs(self._dirs, f)
274 _incdirs(self._dirs, f)
275
275
276 def normal(self, f):
276 def normal(self, f):
277 '''Mark a file normal and clean.'''
277 '''Mark a file normal and clean.'''
278 self._dirty = True
278 self._dirty = True
279 self._addpath(f)
279 self._addpath(f)
280 s = os.lstat(self._join(f))
280 s = os.lstat(self._join(f))
281 self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime))
281 self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime))
282 if f in self._copymap:
282 if f in self._copymap:
283 del self._copymap[f]
283 del self._copymap[f]
284
284
285 def normallookup(self, f):
285 def normallookup(self, f):
286 '''Mark a file normal, but possibly dirty.'''
286 '''Mark a file normal, but possibly dirty.'''
287 if self._pl[1] != nullid and f in self._map:
287 if self._pl[1] != nullid and f in self._map:
288 # if there is a merge going on and the file was either
288 # if there is a merge going on and the file was either
289 # in state 'm' or dirty before being removed, restore that state.
289 # in state 'm' or dirty before being removed, restore that state.
290 entry = self._map[f]
290 entry = self._map[f]
291 if entry[0] == 'r' and entry[2] in (-1, -2):
291 if entry[0] == 'r' and entry[2] in (-1, -2):
292 source = self._copymap.get(f)
292 source = self._copymap.get(f)
293 if entry[2] == -1:
293 if entry[2] == -1:
294 self.merge(f)
294 self.merge(f)
295 elif entry[2] == -2:
295 elif entry[2] == -2:
296 self.normaldirty(f)
296 self.normaldirty(f)
297 if source:
297 if source:
298 self.copy(source, f)
298 self.copy(source, f)
299 return
299 return
300 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
300 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
301 return
301 return
302 self._dirty = True
302 self._dirty = True
303 self._addpath(f)
303 self._addpath(f)
304 self._map[f] = ('n', 0, -1, -1)
304 self._map[f] = ('n', 0, -1, -1)
305 if f in self._copymap:
305 if f in self._copymap:
306 del self._copymap[f]
306 del self._copymap[f]
307
307
308 def normaldirty(self, f):
308 def normaldirty(self, f):
309 '''Mark a file normal, but dirty.'''
309 '''Mark a file normal, but dirty.'''
310 self._dirty = True
310 self._dirty = True
311 self._addpath(f)
311 self._addpath(f)
312 self._map[f] = ('n', 0, -2, -1)
312 self._map[f] = ('n', 0, -2, -1)
313 if f in self._copymap:
313 if f in self._copymap:
314 del self._copymap[f]
314 del self._copymap[f]
315
315
316 def add(self, f):
316 def add(self, f):
317 '''Mark a file added.'''
317 '''Mark a file added.'''
318 self._dirty = True
318 self._dirty = True
319 self._addpath(f, True)
319 self._addpath(f, True)
320 self._map[f] = ('a', 0, -1, -1)
320 self._map[f] = ('a', 0, -1, -1)
321 if f in self._copymap:
321 if f in self._copymap:
322 del self._copymap[f]
322 del self._copymap[f]
323
323
324 def remove(self, f):
324 def remove(self, f):
325 '''Mark a file removed.'''
325 '''Mark a file removed.'''
326 self._dirty = True
326 self._dirty = True
327 self._droppath(f)
327 self._droppath(f)
328 size = 0
328 size = 0
329 if self._pl[1] != nullid and f in self._map:
329 if self._pl[1] != nullid and f in self._map:
330 entry = self._map[f]
330 entry = self._map[f]
331 if entry[0] == 'm':
331 if entry[0] == 'm':
332 size = -1
332 size = -1
333 elif entry[0] == 'n' and entry[2] == -2:
333 elif entry[0] == 'n' and entry[2] == -2:
334 size = -2
334 size = -2
335 self._map[f] = ('r', 0, size, 0)
335 self._map[f] = ('r', 0, size, 0)
336 if size == 0 and f in self._copymap:
336 if size == 0 and f in self._copymap:
337 del self._copymap[f]
337 del self._copymap[f]
338
338
339 def merge(self, f):
339 def merge(self, f):
340 '''Mark a file merged.'''
340 '''Mark a file merged.'''
341 self._dirty = True
341 self._dirty = True
342 s = os.lstat(self._join(f))
342 s = os.lstat(self._join(f))
343 self._addpath(f)
343 self._addpath(f)
344 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
344 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
345 if f in self._copymap:
345 if f in self._copymap:
346 del self._copymap[f]
346 del self._copymap[f]
347
347
348 def forget(self, f):
348 def forget(self, f):
349 '''Forget a file.'''
349 '''Forget a file.'''
350 self._dirty = True
350 self._dirty = True
351 try:
351 try:
352 self._droppath(f)
352 self._droppath(f)
353 del self._map[f]
353 del self._map[f]
354 except KeyError:
354 except KeyError:
355 self._ui.warn(_("not in dirstate: %s\n") % f)
355 self._ui.warn(_("not in dirstate: %s\n") % f)
356
356
357 def _normalize(self, path, knownpath):
357 def _normalize(self, path, knownpath):
358 norm_path = os.path.normcase(path)
358 norm_path = os.path.normcase(path)
359 fold_path = self._foldmap.get(norm_path, None)
359 fold_path = self._foldmap.get(norm_path, None)
360 if fold_path is None:
360 if fold_path is None:
361 if knownpath or not os.path.exists(os.path.join(self._root, path)):
361 if knownpath or not os.path.exists(os.path.join(self._root, path)):
362 fold_path = path
362 fold_path = path
363 else:
363 else:
364 fold_path = self._foldmap.setdefault(norm_path,
364 fold_path = self._foldmap.setdefault(norm_path,
365 util.fspath(path, self._root))
365 util.fspath(path, self._root))
366 return fold_path
366 return fold_path
367
367
368 def clear(self):
368 def clear(self):
369 self._map = {}
369 self._map = {}
370 if "_dirs" in self.__dict__:
370 if "_dirs" in self.__dict__:
371 delattr(self, "_dirs")
371 delattr(self, "_dirs")
372 self._copymap = {}
372 self._copymap = {}
373 self._pl = [nullid, nullid]
373 self._pl = [nullid, nullid]
374 self._dirty = True
374 self._dirty = True
375
375
376 def rebuild(self, parent, files):
376 def rebuild(self, parent, files):
377 self.clear()
377 self.clear()
378 for f in files:
378 for f in files:
379 if 'x' in files.flags(f):
379 if 'x' in files.flags(f):
380 self._map[f] = ('n', 0777, -1, 0)
380 self._map[f] = ('n', 0777, -1, 0)
381 else:
381 else:
382 self._map[f] = ('n', 0666, -1, 0)
382 self._map[f] = ('n', 0666, -1, 0)
383 self._pl = (parent, nullid)
383 self._pl = (parent, nullid)
384 self._dirty = True
384 self._dirty = True
385
385
386 def write(self):
386 def write(self):
387 if not self._dirty:
387 if not self._dirty:
388 return
388 return
389 st = self._opener("dirstate", "w", atomictemp=True)
389 st = self._opener("dirstate", "w", atomictemp=True)
390
390
391 # use the modification time of the newly created temporary file as the
391 # use the modification time of the newly created temporary file as the
392 # filesystem's notion of 'now'
392 # filesystem's notion of 'now'
393 now = int(util.fstat(st).st_mtime)
393 now = int(util.fstat(st).st_mtime)
394
394 for f in self._map.keys():
395 cs = cStringIO.StringIO()
395 e = self._map[f]
396 copymap = self._copymap
397 pack = struct.pack
398 write = cs.write
399 write("".join(self._pl))
400 for f, e in self._map.iteritems():
401 if f in copymap:
402 f = "%s\0%s" % (f, copymap[f])
403
404 if e[0] == 'n' and e[3] == now:
396 if e[0] == 'n' and e[3] == now:
405 # The file was last modified "simultaneously" with the current
397 # The file was last modified "simultaneously" with the current
406 # write to dirstate (i.e. within the same second for file-
398 # write to dirstate (i.e. within the same second for file-
407 # systems with a granularity of 1 sec). This commonly happens
399 # systems with a granularity of 1 sec). This commonly happens
408 # for at least a couple of files on 'update'.
400 # for at least a couple of files on 'update'.
409 # The user could change the file without changing its size
401 # The user could change the file without changing its size
410 # within the same second. Invalidate the file's stat data in
402 # within the same second. Invalidate the file's stat data in
411 # dirstate, forcing future 'status' calls to compare the
403 # dirstate, forcing future 'status' calls to compare the
412 # contents of the file. This prevents mistakenly treating such
404 # contents of the file. This prevents mistakenly treating such
413 # files as clean.
405 # files as clean.
414 e = (e[0], 0, -1, -1) # mark entry as 'unset'
406 self._map[f] = (e[0], 0, -1, -1) # mark entry as 'unset'
415
407
408 cs = cStringIO.StringIO()
409 copymap = self._copymap
410 pack = struct.pack
411 write = cs.write
412 write("".join(self._pl))
413 for f, e in self._map.iteritems():
414 if f in copymap:
415 f = "%s\0%s" % (f, copymap[f])
416 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
416 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
417 write(e)
417 write(e)
418 write(f)
418 write(f)
419 st.write(cs.getvalue())
419 st.write(cs.getvalue())
420 st.rename()
420 st.rename()
421 self._dirty = self._dirtypl = False
421 self._dirty = self._dirtypl = False
422
422
423 def _dirignore(self, f):
423 def _dirignore(self, f):
424 if f == '.':
424 if f == '.':
425 return False
425 return False
426 if self._ignore(f):
426 if self._ignore(f):
427 return True
427 return True
428 for p in _finddirs(f):
428 for p in _finddirs(f):
429 if self._ignore(p):
429 if self._ignore(p):
430 return True
430 return True
431 return False
431 return False
432
432
433 def walk(self, match, subrepos, unknown, ignored):
433 def walk(self, match, subrepos, unknown, ignored):
434 '''
434 '''
435 Walk recursively through the directory tree, finding all files
435 Walk recursively through the directory tree, finding all files
436 matched by match.
436 matched by match.
437
437
438 Return a dict mapping filename to stat-like object (either
438 Return a dict mapping filename to stat-like object (either
439 mercurial.osutil.stat instance or return value of os.stat()).
439 mercurial.osutil.stat instance or return value of os.stat()).
440 '''
440 '''
441
441
442 def fwarn(f, msg):
442 def fwarn(f, msg):
443 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
443 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
444 return False
444 return False
445
445
446 def badtype(mode):
446 def badtype(mode):
447 kind = _('unknown')
447 kind = _('unknown')
448 if stat.S_ISCHR(mode):
448 if stat.S_ISCHR(mode):
449 kind = _('character device')
449 kind = _('character device')
450 elif stat.S_ISBLK(mode):
450 elif stat.S_ISBLK(mode):
451 kind = _('block device')
451 kind = _('block device')
452 elif stat.S_ISFIFO(mode):
452 elif stat.S_ISFIFO(mode):
453 kind = _('fifo')
453 kind = _('fifo')
454 elif stat.S_ISSOCK(mode):
454 elif stat.S_ISSOCK(mode):
455 kind = _('socket')
455 kind = _('socket')
456 elif stat.S_ISDIR(mode):
456 elif stat.S_ISDIR(mode):
457 kind = _('directory')
457 kind = _('directory')
458 return _('unsupported file type (type is %s)') % kind
458 return _('unsupported file type (type is %s)') % kind
459
459
460 ignore = self._ignore
460 ignore = self._ignore
461 dirignore = self._dirignore
461 dirignore = self._dirignore
462 if ignored:
462 if ignored:
463 ignore = util.never
463 ignore = util.never
464 dirignore = util.never
464 dirignore = util.never
465 elif not unknown:
465 elif not unknown:
466 # if unknown and ignored are False, skip step 2
466 # if unknown and ignored are False, skip step 2
467 ignore = util.always
467 ignore = util.always
468 dirignore = util.always
468 dirignore = util.always
469
469
470 matchfn = match.matchfn
470 matchfn = match.matchfn
471 badfn = match.bad
471 badfn = match.bad
472 dmap = self._map
472 dmap = self._map
473 normpath = util.normpath
473 normpath = util.normpath
474 listdir = osutil.listdir
474 listdir = osutil.listdir
475 lstat = os.lstat
475 lstat = os.lstat
476 getkind = stat.S_IFMT
476 getkind = stat.S_IFMT
477 dirkind = stat.S_IFDIR
477 dirkind = stat.S_IFDIR
478 regkind = stat.S_IFREG
478 regkind = stat.S_IFREG
479 lnkkind = stat.S_IFLNK
479 lnkkind = stat.S_IFLNK
480 join = self._join
480 join = self._join
481 work = []
481 work = []
482 wadd = work.append
482 wadd = work.append
483
483
484 if self._checkcase:
484 if self._checkcase:
485 normalize = self._normalize
485 normalize = self._normalize
486 else:
486 else:
487 normalize = lambda x, y: x
487 normalize = lambda x, y: x
488
488
489 exact = skipstep3 = False
489 exact = skipstep3 = False
490 if matchfn == match.exact: # match.exact
490 if matchfn == match.exact: # match.exact
491 exact = True
491 exact = True
492 dirignore = util.always # skip step 2
492 dirignore = util.always # skip step 2
493 elif match.files() and not match.anypats(): # match.match, no patterns
493 elif match.files() and not match.anypats(): # match.match, no patterns
494 skipstep3 = True
494 skipstep3 = True
495
495
496 files = set(match.files())
496 files = set(match.files())
497 if not files or '.' in files:
497 if not files or '.' in files:
498 files = ['']
498 files = ['']
499 results = dict.fromkeys(subrepos)
499 results = dict.fromkeys(subrepos)
500 results['.hg'] = None
500 results['.hg'] = None
501
501
502 # step 1: find all explicit files
502 # step 1: find all explicit files
503 for ff in sorted(files):
503 for ff in sorted(files):
504 nf = normalize(normpath(ff), False)
504 nf = normalize(normpath(ff), False)
505 if nf in results:
505 if nf in results:
506 continue
506 continue
507
507
508 try:
508 try:
509 st = lstat(join(nf))
509 st = lstat(join(nf))
510 kind = getkind(st.st_mode)
510 kind = getkind(st.st_mode)
511 if kind == dirkind:
511 if kind == dirkind:
512 skipstep3 = False
512 skipstep3 = False
513 if nf in dmap:
513 if nf in dmap:
514 #file deleted on disk but still in dirstate
514 #file deleted on disk but still in dirstate
515 results[nf] = None
515 results[nf] = None
516 match.dir(nf)
516 match.dir(nf)
517 if not dirignore(nf):
517 if not dirignore(nf):
518 wadd(nf)
518 wadd(nf)
519 elif kind == regkind or kind == lnkkind:
519 elif kind == regkind or kind == lnkkind:
520 results[nf] = st
520 results[nf] = st
521 else:
521 else:
522 badfn(ff, badtype(kind))
522 badfn(ff, badtype(kind))
523 if nf in dmap:
523 if nf in dmap:
524 results[nf] = None
524 results[nf] = None
525 except OSError, inst:
525 except OSError, inst:
526 if nf in dmap: # does it exactly match a file?
526 if nf in dmap: # does it exactly match a file?
527 results[nf] = None
527 results[nf] = None
528 else: # does it match a directory?
528 else: # does it match a directory?
529 prefix = nf + "/"
529 prefix = nf + "/"
530 for fn in dmap:
530 for fn in dmap:
531 if fn.startswith(prefix):
531 if fn.startswith(prefix):
532 match.dir(nf)
532 match.dir(nf)
533 skipstep3 = False
533 skipstep3 = False
534 break
534 break
535 else:
535 else:
536 badfn(ff, inst.strerror)
536 badfn(ff, inst.strerror)
537
537
538 # step 2: visit subdirectories
538 # step 2: visit subdirectories
539 while work:
539 while work:
540 nd = work.pop()
540 nd = work.pop()
541 skip = None
541 skip = None
542 if nd == '.':
542 if nd == '.':
543 nd = ''
543 nd = ''
544 else:
544 else:
545 skip = '.hg'
545 skip = '.hg'
546 try:
546 try:
547 entries = listdir(join(nd), stat=True, skip=skip)
547 entries = listdir(join(nd), stat=True, skip=skip)
548 except OSError, inst:
548 except OSError, inst:
549 if inst.errno == errno.EACCES:
549 if inst.errno == errno.EACCES:
550 fwarn(nd, inst.strerror)
550 fwarn(nd, inst.strerror)
551 continue
551 continue
552 raise
552 raise
553 for f, kind, st in entries:
553 for f, kind, st in entries:
554 nf = normalize(nd and (nd + "/" + f) or f, True)
554 nf = normalize(nd and (nd + "/" + f) or f, True)
555 if nf not in results:
555 if nf not in results:
556 if kind == dirkind:
556 if kind == dirkind:
557 if not ignore(nf):
557 if not ignore(nf):
558 match.dir(nf)
558 match.dir(nf)
559 wadd(nf)
559 wadd(nf)
560 if nf in dmap and matchfn(nf):
560 if nf in dmap and matchfn(nf):
561 results[nf] = None
561 results[nf] = None
562 elif kind == regkind or kind == lnkkind:
562 elif kind == regkind or kind == lnkkind:
563 if nf in dmap:
563 if nf in dmap:
564 if matchfn(nf):
564 if matchfn(nf):
565 results[nf] = st
565 results[nf] = st
566 elif matchfn(nf) and not ignore(nf):
566 elif matchfn(nf) and not ignore(nf):
567 results[nf] = st
567 results[nf] = st
568 elif nf in dmap and matchfn(nf):
568 elif nf in dmap and matchfn(nf):
569 results[nf] = None
569 results[nf] = None
570
570
571 # step 3: report unseen items in the dmap hash
571 # step 3: report unseen items in the dmap hash
572 if not skipstep3 and not exact:
572 if not skipstep3 and not exact:
573 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
573 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
574 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
574 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
575 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
575 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
576 st = None
576 st = None
577 results[nf] = st
577 results[nf] = st
578 for s in subrepos:
578 for s in subrepos:
579 del results[s]
579 del results[s]
580 del results['.hg']
580 del results['.hg']
581 return results
581 return results
582
582
583 def status(self, match, subrepos, ignored, clean, unknown):
583 def status(self, match, subrepos, ignored, clean, unknown):
584 '''Determine the status of the working copy relative to the
584 '''Determine the status of the working copy relative to the
585 dirstate and return a tuple of lists (unsure, modified, added,
585 dirstate and return a tuple of lists (unsure, modified, added,
586 removed, deleted, unknown, ignored, clean), where:
586 removed, deleted, unknown, ignored, clean), where:
587
587
588 unsure:
588 unsure:
589 files that might have been modified since the dirstate was
589 files that might have been modified since the dirstate was
590 written, but need to be read to be sure (size is the same
590 written, but need to be read to be sure (size is the same
591 but mtime differs)
591 but mtime differs)
592 modified:
592 modified:
593 files that have definitely been modified since the dirstate
593 files that have definitely been modified since the dirstate
594 was written (different size or mode)
594 was written (different size or mode)
595 added:
595 added:
596 files that have been explicitly added with hg add
596 files that have been explicitly added with hg add
597 removed:
597 removed:
598 files that have been explicitly removed with hg remove
598 files that have been explicitly removed with hg remove
599 deleted:
599 deleted:
600 files that have been deleted through other means ("missing")
600 files that have been deleted through other means ("missing")
601 unknown:
601 unknown:
602 files not in the dirstate that are not ignored
602 files not in the dirstate that are not ignored
603 ignored:
603 ignored:
604 files not in the dirstate that are ignored
604 files not in the dirstate that are ignored
605 (by _dirignore())
605 (by _dirignore())
606 clean:
606 clean:
607 files that have definitely not been modified since the
607 files that have definitely not been modified since the
608 dirstate was written
608 dirstate was written
609 '''
609 '''
610 listignored, listclean, listunknown = ignored, clean, unknown
610 listignored, listclean, listunknown = ignored, clean, unknown
611 lookup, modified, added, unknown, ignored = [], [], [], [], []
611 lookup, modified, added, unknown, ignored = [], [], [], [], []
612 removed, deleted, clean = [], [], []
612 removed, deleted, clean = [], [], []
613
613
614 dmap = self._map
614 dmap = self._map
615 ladd = lookup.append # aka "unsure"
615 ladd = lookup.append # aka "unsure"
616 madd = modified.append
616 madd = modified.append
617 aadd = added.append
617 aadd = added.append
618 uadd = unknown.append
618 uadd = unknown.append
619 iadd = ignored.append
619 iadd = ignored.append
620 radd = removed.append
620 radd = removed.append
621 dadd = deleted.append
621 dadd = deleted.append
622 cadd = clean.append
622 cadd = clean.append
623
623
624 for fn, st in self.walk(match, subrepos, listunknown,
624 for fn, st in self.walk(match, subrepos, listunknown,
625 listignored).iteritems():
625 listignored).iteritems():
626 if fn not in dmap:
626 if fn not in dmap:
627 if (listignored or match.exact(fn)) and self._dirignore(fn):
627 if (listignored or match.exact(fn)) and self._dirignore(fn):
628 if listignored:
628 if listignored:
629 iadd(fn)
629 iadd(fn)
630 elif listunknown:
630 elif listunknown:
631 uadd(fn)
631 uadd(fn)
632 continue
632 continue
633
633
634 state, mode, size, time = dmap[fn]
634 state, mode, size, time = dmap[fn]
635
635
636 if not st and state in "nma":
636 if not st and state in "nma":
637 dadd(fn)
637 dadd(fn)
638 elif state == 'n':
638 elif state == 'n':
639 if (size >= 0 and
639 if (size >= 0 and
640 (size != st.st_size
640 (size != st.st_size
641 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
641 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
642 or size == -2
642 or size == -2
643 or fn in self._copymap):
643 or fn in self._copymap):
644 madd(fn)
644 madd(fn)
645 elif time != int(st.st_mtime):
645 elif time != int(st.st_mtime):
646 ladd(fn)
646 ladd(fn)
647 elif listclean:
647 elif listclean:
648 cadd(fn)
648 cadd(fn)
649 elif state == 'm':
649 elif state == 'm':
650 madd(fn)
650 madd(fn)
651 elif state == 'a':
651 elif state == 'a':
652 aadd(fn)
652 aadd(fn)
653 elif state == 'r':
653 elif state == 'r':
654 radd(fn)
654 radd(fn)
655
655
656 return (lookup, modified, added, removed, deleted, unknown, ignored,
656 return (lookup, modified, added, removed, deleted, unknown, ignored,
657 clean)
657 clean)
General Comments 0
You need to be logged in to leave comments. Login now