let path = args[0].trim_start_matches('/');
ensure!(!path.is_empty(), "empty repository path");
- let repo_dir = quire.repo(path)?;
- ensure!(repo_dir.is_dir(), "repository not found: {path}");
+ let repo = quire.repo(path)?;
+ ensure!(repo.exists(), "repository not found: {path}");
tracing::info!(%git_cmd, %path, "dispatching git command");
- let err = Command::new(git_cmd).arg(".").current_dir(&repo_dir).exec();
+ let err = Command::new(git_cmd)
+ .arg(".")
+ .current_dir(repo.path())
+ .exec();
bail!("exec failed: {err}")
}
use quire::Quire;
pub async fn new(quire: &Quire, name: &str) -> Result<()> {
- let repo_dir = quire.repo(name)?;
- ensure!(!repo_dir.is_dir(), "repository already exists: {name}");
+ let repo = quire.repo(name)?;
+ ensure!(!repo.exists(), "repository already exists: {name}");
// Create parent directory for grouped repos (e.g. work/foo.git).
- if let Some(parent) = repo_dir.parent() {
+ if let Some(parent) = repo.path().parent() {
fs_err::create_dir_all(parent).into_diagnostic()?;
}
}
pub async fn rm(quire: &Quire, name: &str) -> Result<()> {
- let repo_dir = quire.repo(name)?;
- ensure!(repo_dir.is_dir(), "repository not found: {name}");
+ let repo = quire.repo(name)?;
+ ensure!(repo.exists(), "repository not found: {name}");
- fs_err::remove_dir_all(&repo_dir).into_diagnostic()?;
+ fs_err::remove_dir_all(repo.path()).into_diagnostic()?;
// Clean up empty parent directory for grouped repos.
- if let Some(parent) = repo_dir.parent()
+ if let Some(parent) = repo.path().parent()
&& parent != quire.repos_dir()
{
let _ = fs_err::remove_dir(parent);
use crate::config::Config;
+/// A resolved repository path.
+///
+/// Created by `Quire::repo` after validating the name.
+pub struct Repo {
+ path: PathBuf,
+}
+
+impl Repo {
+ pub fn path(&self) -> &Path {
+ &self.path
+ }
+
+ pub fn exists(&self) -> bool {
+ self.path.is_dir()
+ }
+}
+
/// Application runtime context.
///
/// Carries configuration and provides resolved paths to repositories.
///
/// Rejects path traversal, missing `.git` suffix, empty segments,
/// reserved path components, and more than one level of grouping.
- pub fn repo(&self, name: &str) -> Result<PathBuf> {
+ pub fn repo(&self, name: &str) -> Result<Repo> {
validate_repo_name(name)?;
- Ok(self.config.repos_dir.join(name))
+ Ok(Repo {
+ path: self.config.repos_dir.join(name),
+ })
}
/// List all repository names under the repos directory.
fn repo_resolves_path() {
let q = quire();
assert_eq!(
- q.repo("foo.git").unwrap(),
- PathBuf::from("/var/quire/repos/foo.git")
+ q.repo("foo.git").unwrap().path(),
+ Path::new("/var/quire/repos/foo.git")
);
}