pray-core 1.18.0

Core library for Prayfile, the package manager for the language placed before inference
Documentation
use crate::resolve_git_command::{run_git_command, run_git_success};
use crate::PrayResult;
use std::path::{Path, PathBuf};

pub(crate) fn materialize_git_catalog_file(source_root: &Path, relative: &Path) -> PrayResult<()> {
    let full_path = source_root.join(relative);
    if full_path.is_file() {
        return Ok(());
    }
    if !sparse_checkout_enabled(source_root) {
        return Ok(());
    }
    let Some(toplevel) = git_toplevel(source_root) else {
        return Ok(());
    };
    let Some(repo_relative) = repo_relative_text(source_root, relative) else {
        return Ok(());
    };
    if let Some(cone) = artifact_sparse_cone(Path::new(&repo_relative)) {
        run_git_success(&toplevel, &["sparse-checkout", "add", &cone])?;
    }
    run_git_success(&toplevel, &["checkout", "HEAD", "--", &repo_relative])?;
    Ok(())
}

fn repo_relative_text(source_root: &Path, relative: &Path) -> Option<String> {
    let prefix = git_show_prefix(source_root)?;
    let combined = if prefix.is_empty() {
        relative.to_path_buf()
    } else {
        Path::new(&prefix).join(relative)
    };
    combined.to_str().map(str::to_string)
}

fn sparse_checkout_enabled(source_root: &Path) -> bool {
    let Ok(output) = run_git_command(source_root, &["config", "--get", "core.sparseCheckout"])
    else {
        return false;
    };
    if !output.status.success() {
        return false;
    }
    String::from_utf8_lossy(&output.stdout).trim() == "true"
}

fn git_toplevel(start: &Path) -> Option<PathBuf> {
    let output = run_git_command(start, &["rev-parse", "--show-toplevel"]).ok()?;
    if !output.status.success() {
        return None;
    }
    let text = String::from_utf8_lossy(&output.stdout).trim().to_string();
    if text.is_empty() {
        None
    } else {
        Some(PathBuf::from(text))
    }
}

fn git_show_prefix(source_root: &Path) -> Option<String> {
    let output = run_git_command(source_root, &["rev-parse", "--show-prefix"]).ok()?;
    if !output.status.success() {
        return None;
    }
    Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
}

fn artifact_sparse_cone(repo_relative: &Path) -> Option<String> {
    let mut parts: Vec<_> = repo_relative.iter().collect();
    if parts.len() < 2 {
        return repo_relative.to_str().map(str::to_string);
    }
    parts.pop();
    if parts.last().is_some_and(|part| *part != "artifacts") && parts.len() > 3 {
        parts.pop();
    }
    PathBuf::from_iter(parts).to_str().map(str::to_string)
}