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