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