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