diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ec948c953..6d19e685cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ![](assets/cmdbar.gif) ### Added +- support adding untracked file/folder to `.gitignore` ([#44](https://github.com/extrawurst/gitui/issues/44)) - support reverse tabbing using shift+tab ([#92](https://github.com/extrawurst/gitui/issues/92)) - switch to using cmd line args instead of `ENV` (`-l` for logging and `--version`) **please convert your GITUI_LOGGING usage** [[@shenek](https://github.com/shenek)] ([#88](https://github.com/extrawurst/gitui/issues/88)) - added missing LICENSE.md files in sub-crates [[@ignatenkobrain](https://github.com/ignatenkobrain)] ([#94](https://github.com/extrawurst/gitui/pull/94)) diff --git a/asyncgit/src/sync/diff.rs b/asyncgit/src/sync/diff.rs index bda8b6ea3d..4d6b143368 100644 --- a/asyncgit/src/sync/diff.rs +++ b/asyncgit/src/sync/diff.rs @@ -8,6 +8,7 @@ use git2::{ }; use scopetime::scope_time; use std::{fs, path::Path}; +use utils::work_dir; /// type of diff of a single line #[derive(Copy, Clone, PartialEq, Hash, Debug)] @@ -127,12 +128,7 @@ pub fn get_diff( scope_time!("get_diff"); let repo = utils::repo(repo_path)?; - let repo_path = repo.path().parent().ok_or_else(|| { - Error::Generic( - "repositories located at root are not supported." - .to_string(), - ) - })?; + let work_dir = work_dir(&repo); let diff = get_diff_raw(&repo, &p, stage, false)?; let mut res: FileDiff = FileDiff::default(); @@ -190,7 +186,7 @@ pub fn get_diff( ) })?; - let newfile_path = repo_path.join(relative_path); + let newfile_path = work_dir.join(relative_path); if let Some(newfile_content) = new_file_content(&newfile_path) diff --git a/asyncgit/src/sync/ignore.rs b/asyncgit/src/sync/ignore.rs new file mode 100644 index 0000000000..bd78e26c60 --- /dev/null +++ b/asyncgit/src/sync/ignore.rs @@ -0,0 +1,27 @@ +use super::utils::{repo, work_dir}; +use crate::error::Result; +use scopetime::scope_time; +use std::{fs::OpenOptions, io::Write}; + +static GITIGNORE: &str = ".gitignore"; + +/// add file or path to root ignore file +pub fn add_to_ignore( + repo_path: &str, + path_to_ignore: String, +) -> Result<()> { + scope_time!("add_to_ignore"); + + let repo = repo(repo_path)?; + + let ignore_file = work_dir(&repo).join(GITIGNORE); + + let mut file = OpenOptions::new() + .append(true) + .create(true) + .open(ignore_file)?; + + writeln!(file, "{}", path_to_ignore)?; + + Ok(()) +} diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 456be51c0d..b9aac6cc1e 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -4,6 +4,7 @@ mod commits_info; pub mod diff; mod hooks; mod hunks; +mod ignore; mod logwalker; mod reset; mod stash; @@ -14,6 +15,7 @@ pub mod utils; pub use commits_info::{get_commits_info, CommitId, CommitInfo}; pub use hooks::{hooks_commit_msg, hooks_post_commit, HookResult}; pub use hunks::{stage_hunk, unstage_hunk}; +pub use ignore::add_to_ignore; pub use logwalker::LogWalker; pub use reset::{ reset_stage, reset_workdir_file, reset_workdir_folder, diff --git a/asyncgit/src/sync/utils.rs b/asyncgit/src/sync/utils.rs index 5509fb6c79..dd29595c36 100644 --- a/asyncgit/src/sync/utils.rs +++ b/asyncgit/src/sync/utils.rs @@ -41,6 +41,11 @@ pub fn repo(repo_path: &str) -> Result { Ok(repo) } +/// +pub fn work_dir(repo: &Repository) -> &Path { + repo.workdir().expect("unable to query workdir") +} + /// this does not run any git hooks pub fn commit(repo_path: &str, msg: &str) -> Result { scope_time!("commit"); diff --git a/src/components/changes.rs b/src/components/changes.rs index c2adc9ea44..2584510658 100644 --- a/src/components/changes.rs +++ b/src/components/changes.rs @@ -124,6 +124,17 @@ impl ChangesComponent { } false } + + fn add_to_ignore(&mut self) -> Result { + if let Some(tree_item) = self.selection() { + sync::add_to_ignore(CWD, tree_item.info.full_path)?; + self.queue + .borrow_mut() + .push_back(InternalEvent::Update(NeedsUpdate::ALL)); + return Ok(true); + } + Ok(false) + } } impl DrawableComponent for ChangesComponent { @@ -159,6 +170,11 @@ impl Component for ChangesComponent { some_selection, self.focused(), )); + out.push(CommandInfo::new( + commands::IGNORE_ITEM, + some_selection, + self.focused(), + )); } else { out.push(CommandInfo::new( commands::UNSTAGE_ITEM, @@ -210,6 +226,13 @@ impl Component for ChangesComponent { { Ok(self.dispatch_reset_workdir()) } + + keys::STATUS_IGNORE_FILE + if self.is_working_dir + && !self.is_empty() => + { + Ok(self.add_to_ignore()?) + } _ => Ok(false), }; } diff --git a/src/keys.rs b/src/keys.rs index 90533b7ba7..7d04a46107 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -42,6 +42,7 @@ pub const ENTER: KeyEvent = no_mod(KeyCode::Enter); pub const STATUS_STAGE_FILE: KeyEvent = no_mod(KeyCode::Enter); pub const STATUS_RESET_FILE: KeyEvent = with_mod(KeyCode::Char('D'), KeyModifiers::SHIFT); +pub const STATUS_IGNORE_FILE: KeyEvent = no_mod(KeyCode::Char('i')); pub const STASHING_SAVE: KeyEvent = no_mod(KeyCode::Char('s')); pub const STASHING_TOGGLE_UNTRACKED: KeyEvent = no_mod(KeyCode::Char('u')); diff --git a/src/strings.rs b/src/strings.rs index 4aa8f7429d..ed78401743 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -140,6 +140,12 @@ pub mod commands { "revert changes in selected file or entire path", CMD_GROUP_CHANGES, ); + /// + pub static IGNORE_ITEM: CommandText = CommandText::new( + "Ignore [i]", + "Add file or path to .gitignore", + CMD_GROUP_CHANGES, + ); /// pub static STATUS_FOCUS_LEFT: CommandText = CommandText::new(