pub use crate::github::GitHubRepository;
pub use crate::oid::{GitOid, OidParseError};
pub use crate::reference::GitReference;
use std::cmp::Ordering;
use std::sync::LazyLock;
use thiserror::Error;
use uv_cache_key::RepositoryUrl;
use uv_redacted::DisplaySafeUrl;
use uv_static::EnvVars;
mod github;
mod oid;
mod reference;
pub static UV_GIT_LFS: LazyLock<GitLfs> = LazyLock::new(|| {
if std::env::var_os(EnvVars::UV_GIT_LFS)
.and_then(|v| v.to_str().map(str::to_lowercase))
.is_some_and(|v| matches!(v.as_str(), "y" | "yes" | "t" | "true" | "on" | "1"))
{
GitLfs::Enabled
} else {
GitLfs::Disabled
}
});
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub enum GitLfs {
#[default]
Disabled,
Enabled,
}
impl GitLfs {
pub fn from_env() -> Self {
*UV_GIT_LFS
}
pub fn enabled(self) -> bool {
matches!(self, Self::Enabled)
}
}
impl From<Option<bool>> for GitLfs {
fn from(value: Option<bool>) -> Self {
match value {
Some(true) => Self::Enabled,
Some(false) => Self::Disabled,
None => Self::from_env(),
}
}
}
impl From<bool> for GitLfs {
fn from(value: bool) -> Self {
if value { Self::Enabled } else { Self::Disabled }
}
}
impl std::fmt::Display for GitLfs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Enabled => write!(f, "enabled"),
Self::Disabled => write!(f, "disabled"),
}
}
}
#[derive(Debug, Error)]
pub enum GitUrlParseError {
#[error(
"Unsupported Git URL scheme `{0}:` in `{1}` (expected one of `https:`, `ssh:`, or `file:`)"
)]
UnsupportedGitScheme(String, DisplaySafeUrl),
}
#[derive(Debug, Clone)]
pub struct GitUrl {
url: DisplaySafeUrl,
repository: RepositoryUrl,
reference: GitReference,
precise: Option<GitOid>,
lfs: GitLfs,
}
impl GitUrl {
pub fn from_reference(
url: DisplaySafeUrl,
reference: GitReference,
lfs: GitLfs,
) -> Result<Self, GitUrlParseError> {
Self::from_fields(url, reference, None, lfs)
}
pub fn from_commit(
url: DisplaySafeUrl,
reference: GitReference,
precise: GitOid,
lfs: GitLfs,
) -> Result<Self, GitUrlParseError> {
Self::from_fields(url, reference, Some(precise), lfs)
}
pub fn from_fields(
url: DisplaySafeUrl,
reference: GitReference,
precise: Option<GitOid>,
lfs: GitLfs,
) -> Result<Self, GitUrlParseError> {
match url.scheme() {
"http" | "https" | "ssh" | "file" => {}
unsupported => {
return Err(GitUrlParseError::UnsupportedGitScheme(
unsupported.to_string(),
url,
));
}
}
Ok(Self {
repository: RepositoryUrl::new(&url),
url,
reference,
precise,
lfs,
})
}
#[must_use]
pub fn with_precise(mut self, precise: GitOid) -> Self {
self.precise = Some(precise);
self
}
#[must_use]
pub fn with_reference(mut self, reference: GitReference) -> Self {
self.reference = reference;
self
}
pub fn url(&self) -> &DisplaySafeUrl {
&self.url
}
pub fn repository(&self) -> &RepositoryUrl {
&self.repository
}
pub fn reference(&self) -> &GitReference {
&self.reference
}
pub fn precise(&self) -> Option<GitOid> {
self.precise
}
pub fn lfs(&self) -> GitLfs {
self.lfs
}
#[must_use]
pub fn with_lfs(mut self, lfs: GitLfs) -> Self {
self.lfs = lfs;
self
}
}
impl PartialEq for GitUrl {
fn eq(&self, other: &Self) -> bool {
self.repository == other.repository
&& self.reference == other.reference
&& self.precise == other.precise
&& self.lfs == other.lfs
}
}
impl Eq for GitUrl {}
impl PartialOrd for GitUrl {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for GitUrl {
fn cmp(&self, other: &Self) -> Ordering {
self.repository
.cmp(&other.repository)
.then_with(|| self.reference.cmp(&other.reference))
.then_with(|| self.precise.cmp(&other.precise))
.then_with(|| self.lfs.cmp(&other.lfs))
}
}
impl std::hash::Hash for GitUrl {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.repository.hash(state);
self.reference.hash(state);
self.precise.hash(state);
self.lfs.hash(state);
}
}
impl TryFrom<DisplaySafeUrl> for GitUrl {
type Error = GitUrlParseError;
fn try_from(mut url: DisplaySafeUrl) -> Result<Self, Self::Error> {
url.set_fragment(None);
url.set_query(None);
let mut reference = GitReference::DefaultBranch;
if let Some((prefix, suffix)) = url
.path()
.rsplit_once('@')
.map(|(prefix, suffix)| (prefix.to_string(), suffix.to_string()))
{
reference = GitReference::from_rev(suffix);
url.set_path(&prefix);
}
Self::from_reference(url, reference, GitLfs::from_env())
}
}
impl From<GitUrl> for DisplaySafeUrl {
fn from(git: GitUrl) -> Self {
let mut url = git.url;
if let Some(precise) = git.precise {
let path = format!("{}@{}", url.path(), precise);
url.set_path(&path);
} else {
match git.reference {
GitReference::Branch(rev)
| GitReference::Tag(rev)
| GitReference::BranchOrTag(rev)
| GitReference::NamedRef(rev)
| GitReference::BranchOrTagOrCommit(rev) => {
let path = format!("{}@{}", url.path(), rev);
url.set_path(&path);
}
GitReference::DefaultBranch => {}
}
}
url
}
}
impl std::fmt::Display for GitUrl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &self.url)
}
}