From c7043d3608d9fc515d8ec99210c6eac6fc2c0b14 Mon Sep 17 00:00:00 2001 From: Alpha Chen Date: Fri, 24 Apr 2026 06:31:19 -0700 Subject: [PATCH] Extract Config struct for shared app configuration Multiple commands need the repos directory path. A Config struct centralizes the default and lets QUIRE_REPOS_DIR override it. Assisted-by: GLM-5.1 via pi --- src/bin/quire/commands/exec.rs | 13 ++++------ src/bin/quire/commands/serve.rs | 5 +++- src/bin/quire/main.rs | 7 +++-- src/config.rs | 46 +++++++++++++++++++++++++++++++++ src/lib.rs | 2 ++ 5 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 src/config.rs diff --git a/src/bin/quire/commands/exec.rs b/src/bin/quire/commands/exec.rs index 0b2ad73..5faf621 100644 --- a/src/bin/quire/commands/exec.rs +++ b/src/bin/quire/commands/exec.rs @@ -1,14 +1,13 @@ 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) -> Result<()> { +pub async fn run(config: &Config, command: Vec) -> Result<()> { let input = if command.len() == 1 { // Single argument: the full SSH_ORIGINAL_COMMAND string. // e.g. git-receive-pack '/foo.git' @@ -41,14 +40,12 @@ pub async fn run(command: Vec) -> Result<()> { 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}")) diff --git a/src/bin/quire/commands/serve.rs b/src/bin/quire/commands/serve.rs index 114afed..af2a70a 100644 --- a/src/bin/quire/commands/serve.rs +++ b/src/bin/quire/commands/serve.rs @@ -1,7 +1,10 @@ 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(()) } diff --git a/src/bin/quire/main.rs b/src/bin/quire/main.rs index f9d56fc..9beb3b8 100644 --- a/src/bin/quire/main.rs +++ b/src/bin/quire/main.rs @@ -4,6 +4,7 @@ use clap::{CommandFactory, Parser, Subcommand}; 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::*; @@ -57,9 +58,11 @@ async fn main() -> Result<()> { 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(()) diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..59ef55f --- /dev/null +++ b/src/config.rs @@ -0,0 +1,46 @@ +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")); + } +} diff --git a/src/lib.rs b/src/lib.rs index f68881b..94e1a3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,6 @@ +mod config; mod error; +pub use config::Config; pub use error::Error; pub use error::Result; -- 2.54.0