##// END OF EJS Templates
dirstate: isolate node len dependency for the pure version...
Joerg Sonnenberger -
r45778:4f0e03d9 default
parent child Browse files
Show More
@@ -1,1927 +1,1933
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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import collections
10 import collections
11 import contextlib
11 import contextlib
12 import errno
12 import errno
13 import os
13 import os
14 import stat
14 import stat
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import nullid
17 from .node import nullid
18 from .pycompat import delattr
18 from .pycompat import delattr
19
19
20 from hgdemandimport import tracing
20 from hgdemandimport import tracing
21
21
22 from . import (
22 from . import (
23 encoding,
23 encoding,
24 error,
24 error,
25 match as matchmod,
25 match as matchmod,
26 pathutil,
26 pathutil,
27 policy,
27 policy,
28 pycompat,
28 pycompat,
29 scmutil,
29 scmutil,
30 sparse,
30 sparse,
31 txnutil,
31 txnutil,
32 util,
32 util,
33 )
33 )
34
34
35 from .interfaces import (
35 from .interfaces import (
36 dirstate as intdirstate,
36 dirstate as intdirstate,
37 util as interfaceutil,
37 util as interfaceutil,
38 )
38 )
39
39
40 parsers = policy.importmod('parsers')
40 parsers = policy.importmod('parsers')
41 rustmod = policy.importrust('dirstate')
41 rustmod = policy.importrust('dirstate')
42
42
43 propertycache = util.propertycache
43 propertycache = util.propertycache
44 filecache = scmutil.filecache
44 filecache = scmutil.filecache
45 _rangemask = 0x7FFFFFFF
45 _rangemask = 0x7FFFFFFF
46
46
47 dirstatetuple = parsers.dirstatetuple
47 dirstatetuple = parsers.dirstatetuple
48
48
49
49
50 class repocache(filecache):
50 class repocache(filecache):
51 """filecache for files in .hg/"""
51 """filecache for files in .hg/"""
52
52
53 def join(self, obj, fname):
53 def join(self, obj, fname):
54 return obj._opener.join(fname)
54 return obj._opener.join(fname)
55
55
56
56
57 class rootcache(filecache):
57 class rootcache(filecache):
58 """filecache for files in the repository root"""
58 """filecache for files in the repository root"""
59
59
60 def join(self, obj, fname):
60 def join(self, obj, fname):
61 return obj._join(fname)
61 return obj._join(fname)
62
62
63
63
64 def _getfsnow(vfs):
64 def _getfsnow(vfs):
65 '''Get "now" timestamp on filesystem'''
65 '''Get "now" timestamp on filesystem'''
66 tmpfd, tmpname = vfs.mkstemp()
66 tmpfd, tmpname = vfs.mkstemp()
67 try:
67 try:
68 return os.fstat(tmpfd)[stat.ST_MTIME]
68 return os.fstat(tmpfd)[stat.ST_MTIME]
69 finally:
69 finally:
70 os.close(tmpfd)
70 os.close(tmpfd)
71 vfs.unlink(tmpname)
71 vfs.unlink(tmpname)
72
72
73
73
74 @interfaceutil.implementer(intdirstate.idirstate)
74 @interfaceutil.implementer(intdirstate.idirstate)
75 class dirstate(object):
75 class dirstate(object):
76 def __init__(self, opener, ui, root, validate, sparsematchfn):
76 def __init__(self, opener, ui, root, validate, sparsematchfn):
77 '''Create a new dirstate object.
77 '''Create a new dirstate object.
78
78
79 opener is an open()-like callable that can be used to open the
79 opener is an open()-like callable that can be used to open the
80 dirstate file; root is the root of the directory tracked by
80 dirstate file; root is the root of the directory tracked by
81 the dirstate.
81 the dirstate.
82 '''
82 '''
83 self._opener = opener
83 self._opener = opener
84 self._validate = validate
84 self._validate = validate
85 self._root = root
85 self._root = root
86 self._sparsematchfn = sparsematchfn
86 self._sparsematchfn = sparsematchfn
87 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
87 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
88 # UNC path pointing to root share (issue4557)
88 # UNC path pointing to root share (issue4557)
89 self._rootdir = pathutil.normasprefix(root)
89 self._rootdir = pathutil.normasprefix(root)
90 self._dirty = False
90 self._dirty = False
91 self._lastnormaltime = 0
91 self._lastnormaltime = 0
92 self._ui = ui
92 self._ui = ui
93 self._filecache = {}
93 self._filecache = {}
94 self._parentwriters = 0
94 self._parentwriters = 0
95 self._filename = b'dirstate'
95 self._filename = b'dirstate'
96 self._pendingfilename = b'%s.pending' % self._filename
96 self._pendingfilename = b'%s.pending' % self._filename
97 self._plchangecallbacks = {}
97 self._plchangecallbacks = {}
98 self._origpl = None
98 self._origpl = None
99 self._updatedfiles = set()
99 self._updatedfiles = set()
100 self._mapcls = dirstatemap
100 self._mapcls = dirstatemap
101 # Access and cache cwd early, so we don't access it for the first time
101 # Access and cache cwd early, so we don't access it for the first time
102 # after a working-copy update caused it to not exist (accessing it then
102 # after a working-copy update caused it to not exist (accessing it then
103 # raises an exception).
103 # raises an exception).
104 self._cwd
104 self._cwd
105
105
106 def prefetch_parents(self):
106 def prefetch_parents(self):
107 """make sure the parents are loaded
107 """make sure the parents are loaded
108
108
109 Used to avoid a race condition.
109 Used to avoid a race condition.
110 """
110 """
111 self._pl
111 self._pl
112
112
113 @contextlib.contextmanager
113 @contextlib.contextmanager
114 def parentchange(self):
114 def parentchange(self):
115 '''Context manager for handling dirstate parents.
115 '''Context manager for handling dirstate parents.
116
116
117 If an exception occurs in the scope of the context manager,
117 If an exception occurs in the scope of the context manager,
118 the incoherent dirstate won't be written when wlock is
118 the incoherent dirstate won't be written when wlock is
119 released.
119 released.
120 '''
120 '''
121 self._parentwriters += 1
121 self._parentwriters += 1
122 yield
122 yield
123 # Typically we want the "undo" step of a context manager in a
123 # Typically we want the "undo" step of a context manager in a
124 # finally block so it happens even when an exception
124 # finally block so it happens even when an exception
125 # occurs. In this case, however, we only want to decrement
125 # occurs. In this case, however, we only want to decrement
126 # parentwriters if the code in the with statement exits
126 # parentwriters if the code in the with statement exits
127 # normally, so we don't have a try/finally here on purpose.
127 # normally, so we don't have a try/finally here on purpose.
128 self._parentwriters -= 1
128 self._parentwriters -= 1
129
129
130 def pendingparentchange(self):
130 def pendingparentchange(self):
131 '''Returns true if the dirstate is in the middle of a set of changes
131 '''Returns true if the dirstate is in the middle of a set of changes
132 that modify the dirstate parent.
132 that modify the dirstate parent.
133 '''
133 '''
134 return self._parentwriters > 0
134 return self._parentwriters > 0
135
135
136 @propertycache
136 @propertycache
137 def _map(self):
137 def _map(self):
138 """Return the dirstate contents (see documentation for dirstatemap)."""
138 """Return the dirstate contents (see documentation for dirstatemap)."""
139 self._map = self._mapcls(self._ui, self._opener, self._root)
139 self._map = self._mapcls(self._ui, self._opener, self._root)
140 return self._map
140 return self._map
141
141
142 @property
142 @property
143 def _sparsematcher(self):
143 def _sparsematcher(self):
144 """The matcher for the sparse checkout.
144 """The matcher for the sparse checkout.
145
145
146 The working directory may not include every file from a manifest. The
146 The working directory may not include every file from a manifest. The
147 matcher obtained by this property will match a path if it is to be
147 matcher obtained by this property will match a path if it is to be
148 included in the working directory.
148 included in the working directory.
149 """
149 """
150 # TODO there is potential to cache this property. For now, the matcher
150 # TODO there is potential to cache this property. For now, the matcher
151 # is resolved on every access. (But the called function does use a
151 # is resolved on every access. (But the called function does use a
152 # cache to keep the lookup fast.)
152 # cache to keep the lookup fast.)
153 return self._sparsematchfn()
153 return self._sparsematchfn()
154
154
155 @repocache(b'branch')
155 @repocache(b'branch')
156 def _branch(self):
156 def _branch(self):
157 try:
157 try:
158 return self._opener.read(b"branch").strip() or b"default"
158 return self._opener.read(b"branch").strip() or b"default"
159 except IOError as inst:
159 except IOError as inst:
160 if inst.errno != errno.ENOENT:
160 if inst.errno != errno.ENOENT:
161 raise
161 raise
162 return b"default"
162 return b"default"
163
163
164 @property
164 @property
165 def _pl(self):
165 def _pl(self):
166 return self._map.parents()
166 return self._map.parents()
167
167
168 def hasdir(self, d):
168 def hasdir(self, d):
169 return self._map.hastrackeddir(d)
169 return self._map.hastrackeddir(d)
170
170
171 @rootcache(b'.hgignore')
171 @rootcache(b'.hgignore')
172 def _ignore(self):
172 def _ignore(self):
173 files = self._ignorefiles()
173 files = self._ignorefiles()
174 if not files:
174 if not files:
175 return matchmod.never()
175 return matchmod.never()
176
176
177 pats = [b'include:%s' % f for f in files]
177 pats = [b'include:%s' % f for f in files]
178 return matchmod.match(self._root, b'', [], pats, warn=self._ui.warn)
178 return matchmod.match(self._root, b'', [], pats, warn=self._ui.warn)
179
179
180 @propertycache
180 @propertycache
181 def _slash(self):
181 def _slash(self):
182 return self._ui.configbool(b'ui', b'slash') and pycompat.ossep != b'/'
182 return self._ui.configbool(b'ui', b'slash') and pycompat.ossep != b'/'
183
183
184 @propertycache
184 @propertycache
185 def _checklink(self):
185 def _checklink(self):
186 return util.checklink(self._root)
186 return util.checklink(self._root)
187
187
188 @propertycache
188 @propertycache
189 def _checkexec(self):
189 def _checkexec(self):
190 return bool(util.checkexec(self._root))
190 return bool(util.checkexec(self._root))
191
191
192 @propertycache
192 @propertycache
193 def _checkcase(self):
193 def _checkcase(self):
194 return not util.fscasesensitive(self._join(b'.hg'))
194 return not util.fscasesensitive(self._join(b'.hg'))
195
195
196 def _join(self, f):
196 def _join(self, f):
197 # much faster than os.path.join()
197 # much faster than os.path.join()
198 # it's safe because f is always a relative path
198 # it's safe because f is always a relative path
199 return self._rootdir + f
199 return self._rootdir + f
200
200
201 def flagfunc(self, buildfallback):
201 def flagfunc(self, buildfallback):
202 if self._checklink and self._checkexec:
202 if self._checklink and self._checkexec:
203
203
204 def f(x):
204 def f(x):
205 try:
205 try:
206 st = os.lstat(self._join(x))
206 st = os.lstat(self._join(x))
207 if util.statislink(st):
207 if util.statislink(st):
208 return b'l'
208 return b'l'
209 if util.statisexec(st):
209 if util.statisexec(st):
210 return b'x'
210 return b'x'
211 except OSError:
211 except OSError:
212 pass
212 pass
213 return b''
213 return b''
214
214
215 return f
215 return f
216
216
217 fallback = buildfallback()
217 fallback = buildfallback()
218 if self._checklink:
218 if self._checklink:
219
219
220 def f(x):
220 def f(x):
221 if os.path.islink(self._join(x)):
221 if os.path.islink(self._join(x)):
222 return b'l'
222 return b'l'
223 if b'x' in fallback(x):
223 if b'x' in fallback(x):
224 return b'x'
224 return b'x'
225 return b''
225 return b''
226
226
227 return f
227 return f
228 if self._checkexec:
228 if self._checkexec:
229
229
230 def f(x):
230 def f(x):
231 if b'l' in fallback(x):
231 if b'l' in fallback(x):
232 return b'l'
232 return b'l'
233 if util.isexec(self._join(x)):
233 if util.isexec(self._join(x)):
234 return b'x'
234 return b'x'
235 return b''
235 return b''
236
236
237 return f
237 return f
238 else:
238 else:
239 return fallback
239 return fallback
240
240
241 @propertycache
241 @propertycache
242 def _cwd(self):
242 def _cwd(self):
243 # internal config: ui.forcecwd
243 # internal config: ui.forcecwd
244 forcecwd = self._ui.config(b'ui', b'forcecwd')
244 forcecwd = self._ui.config(b'ui', b'forcecwd')
245 if forcecwd:
245 if forcecwd:
246 return forcecwd
246 return forcecwd
247 return encoding.getcwd()
247 return encoding.getcwd()
248
248
249 def getcwd(self):
249 def getcwd(self):
250 '''Return the path from which a canonical path is calculated.
250 '''Return the path from which a canonical path is calculated.
251
251
252 This path should be used to resolve file patterns or to convert
252 This path should be used to resolve file patterns or to convert
253 canonical paths back to file paths for display. It shouldn't be
253 canonical paths back to file paths for display. It shouldn't be
254 used to get real file paths. Use vfs functions instead.
254 used to get real file paths. Use vfs functions instead.
255 '''
255 '''
256 cwd = self._cwd
256 cwd = self._cwd
257 if cwd == self._root:
257 if cwd == self._root:
258 return b''
258 return b''
259 # self._root ends with a path separator if self._root is '/' or 'C:\'
259 # self._root ends with a path separator if self._root is '/' or 'C:\'
260 rootsep = self._root
260 rootsep = self._root
261 if not util.endswithsep(rootsep):
261 if not util.endswithsep(rootsep):
262 rootsep += pycompat.ossep
262 rootsep += pycompat.ossep
263 if cwd.startswith(rootsep):
263 if cwd.startswith(rootsep):
264 return cwd[len(rootsep) :]
264 return cwd[len(rootsep) :]
265 else:
265 else:
266 # we're outside the repo. return an absolute path.
266 # we're outside the repo. return an absolute path.
267 return cwd
267 return cwd
268
268
269 def pathto(self, f, cwd=None):
269 def pathto(self, f, cwd=None):
270 if cwd is None:
270 if cwd is None:
271 cwd = self.getcwd()
271 cwd = self.getcwd()
272 path = util.pathto(self._root, cwd, f)
272 path = util.pathto(self._root, cwd, f)
273 if self._slash:
273 if self._slash:
274 return util.pconvert(path)
274 return util.pconvert(path)
275 return path
275 return path
276
276
277 def __getitem__(self, key):
277 def __getitem__(self, key):
278 '''Return the current state of key (a filename) in the dirstate.
278 '''Return the current state of key (a filename) in the dirstate.
279
279
280 States are:
280 States are:
281 n normal
281 n normal
282 m needs merging
282 m needs merging
283 r marked for removal
283 r marked for removal
284 a marked for addition
284 a marked for addition
285 ? not tracked
285 ? not tracked
286 '''
286 '''
287 return self._map.get(key, (b"?",))[0]
287 return self._map.get(key, (b"?",))[0]
288
288
289 def __contains__(self, key):
289 def __contains__(self, key):
290 return key in self._map
290 return key in self._map
291
291
292 def __iter__(self):
292 def __iter__(self):
293 return iter(sorted(self._map))
293 return iter(sorted(self._map))
294
294
295 def items(self):
295 def items(self):
296 return pycompat.iteritems(self._map)
296 return pycompat.iteritems(self._map)
297
297
298 iteritems = items
298 iteritems = items
299
299
300 def parents(self):
300 def parents(self):
301 return [self._validate(p) for p in self._pl]
301 return [self._validate(p) for p in self._pl]
302
302
303 def p1(self):
303 def p1(self):
304 return self._validate(self._pl[0])
304 return self._validate(self._pl[0])
305
305
306 def p2(self):
306 def p2(self):
307 return self._validate(self._pl[1])
307 return self._validate(self._pl[1])
308
308
309 def branch(self):
309 def branch(self):
310 return encoding.tolocal(self._branch)
310 return encoding.tolocal(self._branch)
311
311
312 def setparents(self, p1, p2=nullid):
312 def setparents(self, p1, p2=nullid):
313 """Set dirstate parents to p1 and p2.
313 """Set dirstate parents to p1 and p2.
314
314
315 When moving from two parents to one, 'm' merged entries a
315 When moving from two parents to one, 'm' merged entries a
316 adjusted to normal and previous copy records discarded and
316 adjusted to normal and previous copy records discarded and
317 returned by the call.
317 returned by the call.
318
318
319 See localrepo.setparents()
319 See localrepo.setparents()
320 """
320 """
321 if self._parentwriters == 0:
321 if self._parentwriters == 0:
322 raise ValueError(
322 raise ValueError(
323 b"cannot set dirstate parent outside of "
323 b"cannot set dirstate parent outside of "
324 b"dirstate.parentchange context manager"
324 b"dirstate.parentchange context manager"
325 )
325 )
326
326
327 self._dirty = True
327 self._dirty = True
328 oldp2 = self._pl[1]
328 oldp2 = self._pl[1]
329 if self._origpl is None:
329 if self._origpl is None:
330 self._origpl = self._pl
330 self._origpl = self._pl
331 self._map.setparents(p1, p2)
331 self._map.setparents(p1, p2)
332 copies = {}
332 copies = {}
333 if oldp2 != nullid and p2 == nullid:
333 if oldp2 != nullid and p2 == nullid:
334 candidatefiles = self._map.nonnormalset.union(
334 candidatefiles = self._map.nonnormalset.union(
335 self._map.otherparentset
335 self._map.otherparentset
336 )
336 )
337 for f in candidatefiles:
337 for f in candidatefiles:
338 s = self._map.get(f)
338 s = self._map.get(f)
339 if s is None:
339 if s is None:
340 continue
340 continue
341
341
342 # Discard 'm' markers when moving away from a merge state
342 # Discard 'm' markers when moving away from a merge state
343 if s[0] == b'm':
343 if s[0] == b'm':
344 source = self._map.copymap.get(f)
344 source = self._map.copymap.get(f)
345 if source:
345 if source:
346 copies[f] = source
346 copies[f] = source
347 self.normallookup(f)
347 self.normallookup(f)
348 # Also fix up otherparent markers
348 # Also fix up otherparent markers
349 elif s[0] == b'n' and s[2] == -2:
349 elif s[0] == b'n' and s[2] == -2:
350 source = self._map.copymap.get(f)
350 source = self._map.copymap.get(f)
351 if source:
351 if source:
352 copies[f] = source
352 copies[f] = source
353 self.add(f)
353 self.add(f)
354 return copies
354 return copies
355
355
356 def setbranch(self, branch):
356 def setbranch(self, branch):
357 self.__class__._branch.set(self, encoding.fromlocal(branch))
357 self.__class__._branch.set(self, encoding.fromlocal(branch))
358 f = self._opener(b'branch', b'w', atomictemp=True, checkambig=True)
358 f = self._opener(b'branch', b'w', atomictemp=True, checkambig=True)
359 try:
359 try:
360 f.write(self._branch + b'\n')
360 f.write(self._branch + b'\n')
361 f.close()
361 f.close()
362
362
363 # make sure filecache has the correct stat info for _branch after
363 # make sure filecache has the correct stat info for _branch after
364 # replacing the underlying file
364 # replacing the underlying file
365 ce = self._filecache[b'_branch']
365 ce = self._filecache[b'_branch']
366 if ce:
366 if ce:
367 ce.refresh()
367 ce.refresh()
368 except: # re-raises
368 except: # re-raises
369 f.discard()
369 f.discard()
370 raise
370 raise
371
371
372 def invalidate(self):
372 def invalidate(self):
373 '''Causes the next access to reread the dirstate.
373 '''Causes the next access to reread the dirstate.
374
374
375 This is different from localrepo.invalidatedirstate() because it always
375 This is different from localrepo.invalidatedirstate() because it always
376 rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
376 rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
377 check whether the dirstate has changed before rereading it.'''
377 check whether the dirstate has changed before rereading it.'''
378
378
379 for a in ("_map", "_branch", "_ignore"):
379 for a in ("_map", "_branch", "_ignore"):
380 if a in self.__dict__:
380 if a in self.__dict__:
381 delattr(self, a)
381 delattr(self, a)
382 self._lastnormaltime = 0
382 self._lastnormaltime = 0
383 self._dirty = False
383 self._dirty = False
384 self._updatedfiles.clear()
384 self._updatedfiles.clear()
385 self._parentwriters = 0
385 self._parentwriters = 0
386 self._origpl = None
386 self._origpl = None
387
387
388 def copy(self, source, dest):
388 def copy(self, source, dest):
389 """Mark dest as a copy of source. Unmark dest if source is None."""
389 """Mark dest as a copy of source. Unmark dest if source is None."""
390 if source == dest:
390 if source == dest:
391 return
391 return
392 self._dirty = True
392 self._dirty = True
393 if source is not None:
393 if source is not None:
394 self._map.copymap[dest] = source
394 self._map.copymap[dest] = source
395 self._updatedfiles.add(source)
395 self._updatedfiles.add(source)
396 self._updatedfiles.add(dest)
396 self._updatedfiles.add(dest)
397 elif self._map.copymap.pop(dest, None):
397 elif self._map.copymap.pop(dest, None):
398 self._updatedfiles.add(dest)
398 self._updatedfiles.add(dest)
399
399
400 def copied(self, file):
400 def copied(self, file):
401 return self._map.copymap.get(file, None)
401 return self._map.copymap.get(file, None)
402
402
403 def copies(self):
403 def copies(self):
404 return self._map.copymap
404 return self._map.copymap
405
405
406 def _addpath(self, f, state, mode, size, mtime):
406 def _addpath(self, f, state, mode, size, mtime):
407 oldstate = self[f]
407 oldstate = self[f]
408 if state == b'a' or oldstate == b'r':
408 if state == b'a' or oldstate == b'r':
409 scmutil.checkfilename(f)
409 scmutil.checkfilename(f)
410 if self._map.hastrackeddir(f):
410 if self._map.hastrackeddir(f):
411 raise error.Abort(
411 raise error.Abort(
412 _(b'directory %r already in dirstate') % pycompat.bytestr(f)
412 _(b'directory %r already in dirstate') % pycompat.bytestr(f)
413 )
413 )
414 # shadows
414 # shadows
415 for d in pathutil.finddirs(f):
415 for d in pathutil.finddirs(f):
416 if self._map.hastrackeddir(d):
416 if self._map.hastrackeddir(d):
417 break
417 break
418 entry = self._map.get(d)
418 entry = self._map.get(d)
419 if entry is not None and entry[0] != b'r':
419 if entry is not None and entry[0] != b'r':
420 raise error.Abort(
420 raise error.Abort(
421 _(b'file %r in dirstate clashes with %r')
421 _(b'file %r in dirstate clashes with %r')
422 % (pycompat.bytestr(d), pycompat.bytestr(f))
422 % (pycompat.bytestr(d), pycompat.bytestr(f))
423 )
423 )
424 self._dirty = True
424 self._dirty = True
425 self._updatedfiles.add(f)
425 self._updatedfiles.add(f)
426 self._map.addfile(f, oldstate, state, mode, size, mtime)
426 self._map.addfile(f, oldstate, state, mode, size, mtime)
427
427
428 def normal(self, f, parentfiledata=None):
428 def normal(self, f, parentfiledata=None):
429 '''Mark a file normal and clean.
429 '''Mark a file normal and clean.
430
430
431 parentfiledata: (mode, size, mtime) of the clean file
431 parentfiledata: (mode, size, mtime) of the clean file
432
432
433 parentfiledata should be computed from memory (for mode,
433 parentfiledata should be computed from memory (for mode,
434 size), as or close as possible from the point where we
434 size), as or close as possible from the point where we
435 determined the file was clean, to limit the risk of the
435 determined the file was clean, to limit the risk of the
436 file having been changed by an external process between the
436 file having been changed by an external process between the
437 moment where the file was determined to be clean and now.'''
437 moment where the file was determined to be clean and now.'''
438 if parentfiledata:
438 if parentfiledata:
439 (mode, size, mtime) = parentfiledata
439 (mode, size, mtime) = parentfiledata
440 else:
440 else:
441 s = os.lstat(self._join(f))
441 s = os.lstat(self._join(f))
442 mode = s.st_mode
442 mode = s.st_mode
443 size = s.st_size
443 size = s.st_size
444 mtime = s[stat.ST_MTIME]
444 mtime = s[stat.ST_MTIME]
445 self._addpath(f, b'n', mode, size & _rangemask, mtime & _rangemask)
445 self._addpath(f, b'n', mode, size & _rangemask, mtime & _rangemask)
446 self._map.copymap.pop(f, None)
446 self._map.copymap.pop(f, None)
447 if f in self._map.nonnormalset:
447 if f in self._map.nonnormalset:
448 self._map.nonnormalset.remove(f)
448 self._map.nonnormalset.remove(f)
449 if mtime > self._lastnormaltime:
449 if mtime > self._lastnormaltime:
450 # Remember the most recent modification timeslot for status(),
450 # Remember the most recent modification timeslot for status(),
451 # to make sure we won't miss future size-preserving file content
451 # to make sure we won't miss future size-preserving file content
452 # modifications that happen within the same timeslot.
452 # modifications that happen within the same timeslot.
453 self._lastnormaltime = mtime
453 self._lastnormaltime = mtime
454
454
455 def normallookup(self, f):
455 def normallookup(self, f):
456 '''Mark a file normal, but possibly dirty.'''
456 '''Mark a file normal, but possibly dirty.'''
457 if self._pl[1] != nullid:
457 if self._pl[1] != nullid:
458 # if there is a merge going on and the file was either
458 # if there is a merge going on and the file was either
459 # in state 'm' (-1) or coming from other parent (-2) before
459 # in state 'm' (-1) or coming from other parent (-2) before
460 # being removed, restore that state.
460 # being removed, restore that state.
461 entry = self._map.get(f)
461 entry = self._map.get(f)
462 if entry is not None:
462 if entry is not None:
463 if entry[0] == b'r' and entry[2] in (-1, -2):
463 if entry[0] == b'r' and entry[2] in (-1, -2):
464 source = self._map.copymap.get(f)
464 source = self._map.copymap.get(f)
465 if entry[2] == -1:
465 if entry[2] == -1:
466 self.merge(f)
466 self.merge(f)
467 elif entry[2] == -2:
467 elif entry[2] == -2:
468 self.otherparent(f)
468 self.otherparent(f)
469 if source:
469 if source:
470 self.copy(source, f)
470 self.copy(source, f)
471 return
471 return
472 if entry[0] == b'm' or entry[0] == b'n' and entry[2] == -2:
472 if entry[0] == b'm' or entry[0] == b'n' and entry[2] == -2:
473 return
473 return
474 self._addpath(f, b'n', 0, -1, -1)
474 self._addpath(f, b'n', 0, -1, -1)
475 self._map.copymap.pop(f, None)
475 self._map.copymap.pop(f, None)
476
476
477 def otherparent(self, f):
477 def otherparent(self, f):
478 '''Mark as coming from the other parent, always dirty.'''
478 '''Mark as coming from the other parent, always dirty.'''
479 if self._pl[1] == nullid:
479 if self._pl[1] == nullid:
480 raise error.Abort(
480 raise error.Abort(
481 _(b"setting %r to other parent only allowed in merges") % f
481 _(b"setting %r to other parent only allowed in merges") % f
482 )
482 )
483 if f in self and self[f] == b'n':
483 if f in self and self[f] == b'n':
484 # merge-like
484 # merge-like
485 self._addpath(f, b'm', 0, -2, -1)
485 self._addpath(f, b'm', 0, -2, -1)
486 else:
486 else:
487 # add-like
487 # add-like
488 self._addpath(f, b'n', 0, -2, -1)
488 self._addpath(f, b'n', 0, -2, -1)
489 self._map.copymap.pop(f, None)
489 self._map.copymap.pop(f, None)
490
490
491 def add(self, f):
491 def add(self, f):
492 '''Mark a file added.'''
492 '''Mark a file added.'''
493 self._addpath(f, b'a', 0, -1, -1)
493 self._addpath(f, b'a', 0, -1, -1)
494 self._map.copymap.pop(f, None)
494 self._map.copymap.pop(f, None)
495
495
496 def remove(self, f):
496 def remove(self, f):
497 '''Mark a file removed.'''
497 '''Mark a file removed.'''
498 self._dirty = True
498 self._dirty = True
499 oldstate = self[f]
499 oldstate = self[f]
500 size = 0
500 size = 0
501 if self._pl[1] != nullid:
501 if self._pl[1] != nullid:
502 entry = self._map.get(f)
502 entry = self._map.get(f)
503 if entry is not None:
503 if entry is not None:
504 # backup the previous state
504 # backup the previous state
505 if entry[0] == b'm': # merge
505 if entry[0] == b'm': # merge
506 size = -1
506 size = -1
507 elif entry[0] == b'n' and entry[2] == -2: # other parent
507 elif entry[0] == b'n' and entry[2] == -2: # other parent
508 size = -2
508 size = -2
509 self._map.otherparentset.add(f)
509 self._map.otherparentset.add(f)
510 self._updatedfiles.add(f)
510 self._updatedfiles.add(f)
511 self._map.removefile(f, oldstate, size)
511 self._map.removefile(f, oldstate, size)
512 if size == 0:
512 if size == 0:
513 self._map.copymap.pop(f, None)
513 self._map.copymap.pop(f, None)
514
514
515 def merge(self, f):
515 def merge(self, f):
516 '''Mark a file merged.'''
516 '''Mark a file merged.'''
517 if self._pl[1] == nullid:
517 if self._pl[1] == nullid:
518 return self.normallookup(f)
518 return self.normallookup(f)
519 return self.otherparent(f)
519 return self.otherparent(f)
520
520
521 def drop(self, f):
521 def drop(self, f):
522 '''Drop a file from the dirstate'''
522 '''Drop a file from the dirstate'''
523 oldstate = self[f]
523 oldstate = self[f]
524 if self._map.dropfile(f, oldstate):
524 if self._map.dropfile(f, oldstate):
525 self._dirty = True
525 self._dirty = True
526 self._updatedfiles.add(f)
526 self._updatedfiles.add(f)
527 self._map.copymap.pop(f, None)
527 self._map.copymap.pop(f, None)
528
528
529 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
529 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
530 if exists is None:
530 if exists is None:
531 exists = os.path.lexists(os.path.join(self._root, path))
531 exists = os.path.lexists(os.path.join(self._root, path))
532 if not exists:
532 if not exists:
533 # Maybe a path component exists
533 # Maybe a path component exists
534 if not ignoremissing and b'/' in path:
534 if not ignoremissing and b'/' in path:
535 d, f = path.rsplit(b'/', 1)
535 d, f = path.rsplit(b'/', 1)
536 d = self._normalize(d, False, ignoremissing, None)
536 d = self._normalize(d, False, ignoremissing, None)
537 folded = d + b"/" + f
537 folded = d + b"/" + f
538 else:
538 else:
539 # No path components, preserve original case
539 # No path components, preserve original case
540 folded = path
540 folded = path
541 else:
541 else:
542 # recursively normalize leading directory components
542 # recursively normalize leading directory components
543 # against dirstate
543 # against dirstate
544 if b'/' in normed:
544 if b'/' in normed:
545 d, f = normed.rsplit(b'/', 1)
545 d, f = normed.rsplit(b'/', 1)
546 d = self._normalize(d, False, ignoremissing, True)
546 d = self._normalize(d, False, ignoremissing, True)
547 r = self._root + b"/" + d
547 r = self._root + b"/" + d
548 folded = d + b"/" + util.fspath(f, r)
548 folded = d + b"/" + util.fspath(f, r)
549 else:
549 else:
550 folded = util.fspath(normed, self._root)
550 folded = util.fspath(normed, self._root)
551 storemap[normed] = folded
551 storemap[normed] = folded
552
552
553 return folded
553 return folded
554
554
555 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
555 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
556 normed = util.normcase(path)
556 normed = util.normcase(path)
557 folded = self._map.filefoldmap.get(normed, None)
557 folded = self._map.filefoldmap.get(normed, None)
558 if folded is None:
558 if folded is None:
559 if isknown:
559 if isknown:
560 folded = path
560 folded = path
561 else:
561 else:
562 folded = self._discoverpath(
562 folded = self._discoverpath(
563 path, normed, ignoremissing, exists, self._map.filefoldmap
563 path, normed, ignoremissing, exists, self._map.filefoldmap
564 )
564 )
565 return folded
565 return folded
566
566
567 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
567 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
568 normed = util.normcase(path)
568 normed = util.normcase(path)
569 folded = self._map.filefoldmap.get(normed, None)
569 folded = self._map.filefoldmap.get(normed, None)
570 if folded is None:
570 if folded is None:
571 folded = self._map.dirfoldmap.get(normed, None)
571 folded = self._map.dirfoldmap.get(normed, None)
572 if folded is None:
572 if folded is None:
573 if isknown:
573 if isknown:
574 folded = path
574 folded = path
575 else:
575 else:
576 # store discovered result in dirfoldmap so that future
576 # store discovered result in dirfoldmap so that future
577 # normalizefile calls don't start matching directories
577 # normalizefile calls don't start matching directories
578 folded = self._discoverpath(
578 folded = self._discoverpath(
579 path, normed, ignoremissing, exists, self._map.dirfoldmap
579 path, normed, ignoremissing, exists, self._map.dirfoldmap
580 )
580 )
581 return folded
581 return folded
582
582
583 def normalize(self, path, isknown=False, ignoremissing=False):
583 def normalize(self, path, isknown=False, ignoremissing=False):
584 '''
584 '''
585 normalize the case of a pathname when on a casefolding filesystem
585 normalize the case of a pathname when on a casefolding filesystem
586
586
587 isknown specifies whether the filename came from walking the
587 isknown specifies whether the filename came from walking the
588 disk, to avoid extra filesystem access.
588 disk, to avoid extra filesystem access.
589
589
590 If ignoremissing is True, missing path are returned
590 If ignoremissing is True, missing path are returned
591 unchanged. Otherwise, we try harder to normalize possibly
591 unchanged. Otherwise, we try harder to normalize possibly
592 existing path components.
592 existing path components.
593
593
594 The normalized case is determined based on the following precedence:
594 The normalized case is determined based on the following precedence:
595
595
596 - version of name already stored in the dirstate
596 - version of name already stored in the dirstate
597 - version of name stored on disk
597 - version of name stored on disk
598 - version provided via command arguments
598 - version provided via command arguments
599 '''
599 '''
600
600
601 if self._checkcase:
601 if self._checkcase:
602 return self._normalize(path, isknown, ignoremissing)
602 return self._normalize(path, isknown, ignoremissing)
603 return path
603 return path
604
604
605 def clear(self):
605 def clear(self):
606 self._map.clear()
606 self._map.clear()
607 self._lastnormaltime = 0
607 self._lastnormaltime = 0
608 self._updatedfiles.clear()
608 self._updatedfiles.clear()
609 self._dirty = True
609 self._dirty = True
610
610
611 def rebuild(self, parent, allfiles, changedfiles=None):
611 def rebuild(self, parent, allfiles, changedfiles=None):
612 if changedfiles is None:
612 if changedfiles is None:
613 # Rebuild entire dirstate
613 # Rebuild entire dirstate
614 to_lookup = allfiles
614 to_lookup = allfiles
615 to_drop = []
615 to_drop = []
616 lastnormaltime = self._lastnormaltime
616 lastnormaltime = self._lastnormaltime
617 self.clear()
617 self.clear()
618 self._lastnormaltime = lastnormaltime
618 self._lastnormaltime = lastnormaltime
619 elif len(changedfiles) < 10:
619 elif len(changedfiles) < 10:
620 # Avoid turning allfiles into a set, which can be expensive if it's
620 # Avoid turning allfiles into a set, which can be expensive if it's
621 # large.
621 # large.
622 to_lookup = []
622 to_lookup = []
623 to_drop = []
623 to_drop = []
624 for f in changedfiles:
624 for f in changedfiles:
625 if f in allfiles:
625 if f in allfiles:
626 to_lookup.append(f)
626 to_lookup.append(f)
627 else:
627 else:
628 to_drop.append(f)
628 to_drop.append(f)
629 else:
629 else:
630 changedfilesset = set(changedfiles)
630 changedfilesset = set(changedfiles)
631 to_lookup = changedfilesset & set(allfiles)
631 to_lookup = changedfilesset & set(allfiles)
632 to_drop = changedfilesset - to_lookup
632 to_drop = changedfilesset - to_lookup
633
633
634 if self._origpl is None:
634 if self._origpl is None:
635 self._origpl = self._pl
635 self._origpl = self._pl
636 self._map.setparents(parent, nullid)
636 self._map.setparents(parent, nullid)
637
637
638 for f in to_lookup:
638 for f in to_lookup:
639 self.normallookup(f)
639 self.normallookup(f)
640 for f in to_drop:
640 for f in to_drop:
641 self.drop(f)
641 self.drop(f)
642
642
643 self._dirty = True
643 self._dirty = True
644
644
645 def identity(self):
645 def identity(self):
646 '''Return identity of dirstate itself to detect changing in storage
646 '''Return identity of dirstate itself to detect changing in storage
647
647
648 If identity of previous dirstate is equal to this, writing
648 If identity of previous dirstate is equal to this, writing
649 changes based on the former dirstate out can keep consistency.
649 changes based on the former dirstate out can keep consistency.
650 '''
650 '''
651 return self._map.identity
651 return self._map.identity
652
652
653 def write(self, tr):
653 def write(self, tr):
654 if not self._dirty:
654 if not self._dirty:
655 return
655 return
656
656
657 filename = self._filename
657 filename = self._filename
658 if tr:
658 if tr:
659 # 'dirstate.write()' is not only for writing in-memory
659 # 'dirstate.write()' is not only for writing in-memory
660 # changes out, but also for dropping ambiguous timestamp.
660 # changes out, but also for dropping ambiguous timestamp.
661 # delayed writing re-raise "ambiguous timestamp issue".
661 # delayed writing re-raise "ambiguous timestamp issue".
662 # See also the wiki page below for detail:
662 # See also the wiki page below for detail:
663 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
663 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
664
664
665 # emulate dropping timestamp in 'parsers.pack_dirstate'
665 # emulate dropping timestamp in 'parsers.pack_dirstate'
666 now = _getfsnow(self._opener)
666 now = _getfsnow(self._opener)
667 self._map.clearambiguoustimes(self._updatedfiles, now)
667 self._map.clearambiguoustimes(self._updatedfiles, now)
668
668
669 # emulate that all 'dirstate.normal' results are written out
669 # emulate that all 'dirstate.normal' results are written out
670 self._lastnormaltime = 0
670 self._lastnormaltime = 0
671 self._updatedfiles.clear()
671 self._updatedfiles.clear()
672
672
673 # delay writing in-memory changes out
673 # delay writing in-memory changes out
674 tr.addfilegenerator(
674 tr.addfilegenerator(
675 b'dirstate',
675 b'dirstate',
676 (self._filename,),
676 (self._filename,),
677 self._writedirstate,
677 self._writedirstate,
678 location=b'plain',
678 location=b'plain',
679 )
679 )
680 return
680 return
681
681
682 st = self._opener(filename, b"w", atomictemp=True, checkambig=True)
682 st = self._opener(filename, b"w", atomictemp=True, checkambig=True)
683 self._writedirstate(st)
683 self._writedirstate(st)
684
684
685 def addparentchangecallback(self, category, callback):
685 def addparentchangecallback(self, category, callback):
686 """add a callback to be called when the wd parents are changed
686 """add a callback to be called when the wd parents are changed
687
687
688 Callback will be called with the following arguments:
688 Callback will be called with the following arguments:
689 dirstate, (oldp1, oldp2), (newp1, newp2)
689 dirstate, (oldp1, oldp2), (newp1, newp2)
690
690
691 Category is a unique identifier to allow overwriting an old callback
691 Category is a unique identifier to allow overwriting an old callback
692 with a newer callback.
692 with a newer callback.
693 """
693 """
694 self._plchangecallbacks[category] = callback
694 self._plchangecallbacks[category] = callback
695
695
696 def _writedirstate(self, st):
696 def _writedirstate(self, st):
697 # notify callbacks about parents change
697 # notify callbacks about parents change
698 if self._origpl is not None and self._origpl != self._pl:
698 if self._origpl is not None and self._origpl != self._pl:
699 for c, callback in sorted(
699 for c, callback in sorted(
700 pycompat.iteritems(self._plchangecallbacks)
700 pycompat.iteritems(self._plchangecallbacks)
701 ):
701 ):
702 callback(self, self._origpl, self._pl)
702 callback(self, self._origpl, self._pl)
703 self._origpl = None
703 self._origpl = None
704 # use the modification time of the newly created temporary file as the
704 # use the modification time of the newly created temporary file as the
705 # filesystem's notion of 'now'
705 # filesystem's notion of 'now'
706 now = util.fstat(st)[stat.ST_MTIME] & _rangemask
706 now = util.fstat(st)[stat.ST_MTIME] & _rangemask
707
707
708 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
708 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
709 # timestamp of each entries in dirstate, because of 'now > mtime'
709 # timestamp of each entries in dirstate, because of 'now > mtime'
710 delaywrite = self._ui.configint(b'debug', b'dirstate.delaywrite')
710 delaywrite = self._ui.configint(b'debug', b'dirstate.delaywrite')
711 if delaywrite > 0:
711 if delaywrite > 0:
712 # do we have any files to delay for?
712 # do we have any files to delay for?
713 for f, e in pycompat.iteritems(self._map):
713 for f, e in pycompat.iteritems(self._map):
714 if e[0] == b'n' and e[3] == now:
714 if e[0] == b'n' and e[3] == now:
715 import time # to avoid useless import
715 import time # to avoid useless import
716
716
717 # rather than sleep n seconds, sleep until the next
717 # rather than sleep n seconds, sleep until the next
718 # multiple of n seconds
718 # multiple of n seconds
719 clock = time.time()
719 clock = time.time()
720 start = int(clock) - (int(clock) % delaywrite)
720 start = int(clock) - (int(clock) % delaywrite)
721 end = start + delaywrite
721 end = start + delaywrite
722 time.sleep(end - clock)
722 time.sleep(end - clock)
723 now = end # trust our estimate that the end is near now
723 now = end # trust our estimate that the end is near now
724 break
724 break
725
725
726 self._map.write(st, now)
726 self._map.write(st, now)
727 self._lastnormaltime = 0
727 self._lastnormaltime = 0
728 self._dirty = False
728 self._dirty = False
729
729
730 def _dirignore(self, f):
730 def _dirignore(self, f):
731 if self._ignore(f):
731 if self._ignore(f):
732 return True
732 return True
733 for p in pathutil.finddirs(f):
733 for p in pathutil.finddirs(f):
734 if self._ignore(p):
734 if self._ignore(p):
735 return True
735 return True
736 return False
736 return False
737
737
738 def _ignorefiles(self):
738 def _ignorefiles(self):
739 files = []
739 files = []
740 if os.path.exists(self._join(b'.hgignore')):
740 if os.path.exists(self._join(b'.hgignore')):
741 files.append(self._join(b'.hgignore'))
741 files.append(self._join(b'.hgignore'))
742 for name, path in self._ui.configitems(b"ui"):
742 for name, path in self._ui.configitems(b"ui"):
743 if name == b'ignore' or name.startswith(b'ignore.'):
743 if name == b'ignore' or name.startswith(b'ignore.'):
744 # we need to use os.path.join here rather than self._join
744 # we need to use os.path.join here rather than self._join
745 # because path is arbitrary and user-specified
745 # because path is arbitrary and user-specified
746 files.append(os.path.join(self._rootdir, util.expandpath(path)))
746 files.append(os.path.join(self._rootdir, util.expandpath(path)))
747 return files
747 return files
748
748
749 def _ignorefileandline(self, f):
749 def _ignorefileandline(self, f):
750 files = collections.deque(self._ignorefiles())
750 files = collections.deque(self._ignorefiles())
751 visited = set()
751 visited = set()
752 while files:
752 while files:
753 i = files.popleft()
753 i = files.popleft()
754 patterns = matchmod.readpatternfile(
754 patterns = matchmod.readpatternfile(
755 i, self._ui.warn, sourceinfo=True
755 i, self._ui.warn, sourceinfo=True
756 )
756 )
757 for pattern, lineno, line in patterns:
757 for pattern, lineno, line in patterns:
758 kind, p = matchmod._patsplit(pattern, b'glob')
758 kind, p = matchmod._patsplit(pattern, b'glob')
759 if kind == b"subinclude":
759 if kind == b"subinclude":
760 if p not in visited:
760 if p not in visited:
761 files.append(p)
761 files.append(p)
762 continue
762 continue
763 m = matchmod.match(
763 m = matchmod.match(
764 self._root, b'', [], [pattern], warn=self._ui.warn
764 self._root, b'', [], [pattern], warn=self._ui.warn
765 )
765 )
766 if m(f):
766 if m(f):
767 return (i, lineno, line)
767 return (i, lineno, line)
768 visited.add(i)
768 visited.add(i)
769 return (None, -1, b"")
769 return (None, -1, b"")
770
770
771 def _walkexplicit(self, match, subrepos):
771 def _walkexplicit(self, match, subrepos):
772 '''Get stat data about the files explicitly specified by match.
772 '''Get stat data about the files explicitly specified by match.
773
773
774 Return a triple (results, dirsfound, dirsnotfound).
774 Return a triple (results, dirsfound, dirsnotfound).
775 - results is a mapping from filename to stat result. It also contains
775 - results is a mapping from filename to stat result. It also contains
776 listings mapping subrepos and .hg to None.
776 listings mapping subrepos and .hg to None.
777 - dirsfound is a list of files found to be directories.
777 - dirsfound is a list of files found to be directories.
778 - dirsnotfound is a list of files that the dirstate thinks are
778 - dirsnotfound is a list of files that the dirstate thinks are
779 directories and that were not found.'''
779 directories and that were not found.'''
780
780
781 def badtype(mode):
781 def badtype(mode):
782 kind = _(b'unknown')
782 kind = _(b'unknown')
783 if stat.S_ISCHR(mode):
783 if stat.S_ISCHR(mode):
784 kind = _(b'character device')
784 kind = _(b'character device')
785 elif stat.S_ISBLK(mode):
785 elif stat.S_ISBLK(mode):
786 kind = _(b'block device')
786 kind = _(b'block device')
787 elif stat.S_ISFIFO(mode):
787 elif stat.S_ISFIFO(mode):
788 kind = _(b'fifo')
788 kind = _(b'fifo')
789 elif stat.S_ISSOCK(mode):
789 elif stat.S_ISSOCK(mode):
790 kind = _(b'socket')
790 kind = _(b'socket')
791 elif stat.S_ISDIR(mode):
791 elif stat.S_ISDIR(mode):
792 kind = _(b'directory')
792 kind = _(b'directory')
793 return _(b'unsupported file type (type is %s)') % kind
793 return _(b'unsupported file type (type is %s)') % kind
794
794
795 badfn = match.bad
795 badfn = match.bad
796 dmap = self._map
796 dmap = self._map
797 lstat = os.lstat
797 lstat = os.lstat
798 getkind = stat.S_IFMT
798 getkind = stat.S_IFMT
799 dirkind = stat.S_IFDIR
799 dirkind = stat.S_IFDIR
800 regkind = stat.S_IFREG
800 regkind = stat.S_IFREG
801 lnkkind = stat.S_IFLNK
801 lnkkind = stat.S_IFLNK
802 join = self._join
802 join = self._join
803 dirsfound = []
803 dirsfound = []
804 foundadd = dirsfound.append
804 foundadd = dirsfound.append
805 dirsnotfound = []
805 dirsnotfound = []
806 notfoundadd = dirsnotfound.append
806 notfoundadd = dirsnotfound.append
807
807
808 if not match.isexact() and self._checkcase:
808 if not match.isexact() and self._checkcase:
809 normalize = self._normalize
809 normalize = self._normalize
810 else:
810 else:
811 normalize = None
811 normalize = None
812
812
813 files = sorted(match.files())
813 files = sorted(match.files())
814 subrepos.sort()
814 subrepos.sort()
815 i, j = 0, 0
815 i, j = 0, 0
816 while i < len(files) and j < len(subrepos):
816 while i < len(files) and j < len(subrepos):
817 subpath = subrepos[j] + b"/"
817 subpath = subrepos[j] + b"/"
818 if files[i] < subpath:
818 if files[i] < subpath:
819 i += 1
819 i += 1
820 continue
820 continue
821 while i < len(files) and files[i].startswith(subpath):
821 while i < len(files) and files[i].startswith(subpath):
822 del files[i]
822 del files[i]
823 j += 1
823 j += 1
824
824
825 if not files or b'' in files:
825 if not files or b'' in files:
826 files = [b'']
826 files = [b'']
827 # constructing the foldmap is expensive, so don't do it for the
827 # constructing the foldmap is expensive, so don't do it for the
828 # common case where files is ['']
828 # common case where files is ['']
829 normalize = None
829 normalize = None
830 results = dict.fromkeys(subrepos)
830 results = dict.fromkeys(subrepos)
831 results[b'.hg'] = None
831 results[b'.hg'] = None
832
832
833 for ff in files:
833 for ff in files:
834 if normalize:
834 if normalize:
835 nf = normalize(ff, False, True)
835 nf = normalize(ff, False, True)
836 else:
836 else:
837 nf = ff
837 nf = ff
838 if nf in results:
838 if nf in results:
839 continue
839 continue
840
840
841 try:
841 try:
842 st = lstat(join(nf))
842 st = lstat(join(nf))
843 kind = getkind(st.st_mode)
843 kind = getkind(st.st_mode)
844 if kind == dirkind:
844 if kind == dirkind:
845 if nf in dmap:
845 if nf in dmap:
846 # file replaced by dir on disk but still in dirstate
846 # file replaced by dir on disk but still in dirstate
847 results[nf] = None
847 results[nf] = None
848 foundadd((nf, ff))
848 foundadd((nf, ff))
849 elif kind == regkind or kind == lnkkind:
849 elif kind == regkind or kind == lnkkind:
850 results[nf] = st
850 results[nf] = st
851 else:
851 else:
852 badfn(ff, badtype(kind))
852 badfn(ff, badtype(kind))
853 if nf in dmap:
853 if nf in dmap:
854 results[nf] = None
854 results[nf] = None
855 except OSError as inst: # nf not found on disk - it is dirstate only
855 except OSError as inst: # nf not found on disk - it is dirstate only
856 if nf in dmap: # does it exactly match a missing file?
856 if nf in dmap: # does it exactly match a missing file?
857 results[nf] = None
857 results[nf] = None
858 else: # does it match a missing directory?
858 else: # does it match a missing directory?
859 if self._map.hasdir(nf):
859 if self._map.hasdir(nf):
860 notfoundadd(nf)
860 notfoundadd(nf)
861 else:
861 else:
862 badfn(ff, encoding.strtolocal(inst.strerror))
862 badfn(ff, encoding.strtolocal(inst.strerror))
863
863
864 # match.files() may contain explicitly-specified paths that shouldn't
864 # match.files() may contain explicitly-specified paths that shouldn't
865 # be taken; drop them from the list of files found. dirsfound/notfound
865 # be taken; drop them from the list of files found. dirsfound/notfound
866 # aren't filtered here because they will be tested later.
866 # aren't filtered here because they will be tested later.
867 if match.anypats():
867 if match.anypats():
868 for f in list(results):
868 for f in list(results):
869 if f == b'.hg' or f in subrepos:
869 if f == b'.hg' or f in subrepos:
870 # keep sentinel to disable further out-of-repo walks
870 # keep sentinel to disable further out-of-repo walks
871 continue
871 continue
872 if not match(f):
872 if not match(f):
873 del results[f]
873 del results[f]
874
874
875 # Case insensitive filesystems cannot rely on lstat() failing to detect
875 # Case insensitive filesystems cannot rely on lstat() failing to detect
876 # a case-only rename. Prune the stat object for any file that does not
876 # a case-only rename. Prune the stat object for any file that does not
877 # match the case in the filesystem, if there are multiple files that
877 # match the case in the filesystem, if there are multiple files that
878 # normalize to the same path.
878 # normalize to the same path.
879 if match.isexact() and self._checkcase:
879 if match.isexact() and self._checkcase:
880 normed = {}
880 normed = {}
881
881
882 for f, st in pycompat.iteritems(results):
882 for f, st in pycompat.iteritems(results):
883 if st is None:
883 if st is None:
884 continue
884 continue
885
885
886 nc = util.normcase(f)
886 nc = util.normcase(f)
887 paths = normed.get(nc)
887 paths = normed.get(nc)
888
888
889 if paths is None:
889 if paths is None:
890 paths = set()
890 paths = set()
891 normed[nc] = paths
891 normed[nc] = paths
892
892
893 paths.add(f)
893 paths.add(f)
894
894
895 for norm, paths in pycompat.iteritems(normed):
895 for norm, paths in pycompat.iteritems(normed):
896 if len(paths) > 1:
896 if len(paths) > 1:
897 for path in paths:
897 for path in paths:
898 folded = self._discoverpath(
898 folded = self._discoverpath(
899 path, norm, True, None, self._map.dirfoldmap
899 path, norm, True, None, self._map.dirfoldmap
900 )
900 )
901 if path != folded:
901 if path != folded:
902 results[path] = None
902 results[path] = None
903
903
904 return results, dirsfound, dirsnotfound
904 return results, dirsfound, dirsnotfound
905
905
906 def walk(self, match, subrepos, unknown, ignored, full=True):
906 def walk(self, match, subrepos, unknown, ignored, full=True):
907 '''
907 '''
908 Walk recursively through the directory tree, finding all files
908 Walk recursively through the directory tree, finding all files
909 matched by match.
909 matched by match.
910
910
911 If full is False, maybe skip some known-clean files.
911 If full is False, maybe skip some known-clean files.
912
912
913 Return a dict mapping filename to stat-like object (either
913 Return a dict mapping filename to stat-like object (either
914 mercurial.osutil.stat instance or return value of os.stat()).
914 mercurial.osutil.stat instance or return value of os.stat()).
915
915
916 '''
916 '''
917 # full is a flag that extensions that hook into walk can use -- this
917 # full is a flag that extensions that hook into walk can use -- this
918 # implementation doesn't use it at all. This satisfies the contract
918 # implementation doesn't use it at all. This satisfies the contract
919 # because we only guarantee a "maybe".
919 # because we only guarantee a "maybe".
920
920
921 if ignored:
921 if ignored:
922 ignore = util.never
922 ignore = util.never
923 dirignore = util.never
923 dirignore = util.never
924 elif unknown:
924 elif unknown:
925 ignore = self._ignore
925 ignore = self._ignore
926 dirignore = self._dirignore
926 dirignore = self._dirignore
927 else:
927 else:
928 # if not unknown and not ignored, drop dir recursion and step 2
928 # if not unknown and not ignored, drop dir recursion and step 2
929 ignore = util.always
929 ignore = util.always
930 dirignore = util.always
930 dirignore = util.always
931
931
932 matchfn = match.matchfn
932 matchfn = match.matchfn
933 matchalways = match.always()
933 matchalways = match.always()
934 matchtdir = match.traversedir
934 matchtdir = match.traversedir
935 dmap = self._map
935 dmap = self._map
936 listdir = util.listdir
936 listdir = util.listdir
937 lstat = os.lstat
937 lstat = os.lstat
938 dirkind = stat.S_IFDIR
938 dirkind = stat.S_IFDIR
939 regkind = stat.S_IFREG
939 regkind = stat.S_IFREG
940 lnkkind = stat.S_IFLNK
940 lnkkind = stat.S_IFLNK
941 join = self._join
941 join = self._join
942
942
943 exact = skipstep3 = False
943 exact = skipstep3 = False
944 if match.isexact(): # match.exact
944 if match.isexact(): # match.exact
945 exact = True
945 exact = True
946 dirignore = util.always # skip step 2
946 dirignore = util.always # skip step 2
947 elif match.prefix(): # match.match, no patterns
947 elif match.prefix(): # match.match, no patterns
948 skipstep3 = True
948 skipstep3 = True
949
949
950 if not exact and self._checkcase:
950 if not exact and self._checkcase:
951 normalize = self._normalize
951 normalize = self._normalize
952 normalizefile = self._normalizefile
952 normalizefile = self._normalizefile
953 skipstep3 = False
953 skipstep3 = False
954 else:
954 else:
955 normalize = self._normalize
955 normalize = self._normalize
956 normalizefile = None
956 normalizefile = None
957
957
958 # step 1: find all explicit files
958 # step 1: find all explicit files
959 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
959 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
960 if matchtdir:
960 if matchtdir:
961 for d in work:
961 for d in work:
962 matchtdir(d[0])
962 matchtdir(d[0])
963 for d in dirsnotfound:
963 for d in dirsnotfound:
964 matchtdir(d)
964 matchtdir(d)
965
965
966 skipstep3 = skipstep3 and not (work or dirsnotfound)
966 skipstep3 = skipstep3 and not (work or dirsnotfound)
967 work = [d for d in work if not dirignore(d[0])]
967 work = [d for d in work if not dirignore(d[0])]
968
968
969 # step 2: visit subdirectories
969 # step 2: visit subdirectories
970 def traverse(work, alreadynormed):
970 def traverse(work, alreadynormed):
971 wadd = work.append
971 wadd = work.append
972 while work:
972 while work:
973 tracing.counter('dirstate.walk work', len(work))
973 tracing.counter('dirstate.walk work', len(work))
974 nd = work.pop()
974 nd = work.pop()
975 visitentries = match.visitchildrenset(nd)
975 visitentries = match.visitchildrenset(nd)
976 if not visitentries:
976 if not visitentries:
977 continue
977 continue
978 if visitentries == b'this' or visitentries == b'all':
978 if visitentries == b'this' or visitentries == b'all':
979 visitentries = None
979 visitentries = None
980 skip = None
980 skip = None
981 if nd != b'':
981 if nd != b'':
982 skip = b'.hg'
982 skip = b'.hg'
983 try:
983 try:
984 with tracing.log('dirstate.walk.traverse listdir %s', nd):
984 with tracing.log('dirstate.walk.traverse listdir %s', nd):
985 entries = listdir(join(nd), stat=True, skip=skip)
985 entries = listdir(join(nd), stat=True, skip=skip)
986 except OSError as inst:
986 except OSError as inst:
987 if inst.errno in (errno.EACCES, errno.ENOENT):
987 if inst.errno in (errno.EACCES, errno.ENOENT):
988 match.bad(
988 match.bad(
989 self.pathto(nd), encoding.strtolocal(inst.strerror)
989 self.pathto(nd), encoding.strtolocal(inst.strerror)
990 )
990 )
991 continue
991 continue
992 raise
992 raise
993 for f, kind, st in entries:
993 for f, kind, st in entries:
994 # Some matchers may return files in the visitentries set,
994 # Some matchers may return files in the visitentries set,
995 # instead of 'this', if the matcher explicitly mentions them
995 # instead of 'this', if the matcher explicitly mentions them
996 # and is not an exactmatcher. This is acceptable; we do not
996 # and is not an exactmatcher. This is acceptable; we do not
997 # make any hard assumptions about file-or-directory below
997 # make any hard assumptions about file-or-directory below
998 # based on the presence of `f` in visitentries. If
998 # based on the presence of `f` in visitentries. If
999 # visitchildrenset returned a set, we can always skip the
999 # visitchildrenset returned a set, we can always skip the
1000 # entries *not* in the set it provided regardless of whether
1000 # entries *not* in the set it provided regardless of whether
1001 # they're actually a file or a directory.
1001 # they're actually a file or a directory.
1002 if visitentries and f not in visitentries:
1002 if visitentries and f not in visitentries:
1003 continue
1003 continue
1004 if normalizefile:
1004 if normalizefile:
1005 # even though f might be a directory, we're only
1005 # even though f might be a directory, we're only
1006 # interested in comparing it to files currently in the
1006 # interested in comparing it to files currently in the
1007 # dmap -- therefore normalizefile is enough
1007 # dmap -- therefore normalizefile is enough
1008 nf = normalizefile(
1008 nf = normalizefile(
1009 nd and (nd + b"/" + f) or f, True, True
1009 nd and (nd + b"/" + f) or f, True, True
1010 )
1010 )
1011 else:
1011 else:
1012 nf = nd and (nd + b"/" + f) or f
1012 nf = nd and (nd + b"/" + f) or f
1013 if nf not in results:
1013 if nf not in results:
1014 if kind == dirkind:
1014 if kind == dirkind:
1015 if not ignore(nf):
1015 if not ignore(nf):
1016 if matchtdir:
1016 if matchtdir:
1017 matchtdir(nf)
1017 matchtdir(nf)
1018 wadd(nf)
1018 wadd(nf)
1019 if nf in dmap and (matchalways or matchfn(nf)):
1019 if nf in dmap and (matchalways or matchfn(nf)):
1020 results[nf] = None
1020 results[nf] = None
1021 elif kind == regkind or kind == lnkkind:
1021 elif kind == regkind or kind == lnkkind:
1022 if nf in dmap:
1022 if nf in dmap:
1023 if matchalways or matchfn(nf):
1023 if matchalways or matchfn(nf):
1024 results[nf] = st
1024 results[nf] = st
1025 elif (matchalways or matchfn(nf)) and not ignore(
1025 elif (matchalways or matchfn(nf)) and not ignore(
1026 nf
1026 nf
1027 ):
1027 ):
1028 # unknown file -- normalize if necessary
1028 # unknown file -- normalize if necessary
1029 if not alreadynormed:
1029 if not alreadynormed:
1030 nf = normalize(nf, False, True)
1030 nf = normalize(nf, False, True)
1031 results[nf] = st
1031 results[nf] = st
1032 elif nf in dmap and (matchalways or matchfn(nf)):
1032 elif nf in dmap and (matchalways or matchfn(nf)):
1033 results[nf] = None
1033 results[nf] = None
1034
1034
1035 for nd, d in work:
1035 for nd, d in work:
1036 # alreadynormed means that processwork doesn't have to do any
1036 # alreadynormed means that processwork doesn't have to do any
1037 # expensive directory normalization
1037 # expensive directory normalization
1038 alreadynormed = not normalize or nd == d
1038 alreadynormed = not normalize or nd == d
1039 traverse([d], alreadynormed)
1039 traverse([d], alreadynormed)
1040
1040
1041 for s in subrepos:
1041 for s in subrepos:
1042 del results[s]
1042 del results[s]
1043 del results[b'.hg']
1043 del results[b'.hg']
1044
1044
1045 # step 3: visit remaining files from dmap
1045 # step 3: visit remaining files from dmap
1046 if not skipstep3 and not exact:
1046 if not skipstep3 and not exact:
1047 # If a dmap file is not in results yet, it was either
1047 # If a dmap file is not in results yet, it was either
1048 # a) not matching matchfn b) ignored, c) missing, or d) under a
1048 # a) not matching matchfn b) ignored, c) missing, or d) under a
1049 # symlink directory.
1049 # symlink directory.
1050 if not results and matchalways:
1050 if not results and matchalways:
1051 visit = [f for f in dmap]
1051 visit = [f for f in dmap]
1052 else:
1052 else:
1053 visit = [f for f in dmap if f not in results and matchfn(f)]
1053 visit = [f for f in dmap if f not in results and matchfn(f)]
1054 visit.sort()
1054 visit.sort()
1055
1055
1056 if unknown:
1056 if unknown:
1057 # unknown == True means we walked all dirs under the roots
1057 # unknown == True means we walked all dirs under the roots
1058 # that wasn't ignored, and everything that matched was stat'ed
1058 # that wasn't ignored, and everything that matched was stat'ed
1059 # and is already in results.
1059 # and is already in results.
1060 # The rest must thus be ignored or under a symlink.
1060 # The rest must thus be ignored or under a symlink.
1061 audit_path = pathutil.pathauditor(self._root, cached=True)
1061 audit_path = pathutil.pathauditor(self._root, cached=True)
1062
1062
1063 for nf in iter(visit):
1063 for nf in iter(visit):
1064 # If a stat for the same file was already added with a
1064 # If a stat for the same file was already added with a
1065 # different case, don't add one for this, since that would
1065 # different case, don't add one for this, since that would
1066 # make it appear as if the file exists under both names
1066 # make it appear as if the file exists under both names
1067 # on disk.
1067 # on disk.
1068 if (
1068 if (
1069 normalizefile
1069 normalizefile
1070 and normalizefile(nf, True, True) in results
1070 and normalizefile(nf, True, True) in results
1071 ):
1071 ):
1072 results[nf] = None
1072 results[nf] = None
1073 # Report ignored items in the dmap as long as they are not
1073 # Report ignored items in the dmap as long as they are not
1074 # under a symlink directory.
1074 # under a symlink directory.
1075 elif audit_path.check(nf):
1075 elif audit_path.check(nf):
1076 try:
1076 try:
1077 results[nf] = lstat(join(nf))
1077 results[nf] = lstat(join(nf))
1078 # file was just ignored, no links, and exists
1078 # file was just ignored, no links, and exists
1079 except OSError:
1079 except OSError:
1080 # file doesn't exist
1080 # file doesn't exist
1081 results[nf] = None
1081 results[nf] = None
1082 else:
1082 else:
1083 # It's either missing or under a symlink directory
1083 # It's either missing or under a symlink directory
1084 # which we in this case report as missing
1084 # which we in this case report as missing
1085 results[nf] = None
1085 results[nf] = None
1086 else:
1086 else:
1087 # We may not have walked the full directory tree above,
1087 # We may not have walked the full directory tree above,
1088 # so stat and check everything we missed.
1088 # so stat and check everything we missed.
1089 iv = iter(visit)
1089 iv = iter(visit)
1090 for st in util.statfiles([join(i) for i in visit]):
1090 for st in util.statfiles([join(i) for i in visit]):
1091 results[next(iv)] = st
1091 results[next(iv)] = st
1092 return results
1092 return results
1093
1093
1094 def _rust_status(self, matcher, list_clean, list_ignored, list_unknown):
1094 def _rust_status(self, matcher, list_clean, list_ignored, list_unknown):
1095 # Force Rayon (Rust parallelism library) to respect the number of
1095 # Force Rayon (Rust parallelism library) to respect the number of
1096 # workers. This is a temporary workaround until Rust code knows
1096 # workers. This is a temporary workaround until Rust code knows
1097 # how to read the config file.
1097 # how to read the config file.
1098 numcpus = self._ui.configint(b"worker", b"numcpus")
1098 numcpus = self._ui.configint(b"worker", b"numcpus")
1099 if numcpus is not None:
1099 if numcpus is not None:
1100 encoding.environ.setdefault(b'RAYON_NUM_THREADS', b'%d' % numcpus)
1100 encoding.environ.setdefault(b'RAYON_NUM_THREADS', b'%d' % numcpus)
1101
1101
1102 workers_enabled = self._ui.configbool(b"worker", b"enabled", True)
1102 workers_enabled = self._ui.configbool(b"worker", b"enabled", True)
1103 if not workers_enabled:
1103 if not workers_enabled:
1104 encoding.environ[b"RAYON_NUM_THREADS"] = b"1"
1104 encoding.environ[b"RAYON_NUM_THREADS"] = b"1"
1105
1105
1106 (
1106 (
1107 lookup,
1107 lookup,
1108 modified,
1108 modified,
1109 added,
1109 added,
1110 removed,
1110 removed,
1111 deleted,
1111 deleted,
1112 clean,
1112 clean,
1113 ignored,
1113 ignored,
1114 unknown,
1114 unknown,
1115 warnings,
1115 warnings,
1116 bad,
1116 bad,
1117 traversed,
1117 traversed,
1118 ) = rustmod.status(
1118 ) = rustmod.status(
1119 self._map._rustmap,
1119 self._map._rustmap,
1120 matcher,
1120 matcher,
1121 self._rootdir,
1121 self._rootdir,
1122 self._ignorefiles(),
1122 self._ignorefiles(),
1123 self._checkexec,
1123 self._checkexec,
1124 self._lastnormaltime,
1124 self._lastnormaltime,
1125 bool(list_clean),
1125 bool(list_clean),
1126 bool(list_ignored),
1126 bool(list_ignored),
1127 bool(list_unknown),
1127 bool(list_unknown),
1128 bool(matcher.traversedir),
1128 bool(matcher.traversedir),
1129 )
1129 )
1130
1130
1131 if matcher.traversedir:
1131 if matcher.traversedir:
1132 for dir in traversed:
1132 for dir in traversed:
1133 matcher.traversedir(dir)
1133 matcher.traversedir(dir)
1134
1134
1135 if self._ui.warn:
1135 if self._ui.warn:
1136 for item in warnings:
1136 for item in warnings:
1137 if isinstance(item, tuple):
1137 if isinstance(item, tuple):
1138 file_path, syntax = item
1138 file_path, syntax = item
1139 msg = _(b"%s: ignoring invalid syntax '%s'\n") % (
1139 msg = _(b"%s: ignoring invalid syntax '%s'\n") % (
1140 file_path,
1140 file_path,
1141 syntax,
1141 syntax,
1142 )
1142 )
1143 self._ui.warn(msg)
1143 self._ui.warn(msg)
1144 else:
1144 else:
1145 msg = _(b"skipping unreadable pattern file '%s': %s\n")
1145 msg = _(b"skipping unreadable pattern file '%s': %s\n")
1146 self._ui.warn(
1146 self._ui.warn(
1147 msg
1147 msg
1148 % (
1148 % (
1149 pathutil.canonpath(
1149 pathutil.canonpath(
1150 self._rootdir, self._rootdir, item
1150 self._rootdir, self._rootdir, item
1151 ),
1151 ),
1152 b"No such file or directory",
1152 b"No such file or directory",
1153 )
1153 )
1154 )
1154 )
1155
1155
1156 for (fn, message) in bad:
1156 for (fn, message) in bad:
1157 matcher.bad(fn, encoding.strtolocal(message))
1157 matcher.bad(fn, encoding.strtolocal(message))
1158
1158
1159 status = scmutil.status(
1159 status = scmutil.status(
1160 modified=modified,
1160 modified=modified,
1161 added=added,
1161 added=added,
1162 removed=removed,
1162 removed=removed,
1163 deleted=deleted,
1163 deleted=deleted,
1164 unknown=unknown,
1164 unknown=unknown,
1165 ignored=ignored,
1165 ignored=ignored,
1166 clean=clean,
1166 clean=clean,
1167 )
1167 )
1168 return (lookup, status)
1168 return (lookup, status)
1169
1169
1170 def status(self, match, subrepos, ignored, clean, unknown):
1170 def status(self, match, subrepos, ignored, clean, unknown):
1171 '''Determine the status of the working copy relative to the
1171 '''Determine the status of the working copy relative to the
1172 dirstate and return a pair of (unsure, status), where status is of type
1172 dirstate and return a pair of (unsure, status), where status is of type
1173 scmutil.status and:
1173 scmutil.status and:
1174
1174
1175 unsure:
1175 unsure:
1176 files that might have been modified since the dirstate was
1176 files that might have been modified since the dirstate was
1177 written, but need to be read to be sure (size is the same
1177 written, but need to be read to be sure (size is the same
1178 but mtime differs)
1178 but mtime differs)
1179 status.modified:
1179 status.modified:
1180 files that have definitely been modified since the dirstate
1180 files that have definitely been modified since the dirstate
1181 was written (different size or mode)
1181 was written (different size or mode)
1182 status.clean:
1182 status.clean:
1183 files that have definitely not been modified since the
1183 files that have definitely not been modified since the
1184 dirstate was written
1184 dirstate was written
1185 '''
1185 '''
1186 listignored, listclean, listunknown = ignored, clean, unknown
1186 listignored, listclean, listunknown = ignored, clean, unknown
1187 lookup, modified, added, unknown, ignored = [], [], [], [], []
1187 lookup, modified, added, unknown, ignored = [], [], [], [], []
1188 removed, deleted, clean = [], [], []
1188 removed, deleted, clean = [], [], []
1189
1189
1190 dmap = self._map
1190 dmap = self._map
1191 dmap.preload()
1191 dmap.preload()
1192
1192
1193 use_rust = True
1193 use_rust = True
1194
1194
1195 allowed_matchers = (
1195 allowed_matchers = (
1196 matchmod.alwaysmatcher,
1196 matchmod.alwaysmatcher,
1197 matchmod.exactmatcher,
1197 matchmod.exactmatcher,
1198 matchmod.includematcher,
1198 matchmod.includematcher,
1199 )
1199 )
1200
1200
1201 if rustmod is None:
1201 if rustmod is None:
1202 use_rust = False
1202 use_rust = False
1203 elif self._checkcase:
1203 elif self._checkcase:
1204 # Case-insensitive filesystems are not handled yet
1204 # Case-insensitive filesystems are not handled yet
1205 use_rust = False
1205 use_rust = False
1206 elif subrepos:
1206 elif subrepos:
1207 use_rust = False
1207 use_rust = False
1208 elif sparse.enabled:
1208 elif sparse.enabled:
1209 use_rust = False
1209 use_rust = False
1210 elif not isinstance(match, allowed_matchers):
1210 elif not isinstance(match, allowed_matchers):
1211 # Some matchers have yet to be implemented
1211 # Some matchers have yet to be implemented
1212 use_rust = False
1212 use_rust = False
1213
1213
1214 if use_rust:
1214 if use_rust:
1215 try:
1215 try:
1216 return self._rust_status(
1216 return self._rust_status(
1217 match, listclean, listignored, listunknown
1217 match, listclean, listignored, listunknown
1218 )
1218 )
1219 except rustmod.FallbackError:
1219 except rustmod.FallbackError:
1220 pass
1220 pass
1221
1221
1222 def noop(f):
1222 def noop(f):
1223 pass
1223 pass
1224
1224
1225 dcontains = dmap.__contains__
1225 dcontains = dmap.__contains__
1226 dget = dmap.__getitem__
1226 dget = dmap.__getitem__
1227 ladd = lookup.append # aka "unsure"
1227 ladd = lookup.append # aka "unsure"
1228 madd = modified.append
1228 madd = modified.append
1229 aadd = added.append
1229 aadd = added.append
1230 uadd = unknown.append if listunknown else noop
1230 uadd = unknown.append if listunknown else noop
1231 iadd = ignored.append if listignored else noop
1231 iadd = ignored.append if listignored else noop
1232 radd = removed.append
1232 radd = removed.append
1233 dadd = deleted.append
1233 dadd = deleted.append
1234 cadd = clean.append if listclean else noop
1234 cadd = clean.append if listclean else noop
1235 mexact = match.exact
1235 mexact = match.exact
1236 dirignore = self._dirignore
1236 dirignore = self._dirignore
1237 checkexec = self._checkexec
1237 checkexec = self._checkexec
1238 copymap = self._map.copymap
1238 copymap = self._map.copymap
1239 lastnormaltime = self._lastnormaltime
1239 lastnormaltime = self._lastnormaltime
1240
1240
1241 # We need to do full walks when either
1241 # We need to do full walks when either
1242 # - we're listing all clean files, or
1242 # - we're listing all clean files, or
1243 # - match.traversedir does something, because match.traversedir should
1243 # - match.traversedir does something, because match.traversedir should
1244 # be called for every dir in the working dir
1244 # be called for every dir in the working dir
1245 full = listclean or match.traversedir is not None
1245 full = listclean or match.traversedir is not None
1246 for fn, st in pycompat.iteritems(
1246 for fn, st in pycompat.iteritems(
1247 self.walk(match, subrepos, listunknown, listignored, full=full)
1247 self.walk(match, subrepos, listunknown, listignored, full=full)
1248 ):
1248 ):
1249 if not dcontains(fn):
1249 if not dcontains(fn):
1250 if (listignored or mexact(fn)) and dirignore(fn):
1250 if (listignored or mexact(fn)) and dirignore(fn):
1251 if listignored:
1251 if listignored:
1252 iadd(fn)
1252 iadd(fn)
1253 else:
1253 else:
1254 uadd(fn)
1254 uadd(fn)
1255 continue
1255 continue
1256
1256
1257 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1257 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1258 # written like that for performance reasons. dmap[fn] is not a
1258 # written like that for performance reasons. dmap[fn] is not a
1259 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1259 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1260 # opcode has fast paths when the value to be unpacked is a tuple or
1260 # opcode has fast paths when the value to be unpacked is a tuple or
1261 # a list, but falls back to creating a full-fledged iterator in
1261 # a list, but falls back to creating a full-fledged iterator in
1262 # general. That is much slower than simply accessing and storing the
1262 # general. That is much slower than simply accessing and storing the
1263 # tuple members one by one.
1263 # tuple members one by one.
1264 t = dget(fn)
1264 t = dget(fn)
1265 state = t[0]
1265 state = t[0]
1266 mode = t[1]
1266 mode = t[1]
1267 size = t[2]
1267 size = t[2]
1268 time = t[3]
1268 time = t[3]
1269
1269
1270 if not st and state in b"nma":
1270 if not st and state in b"nma":
1271 dadd(fn)
1271 dadd(fn)
1272 elif state == b'n':
1272 elif state == b'n':
1273 if (
1273 if (
1274 size >= 0
1274 size >= 0
1275 and (
1275 and (
1276 (size != st.st_size and size != st.st_size & _rangemask)
1276 (size != st.st_size and size != st.st_size & _rangemask)
1277 or ((mode ^ st.st_mode) & 0o100 and checkexec)
1277 or ((mode ^ st.st_mode) & 0o100 and checkexec)
1278 )
1278 )
1279 or size == -2 # other parent
1279 or size == -2 # other parent
1280 or fn in copymap
1280 or fn in copymap
1281 ):
1281 ):
1282 madd(fn)
1282 madd(fn)
1283 elif (
1283 elif (
1284 time != st[stat.ST_MTIME]
1284 time != st[stat.ST_MTIME]
1285 and time != st[stat.ST_MTIME] & _rangemask
1285 and time != st[stat.ST_MTIME] & _rangemask
1286 ):
1286 ):
1287 ladd(fn)
1287 ladd(fn)
1288 elif st[stat.ST_MTIME] == lastnormaltime:
1288 elif st[stat.ST_MTIME] == lastnormaltime:
1289 # fn may have just been marked as normal and it may have
1289 # fn may have just been marked as normal and it may have
1290 # changed in the same second without changing its size.
1290 # changed in the same second without changing its size.
1291 # This can happen if we quickly do multiple commits.
1291 # This can happen if we quickly do multiple commits.
1292 # Force lookup, so we don't miss such a racy file change.
1292 # Force lookup, so we don't miss such a racy file change.
1293 ladd(fn)
1293 ladd(fn)
1294 elif listclean:
1294 elif listclean:
1295 cadd(fn)
1295 cadd(fn)
1296 elif state == b'm':
1296 elif state == b'm':
1297 madd(fn)
1297 madd(fn)
1298 elif state == b'a':
1298 elif state == b'a':
1299 aadd(fn)
1299 aadd(fn)
1300 elif state == b'r':
1300 elif state == b'r':
1301 radd(fn)
1301 radd(fn)
1302 status = scmutil.status(
1302 status = scmutil.status(
1303 modified, added, removed, deleted, unknown, ignored, clean
1303 modified, added, removed, deleted, unknown, ignored, clean
1304 )
1304 )
1305 return (lookup, status)
1305 return (lookup, status)
1306
1306
1307 def matches(self, match):
1307 def matches(self, match):
1308 '''
1308 '''
1309 return files in the dirstate (in whatever state) filtered by match
1309 return files in the dirstate (in whatever state) filtered by match
1310 '''
1310 '''
1311 dmap = self._map
1311 dmap = self._map
1312 if rustmod is not None:
1312 if rustmod is not None:
1313 dmap = self._map._rustmap
1313 dmap = self._map._rustmap
1314
1314
1315 if match.always():
1315 if match.always():
1316 return dmap.keys()
1316 return dmap.keys()
1317 files = match.files()
1317 files = match.files()
1318 if match.isexact():
1318 if match.isexact():
1319 # fast path -- filter the other way around, since typically files is
1319 # fast path -- filter the other way around, since typically files is
1320 # much smaller than dmap
1320 # much smaller than dmap
1321 return [f for f in files if f in dmap]
1321 return [f for f in files if f in dmap]
1322 if match.prefix() and all(fn in dmap for fn in files):
1322 if match.prefix() and all(fn in dmap for fn in files):
1323 # fast path -- all the values are known to be files, so just return
1323 # fast path -- all the values are known to be files, so just return
1324 # that
1324 # that
1325 return list(files)
1325 return list(files)
1326 return [f for f in dmap if match(f)]
1326 return [f for f in dmap if match(f)]
1327
1327
1328 def _actualfilename(self, tr):
1328 def _actualfilename(self, tr):
1329 if tr:
1329 if tr:
1330 return self._pendingfilename
1330 return self._pendingfilename
1331 else:
1331 else:
1332 return self._filename
1332 return self._filename
1333
1333
1334 def savebackup(self, tr, backupname):
1334 def savebackup(self, tr, backupname):
1335 '''Save current dirstate into backup file'''
1335 '''Save current dirstate into backup file'''
1336 filename = self._actualfilename(tr)
1336 filename = self._actualfilename(tr)
1337 assert backupname != filename
1337 assert backupname != filename
1338
1338
1339 # use '_writedirstate' instead of 'write' to write changes certainly,
1339 # use '_writedirstate' instead of 'write' to write changes certainly,
1340 # because the latter omits writing out if transaction is running.
1340 # because the latter omits writing out if transaction is running.
1341 # output file will be used to create backup of dirstate at this point.
1341 # output file will be used to create backup of dirstate at this point.
1342 if self._dirty or not self._opener.exists(filename):
1342 if self._dirty or not self._opener.exists(filename):
1343 self._writedirstate(
1343 self._writedirstate(
1344 self._opener(filename, b"w", atomictemp=True, checkambig=True)
1344 self._opener(filename, b"w", atomictemp=True, checkambig=True)
1345 )
1345 )
1346
1346
1347 if tr:
1347 if tr:
1348 # ensure that subsequent tr.writepending returns True for
1348 # ensure that subsequent tr.writepending returns True for
1349 # changes written out above, even if dirstate is never
1349 # changes written out above, even if dirstate is never
1350 # changed after this
1350 # changed after this
1351 tr.addfilegenerator(
1351 tr.addfilegenerator(
1352 b'dirstate',
1352 b'dirstate',
1353 (self._filename,),
1353 (self._filename,),
1354 self._writedirstate,
1354 self._writedirstate,
1355 location=b'plain',
1355 location=b'plain',
1356 )
1356 )
1357
1357
1358 # ensure that pending file written above is unlinked at
1358 # ensure that pending file written above is unlinked at
1359 # failure, even if tr.writepending isn't invoked until the
1359 # failure, even if tr.writepending isn't invoked until the
1360 # end of this transaction
1360 # end of this transaction
1361 tr.registertmp(filename, location=b'plain')
1361 tr.registertmp(filename, location=b'plain')
1362
1362
1363 self._opener.tryunlink(backupname)
1363 self._opener.tryunlink(backupname)
1364 # hardlink backup is okay because _writedirstate is always called
1364 # hardlink backup is okay because _writedirstate is always called
1365 # with an "atomictemp=True" file.
1365 # with an "atomictemp=True" file.
1366 util.copyfile(
1366 util.copyfile(
1367 self._opener.join(filename),
1367 self._opener.join(filename),
1368 self._opener.join(backupname),
1368 self._opener.join(backupname),
1369 hardlink=True,
1369 hardlink=True,
1370 )
1370 )
1371
1371
1372 def restorebackup(self, tr, backupname):
1372 def restorebackup(self, tr, backupname):
1373 '''Restore dirstate by backup file'''
1373 '''Restore dirstate by backup file'''
1374 # this "invalidate()" prevents "wlock.release()" from writing
1374 # this "invalidate()" prevents "wlock.release()" from writing
1375 # changes of dirstate out after restoring from backup file
1375 # changes of dirstate out after restoring from backup file
1376 self.invalidate()
1376 self.invalidate()
1377 filename = self._actualfilename(tr)
1377 filename = self._actualfilename(tr)
1378 o = self._opener
1378 o = self._opener
1379 if util.samefile(o.join(backupname), o.join(filename)):
1379 if util.samefile(o.join(backupname), o.join(filename)):
1380 o.unlink(backupname)
1380 o.unlink(backupname)
1381 else:
1381 else:
1382 o.rename(backupname, filename, checkambig=True)
1382 o.rename(backupname, filename, checkambig=True)
1383
1383
1384 def clearbackup(self, tr, backupname):
1384 def clearbackup(self, tr, backupname):
1385 '''Clear backup file'''
1385 '''Clear backup file'''
1386 self._opener.unlink(backupname)
1386 self._opener.unlink(backupname)
1387
1387
1388
1388
1389 class dirstatemap(object):
1389 class dirstatemap(object):
1390 """Map encapsulating the dirstate's contents.
1390 """Map encapsulating the dirstate's contents.
1391
1391
1392 The dirstate contains the following state:
1392 The dirstate contains the following state:
1393
1393
1394 - `identity` is the identity of the dirstate file, which can be used to
1394 - `identity` is the identity of the dirstate file, which can be used to
1395 detect when changes have occurred to the dirstate file.
1395 detect when changes have occurred to the dirstate file.
1396
1396
1397 - `parents` is a pair containing the parents of the working copy. The
1397 - `parents` is a pair containing the parents of the working copy. The
1398 parents are updated by calling `setparents`.
1398 parents are updated by calling `setparents`.
1399
1399
1400 - the state map maps filenames to tuples of (state, mode, size, mtime),
1400 - the state map maps filenames to tuples of (state, mode, size, mtime),
1401 where state is a single character representing 'normal', 'added',
1401 where state is a single character representing 'normal', 'added',
1402 'removed', or 'merged'. It is read by treating the dirstate as a
1402 'removed', or 'merged'. It is read by treating the dirstate as a
1403 dict. File state is updated by calling the `addfile`, `removefile` and
1403 dict. File state is updated by calling the `addfile`, `removefile` and
1404 `dropfile` methods.
1404 `dropfile` methods.
1405
1405
1406 - `copymap` maps destination filenames to their source filename.
1406 - `copymap` maps destination filenames to their source filename.
1407
1407
1408 The dirstate also provides the following views onto the state:
1408 The dirstate also provides the following views onto the state:
1409
1409
1410 - `nonnormalset` is a set of the filenames that have state other
1410 - `nonnormalset` is a set of the filenames that have state other
1411 than 'normal', or are normal but have an mtime of -1 ('normallookup').
1411 than 'normal', or are normal but have an mtime of -1 ('normallookup').
1412
1412
1413 - `otherparentset` is a set of the filenames that are marked as coming
1413 - `otherparentset` is a set of the filenames that are marked as coming
1414 from the second parent when the dirstate is currently being merged.
1414 from the second parent when the dirstate is currently being merged.
1415
1415
1416 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
1416 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
1417 form that they appear as in the dirstate.
1417 form that they appear as in the dirstate.
1418
1418
1419 - `dirfoldmap` is a dict mapping normalized directory names to the
1419 - `dirfoldmap` is a dict mapping normalized directory names to the
1420 denormalized form that they appear as in the dirstate.
1420 denormalized form that they appear as in the dirstate.
1421 """
1421 """
1422
1422
1423 def __init__(self, ui, opener, root):
1423 def __init__(self, ui, opener, root):
1424 self._ui = ui
1424 self._ui = ui
1425 self._opener = opener
1425 self._opener = opener
1426 self._root = root
1426 self._root = root
1427 self._filename = b'dirstate'
1427 self._filename = b'dirstate'
1428 self._nodelen = 20
1428
1429
1429 self._parents = None
1430 self._parents = None
1430 self._dirtyparents = False
1431 self._dirtyparents = False
1431
1432
1432 # for consistent view between _pl() and _read() invocations
1433 # for consistent view between _pl() and _read() invocations
1433 self._pendingmode = None
1434 self._pendingmode = None
1434
1435
1435 @propertycache
1436 @propertycache
1436 def _map(self):
1437 def _map(self):
1437 self._map = {}
1438 self._map = {}
1438 self.read()
1439 self.read()
1439 return self._map
1440 return self._map
1440
1441
1441 @propertycache
1442 @propertycache
1442 def copymap(self):
1443 def copymap(self):
1443 self.copymap = {}
1444 self.copymap = {}
1444 self._map
1445 self._map
1445 return self.copymap
1446 return self.copymap
1446
1447
1447 def clear(self):
1448 def clear(self):
1448 self._map.clear()
1449 self._map.clear()
1449 self.copymap.clear()
1450 self.copymap.clear()
1450 self.setparents(nullid, nullid)
1451 self.setparents(nullid, nullid)
1451 util.clearcachedproperty(self, b"_dirs")
1452 util.clearcachedproperty(self, b"_dirs")
1452 util.clearcachedproperty(self, b"_alldirs")
1453 util.clearcachedproperty(self, b"_alldirs")
1453 util.clearcachedproperty(self, b"filefoldmap")
1454 util.clearcachedproperty(self, b"filefoldmap")
1454 util.clearcachedproperty(self, b"dirfoldmap")
1455 util.clearcachedproperty(self, b"dirfoldmap")
1455 util.clearcachedproperty(self, b"nonnormalset")
1456 util.clearcachedproperty(self, b"nonnormalset")
1456 util.clearcachedproperty(self, b"otherparentset")
1457 util.clearcachedproperty(self, b"otherparentset")
1457
1458
1458 def items(self):
1459 def items(self):
1459 return pycompat.iteritems(self._map)
1460 return pycompat.iteritems(self._map)
1460
1461
1461 # forward for python2,3 compat
1462 # forward for python2,3 compat
1462 iteritems = items
1463 iteritems = items
1463
1464
1464 def __len__(self):
1465 def __len__(self):
1465 return len(self._map)
1466 return len(self._map)
1466
1467
1467 def __iter__(self):
1468 def __iter__(self):
1468 return iter(self._map)
1469 return iter(self._map)
1469
1470
1470 def get(self, key, default=None):
1471 def get(self, key, default=None):
1471 return self._map.get(key, default)
1472 return self._map.get(key, default)
1472
1473
1473 def __contains__(self, key):
1474 def __contains__(self, key):
1474 return key in self._map
1475 return key in self._map
1475
1476
1476 def __getitem__(self, key):
1477 def __getitem__(self, key):
1477 return self._map[key]
1478 return self._map[key]
1478
1479
1479 def keys(self):
1480 def keys(self):
1480 return self._map.keys()
1481 return self._map.keys()
1481
1482
1482 def preload(self):
1483 def preload(self):
1483 """Loads the underlying data, if it's not already loaded"""
1484 """Loads the underlying data, if it's not already loaded"""
1484 self._map
1485 self._map
1485
1486
1486 def addfile(self, f, oldstate, state, mode, size, mtime):
1487 def addfile(self, f, oldstate, state, mode, size, mtime):
1487 """Add a tracked file to the dirstate."""
1488 """Add a tracked file to the dirstate."""
1488 if oldstate in b"?r" and "_dirs" in self.__dict__:
1489 if oldstate in b"?r" and "_dirs" in self.__dict__:
1489 self._dirs.addpath(f)
1490 self._dirs.addpath(f)
1490 if oldstate == b"?" and "_alldirs" in self.__dict__:
1491 if oldstate == b"?" and "_alldirs" in self.__dict__:
1491 self._alldirs.addpath(f)
1492 self._alldirs.addpath(f)
1492 self._map[f] = dirstatetuple(state, mode, size, mtime)
1493 self._map[f] = dirstatetuple(state, mode, size, mtime)
1493 if state != b'n' or mtime == -1:
1494 if state != b'n' or mtime == -1:
1494 self.nonnormalset.add(f)
1495 self.nonnormalset.add(f)
1495 if size == -2:
1496 if size == -2:
1496 self.otherparentset.add(f)
1497 self.otherparentset.add(f)
1497
1498
1498 def removefile(self, f, oldstate, size):
1499 def removefile(self, f, oldstate, size):
1499 """
1500 """
1500 Mark a file as removed in the dirstate.
1501 Mark a file as removed in the dirstate.
1501
1502
1502 The `size` parameter is used to store sentinel values that indicate
1503 The `size` parameter is used to store sentinel values that indicate
1503 the file's previous state. In the future, we should refactor this
1504 the file's previous state. In the future, we should refactor this
1504 to be more explicit about what that state is.
1505 to be more explicit about what that state is.
1505 """
1506 """
1506 if oldstate not in b"?r" and "_dirs" in self.__dict__:
1507 if oldstate not in b"?r" and "_dirs" in self.__dict__:
1507 self._dirs.delpath(f)
1508 self._dirs.delpath(f)
1508 if oldstate == b"?" and "_alldirs" in self.__dict__:
1509 if oldstate == b"?" and "_alldirs" in self.__dict__:
1509 self._alldirs.addpath(f)
1510 self._alldirs.addpath(f)
1510 if "filefoldmap" in self.__dict__:
1511 if "filefoldmap" in self.__dict__:
1511 normed = util.normcase(f)
1512 normed = util.normcase(f)
1512 self.filefoldmap.pop(normed, None)
1513 self.filefoldmap.pop(normed, None)
1513 self._map[f] = dirstatetuple(b'r', 0, size, 0)
1514 self._map[f] = dirstatetuple(b'r', 0, size, 0)
1514 self.nonnormalset.add(f)
1515 self.nonnormalset.add(f)
1515
1516
1516 def dropfile(self, f, oldstate):
1517 def dropfile(self, f, oldstate):
1517 """
1518 """
1518 Remove a file from the dirstate. Returns True if the file was
1519 Remove a file from the dirstate. Returns True if the file was
1519 previously recorded.
1520 previously recorded.
1520 """
1521 """
1521 exists = self._map.pop(f, None) is not None
1522 exists = self._map.pop(f, None) is not None
1522 if exists:
1523 if exists:
1523 if oldstate != b"r" and "_dirs" in self.__dict__:
1524 if oldstate != b"r" and "_dirs" in self.__dict__:
1524 self._dirs.delpath(f)
1525 self._dirs.delpath(f)
1525 if "_alldirs" in self.__dict__:
1526 if "_alldirs" in self.__dict__:
1526 self._alldirs.delpath(f)
1527 self._alldirs.delpath(f)
1527 if "filefoldmap" in self.__dict__:
1528 if "filefoldmap" in self.__dict__:
1528 normed = util.normcase(f)
1529 normed = util.normcase(f)
1529 self.filefoldmap.pop(normed, None)
1530 self.filefoldmap.pop(normed, None)
1530 self.nonnormalset.discard(f)
1531 self.nonnormalset.discard(f)
1531 return exists
1532 return exists
1532
1533
1533 def clearambiguoustimes(self, files, now):
1534 def clearambiguoustimes(self, files, now):
1534 for f in files:
1535 for f in files:
1535 e = self.get(f)
1536 e = self.get(f)
1536 if e is not None and e[0] == b'n' and e[3] == now:
1537 if e is not None and e[0] == b'n' and e[3] == now:
1537 self._map[f] = dirstatetuple(e[0], e[1], e[2], -1)
1538 self._map[f] = dirstatetuple(e[0], e[1], e[2], -1)
1538 self.nonnormalset.add(f)
1539 self.nonnormalset.add(f)
1539
1540
1540 def nonnormalentries(self):
1541 def nonnormalentries(self):
1541 '''Compute the nonnormal dirstate entries from the dmap'''
1542 '''Compute the nonnormal dirstate entries from the dmap'''
1542 try:
1543 try:
1543 return parsers.nonnormalotherparententries(self._map)
1544 return parsers.nonnormalotherparententries(self._map)
1544 except AttributeError:
1545 except AttributeError:
1545 nonnorm = set()
1546 nonnorm = set()
1546 otherparent = set()
1547 otherparent = set()
1547 for fname, e in pycompat.iteritems(self._map):
1548 for fname, e in pycompat.iteritems(self._map):
1548 if e[0] != b'n' or e[3] == -1:
1549 if e[0] != b'n' or e[3] == -1:
1549 nonnorm.add(fname)
1550 nonnorm.add(fname)
1550 if e[0] == b'n' and e[2] == -2:
1551 if e[0] == b'n' and e[2] == -2:
1551 otherparent.add(fname)
1552 otherparent.add(fname)
1552 return nonnorm, otherparent
1553 return nonnorm, otherparent
1553
1554
1554 @propertycache
1555 @propertycache
1555 def filefoldmap(self):
1556 def filefoldmap(self):
1556 """Returns a dictionary mapping normalized case paths to their
1557 """Returns a dictionary mapping normalized case paths to their
1557 non-normalized versions.
1558 non-normalized versions.
1558 """
1559 """
1559 try:
1560 try:
1560 makefilefoldmap = parsers.make_file_foldmap
1561 makefilefoldmap = parsers.make_file_foldmap
1561 except AttributeError:
1562 except AttributeError:
1562 pass
1563 pass
1563 else:
1564 else:
1564 return makefilefoldmap(
1565 return makefilefoldmap(
1565 self._map, util.normcasespec, util.normcasefallback
1566 self._map, util.normcasespec, util.normcasefallback
1566 )
1567 )
1567
1568
1568 f = {}
1569 f = {}
1569 normcase = util.normcase
1570 normcase = util.normcase
1570 for name, s in pycompat.iteritems(self._map):
1571 for name, s in pycompat.iteritems(self._map):
1571 if s[0] != b'r':
1572 if s[0] != b'r':
1572 f[normcase(name)] = name
1573 f[normcase(name)] = name
1573 f[b'.'] = b'.' # prevents useless util.fspath() invocation
1574 f[b'.'] = b'.' # prevents useless util.fspath() invocation
1574 return f
1575 return f
1575
1576
1576 def hastrackeddir(self, d):
1577 def hastrackeddir(self, d):
1577 """
1578 """
1578 Returns True if the dirstate contains a tracked (not removed) file
1579 Returns True if the dirstate contains a tracked (not removed) file
1579 in this directory.
1580 in this directory.
1580 """
1581 """
1581 return d in self._dirs
1582 return d in self._dirs
1582
1583
1583 def hasdir(self, d):
1584 def hasdir(self, d):
1584 """
1585 """
1585 Returns True if the dirstate contains a file (tracked or removed)
1586 Returns True if the dirstate contains a file (tracked or removed)
1586 in this directory.
1587 in this directory.
1587 """
1588 """
1588 return d in self._alldirs
1589 return d in self._alldirs
1589
1590
1590 @propertycache
1591 @propertycache
1591 def _dirs(self):
1592 def _dirs(self):
1592 return pathutil.dirs(self._map, b'r')
1593 return pathutil.dirs(self._map, b'r')
1593
1594
1594 @propertycache
1595 @propertycache
1595 def _alldirs(self):
1596 def _alldirs(self):
1596 return pathutil.dirs(self._map)
1597 return pathutil.dirs(self._map)
1597
1598
1598 def _opendirstatefile(self):
1599 def _opendirstatefile(self):
1599 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
1600 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
1600 if self._pendingmode is not None and self._pendingmode != mode:
1601 if self._pendingmode is not None and self._pendingmode != mode:
1601 fp.close()
1602 fp.close()
1602 raise error.Abort(
1603 raise error.Abort(
1603 _(b'working directory state may be changed parallelly')
1604 _(b'working directory state may be changed parallelly')
1604 )
1605 )
1605 self._pendingmode = mode
1606 self._pendingmode = mode
1606 return fp
1607 return fp
1607
1608
1608 def parents(self):
1609 def parents(self):
1609 if not self._parents:
1610 if not self._parents:
1610 try:
1611 try:
1611 fp = self._opendirstatefile()
1612 fp = self._opendirstatefile()
1612 st = fp.read(40)
1613 st = fp.read(2 * self._nodelen)
1613 fp.close()
1614 fp.close()
1614 except IOError as err:
1615 except IOError as err:
1615 if err.errno != errno.ENOENT:
1616 if err.errno != errno.ENOENT:
1616 raise
1617 raise
1617 # File doesn't exist, so the current state is empty
1618 # File doesn't exist, so the current state is empty
1618 st = b''
1619 st = b''
1619
1620
1620 l = len(st)
1621 l = len(st)
1621 if l == 40:
1622 if l == self._nodelen * 2:
1622 self._parents = (st[:20], st[20:40])
1623 self._parents = (
1624 st[: self._nodelen],
1625 st[self._nodelen : 2 * self._nodelen],
1626 )
1623 elif l == 0:
1627 elif l == 0:
1624 self._parents = (nullid, nullid)
1628 self._parents = (nullid, nullid)
1625 else:
1629 else:
1626 raise error.Abort(
1630 raise error.Abort(
1627 _(b'working directory state appears damaged!')
1631 _(b'working directory state appears damaged!')
1628 )
1632 )
1629
1633
1630 return self._parents
1634 return self._parents
1631
1635
1632 def setparents(self, p1, p2):
1636 def setparents(self, p1, p2):
1633 self._parents = (p1, p2)
1637 self._parents = (p1, p2)
1634 self._dirtyparents = True
1638 self._dirtyparents = True
1635
1639
1636 def read(self):
1640 def read(self):
1637 # ignore HG_PENDING because identity is used only for writing
1641 # ignore HG_PENDING because identity is used only for writing
1638 self.identity = util.filestat.frompath(
1642 self.identity = util.filestat.frompath(
1639 self._opener.join(self._filename)
1643 self._opener.join(self._filename)
1640 )
1644 )
1641
1645
1642 try:
1646 try:
1643 fp = self._opendirstatefile()
1647 fp = self._opendirstatefile()
1644 try:
1648 try:
1645 st = fp.read()
1649 st = fp.read()
1646 finally:
1650 finally:
1647 fp.close()
1651 fp.close()
1648 except IOError as err:
1652 except IOError as err:
1649 if err.errno != errno.ENOENT:
1653 if err.errno != errno.ENOENT:
1650 raise
1654 raise
1651 return
1655 return
1652 if not st:
1656 if not st:
1653 return
1657 return
1654
1658
1655 if util.safehasattr(parsers, b'dict_new_presized'):
1659 if util.safehasattr(parsers, b'dict_new_presized'):
1656 # Make an estimate of the number of files in the dirstate based on
1660 # Make an estimate of the number of files in the dirstate based on
1657 # its size. From a linear regression on a set of real-world repos,
1661 # its size. From a linear regression on a set of real-world repos,
1658 # all over 10,000 files, the size of a dirstate entry is 85
1662 # all over 10,000 files, the size of a dirstate entry is 2 nodes
1659 # bytes. The cost of resizing is significantly higher than the cost
1663 # plus 45 bytes. The cost of resizing is significantly higher than the cost
1660 # of filling in a larger presized dict, so subtract 20% from the
1664 # of filling in a larger presized dict, so subtract 20% from the
1661 # size.
1665 # size.
1662 #
1666 #
1663 # This heuristic is imperfect in many ways, so in a future dirstate
1667 # This heuristic is imperfect in many ways, so in a future dirstate
1664 # format update it makes sense to just record the number of entries
1668 # format update it makes sense to just record the number of entries
1665 # on write.
1669 # on write.
1666 self._map = parsers.dict_new_presized(len(st) // 71)
1670 self._map = parsers.dict_new_presized(
1671 len(st) // ((2 * self._nodelen + 45) * 4 // 5)
1672 )
1667
1673
1668 # Python's garbage collector triggers a GC each time a certain number
1674 # Python's garbage collector triggers a GC each time a certain number
1669 # of container objects (the number being defined by
1675 # of container objects (the number being defined by
1670 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
1676 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
1671 # for each file in the dirstate. The C version then immediately marks
1677 # for each file in the dirstate. The C version then immediately marks
1672 # them as not to be tracked by the collector. However, this has no
1678 # them as not to be tracked by the collector. However, this has no
1673 # effect on when GCs are triggered, only on what objects the GC looks
1679 # effect on when GCs are triggered, only on what objects the GC looks
1674 # into. This means that O(number of files) GCs are unavoidable.
1680 # into. This means that O(number of files) GCs are unavoidable.
1675 # Depending on when in the process's lifetime the dirstate is parsed,
1681 # Depending on when in the process's lifetime the dirstate is parsed,
1676 # this can get very expensive. As a workaround, disable GC while
1682 # this can get very expensive. As a workaround, disable GC while
1677 # parsing the dirstate.
1683 # parsing the dirstate.
1678 #
1684 #
1679 # (we cannot decorate the function directly since it is in a C module)
1685 # (we cannot decorate the function directly since it is in a C module)
1680 parse_dirstate = util.nogc(parsers.parse_dirstate)
1686 parse_dirstate = util.nogc(parsers.parse_dirstate)
1681 p = parse_dirstate(self._map, self.copymap, st)
1687 p = parse_dirstate(self._map, self.copymap, st)
1682 if not self._dirtyparents:
1688 if not self._dirtyparents:
1683 self.setparents(*p)
1689 self.setparents(*p)
1684
1690
1685 # Avoid excess attribute lookups by fast pathing certain checks
1691 # Avoid excess attribute lookups by fast pathing certain checks
1686 self.__contains__ = self._map.__contains__
1692 self.__contains__ = self._map.__contains__
1687 self.__getitem__ = self._map.__getitem__
1693 self.__getitem__ = self._map.__getitem__
1688 self.get = self._map.get
1694 self.get = self._map.get
1689
1695
1690 def write(self, st, now):
1696 def write(self, st, now):
1691 st.write(
1697 st.write(
1692 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
1698 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
1693 )
1699 )
1694 st.close()
1700 st.close()
1695 self._dirtyparents = False
1701 self._dirtyparents = False
1696 self.nonnormalset, self.otherparentset = self.nonnormalentries()
1702 self.nonnormalset, self.otherparentset = self.nonnormalentries()
1697
1703
1698 @propertycache
1704 @propertycache
1699 def nonnormalset(self):
1705 def nonnormalset(self):
1700 nonnorm, otherparents = self.nonnormalentries()
1706 nonnorm, otherparents = self.nonnormalentries()
1701 self.otherparentset = otherparents
1707 self.otherparentset = otherparents
1702 return nonnorm
1708 return nonnorm
1703
1709
1704 @propertycache
1710 @propertycache
1705 def otherparentset(self):
1711 def otherparentset(self):
1706 nonnorm, otherparents = self.nonnormalentries()
1712 nonnorm, otherparents = self.nonnormalentries()
1707 self.nonnormalset = nonnorm
1713 self.nonnormalset = nonnorm
1708 return otherparents
1714 return otherparents
1709
1715
1710 @propertycache
1716 @propertycache
1711 def identity(self):
1717 def identity(self):
1712 self._map
1718 self._map
1713 return self.identity
1719 return self.identity
1714
1720
1715 @propertycache
1721 @propertycache
1716 def dirfoldmap(self):
1722 def dirfoldmap(self):
1717 f = {}
1723 f = {}
1718 normcase = util.normcase
1724 normcase = util.normcase
1719 for name in self._dirs:
1725 for name in self._dirs:
1720 f[normcase(name)] = name
1726 f[normcase(name)] = name
1721 return f
1727 return f
1722
1728
1723
1729
1724 if rustmod is not None:
1730 if rustmod is not None:
1725
1731
1726 class dirstatemap(object):
1732 class dirstatemap(object):
1727 def __init__(self, ui, opener, root):
1733 def __init__(self, ui, opener, root):
1728 self._ui = ui
1734 self._ui = ui
1729 self._opener = opener
1735 self._opener = opener
1730 self._root = root
1736 self._root = root
1731 self._filename = b'dirstate'
1737 self._filename = b'dirstate'
1732 self._parents = None
1738 self._parents = None
1733 self._dirtyparents = False
1739 self._dirtyparents = False
1734
1740
1735 # for consistent view between _pl() and _read() invocations
1741 # for consistent view between _pl() and _read() invocations
1736 self._pendingmode = None
1742 self._pendingmode = None
1737
1743
1738 def addfile(self, *args, **kwargs):
1744 def addfile(self, *args, **kwargs):
1739 return self._rustmap.addfile(*args, **kwargs)
1745 return self._rustmap.addfile(*args, **kwargs)
1740
1746
1741 def removefile(self, *args, **kwargs):
1747 def removefile(self, *args, **kwargs):
1742 return self._rustmap.removefile(*args, **kwargs)
1748 return self._rustmap.removefile(*args, **kwargs)
1743
1749
1744 def dropfile(self, *args, **kwargs):
1750 def dropfile(self, *args, **kwargs):
1745 return self._rustmap.dropfile(*args, **kwargs)
1751 return self._rustmap.dropfile(*args, **kwargs)
1746
1752
1747 def clearambiguoustimes(self, *args, **kwargs):
1753 def clearambiguoustimes(self, *args, **kwargs):
1748 return self._rustmap.clearambiguoustimes(*args, **kwargs)
1754 return self._rustmap.clearambiguoustimes(*args, **kwargs)
1749
1755
1750 def nonnormalentries(self):
1756 def nonnormalentries(self):
1751 return self._rustmap.nonnormalentries()
1757 return self._rustmap.nonnormalentries()
1752
1758
1753 def get(self, *args, **kwargs):
1759 def get(self, *args, **kwargs):
1754 return self._rustmap.get(*args, **kwargs)
1760 return self._rustmap.get(*args, **kwargs)
1755
1761
1756 @propertycache
1762 @propertycache
1757 def _rustmap(self):
1763 def _rustmap(self):
1758 """
1764 """
1759 Fills the Dirstatemap when called.
1765 Fills the Dirstatemap when called.
1760 Use `self._inner_rustmap` if reading the dirstate is not necessary.
1766 Use `self._inner_rustmap` if reading the dirstate is not necessary.
1761 """
1767 """
1762 self._rustmap = self._inner_rustmap
1768 self._rustmap = self._inner_rustmap
1763 self.read()
1769 self.read()
1764 return self._rustmap
1770 return self._rustmap
1765
1771
1766 @propertycache
1772 @propertycache
1767 def _inner_rustmap(self):
1773 def _inner_rustmap(self):
1768 """
1774 """
1769 Does not fill the Dirstatemap when called. This allows for
1775 Does not fill the Dirstatemap when called. This allows for
1770 optimizations where only setting/getting the parents is needed.
1776 optimizations where only setting/getting the parents is needed.
1771 """
1777 """
1772 self._inner_rustmap = rustmod.DirstateMap(self._root)
1778 self._inner_rustmap = rustmod.DirstateMap(self._root)
1773 return self._inner_rustmap
1779 return self._inner_rustmap
1774
1780
1775 @property
1781 @property
1776 def copymap(self):
1782 def copymap(self):
1777 return self._rustmap.copymap()
1783 return self._rustmap.copymap()
1778
1784
1779 def preload(self):
1785 def preload(self):
1780 self._rustmap
1786 self._rustmap
1781
1787
1782 def clear(self):
1788 def clear(self):
1783 self._rustmap.clear()
1789 self._rustmap.clear()
1784 self._inner_rustmap.clear()
1790 self._inner_rustmap.clear()
1785 self.setparents(nullid, nullid)
1791 self.setparents(nullid, nullid)
1786 util.clearcachedproperty(self, b"_dirs")
1792 util.clearcachedproperty(self, b"_dirs")
1787 util.clearcachedproperty(self, b"_alldirs")
1793 util.clearcachedproperty(self, b"_alldirs")
1788 util.clearcachedproperty(self, b"dirfoldmap")
1794 util.clearcachedproperty(self, b"dirfoldmap")
1789
1795
1790 def items(self):
1796 def items(self):
1791 return self._rustmap.items()
1797 return self._rustmap.items()
1792
1798
1793 def keys(self):
1799 def keys(self):
1794 return iter(self._rustmap)
1800 return iter(self._rustmap)
1795
1801
1796 def __contains__(self, key):
1802 def __contains__(self, key):
1797 return key in self._rustmap
1803 return key in self._rustmap
1798
1804
1799 def __getitem__(self, item):
1805 def __getitem__(self, item):
1800 return self._rustmap[item]
1806 return self._rustmap[item]
1801
1807
1802 def __len__(self):
1808 def __len__(self):
1803 return len(self._rustmap)
1809 return len(self._rustmap)
1804
1810
1805 def __iter__(self):
1811 def __iter__(self):
1806 return iter(self._rustmap)
1812 return iter(self._rustmap)
1807
1813
1808 # forward for python2,3 compat
1814 # forward for python2,3 compat
1809 iteritems = items
1815 iteritems = items
1810
1816
1811 def _opendirstatefile(self):
1817 def _opendirstatefile(self):
1812 fp, mode = txnutil.trypending(
1818 fp, mode = txnutil.trypending(
1813 self._root, self._opener, self._filename
1819 self._root, self._opener, self._filename
1814 )
1820 )
1815 if self._pendingmode is not None and self._pendingmode != mode:
1821 if self._pendingmode is not None and self._pendingmode != mode:
1816 fp.close()
1822 fp.close()
1817 raise error.Abort(
1823 raise error.Abort(
1818 _(b'working directory state may be changed parallelly')
1824 _(b'working directory state may be changed parallelly')
1819 )
1825 )
1820 self._pendingmode = mode
1826 self._pendingmode = mode
1821 return fp
1827 return fp
1822
1828
1823 def setparents(self, p1, p2):
1829 def setparents(self, p1, p2):
1824 self._rustmap.setparents(p1, p2)
1830 self._rustmap.setparents(p1, p2)
1825 self._parents = (p1, p2)
1831 self._parents = (p1, p2)
1826 self._dirtyparents = True
1832 self._dirtyparents = True
1827
1833
1828 def parents(self):
1834 def parents(self):
1829 if not self._parents:
1835 if not self._parents:
1830 try:
1836 try:
1831 fp = self._opendirstatefile()
1837 fp = self._opendirstatefile()
1832 st = fp.read(40)
1838 st = fp.read(2 * self._nodelen)
1833 fp.close()
1839 fp.close()
1834 except IOError as err:
1840 except IOError as err:
1835 if err.errno != errno.ENOENT:
1841 if err.errno != errno.ENOENT:
1836 raise
1842 raise
1837 # File doesn't exist, so the current state is empty
1843 # File doesn't exist, so the current state is empty
1838 st = b''
1844 st = b''
1839
1845
1840 try:
1846 try:
1841 self._parents = self._inner_rustmap.parents(st)
1847 self._parents = self._inner_rustmap.parents(st)
1842 except ValueError:
1848 except ValueError:
1843 raise error.Abort(
1849 raise error.Abort(
1844 _(b'working directory state appears damaged!')
1850 _(b'working directory state appears damaged!')
1845 )
1851 )
1846
1852
1847 return self._parents
1853 return self._parents
1848
1854
1849 def read(self):
1855 def read(self):
1850 # ignore HG_PENDING because identity is used only for writing
1856 # ignore HG_PENDING because identity is used only for writing
1851 self.identity = util.filestat.frompath(
1857 self.identity = util.filestat.frompath(
1852 self._opener.join(self._filename)
1858 self._opener.join(self._filename)
1853 )
1859 )
1854
1860
1855 try:
1861 try:
1856 fp = self._opendirstatefile()
1862 fp = self._opendirstatefile()
1857 try:
1863 try:
1858 st = fp.read()
1864 st = fp.read()
1859 finally:
1865 finally:
1860 fp.close()
1866 fp.close()
1861 except IOError as err:
1867 except IOError as err:
1862 if err.errno != errno.ENOENT:
1868 if err.errno != errno.ENOENT:
1863 raise
1869 raise
1864 return
1870 return
1865 if not st:
1871 if not st:
1866 return
1872 return
1867
1873
1868 parse_dirstate = util.nogc(self._rustmap.read)
1874 parse_dirstate = util.nogc(self._rustmap.read)
1869 parents = parse_dirstate(st)
1875 parents = parse_dirstate(st)
1870 if parents and not self._dirtyparents:
1876 if parents and not self._dirtyparents:
1871 self.setparents(*parents)
1877 self.setparents(*parents)
1872
1878
1873 self.__contains__ = self._rustmap.__contains__
1879 self.__contains__ = self._rustmap.__contains__
1874 self.__getitem__ = self._rustmap.__getitem__
1880 self.__getitem__ = self._rustmap.__getitem__
1875 self.get = self._rustmap.get
1881 self.get = self._rustmap.get
1876
1882
1877 def write(self, st, now):
1883 def write(self, st, now):
1878 parents = self.parents()
1884 parents = self.parents()
1879 st.write(self._rustmap.write(parents[0], parents[1], now))
1885 st.write(self._rustmap.write(parents[0], parents[1], now))
1880 st.close()
1886 st.close()
1881 self._dirtyparents = False
1887 self._dirtyparents = False
1882
1888
1883 @propertycache
1889 @propertycache
1884 def filefoldmap(self):
1890 def filefoldmap(self):
1885 """Returns a dictionary mapping normalized case paths to their
1891 """Returns a dictionary mapping normalized case paths to their
1886 non-normalized versions.
1892 non-normalized versions.
1887 """
1893 """
1888 return self._rustmap.filefoldmapasdict()
1894 return self._rustmap.filefoldmapasdict()
1889
1895
1890 def hastrackeddir(self, d):
1896 def hastrackeddir(self, d):
1891 self._dirs # Trigger Python's propertycache
1897 self._dirs # Trigger Python's propertycache
1892 return self._rustmap.hastrackeddir(d)
1898 return self._rustmap.hastrackeddir(d)
1893
1899
1894 def hasdir(self, d):
1900 def hasdir(self, d):
1895 self._dirs # Trigger Python's propertycache
1901 self._dirs # Trigger Python's propertycache
1896 return self._rustmap.hasdir(d)
1902 return self._rustmap.hasdir(d)
1897
1903
1898 @propertycache
1904 @propertycache
1899 def _dirs(self):
1905 def _dirs(self):
1900 return self._rustmap.getdirs()
1906 return self._rustmap.getdirs()
1901
1907
1902 @propertycache
1908 @propertycache
1903 def _alldirs(self):
1909 def _alldirs(self):
1904 return self._rustmap.getalldirs()
1910 return self._rustmap.getalldirs()
1905
1911
1906 @propertycache
1912 @propertycache
1907 def identity(self):
1913 def identity(self):
1908 self._rustmap
1914 self._rustmap
1909 return self.identity
1915 return self.identity
1910
1916
1911 @property
1917 @property
1912 def nonnormalset(self):
1918 def nonnormalset(self):
1913 nonnorm = self._rustmap.non_normal_entries()
1919 nonnorm = self._rustmap.non_normal_entries()
1914 return nonnorm
1920 return nonnorm
1915
1921
1916 @propertycache
1922 @propertycache
1917 def otherparentset(self):
1923 def otherparentset(self):
1918 otherparents = self._rustmap.other_parent_entries()
1924 otherparents = self._rustmap.other_parent_entries()
1919 return otherparents
1925 return otherparents
1920
1926
1921 @propertycache
1927 @propertycache
1922 def dirfoldmap(self):
1928 def dirfoldmap(self):
1923 f = {}
1929 f = {}
1924 normcase = util.normcase
1930 normcase = util.normcase
1925 for name in self._dirs:
1931 for name in self._dirs:
1926 f[normcase(name)] = name
1932 f[normcase(name)] = name
1927 return f
1933 return f
General Comments 0
You need to be logged in to leave comments. Login now