From 24da2f200e2916ab2c7fab2f83cf9b991130107d Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Sun, 14 Jun 2020 10:46:25 +0200 Subject: [PATCH] Reset individual hunks (#125) closes #11 --- CHANGELOG.md | 1 + asyncgit/src/sync/diff.rs | 1 + asyncgit/src/sync/hunks.rs | 41 +++++++++++++++++++++++++++++++------- asyncgit/src/sync/mod.rs | 2 +- asyncgit/src/sync/reset.rs | 1 + asyncgit/src/sync/utils.rs | 1 + src/app.rs | 4 ++++ src/components/diff.rs | 41 +++++++++++++++++++++++++++++++++++--- src/components/reset.rs | 4 ++++ src/keys.rs | 1 + src/queue.rs | 1 + src/strings.rs | 7 +++++++ 12 files changed, 94 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7aedfecb..70336d04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Inspect stash commit in detail ([#121](https://github.com/extrawurst/gitui/issues/121)) +- Support reset/revert individual hunks ([#11](https://github.com/extrawurst/gitui/issues/11)) - Commit Amend (`ctrl+a`) when in commit popup ([#89](https://github.com/extrawurst/gitui/issues/89)) ![](assets/amend.gif) diff --git a/asyncgit/src/sync/diff.rs b/asyncgit/src/sync/diff.rs index a40956a3..2b23731c 100644 --- a/asyncgit/src/sync/diff.rs +++ b/asyncgit/src/sync/diff.rs @@ -91,6 +91,7 @@ pub(crate) fn get_diff_raw<'a>( // diff against head if let Ok(ref_head) = repo.head() { let parent = repo.find_commit( + //TODO: use new NoHead Error ref_head.target().ok_or_else(|| { let name = ref_head.name().unwrap_or("??"); Error::Generic( diff --git a/asyncgit/src/sync/hunks.rs b/asyncgit/src/sync/hunks.rs index 6a54c06d..271a2fd3 100644 --- a/asyncgit/src/sync/hunks.rs +++ b/asyncgit/src/sync/hunks.rs @@ -32,6 +32,38 @@ pub fn stage_hunk( Ok(()) } +/// +pub fn reset_hunk( + repo_path: &str, + file_path: String, + hunk_hash: u64, +) -> Result<()> { + scope_time!("reset_hunk"); + + let repo = repo(repo_path)?; + + let diff = get_diff_raw(&repo, &file_path, false, false)?; + + let hunk_index = find_hunk_index(&diff, hunk_hash); + if let Some(hunk_index) = hunk_index { + let mut hunk_idx = 0; + let mut opt = ApplyOptions::new(); + opt.hunk_callback(|_hunk| { + let res = hunk_idx == hunk_index; + hunk_idx += 1; + res + }); + + let diff = get_diff_raw(&repo, &file_path, false, true)?; + + repo.apply(&diff, ApplyLocation::WorkDir, Some(&mut opt))?; + + Ok(()) + } else { + Err(Error::Generic("hunk not found".to_string())) + } +} + fn find_hunk_index(diff: &Diff, hunk_hash: u64) -> Option { let mut result = None; @@ -72,7 +104,6 @@ pub fn unstage_hunk( let diff_count_positive = diff.deltas().len(); let hunk_index = find_hunk_index(&diff, hunk_hash); - if hunk_index.is_none() { return Err(Error::Generic("hunk not found".to_string())); } @@ -97,12 +128,8 @@ pub fn unstage_hunk( res }); - if repo - .apply(&diff, ApplyLocation::Index, Some(&mut opt)) - .is_err() - { - return Err(Error::Generic("apply failed".to_string())); - } + + repo.apply(&diff, ApplyLocation::Index, Some(&mut opt))?; } Ok(count == 1) diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 46ce63f1..fb4d4e42 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -23,7 +23,7 @@ pub use commit_files::get_commit_files; pub use commits_info::{get_commits_info, CommitId, CommitInfo}; pub use diff::get_diff_commit; pub use hooks::{hooks_commit_msg, hooks_post_commit, HookResult}; -pub use hunks::{stage_hunk, unstage_hunk}; +pub use hunks::{reset_hunk, stage_hunk, unstage_hunk}; pub use ignore::add_to_ignore; pub use logwalker::LogWalker; pub use reset::{reset_stage, reset_workdir}; diff --git a/asyncgit/src/sync/reset.rs b/asyncgit/src/sync/reset.rs index b0247b88..4140cb31 100644 --- a/asyncgit/src/sync/reset.rs +++ b/asyncgit/src/sync/reset.rs @@ -13,6 +13,7 @@ pub fn reset_stage(repo_path: &str, path: &str) -> Result<()> { if let Ok(reference) = head { let obj = repo.find_object( + //TODO: use NoHead error type reference.target().ok_or_else(|| { Error::Generic( "can't get reference to symbolic reference," diff --git a/asyncgit/src/sync/utils.rs b/asyncgit/src/sync/utils.rs index fdeb83d3..813bc7d9 100644 --- a/asyncgit/src/sync/utils.rs +++ b/asyncgit/src/sync/utils.rs @@ -63,6 +63,7 @@ pub fn commit(repo_path: &str, msg: &str) -> Result { let tree_id = index.write_tree()?; let tree = repo.find_tree(tree_id)?; + //TODO: use NoHead error let parents = if let Ok(reference) = repo.head() { let parent = repo.find_commit( reference.target().ok_or_else(|| { diff --git a/src/app.rs b/src/app.rs index 61463520..82cdbbf7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -334,6 +334,10 @@ impl App { flags.insert(NeedsUpdate::ALL); } } + Action::ResetHunk(path, hash) => { + sync::reset_hunk(CWD, path, hash)?; + flags.insert(NeedsUpdate::ALL); + } }, InternalEvent::ConfirmAction(action) => { self.reset.open(action)?; diff --git a/src/components/diff.rs b/src/components/diff.rs index 58da6a8a..06514c8c 100644 --- a/src/components/diff.rs +++ b/src/components/diff.rs @@ -2,7 +2,7 @@ use super::{CommandBlocking, DrawableComponent, ScrollType}; use crate::{ components::{CommandInfo, Component}, keys, - queue::{InternalEvent, Queue}, + queue::{Action, InternalEvent, Queue}, strings, ui::{calc_scroll_top, style::Theme}, }; @@ -284,9 +284,32 @@ impl DiffComponent { Ok(()) } + fn reset_hunk(&self) -> Result<()> { + if let Some(hunk) = self.selected_hunk { + let hash = self.diff.hunks[hunk].header_hash; + + self.queue + .as_ref() + .expect("try using queue in immutable diff") + .borrow_mut() + .push_back(InternalEvent::ConfirmAction( + Action::ResetHunk( + self.current.path.clone(), + hash, + ), + )); + } + + Ok(()) + } + fn is_immutable(&self) -> bool { self.queue.is_none() } + + const fn is_stage(&self) -> bool { + self.current.is_stage + } } impl DrawableComponent for DiffComponent { @@ -350,12 +373,17 @@ impl Component for DiffComponent { out.push(CommandInfo::new( commands::DIFF_HUNK_REMOVE, self.selected_hunk.is_some(), - self.focused && self.current.is_stage, + self.focused && self.is_stage(), )); out.push(CommandInfo::new( commands::DIFF_HUNK_ADD, self.selected_hunk.is_some(), - self.focused && !self.current.is_stage, + self.focused && !self.is_stage(), + )); + out.push(CommandInfo::new( + commands::DIFF_HUNK_REVERT, + self.selected_hunk.is_some(), + self.focused && !self.is_stage(), )); } @@ -394,6 +422,13 @@ impl Component for DiffComponent { self.add_hunk()?; Ok(true) } + keys::DIFF_RESET_HUNK + if !self.is_immutable() + && !self.is_stage() => + { + self.reset_hunk()?; + Ok(true) + } _ => Ok(false), }; } diff --git a/src/components/reset.rs b/src/components/reset.rs index 8fb1e5da..04fe8b58 100644 --- a/src/components/reset.rs +++ b/src/components/reset.rs @@ -148,6 +148,10 @@ impl ResetComponent { strings::CONFIRM_TITLE_STASHDROP, strings::CONFIRM_MSG_STASHDROP, ), + Action::ResetHunk(_, _) => ( + strings::CONFIRM_TITLE_RESET, + strings::CONFIRM_MSG_RESETHUNK, + ), }; } diff --git a/src/keys.rs b/src/keys.rs index 7fad8a20..4cb6803b 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -50,6 +50,7 @@ pub const STATUS_STAGE_FILE: KeyEvent = no_mod(KeyCode::Enter); pub const STATUS_STAGE_ALL: KeyEvent = no_mod(KeyCode::Char('a')); pub const STATUS_RESET_FILE: KeyEvent = with_mod(KeyCode::Char('D'), KeyModifiers::SHIFT); +pub const DIFF_RESET_HUNK: KeyEvent = STATUS_RESET_FILE; 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 = diff --git a/src/queue.rs b/src/queue.rs index 0c19867a..1b467da1 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -26,6 +26,7 @@ pub struct ResetItem { /// pub enum Action { Reset(ResetItem), + ResetHunk(String, u64), StashDrop(CommitId), } diff --git a/src/strings.rs b/src/strings.rs index 84423050..549e293b 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -20,6 +20,7 @@ pub static CONFIRM_TITLE_RESET: &str = "Reset"; pub static CONFIRM_TITLE_STASHDROP: &str = "Drop"; pub static CONFIRM_MSG_RESET: &str = "confirm file reset?"; pub static CONFIRM_MSG_STASHDROP: &str = "confirm stash drop?"; +pub static CONFIRM_MSG_RESETHUNK: &str = "confirm reset hunk?"; pub static LOG_TITLE: &str = "Commit"; pub static STASHLIST_TITLE: &str = "Stashes"; @@ -100,6 +101,12 @@ pub mod commands { CMD_GROUP_DIFF, ); /// + pub static DIFF_HUNK_REVERT: CommandText = CommandText::new( + "Revert hunk [D]", + "reverts selected hunk", + CMD_GROUP_DIFF, + ); + /// pub static DIFF_HUNK_REMOVE: CommandText = CommandText::new( "Remove hunk [enter]", "removes selected hunk from stage",