use git2::Repository;
use std::fs;
use std::process::Command;
use tempfile::TempDir;
fn create_test_repo_with_remote(dir: &std::path::Path, remote_url: &str) {
let repo = Repository::init(dir).expect("Failed to init repo");
let sig = git2::Signature::now("Test", "test@example.com").unwrap();
let tree_id = {
let mut index = repo.index().unwrap();
let file_path = dir.join("README.md");
fs::write(&file_path, "# Test").unwrap();
index.add_path(std::path::Path::new("README.md")).unwrap();
index.write().unwrap();
index.write_tree().unwrap()
};
let tree = repo.find_tree(tree_id).unwrap();
repo.commit(Some("HEAD"), &sig, &sig, "Initial commit", &tree, &[])
.unwrap();
repo.remote("origin", remote_url).unwrap();
}
fn run_init_from_dirs(workspace_dir: &std::path::Path) -> std::process::Output {
Command::new("cargo")
.args([
"run",
"--quiet",
"--bin",
"gr",
"--",
"init",
"--from-dirs",
"-p",
])
.arg(workspace_dir)
.current_dir(env!("CARGO_MANIFEST_DIR"))
.output()
.expect("Failed to run gr init")
}
fn workspace_manifest_path(workspace: &std::path::Path) -> std::path::PathBuf {
workspace.join(".gitgrip/spaces/main/gripspace.yml")
}
#[test]
fn test_init_from_dirs_discovers_repos() {
let temp = TempDir::new().unwrap();
let workspace = temp.path();
let repo1 = workspace.join("frontend");
let repo2 = workspace.join("backend");
fs::create_dir_all(&repo1).unwrap();
fs::create_dir_all(&repo2).unwrap();
create_test_repo_with_remote(&repo1, "git@github.com:myorg/frontend.git");
create_test_repo_with_remote(&repo2, "git@github.com:myorg/backend.git");
let output = run_init_from_dirs(workspace);
assert!(output.status.success(), "init failed: {:?}", output);
let manifest_path = workspace_manifest_path(workspace);
assert!(manifest_path.exists(), "gripspace.yml not created");
let manifest_content = fs::read_to_string(&manifest_path).unwrap();
assert!(
manifest_content.contains("frontend"),
"frontend not in manifest"
);
assert!(
manifest_content.contains("backend"),
"backend not in manifest"
);
assert!(
manifest_content.contains("github.com"),
"github.com URL not in manifest"
);
}
#[test]
fn test_init_from_dirs_handles_mixed_platforms() {
let temp = TempDir::new().unwrap();
let workspace = temp.path();
let gh_repo = workspace.join("github-app");
let gl_repo = workspace.join("gitlab-lib");
fs::create_dir_all(&gh_repo).unwrap();
fs::create_dir_all(&gl_repo).unwrap();
create_test_repo_with_remote(&gh_repo, "git@github.com:myorg/github-app.git");
create_test_repo_with_remote(&gl_repo, "git@gitlab.com:mygroup/gitlab-lib.git");
let output = run_init_from_dirs(workspace);
assert!(
output.status.success(),
"init failed with mixed platforms: {:?}",
output
);
let manifest_path = workspace_manifest_path(workspace);
assert!(manifest_path.exists(), "gripspace.yml not created");
let manifest_content = fs::read_to_string(&manifest_path).unwrap();
assert!(
manifest_content.contains("github-app"),
"github-app not in manifest"
);
assert!(
manifest_content.contains("gitlab-lib"),
"gitlab-lib not in manifest"
);
}
#[test]
fn test_init_from_dirs_handles_repos_without_remotes() {
let temp = TempDir::new().unwrap();
let workspace = temp.path();
let local_repo = workspace.join("local-only");
fs::create_dir_all(&local_repo).unwrap();
let repo = Repository::init(&local_repo).expect("Failed to init repo");
let sig = git2::Signature::now("Test", "test@example.com").unwrap();
let tree_id = {
let mut index = repo.index().unwrap();
let file_path = local_repo.join("README.md");
fs::write(&file_path, "# Local").unwrap();
index.add_path(std::path::Path::new("README.md")).unwrap();
index.write().unwrap();
index.write_tree().unwrap()
};
let tree = repo.find_tree(tree_id).unwrap();
repo.commit(Some("HEAD"), &sig, &sig, "Initial commit", &tree, &[])
.unwrap();
let output = run_init_from_dirs(workspace);
assert!(
output.status.success(),
"init failed with local-only repo: {:?}",
output
);
let manifest_path = workspace_manifest_path(workspace);
assert!(manifest_path.exists(), "gripspace.yml not created");
let manifest_content = fs::read_to_string(&manifest_path).unwrap();
assert!(
manifest_content.contains("local-only"),
"local-only not in manifest"
);
assert!(
manifest_content.contains("OWNER"),
"placeholder URL not in manifest"
);
}
#[test]
fn test_init_from_dirs_detects_azure_repos() {
let temp = TempDir::new().unwrap();
let workspace = temp.path();
let repo1 = workspace.join("azure-frontend");
let repo2 = workspace.join("azure-backend");
fs::create_dir_all(&repo1).unwrap();
fs::create_dir_all(&repo2).unwrap();
create_test_repo_with_remote(
&repo1,
"git@ssh.dev.azure.com:v3/myorg/myproject/azure-frontend",
);
create_test_repo_with_remote(
&repo2,
"https://dev.azure.com/myorg/myproject/_git/azure-backend",
);
let output = run_init_from_dirs(workspace);
assert!(
output.status.success(),
"init failed with Azure repos: {:?}",
output
);
let manifest_path = workspace_manifest_path(workspace);
assert!(manifest_path.exists(), "gripspace.yml not created");
let manifest_content = fs::read_to_string(&manifest_path).unwrap();
assert!(
manifest_content.contains("azure-frontend"),
"azure-frontend not in manifest"
);
assert!(
manifest_content.contains("azure-backend"),
"azure-backend not in manifest"
);
}
#[test]
fn test_init_fails_on_existing_workspace() {
let temp = TempDir::new().unwrap();
let workspace = temp.path();
let repo_dir = workspace.join("app");
fs::create_dir_all(&repo_dir).unwrap();
create_test_repo_with_remote(&repo_dir, "git@github.com:myorg/app.git");
let gitgrip_dir = workspace.join(".gitgrip");
fs::create_dir_all(&gitgrip_dir).unwrap();
let output = run_init_from_dirs(workspace);
assert!(
!output.status.success(),
"init should fail on existing workspace"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("already exists") || stderr.contains("reinitialize"),
"should mention existing workspace: {}",
stderr
);
}
#[test]
fn test_init_fails_on_empty_directory() {
let temp = TempDir::new().unwrap();
let workspace = temp.path();
let output = run_init_from_dirs(workspace);
assert!(
!output.status.success(),
"init should fail on empty directory"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("No git repositories found"),
"should mention no repos found: {}",
stderr
);
}
#[test]
fn test_init_skips_hidden_directories() {
let temp = TempDir::new().unwrap();
let workspace = temp.path();
let hidden_repo = workspace.join(".hidden-repo");
fs::create_dir_all(&hidden_repo).unwrap();
create_test_repo_with_remote(&hidden_repo, "git@github.com:myorg/hidden.git");
let visible_repo = workspace.join("visible-app");
fs::create_dir_all(&visible_repo).unwrap();
create_test_repo_with_remote(&visible_repo, "git@github.com:myorg/visible-app.git");
let output = run_init_from_dirs(workspace);
assert!(output.status.success(), "init failed: {:?}", output);
let manifest_path = workspace_manifest_path(workspace);
let manifest_content = fs::read_to_string(&manifest_path).unwrap();
assert!(
manifest_content.contains("visible-app"),
"visible-app should be in manifest"
);
assert!(
!manifest_content.contains("hidden-repo"),
"hidden-repo should not be in manifest"
);
}