##// END OF EJS Templates
rust-status: only visit parts of the tree requested by the matcher...
Spencer Baugh -
r51757:76387f79 default
parent child Browse files
Show More
@@ -1,1026 +1,1055 b''
1 use crate::dirstate::entry::TruncatedTimestamp;
1 use crate::dirstate::entry::TruncatedTimestamp;
2 use crate::dirstate::status::IgnoreFnType;
2 use crate::dirstate::status::IgnoreFnType;
3 use crate::dirstate::status::StatusPath;
3 use crate::dirstate::status::StatusPath;
4 use crate::dirstate_tree::dirstate_map::BorrowedPath;
4 use crate::dirstate_tree::dirstate_map::BorrowedPath;
5 use crate::dirstate_tree::dirstate_map::ChildNodesRef;
5 use crate::dirstate_tree::dirstate_map::ChildNodesRef;
6 use crate::dirstate_tree::dirstate_map::DirstateMap;
6 use crate::dirstate_tree::dirstate_map::DirstateMap;
7 use crate::dirstate_tree::dirstate_map::DirstateVersion;
7 use crate::dirstate_tree::dirstate_map::DirstateVersion;
8 use crate::dirstate_tree::dirstate_map::NodeRef;
8 use crate::dirstate_tree::dirstate_map::NodeRef;
9 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
9 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
10 use crate::matchers::get_ignore_function;
10 use crate::matchers::get_ignore_function;
11 use crate::matchers::Matcher;
11 use crate::matchers::{Matcher, VisitChildrenSet};
12 use crate::utils::files::get_bytes_from_os_string;
12 use crate::utils::files::get_bytes_from_os_string;
13 use crate::utils::files::get_bytes_from_path;
13 use crate::utils::files::get_bytes_from_path;
14 use crate::utils::files::get_path_from_bytes;
14 use crate::utils::files::get_path_from_bytes;
15 use crate::utils::hg_path::hg_path_to_path_buf;
15 use crate::utils::hg_path::hg_path_to_path_buf;
16 use crate::utils::hg_path::HgPath;
16 use crate::utils::hg_path::HgPath;
17 use crate::BadMatch;
17 use crate::BadMatch;
18 use crate::BadType;
18 use crate::BadType;
19 use crate::DirstateStatus;
19 use crate::DirstateStatus;
20 use crate::HgPathCow;
20 use crate::HgPathCow;
21 use crate::PatternFileWarning;
21 use crate::PatternFileWarning;
22 use crate::StatusError;
22 use crate::StatusError;
23 use crate::StatusOptions;
23 use crate::StatusOptions;
24 use once_cell::sync::OnceCell;
24 use once_cell::sync::OnceCell;
25 use rayon::prelude::*;
25 use rayon::prelude::*;
26 use sha1::{Digest, Sha1};
26 use sha1::{Digest, Sha1};
27 use std::borrow::Cow;
27 use std::borrow::Cow;
28 use std::io;
28 use std::io;
29 use std::os::unix::prelude::FileTypeExt;
29 use std::os::unix::prelude::FileTypeExt;
30 use std::path::Path;
30 use std::path::Path;
31 use std::path::PathBuf;
31 use std::path::PathBuf;
32 use std::sync::Mutex;
32 use std::sync::Mutex;
33 use std::time::SystemTime;
33 use std::time::SystemTime;
34
34
35 /// Returns the status of the working directory compared to its parent
35 /// Returns the status of the working directory compared to its parent
36 /// changeset.
36 /// changeset.
37 ///
37 ///
38 /// This algorithm is based on traversing the filesystem tree (`fs` in function
38 /// This algorithm is based on traversing the filesystem tree (`fs` in function
39 /// and variable names) and dirstate tree at the same time. The core of this
39 /// and variable names) and dirstate tree at the same time. The core of this
40 /// traversal is the recursive `traverse_fs_directory_and_dirstate` function
40 /// traversal is the recursive `traverse_fs_directory_and_dirstate` function
41 /// and its use of `itertools::merge_join_by`. When reaching a path that only
41 /// and its use of `itertools::merge_join_by`. When reaching a path that only
42 /// exists in one of the two trees, depending on information requested by
42 /// exists in one of the two trees, depending on information requested by
43 /// `options` we may need to traverse the remaining subtree.
43 /// `options` we may need to traverse the remaining subtree.
44 #[logging_timer::time("trace")]
44 #[logging_timer::time("trace")]
45 pub fn status<'dirstate>(
45 pub fn status<'dirstate>(
46 dmap: &'dirstate mut DirstateMap,
46 dmap: &'dirstate mut DirstateMap,
47 matcher: &(dyn Matcher + Sync),
47 matcher: &(dyn Matcher + Sync),
48 root_dir: PathBuf,
48 root_dir: PathBuf,
49 ignore_files: Vec<PathBuf>,
49 ignore_files: Vec<PathBuf>,
50 options: StatusOptions,
50 options: StatusOptions,
51 ) -> Result<(DirstateStatus<'dirstate>, Vec<PatternFileWarning>), StatusError>
51 ) -> Result<(DirstateStatus<'dirstate>, Vec<PatternFileWarning>), StatusError>
52 {
52 {
53 // Also cap for a Python caller of this function, but don't complain if
53 // Also cap for a Python caller of this function, but don't complain if
54 // the global threadpool has already been set since this code path is also
54 // the global threadpool has already been set since this code path is also
55 // being used by `rhg`, which calls this early.
55 // being used by `rhg`, which calls this early.
56 let _ = crate::utils::cap_default_rayon_threads();
56 let _ = crate::utils::cap_default_rayon_threads();
57
57
58 let (ignore_fn, warnings, patterns_changed): (IgnoreFnType, _, _) =
58 let (ignore_fn, warnings, patterns_changed): (IgnoreFnType, _, _) =
59 if options.list_ignored || options.list_unknown {
59 if options.list_ignored || options.list_unknown {
60 let (ignore_fn, warnings, changed) = match dmap.dirstate_version {
60 let (ignore_fn, warnings, changed) = match dmap.dirstate_version {
61 DirstateVersion::V1 => {
61 DirstateVersion::V1 => {
62 let (ignore_fn, warnings) = get_ignore_function(
62 let (ignore_fn, warnings) = get_ignore_function(
63 ignore_files,
63 ignore_files,
64 &root_dir,
64 &root_dir,
65 &mut |_source, _pattern_bytes| {},
65 &mut |_source, _pattern_bytes| {},
66 )?;
66 )?;
67 (ignore_fn, warnings, None)
67 (ignore_fn, warnings, None)
68 }
68 }
69 DirstateVersion::V2 => {
69 DirstateVersion::V2 => {
70 let mut hasher = Sha1::new();
70 let mut hasher = Sha1::new();
71 let (ignore_fn, warnings) = get_ignore_function(
71 let (ignore_fn, warnings) = get_ignore_function(
72 ignore_files,
72 ignore_files,
73 &root_dir,
73 &root_dir,
74 &mut |source, pattern_bytes| {
74 &mut |source, pattern_bytes| {
75 // If inside the repo, use the relative version to
75 // If inside the repo, use the relative version to
76 // make it deterministic inside tests.
76 // make it deterministic inside tests.
77 // The performance hit should be negligible.
77 // The performance hit should be negligible.
78 let source = source
78 let source = source
79 .strip_prefix(&root_dir)
79 .strip_prefix(&root_dir)
80 .unwrap_or(source);
80 .unwrap_or(source);
81 let source = get_bytes_from_path(source);
81 let source = get_bytes_from_path(source);
82
82
83 let mut subhasher = Sha1::new();
83 let mut subhasher = Sha1::new();
84 subhasher.update(pattern_bytes);
84 subhasher.update(pattern_bytes);
85 let patterns_hash = subhasher.finalize();
85 let patterns_hash = subhasher.finalize();
86
86
87 hasher.update(source);
87 hasher.update(source);
88 hasher.update(b" ");
88 hasher.update(b" ");
89 hasher.update(patterns_hash);
89 hasher.update(patterns_hash);
90 hasher.update(b"\n");
90 hasher.update(b"\n");
91 },
91 },
92 )?;
92 )?;
93 let new_hash = *hasher.finalize().as_ref();
93 let new_hash = *hasher.finalize().as_ref();
94 let changed = new_hash != dmap.ignore_patterns_hash;
94 let changed = new_hash != dmap.ignore_patterns_hash;
95 dmap.ignore_patterns_hash = new_hash;
95 dmap.ignore_patterns_hash = new_hash;
96 (ignore_fn, warnings, Some(changed))
96 (ignore_fn, warnings, Some(changed))
97 }
97 }
98 };
98 };
99 (ignore_fn, warnings, changed)
99 (ignore_fn, warnings, changed)
100 } else {
100 } else {
101 (Box::new(|&_| true), vec![], None)
101 (Box::new(|&_| true), vec![], None)
102 };
102 };
103
103
104 let filesystem_time_at_status_start =
104 let filesystem_time_at_status_start =
105 filesystem_now(&root_dir).ok().map(TruncatedTimestamp::from);
105 filesystem_now(&root_dir).ok().map(TruncatedTimestamp::from);
106
106
107 // If the repository is under the current directory, prefer using a
107 // If the repository is under the current directory, prefer using a
108 // relative path, so the kernel needs to traverse fewer directory in every
108 // relative path, so the kernel needs to traverse fewer directory in every
109 // call to `read_dir` or `symlink_metadata`.
109 // call to `read_dir` or `symlink_metadata`.
110 // This is effective in the common case where the current directory is the
110 // This is effective in the common case where the current directory is the
111 // repository root.
111 // repository root.
112
112
113 // TODO: Better yet would be to use libc functions like `openat` and
113 // TODO: Better yet would be to use libc functions like `openat` and
114 // `fstatat` to remove such repeated traversals entirely, but the standard
114 // `fstatat` to remove such repeated traversals entirely, but the standard
115 // library does not provide APIs based on those.
115 // library does not provide APIs based on those.
116 // Maybe with a crate like https://crates.io/crates/openat instead?
116 // Maybe with a crate like https://crates.io/crates/openat instead?
117 let root_dir = if let Some(relative) = std::env::current_dir()
117 let root_dir = if let Some(relative) = std::env::current_dir()
118 .ok()
118 .ok()
119 .and_then(|cwd| root_dir.strip_prefix(cwd).ok())
119 .and_then(|cwd| root_dir.strip_prefix(cwd).ok())
120 {
120 {
121 relative
121 relative
122 } else {
122 } else {
123 &root_dir
123 &root_dir
124 };
124 };
125
125
126 let outcome = DirstateStatus {
126 let outcome = DirstateStatus {
127 filesystem_time_at_status_start,
127 filesystem_time_at_status_start,
128 ..Default::default()
128 ..Default::default()
129 };
129 };
130 let common = StatusCommon {
130 let common = StatusCommon {
131 dmap,
131 dmap,
132 options,
132 options,
133 matcher,
133 matcher,
134 ignore_fn,
134 ignore_fn,
135 outcome: Mutex::new(outcome),
135 outcome: Mutex::new(outcome),
136 ignore_patterns_have_changed: patterns_changed,
136 ignore_patterns_have_changed: patterns_changed,
137 new_cacheable_directories: Default::default(),
137 new_cacheable_directories: Default::default(),
138 outdated_cached_directories: Default::default(),
138 outdated_cached_directories: Default::default(),
139 filesystem_time_at_status_start,
139 filesystem_time_at_status_start,
140 };
140 };
141 let is_at_repo_root = true;
141 let is_at_repo_root = true;
142 let hg_path = &BorrowedPath::OnDisk(HgPath::new(""));
142 let hg_path = &BorrowedPath::OnDisk(HgPath::new(""));
143 let has_ignored_ancestor = HasIgnoredAncestor::create(None, hg_path);
143 let has_ignored_ancestor = HasIgnoredAncestor::create(None, hg_path);
144 let root_cached_mtime = None;
144 let root_cached_mtime = None;
145 // If the path we have for the repository root is a symlink, do follow it.
145 // If the path we have for the repository root is a symlink, do follow it.
146 // (As opposed to symlinks within the working directory which are not
146 // (As opposed to symlinks within the working directory which are not
147 // followed, using `std::fs::symlink_metadata`.)
147 // followed, using `std::fs::symlink_metadata`.)
148 common.traverse_fs_directory_and_dirstate(
148 common.traverse_fs_directory_and_dirstate(
149 &has_ignored_ancestor,
149 &has_ignored_ancestor,
150 dmap.root.as_ref(),
150 dmap.root.as_ref(),
151 hg_path,
151 hg_path,
152 &DirEntry {
152 &DirEntry {
153 hg_path: Cow::Borrowed(HgPath::new(b"")),
153 hg_path: Cow::Borrowed(HgPath::new(b"")),
154 fs_path: Cow::Borrowed(root_dir),
154 fs_path: Cow::Borrowed(root_dir),
155 symlink_metadata: None,
155 symlink_metadata: None,
156 file_type: FakeFileType::Directory,
156 file_type: FakeFileType::Directory,
157 },
157 },
158 root_cached_mtime,
158 root_cached_mtime,
159 is_at_repo_root,
159 is_at_repo_root,
160 )?;
160 )?;
161 if let Some(file_set) = common.matcher.file_set() {
161 if let Some(file_set) = common.matcher.file_set() {
162 for file in file_set {
162 for file in file_set {
163 if !file.is_empty() && !dmap.has_node(file)? {
163 if !file.is_empty() && !dmap.has_node(file)? {
164 let path = hg_path_to_path_buf(file)?;
164 let path = hg_path_to_path_buf(file)?;
165 if let io::Result::Err(error) =
165 if let io::Result::Err(error) =
166 root_dir.join(path).symlink_metadata()
166 root_dir.join(path).symlink_metadata()
167 {
167 {
168 common.io_error(error, file)
168 common.io_error(error, file)
169 }
169 }
170 }
170 }
171 }
171 }
172 }
172 }
173 let mut outcome = common.outcome.into_inner().unwrap();
173 let mut outcome = common.outcome.into_inner().unwrap();
174 let new_cacheable = common.new_cacheable_directories.into_inner().unwrap();
174 let new_cacheable = common.new_cacheable_directories.into_inner().unwrap();
175 let outdated = common.outdated_cached_directories.into_inner().unwrap();
175 let outdated = common.outdated_cached_directories.into_inner().unwrap();
176
176
177 outcome.dirty = common.ignore_patterns_have_changed == Some(true)
177 outcome.dirty = common.ignore_patterns_have_changed == Some(true)
178 || !outdated.is_empty()
178 || !outdated.is_empty()
179 || (!new_cacheable.is_empty()
179 || (!new_cacheable.is_empty()
180 && dmap.dirstate_version == DirstateVersion::V2);
180 && dmap.dirstate_version == DirstateVersion::V2);
181
181
182 // Remove outdated mtimes before adding new mtimes, in case a given
182 // Remove outdated mtimes before adding new mtimes, in case a given
183 // directory is both
183 // directory is both
184 for path in &outdated {
184 for path in &outdated {
185 dmap.clear_cached_mtime(path)?;
185 dmap.clear_cached_mtime(path)?;
186 }
186 }
187 for (path, mtime) in &new_cacheable {
187 for (path, mtime) in &new_cacheable {
188 dmap.set_cached_mtime(path, *mtime)?;
188 dmap.set_cached_mtime(path, *mtime)?;
189 }
189 }
190
190
191 Ok((outcome, warnings))
191 Ok((outcome, warnings))
192 }
192 }
193
193
194 /// Bag of random things needed by various parts of the algorithm. Reduces the
194 /// Bag of random things needed by various parts of the algorithm. Reduces the
195 /// number of parameters passed to functions.
195 /// number of parameters passed to functions.
196 struct StatusCommon<'a, 'tree, 'on_disk: 'tree> {
196 struct StatusCommon<'a, 'tree, 'on_disk: 'tree> {
197 dmap: &'tree DirstateMap<'on_disk>,
197 dmap: &'tree DirstateMap<'on_disk>,
198 options: StatusOptions,
198 options: StatusOptions,
199 matcher: &'a (dyn Matcher + Sync),
199 matcher: &'a (dyn Matcher + Sync),
200 ignore_fn: IgnoreFnType<'a>,
200 ignore_fn: IgnoreFnType<'a>,
201 outcome: Mutex<DirstateStatus<'on_disk>>,
201 outcome: Mutex<DirstateStatus<'on_disk>>,
202 /// New timestamps of directories to be used for caching their readdirs
202 /// New timestamps of directories to be used for caching their readdirs
203 new_cacheable_directories:
203 new_cacheable_directories:
204 Mutex<Vec<(Cow<'on_disk, HgPath>, TruncatedTimestamp)>>,
204 Mutex<Vec<(Cow<'on_disk, HgPath>, TruncatedTimestamp)>>,
205 /// Used to invalidate the readdir cache of directories
205 /// Used to invalidate the readdir cache of directories
206 outdated_cached_directories: Mutex<Vec<Cow<'on_disk, HgPath>>>,
206 outdated_cached_directories: Mutex<Vec<Cow<'on_disk, HgPath>>>,
207
207
208 /// Whether ignore files like `.hgignore` have changed since the previous
208 /// Whether ignore files like `.hgignore` have changed since the previous
209 /// time a `status()` call wrote their hash to the dirstate. `None` means
209 /// time a `status()` call wrote their hash to the dirstate. `None` means
210 /// we don’t know as this run doesn’t list either ignored or uknown files
210 /// we don’t know as this run doesn’t list either ignored or uknown files
211 /// and therefore isn’t reading `.hgignore`.
211 /// and therefore isn’t reading `.hgignore`.
212 ignore_patterns_have_changed: Option<bool>,
212 ignore_patterns_have_changed: Option<bool>,
213
213
214 /// The current time at the start of the `status()` algorithm, as measured
214 /// The current time at the start of the `status()` algorithm, as measured
215 /// and possibly truncated by the filesystem.
215 /// and possibly truncated by the filesystem.
216 filesystem_time_at_status_start: Option<TruncatedTimestamp>,
216 filesystem_time_at_status_start: Option<TruncatedTimestamp>,
217 }
217 }
218
218
219 enum Outcome {
219 enum Outcome {
220 Modified,
220 Modified,
221 Added,
221 Added,
222 Removed,
222 Removed,
223 Deleted,
223 Deleted,
224 Clean,
224 Clean,
225 Ignored,
225 Ignored,
226 Unknown,
226 Unknown,
227 Unsure,
227 Unsure,
228 }
228 }
229
229
230 /// Lazy computation of whether a given path has a hgignored
230 /// Lazy computation of whether a given path has a hgignored
231 /// ancestor.
231 /// ancestor.
232 struct HasIgnoredAncestor<'a> {
232 struct HasIgnoredAncestor<'a> {
233 /// `path` and `parent` constitute the inputs to the computation,
233 /// `path` and `parent` constitute the inputs to the computation,
234 /// `cache` stores the outcome.
234 /// `cache` stores the outcome.
235 path: &'a HgPath,
235 path: &'a HgPath,
236 parent: Option<&'a HasIgnoredAncestor<'a>>,
236 parent: Option<&'a HasIgnoredAncestor<'a>>,
237 cache: OnceCell<bool>,
237 cache: OnceCell<bool>,
238 }
238 }
239
239
240 impl<'a> HasIgnoredAncestor<'a> {
240 impl<'a> HasIgnoredAncestor<'a> {
241 fn create(
241 fn create(
242 parent: Option<&'a HasIgnoredAncestor<'a>>,
242 parent: Option<&'a HasIgnoredAncestor<'a>>,
243 path: &'a HgPath,
243 path: &'a HgPath,
244 ) -> HasIgnoredAncestor<'a> {
244 ) -> HasIgnoredAncestor<'a> {
245 Self {
245 Self {
246 path,
246 path,
247 parent,
247 parent,
248 cache: OnceCell::new(),
248 cache: OnceCell::new(),
249 }
249 }
250 }
250 }
251
251
252 fn force<'b>(&self, ignore_fn: &IgnoreFnType<'b>) -> bool {
252 fn force<'b>(&self, ignore_fn: &IgnoreFnType<'b>) -> bool {
253 match self.parent {
253 match self.parent {
254 None => false,
254 None => false,
255 Some(parent) => {
255 Some(parent) => {
256 *(self.cache.get_or_init(|| {
256 *(self.cache.get_or_init(|| {
257 parent.force(ignore_fn) || ignore_fn(self.path)
257 parent.force(ignore_fn) || ignore_fn(self.path)
258 }))
258 }))
259 }
259 }
260 }
260 }
261 }
261 }
262 }
262 }
263
263
264 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> {
264 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> {
265 fn push_outcome(
265 fn push_outcome(
266 &self,
266 &self,
267 which: Outcome,
267 which: Outcome,
268 dirstate_node: &NodeRef<'tree, 'on_disk>,
268 dirstate_node: &NodeRef<'tree, 'on_disk>,
269 ) -> Result<(), DirstateV2ParseError> {
269 ) -> Result<(), DirstateV2ParseError> {
270 let path = dirstate_node
270 let path = dirstate_node
271 .full_path_borrowed(self.dmap.on_disk)?
271 .full_path_borrowed(self.dmap.on_disk)?
272 .detach_from_tree();
272 .detach_from_tree();
273 let copy_source = if self.options.list_copies {
273 let copy_source = if self.options.list_copies {
274 dirstate_node
274 dirstate_node
275 .copy_source_borrowed(self.dmap.on_disk)?
275 .copy_source_borrowed(self.dmap.on_disk)?
276 .map(|source| source.detach_from_tree())
276 .map(|source| source.detach_from_tree())
277 } else {
277 } else {
278 None
278 None
279 };
279 };
280 self.push_outcome_common(which, path, copy_source);
280 self.push_outcome_common(which, path, copy_source);
281 Ok(())
281 Ok(())
282 }
282 }
283
283
284 fn push_outcome_without_copy_source(
284 fn push_outcome_without_copy_source(
285 &self,
285 &self,
286 which: Outcome,
286 which: Outcome,
287 path: &BorrowedPath<'_, 'on_disk>,
287 path: &BorrowedPath<'_, 'on_disk>,
288 ) {
288 ) {
289 self.push_outcome_common(which, path.detach_from_tree(), None)
289 self.push_outcome_common(which, path.detach_from_tree(), None)
290 }
290 }
291
291
292 fn push_outcome_common(
292 fn push_outcome_common(
293 &self,
293 &self,
294 which: Outcome,
294 which: Outcome,
295 path: HgPathCow<'on_disk>,
295 path: HgPathCow<'on_disk>,
296 copy_source: Option<HgPathCow<'on_disk>>,
296 copy_source: Option<HgPathCow<'on_disk>>,
297 ) {
297 ) {
298 let mut outcome = self.outcome.lock().unwrap();
298 let mut outcome = self.outcome.lock().unwrap();
299 let vec = match which {
299 let vec = match which {
300 Outcome::Modified => &mut outcome.modified,
300 Outcome::Modified => &mut outcome.modified,
301 Outcome::Added => &mut outcome.added,
301 Outcome::Added => &mut outcome.added,
302 Outcome::Removed => &mut outcome.removed,
302 Outcome::Removed => &mut outcome.removed,
303 Outcome::Deleted => &mut outcome.deleted,
303 Outcome::Deleted => &mut outcome.deleted,
304 Outcome::Clean => &mut outcome.clean,
304 Outcome::Clean => &mut outcome.clean,
305 Outcome::Ignored => &mut outcome.ignored,
305 Outcome::Ignored => &mut outcome.ignored,
306 Outcome::Unknown => &mut outcome.unknown,
306 Outcome::Unknown => &mut outcome.unknown,
307 Outcome::Unsure => &mut outcome.unsure,
307 Outcome::Unsure => &mut outcome.unsure,
308 };
308 };
309 vec.push(StatusPath { path, copy_source });
309 vec.push(StatusPath { path, copy_source });
310 }
310 }
311
311
312 fn read_dir(
312 fn read_dir(
313 &self,
313 &self,
314 hg_path: &HgPath,
314 hg_path: &HgPath,
315 fs_path: &Path,
315 fs_path: &Path,
316 is_at_repo_root: bool,
316 is_at_repo_root: bool,
317 ) -> Result<Vec<DirEntry>, ()> {
317 ) -> Result<Vec<DirEntry>, ()> {
318 DirEntry::read_dir(fs_path, is_at_repo_root)
318 DirEntry::read_dir(fs_path, is_at_repo_root)
319 .map_err(|error| self.io_error(error, hg_path))
319 .map_err(|error| self.io_error(error, hg_path))
320 }
320 }
321
321
322 fn io_error(&self, error: std::io::Error, hg_path: &HgPath) {
322 fn io_error(&self, error: std::io::Error, hg_path: &HgPath) {
323 let errno = error.raw_os_error().expect("expected real OS error");
323 let errno = error.raw_os_error().expect("expected real OS error");
324 self.outcome
324 self.outcome
325 .lock()
325 .lock()
326 .unwrap()
326 .unwrap()
327 .bad
327 .bad
328 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
328 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
329 }
329 }
330
330
331 fn check_for_outdated_directory_cache(
331 fn check_for_outdated_directory_cache(
332 &self,
332 &self,
333 dirstate_node: &NodeRef<'tree, 'on_disk>,
333 dirstate_node: &NodeRef<'tree, 'on_disk>,
334 ) -> Result<bool, DirstateV2ParseError> {
334 ) -> Result<bool, DirstateV2ParseError> {
335 if self.ignore_patterns_have_changed == Some(true)
335 if self.ignore_patterns_have_changed == Some(true)
336 && dirstate_node.cached_directory_mtime()?.is_some()
336 && dirstate_node.cached_directory_mtime()?.is_some()
337 {
337 {
338 self.outdated_cached_directories.lock().unwrap().push(
338 self.outdated_cached_directories.lock().unwrap().push(
339 dirstate_node
339 dirstate_node
340 .full_path_borrowed(self.dmap.on_disk)?
340 .full_path_borrowed(self.dmap.on_disk)?
341 .detach_from_tree(),
341 .detach_from_tree(),
342 );
342 );
343 return Ok(true);
343 return Ok(true);
344 }
344 }
345 Ok(false)
345 Ok(false)
346 }
346 }
347
347
348 /// If this returns true, we can get accurate results by only using
348 /// If this returns true, we can get accurate results by only using
349 /// `symlink_metadata` for child nodes that exist in the dirstate and don’t
349 /// `symlink_metadata` for child nodes that exist in the dirstate and don’t
350 /// need to call `read_dir`.
350 /// need to call `read_dir`.
351 fn can_skip_fs_readdir(
351 fn can_skip_fs_readdir(
352 &self,
352 &self,
353 directory_entry: &DirEntry,
353 directory_entry: &DirEntry,
354 cached_directory_mtime: Option<TruncatedTimestamp>,
354 cached_directory_mtime: Option<TruncatedTimestamp>,
355 ) -> bool {
355 ) -> bool {
356 if !self.options.list_unknown && !self.options.list_ignored {
356 if !self.options.list_unknown && !self.options.list_ignored {
357 // All states that we care about listing have corresponding
357 // All states that we care about listing have corresponding
358 // dirstate entries.
358 // dirstate entries.
359 // This happens for example with `hg status -mard`.
359 // This happens for example with `hg status -mard`.
360 return true;
360 return true;
361 }
361 }
362 if !self.options.list_ignored
362 if !self.options.list_ignored
363 && self.ignore_patterns_have_changed == Some(false)
363 && self.ignore_patterns_have_changed == Some(false)
364 {
364 {
365 if let Some(cached_mtime) = cached_directory_mtime {
365 if let Some(cached_mtime) = cached_directory_mtime {
366 // The dirstate contains a cached mtime for this directory, set
366 // The dirstate contains a cached mtime for this directory, set
367 // by a previous run of the `status` algorithm which found this
367 // by a previous run of the `status` algorithm which found this
368 // directory eligible for `read_dir` caching.
368 // directory eligible for `read_dir` caching.
369 if let Ok(meta) = directory_entry.symlink_metadata() {
369 if let Ok(meta) = directory_entry.symlink_metadata() {
370 if cached_mtime
370 if cached_mtime
371 .likely_equal_to_mtime_of(&meta)
371 .likely_equal_to_mtime_of(&meta)
372 .unwrap_or(false)
372 .unwrap_or(false)
373 {
373 {
374 // The mtime of that directory has not changed
374 // The mtime of that directory has not changed
375 // since then, which means that the results of
375 // since then, which means that the results of
376 // `read_dir` should also be unchanged.
376 // `read_dir` should also be unchanged.
377 return true;
377 return true;
378 }
378 }
379 }
379 }
380 }
380 }
381 }
381 }
382 false
382 false
383 }
383 }
384
384
385 fn should_visit(set: &VisitChildrenSet, basename: &HgPath) -> bool {
386 match set {
387 VisitChildrenSet::This | VisitChildrenSet::Recursive => true,
388 VisitChildrenSet::Empty => false,
389 VisitChildrenSet::Set(children_to_visit) => {
390 children_to_visit.contains(basename)
391 }
392 }
393 }
394
385 /// Returns whether all child entries of the filesystem directory have a
395 /// Returns whether all child entries of the filesystem directory have a
386 /// corresponding dirstate node or are ignored.
396 /// corresponding dirstate node or are ignored.
387 fn traverse_fs_directory_and_dirstate<'ancestor>(
397 fn traverse_fs_directory_and_dirstate<'ancestor>(
388 &self,
398 &self,
389 has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>,
399 has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>,
390 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>,
400 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>,
391 directory_hg_path: &BorrowedPath<'tree, 'on_disk>,
401 directory_hg_path: &BorrowedPath<'tree, 'on_disk>,
392 directory_entry: &DirEntry,
402 directory_entry: &DirEntry,
393 cached_directory_mtime: Option<TruncatedTimestamp>,
403 cached_directory_mtime: Option<TruncatedTimestamp>,
394 is_at_repo_root: bool,
404 is_at_repo_root: bool,
395 ) -> Result<bool, DirstateV2ParseError> {
405 ) -> Result<bool, DirstateV2ParseError> {
406 let children_set = self.matcher.visit_children_set(directory_hg_path);
407 if let VisitChildrenSet::Empty = children_set {
408 return Ok(false);
409 }
396 if self.can_skip_fs_readdir(directory_entry, cached_directory_mtime) {
410 if self.can_skip_fs_readdir(directory_entry, cached_directory_mtime) {
397 dirstate_nodes
411 dirstate_nodes
398 .par_iter()
412 .par_iter()
399 .map(|dirstate_node| {
413 .map(|dirstate_node| {
400 let fs_path = &directory_entry.fs_path;
414 let fs_path = &directory_entry.fs_path;
401 let fs_path = fs_path.join(get_path_from_bytes(
415 let basename =
402 dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(),
416 dirstate_node.base_name(self.dmap.on_disk)?.as_bytes();
403 ));
417 let fs_path = fs_path.join(get_path_from_bytes(basename));
418 if !Self::should_visit(
419 &children_set,
420 HgPath::new(basename),
421 ) {
422 return Ok(());
423 }
404 match std::fs::symlink_metadata(&fs_path) {
424 match std::fs::symlink_metadata(&fs_path) {
405 Ok(fs_metadata) => {
425 Ok(fs_metadata) => {
406 let file_type = fs_metadata.file_type().into();
426 let file_type = fs_metadata.file_type().into();
407 let entry = DirEntry {
427 let entry = DirEntry {
408 hg_path: Cow::Borrowed(
428 hg_path: Cow::Borrowed(
409 dirstate_node
429 dirstate_node
410 .full_path(self.dmap.on_disk)?,
430 .full_path(self.dmap.on_disk)?,
411 ),
431 ),
412 fs_path: Cow::Borrowed(&fs_path),
432 fs_path: Cow::Borrowed(&fs_path),
413 symlink_metadata: Some(fs_metadata),
433 symlink_metadata: Some(fs_metadata),
414 file_type,
434 file_type,
415 };
435 };
416 self.traverse_fs_and_dirstate(
436 self.traverse_fs_and_dirstate(
417 &entry,
437 &entry,
418 dirstate_node,
438 dirstate_node,
419 has_ignored_ancestor,
439 has_ignored_ancestor,
420 )
440 )
421 }
441 }
422 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
442 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
423 self.traverse_dirstate_only(dirstate_node)
443 self.traverse_dirstate_only(dirstate_node)
424 }
444 }
425 Err(error) => {
445 Err(error) => {
426 let hg_path =
446 let hg_path =
427 dirstate_node.full_path(self.dmap.on_disk)?;
447 dirstate_node.full_path(self.dmap.on_disk)?;
428 self.io_error(error, hg_path);
448 self.io_error(error, hg_path);
429 Ok(())
449 Ok(())
430 }
450 }
431 }
451 }
432 })
452 })
433 .collect::<Result<_, _>>()?;
453 .collect::<Result<_, _>>()?;
434
454
435 // We don’t know, so conservatively say this isn’t the case
455 // We don’t know, so conservatively say this isn’t the case
436 let children_all_have_dirstate_node_or_are_ignored = false;
456 let children_all_have_dirstate_node_or_are_ignored = false;
437
457
438 return Ok(children_all_have_dirstate_node_or_are_ignored);
458 return Ok(children_all_have_dirstate_node_or_are_ignored);
439 }
459 }
440
460
441 let readdir_succeeded;
461 let readdir_succeeded;
442 let mut fs_entries = if let Ok(entries) = self.read_dir(
462 let mut fs_entries = if let Ok(entries) = self.read_dir(
443 directory_hg_path,
463 directory_hg_path,
444 &directory_entry.fs_path,
464 &directory_entry.fs_path,
445 is_at_repo_root,
465 is_at_repo_root,
446 ) {
466 ) {
447 readdir_succeeded = true;
467 readdir_succeeded = true;
448 entries
468 entries
449 } else {
469 } else {
450 // Treat an unreadable directory (typically because of insufficient
470 // Treat an unreadable directory (typically because of insufficient
451 // permissions) like an empty directory. `self.read_dir` has
471 // permissions) like an empty directory. `self.read_dir` has
452 // already called `self.io_error` so a warning will be emitted.
472 // already called `self.io_error` so a warning will be emitted.
453 // We still need to remember that there was an error so that we
473 // We still need to remember that there was an error so that we
454 // know not to cache this result.
474 // know not to cache this result.
455 readdir_succeeded = false;
475 readdir_succeeded = false;
456 Vec::new()
476 Vec::new()
457 };
477 };
458
478
459 // `merge_join_by` requires both its input iterators to be sorted:
479 // `merge_join_by` requires both its input iterators to be sorted:
460
480
461 let dirstate_nodes = dirstate_nodes.sorted();
481 let dirstate_nodes = dirstate_nodes.sorted();
462 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
482 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
463 // https://github.com/rust-lang/rust/issues/34162
483 // https://github.com/rust-lang/rust/issues/34162
464 fs_entries.sort_unstable_by(|e1, e2| e1.hg_path.cmp(&e2.hg_path));
484 fs_entries.sort_unstable_by(|e1, e2| e1.hg_path.cmp(&e2.hg_path));
465
485
466 // Propagate here any error that would happen inside the comparison
486 // Propagate here any error that would happen inside the comparison
467 // callback below
487 // callback below
468 for dirstate_node in &dirstate_nodes {
488 for dirstate_node in &dirstate_nodes {
469 dirstate_node.base_name(self.dmap.on_disk)?;
489 dirstate_node.base_name(self.dmap.on_disk)?;
470 }
490 }
471 itertools::merge_join_by(
491 itertools::merge_join_by(
472 dirstate_nodes,
492 dirstate_nodes,
473 &fs_entries,
493 &fs_entries,
474 |dirstate_node, fs_entry| {
494 |dirstate_node, fs_entry| {
475 // This `unwrap` never panics because we already propagated
495 // This `unwrap` never panics because we already propagated
476 // those errors above
496 // those errors above
477 dirstate_node
497 dirstate_node
478 .base_name(self.dmap.on_disk)
498 .base_name(self.dmap.on_disk)
479 .unwrap()
499 .unwrap()
480 .cmp(&fs_entry.hg_path)
500 .cmp(&fs_entry.hg_path)
481 },
501 },
482 )
502 )
483 .par_bridge()
503 .par_bridge()
484 .map(|pair| {
504 .map(|pair| {
485 use itertools::EitherOrBoth::*;
505 use itertools::EitherOrBoth::*;
506 let basename = match &pair {
507 Left(dirstate_node) | Both(dirstate_node, _) => HgPath::new(
508 dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(),
509 ),
510 Right(fs_entry) => &fs_entry.hg_path,
511 };
512 if !Self::should_visit(&children_set, basename) {
513 return Ok(false);
514 }
486 let has_dirstate_node_or_is_ignored = match pair {
515 let has_dirstate_node_or_is_ignored = match pair {
487 Both(dirstate_node, fs_entry) => {
516 Both(dirstate_node, fs_entry) => {
488 self.traverse_fs_and_dirstate(
517 self.traverse_fs_and_dirstate(
489 fs_entry,
518 fs_entry,
490 dirstate_node,
519 dirstate_node,
491 has_ignored_ancestor,
520 has_ignored_ancestor,
492 )?;
521 )?;
493 true
522 true
494 }
523 }
495 Left(dirstate_node) => {
524 Left(dirstate_node) => {
496 self.traverse_dirstate_only(dirstate_node)?;
525 self.traverse_dirstate_only(dirstate_node)?;
497 true
526 true
498 }
527 }
499 Right(fs_entry) => self.traverse_fs_only(
528 Right(fs_entry) => self.traverse_fs_only(
500 has_ignored_ancestor.force(&self.ignore_fn),
529 has_ignored_ancestor.force(&self.ignore_fn),
501 directory_hg_path,
530 directory_hg_path,
502 fs_entry,
531 fs_entry,
503 ),
532 ),
504 };
533 };
505 Ok(has_dirstate_node_or_is_ignored)
534 Ok(has_dirstate_node_or_is_ignored)
506 })
535 })
507 .try_reduce(|| true, |a, b| Ok(a && b))
536 .try_reduce(|| true, |a, b| Ok(a && b))
508 .map(|res| res && readdir_succeeded)
537 .map(|res| res && readdir_succeeded)
509 }
538 }
510
539
511 fn traverse_fs_and_dirstate<'ancestor>(
540 fn traverse_fs_and_dirstate<'ancestor>(
512 &self,
541 &self,
513 fs_entry: &DirEntry,
542 fs_entry: &DirEntry,
514 dirstate_node: NodeRef<'tree, 'on_disk>,
543 dirstate_node: NodeRef<'tree, 'on_disk>,
515 has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>,
544 has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>,
516 ) -> Result<(), DirstateV2ParseError> {
545 ) -> Result<(), DirstateV2ParseError> {
517 let outdated_dircache =
546 let outdated_dircache =
518 self.check_for_outdated_directory_cache(&dirstate_node)?;
547 self.check_for_outdated_directory_cache(&dirstate_node)?;
519 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
548 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
520 let file_or_symlink = fs_entry.is_file() || fs_entry.is_symlink();
549 let file_or_symlink = fs_entry.is_file() || fs_entry.is_symlink();
521 if !file_or_symlink {
550 if !file_or_symlink {
522 // If we previously had a file here, it was removed (with
551 // If we previously had a file here, it was removed (with
523 // `hg rm` or similar) or deleted before it could be
552 // `hg rm` or similar) or deleted before it could be
524 // replaced by a directory or something else.
553 // replaced by a directory or something else.
525 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
554 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
526 }
555 }
527 if let Some(bad_type) = fs_entry.is_bad() {
556 if let Some(bad_type) = fs_entry.is_bad() {
528 if self.matcher.exact_match(hg_path) {
557 if self.matcher.exact_match(hg_path) {
529 let path = dirstate_node.full_path(self.dmap.on_disk)?;
558 let path = dirstate_node.full_path(self.dmap.on_disk)?;
530 self.outcome.lock().unwrap().bad.push((
559 self.outcome.lock().unwrap().bad.push((
531 path.to_owned().into(),
560 path.to_owned().into(),
532 BadMatch::BadType(bad_type),
561 BadMatch::BadType(bad_type),
533 ))
562 ))
534 }
563 }
535 }
564 }
536 if fs_entry.is_dir() {
565 if fs_entry.is_dir() {
537 if self.options.collect_traversed_dirs {
566 if self.options.collect_traversed_dirs {
538 self.outcome
567 self.outcome
539 .lock()
568 .lock()
540 .unwrap()
569 .unwrap()
541 .traversed
570 .traversed
542 .push(hg_path.detach_from_tree())
571 .push(hg_path.detach_from_tree())
543 }
572 }
544 let is_ignored = HasIgnoredAncestor::create(
573 let is_ignored = HasIgnoredAncestor::create(
545 Some(has_ignored_ancestor),
574 Some(has_ignored_ancestor),
546 hg_path,
575 hg_path,
547 );
576 );
548 let is_at_repo_root = false;
577 let is_at_repo_root = false;
549 let children_all_have_dirstate_node_or_are_ignored = self
578 let children_all_have_dirstate_node_or_are_ignored = self
550 .traverse_fs_directory_and_dirstate(
579 .traverse_fs_directory_and_dirstate(
551 &is_ignored,
580 &is_ignored,
552 dirstate_node.children(self.dmap.on_disk)?,
581 dirstate_node.children(self.dmap.on_disk)?,
553 hg_path,
582 hg_path,
554 fs_entry,
583 fs_entry,
555 dirstate_node.cached_directory_mtime()?,
584 dirstate_node.cached_directory_mtime()?,
556 is_at_repo_root,
585 is_at_repo_root,
557 )?;
586 )?;
558 self.maybe_save_directory_mtime(
587 self.maybe_save_directory_mtime(
559 children_all_have_dirstate_node_or_are_ignored,
588 children_all_have_dirstate_node_or_are_ignored,
560 fs_entry,
589 fs_entry,
561 dirstate_node,
590 dirstate_node,
562 outdated_dircache,
591 outdated_dircache,
563 )?
592 )?
564 } else {
593 } else {
565 if file_or_symlink && self.matcher.matches(hg_path) {
594 if file_or_symlink && self.matcher.matches(hg_path) {
566 if let Some(entry) = dirstate_node.entry()? {
595 if let Some(entry) = dirstate_node.entry()? {
567 if !entry.any_tracked() {
596 if !entry.any_tracked() {
568 // Forward-compat if we start tracking unknown/ignored
597 // Forward-compat if we start tracking unknown/ignored
569 // files for caching reasons
598 // files for caching reasons
570 self.mark_unknown_or_ignored(
599 self.mark_unknown_or_ignored(
571 has_ignored_ancestor.force(&self.ignore_fn),
600 has_ignored_ancestor.force(&self.ignore_fn),
572 hg_path,
601 hg_path,
573 );
602 );
574 }
603 }
575 if entry.added() {
604 if entry.added() {
576 self.push_outcome(Outcome::Added, &dirstate_node)?;
605 self.push_outcome(Outcome::Added, &dirstate_node)?;
577 } else if entry.removed() {
606 } else if entry.removed() {
578 self.push_outcome(Outcome::Removed, &dirstate_node)?;
607 self.push_outcome(Outcome::Removed, &dirstate_node)?;
579 } else if entry.modified() {
608 } else if entry.modified() {
580 self.push_outcome(Outcome::Modified, &dirstate_node)?;
609 self.push_outcome(Outcome::Modified, &dirstate_node)?;
581 } else {
610 } else {
582 self.handle_normal_file(&dirstate_node, fs_entry)?;
611 self.handle_normal_file(&dirstate_node, fs_entry)?;
583 }
612 }
584 } else {
613 } else {
585 // `node.entry.is_none()` indicates a "directory"
614 // `node.entry.is_none()` indicates a "directory"
586 // node, but the filesystem has a file
615 // node, but the filesystem has a file
587 self.mark_unknown_or_ignored(
616 self.mark_unknown_or_ignored(
588 has_ignored_ancestor.force(&self.ignore_fn),
617 has_ignored_ancestor.force(&self.ignore_fn),
589 hg_path,
618 hg_path,
590 );
619 );
591 }
620 }
592 }
621 }
593
622
594 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter()
623 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter()
595 {
624 {
596 self.traverse_dirstate_only(child_node)?
625 self.traverse_dirstate_only(child_node)?
597 }
626 }
598 }
627 }
599 Ok(())
628 Ok(())
600 }
629 }
601
630
602 /// Save directory mtime if applicable.
631 /// Save directory mtime if applicable.
603 ///
632 ///
604 /// `outdated_directory_cache` is `true` if we've just invalidated the
633 /// `outdated_directory_cache` is `true` if we've just invalidated the
605 /// cache for this directory in `check_for_outdated_directory_cache`,
634 /// cache for this directory in `check_for_outdated_directory_cache`,
606 /// which forces the update.
635 /// which forces the update.
607 fn maybe_save_directory_mtime(
636 fn maybe_save_directory_mtime(
608 &self,
637 &self,
609 children_all_have_dirstate_node_or_are_ignored: bool,
638 children_all_have_dirstate_node_or_are_ignored: bool,
610 directory_entry: &DirEntry,
639 directory_entry: &DirEntry,
611 dirstate_node: NodeRef<'tree, 'on_disk>,
640 dirstate_node: NodeRef<'tree, 'on_disk>,
612 outdated_directory_cache: bool,
641 outdated_directory_cache: bool,
613 ) -> Result<(), DirstateV2ParseError> {
642 ) -> Result<(), DirstateV2ParseError> {
614 if !children_all_have_dirstate_node_or_are_ignored {
643 if !children_all_have_dirstate_node_or_are_ignored {
615 return Ok(());
644 return Ok(());
616 }
645 }
617 // All filesystem directory entries from `read_dir` have a
646 // All filesystem directory entries from `read_dir` have a
618 // corresponding node in the dirstate, so we can reconstitute the
647 // corresponding node in the dirstate, so we can reconstitute the
619 // names of those entries without calling `read_dir` again.
648 // names of those entries without calling `read_dir` again.
620
649
621 // TODO: use let-else here and below when available:
650 // TODO: use let-else here and below when available:
622 // https://github.com/rust-lang/rust/issues/87335
651 // https://github.com/rust-lang/rust/issues/87335
623 let status_start = if let Some(status_start) =
652 let status_start = if let Some(status_start) =
624 &self.filesystem_time_at_status_start
653 &self.filesystem_time_at_status_start
625 {
654 {
626 status_start
655 status_start
627 } else {
656 } else {
628 return Ok(());
657 return Ok(());
629 };
658 };
630
659
631 // Although the Rust standard library’s `SystemTime` type
660 // Although the Rust standard library’s `SystemTime` type
632 // has nanosecond precision, the times reported for a
661 // has nanosecond precision, the times reported for a
633 // directory’s (or file’s) modified time may have lower
662 // directory’s (or file’s) modified time may have lower
634 // resolution based on the filesystem (for example ext3
663 // resolution based on the filesystem (for example ext3
635 // only stores integer seconds), kernel (see
664 // only stores integer seconds), kernel (see
636 // https://stackoverflow.com/a/14393315/1162888), etc.
665 // https://stackoverflow.com/a/14393315/1162888), etc.
637 let metadata = match directory_entry.symlink_metadata() {
666 let metadata = match directory_entry.symlink_metadata() {
638 Ok(meta) => meta,
667 Ok(meta) => meta,
639 Err(_) => return Ok(()),
668 Err(_) => return Ok(()),
640 };
669 };
641
670
642 let directory_mtime = match TruncatedTimestamp::for_reliable_mtime_of(
671 let directory_mtime = match TruncatedTimestamp::for_reliable_mtime_of(
643 &metadata,
672 &metadata,
644 status_start,
673 status_start,
645 ) {
674 ) {
646 Ok(Some(directory_mtime)) => directory_mtime,
675 Ok(Some(directory_mtime)) => directory_mtime,
647 Ok(None) => {
676 Ok(None) => {
648 // The directory was modified too recently,
677 // The directory was modified too recently,
649 // don’t cache its `read_dir` results.
678 // don’t cache its `read_dir` results.
650 //
679 //
651 // 1. A change to this directory (direct child was
680 // 1. A change to this directory (direct child was
652 // added or removed) cause its mtime to be set
681 // added or removed) cause its mtime to be set
653 // (possibly truncated) to `directory_mtime`
682 // (possibly truncated) to `directory_mtime`
654 // 2. This `status` algorithm calls `read_dir`
683 // 2. This `status` algorithm calls `read_dir`
655 // 3. An other change is made to the same directory is
684 // 3. An other change is made to the same directory is
656 // made so that calling `read_dir` agin would give
685 // made so that calling `read_dir` agin would give
657 // different results, but soon enough after 1. that
686 // different results, but soon enough after 1. that
658 // the mtime stays the same
687 // the mtime stays the same
659 //
688 //
660 // On a system where the time resolution poor, this
689 // On a system where the time resolution poor, this
661 // scenario is not unlikely if all three steps are caused
690 // scenario is not unlikely if all three steps are caused
662 // by the same script.
691 // by the same script.
663 return Ok(());
692 return Ok(());
664 }
693 }
665 Err(_) => {
694 Err(_) => {
666 // OS/libc does not support mtime?
695 // OS/libc does not support mtime?
667 return Ok(());
696 return Ok(());
668 }
697 }
669 };
698 };
670 // We’ve observed (through `status_start`) that time has
699 // We’ve observed (through `status_start`) that time has
671 // “progressed” since `directory_mtime`, so any further
700 // “progressed” since `directory_mtime`, so any further
672 // change to this directory is extremely likely to cause a
701 // change to this directory is extremely likely to cause a
673 // different mtime.
702 // different mtime.
674 //
703 //
675 // Having the same mtime again is not entirely impossible
704 // Having the same mtime again is not entirely impossible
676 // since the system clock is not monotonous. It could jump
705 // since the system clock is not monotonous. It could jump
677 // backward to some point before `directory_mtime`, then a
706 // backward to some point before `directory_mtime`, then a
678 // directory change could potentially happen during exactly
707 // directory change could potentially happen during exactly
679 // the wrong tick.
708 // the wrong tick.
680 //
709 //
681 // We deem this scenario (unlike the previous one) to be
710 // We deem this scenario (unlike the previous one) to be
682 // unlikely enough in practice.
711 // unlikely enough in practice.
683
712
684 let is_up_to_date = if let Some(cached) =
713 let is_up_to_date = if let Some(cached) =
685 dirstate_node.cached_directory_mtime()?
714 dirstate_node.cached_directory_mtime()?
686 {
715 {
687 !outdated_directory_cache && cached.likely_equal(directory_mtime)
716 !outdated_directory_cache && cached.likely_equal(directory_mtime)
688 } else {
717 } else {
689 false
718 false
690 };
719 };
691 if !is_up_to_date {
720 if !is_up_to_date {
692 let hg_path = dirstate_node
721 let hg_path = dirstate_node
693 .full_path_borrowed(self.dmap.on_disk)?
722 .full_path_borrowed(self.dmap.on_disk)?
694 .detach_from_tree();
723 .detach_from_tree();
695 self.new_cacheable_directories
724 self.new_cacheable_directories
696 .lock()
725 .lock()
697 .unwrap()
726 .unwrap()
698 .push((hg_path, directory_mtime))
727 .push((hg_path, directory_mtime))
699 }
728 }
700 Ok(())
729 Ok(())
701 }
730 }
702
731
703 /// A file that is clean in the dirstate was found in the filesystem
732 /// A file that is clean in the dirstate was found in the filesystem
704 fn handle_normal_file(
733 fn handle_normal_file(
705 &self,
734 &self,
706 dirstate_node: &NodeRef<'tree, 'on_disk>,
735 dirstate_node: &NodeRef<'tree, 'on_disk>,
707 fs_entry: &DirEntry,
736 fs_entry: &DirEntry,
708 ) -> Result<(), DirstateV2ParseError> {
737 ) -> Result<(), DirstateV2ParseError> {
709 // Keep the low 31 bits
738 // Keep the low 31 bits
710 fn truncate_u64(value: u64) -> i32 {
739 fn truncate_u64(value: u64) -> i32 {
711 (value & 0x7FFF_FFFF) as i32
740 (value & 0x7FFF_FFFF) as i32
712 }
741 }
713
742
714 let fs_metadata = match fs_entry.symlink_metadata() {
743 let fs_metadata = match fs_entry.symlink_metadata() {
715 Ok(meta) => meta,
744 Ok(meta) => meta,
716 Err(_) => return Ok(()),
745 Err(_) => return Ok(()),
717 };
746 };
718
747
719 let entry = dirstate_node
748 let entry = dirstate_node
720 .entry()?
749 .entry()?
721 .expect("handle_normal_file called with entry-less node");
750 .expect("handle_normal_file called with entry-less node");
722 let mode_changed =
751 let mode_changed =
723 || self.options.check_exec && entry.mode_changed(&fs_metadata);
752 || self.options.check_exec && entry.mode_changed(&fs_metadata);
724 let size = entry.size();
753 let size = entry.size();
725 let size_changed = size != truncate_u64(fs_metadata.len());
754 let size_changed = size != truncate_u64(fs_metadata.len());
726 if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() {
755 if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() {
727 // issue6456: Size returned may be longer due to encryption
756 // issue6456: Size returned may be longer due to encryption
728 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
757 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
729 self.push_outcome(Outcome::Unsure, dirstate_node)?
758 self.push_outcome(Outcome::Unsure, dirstate_node)?
730 } else if dirstate_node.has_copy_source()
759 } else if dirstate_node.has_copy_source()
731 || entry.is_from_other_parent()
760 || entry.is_from_other_parent()
732 || (size >= 0 && (size_changed || mode_changed()))
761 || (size >= 0 && (size_changed || mode_changed()))
733 {
762 {
734 self.push_outcome(Outcome::Modified, dirstate_node)?
763 self.push_outcome(Outcome::Modified, dirstate_node)?
735 } else {
764 } else {
736 let mtime_looks_clean = if let Some(dirstate_mtime) =
765 let mtime_looks_clean = if let Some(dirstate_mtime) =
737 entry.truncated_mtime()
766 entry.truncated_mtime()
738 {
767 {
739 let fs_mtime = TruncatedTimestamp::for_mtime_of(&fs_metadata)
768 let fs_mtime = TruncatedTimestamp::for_mtime_of(&fs_metadata)
740 .expect("OS/libc does not support mtime?");
769 .expect("OS/libc does not support mtime?");
741 // There might be a change in the future if for example the
770 // There might be a change in the future if for example the
742 // internal clock become off while process run, but this is a
771 // internal clock become off while process run, but this is a
743 // case where the issues the user would face
772 // case where the issues the user would face
744 // would be a lot worse and there is nothing we
773 // would be a lot worse and there is nothing we
745 // can really do.
774 // can really do.
746 fs_mtime.likely_equal(dirstate_mtime)
775 fs_mtime.likely_equal(dirstate_mtime)
747 } else {
776 } else {
748 // No mtime in the dirstate entry
777 // No mtime in the dirstate entry
749 false
778 false
750 };
779 };
751 if !mtime_looks_clean {
780 if !mtime_looks_clean {
752 self.push_outcome(Outcome::Unsure, dirstate_node)?
781 self.push_outcome(Outcome::Unsure, dirstate_node)?
753 } else if self.options.list_clean {
782 } else if self.options.list_clean {
754 self.push_outcome(Outcome::Clean, dirstate_node)?
783 self.push_outcome(Outcome::Clean, dirstate_node)?
755 }
784 }
756 }
785 }
757 Ok(())
786 Ok(())
758 }
787 }
759
788
760 /// A node in the dirstate tree has no corresponding filesystem entry
789 /// A node in the dirstate tree has no corresponding filesystem entry
761 fn traverse_dirstate_only(
790 fn traverse_dirstate_only(
762 &self,
791 &self,
763 dirstate_node: NodeRef<'tree, 'on_disk>,
792 dirstate_node: NodeRef<'tree, 'on_disk>,
764 ) -> Result<(), DirstateV2ParseError> {
793 ) -> Result<(), DirstateV2ParseError> {
765 self.check_for_outdated_directory_cache(&dirstate_node)?;
794 self.check_for_outdated_directory_cache(&dirstate_node)?;
766 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
795 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
767 dirstate_node
796 dirstate_node
768 .children(self.dmap.on_disk)?
797 .children(self.dmap.on_disk)?
769 .par_iter()
798 .par_iter()
770 .map(|child_node| self.traverse_dirstate_only(child_node))
799 .map(|child_node| self.traverse_dirstate_only(child_node))
771 .collect()
800 .collect()
772 }
801 }
773
802
774 /// A node in the dirstate tree has no corresponding *file* on the
803 /// A node in the dirstate tree has no corresponding *file* on the
775 /// filesystem
804 /// filesystem
776 ///
805 ///
777 /// Does nothing on a "directory" node
806 /// Does nothing on a "directory" node
778 fn mark_removed_or_deleted_if_file(
807 fn mark_removed_or_deleted_if_file(
779 &self,
808 &self,
780 dirstate_node: &NodeRef<'tree, 'on_disk>,
809 dirstate_node: &NodeRef<'tree, 'on_disk>,
781 ) -> Result<(), DirstateV2ParseError> {
810 ) -> Result<(), DirstateV2ParseError> {
782 if let Some(entry) = dirstate_node.entry()? {
811 if let Some(entry) = dirstate_node.entry()? {
783 if !entry.any_tracked() {
812 if !entry.any_tracked() {
784 // Future-compat for when we start storing ignored and unknown
813 // Future-compat for when we start storing ignored and unknown
785 // files for caching reasons
814 // files for caching reasons
786 return Ok(());
815 return Ok(());
787 }
816 }
788 let path = dirstate_node.full_path(self.dmap.on_disk)?;
817 let path = dirstate_node.full_path(self.dmap.on_disk)?;
789 if self.matcher.matches(path) {
818 if self.matcher.matches(path) {
790 if entry.removed() {
819 if entry.removed() {
791 self.push_outcome(Outcome::Removed, dirstate_node)?
820 self.push_outcome(Outcome::Removed, dirstate_node)?
792 } else {
821 } else {
793 self.push_outcome(Outcome::Deleted, dirstate_node)?
822 self.push_outcome(Outcome::Deleted, dirstate_node)?
794 }
823 }
795 }
824 }
796 }
825 }
797 Ok(())
826 Ok(())
798 }
827 }
799
828
800 /// Something in the filesystem has no corresponding dirstate node
829 /// Something in the filesystem has no corresponding dirstate node
801 ///
830 ///
802 /// Returns whether that path is ignored
831 /// Returns whether that path is ignored
803 fn traverse_fs_only(
832 fn traverse_fs_only(
804 &self,
833 &self,
805 has_ignored_ancestor: bool,
834 has_ignored_ancestor: bool,
806 directory_hg_path: &HgPath,
835 directory_hg_path: &HgPath,
807 fs_entry: &DirEntry,
836 fs_entry: &DirEntry,
808 ) -> bool {
837 ) -> bool {
809 let hg_path = directory_hg_path.join(&fs_entry.hg_path);
838 let hg_path = directory_hg_path.join(&fs_entry.hg_path);
810 let file_or_symlink = fs_entry.is_file() || fs_entry.is_symlink();
839 let file_or_symlink = fs_entry.is_file() || fs_entry.is_symlink();
811 if fs_entry.is_dir() {
840 if fs_entry.is_dir() {
812 let is_ignored =
841 let is_ignored =
813 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
842 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
814 let traverse_children = if is_ignored {
843 let traverse_children = if is_ignored {
815 // Descendants of an ignored directory are all ignored
844 // Descendants of an ignored directory are all ignored
816 self.options.list_ignored
845 self.options.list_ignored
817 } else {
846 } else {
818 // Descendants of an unknown directory may be either unknown or
847 // Descendants of an unknown directory may be either unknown or
819 // ignored
848 // ignored
820 self.options.list_unknown || self.options.list_ignored
849 self.options.list_unknown || self.options.list_ignored
821 };
850 };
822 if traverse_children {
851 if traverse_children {
823 let is_at_repo_root = false;
852 let is_at_repo_root = false;
824 if let Ok(children_fs_entries) =
853 if let Ok(children_fs_entries) =
825 self.read_dir(&hg_path, &fs_entry.fs_path, is_at_repo_root)
854 self.read_dir(&hg_path, &fs_entry.fs_path, is_at_repo_root)
826 {
855 {
827 children_fs_entries.par_iter().for_each(|child_fs_entry| {
856 children_fs_entries.par_iter().for_each(|child_fs_entry| {
828 self.traverse_fs_only(
857 self.traverse_fs_only(
829 is_ignored,
858 is_ignored,
830 &hg_path,
859 &hg_path,
831 child_fs_entry,
860 child_fs_entry,
832 );
861 );
833 })
862 })
834 }
863 }
835 if self.options.collect_traversed_dirs {
864 if self.options.collect_traversed_dirs {
836 self.outcome.lock().unwrap().traversed.push(hg_path.into())
865 self.outcome.lock().unwrap().traversed.push(hg_path.into())
837 }
866 }
838 }
867 }
839 is_ignored
868 is_ignored
840 } else if file_or_symlink {
869 } else if file_or_symlink {
841 if self.matcher.matches(&hg_path) {
870 if self.matcher.matches(&hg_path) {
842 self.mark_unknown_or_ignored(
871 self.mark_unknown_or_ignored(
843 has_ignored_ancestor,
872 has_ignored_ancestor,
844 &BorrowedPath::InMemory(&hg_path),
873 &BorrowedPath::InMemory(&hg_path),
845 )
874 )
846 } else {
875 } else {
847 // We haven’t computed whether this path is ignored. It
876 // We haven’t computed whether this path is ignored. It
848 // might not be, and a future run of status might have a
877 // might not be, and a future run of status might have a
849 // different matcher that matches it. So treat it as not
878 // different matcher that matches it. So treat it as not
850 // ignored. That is, inhibit readdir caching of the parent
879 // ignored. That is, inhibit readdir caching of the parent
851 // directory.
880 // directory.
852 false
881 false
853 }
882 }
854 } else {
883 } else {
855 // This is neither a directory, a plain file, or a symlink.
884 // This is neither a directory, a plain file, or a symlink.
856 // Treat it like an ignored file.
885 // Treat it like an ignored file.
857 true
886 true
858 }
887 }
859 }
888 }
860
889
861 /// Returns whether that path is ignored
890 /// Returns whether that path is ignored
862 fn mark_unknown_or_ignored(
891 fn mark_unknown_or_ignored(
863 &self,
892 &self,
864 has_ignored_ancestor: bool,
893 has_ignored_ancestor: bool,
865 hg_path: &BorrowedPath<'_, 'on_disk>,
894 hg_path: &BorrowedPath<'_, 'on_disk>,
866 ) -> bool {
895 ) -> bool {
867 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
896 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
868 if is_ignored {
897 if is_ignored {
869 if self.options.list_ignored {
898 if self.options.list_ignored {
870 self.push_outcome_without_copy_source(
899 self.push_outcome_without_copy_source(
871 Outcome::Ignored,
900 Outcome::Ignored,
872 hg_path,
901 hg_path,
873 )
902 )
874 }
903 }
875 } else if self.options.list_unknown {
904 } else if self.options.list_unknown {
876 self.push_outcome_without_copy_source(Outcome::Unknown, hg_path)
905 self.push_outcome_without_copy_source(Outcome::Unknown, hg_path)
877 }
906 }
878 is_ignored
907 is_ignored
879 }
908 }
880 }
909 }
881
910
882 /// Since [`std::fs::FileType`] cannot be built directly, we emulate what we
911 /// Since [`std::fs::FileType`] cannot be built directly, we emulate what we
883 /// care about.
912 /// care about.
884 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
913 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
885 enum FakeFileType {
914 enum FakeFileType {
886 File,
915 File,
887 Directory,
916 Directory,
888 Symlink,
917 Symlink,
889 BadType(BadType),
918 BadType(BadType),
890 }
919 }
891
920
892 impl From<std::fs::FileType> for FakeFileType {
921 impl From<std::fs::FileType> for FakeFileType {
893 fn from(f: std::fs::FileType) -> Self {
922 fn from(f: std::fs::FileType) -> Self {
894 if f.is_dir() {
923 if f.is_dir() {
895 Self::Directory
924 Self::Directory
896 } else if f.is_file() {
925 } else if f.is_file() {
897 Self::File
926 Self::File
898 } else if f.is_symlink() {
927 } else if f.is_symlink() {
899 Self::Symlink
928 Self::Symlink
900 } else if f.is_fifo() {
929 } else if f.is_fifo() {
901 Self::BadType(BadType::FIFO)
930 Self::BadType(BadType::FIFO)
902 } else if f.is_block_device() {
931 } else if f.is_block_device() {
903 Self::BadType(BadType::BlockDevice)
932 Self::BadType(BadType::BlockDevice)
904 } else if f.is_char_device() {
933 } else if f.is_char_device() {
905 Self::BadType(BadType::CharacterDevice)
934 Self::BadType(BadType::CharacterDevice)
906 } else if f.is_socket() {
935 } else if f.is_socket() {
907 Self::BadType(BadType::Socket)
936 Self::BadType(BadType::Socket)
908 } else {
937 } else {
909 Self::BadType(BadType::Unknown)
938 Self::BadType(BadType::Unknown)
910 }
939 }
911 }
940 }
912 }
941 }
913
942
914 struct DirEntry<'a> {
943 struct DirEntry<'a> {
915 /// Path as stored in the dirstate, or just the filename for optimization.
944 /// Path as stored in the dirstate, or just the filename for optimization.
916 hg_path: HgPathCow<'a>,
945 hg_path: HgPathCow<'a>,
917 /// Filesystem path
946 /// Filesystem path
918 fs_path: Cow<'a, Path>,
947 fs_path: Cow<'a, Path>,
919 /// Lazily computed
948 /// Lazily computed
920 symlink_metadata: Option<std::fs::Metadata>,
949 symlink_metadata: Option<std::fs::Metadata>,
921 /// Already computed for ergonomics.
950 /// Already computed for ergonomics.
922 file_type: FakeFileType,
951 file_type: FakeFileType,
923 }
952 }
924
953
925 impl<'a> DirEntry<'a> {
954 impl<'a> DirEntry<'a> {
926 /// Returns **unsorted** entries in the given directory, with name,
955 /// Returns **unsorted** entries in the given directory, with name,
927 /// metadata and file type.
956 /// metadata and file type.
928 ///
957 ///
929 /// If a `.hg` sub-directory is encountered:
958 /// If a `.hg` sub-directory is encountered:
930 ///
959 ///
931 /// * At the repository root, ignore that sub-directory
960 /// * At the repository root, ignore that sub-directory
932 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
961 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
933 /// list instead.
962 /// list instead.
934 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
963 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
935 // `read_dir` returns a "not found" error for the empty path
964 // `read_dir` returns a "not found" error for the empty path
936 let at_cwd = path == Path::new("");
965 let at_cwd = path == Path::new("");
937 let read_dir_path = if at_cwd { Path::new(".") } else { path };
966 let read_dir_path = if at_cwd { Path::new(".") } else { path };
938 let mut results = Vec::new();
967 let mut results = Vec::new();
939 for entry in read_dir_path.read_dir()? {
968 for entry in read_dir_path.read_dir()? {
940 let entry = entry?;
969 let entry = entry?;
941 let file_type = match entry.file_type() {
970 let file_type = match entry.file_type() {
942 Ok(v) => v,
971 Ok(v) => v,
943 Err(e) => {
972 Err(e) => {
944 // race with file deletion?
973 // race with file deletion?
945 if e.kind() == std::io::ErrorKind::NotFound {
974 if e.kind() == std::io::ErrorKind::NotFound {
946 continue;
975 continue;
947 } else {
976 } else {
948 return Err(e);
977 return Err(e);
949 }
978 }
950 }
979 }
951 };
980 };
952 let file_name = entry.file_name();
981 let file_name = entry.file_name();
953 // FIXME don't do this when cached
982 // FIXME don't do this when cached
954 if file_name == ".hg" {
983 if file_name == ".hg" {
955 if is_at_repo_root {
984 if is_at_repo_root {
956 // Skip the repo’s own .hg (might be a symlink)
985 // Skip the repo’s own .hg (might be a symlink)
957 continue;
986 continue;
958 } else if file_type.is_dir() {
987 } else if file_type.is_dir() {
959 // A .hg sub-directory at another location means a subrepo,
988 // A .hg sub-directory at another location means a subrepo,
960 // skip it entirely.
989 // skip it entirely.
961 return Ok(Vec::new());
990 return Ok(Vec::new());
962 }
991 }
963 }
992 }
964 let full_path = if at_cwd {
993 let full_path = if at_cwd {
965 file_name.clone().into()
994 file_name.clone().into()
966 } else {
995 } else {
967 entry.path()
996 entry.path()
968 };
997 };
969 let filename =
998 let filename =
970 Cow::Owned(get_bytes_from_os_string(file_name).into());
999 Cow::Owned(get_bytes_from_os_string(file_name).into());
971 let file_type = FakeFileType::from(file_type);
1000 let file_type = FakeFileType::from(file_type);
972 results.push(DirEntry {
1001 results.push(DirEntry {
973 hg_path: filename,
1002 hg_path: filename,
974 fs_path: Cow::Owned(full_path.to_path_buf()),
1003 fs_path: Cow::Owned(full_path.to_path_buf()),
975 symlink_metadata: None,
1004 symlink_metadata: None,
976 file_type,
1005 file_type,
977 })
1006 })
978 }
1007 }
979 Ok(results)
1008 Ok(results)
980 }
1009 }
981
1010
982 fn symlink_metadata(&self) -> Result<std::fs::Metadata, std::io::Error> {
1011 fn symlink_metadata(&self) -> Result<std::fs::Metadata, std::io::Error> {
983 match &self.symlink_metadata {
1012 match &self.symlink_metadata {
984 Some(meta) => Ok(meta.clone()),
1013 Some(meta) => Ok(meta.clone()),
985 None => std::fs::symlink_metadata(&self.fs_path),
1014 None => std::fs::symlink_metadata(&self.fs_path),
986 }
1015 }
987 }
1016 }
988
1017
989 fn is_dir(&self) -> bool {
1018 fn is_dir(&self) -> bool {
990 self.file_type == FakeFileType::Directory
1019 self.file_type == FakeFileType::Directory
991 }
1020 }
992
1021
993 fn is_file(&self) -> bool {
1022 fn is_file(&self) -> bool {
994 self.file_type == FakeFileType::File
1023 self.file_type == FakeFileType::File
995 }
1024 }
996
1025
997 fn is_symlink(&self) -> bool {
1026 fn is_symlink(&self) -> bool {
998 self.file_type == FakeFileType::Symlink
1027 self.file_type == FakeFileType::Symlink
999 }
1028 }
1000
1029
1001 fn is_bad(&self) -> Option<BadType> {
1030 fn is_bad(&self) -> Option<BadType> {
1002 match self.file_type {
1031 match self.file_type {
1003 FakeFileType::BadType(ty) => Some(ty),
1032 FakeFileType::BadType(ty) => Some(ty),
1004 _ => None,
1033 _ => None,
1005 }
1034 }
1006 }
1035 }
1007 }
1036 }
1008
1037
1009 /// Return the `mtime` of a temporary file newly-created in the `.hg` directory
1038 /// Return the `mtime` of a temporary file newly-created in the `.hg` directory
1010 /// of the give repository.
1039 /// of the give repository.
1011 ///
1040 ///
1012 /// This is similar to `SystemTime::now()`, with the result truncated to the
1041 /// This is similar to `SystemTime::now()`, with the result truncated to the
1013 /// same time resolution as other files’ modification times. Using `.hg`
1042 /// same time resolution as other files’ modification times. Using `.hg`
1014 /// instead of the system’s default temporary directory (such as `/tmp`) makes
1043 /// instead of the system’s default temporary directory (such as `/tmp`) makes
1015 /// it more likely the temporary file is in the same disk partition as contents
1044 /// it more likely the temporary file is in the same disk partition as contents
1016 /// of the working directory, which can matter since different filesystems may
1045 /// of the working directory, which can matter since different filesystems may
1017 /// store timestamps with different resolutions.
1046 /// store timestamps with different resolutions.
1018 ///
1047 ///
1019 /// This may fail, typically if we lack write permissions. In that case we
1048 /// This may fail, typically if we lack write permissions. In that case we
1020 /// should continue the `status()` algoritm anyway and consider the current
1049 /// should continue the `status()` algoritm anyway and consider the current
1021 /// date/time to be unknown.
1050 /// date/time to be unknown.
1022 fn filesystem_now(repo_root: &Path) -> Result<SystemTime, io::Error> {
1051 fn filesystem_now(repo_root: &Path) -> Result<SystemTime, io::Error> {
1023 tempfile::tempfile_in(repo_root.join(".hg"))?
1052 tempfile::tempfile_in(repo_root.join(".hg"))?
1024 .metadata()?
1053 .metadata()?
1025 .modified()
1054 .modified()
1026 }
1055 }
General Comments 0
You need to be logged in to leave comments. Login now