mdbook_gitinfo/git.rs
1//! Utility module for running Git commands.
2//!
3//! This module provides helpers for interacting with a Git repository,
4//! primarily to extract metadata (commit hash, tag, timestamp, branch).
5//!
6//! All functions return [`mdbook::errors::Error`] on failure so they can be
7//! integrated directly into the `mdbook` preprocessor error flow.
8//!
9//! See also:
10//! - [`get_git_output`] — Run arbitrary Git commands and capture output.
11//! - [`verify_branch`] — Convenience wrapper to check branch existence.
12
13use mdbook::errors::Error;
14use std::ffi::OsStr;
15use std::path::Path;
16use std::process::{Command, Stdio};
17
18/// Run a Git command and return the trimmed `stdout` output as a [`String`].
19///
20/// This is the central utility for invoking Git. It is used by the
21/// `mdbook-gitinfo` preprocessor to fetch commit information such as:
22/// - short or long commit hash
23/// - nearest tag
24/// - commit date/time
25///
26/// See also: [`verify_branch`], which builds on this function to check
27/// if a branch exists locally.
28///
29/// # Type Parameters
30///
31/// - `I`: An iterator of arguments (e.g., a string slice array).
32/// - `S`: Each argument, convertible to [`OsStr`].
33///
34/// # Arguments
35///
36/// * `args` — Git command-line arguments (e.g., `["rev-parse", "HEAD"]`).
37/// * `dir` — Path to the Git repository root or working directory.
38///
39/// # Returns
40///
41/// * `Ok(String)` — Trimmed `stdout` output from Git.
42/// * `Err(Error)` — If Git fails to launch or exits with non-zero status.
43///
44/// # Errors
45///
46/// This function returns an [`Error`] if:
47/// - The `git` binary is missing or fails to start.
48/// - The command returns a non-zero exit code.
49/// - The output cannot be decoded as UTF-8.
50///
51/// # Example
52///
53/// ```no_run
54/// use std::path::Path;
55/// use mdbook_gitinfo::git::get_git_output;
56///
57/// let hash = get_git_output(["rev-parse", "--short", "HEAD"], Path::new("."))
58/// .expect("failed to get commit hash");
59/// println!("Current short commit hash: {}", hash);
60/// ```
61pub fn get_git_output<I, S>(args: I, dir: &Path) -> Result<String, Error>
62where
63 I: IntoIterator<Item = S>,
64 S: AsRef<OsStr>,
65{
66 let output = Command::new("git")
67 .args(args)
68 .current_dir(dir)
69 .stdout(Stdio::piped())
70 .output()
71 .map_err(|e| Error::msg(format!("Git command failed: {e}")))?;
72
73 if output.status.success() {
74 Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
75 } else {
76 Err(Error::msg("Git command returned non-zero exit code"))
77 }
78}
79
80/// Verify that a branch exists locally in the given repository.
81///
82/// Internally runs:
83/// ```text
84/// git rev-parse --verify <branch>
85/// ```
86///
87/// This is a thin wrapper around [`get_git_output`], returning `true` if the
88/// Git call succeeds and `false` otherwise.
89///
90/// # Arguments
91///
92/// * `branch` — The name of the branch to check.
93/// * `dir` — Path to the Git repository root or working directory.
94///
95/// # Returns
96///
97/// * `true` if the branch exists locally.
98/// * `false` otherwise.
99///
100/// # Example
101///
102/// ```no_run
103/// use std::path::Path;
104/// use mdbook_gitinfo::git::verify_branch;
105///
106/// let dir = Path::new(".");
107/// if !verify_branch("dev", dir) {
108/// eprintln!("Branch 'dev' not found, falling back to 'main'");
109/// }
110/// ```
111pub fn verify_branch(branch: &str, dir: &Path) -> bool {
112 get_git_output(["rev-parse", "--verify", branch], dir).is_ok()
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use std::path::PathBuf;
119
120 #[test]
121 fn returns_error_on_invalid_git_command() {
122 let result = get_git_output(["non-existent-command"], &PathBuf::from("."));
123 assert!(result.is_err());
124 }
125}