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