rust/rhg/src/commands/status.rs
changeset 48468 000130cfafb6
parent 48457 005ae1a343f8
child 48482 112184713852
equal deleted inserted replaced
48467:2097f63575a5 48468:000130cfafb6
    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`.
   227         info!(
   231         info!(
   228             "Files to be rechecked by retrieval from filelog: {:?}",
   232             "Files to be rechecked by retrieval from filelog: {:?}",
   229             &ds_status.unsure
   233             &ds_status.unsure
   230         );
   234         );
   231     }
   235     }
       
   236     let mut fixup = Vec::new();
   232     if !ds_status.unsure.is_empty()
   237     if !ds_status.unsure.is_empty()
   233         && (display_states.modified || display_states.clean)
   238         && (display_states.modified || display_states.clean)
   234     {
   239     {
   235         let p1 = repo.dirstate_parents()?.p1;
   240         let p1 = repo.dirstate_parents()?.p1;
   236         let manifest = repo.manifest_for_node(p1).map_err(|e| {
   241         let manifest = repo.manifest_for_node(p1).map_err(|e| {
   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
   366     repo: &Repo,
   437     repo: &Repo,
   367     manifest: &Manifest,
   438     manifest: &Manifest,
   368     hg_path: &HgPath,
   439     hg_path: &HgPath,
   369 ) -> Result<bool, HgError> {
   440 ) -> Result<bool, HgError> {
   370     let vfs = repo.working_directory_vfs();
   441     let vfs = repo.working_directory_vfs();
   371     let fs_path = hg_path_to_os_string(hg_path).expect("HgPath conversion");
   442     let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
   372     let fs_metadata = vfs.symlink_metadata(&fs_path)?;
   443     let fs_metadata = vfs.symlink_metadata(&fs_path)?;
   373     let is_symlink = fs_metadata.file_type().is_symlink();
   444     let is_symlink = fs_metadata.file_type().is_symlink();
   374     // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
   445     // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
   375     // dirstate
   446     // dirstate
   376     let fs_flags = if is_symlink {
   447     let fs_flags = if is_symlink {
   397     let fs_contents = if is_symlink {
   468     let fs_contents = if is_symlink {
   398         get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
   469         get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
   399     } else {
   470     } else {
   400         vfs.read(fs_path)?
   471         vfs.read(fs_path)?
   401     };
   472     };
   402     return Ok(contents_in_p1 != &*fs_contents);
   473     Ok(contents_in_p1 != &*fs_contents)
   403 }
   474 }