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