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