##// END OF EJS Templates
dirstate: don't rename branch file if writing it failed
Idan Kamara -
r18076:3bc21f6d stable
parent child Browse files
Show More
@@ -1,799 +1,801
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 self._branch = encoding.fromlocal(branch)
264 self._branch = encoding.fromlocal(branch)
265 f = self._opener('branch', 'w', atomictemp=True)
265 f = self._opener('branch', 'w', atomictemp=True)
266 try:
266 try:
267 f.write(self._branch + '\n')
267 f.write(self._branch + '\n')
268 finally:
269 f.close()
268 f.close()
269 except: # re-raises
270 f.discard()
271 raise
270
272
271 def _read(self):
273 def _read(self):
272 self._map = {}
274 self._map = {}
273 self._copymap = {}
275 self._copymap = {}
274 try:
276 try:
275 st = self._opener.read("dirstate")
277 st = self._opener.read("dirstate")
276 except IOError, err:
278 except IOError, err:
277 if err.errno != errno.ENOENT:
279 if err.errno != errno.ENOENT:
278 raise
280 raise
279 return
281 return
280 if not st:
282 if not st:
281 return
283 return
282
284
283 p = parsers.parse_dirstate(self._map, self._copymap, st)
285 p = parsers.parse_dirstate(self._map, self._copymap, st)
284 if not self._dirtypl:
286 if not self._dirtypl:
285 self._pl = p
287 self._pl = p
286
288
287 def invalidate(self):
289 def invalidate(self):
288 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
290 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
289 "_ignore"):
291 "_ignore"):
290 if a in self.__dict__:
292 if a in self.__dict__:
291 delattr(self, a)
293 delattr(self, a)
292 self._lastnormaltime = 0
294 self._lastnormaltime = 0
293 self._dirty = False
295 self._dirty = False
294
296
295 def copy(self, source, dest):
297 def copy(self, source, dest):
296 """Mark dest as a copy of source. Unmark dest if source is None."""
298 """Mark dest as a copy of source. Unmark dest if source is None."""
297 if source == dest:
299 if source == dest:
298 return
300 return
299 self._dirty = True
301 self._dirty = True
300 if source is not None:
302 if source is not None:
301 self._copymap[dest] = source
303 self._copymap[dest] = source
302 elif dest in self._copymap:
304 elif dest in self._copymap:
303 del self._copymap[dest]
305 del self._copymap[dest]
304
306
305 def copied(self, file):
307 def copied(self, file):
306 return self._copymap.get(file, None)
308 return self._copymap.get(file, None)
307
309
308 def copies(self):
310 def copies(self):
309 return self._copymap
311 return self._copymap
310
312
311 def _droppath(self, f):
313 def _droppath(self, f):
312 if self[f] not in "?r" and "_dirs" in self.__dict__:
314 if self[f] not in "?r" and "_dirs" in self.__dict__:
313 _decdirs(self._dirs, f)
315 _decdirs(self._dirs, f)
314
316
315 def _addpath(self, f, state, mode, size, mtime):
317 def _addpath(self, f, state, mode, size, mtime):
316 oldstate = self[f]
318 oldstate = self[f]
317 if state == 'a' or oldstate == 'r':
319 if state == 'a' or oldstate == 'r':
318 scmutil.checkfilename(f)
320 scmutil.checkfilename(f)
319 if f in self._dirs:
321 if f in self._dirs:
320 raise util.Abort(_('directory %r already in dirstate') % f)
322 raise util.Abort(_('directory %r already in dirstate') % f)
321 # shadows
323 # shadows
322 for d in _finddirs(f):
324 for d in _finddirs(f):
323 if d in self._dirs:
325 if d in self._dirs:
324 break
326 break
325 if d in self._map and self[d] != 'r':
327 if d in self._map and self[d] != 'r':
326 raise util.Abort(
328 raise util.Abort(
327 _('file %r in dirstate clashes with %r') % (d, f))
329 _('file %r in dirstate clashes with %r') % (d, f))
328 if oldstate in "?r" and "_dirs" in self.__dict__:
330 if oldstate in "?r" and "_dirs" in self.__dict__:
329 _incdirs(self._dirs, f)
331 _incdirs(self._dirs, f)
330 self._dirty = True
332 self._dirty = True
331 self._map[f] = (state, mode, size, mtime)
333 self._map[f] = (state, mode, size, mtime)
332
334
333 def normal(self, f):
335 def normal(self, f):
334 '''Mark a file normal and clean.'''
336 '''Mark a file normal and clean.'''
335 s = os.lstat(self._join(f))
337 s = os.lstat(self._join(f))
336 mtime = int(s.st_mtime)
338 mtime = int(s.st_mtime)
337 self._addpath(f, 'n', s.st_mode,
339 self._addpath(f, 'n', s.st_mode,
338 s.st_size & _rangemask, mtime & _rangemask)
340 s.st_size & _rangemask, mtime & _rangemask)
339 if f in self._copymap:
341 if f in self._copymap:
340 del self._copymap[f]
342 del self._copymap[f]
341 if mtime > self._lastnormaltime:
343 if mtime > self._lastnormaltime:
342 # Remember the most recent modification timeslot for status(),
344 # Remember the most recent modification timeslot for status(),
343 # to make sure we won't miss future size-preserving file content
345 # to make sure we won't miss future size-preserving file content
344 # modifications that happen within the same timeslot.
346 # modifications that happen within the same timeslot.
345 self._lastnormaltime = mtime
347 self._lastnormaltime = mtime
346
348
347 def normallookup(self, f):
349 def normallookup(self, f):
348 '''Mark a file normal, but possibly dirty.'''
350 '''Mark a file normal, but possibly dirty.'''
349 if self._pl[1] != nullid and f in self._map:
351 if self._pl[1] != nullid and f in self._map:
350 # if there is a merge going on and the file was either
352 # if there is a merge going on and the file was either
351 # in state 'm' (-1) or coming from other parent (-2) before
353 # in state 'm' (-1) or coming from other parent (-2) before
352 # being removed, restore that state.
354 # being removed, restore that state.
353 entry = self._map[f]
355 entry = self._map[f]
354 if entry[0] == 'r' and entry[2] in (-1, -2):
356 if entry[0] == 'r' and entry[2] in (-1, -2):
355 source = self._copymap.get(f)
357 source = self._copymap.get(f)
356 if entry[2] == -1:
358 if entry[2] == -1:
357 self.merge(f)
359 self.merge(f)
358 elif entry[2] == -2:
360 elif entry[2] == -2:
359 self.otherparent(f)
361 self.otherparent(f)
360 if source:
362 if source:
361 self.copy(source, f)
363 self.copy(source, f)
362 return
364 return
363 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
365 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
364 return
366 return
365 self._addpath(f, 'n', 0, -1, -1)
367 self._addpath(f, 'n', 0, -1, -1)
366 if f in self._copymap:
368 if f in self._copymap:
367 del self._copymap[f]
369 del self._copymap[f]
368
370
369 def otherparent(self, f):
371 def otherparent(self, f):
370 '''Mark as coming from the other parent, always dirty.'''
372 '''Mark as coming from the other parent, always dirty.'''
371 if self._pl[1] == nullid:
373 if self._pl[1] == nullid:
372 raise util.Abort(_("setting %r to other parent "
374 raise util.Abort(_("setting %r to other parent "
373 "only allowed in merges") % f)
375 "only allowed in merges") % f)
374 self._addpath(f, 'n', 0, -2, -1)
376 self._addpath(f, 'n', 0, -2, -1)
375 if f in self._copymap:
377 if f in self._copymap:
376 del self._copymap[f]
378 del self._copymap[f]
377
379
378 def add(self, f):
380 def add(self, f):
379 '''Mark a file added.'''
381 '''Mark a file added.'''
380 self._addpath(f, 'a', 0, -1, -1)
382 self._addpath(f, 'a', 0, -1, -1)
381 if f in self._copymap:
383 if f in self._copymap:
382 del self._copymap[f]
384 del self._copymap[f]
383
385
384 def remove(self, f):
386 def remove(self, f):
385 '''Mark a file removed.'''
387 '''Mark a file removed.'''
386 self._dirty = True
388 self._dirty = True
387 self._droppath(f)
389 self._droppath(f)
388 size = 0
390 size = 0
389 if self._pl[1] != nullid and f in self._map:
391 if self._pl[1] != nullid and f in self._map:
390 # backup the previous state
392 # backup the previous state
391 entry = self._map[f]
393 entry = self._map[f]
392 if entry[0] == 'm': # merge
394 if entry[0] == 'm': # merge
393 size = -1
395 size = -1
394 elif entry[0] == 'n' and entry[2] == -2: # other parent
396 elif entry[0] == 'n' and entry[2] == -2: # other parent
395 size = -2
397 size = -2
396 self._map[f] = ('r', 0, size, 0)
398 self._map[f] = ('r', 0, size, 0)
397 if size == 0 and f in self._copymap:
399 if size == 0 and f in self._copymap:
398 del self._copymap[f]
400 del self._copymap[f]
399
401
400 def merge(self, f):
402 def merge(self, f):
401 '''Mark a file merged.'''
403 '''Mark a file merged.'''
402 if self._pl[1] == nullid:
404 if self._pl[1] == nullid:
403 return self.normallookup(f)
405 return self.normallookup(f)
404 s = os.lstat(self._join(f))
406 s = os.lstat(self._join(f))
405 self._addpath(f, 'm', s.st_mode,
407 self._addpath(f, 'm', s.st_mode,
406 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
408 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
407 if f in self._copymap:
409 if f in self._copymap:
408 del self._copymap[f]
410 del self._copymap[f]
409
411
410 def drop(self, f):
412 def drop(self, f):
411 '''Drop a file from the dirstate'''
413 '''Drop a file from the dirstate'''
412 if f in self._map:
414 if f in self._map:
413 self._dirty = True
415 self._dirty = True
414 self._droppath(f)
416 self._droppath(f)
415 del self._map[f]
417 del self._map[f]
416
418
417 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
419 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
418 normed = util.normcase(path)
420 normed = util.normcase(path)
419 folded = self._foldmap.get(normed, None)
421 folded = self._foldmap.get(normed, None)
420 if folded is None:
422 if folded is None:
421 if isknown:
423 if isknown:
422 folded = path
424 folded = path
423 else:
425 else:
424 if exists is None:
426 if exists is None:
425 exists = os.path.lexists(os.path.join(self._root, path))
427 exists = os.path.lexists(os.path.join(self._root, path))
426 if not exists:
428 if not exists:
427 # Maybe a path component exists
429 # Maybe a path component exists
428 if not ignoremissing and '/' in path:
430 if not ignoremissing and '/' in path:
429 d, f = path.rsplit('/', 1)
431 d, f = path.rsplit('/', 1)
430 d = self._normalize(d, isknown, ignoremissing, None)
432 d = self._normalize(d, isknown, ignoremissing, None)
431 folded = d + "/" + f
433 folded = d + "/" + f
432 else:
434 else:
433 # No path components, preserve original case
435 # No path components, preserve original case
434 folded = path
436 folded = path
435 else:
437 else:
436 # recursively normalize leading directory components
438 # recursively normalize leading directory components
437 # against dirstate
439 # against dirstate
438 if '/' in normed:
440 if '/' in normed:
439 d, f = normed.rsplit('/', 1)
441 d, f = normed.rsplit('/', 1)
440 d = self._normalize(d, isknown, ignoremissing, True)
442 d = self._normalize(d, isknown, ignoremissing, True)
441 r = self._root + "/" + d
443 r = self._root + "/" + d
442 folded = d + "/" + util.fspath(f, r)
444 folded = d + "/" + util.fspath(f, r)
443 else:
445 else:
444 folded = util.fspath(normed, self._root)
446 folded = util.fspath(normed, self._root)
445 self._foldmap[normed] = folded
447 self._foldmap[normed] = folded
446
448
447 return folded
449 return folded
448
450
449 def normalize(self, path, isknown=False, ignoremissing=False):
451 def normalize(self, path, isknown=False, ignoremissing=False):
450 '''
452 '''
451 normalize the case of a pathname when on a casefolding filesystem
453 normalize the case of a pathname when on a casefolding filesystem
452
454
453 isknown specifies whether the filename came from walking the
455 isknown specifies whether the filename came from walking the
454 disk, to avoid extra filesystem access.
456 disk, to avoid extra filesystem access.
455
457
456 If ignoremissing is True, missing path are returned
458 If ignoremissing is True, missing path are returned
457 unchanged. Otherwise, we try harder to normalize possibly
459 unchanged. Otherwise, we try harder to normalize possibly
458 existing path components.
460 existing path components.
459
461
460 The normalized case is determined based on the following precedence:
462 The normalized case is determined based on the following precedence:
461
463
462 - version of name already stored in the dirstate
464 - version of name already stored in the dirstate
463 - version of name stored on disk
465 - version of name stored on disk
464 - version provided via command arguments
466 - version provided via command arguments
465 '''
467 '''
466
468
467 if self._checkcase:
469 if self._checkcase:
468 return self._normalize(path, isknown, ignoremissing)
470 return self._normalize(path, isknown, ignoremissing)
469 return path
471 return path
470
472
471 def clear(self):
473 def clear(self):
472 self._map = {}
474 self._map = {}
473 if "_dirs" in self.__dict__:
475 if "_dirs" in self.__dict__:
474 delattr(self, "_dirs")
476 delattr(self, "_dirs")
475 self._copymap = {}
477 self._copymap = {}
476 self._pl = [nullid, nullid]
478 self._pl = [nullid, nullid]
477 self._lastnormaltime = 0
479 self._lastnormaltime = 0
478 self._dirty = True
480 self._dirty = True
479
481
480 def rebuild(self, parent, files):
482 def rebuild(self, parent, files):
481 self.clear()
483 self.clear()
482 for f in files:
484 for f in files:
483 if 'x' in files.flags(f):
485 if 'x' in files.flags(f):
484 self._map[f] = ('n', 0777, -1, 0)
486 self._map[f] = ('n', 0777, -1, 0)
485 else:
487 else:
486 self._map[f] = ('n', 0666, -1, 0)
488 self._map[f] = ('n', 0666, -1, 0)
487 self._pl = (parent, nullid)
489 self._pl = (parent, nullid)
488 self._dirty = True
490 self._dirty = True
489
491
490 def write(self):
492 def write(self):
491 if not self._dirty:
493 if not self._dirty:
492 return
494 return
493 st = self._opener("dirstate", "w", atomictemp=True)
495 st = self._opener("dirstate", "w", atomictemp=True)
494
496
495 def finish(s):
497 def finish(s):
496 st.write(s)
498 st.write(s)
497 st.close()
499 st.close()
498 self._lastnormaltime = 0
500 self._lastnormaltime = 0
499 self._dirty = self._dirtypl = False
501 self._dirty = self._dirtypl = False
500
502
501 # use the modification time of the newly created temporary file as the
503 # use the modification time of the newly created temporary file as the
502 # filesystem's notion of 'now'
504 # filesystem's notion of 'now'
503 now = util.fstat(st).st_mtime
505 now = util.fstat(st).st_mtime
504 copymap = self._copymap
506 copymap = self._copymap
505 try:
507 try:
506 finish(parsers.pack_dirstate(self._map, copymap, self._pl, now))
508 finish(parsers.pack_dirstate(self._map, copymap, self._pl, now))
507 return
509 return
508 except AttributeError:
510 except AttributeError:
509 pass
511 pass
510
512
511 now = int(now)
513 now = int(now)
512 cs = cStringIO.StringIO()
514 cs = cStringIO.StringIO()
513 pack = struct.pack
515 pack = struct.pack
514 write = cs.write
516 write = cs.write
515 write("".join(self._pl))
517 write("".join(self._pl))
516 for f, e in self._map.iteritems():
518 for f, e in self._map.iteritems():
517 if e[0] == 'n' and e[3] == now:
519 if e[0] == 'n' and e[3] == now:
518 # The file was last modified "simultaneously" with the current
520 # The file was last modified "simultaneously" with the current
519 # write to dirstate (i.e. within the same second for file-
521 # write to dirstate (i.e. within the same second for file-
520 # systems with a granularity of 1 sec). This commonly happens
522 # systems with a granularity of 1 sec). This commonly happens
521 # for at least a couple of files on 'update'.
523 # for at least a couple of files on 'update'.
522 # The user could change the file without changing its size
524 # The user could change the file without changing its size
523 # within the same second. Invalidate the file's stat data in
525 # within the same second. Invalidate the file's stat data in
524 # dirstate, forcing future 'status' calls to compare the
526 # dirstate, forcing future 'status' calls to compare the
525 # contents of the file. This prevents mistakenly treating such
527 # contents of the file. This prevents mistakenly treating such
526 # files as clean.
528 # files as clean.
527 e = (e[0], 0, -1, -1) # mark entry as 'unset'
529 e = (e[0], 0, -1, -1) # mark entry as 'unset'
528 self._map[f] = e
530 self._map[f] = e
529
531
530 if f in copymap:
532 if f in copymap:
531 f = "%s\0%s" % (f, copymap[f])
533 f = "%s\0%s" % (f, copymap[f])
532 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
534 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
533 write(e)
535 write(e)
534 write(f)
536 write(f)
535 finish(cs.getvalue())
537 finish(cs.getvalue())
536
538
537 def _dirignore(self, f):
539 def _dirignore(self, f):
538 if f == '.':
540 if f == '.':
539 return False
541 return False
540 if self._ignore(f):
542 if self._ignore(f):
541 return True
543 return True
542 for p in _finddirs(f):
544 for p in _finddirs(f):
543 if self._ignore(p):
545 if self._ignore(p):
544 return True
546 return True
545 return False
547 return False
546
548
547 def walk(self, match, subrepos, unknown, ignored):
549 def walk(self, match, subrepos, unknown, ignored):
548 '''
550 '''
549 Walk recursively through the directory tree, finding all files
551 Walk recursively through the directory tree, finding all files
550 matched by match.
552 matched by match.
551
553
552 Return a dict mapping filename to stat-like object (either
554 Return a dict mapping filename to stat-like object (either
553 mercurial.osutil.stat instance or return value of os.stat()).
555 mercurial.osutil.stat instance or return value of os.stat()).
554 '''
556 '''
555
557
556 def fwarn(f, msg):
558 def fwarn(f, msg):
557 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
559 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
558 return False
560 return False
559
561
560 def badtype(mode):
562 def badtype(mode):
561 kind = _('unknown')
563 kind = _('unknown')
562 if stat.S_ISCHR(mode):
564 if stat.S_ISCHR(mode):
563 kind = _('character device')
565 kind = _('character device')
564 elif stat.S_ISBLK(mode):
566 elif stat.S_ISBLK(mode):
565 kind = _('block device')
567 kind = _('block device')
566 elif stat.S_ISFIFO(mode):
568 elif stat.S_ISFIFO(mode):
567 kind = _('fifo')
569 kind = _('fifo')
568 elif stat.S_ISSOCK(mode):
570 elif stat.S_ISSOCK(mode):
569 kind = _('socket')
571 kind = _('socket')
570 elif stat.S_ISDIR(mode):
572 elif stat.S_ISDIR(mode):
571 kind = _('directory')
573 kind = _('directory')
572 return _('unsupported file type (type is %s)') % kind
574 return _('unsupported file type (type is %s)') % kind
573
575
574 ignore = self._ignore
576 ignore = self._ignore
575 dirignore = self._dirignore
577 dirignore = self._dirignore
576 if ignored:
578 if ignored:
577 ignore = util.never
579 ignore = util.never
578 dirignore = util.never
580 dirignore = util.never
579 elif not unknown:
581 elif not unknown:
580 # if unknown and ignored are False, skip step 2
582 # if unknown and ignored are False, skip step 2
581 ignore = util.always
583 ignore = util.always
582 dirignore = util.always
584 dirignore = util.always
583
585
584 matchfn = match.matchfn
586 matchfn = match.matchfn
585 badfn = match.bad
587 badfn = match.bad
586 dmap = self._map
588 dmap = self._map
587 normpath = util.normpath
589 normpath = util.normpath
588 listdir = osutil.listdir
590 listdir = osutil.listdir
589 lstat = os.lstat
591 lstat = os.lstat
590 getkind = stat.S_IFMT
592 getkind = stat.S_IFMT
591 dirkind = stat.S_IFDIR
593 dirkind = stat.S_IFDIR
592 regkind = stat.S_IFREG
594 regkind = stat.S_IFREG
593 lnkkind = stat.S_IFLNK
595 lnkkind = stat.S_IFLNK
594 join = self._join
596 join = self._join
595 work = []
597 work = []
596 wadd = work.append
598 wadd = work.append
597
599
598 exact = skipstep3 = False
600 exact = skipstep3 = False
599 if matchfn == match.exact: # match.exact
601 if matchfn == match.exact: # match.exact
600 exact = True
602 exact = True
601 dirignore = util.always # skip step 2
603 dirignore = util.always # skip step 2
602 elif match.files() and not match.anypats(): # match.match, no patterns
604 elif match.files() and not match.anypats(): # match.match, no patterns
603 skipstep3 = True
605 skipstep3 = True
604
606
605 if not exact and self._checkcase:
607 if not exact and self._checkcase:
606 normalize = self._normalize
608 normalize = self._normalize
607 skipstep3 = False
609 skipstep3 = False
608 else:
610 else:
609 normalize = lambda x, y, z: x
611 normalize = lambda x, y, z: x
610
612
611 files = sorted(match.files())
613 files = sorted(match.files())
612 subrepos.sort()
614 subrepos.sort()
613 i, j = 0, 0
615 i, j = 0, 0
614 while i < len(files) and j < len(subrepos):
616 while i < len(files) and j < len(subrepos):
615 subpath = subrepos[j] + "/"
617 subpath = subrepos[j] + "/"
616 if files[i] < subpath:
618 if files[i] < subpath:
617 i += 1
619 i += 1
618 continue
620 continue
619 while i < len(files) and files[i].startswith(subpath):
621 while i < len(files) and files[i].startswith(subpath):
620 del files[i]
622 del files[i]
621 j += 1
623 j += 1
622
624
623 if not files or '.' in files:
625 if not files or '.' in files:
624 files = ['']
626 files = ['']
625 results = dict.fromkeys(subrepos)
627 results = dict.fromkeys(subrepos)
626 results['.hg'] = None
628 results['.hg'] = None
627
629
628 # step 1: find all explicit files
630 # step 1: find all explicit files
629 for ff in files:
631 for ff in files:
630 nf = normalize(normpath(ff), False, True)
632 nf = normalize(normpath(ff), False, True)
631 if nf in results:
633 if nf in results:
632 continue
634 continue
633
635
634 try:
636 try:
635 st = lstat(join(nf))
637 st = lstat(join(nf))
636 kind = getkind(st.st_mode)
638 kind = getkind(st.st_mode)
637 if kind == dirkind:
639 if kind == dirkind:
638 skipstep3 = False
640 skipstep3 = False
639 if nf in dmap:
641 if nf in dmap:
640 #file deleted on disk but still in dirstate
642 #file deleted on disk but still in dirstate
641 results[nf] = None
643 results[nf] = None
642 match.dir(nf)
644 match.dir(nf)
643 if not dirignore(nf):
645 if not dirignore(nf):
644 wadd(nf)
646 wadd(nf)
645 elif kind == regkind or kind == lnkkind:
647 elif kind == regkind or kind == lnkkind:
646 results[nf] = st
648 results[nf] = st
647 else:
649 else:
648 badfn(ff, badtype(kind))
650 badfn(ff, badtype(kind))
649 if nf in dmap:
651 if nf in dmap:
650 results[nf] = None
652 results[nf] = None
651 except OSError, inst:
653 except OSError, inst:
652 if nf in dmap: # does it exactly match a file?
654 if nf in dmap: # does it exactly match a file?
653 results[nf] = None
655 results[nf] = None
654 else: # does it match a directory?
656 else: # does it match a directory?
655 prefix = nf + "/"
657 prefix = nf + "/"
656 for fn in dmap:
658 for fn in dmap:
657 if fn.startswith(prefix):
659 if fn.startswith(prefix):
658 match.dir(nf)
660 match.dir(nf)
659 skipstep3 = False
661 skipstep3 = False
660 break
662 break
661 else:
663 else:
662 badfn(ff, inst.strerror)
664 badfn(ff, inst.strerror)
663
665
664 # step 2: visit subdirectories
666 # step 2: visit subdirectories
665 while work:
667 while work:
666 nd = work.pop()
668 nd = work.pop()
667 skip = None
669 skip = None
668 if nd == '.':
670 if nd == '.':
669 nd = ''
671 nd = ''
670 else:
672 else:
671 skip = '.hg'
673 skip = '.hg'
672 try:
674 try:
673 entries = listdir(join(nd), stat=True, skip=skip)
675 entries = listdir(join(nd), stat=True, skip=skip)
674 except OSError, inst:
676 except OSError, inst:
675 if inst.errno in (errno.EACCES, errno.ENOENT):
677 if inst.errno in (errno.EACCES, errno.ENOENT):
676 fwarn(nd, inst.strerror)
678 fwarn(nd, inst.strerror)
677 continue
679 continue
678 raise
680 raise
679 for f, kind, st in entries:
681 for f, kind, st in entries:
680 nf = normalize(nd and (nd + "/" + f) or f, True, True)
682 nf = normalize(nd and (nd + "/" + f) or f, True, True)
681 if nf not in results:
683 if nf not in results:
682 if kind == dirkind:
684 if kind == dirkind:
683 if not ignore(nf):
685 if not ignore(nf):
684 match.dir(nf)
686 match.dir(nf)
685 wadd(nf)
687 wadd(nf)
686 if nf in dmap and matchfn(nf):
688 if nf in dmap and matchfn(nf):
687 results[nf] = None
689 results[nf] = None
688 elif kind == regkind or kind == lnkkind:
690 elif kind == regkind or kind == lnkkind:
689 if nf in dmap:
691 if nf in dmap:
690 if matchfn(nf):
692 if matchfn(nf):
691 results[nf] = st
693 results[nf] = st
692 elif matchfn(nf) and not ignore(nf):
694 elif matchfn(nf) and not ignore(nf):
693 results[nf] = st
695 results[nf] = st
694 elif nf in dmap and matchfn(nf):
696 elif nf in dmap and matchfn(nf):
695 results[nf] = None
697 results[nf] = None
696
698
697 # step 3: report unseen items in the dmap hash
699 # step 3: report unseen items in the dmap hash
698 if not skipstep3 and not exact:
700 if not skipstep3 and not exact:
699 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
701 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
700 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
702 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
701 if (not st is None and
703 if (not st is None and
702 getkind(st.st_mode) not in (regkind, lnkkind)):
704 getkind(st.st_mode) not in (regkind, lnkkind)):
703 st = None
705 st = None
704 results[nf] = st
706 results[nf] = st
705 for s in subrepos:
707 for s in subrepos:
706 del results[s]
708 del results[s]
707 del results['.hg']
709 del results['.hg']
708 return results
710 return results
709
711
710 def status(self, match, subrepos, ignored, clean, unknown):
712 def status(self, match, subrepos, ignored, clean, unknown):
711 '''Determine the status of the working copy relative to the
713 '''Determine the status of the working copy relative to the
712 dirstate and return a tuple of lists (unsure, modified, added,
714 dirstate and return a tuple of lists (unsure, modified, added,
713 removed, deleted, unknown, ignored, clean), where:
715 removed, deleted, unknown, ignored, clean), where:
714
716
715 unsure:
717 unsure:
716 files that might have been modified since the dirstate was
718 files that might have been modified since the dirstate was
717 written, but need to be read to be sure (size is the same
719 written, but need to be read to be sure (size is the same
718 but mtime differs)
720 but mtime differs)
719 modified:
721 modified:
720 files that have definitely been modified since the dirstate
722 files that have definitely been modified since the dirstate
721 was written (different size or mode)
723 was written (different size or mode)
722 added:
724 added:
723 files that have been explicitly added with hg add
725 files that have been explicitly added with hg add
724 removed:
726 removed:
725 files that have been explicitly removed with hg remove
727 files that have been explicitly removed with hg remove
726 deleted:
728 deleted:
727 files that have been deleted through other means ("missing")
729 files that have been deleted through other means ("missing")
728 unknown:
730 unknown:
729 files not in the dirstate that are not ignored
731 files not in the dirstate that are not ignored
730 ignored:
732 ignored:
731 files not in the dirstate that are ignored
733 files not in the dirstate that are ignored
732 (by _dirignore())
734 (by _dirignore())
733 clean:
735 clean:
734 files that have definitely not been modified since the
736 files that have definitely not been modified since the
735 dirstate was written
737 dirstate was written
736 '''
738 '''
737 listignored, listclean, listunknown = ignored, clean, unknown
739 listignored, listclean, listunknown = ignored, clean, unknown
738 lookup, modified, added, unknown, ignored = [], [], [], [], []
740 lookup, modified, added, unknown, ignored = [], [], [], [], []
739 removed, deleted, clean = [], [], []
741 removed, deleted, clean = [], [], []
740
742
741 dmap = self._map
743 dmap = self._map
742 ladd = lookup.append # aka "unsure"
744 ladd = lookup.append # aka "unsure"
743 madd = modified.append
745 madd = modified.append
744 aadd = added.append
746 aadd = added.append
745 uadd = unknown.append
747 uadd = unknown.append
746 iadd = ignored.append
748 iadd = ignored.append
747 radd = removed.append
749 radd = removed.append
748 dadd = deleted.append
750 dadd = deleted.append
749 cadd = clean.append
751 cadd = clean.append
750
752
751 lnkkind = stat.S_IFLNK
753 lnkkind = stat.S_IFLNK
752
754
753 for fn, st in self.walk(match, subrepos, listunknown,
755 for fn, st in self.walk(match, subrepos, listunknown,
754 listignored).iteritems():
756 listignored).iteritems():
755 if fn not in dmap:
757 if fn not in dmap:
756 if (listignored or match.exact(fn)) and self._dirignore(fn):
758 if (listignored or match.exact(fn)) and self._dirignore(fn):
757 if listignored:
759 if listignored:
758 iadd(fn)
760 iadd(fn)
759 elif listunknown:
761 elif listunknown:
760 uadd(fn)
762 uadd(fn)
761 continue
763 continue
762
764
763 state, mode, size, time = dmap[fn]
765 state, mode, size, time = dmap[fn]
764
766
765 if not st and state in "nma":
767 if not st and state in "nma":
766 dadd(fn)
768 dadd(fn)
767 elif state == 'n':
769 elif state == 'n':
768 # The "mode & lnkkind != lnkkind or self._checklink"
770 # The "mode & lnkkind != lnkkind or self._checklink"
769 # lines are an expansion of "islink => checklink"
771 # lines are an expansion of "islink => checklink"
770 # where islink means "is this a link?" and checklink
772 # where islink means "is this a link?" and checklink
771 # means "can we check links?".
773 # means "can we check links?".
772 mtime = int(st.st_mtime)
774 mtime = int(st.st_mtime)
773 if (size >= 0 and
775 if (size >= 0 and
774 ((size != st.st_size and size != st.st_size & _rangemask)
776 ((size != st.st_size and size != st.st_size & _rangemask)
775 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
777 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
776 and (mode & lnkkind != lnkkind or self._checklink)
778 and (mode & lnkkind != lnkkind or self._checklink)
777 or size == -2 # other parent
779 or size == -2 # other parent
778 or fn in self._copymap):
780 or fn in self._copymap):
779 madd(fn)
781 madd(fn)
780 elif ((time != mtime and time != mtime & _rangemask)
782 elif ((time != mtime and time != mtime & _rangemask)
781 and (mode & lnkkind != lnkkind or self._checklink)):
783 and (mode & lnkkind != lnkkind or self._checklink)):
782 ladd(fn)
784 ladd(fn)
783 elif mtime == self._lastnormaltime:
785 elif mtime == self._lastnormaltime:
784 # fn may have been changed in the same timeslot without
786 # fn may have been changed in the same timeslot without
785 # changing its size. This can happen if we quickly do
787 # changing its size. This can happen if we quickly do
786 # multiple commits in a single transaction.
788 # multiple commits in a single transaction.
787 # Force lookup, so we don't miss such a racy file change.
789 # Force lookup, so we don't miss such a racy file change.
788 ladd(fn)
790 ladd(fn)
789 elif listclean:
791 elif listclean:
790 cadd(fn)
792 cadd(fn)
791 elif state == 'm':
793 elif state == 'm':
792 madd(fn)
794 madd(fn)
793 elif state == 'a':
795 elif state == 'a':
794 aadd(fn)
796 aadd(fn)
795 elif state == 'r':
797 elif state == 'r':
796 radd(fn)
798 radd(fn)
797
799
798 return (lookup, modified, added, removed, deleted, unknown, ignored,
800 return (lookup, modified, added, removed, deleted, unknown, ignored,
799 clean)
801 clean)
General Comments 0
You need to be logged in to leave comments. Login now