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