support doing merge commit (#701)

This commit is contained in:
Stephan Dilly 2021-05-12 11:20:39 +02:00 committed by GitHub
parent a073e0ac02
commit f35ce0cbf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 130 additions and 51 deletions

View File

@ -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<CommitId> {
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)
}

View File

@ -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<String> {
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<CommitId> {
scope_time!("merge_commit");
let repo = utils::repo(repo_path)?;
let mut commits: Vec<Commit> = 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::*;

View File

@ -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,

View File

@ -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<CommitId>),
}
pub struct CommitComponent {
input: TextInputComponent,
amend: Option<CommitId>,
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(())

View File

@ -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()
}