use crate::cli::UI;
use crate::ops::oplog;
use crate::ops::utils::short_oid;
use anyhow::{bail, Result};
use std::path::Path;
pub fn execute(
path: &Path,
branch_name: &str,
abort: bool,
no_ff: bool,
squash: bool,
ff_only: bool,
ui: &UI,
) -> Result<()> {
let desc = if abort {
"abort merge".to_string()
} else {
format!(
"merge '{}'{}",
branch_name,
if squash {
" --squash"
} else if no_ff {
" --no-ff"
} else if ff_only {
" --ff-only"
} else {
""
}
)
};
oplog::with_oplog(path, "merge", &desc, || {
execute_inner(path, branch_name, abort, no_ff, squash, ff_only, ui)
})
}
fn execute_inner(
path: &Path,
branch_name: &str,
abort: bool,
no_ff: bool,
squash: bool,
ff_only: bool,
ui: &UI,
) -> Result<()> {
let repo = crate::ops::open_repo(path)?;
if abort {
if repo.state() != git2::RepositoryState::Merge {
bail!("No merge in progress to abort.");
}
repo.cleanup_state()?;
let head = repo.head()?.peel_to_commit()?;
repo.reset(head.as_object(), git2::ResetType::Hard, None)?;
ui.success("Merge aborted");
return Ok(());
}
let reference = repo
.find_branch(branch_name, git2::BranchType::Local)
.or_else(|_| {
repo.find_branch(&format!("origin/{}", branch_name), git2::BranchType::Remote)
})?;
let annotated = repo.reference_to_annotated_commit(reference.get())?;
let (analysis, _preference) = repo.merge_analysis(&[&annotated])?;
if analysis.is_up_to_date() {
ui.success("Already up to date");
return Ok(());
}
if analysis.is_fast_forward() && !no_ff && !squash {
let target_commit = repo.find_commit(annotated.id())?;
repo.checkout_tree(target_commit.as_object(), None)?;
let mut head_ref = repo.head()?;
head_ref.set_target(annotated.id(), &format!("Fast-forward to {}", branch_name))?;
ui.success(format!("Fast-forward merge to {}", branch_name));
return Ok(());
}
if ff_only {
bail!("Not possible to fast-forward, aborting.");
}
if analysis.is_normal() || (analysis.is_fast_forward() && (no_ff || squash)) {
repo.merge(&[&annotated], None, None)?;
let index = repo.index()?;
if index.has_conflicts() {
let short_id = short_oid(&annotated.id());
let _ = crate::ops::conflicts::record_conflicts(path, &short_id, "merge", branch_name);
ui.warning("Merge conflict detected. Resolve conflicts and commit.");
ui.info("Use 'securegit conflicts' to see details");
ui.info("Use 'securegit merge --abort' to abort");
return Ok(());
}
if squash {
repo.cleanup_state()?;
ui.success("Squash commit -- not updating HEAD");
ui.info("Use 'securegit commit' to finish the squash merge");
return Ok(());
}
let mut index = repo.index()?;
let tree_oid = index.write_tree()?;
let tree = repo.find_tree(tree_oid)?;
let sig = repo.signature()?;
let head_commit = repo.head()?.peel_to_commit()?;
let merge_commit = repo.find_commit(annotated.id())?;
let msg = format!("Merge branch '{}'", branch_name);
repo.commit(
Some("HEAD"),
&sig,
&sig,
&msg,
&tree,
&[&head_commit, &merge_commit],
)?;
repo.cleanup_state()?;
ui.success("Merge made by the 'ort' strategy");
return Ok(());
}
bail!("Merge analysis returned unexpected result");
}