##// END OF EJS Templates
dirstate: introduce function to normalize just filenames...
Siddharth Agarwal -
r24539:3a8eba78 default
parent child Browse files
Show More
@@ -1,929 +1,940 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 _discoverpath(self, path, normed, ignoremissing, exists, storemap):
467 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
468 if exists is None:
468 if exists is None:
469 exists = os.path.lexists(os.path.join(self._root, path))
469 exists = os.path.lexists(os.path.join(self._root, path))
470 if not exists:
470 if not exists:
471 # Maybe a path component exists
471 # Maybe a path component exists
472 if not ignoremissing and '/' in path:
472 if not ignoremissing and '/' in path:
473 d, f = path.rsplit('/', 1)
473 d, f = path.rsplit('/', 1)
474 d = self._normalize(d, False, ignoremissing, None)
474 d = self._normalize(d, False, ignoremissing, None)
475 folded = d + "/" + f
475 folded = d + "/" + f
476 else:
476 else:
477 # No path components, preserve original case
477 # No path components, preserve original case
478 folded = path
478 folded = path
479 else:
479 else:
480 # recursively normalize leading directory components
480 # recursively normalize leading directory components
481 # against dirstate
481 # against dirstate
482 if '/' in normed:
482 if '/' in normed:
483 d, f = normed.rsplit('/', 1)
483 d, f = normed.rsplit('/', 1)
484 d = self._normalize(d, False, ignoremissing, True)
484 d = self._normalize(d, False, ignoremissing, True)
485 r = self._root + "/" + d
485 r = self._root + "/" + d
486 folded = d + "/" + util.fspath(f, r)
486 folded = d + "/" + util.fspath(f, r)
487 else:
487 else:
488 folded = util.fspath(normed, self._root)
488 folded = util.fspath(normed, self._root)
489 storemap[normed] = folded
489 storemap[normed] = folded
490
490
491 return folded
491 return folded
492
492
493 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
494 normed = util.normcase(path)
495 folded = self._foldmap.get(normed, None)
496 if folded is None:
497 if isknown:
498 folded = path
499 else:
500 folded = self._discoverpath(path, normed, ignoremissing, exists,
501 self._foldmap)
502 return folded
503
493 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
504 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
494 normed = util.normcase(path)
505 normed = util.normcase(path)
495 folded = self._foldmap.get(normed, None)
506 folded = self._foldmap.get(normed, None)
496 if folded is None:
507 if folded is None:
497 if isknown:
508 if isknown:
498 folded = path
509 folded = path
499 else:
510 else:
500 folded = self._discoverpath(path, normed, ignoremissing, exists,
511 folded = self._discoverpath(path, normed, ignoremissing, exists,
501 self._foldmap)
512 self._foldmap)
502 return folded
513 return folded
503
514
504 def normalize(self, path, isknown=False, ignoremissing=False):
515 def normalize(self, path, isknown=False, ignoremissing=False):
505 '''
516 '''
506 normalize the case of a pathname when on a casefolding filesystem
517 normalize the case of a pathname when on a casefolding filesystem
507
518
508 isknown specifies whether the filename came from walking the
519 isknown specifies whether the filename came from walking the
509 disk, to avoid extra filesystem access.
520 disk, to avoid extra filesystem access.
510
521
511 If ignoremissing is True, missing path are returned
522 If ignoremissing is True, missing path are returned
512 unchanged. Otherwise, we try harder to normalize possibly
523 unchanged. Otherwise, we try harder to normalize possibly
513 existing path components.
524 existing path components.
514
525
515 The normalized case is determined based on the following precedence:
526 The normalized case is determined based on the following precedence:
516
527
517 - version of name already stored in the dirstate
528 - version of name already stored in the dirstate
518 - version of name stored on disk
529 - version of name stored on disk
519 - version provided via command arguments
530 - version provided via command arguments
520 '''
531 '''
521
532
522 if self._checkcase:
533 if self._checkcase:
523 return self._normalize(path, isknown, ignoremissing)
534 return self._normalize(path, isknown, ignoremissing)
524 return path
535 return path
525
536
526 def clear(self):
537 def clear(self):
527 self._map = {}
538 self._map = {}
528 if "_dirs" in self.__dict__:
539 if "_dirs" in self.__dict__:
529 delattr(self, "_dirs")
540 delattr(self, "_dirs")
530 self._copymap = {}
541 self._copymap = {}
531 self._pl = [nullid, nullid]
542 self._pl = [nullid, nullid]
532 self._lastnormaltime = 0
543 self._lastnormaltime = 0
533 self._dirty = True
544 self._dirty = True
534
545
535 def rebuild(self, parent, allfiles, changedfiles=None):
546 def rebuild(self, parent, allfiles, changedfiles=None):
536 changedfiles = changedfiles or allfiles
547 changedfiles = changedfiles or allfiles
537 oldmap = self._map
548 oldmap = self._map
538 self.clear()
549 self.clear()
539 for f in allfiles:
550 for f in allfiles:
540 if f not in changedfiles:
551 if f not in changedfiles:
541 self._map[f] = oldmap[f]
552 self._map[f] = oldmap[f]
542 else:
553 else:
543 if 'x' in allfiles.flags(f):
554 if 'x' in allfiles.flags(f):
544 self._map[f] = dirstatetuple('n', 0777, -1, 0)
555 self._map[f] = dirstatetuple('n', 0777, -1, 0)
545 else:
556 else:
546 self._map[f] = dirstatetuple('n', 0666, -1, 0)
557 self._map[f] = dirstatetuple('n', 0666, -1, 0)
547 self._pl = (parent, nullid)
558 self._pl = (parent, nullid)
548 self._dirty = True
559 self._dirty = True
549
560
550 def write(self):
561 def write(self):
551 if not self._dirty:
562 if not self._dirty:
552 return
563 return
553
564
554 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
565 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
555 # timestamp of each entries in dirstate, because of 'now > mtime'
566 # timestamp of each entries in dirstate, because of 'now > mtime'
556 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
567 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
557 if delaywrite > 0:
568 if delaywrite > 0:
558 import time # to avoid useless import
569 import time # to avoid useless import
559 time.sleep(delaywrite)
570 time.sleep(delaywrite)
560
571
561 st = self._opener("dirstate", "w", atomictemp=True)
572 st = self._opener("dirstate", "w", atomictemp=True)
562 # use the modification time of the newly created temporary file as the
573 # use the modification time of the newly created temporary file as the
563 # filesystem's notion of 'now'
574 # filesystem's notion of 'now'
564 now = util.fstat(st).st_mtime
575 now = util.fstat(st).st_mtime
565 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
576 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
566 st.close()
577 st.close()
567 self._lastnormaltime = 0
578 self._lastnormaltime = 0
568 self._dirty = self._dirtypl = False
579 self._dirty = self._dirtypl = False
569
580
570 def _dirignore(self, f):
581 def _dirignore(self, f):
571 if f == '.':
582 if f == '.':
572 return False
583 return False
573 if self._ignore(f):
584 if self._ignore(f):
574 return True
585 return True
575 for p in scmutil.finddirs(f):
586 for p in scmutil.finddirs(f):
576 if self._ignore(p):
587 if self._ignore(p):
577 return True
588 return True
578 return False
589 return False
579
590
580 def _walkexplicit(self, match, subrepos):
591 def _walkexplicit(self, match, subrepos):
581 '''Get stat data about the files explicitly specified by match.
592 '''Get stat data about the files explicitly specified by match.
582
593
583 Return a triple (results, dirsfound, dirsnotfound).
594 Return a triple (results, dirsfound, dirsnotfound).
584 - results is a mapping from filename to stat result. It also contains
595 - results is a mapping from filename to stat result. It also contains
585 listings mapping subrepos and .hg to None.
596 listings mapping subrepos and .hg to None.
586 - dirsfound is a list of files found to be directories.
597 - dirsfound is a list of files found to be directories.
587 - dirsnotfound is a list of files that the dirstate thinks are
598 - dirsnotfound is a list of files that the dirstate thinks are
588 directories and that were not found.'''
599 directories and that were not found.'''
589
600
590 def badtype(mode):
601 def badtype(mode):
591 kind = _('unknown')
602 kind = _('unknown')
592 if stat.S_ISCHR(mode):
603 if stat.S_ISCHR(mode):
593 kind = _('character device')
604 kind = _('character device')
594 elif stat.S_ISBLK(mode):
605 elif stat.S_ISBLK(mode):
595 kind = _('block device')
606 kind = _('block device')
596 elif stat.S_ISFIFO(mode):
607 elif stat.S_ISFIFO(mode):
597 kind = _('fifo')
608 kind = _('fifo')
598 elif stat.S_ISSOCK(mode):
609 elif stat.S_ISSOCK(mode):
599 kind = _('socket')
610 kind = _('socket')
600 elif stat.S_ISDIR(mode):
611 elif stat.S_ISDIR(mode):
601 kind = _('directory')
612 kind = _('directory')
602 return _('unsupported file type (type is %s)') % kind
613 return _('unsupported file type (type is %s)') % kind
603
614
604 matchedir = match.explicitdir
615 matchedir = match.explicitdir
605 badfn = match.bad
616 badfn = match.bad
606 dmap = self._map
617 dmap = self._map
607 lstat = os.lstat
618 lstat = os.lstat
608 getkind = stat.S_IFMT
619 getkind = stat.S_IFMT
609 dirkind = stat.S_IFDIR
620 dirkind = stat.S_IFDIR
610 regkind = stat.S_IFREG
621 regkind = stat.S_IFREG
611 lnkkind = stat.S_IFLNK
622 lnkkind = stat.S_IFLNK
612 join = self._join
623 join = self._join
613 dirsfound = []
624 dirsfound = []
614 foundadd = dirsfound.append
625 foundadd = dirsfound.append
615 dirsnotfound = []
626 dirsnotfound = []
616 notfoundadd = dirsnotfound.append
627 notfoundadd = dirsnotfound.append
617
628
618 if not match.isexact() and self._checkcase:
629 if not match.isexact() and self._checkcase:
619 normalize = self._normalize
630 normalize = self._normalize
620 else:
631 else:
621 normalize = None
632 normalize = None
622
633
623 files = sorted(match.files())
634 files = sorted(match.files())
624 subrepos.sort()
635 subrepos.sort()
625 i, j = 0, 0
636 i, j = 0, 0
626 while i < len(files) and j < len(subrepos):
637 while i < len(files) and j < len(subrepos):
627 subpath = subrepos[j] + "/"
638 subpath = subrepos[j] + "/"
628 if files[i] < subpath:
639 if files[i] < subpath:
629 i += 1
640 i += 1
630 continue
641 continue
631 while i < len(files) and files[i].startswith(subpath):
642 while i < len(files) and files[i].startswith(subpath):
632 del files[i]
643 del files[i]
633 j += 1
644 j += 1
634
645
635 if not files or '.' in files:
646 if not files or '.' in files:
636 files = ['.']
647 files = ['.']
637 results = dict.fromkeys(subrepos)
648 results = dict.fromkeys(subrepos)
638 results['.hg'] = None
649 results['.hg'] = None
639
650
640 alldirs = None
651 alldirs = None
641 for ff in files:
652 for ff in files:
642 # constructing the foldmap is expensive, so don't do it for the
653 # constructing the foldmap is expensive, so don't do it for the
643 # common case where files is ['.']
654 # common case where files is ['.']
644 if normalize and ff != '.':
655 if normalize and ff != '.':
645 nf = normalize(ff, False, True)
656 nf = normalize(ff, False, True)
646 else:
657 else:
647 nf = ff
658 nf = ff
648 if nf in results:
659 if nf in results:
649 continue
660 continue
650
661
651 try:
662 try:
652 st = lstat(join(nf))
663 st = lstat(join(nf))
653 kind = getkind(st.st_mode)
664 kind = getkind(st.st_mode)
654 if kind == dirkind:
665 if kind == dirkind:
655 if nf in dmap:
666 if nf in dmap:
656 # file replaced by dir on disk but still in dirstate
667 # file replaced by dir on disk but still in dirstate
657 results[nf] = None
668 results[nf] = None
658 if matchedir:
669 if matchedir:
659 matchedir(nf)
670 matchedir(nf)
660 foundadd(nf)
671 foundadd(nf)
661 elif kind == regkind or kind == lnkkind:
672 elif kind == regkind or kind == lnkkind:
662 results[nf] = st
673 results[nf] = st
663 else:
674 else:
664 badfn(ff, badtype(kind))
675 badfn(ff, badtype(kind))
665 if nf in dmap:
676 if nf in dmap:
666 results[nf] = None
677 results[nf] = None
667 except OSError, inst: # nf not found on disk - it is dirstate only
678 except OSError, inst: # nf not found on disk - it is dirstate only
668 if nf in dmap: # does it exactly match a missing file?
679 if nf in dmap: # does it exactly match a missing file?
669 results[nf] = None
680 results[nf] = None
670 else: # does it match a missing directory?
681 else: # does it match a missing directory?
671 if alldirs is None:
682 if alldirs is None:
672 alldirs = scmutil.dirs(dmap)
683 alldirs = scmutil.dirs(dmap)
673 if nf in alldirs:
684 if nf in alldirs:
674 if matchedir:
685 if matchedir:
675 matchedir(nf)
686 matchedir(nf)
676 notfoundadd(nf)
687 notfoundadd(nf)
677 else:
688 else:
678 badfn(ff, inst.strerror)
689 badfn(ff, inst.strerror)
679
690
680 return results, dirsfound, dirsnotfound
691 return results, dirsfound, dirsnotfound
681
692
682 def walk(self, match, subrepos, unknown, ignored, full=True):
693 def walk(self, match, subrepos, unknown, ignored, full=True):
683 '''
694 '''
684 Walk recursively through the directory tree, finding all files
695 Walk recursively through the directory tree, finding all files
685 matched by match.
696 matched by match.
686
697
687 If full is False, maybe skip some known-clean files.
698 If full is False, maybe skip some known-clean files.
688
699
689 Return a dict mapping filename to stat-like object (either
700 Return a dict mapping filename to stat-like object (either
690 mercurial.osutil.stat instance or return value of os.stat()).
701 mercurial.osutil.stat instance or return value of os.stat()).
691
702
692 '''
703 '''
693 # full is a flag that extensions that hook into walk can use -- this
704 # full is a flag that extensions that hook into walk can use -- this
694 # implementation doesn't use it at all. This satisfies the contract
705 # implementation doesn't use it at all. This satisfies the contract
695 # because we only guarantee a "maybe".
706 # because we only guarantee a "maybe".
696
707
697 if ignored:
708 if ignored:
698 ignore = util.never
709 ignore = util.never
699 dirignore = util.never
710 dirignore = util.never
700 elif unknown:
711 elif unknown:
701 ignore = self._ignore
712 ignore = self._ignore
702 dirignore = self._dirignore
713 dirignore = self._dirignore
703 else:
714 else:
704 # if not unknown and not ignored, drop dir recursion and step 2
715 # if not unknown and not ignored, drop dir recursion and step 2
705 ignore = util.always
716 ignore = util.always
706 dirignore = util.always
717 dirignore = util.always
707
718
708 matchfn = match.matchfn
719 matchfn = match.matchfn
709 matchalways = match.always()
720 matchalways = match.always()
710 matchtdir = match.traversedir
721 matchtdir = match.traversedir
711 dmap = self._map
722 dmap = self._map
712 listdir = osutil.listdir
723 listdir = osutil.listdir
713 lstat = os.lstat
724 lstat = os.lstat
714 dirkind = stat.S_IFDIR
725 dirkind = stat.S_IFDIR
715 regkind = stat.S_IFREG
726 regkind = stat.S_IFREG
716 lnkkind = stat.S_IFLNK
727 lnkkind = stat.S_IFLNK
717 join = self._join
728 join = self._join
718
729
719 exact = skipstep3 = False
730 exact = skipstep3 = False
720 if match.isexact(): # match.exact
731 if match.isexact(): # match.exact
721 exact = True
732 exact = True
722 dirignore = util.always # skip step 2
733 dirignore = util.always # skip step 2
723 elif match.files() and not match.anypats(): # match.match, no patterns
734 elif match.files() and not match.anypats(): # match.match, no patterns
724 skipstep3 = True
735 skipstep3 = True
725
736
726 if not exact and self._checkcase:
737 if not exact and self._checkcase:
727 normalize = self._normalize
738 normalize = self._normalize
728 skipstep3 = False
739 skipstep3 = False
729 else:
740 else:
730 normalize = None
741 normalize = None
731
742
732 # step 1: find all explicit files
743 # step 1: find all explicit files
733 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
744 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
734
745
735 skipstep3 = skipstep3 and not (work or dirsnotfound)
746 skipstep3 = skipstep3 and not (work or dirsnotfound)
736 work = [d for d in work if not dirignore(d)]
747 work = [d for d in work if not dirignore(d)]
737 wadd = work.append
748 wadd = work.append
738
749
739 # step 2: visit subdirectories
750 # step 2: visit subdirectories
740 while work:
751 while work:
741 nd = work.pop()
752 nd = work.pop()
742 skip = None
753 skip = None
743 if nd == '.':
754 if nd == '.':
744 nd = ''
755 nd = ''
745 else:
756 else:
746 skip = '.hg'
757 skip = '.hg'
747 try:
758 try:
748 entries = listdir(join(nd), stat=True, skip=skip)
759 entries = listdir(join(nd), stat=True, skip=skip)
749 except OSError, inst:
760 except OSError, inst:
750 if inst.errno in (errno.EACCES, errno.ENOENT):
761 if inst.errno in (errno.EACCES, errno.ENOENT):
751 match.bad(self.pathto(nd), inst.strerror)
762 match.bad(self.pathto(nd), inst.strerror)
752 continue
763 continue
753 raise
764 raise
754 for f, kind, st in entries:
765 for f, kind, st in entries:
755 if normalize:
766 if normalize:
756 nf = normalize(nd and (nd + "/" + f) or f, True, True)
767 nf = normalize(nd and (nd + "/" + f) or f, True, True)
757 else:
768 else:
758 nf = nd and (nd + "/" + f) or f
769 nf = nd and (nd + "/" + f) or f
759 if nf not in results:
770 if nf not in results:
760 if kind == dirkind:
771 if kind == dirkind:
761 if not ignore(nf):
772 if not ignore(nf):
762 if matchtdir:
773 if matchtdir:
763 matchtdir(nf)
774 matchtdir(nf)
764 wadd(nf)
775 wadd(nf)
765 if nf in dmap and (matchalways or matchfn(nf)):
776 if nf in dmap and (matchalways or matchfn(nf)):
766 results[nf] = None
777 results[nf] = None
767 elif kind == regkind or kind == lnkkind:
778 elif kind == regkind or kind == lnkkind:
768 if nf in dmap:
779 if nf in dmap:
769 if matchalways or matchfn(nf):
780 if matchalways or matchfn(nf):
770 results[nf] = st
781 results[nf] = st
771 elif (matchalways or matchfn(nf)) and not ignore(nf):
782 elif (matchalways or matchfn(nf)) and not ignore(nf):
772 results[nf] = st
783 results[nf] = st
773 elif nf in dmap and (matchalways or matchfn(nf)):
784 elif nf in dmap and (matchalways or matchfn(nf)):
774 results[nf] = None
785 results[nf] = None
775
786
776 for s in subrepos:
787 for s in subrepos:
777 del results[s]
788 del results[s]
778 del results['.hg']
789 del results['.hg']
779
790
780 # step 3: visit remaining files from dmap
791 # step 3: visit remaining files from dmap
781 if not skipstep3 and not exact:
792 if not skipstep3 and not exact:
782 # If a dmap file is not in results yet, it was either
793 # If a dmap file is not in results yet, it was either
783 # a) not matching matchfn b) ignored, c) missing, or d) under a
794 # a) not matching matchfn b) ignored, c) missing, or d) under a
784 # symlink directory.
795 # symlink directory.
785 if not results and matchalways:
796 if not results and matchalways:
786 visit = dmap.keys()
797 visit = dmap.keys()
787 else:
798 else:
788 visit = [f for f in dmap if f not in results and matchfn(f)]
799 visit = [f for f in dmap if f not in results and matchfn(f)]
789 visit.sort()
800 visit.sort()
790
801
791 if unknown:
802 if unknown:
792 # unknown == True means we walked all dirs under the roots
803 # unknown == True means we walked all dirs under the roots
793 # that wasn't ignored, and everything that matched was stat'ed
804 # that wasn't ignored, and everything that matched was stat'ed
794 # and is already in results.
805 # and is already in results.
795 # The rest must thus be ignored or under a symlink.
806 # The rest must thus be ignored or under a symlink.
796 audit_path = pathutil.pathauditor(self._root)
807 audit_path = pathutil.pathauditor(self._root)
797
808
798 for nf in iter(visit):
809 for nf in iter(visit):
799 # Report ignored items in the dmap as long as they are not
810 # Report ignored items in the dmap as long as they are not
800 # under a symlink directory.
811 # under a symlink directory.
801 if audit_path.check(nf):
812 if audit_path.check(nf):
802 try:
813 try:
803 results[nf] = lstat(join(nf))
814 results[nf] = lstat(join(nf))
804 # file was just ignored, no links, and exists
815 # file was just ignored, no links, and exists
805 except OSError:
816 except OSError:
806 # file doesn't exist
817 # file doesn't exist
807 results[nf] = None
818 results[nf] = None
808 else:
819 else:
809 # It's either missing or under a symlink directory
820 # It's either missing or under a symlink directory
810 # which we in this case report as missing
821 # which we in this case report as missing
811 results[nf] = None
822 results[nf] = None
812 else:
823 else:
813 # We may not have walked the full directory tree above,
824 # We may not have walked the full directory tree above,
814 # so stat and check everything we missed.
825 # so stat and check everything we missed.
815 nf = iter(visit).next
826 nf = iter(visit).next
816 for st in util.statfiles([join(i) for i in visit]):
827 for st in util.statfiles([join(i) for i in visit]):
817 results[nf()] = st
828 results[nf()] = st
818 return results
829 return results
819
830
820 def status(self, match, subrepos, ignored, clean, unknown):
831 def status(self, match, subrepos, ignored, clean, unknown):
821 '''Determine the status of the working copy relative to the
832 '''Determine the status of the working copy relative to the
822 dirstate and return a pair of (unsure, status), where status is of type
833 dirstate and return a pair of (unsure, status), where status is of type
823 scmutil.status and:
834 scmutil.status and:
824
835
825 unsure:
836 unsure:
826 files that might have been modified since the dirstate was
837 files that might have been modified since the dirstate was
827 written, but need to be read to be sure (size is the same
838 written, but need to be read to be sure (size is the same
828 but mtime differs)
839 but mtime differs)
829 status.modified:
840 status.modified:
830 files that have definitely been modified since the dirstate
841 files that have definitely been modified since the dirstate
831 was written (different size or mode)
842 was written (different size or mode)
832 status.clean:
843 status.clean:
833 files that have definitely not been modified since the
844 files that have definitely not been modified since the
834 dirstate was written
845 dirstate was written
835 '''
846 '''
836 listignored, listclean, listunknown = ignored, clean, unknown
847 listignored, listclean, listunknown = ignored, clean, unknown
837 lookup, modified, added, unknown, ignored = [], [], [], [], []
848 lookup, modified, added, unknown, ignored = [], [], [], [], []
838 removed, deleted, clean = [], [], []
849 removed, deleted, clean = [], [], []
839
850
840 dmap = self._map
851 dmap = self._map
841 ladd = lookup.append # aka "unsure"
852 ladd = lookup.append # aka "unsure"
842 madd = modified.append
853 madd = modified.append
843 aadd = added.append
854 aadd = added.append
844 uadd = unknown.append
855 uadd = unknown.append
845 iadd = ignored.append
856 iadd = ignored.append
846 radd = removed.append
857 radd = removed.append
847 dadd = deleted.append
858 dadd = deleted.append
848 cadd = clean.append
859 cadd = clean.append
849 mexact = match.exact
860 mexact = match.exact
850 dirignore = self._dirignore
861 dirignore = self._dirignore
851 checkexec = self._checkexec
862 checkexec = self._checkexec
852 copymap = self._copymap
863 copymap = self._copymap
853 lastnormaltime = self._lastnormaltime
864 lastnormaltime = self._lastnormaltime
854
865
855 # We need to do full walks when either
866 # We need to do full walks when either
856 # - we're listing all clean files, or
867 # - we're listing all clean files, or
857 # - match.traversedir does something, because match.traversedir should
868 # - match.traversedir does something, because match.traversedir should
858 # be called for every dir in the working dir
869 # be called for every dir in the working dir
859 full = listclean or match.traversedir is not None
870 full = listclean or match.traversedir is not None
860 for fn, st in self.walk(match, subrepos, listunknown, listignored,
871 for fn, st in self.walk(match, subrepos, listunknown, listignored,
861 full=full).iteritems():
872 full=full).iteritems():
862 if fn not in dmap:
873 if fn not in dmap:
863 if (listignored or mexact(fn)) and dirignore(fn):
874 if (listignored or mexact(fn)) and dirignore(fn):
864 if listignored:
875 if listignored:
865 iadd(fn)
876 iadd(fn)
866 else:
877 else:
867 uadd(fn)
878 uadd(fn)
868 continue
879 continue
869
880
870 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
881 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
871 # written like that for performance reasons. dmap[fn] is not a
882 # written like that for performance reasons. dmap[fn] is not a
872 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
883 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
873 # opcode has fast paths when the value to be unpacked is a tuple or
884 # opcode has fast paths when the value to be unpacked is a tuple or
874 # a list, but falls back to creating a full-fledged iterator in
885 # a list, but falls back to creating a full-fledged iterator in
875 # general. That is much slower than simply accessing and storing the
886 # general. That is much slower than simply accessing and storing the
876 # tuple members one by one.
887 # tuple members one by one.
877 t = dmap[fn]
888 t = dmap[fn]
878 state = t[0]
889 state = t[0]
879 mode = t[1]
890 mode = t[1]
880 size = t[2]
891 size = t[2]
881 time = t[3]
892 time = t[3]
882
893
883 if not st and state in "nma":
894 if not st and state in "nma":
884 dadd(fn)
895 dadd(fn)
885 elif state == 'n':
896 elif state == 'n':
886 mtime = int(st.st_mtime)
897 mtime = int(st.st_mtime)
887 if (size >= 0 and
898 if (size >= 0 and
888 ((size != st.st_size and size != st.st_size & _rangemask)
899 ((size != st.st_size and size != st.st_size & _rangemask)
889 or ((mode ^ st.st_mode) & 0100 and checkexec))
900 or ((mode ^ st.st_mode) & 0100 and checkexec))
890 or size == -2 # other parent
901 or size == -2 # other parent
891 or fn in copymap):
902 or fn in copymap):
892 madd(fn)
903 madd(fn)
893 elif time != mtime and time != mtime & _rangemask:
904 elif time != mtime and time != mtime & _rangemask:
894 ladd(fn)
905 ladd(fn)
895 elif mtime == lastnormaltime:
906 elif mtime == lastnormaltime:
896 # fn may have just been marked as normal and it may have
907 # fn may have just been marked as normal and it may have
897 # changed in the same second without changing its size.
908 # changed in the same second without changing its size.
898 # This can happen if we quickly do multiple commits.
909 # This can happen if we quickly do multiple commits.
899 # Force lookup, so we don't miss such a racy file change.
910 # Force lookup, so we don't miss such a racy file change.
900 ladd(fn)
911 ladd(fn)
901 elif listclean:
912 elif listclean:
902 cadd(fn)
913 cadd(fn)
903 elif state == 'm':
914 elif state == 'm':
904 madd(fn)
915 madd(fn)
905 elif state == 'a':
916 elif state == 'a':
906 aadd(fn)
917 aadd(fn)
907 elif state == 'r':
918 elif state == 'r':
908 radd(fn)
919 radd(fn)
909
920
910 return (lookup, scmutil.status(modified, added, removed, deleted,
921 return (lookup, scmutil.status(modified, added, removed, deleted,
911 unknown, ignored, clean))
922 unknown, ignored, clean))
912
923
913 def matches(self, match):
924 def matches(self, match):
914 '''
925 '''
915 return files in the dirstate (in whatever state) filtered by match
926 return files in the dirstate (in whatever state) filtered by match
916 '''
927 '''
917 dmap = self._map
928 dmap = self._map
918 if match.always():
929 if match.always():
919 return dmap.keys()
930 return dmap.keys()
920 files = match.files()
931 files = match.files()
921 if match.isexact():
932 if match.isexact():
922 # fast path -- filter the other way around, since typically files is
933 # fast path -- filter the other way around, since typically files is
923 # much smaller than dmap
934 # much smaller than dmap
924 return [f for f in files if f in dmap]
935 return [f for f in files if f in dmap]
925 if not match.anypats() and util.all(fn in dmap for fn in files):
936 if not match.anypats() and util.all(fn in dmap for fn in files):
926 # fast path -- all the values are known to be files, so just return
937 # fast path -- all the values are known to be files, so just return
927 # that
938 # that
928 return list(files)
939 return list(files)
929 return [f for f in dmap if match(f)]
940 return [f for f in dmap if match(f)]
General Comments 0
You need to be logged in to leave comments. Login now