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