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