##// END OF EJS Templates
dirstate.walk: use the file foldmap to normalize...
Siddharth Agarwal -
r24541:e235b5dc default
parent child Browse files
Show More
@@ -1,949 +1,952
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)
680 foundadd(nf)
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 normalize = self._normalize
747 normalizefile = self._normalizefile
748 skipstep3 = False
748 skipstep3 = False
749 else:
749 else:
750 normalize = 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)]
756 work = [d for d in work if not dirignore(d)]
757 wadd = work.append
757 wadd = work.append
758
758
759 # step 2: visit subdirectories
759 # step 2: visit subdirectories
760 while work:
760 while work:
761 nd = work.pop()
761 nd = work.pop()
762 skip = None
762 skip = None
763 if nd == '.':
763 if nd == '.':
764 nd = ''
764 nd = ''
765 else:
765 else:
766 skip = '.hg'
766 skip = '.hg'
767 try:
767 try:
768 entries = listdir(join(nd), stat=True, skip=skip)
768 entries = listdir(join(nd), stat=True, skip=skip)
769 except OSError, inst:
769 except OSError, inst:
770 if inst.errno in (errno.EACCES, errno.ENOENT):
770 if inst.errno in (errno.EACCES, errno.ENOENT):
771 match.bad(self.pathto(nd), inst.strerror)
771 match.bad(self.pathto(nd), inst.strerror)
772 continue
772 continue
773 raise
773 raise
774 for f, kind, st in entries:
774 for f, kind, st in entries:
775 if normalize:
775 if normalizefile:
776 nf = normalize(nd and (nd + "/" + f) or f, True, True)
776 # even though f might be a directory, we're only interested
777 # in comparing it to files currently in the dmap --
778 # therefore normalizefile is enough
779 nf = normalizefile(nd and (nd + "/" + f) or f, True, True)
777 else:
780 else:
778 nf = nd and (nd + "/" + f) or f
781 nf = nd and (nd + "/" + f) or f
779 if nf not in results:
782 if nf not in results:
780 if kind == dirkind:
783 if kind == dirkind:
781 if not ignore(nf):
784 if not ignore(nf):
782 if matchtdir:
785 if matchtdir:
783 matchtdir(nf)
786 matchtdir(nf)
784 wadd(nf)
787 wadd(nf)
785 if nf in dmap and (matchalways or matchfn(nf)):
788 if nf in dmap and (matchalways or matchfn(nf)):
786 results[nf] = None
789 results[nf] = None
787 elif kind == regkind or kind == lnkkind:
790 elif kind == regkind or kind == lnkkind:
788 if nf in dmap:
791 if nf in dmap:
789 if matchalways or matchfn(nf):
792 if matchalways or matchfn(nf):
790 results[nf] = st
793 results[nf] = st
791 elif (matchalways or matchfn(nf)) and not ignore(nf):
794 elif (matchalways or matchfn(nf)) and not ignore(nf):
792 results[nf] = st
795 results[nf] = st
793 elif nf in dmap and (matchalways or matchfn(nf)):
796 elif nf in dmap and (matchalways or matchfn(nf)):
794 results[nf] = None
797 results[nf] = None
795
798
796 for s in subrepos:
799 for s in subrepos:
797 del results[s]
800 del results[s]
798 del results['.hg']
801 del results['.hg']
799
802
800 # step 3: visit remaining files from dmap
803 # step 3: visit remaining files from dmap
801 if not skipstep3 and not exact:
804 if not skipstep3 and not exact:
802 # If a dmap file is not in results yet, it was either
805 # If a dmap file is not in results yet, it was either
803 # a) not matching matchfn b) ignored, c) missing, or d) under a
806 # a) not matching matchfn b) ignored, c) missing, or d) under a
804 # symlink directory.
807 # symlink directory.
805 if not results and matchalways:
808 if not results and matchalways:
806 visit = dmap.keys()
809 visit = dmap.keys()
807 else:
810 else:
808 visit = [f for f in dmap if f not in results and matchfn(f)]
811 visit = [f for f in dmap if f not in results and matchfn(f)]
809 visit.sort()
812 visit.sort()
810
813
811 if unknown:
814 if unknown:
812 # unknown == True means we walked all dirs under the roots
815 # unknown == True means we walked all dirs under the roots
813 # that wasn't ignored, and everything that matched was stat'ed
816 # that wasn't ignored, and everything that matched was stat'ed
814 # and is already in results.
817 # and is already in results.
815 # The rest must thus be ignored or under a symlink.
818 # The rest must thus be ignored or under a symlink.
816 audit_path = pathutil.pathauditor(self._root)
819 audit_path = pathutil.pathauditor(self._root)
817
820
818 for nf in iter(visit):
821 for nf in iter(visit):
819 # Report ignored items in the dmap as long as they are not
822 # Report ignored items in the dmap as long as they are not
820 # under a symlink directory.
823 # under a symlink directory.
821 if audit_path.check(nf):
824 if audit_path.check(nf):
822 try:
825 try:
823 results[nf] = lstat(join(nf))
826 results[nf] = lstat(join(nf))
824 # file was just ignored, no links, and exists
827 # file was just ignored, no links, and exists
825 except OSError:
828 except OSError:
826 # file doesn't exist
829 # file doesn't exist
827 results[nf] = None
830 results[nf] = None
828 else:
831 else:
829 # It's either missing or under a symlink directory
832 # It's either missing or under a symlink directory
830 # which we in this case report as missing
833 # which we in this case report as missing
831 results[nf] = None
834 results[nf] = None
832 else:
835 else:
833 # We may not have walked the full directory tree above,
836 # We may not have walked the full directory tree above,
834 # so stat and check everything we missed.
837 # so stat and check everything we missed.
835 nf = iter(visit).next
838 nf = iter(visit).next
836 for st in util.statfiles([join(i) for i in visit]):
839 for st in util.statfiles([join(i) for i in visit]):
837 results[nf()] = st
840 results[nf()] = st
838 return results
841 return results
839
842
840 def status(self, match, subrepos, ignored, clean, unknown):
843 def status(self, match, subrepos, ignored, clean, unknown):
841 '''Determine the status of the working copy relative to the
844 '''Determine the status of the working copy relative to the
842 dirstate and return a pair of (unsure, status), where status is of type
845 dirstate and return a pair of (unsure, status), where status is of type
843 scmutil.status and:
846 scmutil.status and:
844
847
845 unsure:
848 unsure:
846 files that might have been modified since the dirstate was
849 files that might have been modified since the dirstate was
847 written, but need to be read to be sure (size is the same
850 written, but need to be read to be sure (size is the same
848 but mtime differs)
851 but mtime differs)
849 status.modified:
852 status.modified:
850 files that have definitely been modified since the dirstate
853 files that have definitely been modified since the dirstate
851 was written (different size or mode)
854 was written (different size or mode)
852 status.clean:
855 status.clean:
853 files that have definitely not been modified since the
856 files that have definitely not been modified since the
854 dirstate was written
857 dirstate was written
855 '''
858 '''
856 listignored, listclean, listunknown = ignored, clean, unknown
859 listignored, listclean, listunknown = ignored, clean, unknown
857 lookup, modified, added, unknown, ignored = [], [], [], [], []
860 lookup, modified, added, unknown, ignored = [], [], [], [], []
858 removed, deleted, clean = [], [], []
861 removed, deleted, clean = [], [], []
859
862
860 dmap = self._map
863 dmap = self._map
861 ladd = lookup.append # aka "unsure"
864 ladd = lookup.append # aka "unsure"
862 madd = modified.append
865 madd = modified.append
863 aadd = added.append
866 aadd = added.append
864 uadd = unknown.append
867 uadd = unknown.append
865 iadd = ignored.append
868 iadd = ignored.append
866 radd = removed.append
869 radd = removed.append
867 dadd = deleted.append
870 dadd = deleted.append
868 cadd = clean.append
871 cadd = clean.append
869 mexact = match.exact
872 mexact = match.exact
870 dirignore = self._dirignore
873 dirignore = self._dirignore
871 checkexec = self._checkexec
874 checkexec = self._checkexec
872 copymap = self._copymap
875 copymap = self._copymap
873 lastnormaltime = self._lastnormaltime
876 lastnormaltime = self._lastnormaltime
874
877
875 # We need to do full walks when either
878 # We need to do full walks when either
876 # - we're listing all clean files, or
879 # - we're listing all clean files, or
877 # - match.traversedir does something, because match.traversedir should
880 # - match.traversedir does something, because match.traversedir should
878 # be called for every dir in the working dir
881 # be called for every dir in the working dir
879 full = listclean or match.traversedir is not None
882 full = listclean or match.traversedir is not None
880 for fn, st in self.walk(match, subrepos, listunknown, listignored,
883 for fn, st in self.walk(match, subrepos, listunknown, listignored,
881 full=full).iteritems():
884 full=full).iteritems():
882 if fn not in dmap:
885 if fn not in dmap:
883 if (listignored or mexact(fn)) and dirignore(fn):
886 if (listignored or mexact(fn)) and dirignore(fn):
884 if listignored:
887 if listignored:
885 iadd(fn)
888 iadd(fn)
886 else:
889 else:
887 uadd(fn)
890 uadd(fn)
888 continue
891 continue
889
892
890 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
893 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
891 # written like that for performance reasons. dmap[fn] is not a
894 # written like that for performance reasons. dmap[fn] is not a
892 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
895 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
893 # opcode has fast paths when the value to be unpacked is a tuple or
896 # opcode has fast paths when the value to be unpacked is a tuple or
894 # a list, but falls back to creating a full-fledged iterator in
897 # a list, but falls back to creating a full-fledged iterator in
895 # general. That is much slower than simply accessing and storing the
898 # general. That is much slower than simply accessing and storing the
896 # tuple members one by one.
899 # tuple members one by one.
897 t = dmap[fn]
900 t = dmap[fn]
898 state = t[0]
901 state = t[0]
899 mode = t[1]
902 mode = t[1]
900 size = t[2]
903 size = t[2]
901 time = t[3]
904 time = t[3]
902
905
903 if not st and state in "nma":
906 if not st and state in "nma":
904 dadd(fn)
907 dadd(fn)
905 elif state == 'n':
908 elif state == 'n':
906 mtime = int(st.st_mtime)
909 mtime = int(st.st_mtime)
907 if (size >= 0 and
910 if (size >= 0 and
908 ((size != st.st_size and size != st.st_size & _rangemask)
911 ((size != st.st_size and size != st.st_size & _rangemask)
909 or ((mode ^ st.st_mode) & 0100 and checkexec))
912 or ((mode ^ st.st_mode) & 0100 and checkexec))
910 or size == -2 # other parent
913 or size == -2 # other parent
911 or fn in copymap):
914 or fn in copymap):
912 madd(fn)
915 madd(fn)
913 elif time != mtime and time != mtime & _rangemask:
916 elif time != mtime and time != mtime & _rangemask:
914 ladd(fn)
917 ladd(fn)
915 elif mtime == lastnormaltime:
918 elif mtime == lastnormaltime:
916 # fn may have just been marked as normal and it may have
919 # fn may have just been marked as normal and it may have
917 # changed in the same second without changing its size.
920 # changed in the same second without changing its size.
918 # This can happen if we quickly do multiple commits.
921 # This can happen if we quickly do multiple commits.
919 # Force lookup, so we don't miss such a racy file change.
922 # Force lookup, so we don't miss such a racy file change.
920 ladd(fn)
923 ladd(fn)
921 elif listclean:
924 elif listclean:
922 cadd(fn)
925 cadd(fn)
923 elif state == 'm':
926 elif state == 'm':
924 madd(fn)
927 madd(fn)
925 elif state == 'a':
928 elif state == 'a':
926 aadd(fn)
929 aadd(fn)
927 elif state == 'r':
930 elif state == 'r':
928 radd(fn)
931 radd(fn)
929
932
930 return (lookup, scmutil.status(modified, added, removed, deleted,
933 return (lookup, scmutil.status(modified, added, removed, deleted,
931 unknown, ignored, clean))
934 unknown, ignored, clean))
932
935
933 def matches(self, match):
936 def matches(self, match):
934 '''
937 '''
935 return files in the dirstate (in whatever state) filtered by match
938 return files in the dirstate (in whatever state) filtered by match
936 '''
939 '''
937 dmap = self._map
940 dmap = self._map
938 if match.always():
941 if match.always():
939 return dmap.keys()
942 return dmap.keys()
940 files = match.files()
943 files = match.files()
941 if match.isexact():
944 if match.isexact():
942 # fast path -- filter the other way around, since typically files is
945 # fast path -- filter the other way around, since typically files is
943 # much smaller than dmap
946 # much smaller than dmap
944 return [f for f in files if f in dmap]
947 return [f for f in files if f in dmap]
945 if not match.anypats() and util.all(fn in dmap for fn in files):
948 if not match.anypats() and util.all(fn in dmap for fn in files):
946 # fast path -- all the values are known to be files, so just return
949 # fast path -- all the values are known to be files, so just return
947 # that
950 # that
948 return list(files)
951 return list(files)
949 return [f for f in dmap if match(f)]
952 return [f for f in dmap if match(f)]
General Comments 0
You need to be logged in to leave comments. Login now