xbp 10.40.0

XBP is a zero-config build pack that can also interact with proxies, kafka, sockets, synthetic monitors.
Documentation
//! Standalone branch sync + push.
//!
//! Automates the common recovery flow:
//!   git push  → rejected (non-fast-forward / behind)
//!   git pull  → aborts under `pull.ff=only` when histories diverged
//!
//! `xbp push` instead:
//! 1. Detects git root from the current directory
//! 2. Fetches the remote tip
//! 3. When behind/diverged: `git pull --rebase` (stashes dirty WIP first)
//! 4. Pushes
//! 5. Restores any temporary stash

use std::env;
use std::path::PathBuf;

use colored::Colorize;

use crate::cli::auto_commit::{print_push_summary, sync_and_push_current_branch};
use crate::utils::command_exists;

/// Run `xbp push` from the process working directory (or any nested path in a repo).
pub async fn run_push() -> Result<(), String> {
    if !command_exists("git") {
        return Err("Git is not installed on this machine.".to_string());
    }

    let cwd = env::current_dir().map_err(|error| format!("Failed to read cwd: {error}"))?;
    let repo_root = resolve_git_root(&cwd).await?;

    println!(
        "{} {}",
        "Push".bright_cyan().bold(),
        format!("syncing and pushing from {}", repo_root.display()).bright_white()
    );

    match sync_and_push_current_branch(&repo_root).await? {
        Some(outcome) => {
            print_push_summary(&outcome);
            if outcome.rebased {
                println!(
                    "{}",
                    "Integrated remote commits with stash-safe rebase before push."
                        .dimmed()
                );
            }
            Ok(())
        }
        None => Err(
            "Push skipped: HEAD is detached or no branch name is available. Checkout a branch first."
                .to_string(),
        ),
    }
}

async fn resolve_git_root(start: &std::path::Path) -> Result<PathBuf, String> {
    let output = tokio::process::Command::new("git")
        .current_dir(start)
        .args(["rev-parse", "--show-toplevel"])
        .output()
        .await
        .map_err(|error| format!("Failed to run git: {error}"))?;

    if !output.status.success() {
        return Err(
            "Current directory is not inside a git repository. `cd` into the repo and retry."
                .to_string(),
        );
    }

    let root = String::from_utf8_lossy(&output.stdout).trim().to_string();
    if root.is_empty() {
        return Err("Could not resolve git repository root.".to_string());
    }
    Ok(PathBuf::from(root))
}