]> quire.kejadlen.dev Git - quire.git/commitdiff
Extract Config struct for shared app configuration
authorAlpha Chen <alpha@kejadlen.dev>
Fri, 24 Apr 2026 13:31:19 +0000 (06:31 -0700)
committerAlpha Chen <alpha@kejadlen.dev>
Fri, 24 Apr 2026 14:33:57 +0000 (07:33 -0700)
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
src/bin/quire/commands/serve.rs
src/bin/quire/main.rs
src/config.rs [new file with mode: 0644]
src/lib.rs

index 0b2ad73daa6e6da53a3eedf422641a811dd4dd3c..5faf6215193f8c3ff18ef23662ee3b801b017d5b 100644 (file)
@@ -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<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'
@@ -41,14 +40,12 @@ pub async fn run(command: Vec<String>) -> 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}"))
index 114afed6c6ba9399661ff5cd8110208c139f2282..af2a70a2e95e5a348ee51dee1cf5414677ba6f71 100644 (file)
@@ -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(())
 }
index f9d56fc83c1c1d2e5960c4c86f2aca22bf173a7a..9beb3b885e4def140a5295506ce033189828b7d9 100644 (file)
@@ -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 (file)
index 0000000..59ef55f
--- /dev/null
@@ -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"));
+    }
+}
index f68881b22c7381a7d79c7a803efc882162f45648..94e1a3af9284e8265a6e722650adabef5f991c2e 100644 (file)
@@ -1,4 +1,6 @@
+mod config;
 mod error;
 
+pub use config::Config;
 pub use error::Error;
 pub use error::Result;