Skip to content

Commit 24da2f2

Browse files
author
Stephan Dilly
authored
Reset individual hunks (#125)
closes #11
1 parent 0cdaabf commit 24da2f2

File tree

12 files changed

+94
-11
lines changed

12 files changed

+94
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
### Added
1010
- Inspect stash commit in detail ([#121](https://github.com/extrawurst/gitui/issues/121))
11+
- Support reset/revert individual hunks ([#11](https://github.com/extrawurst/gitui/issues/11))
1112
- Commit Amend (`ctrl+a`) when in commit popup ([#89](https://github.com/extrawurst/gitui/issues/89))
1213

1314
![](assets/amend.gif)

asyncgit/src/sync/diff.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ pub(crate) fn get_diff_raw<'a>(
9191
// diff against head
9292
if let Ok(ref_head) = repo.head() {
9393
let parent = repo.find_commit(
94+
//TODO: use new NoHead Error
9495
ref_head.target().ok_or_else(|| {
9596
let name = ref_head.name().unwrap_or("??");
9697
Error::Generic(

asyncgit/src/sync/hunks.rs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,38 @@ pub fn stage_hunk(
3232
Ok(())
3333
}
3434

35+
///
36+
pub fn reset_hunk(
37+
repo_path: &str,
38+
file_path: String,
39+
hunk_hash: u64,
40+
) -> Result<()> {
41+
scope_time!("reset_hunk");
42+
43+
let repo = repo(repo_path)?;
44+
45+
let diff = get_diff_raw(&repo, &file_path, false, false)?;
46+
47+
let hunk_index = find_hunk_index(&diff, hunk_hash);
48+
if let Some(hunk_index) = hunk_index {
49+
let mut hunk_idx = 0;
50+
let mut opt = ApplyOptions::new();
51+
opt.hunk_callback(|_hunk| {
52+
let res = hunk_idx == hunk_index;
53+
hunk_idx += 1;
54+
res
55+
});
56+
57+
let diff = get_diff_raw(&repo, &file_path, false, true)?;
58+
59+
repo.apply(&diff, ApplyLocation::WorkDir, Some(&mut opt))?;
60+
61+
Ok(())
62+
} else {
63+
Err(Error::Generic("hunk not found".to_string()))
64+
}
65+
}
66+
3567
fn find_hunk_index(diff: &Diff, hunk_hash: u64) -> Option<usize> {
3668
let mut result = None;
3769

@@ -72,7 +104,6 @@ pub fn unstage_hunk(
72104
let diff_count_positive = diff.deltas().len();
73105

74106
let hunk_index = find_hunk_index(&diff, hunk_hash);
75-
76107
if hunk_index.is_none() {
77108
return Err(Error::Generic("hunk not found".to_string()));
78109
}
@@ -97,12 +128,8 @@ pub fn unstage_hunk(
97128

98129
res
99130
});
100-
if repo
101-
.apply(&diff, ApplyLocation::Index, Some(&mut opt))
102-
.is_err()
103-
{
104-
return Err(Error::Generic("apply failed".to_string()));
105-
}
131+
132+
repo.apply(&diff, ApplyLocation::Index, Some(&mut opt))?;
106133
}
107134

108135
Ok(count == 1)

asyncgit/src/sync/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub use commit_files::get_commit_files;
2323
pub use commits_info::{get_commits_info, CommitId, CommitInfo};
2424
pub use diff::get_diff_commit;
2525
pub use hooks::{hooks_commit_msg, hooks_post_commit, HookResult};
26-
pub use hunks::{stage_hunk, unstage_hunk};
26+
pub use hunks::{reset_hunk, stage_hunk, unstage_hunk};
2727
pub use ignore::add_to_ignore;
2828
pub use logwalker::LogWalker;
2929
pub use reset::{reset_stage, reset_workdir};

asyncgit/src/sync/reset.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub fn reset_stage(repo_path: &str, path: &str) -> Result<()> {
1313

1414
if let Ok(reference) = head {
1515
let obj = repo.find_object(
16+
//TODO: use NoHead error type
1617
reference.target().ok_or_else(|| {
1718
Error::Generic(
1819
"can't get reference to symbolic reference,"

asyncgit/src/sync/utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ pub fn commit(repo_path: &str, msg: &str) -> Result<Oid> {
6363
let tree_id = index.write_tree()?;
6464
let tree = repo.find_tree(tree_id)?;
6565

66+
//TODO: use NoHead error
6667
let parents = if let Ok(reference) = repo.head() {
6768
let parent = repo.find_commit(
6869
reference.target().ok_or_else(|| {

src/app.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,10 @@ impl App {
334334
flags.insert(NeedsUpdate::ALL);
335335
}
336336
}
337+
Action::ResetHunk(path, hash) => {
338+
sync::reset_hunk(CWD, path, hash)?;
339+
flags.insert(NeedsUpdate::ALL);
340+
}
337341
},
338342
InternalEvent::ConfirmAction(action) => {
339343
self.reset.open(action)?;

src/components/diff.rs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use super::{CommandBlocking, DrawableComponent, ScrollType};
22
use crate::{
33
components::{CommandInfo, Component},
44
keys,
5-
queue::{InternalEvent, Queue},
5+
queue::{Action, InternalEvent, Queue},
66
strings,
77
ui::{calc_scroll_top, style::Theme},
88
};
@@ -284,9 +284,32 @@ impl DiffComponent {
284284
Ok(())
285285
}
286286

287+
fn reset_hunk(&self) -> Result<()> {
288+
if let Some(hunk) = self.selected_hunk {
289+
let hash = self.diff.hunks[hunk].header_hash;
290+
291+
self.queue
292+
.as_ref()
293+
.expect("try using queue in immutable diff")
294+
.borrow_mut()
295+
.push_back(InternalEvent::ConfirmAction(
296+
Action::ResetHunk(
297+
self.current.path.clone(),
298+
hash,
299+
),
300+
));
301+
}
302+
303+
Ok(())
304+
}
305+
287306
fn is_immutable(&self) -> bool {
288307
self.queue.is_none()
289308
}
309+
310+
const fn is_stage(&self) -> bool {
311+
self.current.is_stage
312+
}
290313
}
291314

292315
impl DrawableComponent for DiffComponent {
@@ -350,12 +373,17 @@ impl Component for DiffComponent {
350373
out.push(CommandInfo::new(
351374
commands::DIFF_HUNK_REMOVE,
352375
self.selected_hunk.is_some(),
353-
self.focused && self.current.is_stage,
376+
self.focused && self.is_stage(),
354377
));
355378
out.push(CommandInfo::new(
356379
commands::DIFF_HUNK_ADD,
357380
self.selected_hunk.is_some(),
358-
self.focused && !self.current.is_stage,
381+
self.focused && !self.is_stage(),
382+
));
383+
out.push(CommandInfo::new(
384+
commands::DIFF_HUNK_REVERT,
385+
self.selected_hunk.is_some(),
386+
self.focused && !self.is_stage(),
359387
));
360388
}
361389

@@ -394,6 +422,13 @@ impl Component for DiffComponent {
394422
self.add_hunk()?;
395423
Ok(true)
396424
}
425+
keys::DIFF_RESET_HUNK
426+
if !self.is_immutable()
427+
&& !self.is_stage() =>
428+
{
429+
self.reset_hunk()?;
430+
Ok(true)
431+
}
397432
_ => Ok(false),
398433
};
399434
}

src/components/reset.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ impl ResetComponent {
148148
strings::CONFIRM_TITLE_STASHDROP,
149149
strings::CONFIRM_MSG_STASHDROP,
150150
),
151+
Action::ResetHunk(_, _) => (
152+
strings::CONFIRM_TITLE_RESET,
153+
strings::CONFIRM_MSG_RESETHUNK,
154+
),
151155
};
152156
}
153157

src/keys.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pub const STATUS_STAGE_FILE: KeyEvent = no_mod(KeyCode::Enter);
5050
pub const STATUS_STAGE_ALL: KeyEvent = no_mod(KeyCode::Char('a'));
5151
pub const STATUS_RESET_FILE: KeyEvent =
5252
with_mod(KeyCode::Char('D'), KeyModifiers::SHIFT);
53+
pub const DIFF_RESET_HUNK: KeyEvent = STATUS_RESET_FILE;
5354
pub const STATUS_IGNORE_FILE: KeyEvent = no_mod(KeyCode::Char('i'));
5455
pub const STASHING_SAVE: KeyEvent = no_mod(KeyCode::Char('s'));
5556
pub const STASHING_TOGGLE_UNTRACKED: KeyEvent =

0 commit comments

Comments
 (0)