mirror of
https://github.com/gitui-org/gitui
synced 2026-05-23 17:08:21 +00:00
honor showUntrackedFiles config (#753)
This commit is contained in:
parent
eb28ba1af5
commit
3cd218de57
12 changed files with 214 additions and 100 deletions
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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(¶ms);
|
||||
|
||||
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
114
asyncgit/src/sync/config.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue