use async_trait::async_trait;
use crate::domain::{commit::Commit, version::SemanticVersion, workspace::WorkingTreeStatus};
#[derive(Debug, thiserror::Error)]
pub enum ScmError {
#[error("Repository not found: {0}")]
NotFound(String),
#[error("Git operation failed: {0}")]
GitError(String),
#[error("No git repository found")]
NotAGitRepo,
#[error("Tag not found: {0}")]
TagNotFound(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("No commits found")]
NoCommits,
}
#[async_trait]
pub trait SourceControl: Send + Sync {
fn name(&self) -> &str;
async fn get_commits_since(&self, tag: Option<&str>) -> Result<Vec<Commit>, ScmError>;
async fn get_last_tag(&self, pattern: Option<&str>) -> Result<Option<String>, ScmError>;
async fn create_tag(&self, name: &str, message: &str) -> Result<String, ScmError>;
async fn delete_tag(&self, name: &str) -> Result<(), ScmError>;
async fn get_current_commit(&self) -> Result<String, ScmError>;
async fn get_current_branch(&self) -> Result<String, ScmError>;
async fn get_working_tree_status(&self) -> Result<WorkingTreeStatus, ScmError>;
async fn commit(&self, message: &str, files: &[String]) -> Result<String, ScmError>;
async fn stage_files(&self, files: &[String]) -> Result<(), ScmError>;
async fn push(&self, remote: Option<&str>, branch: Option<&str>) -> Result<(), ScmError>;
fn repository_root(&self) -> Option<&std::path::Path>;
async fn is_on_branch(&self, branch: &str) -> Result<bool, ScmError>;
async fn get_tags_for_commit(&self, commit_hash: &str) -> Result<Vec<String>, ScmError>;
async fn get_remote_url(&self, remote: Option<&str>) -> Result<Option<String>, ScmError>;
}
#[derive(Debug, Clone)]
pub struct ScmConfig {
pub repository_path: Option<std::path::PathBuf>,
pub default_remote: String,
pub sign_commits: bool,
pub sign_tags: bool,
pub commit_template: Option<String>,
pub tag_template: Option<String>,
}
impl Default for ScmConfig {
fn default() -> Self {
Self {
repository_path: None,
default_remote: "origin".to_string(),
sign_commits: false,
sign_tags: false,
commit_template: Some("chore(release): bump version to {{version}}".to_string()),
tag_template: Some("v{{version}}".to_string()),
}
}
}
#[must_use]
pub fn format_commit_message(template: &str, version: &SemanticVersion) -> String {
template.replace("{{version}}", &version.to_string())
}
#[must_use]
pub fn format_tag_name(template: &str, version: &SemanticVersion) -> String {
template.replace("{{version}}", &version.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_commit_message() {
let version = SemanticVersion::parse("1.2.3").unwrap();
let template = "chore(release): bump version to {{version}}";
assert_eq!(
format_commit_message(template, &version),
"chore(release): bump version to 1.2.3"
);
}
#[test]
fn test_format_tag_name() {
let version = SemanticVersion::parse("1.2.3").unwrap();
let template = "v{{version}}";
assert_eq!(format_tag_name(template, &version), "v1.2.3");
}
}