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