use std::path::{Path, PathBuf};
use anyhow::{Context, Result, anyhow, bail};
use crate::git;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProjectSelector {
Alias(String),
RepoPath(PathBuf),
InferredFromCwd,
}
#[derive(Debug, Clone)]
pub struct ProjectContext {
pub alias: String,
pub repo_root: Option<PathBuf>,
}
pub fn selector_from_arg(project: Option<&str>) -> ProjectSelector {
match project {
Some(value) => {
let path = Path::new(value);
if path.exists() {
ProjectSelector::RepoPath(path.to_path_buf())
} else {
ProjectSelector::Alias(value.to_owned())
}
}
None => ProjectSelector::InferredFromCwd,
}
}
pub fn resolve_project(project: Option<&str>, cwd: &Path) -> Result<ProjectContext> {
match selector_from_arg(project) {
ProjectSelector::RepoPath(path) => {
let root = git::repo_root(&path)?;
let origin = git::origin_url(&root).with_context(|| {
format!("failed to resolve project alias from {}", root.display())
})?;
let alias = alias_from_origin_url(&origin)?;
Ok(ProjectContext {
alias,
repo_root: Some(root),
})
}
ProjectSelector::Alias(alias) => Ok(ProjectContext {
alias,
repo_root: None,
}),
ProjectSelector::InferredFromCwd => {
let root = git::repo_root(cwd).map_err(|_| {
anyhow!("not inside a git repository and --project was not provided")
})?;
let origin = git::origin_url(&root).with_context(|| {
format!("failed to resolve project alias from {}", root.display())
})?;
let alias = alias_from_origin_url(&origin)?;
Ok(ProjectContext {
alias,
repo_root: Some(root),
})
}
}
}
pub fn require_repo_root(context: &ProjectContext) -> Result<&Path> {
context.repo_root.as_deref().ok_or_else(|| {
anyhow!(
"this command needs a local git repository; pass --project with a repository path or run inside the repo"
)
})
}
pub fn alias_from_origin_url(origin: &str) -> Result<String> {
let trimmed = origin.trim_end_matches('/');
let last = trimmed
.rsplit(['/', ':'])
.next()
.ok_or_else(|| anyhow!("failed to extract project alias from origin URL '{origin}'"))?;
let alias = last.trim_end_matches(".git");
if alias.is_empty() {
bail!("failed to extract project alias from origin URL '{origin}'")
}
Ok(alias.to_owned())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extracts_alias_from_ssh_url() {
assert_eq!(
alias_from_origin_url("git@github.com:some-org/my-project.git").unwrap(),
"my-project"
);
}
#[test]
fn extracts_alias_from_https_url() {
assert_eq!(
alias_from_origin_url("https://github.com/some-org/my-project.git").unwrap(),
"my-project"
);
}
#[test]
fn treats_missing_project_as_inferred() {
assert_eq!(selector_from_arg(None), ProjectSelector::InferredFromCwd);
}
#[test]
fn treats_nonexistent_argument_as_alias() {
assert_eq!(
selector_from_arg(Some("my-project")),
ProjectSelector::Alias("my-project".to_owned())
);
}
}