binocular-cli 0.2.3

Not exactly a telescope, but it's useful sometimes. TUI to search/navigate through files and workspaces.
Documentation
use super::scope::if_empty_then;
use anyhow::{bail, Context};
use std::path::Path;
use std::process::Command;

pub fn read_history_blob(
    repo_root: &Path,
    commit: &str,
    file_path: &Path,
) -> anyhow::Result<Vec<u8>> {
    let object = format!("{}:{}", commit, file_path.to_string_lossy());
    let output = Command::new("git")
        .arg("show")
        .arg(&object)
        .current_dir(repo_root)
        .output()
        .with_context(|| format!("failed to read historical blob for {object}"))?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        bail!(
            "git show failed for {object}: {}",
            if_empty_then(stderr.trim(), "unknown git error")
        );
    }

    Ok(output.stdout)
}

pub fn read_branch_preview(repo_root: &Path, branch: &str) -> anyhow::Result<String> {
    run_git_text(
        repo_root,
        &[
            "log",
            "--color=always",
            "--decorate",
            "--stat",
            "--max-count=25",
            branch,
        ],
        &format!("failed to read branch preview for {branch}"),
    )
}

pub fn read_commit_preview(repo_root: &Path, commit: &str) -> anyhow::Result<String> {
    run_git_text(
        repo_root,
        &[
            "show",
            "--color=always",
            "--stat",
            "--patch",
            "--decorate",
            commit,
        ],
        &format!("failed to read commit preview for {commit}"),
    )
}

fn run_git_text(repo_root: &Path, args: &[&str], context: &str) -> anyhow::Result<String> {
    let output = Command::new("git")
        .args(args)
        .current_dir(repo_root)
        .output()
        .context(context.to_string())?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        bail!(
            "{}: {}",
            context,
            if_empty_then(stderr.trim(), "unknown git error")
        );
    }

    let mut text = String::from_utf8_lossy(&output.stdout).into_owned();
    if text.trim().is_empty() {
        text = "No output".to_string();
    }
    Ok(text)
}