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::errors::HgError; |
16 use hg::dirstate::TruncatedTimestamp; |
|
17 use hg::dirstate::RANGE_MASK_31BIT; |
|
18 use hg::errors::{HgError, IoResultExt}; |
|
19 use hg::lock::LockError; |
17 use hg::manifest::Manifest; |
20 use hg::manifest::Manifest; |
18 use hg::matchers::AlwaysMatcher; |
21 use hg::matchers::AlwaysMatcher; |
19 use hg::repo::Repo; |
22 use hg::repo::Repo; |
20 use hg::utils::files::get_bytes_from_os_string; |
23 use hg::utils::files::get_bytes_from_os_string; |
21 use hg::utils::hg_path::{hg_path_to_os_string, HgPath}; |
24 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath}; |
22 use hg::{HgPathCow, StatusOptions}; |
25 use hg::{HgPathCow, StatusOptions}; |
23 use log::{info, warn}; |
26 use log::{info, warn}; |
|
27 use std::io; |
24 |
28 |
25 pub const HELP_TEXT: &str = " |
29 pub const HELP_TEXT: &str = " |
26 Show changed files in the working directory |
30 Show changed files in the working directory |
27 |
31 |
28 This is a pure Rust version of `hg status`. |
32 This is a pure Rust version of `hg status`. |
241 if display_states.modified { |
246 if display_states.modified { |
242 ds_status.modified.push(to_check); |
247 ds_status.modified.push(to_check); |
243 } |
248 } |
244 } else { |
249 } else { |
245 if display_states.clean { |
250 if display_states.clean { |
246 ds_status.clean.push(to_check); |
251 ds_status.clean.push(to_check.clone()); |
|
252 } |
|
253 fixup.push(to_check.into_owned()) |
|
254 } |
|
255 } |
|
256 } |
|
257 if display_states.modified { |
|
258 display_status_paths( |
|
259 ui, |
|
260 repo, |
|
261 config, |
|
262 no_status, |
|
263 &mut ds_status.modified, |
|
264 b"M", |
|
265 )?; |
|
266 } |
|
267 if display_states.added { |
|
268 display_status_paths( |
|
269 ui, |
|
270 repo, |
|
271 config, |
|
272 no_status, |
|
273 &mut ds_status.added, |
|
274 b"A", |
|
275 )?; |
|
276 } |
|
277 if display_states.removed { |
|
278 display_status_paths( |
|
279 ui, |
|
280 repo, |
|
281 config, |
|
282 no_status, |
|
283 &mut ds_status.removed, |
|
284 b"R", |
|
285 )?; |
|
286 } |
|
287 if display_states.deleted { |
|
288 display_status_paths( |
|
289 ui, |
|
290 repo, |
|
291 config, |
|
292 no_status, |
|
293 &mut ds_status.deleted, |
|
294 b"!", |
|
295 )?; |
|
296 } |
|
297 if display_states.unknown { |
|
298 display_status_paths( |
|
299 ui, |
|
300 repo, |
|
301 config, |
|
302 no_status, |
|
303 &mut ds_status.unknown, |
|
304 b"?", |
|
305 )?; |
|
306 } |
|
307 if display_states.ignored { |
|
308 display_status_paths( |
|
309 ui, |
|
310 repo, |
|
311 config, |
|
312 no_status, |
|
313 &mut ds_status.ignored, |
|
314 b"I", |
|
315 )?; |
|
316 } |
|
317 if display_states.clean { |
|
318 display_status_paths( |
|
319 ui, |
|
320 repo, |
|
321 config, |
|
322 no_status, |
|
323 &mut ds_status.clean, |
|
324 b"C", |
|
325 )?; |
|
326 } |
|
327 |
|
328 let mut dirstate_write_needed = ds_status.dirty; |
|
329 let filesystem_time_at_status_start = ds_status |
|
330 .filesystem_time_at_status_start |
|
331 .map(TruncatedTimestamp::from); |
|
332 |
|
333 if (fixup.is_empty() || filesystem_time_at_status_start.is_none()) |
|
334 && !dirstate_write_needed |
|
335 { |
|
336 // Nothing to update |
|
337 return Ok(()); |
|
338 } |
|
339 |
|
340 // Update the dirstate on disk if we can |
|
341 let with_lock_result = |
|
342 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> { |
|
343 if let Some(mtime_boundary) = filesystem_time_at_status_start { |
|
344 for hg_path in fixup { |
|
345 use std::os::unix::fs::MetadataExt; |
|
346 let fs_path = hg_path_to_path_buf(&hg_path) |
|
347 .expect("HgPath conversion"); |
|
348 // Specifically do not reuse `fs_metadata` from |
|
349 // `unsure_is_clean` which was needed before reading |
|
350 // contents. Here we access metadata again after reading |
|
351 // content, in case it changed in the meantime. |
|
352 let fs_metadata = repo |
|
353 .working_directory_vfs() |
|
354 .symlink_metadata(&fs_path)?; |
|
355 let mtime = TruncatedTimestamp::for_mtime_of(&fs_metadata) |
|
356 .when_reading_file(&fs_path)?; |
|
357 if mtime.is_reliable_mtime(&mtime_boundary) { |
|
358 let mode = fs_metadata.mode(); |
|
359 let size = fs_metadata.len() as u32 & RANGE_MASK_31BIT; |
|
360 let mut entry = dmap |
|
361 .get(&hg_path)? |
|
362 .expect("ambiguous file not in dirstate"); |
|
363 entry.set_clean(mode, size, mtime); |
|
364 dmap.add_file(&hg_path, entry)?; |
|
365 dirstate_write_needed = true |
|
366 } |
247 } |
367 } |
248 } |
368 } |
249 } |
369 drop(dmap); // Avoid "already mutably borrowed" RefCell panics |
250 } |
370 if dirstate_write_needed { |
251 if display_states.modified { |
371 repo.write_dirstate()? |
252 display_status_paths( |
372 } |
253 ui, |
373 Ok(()) |
254 repo, |
374 }); |
255 config, |
375 match with_lock_result { |
256 no_status, |
376 Ok(closure_result) => closure_result?, |
257 &mut ds_status.modified, |
377 Err(LockError::AlreadyHeld) => { |
258 b"M", |
378 // Not updating the dirstate is not ideal but not critical: |
259 )?; |
379 // don’t keep our caller waiting until some other Mercurial |
260 } |
380 // process releases the lock. |
261 if display_states.added { |
381 } |
262 display_status_paths( |
382 Err(LockError::Other(HgError::IoError { error, .. })) |
263 ui, |
383 if error.kind() == io::ErrorKind::PermissionDenied => |
264 repo, |
384 { |
265 config, |
385 // `hg status` on a read-only repository is fine |
266 no_status, |
386 } |
267 &mut ds_status.added, |
387 Err(LockError::Other(error)) => { |
268 b"A", |
388 // Report other I/O errors |
269 )?; |
389 Err(error)? |
270 } |
390 } |
271 if display_states.removed { |
|
272 display_status_paths( |
|
273 ui, |
|
274 repo, |
|
275 config, |
|
276 no_status, |
|
277 &mut ds_status.removed, |
|
278 b"R", |
|
279 )?; |
|
280 } |
|
281 if display_states.deleted { |
|
282 display_status_paths( |
|
283 ui, |
|
284 repo, |
|
285 config, |
|
286 no_status, |
|
287 &mut ds_status.deleted, |
|
288 b"!", |
|
289 )?; |
|
290 } |
|
291 if display_states.unknown { |
|
292 display_status_paths( |
|
293 ui, |
|
294 repo, |
|
295 config, |
|
296 no_status, |
|
297 &mut ds_status.unknown, |
|
298 b"?", |
|
299 )?; |
|
300 } |
|
301 if display_states.ignored { |
|
302 display_status_paths( |
|
303 ui, |
|
304 repo, |
|
305 config, |
|
306 no_status, |
|
307 &mut ds_status.ignored, |
|
308 b"I", |
|
309 )?; |
|
310 } |
|
311 if display_states.clean { |
|
312 display_status_paths( |
|
313 ui, |
|
314 repo, |
|
315 config, |
|
316 no_status, |
|
317 &mut ds_status.clean, |
|
318 b"C", |
|
319 )?; |
|
320 } |
391 } |
321 Ok(()) |
392 Ok(()) |
322 } |
393 } |
323 |
394 |
324 // Probably more elegant to use a Deref or Borrow trait rather than |
395 // Probably more elegant to use a Deref or Borrow trait rather than |