use std::{
ffi::OsStr,
fs::{self, File},
io,
path::Path,
process::Command,
};
use tempfile::TempDir;
use walkdir::WalkDir;
use crate::{Metadata, Mime, serve::Serve};
use super::{Mount, Url};
#[derive(Debug)]
pub struct GitRepo {
mount: Mount,
clone: TempDir,
}
impl GitRepo {
pub fn local(url: impl Into<Url>, src: &Path) -> io::Result<Self> {
Self::clone(url, &["--no-local"], src, TempDir::new()?)
}
pub fn local_in(url: impl Into<Url>, tmp: &Path, src: &Path) -> io::Result<Self> {
Self::clone(url, &["--no-local"], src, TempDir::new_in(tmp)?)
}
pub fn remote(url: impl Into<Url>, src: impl Into<url::Url>) -> io::Result<Self> {
Self::clone(url, &["--no-local"], src.into().as_str(), TempDir::new()?)
}
pub fn remote_in(
url: impl Into<Url>,
tmp: &Path,
src: impl Into<url::Url>,
) -> io::Result<Self> {
Self::clone(url, &["--no-local"], src.into().as_str(), TempDir::new_in(tmp)?)
}
fn clone(
url: impl Into<Url>,
extra: &[&str],
src: impl AsRef<OsStr>,
dest: TempDir,
) -> io::Result<Self> {
let src = src.as_ref();
Self::run(
Command::new("git")
.args(["clone", "--bare"])
.args(extra)
.arg(src)
.arg(dest.path()),
)?;
Self::run(Command::new("git").arg("-C").arg(dest.path()).arg("update-server-info"))?;
fs::create_dir_all(dest.path().join("hooks"))?;
File::options()
.create(true)
.append(true)
.truncate(false)
.open(dest.path().join("hooks/post-receive"))?;
Ok(Self {
mount: Mount::new(url, dest.path()),
clone: dest,
})
}
fn run(cmd: &mut Command) -> io::Result<()> {
let output = cmd.output()?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(io::Error::other(format!("`git` errored:\n{stderr}")));
}
Ok(())
}
}
impl Serve for GitRepo {
fn base(&self) -> &Url {
&self.mount.url
}
fn scan(&self, _dir: &Path) -> io::Result<crate::Metadata> {
let mut res = Metadata::default();
for de in WalkDir::new(self.clone.path()).follow_links(true) {
let de = de?;
if !de.file_type().is_file() {
continue;
}
let Ok(rel) = de.path().strip_prefix(self.clone.path()) else {
continue;
};
let url = Url(self.mount.url.join(rel));
res.files.insert(url, Mime::BYTES);
}
res.files.entry(Url(self.mount.url.join("hooks/post-receive"))).or_insert(Mime::BYTES);
Ok(res)
}
fn render(
&self,
_: &crate::Blog,
url: &Url,
into: &mut dyn io::prelude::Write,
) -> io::Result<()> {
let Ok(rel) = url.0.strip_prefix(&self.mount.url.0) else {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("url {:?} not under base {:?}", url, self.mount.url),
));
};
let in_dir = self.clone.path().join(rel);
let mut f = File::open(in_dir)?;
io::copy(&mut f, into)?;
Ok(())
}
}