1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// SPDX-License-Identifier: MIT
// Copyright 2023 IROX Contributors

//!
//! Wrappers and scripts around the `git2` crate
//!

#![forbid(unsafe_code)]

pub use crate::describe::*;
pub use crate::error::Error;
use git2::{Branch, BranchType, Commit, Repository};
use std::path::Path;

mod describe;
mod error;

///
/// Attempts to find the repository at the specified location
pub fn discover_repository<T: AsRef<Path>>(directory: T) -> Result<Repository, Error> {
    let starting_point = directory.as_ref();
    Repository::discover(starting_point).map_err(|_e| Error::Discovery {
        path: starting_point.display().to_string(),
    })
}
///
/// If the repository represents a worktree, peels it to find the real `.git` directory, and returns
/// a repository against the real `.git` directory.
pub fn git_repodir_for_repo(repo: Repository) -> Result<Repository, Error> {
    if repo.is_worktree() {
        let repo_path = repo.path();
        return Repository::open(repo_path).map_err(|_e| Error::Worktree {
            path: repo_path.display().to_string(),
        });
    }
    Ok(repo)
}

///
/// Returns a discovered [`Repository`], or if the path is a git worktree, then returns the
/// actual backed `.git` folder for this worktree in the main repository.
pub fn discover_repo_or_worktree_at<T: AsRef<Path>>(directory: T) -> Result<Repository, Error> {
    let repo = discover_repository(directory)?;
    let repo = git_repodir_for_repo(repo)?;
    Ok(repo)
}

///
/// Resolves `HEAD` to a commit for the specified repo.  Returns an error if `HEAD` doesn't exist, or `HEAD` doesn't
/// point to a commit in the provided repository
pub fn get_head_for_repo(repo: &Repository) -> Result<Commit, Error> {
    let Ok(head) = repo.head() else {
        return Error::head(repo.path());
    };
    head.peel_to_commit()
        .map_err(|_| Error::ref_not_in_repo(repo, &head))
}

///
/// Resolved the branch for `HEAD` if one exists
pub fn get_branch_for_head(repo: &Repository) -> Result<Branch, Error> {
    let Ok(reference) = repo.head() else {
        return Error::head(repo.path());
    };
    let name = String::from_utf8_lossy(reference.shorthand_bytes()).to_string();

    let Ok(branch) = repo.find_branch(&name, BranchType::Local) else {
        return Error::branch_err(repo, &name);
    };
    Ok(branch)
}