wsb 0.196.4203

Workspace - A tool suite for file operations, version management, and development workflow automation
Documentation
use crate::prelude::*;
use crate::fleet::error::{Result, SuperGitError};
use crate::git_backend;

pub struct GitOps;

impl GitOps {
    /// Discover the workspace root for the repository containing `path`.
    pub fn discover_repo_root<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
        git_backend::repo_root(path.as_ref()).map_err(|_| SuperGitError::NotARepository)
    }

    /// Open a repository at the given path
    pub fn open_repo<P: AsRef<Path>>(path: P) -> Result<gix::Repository> {
        git_backend::open_repo(path.as_ref()).map_err(|_| SuperGitError::NotARepository)
    }

    /// Get the URL for a named remote.
    pub fn remote_url(repo: &gix::Repository, remote_name: &str) -> Result<String> {
        git_backend::remote_url(repo, remote_name).map_err(Into::into)
    }

    /// Get current branch name
    pub fn current_branch(repo: &gix::Repository) -> Result<String> {
        git_backend::current_branch(repo).map_err(Into::into)
    }

    /// Check if working directory is clean
    pub fn is_clean(repo: &gix::Repository) -> Result<bool> {
        git_backend::is_clean(repo).map_err(Into::into)
    }

    /// Get list of uncommitted files
    pub fn get_uncommitted_files(repo: &gix::Repository) -> Result<Vec<String>> {
        Ok(git_backend::status_entries(repo)?
            .into_iter()
            .filter(|entry| entry.status != git_backend::FileStatusKind::Ignored)
            .map(|entry| entry.path)
            .collect())
    }

    /// Check if branch exists locally
    pub fn branch_exists(repo: &gix::Repository, branch_name: &str) -> Result<bool> {
        git_backend::branch_exists(repo, branch_name, git_backend::BranchKind::Local).map_err(Into::into)
    }

    /// Check if remote branch exists
    pub fn remote_branch_exists(repo: &gix::Repository, branch_name: &str) -> Result<bool> {
        let remote_branch = format!("origin/{branch_name}");
        git_backend::branch_exists(repo, &remote_branch, git_backend::BranchKind::Remote)
            .map_err(Into::into)
    }

    /// Get ahead/behind counts for current branch
    pub fn get_ahead_behind(repo: &gix::Repository) -> Result<(usize, usize)> {
        let head = repo.head().map_err(anyhow::Error::from)?;
        let branch = match head.referent_name() {
            Some(name) => name,
            None => return Ok((0, 0)),
        };
        let tracking = match repo.branch_remote_tracking_ref_name(branch, gix::remote::Direction::Fetch) {
            Some(Ok(name)) => name,
            Some(Err(err)) => return Err(anyhow::Error::from(err).into()),
            None => return Ok((0, 0)),
        };
        let head_id = repo.head_id().map_err(anyhow::Error::from)?.detach();
        let upstream_id = match repo.find_reference(tracking.as_ref()) {
            Ok(mut reference) => reference.peel_to_id().map_err(anyhow::Error::from)?.detach(),
            Err(_) => return Ok((0, 0)),
        };
        let merge_base = repo
            .merge_base(head_id, upstream_id)
            .map_err(anyhow::Error::from)?;
        let ahead = count_commits_between(repo, head_id, merge_base.into())?;
        let behind = count_commits_between(repo, upstream_id, merge_base.into())?;
        Ok((ahead, behind))
    }

    /// Fetch from remote
    pub fn fetch(repo: &gix::Repository, remote_name: &str) -> Result<()> {
        git_backend::fetch(repo, Some(remote_name)).map_err(Into::into)
    }

    /// Check if repository is in detached HEAD state
    pub fn is_detached_head(repo: &gix::Repository) -> Result<bool> {
        git_backend::is_detached_head(repo).map_err(Into::into)
    }

    /// Get list of all local branches
    pub fn get_local_branches(repo: &gix::Repository) -> Result<Vec<String>> {
        git_backend::local_branches(repo).map_err(Into::into)
    }

    /// Get list of all remote branches
    pub fn get_remote_branches(repo: &gix::Repository) -> Result<Vec<String>> {
        git_backend::remote_branches(repo).map_err(Into::into)
    }

    /// Get the default branch name (main or master)
    pub fn get_default_branch(repo: &gix::Repository) -> Result<String> {
        git_backend::get_default_branch(repo).map_err(Into::into)
    }
}

fn count_commits_between(
    repo: &gix::Repository,
    from: gix::ObjectId,
    stop_at: gix::ObjectId,
) -> Result<usize> {
    let commit = repo.find_commit(from).map_err(anyhow::Error::from)?;
    let mut count = 0usize;
    for ancestor in commit.ancestors().all().map_err(anyhow::Error::from)? {
        let ancestor = ancestor.map_err(anyhow::Error::from)?;
        if ancestor.id == stop_at {
            break;
        }
        count += 1;
    }
    Ok(count)
}