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