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