##// END OF EJS Templates
rhg-status: extract a function for printing pattern file warnings...
Raphaël Gomès -
r50378:b18e877e default
parent child Browse files
Show More
@@ -1,584 +1,590 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::Ui;
9 use crate::ui::Ui;
10 use crate::utils::path_utils::RelativizePaths;
10 use crate::utils::path_utils::RelativizePaths;
11 use clap::{Arg, SubCommand};
11 use clap::{Arg, SubCommand};
12 use format_bytes::format_bytes;
12 use format_bytes::format_bytes;
13 use hg;
13 use hg;
14 use hg::config::Config;
14 use hg::config::Config;
15 use hg::dirstate::has_exec_bit;
15 use hg::dirstate::has_exec_bit;
16 use hg::dirstate::status::StatusPath;
16 use hg::dirstate::status::StatusPath;
17 use hg::dirstate::TruncatedTimestamp;
17 use hg::dirstate::TruncatedTimestamp;
18 use hg::errors::{HgError, IoResultExt};
18 use hg::errors::{HgError, IoResultExt};
19 use hg::lock::LockError;
19 use hg::lock::LockError;
20 use hg::manifest::Manifest;
20 use hg::manifest::Manifest;
21 use hg::matchers::AlwaysMatcher;
21 use hg::matchers::AlwaysMatcher;
22 use hg::repo::Repo;
22 use hg::repo::Repo;
23 use hg::utils::files::get_bytes_from_os_string;
23 use hg::utils::files::get_bytes_from_os_string;
24 use hg::utils::files::get_bytes_from_path;
24 use hg::utils::files::get_bytes_from_path;
25 use hg::utils::files::get_path_from_bytes;
25 use hg::utils::files::get_path_from_bytes;
26 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
26 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
27 use hg::DirstateStatus;
27 use hg::DirstateStatus;
28 use hg::PatternFileWarning;
28 use hg::PatternFileWarning;
29 use hg::StatusError;
29 use hg::StatusError;
30 use hg::StatusOptions;
30 use hg::StatusOptions;
31 use log::info;
31 use log::info;
32 use std::io;
32 use std::io;
33 use std::path::PathBuf;
33 use std::path::PathBuf;
34
34
35 pub const HELP_TEXT: &str = "
35 pub const HELP_TEXT: &str = "
36 Show changed files in the working directory
36 Show changed files in the working directory
37
37
38 This is a pure Rust version of `hg status`.
38 This is a pure Rust version of `hg status`.
39
39
40 Some options might be missing, check the list below.
40 Some options might be missing, check the list below.
41 ";
41 ";
42
42
43 pub fn args() -> clap::App<'static, 'static> {
43 pub fn args() -> clap::App<'static, 'static> {
44 SubCommand::with_name("status")
44 SubCommand::with_name("status")
45 .alias("st")
45 .alias("st")
46 .about(HELP_TEXT)
46 .about(HELP_TEXT)
47 .arg(
47 .arg(
48 Arg::with_name("all")
48 Arg::with_name("all")
49 .help("show status of all files")
49 .help("show status of all files")
50 .short("-A")
50 .short("-A")
51 .long("--all"),
51 .long("--all"),
52 )
52 )
53 .arg(
53 .arg(
54 Arg::with_name("modified")
54 Arg::with_name("modified")
55 .help("show only modified files")
55 .help("show only modified files")
56 .short("-m")
56 .short("-m")
57 .long("--modified"),
57 .long("--modified"),
58 )
58 )
59 .arg(
59 .arg(
60 Arg::with_name("added")
60 Arg::with_name("added")
61 .help("show only added files")
61 .help("show only added files")
62 .short("-a")
62 .short("-a")
63 .long("--added"),
63 .long("--added"),
64 )
64 )
65 .arg(
65 .arg(
66 Arg::with_name("removed")
66 Arg::with_name("removed")
67 .help("show only removed files")
67 .help("show only removed files")
68 .short("-r")
68 .short("-r")
69 .long("--removed"),
69 .long("--removed"),
70 )
70 )
71 .arg(
71 .arg(
72 Arg::with_name("clean")
72 Arg::with_name("clean")
73 .help("show only clean files")
73 .help("show only clean files")
74 .short("-c")
74 .short("-c")
75 .long("--clean"),
75 .long("--clean"),
76 )
76 )
77 .arg(
77 .arg(
78 Arg::with_name("deleted")
78 Arg::with_name("deleted")
79 .help("show only deleted files")
79 .help("show only deleted files")
80 .short("-d")
80 .short("-d")
81 .long("--deleted"),
81 .long("--deleted"),
82 )
82 )
83 .arg(
83 .arg(
84 Arg::with_name("unknown")
84 Arg::with_name("unknown")
85 .help("show only unknown (not tracked) files")
85 .help("show only unknown (not tracked) files")
86 .short("-u")
86 .short("-u")
87 .long("--unknown"),
87 .long("--unknown"),
88 )
88 )
89 .arg(
89 .arg(
90 Arg::with_name("ignored")
90 Arg::with_name("ignored")
91 .help("show only ignored files")
91 .help("show only ignored files")
92 .short("-i")
92 .short("-i")
93 .long("--ignored"),
93 .long("--ignored"),
94 )
94 )
95 .arg(
95 .arg(
96 Arg::with_name("copies")
96 Arg::with_name("copies")
97 .help("show source of copied files (DEFAULT: ui.statuscopies)")
97 .help("show source of copied files (DEFAULT: ui.statuscopies)")
98 .short("-C")
98 .short("-C")
99 .long("--copies"),
99 .long("--copies"),
100 )
100 )
101 .arg(
101 .arg(
102 Arg::with_name("no-status")
102 Arg::with_name("no-status")
103 .help("hide status prefix")
103 .help("hide status prefix")
104 .short("-n")
104 .short("-n")
105 .long("--no-status"),
105 .long("--no-status"),
106 )
106 )
107 .arg(
107 .arg(
108 Arg::with_name("verbose")
108 Arg::with_name("verbose")
109 .help("enable additional output")
109 .help("enable additional output")
110 .short("-v")
110 .short("-v")
111 .long("--verbose"),
111 .long("--verbose"),
112 )
112 )
113 }
113 }
114
114
115 /// Pure data type allowing the caller to specify file states to display
115 /// Pure data type allowing the caller to specify file states to display
116 #[derive(Copy, Clone, Debug)]
116 #[derive(Copy, Clone, Debug)]
117 pub struct DisplayStates {
117 pub struct DisplayStates {
118 pub modified: bool,
118 pub modified: bool,
119 pub added: bool,
119 pub added: bool,
120 pub removed: bool,
120 pub removed: bool,
121 pub clean: bool,
121 pub clean: bool,
122 pub deleted: bool,
122 pub deleted: bool,
123 pub unknown: bool,
123 pub unknown: bool,
124 pub ignored: bool,
124 pub ignored: bool,
125 }
125 }
126
126
127 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
127 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
128 modified: true,
128 modified: true,
129 added: true,
129 added: true,
130 removed: true,
130 removed: true,
131 clean: false,
131 clean: false,
132 deleted: true,
132 deleted: true,
133 unknown: true,
133 unknown: true,
134 ignored: false,
134 ignored: false,
135 };
135 };
136
136
137 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
137 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
138 modified: true,
138 modified: true,
139 added: true,
139 added: true,
140 removed: true,
140 removed: true,
141 clean: true,
141 clean: true,
142 deleted: true,
142 deleted: true,
143 unknown: true,
143 unknown: true,
144 ignored: true,
144 ignored: true,
145 };
145 };
146
146
147 impl DisplayStates {
147 impl DisplayStates {
148 pub fn is_empty(&self) -> bool {
148 pub fn is_empty(&self) -> bool {
149 !(self.modified
149 !(self.modified
150 || self.added
150 || self.added
151 || self.removed
151 || self.removed
152 || self.clean
152 || self.clean
153 || self.deleted
153 || self.deleted
154 || self.unknown
154 || self.unknown
155 || self.ignored)
155 || self.ignored)
156 }
156 }
157 }
157 }
158
158
159 fn has_unfinished_merge(repo: &Repo) -> Result<bool, CommandError> {
159 fn has_unfinished_merge(repo: &Repo) -> Result<bool, CommandError> {
160 return Ok(repo.dirstate_parents()?.is_merge());
160 return Ok(repo.dirstate_parents()?.is_merge());
161 }
161 }
162
162
163 fn has_unfinished_state(repo: &Repo) -> Result<bool, CommandError> {
163 fn has_unfinished_state(repo: &Repo) -> Result<bool, CommandError> {
164 // These are all the known values for the [fname] argument of
164 // These are all the known values for the [fname] argument of
165 // [addunfinished] function in [state.py]
165 // [addunfinished] function in [state.py]
166 let known_state_files: &[&str] = &[
166 let known_state_files: &[&str] = &[
167 "bisect.state",
167 "bisect.state",
168 "graftstate",
168 "graftstate",
169 "histedit-state",
169 "histedit-state",
170 "rebasestate",
170 "rebasestate",
171 "shelvedstate",
171 "shelvedstate",
172 "transplant/journal",
172 "transplant/journal",
173 "updatestate",
173 "updatestate",
174 ];
174 ];
175 if has_unfinished_merge(repo)? {
175 if has_unfinished_merge(repo)? {
176 return Ok(true);
176 return Ok(true);
177 };
177 };
178 for f in known_state_files {
178 for f in known_state_files {
179 if repo.hg_vfs().join(f).exists() {
179 if repo.hg_vfs().join(f).exists() {
180 return Ok(true);
180 return Ok(true);
181 }
181 }
182 }
182 }
183 return Ok(false);
183 return Ok(false);
184 }
184 }
185
185
186 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
186 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
187 // TODO: lift these limitations
187 // TODO: lift these limitations
188 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
188 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
189 return Err(CommandError::unsupported(
189 return Err(CommandError::unsupported(
190 "ui.tweakdefaults is not yet supported with rhg status",
190 "ui.tweakdefaults is not yet supported with rhg status",
191 ));
191 ));
192 }
192 }
193 if invocation.config.get_bool(b"ui", b"statuscopies")? {
193 if invocation.config.get_bool(b"ui", b"statuscopies")? {
194 return Err(CommandError::unsupported(
194 return Err(CommandError::unsupported(
195 "ui.statuscopies is not yet supported with rhg status",
195 "ui.statuscopies is not yet supported with rhg status",
196 ));
196 ));
197 }
197 }
198 if invocation
198 if invocation
199 .config
199 .config
200 .get(b"commands", b"status.terse")
200 .get(b"commands", b"status.terse")
201 .is_some()
201 .is_some()
202 {
202 {
203 return Err(CommandError::unsupported(
203 return Err(CommandError::unsupported(
204 "status.terse is not yet supported with rhg status",
204 "status.terse is not yet supported with rhg status",
205 ));
205 ));
206 }
206 }
207
207
208 let ui = invocation.ui;
208 let ui = invocation.ui;
209 let config = invocation.config;
209 let config = invocation.config;
210 let args = invocation.subcommand_args;
210 let args = invocation.subcommand_args;
211
211
212 let verbose = !ui.plain(None)
212 let verbose = !ui.plain(None)
213 && !args.is_present("print0")
213 && !args.is_present("print0")
214 && (args.is_present("verbose")
214 && (args.is_present("verbose")
215 || config.get_bool(b"ui", b"verbose")?
215 || config.get_bool(b"ui", b"verbose")?
216 || config.get_bool(b"commands", b"status.verbose")?);
216 || config.get_bool(b"commands", b"status.verbose")?);
217
217
218 let all = args.is_present("all");
218 let all = args.is_present("all");
219 let display_states = if all {
219 let display_states = if all {
220 // TODO when implementing `--quiet`: it excludes clean files
220 // TODO when implementing `--quiet`: it excludes clean files
221 // from `--all`
221 // from `--all`
222 ALL_DISPLAY_STATES
222 ALL_DISPLAY_STATES
223 } else {
223 } else {
224 let requested = DisplayStates {
224 let requested = DisplayStates {
225 modified: args.is_present("modified"),
225 modified: args.is_present("modified"),
226 added: args.is_present("added"),
226 added: args.is_present("added"),
227 removed: args.is_present("removed"),
227 removed: args.is_present("removed"),
228 clean: args.is_present("clean"),
228 clean: args.is_present("clean"),
229 deleted: args.is_present("deleted"),
229 deleted: args.is_present("deleted"),
230 unknown: args.is_present("unknown"),
230 unknown: args.is_present("unknown"),
231 ignored: args.is_present("ignored"),
231 ignored: args.is_present("ignored"),
232 };
232 };
233 if requested.is_empty() {
233 if requested.is_empty() {
234 DEFAULT_DISPLAY_STATES
234 DEFAULT_DISPLAY_STATES
235 } else {
235 } else {
236 requested
236 requested
237 }
237 }
238 };
238 };
239 let no_status = args.is_present("no-status");
239 let no_status = args.is_present("no-status");
240 let list_copies = all
240 let list_copies = all
241 || args.is_present("copies")
241 || args.is_present("copies")
242 || config.get_bool(b"ui", b"statuscopies")?;
242 || config.get_bool(b"ui", b"statuscopies")?;
243
243
244 let repo = invocation.repo?;
244 let repo = invocation.repo?;
245
245
246 if verbose {
246 if verbose {
247 if has_unfinished_state(repo)? {
247 if has_unfinished_state(repo)? {
248 return Err(CommandError::unsupported(
248 return Err(CommandError::unsupported(
249 "verbose status output is not supported by rhg (and is needed because we're in an unfinished operation)",
249 "verbose status output is not supported by rhg (and is needed because we're in an unfinished operation)",
250 ));
250 ));
251 };
251 };
252 }
252 }
253
253
254 if repo.has_sparse() || repo.has_narrow() {
254 if repo.has_sparse() || repo.has_narrow() {
255 return Err(CommandError::unsupported(
255 return Err(CommandError::unsupported(
256 "rhg status is not supported for sparse checkouts or narrow clones yet"
256 "rhg status is not supported for sparse checkouts or narrow clones yet"
257 ));
257 ));
258 }
258 }
259
259
260 let mut dmap = repo.dirstate_map_mut()?;
260 let mut dmap = repo.dirstate_map_mut()?;
261
261
262 let options = StatusOptions {
262 let options = StatusOptions {
263 // we're currently supporting file systems with exec flags only
263 // we're currently supporting file systems with exec flags only
264 // anyway
264 // anyway
265 check_exec: true,
265 check_exec: true,
266 list_clean: display_states.clean,
266 list_clean: display_states.clean,
267 list_unknown: display_states.unknown,
267 list_unknown: display_states.unknown,
268 list_ignored: display_states.ignored,
268 list_ignored: display_states.ignored,
269 list_copies,
269 list_copies,
270 collect_traversed_dirs: false,
270 collect_traversed_dirs: false,
271 };
271 };
272
272
273 type StatusResult<'a> =
273 type StatusResult<'a> =
274 Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
274 Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
275
275
276 let after_status = |res: StatusResult| -> Result<_, CommandError> {
276 let after_status = |res: StatusResult| -> Result<_, CommandError> {
277 let (mut ds_status, pattern_warnings) = res?;
277 let (mut ds_status, pattern_warnings) = res?;
278 for warning in pattern_warnings {
278 for warning in pattern_warnings {
279 match warning {
279 ui.write_stderr(&print_pattern_file_warning(&warning, &repo))?;
280 hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui
281 .write_stderr(&format_bytes!(
282 b"{}: ignoring invalid syntax '{}'\n",
283 get_bytes_from_path(path),
284 &*syntax
285 ))?,
286 hg::PatternFileWarning::NoSuchFile(path) => {
287 let path = if let Ok(relative) =
288 path.strip_prefix(repo.working_directory_path())
289 {
290 relative
291 } else {
292 &*path
293 };
294 ui.write_stderr(&format_bytes!(
295 b"skipping unreadable pattern file '{}': \
296 No such file or directory\n",
297 get_bytes_from_path(path),
298 ))?
299 }
300 }
301 }
280 }
302
281
303 for (path, error) in ds_status.bad {
282 for (path, error) in ds_status.bad {
304 let error = match error {
283 let error = match error {
305 hg::BadMatch::OsError(code) => {
284 hg::BadMatch::OsError(code) => {
306 std::io::Error::from_raw_os_error(code).to_string()
285 std::io::Error::from_raw_os_error(code).to_string()
307 }
286 }
308 hg::BadMatch::BadType(ty) => {
287 hg::BadMatch::BadType(ty) => {
309 format!("unsupported file type (type is {})", ty)
288 format!("unsupported file type (type is {})", ty)
310 }
289 }
311 };
290 };
312 ui.write_stderr(&format_bytes!(
291 ui.write_stderr(&format_bytes!(
313 b"{}: {}\n",
292 b"{}: {}\n",
314 path.as_bytes(),
293 path.as_bytes(),
315 error.as_bytes()
294 error.as_bytes()
316 ))?
295 ))?
317 }
296 }
318 if !ds_status.unsure.is_empty() {
297 if !ds_status.unsure.is_empty() {
319 info!(
298 info!(
320 "Files to be rechecked by retrieval from filelog: {:?}",
299 "Files to be rechecked by retrieval from filelog: {:?}",
321 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
300 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
322 );
301 );
323 }
302 }
324 let mut fixup = Vec::new();
303 let mut fixup = Vec::new();
325 if !ds_status.unsure.is_empty()
304 if !ds_status.unsure.is_empty()
326 && (display_states.modified || display_states.clean)
305 && (display_states.modified || display_states.clean)
327 {
306 {
328 let p1 = repo.dirstate_parents()?.p1;
307 let p1 = repo.dirstate_parents()?.p1;
329 let manifest = repo.manifest_for_node(p1).map_err(|e| {
308 let manifest = repo.manifest_for_node(p1).map_err(|e| {
330 CommandError::from((e, &*format!("{:x}", p1.short())))
309 CommandError::from((e, &*format!("{:x}", p1.short())))
331 })?;
310 })?;
332 for to_check in ds_status.unsure {
311 for to_check in ds_status.unsure {
333 if unsure_is_modified(repo, &manifest, &to_check.path)? {
312 if unsure_is_modified(repo, &manifest, &to_check.path)? {
334 if display_states.modified {
313 if display_states.modified {
335 ds_status.modified.push(to_check);
314 ds_status.modified.push(to_check);
336 }
315 }
337 } else {
316 } else {
338 if display_states.clean {
317 if display_states.clean {
339 ds_status.clean.push(to_check.clone());
318 ds_status.clean.push(to_check.clone());
340 }
319 }
341 fixup.push(to_check.path.into_owned())
320 fixup.push(to_check.path.into_owned())
342 }
321 }
343 }
322 }
344 }
323 }
345 let relative_paths = (!ui.plain(None))
324 let relative_paths = (!ui.plain(None))
346 && config
325 && config
347 .get_option(b"commands", b"status.relative")?
326 .get_option(b"commands", b"status.relative")?
348 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
327 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
349 let output = DisplayStatusPaths {
328 let output = DisplayStatusPaths {
350 ui,
329 ui,
351 no_status,
330 no_status,
352 relativize: if relative_paths {
331 relativize: if relative_paths {
353 Some(RelativizePaths::new(repo)?)
332 Some(RelativizePaths::new(repo)?)
354 } else {
333 } else {
355 None
334 None
356 },
335 },
357 };
336 };
358 if display_states.modified {
337 if display_states.modified {
359 output.display(b"M ", "status.modified", ds_status.modified)?;
338 output.display(b"M ", "status.modified", ds_status.modified)?;
360 }
339 }
361 if display_states.added {
340 if display_states.added {
362 output.display(b"A ", "status.added", ds_status.added)?;
341 output.display(b"A ", "status.added", ds_status.added)?;
363 }
342 }
364 if display_states.removed {
343 if display_states.removed {
365 output.display(b"R ", "status.removed", ds_status.removed)?;
344 output.display(b"R ", "status.removed", ds_status.removed)?;
366 }
345 }
367 if display_states.deleted {
346 if display_states.deleted {
368 output.display(b"! ", "status.deleted", ds_status.deleted)?;
347 output.display(b"! ", "status.deleted", ds_status.deleted)?;
369 }
348 }
370 if display_states.unknown {
349 if display_states.unknown {
371 output.display(b"? ", "status.unknown", ds_status.unknown)?;
350 output.display(b"? ", "status.unknown", ds_status.unknown)?;
372 }
351 }
373 if display_states.ignored {
352 if display_states.ignored {
374 output.display(b"I ", "status.ignored", ds_status.ignored)?;
353 output.display(b"I ", "status.ignored", ds_status.ignored)?;
375 }
354 }
376 if display_states.clean {
355 if display_states.clean {
377 output.display(b"C ", "status.clean", ds_status.clean)?;
356 output.display(b"C ", "status.clean", ds_status.clean)?;
378 }
357 }
379
358
380 let dirstate_write_needed = ds_status.dirty;
359 let dirstate_write_needed = ds_status.dirty;
381 let filesystem_time_at_status_start =
360 let filesystem_time_at_status_start =
382 ds_status.filesystem_time_at_status_start;
361 ds_status.filesystem_time_at_status_start;
383
362
384 Ok((
363 Ok((
385 fixup,
364 fixup,
386 dirstate_write_needed,
365 dirstate_write_needed,
387 filesystem_time_at_status_start,
366 filesystem_time_at_status_start,
388 ))
367 ))
389 };
368 };
390 let (fixup, mut dirstate_write_needed, filesystem_time_at_status_start) =
369 let (fixup, mut dirstate_write_needed, filesystem_time_at_status_start) =
391 dmap.with_status(
370 dmap.with_status(
392 &AlwaysMatcher,
371 &AlwaysMatcher,
393 repo.working_directory_path().to_owned(),
372 repo.working_directory_path().to_owned(),
394 ignore_files(repo, config),
373 ignore_files(repo, config),
395 options,
374 options,
396 after_status,
375 after_status,
397 )?;
376 )?;
398
377
399 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
378 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
400 && !dirstate_write_needed
379 && !dirstate_write_needed
401 {
380 {
402 // Nothing to update
381 // Nothing to update
403 return Ok(());
382 return Ok(());
404 }
383 }
405
384
406 // Update the dirstate on disk if we can
385 // Update the dirstate on disk if we can
407 let with_lock_result =
386 let with_lock_result =
408 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
387 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
409 if let Some(mtime_boundary) = filesystem_time_at_status_start {
388 if let Some(mtime_boundary) = filesystem_time_at_status_start {
410 for hg_path in fixup {
389 for hg_path in fixup {
411 use std::os::unix::fs::MetadataExt;
390 use std::os::unix::fs::MetadataExt;
412 let fs_path = hg_path_to_path_buf(&hg_path)
391 let fs_path = hg_path_to_path_buf(&hg_path)
413 .expect("HgPath conversion");
392 .expect("HgPath conversion");
414 // Specifically do not reuse `fs_metadata` from
393 // Specifically do not reuse `fs_metadata` from
415 // `unsure_is_clean` which was needed before reading
394 // `unsure_is_clean` which was needed before reading
416 // contents. Here we access metadata again after reading
395 // contents. Here we access metadata again after reading
417 // content, in case it changed in the meantime.
396 // content, in case it changed in the meantime.
418 let fs_metadata = repo
397 let fs_metadata = repo
419 .working_directory_vfs()
398 .working_directory_vfs()
420 .symlink_metadata(&fs_path)?;
399 .symlink_metadata(&fs_path)?;
421 if let Some(mtime) =
400 if let Some(mtime) =
422 TruncatedTimestamp::for_reliable_mtime_of(
401 TruncatedTimestamp::for_reliable_mtime_of(
423 &fs_metadata,
402 &fs_metadata,
424 &mtime_boundary,
403 &mtime_boundary,
425 )
404 )
426 .when_reading_file(&fs_path)?
405 .when_reading_file(&fs_path)?
427 {
406 {
428 let mode = fs_metadata.mode();
407 let mode = fs_metadata.mode();
429 let size = fs_metadata.len();
408 let size = fs_metadata.len();
430 dmap.set_clean(&hg_path, mode, size as u32, mtime)?;
409 dmap.set_clean(&hg_path, mode, size as u32, mtime)?;
431 dirstate_write_needed = true
410 dirstate_write_needed = true
432 }
411 }
433 }
412 }
434 }
413 }
435 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
414 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
436 if dirstate_write_needed {
415 if dirstate_write_needed {
437 repo.write_dirstate()?
416 repo.write_dirstate()?
438 }
417 }
439 Ok(())
418 Ok(())
440 });
419 });
441 match with_lock_result {
420 match with_lock_result {
442 Ok(closure_result) => closure_result?,
421 Ok(closure_result) => closure_result?,
443 Err(LockError::AlreadyHeld) => {
422 Err(LockError::AlreadyHeld) => {
444 // Not updating the dirstate is not ideal but not critical:
423 // Not updating the dirstate is not ideal but not critical:
445 // don’t keep our caller waiting until some other Mercurial
424 // don’t keep our caller waiting until some other Mercurial
446 // process releases the lock.
425 // process releases the lock.
447 }
426 }
448 Err(LockError::Other(HgError::IoError { error, .. }))
427 Err(LockError::Other(HgError::IoError { error, .. }))
449 if error.kind() == io::ErrorKind::PermissionDenied =>
428 if error.kind() == io::ErrorKind::PermissionDenied =>
450 {
429 {
451 // `hg status` on a read-only repository is fine
430 // `hg status` on a read-only repository is fine
452 }
431 }
453 Err(LockError::Other(error)) => {
432 Err(LockError::Other(error)) => {
454 // Report other I/O errors
433 // Report other I/O errors
455 Err(error)?
434 Err(error)?
456 }
435 }
457 }
436 }
458 Ok(())
437 Ok(())
459 }
438 }
460
439
461 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
440 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
462 let mut ignore_files = Vec::new();
441 let mut ignore_files = Vec::new();
463 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
442 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
464 if repo_ignore.exists() {
443 if repo_ignore.exists() {
465 ignore_files.push(repo_ignore)
444 ignore_files.push(repo_ignore)
466 }
445 }
467 for (key, value) in config.iter_section(b"ui") {
446 for (key, value) in config.iter_section(b"ui") {
468 if key == b"ignore" || key.starts_with(b"ignore.") {
447 if key == b"ignore" || key.starts_with(b"ignore.") {
469 let path = get_path_from_bytes(value);
448 let path = get_path_from_bytes(value);
470 // TODO: expand "~/" and environment variable here, like Python
449 // TODO: expand "~/" and environment variable here, like Python
471 // does with `os.path.expanduser` and `os.path.expandvars`
450 // does with `os.path.expanduser` and `os.path.expandvars`
472
451
473 let joined = repo.working_directory_path().join(path);
452 let joined = repo.working_directory_path().join(path);
474 ignore_files.push(joined);
453 ignore_files.push(joined);
475 }
454 }
476 }
455 }
477 ignore_files
456 ignore_files
478 }
457 }
479
458
480 struct DisplayStatusPaths<'a> {
459 struct DisplayStatusPaths<'a> {
481 ui: &'a Ui,
460 ui: &'a Ui,
482 no_status: bool,
461 no_status: bool,
483 relativize: Option<RelativizePaths>,
462 relativize: Option<RelativizePaths>,
484 }
463 }
485
464
486 impl DisplayStatusPaths<'_> {
465 impl DisplayStatusPaths<'_> {
487 // Probably more elegant to use a Deref or Borrow trait rather than
466 // Probably more elegant to use a Deref or Borrow trait rather than
488 // harcode HgPathBuf, but probably not really useful at this point
467 // harcode HgPathBuf, but probably not really useful at this point
489 fn display(
468 fn display(
490 &self,
469 &self,
491 status_prefix: &[u8],
470 status_prefix: &[u8],
492 label: &'static str,
471 label: &'static str,
493 mut paths: Vec<StatusPath<'_>>,
472 mut paths: Vec<StatusPath<'_>>,
494 ) -> Result<(), CommandError> {
473 ) -> Result<(), CommandError> {
495 paths.sort_unstable();
474 paths.sort_unstable();
496 // TODO: get the stdout lock once for the whole loop
475 // TODO: get the stdout lock once for the whole loop
497 // instead of in each write
476 // instead of in each write
498 for StatusPath { path, copy_source } in paths {
477 for StatusPath { path, copy_source } in paths {
499 let relative;
478 let relative;
500 let path = if let Some(relativize) = &self.relativize {
479 let path = if let Some(relativize) = &self.relativize {
501 relative = relativize.relativize(&path);
480 relative = relativize.relativize(&path);
502 &*relative
481 &*relative
503 } else {
482 } else {
504 path.as_bytes()
483 path.as_bytes()
505 };
484 };
506 // TODO: Add a way to use `write_bytes!` instead of `format_bytes!`
485 // TODO: Add a way to use `write_bytes!` instead of `format_bytes!`
507 // in order to stream to stdout instead of allocating an
486 // in order to stream to stdout instead of allocating an
508 // itermediate `Vec<u8>`.
487 // itermediate `Vec<u8>`.
509 if !self.no_status {
488 if !self.no_status {
510 self.ui.write_stdout_labelled(status_prefix, label)?
489 self.ui.write_stdout_labelled(status_prefix, label)?
511 }
490 }
512 self.ui
491 self.ui
513 .write_stdout_labelled(&format_bytes!(b"{}\n", path), label)?;
492 .write_stdout_labelled(&format_bytes!(b"{}\n", path), label)?;
514 if let Some(source) = copy_source {
493 if let Some(source) = copy_source {
515 let label = "status.copied";
494 let label = "status.copied";
516 self.ui.write_stdout_labelled(
495 self.ui.write_stdout_labelled(
517 &format_bytes!(b" {}\n", source.as_bytes()),
496 &format_bytes!(b" {}\n", source.as_bytes()),
518 label,
497 label,
519 )?
498 )?
520 }
499 }
521 }
500 }
522 Ok(())
501 Ok(())
523 }
502 }
524 }
503 }
525
504
526 /// Check if a file is modified by comparing actual repo store and file system.
505 /// Check if a file is modified by comparing actual repo store and file system.
527 ///
506 ///
528 /// This meant to be used for those that the dirstate cannot resolve, due
507 /// This meant to be used for those that the dirstate cannot resolve, due
529 /// to time resolution limits.
508 /// to time resolution limits.
530 fn unsure_is_modified(
509 fn unsure_is_modified(
531 repo: &Repo,
510 repo: &Repo,
532 manifest: &Manifest,
511 manifest: &Manifest,
533 hg_path: &HgPath,
512 hg_path: &HgPath,
534 ) -> Result<bool, HgError> {
513 ) -> Result<bool, HgError> {
535 let vfs = repo.working_directory_vfs();
514 let vfs = repo.working_directory_vfs();
536 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
515 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
537 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
516 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
538 let is_symlink = fs_metadata.file_type().is_symlink();
517 let is_symlink = fs_metadata.file_type().is_symlink();
539 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
518 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
540 // dirstate
519 // dirstate
541 let fs_flags = if is_symlink {
520 let fs_flags = if is_symlink {
542 Some(b'l')
521 Some(b'l')
543 } else if has_exec_bit(&fs_metadata) {
522 } else if has_exec_bit(&fs_metadata) {
544 Some(b'x')
523 Some(b'x')
545 } else {
524 } else {
546 None
525 None
547 };
526 };
548
527
549 let entry = manifest
528 let entry = manifest
550 .find_by_path(hg_path)?
529 .find_by_path(hg_path)?
551 .expect("ambgious file not in p1");
530 .expect("ambgious file not in p1");
552 if entry.flags != fs_flags {
531 if entry.flags != fs_flags {
553 return Ok(true);
532 return Ok(true);
554 }
533 }
555 let filelog = repo.filelog(hg_path)?;
534 let filelog = repo.filelog(hg_path)?;
556 let fs_len = fs_metadata.len();
535 let fs_len = fs_metadata.len();
557 let file_node = entry.node_id()?;
536 let file_node = entry.node_id()?;
558 let filelog_entry = filelog.entry_for_node(file_node).map_err(|_| {
537 let filelog_entry = filelog.entry_for_node(file_node).map_err(|_| {
559 HgError::corrupted(format!(
538 HgError::corrupted(format!(
560 "filelog missing node {:?} from manifest",
539 "filelog missing node {:?} from manifest",
561 file_node
540 file_node
562 ))
541 ))
563 })?;
542 })?;
564 if filelog_entry.file_data_len_not_equal_to(fs_len) {
543 if filelog_entry.file_data_len_not_equal_to(fs_len) {
565 // No need to read file contents:
544 // No need to read file contents:
566 // it cannot be equal if it has a different length.
545 // it cannot be equal if it has a different length.
567 return Ok(true);
546 return Ok(true);
568 }
547 }
569
548
570 let p1_filelog_data = filelog_entry.data()?;
549 let p1_filelog_data = filelog_entry.data()?;
571 let p1_contents = p1_filelog_data.file_data()?;
550 let p1_contents = p1_filelog_data.file_data()?;
572 if p1_contents.len() as u64 != fs_len {
551 if p1_contents.len() as u64 != fs_len {
573 // No need to read file contents:
552 // No need to read file contents:
574 // it cannot be equal if it has a different length.
553 // it cannot be equal if it has a different length.
575 return Ok(true);
554 return Ok(true);
576 }
555 }
577
556
578 let fs_contents = if is_symlink {
557 let fs_contents = if is_symlink {
579 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
558 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
580 } else {
559 } else {
581 vfs.read(fs_path)?
560 vfs.read(fs_path)?
582 };
561 };
583 Ok(p1_contents != &*fs_contents)
562 Ok(p1_contents != &*fs_contents)
584 }
563 }
564
565 fn print_pattern_file_warning(
566 warning: &PatternFileWarning,
567 repo: &Repo,
568 ) -> Vec<u8> {
569 match warning {
570 PatternFileWarning::InvalidSyntax(path, syntax) => format_bytes!(
571 b"{}: ignoring invalid syntax '{}'\n",
572 get_bytes_from_path(path),
573 &*syntax
574 ),
575 PatternFileWarning::NoSuchFile(path) => {
576 let path = if let Ok(relative) =
577 path.strip_prefix(repo.working_directory_path())
578 {
579 relative
580 } else {
581 &*path
582 };
583 format_bytes!(
584 b"skipping unreadable pattern file '{}': \
585 No such file or directory\n",
586 get_bytes_from_path(path),
587 )
588 }
589 }
590 }
General Comments 0
You need to be logged in to leave comments. Login now