mercurial/dirstate.py
branchstable
changeset 49366 288de6f5d724
parent 49361 c2092612c424
child 49467 0705afae6253
equal deleted inserted replaced
49364:e8ea403b1c46 49366:288de6f5d724
     3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
     3 # Copyright 2005-2007 Olivia Mackall <olivia@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
       
     9 
     8 
    10 import collections
     9 import collections
    11 import contextlib
    10 import contextlib
    12 import errno
       
    13 import os
    11 import os
    14 import stat
    12 import stat
    15 import uuid
    13 import uuid
    16 
    14 
    17 from .i18n import _
    15 from .i18n import _
    27     node,
    25     node,
    28     pathutil,
    26     pathutil,
    29     policy,
    27     policy,
    30     pycompat,
    28     pycompat,
    31     scmutil,
    29     scmutil,
    32     sparse,
       
    33     util,
    30     util,
    34 )
    31 )
    35 
    32 
    36 from .dirstateutils import (
    33 from .dirstateutils import (
    37     timestamp,
    34     timestamp,
    89 
    86 
    90     return wrap
    87     return wrap
    91 
    88 
    92 
    89 
    93 @interfaceutil.implementer(intdirstate.idirstate)
    90 @interfaceutil.implementer(intdirstate.idirstate)
    94 class dirstate(object):
    91 class dirstate:
    95     def __init__(
    92     def __init__(
    96         self,
    93         self,
    97         opener,
    94         opener,
    98         ui,
    95         ui,
    99         root,
    96         root,
   113         self._use_tracked_hint = use_tracked_hint
   110         self._use_tracked_hint = use_tracked_hint
   114         self._nodeconstants = nodeconstants
   111         self._nodeconstants = nodeconstants
   115         self._opener = opener
   112         self._opener = opener
   116         self._validate = validate
   113         self._validate = validate
   117         self._root = root
   114         self._root = root
       
   115         # Either build a sparse-matcher or None if sparse is disabled
   118         self._sparsematchfn = sparsematchfn
   116         self._sparsematchfn = sparsematchfn
   119         # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
   117         # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
   120         # UNC path pointing to root share (issue4557)
   118         # UNC path pointing to root share (issue4557)
   121         self._rootdir = pathutil.normasprefix(root)
   119         self._rootdir = pathutil.normasprefix(root)
   122         # True is any internal state may be different
   120         # True is any internal state may be different
   184         """The matcher for the sparse checkout.
   182         """The matcher for the sparse checkout.
   185 
   183 
   186         The working directory may not include every file from a manifest. The
   184         The working directory may not include every file from a manifest. The
   187         matcher obtained by this property will match a path if it is to be
   185         matcher obtained by this property will match a path if it is to be
   188         included in the working directory.
   186         included in the working directory.
   189         """
   187 
       
   188         When sparse if disabled, return None.
       
   189         """
       
   190         if self._sparsematchfn is None:
       
   191             return None
   190         # TODO there is potential to cache this property. For now, the matcher
   192         # TODO there is potential to cache this property. For now, the matcher
   191         # is resolved on every access. (But the called function does use a
   193         # is resolved on every access. (But the called function does use a
   192         # cache to keep the lookup fast.)
   194         # cache to keep the lookup fast.)
   193         return self._sparsematchfn()
   195         return self._sparsematchfn()
   194 
   196 
   195     @repocache(b'branch')
   197     @repocache(b'branch')
   196     def _branch(self):
   198     def _branch(self):
   197         try:
   199         try:
   198             return self._opener.read(b"branch").strip() or b"default"
   200             return self._opener.read(b"branch").strip() or b"default"
   199         except IOError as inst:
   201         except FileNotFoundError:
   200             if inst.errno != errno.ENOENT:
       
   201                 raise
       
   202             return b"default"
   202             return b"default"
   203 
   203 
   204     @property
   204     @property
   205     def _pl(self):
   205     def _pl(self):
   206         return self._map.parents()
   206         return self._map.parents()
   341 
   341 
   342     def __iter__(self):
   342     def __iter__(self):
   343         return iter(sorted(self._map))
   343         return iter(sorted(self._map))
   344 
   344 
   345     def items(self):
   345     def items(self):
   346         return pycompat.iteritems(self._map)
   346         return self._map.items()
   347 
   347 
   348     iteritems = items
   348     iteritems = items
   349 
   349 
   350     def parents(self):
   350     def parents(self):
   351         return [self._validate(p) for p in self._pl]
   351         return [self._validate(p) for p in self._pl]
   425         """Mark dest as a copy of source. Unmark dest if source is None."""
   425         """Mark dest as a copy of source. Unmark dest if source is None."""
   426         if source == dest:
   426         if source == dest:
   427             return
   427             return
   428         self._dirty = True
   428         self._dirty = True
   429         if source is not None:
   429         if source is not None:
       
   430             self._check_sparse(source)
   430             self._map.copymap[dest] = source
   431             self._map.copymap[dest] = source
   431         else:
   432         else:
   432             self._map.copymap.pop(dest, None)
   433             self._map.copymap.pop(dest, None)
   433 
   434 
   434     def copied(self, file):
   435     def copied(self, file):
   586             entry = self._map.get(d)
   587             entry = self._map.get(d)
   587             if entry is not None and not entry.removed:
   588             if entry is not None and not entry.removed:
   588                 msg = _(b'file %r in dirstate clashes with %r')
   589                 msg = _(b'file %r in dirstate clashes with %r')
   589                 msg %= (pycompat.bytestr(d), pycompat.bytestr(filename))
   590                 msg %= (pycompat.bytestr(d), pycompat.bytestr(filename))
   590                 raise error.Abort(msg)
   591                 raise error.Abort(msg)
       
   592         self._check_sparse(filename)
       
   593 
       
   594     def _check_sparse(self, filename):
       
   595         """Check that a filename is inside the sparse profile"""
       
   596         sparsematch = self._sparsematcher
       
   597         if sparsematch is not None and not sparsematch.always():
       
   598             if not sparsematch(filename):
       
   599                 msg = _(b"cannot add '%s' - it is outside the sparse checkout")
       
   600                 hint = _(
       
   601                     b'include file with `hg debugsparse --include <pattern>` or use '
       
   602                     b'`hg add -s <file>` to include file directory while adding'
       
   603                 )
       
   604                 raise error.Abort(msg % filename, hint=hint)
   591 
   605 
   592     def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
   606     def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
   593         if exists is None:
   607         if exists is None:
   594             exists = os.path.lexists(os.path.join(self._root, path))
   608             exists = os.path.lexists(os.path.join(self._root, path))
   595         if not exists:
   609         if not exists:
   668     def clear(self):
   682     def clear(self):
   669         self._map.clear()
   683         self._map.clear()
   670         self._dirty = True
   684         self._dirty = True
   671 
   685 
   672     def rebuild(self, parent, allfiles, changedfiles=None):
   686     def rebuild(self, parent, allfiles, changedfiles=None):
       
   687 
       
   688         matcher = self._sparsematcher
       
   689         if matcher is not None and not matcher.always():
       
   690             # should not add non-matching files
       
   691             allfiles = [f for f in allfiles if matcher(f)]
       
   692             if changedfiles:
       
   693                 changedfiles = [f for f in changedfiles if matcher(f)]
       
   694 
       
   695             if changedfiles is not None:
       
   696                 # these files will be deleted from the dirstate when they are
       
   697                 # not found to be in allfiles
       
   698                 dirstatefilestoremove = {f for f in self if not matcher(f)}
       
   699                 changedfiles = dirstatefilestoremove.union(changedfiles)
       
   700 
   673         if changedfiles is None:
   701         if changedfiles is None:
   674             # Rebuild entire dirstate
   702             # Rebuild entire dirstate
   675             to_lookup = allfiles
   703             to_lookup = allfiles
   676             to_drop = []
   704             to_drop = []
   677             self.clear()
   705             self.clear()
   769         self._plchangecallbacks[category] = callback
   797         self._plchangecallbacks[category] = callback
   770 
   798 
   771     def _writedirstate(self, tr, st):
   799     def _writedirstate(self, tr, st):
   772         # notify callbacks about parents change
   800         # notify callbacks about parents change
   773         if self._origpl is not None and self._origpl != self._pl:
   801         if self._origpl is not None and self._origpl != self._pl:
   774             for c, callback in sorted(
   802             for c, callback in sorted(self._plchangecallbacks.items()):
   775                 pycompat.iteritems(self._plchangecallbacks)
       
   776             ):
       
   777                 callback(self, self._origpl, self._pl)
   803                 callback(self, self._origpl, self._pl)
   778             self._origpl = None
   804             self._origpl = None
   779         self._map.write(tr, st)
   805         self._map.write(tr, st)
   780         self._dirty = False
   806         self._dirty = False
   781         self._dirty_tracked_set = False
   807         self._dirty_tracked_set = False
   934         # match the case in the filesystem, if there are multiple files that
   960         # match the case in the filesystem, if there are multiple files that
   935         # normalize to the same path.
   961         # normalize to the same path.
   936         if match.isexact() and self._checkcase:
   962         if match.isexact() and self._checkcase:
   937             normed = {}
   963             normed = {}
   938 
   964 
   939             for f, st in pycompat.iteritems(results):
   965             for f, st in results.items():
   940                 if st is None:
   966                 if st is None:
   941                     continue
   967                     continue
   942 
   968 
   943                 nc = util.normcase(f)
   969                 nc = util.normcase(f)
   944                 paths = normed.get(nc)
   970                 paths = normed.get(nc)
   947                     paths = set()
   973                     paths = set()
   948                     normed[nc] = paths
   974                     normed[nc] = paths
   949 
   975 
   950                 paths.add(f)
   976                 paths.add(f)
   951 
   977 
   952             for norm, paths in pycompat.iteritems(normed):
   978             for norm, paths in normed.items():
   953                 if len(paths) > 1:
   979                 if len(paths) > 1:
   954                     for path in paths:
   980                     for path in paths:
   955                         folded = self._discoverpath(
   981                         folded = self._discoverpath(
   956                             path, norm, True, None, self._map.dirfoldmap
   982                             path, norm, True, None, self._map.dirfoldmap
   957                         )
   983                         )
   983             dirignore = self._dirignore
  1009             dirignore = self._dirignore
   984         else:
  1010         else:
   985             # if not unknown and not ignored, drop dir recursion and step 2
  1011             # if not unknown and not ignored, drop dir recursion and step 2
   986             ignore = util.always
  1012             ignore = util.always
   987             dirignore = util.always
  1013             dirignore = util.always
       
  1014 
       
  1015         if self._sparsematchfn is not None:
       
  1016             em = matchmod.exact(match.files())
       
  1017             sm = matchmod.unionmatcher([self._sparsematcher, em])
       
  1018             match = matchmod.intersectmatchers(match, sm)
   988 
  1019 
   989         matchfn = match.matchfn
  1020         matchfn = match.matchfn
   990         matchalways = match.always()
  1021         matchalways = match.always()
   991         matchtdir = match.traversedir
  1022         matchtdir = match.traversedir
   992         dmap = self._map
  1023         dmap = self._map
  1038                 if nd != b'':
  1069                 if nd != b'':
  1039                     skip = b'.hg'
  1070                     skip = b'.hg'
  1040                 try:
  1071                 try:
  1041                     with tracing.log('dirstate.walk.traverse listdir %s', nd):
  1072                     with tracing.log('dirstate.walk.traverse listdir %s', nd):
  1042                         entries = listdir(join(nd), stat=True, skip=skip)
  1073                         entries = listdir(join(nd), stat=True, skip=skip)
  1043                 except OSError as inst:
  1074                 except (PermissionError, FileNotFoundError) as inst:
  1044                     if inst.errno in (errno.EACCES, errno.ENOENT):
  1075                     match.bad(
  1045                         match.bad(
  1076                         self.pathto(nd), encoding.strtolocal(inst.strerror)
  1046                             self.pathto(nd), encoding.strtolocal(inst.strerror)
  1077                     )
  1047                         )
  1078                     continue
  1048                         continue
       
  1049                     raise
       
  1050                 for f, kind, st in entries:
  1079                 for f, kind, st in entries:
  1051                     # Some matchers may return files in the visitentries set,
  1080                     # Some matchers may return files in the visitentries set,
  1052                     # instead of 'this', if the matcher explicitly mentions them
  1081                     # instead of 'this', if the matcher explicitly mentions them
  1053                     # and is not an exactmatcher. This is acceptable; we do not
  1082                     # and is not an exactmatcher. This is acceptable; we do not
  1054                     # make any hard assumptions about file-or-directory below
  1083                     # make any hard assumptions about file-or-directory below
  1147                 for st in util.statfiles([join(i) for i in visit]):
  1176                 for st in util.statfiles([join(i) for i in visit]):
  1148                     results[next(iv)] = st
  1177                     results[next(iv)] = st
  1149         return results
  1178         return results
  1150 
  1179 
  1151     def _rust_status(self, matcher, list_clean, list_ignored, list_unknown):
  1180     def _rust_status(self, matcher, list_clean, list_ignored, list_unknown):
       
  1181         if self._sparsematchfn is not None:
       
  1182             em = matchmod.exact(matcher.files())
       
  1183             sm = matchmod.unionmatcher([self._sparsematcher, em])
       
  1184             matcher = matchmod.intersectmatchers(matcher, sm)
  1152         # Force Rayon (Rust parallelism library) to respect the number of
  1185         # Force Rayon (Rust parallelism library) to respect the number of
  1153         # workers. This is a temporary workaround until Rust code knows
  1186         # workers. This is a temporary workaround until Rust code knows
  1154         # how to read the config file.
  1187         # how to read the config file.
  1155         numcpus = self._ui.configint(b"worker", b"numcpus")
  1188         numcpus = self._ui.configint(b"worker", b"numcpus")
  1156         if numcpus is not None:
  1189         if numcpus is not None:
  1253 
  1286 
  1254         allowed_matchers = (
  1287         allowed_matchers = (
  1255             matchmod.alwaysmatcher,
  1288             matchmod.alwaysmatcher,
  1256             matchmod.exactmatcher,
  1289             matchmod.exactmatcher,
  1257             matchmod.includematcher,
  1290             matchmod.includematcher,
       
  1291             matchmod.intersectionmatcher,
       
  1292             matchmod.nevermatcher,
       
  1293             matchmod.unionmatcher,
  1258         )
  1294         )
  1259 
  1295 
  1260         if rustmod is None:
  1296         if rustmod is None:
  1261             use_rust = False
  1297             use_rust = False
  1262         elif self._checkcase:
  1298         elif self._checkcase:
  1263             # Case-insensitive filesystems are not handled yet
  1299             # Case-insensitive filesystems are not handled yet
  1264             use_rust = False
  1300             use_rust = False
  1265         elif subrepos:
  1301         elif subrepos:
  1266             use_rust = False
       
  1267         elif sparse.enabled:
       
  1268             use_rust = False
  1302             use_rust = False
  1269         elif not isinstance(match, allowed_matchers):
  1303         elif not isinstance(match, allowed_matchers):
  1270             # Some matchers have yet to be implemented
  1304             # Some matchers have yet to be implemented
  1271             use_rust = False
  1305             use_rust = False
  1272 
  1306 
  1309         # We need to do full walks when either
  1343         # We need to do full walks when either
  1310         # - we're listing all clean files, or
  1344         # - we're listing all clean files, or
  1311         # - match.traversedir does something, because match.traversedir should
  1345         # - match.traversedir does something, because match.traversedir should
  1312         #   be called for every dir in the working dir
  1346         #   be called for every dir in the working dir
  1313         full = listclean or match.traversedir is not None
  1347         full = listclean or match.traversedir is not None
  1314         for fn, st in pycompat.iteritems(
  1348         for fn, st in self.walk(
  1315             self.walk(match, subrepos, listunknown, listignored, full=full)
  1349             match, subrepos, listunknown, listignored, full=full
  1316         ):
  1350         ).items():
  1317             if not dcontains(fn):
  1351             if not dcontains(fn):
  1318                 if (listignored or mexact(fn)) and dirignore(fn):
  1352                 if (listignored or mexact(fn)) and dirignore(fn):
  1319                     if listignored:
  1353                     if listignored:
  1320                         iadd(fn)
  1354                         iadd(fn)
  1321                 else:
  1355                 else: