honor showUntrackedFiles config (#753)

This commit is contained in:
Stephan Dilly 2021-06-01 09:56:55 +02:00 committed by GitHub
parent eb28ba1af5
commit 3cd218de57
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 214 additions and 100 deletions

View file

@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
## Added
- honor `config.showUntrackedFiles` improving speed with a lot of untracked items ([#752](https://github.com/extrawurst/gitui/issues/752))
## Fixed
- wrong file with same name shown in file tree ([#748](https://github.com/extrawurst/gitui/issues/748))

View file

@ -31,19 +31,14 @@ pub struct Status {
pub struct StatusParams {
tick: u128,
status_type: StatusType,
include_untracked: bool,
}
impl StatusParams {
///
pub fn new(
status_type: StatusType,
include_untracked: bool,
) -> Self {
pub fn new(status_type: StatusType) -> Self {
Self {
tick: current_tick(),
status_type,
include_untracked,
}
}
}
@ -93,10 +88,9 @@ impl AsyncStatus {
let hash_request = hash(&params);
log::trace!(
"request: [hash: {}] (type: {:?}, untracked: {})",
"request: [hash: {}] (type: {:?})",
hash_request,
params.status_type,
params.include_untracked,
);
{
@ -115,14 +109,12 @@ impl AsyncStatus {
let sender = self.sender.clone();
let arc_pending = Arc::clone(&self.pending);
let status_type = params.status_type;
let include_untracked = params.include_untracked;
self.pending.fetch_add(1, Ordering::Relaxed);
rayon_core::spawn(move || {
let ok = Self::fetch_helper(
status_type,
include_untracked,
hash_request,
&arc_current,
&arc_last,
@ -143,17 +135,15 @@ impl AsyncStatus {
fn fetch_helper(
status_type: StatusType,
include_untracked: bool,
hash_request: u64,
arc_current: &Arc<Mutex<Request<u64, Status>>>,
arc_last: &Arc<Mutex<Status>>,
) -> Result<()> {
let res = Self::get_status(status_type, include_untracked)?;
let res = Self::get_status(status_type)?;
log::trace!(
"status fetched: {} (type: {:?}, untracked: {})",
"status fetched: {} (type: {:?})",
hash_request,
status_type,
include_untracked
);
{
@ -171,16 +161,9 @@ impl AsyncStatus {
Ok(())
}
fn get_status(
status_type: StatusType,
include_untracked: bool,
) -> Result<Status> {
fn get_status(status_type: StatusType) -> Result<Status> {
Ok(Status {
items: sync::status::get_status(
CWD,
status_type,
include_untracked,
)?,
items: sync::status::get_status(CWD, status_type)?,
})
}
}

114
asyncgit/src/sync/config.rs Normal file
View file

@ -0,0 +1,114 @@
use super::utils::repo;
use crate::error::Result;
use git2::Repository;
use scopetime::scope_time;
// see https://git-scm.com/docs/git-config#Documentation/git-config.txt-statusshowUntrackedFiles
/// represents the `status.showUntrackedFiles` git config state
pub enum ShowUntrackedFilesConfig {
///
No,
///
Normal,
///
All,
}
impl ShowUntrackedFilesConfig {
///
pub const fn include_none(&self) -> bool {
matches!(self, Self::No)
}
///
pub const fn include_untracked(&self) -> bool {
matches!(self, Self::Normal | Self::All)
}
///
pub const fn recurse_untracked_dirs(&self) -> bool {
matches!(self, Self::All)
}
}
pub fn untracked_files_config_repo(
repo: &Repository,
) -> Result<ShowUntrackedFilesConfig> {
let show_untracked_files =
get_config_string_repo(repo, "status.showUntrackedFiles")?;
if let Some(show_untracked_files) = show_untracked_files {
if &show_untracked_files == "no" {
return Ok(ShowUntrackedFilesConfig::No);
} else if &show_untracked_files == "normal" {
return Ok(ShowUntrackedFilesConfig::Normal);
}
}
Ok(ShowUntrackedFilesConfig::All)
}
///
pub fn untracked_files_config(
repo_path: &str,
) -> Result<ShowUntrackedFilesConfig> {
let repo = repo(repo_path)?;
untracked_files_config_repo(&repo)
}
/// get string from config
pub fn get_config_string(
repo_path: &str,
key: &str,
) -> Result<Option<String>> {
let repo = repo(repo_path)?;
get_config_string_repo(&repo, key)
}
pub fn get_config_string_repo(
repo: &Repository,
key: &str,
) -> Result<Option<String>> {
scope_time!("get_config_string_repo");
let cfg = repo.config()?;
// this code doesnt match what the doc says regarding what
// gets returned when but it actually works
let entry_res = cfg.get_entry(key);
let entry = match entry_res {
Ok(ent) => ent,
Err(_) => return Ok(None),
};
if entry.has_value() {
Ok(entry.value().map(std::string::ToString::to_string))
} else {
Ok(None)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sync::tests::repo_init;
#[test]
fn test_get_config() {
let bad_dir_cfg =
get_config_string("oodly_noodly", "this.doesnt.exist");
assert!(bad_dir_cfg.is_err());
let (_td, repo) = repo_init().unwrap();
let path = repo.path();
let rpath = path.as_os_str().to_str().unwrap();
let bad_cfg = get_config_string(rpath, "this.doesnt.exist");
assert!(bad_cfg.is_ok());
assert!(bad_cfg.unwrap().is_none());
// repo init sets user.name
let good_cfg = get_config_string(rpath, "user.name");
assert!(good_cfg.is_ok());
assert!(good_cfg.unwrap().is_some());
}
}

View file

@ -459,8 +459,8 @@ mod tests {
.unwrap();
}
let res = get_status(repo_path, StatusType::WorkingDir, true)
.unwrap();
let res =
get_status(repo_path, StatusType::WorkingDir).unwrap();
assert_eq!(res.len(), 1);
assert_eq!(res[0].path, "bar.txt");

View file

@ -9,6 +9,7 @@ mod commit;
mod commit_details;
mod commit_files;
mod commits_info;
mod config;
pub mod cred;
pub mod diff;
mod hooks;
@ -44,6 +45,10 @@ pub use commit_files::get_commit_files;
pub use commits_info::{
get_commit_info, get_commits_info, CommitId, CommitInfo,
};
pub use config::{
get_config_string, untracked_files_config,
ShowUntrackedFilesConfig,
};
pub use diff::get_diff_commit;
pub use hooks::{
hooks_commit_msg, hooks_post_commit, hooks_pre_commit, HookResult,
@ -250,12 +255,10 @@ mod tests {
/// helper returning amount of files with changes in the (wd,stage)
pub fn get_statuses(repo_path: &str) -> (usize, usize) {
(
get_status(repo_path, StatusType::WorkingDir, true)
.unwrap()
.len(),
get_status(repo_path, StatusType::Stage, true)
get_status(repo_path, StatusType::WorkingDir)
.unwrap()
.len(),
get_status(repo_path, StatusType::Stage).unwrap().len(),
)
}

View file

@ -88,8 +88,8 @@ mod tests {
let root = repo.path().parent().unwrap();
let repo_path = root.as_os_str().to_str().unwrap();
let res = get_status(repo_path, StatusType::WorkingDir, true)
.unwrap();
let res =
get_status(repo_path, StatusType::WorkingDir).unwrap();
assert_eq!(res.len(), 0);
let file_path = root.join("bar.txt");

View file

@ -1,6 +1,10 @@
//! sync git api for fetching a status
use crate::{error::Error, error::Result, sync::utils};
use crate::{
error::Error,
error::Result,
sync::{config::untracked_files_config_repo, utils},
};
use git2::{Delta, Status, StatusOptions, StatusShow};
use scopetime::scope_time;
use std::path::Path;
@ -92,20 +96,24 @@ impl From<StatusType> for StatusShow {
pub fn get_status(
repo_path: &str,
status_type: StatusType,
include_untracked: bool,
) -> Result<Vec<StatusItem>> {
scope_time!("get_status");
let repo = utils::repo(repo_path)?;
let statuses = repo.statuses(Some(
StatusOptions::default()
.show(status_type.into())
.update_index(true)
.include_untracked(include_untracked)
.renames_head_to_index(true)
.recurse_untracked_dirs(true),
))?;
let show_untracked = untracked_files_config_repo(&repo)?;
let mut options = StatusOptions::default();
options
.show(status_type.into())
.update_index(true)
.include_untracked(show_untracked.include_untracked())
.renames_head_to_index(true)
.recurse_untracked_dirs(
show_untracked.recurse_untracked_dirs(),
);
let statuses = repo.statuses(Some(&mut options))?;
let mut res = Vec::with_capacity(statuses.len());

View file

@ -1,7 +1,10 @@
//! sync git api (various methods)
use super::CommitId;
use crate::error::{Error, Result};
use crate::{
error::{Error, Result},
sync::config::untracked_files_config_repo,
};
use git2::{IndexAddOption, Repository, RepositoryOpenFlags};
use scopetime::scope_time;
use std::{
@ -129,7 +132,18 @@ pub fn stage_add_all(repo_path: &str, pattern: &str) -> Result<()> {
let mut index = repo.index()?;
index.add_all(vec![pattern], IndexAddOption::DEFAULT, None)?;
let config = untracked_files_config_repo(&repo)?;
if config.include_none() {
index.update_all(vec![pattern], None)?;
} else {
index.add_all(
vec![pattern],
IndexAddOption::DEFAULT,
None,
)?;
}
index.write()?;
Ok(())
@ -149,30 +163,6 @@ pub fn stage_addremoved(repo_path: &str, path: &Path) -> Result<()> {
Ok(())
}
/// get string from config
pub fn get_config_string(
repo_path: &str,
key: &str,
) -> Result<Option<String>> {
let repo = repo(repo_path)?;
let cfg = repo.config()?;
// this code doesnt match what the doc says regarding what
// gets returned when but it actually works
let entry_res = cfg.get_entry(key);
let entry = match entry_res {
Ok(ent) => ent,
Err(_) => return Ok(None),
};
if entry.has_value() {
Ok(entry.value().map(std::string::ToString::to_string))
} else {
Ok(None)
}
}
pub(crate) fn bytes2string(bytes: &[u8]) -> Result<String> {
Ok(String::from_utf8(bytes.to_vec())?)
}
@ -239,23 +229,7 @@ mod tests {
false
);
}
#[test]
fn test_get_config() {
let bad_dir_cfg =
get_config_string("oodly_noodly", "this.doesnt.exist");
assert!(bad_dir_cfg.is_err());
let (_td, repo) = repo_init().unwrap();
let path = repo.path();
let rpath = path.as_os_str().to_str().unwrap();
let bad_cfg = get_config_string(rpath, "this.doesnt.exist");
assert!(bad_cfg.is_ok());
assert!(bad_cfg.unwrap().is_none());
// repo init sets user.name
let good_cfg = get_config_string(rpath, "user.name");
assert!(good_cfg.is_ok());
assert!(good_cfg.unwrap().is_some());
}
#[test]
fn test_staging_one_file() {
let file_path = Path::new("file1.txt");
@ -287,7 +261,7 @@ mod tests {
let repo_path = root.as_os_str().to_str().unwrap();
let status_count = |s: StatusType| -> usize {
get_status(repo_path, s, true).unwrap().len()
get_status(repo_path, s).unwrap().len()
};
fs::create_dir_all(&root.join("a/d"))?;
@ -308,6 +282,33 @@ mod tests {
Ok(())
}
#[test]
fn test_not_staging_untracked_folder() -> Result<()> {
let (_td, repo) = repo_init().unwrap();
let root = repo.path().parent().unwrap();
let repo_path = root.as_os_str().to_str().unwrap();
fs::create_dir_all(&root.join("a/d"))?;
File::create(&root.join(Path::new("a/d/f1.txt")))?
.write_all(b"foo")?;
File::create(&root.join(Path::new("a/d/f2.txt")))?
.write_all(b"foo")?;
File::create(&root.join(Path::new("f3.txt")))?
.write_all(b"foo")?;
assert_eq!(get_statuses(repo_path), (3, 0));
repo.config()?.set_str("status.showUntrackedFiles", "no")?;
assert_eq!(get_statuses(repo_path), (0, 0));
stage_add_all(repo_path, "*").unwrap();
assert_eq!(get_statuses(repo_path), (0, 0));
Ok(())
}
#[test]
fn test_staging_deleted_file() {
let file_path = Path::new("file1.txt");
@ -316,7 +317,7 @@ mod tests {
let repo_path = root.as_os_str().to_str().unwrap();
let status_count = |s: StatusType| -> usize {
get_status(repo_path, s, true).unwrap().len()
get_status(repo_path, s).unwrap().len()
};
let full_path = &root.join(file_path);
@ -350,7 +351,7 @@ mod tests {
let repo_path = root.as_os_str().to_str().unwrap();
let status_count = |s: StatusType| -> usize {
get_status(repo_path, s, true).unwrap().len()
get_status(repo_path, s).unwrap().len()
};
let sub = &root.join("sub");

View file

@ -13,8 +13,7 @@ use anyhow::Result;
use asyncgit::{
cached,
sync::{
self, utils::get_config_string, CommitId, HookResult,
RepoState,
self, get_config_string, CommitId, HookResult, RepoState,
},
CWD,
};

View file

@ -9,7 +9,8 @@ use crate::{
};
use anyhow::{anyhow, bail, Result};
use asyncgit::{
sync::utils::get_config_string, sync::utils::repo_work_dir, CWD,
sync::{get_config_string, utils::repo_work_dir},
CWD,
};
use crossterm::{
event::Event,

View file

@ -12,8 +12,8 @@ use crate::{
};
use anyhow::Result;
use asyncgit::{
sync::status::StatusType, AsyncNotification, AsyncStatus,
StatusParams,
sync::{self, status::StatusType},
AsyncNotification, AsyncStatus, StatusParams, CWD,
};
use crossbeam_channel::Sender;
use crossterm::event::Event;
@ -73,10 +73,8 @@ impl Stashing {
///
pub fn update(&mut self) -> Result<()> {
if self.is_visible() {
self.git_status.fetch(&StatusParams::new(
StatusType::Both,
self.options.stash_untracked,
))?;
self.git_status
.fetch(&StatusParams::new(StatusType::Both))?;
}
Ok(())
@ -258,6 +256,12 @@ impl Component for Stashing {
}
fn show(&mut self) -> Result<()> {
let config_untracked_files =
sync::untracked_files_config(CWD)?;
self.options.stash_untracked =
!config_untracked_files.include_none();
self.visible = true;
self.update()?;
Ok(())

View file

@ -318,12 +318,10 @@ impl Status {
if self.is_visible() {
self.git_diff.refresh()?;
self.git_status_workdir.fetch(&StatusParams::new(
StatusType::WorkingDir,
true,
))?;
self.git_status_workdir
.fetch(&StatusParams::new(StatusType::WorkingDir))?;
self.git_status_stage
.fetch(&StatusParams::new(StatusType::Stage, true))?;
.fetch(&StatusParams::new(StatusType::Stage))?;
self.branch_compare();
}