use std::{fmt, mem, num::NonZeroU32, path::Path, str::FromStr, sync::atomic::AtomicBool};
use gix::{
clone, create,
open::{self, Permissions},
remote, Url,
};
use tracing::debug;
mod progress_tracing;
use progress_tracing::TracingProgress;
mod cancellation_token;
pub use cancellation_token::{GitCancelOnDrop, GitCancellationToken};
mod error;
use error::GitErrorInner;
pub use error::{GitError, GitUrlParseError};
#[derive(Clone, Debug)]
pub struct GitUrl(Url);
impl fmt::Display for GitUrl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl FromStr for GitUrl {
type Err = GitUrlParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Url::try_from(s).map(Self).map_err(GitUrlParseError)
}
}
#[derive(Debug)]
pub struct Repository(gix::ThreadSafeRepository);
impl Repository {
fn prepare_fetch(
url: GitUrl,
path: &Path,
kind: create::Kind,
) -> Result<clone::PrepareFetch, GitErrorInner> {
Ok(clone::PrepareFetch::new(
url.0,
path,
kind,
create::Options {
destination_must_be_empty: true,
..Default::default()
},
open::Options::default().permissions(Permissions::all()),
)?
.with_shallow(remote::fetch::Shallow::DepthAtRemote(
NonZeroU32::new(1).unwrap(),
)))
}
pub fn shallow_clone_bare(
url: GitUrl,
path: &Path,
cancellation_token: Option<GitCancellationToken>,
) -> Result<Self, GitError> {
debug!("Shallow cloning {url} to {}", path.display());
Ok(Self(
Self::prepare_fetch(url, path, create::Kind::Bare)?
.fetch_only(
&mut TracingProgress::new("Cloning bare"),
cancellation_token
.as_ref()
.map(GitCancellationToken::get_atomic)
.unwrap_or(&AtomicBool::new(false)),
)
.map_err(GitErrorInner::from)?
.0
.into(),
))
}
pub fn shallow_clone(
url: GitUrl,
path: &Path,
cancellation_token: Option<GitCancellationToken>,
) -> Result<Self, GitError> {
debug!("Shallow cloning {url} to {} with worktree", path.display());
let mut progress = TracingProgress::new("Cloning with worktree");
Ok(Self(
Self::prepare_fetch(url, path, create::Kind::WithWorktree)?
.fetch_then_checkout(&mut progress, &AtomicBool::new(false))
.map_err(GitErrorInner::from)?
.0
.main_worktree(
&mut progress,
cancellation_token
.as_ref()
.map(GitCancellationToken::get_atomic)
.unwrap_or(&AtomicBool::new(false)),
)
.map_err(GitErrorInner::from)?
.0
.into(),
))
}
#[inline(always)]
pub fn get_head_commit_entry_data_by_path(
&self,
path: impl AsRef<Path>,
) -> Result<Option<Vec<u8>>, GitError> {
fn inner(this: &Repository, path: &Path) -> Result<Option<Vec<u8>>, GitErrorInner> {
Ok(
if let Some(entry) = this
.0
.to_thread_local()
.head_commit()?
.tree()?
.peel_to_entry_by_path(path)?
{
Some(mem::take(&mut entry.object()?.data))
} else {
None
},
)
}
Ok(inner(self, path.as_ref())?)
}
pub fn get_head_commit_hash(&self) -> Result<impl fmt::Display, GitError> {
Ok(self
.0
.to_thread_local()
.head_commit()
.map_err(GitErrorInner::from)?
.id)
}
}