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::auth::SecureString;
use crate::cli::UI;
use crate::graphrag;
use crate::ops::fetch;
use crate::ops::oplog;
use anyhow::{bail, Result};
use std::path::Path;

pub struct PullOptions<'a> {
    pub remote_name: &'a str,
    pub tags: bool,
    pub rebase: bool,
    pub ff_only: bool,
    pub token: Option<&'a SecureString>,
    pub ssh_key: Option<&'a std::path::Path>,
}

pub fn execute(path: &Path, opts: &PullOptions, ui: &UI) -> Result<()> {
    oplog::with_oplog(
        path,
        "pull",
        &format!("pull from '{}'", opts.remote_name),
        || execute_inner(path, opts, ui),
    )
}

fn execute_inner(path: &Path, opts: &PullOptions, ui: &UI) -> Result<()> {
    // Step 1: Fetch
    fetch::execute(
        path,
        opts.remote_name,
        opts.tags,
        opts.token,
        opts.ssh_key,
        ui,
    )?;

    // Step 2: Determine upstream branch and decide strategy
    // We do the analysis in a block so repo is dropped before we might call rebase
    let (branch_name, remote_url, action) = {
        let repo = crate::ops::open_repo(path)?;
        let branch_name = {
            let head = repo.head()?;
            head.shorthand().unwrap_or("main").to_string()
        };

        let remote_url = repo
            .find_remote(opts.remote_name)
            .ok()
            .and_then(|r| r.url().map(|s| s.to_string()));

        let remote_branch = format!("{}/{}", opts.remote_name, branch_name);

        let action = match repo.find_branch(&remote_branch, git2::BranchType::Remote) {
            Ok(reference) => {
                let annotated = repo.reference_to_annotated_commit(reference.get())?;
                let (analysis, _) = repo.merge_analysis(&[&annotated])?;

                if analysis.is_up_to_date() {
                    PullAction::UpToDate
                } else if analysis.is_fast_forward() {
                    let target_commit = repo.find_commit(annotated.id())?;
                    let mut head_ref = repo.head()?;
                    head_ref.set_target(annotated.id(), "pull: fast-forward")?;
                    repo.checkout_tree(target_commit.as_object(), None)?;
                    PullAction::FastForward
                } else if opts.ff_only {
                    PullAction::FfOnlyFailed
                } else if opts.rebase {
                    PullAction::Rebase
                } else if analysis.is_normal() {
                    repo.merge(&[&annotated], None, None)?;
                    let index = repo.index()?;
                    if index.has_conflicts() {
                        PullAction::MergeConflict
                    } else {
                        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 '{}' of {}", branch_name, opts.remote_name);
                        repo.commit(
                            Some("HEAD"),
                            &sig,
                            &sig,
                            &msg,
                            &tree,
                            &[&head_commit, &merge_commit],
                        )?;
                        repo.cleanup_state()?;
                        PullAction::Merged
                    }
                } else {
                    PullAction::UpToDate
                }
            }
            Err(_) => PullAction::NoUpstream,
        };

        (branch_name, remote_url, action)
    };
    // repo is dropped here

    let changed = match action {
        PullAction::UpToDate => {
            ui.success("Already up to date");
            false
        }
        PullAction::FastForward => {
            ui.success("Fast-forward");
            true
        }
        PullAction::FfOnlyFailed => {
            bail!(
                "Not possible to fast-forward, aborting.\n\
                 Use 'securegit pull' without --ff-only, or 'securegit pull --rebase'."
            );
        }
        PullAction::Rebase => {
            let upstream_ref = format!("{}/{}", opts.remote_name, branch_name);
            crate::ops::rebase::execute(path, Some(&upstream_ref), false, false, false, None, ui)?;
            true
        }
        PullAction::MergeConflict => {
            ui.warning("Merge conflict detected. Resolve conflicts and commit.");
            true
        }
        PullAction::Merged => {
            ui.success("Merge made");
            true
        }
        PullAction::NoUpstream => {
            ui.warning(format!("No upstream branch found for '{}'", branch_name));
            false
        }
    };

    if changed {
        graphrag::client::trigger_indexing_sync(path, remote_url.as_deref(), Some(&branch_name));
    }

    Ok(())
}

enum PullAction {
    UpToDate,
    FastForward,
    FfOnlyFailed,
    Rebase,
    MergeConflict,
    Merged,
    NoUpstream,
}