#[cfg(test)]
#[allow(clippy::module_inception)]
mod tests {
use super::super::*;
use std::process::Command;
use tempfile::TempDir;
mod mock {
use std::sync::{Arc, Mutex};
#[derive(Clone)]
#[allow(dead_code)]
pub struct MockProgressBar {
#[allow(dead_code)]
pub messages: Arc<Mutex<Vec<String>>>,
#[allow(dead_code)]
pub finished: Arc<Mutex<bool>>,
#[allow(dead_code)]
pub finished_message: Arc<Mutex<Option<String>>>,
}
impl MockProgressBar {
pub fn new() -> Self {
Self {
messages: Arc::new(Mutex::new(Vec::new())),
finished: Arc::new(Mutex::new(false)),
finished_message: Arc::new(Mutex::new(None)),
}
}
#[allow(dead_code)]
pub fn set_message(&self, msg: impl Into<String>) {
self.messages.lock().unwrap().push(msg.into());
}
#[allow(dead_code)]
pub fn finish_with_message(&self, msg: impl Into<String>) {
*self.finished.lock().unwrap() = true;
*self.finished_message.lock().unwrap() = Some(msg.into());
}
#[allow(dead_code)]
pub fn get_messages(&self) -> Vec<String> {
self.messages.lock().unwrap().clone()
}
#[allow(dead_code)]
pub fn is_finished(&self) -> bool {
*self.finished.lock().unwrap()
}
#[allow(dead_code)]
pub fn get_finished_message(&self) -> Option<String> {
self.finished_message.lock().unwrap().clone()
}
}
#[allow(dead_code)]
pub struct ProgressBarWrapper {
inner: MockProgressBar,
}
impl ProgressBarWrapper {
#[allow(dead_code)]
pub fn from_mock(mock: MockProgressBar) -> Self {
Self {
inner: mock,
}
}
#[allow(dead_code)]
pub fn set_message(&self, msg: impl Into<String>) {
self.inner.set_message(msg);
}
#[allow(dead_code)]
pub fn finish_with_message(&self, msg: impl Into<String>) {
self.inner.finish_with_message(msg);
}
}
}
use mock::MockProgressBar;
#[test]
fn test_is_git_installed() {
assert!(is_git_installed());
}
#[test]
fn test_parse_git_url() {
let cases = vec![
("https://github.com/user/repo.git", ("user", "repo")),
("git@github.com:user/repo.git", ("user", "repo")),
("https://gitlab.com/user/repo", ("user", "repo")),
("https://bitbucket.org/user/repo.git", ("user", "repo")),
];
for (url, expected) in cases {
let result = parse_git_url(url).unwrap();
assert_eq!(result.0, expected.0);
assert_eq!(result.1, expected.1);
}
}
#[test]
fn test_parse_git_url_invalid() {
let result = parse_git_url("not-a-url");
assert!(result.is_err());
}
#[test]
fn test_parse_git_url_ssh_format() {
let result = parse_git_url("ssh://git@github.com/user/repo.git");
assert!(result.is_ok());
let (owner, name) = result.unwrap();
assert_eq!(owner, "user");
assert_eq!(name, "repo");
}
#[test]
fn test_parse_git_url_more_formats() {
let test_cases = vec![
("https://github.com/rust-lang/cargo.git", ("rust-lang", "cargo")),
("git@gitlab.com:group/project.git", ("group", "project")),
("ssh://git@bitbucket.org/team/repo", ("team", "repo")),
("https://github.com/user-name/repo-name", ("user-name", "repo-name")),
];
for (url, (expected_owner, expected_repo)) in test_cases {
let result = parse_git_url(url);
assert!(result.is_ok(), "Failed to parse URL: {url}");
let (owner, repo) = result.unwrap();
assert_eq!(owner, expected_owner, "Owner mismatch for URL: {url}");
assert_eq!(repo, expected_repo, "Repo mismatch for URL: {url}");
}
}
#[test]
fn test_parse_git_url_edge_cases() {
let invalid_urls = vec![
"not-a-url",
"https://example.com/something",
"",
];
for url in invalid_urls {
let result = parse_git_url(url);
assert!(result.is_err(), "Expected error for invalid URL: {url}");
}
let valid_local_paths = vec!["/local/path/to/repo", "./relative/path", "../parent/path"];
for path in valid_local_paths {
let result = parse_git_url(path);
assert!(result.is_ok(), "Expected local path to be valid: {path}");
}
}
#[test]
fn test_parse_git_url_file_urls() {
let test_cases = vec![
("file:///home/user/repos/myrepo", ("local", "myrepo")),
("file:///home/user/repos/myrepo.git", ("local", "myrepo")),
("file:///tmp/test", ("local", "test")),
("file:///var/folders/sources/official", ("local", "official")),
];
for (url, (expected_owner, expected_repo)) in test_cases {
let result = parse_git_url(url).unwrap();
assert_eq!(result.0, expected_owner, "Owner mismatch for {url}");
assert_eq!(result.1, expected_repo, "Repo mismatch for {url}");
}
}
#[test]
fn test_parse_git_url_special_cases() {
let url_with_port = "ssh://git@github.com:22/user/repo.git";
let result = parse_git_url(url_with_port);
assert!(result.is_ok());
let gitlab_subgroup = "https://gitlab.com/group/subgroup/project.git";
let result = parse_git_url(gitlab_subgroup);
assert!(result.is_ok());
let (owner, name) = result.unwrap();
assert_eq!(owner, "subgroup");
assert_eq!(name, "project");
let no_git_ext = "https://github.com/user/repo";
let result = parse_git_url(no_git_ext);
assert!(result.is_ok());
let (owner, name) = result.unwrap();
assert_eq!(owner, "user");
assert_eq!(name, "repo");
}
#[test]
fn test_is_git_repo() {
let temp_dir = TempDir::new().unwrap();
let repo = GitRepo::new(temp_dir.path());
assert!(!repo.is_git_repo());
Command::new("git").args(["init"]).current_dir(temp_dir.path()).output().unwrap();
assert!(repo.is_git_repo());
}
#[test]
fn test_git_repo_path() {
let temp_dir = TempDir::new().unwrap();
let repo = GitRepo::new(temp_dir.path());
assert_eq!(repo.path(), temp_dir.path());
}
#[tokio::test]
async fn test_clone_local_repo() {
let temp_dir = TempDir::new().unwrap();
let source_path = temp_dir.path().join("source");
let target_path = temp_dir.path().join("target");
std::fs::create_dir(&source_path).unwrap();
Command::new("git").args(["init", "--bare"]).current_dir(&source_path).output().unwrap();
let result = GitRepo::clone(source_path.to_str().unwrap(), &target_path).await;
assert!(result.is_ok());
let cloned_repo = result.unwrap();
assert!(cloned_repo.is_git_repo());
}
#[tokio::test]
async fn test_clone_with_progress() {
let temp_dir = TempDir::new().unwrap();
let bare_path = temp_dir.path().join("bare");
let clone_path = temp_dir.path().join("clone");
std::fs::create_dir(&bare_path).unwrap();
let output =
Command::new("git").args(["init", "--bare"]).current_dir(&bare_path).output().unwrap();
assert!(output.status.success(), "Failed to init bare repo: {output:?}");
let mock = MockProgressBar::new();
let _mock_clone = mock.clone();
let pb = crate::utils::progress::ProgressBar::new_spinner();
pb.set_message("Test clone");
let result = GitRepo::clone(bare_path.to_str().unwrap(), &clone_path).await;
assert!(result.is_ok());
let repo = result.unwrap();
assert!(repo.is_git_repo());
assert!(clone_path.exists());
pb.finish_with_message("Clone complete");
}
#[tokio::test]
async fn test_clone_invalid_url() {
let target_dir = TempDir::new().unwrap();
let target_path = target_dir.path().join("cloned");
let result = GitRepo::clone("/non/existent/path", &target_path).await;
assert!(result.is_err());
assert!(!target_path.exists());
}
#[tokio::test]
async fn test_clone_invalid_url_detailed() {
let target_dir = TempDir::new().unwrap();
let target_path = target_dir.path().join("cloned");
let invalid_urls =
vec!["/non/existent/path", "http://invalid-git-url.test", "not-a-url", ""];
for url in invalid_urls {
let result = GitRepo::clone(url, &target_path).await;
assert!(result.is_err(), "Expected error for URL: {url}");
if let Err(error) = result {
assert!(
error.to_string().contains("Failed to clone")
|| error.to_string().contains("Failed to execute")
);
}
}
}
#[tokio::test]
async fn test_clone_stderr_error_message() {
let target_dir = TempDir::new().unwrap();
let target_path = target_dir.path().join("cloned");
let result =
GitRepo::clone("https://invalid.host.that.does.not.exist.9999/repo.git", &target_path)
.await;
assert!(result.is_err());
if let Err(error) = result {
let error_msg = error.to_string();
assert!(error_msg.contains("Failed to clone"));
}
}
#[tokio::test]
async fn test_fetch_simple() {
let temp_dir = TempDir::new().unwrap();
let bare_path = temp_dir.path().join("bare");
let clone_path = temp_dir.path().join("clone");
std::fs::create_dir(&bare_path).unwrap();
let output =
Command::new("git").args(["init", "--bare"]).current_dir(&bare_path).output().unwrap();
assert!(output.status.success(), "Failed to init bare repo: {output:?}");
let repo = GitRepo::clone(bare_path.to_str().unwrap(), &clone_path).await.unwrap();
let fetch_result = repo.fetch(None).await;
assert!(fetch_result.is_ok());
let pb = crate::utils::progress::ProgressBar::new_spinner();
pb.set_message("Test fetch");
let fetch_result = repo.fetch(None).await;
assert!(fetch_result.is_ok());
pb.finish_with_message("Fetch complete");
}
#[tokio::test]
async fn test_fetch_with_progress() {
let temp_dir = TempDir::new().unwrap();
let bare_path = temp_dir.path().join("bare");
let repo_path = temp_dir.path().join("repo");
std::fs::create_dir(&bare_path).unwrap();
Command::new("git").args(["init", "--bare"]).current_dir(&bare_path).output().unwrap();
let repo = GitRepo::clone(bare_path.to_str().unwrap(), &repo_path).await.unwrap();
let pb = crate::utils::progress::ProgressBar::new_spinner();
pb.set_message("Test fetch");
let result = repo.fetch(None).await;
assert!(result.is_ok());
pb.finish_with_message("Fetch complete");
}
#[tokio::test]
async fn test_fetch_with_no_network() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
Command::new("git").args(["init"]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["remote", "add", "origin", "https://non-existent-host-9999.test/repo.git"])
.current_dir(repo_path)
.output()
.unwrap();
let repo = GitRepo::new(repo_path);
let result = repo.fetch(None).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Git operation failed: fetch"));
}
#[tokio::test]
async fn test_checkout() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
Command::new("git").args(["init"]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(repo_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(repo_path)
.output()
.unwrap();
std::fs::write(repo_path.join("README.md"), "Test").unwrap();
Command::new("git").args(["add", "."]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(repo_path)
.output()
.unwrap();
Command::new("git").args(["tag", "v1.0.0"]).current_dir(repo_path).output().unwrap();
std::fs::write(repo_path.join("file2.txt"), "Test2").unwrap();
Command::new("git").args(["add", "."]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Second commit"])
.current_dir(repo_path)
.output()
.unwrap();
let repo = GitRepo::new(repo_path);
let result = repo.checkout("v1.0.0").await;
assert!(result.is_ok());
assert!(!repo_path.join("file2.txt").exists());
}
#[tokio::test]
async fn test_checkout_branch() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
Command::new("git").args(["init"]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@agpm.test"])
.current_dir(repo_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "AGPM Test"])
.current_dir(repo_path)
.output()
.unwrap();
std::fs::write(repo_path.join("main.txt"), "Main branch").unwrap();
Command::new("git").args(["add", "."]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Main commit"])
.current_dir(repo_path)
.output()
.unwrap();
Command::new("git")
.args(["checkout", "-b", "feature"])
.current_dir(repo_path)
.output()
.unwrap();
std::fs::write(repo_path.join("feature.txt"), "Feature branch").unwrap();
Command::new("git").args(["add", "."]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Feature commit"])
.current_dir(repo_path)
.output()
.unwrap();
let repo = GitRepo::new(repo_path);
assert_eq!(repo.get_current_branch().await.unwrap(), "feature");
assert!(repo_path.join("feature.txt").exists());
let main_branch = if Command::new("git")
.args(["rev-parse", "--verify", "main"])
.current_dir(repo_path)
.output()
.unwrap()
.status
.success()
{
"main"
} else {
"master"
};
repo.checkout(main_branch).await.unwrap();
assert!(!repo_path.join("feature.txt").exists());
assert!(repo_path.join("main.txt").exists());
repo.checkout("feature").await.unwrap();
assert!(repo_path.join("feature.txt").exists());
}
#[tokio::test]
async fn test_checkout_commit_hash() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
Command::new("git").args(["init"]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@agpm.test"])
.current_dir(repo_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(repo_path)
.output()
.unwrap();
std::fs::write(repo_path.join("file1.txt"), "content1").unwrap();
Command::new("git").args(["add", "."]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "First commit"])
.current_dir(repo_path)
.output()
.unwrap();
let output = Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(repo_path)
.output()
.unwrap();
let first_commit = String::from_utf8_lossy(&output.stdout).trim().to_string();
std::fs::write(repo_path.join("file2.txt"), "content2").unwrap();
Command::new("git").args(["add", "."]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Second commit"])
.current_dir(repo_path)
.output()
.unwrap();
let repo = GitRepo::new(repo_path);
repo.checkout(&first_commit).await.unwrap();
assert!(repo_path.join("file1.txt").exists());
assert!(!repo_path.join("file2.txt").exists());
}
#[tokio::test]
async fn test_checkout_invalid_ref() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
Command::new("git").args(["init"]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@agpm.test"])
.current_dir(repo_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "AGPM Test"])
.current_dir(repo_path)
.output()
.unwrap();
std::fs::write(repo_path.join("README.md"), "# Test").unwrap();
Command::new("git").args(["add", "."]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(repo_path)
.output()
.unwrap();
let repo = GitRepo::new(repo_path);
let result = repo.checkout("non-existent-branch").await;
assert!(result.is_err());
let error_message = format!("{:?}", result.unwrap_err());
assert!(error_message.contains("Failed to checkout"));
}
#[tokio::test]
async fn test_list_tags() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
Command::new("git").args(["init"]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@agpm.test"])
.current_dir(repo_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "AGPM Test"])
.current_dir(repo_path)
.output()
.unwrap();
std::fs::write(repo_path.join("README.md"), "# Test").unwrap();
Command::new("git").args(["add", "."]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(repo_path)
.output()
.unwrap();
let tags_to_add = vec!["v1.0.0", "v1.1.0", "v2.0.0-beta", "release-1.2.3"];
for tag in &tags_to_add {
Command::new("git").args(["tag", tag]).current_dir(repo_path).output().unwrap();
}
let repo = GitRepo::new(repo_path);
let mut tags = repo.list_tags().await.unwrap();
tags.sort();
assert_eq!(tags.len(), 4);
assert!(tags.contains(&"v1.0.0".to_string()));
assert!(tags.contains(&"v2.0.0-beta".to_string()));
}
#[tokio::test]
async fn test_list_tags_sorted() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
Command::new("git").args(["init"]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@agpm.test"])
.current_dir(repo_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(repo_path)
.output()
.unwrap();
std::fs::write(repo_path.join("README.md"), "# Test").unwrap();
Command::new("git").args(["add", "."]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Initial"])
.current_dir(repo_path)
.output()
.unwrap();
let tags = vec!["v2.0.0", "v1.0.0", "v1.2.0", "v1.1.0", "v3.0.0-alpha"];
for tag in &tags {
Command::new("git").args(["tag", tag]).current_dir(repo_path).output().unwrap();
}
let repo = GitRepo::new(repo_path);
let listed_tags = repo.list_tags().await.unwrap();
assert_eq!(listed_tags.len(), 5);
for tag in tags {
assert!(listed_tags.contains(&tag.to_string()));
}
}
#[tokio::test]
async fn test_get_remote_url() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
Command::new("git").args(["init"]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["remote", "add", "origin", "https://github.com/test/repo.git"])
.current_dir(repo_path)
.output()
.unwrap();
let repo = GitRepo::new(repo_path);
let url = repo.get_remote_url().await.unwrap();
assert!(
url == "https://github.com/test/repo.git"
|| url == "ssh://git@github.com/test/repo.git"
|| url == "git@github.com:test/repo.git"
);
}
#[tokio::test]
async fn test_get_remote_url_no_remote() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
Command::new("git").args(["init"]).current_dir(repo_path).output().unwrap();
let repo = GitRepo::new(repo_path);
let result = repo.get_remote_url().await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_get_current_branch() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
Command::new("git").args(["init"]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(repo_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(repo_path)
.output()
.unwrap();
std::fs::write(repo_path.join("README.md"), "Test").unwrap();
Command::new("git").args(["add", "."]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(repo_path)
.output()
.unwrap();
let repo = GitRepo::new(repo_path);
let branch = repo.get_current_branch().await.unwrap();
assert!(branch == "main" || branch == "master");
}
#[tokio::test]
async fn test_error_handling_non_git_repo() {
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path().to_path_buf();
let fake_repo = GitRepo {
path,
};
let result = fake_repo.fetch(None).await;
assert!(result.is_err());
let result = fake_repo.get_current_branch().await;
assert!(result.is_err());
let result = fake_repo.list_tags().await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_concurrent_operations() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path().to_path_buf();
Command::new("git").args(["init"]).current_dir(&repo_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@agpm.test"])
.current_dir(&repo_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(&repo_path)
.output()
.unwrap();
std::fs::write(repo_path.join("initial.txt"), "initial").unwrap();
Command::new("git").args(["add", "."]).current_dir(&repo_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Initial"])
.current_dir(&repo_path)
.output()
.unwrap();
let path1 = repo_path.clone();
let path2 = repo_path.clone();
let handle1 = tokio::spawn(async move {
let repo = GitRepo::new(&path1);
repo.list_tags().await
});
let handle2 = tokio::spawn(async move {
let repo = GitRepo::new(&path2);
repo.get_current_branch().await
});
let result1 = handle1.await.unwrap();
let result2 = handle2.await.unwrap();
assert!(result1.is_ok());
assert!(result2.is_ok());
}
#[tokio::test]
async fn test_trait_implementation() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
Command::new("git").args(["init"]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@agpm.test"])
.current_dir(repo_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "AGPM Test"])
.current_dir(repo_path)
.output()
.unwrap();
std::fs::write(repo_path.join("README.md"), "# Test").unwrap();
Command::new("git").args(["add", "."]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(repo_path)
.output()
.unwrap();
let repo = GitRepo::new(repo_path);
assert!(repo.is_git_repo());
assert!(repo.path().exists());
let tags = repo.list_tags().await.unwrap();
assert_eq!(tags.len(), 0);
}
#[tokio::test]
async fn test_clone_permission_denied() {
let temp_dir = TempDir::new().unwrap();
let source_path = temp_dir.path().join("source");
let target_path = temp_dir.path().join("target");
std::fs::create_dir(&source_path).unwrap();
Command::new("git").args(["init", "--bare"]).current_dir(&source_path).output().unwrap();
std::fs::create_dir(&target_path).unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(&target_path).unwrap().permissions();
perms.set_mode(0o444); std::fs::set_permissions(&target_path, perms).unwrap();
}
let source_url = format!("file://{}", source_path.display().to_string().replace('\\', "/"));
let result = GitRepo::clone(&source_url, &target_path).await;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(&target_path).unwrap().permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&target_path, perms).unwrap();
}
#[cfg(unix)]
assert!(result.is_err());
#[cfg(windows)]
let _ = result; }
#[tokio::test]
async fn test_clone_empty_url() {
let temp_dir = TempDir::new().unwrap();
let target_path = temp_dir.path().join("target");
let result = GitRepo::clone("", &target_path).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Failed to clone"));
}
#[tokio::test]
async fn test_fetch_local_repository() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path().join("repo");
let origin_path = temp_dir.path().join("origin");
std::fs::create_dir_all(&origin_path).unwrap();
Command::new("git").args(["init", "--bare"]).current_dir(&origin_path).output().unwrap();
std::fs::create_dir_all(&repo_path).unwrap();
Command::new("git").args(["init"]).current_dir(&repo_path).output().unwrap();
let origin_url = format!("file://{}", origin_path.display());
Command::new("git")
.args(["remote", "add", "origin", &origin_url])
.current_dir(&repo_path)
.output()
.unwrap();
let repo = GitRepo::new(&repo_path);
let result = repo.fetch(None).await;
assert!(result.is_ok(), "Fetch failed: {:?}", result.err());
}
#[tokio::test]
async fn test_fetch_git_protocol() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
Command::new("git").args(["init"]).current_dir(repo_path).output().unwrap();
let bare_repo = temp_dir.path().join("bare");
std::fs::create_dir(&bare_repo).unwrap();
Command::new("git").args(["init", "--bare"]).current_dir(&bare_repo).output().unwrap();
Command::new("git")
.args(["remote", "add", "origin", &format!("file://{}", bare_repo.display())])
.current_dir(repo_path)
.output()
.unwrap();
let repo = GitRepo::new(repo_path);
let result = repo.fetch(None).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_fetch_with_auth_url() {
let temp_dir = TempDir::new().unwrap();
let bare_path = temp_dir.path().join("bare");
let repo_path = temp_dir.path().join("repo");
std::fs::create_dir(&bare_path).unwrap();
Command::new("git").args(["init", "--bare"]).current_dir(&bare_path).output().unwrap();
let repo = GitRepo::clone(bare_path.to_str().unwrap(), &repo_path).await.unwrap();
let auth_url = format!("file://{}", bare_path.display());
let result = repo.fetch(Some(&auth_url)).await;
assert!(result.is_ok(), "Fetch failed: {:?}", result.err());
}
#[tokio::test]
async fn test_list_tags_non_git_directory() {
let temp_dir = TempDir::new().unwrap();
let non_git_path = temp_dir.path().join("not_git");
std::fs::create_dir(&non_git_path).unwrap();
let repo = GitRepo::new(&non_git_path);
let result = repo.list_tags().await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Not a git repository"));
}
#[tokio::test]
async fn test_list_tags_non_existent_directory() {
let temp_dir = TempDir::new().unwrap();
let non_existent = temp_dir.path().join("does_not_exist");
let repo = GitRepo::new(&non_existent);
let result = repo.list_tags().await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Repository path does not exist"));
}
#[tokio::test]
async fn test_verify_url_file_protocol() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path().join("repo");
std::fs::create_dir(&repo_path).unwrap();
let file_url = format!("file://{}", repo_path.display());
let result = GitRepo::verify_url(&file_url).await;
assert!(result.is_ok());
let bad_file_url = "file:///non/existent/path";
let result = GitRepo::verify_url(bad_file_url).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Local path does not exist"));
}
#[tokio::test]
async fn test_verify_url_remote() {
let result = GitRepo::verify_url("https://invalid-host-9999.test/repo.git").await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Failed to verify remote repository"));
}
#[test]
fn test_strip_auth_from_url() {
let url = "https://user:pass@github.com/owner/repo.git";
let result = strip_auth_from_url(url).unwrap();
assert_eq!(result, "https://github.com/owner/repo.git");
let url = "https://oauth2:ghp_xxxx@github.com/owner/repo.git";
let result = strip_auth_from_url(url).unwrap();
assert_eq!(result, "https://github.com/owner/repo.git");
let url = "http://user:pass@example.com/repo.git";
let result = strip_auth_from_url(url).unwrap();
assert_eq!(result, "http://example.com/repo.git");
let url = "https://github.com/owner/repo.git";
let result = strip_auth_from_url(url).unwrap();
assert_eq!(result, "https://github.com/owner/repo.git");
let url = "git@github.com:owner/repo.git";
let result = strip_auth_from_url(url).unwrap();
assert_eq!(result, "git@github.com:owner/repo.git");
let url = "https://example.com/user@domain/repo.git";
let result = strip_auth_from_url(url).unwrap();
assert_eq!(result, "https://example.com/user@domain/repo.git");
}
#[test]
fn test_parse_git_url_local_paths() {
let result = parse_git_url("/absolute/path/to/repo").unwrap();
assert_eq!(result.0, "local");
assert_eq!(result.1, "repo");
let result = parse_git_url("./relative/path/repo.git").unwrap();
assert_eq!(result.0, "local");
assert_eq!(result.1, "repo");
let result = parse_git_url("../parent/repo").unwrap();
assert_eq!(result.0, "local");
assert_eq!(result.1, "repo");
let result = parse_git_url("repo.git");
assert!(result.is_err());
}
#[test]
fn test_ensure_git_available() {
let result = ensure_git_available();
assert!(result.is_ok());
}
#[test]
fn test_ensure_valid_git_repo() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
let result = ensure_valid_git_repo(repo_path);
assert!(result.is_err());
let err_str = result.unwrap_err().to_string();
assert!(err_str.contains("git repository") || err_str.contains("Git repository"));
Command::new("git").args(["init"]).current_dir(repo_path).output().unwrap();
let result = ensure_valid_git_repo(repo_path);
assert!(result.is_ok());
}
#[test]
fn test_is_valid_git_repo() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
assert!(!is_valid_git_repo(repo_path));
Command::new("git").args(["init"]).current_dir(repo_path).output().unwrap();
assert!(is_valid_git_repo(repo_path));
}
#[tokio::test]
async fn test_checkout_reset_error_handling() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
Command::new("git").args(["init"]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(repo_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(repo_path)
.output()
.unwrap();
std::fs::write(repo_path.join("file.txt"), "content").unwrap();
Command::new("git").args(["add", "."]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Initial"])
.current_dir(repo_path)
.output()
.unwrap();
Command::new("git").args(["tag", "v1.0.0"]).current_dir(repo_path).output().unwrap();
let repo = GitRepo::new(repo_path);
let result = repo.checkout("v1.0.0").await;
assert!(result.is_ok());
let result = repo.checkout("non-existent").await;
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("Failed to checkout"));
}
#[tokio::test]
async fn test_get_remote_url_stderr() {
let temp_dir = TempDir::new().unwrap();
let non_git_path = temp_dir.path().join("not_git");
std::fs::create_dir(&non_git_path).unwrap();
let repo = GitRepo::new(&non_git_path);
let result = repo.get_remote_url().await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Git operation failed"));
}
#[tokio::test]
async fn test_concurrent_git_operations_same_repo() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
Command::new("git").args(["init"]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(repo_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(repo_path)
.output()
.unwrap();
for i in 0..3 {
let file_name = format!("file{i}.txt");
std::fs::write(repo_path.join(&file_name), format!("content{i}")).unwrap();
Command::new("git").args(["add", "."]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", &format!("Commit {i}")])
.current_dir(repo_path)
.output()
.unwrap();
Command::new("git")
.args(["tag", &format!("v{i}.0.0")])
.current_dir(repo_path)
.output()
.unwrap();
}
let repo1 = GitRepo::new(repo_path);
let repo2 = GitRepo::new(repo_path);
let repo3 = GitRepo::new(repo_path);
let handle1 = tokio::spawn(async move { repo1.list_tags().await });
let handle2 = tokio::spawn(async move { repo2.get_current_branch().await });
let handle3 = tokio::spawn(async move { repo3.checkout("v1.0.0").await });
let results = tokio::join!(handle1, handle2, handle3);
assert!(results.0.unwrap().is_ok());
assert!(results.1.unwrap().is_ok());
assert!(results.2.unwrap().is_ok());
}
#[tokio::test]
async fn test_clone_bare() {
let temp_dir = TempDir::new().unwrap();
let source_path = temp_dir.path().join("source");
let bare_path = temp_dir.path().join("bare.git");
std::fs::create_dir(&source_path).unwrap();
Command::new("git").args(["init"]).current_dir(&source_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(&source_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(&source_path)
.output()
.unwrap();
std::fs::write(source_path.join("README.md"), "# Test").unwrap();
Command::new("git").args(["add", "."]).current_dir(&source_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(&source_path)
.output()
.unwrap();
let file_url = format!("file://{}", source_path.display());
let result = GitRepo::clone_bare(&file_url, &bare_path).await;
assert!(result.is_ok(), "Failed to clone bare: {:?}", result.err());
let bare_repo = result.unwrap();
assert!(bare_repo.path().exists());
let has_objects = bare_repo.path().join("objects").exists();
let has_refs = bare_repo.path().join("refs").exists();
let has_head = bare_repo.path().join("HEAD").exists();
assert!(has_objects, "Bare repo missing objects directory");
assert!(has_refs, "Bare repo missing refs directory");
assert!(has_head, "Bare repo missing HEAD file");
let is_bare = bare_repo.is_bare().await.unwrap();
assert!(is_bare);
}
#[tokio::test]
async fn test_clone_bare_with_context() {
let temp_dir = TempDir::new().unwrap();
let source_path = temp_dir.path().join("source");
let bare_path = temp_dir.path().join("bare.git");
std::fs::create_dir(&source_path).unwrap();
Command::new("git").args(["init"]).current_dir(&source_path).output().unwrap();
let result = GitRepo::clone_bare_with_context(
source_path.to_str().unwrap(),
&bare_path,
Some("test-dependency"),
)
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_create_worktree() {
let temp_dir = TempDir::new().unwrap();
let source_path = temp_dir.path().join("source");
let bare_path = temp_dir.path().join("bare.git");
let worktree_path = temp_dir.path().join("worktree");
std::fs::create_dir(&source_path).unwrap();
Command::new("git").args(["init"]).current_dir(&source_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(&source_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(&source_path)
.output()
.unwrap();
std::fs::write(source_path.join("file.txt"), "content").unwrap();
Command::new("git").args(["add", "."]).current_dir(&source_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(&source_path)
.output()
.unwrap();
Command::new("git").args(["tag", "v1.0.0"]).current_dir(&source_path).output().unwrap();
let bare_repo =
GitRepo::clone_bare(source_path.to_str().unwrap(), &bare_path).await.unwrap();
let worktree = bare_repo.create_worktree(&worktree_path, Some("v1.0.0")).await.unwrap();
assert!(worktree.is_git_repo());
assert!(worktree_path.join("file.txt").exists());
}
#[tokio::test]
async fn test_create_worktree_with_context() {
let temp_dir = TempDir::new().unwrap();
let source_path = temp_dir.path().join("source");
let bare_path = temp_dir.path().join("bare.git");
let worktree_path = temp_dir.path().join("worktree");
std::fs::create_dir(&source_path).unwrap();
Command::new("git").args(["init", "--bare"]).current_dir(&source_path).output().unwrap();
let bare_repo =
GitRepo::clone_bare(source_path.to_str().unwrap(), &bare_path).await.unwrap();
let result = bare_repo
.create_worktree_with_context(&worktree_path, None, Some("test-dependency"))
.await;
let _ = result; }
#[tokio::test]
async fn test_remove_worktree() {
let temp_dir = TempDir::new().unwrap();
let source_path = temp_dir.path().join("source");
let bare_path = temp_dir.path().join("bare.git");
let worktree_path = temp_dir.path().join("worktree");
std::fs::create_dir(&source_path).unwrap();
Command::new("git").args(["init"]).current_dir(&source_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(&source_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(&source_path)
.output()
.unwrap();
std::fs::write(source_path.join("file.txt"), "content").unwrap();
Command::new("git").args(["add", "."]).current_dir(&source_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(&source_path)
.output()
.unwrap();
let bare_repo =
GitRepo::clone_bare(source_path.to_str().unwrap(), &bare_path).await.unwrap();
let _worktree = bare_repo.create_worktree(&worktree_path, None).await.unwrap();
assert!(worktree_path.exists());
let result = bare_repo.remove_worktree(&worktree_path).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_list_worktrees() {
let temp_dir = TempDir::new().unwrap();
let source_path = temp_dir.path().join("source");
let bare_path = temp_dir.path().join("bare.git");
std::fs::create_dir(&source_path).unwrap();
Command::new("git").args(["init"]).current_dir(&source_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(&source_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(&source_path)
.output()
.unwrap();
std::fs::write(source_path.join("file.txt"), "content").unwrap();
Command::new("git").args(["add", "."]).current_dir(&source_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(&source_path)
.output()
.unwrap();
let bare_repo =
GitRepo::clone_bare(source_path.to_str().unwrap(), &bare_path).await.unwrap();
let worktrees = bare_repo.list_worktrees().await.unwrap();
assert!(worktrees.len() <= 1); }
#[tokio::test]
async fn test_prune_worktrees() {
let temp_dir = TempDir::new().unwrap();
let source_path = temp_dir.path().join("source");
let bare_path = temp_dir.path().join("bare.git");
std::fs::create_dir(&source_path).unwrap();
Command::new("git").args(["init"]).current_dir(&source_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(&source_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(&source_path)
.output()
.unwrap();
std::fs::write(source_path.join("file.txt"), "content").unwrap();
Command::new("git").args(["add", "."]).current_dir(&source_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(&source_path)
.output()
.unwrap();
let bare_repo =
GitRepo::clone_bare(source_path.to_str().unwrap(), &bare_path).await.unwrap();
let result = bare_repo.prune_worktrees().await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_is_bare() {
let temp_dir = TempDir::new().unwrap();
let normal_repo_path = temp_dir.path().join("normal");
let bare_repo_path = temp_dir.path().join("bare.git");
std::fs::create_dir(&normal_repo_path).unwrap();
Command::new("git").args(["init"]).current_dir(&normal_repo_path).output().unwrap();
std::fs::create_dir(&bare_repo_path).unwrap();
Command::new("git").args(["init", "--bare"]).current_dir(&bare_repo_path).output().unwrap();
let normal_repo = GitRepo::new(&normal_repo_path);
let bare_repo = GitRepo::new(&bare_repo_path);
let is_bare = normal_repo.is_bare().await.unwrap();
assert!(!is_bare);
let is_bare = bare_repo.is_bare().await.unwrap();
assert!(is_bare);
}
#[tokio::test]
async fn test_get_current_commit() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
Command::new("git").args(["init"]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(repo_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(repo_path)
.output()
.unwrap();
std::fs::write(repo_path.join("file.txt"), "content").unwrap();
Command::new("git").args(["add", "."]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(repo_path)
.output()
.unwrap();
let repo = GitRepo::new(repo_path);
let commit = repo.get_current_commit().await.unwrap();
assert_eq!(commit.len(), 40);
assert!(commit.chars().all(|c| c.is_ascii_hexdigit()));
}
#[tokio::test]
async fn test_get_current_commit_error() {
let temp_dir = TempDir::new().unwrap();
let non_git_path = temp_dir.path().join("not_git");
std::fs::create_dir(&non_git_path).unwrap();
let repo = GitRepo::new(&non_git_path);
let result = repo.get_current_commit().await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Failed to get current commit"));
}
#[tokio::test]
async fn test_checkout_remote_branch_fallback() {
let temp_dir = TempDir::new().unwrap();
let origin_path = temp_dir.path().join("origin");
let repo_path = temp_dir.path().join("repo");
std::fs::create_dir(&origin_path).unwrap();
Command::new("git").args(["init"]).current_dir(&origin_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(&origin_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(&origin_path)
.output()
.unwrap();
std::fs::write(origin_path.join("file.txt"), "content").unwrap();
Command::new("git").args(["add", "."]).current_dir(&origin_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(&origin_path)
.output()
.unwrap();
Command::new("git")
.args(["checkout", "-b", "feature"])
.current_dir(&origin_path)
.output()
.unwrap();
std::fs::write(origin_path.join("feature.txt"), "feature").unwrap();
Command::new("git").args(["add", "."]).current_dir(&origin_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Feature commit"])
.current_dir(&origin_path)
.output()
.unwrap();
let repo = GitRepo::clone(origin_path.to_str().unwrap(), &repo_path).await.unwrap();
repo.fetch(None).await.unwrap();
let result = repo.checkout("feature").await;
assert!(result.is_ok(), "Failed to checkout remote branch: {:?}", result.err());
}
#[tokio::test]
async fn test_checkout_error_handling() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
Command::new("git").args(["init"]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(repo_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(repo_path)
.output()
.unwrap();
std::fs::write(repo_path.join("file.txt"), "content").unwrap();
Command::new("git").args(["add", "."]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(repo_path)
.output()
.unwrap();
let repo = GitRepo::new(repo_path);
let result = repo.checkout("definitely-does-not-exist").await;
assert!(result.is_err());
let error_str = result.unwrap_err().to_string();
assert!(
error_str.contains("Failed to checkout") || error_str.contains("GitCheckoutFailed")
);
}
#[tokio::test]
async fn test_resolve_to_sha() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
Command::new("git").args(["init"]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(repo_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(repo_path)
.output()
.unwrap();
std::fs::write(repo_path.join("file.txt"), "content").unwrap();
Command::new("git").args(["add", "."]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(repo_path)
.output()
.unwrap();
let commit_sha = Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(repo_path)
.output()
.unwrap();
let expected_sha = String::from_utf8(commit_sha.stdout).unwrap().trim().to_string();
Command::new("git").args(["tag", "v1.0.0"]).current_dir(repo_path).output().unwrap();
let repo = GitRepo::new(repo_path);
let sha = repo.resolve_to_sha(None).await.unwrap();
assert_eq!(sha, expected_sha);
let sha = repo.resolve_to_sha(Some("HEAD")).await.unwrap();
assert_eq!(sha, expected_sha);
let sha = repo.resolve_to_sha(Some("v1.0.0")).await.unwrap();
assert_eq!(sha, expected_sha);
let full_sha = "a".repeat(40);
let sha = repo.resolve_to_sha(Some(&full_sha)).await.unwrap();
assert_eq!(sha, full_sha);
let main_branch = if Command::new("git")
.args(["rev-parse", "--verify", "main"])
.current_dir(repo_path)
.output()
.unwrap()
.status
.success()
{
"main"
} else {
"master"
};
let sha = repo.resolve_to_sha(Some(main_branch)).await.unwrap();
assert_eq!(sha, expected_sha);
let result = repo.resolve_to_sha(Some("nonexistent")).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_resolve_to_sha_with_multiple_commits() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
Command::new("git").args(["init"]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(repo_path)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(repo_path)
.output()
.unwrap();
std::fs::write(repo_path.join("file1.txt"), "content1").unwrap();
Command::new("git").args(["add", "."]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "First commit"])
.current_dir(repo_path)
.output()
.unwrap();
Command::new("git").args(["tag", "v1.0.0"]).current_dir(repo_path).output().unwrap();
let first_sha = Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(repo_path)
.output()
.unwrap();
let first_sha = String::from_utf8(first_sha.stdout).unwrap().trim().to_string();
std::fs::write(repo_path.join("file2.txt"), "content2").unwrap();
Command::new("git").args(["add", "."]).current_dir(repo_path).output().unwrap();
Command::new("git")
.args(["commit", "-m", "Second commit"])
.current_dir(repo_path)
.output()
.unwrap();
Command::new("git").args(["tag", "v2.0.0"]).current_dir(repo_path).output().unwrap();
let second_sha = Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(repo_path)
.output()
.unwrap();
let second_sha = String::from_utf8(second_sha.stdout).unwrap().trim().to_string();
let repo = GitRepo::new(repo_path);
let sha_v1 = repo.resolve_to_sha(Some("v1.0.0")).await.unwrap();
assert_eq!(sha_v1, first_sha);
let sha_v2 = repo.resolve_to_sha(Some("v2.0.0")).await.unwrap();
assert_eq!(sha_v2, second_sha);
let sha_head = repo.resolve_to_sha(Some("HEAD")).await.unwrap();
assert_eq!(sha_head, second_sha);
let short_sha = &first_sha[..7];
let resolved = repo.resolve_to_sha(Some(short_sha)).await.unwrap();
assert_eq!(resolved, first_sha);
}
}