mirror of
https://github.com/extrawurst/gitui.git
synced 2024-12-28 19:44:14 +03:00
support doing merge commit (#701)
This commit is contained in:
parent
a073e0ac02
commit
f35ce0cbf4
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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::*;
|
||||
|
@ -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,
|
||||
|
@ -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(())
|
||||
|
@ -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()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user