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::Result;
use std::path::Path;

pub fn execute(
    path: &Path,
    upstream: Option<&str>,
    abort: bool,
    continue_rebase: bool,
    skip: bool,
    onto: Option<&str>,
    ui: &UI,
) -> Result<()> {
    let desc = if abort {
        "abort rebase".to_string()
    } else if continue_rebase {
        "continue rebase".to_string()
    } else if skip {
        "skip rebase step".to_string()
    } else {
        format!("rebase onto '{}'", upstream.unwrap_or("?"))
    };
    oplog::with_oplog(path, "rebase", &desc, || {
        execute_inner(path, upstream, abort, continue_rebase, skip, onto, ui)
    })
}

fn execute_inner(
    path: &Path,
    upstream: Option<&str>,
    abort: bool,
    continue_rebase: bool,
    skip: bool,
    onto: Option<&str>,
    ui: &UI,
) -> Result<()> {
    let repo = crate::ops::open_repo(path)?;

    if abort {
        let mut rebase = repo.open_rebase(None)?;
        rebase.abort()?;
        ui.success("Rebase aborted");
        return Ok(());
    }

    if skip {
        let sig = repo.signature()?;
        let mut rebase = repo.open_rebase(None)?;

        // Skip current step by advancing without committing
        while let Some(_) = rebase.next() {
            let index = repo.index()?;
            if index.has_conflicts() {
                let _ = crate::ops::conflicts::record_conflicts(
                    path,
                    "rebase-step",
                    "rebase",
                    "continue",
                );
                ui.warning("Conflict during rebase. Resolve and run: securegit rebase --continue");
                return Ok(());
            }
            rebase.commit(None, &sig, None)?;
        }

        rebase.finish(None)?;
        ui.success("Successfully rebased and updated HEAD");
        return Ok(());
    }

    if continue_rebase {
        let sig = repo.signature()?;
        let mut rebase = repo.open_rebase(None)?;

        // Commit the resolved step
        rebase.commit(None, &sig, None)?;

        // Continue with remaining steps
        while rebase.next().is_some() {
            let index = repo.index()?;
            if index.has_conflicts() {
                let _ = crate::ops::conflicts::record_conflicts(
                    path,
                    "rebase-step",
                    "rebase",
                    "continue",
                );
                ui.warning("Conflict during rebase. Resolve and run: securegit rebase --continue");
                return Ok(());
            }
            rebase.commit(None, &sig, None)?;
        }

        rebase.finish(None)?;
        ui.success("Successfully rebased and updated HEAD");
        return Ok(());
    }

    let upstream_name = upstream.ok_or_else(|| {
        anyhow::anyhow!("Usage: securegit rebase <upstream>\n  e.g. securegit rebase main")
    })?;

    let upstream_obj = repo.revparse_single(upstream_name)?;
    let upstream_commit = upstream_obj.peel_to_commit()?;
    let upstream_annotated = repo.find_annotated_commit(upstream_commit.id())?;

    // Handle --onto: rebase onto a different base
    let onto_annotated = if let Some(onto_ref) = onto {
        let onto_obj = repo.revparse_single(onto_ref)?;
        let onto_commit = onto_obj.peel_to_commit()?;
        Some(repo.find_annotated_commit(onto_commit.id())?)
    } else {
        None
    };

    let head = repo.head()?;
    let head_name = head.shorthand().unwrap_or("HEAD");

    // Check if already up-to-date
    let target_id = onto_annotated
        .as_ref()
        .map(|a| a.id())
        .unwrap_or(upstream_commit.id());
    if let Ok(merge_base) = repo.merge_base(target_id, head.target().unwrap_or(target_id)) {
        if merge_base == head.target().unwrap_or(target_id) {
            ui.success(format!("Current branch {} is up to date", head_name));
            return Ok(());
        }
    }

    let mut rebase = repo.rebase(
        None,
        Some(&upstream_annotated),
        onto_annotated.as_ref(),
        None,
    )?;
    let sig = repo.signature()?;

    let mut count = 0;
    while rebase.next().is_some() {
        let index = repo.index()?;
        if index.has_conflicts() {
            let short_id = short_oid(&upstream_commit.id());
            let _ =
                crate::ops::conflicts::record_conflicts(path, &short_id, "rebase", upstream_name);
            ui.warning("Conflict during rebase. Resolve and run: securegit rebase --continue");
            ui.info("Or abort with: securegit rebase --abort");
            return Ok(());
        }
        rebase.commit(None, &sig, None)?;
        count += 1;
    }

    rebase.finish(None)?;

    if count == 0 {
        ui.success(format!("Current branch {} is up to date", head_name));
    } else {
        let target_name = onto.unwrap_or(upstream_name);
        ui.success(format!(
            "Successfully rebased {} commit{} onto '{}'",
            count,
            if count == 1 { "" } else { "s" },
            target_name
        ));
    }

    Ok(())
}