diff --git a/src/git.rs b/src/git.rs index 56777df..b30279e 100644 --- a/src/git.rs +++ b/src/git.rs @@ -6,7 +6,8 @@ use std::{ process::{Command, Output}, }; -use log::trace; +use log::{trace, warn}; +use regex::bytes::Regex; #[derive(Debug)] pub enum Error { @@ -62,6 +63,45 @@ fn run(mut command: Command) -> Result { } } +pub fn init_bare(path: &Path) -> Result<(), Error> { + let mut command = Command::new("git"); + command.arg("init").arg("--bare").arg("--").arg(path); + run(command)?; + Ok(()) +} + +pub fn fetch_head(path: &Path, url: &str) -> Result<(), Error> { + let mut command = Command::new("git"); + command + .arg("-C") + .arg(path) + .arg("ls-remote") + .arg("--symref") + .arg("--") + .arg(url) + .arg("HEAD"); // Includes other refs like refs/foo/HEAD + let output = run(command)?; + + let regex = Regex::new(r"(?m)^ref: (refs/\S+)\s+HEAD$").unwrap(); + let Some(captures) = regex.captures(&output.stdout) else { + warn!("Did not find HEAD of {url}"); + return Ok(()); + }; + let head = String::from_utf8_lossy(captures.get(1).unwrap().as_bytes()); + + let mut command = Command::new("git"); + command + .arg("-C") + .arg(path) + .arg("symbolic-ref") + .arg("--") + .arg("HEAD") + .arg(&head as &str); + run(command)?; + + Ok(()) +} + pub fn fetch(path: &Path, url: &str, refspecs: &[String]) -> Result<(), Error> { let mut command = Command::new("git"); command diff --git a/src/server.rs b/src/server.rs index 2d68dfa..8fae60b 100644 --- a/src/server.rs +++ b/src/server.rs @@ -9,6 +9,7 @@ use std::{ time::Duration, }; +use anyhow::anyhow; use axum::extract::FromRef; use gix::ThreadSafeRepository; use log::{debug, info}; @@ -18,10 +19,54 @@ use sqlx::{ }; use tokio::select; -use crate::{args::ServerCommand, config::ServerConfig, somehow}; +use crate::{args::ServerCommand, config::ServerConfig, git, somehow}; use self::workers::Workers; +fn open_repo( + path: &Path, + url: &Option, + refspecs: &[String], +) -> somehow::Result { + if path.exists() { + info!("Opening repo at {}", path.display()); + Ok(ThreadSafeRepository::open(path)?) + } else if let Some(url) = url { + info!( + "No repo found at {} but a fetch url is configured", + path.display() + ); + + info!("Creating bare repo"); + git::init_bare(path)?; + + info!("Fetching HEAD from {url}"); + git::fetch_head(path, url)?; + + info!("Fetching refs for the first time (this may take a while)"); + git::fetch(path, url, refspecs)?; + + Ok(ThreadSafeRepository::open(path)?) + } else { + Err(somehow::Error(anyhow!( + "Failed to open repo: No repo found at {} and no fetch url is configured", + path.display() + ))) + } +} + +fn open_bench_repo(path: &Path) -> somehow::Result { + if path.exists() { + info!("Opening bench repo at {}", path.display()); + Ok(ThreadSafeRepository::open(path)?) + } else { + Err(somehow::Error(anyhow!( + "Failed to open bench repo: No repo found at {}", + path.display() + ))) + } +} + async fn open_db(path: &Path) -> sqlx::Result { let options = SqliteConnectOptions::new() // https://www.sqlite.org/pragma.html#pragma_journal_mode @@ -77,16 +122,14 @@ impl Server { command: ServerCommand, ) -> somehow::Result { let repo = if let Some(path) = command.repo.as_ref() { - info!("Opening repo at {}", path.display()); - let repo = ThreadSafeRepository::open(path)?; + let repo = open_repo(path, &config.repo_fetch_url, &config.repo_fetch_refspecs)?; Some(Repo(Arc::new(repo))) } else { None }; let bench_repo = if let Some(path) = command.bench_repo.as_ref() { - info!("Opening bench repo at {}", path.display()); - let repo = ThreadSafeRepository::open(path)?; + let repo = open_bench_repo(path)?; Some(BenchRepo(Arc::new(repo))) } else { None