use std::os::unix::process::CommandExt;
-use std::path::Path;
use std::process::Command;
-use miette::{miette, Context, IntoDiagnostic, Result};
+use miette::{Context, IntoDiagnostic, Result, miette};
-const GIT_COMMANDS: &[&str] = &["git-receive-pack", "git-upload-pack", "git-upload-archive"];
+use quire::Config;
-const REPOS_DIR: &str = "/var/quire/repos";
+const GIT_COMMANDS: &[&str] = &["git-receive-pack", "git-upload-pack", "git-upload-archive"];
-pub async fn run(command: Vec<String>) -> Result<()> {
+pub async fn run(config: &Config, command: Vec<String>) -> Result<()> {
let input = if command.len() == 1 {
// Single argument: the full SSH_ORIGINAL_COMMAND string.
// e.g. git-receive-pack '/foo.git'
let repo = validate_repo_path(&words[1])?;
- let repo_dir = Path::new(REPOS_DIR).join(&repo);
+ let repo_dir = config.repos_dir.join(&repo);
if !repo_dir.is_dir() {
return Err(miette!("repository not found: {repo}"));
}
tracing::info!(%git_cmd, %repo, "dispatching git command");
-
- let repo_dir = Path::new(REPOS_DIR).join(&repo);
let err = Command::new(git_cmd).arg(".").current_dir(&repo_dir).exec();
Err(miette!("exec failed: {err}"))
use miette::Result;
-pub async fn run() -> Result<()> {
+use quire::Config;
+
+pub async fn run(config: &Config) -> Result<()> {
tracing::info!("quire serve starting");
+ let _ = config; // used once HTTP routing lands
// TODO: bind HTTP server
Ok(())
}
use clap_complete::Shell;
use miette::IntoDiagnostic;
use miette::Result;
+use quire::Config;
use tracing_subscriber::EnvFilter;
use tracing_subscriber::fmt;
use tracing_subscriber::prelude::*;
return Ok(());
};
+ let config = Config::load();
+
match command {
- Commands::Serve => commands::serve::run().await?,
- Commands::Exec { command } => commands::exec::run(command).await?,
+ Commands::Serve => commands::serve::run(&config).await?,
+ Commands::Exec { command } => commands::exec::run(&config, command).await?,
}
Ok(())
--- /dev/null
+use std::path::PathBuf;
+
+/// Application configuration resolved from environment and defaults.
+#[derive(Debug, Clone)]
+pub struct Config {
+ /// Root directory containing bare Git repositories.
+ pub repos_dir: PathBuf,
+}
+
+impl Config {
+ /// Environment variable overriding the default repository root.
+ const REPOS_DIR_ENV: &'static str = "QUIRE_REPOS_DIR";
+
+ /// Default repository root.
+ const DEFAULT_REPOS_DIR: &'static str = "/var/quire/repos";
+
+ /// Build config from environment, falling back to defaults.
+ pub fn load() -> Self {
+ let repos_dir = std::env::var(Self::REPOS_DIR_ENV)
+ .map(PathBuf::from)
+ .unwrap_or_else(|_| PathBuf::from(Self::DEFAULT_REPOS_DIR));
+
+ Self { repos_dir }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn default_repos_dir() {
+ let config = Config::load();
+ assert_eq!(config.repos_dir, PathBuf::from(Config::DEFAULT_REPOS_DIR));
+ }
+
+ #[test]
+ fn env_override_repos_dir() {
+ // SAFETY: single-threaded test, no other code reading this env var concurrently.
+ unsafe { std::env::set_var(Config::REPOS_DIR_ENV, "/tmp/test-repos") };
+ let config = Config::load();
+ // SAFETY: same justification.
+ unsafe { std::env::remove_var(Config::REPOS_DIR_ENV) };
+ assert_eq!(config.repos_dir, PathBuf::from("/tmp/test-repos"));
+ }
+}
+mod config;
mod error;
+pub use config::Config;
pub use error::Error;
pub use error::Result;