summaryrefslogtreecommitdiff
path: root/src/model/repo.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/model/repo.rs123
1 files changed, 123 insertions, 0 deletions
diff --git a/src/model/repo.rs b/src/model/repo.rs
new file mode 100644
index 0000000..557efb4
--- /dev/null
+++ b/src/model/repo.rs
@@ -0,0 +1,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 {}