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