use git2::Repository;
use std::path::Path;
use crate::Version;
pub fn is_valid_repository(repo_path: &str) -> bool {
Repository::open(Path::new(repo_path)).is_ok()
}
pub fn find_last_tag_for_date(
repo_path: &str,
version: &Version,
) -> Result<Option<String>, git2::Error> {
let repo = Repository::open(Path::new(repo_path))?;
let prefix = version.get_prefix_without_patch();
let tag_names = repo.tag_names(None)?;
let mut tags_with_patches: Vec<(String, u16)> = Vec::new();
for tag_name in tag_names.iter().flatten() {
if tag_name.starts_with(&prefix) {
let suffix = &tag_name[prefix.len()..];
if let Ok(patch) = suffix.parse::<u16>() {
tags_with_patches.push((tag_name.to_string(), patch));
}
}
}
tags_with_patches.sort_by(|a, b| b.1.cmp(&a.1));
Ok(tags_with_patches.first().map(|(tag, _)| tag.clone()))
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::{TimeZone, Utc};
use tempfile::TempDir;
fn create_test_repo_with_tags(tags: &[&str]) -> Result<TempDir, git2::Error> {
let temp_dir = TempDir::new().unwrap();
let repo = Repository::init(temp_dir.path())?;
let mut config = repo.config()?;
config.set_str("user.name", "Test User")?;
config.set_str("user.email", "test@example.com")?;
let sig = repo.signature()?;
let tree_id = {
let mut index = repo.index()?;
index.write_tree()?
};
let tree = repo.find_tree(tree_id)?;
let commit = repo.commit(Some("HEAD"), &sig, &sig, "Initial commit", &tree, &[])?;
let commit_obj = repo.find_commit(commit)?;
for tag_name in tags {
repo.tag_lightweight(tag_name, commit_obj.as_object(), false)?;
}
Ok(temp_dir)
}
fn create_version_with_date(
year: i32,
month: u32,
day: u32,
format: Option<String>,
separator: Option<String>,
) -> Version {
let mut version = Version::new(format, separator, Some(0));
version.date = Utc.with_ymd_and_hms(year, month, day, 12, 0, 0).unwrap();
version
}
#[test]
fn test_is_valid_repository_with_valid_repo() {
let temp_dir = create_test_repo_with_tags(&[]).unwrap();
let is_valid = is_valid_repository(temp_dir.path().to_str().unwrap());
assert!(is_valid);
}
#[test]
fn test_is_valid_repository_with_tags() {
let temp_dir = create_test_repo_with_tags(&["2025.09.15-0", "2025.09.15-1"]).unwrap();
let is_valid = is_valid_repository(temp_dir.path().to_str().unwrap());
assert!(is_valid);
}
#[test]
fn test_is_valid_repository_with_non_existent_path() {
let is_valid = is_valid_repository("/non/existent/path/to/repo");
assert!(!is_valid);
}
#[test]
fn test_is_valid_repository_with_file_not_directory() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("not_a_repo.txt");
std::fs::write(&file_path, "test content").unwrap();
let is_valid = is_valid_repository(file_path.to_str().unwrap());
assert!(!is_valid);
}
#[test]
fn test_is_valid_repository_with_empty_directory() {
let temp_dir = TempDir::new().unwrap();
let is_valid = is_valid_repository(temp_dir.path().to_str().unwrap());
assert!(!is_valid);
}
#[test]
fn test_is_valid_repository_with_invalid_git_directory() {
let temp_dir = TempDir::new().unwrap();
let git_dir = temp_dir.path().join(".git");
std::fs::create_dir(&git_dir).unwrap();
let is_valid = is_valid_repository(temp_dir.path().to_str().unwrap());
assert!(!is_valid);
}
#[test]
fn test_is_valid_repository_with_bare_repository() {
let temp_dir = TempDir::new().unwrap();
Repository::init_bare(temp_dir.path()).unwrap();
let is_valid = is_valid_repository(temp_dir.path().to_str().unwrap());
assert!(is_valid);
}
#[test]
fn test_is_valid_repository_with_subdirectory() {
let temp_dir = create_test_repo_with_tags(&[]).unwrap();
let subdir = temp_dir.path().join("subdir");
std::fs::create_dir(&subdir).unwrap();
let is_valid = is_valid_repository(subdir.to_str().unwrap());
assert!(!is_valid);
}
#[test]
fn test_is_valid_repository_with_relative_path() {
let temp_dir = create_test_repo_with_tags(&[]).unwrap();
let current_dir = std::env::current_dir().unwrap();
std::env::set_current_dir(temp_dir.path()).unwrap();
let is_valid = is_valid_repository(".");
std::env::set_current_dir(current_dir).unwrap();
assert!(is_valid);
}
#[test]
fn test_is_valid_repository_with_symlink() {
#[cfg(unix)]
{
let temp_dir = create_test_repo_with_tags(&[]).unwrap();
let link_dir = TempDir::new().unwrap();
let link_path = link_dir.path().join("repo_link");
std::os::unix::fs::symlink(temp_dir.path(), &link_path).unwrap();
let is_valid = is_valid_repository(link_path.to_str().unwrap());
assert!(is_valid);
}
}
#[test]
fn test_find_last_tag_single_tag() {
let temp_dir = create_test_repo_with_tags(&["2025.09.15-0"]).unwrap();
let version = create_version_with_date(2025, 9, 15, None, None);
let result = find_last_tag_for_date(temp_dir.path().to_str().unwrap(), &version).unwrap();
assert_eq!(result, Some("2025.09.15-0".to_string()));
}
#[test]
fn test_find_last_tag_multiple_tags_returns_highest() {
let temp_dir = create_test_repo_with_tags(&[
"2025.09.15-0",
"2025.09.15-5",
"2025.09.15-2",
"2025.09.15-10",
])
.unwrap();
let version = create_version_with_date(2025, 9, 15, None, None);
let result = find_last_tag_for_date(temp_dir.path().to_str().unwrap(), &version).unwrap();
assert_eq!(result, Some("2025.09.15-10".to_string()));
}
#[test]
fn test_find_last_tag_no_matching_tags() {
let temp_dir = create_test_repo_with_tags(&["2025.09.14-0", "2025.09.13-5"]).unwrap();
let version = create_version_with_date(2025, 9, 15, None, None);
let result = find_last_tag_for_date(temp_dir.path().to_str().unwrap(), &version).unwrap();
assert_eq!(result, None);
}
#[test]
fn test_find_last_tag_mixed_dates() {
let temp_dir = create_test_repo_with_tags(&[
"2025.09.15-0",
"2025.09.15-3",
"2025.09.14-10",
"2025.09.15-1",
])
.unwrap();
let version = create_version_with_date(2025, 9, 15, None, None);
let result = find_last_tag_for_date(temp_dir.path().to_str().unwrap(), &version).unwrap();
assert_eq!(result, Some("2025.09.15-3".to_string()));
}
#[test]
fn test_find_last_tag_custom_format() {
let temp_dir =
create_test_repo_with_tags(&["20250915-0", "20250915-2", "20250915-1"]).unwrap();
let version = create_version_with_date(2025, 9, 15, Some("%Y%m%d".to_string()), None);
let result = find_last_tag_for_date(temp_dir.path().to_str().unwrap(), &version).unwrap();
assert_eq!(result, Some("20250915-2".to_string()));
}
#[test]
fn test_find_last_tag_custom_separator() {
let temp_dir =
create_test_repo_with_tags(&["2024.03.15_v0", "2024.03.15_v5", "2024.03.15_v2"])
.unwrap();
let version = create_version_with_date(2024, 3, 15, None, Some("_v".to_string()));
let result = find_last_tag_for_date(temp_dir.path().to_str().unwrap(), &version).unwrap();
assert_eq!(result, Some("2024.03.15_v5".to_string()));
}
#[test]
fn test_find_last_tag_invalid_patch_ignored() {
let temp_dir = create_test_repo_with_tags(&[
"2024.03.15-0",
"2024.03.15-abc",
"2024.03.15-3",
"2024.03.15-",
])
.unwrap();
let version = create_version_with_date(2024, 3, 15, None, None);
let result = find_last_tag_for_date(temp_dir.path().to_str().unwrap(), &version).unwrap();
assert_eq!(result, Some("2024.03.15-3".to_string()));
}
#[test]
fn test_find_last_tag_high_patch_numbers() {
let temp_dir =
create_test_repo_with_tags(&["2024.03.15-100", "2024.03.15-65534", "2024.03.15-1000"])
.unwrap();
let version = create_version_with_date(2024, 3, 15, None, None);
let result = find_last_tag_for_date(temp_dir.path().to_str().unwrap(), &version).unwrap();
assert_eq!(result, Some("2024.03.15-65534".to_string()));
}
#[test]
fn test_find_last_tag_empty_repository() {
let temp_dir = create_test_repo_with_tags(&[]).unwrap();
let version = create_version_with_date(2024, 3, 15, None, None);
let result = find_last_tag_for_date(temp_dir.path().to_str().unwrap(), &version).unwrap();
assert_eq!(result, None);
}
#[test]
fn test_find_last_tag_leap_year() {
let temp_dir =
create_test_repo_with_tags(&["2024.02.29-0", "2024.02.29-2", "2024.02.29-1"]).unwrap();
let version = create_version_with_date(2024, 2, 29, None, None);
let result = find_last_tag_for_date(temp_dir.path().to_str().unwrap(), &version).unwrap();
assert_eq!(result, Some("2024.02.29-2".to_string()));
}
#[test]
fn test_find_last_tag_different_separator_no_match() {
let temp_dir = create_test_repo_with_tags(&[
"2024.03.15_5", "2024.03.15.3", ])
.unwrap();
let version = create_version_with_date(2024, 3, 15, None, None);
let result = find_last_tag_for_date(temp_dir.path().to_str().unwrap(), &version).unwrap();
assert_eq!(result, None);
}
}