diff options
author | Freya Murphy <freya@freyacat.org> | 2025-01-16 14:45:14 -0500 |
---|---|---|
committer | Freya Murphy <freya@freyacat.org> | 2025-01-16 14:45:14 -0500 |
commit | 60985236c7070425c28aa0c5ce675306d06ab123 (patch) | |
tree | 7448aef5dadf19d4504151ebc370f9e20757ef10 /src/model/repo.rs | |
download | iris-60985236c7070425c28aa0c5ce675306d06ab123.tar.gz iris-60985236c7070425c28aa0c5ce675306d06ab123.tar.bz2 iris-60985236c7070425c28aa0c5ce675306d06ab123.zip |
Diffstat (limited to 'src/model/repo.rs')
-rw-r--r-- | src/model/repo.rs | 123 |
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 {} |