securegit 0.8.5

Zero-trust git replacement with 12 built-in security scanners, LLM redteam bridge, universal undo, durable backups, and a 50-tool MCP server
Documentation
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())?;
        // Checkout working tree BEFORE moving HEAD — otherwise libgit2's
        // checkout_tree sees baseline==target (both point to the new tree)
        // and computes an empty diff, leaving the working directory unchanged.
        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");
}