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