rskit_git/embedded/
repository.rs1use std::path::{Path, PathBuf};
2use std::sync::Arc;
3
4use rskit_errors::{AppError, AppResult};
5
6use crate::auth::{AuthProvider, DefaultAuthProvider};
7use crate::core::Repository as RepositoryTrait;
8use crate::error::GitError;
9use crate::options::InitOptions;
10use crate::types::{DEFAULT_BRANCH, Oid, Reference};
11
12use super::{map_head_error, oid_from_git2, reference_from_git2};
13
14pub struct Git2Repository {
16 pub(crate) repo: git2::Repository,
17 pub(crate) root: PathBuf,
18 pub(crate) auth: Arc<dyn AuthProvider>,
19}
20
21impl Git2Repository {
22 pub fn root(&self) -> &Path {
24 &self.root
25 }
26}
27
28fn default_auth() -> Arc<dyn AuthProvider> {
29 Arc::new(DefaultAuthProvider)
30}
31
32pub fn open(path: impl AsRef<Path>) -> AppResult<Git2Repository> {
34 open_with_auth(path, default_auth())
35}
36
37pub fn open_with_auth(
39 path: impl AsRef<Path>,
40 auth: Arc<dyn AuthProvider>,
41) -> AppResult<Git2Repository> {
42 let path = path.as_ref();
43 let abs = std::fs::canonicalize(path).map_err(|err| match err.kind() {
44 std::io::ErrorKind::NotFound => GitError::NotFound {
45 path: path.to_path_buf(),
46 },
47 _ => GitError::Internal(git2::Error::from_str(&err.to_string())),
48 })?;
49 let repo = git2::Repository::open(&abs).map_err(|err| {
50 if err.code() == git2::ErrorCode::NotFound {
51 GitError::NotFound { path: abs.clone() }
52 } else {
53 GitError::Internal(err)
54 }
55 })?;
56 let root = repo.workdir().unwrap_or_else(|| repo.path()).to_path_buf();
58 Ok(Git2Repository { repo, root, auth })
59}
60
61pub fn discover(path: impl AsRef<Path>) -> AppResult<Git2Repository> {
63 discover_with_auth(path, default_auth())
64}
65
66pub fn discover_with_auth(
68 path: impl AsRef<Path>,
69 auth: Arc<dyn AuthProvider>,
70) -> AppResult<Git2Repository> {
71 let path = path.as_ref();
72 let repo = git2::Repository::discover(path).map_err(|err| {
73 if err.code() == git2::ErrorCode::NotFound {
74 GitError::NotFound {
75 path: path.to_path_buf(),
76 }
77 } else {
78 GitError::Internal(err)
79 }
80 })?;
81 let root = repo.workdir().unwrap_or_else(|| repo.path()).to_path_buf();
83 Ok(Git2Repository { repo, root, auth })
84}
85
86pub fn clone(url: &str, path: impl AsRef<Path>) -> AppResult<Git2Repository> {
88 let path = path.as_ref();
89 let repo = git2::Repository::clone(url, path).map_err(GitError::Internal)?;
90 let root = repo
91 .workdir()
92 .map(Path::to_path_buf)
93 .unwrap_or_else(|| path.to_path_buf());
94 Ok(Git2Repository {
95 repo,
96 root,
97 auth: default_auth(),
98 })
99}
100
101pub fn init(path: impl AsRef<Path>) -> AppResult<Git2Repository> {
106 init_with(
107 path,
108 &InitOptions::default().with_initial_branch(DEFAULT_BRANCH),
109 )
110}
111
112pub fn init_with(path: impl AsRef<Path>, options: &InitOptions) -> AppResult<Git2Repository> {
114 let path = path.as_ref();
115 let mut git2_options = git2::RepositoryInitOptions::new();
116 if let Some(initial_branch) = &options.initial_branch {
117 let reference = format!("refs/heads/{initial_branch}");
118 if !git2::Reference::is_valid_name(&reference) {
119 return Err(AppError::invalid_input(
120 "initial_branch",
121 format!("invalid branch name '{initial_branch}'"),
122 ));
123 }
124 git2_options.initial_head(initial_branch);
125 }
126 let repo = git2::Repository::init_opts(path, &git2_options).map_err(GitError::Internal)?;
127 let root = repo
128 .workdir()
129 .map(Path::to_path_buf)
130 .unwrap_or_else(|| path.to_path_buf());
131 Ok(Git2Repository {
132 repo,
133 root,
134 auth: default_auth(),
135 })
136}
137
138pub fn init_bare(path: impl AsRef<Path>) -> AppResult<Git2Repository> {
142 let path = path.as_ref();
143 let mut options = git2::RepositoryInitOptions::new();
144 options.bare(true).initial_head(DEFAULT_BRANCH);
145 let repo = git2::Repository::init_opts(path, &options).map_err(GitError::Internal)?;
146 Ok(Git2Repository {
147 repo,
148 root: path.to_path_buf(),
149 auth: default_auth(),
150 })
151}
152
153impl RepositoryTrait for Git2Repository {
154 fn root(&self) -> &Path {
155 &self.root
156 }
157
158 fn head(&self) -> AppResult<Reference> {
159 let head = self.repo.head().map_err(map_head_error)?;
160 Ok(reference_from_git2(&head))
161 }
162
163 fn resolve_ref(&self, refname: &str) -> AppResult<Oid> {
164 let obj = self
165 .repo
166 .revparse_single(refname)
167 .map_err(|_| GitError::RefNotFound {
168 refname: refname.to_string(),
169 })?;
170 Ok(oid_from_git2(obj.id()))
171 }
172
173 fn is_dirty(&self) -> AppResult<bool> {
174 let statuses = self.repo.statuses(None).map_err(GitError::Internal)?;
175 Ok(!statuses.is_empty())
176 }
177}