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