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