##// END OF EJS Templates
dirstate: improve documentation and readability of match and ignore in the walker
Mads Kiilerich -
r21115:1b6e37f4 default
parent child Browse files
Show More
@@ -1,851 +1,858 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 deleted 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:
609 except OSError, inst: # nf not found on disk - it is dirstate only
610 if nf in dmap: # does it exactly match a 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 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):
640 def fwarn(f, msg):
641 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
641 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
642 return False
642 return False
643
643
644 ignore = self._ignore
645 dirignore = self._dirignore
646 if ignored:
644 if ignored:
647 ignore = util.never
645 ignore = util.never
648 dirignore = util.never
646 dirignore = util.never
649 elif not unknown:
647 elif unknown:
650 # if unknown and ignored are False, skip step 2
648 ignore = self._ignore
649 dirignore = self._dirignore
650 else:
651 # if not unknown and not ignored, drop dir recursion and step 2
651 ignore = util.always
652 ignore = util.always
652 dirignore = util.always
653 dirignore = util.always
653
654
654 matchfn = match.matchfn
655 matchfn = match.matchfn
655 matchalways = match.always()
656 matchalways = match.always()
656 matchtdir = match.traversedir
657 matchtdir = match.traversedir
657 dmap = self._map
658 dmap = self._map
658 listdir = osutil.listdir
659 listdir = osutil.listdir
659 lstat = os.lstat
660 lstat = os.lstat
660 dirkind = stat.S_IFDIR
661 dirkind = stat.S_IFDIR
661 regkind = stat.S_IFREG
662 regkind = stat.S_IFREG
662 lnkkind = stat.S_IFLNK
663 lnkkind = stat.S_IFLNK
663 join = self._join
664 join = self._join
664
665
665 exact = skipstep3 = False
666 exact = skipstep3 = False
666 if matchfn == match.exact: # match.exact
667 if matchfn == match.exact: # match.exact
667 exact = True
668 exact = True
668 dirignore = util.always # skip step 2
669 dirignore = util.always # skip step 2
669 elif match.files() and not match.anypats(): # match.match, no patterns
670 elif match.files() and not match.anypats(): # match.match, no patterns
670 skipstep3 = True
671 skipstep3 = True
671
672
672 if not exact and self._checkcase:
673 if not exact and self._checkcase:
673 normalize = self._normalize
674 normalize = self._normalize
674 skipstep3 = False
675 skipstep3 = False
675 else:
676 else:
676 normalize = None
677 normalize = None
677
678
678 # step 1: find all explicit files
679 # step 1: find all explicit files
679 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
680 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
680
681
681 skipstep3 = skipstep3 and not (work or dirsnotfound)
682 skipstep3 = skipstep3 and not (work or dirsnotfound)
682 work = [d for d in work if not dirignore(d)]
683 work = [d for d in work if not dirignore(d)]
683 wadd = work.append
684 wadd = work.append
684
685
685 # step 2: visit subdirectories
686 # step 2: visit subdirectories
686 while work:
687 while work:
687 nd = work.pop()
688 nd = work.pop()
688 skip = None
689 skip = None
689 if nd == '.':
690 if nd == '.':
690 nd = ''
691 nd = ''
691 else:
692 else:
692 skip = '.hg'
693 skip = '.hg'
693 try:
694 try:
694 entries = listdir(join(nd), stat=True, skip=skip)
695 entries = listdir(join(nd), stat=True, skip=skip)
695 except OSError, inst:
696 except OSError, inst:
696 if inst.errno in (errno.EACCES, errno.ENOENT):
697 if inst.errno in (errno.EACCES, errno.ENOENT):
697 fwarn(nd, inst.strerror)
698 fwarn(nd, inst.strerror)
698 continue
699 continue
699 raise
700 raise
700 for f, kind, st in entries:
701 for f, kind, st in entries:
701 if normalize:
702 if normalize:
702 nf = normalize(nd and (nd + "/" + f) or f, True, True)
703 nf = normalize(nd and (nd + "/" + f) or f, True, True)
703 else:
704 else:
704 nf = nd and (nd + "/" + f) or f
705 nf = nd and (nd + "/" + f) or f
705 if nf not in results:
706 if nf not in results:
706 if kind == dirkind:
707 if kind == dirkind:
707 if not ignore(nf):
708 if not ignore(nf):
708 if matchtdir:
709 if matchtdir:
709 matchtdir(nf)
710 matchtdir(nf)
710 wadd(nf)
711 wadd(nf)
711 if nf in dmap and (matchalways or matchfn(nf)):
712 if nf in dmap and (matchalways or matchfn(nf)):
712 results[nf] = None
713 results[nf] = None
713 elif kind == regkind or kind == lnkkind:
714 elif kind == regkind or kind == lnkkind:
714 if nf in dmap:
715 if nf in dmap:
715 if matchalways or matchfn(nf):
716 if matchalways or matchfn(nf):
716 results[nf] = st
717 results[nf] = st
717 elif (matchalways or matchfn(nf)) and not ignore(nf):
718 elif (matchalways or matchfn(nf)) and not ignore(nf):
718 results[nf] = st
719 results[nf] = st
719 elif nf in dmap and (matchalways or matchfn(nf)):
720 elif nf in dmap and (matchalways or matchfn(nf)):
720 results[nf] = None
721 results[nf] = None
721
722
722 for s in subrepos:
723 for s in subrepos:
723 del results[s]
724 del results[s]
724 del results['.hg']
725 del results['.hg']
725
726
726 # step 3: report unseen items in the dmap hash
727 # step 3: visit remaining files from dmap
727 if not skipstep3 and not exact:
728 if not skipstep3 and not exact:
729 # 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
731 # symlink directory.
728 if not results and matchalways:
732 if not results and matchalways:
729 visit = dmap.keys()
733 visit = dmap.keys()
730 else:
734 else:
731 visit = [f for f in dmap if f not in results and matchfn(f)]
735 visit = [f for f in dmap if f not in results and matchfn(f)]
732 visit.sort()
736 visit.sort()
733
737
734 if unknown:
738 if unknown:
735 # unknown == True means we walked the full directory tree above.
739 # unknown == True means we walked all dirs under the roots
736 # So if a file is not seen it was either a) not matching matchfn
740 # that wasn't ignored, and everything that matched was stat'ed
737 # b) ignored, c) missing, or d) under a symlink directory.
741 # and is already in results.
742 # The rest must thus be ignored or under a symlink.
738 audit_path = pathutil.pathauditor(self._root)
743 audit_path = pathutil.pathauditor(self._root)
739
744
740 for nf in iter(visit):
745 for nf in iter(visit):
741 # Report ignored items in the dmap as long as they are not
746 # Report ignored items in the dmap as long as they are not
742 # under a symlink directory.
747 # under a symlink directory.
743 if audit_path.check(nf):
748 if audit_path.check(nf):
744 try:
749 try:
745 results[nf] = lstat(join(nf))
750 results[nf] = lstat(join(nf))
751 # file was just ignored, no links, and exists
746 except OSError:
752 except OSError:
747 # file doesn't exist
753 # file doesn't exist
748 results[nf] = None
754 results[nf] = None
749 else:
755 else:
750 # It's either missing or under a symlink directory
756 # It's either missing or under a symlink directory
757 # which we in this case report as missing
751 results[nf] = None
758 results[nf] = None
752 else:
759 else:
753 # We may not have walked the full directory tree above,
760 # We may not have walked the full directory tree above,
754 # so stat everything we missed.
761 # so stat and check everything we missed.
755 nf = iter(visit).next
762 nf = iter(visit).next
756 for st in util.statfiles([join(i) for i in visit]):
763 for st in util.statfiles([join(i) for i in visit]):
757 results[nf()] = st
764 results[nf()] = st
758 return results
765 return results
759
766
760 def status(self, match, subrepos, ignored, clean, unknown):
767 def status(self, match, subrepos, ignored, clean, unknown):
761 '''Determine the status of the working copy relative to the
768 '''Determine the status of the working copy relative to the
762 dirstate and return a tuple of lists (unsure, modified, added,
769 dirstate and return a tuple of lists (unsure, modified, added,
763 removed, deleted, unknown, ignored, clean), where:
770 removed, deleted, unknown, ignored, clean), where:
764
771
765 unsure:
772 unsure:
766 files that might have been modified since the dirstate was
773 files that might have been modified since the dirstate was
767 written, but need to be read to be sure (size is the same
774 written, but need to be read to be sure (size is the same
768 but mtime differs)
775 but mtime differs)
769 modified:
776 modified:
770 files that have definitely been modified since the dirstate
777 files that have definitely been modified since the dirstate
771 was written (different size or mode)
778 was written (different size or mode)
772 added:
779 added:
773 files that have been explicitly added with hg add
780 files that have been explicitly added with hg add
774 removed:
781 removed:
775 files that have been explicitly removed with hg remove
782 files that have been explicitly removed with hg remove
776 deleted:
783 deleted:
777 files that have been deleted through other means ("missing")
784 files that have been deleted through other means ("missing")
778 unknown:
785 unknown:
779 files not in the dirstate that are not ignored
786 files not in the dirstate that are not ignored
780 ignored:
787 ignored:
781 files not in the dirstate that are ignored
788 files not in the dirstate that are ignored
782 (by _dirignore())
789 (by _dirignore())
783 clean:
790 clean:
784 files that have definitely not been modified since the
791 files that have definitely not been modified since the
785 dirstate was written
792 dirstate was written
786 '''
793 '''
787 listignored, listclean, listunknown = ignored, clean, unknown
794 listignored, listclean, listunknown = ignored, clean, unknown
788 lookup, modified, added, unknown, ignored = [], [], [], [], []
795 lookup, modified, added, unknown, ignored = [], [], [], [], []
789 removed, deleted, clean = [], [], []
796 removed, deleted, clean = [], [], []
790
797
791 dmap = self._map
798 dmap = self._map
792 ladd = lookup.append # aka "unsure"
799 ladd = lookup.append # aka "unsure"
793 madd = modified.append
800 madd = modified.append
794 aadd = added.append
801 aadd = added.append
795 uadd = unknown.append
802 uadd = unknown.append
796 iadd = ignored.append
803 iadd = ignored.append
797 radd = removed.append
804 radd = removed.append
798 dadd = deleted.append
805 dadd = deleted.append
799 cadd = clean.append
806 cadd = clean.append
800 mexact = match.exact
807 mexact = match.exact
801 dirignore = self._dirignore
808 dirignore = self._dirignore
802 checkexec = self._checkexec
809 checkexec = self._checkexec
803 copymap = self._copymap
810 copymap = self._copymap
804 lastnormaltime = self._lastnormaltime
811 lastnormaltime = self._lastnormaltime
805
812
806 # We need to do full walks when either
813 # We need to do full walks when either
807 # - we're listing all clean files, or
814 # - we're listing all clean files, or
808 # - match.traversedir does something, because match.traversedir should
815 # - match.traversedir does something, because match.traversedir should
809 # be called for every dir in the working dir
816 # be called for every dir in the working dir
810 full = listclean or match.traversedir is not None
817 full = listclean or match.traversedir is not None
811 for fn, st in self.walk(match, subrepos, listunknown, listignored,
818 for fn, st in self.walk(match, subrepos, listunknown, listignored,
812 full=full).iteritems():
819 full=full).iteritems():
813 if fn not in dmap:
820 if fn not in dmap:
814 if (listignored or mexact(fn)) and dirignore(fn):
821 if (listignored or mexact(fn)) and dirignore(fn):
815 if listignored:
822 if listignored:
816 iadd(fn)
823 iadd(fn)
817 else:
824 else:
818 uadd(fn)
825 uadd(fn)
819 continue
826 continue
820
827
821 state, mode, size, time = dmap[fn]
828 state, mode, size, time = dmap[fn]
822
829
823 if not st and state in "nma":
830 if not st and state in "nma":
824 dadd(fn)
831 dadd(fn)
825 elif state == 'n':
832 elif state == 'n':
826 mtime = int(st.st_mtime)
833 mtime = int(st.st_mtime)
827 if (size >= 0 and
834 if (size >= 0 and
828 ((size != st.st_size and size != st.st_size & _rangemask)
835 ((size != st.st_size and size != st.st_size & _rangemask)
829 or ((mode ^ st.st_mode) & 0100 and checkexec))
836 or ((mode ^ st.st_mode) & 0100 and checkexec))
830 or size == -2 # other parent
837 or size == -2 # other parent
831 or fn in copymap):
838 or fn in copymap):
832 madd(fn)
839 madd(fn)
833 elif time != mtime and time != mtime & _rangemask:
840 elif time != mtime and time != mtime & _rangemask:
834 ladd(fn)
841 ladd(fn)
835 elif mtime == lastnormaltime:
842 elif mtime == lastnormaltime:
836 # fn may have been changed in the same timeslot without
843 # fn may have been changed in the same timeslot without
837 # changing its size. This can happen if we quickly do
844 # changing its size. This can happen if we quickly do
838 # multiple commits in a single transaction.
845 # multiple commits in a single transaction.
839 # Force lookup, so we don't miss such a racy file change.
846 # Force lookup, so we don't miss such a racy file change.
840 ladd(fn)
847 ladd(fn)
841 elif listclean:
848 elif listclean:
842 cadd(fn)
849 cadd(fn)
843 elif state == 'm':
850 elif state == 'm':
844 madd(fn)
851 madd(fn)
845 elif state == 'a':
852 elif state == 'a':
846 aadd(fn)
853 aadd(fn)
847 elif state == 'r':
854 elif state == 'r':
848 radd(fn)
855 radd(fn)
849
856
850 return (lookup, modified, added, removed, deleted, unknown, ignored,
857 return (lookup, modified, added, removed, deleted, unknown, ignored,
851 clean)
858 clean)
General Comments 0
You need to be logged in to leave comments. Login now