mirror of
https://github.com/gitui-org/gitui
synced 2026-05-24 09:28:21 +00:00
parent
0cdaabf9f8
commit
24da2f200e
12 changed files with 94 additions and 11 deletions
|
|
@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Inspect stash commit in detail ([#121](https://github.com/extrawurst/gitui/issues/121))
|
- 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))
|
- Commit Amend (`ctrl+a`) when in commit popup ([#89](https://github.com/extrawurst/gitui/issues/89))
|
||||||
|
|
||||||

|

|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ pub(crate) fn get_diff_raw<'a>(
|
||||||
// diff against head
|
// diff against head
|
||||||
if let Ok(ref_head) = repo.head() {
|
if let Ok(ref_head) = repo.head() {
|
||||||
let parent = repo.find_commit(
|
let parent = repo.find_commit(
|
||||||
|
//TODO: use new NoHead Error
|
||||||
ref_head.target().ok_or_else(|| {
|
ref_head.target().ok_or_else(|| {
|
||||||
let name = ref_head.name().unwrap_or("??");
|
let name = ref_head.name().unwrap_or("??");
|
||||||
Error::Generic(
|
Error::Generic(
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,38 @@ pub fn stage_hunk(
|
||||||
Ok(())
|
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<usize> {
|
fn find_hunk_index(diff: &Diff, hunk_hash: u64) -> Option<usize> {
|
||||||
let mut result = None;
|
let mut result = None;
|
||||||
|
|
||||||
|
|
@ -72,7 +104,6 @@ pub fn unstage_hunk(
|
||||||
let diff_count_positive = diff.deltas().len();
|
let diff_count_positive = diff.deltas().len();
|
||||||
|
|
||||||
let hunk_index = find_hunk_index(&diff, hunk_hash);
|
let hunk_index = find_hunk_index(&diff, hunk_hash);
|
||||||
|
|
||||||
if hunk_index.is_none() {
|
if hunk_index.is_none() {
|
||||||
return Err(Error::Generic("hunk not found".to_string()));
|
return Err(Error::Generic("hunk not found".to_string()));
|
||||||
}
|
}
|
||||||
|
|
@ -97,12 +128,8 @@ pub fn unstage_hunk(
|
||||||
|
|
||||||
res
|
res
|
||||||
});
|
});
|
||||||
if repo
|
|
||||||
.apply(&diff, ApplyLocation::Index, Some(&mut opt))
|
repo.apply(&diff, ApplyLocation::Index, Some(&mut opt))?;
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
return Err(Error::Generic("apply failed".to_string()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(count == 1)
|
Ok(count == 1)
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ pub use commit_files::get_commit_files;
|
||||||
pub use commits_info::{get_commits_info, CommitId, CommitInfo};
|
pub use commits_info::{get_commits_info, CommitId, CommitInfo};
|
||||||
pub use diff::get_diff_commit;
|
pub use diff::get_diff_commit;
|
||||||
pub use hooks::{hooks_commit_msg, hooks_post_commit, HookResult};
|
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 ignore::add_to_ignore;
|
||||||
pub use logwalker::LogWalker;
|
pub use logwalker::LogWalker;
|
||||||
pub use reset::{reset_stage, reset_workdir};
|
pub use reset::{reset_stage, reset_workdir};
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ pub fn reset_stage(repo_path: &str, path: &str) -> Result<()> {
|
||||||
|
|
||||||
if let Ok(reference) = head {
|
if let Ok(reference) = head {
|
||||||
let obj = repo.find_object(
|
let obj = repo.find_object(
|
||||||
|
//TODO: use NoHead error type
|
||||||
reference.target().ok_or_else(|| {
|
reference.target().ok_or_else(|| {
|
||||||
Error::Generic(
|
Error::Generic(
|
||||||
"can't get reference to symbolic reference,"
|
"can't get reference to symbolic reference,"
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ pub fn commit(repo_path: &str, msg: &str) -> Result<Oid> {
|
||||||
let tree_id = index.write_tree()?;
|
let tree_id = index.write_tree()?;
|
||||||
let tree = repo.find_tree(tree_id)?;
|
let tree = repo.find_tree(tree_id)?;
|
||||||
|
|
||||||
|
//TODO: use NoHead error
|
||||||
let parents = if let Ok(reference) = repo.head() {
|
let parents = if let Ok(reference) = repo.head() {
|
||||||
let parent = repo.find_commit(
|
let parent = repo.find_commit(
|
||||||
reference.target().ok_or_else(|| {
|
reference.target().ok_or_else(|| {
|
||||||
|
|
|
||||||
|
|
@ -334,6 +334,10 @@ impl App {
|
||||||
flags.insert(NeedsUpdate::ALL);
|
flags.insert(NeedsUpdate::ALL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Action::ResetHunk(path, hash) => {
|
||||||
|
sync::reset_hunk(CWD, path, hash)?;
|
||||||
|
flags.insert(NeedsUpdate::ALL);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
InternalEvent::ConfirmAction(action) => {
|
InternalEvent::ConfirmAction(action) => {
|
||||||
self.reset.open(action)?;
|
self.reset.open(action)?;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use super::{CommandBlocking, DrawableComponent, ScrollType};
|
||||||
use crate::{
|
use crate::{
|
||||||
components::{CommandInfo, Component},
|
components::{CommandInfo, Component},
|
||||||
keys,
|
keys,
|
||||||
queue::{InternalEvent, Queue},
|
queue::{Action, InternalEvent, Queue},
|
||||||
strings,
|
strings,
|
||||||
ui::{calc_scroll_top, style::Theme},
|
ui::{calc_scroll_top, style::Theme},
|
||||||
};
|
};
|
||||||
|
|
@ -284,9 +284,32 @@ impl DiffComponent {
|
||||||
Ok(())
|
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 {
|
fn is_immutable(&self) -> bool {
|
||||||
self.queue.is_none()
|
self.queue.is_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fn is_stage(&self) -> bool {
|
||||||
|
self.current.is_stage
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DrawableComponent for DiffComponent {
|
impl DrawableComponent for DiffComponent {
|
||||||
|
|
@ -350,12 +373,17 @@ impl Component for DiffComponent {
|
||||||
out.push(CommandInfo::new(
|
out.push(CommandInfo::new(
|
||||||
commands::DIFF_HUNK_REMOVE,
|
commands::DIFF_HUNK_REMOVE,
|
||||||
self.selected_hunk.is_some(),
|
self.selected_hunk.is_some(),
|
||||||
self.focused && self.current.is_stage,
|
self.focused && self.is_stage(),
|
||||||
));
|
));
|
||||||
out.push(CommandInfo::new(
|
out.push(CommandInfo::new(
|
||||||
commands::DIFF_HUNK_ADD,
|
commands::DIFF_HUNK_ADD,
|
||||||
self.selected_hunk.is_some(),
|
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()?;
|
self.add_hunk()?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
keys::DIFF_RESET_HUNK
|
||||||
|
if !self.is_immutable()
|
||||||
|
&& !self.is_stage() =>
|
||||||
|
{
|
||||||
|
self.reset_hunk()?;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
_ => Ok(false),
|
_ => Ok(false),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,10 @@ impl ResetComponent {
|
||||||
strings::CONFIRM_TITLE_STASHDROP,
|
strings::CONFIRM_TITLE_STASHDROP,
|
||||||
strings::CONFIRM_MSG_STASHDROP,
|
strings::CONFIRM_MSG_STASHDROP,
|
||||||
),
|
),
|
||||||
|
Action::ResetHunk(_, _) => (
|
||||||
|
strings::CONFIRM_TITLE_RESET,
|
||||||
|
strings::CONFIRM_MSG_RESETHUNK,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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_STAGE_ALL: KeyEvent = no_mod(KeyCode::Char('a'));
|
||||||
pub const STATUS_RESET_FILE: KeyEvent =
|
pub const STATUS_RESET_FILE: KeyEvent =
|
||||||
with_mod(KeyCode::Char('D'), KeyModifiers::SHIFT);
|
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 STATUS_IGNORE_FILE: KeyEvent = no_mod(KeyCode::Char('i'));
|
||||||
pub const STASHING_SAVE: KeyEvent = no_mod(KeyCode::Char('s'));
|
pub const STASHING_SAVE: KeyEvent = no_mod(KeyCode::Char('s'));
|
||||||
pub const STASHING_TOGGLE_UNTRACKED: KeyEvent =
|
pub const STASHING_TOGGLE_UNTRACKED: KeyEvent =
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ pub struct ResetItem {
|
||||||
///
|
///
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
Reset(ResetItem),
|
Reset(ResetItem),
|
||||||
|
ResetHunk(String, u64),
|
||||||
StashDrop(CommitId),
|
StashDrop(CommitId),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ pub static CONFIRM_TITLE_RESET: &str = "Reset";
|
||||||
pub static CONFIRM_TITLE_STASHDROP: &str = "Drop";
|
pub static CONFIRM_TITLE_STASHDROP: &str = "Drop";
|
||||||
pub static CONFIRM_MSG_RESET: &str = "confirm file reset?";
|
pub static CONFIRM_MSG_RESET: &str = "confirm file reset?";
|
||||||
pub static CONFIRM_MSG_STASHDROP: &str = "confirm stash drop?";
|
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 LOG_TITLE: &str = "Commit";
|
||||||
pub static STASHLIST_TITLE: &str = "Stashes";
|
pub static STASHLIST_TITLE: &str = "Stashes";
|
||||||
|
|
@ -100,6 +101,12 @@ pub mod commands {
|
||||||
CMD_GROUP_DIFF,
|
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(
|
pub static DIFF_HUNK_REMOVE: CommandText = CommandText::new(
|
||||||
"Remove hunk [enter]",
|
"Remove hunk [enter]",
|
||||||
"removes selected hunk from stage",
|
"removes selected hunk from stage",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue