##// END OF EJS Templates
dirstate: report bad subdirectories as match.bad, not just a warning (BC)...
Mads Kiilerich -
r21116:30c60e28 default
parent child Browse files
Show More
@@ -1,858 +1,854 b''
1 # dirstate.py - working directory tracking for mercurial
1 # dirstate.py - working directory tracking for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import nullid
8 from node import nullid
9 from i18n import _
9 from i18n import _
10 import scmutil, util, ignore, osutil, parsers, encoding, pathutil
10 import scmutil, util, ignore, osutil, parsers, encoding, pathutil
11 import os, stat, errno, gc
11 import os, stat, errno, gc
12
12
13 propertycache = util.propertycache
13 propertycache = util.propertycache
14 filecache = scmutil.filecache
14 filecache = scmutil.filecache
15 _rangemask = 0x7fffffff
15 _rangemask = 0x7fffffff
16
16
17 class repocache(filecache):
17 class repocache(filecache):
18 """filecache for files in .hg/"""
18 """filecache for files in .hg/"""
19 def join(self, obj, fname):
19 def join(self, obj, fname):
20 return obj._opener.join(fname)
20 return obj._opener.join(fname)
21
21
22 class rootcache(filecache):
22 class rootcache(filecache):
23 """filecache for files in the repository root"""
23 """filecache for files in the repository root"""
24 def join(self, obj, fname):
24 def join(self, obj, fname):
25 return obj._join(fname)
25 return obj._join(fname)
26
26
27 class dirstate(object):
27 class dirstate(object):
28
28
29 def __init__(self, opener, ui, root, validate):
29 def __init__(self, opener, ui, root, validate):
30 '''Create a new dirstate object.
30 '''Create a new dirstate object.
31
31
32 opener is an open()-like callable that can be used to open the
32 opener is an open()-like callable that can be used to open the
33 dirstate file; root is the root of the directory tracked by
33 dirstate file; root is the root of the directory tracked by
34 the dirstate.
34 the dirstate.
35 '''
35 '''
36 self._opener = opener
36 self._opener = opener
37 self._validate = validate
37 self._validate = validate
38 self._root = root
38 self._root = root
39 self._rootdir = os.path.join(root, '')
39 self._rootdir = os.path.join(root, '')
40 self._dirty = False
40 self._dirty = False
41 self._dirtypl = False
41 self._dirtypl = False
42 self._lastnormaltime = 0
42 self._lastnormaltime = 0
43 self._ui = ui
43 self._ui = ui
44 self._filecache = {}
44 self._filecache = {}
45
45
46 @propertycache
46 @propertycache
47 def _map(self):
47 def _map(self):
48 '''Return the dirstate contents as a map from filename to
48 '''Return the dirstate contents as a map from filename to
49 (state, mode, size, time).'''
49 (state, mode, size, time).'''
50 self._read()
50 self._read()
51 return self._map
51 return self._map
52
52
53 @propertycache
53 @propertycache
54 def _copymap(self):
54 def _copymap(self):
55 self._read()
55 self._read()
56 return self._copymap
56 return self._copymap
57
57
58 @propertycache
58 @propertycache
59 def _foldmap(self):
59 def _foldmap(self):
60 f = {}
60 f = {}
61 for name, s in self._map.iteritems():
61 for name, s in self._map.iteritems():
62 if s[0] != 'r':
62 if s[0] != 'r':
63 f[util.normcase(name)] = name
63 f[util.normcase(name)] = name
64 for name in self._dirs:
64 for name in self._dirs:
65 f[util.normcase(name)] = name
65 f[util.normcase(name)] = name
66 f['.'] = '.' # prevents useless util.fspath() invocation
66 f['.'] = '.' # prevents useless util.fspath() invocation
67 return f
67 return f
68
68
69 @repocache('branch')
69 @repocache('branch')
70 def _branch(self):
70 def _branch(self):
71 try:
71 try:
72 return self._opener.read("branch").strip() or "default"
72 return self._opener.read("branch").strip() or "default"
73 except IOError, inst:
73 except IOError, inst:
74 if inst.errno != errno.ENOENT:
74 if inst.errno != errno.ENOENT:
75 raise
75 raise
76 return "default"
76 return "default"
77
77
78 @propertycache
78 @propertycache
79 def _pl(self):
79 def _pl(self):
80 try:
80 try:
81 fp = self._opener("dirstate")
81 fp = self._opener("dirstate")
82 st = fp.read(40)
82 st = fp.read(40)
83 fp.close()
83 fp.close()
84 l = len(st)
84 l = len(st)
85 if l == 40:
85 if l == 40:
86 return st[:20], st[20:40]
86 return st[:20], st[20:40]
87 elif l > 0 and l < 40:
87 elif l > 0 and l < 40:
88 raise util.Abort(_('working directory state appears damaged!'))
88 raise util.Abort(_('working directory state appears damaged!'))
89 except IOError, err:
89 except IOError, err:
90 if err.errno != errno.ENOENT:
90 if err.errno != errno.ENOENT:
91 raise
91 raise
92 return [nullid, nullid]
92 return [nullid, nullid]
93
93
94 @propertycache
94 @propertycache
95 def _dirs(self):
95 def _dirs(self):
96 return scmutil.dirs(self._map, 'r')
96 return scmutil.dirs(self._map, 'r')
97
97
98 def dirs(self):
98 def dirs(self):
99 return self._dirs
99 return self._dirs
100
100
101 @rootcache('.hgignore')
101 @rootcache('.hgignore')
102 def _ignore(self):
102 def _ignore(self):
103 files = [self._join('.hgignore')]
103 files = [self._join('.hgignore')]
104 for name, path in self._ui.configitems("ui"):
104 for name, path in self._ui.configitems("ui"):
105 if name == 'ignore' or name.startswith('ignore.'):
105 if name == 'ignore' or name.startswith('ignore.'):
106 files.append(util.expandpath(path))
106 files.append(util.expandpath(path))
107 return ignore.ignore(self._root, files, self._ui.warn)
107 return ignore.ignore(self._root, files, self._ui.warn)
108
108
109 @propertycache
109 @propertycache
110 def _slash(self):
110 def _slash(self):
111 return self._ui.configbool('ui', 'slash') and os.sep != '/'
111 return self._ui.configbool('ui', 'slash') and os.sep != '/'
112
112
113 @propertycache
113 @propertycache
114 def _checklink(self):
114 def _checklink(self):
115 return util.checklink(self._root)
115 return util.checklink(self._root)
116
116
117 @propertycache
117 @propertycache
118 def _checkexec(self):
118 def _checkexec(self):
119 return util.checkexec(self._root)
119 return util.checkexec(self._root)
120
120
121 @propertycache
121 @propertycache
122 def _checkcase(self):
122 def _checkcase(self):
123 return not util.checkcase(self._join('.hg'))
123 return not util.checkcase(self._join('.hg'))
124
124
125 def _join(self, f):
125 def _join(self, f):
126 # much faster than os.path.join()
126 # much faster than os.path.join()
127 # it's safe because f is always a relative path
127 # it's safe because f is always a relative path
128 return self._rootdir + f
128 return self._rootdir + f
129
129
130 def flagfunc(self, buildfallback):
130 def flagfunc(self, buildfallback):
131 if self._checklink and self._checkexec:
131 if self._checklink and self._checkexec:
132 def f(x):
132 def f(x):
133 try:
133 try:
134 st = os.lstat(self._join(x))
134 st = os.lstat(self._join(x))
135 if util.statislink(st):
135 if util.statislink(st):
136 return 'l'
136 return 'l'
137 if util.statisexec(st):
137 if util.statisexec(st):
138 return 'x'
138 return 'x'
139 except OSError:
139 except OSError:
140 pass
140 pass
141 return ''
141 return ''
142 return f
142 return f
143
143
144 fallback = buildfallback()
144 fallback = buildfallback()
145 if self._checklink:
145 if self._checklink:
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.isexec(self._join(x)):
157 if util.isexec(self._join(x)):
158 return 'x'
158 return 'x'
159 return ''
159 return ''
160 return f
160 return f
161 else:
161 else:
162 return fallback
162 return fallback
163
163
164 @propertycache
164 @propertycache
165 def _cwd(self):
165 def _cwd(self):
166 return os.getcwd()
166 return os.getcwd()
167
167
168 def getcwd(self):
168 def getcwd(self):
169 cwd = self._cwd
169 cwd = self._cwd
170 if cwd == self._root:
170 if cwd == self._root:
171 return ''
171 return ''
172 # self._root ends with a path separator if self._root is '/' or 'C:\'
172 # self._root ends with a path separator if self._root is '/' or 'C:\'
173 rootsep = self._root
173 rootsep = self._root
174 if not util.endswithsep(rootsep):
174 if not util.endswithsep(rootsep):
175 rootsep += os.sep
175 rootsep += os.sep
176 if cwd.startswith(rootsep):
176 if cwd.startswith(rootsep):
177 return cwd[len(rootsep):]
177 return cwd[len(rootsep):]
178 else:
178 else:
179 # we're outside the repo. return an absolute path.
179 # we're outside the repo. return an absolute path.
180 return cwd
180 return cwd
181
181
182 def pathto(self, f, cwd=None):
182 def pathto(self, f, cwd=None):
183 if cwd is None:
183 if cwd is None:
184 cwd = self.getcwd()
184 cwd = self.getcwd()
185 path = util.pathto(self._root, cwd, f)
185 path = util.pathto(self._root, cwd, f)
186 if self._slash:
186 if self._slash:
187 return util.pconvert(path)
187 return util.pconvert(path)
188 return path
188 return path
189
189
190 def __getitem__(self, key):
190 def __getitem__(self, key):
191 '''Return the current state of key (a filename) in the dirstate.
191 '''Return the current state of key (a filename) in the dirstate.
192
192
193 States are:
193 States are:
194 n normal
194 n normal
195 m needs merging
195 m needs merging
196 r marked for removal
196 r marked for removal
197 a marked for addition
197 a marked for addition
198 ? not tracked
198 ? not tracked
199 '''
199 '''
200 return self._map.get(key, ("?",))[0]
200 return self._map.get(key, ("?",))[0]
201
201
202 def __contains__(self, key):
202 def __contains__(self, key):
203 return key in self._map
203 return key in self._map
204
204
205 def __iter__(self):
205 def __iter__(self):
206 for x in sorted(self._map):
206 for x in sorted(self._map):
207 yield x
207 yield x
208
208
209 def iteritems(self):
209 def iteritems(self):
210 return self._map.iteritems()
210 return self._map.iteritems()
211
211
212 def parents(self):
212 def parents(self):
213 return [self._validate(p) for p in self._pl]
213 return [self._validate(p) for p in self._pl]
214
214
215 def p1(self):
215 def p1(self):
216 return self._validate(self._pl[0])
216 return self._validate(self._pl[0])
217
217
218 def p2(self):
218 def p2(self):
219 return self._validate(self._pl[1])
219 return self._validate(self._pl[1])
220
220
221 def branch(self):
221 def branch(self):
222 return encoding.tolocal(self._branch)
222 return encoding.tolocal(self._branch)
223
223
224 def setparents(self, p1, p2=nullid):
224 def setparents(self, p1, p2=nullid):
225 """Set dirstate parents to p1 and p2.
225 """Set dirstate parents to p1 and p2.
226
226
227 When moving from two parents to one, 'm' merged entries a
227 When moving from two parents to one, 'm' merged entries a
228 adjusted to normal and previous copy records discarded and
228 adjusted to normal and previous copy records discarded and
229 returned by the call.
229 returned by the call.
230
230
231 See localrepo.setparents()
231 See localrepo.setparents()
232 """
232 """
233 self._dirty = self._dirtypl = True
233 self._dirty = self._dirtypl = True
234 oldp2 = self._pl[1]
234 oldp2 = self._pl[1]
235 self._pl = p1, p2
235 self._pl = p1, p2
236 copies = {}
236 copies = {}
237 if oldp2 != nullid and p2 == nullid:
237 if oldp2 != nullid and p2 == nullid:
238 # Discard 'm' markers when moving away from a merge state
238 # Discard 'm' markers when moving away from a merge state
239 for f, s in self._map.iteritems():
239 for f, s in self._map.iteritems():
240 if s[0] == 'm':
240 if s[0] == 'm':
241 if f in self._copymap:
241 if f in self._copymap:
242 copies[f] = self._copymap[f]
242 copies[f] = self._copymap[f]
243 self.normallookup(f)
243 self.normallookup(f)
244 return copies
244 return copies
245
245
246 def setbranch(self, branch):
246 def setbranch(self, branch):
247 self._branch = encoding.fromlocal(branch)
247 self._branch = encoding.fromlocal(branch)
248 f = self._opener('branch', 'w', atomictemp=True)
248 f = self._opener('branch', 'w', atomictemp=True)
249 try:
249 try:
250 f.write(self._branch + '\n')
250 f.write(self._branch + '\n')
251 f.close()
251 f.close()
252
252
253 # make sure filecache has the correct stat info for _branch after
253 # make sure filecache has the correct stat info for _branch after
254 # replacing the underlying file
254 # replacing the underlying file
255 ce = self._filecache['_branch']
255 ce = self._filecache['_branch']
256 if ce:
256 if ce:
257 ce.refresh()
257 ce.refresh()
258 except: # re-raises
258 except: # re-raises
259 f.discard()
259 f.discard()
260 raise
260 raise
261
261
262 def _read(self):
262 def _read(self):
263 self._map = {}
263 self._map = {}
264 self._copymap = {}
264 self._copymap = {}
265 try:
265 try:
266 st = self._opener.read("dirstate")
266 st = self._opener.read("dirstate")
267 except IOError, err:
267 except IOError, err:
268 if err.errno != errno.ENOENT:
268 if err.errno != errno.ENOENT:
269 raise
269 raise
270 return
270 return
271 if not st:
271 if not st:
272 return
272 return
273
273
274 # Python's garbage collector triggers a GC each time a certain number
274 # Python's garbage collector triggers a GC each time a certain number
275 # of container objects (the number being defined by
275 # of container objects (the number being defined by
276 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
276 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
277 # for each file in the dirstate. The C version then immediately marks
277 # for each file in the dirstate. The C version then immediately marks
278 # them as not to be tracked by the collector. However, this has no
278 # them as not to be tracked by the collector. However, this has no
279 # effect on when GCs are triggered, only on what objects the GC looks
279 # effect on when GCs are triggered, only on what objects the GC looks
280 # into. This means that O(number of files) GCs are unavoidable.
280 # into. This means that O(number of files) GCs are unavoidable.
281 # Depending on when in the process's lifetime the dirstate is parsed,
281 # Depending on when in the process's lifetime the dirstate is parsed,
282 # this can get very expensive. As a workaround, disable GC while
282 # this can get very expensive. As a workaround, disable GC while
283 # parsing the dirstate.
283 # parsing the dirstate.
284 gcenabled = gc.isenabled()
284 gcenabled = gc.isenabled()
285 gc.disable()
285 gc.disable()
286 try:
286 try:
287 p = parsers.parse_dirstate(self._map, self._copymap, st)
287 p = parsers.parse_dirstate(self._map, self._copymap, st)
288 finally:
288 finally:
289 if gcenabled:
289 if gcenabled:
290 gc.enable()
290 gc.enable()
291 if not self._dirtypl:
291 if not self._dirtypl:
292 self._pl = p
292 self._pl = p
293
293
294 def invalidate(self):
294 def invalidate(self):
295 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
295 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
296 "_ignore"):
296 "_ignore"):
297 if a in self.__dict__:
297 if a in self.__dict__:
298 delattr(self, a)
298 delattr(self, a)
299 self._lastnormaltime = 0
299 self._lastnormaltime = 0
300 self._dirty = False
300 self._dirty = False
301
301
302 def copy(self, source, dest):
302 def copy(self, source, dest):
303 """Mark dest as a copy of source. Unmark dest if source is None."""
303 """Mark dest as a copy of source. Unmark dest if source is None."""
304 if source == dest:
304 if source == dest:
305 return
305 return
306 self._dirty = True
306 self._dirty = True
307 if source is not None:
307 if source is not None:
308 self._copymap[dest] = source
308 self._copymap[dest] = source
309 elif dest in self._copymap:
309 elif dest in self._copymap:
310 del self._copymap[dest]
310 del self._copymap[dest]
311
311
312 def copied(self, file):
312 def copied(self, file):
313 return self._copymap.get(file, None)
313 return self._copymap.get(file, None)
314
314
315 def copies(self):
315 def copies(self):
316 return self._copymap
316 return self._copymap
317
317
318 def _droppath(self, f):
318 def _droppath(self, f):
319 if self[f] not in "?r" and "_dirs" in self.__dict__:
319 if self[f] not in "?r" and "_dirs" in self.__dict__:
320 self._dirs.delpath(f)
320 self._dirs.delpath(f)
321
321
322 def _addpath(self, f, state, mode, size, mtime):
322 def _addpath(self, f, state, mode, size, mtime):
323 oldstate = self[f]
323 oldstate = self[f]
324 if state == 'a' or oldstate == 'r':
324 if state == 'a' or oldstate == 'r':
325 scmutil.checkfilename(f)
325 scmutil.checkfilename(f)
326 if f in self._dirs:
326 if f in self._dirs:
327 raise util.Abort(_('directory %r already in dirstate') % f)
327 raise util.Abort(_('directory %r already in dirstate') % f)
328 # shadows
328 # shadows
329 for d in scmutil.finddirs(f):
329 for d in scmutil.finddirs(f):
330 if d in self._dirs:
330 if d in self._dirs:
331 break
331 break
332 if d in self._map and self[d] != 'r':
332 if d in self._map and self[d] != 'r':
333 raise util.Abort(
333 raise util.Abort(
334 _('file %r in dirstate clashes with %r') % (d, f))
334 _('file %r in dirstate clashes with %r') % (d, f))
335 if oldstate in "?r" and "_dirs" in self.__dict__:
335 if oldstate in "?r" and "_dirs" in self.__dict__:
336 self._dirs.addpath(f)
336 self._dirs.addpath(f)
337 self._dirty = True
337 self._dirty = True
338 self._map[f] = (state, mode, size, mtime)
338 self._map[f] = (state, mode, size, mtime)
339
339
340 def normal(self, f):
340 def normal(self, f):
341 '''Mark a file normal and clean.'''
341 '''Mark a file normal and clean.'''
342 s = os.lstat(self._join(f))
342 s = os.lstat(self._join(f))
343 mtime = int(s.st_mtime)
343 mtime = int(s.st_mtime)
344 self._addpath(f, 'n', s.st_mode,
344 self._addpath(f, 'n', s.st_mode,
345 s.st_size & _rangemask, mtime & _rangemask)
345 s.st_size & _rangemask, mtime & _rangemask)
346 if f in self._copymap:
346 if f in self._copymap:
347 del self._copymap[f]
347 del self._copymap[f]
348 if mtime > self._lastnormaltime:
348 if mtime > self._lastnormaltime:
349 # Remember the most recent modification timeslot for status(),
349 # Remember the most recent modification timeslot for status(),
350 # to make sure we won't miss future size-preserving file content
350 # to make sure we won't miss future size-preserving file content
351 # modifications that happen within the same timeslot.
351 # modifications that happen within the same timeslot.
352 self._lastnormaltime = mtime
352 self._lastnormaltime = mtime
353
353
354 def normallookup(self, f):
354 def normallookup(self, f):
355 '''Mark a file normal, but possibly dirty.'''
355 '''Mark a file normal, but possibly dirty.'''
356 if self._pl[1] != nullid and f in self._map:
356 if self._pl[1] != nullid and f in self._map:
357 # if there is a merge going on and the file was either
357 # if there is a merge going on and the file was either
358 # in state 'm' (-1) or coming from other parent (-2) before
358 # in state 'm' (-1) or coming from other parent (-2) before
359 # being removed, restore that state.
359 # being removed, restore that state.
360 entry = self._map[f]
360 entry = self._map[f]
361 if entry[0] == 'r' and entry[2] in (-1, -2):
361 if entry[0] == 'r' and entry[2] in (-1, -2):
362 source = self._copymap.get(f)
362 source = self._copymap.get(f)
363 if entry[2] == -1:
363 if entry[2] == -1:
364 self.merge(f)
364 self.merge(f)
365 elif entry[2] == -2:
365 elif entry[2] == -2:
366 self.otherparent(f)
366 self.otherparent(f)
367 if source:
367 if source:
368 self.copy(source, f)
368 self.copy(source, f)
369 return
369 return
370 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
370 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
371 return
371 return
372 self._addpath(f, 'n', 0, -1, -1)
372 self._addpath(f, 'n', 0, -1, -1)
373 if f in self._copymap:
373 if f in self._copymap:
374 del self._copymap[f]
374 del self._copymap[f]
375
375
376 def otherparent(self, f):
376 def otherparent(self, f):
377 '''Mark as coming from the other parent, always dirty.'''
377 '''Mark as coming from the other parent, always dirty.'''
378 if self._pl[1] == nullid:
378 if self._pl[1] == nullid:
379 raise util.Abort(_("setting %r to other parent "
379 raise util.Abort(_("setting %r to other parent "
380 "only allowed in merges") % f)
380 "only allowed in merges") % f)
381 self._addpath(f, 'n', 0, -2, -1)
381 self._addpath(f, 'n', 0, -2, -1)
382 if f in self._copymap:
382 if f in self._copymap:
383 del self._copymap[f]
383 del self._copymap[f]
384
384
385 def add(self, f):
385 def add(self, f):
386 '''Mark a file added.'''
386 '''Mark a file added.'''
387 self._addpath(f, 'a', 0, -1, -1)
387 self._addpath(f, 'a', 0, -1, -1)
388 if f in self._copymap:
388 if f in self._copymap:
389 del self._copymap[f]
389 del self._copymap[f]
390
390
391 def remove(self, f):
391 def remove(self, f):
392 '''Mark a file removed.'''
392 '''Mark a file removed.'''
393 self._dirty = True
393 self._dirty = True
394 self._droppath(f)
394 self._droppath(f)
395 size = 0
395 size = 0
396 if self._pl[1] != nullid and f in self._map:
396 if self._pl[1] != nullid and f in self._map:
397 # backup the previous state
397 # backup the previous state
398 entry = self._map[f]
398 entry = self._map[f]
399 if entry[0] == 'm': # merge
399 if entry[0] == 'm': # merge
400 size = -1
400 size = -1
401 elif entry[0] == 'n' and entry[2] == -2: # other parent
401 elif entry[0] == 'n' and entry[2] == -2: # other parent
402 size = -2
402 size = -2
403 self._map[f] = ('r', 0, size, 0)
403 self._map[f] = ('r', 0, size, 0)
404 if size == 0 and f in self._copymap:
404 if size == 0 and f in self._copymap:
405 del self._copymap[f]
405 del self._copymap[f]
406
406
407 def merge(self, f):
407 def merge(self, f):
408 '''Mark a file merged.'''
408 '''Mark a file merged.'''
409 if self._pl[1] == nullid:
409 if self._pl[1] == nullid:
410 return self.normallookup(f)
410 return self.normallookup(f)
411 s = os.lstat(self._join(f))
411 s = os.lstat(self._join(f))
412 self._addpath(f, 'm', s.st_mode,
412 self._addpath(f, 'm', s.st_mode,
413 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
413 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
414 if f in self._copymap:
414 if f in self._copymap:
415 del self._copymap[f]
415 del self._copymap[f]
416
416
417 def drop(self, f):
417 def drop(self, f):
418 '''Drop a file from the dirstate'''
418 '''Drop a file from the dirstate'''
419 if f in self._map:
419 if f in self._map:
420 self._dirty = True
420 self._dirty = True
421 self._droppath(f)
421 self._droppath(f)
422 del self._map[f]
422 del self._map[f]
423
423
424 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
424 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
425 normed = util.normcase(path)
425 normed = util.normcase(path)
426 folded = self._foldmap.get(normed, None)
426 folded = self._foldmap.get(normed, None)
427 if folded is None:
427 if folded is None:
428 if isknown:
428 if isknown:
429 folded = path
429 folded = path
430 else:
430 else:
431 if exists is None:
431 if exists is None:
432 exists = os.path.lexists(os.path.join(self._root, path))
432 exists = os.path.lexists(os.path.join(self._root, path))
433 if not exists:
433 if not exists:
434 # Maybe a path component exists
434 # Maybe a path component exists
435 if not ignoremissing and '/' in path:
435 if not ignoremissing and '/' in path:
436 d, f = path.rsplit('/', 1)
436 d, f = path.rsplit('/', 1)
437 d = self._normalize(d, isknown, ignoremissing, None)
437 d = self._normalize(d, isknown, ignoremissing, None)
438 folded = d + "/" + f
438 folded = d + "/" + f
439 else:
439 else:
440 # No path components, preserve original case
440 # No path components, preserve original case
441 folded = path
441 folded = path
442 else:
442 else:
443 # recursively normalize leading directory components
443 # recursively normalize leading directory components
444 # against dirstate
444 # against dirstate
445 if '/' in normed:
445 if '/' in normed:
446 d, f = normed.rsplit('/', 1)
446 d, f = normed.rsplit('/', 1)
447 d = self._normalize(d, isknown, ignoremissing, True)
447 d = self._normalize(d, isknown, ignoremissing, True)
448 r = self._root + "/" + d
448 r = self._root + "/" + d
449 folded = d + "/" + util.fspath(f, r)
449 folded = d + "/" + util.fspath(f, r)
450 else:
450 else:
451 folded = util.fspath(normed, self._root)
451 folded = util.fspath(normed, self._root)
452 self._foldmap[normed] = folded
452 self._foldmap[normed] = folded
453
453
454 return folded
454 return folded
455
455
456 def normalize(self, path, isknown=False, ignoremissing=False):
456 def normalize(self, path, isknown=False, ignoremissing=False):
457 '''
457 '''
458 normalize the case of a pathname when on a casefolding filesystem
458 normalize the case of a pathname when on a casefolding filesystem
459
459
460 isknown specifies whether the filename came from walking the
460 isknown specifies whether the filename came from walking the
461 disk, to avoid extra filesystem access.
461 disk, to avoid extra filesystem access.
462
462
463 If ignoremissing is True, missing path are returned
463 If ignoremissing is True, missing path are returned
464 unchanged. Otherwise, we try harder to normalize possibly
464 unchanged. Otherwise, we try harder to normalize possibly
465 existing path components.
465 existing path components.
466
466
467 The normalized case is determined based on the following precedence:
467 The normalized case is determined based on the following precedence:
468
468
469 - version of name already stored in the dirstate
469 - version of name already stored in the dirstate
470 - version of name stored on disk
470 - version of name stored on disk
471 - version provided via command arguments
471 - version provided via command arguments
472 '''
472 '''
473
473
474 if self._checkcase:
474 if self._checkcase:
475 return self._normalize(path, isknown, ignoremissing)
475 return self._normalize(path, isknown, ignoremissing)
476 return path
476 return path
477
477
478 def clear(self):
478 def clear(self):
479 self._map = {}
479 self._map = {}
480 if "_dirs" in self.__dict__:
480 if "_dirs" in self.__dict__:
481 delattr(self, "_dirs")
481 delattr(self, "_dirs")
482 self._copymap = {}
482 self._copymap = {}
483 self._pl = [nullid, nullid]
483 self._pl = [nullid, nullid]
484 self._lastnormaltime = 0
484 self._lastnormaltime = 0
485 self._dirty = True
485 self._dirty = True
486
486
487 def rebuild(self, parent, allfiles, changedfiles=None):
487 def rebuild(self, parent, allfiles, changedfiles=None):
488 changedfiles = changedfiles or allfiles
488 changedfiles = changedfiles or allfiles
489 oldmap = self._map
489 oldmap = self._map
490 self.clear()
490 self.clear()
491 for f in allfiles:
491 for f in allfiles:
492 if f not in changedfiles:
492 if f not in changedfiles:
493 self._map[f] = oldmap[f]
493 self._map[f] = oldmap[f]
494 else:
494 else:
495 if 'x' in allfiles.flags(f):
495 if 'x' in allfiles.flags(f):
496 self._map[f] = ('n', 0777, -1, 0)
496 self._map[f] = ('n', 0777, -1, 0)
497 else:
497 else:
498 self._map[f] = ('n', 0666, -1, 0)
498 self._map[f] = ('n', 0666, -1, 0)
499 self._pl = (parent, nullid)
499 self._pl = (parent, nullid)
500 self._dirty = True
500 self._dirty = True
501
501
502 def write(self):
502 def write(self):
503 if not self._dirty:
503 if not self._dirty:
504 return
504 return
505 st = self._opener("dirstate", "w", atomictemp=True)
505 st = self._opener("dirstate", "w", atomictemp=True)
506 # use the modification time of the newly created temporary file as the
506 # use the modification time of the newly created temporary file as the
507 # filesystem's notion of 'now'
507 # filesystem's notion of 'now'
508 now = util.fstat(st).st_mtime
508 now = util.fstat(st).st_mtime
509 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
509 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
510 st.close()
510 st.close()
511 self._lastnormaltime = 0
511 self._lastnormaltime = 0
512 self._dirty = self._dirtypl = False
512 self._dirty = self._dirtypl = False
513
513
514 def _dirignore(self, f):
514 def _dirignore(self, f):
515 if f == '.':
515 if f == '.':
516 return False
516 return False
517 if self._ignore(f):
517 if self._ignore(f):
518 return True
518 return True
519 for p in scmutil.finddirs(f):
519 for p in scmutil.finddirs(f):
520 if self._ignore(p):
520 if self._ignore(p):
521 return True
521 return True
522 return False
522 return False
523
523
524 def _walkexplicit(self, match, subrepos):
524 def _walkexplicit(self, match, subrepos):
525 '''Get stat data about the files explicitly specified by match.
525 '''Get stat data about the files explicitly specified by match.
526
526
527 Return a triple (results, dirsfound, dirsnotfound).
527 Return a triple (results, dirsfound, dirsnotfound).
528 - results is a mapping from filename to stat result. It also contains
528 - results is a mapping from filename to stat result. It also contains
529 listings mapping subrepos and .hg to None.
529 listings mapping subrepos and .hg to None.
530 - dirsfound is a list of files found to be directories.
530 - dirsfound is a list of files found to be directories.
531 - dirsnotfound is a list of files that the dirstate thinks are
531 - dirsnotfound is a list of files that the dirstate thinks are
532 directories and that were not found.'''
532 directories and that were not found.'''
533
533
534 def badtype(mode):
534 def badtype(mode):
535 kind = _('unknown')
535 kind = _('unknown')
536 if stat.S_ISCHR(mode):
536 if stat.S_ISCHR(mode):
537 kind = _('character device')
537 kind = _('character device')
538 elif stat.S_ISBLK(mode):
538 elif stat.S_ISBLK(mode):
539 kind = _('block device')
539 kind = _('block device')
540 elif stat.S_ISFIFO(mode):
540 elif stat.S_ISFIFO(mode):
541 kind = _('fifo')
541 kind = _('fifo')
542 elif stat.S_ISSOCK(mode):
542 elif stat.S_ISSOCK(mode):
543 kind = _('socket')
543 kind = _('socket')
544 elif stat.S_ISDIR(mode):
544 elif stat.S_ISDIR(mode):
545 kind = _('directory')
545 kind = _('directory')
546 return _('unsupported file type (type is %s)') % kind
546 return _('unsupported file type (type is %s)') % kind
547
547
548 matchedir = match.explicitdir
548 matchedir = match.explicitdir
549 badfn = match.bad
549 badfn = match.bad
550 dmap = self._map
550 dmap = self._map
551 normpath = util.normpath
551 normpath = util.normpath
552 lstat = os.lstat
552 lstat = os.lstat
553 getkind = stat.S_IFMT
553 getkind = stat.S_IFMT
554 dirkind = stat.S_IFDIR
554 dirkind = stat.S_IFDIR
555 regkind = stat.S_IFREG
555 regkind = stat.S_IFREG
556 lnkkind = stat.S_IFLNK
556 lnkkind = stat.S_IFLNK
557 join = self._join
557 join = self._join
558 dirsfound = []
558 dirsfound = []
559 foundadd = dirsfound.append
559 foundadd = dirsfound.append
560 dirsnotfound = []
560 dirsnotfound = []
561 notfoundadd = dirsnotfound.append
561 notfoundadd = dirsnotfound.append
562
562
563 if match.matchfn != match.exact and self._checkcase:
563 if match.matchfn != match.exact and self._checkcase:
564 normalize = self._normalize
564 normalize = self._normalize
565 else:
565 else:
566 normalize = None
566 normalize = None
567
567
568 files = sorted(match.files())
568 files = sorted(match.files())
569 subrepos.sort()
569 subrepos.sort()
570 i, j = 0, 0
570 i, j = 0, 0
571 while i < len(files) and j < len(subrepos):
571 while i < len(files) and j < len(subrepos):
572 subpath = subrepos[j] + "/"
572 subpath = subrepos[j] + "/"
573 if files[i] < subpath:
573 if files[i] < subpath:
574 i += 1
574 i += 1
575 continue
575 continue
576 while i < len(files) and files[i].startswith(subpath):
576 while i < len(files) and files[i].startswith(subpath):
577 del files[i]
577 del files[i]
578 j += 1
578 j += 1
579
579
580 if not files or '.' in files:
580 if not files or '.' in files:
581 files = ['']
581 files = ['']
582 results = dict.fromkeys(subrepos)
582 results = dict.fromkeys(subrepos)
583 results['.hg'] = None
583 results['.hg'] = None
584
584
585 for ff in files:
585 for ff in files:
586 if normalize:
586 if normalize:
587 nf = normalize(normpath(ff), False, True)
587 nf = normalize(normpath(ff), False, True)
588 else:
588 else:
589 nf = normpath(ff)
589 nf = normpath(ff)
590 if nf in results:
590 if nf in results:
591 continue
591 continue
592
592
593 try:
593 try:
594 st = lstat(join(nf))
594 st = lstat(join(nf))
595 kind = getkind(st.st_mode)
595 kind = getkind(st.st_mode)
596 if kind == dirkind:
596 if kind == dirkind:
597 if nf in dmap:
597 if nf in dmap:
598 # file replaced by dir on disk but still in dirstate
598 # file replaced by dir on disk but still in dirstate
599 results[nf] = None
599 results[nf] = None
600 if matchedir:
600 if matchedir:
601 matchedir(nf)
601 matchedir(nf)
602 foundadd(nf)
602 foundadd(nf)
603 elif kind == regkind or kind == lnkkind:
603 elif kind == regkind or kind == lnkkind:
604 results[nf] = st
604 results[nf] = st
605 else:
605 else:
606 badfn(ff, badtype(kind))
606 badfn(ff, badtype(kind))
607 if nf in dmap:
607 if nf in dmap:
608 results[nf] = None
608 results[nf] = None
609 except OSError, inst: # nf not found on disk - it is dirstate only
609 except OSError, inst: # nf not found on disk - it is dirstate only
610 if nf in dmap: # does it exactly match a missing file?
610 if nf in dmap: # does it exactly match a missing file?
611 results[nf] = None
611 results[nf] = None
612 else: # does it match a missing directory?
612 else: # does it match a missing directory?
613 prefix = nf + "/"
613 prefix = nf + "/"
614 for fn in dmap:
614 for fn in dmap:
615 if fn.startswith(prefix):
615 if fn.startswith(prefix):
616 if matchedir:
616 if matchedir:
617 matchedir(nf)
617 matchedir(nf)
618 notfoundadd(nf)
618 notfoundadd(nf)
619 break
619 break
620 else:
620 else:
621 badfn(ff, inst.strerror)
621 badfn(ff, inst.strerror)
622
622
623 return results, dirsfound, dirsnotfound
623 return results, dirsfound, dirsnotfound
624
624
625 def walk(self, match, subrepos, unknown, ignored, full=True):
625 def walk(self, match, subrepos, unknown, ignored, full=True):
626 '''
626 '''
627 Walk recursively through the directory tree, finding all files
627 Walk recursively through the directory tree, finding all files
628 matched by match.
628 matched by match.
629
629
630 If full is False, maybe skip some known-clean files.
630 If full is False, maybe skip some known-clean files.
631
631
632 Return a dict mapping filename to stat-like object (either
632 Return a dict mapping filename to stat-like object (either
633 mercurial.osutil.stat instance or return value of os.stat()).
633 mercurial.osutil.stat instance or return value of os.stat()).
634
634
635 '''
635 '''
636 # full is a flag that extensions that hook into walk can use -- this
636 # full is a flag that extensions that hook into walk can use -- this
637 # implementation doesn't use it at all. This satisfies the contract
637 # implementation doesn't use it at all. This satisfies the contract
638 # because we only guarantee a "maybe".
638 # because we only guarantee a "maybe".
639
639
640 def fwarn(f, msg):
641 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
642 return False
643
644 if ignored:
640 if ignored:
645 ignore = util.never
641 ignore = util.never
646 dirignore = util.never
642 dirignore = util.never
647 elif unknown:
643 elif unknown:
648 ignore = self._ignore
644 ignore = self._ignore
649 dirignore = self._dirignore
645 dirignore = self._dirignore
650 else:
646 else:
651 # if not unknown and not ignored, drop dir recursion and step 2
647 # if not unknown and not ignored, drop dir recursion and step 2
652 ignore = util.always
648 ignore = util.always
653 dirignore = util.always
649 dirignore = util.always
654
650
655 matchfn = match.matchfn
651 matchfn = match.matchfn
656 matchalways = match.always()
652 matchalways = match.always()
657 matchtdir = match.traversedir
653 matchtdir = match.traversedir
658 dmap = self._map
654 dmap = self._map
659 listdir = osutil.listdir
655 listdir = osutil.listdir
660 lstat = os.lstat
656 lstat = os.lstat
661 dirkind = stat.S_IFDIR
657 dirkind = stat.S_IFDIR
662 regkind = stat.S_IFREG
658 regkind = stat.S_IFREG
663 lnkkind = stat.S_IFLNK
659 lnkkind = stat.S_IFLNK
664 join = self._join
660 join = self._join
665
661
666 exact = skipstep3 = False
662 exact = skipstep3 = False
667 if matchfn == match.exact: # match.exact
663 if matchfn == match.exact: # match.exact
668 exact = True
664 exact = True
669 dirignore = util.always # skip step 2
665 dirignore = util.always # skip step 2
670 elif match.files() and not match.anypats(): # match.match, no patterns
666 elif match.files() and not match.anypats(): # match.match, no patterns
671 skipstep3 = True
667 skipstep3 = True
672
668
673 if not exact and self._checkcase:
669 if not exact and self._checkcase:
674 normalize = self._normalize
670 normalize = self._normalize
675 skipstep3 = False
671 skipstep3 = False
676 else:
672 else:
677 normalize = None
673 normalize = None
678
674
679 # step 1: find all explicit files
675 # step 1: find all explicit files
680 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
676 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
681
677
682 skipstep3 = skipstep3 and not (work or dirsnotfound)
678 skipstep3 = skipstep3 and not (work or dirsnotfound)
683 work = [d for d in work if not dirignore(d)]
679 work = [d for d in work if not dirignore(d)]
684 wadd = work.append
680 wadd = work.append
685
681
686 # step 2: visit subdirectories
682 # step 2: visit subdirectories
687 while work:
683 while work:
688 nd = work.pop()
684 nd = work.pop()
689 skip = None
685 skip = None
690 if nd == '.':
686 if nd == '.':
691 nd = ''
687 nd = ''
692 else:
688 else:
693 skip = '.hg'
689 skip = '.hg'
694 try:
690 try:
695 entries = listdir(join(nd), stat=True, skip=skip)
691 entries = listdir(join(nd), stat=True, skip=skip)
696 except OSError, inst:
692 except OSError, inst:
697 if inst.errno in (errno.EACCES, errno.ENOENT):
693 if inst.errno in (errno.EACCES, errno.ENOENT):
698 fwarn(nd, inst.strerror)
694 match.bad(self.pathto(nd), inst.strerror)
699 continue
695 continue
700 raise
696 raise
701 for f, kind, st in entries:
697 for f, kind, st in entries:
702 if normalize:
698 if normalize:
703 nf = normalize(nd and (nd + "/" + f) or f, True, True)
699 nf = normalize(nd and (nd + "/" + f) or f, True, True)
704 else:
700 else:
705 nf = nd and (nd + "/" + f) or f
701 nf = nd and (nd + "/" + f) or f
706 if nf not in results:
702 if nf not in results:
707 if kind == dirkind:
703 if kind == dirkind:
708 if not ignore(nf):
704 if not ignore(nf):
709 if matchtdir:
705 if matchtdir:
710 matchtdir(nf)
706 matchtdir(nf)
711 wadd(nf)
707 wadd(nf)
712 if nf in dmap and (matchalways or matchfn(nf)):
708 if nf in dmap and (matchalways or matchfn(nf)):
713 results[nf] = None
709 results[nf] = None
714 elif kind == regkind or kind == lnkkind:
710 elif kind == regkind or kind == lnkkind:
715 if nf in dmap:
711 if nf in dmap:
716 if matchalways or matchfn(nf):
712 if matchalways or matchfn(nf):
717 results[nf] = st
713 results[nf] = st
718 elif (matchalways or matchfn(nf)) and not ignore(nf):
714 elif (matchalways or matchfn(nf)) and not ignore(nf):
719 results[nf] = st
715 results[nf] = st
720 elif nf in dmap and (matchalways or matchfn(nf)):
716 elif nf in dmap and (matchalways or matchfn(nf)):
721 results[nf] = None
717 results[nf] = None
722
718
723 for s in subrepos:
719 for s in subrepos:
724 del results[s]
720 del results[s]
725 del results['.hg']
721 del results['.hg']
726
722
727 # step 3: visit remaining files from dmap
723 # step 3: visit remaining files from dmap
728 if not skipstep3 and not exact:
724 if not skipstep3 and not exact:
729 # If a dmap file is not in results yet, it was either
725 # If a dmap file is not in results yet, it was either
730 # a) not matching matchfn b) ignored, c) missing, or d) under a
726 # a) not matching matchfn b) ignored, c) missing, or d) under a
731 # symlink directory.
727 # symlink directory.
732 if not results and matchalways:
728 if not results and matchalways:
733 visit = dmap.keys()
729 visit = dmap.keys()
734 else:
730 else:
735 visit = [f for f in dmap if f not in results and matchfn(f)]
731 visit = [f for f in dmap if f not in results and matchfn(f)]
736 visit.sort()
732 visit.sort()
737
733
738 if unknown:
734 if unknown:
739 # unknown == True means we walked all dirs under the roots
735 # unknown == True means we walked all dirs under the roots
740 # that wasn't ignored, and everything that matched was stat'ed
736 # that wasn't ignored, and everything that matched was stat'ed
741 # and is already in results.
737 # and is already in results.
742 # The rest must thus be ignored or under a symlink.
738 # The rest must thus be ignored or under a symlink.
743 audit_path = pathutil.pathauditor(self._root)
739 audit_path = pathutil.pathauditor(self._root)
744
740
745 for nf in iter(visit):
741 for nf in iter(visit):
746 # Report ignored items in the dmap as long as they are not
742 # Report ignored items in the dmap as long as they are not
747 # under a symlink directory.
743 # under a symlink directory.
748 if audit_path.check(nf):
744 if audit_path.check(nf):
749 try:
745 try:
750 results[nf] = lstat(join(nf))
746 results[nf] = lstat(join(nf))
751 # file was just ignored, no links, and exists
747 # file was just ignored, no links, and exists
752 except OSError:
748 except OSError:
753 # file doesn't exist
749 # file doesn't exist
754 results[nf] = None
750 results[nf] = None
755 else:
751 else:
756 # It's either missing or under a symlink directory
752 # It's either missing or under a symlink directory
757 # which we in this case report as missing
753 # which we in this case report as missing
758 results[nf] = None
754 results[nf] = None
759 else:
755 else:
760 # We may not have walked the full directory tree above,
756 # We may not have walked the full directory tree above,
761 # so stat and check everything we missed.
757 # so stat and check everything we missed.
762 nf = iter(visit).next
758 nf = iter(visit).next
763 for st in util.statfiles([join(i) for i in visit]):
759 for st in util.statfiles([join(i) for i in visit]):
764 results[nf()] = st
760 results[nf()] = st
765 return results
761 return results
766
762
767 def status(self, match, subrepos, ignored, clean, unknown):
763 def status(self, match, subrepos, ignored, clean, unknown):
768 '''Determine the status of the working copy relative to the
764 '''Determine the status of the working copy relative to the
769 dirstate and return a tuple of lists (unsure, modified, added,
765 dirstate and return a tuple of lists (unsure, modified, added,
770 removed, deleted, unknown, ignored, clean), where:
766 removed, deleted, unknown, ignored, clean), where:
771
767
772 unsure:
768 unsure:
773 files that might have been modified since the dirstate was
769 files that might have been modified since the dirstate was
774 written, but need to be read to be sure (size is the same
770 written, but need to be read to be sure (size is the same
775 but mtime differs)
771 but mtime differs)
776 modified:
772 modified:
777 files that have definitely been modified since the dirstate
773 files that have definitely been modified since the dirstate
778 was written (different size or mode)
774 was written (different size or mode)
779 added:
775 added:
780 files that have been explicitly added with hg add
776 files that have been explicitly added with hg add
781 removed:
777 removed:
782 files that have been explicitly removed with hg remove
778 files that have been explicitly removed with hg remove
783 deleted:
779 deleted:
784 files that have been deleted through other means ("missing")
780 files that have been deleted through other means ("missing")
785 unknown:
781 unknown:
786 files not in the dirstate that are not ignored
782 files not in the dirstate that are not ignored
787 ignored:
783 ignored:
788 files not in the dirstate that are ignored
784 files not in the dirstate that are ignored
789 (by _dirignore())
785 (by _dirignore())
790 clean:
786 clean:
791 files that have definitely not been modified since the
787 files that have definitely not been modified since the
792 dirstate was written
788 dirstate was written
793 '''
789 '''
794 listignored, listclean, listunknown = ignored, clean, unknown
790 listignored, listclean, listunknown = ignored, clean, unknown
795 lookup, modified, added, unknown, ignored = [], [], [], [], []
791 lookup, modified, added, unknown, ignored = [], [], [], [], []
796 removed, deleted, clean = [], [], []
792 removed, deleted, clean = [], [], []
797
793
798 dmap = self._map
794 dmap = self._map
799 ladd = lookup.append # aka "unsure"
795 ladd = lookup.append # aka "unsure"
800 madd = modified.append
796 madd = modified.append
801 aadd = added.append
797 aadd = added.append
802 uadd = unknown.append
798 uadd = unknown.append
803 iadd = ignored.append
799 iadd = ignored.append
804 radd = removed.append
800 radd = removed.append
805 dadd = deleted.append
801 dadd = deleted.append
806 cadd = clean.append
802 cadd = clean.append
807 mexact = match.exact
803 mexact = match.exact
808 dirignore = self._dirignore
804 dirignore = self._dirignore
809 checkexec = self._checkexec
805 checkexec = self._checkexec
810 copymap = self._copymap
806 copymap = self._copymap
811 lastnormaltime = self._lastnormaltime
807 lastnormaltime = self._lastnormaltime
812
808
813 # We need to do full walks when either
809 # We need to do full walks when either
814 # - we're listing all clean files, or
810 # - we're listing all clean files, or
815 # - match.traversedir does something, because match.traversedir should
811 # - match.traversedir does something, because match.traversedir should
816 # be called for every dir in the working dir
812 # be called for every dir in the working dir
817 full = listclean or match.traversedir is not None
813 full = listclean or match.traversedir is not None
818 for fn, st in self.walk(match, subrepos, listunknown, listignored,
814 for fn, st in self.walk(match, subrepos, listunknown, listignored,
819 full=full).iteritems():
815 full=full).iteritems():
820 if fn not in dmap:
816 if fn not in dmap:
821 if (listignored or mexact(fn)) and dirignore(fn):
817 if (listignored or mexact(fn)) and dirignore(fn):
822 if listignored:
818 if listignored:
823 iadd(fn)
819 iadd(fn)
824 else:
820 else:
825 uadd(fn)
821 uadd(fn)
826 continue
822 continue
827
823
828 state, mode, size, time = dmap[fn]
824 state, mode, size, time = dmap[fn]
829
825
830 if not st and state in "nma":
826 if not st and state in "nma":
831 dadd(fn)
827 dadd(fn)
832 elif state == 'n':
828 elif state == 'n':
833 mtime = int(st.st_mtime)
829 mtime = int(st.st_mtime)
834 if (size >= 0 and
830 if (size >= 0 and
835 ((size != st.st_size and size != st.st_size & _rangemask)
831 ((size != st.st_size and size != st.st_size & _rangemask)
836 or ((mode ^ st.st_mode) & 0100 and checkexec))
832 or ((mode ^ st.st_mode) & 0100 and checkexec))
837 or size == -2 # other parent
833 or size == -2 # other parent
838 or fn in copymap):
834 or fn in copymap):
839 madd(fn)
835 madd(fn)
840 elif time != mtime and time != mtime & _rangemask:
836 elif time != mtime and time != mtime & _rangemask:
841 ladd(fn)
837 ladd(fn)
842 elif mtime == lastnormaltime:
838 elif mtime == lastnormaltime:
843 # fn may have been changed in the same timeslot without
839 # fn may have been changed in the same timeslot without
844 # changing its size. This can happen if we quickly do
840 # changing its size. This can happen if we quickly do
845 # multiple commits in a single transaction.
841 # multiple commits in a single transaction.
846 # Force lookup, so we don't miss such a racy file change.
842 # Force lookup, so we don't miss such a racy file change.
847 ladd(fn)
843 ladd(fn)
848 elif listclean:
844 elif listclean:
849 cadd(fn)
845 cadd(fn)
850 elif state == 'm':
846 elif state == 'm':
851 madd(fn)
847 madd(fn)
852 elif state == 'a':
848 elif state == 'a':
853 aadd(fn)
849 aadd(fn)
854 elif state == 'r':
850 elif state == 'r':
855 radd(fn)
851 radd(fn)
856
852
857 return (lookup, modified, added, removed, deleted, unknown, ignored,
853 return (lookup, modified, added, removed, deleted, unknown, ignored,
858 clean)
854 clean)
General Comments 0
You need to be logged in to leave comments. Login now