From f35ce0cbf4995ca2516060aeef3a29803a6378c0 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Wed, 12 May 2021 11:20:39 +0200 Subject: [PATCH] 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() }