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() |
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: |