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