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