From 5aef9c8349f80064edb2de46e47b9a50fc465869 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Wed, 7 Oct 2020 20:51:03 +0100 Subject: [PATCH 01/50] Initial attempt --- asyncgit/src/sync/commit.rs | 2 +- asyncgit/src/sync/mod.rs | 2 + asyncgit/src/sync/rebase.rs | 144 +++++++++++++++++++++++++++++++++++ src/app.rs | 16 +++- src/components/mod.rs | 2 + src/components/reword.rs | 148 ++++++++++++++++++++++++++++++++++++ src/keys.rs | 4 +- src/queue.rs | 2 + src/strings.rs | 9 +++ src/tabs/revlog.rs | 18 ++++- 10 files changed, 342 insertions(+), 5 deletions(-) create mode 100644 asyncgit/src/sync/rebase.rs create mode 100644 src/components/reword.rs diff --git a/asyncgit/src/sync/commit.rs b/asyncgit/src/sync/commit.rs index de96c8bc19..4914522d99 100644 --- a/asyncgit/src/sync/commit.rs +++ b/asyncgit/src/sync/commit.rs @@ -33,7 +33,7 @@ pub fn amend( /// Wrap Repository::signature to allow unknown user.name. /// /// See . -fn signature_allow_undefined_name( +pub fn signature_allow_undefined_name( repo: &Repository, ) -> std::result::Result, git2::Error> { match repo.signature() { diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 653248c809..70fd4fc55a 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -14,6 +14,7 @@ mod hooks; mod hunks; mod ignore; mod logwalker; +mod rebase; mod remotes; mod reset; mod stash; @@ -40,6 +41,7 @@ pub use hooks::{ pub use hunks::{reset_hunk, stage_hunk, unstage_hunk}; pub use ignore::add_to_ignore; pub use logwalker::LogWalker; +pub use rebase::reword; pub use remotes::{ fetch_origin, get_remotes, push, ProgressNotification, DEFAULT_REMOTE_NAME, diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs new file mode 100644 index 0000000000..cb6638d4cf --- /dev/null +++ b/asyncgit/src/sync/rebase.rs @@ -0,0 +1,144 @@ +//! + +use super::commit::signature_allow_undefined_name; +use super::CommitId; +use crate::{error::Result, sync::utils}; +use crossbeam_channel::Sender; +use git2::{ + Cred, Error as GitError, FetchOptions, Oid, PackBuilderStage, + PushOptions, RebaseOptions, RemoteCallbacks, +}; +use scopetime::scope_time; + +struct Rebase {} + +impl Rebase {} + +/// Changes the commit message of a commit with a specified hash +/// change_commit_message +/// +/// While this function is most commonly associated with doing a +/// reword opperation in an interactive rebase, that is not how it +/// is implimented in git2rs +pub fn reword( + repo_path: &str, + commit_oid: Oid, + message: &str, +) -> Result<()> { + let repo = utils::repo(repo_path)?; + let sig = signature_allow_undefined_name(&repo)?; + let head = repo + .find_annotated_commit(utils::get_head(repo_path)?.into())?; + //let mut parent_commit_oid = None; + let mut parent_commit_oid = None; + + if let Ok(parent_commit) = repo.find_commit(commit_oid)?.parent(0) + { + parent_commit_oid = Some(parent_commit.id()); + } else { + parent_commit_oid = None; + } + //let parent_commit_oid = + // .unwrap().id(); + + //let cur_commit = repo + // .find_annotated_commit(commit_oid) + //.expect("Unable to find commit"); + // panic!("{:?}", cur_commit.refname()); + + /* let new_commit_oid = c + .amend( + /*cur_commit.refname()*/ + Some("HEAD"), //&commit_oid.to_string()), + None, + None, + None, + Some(message), + None, + ) + .unwrap(); + */ + // panic!("{:?}", c); + // Then begin a rebase + let commit_to_change = if parent_commit_oid.is_some() { + repo.find_annotated_commit(parent_commit_oid.unwrap())? //commit_oid)?; + } else { + repo.find_annotated_commit(commit_oid)? + }; + let mut top_branch_commit = None; + let mut cur_branch_ref = None; + let mut cur_branch_name = None; + for b in repo.branches(None).unwrap() { + let branch = b?.0; + if branch.is_head() { + //cur_branch_ref + cur_branch_ref = + Some(String::from(branch.get().name().unwrap())); + cur_branch_name = + Some(String::from(branch.name().unwrap().unwrap())); + // panic!("{:?}", branch.name()); + top_branch_commit = Some(repo.find_annotated_commit( + branch.get().peel_to_commit()?.id(), + )?); + break; + } + + //.iter().map(|(b, bt)| b.0.ishead()); //(commit_oid)?; + } + if let Some(top_branch_commit) = top_branch_commit { + let mut rebase = repo + .rebase( + Some(&top_branch_commit), + Some(&commit_to_change), + None, + Some(&mut RebaseOptions::default()), + ) + .unwrap(); + + //panic!("{:?}", rebase.operation_current()); + // Go to the first (and only) item + let mut target; + let cur_commit = rebase.next(); + if parent_commit_oid.is_none() { + //repo.set_head(refname: &str) + + // There is no parent + // so immediatly ammend + repo.find_commit(cur_commit.unwrap().unwrap().id()) + .unwrap() + .amend( + Some("rebase-merge-todo"), + None, + None, + None, + Some(message), + None, + ) + .unwrap(); + target = rebase.commit(None, &sig, None)?; + } else { + target = rebase.commit(None, &sig, Some(message))?; //Some(message))?; + } + for item in rebase.next() { + target = rebase.commit(None, &sig, None)?; + } + rebase.finish(None).unwrap(); + + // Now override the current branch + repo.branch( + &cur_branch_name.unwrap(), + &repo.find_commit(target).unwrap(), + true, + ); + + repo.set_head(&cur_branch_ref.unwrap()); + repo.checkout_head(None); + // Now reset master to the commit, which is now detached + //repo.set_head(&(cur_branch_ref.unwrap())); + + //return Ok(()); + } + Ok(()) + //cur_branch. + //Some(&head) +} diff --git a/src/app.rs b/src/app.rs index 70e803c740..ac53b36363 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,8 +6,8 @@ use crate::{ Component, CreateBranchComponent, DrawableComponent, ExternalEditorComponent, HelpComponent, InspectCommitComponent, MsgComponent, PushComponent, - RenameBranchComponent, ResetComponent, SelectBranchComponent, - StashMsgComponent, TagCommitComponent, + RenameBranchComponent, ResetComponent, RewordComponent, + SelectBranchComponent, StashMsgComponent, TagCommitComponent, }, input::{Input, InputEvent, InputState}, keys::{KeyConfig, SharedKeyConfig}, @@ -45,6 +45,7 @@ pub struct App { external_editor_popup: ExternalEditorComponent, push_popup: PushComponent, tag_commit_popup: TagCommitComponent, + reword_popup: RewordComponent, create_branch_popup: CreateBranchComponent, rename_branch_popup: RenameBranchComponent, select_branch_popup: SelectBranchComponent, @@ -114,6 +115,11 @@ impl App { theme.clone(), key_config.clone(), ), + reword_popup: RewordComponent::new( + queue.clone(), + theme.clone(), + key_config.clone(), + ), create_branch_popup: CreateBranchComponent::new( queue.clone(), theme.clone(), @@ -347,6 +353,7 @@ impl App { external_editor_popup, push_popup, tag_commit_popup, + reword_popup, create_branch_popup, rename_branch_popup, select_branch_popup, @@ -513,6 +520,9 @@ impl App { InternalEvent::TagCommit(id) => { self.tag_commit_popup.open(id)?; } + InternalEvent::RewordCommit(id) => { + self.reword_popup.open(id)?; + } InternalEvent::CreateBranch => { self.create_branch_popup.open()?; } @@ -600,6 +610,7 @@ impl App { || self.push_popup.is_visible() || self.select_branch_popup.is_visible() || self.rename_branch_popup.is_visible() + || self.reword_popup.is_visible() } fn draw_popups( @@ -624,6 +635,7 @@ impl App { self.external_editor_popup.draw(f, size)?; self.tag_commit_popup.draw(f, size)?; self.select_branch_popup.draw(f, size)?; + self.reword_popup.draw(f, size)?; self.create_branch_popup.draw(f, size)?; self.rename_branch_popup.draw(f, size)?; self.push_popup.draw(f, size)?; diff --git a/src/components/mod.rs b/src/components/mod.rs index aa73b68661..e65a83a140 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -14,6 +14,7 @@ mod msg; mod push; mod rename_branch; mod reset; +mod reword; mod select_branch; mod stashmsg; mod tag_commit; @@ -38,6 +39,7 @@ pub use msg::MsgComponent; pub use push::PushComponent; pub use rename_branch::RenameBranchComponent; pub use reset::ResetComponent; +pub use reword::RewordComponent; pub use select_branch::SelectBranchComponent; pub use stashmsg::StashMsgComponent; pub use tag_commit::TagCommitComponent; diff --git a/src/components/reword.rs b/src/components/reword.rs new file mode 100644 index 0000000000..332bb0c022 --- /dev/null +++ b/src/components/reword.rs @@ -0,0 +1,148 @@ +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, CommitId}, + CWD, +}; +use crossterm::event::Event; +use tui::{backend::Backend, layout::Rect, Frame}; + +pub struct RewordComponent { + input: TextInputComponent, + commit_id: Option, + queue: Queue, + key_config: SharedKeyConfig, +} + +impl DrawableComponent for RewordComponent { + fn draw( + &self, + f: &mut Frame, + rect: Rect, + ) -> Result<()> { + self.input.draw(f, rect)?; + + Ok(()) + } +} + +impl Component for RewordComponent { + 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::tag_commit_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.reword() + } + + 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 RewordComponent { + /// + pub fn new( + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + queue, + input: TextInputComponent::new( + theme, + key_config.clone(), + &strings::tag_commit_popup_title(&key_config), + &strings::tag_commit_popup_msg(&key_config), + ), + commit_id: None, + key_config, + } + } + + /// + pub fn open(&mut self, id: CommitId) -> Result<()> { + self.commit_id = Some(id); + self.show()?; + + Ok(()) + } + + /// + pub fn reword(&mut self) { + if let Some(commit_id) = self.commit_id { + match sync::reword( + CWD, + commit_id.into(), + self.input.get_text(), + ) { + Ok(_) => { + self.input.clear(); + self.hide(); + + self.queue.borrow_mut().push_back( + InternalEvent::Update(NeedsUpdate::ALL), + ); + } + Err(e) => { + self.hide(); + log::error!("e: {}", e,); + self.queue.borrow_mut().push_back( + InternalEvent::ShowErrorMsg(format!( + "reword error:\n{}", + e, + )), + ); + } + } + } + } +} diff --git a/src/keys.rs b/src/keys.rs index 8c2d640ef8..287ea10e02 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -46,6 +46,7 @@ pub struct KeyConfig { pub shift_up: KeyEvent, pub shift_down: KeyEvent, pub enter: KeyEvent, + pub reword: KeyEvent, pub edit_file: KeyEvent, pub status_stage_all: KeyEvent, pub status_reset_item: KeyEvent, @@ -99,7 +100,8 @@ impl Default for KeyConfig { page_up: KeyEvent { code: KeyCode::PageUp, modifiers: KeyModifiers::empty()}, shift_up: KeyEvent { code: KeyCode::Up, modifiers: KeyModifiers::SHIFT}, shift_down: KeyEvent { code: KeyCode::Down, modifiers: KeyModifiers::SHIFT}, - enter: KeyEvent { code: KeyCode::Enter, modifiers: KeyModifiers::empty()}, + enter: KeyEvent { code: KeyCode::Enter, modifiers: KeyModifiers::empty()}, + reword: KeyEvent { code: KeyCode::Char('r'), modifiers: KeyModifiers::empty() }, edit_file: KeyEvent { code: KeyCode::Char('e'), modifiers: KeyModifiers::empty()}, status_stage_all: KeyEvent { code: KeyCode::Char('a'), modifiers: KeyModifiers::empty()}, status_reset_item: KeyEvent { code: KeyCode::Char('D'), modifiers: KeyModifiers::SHIFT}, diff --git a/src/queue.rs b/src/queue.rs index 678baf6bb8..0b8dd4c9d7 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -52,6 +52,8 @@ pub enum InternalEvent { /// TagCommit(CommitId), /// + RewordCommit(CommitId), + /// CreateBranch, /// RenameBranch(String, String), diff --git a/src/strings.rs b/src/strings.rs index 4c9ea63696..668ca2fda8 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -625,6 +625,15 @@ pub mod commands { CMD_GROUP_LOG, ) } + pub fn reword_commit( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!("Reword [{}]", get_hint(key_config.reword),), + "reword commit", + CMD_GROUP_LOG, + ) + } pub fn create_branch_confirm_msg( key_config: &SharedKeyConfig, ) -> CommandText { diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index f2bd3b42eb..9f17dbb3b1 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -214,6 +214,16 @@ impl Component for Revlog { Ok(true) }, ); + } else if k == self.key_config.reword { + return self.selected_commit().map_or( + Ok(false), + |id| { + self.queue.borrow_mut().push_back( + InternalEvent::RewordCommit(id), + ); + Ok(true) + }, + ); } else if k == self.key_config.focus_right && self.commit_details.is_visible() { @@ -272,7 +282,13 @@ impl Component for Revlog { )); out.push(CommandInfo::new( - strings::commands::open_branch_select_popup( + strings::commands::reword_commit(&self.key_config), + true, + self.visible || force_all, + )); + + out.push(CommandInfo::new( + strings::commands::open_branch_create_popup( &self.key_config, ), true, From 823b06501e53bbf9195e57f2c4931f8612c67acf Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Wed, 7 Oct 2020 22:19:02 +0100 Subject: [PATCH 02/50] Create reword_safe --- asyncgit/src/error.rs | 6 ++ asyncgit/src/sync/mod.rs | 2 +- asyncgit/src/sync/rebase.rs | 123 +++++++++++++++++------------------- src/components/reword.rs | 7 +- src/strings.rs | 6 ++ 5 files changed, 74 insertions(+), 70 deletions(-) diff --git a/asyncgit/src/error.rs b/asyncgit/src/error.rs index 989a2416f1..5baeb3bef5 100644 --- a/asyncgit/src/error.rs +++ b/asyncgit/src/error.rs @@ -15,6 +15,12 @@ pub enum Error { #[error("git: work dir error")] NoWorkDir, + #[error("git: no parent of commit found")] + NoParent, + + #[error("git: rebase error")] + Rebase, + #[error("io error:{0}")] Io(#[from] std::io::Error), diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 70fd4fc55a..4e8216fb75 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -41,7 +41,7 @@ pub use hooks::{ pub use hunks::{reset_hunk, stage_hunk, unstage_hunk}; pub use ignore::add_to_ignore; pub use logwalker::LogWalker; -pub use rebase::reword; +pub use rebase::{reword, reword_safe}; pub use remotes::{ fetch_origin, get_remotes, push, ProgressNotification, DEFAULT_REMOTE_NAME, diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index cb6638d4cf..026ceaae5f 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -2,7 +2,7 @@ use super::commit::signature_allow_undefined_name; use super::CommitId; -use crate::{error::Result, sync::utils}; +use crate::{error::Error, error::Result, sync::utils}; use crossbeam_channel::Sender; use git2::{ Cred, Error as GitError, FetchOptions, Oid, PackBuilderStage, @@ -14,12 +14,37 @@ struct Rebase {} impl Rebase {} +/// This is the same as reword, but will abort and fix the repo if something goes wrong +pub fn reword_safe( + repo_path: &str, + commit_oid: Oid, + message: &str, +) -> Result<()> { + let repo = utils::repo(repo_path)?; + if reword(repo_path, commit_oid, message).is_ok() { + Ok(()) + } else { + // Something went wrong, checkout the master branch + // then error + if let Ok(mut rebase) = repo.open_rebase(None) { + rebase.abort()?; + repo.set_head("master")?; + repo.checkout_head(None)?; + } + Err(Error::Rebase) + } +} + /// Changes the commit message of a commit with a specified hash /// change_commit_message /// /// While this function is most commonly associated with doing a /// reword opperation in an interactive rebase, that is not how it /// is implimented in git2rs +/// +/// This is dangrous if this errors, as the head will be detached so this should +/// always be wrapped by another function which aborts the rebase and checks-out the +/// previous branch if something goes worng pub fn reword( repo_path: &str, commit_oid: Oid, @@ -29,63 +54,45 @@ pub fn reword( let sig = signature_allow_undefined_name(&repo)?; let head = repo .find_annotated_commit(utils::get_head(repo_path)?.into())?; - //let mut parent_commit_oid = None; - let mut parent_commit_oid = None; - if let Ok(parent_commit) = repo.find_commit(commit_oid)?.parent(0) + let parent_commit_oid = if let Ok(parent_commit) = + repo.find_commit(commit_oid)?.parent(0) { - parent_commit_oid = Some(parent_commit.id()); + Some(parent_commit.id()) } else { - parent_commit_oid = None; - } - //let parent_commit_oid = - // .unwrap().id(); - - //let cur_commit = repo - // .find_annotated_commit(commit_oid) - //.expect("Unable to find commit"); - // panic!("{:?}", cur_commit.refname()); + None + }; - /* let new_commit_oid = c - .amend( - /*cur_commit.refname()*/ - Some("HEAD"), //&commit_oid.to_string()), - None, - None, - None, - Some(message), - None, - ) - .unwrap(); - */ - // panic!("{:?}", c); - // Then begin a rebase let commit_to_change = if parent_commit_oid.is_some() { - repo.find_annotated_commit(parent_commit_oid.unwrap())? //commit_oid)?; + // Need to start at one previous to the commit, so + // next point to the actual commit we want to change + repo.find_annotated_commit(parent_commit_oid.unwrap())? } else { - repo.find_annotated_commit(commit_oid)? + return Err(Error::NoParent); + // Would the below work? + // repo.find_annotated_commit(commit_oid)? }; let mut top_branch_commit = None; let mut cur_branch_ref = None; let mut cur_branch_name = None; + + // Find the head branch for b in repo.branches(None).unwrap() { let branch = b?.0; if branch.is_head() { - //cur_branch_ref cur_branch_ref = Some(String::from(branch.get().name().unwrap())); cur_branch_name = Some(String::from(branch.name().unwrap().unwrap())); - // panic!("{:?}", branch.name()); top_branch_commit = Some(repo.find_annotated_commit( branch.get().peel_to_commit()?.id(), )?); break; } - - //.iter().map(|(b, bt)| b.0.ishead()); //(commit_oid)?; } + if let Some(top_branch_commit) = top_branch_commit { + // Branch was found, so start a rebase let mut rebase = repo .rebase( Some(&top_branch_commit), @@ -95,50 +102,34 @@ pub fn reword( ) .unwrap(); - //panic!("{:?}", rebase.operation_current()); - // Go to the first (and only) item let mut target; - let cur_commit = rebase.next(); - if parent_commit_oid.is_none() { - //repo.set_head(refname: &str) - // There is no parent - // so immediatly ammend - repo.find_commit(cur_commit.unwrap().unwrap().id()) - .unwrap() - .amend( - Some("rebase-merge-todo"), - None, - None, - None, - Some(message), - None, - ) - .unwrap(); - target = rebase.commit(None, &sig, None)?; + rebase.next().unwrap()?; + if parent_commit_oid.is_none() { + return Err(Error::NoParent); } else { target = rebase.commit(None, &sig, Some(message))?; //Some(message))?; } - for item in rebase.next() { + + // Set target to top commit, don't know when the rebase will end + // so have to loop till end + for _ in rebase.next() { target = rebase.commit(None, &sig, None)?; } - rebase.finish(None).unwrap(); + rebase.finish(None)?; // Now override the current branch repo.branch( - &cur_branch_name.unwrap(), - &repo.find_commit(target).unwrap(), + &cur_branch_name.expect("Couldn't unwrap branch name"), + &repo.find_commit(target)?, true, - ); - - repo.set_head(&cur_branch_ref.unwrap()); - repo.checkout_head(None); - // Now reset master to the commit, which is now detached - //repo.set_head(&(cur_branch_ref.unwrap())); + )?; - //return Ok(()); + // Reset the head back to the branch then checkout head + repo.set_head( + &cur_branch_ref.expect("Couldn't unwrap branch name"), + )?; + repo.checkout_head(None)?; } Ok(()) - //cur_branch. - //Some(&head) } diff --git a/src/components/reword.rs b/src/components/reword.rs index 332bb0c022..15c7affda3 100644 --- a/src/components/reword.rs +++ b/src/components/reword.rs @@ -100,8 +100,8 @@ impl RewordComponent { input: TextInputComponent::new( theme, key_config.clone(), - &strings::tag_commit_popup_title(&key_config), - &strings::tag_commit_popup_msg(&key_config), + &strings::reword_popup_title(&key_config), + &strings::reword_popup_msg(&key_config), ), commit_id: None, key_config, @@ -119,7 +119,7 @@ impl RewordComponent { /// pub fn reword(&mut self) { if let Some(commit_id) = self.commit_id { - match sync::reword( + match sync::reword_safe( CWD, commit_id.into(), self.input.get_text(), @@ -133,6 +133,7 @@ impl RewordComponent { ); } Err(e) => { + self.input.clear(); self.hide(); log::error!("e: {}", e,); self.queue.borrow_mut().push_back( diff --git a/src/strings.rs b/src/strings.rs index 668ca2fda8..36d20ec341 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -112,6 +112,12 @@ pub fn tag_commit_popup_title( pub fn tag_commit_popup_msg(_key_config: &SharedKeyConfig) -> String { "type tag".to_string() } +pub fn reword_popup_title(_key_config: &SharedKeyConfig) -> String { + "reword".to_string() +} +pub fn reword_popup_msg(_key_config: &SharedKeyConfig) -> String { + "new message".to_string() +} pub fn stashlist_title(_key_config: &SharedKeyConfig) -> String { "Stashes".to_string() } From 4378ecc43636931e984f117f42eaf72adcc04e17 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Wed, 7 Oct 2020 22:23:45 +0100 Subject: [PATCH 03/50] Remove Rebase Struct --- asyncgit/src/sync/rebase.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index 026ceaae5f..5bba598316 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -10,10 +10,6 @@ use git2::{ }; use scopetime::scope_time; -struct Rebase {} - -impl Rebase {} - /// This is the same as reword, but will abort and fix the repo if something goes wrong pub fn reword_safe( repo_path: &str, From 7e6cf14e8ab6c7c99df6b423aef1289e986d6231 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Wed, 7 Oct 2020 22:37:17 +0100 Subject: [PATCH 04/50] reword text fill with previous commit message --- src/components/reword.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/reword.rs b/src/components/reword.rs index 15c7affda3..95f8ad83d4 100644 --- a/src/components/reword.rs +++ b/src/components/reword.rs @@ -111,6 +111,8 @@ impl RewordComponent { /// pub fn open(&mut self, id: CommitId) -> Result<()> { self.commit_id = Some(id); + let commit = sync::get_commit_details(CWD, id)?; + self.input.set_text(commit.message.expect("").combine()); self.show()?; Ok(()) From c44b64b982a88513865e1170ba9c7a6486bcc93b Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Wed, 7 Oct 2020 22:55:11 +0100 Subject: [PATCH 05/50] Remove unnessessary comment --- asyncgit/src/sync/rebase.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index 5bba598316..9108b74765 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -31,8 +31,7 @@ pub fn reword_safe( } } -/// Changes the commit message of a commit with a specified hash -/// change_commit_message +/// Changes the commit message of a commit with a specified oid /// /// While this function is most commonly associated with doing a /// reword opperation in an interactive rebase, that is not how it From b4ee5bc521f7db19e103d6ed0a41144f4be1ec62 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Wed, 7 Oct 2020 22:56:31 +0100 Subject: [PATCH 06/50] Fix comment typo --- asyncgit/src/sync/rebase.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index 9108b74765..539c953fb9 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -37,7 +37,7 @@ pub fn reword_safe( /// reword opperation in an interactive rebase, that is not how it /// is implimented in git2rs /// -/// This is dangrous if this errors, as the head will be detached so this should +/// This is dangerous if this errors, as the head will be detached so this should /// always be wrapped by another function which aborts the rebase and checks-out the /// previous branch if something goes worng pub fn reword( From 34854d8ce35d97dd3fa4857a1a1d89cd03dc6875 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Wed, 7 Oct 2020 22:57:45 +0100 Subject: [PATCH 07/50] Remove unused head --- asyncgit/src/sync/rebase.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index 539c953fb9..7f61dd7353 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -47,8 +47,6 @@ pub fn reword( ) -> Result<()> { let repo = utils::repo(repo_path)?; let sig = signature_allow_undefined_name(&repo)?; - let head = repo - .find_annotated_commit(utils::get_head(repo_path)?.into())?; let parent_commit_oid = if let Ok(parent_commit) = repo.find_commit(commit_oid)?.parent(0) From 6232181a561e240f97977269da7f65ca425fb256 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Wed, 7 Oct 2020 23:30:24 +0100 Subject: [PATCH 08/50] Fix clippy errors --- asyncgit/src/sync/rebase.rs | 50 ++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index 7f61dd7353..5c85230e60 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -1,14 +1,8 @@ //! use super::commit::signature_allow_undefined_name; -use super::CommitId; use crate::{error::Error, error::Result, sync::utils}; -use crossbeam_channel::Sender; -use git2::{ - Cred, Error as GitError, FetchOptions, Oid, PackBuilderStage, - PushOptions, RebaseOptions, RemoteCallbacks, -}; -use scopetime::scope_time; +use git2::{Oid, RebaseOptions}; /// This is the same as reword, but will abort and fix the repo if something goes wrong pub fn reword_safe( @@ -38,8 +32,7 @@ pub fn reword_safe( /// is implimented in git2rs /// /// This is dangerous if this errors, as the head will be detached so this should -/// always be wrapped by another function which aborts the rebase and checks-out the -/// previous branch if something goes worng +/// always be wrapped by another function which aborts the rebase if something goes worng pub fn reword( repo_path: &str, commit_oid: Oid, @@ -56,10 +49,10 @@ pub fn reword( None }; - let commit_to_change = if parent_commit_oid.is_some() { + let commit_to_change = if let Some(pc_oid) = parent_commit_oid { // Need to start at one previous to the commit, so // next point to the actual commit we want to change - repo.find_annotated_commit(parent_commit_oid.unwrap())? + repo.find_annotated_commit(pc_oid)? } else { return Err(Error::NoParent); // Would the below work? @@ -70,13 +63,20 @@ pub fn reword( let mut cur_branch_name = None; // Find the head branch - for b in repo.branches(None).unwrap() { + for b in repo.branches(None)? { let branch = b?.0; if branch.is_head() { - cur_branch_ref = - Some(String::from(branch.get().name().unwrap())); - cur_branch_name = - Some(String::from(branch.name().unwrap().unwrap())); + cur_branch_ref = Some(String::from( + branch + .get() + .name() + .expect("Branch name is not valid utf8"), + )); + cur_branch_name = Some(String::from( + branch + .name()? + .expect("Branch name is not valid utf8"), + )); top_branch_commit = Some(repo.find_annotated_commit( branch.get().peel_to_commit()?.id(), )?); @@ -86,18 +86,16 @@ pub fn reword( if let Some(top_branch_commit) = top_branch_commit { // Branch was found, so start a rebase - let mut rebase = repo - .rebase( - Some(&top_branch_commit), - Some(&commit_to_change), - None, - Some(&mut RebaseOptions::default()), - ) - .unwrap(); + let mut rebase = repo.rebase( + Some(&top_branch_commit), + Some(&commit_to_change), + None, + Some(&mut RebaseOptions::default()), + )?; let mut target; - rebase.next().unwrap()?; + rebase.next(); if parent_commit_oid.is_none() { return Err(Error::NoParent); } else { @@ -106,7 +104,7 @@ pub fn reword( // Set target to top commit, don't know when the rebase will end // so have to loop till end - for _ in rebase.next() { + while rebase.next().is_some() { target = rebase.commit(None, &sig, None)?; } rebase.finish(None)?; From 1a981fd4f3bc806cefbb858f6f58e19ee858f19d Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Wed, 7 Oct 2020 23:37:33 +0100 Subject: [PATCH 09/50] reword_safe gets current branch --- asyncgit/src/sync/rebase.rs | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index 5c85230e60..151756224c 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -11,17 +11,34 @@ pub fn reword_safe( message: &str, ) -> Result<()> { let repo = utils::repo(repo_path)?; - if reword(repo_path, commit_oid, message).is_ok() { - Ok(()) - } else { - // Something went wrong, checkout the master branch - // then error - if let Ok(mut rebase) = repo.open_rebase(None) { - rebase.abort()?; - repo.set_head("master")?; - repo.checkout_head(None)?; + let mut cur_branch_ref = None; + // Find the head branch + for b in repo.branches(None)? { + let branch = b?.0; + if branch.is_head() { + cur_branch_ref = Some(String::from( + branch + .get() + .name() + .expect("Branch name is not valid utf8"), + )); + break; + } + } + + match reword(repo_path, commit_oid, message) { + Ok(()) => Ok(()), + // Something went wrong, checkout the previous branch then error + Err(e) => { + if let Ok(mut rebase) = repo.open_rebase(None) { + if let Some(cur_branch) = cur_branch_ref { + rebase.abort()?; + repo.set_head(&cur_branch)?; + repo.checkout_head(None)?; + } + } + Err(e) } - Err(Error::Rebase) } } From 0c8dc2add8effc4b049b336d692f9ee6f79935e6 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 8 Oct 2020 00:02:45 +0100 Subject: [PATCH 10/50] Fix vim test --- assets/vim_style_key_config.ron | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/vim_style_key_config.ron b/assets/vim_style_key_config.ron index 6afc7ef269..b75aa76663 100644 --- a/assets/vim_style_key_config.ron +++ b/assets/vim_style_key_config.ron @@ -70,4 +70,5 @@ delete_branch: ( code: Char('D'), modifiers: ( bits: 1,),), push: ( code: Char('p'), modifiers: ( bits: 0,),), fetch: ( code: Char('f'), modifiers: ( bits: 0,),), + reword: ( code: Char('r'), modifiers: ( bits: 0,),), ) From 5d7e0f633da4969239e0c6125b638ea25689233a Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 8 Oct 2020 11:31:37 +0100 Subject: [PATCH 11/50] Modify reword function --- asyncgit/src/error.rs | 4 +-- asyncgit/src/sync/rebase.rs | 55 +++++++++++++++++++------------------ 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/asyncgit/src/error.rs b/asyncgit/src/error.rs index 5baeb3bef5..9ae6fe1d5d 100644 --- a/asyncgit/src/error.rs +++ b/asyncgit/src/error.rs @@ -18,8 +18,8 @@ pub enum Error { #[error("git: no parent of commit found")] NoParent, - #[error("git: rebase error")] - Rebase, + #[error("git: not on a branch")] + NoBranch, #[error("io error:{0}")] Io(#[from] std::io::Error), diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index 151756224c..d6c5e036e2 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -12,6 +12,7 @@ pub fn reword_safe( ) -> Result<()> { let repo = utils::repo(repo_path)?; let mut cur_branch_ref = None; + // Find the head branch for b in repo.branches(None)? { let branch = b?.0; @@ -31,10 +32,13 @@ pub fn reword_safe( // Something went wrong, checkout the previous branch then error Err(e) => { if let Ok(mut rebase) = repo.open_rebase(None) { - if let Some(cur_branch) = cur_branch_ref { - rebase.abort()?; - repo.set_head(&cur_branch)?; - repo.checkout_head(None)?; + match cur_branch_ref { + Some(cur_branch) => { + rebase.abort()?; + repo.set_head(&cur_branch)?; + repo.checkout_head(None)?; + } + None => return Err(Error::NoBranch), } } Err(e) @@ -68,12 +72,10 @@ pub fn reword( let commit_to_change = if let Some(pc_oid) = parent_commit_oid { // Need to start at one previous to the commit, so - // next point to the actual commit we want to change + // first rebase.next() points to the actual commit we want to change repo.find_annotated_commit(pc_oid)? } else { return Err(Error::NoParent); - // Would the below work? - // repo.find_annotated_commit(commit_oid)? }; let mut top_branch_commit = None; let mut cur_branch_ref = None; @@ -83,17 +85,13 @@ pub fn reword( for b in repo.branches(None)? { let branch = b?.0; if branch.is_head() { - cur_branch_ref = Some(String::from( - branch - .get() - .name() - .expect("Branch name is not valid utf8"), - )); - cur_branch_name = Some(String::from( - branch - .name()? - .expect("Branch name is not valid utf8"), - )); + // When getting the branch name/ref, make sure both are valid utf8 + cur_branch_ref = Some(String::from_utf8(Vec::from( + branch.get().name_bytes(), + ))?); + cur_branch_name = Some(String::from_utf8(Vec::from( + branch.name_bytes()?, + ))?); top_branch_commit = Some(repo.find_annotated_commit( branch.get().peel_to_commit()?.id(), )?); @@ -101,7 +99,12 @@ pub fn reword( } } - if let Some(top_branch_commit) = top_branch_commit { + if let ( + Some(top_branch_commit), + Some(cur_branch_name), + Some(cur_branch_ref), + ) = (top_branch_commit, cur_branch_name, cur_branch_ref) + { // Branch was found, so start a rebase let mut rebase = repo.rebase( Some(&top_branch_commit), @@ -116,7 +119,7 @@ pub fn reword( if parent_commit_oid.is_none() { return Err(Error::NoParent); } else { - target = rebase.commit(None, &sig, Some(message))?; //Some(message))?; + target = rebase.commit(None, &sig, Some(message))?; } // Set target to top commit, don't know when the rebase will end @@ -126,18 +129,18 @@ pub fn reword( } rebase.finish(None)?; - // Now override the current branch + // Now override the previous branch repo.branch( - &cur_branch_name.expect("Couldn't unwrap branch name"), + &cur_branch_name, &repo.find_commit(target)?, true, )?; // Reset the head back to the branch then checkout head - repo.set_head( - &cur_branch_ref.expect("Couldn't unwrap branch name"), - )?; + repo.set_head(&cur_branch_ref)?; repo.checkout_head(None)?; + return Ok(()); } - Ok(()) + // Repo is not on a branch, possibly detached head + Err(Error::NoBranch) } From 269297a885cb8a2b925d1b3839eb95022b833dba Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 8 Oct 2020 11:41:13 +0100 Subject: [PATCH 12/50] Make reword private --- asyncgit/src/sync/rebase.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index d6c5e036e2..41ec77ca39 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -54,7 +54,7 @@ pub fn reword_safe( /// /// This is dangerous if this errors, as the head will be detached so this should /// always be wrapped by another function which aborts the rebase if something goes worng -pub fn reword( +fn reword( repo_path: &str, commit_oid: Oid, message: &str, From a79ca043332083b2db08151a53b93cc34ec776c0 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 8 Oct 2020 11:46:06 +0100 Subject: [PATCH 13/50] remove except in reword component open function --- src/components/reword.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/reword.rs b/src/components/reword.rs index 95f8ad83d4..7de0bd8164 100644 --- a/src/components/reword.rs +++ b/src/components/reword.rs @@ -111,8 +111,11 @@ impl RewordComponent { /// pub fn open(&mut self, id: CommitId) -> Result<()> { self.commit_id = Some(id); - let commit = sync::get_commit_details(CWD, id)?; - self.input.set_text(commit.message.expect("").combine()); + if let Some(commit_msg) = + sync::get_commit_details(CWD, id)?.message + { + self.input.set_text(commit_msg.combine()); + } self.show()?; Ok(()) From 1b518220eca3d7a6af0704489289f96c01ae86c6 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 8 Oct 2020 11:47:14 +0100 Subject: [PATCH 14/50] Remove reword from sync mod --- asyncgit/src/sync/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 4e8216fb75..a7b702cd22 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -41,7 +41,7 @@ pub use hooks::{ pub use hunks::{reset_hunk, stage_hunk, unstage_hunk}; pub use ignore::add_to_ignore; pub use logwalker::LogWalker; -pub use rebase::{reword, reword_safe}; +pub use rebase::reword_safe; pub use remotes::{ fetch_origin, get_remotes, push, ProgressNotification, DEFAULT_REMOTE_NAME, From b2fcc30528781123e3cdf086e3947fc5f459c4b6 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sat, 10 Oct 2020 23:31:12 +0100 Subject: [PATCH 15/50] Keep previous selection when rewording commit --- src/components/commitlist.rs | 5 +++++ src/tabs/revlog.rs | 3 +++ 2 files changed, 8 insertions(+) diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 2c4fff3807..be579a4ca9 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -78,6 +78,11 @@ impl CommitList { self.selection } + /// + pub fn set_selection(&mut self, new_selection: usize) { + self.selection = new_selection; + } + /// pub fn current_size(&self) -> (u16, u16) { self.current_size.get() diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 9f17dbb3b1..be1b20b3cc 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -78,6 +78,7 @@ impl Revlog { /// pub fn update(&mut self) -> Result<()> { if self.visible { + let cur_selection = self.list.selection(); let log_changed = self.git_log.fetch()? == FetchStatus::Started; @@ -103,6 +104,8 @@ impl Revlog { self.commit_details.set_commit(commit, tags)?; } + + self.list.set_selection(cur_selection); } Ok(()) From 7f5effbd691e1d17a72c6b84d50abb1b71d37845 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Sat, 17 Oct 2020 11:58:03 +0200 Subject: [PATCH 16/50] fix clippy --- src/app.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app.rs b/src/app.rs index ac53b36363..65bacc8633 100644 --- a/src/app.rs +++ b/src/app.rs @@ -68,6 +68,7 @@ pub struct App { // public interface impl App { /// + #[allow(clippy::too_many_lines)] pub fn new( sender: &Sender, input: Input, From 2b27d2084c2a711b6b9c7992227fd5ec7785aa9e Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sun, 25 Oct 2020 23:18:17 +0000 Subject: [PATCH 17/50] Use Utf8Error for reword if invalid utf8 --- asyncgit/src/sync/rebase.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index 41ec77ca39..bd5c1c4c3a 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -17,12 +17,9 @@ pub fn reword_safe( for b in repo.branches(None)? { let branch = b?.0; if branch.is_head() { - cur_branch_ref = Some(String::from( - branch - .get() - .name() - .expect("Branch name is not valid utf8"), - )); + cur_branch_ref = Some(String::from_utf8( + branch.get().name_bytes().to_vec(), + )?); break; } } From 7bba5d5b2023be1be060f6ce33e8e5f0e4a396fb Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Mon, 26 Oct 2020 00:12:20 +0000 Subject: [PATCH 18/50] Add reword_safe test --- asyncgit/src/sync/rebase.rs | 45 +++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index bd5c1c4c3a..e2e67011b1 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -141,3 +141,48 @@ fn reword( // Repo is not on a branch, possibly detached head Err(Error::NoBranch) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::sync::{ + commit, stage_add_file, tests::repo_init_empty, + }; + use std::{fs::File, io::Write, path::Path}; + + #[test] + fn test_reword() -> Result<()> { + let file_path = Path::new("foo"); + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); + + File::create(&root.join(file_path))?.write_all(b"a")?; + stage_add_file(repo_path, file_path).unwrap(); + commit(repo_path, "commit1").unwrap(); + File::create(&root.join(file_path))?.write_all(b"ab")?; + stage_add_file(repo_path, file_path).unwrap(); + let oid2 = commit(repo_path, "commit2").unwrap(); + + let branch = + repo.branches(None).unwrap().next().unwrap().unwrap().0; + let branch_ref = branch.get(); + let commit_ref = branch_ref.peel_to_commit().unwrap(); + let message = commit_ref.message().unwrap(); + + assert_eq!(message, "commit2"); + + reword_safe(repo_path, oid2.into(), "NewCommitMessage") + .unwrap(); + + // Need to get the branch again as top oid has changed + let branch = + repo.branches(None).unwrap().next().unwrap().unwrap().0; + let branch_ref = branch.get(); + let commit_ref_new = branch_ref.peel_to_commit().unwrap(); + let message_new = commit_ref_new.message().unwrap(); + assert_eq!(message_new, "NewCommitMessage"); + + Ok(()) + } +} From fbbb1ac621bf3189136ccd4c429cbbf8f800e926 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Mon, 26 Oct 2020 22:37:33 +0000 Subject: [PATCH 19/50] Allow opening external editor to edit reword --- src/app.rs | 24 ++++++++++++-- src/components/reword.rs | 69 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/src/app.rs b/src/app.rs index 65bacc8633..a14ae4968f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -33,6 +33,13 @@ use tui::{ Frame, }; +/// Used to determine where the user should +/// be put when the external editor is closed +pub enum EditorVisible { + Commit, + Reword, +} + /// pub struct App { do_quit: bool, @@ -59,6 +66,7 @@ pub struct App { theme: SharedTheme, key_config: SharedKeyConfig, input: Input, + cur_editor_visible: EditorVisible, // "Flags" requires_redraw: Cell, @@ -175,6 +183,7 @@ impl App { key_config, requires_redraw: Cell::new(false), file_to_open: None, + cur_editor_visible: EditorVisible::Commit, } } @@ -263,7 +272,14 @@ impl App { Path::new(&path), ) } - None => self.commit.show_editor(), + None => match self.cur_editor_visible { + EditorVisible::Commit => { + self.commit.show_editor() + } + EditorVisible::Reword => { + self.reword_popup.show_editor() + } + }, }; if let Err(e) = result { @@ -513,7 +529,10 @@ impl App { .insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS); } InternalEvent::Update(u) => flags.insert(u), - InternalEvent::OpenCommit => self.commit.show()?, + InternalEvent::OpenCommit => { + self.cur_editor_visible = EditorVisible::Commit; + self.commit.show()?; + } InternalEvent::PopupStashing(opts) => { self.stashmsg_popup.options(opts); self.stashmsg_popup.show()? @@ -522,6 +541,7 @@ impl App { self.tag_commit_popup.open(id)?; } InternalEvent::RewordCommit(id) => { + self.cur_editor_visible = EditorVisible::Reword; self.reword_popup.open(id)?; } InternalEvent::CreateBranch => { diff --git a/src/components/reword.rs b/src/components/reword.rs index 7de0bd8164..f237feabd3 100644 --- a/src/components/reword.rs +++ b/src/components/reword.rs @@ -1,8 +1,10 @@ use super::{ textinput::TextInputComponent, visibility_blocking, CommandBlocking, CommandInfo, Component, DrawableComponent, + ExternalEditorComponent, }; use crate::{ + get_app_config_path, keys::SharedKeyConfig, queue::{InternalEvent, NeedsUpdate, Queue}, strings, @@ -14,6 +16,11 @@ use asyncgit::{ CWD, }; use crossterm::event::Event; +use std::{ + fs::File, + io::{Read, Write}, + path::PathBuf, +}; use tui::{backend::Backend, layout::Rect, Frame}; pub struct RewordComponent { @@ -51,6 +58,14 @@ impl Component for RewordComponent { true, true, )); + + out.push(CommandInfo::new( + strings::commands::commit_open_editor( + &self.key_config, + ), + true, + true, + )); } visibility_blocking(self) @@ -65,6 +80,11 @@ impl Component for RewordComponent { if let Event::Key(e) = ev { if e == self.key_config.enter { self.reword() + } else if e == self.key_config.open_commit_editor { + self.queue.borrow_mut().push_back( + InternalEvent::OpenExternalEditor(None), + ); + self.hide(); } return Ok(true); @@ -121,6 +141,55 @@ impl RewordComponent { Ok(()) } + /// After an external editor has been open, + /// this should be called to put the text in the + /// right place + pub fn show_editor(&mut self) -> Result<()> { + const COMMIT_MSG_FILE_NAME: &str = "COMMITMSG_EDITOR"; + //TODO: use a tmpfile here + let mut config_path: PathBuf = get_app_config_path()?; + config_path.push(COMMIT_MSG_FILE_NAME); + + { + let mut file = File::create(&config_path)?; + file.write_fmt(format_args!( + "{}\n", + self.input.get_text() + ))?; + file.write_all( + strings::commit_editor_msg(&self.key_config) + .as_bytes(), + )?; + } + + ExternalEditorComponent::open_file_in_editor(&config_path)?; + + let mut message = String::new(); + + let mut file = File::open(&config_path)?; + file.read_to_string(&mut message)?; + drop(file); + std::fs::remove_file(&config_path)?; + + let message: String = message + .lines() + .flat_map(|l| { + if l.starts_with('#') { + vec![] + } else { + vec![l, "\n"] + } + }) + .collect(); + + let message = message.trim().to_string(); + + self.input.set_text(message); + self.input.show()?; + + Ok(()) + } + /// pub fn reword(&mut self) { if let Some(commit_id) = self.commit_id { From fc31ac96ae0b50301deaf8ee453804d5cb679318 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Mon, 26 Oct 2020 22:40:59 +0000 Subject: [PATCH 20/50] Change Tag to Reword in Reword component --- src/components/reword.rs | 2 +- src/strings.rs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/reword.rs b/src/components/reword.rs index f237feabd3..4333187a0a 100644 --- a/src/components/reword.rs +++ b/src/components/reword.rs @@ -52,7 +52,7 @@ impl Component for RewordComponent { self.input.commands(out, force_all); out.push(CommandInfo::new( - strings::commands::tag_commit_confirm_msg( + strings::commands::reword_commit_confirm_msg( &self.key_config, ), true, diff --git a/src/strings.rs b/src/strings.rs index 36d20ec341..9275714f6f 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -631,6 +631,15 @@ pub mod commands { CMD_GROUP_LOG, ) } + pub fn reword_commit_confirm_msg( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!("Reword [{}]", get_hint(key_config.enter),), + "tag commit", + CMD_GROUP_LOG, + ) + } pub fn reword_commit( key_config: &SharedKeyConfig, ) -> CommandText { From 69a290d979f4848a995ec634782ee6c0fa1a5945 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 5 Nov 2020 18:55:13 +0000 Subject: [PATCH 21/50] Fix typo in rebase --- asyncgit/src/sync/rebase.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index e2e67011b1..9fb5ccf9cc 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -49,8 +49,8 @@ pub fn reword_safe( /// reword opperation in an interactive rebase, that is not how it /// is implimented in git2rs /// -/// This is dangerous if this errors, as the head will be detached so this should -/// always be wrapped by another function which aborts the rebase if something goes worng +/// This is dangerous if it errors, as the head will be detached so this should +/// always be wrapped by another function which aborts the rebase if something goes wrong fn reword( repo_path: &str, commit_oid: Oid, From 683a1bdb957302a512ddd60cf123c54ed636a06a Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 5 Nov 2020 19:40:52 +0000 Subject: [PATCH 22/50] Make function to find current branch and current branch ref --- asyncgit/src/sync/branch.rs | 28 +++++++++++++++++++++++++++- asyncgit/src/sync/rebase.rs | 14 ++------------ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/asyncgit/src/sync/branch.rs b/asyncgit/src/sync/branch.rs index 6c3310e885..87332b68ad 100644 --- a/asyncgit/src/sync/branch.rs +++ b/asyncgit/src/sync/branch.rs @@ -4,7 +4,7 @@ use crate::{ error::{Error, Result}, sync::{utils, CommitId}, }; -use git2::BranchType; +use git2::{BranchType, Repository}; use scopetime::scope_time; use utils::get_head_repo; @@ -31,6 +31,32 @@ pub(crate) fn get_branch_name(repo_path: &str) -> Result { Err(Error::NoHead) } +/// Gets the current branch the user is on. +/// Returns none if they are not on a branch and Err +/// if there was a problem finding the branch +pub fn get_cur_branch( + repo: &Repository, +) -> Result> { + for b in repo.branches(None)? { + let branch = b?.0; + if branch.is_head() { + return Ok(Some(branch)); + } + } + Ok(None) +} + +/// Convenience function to get the current branch reference +pub fn get_cur_branch_ref(repo_path: &str) -> Result> { + let repo = utils::repo(repo_path)?; + if let Ok(Some(b)) = get_cur_branch(&repo) { + return Ok(Some(String::from_utf8( + b.get().name_bytes().to_vec(), + )?)); + } + Ok(None) +} + /// pub struct BranchForDisplay { /// diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index 9fb5ccf9cc..08360e1645 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -1,5 +1,6 @@ //! +use super::branch::get_cur_branch_ref; use super::commit::signature_allow_undefined_name; use crate::{error::Error, error::Result, sync::utils}; use git2::{Oid, RebaseOptions}; @@ -11,18 +12,7 @@ pub fn reword_safe( message: &str, ) -> Result<()> { let repo = utils::repo(repo_path)?; - let mut cur_branch_ref = None; - - // Find the head branch - for b in repo.branches(None)? { - let branch = b?.0; - if branch.is_head() { - cur_branch_ref = Some(String::from_utf8( - branch.get().name_bytes().to_vec(), - )?); - break; - } - } + let cur_branch_ref = get_cur_branch_ref(repo_path)?; match reword(repo_path, commit_oid, message) { Ok(()) => Ok(()), From a884286a3a2fcc68d84d98df5f74d461e9f7a3fd Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sat, 7 Nov 2020 00:29:33 +0000 Subject: [PATCH 23/50] Find current branch through get_cur_branch --- asyncgit/src/sync/rebase.rs | 45 +++++++++++++------------------------ 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index 08360e1645..16a4b23180 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -2,7 +2,14 @@ use super::branch::get_cur_branch_ref; use super::commit::signature_allow_undefined_name; -use crate::{error::Error, error::Result, sync::utils}; +use crate::{ + error::Error, + error::Result, + sync::{ + branch::get_cur_branch, + utils::{self, bytes2string}, + }, +}; use git2::{Oid, RebaseOptions}; /// This is the same as reword, but will abort and fix the repo if something goes wrong @@ -64,35 +71,15 @@ fn reword( } else { return Err(Error::NoParent); }; - let mut top_branch_commit = None; - let mut cur_branch_ref = None; - let mut cur_branch_name = None; - - // Find the head branch - for b in repo.branches(None)? { - let branch = b?.0; - if branch.is_head() { - // When getting the branch name/ref, make sure both are valid utf8 - cur_branch_ref = Some(String::from_utf8(Vec::from( - branch.get().name_bytes(), - ))?); - cur_branch_name = Some(String::from_utf8(Vec::from( - branch.name_bytes()?, - ))?); - top_branch_commit = Some(repo.find_annotated_commit( - branch.get().peel_to_commit()?.id(), - )?); - break; - } - } - if let ( - Some(top_branch_commit), - Some(cur_branch_name), - Some(cur_branch_ref), - ) = (top_branch_commit, cur_branch_name, cur_branch_ref) - { - // Branch was found, so start a rebase + // If we are on a branch + if let Ok(Some(branch)) = get_cur_branch(&repo) { + let cur_branch_ref = bytes2string(branch.get().name_bytes())?; + let cur_branch_name = bytes2string(branch.name_bytes()?)?; + let top_branch_commit = repo.find_annotated_commit( + branch.get().peel_to_commit()?.id(), + )?; + let mut rebase = repo.rebase( Some(&top_branch_commit), Some(&commit_to_change), From 91c122ed108d0456fe7167da2f7ba0c65f96308b Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sat, 7 Nov 2020 00:32:15 +0000 Subject: [PATCH 24/50] Rename reword and reword_safe --- asyncgit/src/sync/mod.rs | 2 +- asyncgit/src/sync/rebase.rs | 9 ++++----- src/components/reword.rs | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index a7b702cd22..70fd4fc55a 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -41,7 +41,7 @@ pub use hooks::{ pub use hunks::{reset_hunk, stage_hunk, unstage_hunk}; pub use ignore::add_to_ignore; pub use logwalker::LogWalker; -pub use rebase::reword_safe; +pub use rebase::reword; pub use remotes::{ fetch_origin, get_remotes, push, ProgressNotification, DEFAULT_REMOTE_NAME, diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index 16a4b23180..f13c630063 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -13,7 +13,7 @@ use crate::{ use git2::{Oid, RebaseOptions}; /// This is the same as reword, but will abort and fix the repo if something goes wrong -pub fn reword_safe( +pub fn reword( repo_path: &str, commit_oid: Oid, message: &str, @@ -21,7 +21,7 @@ pub fn reword_safe( let repo = utils::repo(repo_path)?; let cur_branch_ref = get_cur_branch_ref(repo_path)?; - match reword(repo_path, commit_oid, message) { + match reword_internal(repo_path, commit_oid, message) { Ok(()) => Ok(()), // Something went wrong, checkout the previous branch then error Err(e) => { @@ -48,7 +48,7 @@ pub fn reword_safe( /// /// This is dangerous if it errors, as the head will be detached so this should /// always be wrapped by another function which aborts the rebase if something goes wrong -fn reword( +fn reword_internal( repo_path: &str, commit_oid: Oid, message: &str, @@ -149,8 +149,7 @@ mod tests { assert_eq!(message, "commit2"); - reword_safe(repo_path, oid2.into(), "NewCommitMessage") - .unwrap(); + reword(repo_path, oid2.into(), "NewCommitMessage").unwrap(); // Need to get the branch again as top oid has changed let branch = diff --git a/src/components/reword.rs b/src/components/reword.rs index 4333187a0a..8e8d0042dd 100644 --- a/src/components/reword.rs +++ b/src/components/reword.rs @@ -193,7 +193,7 @@ impl RewordComponent { /// pub fn reword(&mut self) { if let Some(commit_id) = self.commit_id { - match sync::reword_safe( + match sync::reword( CWD, commit_id.into(), self.input.get_text(), From 2f7e7cb33c4ad76da3cd354ad917c9d744c623cb Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Mon, 16 Nov 2020 22:57:28 +0000 Subject: [PATCH 25/50] Make get_cur_branch pub(crate) --- asyncgit/src/sync/branch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asyncgit/src/sync/branch.rs b/asyncgit/src/sync/branch.rs index 87332b68ad..a802b67bd9 100644 --- a/asyncgit/src/sync/branch.rs +++ b/asyncgit/src/sync/branch.rs @@ -34,7 +34,7 @@ pub(crate) fn get_branch_name(repo_path: &str) -> Result { /// Gets the current branch the user is on. /// Returns none if they are not on a branch and Err /// if there was a problem finding the branch -pub fn get_cur_branch( +pub(crate) fn get_cur_branch( repo: &Repository, ) -> Result> { for b in repo.branches(None)? { From 9007bc96cf52217b582ae55197e315dfdbe614d5 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Mon, 16 Nov 2020 22:58:22 +0000 Subject: [PATCH 26/50] Fix implemented comment typo --- asyncgit/src/sync/rebase.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index f13c630063..a35622fe26 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -44,7 +44,7 @@ pub fn reword( /// /// While this function is most commonly associated with doing a /// reword opperation in an interactive rebase, that is not how it -/// is implimented in git2rs +/// is implemented in git2rs /// /// This is dangerous if it errors, as the head will be detached so this should /// always be wrapped by another function which aborts the rebase if something goes wrong From 8404ec02c03d088d120a2f5d57d9adf6167c45f2 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Mon, 16 Nov 2020 22:59:23 +0000 Subject: [PATCH 27/50] Make signature_allow... pub(crate) --- asyncgit/src/sync/commit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asyncgit/src/sync/commit.rs b/asyncgit/src/sync/commit.rs index 4914522d99..fdae1f894b 100644 --- a/asyncgit/src/sync/commit.rs +++ b/asyncgit/src/sync/commit.rs @@ -33,7 +33,7 @@ pub fn amend( /// Wrap Repository::signature to allow unknown user.name. /// /// See . -pub fn signature_allow_undefined_name( +pub(crate) fn signature_allow_undefined_name( repo: &Repository, ) -> std::result::Result, git2::Error> { match repo.signature() { From e4b761d9d307de7a61a101f91e63b1eb33f6d2ae Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Tue, 2 Feb 2021 20:00:12 +0000 Subject: [PATCH 28/50] Fix get_hint error --- src/strings.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/strings.rs b/src/strings.rs index c8a97e04f7..6d3533213c 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -736,7 +736,10 @@ pub mod commands { key_config: &SharedKeyConfig, ) -> CommandText { CommandText::new( - format!("Reword [{}]", get_hint(key_config.enter),), + format!( + "Reword [{}]", + key_config.get_hint(key_config.enter), + ), "tag commit", CMD_GROUP_LOG, ) @@ -745,7 +748,10 @@ pub mod commands { key_config: &SharedKeyConfig, ) -> CommandText { CommandText::new( - format!("Reword [{}]", get_hint(key_config.reword),), + format!( + "Reword [{}]", + key_config.get_hint(key_config.reword), + ), "reword commit", CMD_GROUP_LOG, ) From 074ff6d70a64bc4a415d6d898ed2bd38185ff505 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Tue, 2 Feb 2021 20:01:54 +0000 Subject: [PATCH 29/50] Set reword text input not to show character count --- src/components/reword.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/reword.rs b/src/components/reword.rs index 8e8d0042dd..2c8e27dbee 100644 --- a/src/components/reword.rs +++ b/src/components/reword.rs @@ -122,6 +122,7 @@ impl RewordComponent { key_config.clone(), &strings::reword_popup_title(&key_config), &strings::reword_popup_msg(&key_config), + false, ), commit_id: None, key_config, From 4274d08748cb6f1390ae2c89144dbeef7b43ca88 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Tue, 2 Feb 2021 20:06:34 +0000 Subject: [PATCH 30/50] Set show character count in the reword text input to true --- src/components/reword.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/reword.rs b/src/components/reword.rs index 2c8e27dbee..b03a4d8a75 100644 --- a/src/components/reword.rs +++ b/src/components/reword.rs @@ -122,7 +122,7 @@ impl RewordComponent { key_config.clone(), &strings::reword_popup_title(&key_config), &strings::reword_popup_msg(&key_config), - false, + true, ), commit_id: None, key_config, From 92415766fe90903c3335ed6a0e8c8fa00d1cd54e Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 14:34:54 +0000 Subject: [PATCH 31/50] Change tag to reword commit in reword_commit_confirm --- src/strings.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings.rs b/src/strings.rs index 6d3533213c..2312328d47 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -740,7 +740,7 @@ pub mod commands { "Reword [{}]", key_config.get_hint(key_config.enter), ), - "tag commit", + "reword commit", CMD_GROUP_LOG, ) } From 8f0fde7daf75430def2091bf58aa7b7bffefe0a5 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 14:37:01 +0000 Subject: [PATCH 32/50] Change get_cur_branch_ref to get_head_refname --- asyncgit/src/sync/branch.rs | 2 +- asyncgit/src/sync/rebase.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/asyncgit/src/sync/branch.rs b/asyncgit/src/sync/branch.rs index ac8a6b0b94..fac6a8e6bd 100644 --- a/asyncgit/src/sync/branch.rs +++ b/asyncgit/src/sync/branch.rs @@ -46,7 +46,7 @@ pub(crate) fn get_cur_branch( } /// Convenience function to get the current branch reference -pub fn get_cur_branch_ref(repo_path: &str) -> Result> { +pub fn get_head_refname(repo_path: &str) -> Result> { let repo = utils::repo(repo_path)?; if let Ok(Some(b)) = get_cur_branch(&repo) { return Ok(Some(String::from_utf8( diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index a35622fe26..7f7c09d682 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -1,6 +1,6 @@ //! -use super::branch::get_cur_branch_ref; +use super::branch::get_head_refname; use super::commit::signature_allow_undefined_name; use crate::{ error::Error, @@ -19,7 +19,7 @@ pub fn reword( message: &str, ) -> Result<()> { let repo = utils::repo(repo_path)?; - let cur_branch_ref = get_cur_branch_ref(repo_path)?; + let cur_branch_ref = get_head_refname(repo_path)?; match reword_internal(repo_path, commit_oid, message) { Ok(()) => Ok(()), From 97edb8fe7a0e54ee21652b29a064cc738fa7cd67 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Mon, 16 Nov 2020 23:34:42 +0000 Subject: [PATCH 33/50] Change to use (Option, EE) for External Editor --- src/app.rs | 54 +++++++++++++++++++++++++++++----------- src/components/commit.rs | 6 ++++- src/components/reword.rs | 6 ++++- src/queue.rs | 3 ++- src/tabs/status.rs | 8 +++--- 5 files changed, 56 insertions(+), 21 deletions(-) diff --git a/src/app.rs b/src/app.rs index f3ee82efa3..ce0596179d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -16,7 +16,7 @@ use crate::{ tabs::{Revlog, StashList, Stashing, Status}, ui::style::{SharedTheme, Theme}, }; -use anyhow::{bail, Result}; +use anyhow::{anyhow, bail, Result}; use asyncgit::{sync, AsyncNotification, CWD}; use crossbeam_channel::Sender; use crossterm::event::{Event, KeyEvent}; @@ -35,7 +35,7 @@ use tui::{ /// Used to determine where the user should /// be put when the external editor is closed -pub enum EditorVisible { +pub enum EditorSource { Commit, Reword, } @@ -66,11 +66,10 @@ pub struct App { theme: SharedTheme, key_config: SharedKeyConfig, input: Input, - cur_editor_visible: EditorVisible, + external_editor: Option<(Option, EditorSource)>, // "Flags" requires_redraw: Cell, - file_to_open: Option, } // public interface @@ -183,8 +182,7 @@ impl App { theme, key_config, requires_redraw: Cell::new(false), - file_to_open: None, - cur_editor_visible: EditorVisible::Commit, + external_editor: None, } } @@ -265,21 +263,41 @@ impl App { } else if let InputEvent::State(polling_state) = ev { self.external_editor_popup.hide(); if let InputState::Paused = polling_state { - let result = match self.file_to_open.take() { + let result = if let Some(ee) = &self.external_editor { + match ee.0 { + Some(path) => { + ExternalEditorComponent::open_file_in_editor( + Path::new(&path) + ) + }, + None => match ee.1 { + EditorSource::Commit => { + self.commit.show_editor() + } + EditorSource::Reword => { + self.reword_popup.show_editor() + } + }, + } + } else { + Err(anyhow!("There was no editor path or return path selected, the app external editor was set to null, put in a bug report at https://github.com/extrawurst/gitui and detail what you tried to do, this is most likely an error")) + }; + + /*let result = match self.file_to_open.take() { Some(path) => { ExternalEditorComponent::open_file_in_editor( Path::new(&path), ) } - None => match self.cur_editor_visible { - EditorVisible::Commit => { + None => match self.external_editor { + Some(Some(p), EditorSource::Commit) => { self.commit.show_editor() } - EditorVisible::Reword => { + EditorSource::Reword => { self.reword_popup.show_editor() } }, - }; + };*/ if let Err(e) = result { let msg = @@ -529,7 +547,10 @@ impl App { } InternalEvent::Update(u) => flags.insert(u), InternalEvent::OpenCommit => { - self.cur_editor_visible = EditorVisible::Commit; + self.external_editor = + Some((None, EditorSource::Commit)); + + // self.cur_editor_visible = EditorSource::Commit; self.commit.show()?; } InternalEvent::PopupStashing(opts) => { @@ -540,7 +561,8 @@ impl App { self.tag_commit_popup.open(id)?; } InternalEvent::RewordCommit(id) => { - self.cur_editor_visible = EditorVisible::Reword; + self.external_editor = + Some((None, EditorSource::Reword)); self.reword_popup.open(id)?; } InternalEvent::CreateBranch => { @@ -558,10 +580,12 @@ impl App { self.inspect_commit_popup.open(id, tags)?; flags.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS) } - InternalEvent::OpenExternalEditor(path) => { + InternalEvent::OpenExternalEditor( + path, + editor_source, + ) => { self.input.set_polling(false); self.external_editor_popup.show()?; - self.file_to_open = path; flags.insert(NeedsUpdate::COMMANDS) } InternalEvent::Push(branch) => { diff --git a/src/components/commit.rs b/src/components/commit.rs index 13a09f38be..5bbe74a3f6 100644 --- a/src/components/commit.rs +++ b/src/components/commit.rs @@ -4,6 +4,7 @@ use super::{ ExternalEditorComponent, }; use crate::{ + app::EditorSource, get_app_config_path, keys::SharedKeyConfig, queue::{InternalEvent, NeedsUpdate, Queue}, @@ -90,7 +91,10 @@ impl Component for CommitComponent { self.amend()?; } else if e == self.key_config.open_commit_editor { self.queue.borrow_mut().push_back( - InternalEvent::OpenExternalEditor(None), + InternalEvent::OpenExternalEditor( + None, + EditorSource::Commit, + ), ); self.hide(); } else { diff --git a/src/components/reword.rs b/src/components/reword.rs index b03a4d8a75..8717369e88 100644 --- a/src/components/reword.rs +++ b/src/components/reword.rs @@ -4,6 +4,7 @@ use super::{ ExternalEditorComponent, }; use crate::{ + app::EditorSource, get_app_config_path, keys::SharedKeyConfig, queue::{InternalEvent, NeedsUpdate, Queue}, @@ -82,7 +83,10 @@ impl Component for RewordComponent { self.reword() } else if e == self.key_config.open_commit_editor { self.queue.borrow_mut().push_back( - InternalEvent::OpenExternalEditor(None), + InternalEvent::OpenExternalEditor( + None, + EditorSource::Reword, + ), ); self.hide(); } diff --git a/src/queue.rs b/src/queue.rs index 0b8dd4c9d7..6cb124ac98 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -1,3 +1,4 @@ +use crate::app::EditorSource; use crate::tabs::StashingOptions; use asyncgit::sync::{CommitId, CommitTags}; use bitflags::bitflags; @@ -60,7 +61,7 @@ pub enum InternalEvent { /// SelectBranch, /// - OpenExternalEditor(Option), + OpenExternalEditor(Option, EditorSource), /// Push(String), } diff --git a/src/tabs/status.rs b/src/tabs/status.rs index 2a61ee07e1..d546fe6c00 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -1,5 +1,6 @@ use crate::{ accessors, + app::EditorSource, components::{ command_pump, event_pump, visibility_blocking, ChangesComponent, CommandBlocking, CommandInfo, Component, @@ -523,9 +524,10 @@ impl Component for Status { { if let Some((path, _)) = self.selected_path() { self.queue.borrow_mut().push_back( - InternalEvent::OpenExternalEditor(Some( - path, - )), + InternalEvent::OpenExternalEditor( + Some(path), + EditorSource::Commit, + ), ); } Ok(true) From e551927180f6359f0c3e86b8272866e1f068497e Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Mon, 16 Nov 2020 23:50:59 +0000 Subject: [PATCH 34/50] small changes in app to fix build --- src/app.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app.rs b/src/app.rs index ce0596179d..040b8ff179 100644 --- a/src/app.rs +++ b/src/app.rs @@ -264,7 +264,7 @@ impl App { self.external_editor_popup.hide(); if let InputState::Paused = polling_state { let result = if let Some(ee) = &self.external_editor { - match ee.0 { + match &ee.0 { Some(path) => { ExternalEditorComponent::open_file_in_editor( Path::new(&path) @@ -581,8 +581,8 @@ impl App { flags.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS) } InternalEvent::OpenExternalEditor( - path, - editor_source, + _path, + _editor_source, ) => { self.input.set_polling(false); self.external_editor_popup.show()?; From 233355d56e6763dfd788c9321378371c620d2a7b Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sat, 6 Feb 2021 14:56:41 +0000 Subject: [PATCH 35/50] Remove unessessary comment --- src/app.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/app.rs b/src/app.rs index 040b8ff179..108136547b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -283,22 +283,6 @@ impl App { Err(anyhow!("There was no editor path or return path selected, the app external editor was set to null, put in a bug report at https://github.com/extrawurst/gitui and detail what you tried to do, this is most likely an error")) }; - /*let result = match self.file_to_open.take() { - Some(path) => { - ExternalEditorComponent::open_file_in_editor( - Path::new(&path), - ) - } - None => match self.external_editor { - Some(Some(p), EditorSource::Commit) => { - self.commit.show_editor() - } - EditorSource::Reword => { - self.reword_popup.show_editor() - } - }, - };*/ - if let Err(e) = result { let msg = format!("failed to launch editor:\n{}", e); From 5318690e76615a284ae46791d2b1a835872e1e79 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sat, 6 Feb 2021 15:14:38 +0000 Subject: [PATCH 36/50] Use tempfile for external editor --- Cargo.lock | 1 + Cargo.toml | 1 + src/components/reword.rs | 23 +++++++++-------------- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85afe425f2..816afc690b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -377,6 +377,7 @@ dependencies = [ "scopetime", "serde", "simplelog", + "tempfile", "textwrap 0.13.2", "tui", "unicode-width", diff --git a/Cargo.toml b/Cargo.toml index a0cf5b1a0b..7c16704b73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ serde = "1.0" anyhow = "1.0.38" unicode-width = "0.1" textwrap = "0.13" +tempfile = "3.2" [target.'cfg(all(target_family="unix",not(target_os="macos")))'.dependencies] which = "4.0" diff --git a/src/components/reword.rs b/src/components/reword.rs index 8717369e88..18d40534e4 100644 --- a/src/components/reword.rs +++ b/src/components/reword.rs @@ -5,7 +5,6 @@ use super::{ }; use crate::{ app::EditorSource, - get_app_config_path, keys::SharedKeyConfig, queue::{InternalEvent, NeedsUpdate, Queue}, strings, @@ -17,11 +16,8 @@ use asyncgit::{ CWD, }; use crossterm::event::Event; -use std::{ - fs::File, - io::{Read, Write}, - path::PathBuf, -}; +use std::io::{Read, Write}; +use tempfile::NamedTempFile; use tui::{backend::Backend, layout::Rect, Frame}; pub struct RewordComponent { @@ -150,13 +146,10 @@ impl RewordComponent { /// this should be called to put the text in the /// right place pub fn show_editor(&mut self) -> Result<()> { - const COMMIT_MSG_FILE_NAME: &str = "COMMITMSG_EDITOR"; - //TODO: use a tmpfile here - let mut config_path: PathBuf = get_app_config_path()?; - config_path.push(COMMIT_MSG_FILE_NAME); + let tmp_file = NamedTempFile::new()?; { - let mut file = File::create(&config_path)?; + let mut file = tmp_file.reopen()?; file.write_fmt(format_args!( "{}\n", self.input.get_text() @@ -167,14 +160,16 @@ impl RewordComponent { )?; } - ExternalEditorComponent::open_file_in_editor(&config_path)?; + ExternalEditorComponent::open_file_in_editor( + &tmp_file.path(), + )?; let mut message = String::new(); - let mut file = File::open(&config_path)?; + let mut file = tmp_file.reopen()?; file.read_to_string(&mut message)?; drop(file); - std::fs::remove_file(&config_path)?; + std::fs::remove_file(&tmp_file.path())?; let message: String = message .lines() From 4960fbb3df93fc27b32ba750e14f98decf7140ea Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sat, 6 Feb 2021 15:16:29 +0000 Subject: [PATCH 37/50] Fix clippy and don't drop temp file --- src/components/reword.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/reword.rs b/src/components/reword.rs index 18d40534e4..60f0edf823 100644 --- a/src/components/reword.rs +++ b/src/components/reword.rs @@ -161,15 +161,13 @@ impl RewordComponent { } ExternalEditorComponent::open_file_in_editor( - &tmp_file.path(), + tmp_file.path(), )?; let mut message = String::new(); let mut file = tmp_file.reopen()?; file.read_to_string(&mut message)?; - drop(file); - std::fs::remove_file(&tmp_file.path())?; let message: String = message .lines() From db6744ce60897a767e7f4a506e9a7fc489ca479d Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sat, 6 Feb 2021 15:17:20 +0000 Subject: [PATCH 38/50] Small formatting changes --- src/components/reword.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/reword.rs b/src/components/reword.rs index 60f0edf823..27dad4ac31 100644 --- a/src/components/reword.rs +++ b/src/components/reword.rs @@ -146,10 +146,9 @@ impl RewordComponent { /// this should be called to put the text in the /// right place pub fn show_editor(&mut self) -> Result<()> { - let tmp_file = NamedTempFile::new()?; - + let temp_file = NamedTempFile::new()?; { - let mut file = tmp_file.reopen()?; + let mut file = temp_file.reopen()?; file.write_fmt(format_args!( "{}\n", self.input.get_text() @@ -161,12 +160,12 @@ impl RewordComponent { } ExternalEditorComponent::open_file_in_editor( - tmp_file.path(), + temp_file.path(), )?; let mut message = String::new(); - let mut file = tmp_file.reopen()?; + let mut file = temp_file.reopen()?; file.read_to_string(&mut message)?; let message: String = message From 90d011450d97f59ac0044b4b5cf2b4c98daf4c4a Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sat, 6 Feb 2021 15:32:47 +0000 Subject: [PATCH 39/50] Refactor show_editor into externaleditor --- src/components/commit.rs | 53 ++++---------------------------- src/components/externaleditor.rs | 39 ++++++++++++++++++++++- src/components/reword.rs | 49 +++++------------------------ src/strings.rs | 2 +- 4 files changed, 52 insertions(+), 91 deletions(-) diff --git a/src/components/commit.rs b/src/components/commit.rs index 5bbe74a3f6..1b66b6ce89 100644 --- a/src/components/commit.rs +++ b/src/components/commit.rs @@ -1,11 +1,10 @@ use super::{ - textinput::TextInputComponent, visibility_blocking, - CommandBlocking, CommandInfo, Component, DrawableComponent, - ExternalEditorComponent, + externaleditor::show_editor, textinput::TextInputComponent, + visibility_blocking, CommandBlocking, CommandInfo, Component, + DrawableComponent, }; use crate::{ app::EditorSource, - get_app_config_path, keys::SharedKeyConfig, queue::{InternalEvent, NeedsUpdate, Queue}, strings, @@ -17,11 +16,6 @@ use asyncgit::{ CWD, }; use crossterm::event::Event; -use std::{ - fs::File, - io::{Read, Write}, - path::PathBuf, -}; use tui::{backend::Backend, layout::Rect, Frame}; pub struct CommitComponent { @@ -149,44 +143,9 @@ impl CommitComponent { } pub fn show_editor(&mut self) -> Result<()> { - const COMMIT_MSG_FILE_NAME: &str = "COMMITMSG_EDITOR"; - //TODO: use a tmpfile here - let mut config_path: PathBuf = get_app_config_path()?; - config_path.push(COMMIT_MSG_FILE_NAME); - - { - let mut file = File::create(&config_path)?; - file.write_fmt(format_args!( - "{}\n", - self.input.get_text() - ))?; - file.write_all( - strings::commit_editor_msg(&self.key_config) - .as_bytes(), - )?; - } - - ExternalEditorComponent::open_file_in_editor(&config_path)?; - - let mut message = String::new(); - - let mut file = File::open(&config_path)?; - file.read_to_string(&mut message)?; - drop(file); - std::fs::remove_file(&config_path)?; - - let message: String = message - .lines() - .flat_map(|l| { - if l.starts_with('#') { - vec![] - } else { - vec![l, "\n"] - } - }) - .collect(); - - let message = message.trim().to_string(); + let message = show_editor(Some(self.input.get_text()))? + .trim() + .to_string(); self.input.set_text(message); self.input.show()?; diff --git a/src/components/externaleditor.rs b/src/components/externaleditor.rs index 4b4e7ef3fc..58e56bac36 100644 --- a/src/components/externaleditor.rs +++ b/src/components/externaleditor.rs @@ -18,7 +18,13 @@ use crossterm::{ }; use scopeguard::defer; use std::ffi::OsStr; -use std::{env, io, path::Path, process::Command}; +use std::{ + env, + io::{self, Read, Write}, + path::Path, + process::Command, +}; +use tempfile::NamedTempFile; use tui::{ backend::Backend, layout::Rect, @@ -187,3 +193,34 @@ impl Component for ExternalEditorComponent { Ok(()) } } + +pub fn show_editor(with_text: Option<&String>) -> Result { + let temp_file = NamedTempFile::new()?; + { + let mut file = temp_file.reopen()?; + if let Some(text) = with_text { + file.write_fmt(format_args!("{}\n", text))?; + } + file.write_all(strings::commit_editor_msg().as_bytes())?; + } + + ExternalEditorComponent::open_file_in_editor(temp_file.path())?; + + let mut message = String::new(); + + let mut file = temp_file.reopen()?; + file.read_to_string(&mut message)?; + + let message: String = message + .lines() + .flat_map(|l| { + if l.starts_with('#') { + vec![] + } else { + vec![l, "\n"] + } + }) + .collect(); + + Ok(message.trim().to_string()) +} diff --git a/src/components/reword.rs b/src/components/reword.rs index 27dad4ac31..c4ab928d64 100644 --- a/src/components/reword.rs +++ b/src/components/reword.rs @@ -1,7 +1,7 @@ use super::{ - textinput::TextInputComponent, visibility_blocking, - CommandBlocking, CommandInfo, Component, DrawableComponent, - ExternalEditorComponent, + externaleditor::show_editor, textinput::TextInputComponent, + visibility_blocking, CommandBlocking, CommandInfo, Component, + DrawableComponent, }; use crate::{ app::EditorSource, @@ -16,8 +16,6 @@ use asyncgit::{ CWD, }; use crossterm::event::Event; -use std::io::{Read, Write}; -use tempfile::NamedTempFile; use tui::{backend::Backend, layout::Rect, Frame}; pub struct RewordComponent { @@ -142,44 +140,11 @@ impl RewordComponent { Ok(()) } - /// After an external editor has been open, - /// this should be called to put the text in the - /// right place + /// Ope external editor pub fn show_editor(&mut self) -> Result<()> { - let temp_file = NamedTempFile::new()?; - { - let mut file = temp_file.reopen()?; - file.write_fmt(format_args!( - "{}\n", - self.input.get_text() - ))?; - file.write_all( - strings::commit_editor_msg(&self.key_config) - .as_bytes(), - )?; - } - - ExternalEditorComponent::open_file_in_editor( - temp_file.path(), - )?; - - let mut message = String::new(); - - let mut file = temp_file.reopen()?; - file.read_to_string(&mut message)?; - - let message: String = message - .lines() - .flat_map(|l| { - if l.starts_with('#') { - vec![] - } else { - vec![l, "\n"] - } - }) - .collect(); - - let message = message.trim().to_string(); + let message = show_editor(Some(self.input.get_text()))? + .trim() + .to_string(); self.input.set_text(message); self.input.show()?; diff --git a/src/strings.rs b/src/strings.rs index 2312328d47..4066c40b30 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -66,7 +66,7 @@ pub fn commit_title_amend(_key_config: &SharedKeyConfig) -> String { pub fn commit_msg(_key_config: &SharedKeyConfig) -> String { "type commit message..".to_string() } -pub fn commit_editor_msg(_key_config: &SharedKeyConfig) -> String { +pub fn commit_editor_msg() -> String { r##" # Edit your commit message # Lines starting with '#' will be ignored"## From 3b5f1655e151e564b331d85fefeec32dd8e40f9a Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sat, 6 Feb 2021 15:36:02 +0000 Subject: [PATCH 40/50] Small comment change --- src/components/commit.rs | 1 + src/components/reword.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/commit.rs b/src/components/commit.rs index 1b66b6ce89..99e02e8ed8 100644 --- a/src/components/commit.rs +++ b/src/components/commit.rs @@ -142,6 +142,7 @@ impl CommitComponent { } } + /// Open external editor pub fn show_editor(&mut self) -> Result<()> { let message = show_editor(Some(self.input.get_text()))? .trim() diff --git a/src/components/reword.rs b/src/components/reword.rs index c4ab928d64..7fda64569b 100644 --- a/src/components/reword.rs +++ b/src/components/reword.rs @@ -140,7 +140,7 @@ impl RewordComponent { Ok(()) } - /// Ope external editor + /// Open external editor pub fn show_editor(&mut self) -> Result<()> { let message = show_editor(Some(self.input.get_text()))? .trim() From 5905942844e77e414c24717aad31720563a0c560 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 29 Apr 2021 22:08:38 +0100 Subject: [PATCH 41/50] Fix keys --- src/keys.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/keys.rs b/src/keys.rs index 8c4e5325c5..064bc66eb0 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -47,11 +47,8 @@ pub struct KeyConfig { pub shift_up: KeyEvent, pub shift_down: KeyEvent, pub enter: KeyEvent, -<<<<<<< HEAD pub reword: KeyEvent, -======= pub blame: KeyEvent, ->>>>>>> master pub edit_file: KeyEvent, pub status_stage_all: KeyEvent, pub status_reset_item: KeyEvent, From 9b78391f903993c33caf2dfe9157abdea113db1c Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 29 Apr 2021 22:09:13 +0100 Subject: [PATCH 42/50] Remove mod select_branch --- src/components/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/mod.rs b/src/components/mod.rs index 30a2140897..1b027046c8 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -19,7 +19,6 @@ mod push_tags; mod rename_branch; mod reset; mod reword; -mod select_branch; mod stashmsg; mod tag_commit; mod textinput; From d82a6b337b7d84ee6d9d6a23c6a3354a6f33a8c2 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 29 Apr 2021 22:12:25 +0100 Subject: [PATCH 43/50] Remove branch --- asyncgit/src/sync/branch.rs | 479 ------------------------------------ src/app.rs | 5 +- 2 files changed, 3 insertions(+), 481 deletions(-) delete mode 100644 asyncgit/src/sync/branch.rs diff --git a/asyncgit/src/sync/branch.rs b/asyncgit/src/sync/branch.rs deleted file mode 100644 index fac6a8e6bd..0000000000 --- a/asyncgit/src/sync/branch.rs +++ /dev/null @@ -1,479 +0,0 @@ -//! - -use super::{remotes::get_first_remote_in_repo, utils::bytes2string}; -use crate::{ - error::{Error, Result}, - sync::{utils, CommitId}, -}; -use git2::{BranchType, Repository}; -use scopetime::scope_time; -use utils::get_head_repo; - -/// returns the branch-name head is currently pointing to -/// this might be expensive, see `cached::BranchName` -pub(crate) fn get_branch_name(repo_path: &str) -> Result { - scope_time!("get_branch_name"); - - let repo = utils::repo(repo_path)?; - - let iter = repo.branches(None)?; - - for b in iter { - let b = b?; - - if b.0.is_head() { - let name = b.0.name()?.unwrap_or(""); - return Ok(name.into()); - } - } - - Err(Error::NoHead) -} - -/// Gets the current branch the user is on. -/// Returns none if they are not on a branch and Err -/// if there was a problem finding the branch -pub(crate) fn get_cur_branch( - repo: &Repository, -) -> Result> { - for b in repo.branches(None)? { - let branch = b?.0; - if branch.is_head() { - return Ok(Some(branch)); - } - } - Ok(None) -} - -/// Convenience function to get the current branch reference -pub fn get_head_refname(repo_path: &str) -> Result> { - let repo = utils::repo(repo_path)?; - if let Ok(Some(b)) = get_cur_branch(&repo) { - return Ok(Some(String::from_utf8( - b.get().name_bytes().to_vec(), - )?)); - } - Ok(None) -} - -/// -pub struct BranchForDisplay { - /// - pub name: String, - /// - pub reference: String, - /// - pub top_commit_message: String, - /// - pub top_commit: CommitId, - /// - pub is_head: bool, - /// - pub has_upstream: bool, -} - -/// Used to return only the nessessary information for displaying a branch -/// rather than an iterator over the actual branches -pub fn get_branches_to_display( - repo_path: &str, -) -> Result> { - scope_time!("get_branches_to_display"); - - let cur_repo = utils::repo(repo_path)?; - let branches_for_display = cur_repo - .branches(Some(BranchType::Local))? - .map(|b| { - let branch = b?.0; - let top_commit = branch.get().peel_to_commit()?; - - Ok(BranchForDisplay { - name: bytes2string(branch.name_bytes()?)?, - reference: bytes2string(branch.get().name_bytes())?, - top_commit_message: bytes2string( - top_commit.summary_bytes().unwrap_or_default(), - )?, - top_commit: top_commit.id().into(), - is_head: branch.is_head(), - has_upstream: branch.upstream().is_ok(), - }) - }) - .filter_map(Result::ok) - .collect(); - - Ok(branches_for_display) -} - -/// -#[derive(Debug, Default)] -pub struct BranchCompare { - /// - pub ahead: usize, - /// - pub behind: usize, -} - -/// -pub(crate) fn branch_set_upstream( - repo: &Repository, - branch_name: &str, -) -> Result<()> { - scope_time!("branch_set_upstream"); - - let mut branch = - repo.find_branch(branch_name, BranchType::Local)?; - - if branch.upstream().is_err() { - let remote = get_first_remote_in_repo(repo)?; - let upstream_name = format!("{}/{}", remote, branch_name); - branch.set_upstream(Some(upstream_name.as_str()))?; - } - - Ok(()) -} - -/// -pub fn branch_compare_upstream( - repo_path: &str, - branch: &str, -) -> Result { - scope_time!("branch_compare_upstream"); - - let repo = utils::repo(repo_path)?; - - let branch = repo.find_branch(branch, BranchType::Local)?; - - let upstream = branch.upstream()?; - - let branch_commit = - branch.into_reference().peel_to_commit()?.id(); - - let upstream_commit = - upstream.into_reference().peel_to_commit()?.id(); - - let (ahead, behind) = - repo.graph_ahead_behind(branch_commit, upstream_commit)?; - - Ok(BranchCompare { ahead, behind }) -} - -/// Modify HEAD to point to a branch then checkout head, does not work if there are uncommitted changes -pub fn checkout_branch( - repo_path: &str, - branch_ref: &str, -) -> Result<()> { - scope_time!("checkout_branch"); - - // This defaults to a safe checkout, so don't delete anything that - // hasn't been committed or stashed, in this case it will Err - let repo = utils::repo(repo_path)?; - let cur_ref = repo.head()?; - let statuses = repo.statuses(Some( - git2::StatusOptions::new().include_ignored(false), - ))?; - - if statuses.is_empty() { - repo.set_head(branch_ref)?; - - if let Err(e) = repo.checkout_head(Some( - git2::build::CheckoutBuilder::new().force(), - )) { - // This is safe beacuse cur_ref was just found - repo.set_head( - bytes2string(cur_ref.name_bytes())?.as_str(), - )?; - return Err(Error::Git(e)); - } - Ok(()) - } else { - Err(Error::Generic( - format!("Cannot change branch. There are unstaged/staged changes which have not been committed/stashed. There is {:?} changes preventing checking out a different branch.", statuses.len()), - )) - } -} - -/// The user must not be on the branch for the branch to be deleted -pub fn delete_branch( - repo_path: &str, - branch_ref: &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); - if !branch.is_head() { - branch.delete()?; - } else { - return Err(Error::Generic("You cannot be on the branch you want to delete, switch branch, then delete this branch".to_string())); - } - 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"); - - let repo = utils::repo(repo_path)?; - - let head_id = get_head_repo(&repo)?; - let head_commit = repo.find_commit(head_id.into())?; - - let branch = repo.branch(name, &head_commit, false)?; - let branch_ref = branch.into_reference(); - let branch_ref_name = bytes2string(branch_ref.name_bytes())?; - repo.set_head(branch_ref_name.as_str())?; - - Ok(()) -} - -#[cfg(test)] -mod tests_branch_name { - use super::*; - use crate::sync::tests::{repo_init, repo_init_empty}; - - #[test] - fn test_smoke() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); - - assert_eq!( - get_branch_name(repo_path).unwrap().as_str(), - "master" - ); - } - - #[test] - fn test_empty_repo() { - let (_td, repo) = repo_init_empty().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); - - assert!(matches!( - get_branch_name(repo_path), - Err(Error::NoHead) - )); - } -} - -#[cfg(test)] -mod tests_create_branch { - use super::*; - use crate::sync::tests::repo_init; - - #[test] - fn test_smoke() { - 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(); - - assert_eq!( - get_branch_name(repo_path).unwrap().as_str(), - "branch1" - ); - } -} - -#[cfg(test)] -mod tests_branch_compare { - use super::*; - use crate::sync::tests::repo_init; - - #[test] - fn test_smoke() { - 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, "test").unwrap(); - - let res = branch_compare_upstream(repo_path, "test"); - - assert_eq!(res.is_err(), true); - } -} - -#[cfg(test)] -mod tests_branches { - use super::*; - use crate::sync::tests::repo_init; - - #[test] - fn test_smoke() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); - - assert_eq!( - get_branches_to_display(repo_path) - .unwrap() - .iter() - .map(|b| b.name.clone()) - .collect::>(), - vec!["master"] - ); - } - - #[test] - fn test_multiple() { - 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, "test").unwrap(); - - assert_eq!( - get_branches_to_display(repo_path) - .unwrap() - .iter() - .map(|b| b.name.clone()) - .collect::>(), - vec!["master", "test"] - ); - } -} - -#[cfg(test)] -mod tests_checkout { - use super::*; - use crate::sync::tests::repo_init; - - #[test] - fn test_smoke() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); - - assert!( - checkout_branch(repo_path, "refs/heads/master").is_ok() - ); - assert!( - checkout_branch(repo_path, "refs/heads/foobar").is_err() - ); - } - - #[test] - fn test_multiple() { - 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, "test").unwrap(); - - assert!(checkout_branch(repo_path, "refs/heads/test").is_ok()); - assert!( - checkout_branch(repo_path, "refs/heads/master").is_ok() - ); - assert!(checkout_branch(repo_path, "refs/heads/test").is_ok()); - } -} - -#[cfg(test)] -mod test_delete_branch { - use super::*; - use crate::sync::tests::repo_init; - - #[test] - fn test_delete_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(); - create_branch(repo_path, "branch2").unwrap(); - - checkout_branch(repo_path, "refs/heads/branch1").unwrap(); - - assert_eq!( - repo.branches(None) - .unwrap() - .nth(1) - .unwrap() - .unwrap() - .0 - .name() - .unwrap() - .unwrap(), - "branch2" - ); - - delete_branch(repo_path, "refs/heads/branch2").unwrap(); - - assert_eq!( - repo.branches(None) - .unwrap() - .nth(1) - .unwrap() - .unwrap() - .0 - .name() - .unwrap() - .unwrap(), - "master" - ); - } -} - -#[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/src/app.rs b/src/app.rs index f0680424eb..eca426779a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,8 +7,9 @@ use crate::{ CreateBranchComponent, DrawableComponent, ExternalEditorComponent, HelpComponent, InspectCommitComponent, MsgComponent, PullComponent, - PushComponent, PushTagsComponent, RenameBranchComponent, RewordComponent - ResetComponent, StashMsgComponent, TagCommitComponent, + PushComponent, PushTagsComponent, RenameBranchComponent, + ResetComponent, RewordComponent, StashMsgComponent, + TagCommitComponent, }, input::{Input, InputEvent, InputState}, keys::{KeyConfig, SharedKeyConfig}, From 5e18d254c0ba9cd886d1beaf3f914c25ca9ed38c Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 29 Apr 2021 22:25:09 +0100 Subject: [PATCH 44/50] Some Fixes --- asyncgit/src/sync/branch/mod.rs | 26 ++++++++++++++++++++++++++ asyncgit/src/sync/rebase.rs | 3 +-- src/components/mod.rs | 1 - src/tabs/revlog.rs | 2 +- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/asyncgit/src/sync/branch/mod.rs b/asyncgit/src/sync/branch/mod.rs index edfd073067..bd95266892 100644 --- a/asyncgit/src/sync/branch/mod.rs +++ b/asyncgit/src/sync/branch/mod.rs @@ -44,6 +44,32 @@ pub(crate) fn get_branch_name_repo( Err(Error::NoHead) } +/// Gets the current branch the user is on. +/// Returns none if they are not on a branch and Err +/// if there was a problem finding the branch +pub(crate) fn get_cur_branch( + repo: &Repository, +) -> Result> { + for b in repo.branches(None)? { + let branch = b?.0; + if branch.is_head() { + return Ok(Some(branch)); + } + } + Ok(None) +} + +/// Convenience function to get the current branch reference +pub fn get_head_refname(repo_path: &str) -> Result> { + let repo = utils::repo(repo_path)?; + if let Ok(Some(b)) = get_cur_branch(&repo) { + return Ok(Some(String::from_utf8( + b.get().name_bytes().to_vec(), + )?)); + } + Ok(None) +} + /// #[derive(Debug)] pub struct LocalBranch { diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index 7f7c09d682..2071ebd82a 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -92,9 +92,8 @@ fn reword_internal( rebase.next(); if parent_commit_oid.is_none() { return Err(Error::NoParent); - } else { - target = rebase.commit(None, &sig, Some(message))?; } + target = rebase.commit(None, &sig, Some(message))?; // Set target to top commit, don't know when the rebase will end // so have to loop till end diff --git a/src/components/mod.rs b/src/components/mod.rs index 1b027046c8..be592869b3 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -44,7 +44,6 @@ pub use push_tags::PushTagsComponent; pub use rename_branch::RenameBranchComponent; pub use reset::ResetComponent; pub use reword::RewordComponent; -pub use select_branch::SelectBranchComponent; pub use stashmsg::StashMsgComponent; pub use tag_commit::TagCommitComponent; pub use textinput::{InputType, TextInputComponent}; diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index ef1fe45508..73f4f6cf37 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -104,7 +104,7 @@ impl Revlog { self.commit_details.set_commit(commit, tags)?; } - self.list.set_selection(cur_selection); + //self.list.set_selection(cur_selection); } Ok(()) From 258cc76cf2bc5627e62e876ba334617bbb7a384c Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 29 Apr 2021 22:32:48 +0100 Subject: [PATCH 45/50] Fix build --- src/components/commit.rs | 8 ++------ src/components/commitlist.rs | 5 ----- src/components/reword.rs | 12 ++++++------ src/tabs/revlog.rs | 4 ++-- 4 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/components/commit.rs b/src/components/commit.rs index 21f844aa61..e38e50f588 100644 --- a/src/components/commit.rs +++ b/src/components/commit.rs @@ -1,7 +1,7 @@ use super::{ externaleditor::show_editor, textinput::TextInputComponent, visibility_blocking, CommandBlocking, CommandInfo, Component, - DrawableComponent, EventState, ExternalEditorComponent, + DrawableComponent, EventState, }; use crate::{ app::EditorSource, @@ -17,11 +17,7 @@ use asyncgit::{ CWD, }; use crossterm::event::Event; -use std::{ - fs::{read_to_string, File}, - io::{Read, Write}, - path::PathBuf, -}; +use std::fs::read_to_string; use tui::{ backend::Backend, layout::{Alignment, Rect}, diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 7f64ef026d..0d669ecbbd 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -77,11 +77,6 @@ impl CommitList { self.selection } - /// - pub fn set_selection(&mut self, new_selection: usize) { - self.selection = new_selection; - } - /// pub fn current_size(&self) -> (u16, u16) { self.current_size.get() diff --git a/src/components/reword.rs b/src/components/reword.rs index 7fda64569b..e826cf85b9 100644 --- a/src/components/reword.rs +++ b/src/components/reword.rs @@ -1,7 +1,7 @@ use super::{ externaleditor::show_editor, textinput::TextInputComponent, visibility_blocking, CommandBlocking, CommandInfo, Component, - DrawableComponent, + DrawableComponent, EventState, }; use crate::{ app::EditorSource, @@ -66,10 +66,10 @@ impl Component for RewordComponent { visibility_blocking(self) } - fn event(&mut self, ev: Event) -> Result { + fn event(&mut self, ev: Event) -> Result { if self.is_visible() { - if self.input.event(ev)? { - return Ok(true); + if let Ok(EventState::Consumed) = self.input.event(ev) { + return Ok(EventState::Consumed); } if let Event::Key(e) = ev { @@ -85,10 +85,10 @@ impl Component for RewordComponent { self.hide(); } - return Ok(true); + return Ok(EventState::Consumed); } } - Ok(false) + Ok(EventState::NotConsumed) } fn is_visible(&self) -> bool { diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 73f4f6cf37..8c8783d860 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -231,12 +231,12 @@ impl Component for Revlog { ); } else if k == self.key_config.reword { return self.selected_commit().map_or( - Ok(false), + Ok(EventState::NotConsumed), |id| { self.queue.borrow_mut().push_back( InternalEvent::RewordCommit(id), ); - Ok(true) + return Ok(EventState::Consumed); }, ); } else if k == self.key_config.focus_right From 24bed95b01ddfa1a33f24ee6c579b6f6e4031cce Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 29 Apr 2021 22:43:18 +0100 Subject: [PATCH 46/50] Small Fixes --- asyncgit/src/sync/branch/mod.rs | 4 ++-- asyncgit/src/sync/rebase.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/asyncgit/src/sync/branch/mod.rs b/asyncgit/src/sync/branch/mod.rs index bd95266892..b32df9d590 100644 --- a/asyncgit/src/sync/branch/mod.rs +++ b/asyncgit/src/sync/branch/mod.rs @@ -45,8 +45,8 @@ pub(crate) fn get_branch_name_repo( } /// Gets the current branch the user is on. -/// Returns none if they are not on a branch and Err -/// if there was a problem finding the branch +/// Returns none if they are not on a branch +/// and Err if there was a problem finding the branch pub(crate) fn get_cur_branch( repo: &Repository, ) -> Result> { diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index 2071ebd82a..9c92e3133d 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -1,6 +1,6 @@ //! -use super::branch::get_head_refname; +use super::branch::{get_branch_name, get_head_refname}; use super::commit::signature_allow_undefined_name; use crate::{ error::Error, From f3ecf33d92b81ce8811462bb3f1a4382b7da7325 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 29 Apr 2021 22:46:52 +0100 Subject: [PATCH 47/50] Remove unnessessary comments --- src/app.rs | 2 -- src/tabs/revlog.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/app.rs b/src/app.rs index eca426779a..0a42a0d89e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -543,8 +543,6 @@ impl App { InternalEvent::OpenCommit => { self.external_editor = Some((None, EditorSource::Commit)); - - // self.cur_editor_visible = EditorSource::Commit; self.commit.show()?; } InternalEvent::PopupStashing(opts) => { diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 8c8783d860..e126ab5438 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -103,8 +103,6 @@ impl Revlog { self.commit_details.set_commit(commit, tags)?; } - - //self.list.set_selection(cur_selection); } Ok(()) From a8ca289fbaf3ca62501093fdab4496c4810f8d47 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 29 Apr 2021 22:49:09 +0100 Subject: [PATCH 48/50] Fix clippy errors --- asyncgit/src/sync/rebase.rs | 2 +- src/tabs/revlog.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index 9c92e3133d..2071ebd82a 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -1,6 +1,6 @@ //! -use super::branch::{get_branch_name, get_head_refname}; +use super::branch::get_head_refname; use super::commit::signature_allow_undefined_name; use crate::{ error::Error, diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index e126ab5438..7f91856906 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -234,7 +234,7 @@ impl Component for Revlog { self.queue.borrow_mut().push_back( InternalEvent::RewordCommit(id), ); - return Ok(EventState::Consumed); + Ok(EventState::Consumed) }, ); } else if k == self.key_config.focus_right From 1107a72e4e4653d123bdef7e10fd34936c008970 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sun, 23 May 2021 11:38:11 +0100 Subject: [PATCH 49/50] Fix merge problems --- src/app.rs | 7 ++----- src/components/commit.rs | 7 +------ src/strings.rs | 2 +- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/app.rs b/src/app.rs index 26c0a7b8ff..6861433372 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,8 +8,8 @@ use crate::{ ExternalEditorComponent, HelpComponent, InspectCommitComponent, MsgComponent, PullComponent, PushComponent, PushTagsComponent, RenameBranchComponent, - ResetComponent, RevisionFilesComponent, RewordComponent, StashMsgComponent, - TagCommitComponent, + ResetComponent, RevisionFilesComponent, RewordComponent, + StashMsgComponent, TagCommitComponent, }, input::{Input, InputEvent, InputState}, keys::{KeyConfig, SharedKeyConfig}, @@ -733,11 +733,8 @@ impl App { || self.pull_popup.is_visible() || self.select_branch_popup.is_visible() || self.rename_branch_popup.is_visible() -<<<<<<< HEAD || self.reword_popup.is_visible() -======= || self.revision_files_popup.is_visible() ->>>>>>> master } fn draw_popups( diff --git a/src/components/commit.rs b/src/components/commit.rs index bd2a6825c8..6d3929d9ae 100644 --- a/src/components/commit.rs +++ b/src/components/commit.rs @@ -5,7 +5,6 @@ use super::{ }; use crate::{ app::EditorSource, - args::get_app_config_path, keys::SharedKeyConfig, queue::{InternalEvent, NeedsUpdate, Queue}, strings, @@ -22,11 +21,7 @@ use asyncgit::{ }; use crossterm::event::Event; use easy_cast::Cast; -use std::{ - fs::{read_to_string, File}, - io::{Read, Write}, - path::PathBuf, -}; +use std::fs::read_to_string; use tui::{ backend::Backend, layout::{Alignment, Rect}, diff --git a/src/strings.rs b/src/strings.rs index 6a5daab948..162efd4dee 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -77,7 +77,7 @@ pub fn commit_msg(_key_config: &SharedKeyConfig) -> String { pub fn commit_first_line_warning(count: usize) -> String { format!("[subject length: {}]", count) } -pub fn commit_editor_msg(_key_config: &SharedKeyConfig) -> String { +pub fn commit_editor_msg() -> String { r##" # Edit your commit message # Lines starting with '#' will be ignored"## From ff4afb28ea897305ef6cec526cf8b5dfa4b3b98d Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Tue, 25 May 2021 11:22:48 +0100 Subject: [PATCH 50/50] Fix merge problems --- src/components/commit.rs | 43 +--------------------------------------- 1 file changed, 1 insertion(+), 42 deletions(-) diff --git a/src/components/commit.rs b/src/components/commit.rs index 8a9fded403..6d3929d9ae 100644 --- a/src/components/commit.rs +++ b/src/components/commit.rs @@ -21,10 +21,7 @@ use asyncgit::{ }; use crossterm::event::Event; use easy_cast::Cast; -use std::{ - fs::{read_to_string, File}, - io::{Read, Write}, -}; +use std::fs::read_to_string; use tui::{ backend::Backend, layout::{Alignment, Rect}, @@ -255,47 +252,9 @@ impl CommitComponent { /// Open external editor pub fn show_editor(&mut self) -> Result<()> { -<<<<<<< HEAD let message = show_editor(Some(self.input.get_text()))? .trim() .to_string(); -======= - let file_path = sync::repo_dir(CWD)?.join("COMMIT_EDITMSG"); - - { - let mut file = File::create(&file_path)?; - file.write_fmt(format_args!( - "{}\n", - self.input.get_text() - ))?; - file.write_all( - strings::commit_editor_msg(&self.key_config) - .as_bytes(), - )?; - } - - ExternalEditorComponent::open_file_in_editor(&file_path)?; - - let mut message = String::new(); - - let mut file = File::open(&file_path)?; - file.read_to_string(&mut message)?; - drop(file); - std::fs::remove_file(&file_path)?; - - let message: String = message - .lines() - .flat_map(|l| { - if l.starts_with('#') { - vec![] - } else { - vec![l, "\n"] - } - }) - .collect(); - - let message = message.trim().to_string(); ->>>>>>> master self.input.set_text(message); self.input.show()?;