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