irox_git_tools/
lib.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2023 IROX Contributors
3
4//!
5//! Wrappers and scripts around the `git2` crate
6//!
7
8#![forbid(unsafe_code)]
9
10pub use crate::describe::*;
11pub use crate::error::Error;
12use git2::{Branch, BranchType, Commit, Repository};
13use std::path::Path;
14
15mod describe;
16mod error;
17
18///
19/// Attempts to find the repository at the specified location
20pub fn discover_repository<T: AsRef<Path>>(directory: T) -> Result<Repository, Error> {
21    let starting_point = directory.as_ref();
22    Repository::discover(starting_point).map_err(|_e| Error::Discovery {
23        path: starting_point.display().to_string(),
24    })
25}
26///
27/// If the repository represents a worktree, peels it to find the real `.git` directory, and returns
28/// a repository against the real `.git` directory.
29pub fn git_repodir_for_repo(repo: Repository) -> Result<Repository, Error> {
30    if repo.is_worktree() {
31        let repo_path = repo.path();
32        return Repository::open(repo_path).map_err(|_e| Error::Worktree {
33            path: repo_path.display().to_string(),
34        });
35    }
36    Ok(repo)
37}
38
39///
40/// Returns a discovered [`Repository`], or if the path is a git worktree, then returns the
41/// actual backed `.git` folder for this worktree in the main repository.
42pub fn discover_repo_or_worktree_at<T: AsRef<Path>>(directory: T) -> Result<Repository, Error> {
43    let repo = discover_repository(directory)?;
44    let repo = git_repodir_for_repo(repo)?;
45    Ok(repo)
46}
47
48///
49/// Resolves `HEAD` to a commit for the specified repo.  Returns an error if `HEAD` doesn't exist, or `HEAD` doesn't
50/// point to a commit in the provided repository
51pub fn get_head_for_repo(repo: &Repository) -> Result<Commit, Error> {
52    let Ok(head) = repo.head() else {
53        return Error::head(repo.path());
54    };
55    head.peel_to_commit()
56        .map_err(|_| Error::ref_not_in_repo(repo, &head))
57}
58
59///
60/// Resolved the branch for `HEAD` if one exists
61pub fn get_branch_for_head(repo: &Repository) -> Result<Branch, Error> {
62    let Ok(reference) = repo.head() else {
63        return Error::head(repo.path());
64    };
65    let name = String::from_utf8_lossy(reference.shorthand_bytes()).to_string();
66
67    let Ok(branch) = repo.find_branch(&name, BranchType::Local) else {
68        return Error::branch_err(repo, &name);
69    };
70    Ok(branch)
71}