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