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