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