pub mod export;
pub mod import;
use anyhow::Context;
use async_trait::async_trait;
use std::process::Command;
pub use export::{
clone_git_to_noa, detect_lfs_available, export_noa_to_git, has_lfs_tracking, lfs_install,
lfs_pull, lfs_push_all,
};
pub use import::{import_git_to_noa, is_lfs_pointer};
use crate::{
error::Result,
remote::{FetchResult, FetchSpec, PushResult, PushSpec, RemoteBackend, RemoteRef},
};
pub struct GitBackend;
impl Default for GitBackend {
fn default() -> Self {
Self::new()
}
}
impl GitBackend {
#[must_use]
pub fn new() -> Self {
GitBackend
}
}
#[async_trait]
impl RemoteBackend for GitBackend {
fn protocol(&self) -> &'static str {
"git"
}
async fn push(&self, url: &str, _: &[PushSpec]) -> Result<PushResult> {
export::validate_git_url(url)?;
let url_owned = url.to_string();
let output = tokio::task::spawn_blocking(move || {
Command::new("git")
.args(["push", &url_owned])
.output()
.with_context(|| "git push failed")
})
.await??;
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
if output.status.success() {
Ok(PushResult {
ok: true,
message: format!("{stdout}\n{stderr}").trim().to_string(),
})
} else {
anyhow::bail!("git push failed: {stderr}")
}
}
async fn fetch(&self, url: &str, _: &[FetchSpec]) -> Result<FetchResult> {
export::validate_git_url(url)?;
let url_owned = url.to_string();
let output = tokio::task::spawn_blocking(move || {
Command::new("git")
.args(["fetch", &url_owned])
.output()
.with_context(|| "git fetch failed")
})
.await??;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("git fetch failed: {stderr}");
}
self.list_refs(url).await.map(|refs| FetchResult { refs })
}
async fn list_refs(&self, url: &str) -> Result<Vec<RemoteRef>> {
export::validate_git_url(url)?;
let url_owned = url.to_string();
let output = tokio::task::spawn_blocking(move || {
Command::new("git")
.args(["ls-remote", "--refs", &url_owned])
.output()
.with_context(|| "git ls-remote failed")
})
.await??;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("git ls-remote failed: {stderr}");
}
let stdout = String::from_utf8_lossy(&output.stdout);
let mut refs = Vec::new();
for line in stdout.lines() {
let parts: Vec<&str> = line.splitn(2, '\t').collect();
if parts.len() == 2 {
refs.push(RemoteRef {
commit_hash: parts[0].to_string(),
name: parts[1].to_string(),
});
}
}
Ok(refs)
}
}