From 2104ed2eff528057bbd1a1b2945d8ad37dbfbd87 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Wed, 29 Sep 2021 17:28:39 +0200 Subject: [PATCH 01/10] resolve conflicts --- asyncgit/src/sync/merge.rs | 16 +++- asyncgit/src/sync/mod.rs | 3 +- asyncgit/src/sync/rebase.rs | 165 ++++++++++++++++++++++++++++++++++-- asyncgit/src/sync/state.rs | 9 +- src/tabs/status.rs | 36 ++++++-- 5 files changed, 209 insertions(+), 20 deletions(-) diff --git a/asyncgit/src/sync/merge.rs b/asyncgit/src/sync/merge.rs index f91b33a4d6..f3242c2473 100644 --- a/asyncgit/src/sync/merge.rs +++ b/asyncgit/src/sync/merge.rs @@ -1,13 +1,16 @@ use crate::{ error::{Error, Result}, sync::{ - branch::merge_commit::commit_merge_with_head, reset_stage, - reset_workdir, utils, CommitId, + branch::merge_commit::commit_merge_with_head, + rebase::get_rebase_progress, reset_stage, reset_workdir, + utils, CommitId, }, }; use git2::{BranchType, Commit, MergeOptions, Repository}; use scopetime::scope_time; +use super::rebase::RebaseProgress; + /// pub fn mergehead_ids(repo_path: &str) -> Result> { scope_time!("mergehead_ids"); @@ -51,6 +54,15 @@ pub fn merge_branch(repo_path: &str, branch: &str) -> Result<()> { Ok(()) } +/// +pub fn rebase_progress(repo_path: &str) -> Result { + scope_time!("rebase_progress"); + + let repo = utils::repo(repo_path)?; + + get_rebase_progress(&repo) +} + /// pub fn merge_branch_repo( repo: &Repository, diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 331e3c8a8a..33e6320838 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -58,7 +58,8 @@ pub use hunks::{reset_hunk, stage_hunk, unstage_hunk}; pub use ignore::add_to_ignore; pub use logwalker::{LogWalker, LogWalkerFilter}; pub use merge::{ - abort_merge, merge_branch, merge_commit, merge_msg, mergehead_ids, + abort_merge, merge_branch, merge_commit, merge_msg, + mergehead_ids, rebase_progress, }; pub use rebase::rebase_branch; pub use remotes::{ diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index be8a700205..31d2022978 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -12,7 +12,7 @@ use super::CommitId; pub fn rebase_branch( repo_path: &str, branch: &str, -) -> Result { +) -> Result { scope_time!("rebase_branch"); let repo = utils::repo(repo_path)?; @@ -23,13 +23,13 @@ pub fn rebase_branch( fn rebase_branch_repo( repo: &Repository, branch_name: &str, -) -> Result { +) -> Result { let branch = repo.find_branch(branch_name, BranchType::Local)?; let annotated = repo.reference_to_annotated_commit(&branch.into_reference())?; - conflict_free_rebase(repo, &annotated) + rebase(repo, &annotated) } /// rebase attempt which aborts and undo's rebase if any conflict appears @@ -66,16 +66,91 @@ pub fn conflict_free_rebase( }) } +/// +#[derive(PartialEq, Debug)] +pub enum RebaseState { + /// + Finished, + /// + Conflicted, +} + +/// rebase +#[allow(dead_code)] +fn rebase( + repo: &git2::Repository, + commit: &git2::AnnotatedCommit, +) -> Result { + let mut rebase = repo.rebase(None, Some(commit), None, None)?; + let signature = + crate::sync::commit::signature_allow_undefined_name(repo)?; + + while let Some(op) = rebase.next() { + let _op = op?; + // dbg!(op.id()); + + if repo.index()?.has_conflicts() { + return Ok(RebaseState::Conflicted); + } + + rebase.commit(None, &signature, None)?; + } + + if repo.index()?.has_conflicts() { + return Ok(RebaseState::Conflicted); + } + + rebase.finish(Some(&signature))?; + + Ok(RebaseState::Finished) +} + +/// +#[derive(PartialEq, Debug)] +pub struct RebaseProgress { + /// + pub steps: usize, + /// + pub current: usize, +} + +/// +#[allow(dead_code)] +pub fn get_rebase_progress( + repo: &git2::Repository, +) -> Result { + let mut rebase = repo.open_rebase(None)?; + + let progress = RebaseProgress { + steps: rebase.len(), + current: rebase.operation_current().unwrap_or_default(), + }; + + Ok(progress) +} + +/// +#[allow(dead_code)] +pub fn abort_rebase(repo: &git2::Repository) -> Result<()> { + let mut rebase = repo.open_rebase(None)?; + + rebase.abort()?; + + Ok(()) +} + #[cfg(test)] -mod tests { +mod test_conflict_free_rebase { use crate::sync::{ checkout_branch, create_branch, - rebase::rebase_branch, + rebase::{rebase_branch, RebaseState}, repo_state, tests::{repo_init, write_commit_file}, - CommitId, RepoState, + utils, CommitId, RepoState, }; - use git2::Repository; + use git2::{BranchType, Repository}; + + use super::conflict_free_rebase; fn parent_ids(repo: &Repository, c: CommitId) -> Vec { let foo = repo @@ -88,6 +163,23 @@ mod tests { foo } + /// + fn test_rebase_branch_repo( + repo_path: &str, + branch_name: &str, + ) -> CommitId { + let repo = utils::repo(repo_path).unwrap(); + + let branch = + repo.find_branch(branch_name, BranchType::Local).unwrap(); + + let annotated = repo + .reference_to_annotated_commit(&branch.into_reference()) + .unwrap(); + + conflict_free_rebase(&repo, &annotated).unwrap() + } + #[test] fn test_smoke() { let (_td, repo) = repo_init().unwrap(); @@ -111,7 +203,7 @@ mod tests { checkout_branch(repo_path, "refs/heads/foo").unwrap(); - let r = rebase_branch(repo_path, "master").unwrap(); + let r = test_rebase_branch_repo(repo_path, "master"); assert_eq!(parent_ids(&repo, r), vec![c3]); } @@ -136,7 +228,62 @@ mod tests { let res = rebase_branch(repo_path, "master"); - assert!(res.is_err()); + assert!(matches!(res.unwrap(), RebaseState::Conflicted)); + + assert_eq!(repo_state(repo_path).unwrap(), RepoState::Rebase); + } +} + +#[cfg(test)] +mod test_rebase { + use crate::sync::{ + checkout_branch, create_branch, + rebase::{ + abort_rebase, get_rebase_progress, RebaseProgress, + RebaseState, + }, + rebase_branch, repo_state, + tests::{repo_init, write_commit_file}, + RepoState, + }; + + #[test] + fn test_conflicted_abort() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); + + write_commit_file(&repo, "test.txt", "test1", "commit1"); + + create_branch(repo_path, "foo").unwrap(); + + write_commit_file(&repo, "test.txt", "test2", "commit2"); + + checkout_branch(repo_path, "refs/heads/master").unwrap(); + + write_commit_file(&repo, "test.txt", "test3", "commit3"); + + checkout_branch(repo_path, "refs/heads/foo").unwrap(); + + assert!(get_rebase_progress(&repo).is_err()); + + // rebase + + let r = rebase_branch(repo_path, "master").unwrap(); + + assert_eq!(r, RebaseState::Conflicted); + assert_eq!(repo_state(repo_path).unwrap(), RepoState::Rebase); + assert_eq!( + get_rebase_progress(&repo).unwrap(), + RebaseProgress { + current: 0, + steps: 1 + } + ); + + // abort + + abort_rebase(&repo).unwrap(); assert_eq!(repo_state(repo_path).unwrap(), RepoState::Clean); } diff --git a/asyncgit/src/sync/state.rs b/asyncgit/src/sync/state.rs index 410f6dfafd..6d78238c04 100644 --- a/asyncgit/src/sync/state.rs +++ b/asyncgit/src/sync/state.rs @@ -10,6 +10,8 @@ pub enum RepoState { /// Merge, /// + Rebase, + /// Other, } @@ -18,6 +20,7 @@ impl From for RepoState { match state { RepositoryState::Clean => Self::Clean, RepositoryState::Merge => Self::Merge, + RepositoryState::RebaseMerge => Self::Rebase, _ => Self::Other, } } @@ -29,5 +32,9 @@ pub fn repo_state(repo_path: &str) -> Result { let repo = utils::repo(repo_path)?; - Ok(repo.state().into()) + let state = repo.state(); + + // dbg!(&state); + + Ok(state.into()) } diff --git a/src/tabs/status.rs b/src/tabs/status.rs index 517a68b1b1..2404858444 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -215,14 +215,12 @@ impl Status { } } - fn draw_repo_state( - f: &mut tui::Frame, - r: tui::layout::Rect, - ) -> Result<()> { - if let Ok(state) = sync::repo_state(CWD) { - if state != RepoState::Clean { + fn repo_state_text(state: RepoState) -> String { + match state { + RepoState::Merge => { let ids = sync::mergehead_ids(CWD).unwrap_or_default(); + let ids = format!( "({})", ids.iter() @@ -231,7 +229,31 @@ impl Status { )) .join(",") ); - let txt = format!("{:?} {}", state, ids); + + format!("{:?} {}", state, ids) + } + RepoState::Rebase => { + let progress = + if let Ok(p) = sync::rebase_progress(CWD) { + format!("{}/{}", p.current + 1, p.steps) + } else { + String::new() + }; + + format!("{:?} ({})", state, progress) + } + _ => format!("{:?}", state), + } + } + + fn draw_repo_state( + f: &mut tui::Frame, + r: tui::layout::Rect, + ) -> Result<()> { + if let Ok(state) = sync::repo_state(CWD) { + if state != RepoState::Clean { + let txt = Self::repo_state_text(state); + let txt_len = u16::try_from(txt.len())?; let w = Paragraph::new(txt) .style(Style::default().fg(Color::Red)) From 7804e4f7f070ca15e100578558de57bc440fa940 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Wed, 29 Sep 2021 12:41:08 +0200 Subject: [PATCH 02/10] less commands depending on context --- src/components/changes.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/components/changes.rs b/src/components/changes.rs index 42ece875b0..311672d755 100644 --- a/src/components/changes.rs +++ b/src/components/changes.rs @@ -192,40 +192,40 @@ impl Component for ChangesComponent { if self.is_working_dir { out.push(CommandInfo::new( strings::commands::stage_all(&self.key_config), - some_selection, - self.focused(), + true, + some_selection && self.focused(), )); out.push(CommandInfo::new( strings::commands::stage_item(&self.key_config), - some_selection, - self.focused(), + true, + some_selection && self.focused(), )); out.push(CommandInfo::new( strings::commands::reset_item(&self.key_config), - some_selection, - self.focused(), + true, + some_selection && self.focused(), )); out.push(CommandInfo::new( strings::commands::ignore_item(&self.key_config), - some_selection, - self.focused(), + true, + some_selection && self.focused(), )); } else { out.push(CommandInfo::new( strings::commands::unstage_item(&self.key_config), - some_selection, - self.focused(), + true, + some_selection && self.focused(), )); out.push(CommandInfo::new( strings::commands::unstage_all(&self.key_config), - some_selection, - self.focused(), + true, + some_selection && self.focused(), )); out.push( CommandInfo::new( strings::commands::commit_open(&self.key_config), - !self.is_empty(), - self.focused() || force_all, + true, + (!self.is_empty() && self.focused()) || force_all, ) .order(-1), ); From 58397b74f99b85b6416e5f61e3763f425e3fd99a Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Wed, 29 Sep 2021 17:30:44 +0200 Subject: [PATCH 03/10] fix conflicts --- asyncgit/src/sync/merge.rs | 17 ++++++++++++++--- asyncgit/src/sync/mod.rs | 4 ++-- asyncgit/src/sync/rebase.rs | 36 +++++++++++++++++++++++++++++++++--- src/strings.rs | 13 +++++++++++++ src/tabs/status.rs | 26 ++++++++++++++++++++++++++ 5 files changed, 88 insertions(+), 8 deletions(-) diff --git a/asyncgit/src/sync/merge.rs b/asyncgit/src/sync/merge.rs index f3242c2473..6b1522a758 100644 --- a/asyncgit/src/sync/merge.rs +++ b/asyncgit/src/sync/merge.rs @@ -2,14 +2,14 @@ use crate::{ error::{Error, Result}, sync::{ branch::merge_commit::commit_merge_with_head, - rebase::get_rebase_progress, reset_stage, reset_workdir, - utils, CommitId, + rebase::{continue_rebase, get_rebase_progress}, + reset_stage, reset_workdir, utils, CommitId, }, }; use git2::{BranchType, Commit, MergeOptions, Repository}; use scopetime::scope_time; -use super::rebase::RebaseProgress; +use super::rebase::{RebaseProgress, RebaseState}; /// pub fn mergehead_ids(repo_path: &str) -> Result> { @@ -63,6 +63,17 @@ pub fn rebase_progress(repo_path: &str) -> Result { get_rebase_progress(&repo) } +/// +pub fn continue_pending_rebase( + repo_path: &str, +) -> Result { + scope_time!("continue_pending_rebase"); + + let repo = utils::repo(repo_path)?; + + continue_rebase(&repo) +} + /// pub fn merge_branch_repo( repo: &Repository, diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 33e6320838..bab52e15e3 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -58,8 +58,8 @@ pub use hunks::{reset_hunk, stage_hunk, unstage_hunk}; pub use ignore::add_to_ignore; pub use logwalker::{LogWalker, LogWalkerFilter}; pub use merge::{ - abort_merge, merge_branch, merge_commit, merge_msg, - mergehead_ids, rebase_progress, + abort_merge, continue_pending_rebase, merge_branch, merge_commit, + merge_msg, mergehead_ids, rebase_progress, }; pub use rebase::rebase_branch; pub use remotes::{ diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index 31d2022978..4e2ed1a32d 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -76,8 +76,7 @@ pub enum RebaseState { } /// rebase -#[allow(dead_code)] -fn rebase( +pub fn rebase( repo: &git2::Repository, commit: &git2::AnnotatedCommit, ) -> Result { @@ -105,6 +104,38 @@ fn rebase( Ok(RebaseState::Finished) } +/// continue pending rebase +pub fn continue_rebase( + repo: &git2::Repository, +) -> Result { + if repo.index()?.has_conflicts() { + return Ok(RebaseState::Conflicted); + } + + let mut rebase = repo.open_rebase(None)?; + let signature = + crate::sync::commit::signature_allow_undefined_name(repo)?; + + while let Some(op) = rebase.next() { + let _op = op?; + // dbg!(op.id()); + + if repo.index()?.has_conflicts() { + return Ok(RebaseState::Conflicted); + } + + rebase.commit(None, &signature, None)?; + } + + if repo.index()?.has_conflicts() { + return Ok(RebaseState::Conflicted); + } + + rebase.finish(Some(&signature))?; + + Ok(RebaseState::Finished) +} + /// #[derive(PartialEq, Debug)] pub struct RebaseProgress { @@ -115,7 +146,6 @@ pub struct RebaseProgress { } /// -#[allow(dead_code)] pub fn get_rebase_progress( repo: &git2::Repository, ) -> Result { diff --git a/src/strings.rs b/src/strings.rs index fcc45bc800..d41195173b 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -628,6 +628,19 @@ pub mod commands { CMD_GROUP_GENERAL, ) } + + pub fn continue_rebase( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Continue rebase [{}]", + key_config.get_hint(key_config.rebase_branch), + ), + "continue ongoing rebase", + CMD_GROUP_GENERAL, + ) + } pub fn select_staging( key_config: &SharedKeyConfig, ) -> CommandText { diff --git a/src/tabs/status.rs b/src/tabs/status.rs index 2404858444..b071a567a0 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -541,10 +541,23 @@ impl Status { == RepoState::Merge } + fn pending_rebase() -> bool { + sync::repo_state(CWD).unwrap_or(RepoState::Clean) + == RepoState::Rebase + } + pub fn abort_merge(&self) { try_or_popup!(self, "abort merge", sync::abort_merge(CWD)); } + fn continue_rebase(&self) { + try_or_popup!( + self, + "continue rebase", + sync::continue_pending_rebase(CWD) + ); + } + fn commands_nav( &self, out: &mut Vec, @@ -642,6 +655,11 @@ impl Component for Status { true, Self::can_abort_merge() || force_all, )); + out.push(CommandInfo::new( + strings::commands::continue_rebase(&self.key_config), + true, + Self::pending_rebase() || force_all, + )); } { @@ -747,6 +765,14 @@ impl Component for Status { Action::AbortMerge, )); + Ok(EventState::Consumed) + } else if k == self.key_config.rebase_branch + && Self::pending_rebase() + { + self.continue_rebase(); + self.queue.push(InternalEvent::Update( + NeedsUpdate::ALL, + )); Ok(EventState::Consumed) } else { Ok(EventState::NotConsumed) From 51e37a1bb9f1c5ccc7a77e3e680196a62238e2c7 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Wed, 29 Sep 2021 14:34:05 +0200 Subject: [PATCH 04/10] clippy fixes --- src/tabs/status.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tabs/status.rs b/src/tabs/status.rs index b071a567a0..f5456957b7 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -215,7 +215,7 @@ impl Status { } } - fn repo_state_text(state: RepoState) -> String { + fn repo_state_text(state: &RepoState) -> String { match state { RepoState::Merge => { let ids = @@ -252,7 +252,7 @@ impl Status { ) -> Result<()> { if let Ok(state) = sync::repo_state(CWD) { if state != RepoState::Clean { - let txt = Self::repo_state_text(state); + let txt = Self::repo_state_text(&state); let txt_len = u16::try_from(txt.len())?; let w = Paragraph::new(txt) From d1efa04a126d578c7a4753c8e8311b58335a0880 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Wed, 29 Sep 2021 17:31:51 +0200 Subject: [PATCH 05/10] fix conflict --- asyncgit/src/sync/merge.rs | 13 ++++++++++++- asyncgit/src/sync/mod.rs | 5 +++-- asyncgit/src/sync/rebase.rs | 1 - src/app.rs | 4 ++++ src/components/reset.rs | 4 ++++ src/keys.rs | 2 +- src/queue.rs | 1 + src/strings.rs | 19 +++++++++++++++++++ src/tabs/status.rs | 22 ++++++++++++++++++++++ 9 files changed, 66 insertions(+), 5 deletions(-) diff --git a/asyncgit/src/sync/merge.rs b/asyncgit/src/sync/merge.rs index 6b1522a758..10d74d6415 100644 --- a/asyncgit/src/sync/merge.rs +++ b/asyncgit/src/sync/merge.rs @@ -2,7 +2,9 @@ use crate::{ error::{Error, Result}, sync::{ branch::merge_commit::commit_merge_with_head, - rebase::{continue_rebase, get_rebase_progress}, + rebase::{ + abort_rebase, continue_rebase, get_rebase_progress, + }, reset_stage, reset_workdir, utils, CommitId, }, }; @@ -74,6 +76,15 @@ pub fn continue_pending_rebase( continue_rebase(&repo) } +/// +pub fn abort_pending_rebase(repo_path: &str) -> Result<()> { + scope_time!("abort_pending_rebase"); + + let repo = utils::repo(repo_path)?; + + abort_rebase(&repo) +} + /// pub fn merge_branch_repo( repo: &Repository, diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index bab52e15e3..2eb32bb34b 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -58,8 +58,9 @@ pub use hunks::{reset_hunk, stage_hunk, unstage_hunk}; pub use ignore::add_to_ignore; pub use logwalker::{LogWalker, LogWalkerFilter}; pub use merge::{ - abort_merge, continue_pending_rebase, merge_branch, merge_commit, - merge_msg, mergehead_ids, rebase_progress, + abort_merge, abort_pending_rebase, continue_pending_rebase, + merge_branch, merge_commit, merge_msg, mergehead_ids, + rebase_progress, }; pub use rebase::rebase_branch; pub use remotes::{ diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index 4e2ed1a32d..c90f2478e0 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -160,7 +160,6 @@ pub fn get_rebase_progress( } /// -#[allow(dead_code)] pub fn abort_rebase(repo: &git2::Repository) -> Result<()> { let mut rebase = repo.open_rebase(None)?; diff --git a/src/app.rs b/src/app.rs index c47ed3bc11..58f68876bd 100644 --- a/src/app.rs +++ b/src/app.rs @@ -821,6 +821,10 @@ impl App { self.status_tab.abort_merge(); flags.insert(NeedsUpdate::ALL); } + Action::AbortRebase => { + self.status_tab.abort_rebase(); + flags.insert(NeedsUpdate::ALL); + } }; Ok(()) diff --git a/src/components/reset.rs b/src/components/reset.rs index 0ff0a704f0..e389afb851 100644 --- a/src/components/reset.rs +++ b/src/components/reset.rs @@ -200,6 +200,10 @@ impl ConfirmComponent { Action::AbortMerge => ( strings::confirm_title_abortmerge(), strings::confirm_msg_abortmerge(), + ), + Action::AbortRebase => ( + strings::confirm_title_abortrebase(), + strings::confirm_msg_abortrebase(), ), }; } diff --git a/src/keys.rs b/src/keys.rs index dd6c2cffae..0b6a78a39d 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -158,7 +158,7 @@ impl Default for KeyConfig { force_push: KeyEvent { code: KeyCode::Char('P'), modifiers: KeyModifiers::SHIFT}, undo_commit: KeyEvent { code: KeyCode::Char('U'), modifiers: KeyModifiers::SHIFT}, pull: KeyEvent { code: KeyCode::Char('f'), modifiers: KeyModifiers::empty()}, - abort_merge: KeyEvent { code: KeyCode::Char('M'), modifiers: KeyModifiers::SHIFT}, + abort_merge: KeyEvent { code: KeyCode::Char('A'), modifiers: KeyModifiers::SHIFT}, open_file_tree: KeyEvent { code: KeyCode::Char('F'), modifiers: KeyModifiers::SHIFT}, file_find: KeyEvent { code: KeyCode::Char('f'), modifiers: KeyModifiers::empty()}, } diff --git a/src/queue.rs b/src/queue.rs index 94cc43ab46..8bd43b4dab 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -41,6 +41,7 @@ pub enum Action { ForcePush(String, bool), PullMerge { incoming: usize, rebase: bool }, AbortMerge, + AbortRebase, } /// diff --git a/src/strings.rs b/src/strings.rs index d41195173b..0ed98e03cd 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -153,6 +153,13 @@ pub fn confirm_msg_abortmerge() -> String { "This will revert all uncommitted changes. Are you sure?" .to_string() } +pub fn confirm_title_abortrebase() -> String { + "Abort rebase?".to_string() +} +pub fn confirm_msg_abortrebase() -> String { + "This will revert all uncommitted changes. Are you sure?" + .to_string() +} pub fn confirm_msg_reset() -> String { "confirm file reset?".to_string() } @@ -641,6 +648,18 @@ pub mod commands { CMD_GROUP_GENERAL, ) } + + pub fn abort_rebase(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Abort rebase [{}]", + key_config.get_hint(key_config.abort_merge), + ), + "abort ongoing rebase", + CMD_GROUP_GENERAL, + ) + } + pub fn select_staging( key_config: &SharedKeyConfig, ) -> CommandText { diff --git a/src/tabs/status.rs b/src/tabs/status.rs index f5456957b7..3d834ec914 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -550,6 +550,14 @@ impl Status { try_or_popup!(self, "abort merge", sync::abort_merge(CWD)); } + pub fn abort_rebase(&self) { + try_or_popup!( + self, + "abort rebase", + sync::abort_pending_rebase(CWD) + ); + } + fn continue_rebase(&self) { try_or_popup!( self, @@ -655,11 +663,17 @@ impl Component for Status { true, Self::can_abort_merge() || force_all, )); + out.push(CommandInfo::new( strings::commands::continue_rebase(&self.key_config), true, Self::pending_rebase() || force_all, )); + out.push(CommandInfo::new( + strings::commands::abort_rebase(&self.key_config), + true, + Self::pending_rebase() || force_all, + )); } { @@ -765,6 +779,14 @@ impl Component for Status { Action::AbortMerge, )); + Ok(EventState::Consumed) + } else if k == self.key_config.abort_merge + && Self::pending_rebase() + { + self.queue.push(InternalEvent::ConfirmAction( + Action::AbortRebase, + )); + Ok(EventState::Consumed) } else if k == self.key_config.rebase_branch && Self::pending_rebase() From d1d47c4343057a94333ff4867ecd2a574ebc06fa Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Wed, 29 Sep 2021 16:52:07 +0200 Subject: [PATCH 06/10] fix cklippy --- src/tabs/status.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tabs/status.rs b/src/tabs/status.rs index 3d834ec914..4f32baa75a 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -693,6 +693,7 @@ impl Component for Status { visibility_blocking(self) } + #[allow(clippy::too_many_lines)] fn event( &mut self, ev: crossterm::event::Event, From dac02148aa73c603e0519cc83a4160a8e400e02b Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Wed, 29 Sep 2021 18:08:45 +0200 Subject: [PATCH 07/10] preset rebase commit msg --- asyncgit/src/sync/rebase.rs | 14 +++++++-- src/components/commit.rs | 62 +++++++++++++++++++++++++------------ src/strings.rs | 3 ++ src/tabs/status.rs | 12 +++++-- 4 files changed, 68 insertions(+), 23 deletions(-) diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index c90f2478e0..3961eef53a 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -143,6 +143,8 @@ pub struct RebaseProgress { pub steps: usize, /// pub current: usize, + /// + pub current_commit: Option, } /// @@ -151,9 +153,15 @@ pub fn get_rebase_progress( ) -> Result { let mut rebase = repo.open_rebase(None)?; + let current_commit: Option = rebase + .operation_current() + .and_then(|idx| rebase.nth(idx)) + .map(|op| op.id().into()); + let progress = RebaseProgress { steps: rebase.len(), current: rebase.operation_current().unwrap_or_default(), + current_commit, }; Ok(progress) @@ -286,7 +294,8 @@ mod test_rebase { create_branch(repo_path, "foo").unwrap(); - write_commit_file(&repo, "test.txt", "test2", "commit2"); + let c = + write_commit_file(&repo, "test.txt", "test2", "commit2"); checkout_branch(repo_path, "refs/heads/master").unwrap(); @@ -306,7 +315,8 @@ mod test_rebase { get_rebase_progress(&repo).unwrap(), RebaseProgress { current: 0, - steps: 1 + steps: 1, + current_commit: Some(c) } ); diff --git a/src/components/commit.rs b/src/components/commit.rs index 851bbb1f75..938c138c2e 100644 --- a/src/components/commit.rs +++ b/src/components/commit.rs @@ -13,7 +13,8 @@ use anyhow::Result; use asyncgit::{ cached, sync::{ - self, get_config_string, CommitId, HookResult, RepoState, + self, get_commit_info, get_config_string, CommitId, + HookResult, RepoState, }, CWD, }; @@ -254,6 +255,19 @@ impl CommitComponent { != self.commit_template.as_ref().map(|s| s.trim()) } + fn rebase_commit_msg() -> Result { + let progress = sync::rebase_progress(CWD)?; + + let id = if let Some(id) = progress.current_commit { + id + } else { + anyhow::bail!("no commit id") + }; + + let info = get_commit_info(CWD, &id)?; + Ok(info.message) + } + fn amend(&mut self) -> Result<()> { if self.can_amend() { let id = sync::get_head(CWD)?; @@ -369,26 +383,36 @@ impl Component for CommitComponent { self.mode = Mode::Normal; - self.mode = if sync::repo_state(CWD)? == RepoState::Merge { - let ids = sync::mergehead_ids(CWD)?; - self.input.set_title(strings::commit_title_merge()); - self.input.set_text(sync::merge_msg(CWD)?); - Mode::Merge(ids) - } else { - self.commit_template = - get_config_string(CWD, "commit.template") - .ok() - .flatten() - .and_then(|path| read_to_string(path).ok()); - - if self.is_empty() { - if let Some(s) = &self.commit_template { - self.input.set_text(s.clone()); - } + self.mode = match sync::repo_state(CWD)? { + RepoState::Merge => { + let ids = sync::mergehead_ids(CWD)?; + self.input.set_title(strings::commit_title_merge()); + self.input.set_text(sync::merge_msg(CWD)?); + Mode::Merge(ids) + } + RepoState::Rebase => { + self.input.set_title(strings::commit_title_rebase()); + self.input.set_text( + Self::rebase_commit_msg().unwrap_or_default(), + ); + Mode::Normal } + _ => { + self.commit_template = + get_config_string(CWD, "commit.template") + .ok() + .flatten() + .and_then(|path| read_to_string(path).ok()); + + if self.is_empty() { + if let Some(s) = &self.commit_template { + self.input.set_text(s.clone()); + } + } - self.input.set_title(strings::commit_title()); - Mode::Normal + self.input.set_title(strings::commit_title()); + Mode::Normal + } }; self.input.show()?; diff --git a/src/strings.rs b/src/strings.rs index 0ed98e03cd..e1be096d07 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -86,6 +86,9 @@ pub fn commit_title() -> String { pub fn commit_title_merge() -> String { "Commit (Merge)".to_string() } +pub fn commit_title_rebase() -> String { + "Commit (Rebase)".to_string() +} pub fn commit_title_amend() -> String { "Commit (Amend)".to_string() } diff --git a/src/tabs/status.rs b/src/tabs/status.rs index 4f32baa75a..52ef1a059b 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -14,8 +14,8 @@ use crate::{ use anyhow::Result; use asyncgit::{ cached, - sync::BranchCompare, sync::{self, status::StatusType, RepoState}, + sync::{BranchCompare, CommitId}, AsyncDiff, AsyncGitNotification, AsyncStatus, DiffParams, DiffType, StatusParams, CWD, }; @@ -235,7 +235,15 @@ impl Status { RepoState::Rebase => { let progress = if let Ok(p) = sync::rebase_progress(CWD) { - format!("{}/{}", p.current + 1, p.steps) + format!( + "[{}] {}/{}", + p.current_commit + .as_ref() + .map(CommitId::get_short_string) + .unwrap_or_default(), + p.current + 1, + p.steps + ) } else { String::new() }; From 42ca073a9ad3ffe01d30eacac4d93624752db947 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Wed, 29 Sep 2021 18:35:24 +0200 Subject: [PATCH 08/10] dont allow commit when in rebase --- src/components/changes.rs | 16 +--------- src/components/commit.rs | 62 ++++++++++++--------------------------- src/strings.rs | 3 -- src/tabs/status.rs | 27 +++++++++++++++-- 4 files changed, 45 insertions(+), 63 deletions(-) diff --git a/src/components/changes.rs b/src/components/changes.rs index 311672d755..86fa801362 100644 --- a/src/components/changes.rs +++ b/src/components/changes.rs @@ -221,14 +221,6 @@ impl Component for ChangesComponent { true, some_selection && self.focused(), )); - out.push( - CommandInfo::new( - strings::commands::commit_open(&self.key_config), - true, - (!self.is_empty() && self.focused()) || force_all, - ) - .order(-1), - ); } CommandBlocking::PassingOn @@ -241,13 +233,7 @@ impl Component for ChangesComponent { if self.focused() { if let Event::Key(e) = ev { - return if e == self.key_config.open_commit - && !self.is_working_dir - && !self.is_empty() - { - self.queue.push(InternalEvent::OpenCommit); - Ok(EventState::Consumed) - } else if e == self.key_config.enter { + return if e == self.key_config.enter { try_or_popup!( self, "staging error:", diff --git a/src/components/commit.rs b/src/components/commit.rs index 938c138c2e..851bbb1f75 100644 --- a/src/components/commit.rs +++ b/src/components/commit.rs @@ -13,8 +13,7 @@ use anyhow::Result; use asyncgit::{ cached, sync::{ - self, get_commit_info, get_config_string, CommitId, - HookResult, RepoState, + self, get_config_string, CommitId, HookResult, RepoState, }, CWD, }; @@ -255,19 +254,6 @@ impl CommitComponent { != self.commit_template.as_ref().map(|s| s.trim()) } - fn rebase_commit_msg() -> Result { - let progress = sync::rebase_progress(CWD)?; - - let id = if let Some(id) = progress.current_commit { - id - } else { - anyhow::bail!("no commit id") - }; - - let info = get_commit_info(CWD, &id)?; - Ok(info.message) - } - fn amend(&mut self) -> Result<()> { if self.can_amend() { let id = sync::get_head(CWD)?; @@ -383,36 +369,26 @@ impl Component for CommitComponent { self.mode = Mode::Normal; - self.mode = match sync::repo_state(CWD)? { - RepoState::Merge => { - let ids = sync::mergehead_ids(CWD)?; - self.input.set_title(strings::commit_title_merge()); - self.input.set_text(sync::merge_msg(CWD)?); - Mode::Merge(ids) - } - RepoState::Rebase => { - self.input.set_title(strings::commit_title_rebase()); - self.input.set_text( - Self::rebase_commit_msg().unwrap_or_default(), - ); - Mode::Normal - } - _ => { - self.commit_template = - get_config_string(CWD, "commit.template") - .ok() - .flatten() - .and_then(|path| read_to_string(path).ok()); - - if self.is_empty() { - if let Some(s) = &self.commit_template { - self.input.set_text(s.clone()); - } + self.mode = if sync::repo_state(CWD)? == RepoState::Merge { + let ids = sync::mergehead_ids(CWD)?; + self.input.set_title(strings::commit_title_merge()); + self.input.set_text(sync::merge_msg(CWD)?); + Mode::Merge(ids) + } else { + self.commit_template = + get_config_string(CWD, "commit.template") + .ok() + .flatten() + .and_then(|path| read_to_string(path).ok()); + + if self.is_empty() { + if let Some(s) = &self.commit_template { + self.input.set_text(s.clone()); } - - self.input.set_title(strings::commit_title()); - Mode::Normal } + + self.input.set_title(strings::commit_title()); + Mode::Normal }; self.input.show()?; diff --git a/src/strings.rs b/src/strings.rs index e1be096d07..0ed98e03cd 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -86,9 +86,6 @@ pub fn commit_title() -> String { pub fn commit_title_merge() -> String { "Commit (Merge)".to_string() } -pub fn commit_title_rebase() -> String { - "Commit (Rebase)".to_string() -} pub fn commit_title_amend() -> String { "Commit (Amend)".to_string() } diff --git a/src/tabs/status.rs b/src/tabs/status.rs index 52ef1a059b..14c4a3bb01 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -617,6 +617,12 @@ impl Status { .order(strings::order::NAV), ); } + + fn can_commit(&self) -> bool { + self.index.focused() + && !self.index.is_empty() + && !Self::pending_rebase() + } } impl Component for Status { @@ -634,6 +640,17 @@ impl Component for Status { self.components().as_slice(), ); + let can_commit = + self.index.focused() && !self.index.is_empty(); + out.push( + CommandInfo::new( + strings::commands::commit_open(&self.key_config), + true, + can_commit || force_all, + ) + .order(-1), + ); + out.push(CommandInfo::new( strings::commands::open_branch_select_popup( &self.key_config, @@ -663,7 +680,8 @@ impl Component for Status { out.push(CommandInfo::new( strings::commands::undo_commit(&self.key_config), true, - !focus_on_diff, + (!Self::pending_rebase() && !focus_on_diff) + || force_all, )); out.push(CommandInfo::new( @@ -701,7 +719,7 @@ impl Component for Status { visibility_blocking(self) } - #[allow(clippy::too_many_lines)] + #[allow(clippy::too_many_lines, clippy::cognitive_complexity)] fn event( &mut self, ev: crossterm::event::Event, @@ -727,6 +745,11 @@ impl Component for Status { ); } Ok(EventState::Consumed) + } else if k == self.key_config.open_commit + && self.can_commit() + { + self.queue.push(InternalEvent::OpenCommit); + Ok(EventState::Consumed) } else if k == self.key_config.toggle_workarea && !self.is_focus_on_diff() { From 30d7752b998d486619473a5d8b124c1efa3d5c5a Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Wed, 29 Sep 2021 18:39:56 +0200 Subject: [PATCH 09/10] commit pending rebase on continue --- asyncgit/src/sync/rebase.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index 3961eef53a..b98bda0814 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -108,13 +108,18 @@ pub fn rebase( pub fn continue_rebase( repo: &git2::Repository, ) -> Result { + let mut rebase = repo.open_rebase(None)?; + let signature = + crate::sync::commit::signature_allow_undefined_name(repo)?; + if repo.index()?.has_conflicts() { return Ok(RebaseState::Conflicted); } - let mut rebase = repo.open_rebase(None)?; - let signature = - crate::sync::commit::signature_allow_undefined_name(repo)?; + // try commit current rebase step + if !repo.index()?.is_empty() { + rebase.commit(None, &signature, None)?; + } while let Some(op) = rebase.next() { let _op = op?; From 1c575532dd99f4ffddc65d3b75466521026c9a64 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Wed, 29 Sep 2021 18:44:50 +0200 Subject: [PATCH 10/10] do not show commit if not available --- src/tabs/status.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/tabs/status.rs b/src/tabs/status.rs index 14c4a3bb01..51606bb06c 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -640,13 +640,11 @@ impl Component for Status { self.components().as_slice(), ); - let can_commit = - self.index.focused() && !self.index.is_empty(); out.push( CommandInfo::new( strings::commands::commit_open(&self.key_config), true, - can_commit || force_all, + self.can_commit() || force_all, ) .order(-1), );