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