diff --git a/assets/vim_style_key_config.ron b/assets/vim_style_key_config.ron index 1ba5cdc6..6afc7ef2 100644 --- a/assets/vim_style_key_config.ron +++ b/assets/vim_style_key_config.ron @@ -65,6 +65,7 @@ commit_amend: ( code: Char('A'), modifiers: ( bits: 1,),), copy: ( code: Char('y'), modifiers: ( bits: 0,),), create_branch: ( code: Char('c'), modifiers: ( bits: 0,),), + rename_branch: ( code: Char('r'), modifiers: ( bits: 0,),), select_branch: ( code: Char('b'), modifiers: ( bits: 0,),), delete_branch: ( code: Char('D'), modifiers: ( bits: 1,),), push: ( code: Char('p'), modifiers: ( bits: 0,),), diff --git a/asyncgit/src/sync/branch.rs b/asyncgit/src/sync/branch.rs index 3799568b..0b340376 100644 --- a/asyncgit/src/sync/branch.rs +++ b/asyncgit/src/sync/branch.rs @@ -130,6 +130,22 @@ pub fn delete_branch( Ok(()) } +/// Rename the branch reference +pub fn rename_branch( + repo_path: &str, + branch_ref: &str, + new_name: &str, +) -> Result<()> { + scope_time!("delete_branch"); + + let repo = utils::repo(repo_path)?; + let branch_as_ref = repo.find_reference(branch_ref)?; + let mut branch = git2::Branch::wrap(branch_as_ref); + branch.rename(new_name, true)?; + + Ok(()) +} + /// creates a new branch pointing to current HEAD commit and updating HEAD to new branch pub fn create_branch(repo_path: &str, name: &str) -> Result<()> { scope_time!("create_branch"); @@ -318,3 +334,49 @@ mod test_delete_branch { ); } } + +#[cfg(test)] +mod test_rename_branch { + use super::*; + use crate::sync::tests::repo_init; + + #[test] + fn test_rename_branch() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); + + create_branch(repo_path, "branch1").unwrap(); + + checkout_branch(repo_path, "refs/heads/branch1").unwrap(); + + assert_eq!( + repo.branches(None) + .unwrap() + .nth(0) + .unwrap() + .unwrap() + .0 + .name() + .unwrap() + .unwrap(), + "branch1" + ); + + rename_branch(repo_path, "refs/heads/branch1", "AnotherName") + .unwrap(); + + assert_eq!( + repo.branches(None) + .unwrap() + .nth(0) + .unwrap() + .unwrap() + .0 + .name() + .unwrap() + .unwrap(), + "AnotherName" + ); + } +} diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 3f0dc369..22ab4cf9 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -20,7 +20,7 @@ pub mod utils; pub(crate) use branch::get_branch_name; pub use branch::{ checkout_branch, create_branch, delete_branch, - get_branches_to_display, BranchForDisplay, + get_branches_to_display, rename_branch, BranchForDisplay, }; pub use commit::{amend, commit, tag}; pub use commit_details::{ diff --git a/src/app.rs b/src/app.rs index fa3ac5e0..e48c2039 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,8 +6,8 @@ use crate::{ Component, CreateBranchComponent, DrawableComponent, ExternalEditorComponent, HelpComponent, InspectCommitComponent, MsgComponent, PushComponent, - ResetComponent, SelectBranchComponent, StashMsgComponent, - TagCommitComponent, + RenameBranchComponent, ResetComponent, SelectBranchComponent, + StashMsgComponent, TagCommitComponent, }, input::{Input, InputEvent, InputState}, keys::{KeyConfig, SharedKeyConfig}, @@ -46,6 +46,7 @@ pub struct App { push_popup: PushComponent, tag_commit_popup: TagCommitComponent, create_branch_popup: CreateBranchComponent, + rename_branch_popup: RenameBranchComponent, select_branch_popup: SelectBranchComponent, cmdbar: RefCell, tab: usize, @@ -118,6 +119,11 @@ impl App { theme.clone(), key_config.clone(), ), + rename_branch_popup: RenameBranchComponent::new( + queue.clone(), + theme.clone(), + key_config.clone(), + ), select_branch_popup: SelectBranchComponent::new( queue.clone(), theme.clone(), @@ -342,6 +348,7 @@ impl App { push_popup, tag_commit_popup, create_branch_popup, + rename_branch_popup, select_branch_popup, help, revlog, @@ -509,6 +516,10 @@ impl App { InternalEvent::CreateBranch => { self.create_branch_popup.open()?; } + InternalEvent::RenameBranch(branch_ref, cur_name) => { + self.rename_branch_popup + .open(branch_ref, cur_name)?; + } InternalEvent::SelectBranch => { self.select_branch_popup.open()?; } @@ -588,6 +599,7 @@ impl App { || self.create_branch_popup.is_visible() || self.push_popup.is_visible() || self.select_branch_popup.is_visible() + || self.rename_branch_popup.is_visible() } fn draw_popups( @@ -613,6 +625,7 @@ impl App { self.tag_commit_popup.draw(f, size)?; self.select_branch_popup.draw(f, size)?; self.create_branch_popup.draw(f, size)?; + self.rename_branch_popup.draw(f, size)?; self.push_popup.draw(f, size)?; self.reset.draw(f, size)?; self.msg.draw(f, size)?; diff --git a/src/components/mod.rs b/src/components/mod.rs index 78d3713b..3c6695d7 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -11,6 +11,7 @@ mod help; mod inspect_commit; mod msg; mod push; +mod rename_branch; mod reset; mod select_branch; mod stashmsg; @@ -34,6 +35,7 @@ pub use help::HelpComponent; pub use inspect_commit::InspectCommitComponent; pub use msg::MsgComponent; pub use push::PushComponent; +pub use rename_branch::RenameBranchComponent; pub use reset::ResetComponent; pub use select_branch::SelectBranchComponent; pub use stashmsg::StashMsgComponent; diff --git a/src/components/rename_branch.rs b/src/components/rename_branch.rs new file mode 100644 index 00000000..81a9cda7 --- /dev/null +++ b/src/components/rename_branch.rs @@ -0,0 +1,166 @@ +use super::{ + textinput::TextInputComponent, visibility_blocking, + CommandBlocking, CommandInfo, Component, DrawableComponent, +}; +use crate::{ + keys::SharedKeyConfig, + queue::{InternalEvent, NeedsUpdate, Queue}, + strings, + ui::style::SharedTheme, +}; +use anyhow::Result; +use asyncgit::{ + sync::{self}, + CWD, +}; +use crossterm::event::Event; +use tui::{backend::Backend, layout::Rect, Frame}; + +pub struct RenameBranchComponent { + input: TextInputComponent, + branch_ref: Option, + queue: Queue, + key_config: SharedKeyConfig, +} + +impl DrawableComponent for RenameBranchComponent { + fn draw( + &self, + f: &mut Frame, + rect: Rect, + ) -> Result<()> { + self.input.draw(f, rect)?; + + Ok(()) + } +} + +impl Component for RenameBranchComponent { + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.is_visible() || force_all { + self.input.commands(out, force_all); + + out.push(CommandInfo::new( + strings::commands::rename_branch_confirm_msg( + &self.key_config, + ), + true, + true, + )); + } + + visibility_blocking(self) + } + + fn event(&mut self, ev: Event) -> Result { + if self.is_visible() { + if self.input.event(ev)? { + return Ok(true); + } + + if let Event::Key(e) = ev { + if e == self.key_config.enter { + self.rename_branch(); + } + + return Ok(true); + } + } + Ok(false) + } + + fn is_visible(&self) -> bool { + self.input.is_visible() + } + + fn hide(&mut self) { + self.input.hide() + } + + fn show(&mut self) -> Result<()> { + self.input.show()?; + + Ok(()) + } +} + +impl RenameBranchComponent { + /// + pub fn new( + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + queue, + input: TextInputComponent::new( + theme, + key_config.clone(), + &strings::rename_branch_popup_title(&key_config), + &strings::rename_branch_popup_msg(&key_config), + ), + branch_ref: None, + key_config, + } + } + + /// + pub fn open( + &mut self, + branch_ref: String, + cur_name: String, + ) -> Result<()> { + self.branch_ref = None; + self.branch_ref = Some(branch_ref); + self.input.set_text(cur_name); + self.show()?; + + Ok(()) + } + + /// + pub fn rename_branch(&mut self) { + if let Some(br) = &self.branch_ref { + let res = sync::rename_branch( + CWD, + br, + self.input.get_text().as_str(), + ); + + match res { + Ok(_) => { + self.queue.borrow_mut().push_back( + InternalEvent::Update(NeedsUpdate::ALL), + ); + self.hide(); + self.queue + .borrow_mut() + .push_back(InternalEvent::SelectBranch); + } + Err(e) => { + log::error!("create branch: {}", e,); + self.queue.borrow_mut().push_back( + InternalEvent::ShowErrorMsg(format!( + "rename branch error:\n{}", + e, + )), + ); + } + } + } else { + log::error!("create branch: No branch selected"); + self.queue + .borrow_mut() + .push_back(InternalEvent::ShowErrorMsg( + "rename branch error: No branch selected to rename" + .to_string(), + )); + } + + self.input.clear(); + } +} diff --git a/src/components/select_branch.rs b/src/components/select_branch.rs index d86c4a7e..c0ea6e4b 100644 --- a/src/components/select_branch.rs +++ b/src/components/select_branch.rs @@ -121,6 +121,14 @@ impl Component for SelectBranchComponent { !self.selection_is_cur_branch(), true, )); + + out.push(CommandInfo::new( + strings::commands::rename_branch_popup( + &self.key_config, + ), + true, + true, + )); } visibility_blocking(self) } @@ -150,6 +158,16 @@ impl Component for SelectBranchComponent { .borrow_mut() .push_back(InternalEvent::CreateBranch); self.hide(); + } else if e == self.key_config.rename_branch { + let cur_branch = + &self.branch_names[self.selection as usize]; + self.queue.borrow_mut().push_back( + InternalEvent::RenameBranch( + cur_branch.reference.clone(), + cur_branch.name.clone(), + ), + ); + self.hide(); } else if e == self.key_config.delete_branch && !self.selection_is_cur_branch() { diff --git a/src/keys.rs b/src/keys.rs index 8a4169f5..c92501b6 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -60,6 +60,7 @@ pub struct KeyConfig { pub commit_amend: KeyEvent, pub copy: KeyEvent, pub create_branch: KeyEvent, + pub rename_branch: KeyEvent, pub select_branch: KeyEvent, pub delete_branch: KeyEvent, pub push: KeyEvent, @@ -113,6 +114,7 @@ impl Default for KeyConfig { commit_amend: KeyEvent { code: KeyCode::Char('a'), modifiers: KeyModifiers::CONTROL}, copy: KeyEvent { code: KeyCode::Char('y'), modifiers: KeyModifiers::empty()}, create_branch: KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::NONE}, + rename_branch: KeyEvent { code: KeyCode::Char('r'), modifiers: KeyModifiers::NONE}, select_branch: KeyEvent { code: KeyCode::Char('b'), modifiers: KeyModifiers::NONE}, delete_branch: KeyEvent{code: KeyCode::Char('D'), modifiers: KeyModifiers::SHIFT}, push: KeyEvent { code: KeyCode::Char('p'), modifiers: KeyModifiers::empty()}, diff --git a/src/queue.rs b/src/queue.rs index 5d5fbbc2..678baf6b 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -54,6 +54,8 @@ pub enum InternalEvent { /// CreateBranch, /// + RenameBranch(String, String), + /// SelectBranch, /// OpenExternalEditor(Option), diff --git a/src/strings.rs b/src/strings.rs index 7f2df63c..8b98c5d5 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -140,6 +140,17 @@ pub fn create_branch_popup_msg( "type branch name".to_string() } +pub fn rename_branch_popup_title( + _key_config: &SharedKeyConfig, +) -> String { + "Rename Branch".to_string() +} +pub fn rename_branch_popup_msg( + _key_config: &SharedKeyConfig, +) -> String { + "new branch name".to_string() +} + pub mod commit { use crate::keys::SharedKeyConfig; pub fn details_author(_key_config: &SharedKeyConfig) -> String { @@ -615,6 +626,27 @@ pub mod commands { CMD_GROUP_GENERAL, ) } + pub fn rename_branch_confirm_msg( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!("Rename Branch [{}]", get_hint(key_config.enter),), + "rename branch", + CMD_GROUP_GENERAL, + ) + } + pub fn rename_branch_popup( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Rename Branch [{}]", + get_hint(key_config.rename_branch), + ), + "rename branch", + CMD_GROUP_GENERAL, + ) + } pub fn delete_branch_popup( key_config: &SharedKeyConfig, ) -> CommandText {