pub mod error;
pub mod types;
#[cfg(feature = "git-backend-libgit2")]
pub mod libgit2;
#[cfg(feature = "git-backend-cli")]
pub mod cli;
use std::{
panic::{RefUnwindSafe, UnwindSafe},
path::{Path, PathBuf},
str::FromStr,
};
use anyhow::bail;
use log::info;
use serde::Deserialize;
use error::GitBackendError;
use types::GitOid;
pub trait GitRepository {
fn remote_add(&self, name: &str, url: &str) -> Result<(), GitBackendError>;
fn remote_get_url(&self, name: &str) -> Result<Option<String>, GitBackendError>;
fn remote_set_url(&self, name: &str, url: &str) -> Result<(), GitBackendError>;
fn fetch(&self, remote_name: &str, refspecs: &[String]) -> Result<(), GitBackendError>;
fn commit_exists(&self, oid: &str) -> Result<bool, GitBackendError>;
fn revparse_commit(&self, spec: &str) -> Result<GitOid, GitBackendError>;
fn read_blob(&self, commit: &str, blob_path: &str) -> Result<Option<Vec<u8>>, GitBackendError>;
fn is_ancestor(&self, ancestor: &GitOid, descendant: &GitOid) -> Result<bool, GitBackendError>;
fn create_worktree(
&self,
name: &str,
worktree_path: &Path,
commit: &str,
) -> Result<WorktreeResult, GitBackendError>;
fn reset(&self, commit: &str) -> Result<(), GitBackendError>;
}
pub trait GitBackend: Send + Sync + UnwindSafe + RefUnwindSafe {
fn init_bare(&self, path: &Path) -> Result<Box<dyn GitRepository>, GitBackendError>;
fn open(&self, path: &Path) -> Result<Box<dyn GitRepository>, GitBackendError>;
}
pub enum WorktreeResult {
Created(Box<dyn GitRepository>),
Existing(PathBuf, Box<dyn GitRepository>),
}
impl std::fmt::Debug for WorktreeResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WorktreeResult::Created(_) => write!(f, "Created(..)"),
WorktreeResult::Existing(p, _) => write!(f, "Existing({:?}, ..)", p),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
pub enum GitBackendType {
#[cfg(feature = "git-backend-libgit2")]
#[serde(rename = "libgit2")]
Libgit2,
#[cfg(feature = "git-backend-cli")]
#[serde(rename = "cli")]
Cli,
}
impl GitBackendType {
#[allow(unreachable_code)]
pub fn try_default() -> anyhow::Result<Self> {
#[cfg(feature = "git-backend-libgit2")]
return Ok(GitBackendType::Libgit2);
#[cfg(feature = "git-backend-cli")]
return Ok(GitBackendType::Cli);
bail!("No git backend is enabled. Please enable at least one of `git-backend-libgit2` or `git-backend-cli` features.");
}
}
impl FromStr for GitBackendType {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
#[cfg(feature = "git-backend-libgit2")]
"libgit2" => Ok(GitBackendType::Libgit2),
#[cfg(feature = "git-backend-cli")]
"cli" => Ok(GitBackendType::Cli),
_ => bail!("invalid git backend type: {s}"),
}
}
}
pub fn create_backend(
backend_type: GitBackendType,
git_executable: Option<String>,
) -> Box<dyn GitBackend> {
match backend_type {
#[cfg(feature = "git-backend-libgit2")]
GitBackendType::Libgit2 => {
info!("Using libgit2 git backend");
Box::new(libgit2::Libgit2Backend::new())
}
#[cfg(feature = "git-backend-cli")]
GitBackendType::Cli => {
info!("Using git CLI backend");
Box::new(cli::CliBackend::new(
git_executable.unwrap_or_else(|| "git".to_string()),
))
}
}
}