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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
//! Definition of an iris [repo][Repository].
use crate::{error, Result};
use log::{debug, trace};
use std::{path::PathBuf, sync::OnceLock};
use url::Url;
/// A handle to a locally stored git repository.
pub struct Repository {
/// The remote url that the git repository is located at.
pub url: Url,
/// The path on disk that the this repository is/will be located at.
pub path: PathBuf,
/// The git2 lib handle to the repo
inner: OnceLock<git2::Repository>,
}
impl Repository {
/// Creates a new handle to a local git repository.
pub const fn new(url: Url, path: PathBuf) -> Self {
Self {
url,
path,
inner: OnceLock::new(),
}
}
/// Gets the handle to the local repository
fn repo(&self) -> Result<&git2::Repository> {
// we only need to initalize once
if let Some(repo) = self.inner.get() {
return Ok(repo);
}
trace!("loading repo");
// repository not loaded, try loading it
let repo = if self.path.exists() {
// repo exists on file system, just open it
git2::Repository::open(&self.path)?
} else {
// repo needs to be cloned
git2::Repository::clone(self.url.as_str(), &self.path)?
};
// save and return repo
Ok(self.inner.get_or_init(|| repo))
}
/// Gets the current remote in the git repository
fn remote(&self) -> Result<git2::Remote> {
let repo = self.repo()?;
let remote_name = "origin";
let mut remote = repo.find_remote(remote_name)?;
// make sure that out remote is connected before
// we do anything with it. otherwise things will
// fail!
if !remote.connected() {
trace!("connecting to remote");
remote.connect(git2::Direction::Fetch)?;
}
Ok(remote)
}
/// Fetches the latest commits in the provided branch.
pub fn fetch(&self, branch: &str) -> Result<()> {
let mut remote = self.remote()?;
trace!("fetching '{branch}");
remote.fetch(&[branch], None, None)?;
Ok(())
}
/// Returns the default branch of the git repositroy.
pub fn default_branch(&self) -> Result<String> {
let remote = self.remote()?;
let buf = remote.default_branch()?;
let name = buf
.as_str()
.and_then(|s| s.strip_prefix("refs/heads/"))
.map(String::from)
.ok_or_else(|| error::error!("cannot get default branch"))?;
debug!("default branch is '{name}'");
Ok(name)
}
/// Returns the latest fetched commit in `branch`.
pub fn latest_commit(&self, branch: &str) -> Result<String> {
let repo = self.repo()?;
let refr_name = format!("refs/remotes/origin/{branch}");
let refr = repo.find_reference(&refr_name)?;
let commit = refr.peel_to_commit()?;
let oid = commit.id().to_string();
Ok(oid)
}
/// Checks out the local repository to the given commit and detaches
/// HEAD to that commit.
pub fn checkout(&self, commit: &str) -> Result<()> {
let repo = self.repo()?;
trace!("checkout to '{commit}'");
let oid = git2::Oid::from_str(commit)?;
let commit = repo.find_commit(oid)?;
repo.reset(commit.as_object(), git2::ResetType::Hard, None)?;
Ok(())
}
}
impl std::fmt::Debug for Repository {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "_")
}
}
impl PartialEq for Repository {
fn eq(&self, other: &Self) -> bool {
self.url.eq(&other.url) && self.path.eq(&other.path)
}
}
impl Eq for Repository {}
|