##// END OF EJS Templates
hg-core: refactor rev parsing in operations::status_rev_rev...
Mitchell Kember -
r53294:42bd36bb default
parent child Browse files
Show More
@@ -1,832 +1,827
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
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 use crate::error::CommandError;
8 use crate::error::CommandError;
9 use crate::ui::{
9 use crate::ui::{
10 format_pattern_file_warning, print_narrow_sparse_warnings, relative_paths,
10 format_pattern_file_warning, print_narrow_sparse_warnings, relative_paths,
11 RelativePaths, Ui,
11 RelativePaths, Ui,
12 };
12 };
13 use crate::utils::path_utils::RelativizePaths;
13 use crate::utils::path_utils::RelativizePaths;
14 use clap::Arg;
14 use clap::Arg;
15 use format_bytes::format_bytes;
15 use format_bytes::format_bytes;
16 use hg::config::Config;
16 use hg::config::Config;
17 use hg::dirstate::entry::{has_exec_bit, TruncatedTimestamp};
17 use hg::dirstate::entry::{has_exec_bit, TruncatedTimestamp};
18 use hg::dirstate::status::{
18 use hg::dirstate::status::{
19 BadMatch, DirstateStatus, StatusError, StatusOptions, StatusPath,
19 BadMatch, DirstateStatus, StatusError, StatusOptions, StatusPath,
20 };
20 };
21 use hg::errors::{HgError, IoResultExt};
21 use hg::errors::{HgError, IoResultExt};
22 use hg::filepatterns::{parse_pattern_args, PatternFileWarning};
22 use hg::filepatterns::{parse_pattern_args, PatternFileWarning};
23 use hg::lock::LockError;
23 use hg::lock::LockError;
24 use hg::matchers::{AlwaysMatcher, IntersectionMatcher};
24 use hg::matchers::{AlwaysMatcher, IntersectionMatcher};
25 use hg::repo::Repo;
25 use hg::repo::Repo;
26 use hg::revlog::manifest::Manifest;
26 use hg::revlog::manifest::Manifest;
27 use hg::revlog::options::{default_revlog_options, RevlogOpenOptions};
27 use hg::revlog::options::{default_revlog_options, RevlogOpenOptions};
28 use hg::revlog::RevlogType;
28 use hg::revlog::RevlogType;
29 use hg::utils::debug::debug_wait_for_file;
29 use hg::utils::debug::debug_wait_for_file;
30 use hg::utils::files::{
30 use hg::utils::files::{
31 get_bytes_from_os_str, get_bytes_from_os_string, get_path_from_bytes,
31 get_bytes_from_os_str, get_bytes_from_os_string, get_path_from_bytes,
32 };
32 };
33 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
33 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
34 use hg::Revision;
34 use hg::Revision;
35 use hg::{self, narrow, sparse};
35 use hg::{self, narrow, sparse};
36 use log::info;
36 use log::info;
37 use rayon::prelude::*;
37 use rayon::prelude::*;
38 use std::borrow::Cow;
38 use std::borrow::Cow;
39 use std::io;
39 use std::io;
40 use std::mem::take;
40 use std::mem::take;
41 use std::path::PathBuf;
41 use std::path::PathBuf;
42
42
43 pub const HELP_TEXT: &str = "
43 pub const HELP_TEXT: &str = "
44 Show changed files in the working directory
44 Show changed files in the working directory
45
45
46 This is a pure Rust version of `hg status`.
46 This is a pure Rust version of `hg status`.
47
47
48 Some options might be missing, check the list below.
48 Some options might be missing, check the list below.
49 ";
49 ";
50
50
51 pub fn args() -> clap::Command {
51 pub fn args() -> clap::Command {
52 clap::command!("status")
52 clap::command!("status")
53 .alias("st")
53 .alias("st")
54 .about(HELP_TEXT)
54 .about(HELP_TEXT)
55 .arg(
55 .arg(
56 Arg::new("file")
56 Arg::new("file")
57 .value_parser(clap::value_parser!(std::ffi::OsString))
57 .value_parser(clap::value_parser!(std::ffi::OsString))
58 .help("show only these files")
58 .help("show only these files")
59 .action(clap::ArgAction::Append),
59 .action(clap::ArgAction::Append),
60 )
60 )
61 .arg(
61 .arg(
62 Arg::new("all")
62 Arg::new("all")
63 .help("show status of all files")
63 .help("show status of all files")
64 .short('A')
64 .short('A')
65 .action(clap::ArgAction::SetTrue)
65 .action(clap::ArgAction::SetTrue)
66 .long("all"),
66 .long("all"),
67 )
67 )
68 .arg(
68 .arg(
69 Arg::new("modified")
69 Arg::new("modified")
70 .help("show only modified files")
70 .help("show only modified files")
71 .short('m')
71 .short('m')
72 .action(clap::ArgAction::SetTrue)
72 .action(clap::ArgAction::SetTrue)
73 .long("modified"),
73 .long("modified"),
74 )
74 )
75 .arg(
75 .arg(
76 Arg::new("added")
76 Arg::new("added")
77 .help("show only added files")
77 .help("show only added files")
78 .short('a')
78 .short('a')
79 .action(clap::ArgAction::SetTrue)
79 .action(clap::ArgAction::SetTrue)
80 .long("added"),
80 .long("added"),
81 )
81 )
82 .arg(
82 .arg(
83 Arg::new("removed")
83 Arg::new("removed")
84 .help("show only removed files")
84 .help("show only removed files")
85 .short('r')
85 .short('r')
86 .action(clap::ArgAction::SetTrue)
86 .action(clap::ArgAction::SetTrue)
87 .long("removed"),
87 .long("removed"),
88 )
88 )
89 .arg(
89 .arg(
90 Arg::new("clean")
90 Arg::new("clean")
91 .help("show only clean files")
91 .help("show only clean files")
92 .short('c')
92 .short('c')
93 .action(clap::ArgAction::SetTrue)
93 .action(clap::ArgAction::SetTrue)
94 .long("clean"),
94 .long("clean"),
95 )
95 )
96 .arg(
96 .arg(
97 Arg::new("deleted")
97 Arg::new("deleted")
98 .help("show only deleted files")
98 .help("show only deleted files")
99 .short('d')
99 .short('d')
100 .action(clap::ArgAction::SetTrue)
100 .action(clap::ArgAction::SetTrue)
101 .long("deleted"),
101 .long("deleted"),
102 )
102 )
103 .arg(
103 .arg(
104 Arg::new("unknown")
104 Arg::new("unknown")
105 .help("show only unknown (not tracked) files")
105 .help("show only unknown (not tracked) files")
106 .short('u')
106 .short('u')
107 .action(clap::ArgAction::SetTrue)
107 .action(clap::ArgAction::SetTrue)
108 .long("unknown"),
108 .long("unknown"),
109 )
109 )
110 .arg(
110 .arg(
111 Arg::new("ignored")
111 Arg::new("ignored")
112 .help("show only ignored files")
112 .help("show only ignored files")
113 .short('i')
113 .short('i')
114 .action(clap::ArgAction::SetTrue)
114 .action(clap::ArgAction::SetTrue)
115 .long("ignored"),
115 .long("ignored"),
116 )
116 )
117 .arg(
117 .arg(
118 Arg::new("copies")
118 Arg::new("copies")
119 .help("show source of copied files (DEFAULT: ui.statuscopies)")
119 .help("show source of copied files (DEFAULT: ui.statuscopies)")
120 .short('C')
120 .short('C')
121 .action(clap::ArgAction::SetTrue)
121 .action(clap::ArgAction::SetTrue)
122 .long("copies"),
122 .long("copies"),
123 )
123 )
124 .arg(
124 .arg(
125 Arg::new("no-copies")
125 Arg::new("no-copies")
126 .action(clap::ArgAction::SetTrue)
126 .action(clap::ArgAction::SetTrue)
127 .long("no-copies")
127 .long("no-copies")
128 .overrides_with("copies"),
128 .overrides_with("copies"),
129 )
129 )
130 .arg(
130 .arg(
131 Arg::new("print0")
131 Arg::new("print0")
132 .help("end filenames with NUL, for use with xargs")
132 .help("end filenames with NUL, for use with xargs")
133 .short('0')
133 .short('0')
134 .action(clap::ArgAction::SetTrue)
134 .action(clap::ArgAction::SetTrue)
135 .long("print0"),
135 .long("print0"),
136 )
136 )
137 .arg(
137 .arg(
138 Arg::new("no-status")
138 Arg::new("no-status")
139 .help("hide status prefix")
139 .help("hide status prefix")
140 .short('n')
140 .short('n')
141 .action(clap::ArgAction::SetTrue)
141 .action(clap::ArgAction::SetTrue)
142 .long("no-status"),
142 .long("no-status"),
143 )
143 )
144 .arg(
144 .arg(
145 Arg::new("verbose")
145 Arg::new("verbose")
146 .help("enable additional output")
146 .help("enable additional output")
147 .short('v')
147 .short('v')
148 .action(clap::ArgAction::SetTrue)
148 .action(clap::ArgAction::SetTrue)
149 .long("verbose"),
149 .long("verbose"),
150 )
150 )
151 .arg(
151 .arg(
152 Arg::new("rev")
152 Arg::new("rev")
153 .help("show difference from/to revision")
153 .help("show difference from/to revision")
154 .long("rev")
154 .long("rev")
155 .num_args(1)
155 .num_args(1)
156 .action(clap::ArgAction::Append)
156 .action(clap::ArgAction::Append)
157 .value_name("REV"),
157 .value_name("REV"),
158 )
158 )
159 }
159 }
160
160
161 fn parse_revpair(
161 fn parse_revpair(
162 repo: &Repo,
162 repo: &Repo,
163 revs: Option<Vec<String>>,
163 revs: Option<Vec<String>>,
164 ) -> Result<Option<(Revision, Revision)>, CommandError> {
164 ) -> Result<Option<(Revision, Revision)>, CommandError> {
165 let revs = match revs {
165 let Some(revs) = revs else {
166 None => return Ok(None),
166 return Ok(None);
167 Some(revs) => revs,
168 };
167 };
169 if revs.is_empty() {
168 match revs.as_slice() {
170 return Ok(None);
169 [] => Ok(None),
170 [rev1, rev2] => Ok(Some((
171 hg::revset::resolve_single(rev1, repo)?,
172 hg::revset::resolve_single(rev2, repo)?,
173 ))),
174 _ => Err(CommandError::unsupported("expected 0 or 2 --rev flags")),
171 }
175 }
172 if revs.len() != 2 {
173 return Err(CommandError::unsupported("expected 0 or 2 --rev flags"));
174 }
175
176 let rev1 = &revs[0];
177 let rev2 = &revs[1];
178 let rev1 = hg::revset::resolve_single(rev1, repo)?;
179 let rev2 = hg::revset::resolve_single(rev2, repo)?;
180 Ok(Some((rev1, rev2)))
181 }
176 }
182
177
183 /// Pure data type allowing the caller to specify file states to display
178 /// Pure data type allowing the caller to specify file states to display
184 #[derive(Copy, Clone, Debug)]
179 #[derive(Copy, Clone, Debug)]
185 pub struct DisplayStates {
180 pub struct DisplayStates {
186 pub modified: bool,
181 pub modified: bool,
187 pub added: bool,
182 pub added: bool,
188 pub removed: bool,
183 pub removed: bool,
189 pub clean: bool,
184 pub clean: bool,
190 pub deleted: bool,
185 pub deleted: bool,
191 pub unknown: bool,
186 pub unknown: bool,
192 pub ignored: bool,
187 pub ignored: bool,
193 }
188 }
194
189
195 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
190 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
196 modified: true,
191 modified: true,
197 added: true,
192 added: true,
198 removed: true,
193 removed: true,
199 clean: false,
194 clean: false,
200 deleted: true,
195 deleted: true,
201 unknown: true,
196 unknown: true,
202 ignored: false,
197 ignored: false,
203 };
198 };
204
199
205 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
200 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
206 modified: true,
201 modified: true,
207 added: true,
202 added: true,
208 removed: true,
203 removed: true,
209 clean: true,
204 clean: true,
210 deleted: true,
205 deleted: true,
211 unknown: true,
206 unknown: true,
212 ignored: true,
207 ignored: true,
213 };
208 };
214
209
215 impl DisplayStates {
210 impl DisplayStates {
216 pub fn is_empty(&self) -> bool {
211 pub fn is_empty(&self) -> bool {
217 !(self.modified
212 !(self.modified
218 || self.added
213 || self.added
219 || self.removed
214 || self.removed
220 || self.clean
215 || self.clean
221 || self.deleted
216 || self.deleted
222 || self.unknown
217 || self.unknown
223 || self.ignored)
218 || self.ignored)
224 }
219 }
225 }
220 }
226
221
227 fn has_unfinished_merge(repo: &Repo) -> Result<bool, CommandError> {
222 fn has_unfinished_merge(repo: &Repo) -> Result<bool, CommandError> {
228 Ok(repo.dirstate_parents()?.is_merge())
223 Ok(repo.dirstate_parents()?.is_merge())
229 }
224 }
230
225
231 fn has_unfinished_state(repo: &Repo) -> Result<bool, CommandError> {
226 fn has_unfinished_state(repo: &Repo) -> Result<bool, CommandError> {
232 // These are all the known values for the [fname] argument of
227 // These are all the known values for the [fname] argument of
233 // [addunfinished] function in [state.py]
228 // [addunfinished] function in [state.py]
234 let known_state_files: &[&str] = &[
229 let known_state_files: &[&str] = &[
235 "bisect.state",
230 "bisect.state",
236 "graftstate",
231 "graftstate",
237 "histedit-state",
232 "histedit-state",
238 "rebasestate",
233 "rebasestate",
239 "shelvedstate",
234 "shelvedstate",
240 "transplant/journal",
235 "transplant/journal",
241 "updatestate",
236 "updatestate",
242 ];
237 ];
243 if has_unfinished_merge(repo)? {
238 if has_unfinished_merge(repo)? {
244 return Ok(true);
239 return Ok(true);
245 };
240 };
246 for f in known_state_files {
241 for f in known_state_files {
247 if repo.hg_vfs().join(f).exists() {
242 if repo.hg_vfs().join(f).exists() {
248 return Ok(true);
243 return Ok(true);
249 }
244 }
250 }
245 }
251 Ok(false)
246 Ok(false)
252 }
247 }
253
248
254 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
249 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
255 // TODO: lift these limitations
250 // TODO: lift these limitations
256 if invocation
251 if invocation
257 .config
252 .config
258 .get(b"commands", b"status.terse")
253 .get(b"commands", b"status.terse")
259 .is_some()
254 .is_some()
260 {
255 {
261 return Err(CommandError::unsupported(
256 return Err(CommandError::unsupported(
262 "status.terse is not yet supported with rhg status",
257 "status.terse is not yet supported with rhg status",
263 ));
258 ));
264 }
259 }
265
260
266 let ui = invocation.ui;
261 let ui = invocation.ui;
267 let config = invocation.config;
262 let config = invocation.config;
268 let args = invocation.subcommand_args;
263 let args = invocation.subcommand_args;
269
264
270 let revs = args.get_many::<String>("rev");
265 let revs = args.get_many::<String>("rev");
271 let print0 = args.get_flag("print0");
266 let print0 = args.get_flag("print0");
272 let verbose = args.get_flag("verbose")
267 let verbose = args.get_flag("verbose")
273 || config.get_bool(b"ui", b"verbose")?
268 || config.get_bool(b"ui", b"verbose")?
274 || config.get_bool(b"commands", b"status.verbose")?;
269 || config.get_bool(b"commands", b"status.verbose")?;
275 let verbose = verbose && !print0;
270 let verbose = verbose && !print0;
276
271
277 let all = args.get_flag("all");
272 let all = args.get_flag("all");
278 let display_states = if all {
273 let display_states = if all {
279 // TODO when implementing `--quiet`: it excludes clean files
274 // TODO when implementing `--quiet`: it excludes clean files
280 // from `--all`
275 // from `--all`
281 ALL_DISPLAY_STATES
276 ALL_DISPLAY_STATES
282 } else {
277 } else {
283 let requested = DisplayStates {
278 let requested = DisplayStates {
284 modified: args.get_flag("modified"),
279 modified: args.get_flag("modified"),
285 added: args.get_flag("added"),
280 added: args.get_flag("added"),
286 removed: args.get_flag("removed"),
281 removed: args.get_flag("removed"),
287 clean: args.get_flag("clean"),
282 clean: args.get_flag("clean"),
288 deleted: args.get_flag("deleted"),
283 deleted: args.get_flag("deleted"),
289 unknown: args.get_flag("unknown"),
284 unknown: args.get_flag("unknown"),
290 ignored: args.get_flag("ignored"),
285 ignored: args.get_flag("ignored"),
291 };
286 };
292 if requested.is_empty() {
287 if requested.is_empty() {
293 DEFAULT_DISPLAY_STATES
288 DEFAULT_DISPLAY_STATES
294 } else {
289 } else {
295 requested
290 requested
296 }
291 }
297 };
292 };
298 let no_status = args.get_flag("no-status");
293 let no_status = args.get_flag("no-status");
299 let list_copies = if args.get_flag("copies") {
294 let list_copies = if args.get_flag("copies") {
300 true
295 true
301 } else if args.get_flag("no-copies") {
296 } else if args.get_flag("no-copies") {
302 false
297 false
303 } else {
298 } else {
304 config.get_bool(b"ui", b"statuscopies")?
299 config.get_bool(b"ui", b"statuscopies")?
305 };
300 };
306 let list_copies = (list_copies || all) && !no_status;
301 let list_copies = (list_copies || all) && !no_status;
307
302
308 let repo = invocation.repo?;
303 let repo = invocation.repo?;
309 let revpair = parse_revpair(repo, revs.map(|i| i.cloned().collect()))?;
304 let revpair = parse_revpair(repo, revs.map(|i| i.cloned().collect()))?;
310
305
311 if verbose && has_unfinished_state(repo)? {
306 if verbose && has_unfinished_state(repo)? {
312 return Err(CommandError::unsupported(
307 return Err(CommandError::unsupported(
313 "verbose status output is not supported by rhg (and is needed because we're in an unfinished operation)",
308 "verbose status output is not supported by rhg (and is needed because we're in an unfinished operation)",
314 ));
309 ));
315 }
310 }
316
311
317 let mut dmap = repo.dirstate_map_mut()?;
312 let mut dmap = repo.dirstate_map_mut()?;
318
313
319 let check_exec = hg::checkexec::check_exec(repo.working_directory_path());
314 let check_exec = hg::checkexec::check_exec(repo.working_directory_path());
320
315
321 let options = StatusOptions {
316 let options = StatusOptions {
322 check_exec,
317 check_exec,
323 list_clean: display_states.clean,
318 list_clean: display_states.clean,
324 list_unknown: display_states.unknown,
319 list_unknown: display_states.unknown,
325 list_ignored: display_states.ignored,
320 list_ignored: display_states.ignored,
326 list_copies,
321 list_copies,
327 collect_traversed_dirs: false,
322 collect_traversed_dirs: false,
328 };
323 };
329
324
330 type StatusResult<'a> =
325 type StatusResult<'a> =
331 Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
326 Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
332
327
333 let relative_status = config
328 let relative_status = config
334 .get_option(b"commands", b"status.relative")?
329 .get_option(b"commands", b"status.relative")?
335 .expect("commands.status.relative should have a default value");
330 .expect("commands.status.relative should have a default value");
336
331
337 let relativize_paths = relative_status || {
332 let relativize_paths = relative_status || {
338 // See in Python code with `getuipathfn` usage in `commands.py`.
333 // See in Python code with `getuipathfn` usage in `commands.py`.
339 let legacy_relative_behavior = args.contains_id("file");
334 let legacy_relative_behavior = args.contains_id("file");
340 match relative_paths(invocation.config)? {
335 match relative_paths(invocation.config)? {
341 RelativePaths::Legacy => legacy_relative_behavior,
336 RelativePaths::Legacy => legacy_relative_behavior,
342 RelativePaths::Bool(v) => v,
337 RelativePaths::Bool(v) => v,
343 }
338 }
344 };
339 };
345
340
346 let mut output = DisplayStatusPaths {
341 let mut output = DisplayStatusPaths {
347 ui,
342 ui,
348 no_status,
343 no_status,
349 relativize: if relativize_paths {
344 relativize: if relativize_paths {
350 Some(RelativizePaths::new(repo)?)
345 Some(RelativizePaths::new(repo)?)
351 } else {
346 } else {
352 None
347 None
353 },
348 },
354 print0,
349 print0,
355 };
350 };
356
351
357 let after_status = |res: StatusResult| -> Result<_, CommandError> {
352 let after_status = |res: StatusResult| -> Result<_, CommandError> {
358 let (mut ds_status, pattern_warnings) = res?;
353 let (mut ds_status, pattern_warnings) = res?;
359 for warning in pattern_warnings {
354 for warning in pattern_warnings {
360 ui.write_stderr(&format_pattern_file_warning(&warning, repo))?;
355 ui.write_stderr(&format_pattern_file_warning(&warning, repo))?;
361 }
356 }
362
357
363 for (path, error) in take(&mut ds_status.bad) {
358 for (path, error) in take(&mut ds_status.bad) {
364 let error = match error {
359 let error = match error {
365 BadMatch::OsError(code) => {
360 BadMatch::OsError(code) => {
366 std::io::Error::from_raw_os_error(code).to_string()
361 std::io::Error::from_raw_os_error(code).to_string()
367 }
362 }
368 BadMatch::BadType(ty) => {
363 BadMatch::BadType(ty) => {
369 format!("unsupported file type (type is {})", ty)
364 format!("unsupported file type (type is {})", ty)
370 }
365 }
371 };
366 };
372 ui.write_stderr(&format_bytes!(
367 ui.write_stderr(&format_bytes!(
373 b"{}: {}\n",
368 b"{}: {}\n",
374 path.as_bytes(),
369 path.as_bytes(),
375 error.as_bytes()
370 error.as_bytes()
376 ))?
371 ))?
377 }
372 }
378 if !ds_status.unsure.is_empty() {
373 if !ds_status.unsure.is_empty() {
379 info!(
374 info!(
380 "Files to be rechecked by retrieval from filelog: {:?}",
375 "Files to be rechecked by retrieval from filelog: {:?}",
381 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
376 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
382 );
377 );
383 }
378 }
384 let mut fixup = Vec::new();
379 let mut fixup = Vec::new();
385 if !ds_status.unsure.is_empty()
380 if !ds_status.unsure.is_empty()
386 && (display_states.modified || display_states.clean)
381 && (display_states.modified || display_states.clean)
387 {
382 {
388 let p1 = repo.dirstate_parents()?.p1;
383 let p1 = repo.dirstate_parents()?.p1;
389 let manifest = repo.manifest_for_node(p1)?;
384 let manifest = repo.manifest_for_node(p1)?;
390 let working_directory_vfs = repo.working_directory_vfs();
385 let working_directory_vfs = repo.working_directory_vfs();
391 let store_vfs = repo.store_vfs();
386 let store_vfs = repo.store_vfs();
392 let filelog_open_options = default_revlog_options(
387 let filelog_open_options = default_revlog_options(
393 repo.config(),
388 repo.config(),
394 repo.requirements(),
389 repo.requirements(),
395 RevlogType::Filelog,
390 RevlogType::Filelog,
396 )?;
391 )?;
397 let res: Vec<_> = take(&mut ds_status.unsure)
392 let res: Vec<_> = take(&mut ds_status.unsure)
398 .into_par_iter()
393 .into_par_iter()
399 .map(|to_check| {
394 .map(|to_check| {
400 // The compiler seems to get a bit confused with complex
395 // The compiler seems to get a bit confused with complex
401 // inference when using a parallel iterator + map
396 // inference when using a parallel iterator + map
402 // + map_err + collect, so let's just inline some of the
397 // + map_err + collect, so let's just inline some of the
403 // logic.
398 // logic.
404 match unsure_is_modified(
399 match unsure_is_modified(
405 &working_directory_vfs,
400 &working_directory_vfs,
406 &store_vfs,
401 &store_vfs,
407 check_exec,
402 check_exec,
408 &manifest,
403 &manifest,
409 &to_check.path,
404 &to_check.path,
410 filelog_open_options,
405 filelog_open_options,
411 ) {
406 ) {
412 Err(HgError::IoError { .. }) => {
407 Err(HgError::IoError { .. }) => {
413 // IO errors most likely stem from the file being
408 // IO errors most likely stem from the file being
414 // deleted even though we know it's in the
409 // deleted even though we know it's in the
415 // dirstate.
410 // dirstate.
416 Ok((to_check, UnsureOutcome::Deleted))
411 Ok((to_check, UnsureOutcome::Deleted))
417 }
412 }
418 Ok(outcome) => Ok((to_check, outcome)),
413 Ok(outcome) => Ok((to_check, outcome)),
419 Err(e) => Err(e),
414 Err(e) => Err(e),
420 }
415 }
421 })
416 })
422 .collect::<Result<_, _>>()?;
417 .collect::<Result<_, _>>()?;
423 for (status_path, outcome) in res.into_iter() {
418 for (status_path, outcome) in res.into_iter() {
424 match outcome {
419 match outcome {
425 UnsureOutcome::Clean => {
420 UnsureOutcome::Clean => {
426 if display_states.clean {
421 if display_states.clean {
427 ds_status.clean.push(status_path.clone());
422 ds_status.clean.push(status_path.clone());
428 }
423 }
429 fixup.push(status_path.path.into_owned())
424 fixup.push(status_path.path.into_owned())
430 }
425 }
431 UnsureOutcome::Modified => {
426 UnsureOutcome::Modified => {
432 if display_states.modified {
427 if display_states.modified {
433 ds_status.modified.push(status_path);
428 ds_status.modified.push(status_path);
434 }
429 }
435 }
430 }
436 UnsureOutcome::Deleted => {
431 UnsureOutcome::Deleted => {
437 if display_states.deleted {
432 if display_states.deleted {
438 ds_status.deleted.push(status_path);
433 ds_status.deleted.push(status_path);
439 }
434 }
440 }
435 }
441 }
436 }
442 }
437 }
443 }
438 }
444
439
445 let dirstate_write_needed = ds_status.dirty;
440 let dirstate_write_needed = ds_status.dirty;
446 let filesystem_time_at_status_start =
441 let filesystem_time_at_status_start =
447 ds_status.filesystem_time_at_status_start;
442 ds_status.filesystem_time_at_status_start;
448
443
449 output.output(display_states, ds_status)?;
444 output.output(display_states, ds_status)?;
450
445
451 Ok((
446 Ok((
452 fixup,
447 fixup,
453 dirstate_write_needed,
448 dirstate_write_needed,
454 filesystem_time_at_status_start,
449 filesystem_time_at_status_start,
455 ))
450 ))
456 };
451 };
457
452
458 let (narrow_matcher, narrow_warnings) = narrow::matcher(repo)?;
453 let (narrow_matcher, narrow_warnings) = narrow::matcher(repo)?;
459 let (sparse_matcher, sparse_warnings) = sparse::matcher(repo)?;
454 let (sparse_matcher, sparse_warnings) = sparse::matcher(repo)?;
460 let matcher = match (repo.has_narrow(), repo.has_sparse()) {
455 let matcher = match (repo.has_narrow(), repo.has_sparse()) {
461 (true, true) => {
456 (true, true) => {
462 Box::new(IntersectionMatcher::new(narrow_matcher, sparse_matcher))
457 Box::new(IntersectionMatcher::new(narrow_matcher, sparse_matcher))
463 }
458 }
464 (true, false) => narrow_matcher,
459 (true, false) => narrow_matcher,
465 (false, true) => sparse_matcher,
460 (false, true) => sparse_matcher,
466 (false, false) => Box::new(AlwaysMatcher),
461 (false, false) => Box::new(AlwaysMatcher),
467 };
462 };
468 let matcher = match args.get_many::<std::ffi::OsString>("file") {
463 let matcher = match args.get_many::<std::ffi::OsString>("file") {
469 None => matcher,
464 None => matcher,
470 Some(files) => {
465 Some(files) => {
471 let patterns: Vec<Vec<u8>> = files
466 let patterns: Vec<Vec<u8>> = files
472 .filter(|s| !s.is_empty())
467 .filter(|s| !s.is_empty())
473 .map(get_bytes_from_os_str)
468 .map(get_bytes_from_os_str)
474 .collect();
469 .collect();
475 for file in &patterns {
470 for file in &patterns {
476 if file.starts_with(b"set:") {
471 if file.starts_with(b"set:") {
477 return Err(CommandError::unsupported("fileset"));
472 return Err(CommandError::unsupported("fileset"));
478 }
473 }
479 }
474 }
480 let cwd = hg::utils::current_dir()?;
475 let cwd = hg::utils::current_dir()?;
481 let root = repo.working_directory_path();
476 let root = repo.working_directory_path();
482 let ignore_patterns = parse_pattern_args(patterns, &cwd, root)?;
477 let ignore_patterns = parse_pattern_args(patterns, &cwd, root)?;
483 let files_matcher =
478 let files_matcher =
484 hg::matchers::PatternMatcher::new(ignore_patterns)?;
479 hg::matchers::PatternMatcher::new(ignore_patterns)?;
485 Box::new(IntersectionMatcher::new(
480 Box::new(IntersectionMatcher::new(
486 Box::new(files_matcher),
481 Box::new(files_matcher),
487 matcher,
482 matcher,
488 ))
483 ))
489 }
484 }
490 };
485 };
491 print_narrow_sparse_warnings(
486 print_narrow_sparse_warnings(
492 &narrow_warnings,
487 &narrow_warnings,
493 &sparse_warnings,
488 &sparse_warnings,
494 ui,
489 ui,
495 repo,
490 repo,
496 )?;
491 )?;
497
492
498 if let Some((rev1, rev2)) = revpair {
493 if let Some((rev1, rev2)) = revpair {
499 let mut ds_status = DirstateStatus::default();
494 let mut ds_status = DirstateStatus::default();
500 if list_copies {
495 if list_copies {
501 return Err(CommandError::unsupported(
496 return Err(CommandError::unsupported(
502 "status --rev --rev with copy information is not implemented yet",
497 "status --rev --rev with copy information is not implemented yet",
503 ));
498 ));
504 }
499 }
505
500
506 let stat = hg::operations::status_rev_rev_no_copies(
501 let stat = hg::operations::status_rev_rev_no_copies(
507 repo, rev1, rev2, matcher,
502 repo, rev1, rev2, matcher,
508 )?;
503 )?;
509 for entry in stat.iter() {
504 for entry in stat.iter() {
510 let (path, status) = entry?;
505 let (path, status) = entry?;
511 let path = StatusPath {
506 let path = StatusPath {
512 path: Cow::Borrowed(path),
507 path: Cow::Borrowed(path),
513 copy_source: None,
508 copy_source: None,
514 };
509 };
515 match status {
510 match status {
516 hg::operations::DiffStatus::Removed => {
511 hg::operations::DiffStatus::Removed => {
517 if display_states.removed {
512 if display_states.removed {
518 ds_status.removed.push(path)
513 ds_status.removed.push(path)
519 }
514 }
520 }
515 }
521 hg::operations::DiffStatus::Added => {
516 hg::operations::DiffStatus::Added => {
522 if display_states.added {
517 if display_states.added {
523 ds_status.added.push(path)
518 ds_status.added.push(path)
524 }
519 }
525 }
520 }
526 hg::operations::DiffStatus::Modified => {
521 hg::operations::DiffStatus::Modified => {
527 if display_states.modified {
522 if display_states.modified {
528 ds_status.modified.push(path)
523 ds_status.modified.push(path)
529 }
524 }
530 }
525 }
531 hg::operations::DiffStatus::Matching => {
526 hg::operations::DiffStatus::Matching => {
532 if display_states.clean {
527 if display_states.clean {
533 ds_status.clean.push(path)
528 ds_status.clean.push(path)
534 }
529 }
535 }
530 }
536 }
531 }
537 }
532 }
538 output.output(display_states, ds_status)?;
533 output.output(display_states, ds_status)?;
539 return Ok(());
534 return Ok(());
540 }
535 }
541
536
542 let (fixup, mut dirstate_write_needed, filesystem_time_at_status_start) =
537 let (fixup, mut dirstate_write_needed, filesystem_time_at_status_start) =
543 dmap.with_status(
538 dmap.with_status(
544 matcher.as_ref(),
539 matcher.as_ref(),
545 repo.working_directory_path().to_owned(),
540 repo.working_directory_path().to_owned(),
546 ignore_files(repo, config),
541 ignore_files(repo, config),
547 options,
542 options,
548 after_status,
543 after_status,
549 )?;
544 )?;
550
545
551 // Development config option to test write races
546 // Development config option to test write races
552 if let Err(e) =
547 if let Err(e) =
553 debug_wait_for_file(config, "status.pre-dirstate-write-file")
548 debug_wait_for_file(config, "status.pre-dirstate-write-file")
554 {
549 {
555 ui.write_stderr(e.as_bytes()).ok();
550 ui.write_stderr(e.as_bytes()).ok();
556 }
551 }
557
552
558 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
553 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
559 && !dirstate_write_needed
554 && !dirstate_write_needed
560 {
555 {
561 // Nothing to update
556 // Nothing to update
562 return Ok(());
557 return Ok(());
563 }
558 }
564
559
565 // Update the dirstate on disk if we can
560 // Update the dirstate on disk if we can
566 let with_lock_result =
561 let with_lock_result =
567 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
562 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
568 if let Some(mtime_boundary) = filesystem_time_at_status_start {
563 if let Some(mtime_boundary) = filesystem_time_at_status_start {
569 for hg_path in fixup {
564 for hg_path in fixup {
570 use std::os::unix::fs::MetadataExt;
565 use std::os::unix::fs::MetadataExt;
571 let fs_path = hg_path_to_path_buf(&hg_path)
566 let fs_path = hg_path_to_path_buf(&hg_path)
572 .expect("HgPath conversion");
567 .expect("HgPath conversion");
573 // Specifically do not reuse `fs_metadata` from
568 // Specifically do not reuse `fs_metadata` from
574 // `unsure_is_clean` which was needed before reading
569 // `unsure_is_clean` which was needed before reading
575 // contents. Here we access metadata again after reading
570 // contents. Here we access metadata again after reading
576 // content, in case it changed in the meantime.
571 // content, in case it changed in the meantime.
577 let metadata_res = repo
572 let metadata_res = repo
578 .working_directory_vfs()
573 .working_directory_vfs()
579 .symlink_metadata(&fs_path);
574 .symlink_metadata(&fs_path);
580 let fs_metadata = match metadata_res {
575 let fs_metadata = match metadata_res {
581 Ok(meta) => meta,
576 Ok(meta) => meta,
582 Err(err) => match err {
577 Err(err) => match err {
583 HgError::IoError { .. } => {
578 HgError::IoError { .. } => {
584 // The file has probably been deleted. In any
579 // The file has probably been deleted. In any
585 // case, it was in the dirstate before, so
580 // case, it was in the dirstate before, so
586 // let's ignore the error.
581 // let's ignore the error.
587 continue;
582 continue;
588 }
583 }
589 _ => return Err(err.into()),
584 _ => return Err(err.into()),
590 },
585 },
591 };
586 };
592 if let Some(mtime) =
587 if let Some(mtime) =
593 TruncatedTimestamp::for_reliable_mtime_of(
588 TruncatedTimestamp::for_reliable_mtime_of(
594 &fs_metadata,
589 &fs_metadata,
595 &mtime_boundary,
590 &mtime_boundary,
596 )
591 )
597 .when_reading_file(&fs_path)?
592 .when_reading_file(&fs_path)?
598 {
593 {
599 let mode = fs_metadata.mode();
594 let mode = fs_metadata.mode();
600 let size = fs_metadata.len();
595 let size = fs_metadata.len();
601 dmap.set_clean(&hg_path, mode, size as u32, mtime)?;
596 dmap.set_clean(&hg_path, mode, size as u32, mtime)?;
602 dirstate_write_needed = true
597 dirstate_write_needed = true
603 }
598 }
604 }
599 }
605 }
600 }
606 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
601 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
607 if dirstate_write_needed {
602 if dirstate_write_needed {
608 repo.write_dirstate()?
603 repo.write_dirstate()?
609 }
604 }
610 Ok(())
605 Ok(())
611 });
606 });
612 match with_lock_result {
607 match with_lock_result {
613 Ok(closure_result) => closure_result?,
608 Ok(closure_result) => closure_result?,
614 Err(LockError::AlreadyHeld) => {
609 Err(LockError::AlreadyHeld) => {
615 // Not updating the dirstate is not ideal but not critical:
610 // Not updating the dirstate is not ideal but not critical:
616 // don’t keep our caller waiting until some other Mercurial
611 // don’t keep our caller waiting until some other Mercurial
617 // process releases the lock.
612 // process releases the lock.
618 log::info!("not writing dirstate from `status`: lock is held")
613 log::info!("not writing dirstate from `status`: lock is held")
619 }
614 }
620 Err(LockError::Other(HgError::IoError { error, .. }))
615 Err(LockError::Other(HgError::IoError { error, .. }))
621 if error.kind() == io::ErrorKind::PermissionDenied
616 if error.kind() == io::ErrorKind::PermissionDenied
622 || match error.raw_os_error() {
617 || match error.raw_os_error() {
623 None => false,
618 None => false,
624 Some(errno) => libc::EROFS == errno,
619 Some(errno) => libc::EROFS == errno,
625 } =>
620 } =>
626 {
621 {
627 // `hg status` on a read-only repository is fine
622 // `hg status` on a read-only repository is fine
628 }
623 }
629 Err(LockError::Other(error)) => {
624 Err(LockError::Other(error)) => {
630 // Report other I/O errors
625 // Report other I/O errors
631 Err(error)?
626 Err(error)?
632 }
627 }
633 }
628 }
634 Ok(())
629 Ok(())
635 }
630 }
636
631
637 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
632 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
638 let mut ignore_files = Vec::new();
633 let mut ignore_files = Vec::new();
639 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
634 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
640 if repo_ignore.exists() {
635 if repo_ignore.exists() {
641 ignore_files.push(repo_ignore)
636 ignore_files.push(repo_ignore)
642 }
637 }
643 for (key, value) in config.iter_section(b"ui") {
638 for (key, value) in config.iter_section(b"ui") {
644 if key == b"ignore" || key.starts_with(b"ignore.") {
639 if key == b"ignore" || key.starts_with(b"ignore.") {
645 let path = get_path_from_bytes(value);
640 let path = get_path_from_bytes(value);
646 let path = shellexpand::path::full_with_context_no_errors(
641 let path = shellexpand::path::full_with_context_no_errors(
647 path,
642 path,
648 home::home_dir,
643 home::home_dir,
649 |s| std::env::var(s).ok(),
644 |s| std::env::var(s).ok(),
650 );
645 );
651 let joined = repo.working_directory_path().join(path);
646 let joined = repo.working_directory_path().join(path);
652 ignore_files.push(joined);
647 ignore_files.push(joined);
653 }
648 }
654 }
649 }
655 ignore_files
650 ignore_files
656 }
651 }
657
652
658 struct DisplayStatusPaths<'a> {
653 struct DisplayStatusPaths<'a> {
659 ui: &'a Ui,
654 ui: &'a Ui,
660 no_status: bool,
655 no_status: bool,
661 relativize: Option<RelativizePaths>,
656 relativize: Option<RelativizePaths>,
662 print0: bool,
657 print0: bool,
663 }
658 }
664
659
665 impl DisplayStatusPaths<'_> {
660 impl DisplayStatusPaths<'_> {
666 // Probably more elegant to use a Deref or Borrow trait rather than
661 // Probably more elegant to use a Deref or Borrow trait rather than
667 // harcode HgPathBuf, but probably not really useful at this point
662 // harcode HgPathBuf, but probably not really useful at this point
668 fn display(
663 fn display(
669 &self,
664 &self,
670 status_prefix: &[u8],
665 status_prefix: &[u8],
671 label: &'static str,
666 label: &'static str,
672 mut paths: Vec<StatusPath<'_>>,
667 mut paths: Vec<StatusPath<'_>>,
673 ) -> Result<(), CommandError> {
668 ) -> Result<(), CommandError> {
674 paths.sort_unstable();
669 paths.sort_unstable();
675 // TODO: get the stdout lock once for the whole loop
670 // TODO: get the stdout lock once for the whole loop
676 // instead of in each write
671 // instead of in each write
677 for StatusPath { path, copy_source } in paths {
672 for StatusPath { path, copy_source } in paths {
678 let relative_path;
673 let relative_path;
679 let relative_source;
674 let relative_source;
680 let (path, copy_source) = if let Some(relativize) =
675 let (path, copy_source) = if let Some(relativize) =
681 &self.relativize
676 &self.relativize
682 {
677 {
683 relative_path = relativize.relativize(&path);
678 relative_path = relativize.relativize(&path);
684 relative_source =
679 relative_source =
685 copy_source.as_ref().map(|s| relativize.relativize(s));
680 copy_source.as_ref().map(|s| relativize.relativize(s));
686 (&*relative_path, relative_source.as_deref())
681 (&*relative_path, relative_source.as_deref())
687 } else {
682 } else {
688 (path.as_bytes(), copy_source.as_ref().map(|s| s.as_bytes()))
683 (path.as_bytes(), copy_source.as_ref().map(|s| s.as_bytes()))
689 };
684 };
690 // TODO: Add a way to use `write_bytes!` instead of `format_bytes!`
685 // TODO: Add a way to use `write_bytes!` instead of `format_bytes!`
691 // in order to stream to stdout instead of allocating an
686 // in order to stream to stdout instead of allocating an
692 // itermediate `Vec<u8>`.
687 // itermediate `Vec<u8>`.
693 if !self.no_status {
688 if !self.no_status {
694 self.ui.write_stdout_labelled(status_prefix, label)?
689 self.ui.write_stdout_labelled(status_prefix, label)?
695 }
690 }
696 let linebreak = if self.print0 { b"\x00" } else { b"\n" };
691 let linebreak = if self.print0 { b"\x00" } else { b"\n" };
697 self.ui.write_stdout_labelled(
692 self.ui.write_stdout_labelled(
698 &format_bytes!(b"{}{}", path, linebreak),
693 &format_bytes!(b"{}{}", path, linebreak),
699 label,
694 label,
700 )?;
695 )?;
701 if let Some(source) = copy_source {
696 if let Some(source) = copy_source {
702 let label = "status.copied";
697 let label = "status.copied";
703 self.ui.write_stdout_labelled(
698 self.ui.write_stdout_labelled(
704 &format_bytes!(b" {}{}", source, linebreak),
699 &format_bytes!(b" {}{}", source, linebreak),
705 label,
700 label,
706 )?
701 )?
707 }
702 }
708 }
703 }
709 Ok(())
704 Ok(())
710 }
705 }
711
706
712 fn output(
707 fn output(
713 &mut self,
708 &mut self,
714 display_states: DisplayStates,
709 display_states: DisplayStates,
715 ds_status: DirstateStatus,
710 ds_status: DirstateStatus,
716 ) -> Result<(), CommandError> {
711 ) -> Result<(), CommandError> {
717 if display_states.modified {
712 if display_states.modified {
718 self.display(b"M ", "status.modified", ds_status.modified)?;
713 self.display(b"M ", "status.modified", ds_status.modified)?;
719 }
714 }
720 if display_states.added {
715 if display_states.added {
721 self.display(b"A ", "status.added", ds_status.added)?;
716 self.display(b"A ", "status.added", ds_status.added)?;
722 }
717 }
723 if display_states.removed {
718 if display_states.removed {
724 self.display(b"R ", "status.removed", ds_status.removed)?;
719 self.display(b"R ", "status.removed", ds_status.removed)?;
725 }
720 }
726 if display_states.deleted {
721 if display_states.deleted {
727 self.display(b"! ", "status.deleted", ds_status.deleted)?;
722 self.display(b"! ", "status.deleted", ds_status.deleted)?;
728 }
723 }
729 if display_states.unknown {
724 if display_states.unknown {
730 self.display(b"? ", "status.unknown", ds_status.unknown)?;
725 self.display(b"? ", "status.unknown", ds_status.unknown)?;
731 }
726 }
732 if display_states.ignored {
727 if display_states.ignored {
733 self.display(b"I ", "status.ignored", ds_status.ignored)?;
728 self.display(b"I ", "status.ignored", ds_status.ignored)?;
734 }
729 }
735 if display_states.clean {
730 if display_states.clean {
736 self.display(b"C ", "status.clean", ds_status.clean)?;
731 self.display(b"C ", "status.clean", ds_status.clean)?;
737 }
732 }
738 Ok(())
733 Ok(())
739 }
734 }
740 }
735 }
741
736
742 /// Outcome of the additional check for an ambiguous tracked file
737 /// Outcome of the additional check for an ambiguous tracked file
743 enum UnsureOutcome {
738 enum UnsureOutcome {
744 /// The file is actually clean
739 /// The file is actually clean
745 Clean,
740 Clean,
746 /// The file has been modified
741 /// The file has been modified
747 Modified,
742 Modified,
748 /// The file was deleted on disk (or became another type of fs entry)
743 /// The file was deleted on disk (or became another type of fs entry)
749 Deleted,
744 Deleted,
750 }
745 }
751
746
752 /// Check if a file is modified by comparing actual repo store and file system.
747 /// Check if a file is modified by comparing actual repo store and file system.
753 ///
748 ///
754 /// This meant to be used for those that the dirstate cannot resolve, due
749 /// This meant to be used for those that the dirstate cannot resolve, due
755 /// to time resolution limits.
750 /// to time resolution limits.
756 fn unsure_is_modified(
751 fn unsure_is_modified(
757 working_directory_vfs: &hg::vfs::VfsImpl,
752 working_directory_vfs: &hg::vfs::VfsImpl,
758 store_vfs: &hg::vfs::VfsImpl,
753 store_vfs: &hg::vfs::VfsImpl,
759 check_exec: bool,
754 check_exec: bool,
760 manifest: &Manifest,
755 manifest: &Manifest,
761 hg_path: &HgPath,
756 hg_path: &HgPath,
762 revlog_open_options: RevlogOpenOptions,
757 revlog_open_options: RevlogOpenOptions,
763 ) -> Result<UnsureOutcome, HgError> {
758 ) -> Result<UnsureOutcome, HgError> {
764 let vfs = working_directory_vfs;
759 let vfs = working_directory_vfs;
765 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
760 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
766 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
761 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
767 let is_symlink = fs_metadata.file_type().is_symlink();
762 let is_symlink = fs_metadata.file_type().is_symlink();
768
763
769 let entry = manifest
764 let entry = manifest
770 .find_by_path(hg_path)?
765 .find_by_path(hg_path)?
771 .expect("ambgious file not in p1");
766 .expect("ambgious file not in p1");
772
767
773 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
768 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
774 // dirstate
769 // dirstate
775 let fs_flags = if is_symlink {
770 let fs_flags = if is_symlink {
776 Some(b'l')
771 Some(b'l')
777 } else if check_exec && has_exec_bit(&fs_metadata) {
772 } else if check_exec && has_exec_bit(&fs_metadata) {
778 Some(b'x')
773 Some(b'x')
779 } else {
774 } else {
780 None
775 None
781 };
776 };
782
777
783 let entry_flags = if check_exec {
778 let entry_flags = if check_exec {
784 entry.flags
779 entry.flags
785 } else if entry.flags.map(|f| f.into()) == Some(b'x') {
780 } else if entry.flags.map(|f| f.into()) == Some(b'x') {
786 None
781 None
787 } else {
782 } else {
788 entry.flags
783 entry.flags
789 };
784 };
790
785
791 if entry_flags.map(|f| f.into()) != fs_flags {
786 if entry_flags.map(|f| f.into()) != fs_flags {
792 return Ok(UnsureOutcome::Modified);
787 return Ok(UnsureOutcome::Modified);
793 }
788 }
794 let filelog = hg::revlog::filelog::Filelog::open_vfs(
789 let filelog = hg::revlog::filelog::Filelog::open_vfs(
795 store_vfs,
790 store_vfs,
796 hg_path,
791 hg_path,
797 revlog_open_options,
792 revlog_open_options,
798 )?;
793 )?;
799 let fs_len = fs_metadata.len();
794 let fs_len = fs_metadata.len();
800 let file_node = entry.node_id()?;
795 let file_node = entry.node_id()?;
801 let filelog_entry = filelog.entry_for_node(file_node).map_err(|_| {
796 let filelog_entry = filelog.entry_for_node(file_node).map_err(|_| {
802 HgError::corrupted(format!(
797 HgError::corrupted(format!(
803 "filelog {:?} missing node {:?} from manifest",
798 "filelog {:?} missing node {:?} from manifest",
804 hg_path, file_node
799 hg_path, file_node
805 ))
800 ))
806 })?;
801 })?;
807 if filelog_entry.file_data_len_not_equal_to(fs_len) {
802 if filelog_entry.file_data_len_not_equal_to(fs_len) {
808 // No need to read file contents:
803 // No need to read file contents:
809 // it cannot be equal if it has a different length.
804 // it cannot be equal if it has a different length.
810 return Ok(UnsureOutcome::Modified);
805 return Ok(UnsureOutcome::Modified);
811 }
806 }
812
807
813 let p1_filelog_data = filelog_entry.data()?;
808 let p1_filelog_data = filelog_entry.data()?;
814 let p1_contents = p1_filelog_data.file_data()?;
809 let p1_contents = p1_filelog_data.file_data()?;
815 if p1_contents.len() as u64 != fs_len {
810 if p1_contents.len() as u64 != fs_len {
816 // No need to read file contents:
811 // No need to read file contents:
817 // it cannot be equal if it has a different length.
812 // it cannot be equal if it has a different length.
818 return Ok(UnsureOutcome::Modified);
813 return Ok(UnsureOutcome::Modified);
819 }
814 }
820
815
821 let fs_contents = if is_symlink {
816 let fs_contents = if is_symlink {
822 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
817 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
823 } else {
818 } else {
824 vfs.read(fs_path)?
819 vfs.read(fs_path)?
825 };
820 };
826
821
827 Ok(if p1_contents != &*fs_contents {
822 Ok(if p1_contents != &*fs_contents {
828 UnsureOutcome::Modified
823 UnsureOutcome::Modified
829 } else {
824 } else {
830 UnsureOutcome::Clean
825 UnsureOutcome::Clean
831 })
826 })
832 }
827 }
General Comments 0
You need to be logged in to leave comments. Login now