use std::os::unix::process::CommandExt;
use std::process::Command;
-use miette::{Context, IntoDiagnostic, Result, miette};
+use miette::{Context, IntoDiagnostic, Result, miette, bail, ensure};
use quire::Config;
.into_diagnostic()
.context("failed to parse command")?;
- if words.is_empty() {
- return Err(miette!("no command provided"));
- }
+ ensure!(!words.is_empty(), "no command provided");
let git_cmd = &words[0];
- if !GIT_COMMANDS.contains(&git_cmd.as_str()) {
- return Err(miette!("unsupported command: {git_cmd}"));
- }
+ ensure!(
+ GIT_COMMANDS.contains(&git_cmd.as_str()),
+ "unsupported command: {git_cmd}"
+ );
- if words.len() != 2 {
- return Err(miette!(
- "expected usage: {git_cmd} '<repo>', got {} arguments",
- words.len() - 1
- ));
- }
+ ensure!(
+ words.len() == 2,
+ "expected usage: {git_cmd} '<repo>', got {} arguments",
+ words.len() - 1
+ );
let repo = validate_repo_path(&words[1])?;
let repo_dir = config.repos_dir.join(&repo);
- if !repo_dir.is_dir() {
- return Err(miette!("repository not found: {repo}"));
- }
+ ensure!(repo_dir.is_dir(), "repository not found: {repo}");
tracing::info!(%git_cmd, %repo, "dispatching git command");
let err = Command::new(git_cmd).arg(".").current_dir(&repo_dir).exec();
- Err(miette!("exec failed: {err}"))
+ bail!("exec failed: {err}")
}
/// Validate a repo path argument from the SSH protocol.
fn validate_repo_path(raw: &str) -> Result<String> {
let path = raw.trim_start_matches('/');
- if path.is_empty() {
- return Err(miette!("empty repository path"));
- }
-
- if path.contains("..") {
- return Err(miette!("invalid repository path: {raw}"));
- }
-
- if !path.ends_with(".git") {
- return Err(miette!("invalid repository path (must end in .git): {raw}"));
- }
-
- if path.contains("//") {
- return Err(miette!("invalid repository path: {raw}"));
- }
+ ensure!(!path.is_empty(), "empty repository path");
+ ensure!(!path.contains(".."), "invalid repository path: {raw}");
+ ensure!(
+ path.ends_with(".git"),
+ "invalid repository path (must end in .git): {raw}"
+ );
+ ensure!(!path.contains("//"), "invalid repository path: {raw}");
Ok(path.to_string())
}
use std::path::PathBuf;
-/// Application configuration resolved from environment and defaults.
+/// Application configuration.
#[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 }
+impl Default for Config {
+ fn default() -> Self {
+ Self {
+ repos_dir: PathBuf::from("/var/quire/repos"),
+ }
}
}
#[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"));
+ let config = Config::default();
+ assert_eq!(config.repos_dir, PathBuf::from("/var/quire/repos"));
}
}