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