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