From 423a014b0e4946bf195f38f0a03539330aa47e09 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Sun, 9 May 2021 19:13:02 +0200 Subject: [PATCH 01/15] allow input error to be logged and abort program (#693) --- src/input.rs | 68 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/src/input.rs b/src/input.rs index 794bb2c3..b34730e1 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,7 +1,9 @@ use crate::notify_mutex::NotifyableMutex; -use crossbeam_channel::{unbounded, Receiver}; +use anyhow::Result; +use crossbeam_channel::{unbounded, Receiver, Sender}; use crossterm::event::{self, Event}; use std::{ + process, sync::{ atomic::{AtomicBool, Ordering}, Arc, @@ -44,33 +46,12 @@ impl Input { let arc_desired = Arc::clone(&desired_state); let arc_current = Arc::clone(¤t_state); - thread::spawn(move || loop { - if arc_desired.get() { - if !arc_current.load(Ordering::Relaxed) { - log::info!("input polling resumed"); - - tx.send(InputEvent::State(InputState::Polling)) - .expect("send state failed"); - } - arc_current.store(true, Ordering::Relaxed); - - if let Some(e) = Self::poll(POLL_DURATION) - .expect("failed to pull events.") - { - tx.send(InputEvent::Input(e)) - .expect("send input failed"); - } - } else { - if arc_current.load(Ordering::Relaxed) { - log::info!("input polling suspended"); - - tx.send(InputEvent::State(InputState::Paused)) - .expect("send state failed"); - } - - arc_current.store(false, Ordering::Relaxed); - - arc_desired.wait(true); + thread::spawn(move || { + if let Err(e) = + Self::input_loop(&arc_desired, &arc_current, &tx) + { + log::error!("input thread error: {}", e); + process::abort(); } }); @@ -108,4 +89,35 @@ impl Input { Ok(None) } } + + fn input_loop( + arc_desired: &Arc>, + arc_current: &Arc, + tx: &Sender, + ) -> Result<()> { + loop { + if arc_desired.get() { + if !arc_current.load(Ordering::Relaxed) { + log::info!("input polling resumed"); + + tx.send(InputEvent::State(InputState::Polling))?; + } + arc_current.store(true, Ordering::Relaxed); + + if let Some(e) = Self::poll(POLL_DURATION)? { + tx.send(InputEvent::Input(e))?; + } + } else { + if arc_current.load(Ordering::Relaxed) { + log::info!("input polling suspended"); + + tx.send(InputEvent::State(InputState::Paused))? + } + + arc_current.store(false, Ordering::Relaxed); + + arc_desired.wait(true); + } + } + } } From 9f37835dc44f151c52769fc38686acb8eef41344 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Sun, 9 May 2021 20:39:26 +0200 Subject: [PATCH 02/15] cleanup some more expects --- src/components/utils/filetree.rs | 1 + src/main.rs | 28 ++++++++++++++++++++-------- src/tabs/status.rs | 9 +++++---- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/components/utils/filetree.rs b/src/components/utils/filetree.rs index b77c7ad2..bc3b040b 100644 --- a/src/components/utils/filetree.rs +++ b/src/components/utils/filetree.rs @@ -207,6 +207,7 @@ impl FileTreeItems { for c in &ancestors { if c.parent().is_some() && !paths_added.contains(c) { paths_added.insert(c); + //TODO: get rid of expect let path_string = String::from(c.to_str().expect("invalid path")); let is_collapsed = collapsed.contains(&path_string); diff --git a/src/main.rs b/src/main.rs index f326bc41..3263e6ce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,8 @@ #![deny(clippy::needless_update)] #![allow(clippy::module_name_repetitions)] #![allow(clippy::multiple_crate_versions)] +//TODO: +// #![deny(clippy::expect_used)] mod app; mod clipboard; @@ -100,7 +102,7 @@ fn main() -> Result<()> { setup_terminal()?; defer! { - shutdown_terminal().expect("shutdown failed"); + shutdown_terminal(); } set_panic_handlers()?; @@ -181,10 +183,19 @@ fn setup_terminal() -> Result<()> { Ok(()) } -fn shutdown_terminal() -> Result<()> { - io::stdout().execute(LeaveAlternateScreen)?; - disable_raw_mode()?; - Ok(()) +fn shutdown_terminal() { + let leave_screen = + io::stdout().execute(LeaveAlternateScreen).map(|_f| ()); + + if let Err(e) = leave_screen { + eprintln!("leave_screen failed:\n{}", e); + } + + let leave_raw_mode = disable_raw_mode(); + + if let Err(e) = leave_raw_mode { + eprintln!("leave_raw_mode failed:\n{}", e); + } } fn draw( @@ -336,19 +347,20 @@ fn set_panic_handlers() -> Result<()> { // regular panic handler panic::set_hook(Box::new(|e| { let backtrace = Backtrace::new(); + //TODO: create macro to do both in one log::error!("panic: {:?}\ntrace:\n{:?}", e, backtrace); - shutdown_terminal().expect("shutdown failed inside panic"); eprintln!("panic: {:?}\ntrace:\n{:?}", e, backtrace); + shutdown_terminal(); })); // global threadpool rayon_core::ThreadPoolBuilder::new() .panic_handler(|e| { let backtrace = Backtrace::new(); + //TODO: create macro to do both in one log::error!("panic: {:?}\ntrace:\n{:?}", e, backtrace); - shutdown_terminal() - .expect("shutdown failed inside panic"); eprintln!("panic: {:?}\ntrace:\n{:?}", e, backtrace); + shutdown_terminal(); process::abort(); }) .num_threads(4) diff --git a/src/tabs/status.rs b/src/tabs/status.rs index be59e777..bb5f285a 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -118,7 +118,7 @@ impl DrawableComponent for Status { self.index.draw(f, left_chunks[1])?; self.diff.draw(f, chunks[1])?; self.draw_branch_state(f, &left_chunks); - Self::draw_repo_state(f, left_chunks[0]); + Self::draw_repo_state(f, left_chunks[0])?; Ok(()) } @@ -213,12 +213,11 @@ impl Status { fn draw_repo_state( f: &mut tui::Frame, r: tui::layout::Rect, - ) { + ) -> Result<()> { if let Ok(state) = asyncgit::sync::repo_state(CWD) { if state != RepoState::Clean { let txt = format!("{:?}", state); - let txt_len = u16::try_from(txt.len()) - .expect("state name too long"); + let txt_len = u16::try_from(txt.len())?; let w = Paragraph::new(txt) .style(Style::default().fg(Color::Red)) .alignment(Alignment::Left); @@ -235,6 +234,8 @@ impl Status { f.render_widget(w, rect); } } + + Ok(()) } fn can_focus_diff(&self) -> bool { From 4f6aceb3ec9a2631d795654381f6e9841d889743 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Sun, 9 May 2021 22:53:12 +0200 Subject: [PATCH 03/15] merging a branch (#696) * merging arbitrary branch * cleanup intermediate in-merge state --- asyncgit/src/sync/merge.rs | 49 ++++++++++++++ asyncgit/src/sync/mod.rs | 2 + src/app.rs | 4 ++ src/components/branchlist.rs | 30 ++++++++- src/components/reset.rs | 14 ++-- src/keys.rs | 10 ++- src/queue.rs | 1 + src/strings.rs | 38 +++++++++-- src/tabs/status.rs | 121 +++++++++++++++++++++-------------- vim_style_key_config.ron | 3 + 10 files changed, 210 insertions(+), 62 deletions(-) create mode 100644 asyncgit/src/sync/merge.rs diff --git a/asyncgit/src/sync/merge.rs b/asyncgit/src/sync/merge.rs new file mode 100644 index 00000000..c61e7411 --- /dev/null +++ b/asyncgit/src/sync/merge.rs @@ -0,0 +1,49 @@ +use crate::{ + error::{Error, Result}, + sync::{reset_stage, reset_workdir, utils}, +}; +use git2::{BranchType, MergeOptions}; +use scopetime::scope_time; + +/// does these steps: +/// * reset all staged changes, +/// * revert all changes in workdir +/// * cleanup repo merge state +pub fn abort_merge(repo_path: &str) -> Result<()> { + scope_time!("cleanup_state"); + + let repo = utils::repo(repo_path)?; + + reset_stage(repo_path, "*")?; + reset_workdir(repo_path, "*")?; + + repo.cleanup_state()?; + + Ok(()) +} + +/// +pub fn merge_branch(repo_path: &str, branch: &str) -> Result<()> { + scope_time!("merge_branch"); + + let repo = utils::repo(repo_path)?; + + let branch = repo.find_branch(branch, BranchType::Local)?; + + let id = branch.into_reference().peel_to_commit()?; + + let annotated = repo.find_annotated_commit(id.id())?; + + let (analysis, _) = repo.merge_analysis(&[&annotated])?; + + //TODO: support merge on unborn + if analysis.is_unborn() { + return Err(Error::Generic("head is unborn".into())); + } + + let mut opt = MergeOptions::default(); + + repo.merge(&[&annotated], Some(&mut opt), None)?; + + Ok(()) +} diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 1bcba4b8..7bb41c95 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -15,6 +15,7 @@ mod hooks; mod hunks; mod ignore; mod logwalker; +mod merge; mod patches; pub mod remotes; mod reset; @@ -49,6 +50,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 merge::{abort_merge, merge_branch}; pub use remotes::{ get_default_remote, get_remotes, push::AsyncProgress, tags::PushTagsProgress, diff --git a/src/app.rs b/src/app.rs index 801f0105..0daa984a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -607,6 +607,10 @@ impl App { self.pull_popup.try_conflict_free_merge(rebase); flags.insert(NeedsUpdate::ALL); } + Action::AbortMerge => { + self.status_tab.abort_merge(); + flags.insert(NeedsUpdate::ALL); + } }; Ok(()) diff --git a/src/components/branchlist.rs b/src/components/branchlist.rs index de41295c..c79a22e4 100644 --- a/src/components/branchlist.rs +++ b/src/components/branchlist.rs @@ -12,7 +12,7 @@ use crate::{ use anyhow::Result; use asyncgit::{ sync::{ - branch::checkout_remote_branch, checkout_branch, + self, branch::checkout_remote_branch, checkout_branch, get_branches_info, BranchInfo, }, CWD, @@ -150,6 +150,14 @@ impl Component for BranchListComponent { self.local, )); + out.push(CommandInfo::new( + strings::commands::merge_branch_popup( + &self.key_config, + ), + !self.selection_is_cur_branch(), + self.local, + )); + out.push(CommandInfo::new( strings::commands::rename_branch_popup( &self.key_config, @@ -222,6 +230,16 @@ impl Component for BranchListComponent { ), ), ); + } else if e == self.key_config.merge_branch + && !self.selection_is_cur_branch() + && self.valid_selection() + { + try_or_popup!( + self, + "merge branch error:", + self.merge_branch() + ); + self.hide(); } else if e == self.key_config.tab_toggle { self.local = !self.local; self.update_branches()?; @@ -294,6 +312,16 @@ impl BranchListComponent { !self.branches.is_empty() } + fn merge_branch(&self) -> Result<()> { + if let Some(branch) = + self.branches.get(usize::from(self.selection)) + { + sync::merge_branch(CWD, &branch.name)?; + } + + Ok(()) + } + fn selection_is_cur_branch(&self) -> bool { self.branches .iter() diff --git a/src/components/reset.rs b/src/components/reset.rs index eeef90d1..2004545f 100644 --- a/src/components/reset.rs +++ b/src/components/reset.rs @@ -138,8 +138,8 @@ impl ResetComponent { if let Some(ref a) = self.target { return match a { Action::Reset(_) => ( - strings::confirm_title_reset(&self.key_config), - strings::confirm_msg_reset(&self.key_config), + strings::confirm_title_reset(), + strings::confirm_msg_reset(), ), Action::StashDrop(_) => ( strings::confirm_title_stashdrop( @@ -152,12 +152,12 @@ impl ResetComponent { strings::confirm_msg_stashpop(&self.key_config), ), Action::ResetHunk(_, _) => ( - strings::confirm_title_reset(&self.key_config), + strings::confirm_title_reset(), strings::confirm_msg_resethunk(&self.key_config), ), Action::ResetLines(_, lines) => ( - strings::confirm_title_reset(&self.key_config), - strings::confirm_msg_reset_lines(&self.key_config,lines.len()), + strings::confirm_title_reset(), + strings::confirm_msg_reset_lines(lines.len()), ), Action::DeleteBranch(branch_ref) => ( strings::confirm_title_delete_branch( @@ -181,6 +181,10 @@ impl ResetComponent { strings::confirm_title_merge(&self.key_config,*rebase), strings::confirm_msg_merge(&self.key_config,*incoming,*rebase), ), + Action::AbortMerge => ( + strings::confirm_title_abortmerge(), + strings::confirm_msg_abortmerge(), + ), }; } diff --git a/src/keys.rs b/src/keys.rs index 168ed72c..6ee72ea9 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -68,9 +68,11 @@ pub struct KeyConfig { pub rename_branch: KeyEvent, pub select_branch: KeyEvent, pub delete_branch: KeyEvent, + pub merge_branch: KeyEvent, pub push: KeyEvent, pub force_push: KeyEvent, pub pull: KeyEvent, + pub abort_merge: KeyEvent, } #[rustfmt::skip] @@ -121,13 +123,15 @@ impl Default for KeyConfig { log_tag_commit: KeyEvent { code: KeyCode::Char('t'), modifiers: KeyModifiers::empty()}, commit_amend: KeyEvent { code: KeyCode::Char('a'), modifiers: KeyModifiers::CONTROL}, copy: KeyEvent { code: KeyCode::Char('y'), modifiers: KeyModifiers::empty()}, - create_branch: KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::NONE}, - rename_branch: KeyEvent { code: KeyCode::Char('r'), modifiers: KeyModifiers::NONE}, - select_branch: KeyEvent { code: KeyCode::Char('b'), modifiers: KeyModifiers::NONE}, + create_branch: KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::empty()}, + rename_branch: KeyEvent { code: KeyCode::Char('r'), modifiers: KeyModifiers::empty()}, + select_branch: KeyEvent { code: KeyCode::Char('b'), modifiers: KeyModifiers::empty()}, delete_branch: KeyEvent{code: KeyCode::Char('D'), modifiers: KeyModifiers::SHIFT}, + merge_branch: KeyEvent{code: KeyCode::Char('m'), modifiers: KeyModifiers::empty()}, push: KeyEvent { code: KeyCode::Char('p'), modifiers: KeyModifiers::empty()}, force_push: KeyEvent { code: KeyCode::Char('P'), modifiers: KeyModifiers::SHIFT}, pull: KeyEvent { code: KeyCode::Char('f'), modifiers: KeyModifiers::empty()}, + abort_merge: KeyEvent { code: KeyCode::Char('M'), modifiers: KeyModifiers::SHIFT}, } } } diff --git a/src/queue.rs b/src/queue.rs index 131de065..a5226e51 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -33,6 +33,7 @@ pub enum Action { DeleteBranch(String), ForcePush(String, bool), PullMerge { incoming: usize, rebase: bool }, + AbortMerge, } /// diff --git a/src/strings.rs b/src/strings.rs index 01ce8faa..c52298a4 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -86,7 +86,7 @@ pub fn stash_popup_title(_key_config: &SharedKeyConfig) -> String { pub fn stash_popup_msg(_key_config: &SharedKeyConfig) -> String { "type name (optional)".to_string() } -pub fn confirm_title_reset(_key_config: &SharedKeyConfig) -> String { +pub fn confirm_title_reset() -> String { "Reset".to_string() } pub fn confirm_title_stashdrop( @@ -120,13 +120,17 @@ pub fn confirm_msg_merge( format!("Merge of {} incoming commits?", incoming) } } -pub fn confirm_msg_reset(_key_config: &SharedKeyConfig) -> String { + +pub fn confirm_title_abortmerge() -> String { + "Abort merge?".to_string() +} +pub fn confirm_msg_abortmerge() -> String { + "This will revert all changes. Are you sure?".to_string() +} +pub fn confirm_msg_reset() -> String { "confirm file reset?".to_string() } -pub fn confirm_msg_reset_lines( - _key_config: &SharedKeyConfig, - lines: usize, -) -> String { +pub fn confirm_msg_reset_lines(lines: usize) -> String { format!( "are you sure you want to discard {} selected lines?", lines @@ -520,6 +524,16 @@ pub mod commands { CMD_GROUP_GENERAL, ) } + pub fn abort_merge(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Abort merge [{}]", + key_config.get_hint(key_config.abort_merge), + ), + "abort ongoing merge", + CMD_GROUP_GENERAL, + ) + } pub fn select_staging( key_config: &SharedKeyConfig, ) -> CommandText { @@ -918,6 +932,18 @@ pub mod commands { CMD_GROUP_GENERAL, ) } + pub fn merge_branch_popup( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Merge [{}]", + key_config.get_hint(key_config.merge_branch), + ), + "merge a branch", + CMD_GROUP_GENERAL, + ) + } pub fn select_branch_popup( key_config: &SharedKeyConfig, ) -> CommandText { diff --git a/src/tabs/status.rs b/src/tabs/status.rs index bb5f285a..cccca383 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -8,7 +8,7 @@ use crate::{ }, keys::SharedKeyConfig, queue::{Action, InternalEvent, Queue, ResetItem}, - strings, + strings, try_or_popup, ui::style::SharedTheme, }; use anyhow::Result; @@ -465,6 +465,61 @@ impl Status { .as_ref() .map_or(true, |state| state.ahead > 0) } + + fn can_abort_merge() -> bool { + sync::repo_state(CWD).unwrap_or(RepoState::Clean) + == RepoState::Merge + } + + pub fn abort_merge(&self) { + try_or_popup!(self, "abort merge", sync::abort_merge(CWD)) + } + + fn commands_nav( + &self, + out: &mut Vec, + force_all: bool, + ) { + let focus_on_diff = self.is_focus_on_diff(); + out.push( + CommandInfo::new( + strings::commands::diff_focus_left(&self.key_config), + true, + (self.visible && focus_on_diff) || force_all, + ) + .order(strings::order::NAV), + ); + out.push( + CommandInfo::new( + strings::commands::diff_focus_right(&self.key_config), + self.can_focus_diff(), + (self.visible && !focus_on_diff) || force_all, + ) + .order(strings::order::NAV), + ); + out.push( + CommandInfo::new( + strings::commands::select_staging(&self.key_config), + !focus_on_diff, + (self.visible + && !focus_on_diff + && self.focus == Focus::WorkDir) + || force_all, + ) + .order(strings::order::NAV), + ); + out.push( + CommandInfo::new( + strings::commands::select_unstaged(&self.key_config), + !focus_on_diff, + (self.visible + && !focus_on_diff + && self.focus == Focus::Stage) + || force_all, + ) + .order(strings::order::NAV), + ); + } } impl Component for Status { @@ -507,6 +562,12 @@ impl Component for Status { true, !focus_on_diff, )); + + out.push(CommandInfo::new( + strings::commands::abort_merge(&self.key_config), + true, + Self::can_abort_merge() || force_all, + )); } { @@ -519,52 +580,6 @@ impl Component for Status { }, self.visible || force_all, )); - out.push( - CommandInfo::new( - strings::commands::diff_focus_left( - &self.key_config, - ), - true, - (self.visible && focus_on_diff) || force_all, - ) - .order(strings::order::NAV), - ); - out.push( - CommandInfo::new( - strings::commands::diff_focus_right( - &self.key_config, - ), - self.can_focus_diff(), - (self.visible && !focus_on_diff) || force_all, - ) - .order(strings::order::NAV), - ); - out.push( - CommandInfo::new( - strings::commands::select_staging( - &self.key_config, - ), - !focus_on_diff, - (self.visible - && !focus_on_diff - && self.focus == Focus::WorkDir) - || force_all, - ) - .order(strings::order::NAV), - ); - out.push( - CommandInfo::new( - strings::commands::select_unstaged( - &self.key_config, - ), - !focus_on_diff, - (self.visible - && !focus_on_diff - && self.focus == Focus::Stage) - || force_all, - ) - .order(strings::order::NAV), - ); out.push( CommandInfo::new( @@ -576,6 +591,8 @@ impl Component for Status { ) .hidden(), ); + + self.commands_nav(out, force_all); } visibility_blocking(self) @@ -653,6 +670,16 @@ impl Component for Status { && !self.is_focus_on_diff() { self.pull(); + Ok(EventState::Consumed) + } else if k == self.key_config.abort_merge + && Self::can_abort_merge() + { + self.queue.borrow_mut().push_back( + InternalEvent::ConfirmAction( + Action::AbortMerge, + ), + ); + Ok(EventState::Consumed) } else { Ok(EventState::NotConsumed) diff --git a/vim_style_key_config.ron b/vim_style_key_config.ron index abdf4986..0a64ae4d 100644 --- a/vim_style_key_config.ron +++ b/vim_style_key_config.ron @@ -72,6 +72,9 @@ rename_branch: ( code: Char('r'), modifiers: ( bits: 0,),), select_branch: ( code: Char('b'), modifiers: ( bits: 0,),), delete_branch: ( code: Char('D'), modifiers: ( bits: 1,),), + merge_branch: ( code: Char('m'), modifiers: ( bits: 0,),), + abort_merge: ( code: Char('M'), modifiers: ( bits: 1,),), + push: ( code: Char('p'), modifiers: ( bits: 0,),), force_push: ( code: Char('P'), modifiers: ( bits: 1,),), pull: ( code: Char('f'), modifiers: ( bits: 0,),), From 755f9c08bfdf14b70305e23e93fbbf7b69b778da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 May 2021 10:17:44 +0000 Subject: [PATCH 04/15] Bump backtrace from 0.3.58 to 0.3.59 Bumps [backtrace](https://github.com/rust-lang/backtrace-rs) from 0.3.58 to 0.3.59. - [Release notes](https://github.com/rust-lang/backtrace-rs/releases) - [Commits](https://github.com/rust-lang/backtrace-rs/compare/0.3.58...0.3.59) Signed-off-by: dependabot[bot] --- Cargo.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ce98a44..73c67223 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,9 +2,9 @@ # It is not intended for manual editing. [[package]] name = "addr2line" -version = "0.14.1" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7" +checksum = "03345e98af8f3d786b6d9f656ccfa6ac316d954e92bc4841f0bba20789d5fb5a" dependencies = [ "gimli", ] @@ -78,9 +78,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" -version = "0.3.58" +version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88fb5a785d6b44fd9d6700935608639af1b8356de1e55d5f7c2740f4faa15d82" +checksum = "4717cfcbfaa661a0fd48f8453951837ae7e8f81e481fbb136e3202d72805a744" dependencies = [ "addr2line", "cc", @@ -306,9 +306,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" +checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" [[package]] name = "git2" @@ -654,9 +654,9 @@ dependencies = [ [[package]] name = "object" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4" +checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170" [[package]] name = "once_cell" From 7e9ab3d2d35d3484dda0b872b595759c57da3c8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 May 2021 10:17:41 +0000 Subject: [PATCH 05/15] Bump url from 2.2.1 to 2.2.2 Bumps [url](https://github.com/servo/rust-url) from 2.2.1 to 2.2.2. - [Release notes](https://github.com/servo/rust-url/releases) - [Commits](https://github.com/servo/rust-url/compare/v2.2.1...v2.2.2) Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 73c67223..64258d68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1170,9 +1170,9 @@ checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "url" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" dependencies = [ "form_urlencoded", "idna", From a3b4b98d4f2c95d38369dafaeb9b614bd4527605 Mon Sep 17 00:00:00 2001 From: bruceCoelho Date: Mon, 10 May 2021 23:18:12 +0100 Subject: [PATCH 06/15] Preserves same modal behavior per #679 --- src/components/branchlist.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/branchlist.rs b/src/components/branchlist.rs index c79a22e4..881f84ed 100644 --- a/src/components/branchlist.rs +++ b/src/components/branchlist.rs @@ -202,7 +202,6 @@ impl Component for BranchListComponent { self.queue .borrow_mut() .push_back(InternalEvent::CreateBranch); - self.hide(); } else if e == self.key_config.rename_branch && self.valid_selection() { From bc7cef747c5f2973f788752b3ba9b6b0dc4e4864 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Tue, 11 May 2021 09:57:55 +0200 Subject: [PATCH 07/15] update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c648c3f4..9a68e07c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - warning if commit subject line gets too long ([#478](https://github.com/extrawurst/gitui/issues/478)) ## Changed -- smarter log timestamps ([#682](https://github.com/extrawurst/gitui/issues/682)) +- smarter log timestamps ([#682](https://github.com/extrawurst/gitui/issues/682)) +- create-branch popup aligned with rename-branch [[@bruceCoelho](https://github.com/bruceCoelho)] ([#679](https://github.com/extrawurst/gitui/issues/679)) ## [0.15.0] - 2020-04-27 From f30ec498862115dd9b14aa8b2d1836e00336c43e Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Tue, 11 May 2021 18:26:12 +0200 Subject: [PATCH 08/15] show merge head ids in merge state (#697) --- .github/workflows/ci.yml | 2 +- Cargo.lock | 12 +++++----- asyncgit/Cargo.toml | 2 ++ asyncgit/src/sync/merge.rs | 46 +++++++++++++++++++++++++++++++++++++- asyncgit/src/sync/mod.rs | 2 +- src/tabs/status.rs | 15 +++++++++++-- 6 files changed, 68 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24e953ea..1bedeb35 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: - name: MacOS Workaround if: matrix.os == 'macos-latest' - run: cargo clean --locked -p serde_derive -p thiserror + run: cargo clean -p serde_derive -p thiserror - name: Install Rust uses: actions-rs/toolchain@v1 diff --git a/Cargo.lock b/Cargo.lock index 64258d68..9b9f2f14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -312,9 +312,9 @@ checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" [[package]] name = "git2" -version = "0.13.18" +version = "0.13.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b483c6c2145421099df1b4efd50e0f6205479a072199460eff852fa15e5603c7" +checksum = "17929de7239dea9f68aa14f94b2ab4974e7b24c1314275ffcc12a7758172fa18" dependencies = [ "bitflags", "libc", @@ -467,9 +467,9 @@ checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" [[package]] name = "libgit2-sys" -version = "0.12.19+1.1.0" +version = "0.12.20+1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f322155d574c8b9ebe991a04f6908bb49e68a79463338d24a43d6274cb6443e6" +checksum = "1e2f09917e00b9ad194ae72072bb5ada2cca16d8171a43e91ddba2afbb02664b" dependencies = [ "cc", "libc", @@ -666,9 +666,9 @@ checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" [[package]] name = "openssl-probe" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" +checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" [[package]] name = "openssl-src" diff --git a/asyncgit/Cargo.toml b/asyncgit/Cargo.toml index a3240c21..b1872a65 100644 --- a/asyncgit/Cargo.toml +++ b/asyncgit/Cargo.toml @@ -14,6 +14,8 @@ keywords = ["git"] [dependencies] scopetime = { path = "../scopetime", version = "0.1" } git2 = { version = "0.13", features = ["vendored-openssl"] } +# git2 = { path = "../../github/git2-rs", features = ["vendored-openssl"]} +# git2 = { git="https://github.com/extrawurst/git2-rs.git", rev="513a8c9", features = ["vendored-openssl"]} rayon-core = "1.9" crossbeam-channel = "0.5" log = "0.4" diff --git a/asyncgit/src/sync/merge.rs b/asyncgit/src/sync/merge.rs index c61e7411..323d451b 100644 --- a/asyncgit/src/sync/merge.rs +++ b/asyncgit/src/sync/merge.rs @@ -1,10 +1,25 @@ use crate::{ error::{Error, Result}, - sync::{reset_stage, reset_workdir, utils}, + sync::{reset_stage, reset_workdir, utils, CommitId}, }; use git2::{BranchType, MergeOptions}; use scopetime::scope_time; +/// +pub fn merge_state_info(repo_path: &str) -> Result> { + scope_time!("merge_state_info"); + + let mut repo = utils::repo(repo_path)?; + + let mut ids: Vec = Vec::new(); + repo.mergehead_foreach(|id| { + ids.push(CommitId::from(*id)); + true + })?; + + Ok(ids) +} + /// does these steps: /// * reset all staged changes, /// * revert all changes in workdir @@ -47,3 +62,32 @@ pub fn merge_branch(repo_path: &str, branch: &str) -> Result<()> { Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::sync::{ + create_branch, + tests::{repo_init, write_commit_file}, + }; + + #[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(); + + let c1 = + write_commit_file(&repo, "test.txt", "test", "commit1"); + + create_branch(repo_path, "foo").unwrap(); + + write_commit_file(&repo, "test.txt", "test2", "commit2"); + + merge_branch(repo_path, "master").unwrap(); + + let mergeheads = merge_state_info(repo_path).unwrap(); + + assert_eq!(mergeheads[0], c1); + } +} diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 7bb41c95..94074d96 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -50,7 +50,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 merge::{abort_merge, merge_branch}; +pub use merge::{abort_merge, merge_branch, merge_state_info}; pub use remotes::{ get_default_remote, get_remotes, push::AsyncProgress, tags::PushTagsProgress, diff --git a/src/tabs/status.rs b/src/tabs/status.rs index cccca383..275dac4b 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -21,6 +21,7 @@ use asyncgit::{ }; use crossbeam_channel::Sender; use crossterm::event::Event; +use itertools::Itertools; use std::convert::Into; use std::convert::TryFrom; use tui::{ @@ -214,9 +215,19 @@ impl Status { f: &mut tui::Frame, r: tui::layout::Rect, ) -> Result<()> { - if let Ok(state) = asyncgit::sync::repo_state(CWD) { + if let Ok(state) = sync::repo_state(CWD) { if state != RepoState::Clean { - let txt = format!("{:?}", state); + let ids = + sync::merge_state_info(CWD).unwrap_or_default(); + let ids = format!( + "({})", + ids.iter() + .map(|id| sync::CommitId::get_short_string( + id + )) + .join(",") + ); + let txt = format!("{:?} {}", state, ids); let txt_len = u16::try_from(txt.len())?; let w = Paragraph::new(txt) .style(Style::default().fg(Color::Red)) From b5327a65c111b5ca867b08c55450f22602544635 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Wed, 12 May 2021 09:01:08 +0200 Subject: [PATCH 09/15] rename --- asyncgit/src/sync/merge.rs | 6 +++--- asyncgit/src/sync/mod.rs | 2 +- src/tabs/status.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/asyncgit/src/sync/merge.rs b/asyncgit/src/sync/merge.rs index 323d451b..5c3caa55 100644 --- a/asyncgit/src/sync/merge.rs +++ b/asyncgit/src/sync/merge.rs @@ -6,8 +6,8 @@ use git2::{BranchType, MergeOptions}; use scopetime::scope_time; /// -pub fn merge_state_info(repo_path: &str) -> Result> { - scope_time!("merge_state_info"); +pub fn mergehead_ids(repo_path: &str) -> Result> { + scope_time!("mergehead_ids"); let mut repo = utils::repo(repo_path)?; @@ -86,7 +86,7 @@ mod tests { merge_branch(repo_path, "master").unwrap(); - let mergeheads = merge_state_info(repo_path).unwrap(); + let mergeheads = mergehead_ids(repo_path).unwrap(); assert_eq!(mergeheads[0], c1); } diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 94074d96..592dc1d1 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -50,7 +50,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 merge::{abort_merge, merge_branch, merge_state_info}; +pub use merge::{abort_merge, merge_branch, mergehead_ids}; pub use remotes::{ get_default_remote, get_remotes, push::AsyncProgress, tags::PushTagsProgress, diff --git a/src/tabs/status.rs b/src/tabs/status.rs index 275dac4b..f265f582 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -218,7 +218,7 @@ impl Status { if let Ok(state) = sync::repo_state(CWD) { if state != RepoState::Clean { let ids = - sync::merge_state_info(CWD).unwrap_or_default(); + sync::mergehead_ids(CWD).unwrap_or_default(); let ids = format!( "({})", ids.iter() From a073e0ac0225770b92ea638f415893cd40945c5e Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Wed, 12 May 2021 09:13:28 +0200 Subject: [PATCH 10/15] preserve ref name in annotated commit --- asyncgit/src/sync/merge.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/asyncgit/src/sync/merge.rs b/asyncgit/src/sync/merge.rs index 5c3caa55..20646636 100644 --- a/asyncgit/src/sync/merge.rs +++ b/asyncgit/src/sync/merge.rs @@ -45,9 +45,8 @@ pub fn merge_branch(repo_path: &str, branch: &str) -> Result<()> { let branch = repo.find_branch(branch, BranchType::Local)?; - let id = branch.into_reference().peel_to_commit()?; - - let annotated = repo.find_annotated_commit(id.id())?; + let annotated = + repo.reference_to_annotated_commit(&branch.into_reference())?; let (analysis, _) = repo.merge_analysis(&[&annotated])?; From f35ce0cbf4995ca2516060aeef3a29803a6378c0 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Wed, 12 May 2021 11:20:39 +0200 Subject: [PATCH 11/15] support doing merge commit (#701) --- asyncgit/src/sync/branch/merge_commit.rs | 45 ++++++++----- asyncgit/src/sync/merge.rs | 43 +++++++++++- asyncgit/src/sync/mod.rs | 4 +- src/components/commit.rs | 84 +++++++++++++++--------- src/strings.rs | 5 +- 5 files changed, 130 insertions(+), 51 deletions(-) diff --git a/asyncgit/src/sync/branch/merge_commit.rs b/asyncgit/src/sync/branch/merge_commit.rs index 40e436c6..368d3228 100644 --- a/asyncgit/src/sync/branch/merge_commit.rs +++ b/asyncgit/src/sync/branch/merge_commit.rs @@ -5,7 +5,7 @@ use crate::{ error::{Error, Result}, sync::{utils, CommitId}, }; -use git2::MergeOptions; +use git2::{Commit, MergeOptions}; use scopetime::scope_time; /// merge upstream using a merge commit without conflicts. fails if not possible without conflicts @@ -48,18 +48,6 @@ pub fn merge_upstream_commit( return Err(Error::Generic("creates conflicts".into())); } - let signature = - crate::sync::commit::signature_allow_undefined_name(&repo)?; - let mut index = repo.index()?; - let tree_id = index.write_tree()?; - let tree = repo.find_tree(tree_id)?; - - let head_commit = repo.find_commit( - crate::sync::utils::get_head_repo(&repo)?.into(), - )?; - let parents = vec![&head_commit, &upstream_commit]; - - //find remote url for this branch let remote_url = { let branch_refname = branch.get().name().ok_or_else(|| { @@ -75,20 +63,43 @@ pub fn merge_upstream_commit( remote.url().unwrap_or_default().to_string() }; + let commit_id = commit_merge_with_head( + &repo, + &[upstream_commit], + format!("Merge '{}' of {}", branch_name, remote_url).as_str(), + )?; + + Ok(commit_id) +} + +pub(crate) fn commit_merge_with_head( + repo: &git2::Repository, + commits: &[Commit], + msg: &str, +) -> Result { + let signature = + crate::sync::commit::signature_allow_undefined_name(repo)?; + let mut index = repo.index()?; + let tree_id = index.write_tree()?; + let tree = repo.find_tree(tree_id)?; + let head_commit = repo.find_commit( + crate::sync::utils::get_head_repo(repo)?.into(), + )?; + + let mut parents = vec![&head_commit]; + parents.extend(commits); + let commit_id = repo .commit( Some("HEAD"), &signature, &signature, - format!("Merge '{}' of {}", branch_name, remote_url) - .as_str(), + msg, &tree, parents.as_slice(), )? .into(); - repo.cleanup_state()?; - Ok(commit_id) } diff --git a/asyncgit/src/sync/merge.rs b/asyncgit/src/sync/merge.rs index 20646636..e7f9609f 100644 --- a/asyncgit/src/sync/merge.rs +++ b/asyncgit/src/sync/merge.rs @@ -1,8 +1,13 @@ +use std::fs::read_to_string; + use crate::{ error::{Error, Result}, - sync::{reset_stage, reset_workdir, utils, CommitId}, + sync::{ + branch::merge_commit::commit_merge_with_head, reset_stage, + reset_workdir, utils, CommitId, + }, }; -use git2::{BranchType, MergeOptions}; +use git2::{BranchType, Commit, MergeOptions}; use scopetime::scope_time; /// @@ -62,6 +67,40 @@ pub fn merge_branch(repo_path: &str, branch: &str) -> Result<()> { Ok(()) } +/// +pub fn merge_msg(repo_path: &str) -> Result { + scope_time!("merge_msg"); + + let repo = utils::repo(repo_path)?; + + let msg_file = repo.path().join("MERGE_MSG"); + + let content = read_to_string(msg_file).unwrap_or_default(); + + Ok(content) +} + +/// +pub fn merge_commit( + repo_path: &str, + msg: &str, + ids: &[CommitId], +) -> Result { + scope_time!("merge_commit"); + + let repo = utils::repo(repo_path)?; + + let mut commits: Vec = Vec::new(); + + for id in ids { + commits.push(repo.find_commit((*id).into())?); + } + + let id = commit_merge_with_head(&repo, &commits, msg)?; + + Ok(id) +} + #[cfg(test)] mod tests { use super::*; diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 592dc1d1..838724d6 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -50,7 +50,9 @@ pub use hooks::{ pub use hunks::{reset_hunk, stage_hunk, unstage_hunk}; pub use ignore::add_to_ignore; pub use logwalker::LogWalker; -pub use merge::{abort_merge, merge_branch, mergehead_ids}; +pub use merge::{ + abort_merge, merge_branch, merge_commit, merge_msg, mergehead_ids, +}; pub use remotes::{ get_default_remote, get_remotes, push::AsyncProgress, tags::PushTagsProgress, diff --git a/src/components/commit.rs b/src/components/commit.rs index c7b7efea..49ff4df7 100644 --- a/src/components/commit.rs +++ b/src/components/commit.rs @@ -13,7 +13,10 @@ use crate::{ use anyhow::Result; use asyncgit::{ cached, - sync::{self, utils::get_config_string, CommitId, HookResult}, + sync::{ + self, utils::get_config_string, CommitId, HookResult, + RepoState, + }, CWD, }; use crossterm::event::Event; @@ -30,9 +33,15 @@ use tui::{ Frame, }; +enum Mode { + Normal, + Amend(CommitId), + Merge(Vec), +} + pub struct CommitComponent { input: TextInputComponent, - amend: Option, + mode: Mode, queue: Queue, key_config: SharedKeyConfig, git_branch_name: cached::BranchName, @@ -128,25 +137,34 @@ impl Component for CommitComponent { } fn show(&mut self) -> Result<()> { - if self.amend.is_some() { + //only clear text if it was not a normal commit dlg before, so to preserve old commit msg that was edited + if !matches!(self.mode, Mode::Normal) { self.input.clear(); } - self.amend = None; - self.input - .set_title(strings::commit_title(&self.key_config)); + self.mode = Mode::Normal; - self.commit_template = - get_config_string(CWD, "commit.template") - .ok() - .flatten() - .and_then(|path| read_to_string(path).ok()); + self.mode = if sync::repo_state(CWD)? == RepoState::Merge { + let ids = sync::mergehead_ids(CWD)?; + self.input.set_title(strings::commit_title_merge()); + self.input.set_text(sync::merge_msg(CWD)?); + Mode::Merge(ids) + } else { + self.commit_template = + get_config_string(CWD, "commit.template") + .ok() + .flatten() + .and_then(|path| read_to_string(path).ok()); - if self.is_empty() { - if let Some(s) = &self.commit_template { - self.input.set_text(s.clone()); + if self.is_empty() { + if let Some(s) = &self.commit_template { + self.input.set_text(s.clone()); + } } - } + + self.input.set_title(strings::commit_title()); + Mode::Normal + }; self.input.show()?; @@ -163,7 +181,8 @@ impl CommitComponent { ) -> Self { Self { queue, - amend: None, + mode: Mode::Normal, + input: TextInputComponent::new( theme.clone(), key_config.clone(), @@ -281,10 +300,10 @@ impl CommitComponent { fn commit(&mut self) -> Result<()> { let msg = self.input.get_text().clone(); self.input.clear(); - self.commit_msg(msg) + self.commit_with_msg(msg) } - fn commit_msg(&mut self, msg: String) -> Result<()> { + fn commit_with_msg(&mut self, msg: String) -> Result<()> { if let HookResult::NotOk(e) = sync::hooks_pre_commit(CWD)? { log::error!("pre-commit hook error: {}", e); self.queue.borrow_mut().push_back( @@ -309,10 +328,12 @@ impl CommitComponent { return Ok(()); } - let res = self.amend.map_or_else( - || sync::commit(CWD, &msg), - |amend| sync::amend(CWD, amend, &msg), - ); + let res = match &self.mode { + Mode::Normal => sync::commit(CWD, &msg), + Mode::Amend(amend) => sync::amend(CWD, *amend, &msg), + Mode::Merge(ids) => sync::merge_commit(CWD, &msg, ids), + }; + if let Err(e) = res { log::error!("commit error: {}", &e); self.queue.borrow_mut().push_back( @@ -348,7 +369,7 @@ impl CommitComponent { } fn can_amend(&self) -> bool { - self.amend.is_none() + matches!(self.mode, Mode::Normal) && sync::get_head(CWD).is_ok() && (self.is_empty() || !self.is_changed()) } @@ -363,16 +384,19 @@ impl CommitComponent { } fn amend(&mut self) -> Result<()> { - let id = sync::get_head(CWD)?; - self.amend = Some(id); + if self.can_amend() { + let id = sync::get_head(CWD)?; + self.mode = Mode::Amend(id); - let details = sync::get_commit_details(CWD, id)?; + let details = sync::get_commit_details(CWD, id)?; - self.input - .set_title(strings::commit_title_amend(&self.key_config)); + self.input.set_title(strings::commit_title_amend( + &self.key_config, + )); - if let Some(msg) = details.message { - self.input.set_text(msg.combine()); + if let Some(msg) = details.message { + self.input.set_text(msg.combine()); + } } Ok(()) diff --git a/src/strings.rs b/src/strings.rs index c52298a4..2b6286f3 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -62,9 +62,12 @@ pub fn msg_opening_editor(_key_config: &SharedKeyConfig) -> String { pub fn msg_title_error(_key_config: &SharedKeyConfig) -> String { "Error".to_string() } -pub fn commit_title(_key_config: &SharedKeyConfig) -> String { +pub fn commit_title() -> String { "Commit".to_string() } +pub fn commit_title_merge() -> String { + "Commit (Merge)".to_string() +} pub fn commit_title_amend(_key_config: &SharedKeyConfig) -> String { "Commit (Amend)".to_string() } From 3643cea44db73c24d899931f3726273d8a32c8b8 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Wed, 12 May 2021 11:37:08 +0200 Subject: [PATCH 12/15] update branchlist after creating a branch (#702) --- src/app.rs | 3 +++ src/components/create_branch.rs | 2 +- src/queue.rs | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index 0daa984a..9ce52a65 100644 --- a/src/app.rs +++ b/src/app.rs @@ -475,6 +475,9 @@ impl App { if flags.contains(NeedsUpdate::COMMANDS) { self.update_commands(); } + if flags.contains(NeedsUpdate::BRANCHES) { + self.select_branch_popup.update_branches()?; + } Ok(()) } diff --git a/src/components/create_branch.rs b/src/components/create_branch.rs index d74b1ec7..f3c77ebb 100644 --- a/src/components/create_branch.rs +++ b/src/components/create_branch.rs @@ -123,7 +123,7 @@ impl CreateBranchComponent { match res { Ok(_) => { self.queue.borrow_mut().push_back( - InternalEvent::Update(NeedsUpdate::ALL), + InternalEvent::Update(NeedsUpdate::BRANCHES), ); } Err(e) => { diff --git a/src/queue.rs b/src/queue.rs index a5226e51..6db977b1 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -12,6 +12,8 @@ bitflags! { const DIFF = 0b010; /// commands might need updating (app::update_commands) const COMMANDS = 0b100; + /// branches have changed + const BRANCHES = 0b1000; } } From ca808ca300d9658527d885f0643c819001336c15 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Wed, 12 May 2021 11:37:21 +0200 Subject: [PATCH 13/15] update state after merge (#703) --- src/components/branchlist.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/branchlist.rs b/src/components/branchlist.rs index 881f84ed..7db47b4f 100644 --- a/src/components/branchlist.rs +++ b/src/components/branchlist.rs @@ -239,6 +239,9 @@ impl Component for BranchListComponent { self.merge_branch() ); self.hide(); + self.queue.borrow_mut().push_back( + InternalEvent::Update(NeedsUpdate::ALL), + ); } else if e == self.key_config.tab_toggle { self.local = !self.local; self.update_branches()?; From 8767642f93fa49d8eb562657638fec98ac38ac79 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Wed, 12 May 2021 15:24:35 +0200 Subject: [PATCH 14/15] allow pull that creates conflicts now --- asyncgit/src/sync/branch/merge_commit.rs | 95 ++++++++++++------------ asyncgit/src/sync/branch/merge_ff.rs | 6 +- asyncgit/src/sync/merge.rs | 12 ++- 3 files changed, 63 insertions(+), 50 deletions(-) diff --git a/asyncgit/src/sync/branch/merge_commit.rs b/asyncgit/src/sync/branch/merge_commit.rs index 368d3228..2084bd12 100644 --- a/asyncgit/src/sync/branch/merge_commit.rs +++ b/asyncgit/src/sync/branch/merge_commit.rs @@ -3,16 +3,18 @@ use super::BranchType; use crate::{ error::{Error, Result}, - sync::{utils, CommitId}, + sync::{merge_msg, utils, CommitId}, }; -use git2::{Commit, MergeOptions}; +use git2::Commit; use scopetime::scope_time; -/// merge upstream using a merge commit without conflicts. fails if not possible without conflicts +/// merge upstream using a merge commit if we did not create conflicts. +/// if we did not create conflicts we create a merge commit and return the commit id. +/// Otherwise we return `None` pub fn merge_upstream_commit( repo_path: &str, branch_name: &str, -) -> Result { +) -> Result> { scope_time!("merge_upstream_commit"); let repo = utils::repo(repo_path)?; @@ -22,10 +24,10 @@ pub fn merge_upstream_commit( let upstream_commit = upstream.get().peel_to_commit()?; - let annotated_upstream = - repo.find_annotated_commit(upstream_commit.id())?; + let annotated_upstream = repo + .reference_to_annotated_commit(&upstream.into_reference())?; - let (analysis, _) = + let (analysis, pref) = repo.merge_analysis(&[&annotated_upstream])?; if !analysis.is_normal() { @@ -34,42 +36,29 @@ pub fn merge_upstream_commit( )); } - //TODO: support merge on unborn + if analysis.is_fast_forward() && pref.is_fastforward_only() { + return Err(Error::Generic( + "ff merge would be possible".into(), + )); + } + + //TODO: support merge on unborn? if analysis.is_unborn() { return Err(Error::Generic("head is unborn".into())); } - let mut opt = MergeOptions::default(); - opt.fail_on_conflict(true); + repo.merge(&[&annotated_upstream], None, None)?; - repo.merge(&[&annotated_upstream], Some(&mut opt), None)?; + if !repo.index()?.has_conflicts() { + let msg = merge_msg(repo_path)?; - if repo.index()?.has_conflicts() { - return Err(Error::Generic("creates conflicts".into())); + let commit_id = + commit_merge_with_head(&repo, &[upstream_commit], &msg)?; + + return Ok(Some(commit_id)); } - let remote_url = { - let branch_refname = - branch.get().name().ok_or_else(|| { - Error::Generic(String::from( - "branch refname not found", - )) - })?; - let buf = repo.branch_upstream_remote(branch_refname)?; - let remote = - repo.find_remote(buf.as_str().ok_or_else(|| { - Error::Generic(String::from("remote name not found")) - })?)?; - remote.url().unwrap_or_default().to_string() - }; - - let commit_id = commit_merge_with_head( - &repo, - &[upstream_commit], - format!("Merge '{}' of {}", branch_name, remote_url).as_str(), - )?; - - Ok(commit_id) + Ok(None) } pub(crate) fn commit_merge_with_head( @@ -171,7 +160,9 @@ mod test { ); let merge_commit = - merge_upstream_commit(clone2_dir, "master").unwrap(); + merge_upstream_commit(clone2_dir, "master") + .unwrap() + .unwrap(); let state = crate::sync::repo_state(clone2_dir).unwrap(); assert_eq!(state, RepoState::Clean); @@ -190,15 +181,12 @@ mod test { .unwrap(); assert_eq!( details.message.unwrap().combine(), - format!( - "Merge 'master' of {}", - r1_dir.path().to_str().unwrap() - ) + String::from("Merge remote-tracking branch 'refs/remotes/origin/master'") ); } #[test] - fn test_merge_normal_conflict() { + fn test_merge_normal_non_ff() { let (r1_dir, _repo) = repo_init_bare().unwrap(); let (clone1_dir, clone1) = @@ -209,7 +197,12 @@ mod test { // clone1 - write_commit_file(&clone1, "test.bin", "test", "commit1"); + write_commit_file( + &clone1, + "test.bin", + "test\nfooo", + "commit1", + ); debug_cmd_print( clone2_dir.path().to_str().unwrap(), @@ -228,7 +221,12 @@ mod test { // clone2 - write_commit_file(&clone2, "test.bin", "foobar", "commit2"); + write_commit_file( + &clone2, + "test.bin", + "foobar\ntest", + "commit2", + ); let bytes = fetch( clone2_dir.path().to_str().unwrap(), @@ -242,18 +240,19 @@ mod test { let res = merge_upstream_commit( clone2_dir.path().to_str().unwrap(), "master", - ); + ) + .unwrap(); - //this should have failed cause it would create a conflict - assert!(res.is_err()); + //this should not have commited cause we left conflicts behind + assert_eq!(res, None); let state = crate::sync::repo_state( clone2_dir.path().to_str().unwrap(), ) .unwrap(); - //make sure we left the repo not in some merging state - assert_eq!(state, RepoState::Clean); + //validate the repo is in a merge state now + assert_eq!(state, RepoState::Merge); //check that we still only have the first commit let commits = get_commit_ids(&clone1, 10); diff --git a/asyncgit/src/sync/branch/merge_ff.rs b/asyncgit/src/sync/branch/merge_ff.rs index f5265a70..fb45fe67 100644 --- a/asyncgit/src/sync/branch/merge_ff.rs +++ b/asyncgit/src/sync/branch/merge_ff.rs @@ -25,7 +25,7 @@ pub fn branch_merge_upstream_fastforward( let annotated = repo.find_annotated_commit(upstream_commit.id())?; - let (analysis, _) = repo.merge_analysis(&[&annotated])?; + let (analysis, pref) = repo.merge_analysis(&[&annotated])?; if !analysis.is_fast_forward() { return Err(Error::Generic( @@ -33,6 +33,10 @@ pub fn branch_merge_upstream_fastforward( )); } + if pref.is_no_fast_forward() { + return Err(Error::Generic("fast forward not wanted".into())); + } + //TODO: support merge on unborn if analysis.is_unborn() { return Err(Error::Generic("head is unborn".into())); diff --git a/asyncgit/src/sync/merge.rs b/asyncgit/src/sync/merge.rs index e7f9609f..bb6e5d2f 100644 --- a/asyncgit/src/sync/merge.rs +++ b/asyncgit/src/sync/merge.rs @@ -7,7 +7,7 @@ use crate::{ reset_workdir, utils, CommitId, }, }; -use git2::{BranchType, Commit, MergeOptions}; +use git2::{BranchType, Commit, MergeOptions, Repository}; use scopetime::scope_time; /// @@ -48,6 +48,16 @@ pub fn merge_branch(repo_path: &str, branch: &str) -> Result<()> { let repo = utils::repo(repo_path)?; + merge_branch_repo(&repo, branch)?; + + Ok(()) +} + +/// +pub fn merge_branch_repo( + repo: &Repository, + branch: &str, +) -> Result<()> { let branch = repo.find_branch(branch, BranchType::Local)?; let annotated = From 017701ffac5f35447ce9d25faae2ad5456d04760 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Wed, 12 May 2021 15:29:56 +0200 Subject: [PATCH 15/15] update changelog --- CHANGELOG.md | 1 + README.md | 11 +++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a68e07c..40ac8e31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ![warning](assets/commit-msg-length-limit.gif) ## Added +- merging arbitrary branch, commit merges ([#485](https://github.com/extrawurst/gitui/issues/485)) - warning if commit subject line gets too long ([#478](https://github.com/extrawurst/gitui/issues/478)) ## Changed diff --git a/README.md b/README.md index 978c4482..5d71093f 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,11 @@ For a [RustBerlin meetup presentation](https://youtu.be/rpilJV-eIVw?t=5334) ([slides](https://github.com/extrawurst/gitui-presentation)) I compared `lazygit`,`tig` and `gitui` by parsing the entire Linux git repository (which contains over 900k commits): -| | Time | Memory (GB) | Binary (MB) | Freezes | Crashes | -| --------- | ----------- | ----------- | ----------- | --------- | --------- | -| `gitui` | **24 s** ✅ | **0.17** ✅ | 1.4 | **No** ✅ | **No** ✅ | -| `lazygit` | 57 s | 2.6 | 16 | Yes | Sometimes | -| `tig` | 4 m 20 s | 1.3 | **0.6** ✅ | Sometimes | **No** ✅ | +| | Time | Memory (GB) | Binary (MB) | Freezes | Crashes | +| --------- | ---------- | ----------- | ----------- | --------- | --------- | +| `gitui` | **24 s** ✅ | **0.17** ✅ | 1.4 | **No** ✅ | **No** ✅ | +| `lazygit` | 57 s | 2.6 | 16 | Yes | Sometimes | +| `tig` | 4 m 20 s | 1.3 | **0.6** ✅ | Sometimes | **No** ✅ | ## 3. Motivation [Top ▲](#table-of-contents) @@ -71,7 +71,6 @@ Over the last 2 years my go-to GUI tool for this was [fork](https://git-fork.com These are the high level goals before calling out `1.0`: -* merging with conflicts ([#485](https://github.com/extrawurst/gitui/issues/485)) * log search (commit, author, sha) ([#449](https://github.com/extrawurst/gitui/issues/449),[#429](https://github.com/extrawurst/gitui/issues/429)) * file history log ([#381](https://github.com/extrawurst/gitui/issues/381)) * more tag support ([#483](https://github.com/extrawurst/gitui/issues/483))