Skip to main content

rskit_git/embedded/
repository.rs

1use std::path::{Path, PathBuf};
2
3use rskit_errors::AppResult;
4
5use crate::core::Repository as RepositoryTrait;
6use crate::error::GitError;
7use crate::types::{Oid, Reference};
8
9use super::{map_head_error, oid_from_git2, reference_from_git2};
10
11/// Repository implementation backed by libgit2.
12pub struct Git2Repository {
13    pub(crate) repo: git2::Repository,
14    pub(crate) root: PathBuf,
15}
16
17impl Git2Repository {
18    /// Returns the repository root path.
19    pub fn root(&self) -> &Path {
20        &self.root
21    }
22}
23
24/// Opens a git repository at the given path (canonicalized).
25pub fn open(path: impl AsRef<Path>) -> AppResult<Git2Repository> {
26    let path = path.as_ref();
27    let abs = std::fs::canonicalize(path).map_err(|err| match err.kind() {
28        std::io::ErrorKind::NotFound => GitError::NotFound {
29            path: path.to_path_buf(),
30        },
31        _ => GitError::Internal(git2::Error::from_str(&err.to_string())),
32    })?;
33    let repo = git2::Repository::open(&abs).map_err(|err| {
34        if err.code() == git2::ErrorCode::NotFound {
35            GitError::NotFound { path: abs.clone() }
36        } else {
37            GitError::Internal(err)
38        }
39    })?;
40    // workdir() is None for bare repos; fall back to the .git dir path
41    let root = repo.workdir().unwrap_or_else(|| repo.path()).to_path_buf();
42    Ok(Git2Repository { repo, root })
43}
44
45/// Discovers a git repository by walking up from the given path.
46pub fn discover(path: impl AsRef<Path>) -> AppResult<Git2Repository> {
47    let path = path.as_ref();
48    let repo = git2::Repository::discover(path).map_err(|err| {
49        if err.code() == git2::ErrorCode::NotFound {
50            GitError::NotFound {
51                path: path.to_path_buf(),
52            }
53        } else {
54            GitError::Internal(err)
55        }
56    })?;
57    // workdir() is None for bare repos; fall back to the .git dir path
58    let root = repo.workdir().unwrap_or_else(|| repo.path()).to_path_buf();
59    Ok(Git2Repository { repo, root })
60}
61
62/// Clones a git repository into the given path.
63pub fn clone(url: &str, path: impl AsRef<Path>) -> AppResult<Git2Repository> {
64    let path = path.as_ref();
65    let repo = git2::Repository::clone(url, path).map_err(GitError::Internal)?;
66    let root = repo
67        .workdir()
68        .map(Path::to_path_buf)
69        .unwrap_or_else(|| path.to_path_buf());
70    Ok(Git2Repository { repo, root })
71}
72
73/// Creates a new git repository at the given path.
74pub fn init(path: impl AsRef<Path>) -> AppResult<Git2Repository> {
75    let path = path.as_ref();
76    let repo = git2::Repository::init(path).map_err(GitError::Internal)?;
77    let root = repo
78        .workdir()
79        .map(Path::to_path_buf)
80        .unwrap_or_else(|| path.to_path_buf());
81    Ok(Git2Repository { repo, root })
82}
83
84/// Creates a new bare git repository at the given path.
85pub fn init_bare(path: impl AsRef<Path>) -> AppResult<Git2Repository> {
86    let path = path.as_ref();
87    let repo = git2::Repository::init_bare(path).map_err(GitError::Internal)?;
88    Ok(Git2Repository {
89        repo,
90        root: path.to_path_buf(),
91    })
92}
93
94impl RepositoryTrait for Git2Repository {
95    fn root(&self) -> &Path {
96        &self.root
97    }
98
99    fn head(&self) -> AppResult<Reference> {
100        let head = self.repo.head().map_err(map_head_error)?;
101        Ok(reference_from_git2(&head))
102    }
103
104    fn resolve_ref(&self, refname: &str) -> AppResult<Oid> {
105        let obj = self
106            .repo
107            .revparse_single(refname)
108            .map_err(|_| GitError::RefNotFound {
109                refname: refname.to_string(),
110            })?;
111        Ok(oid_from_git2(obj.id()))
112    }
113
114    fn is_dirty(&self) -> AppResult<bool> {
115        let statuses = self.repo.statuses(None).map_err(GitError::Internal)?;
116        Ok(!statuses.is_empty())
117    }
118}