Skip to main content

git_valet/
git_helpers.rs

1//! Git command wrappers for both the main repo and the valet bare repo.
2
3use anyhow::{Context, Result, bail};
4use std::path::{Path, PathBuf};
5use std::process::{Command, Output};
6
7use crate::config::ValetConfig;
8
9/// Converts a Path to a UTF-8 string, with a descriptive error on failure.
10pub fn path_str(path: &Path) -> Result<&str> {
11    path.to_str().with_context(|| format!("Path contains invalid UTF-8: {}", path.display()))
12}
13
14/// Runs a git command in the main repo and checks exit status
15pub fn git(args: &[&str], work_tree: &Path) -> Result<Output> {
16    let out = Command::new("git")
17        .args(args)
18        .current_dir(work_tree)
19        .output()
20        .context("Failed to execute git")?;
21    if !out.status.success() {
22        let stderr = String::from_utf8_lossy(&out.stderr);
23        bail!("git {} failed: {}", args.first().unwrap_or(&""), stderr.trim());
24    }
25    Ok(out)
26}
27
28/// Runs a git command against the valet bare repo + work-tree (does not check exit status).
29///
30/// Clears inherited git env vars (`GIT_INDEX_FILE`, `GIT_DIR`, etc.) to prevent
31/// the main repo's state from leaking into the valet subprocess — especially
32/// critical when called from git hooks where these vars are set by git.
33pub fn sgit(args: &[&str], config: &ValetConfig) -> Result<Output> {
34    let out = Command::new("git")
35        .env_remove("GIT_INDEX_FILE")
36        .env_remove("GIT_DIR")
37        .env_remove("GIT_WORK_TREE")
38        .env_remove("GIT_OBJECT_DIRECTORY")
39        .env_remove("GIT_ALTERNATE_OBJECT_DIRECTORIES")
40        .arg("--git-dir")
41        .arg(&config.bare_path)
42        .arg("--work-tree")
43        .arg(&config.work_tree)
44        .args(args)
45        .output()
46        .context("Failed to execute valet git")?;
47    Ok(out)
48}
49
50/// Returns the stdout of a git command as a String
51pub fn git_output(args: &[&str], work_tree: &Path) -> Result<String> {
52    let out = git(args, work_tree)?;
53    Ok(String::from_utf8_lossy(&out.stdout).trim().to_string())
54}
55
56/// Returns the origin remote URL of the current repo
57pub fn get_origin(work_tree: &Path) -> Result<String> {
58    git_output(&["remote", "get-url", "origin"], work_tree)
59        .context("Could not get remote origin. Does this repo have an 'origin' remote?")
60}
61
62/// Returns the absolute path of the current repo's .git directory
63pub fn get_git_dir(work_tree: &Path) -> Result<PathBuf> {
64    let s = git_output(&["rev-parse", "--absolute-git-dir"], work_tree)?;
65    Ok(PathBuf::from(s))
66}
67
68/// Returns the root of the current git repo
69pub fn get_work_tree() -> Result<PathBuf> {
70    let out = Command::new("git")
71        .args(["rev-parse", "--show-toplevel"])
72        .output()
73        .context("Not inside a git repository")?;
74    let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
75    if s.is_empty() {
76        bail!("Not inside a git repository");
77    }
78    Ok(PathBuf::from(s))
79}
80
81/// Loads the valet config from the current repo
82pub fn load_config() -> Result<ValetConfig> {
83    let work_tree = get_work_tree()?;
84    let origin = get_origin(&work_tree)?;
85    crate::config::load(&origin)
86}