##// END OF EJS Templates
dirstate: inline local finish function...
Mads Kiilerich -
r21026:7ee03e19 default
parent child Browse files
Show More
@@ -1,855 +1,851 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
507 def finish(s):
508 st.write(s)
509 st.close()
510 self._lastnormaltime = 0
511 self._dirty = self._dirtypl = False
512
513 # 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
514 # filesystem's notion of 'now'
507 # filesystem's notion of 'now'
515 now = util.fstat(st).st_mtime
508 now = util.fstat(st).st_mtime
516 finish(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()
511 self._lastnormaltime = 0
512 self._dirty = self._dirtypl = False
517
513
518 def _dirignore(self, f):
514 def _dirignore(self, f):
519 if f == '.':
515 if f == '.':
520 return False
516 return False
521 if self._ignore(f):
517 if self._ignore(f):
522 return True
518 return True
523 for p in scmutil.finddirs(f):
519 for p in scmutil.finddirs(f):
524 if self._ignore(p):
520 if self._ignore(p):
525 return True
521 return True
526 return False
522 return False
527
523
528 def _walkexplicit(self, match, subrepos):
524 def _walkexplicit(self, match, subrepos):
529 '''Get stat data about the files explicitly specified by match.
525 '''Get stat data about the files explicitly specified by match.
530
526
531 Return a triple (results, dirsfound, dirsnotfound).
527 Return a triple (results, dirsfound, dirsnotfound).
532 - 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
533 listings mapping subrepos and .hg to None.
529 listings mapping subrepos and .hg to None.
534 - dirsfound is a list of files found to be directories.
530 - dirsfound is a list of files found to be directories.
535 - dirsnotfound is a list of files that the dirstate thinks are
531 - dirsnotfound is a list of files that the dirstate thinks are
536 directories and that were not found.'''
532 directories and that were not found.'''
537
533
538 def badtype(mode):
534 def badtype(mode):
539 kind = _('unknown')
535 kind = _('unknown')
540 if stat.S_ISCHR(mode):
536 if stat.S_ISCHR(mode):
541 kind = _('character device')
537 kind = _('character device')
542 elif stat.S_ISBLK(mode):
538 elif stat.S_ISBLK(mode):
543 kind = _('block device')
539 kind = _('block device')
544 elif stat.S_ISFIFO(mode):
540 elif stat.S_ISFIFO(mode):
545 kind = _('fifo')
541 kind = _('fifo')
546 elif stat.S_ISSOCK(mode):
542 elif stat.S_ISSOCK(mode):
547 kind = _('socket')
543 kind = _('socket')
548 elif stat.S_ISDIR(mode):
544 elif stat.S_ISDIR(mode):
549 kind = _('directory')
545 kind = _('directory')
550 return _('unsupported file type (type is %s)') % kind
546 return _('unsupported file type (type is %s)') % kind
551
547
552 matchedir = match.explicitdir
548 matchedir = match.explicitdir
553 badfn = match.bad
549 badfn = match.bad
554 dmap = self._map
550 dmap = self._map
555 normpath = util.normpath
551 normpath = util.normpath
556 lstat = os.lstat
552 lstat = os.lstat
557 getkind = stat.S_IFMT
553 getkind = stat.S_IFMT
558 dirkind = stat.S_IFDIR
554 dirkind = stat.S_IFDIR
559 regkind = stat.S_IFREG
555 regkind = stat.S_IFREG
560 lnkkind = stat.S_IFLNK
556 lnkkind = stat.S_IFLNK
561 join = self._join
557 join = self._join
562 dirsfound = []
558 dirsfound = []
563 foundadd = dirsfound.append
559 foundadd = dirsfound.append
564 dirsnotfound = []
560 dirsnotfound = []
565 notfoundadd = dirsnotfound.append
561 notfoundadd = dirsnotfound.append
566
562
567 if match.matchfn != match.exact and self._checkcase:
563 if match.matchfn != match.exact and self._checkcase:
568 normalize = self._normalize
564 normalize = self._normalize
569 else:
565 else:
570 normalize = None
566 normalize = None
571
567
572 files = sorted(match.files())
568 files = sorted(match.files())
573 subrepos.sort()
569 subrepos.sort()
574 i, j = 0, 0
570 i, j = 0, 0
575 while i < len(files) and j < len(subrepos):
571 while i < len(files) and j < len(subrepos):
576 subpath = subrepos[j] + "/"
572 subpath = subrepos[j] + "/"
577 if files[i] < subpath:
573 if files[i] < subpath:
578 i += 1
574 i += 1
579 continue
575 continue
580 while i < len(files) and files[i].startswith(subpath):
576 while i < len(files) and files[i].startswith(subpath):
581 del files[i]
577 del files[i]
582 j += 1
578 j += 1
583
579
584 if not files or '.' in files:
580 if not files or '.' in files:
585 files = ['']
581 files = ['']
586 results = dict.fromkeys(subrepos)
582 results = dict.fromkeys(subrepos)
587 results['.hg'] = None
583 results['.hg'] = None
588
584
589 for ff in files:
585 for ff in files:
590 if normalize:
586 if normalize:
591 nf = normalize(normpath(ff), False, True)
587 nf = normalize(normpath(ff), False, True)
592 else:
588 else:
593 nf = normpath(ff)
589 nf = normpath(ff)
594 if nf in results:
590 if nf in results:
595 continue
591 continue
596
592
597 try:
593 try:
598 st = lstat(join(nf))
594 st = lstat(join(nf))
599 kind = getkind(st.st_mode)
595 kind = getkind(st.st_mode)
600 if kind == dirkind:
596 if kind == dirkind:
601 if nf in dmap:
597 if nf in dmap:
602 #file deleted on disk but still in dirstate
598 #file deleted on disk but still in dirstate
603 results[nf] = None
599 results[nf] = None
604 if matchedir:
600 if matchedir:
605 matchedir(nf)
601 matchedir(nf)
606 foundadd(nf)
602 foundadd(nf)
607 elif kind == regkind or kind == lnkkind:
603 elif kind == regkind or kind == lnkkind:
608 results[nf] = st
604 results[nf] = st
609 else:
605 else:
610 badfn(ff, badtype(kind))
606 badfn(ff, badtype(kind))
611 if nf in dmap:
607 if nf in dmap:
612 results[nf] = None
608 results[nf] = None
613 except OSError, inst:
609 except OSError, inst:
614 if nf in dmap: # does it exactly match a file?
610 if nf in dmap: # does it exactly match a file?
615 results[nf] = None
611 results[nf] = None
616 else: # does it match a directory?
612 else: # does it match a directory?
617 prefix = nf + "/"
613 prefix = nf + "/"
618 for fn in dmap:
614 for fn in dmap:
619 if fn.startswith(prefix):
615 if fn.startswith(prefix):
620 if matchedir:
616 if matchedir:
621 matchedir(nf)
617 matchedir(nf)
622 notfoundadd(nf)
618 notfoundadd(nf)
623 break
619 break
624 else:
620 else:
625 badfn(ff, inst.strerror)
621 badfn(ff, inst.strerror)
626
622
627 return results, dirsfound, dirsnotfound
623 return results, dirsfound, dirsnotfound
628
624
629 def walk(self, match, subrepos, unknown, ignored, full=True):
625 def walk(self, match, subrepos, unknown, ignored, full=True):
630 '''
626 '''
631 Walk recursively through the directory tree, finding all files
627 Walk recursively through the directory tree, finding all files
632 matched by match.
628 matched by match.
633
629
634 If full is False, maybe skip some known-clean files.
630 If full is False, maybe skip some known-clean files.
635
631
636 Return a dict mapping filename to stat-like object (either
632 Return a dict mapping filename to stat-like object (either
637 mercurial.osutil.stat instance or return value of os.stat()).
633 mercurial.osutil.stat instance or return value of os.stat()).
638
634
639 '''
635 '''
640 # 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
641 # implementation doesn't use it at all. This satisfies the contract
637 # implementation doesn't use it at all. This satisfies the contract
642 # because we only guarantee a "maybe".
638 # because we only guarantee a "maybe".
643
639
644 def fwarn(f, msg):
640 def fwarn(f, msg):
645 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
641 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
646 return False
642 return False
647
643
648 ignore = self._ignore
644 ignore = self._ignore
649 dirignore = self._dirignore
645 dirignore = self._dirignore
650 if ignored:
646 if ignored:
651 ignore = util.never
647 ignore = util.never
652 dirignore = util.never
648 dirignore = util.never
653 elif not unknown:
649 elif not unknown:
654 # if unknown and ignored are False, skip step 2
650 # if unknown and ignored are False, skip step 2
655 ignore = util.always
651 ignore = util.always
656 dirignore = util.always
652 dirignore = util.always
657
653
658 matchfn = match.matchfn
654 matchfn = match.matchfn
659 matchalways = match.always()
655 matchalways = match.always()
660 matchtdir = match.traversedir
656 matchtdir = match.traversedir
661 dmap = self._map
657 dmap = self._map
662 listdir = osutil.listdir
658 listdir = osutil.listdir
663 lstat = os.lstat
659 lstat = os.lstat
664 dirkind = stat.S_IFDIR
660 dirkind = stat.S_IFDIR
665 regkind = stat.S_IFREG
661 regkind = stat.S_IFREG
666 lnkkind = stat.S_IFLNK
662 lnkkind = stat.S_IFLNK
667 join = self._join
663 join = self._join
668
664
669 exact = skipstep3 = False
665 exact = skipstep3 = False
670 if matchfn == match.exact: # match.exact
666 if matchfn == match.exact: # match.exact
671 exact = True
667 exact = True
672 dirignore = util.always # skip step 2
668 dirignore = util.always # skip step 2
673 elif match.files() and not match.anypats(): # match.match, no patterns
669 elif match.files() and not match.anypats(): # match.match, no patterns
674 skipstep3 = True
670 skipstep3 = True
675
671
676 if not exact and self._checkcase:
672 if not exact and self._checkcase:
677 normalize = self._normalize
673 normalize = self._normalize
678 skipstep3 = False
674 skipstep3 = False
679 else:
675 else:
680 normalize = None
676 normalize = None
681
677
682 # step 1: find all explicit files
678 # step 1: find all explicit files
683 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
679 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
684
680
685 skipstep3 = skipstep3 and not (work or dirsnotfound)
681 skipstep3 = skipstep3 and not (work or dirsnotfound)
686 work = [d for d in work if not dirignore(d)]
682 work = [d for d in work if not dirignore(d)]
687 wadd = work.append
683 wadd = work.append
688
684
689 # step 2: visit subdirectories
685 # step 2: visit subdirectories
690 while work:
686 while work:
691 nd = work.pop()
687 nd = work.pop()
692 skip = None
688 skip = None
693 if nd == '.':
689 if nd == '.':
694 nd = ''
690 nd = ''
695 else:
691 else:
696 skip = '.hg'
692 skip = '.hg'
697 try:
693 try:
698 entries = listdir(join(nd), stat=True, skip=skip)
694 entries = listdir(join(nd), stat=True, skip=skip)
699 except OSError, inst:
695 except OSError, inst:
700 if inst.errno in (errno.EACCES, errno.ENOENT):
696 if inst.errno in (errno.EACCES, errno.ENOENT):
701 fwarn(nd, inst.strerror)
697 fwarn(nd, inst.strerror)
702 continue
698 continue
703 raise
699 raise
704 for f, kind, st in entries:
700 for f, kind, st in entries:
705 if normalize:
701 if normalize:
706 nf = normalize(nd and (nd + "/" + f) or f, True, True)
702 nf = normalize(nd and (nd + "/" + f) or f, True, True)
707 else:
703 else:
708 nf = nd and (nd + "/" + f) or f
704 nf = nd and (nd + "/" + f) or f
709 if nf not in results:
705 if nf not in results:
710 if kind == dirkind:
706 if kind == dirkind:
711 if not ignore(nf):
707 if not ignore(nf):
712 if matchtdir:
708 if matchtdir:
713 matchtdir(nf)
709 matchtdir(nf)
714 wadd(nf)
710 wadd(nf)
715 if nf in dmap and (matchalways or matchfn(nf)):
711 if nf in dmap and (matchalways or matchfn(nf)):
716 results[nf] = None
712 results[nf] = None
717 elif kind == regkind or kind == lnkkind:
713 elif kind == regkind or kind == lnkkind:
718 if nf in dmap:
714 if nf in dmap:
719 if matchalways or matchfn(nf):
715 if matchalways or matchfn(nf):
720 results[nf] = st
716 results[nf] = st
721 elif (matchalways or matchfn(nf)) and not ignore(nf):
717 elif (matchalways or matchfn(nf)) and not ignore(nf):
722 results[nf] = st
718 results[nf] = st
723 elif nf in dmap and (matchalways or matchfn(nf)):
719 elif nf in dmap and (matchalways or matchfn(nf)):
724 results[nf] = None
720 results[nf] = None
725
721
726 for s in subrepos:
722 for s in subrepos:
727 del results[s]
723 del results[s]
728 del results['.hg']
724 del results['.hg']
729
725
730 # step 3: report unseen items in the dmap hash
726 # step 3: report unseen items in the dmap hash
731 if not skipstep3 and not exact:
727 if not skipstep3 and not exact:
732 if not results and matchalways:
728 if not results and matchalways:
733 visit = dmap.keys()
729 visit = dmap.keys()
734 else:
730 else:
735 visit = [f for f in dmap if f not in results and matchfn(f)]
731 visit = [f for f in dmap if f not in results and matchfn(f)]
736 visit.sort()
732 visit.sort()
737
733
738 if unknown:
734 if unknown:
739 # unknown == True means we walked the full directory tree above.
735 # unknown == True means we walked the full directory tree above.
740 # So if a file is not seen it was either a) not matching matchfn
736 # So if a file is not seen it was either a) not matching matchfn
741 # b) ignored, c) missing, or d) under a symlink directory.
737 # b) ignored, c) missing, or d) under a symlink directory.
742 audit_path = pathutil.pathauditor(self._root)
738 audit_path = pathutil.pathauditor(self._root)
743
739
744 for nf in iter(visit):
740 for nf in iter(visit):
745 # Report ignored items in the dmap as long as they are not
741 # Report ignored items in the dmap as long as they are not
746 # under a symlink directory.
742 # under a symlink directory.
747 if audit_path.check(nf):
743 if audit_path.check(nf):
748 try:
744 try:
749 results[nf] = lstat(join(nf))
745 results[nf] = lstat(join(nf))
750 except OSError:
746 except OSError:
751 # file doesn't exist
747 # file doesn't exist
752 results[nf] = None
748 results[nf] = None
753 else:
749 else:
754 # It's either missing or under a symlink directory
750 # It's either missing or under a symlink directory
755 results[nf] = None
751 results[nf] = None
756 else:
752 else:
757 # We may not have walked the full directory tree above,
753 # We may not have walked the full directory tree above,
758 # so stat everything we missed.
754 # so stat everything we missed.
759 nf = iter(visit).next
755 nf = iter(visit).next
760 for st in util.statfiles([join(i) for i in visit]):
756 for st in util.statfiles([join(i) for i in visit]):
761 results[nf()] = st
757 results[nf()] = st
762 return results
758 return results
763
759
764 def status(self, match, subrepos, ignored, clean, unknown):
760 def status(self, match, subrepos, ignored, clean, unknown):
765 '''Determine the status of the working copy relative to the
761 '''Determine the status of the working copy relative to the
766 dirstate and return a tuple of lists (unsure, modified, added,
762 dirstate and return a tuple of lists (unsure, modified, added,
767 removed, deleted, unknown, ignored, clean), where:
763 removed, deleted, unknown, ignored, clean), where:
768
764
769 unsure:
765 unsure:
770 files that might have been modified since the dirstate was
766 files that might have been modified since the dirstate was
771 written, but need to be read to be sure (size is the same
767 written, but need to be read to be sure (size is the same
772 but mtime differs)
768 but mtime differs)
773 modified:
769 modified:
774 files that have definitely been modified since the dirstate
770 files that have definitely been modified since the dirstate
775 was written (different size or mode)
771 was written (different size or mode)
776 added:
772 added:
777 files that have been explicitly added with hg add
773 files that have been explicitly added with hg add
778 removed:
774 removed:
779 files that have been explicitly removed with hg remove
775 files that have been explicitly removed with hg remove
780 deleted:
776 deleted:
781 files that have been deleted through other means ("missing")
777 files that have been deleted through other means ("missing")
782 unknown:
778 unknown:
783 files not in the dirstate that are not ignored
779 files not in the dirstate that are not ignored
784 ignored:
780 ignored:
785 files not in the dirstate that are ignored
781 files not in the dirstate that are ignored
786 (by _dirignore())
782 (by _dirignore())
787 clean:
783 clean:
788 files that have definitely not been modified since the
784 files that have definitely not been modified since the
789 dirstate was written
785 dirstate was written
790 '''
786 '''
791 listignored, listclean, listunknown = ignored, clean, unknown
787 listignored, listclean, listunknown = ignored, clean, unknown
792 lookup, modified, added, unknown, ignored = [], [], [], [], []
788 lookup, modified, added, unknown, ignored = [], [], [], [], []
793 removed, deleted, clean = [], [], []
789 removed, deleted, clean = [], [], []
794
790
795 dmap = self._map
791 dmap = self._map
796 ladd = lookup.append # aka "unsure"
792 ladd = lookup.append # aka "unsure"
797 madd = modified.append
793 madd = modified.append
798 aadd = added.append
794 aadd = added.append
799 uadd = unknown.append
795 uadd = unknown.append
800 iadd = ignored.append
796 iadd = ignored.append
801 radd = removed.append
797 radd = removed.append
802 dadd = deleted.append
798 dadd = deleted.append
803 cadd = clean.append
799 cadd = clean.append
804 mexact = match.exact
800 mexact = match.exact
805 dirignore = self._dirignore
801 dirignore = self._dirignore
806 checkexec = self._checkexec
802 checkexec = self._checkexec
807 copymap = self._copymap
803 copymap = self._copymap
808 lastnormaltime = self._lastnormaltime
804 lastnormaltime = self._lastnormaltime
809
805
810 # We need to do full walks when either
806 # We need to do full walks when either
811 # - we're listing all clean files, or
807 # - we're listing all clean files, or
812 # - match.traversedir does something, because match.traversedir should
808 # - match.traversedir does something, because match.traversedir should
813 # be called for every dir in the working dir
809 # be called for every dir in the working dir
814 full = listclean or match.traversedir is not None
810 full = listclean or match.traversedir is not None
815 for fn, st in self.walk(match, subrepos, listunknown, listignored,
811 for fn, st in self.walk(match, subrepos, listunknown, listignored,
816 full=full).iteritems():
812 full=full).iteritems():
817 if fn not in dmap:
813 if fn not in dmap:
818 if (listignored or mexact(fn)) and dirignore(fn):
814 if (listignored or mexact(fn)) and dirignore(fn):
819 if listignored:
815 if listignored:
820 iadd(fn)
816 iadd(fn)
821 else:
817 else:
822 uadd(fn)
818 uadd(fn)
823 continue
819 continue
824
820
825 state, mode, size, time = dmap[fn]
821 state, mode, size, time = dmap[fn]
826
822
827 if not st and state in "nma":
823 if not st and state in "nma":
828 dadd(fn)
824 dadd(fn)
829 elif state == 'n':
825 elif state == 'n':
830 mtime = int(st.st_mtime)
826 mtime = int(st.st_mtime)
831 if (size >= 0 and
827 if (size >= 0 and
832 ((size != st.st_size and size != st.st_size & _rangemask)
828 ((size != st.st_size and size != st.st_size & _rangemask)
833 or ((mode ^ st.st_mode) & 0100 and checkexec))
829 or ((mode ^ st.st_mode) & 0100 and checkexec))
834 or size == -2 # other parent
830 or size == -2 # other parent
835 or fn in copymap):
831 or fn in copymap):
836 madd(fn)
832 madd(fn)
837 elif time != mtime and time != mtime & _rangemask:
833 elif time != mtime and time != mtime & _rangemask:
838 ladd(fn)
834 ladd(fn)
839 elif mtime == lastnormaltime:
835 elif mtime == lastnormaltime:
840 # fn may have been changed in the same timeslot without
836 # fn may have been changed in the same timeslot without
841 # changing its size. This can happen if we quickly do
837 # changing its size. This can happen if we quickly do
842 # multiple commits in a single transaction.
838 # multiple commits in a single transaction.
843 # Force lookup, so we don't miss such a racy file change.
839 # Force lookup, so we don't miss such a racy file change.
844 ladd(fn)
840 ladd(fn)
845 elif listclean:
841 elif listclean:
846 cadd(fn)
842 cadd(fn)
847 elif state == 'm':
843 elif state == 'm':
848 madd(fn)
844 madd(fn)
849 elif state == 'a':
845 elif state == 'a':
850 aadd(fn)
846 aadd(fn)
851 elif state == 'r':
847 elif state == 'r':
852 radd(fn)
848 radd(fn)
853
849
854 return (lookup, modified, added, removed, deleted, unknown, ignored,
850 return (lookup, modified, added, removed, deleted, unknown, ignored,
855 clean)
851 clean)
General Comments 0
You need to be logged in to leave comments. Login now