From a8654329ecdaaa480ddeeca9a007f3c6572d7065 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Thu, 26 Aug 2021 20:31:37 +0200 Subject: [PATCH] allow rebase of a branch (#816) --- asyncgit/src/error.rs | 3 + asyncgit/src/sync/branch/merge_rebase.rs | 31 +--- asyncgit/src/sync/merge.rs | 28 +++ asyncgit/src/sync/mod.rs | 4 +- asyncgit/src/sync/rebase.rs | 28 +++ src/components/branchlist.rs | 210 +++++++++++++---------- src/keys.rs | 2 + src/strings.rs | 13 ++ vim_style_key_config.ron | 1 + 9 files changed, 201 insertions(+), 119 deletions(-) create mode 100644 asyncgit/src/sync/rebase.rs diff --git a/asyncgit/src/error.rs b/asyncgit/src/error.rs index d6e00d76..fe7bc214 100644 --- a/asyncgit/src/error.rs +++ b/asyncgit/src/error.rs @@ -11,6 +11,9 @@ pub enum Error { #[error("git: no head found")] NoHead, + #[error("git: conflict during rebase")] + RebaseConflict, + #[error("git: remote url not found")] UnknownRemote, diff --git a/asyncgit/src/sync/branch/merge_rebase.rs b/asyncgit/src/sync/branch/merge_rebase.rs index eb920094..7c8ac7ef 100644 --- a/asyncgit/src/sync/branch/merge_rebase.rs +++ b/asyncgit/src/sync/branch/merge_rebase.rs @@ -2,7 +2,7 @@ use crate::{ error::{Error, Result}, - sync::utils, + sync::{rebase::conflict_free_rebase, utils}, }; use git2::BranchType; use scopetime::scope_time; @@ -27,34 +27,7 @@ pub fn merge_upstream_rebase( let annotated_upstream = repo.find_annotated_commit(upstream_commit.id())?; - let mut rebase = - repo.rebase(None, Some(&annotated_upstream), 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() { - rebase.abort()?; - return Err(Error::Generic(String::from( - "conflicts while merging", - ))); - } - - rebase.commit(None, &signature, None)?; - } - - if repo.index()?.has_conflicts() { - rebase.abort()?; - return Err(Error::Generic(String::from( - "conflicts while merging", - ))); - } - - rebase.finish(Some(&signature))?; + conflict_free_rebase(&repo, &annotated_upstream)?; Ok(()) } diff --git a/asyncgit/src/sync/merge.rs b/asyncgit/src/sync/merge.rs index f91b33a4..c9a5b541 100644 --- a/asyncgit/src/sync/merge.rs +++ b/asyncgit/src/sync/merge.rs @@ -8,6 +8,8 @@ use crate::{ use git2::{BranchType, Commit, MergeOptions, Repository}; use scopetime::scope_time; +use super::rebase::conflict_free_rebase; + /// pub fn mergehead_ids(repo_path: &str) -> Result> { scope_time!("mergehead_ids"); @@ -51,6 +53,17 @@ pub fn merge_branch(repo_path: &str, branch: &str) -> Result<()> { Ok(()) } +/// +pub fn rebase_branch(repo_path: &str, branch: &str) -> Result<()> { + scope_time!("rebase_branch"); + + let repo = utils::repo(repo_path)?; + + rebase_branch_repo(&repo, branch)?; + + Ok(()) +} + /// pub fn merge_branch_repo( repo: &Repository, @@ -75,6 +88,21 @@ pub fn merge_branch_repo( Ok(()) } +/// +pub fn rebase_branch_repo( + repo: &Repository, + branch_name: &str, +) -> 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)?; + + Ok(()) +} + /// pub fn merge_msg(repo_path: &str) -> Result { scope_time!("merge_msg"); diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 58b128fb..1c8df9dd 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -18,6 +18,7 @@ mod ignore; mod logwalker; mod merge; mod patches; +mod rebase; pub mod remotes; mod reset; mod staging; @@ -57,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_branch, }; pub use remotes::{ get_default_remote, get_remotes, push::AsyncProgress, diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs new file mode 100644 index 00000000..cece9bff --- /dev/null +++ b/asyncgit/src/sync/rebase.rs @@ -0,0 +1,28 @@ +use crate::error::{Error, Result}; + +/// rebase attempt which aborts and undo's rebase if any conflict appears +pub fn conflict_free_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() { + rebase.abort()?; + return Err(Error::RebaseConflict); + } + + rebase.commit(None, &signature, None)?; + } + if repo.index()?.has_conflicts() { + rebase.abort()?; + return Err(Error::RebaseConflict); + } + rebase.finish(Some(&signature))?; + Ok(()) +} diff --git a/src/components/branchlist.rs b/src/components/branchlist.rs index 1922f4d3..868061af 100644 --- a/src/components/branchlist.rs +++ b/src/components/branchlist.rs @@ -180,6 +180,14 @@ impl Component for BranchListComponent { self.local, )); + out.push(CommandInfo::new( + strings::commands::branch_popup_rebase( + &self.key_config, + ), + !self.selection_is_cur_branch(), + self.local, + )); + out.push(CommandInfo::new( strings::commands::rename_branch_popup( &self.key_config, @@ -191,100 +199,91 @@ impl Component for BranchListComponent { visibility_blocking(self) } + //TODO: cleanup + #[allow(clippy::cognitive_complexity)] fn event(&mut self, ev: Event) -> Result { - if self.visible { - if let Event::Key(e) = ev { - if e == self.key_config.exit_popup { - self.hide(); - } else if e == self.key_config.move_down { - return self - .move_selection(ScrollType::Up) - .map(Into::into); - } else if e == self.key_config.move_up { - return self - .move_selection(ScrollType::Down) - .map(Into::into); - } else if e == self.key_config.page_down { - return self - .move_selection(ScrollType::PageDown) - .map(Into::into); - } else if e == self.key_config.page_up { - return self - .move_selection(ScrollType::PageUp) - .map(Into::into); - } else if e == self.key_config.tab_toggle { - self.local = !self.local; - self.update_branches()?; - } else if e == self.key_config.enter { - try_or_popup!( - self, - "switch branch error:", - self.switch_to_selected_branch() - ); - } else if e == self.key_config.create_branch - && self.local - { - self.queue.push(InternalEvent::CreateBranch); - } else if e == self.key_config.rename_branch - && self.valid_selection() - { - let cur_branch = - &self.branches[self.selection as usize]; - self.queue.push(InternalEvent::RenameBranch( - cur_branch.reference.clone(), - cur_branch.name.clone(), - )); + if !self.visible { + return Ok(EventState::NotConsumed); + } - self.update_branches()?; - } else if e == self.key_config.delete_branch - && !self.selection_is_cur_branch() - && self.valid_selection() - { - self.queue.push(InternalEvent::ConfirmAction( - Action::DeleteBranch( - self.branches[self.selection as usize] - .reference - .clone(), - self.local, - ), - )); - } else if e == self.key_config.merge_branch - && !self.selection_is_cur_branch() - && self.valid_selection() - { - try_or_popup!( - self, - "merge branch error:", - self.merge_branch() - ); - self.queue.push(InternalEvent::Update( - NeedsUpdate::ALL, - )); - } else if e == self.key_config.move_right - && self.valid_selection() - { - self.hide(); - if let Some(b) = self.get_selected() { - self.queue.push( - InternalEvent::InspectCommit(b, None), - ); - } - } else if e == self.key_config.compare_commits - && self.valid_selection() - { - self.hide(); - if let Some(b) = self.get_selected() { - self.queue.push( - InternalEvent::CompareCommits(b, None), - ); - } + if let Event::Key(e) = ev { + if e == self.key_config.exit_popup { + self.hide(); + } else if e == self.key_config.move_down { + return self + .move_selection(ScrollType::Up) + .map(Into::into); + } else if e == self.key_config.move_up { + return self + .move_selection(ScrollType::Down) + .map(Into::into); + } else if e == self.key_config.page_down { + return self + .move_selection(ScrollType::PageDown) + .map(Into::into); + } else if e == self.key_config.page_up { + return self + .move_selection(ScrollType::PageUp) + .map(Into::into); + } else if e == self.key_config.tab_toggle { + self.local = !self.local; + self.update_branches()?; + } else if e == self.key_config.enter { + try_or_popup!( + self, + "switch branch error:", + self.switch_to_selected_branch() + ); + } else if e == self.key_config.create_branch && self.local + { + self.queue.push(InternalEvent::CreateBranch); + } else if e == self.key_config.rename_branch + && self.valid_selection() + { + self.rename_branch(); + } else if e == self.key_config.delete_branch + && !self.selection_is_cur_branch() + && self.valid_selection() + { + self.delete_branch(); + } else if e == self.key_config.merge_branch + && !self.selection_is_cur_branch() + && self.valid_selection() + { + try_or_popup!( + self, + "merge branch error:", + self.merge_branch() + ); + } else if e == self.key_config.rebase_branch + && !self.selection_is_cur_branch() + && self.valid_selection() + { + try_or_popup!( + self, + "rebase error:", + self.rebase_branch() + ); + } else if e == self.key_config.move_right + && self.valid_selection() + { + self.hide(); + if let Some(b) = self.get_selected() { + self.queue + .push(InternalEvent::InspectCommit(b, None)); + } + } else if e == self.key_config.compare_commits + && self.valid_selection() + { + self.hide(); + if let Some(b) = self.get_selected() { + self.queue + .push(InternalEvent::CompareCommits(b, None)); } } - - Ok(EventState::Consumed) - } else { - Ok(EventState::NotConsumed) } + + Ok(EventState::Consumed) } fn is_visible(&self) -> bool { @@ -368,6 +367,20 @@ impl BranchListComponent { self.branches.get(usize::from(self.selection)) { sync::merge_branch(CWD, &branch.name)?; + + self.queue.push(InternalEvent::Update(NeedsUpdate::ALL)); + } + + Ok(()) + } + + fn rebase_branch(&self) -> Result<()> { + if let Some(branch) = + self.branches.get(usize::from(self.selection)) + { + sync::rebase_branch(CWD, &branch.name)?; + + self.queue.push(InternalEvent::Update(NeedsUpdate::ALL)); } Ok(()) @@ -623,4 +636,23 @@ impl BranchListComponent { Ok(()) } + + fn rename_branch(&mut self) { + let cur_branch = &self.branches[self.selection as usize]; + self.queue.push(InternalEvent::RenameBranch( + cur_branch.reference.clone(), + cur_branch.name.clone(), + )); + } + + fn delete_branch(&mut self) { + self.queue.push(InternalEvent::ConfirmAction( + Action::DeleteBranch( + self.branches[self.selection as usize] + .reference + .clone(), + self.local, + ), + )); + } } diff --git a/src/keys.rs b/src/keys.rs index 4a86655f..201610cf 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -76,6 +76,7 @@ pub struct KeyConfig { pub select_branch: KeyEvent, pub delete_branch: KeyEvent, pub merge_branch: KeyEvent, + pub rebase_branch: KeyEvent, pub compare_commits: KeyEvent, pub tags: KeyEvent, pub delete_tag: KeyEvent, @@ -147,6 +148,7 @@ impl Default for KeyConfig { select_branch: KeyEvent { code: KeyCode::Char('b'), modifiers: KeyModifiers::empty()}, delete_branch: KeyEvent { code: KeyCode::Char('D'), modifiers: KeyModifiers::SHIFT}, merge_branch: KeyEvent { code: KeyCode::Char('m'), modifiers: KeyModifiers::empty()}, + rebase_branch: KeyEvent { code: KeyCode::Char('R'), modifiers: KeyModifiers::SHIFT}, compare_commits: KeyEvent { code: KeyCode::Char('C'), modifiers: KeyModifiers::SHIFT}, tags: KeyEvent { code: KeyCode::Char('T'), modifiers: KeyModifiers::SHIFT}, delete_tag: KeyEvent { code: KeyCode::Char('D'), modifiers: KeyModifiers::SHIFT}, diff --git a/src/strings.rs b/src/strings.rs index dce92874..8d3ed500 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -1059,6 +1059,19 @@ pub mod commands { ) } + pub fn branch_popup_rebase( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Rebase [{}]", + key_config.get_hint(key_config.rebase_branch), + ), + "rebase a branch", + CMD_GROUP_BRANCHES, + ) + } + pub fn compare_with_head( key_config: &SharedKeyConfig, ) -> CommandText { diff --git a/vim_style_key_config.ron b/vim_style_key_config.ron index a945ef7a..fae794be 100644 --- a/vim_style_key_config.ron +++ b/vim_style_key_config.ron @@ -84,6 +84,7 @@ select_branch: ( code: Char('b'), modifiers: ( bits: 0,),), delete_branch: ( code: Char('D'), modifiers: ( bits: 1,),), merge_branch: ( code: Char('m'), modifiers: ( bits: 0,),), + rebase_branch: ( code: Char('R'), modifiers: ( bits: 1,),), abort_merge: ( code: Char('M'), modifiers: ( bits: 1,),), compare_commits: ( code: Char('C'), modifiers: ( bits: 1,),),