cascade_cli/cli/commands/
init.rs

1use crate::cli::output::Output;
2use crate::config::{initialize_repo, is_repo_initialized};
3use crate::errors::{CascadeError, Result};
4use crate::git::{find_repository_root, is_git_repository};
5use std::env;
6
7/// Initialize a repository for Cascade
8pub async fn run(bitbucket_url: Option<String>, force: bool) -> Result<()> {
9    Output::info("Initializing Cascade repository...");
10
11    // Get current directory
12    let current_dir = env::current_dir()
13        .map_err(|e| CascadeError::config(format!("Could not get current directory: {e}")))?;
14
15    // Check if we're in a Git repository
16    if !is_git_repository(&current_dir) {
17        return Err(CascadeError::not_initialized(
18            "Not in a Git repository. Please run this command from within a Git repository.",
19        ));
20    }
21
22    // Find the repository root
23    let repo_root = find_repository_root(&current_dir)?;
24    tracing::debug!("Found Git repository at: {}", repo_root.display());
25
26    // Check if already initialized
27    if is_repo_initialized(&repo_root) && !force {
28        return Err(CascadeError::invalid_operation(
29            "Repository is already initialized for Cascade. Use --force to reinitialize.",
30        ));
31    }
32
33    if force && is_repo_initialized(&repo_root) {
34        Output::warning("Force reinitializing repository...");
35    }
36
37    // Initialize the repository
38    initialize_repo(&repo_root, bitbucket_url.clone())?;
39
40    // Print success message
41    println!("āœ… Cascade repository initialized successfully!");
42
43    if let Some(url) = &bitbucket_url {
44        println!("šŸ“Š Bitbucket Server URL: {url}");
45    }
46
47    println!("\nšŸ“‹ Next steps:");
48    println!("  1. Configure Bitbucket Server settings:");
49    if bitbucket_url.is_none() {
50        println!("     ca config set bitbucket.url https://your-bitbucket-server.com");
51    }
52    println!("     ca config set bitbucket.project YOUR_PROJECT_KEY");
53    println!("     ca config set bitbucket.repo your-repo-name");
54    println!("     ca config set bitbucket.token your-personal-access-token");
55    println!("  2. Verify configuration:");
56    println!("     ca doctor");
57    println!("  3. Create your first stack:");
58    println!("     ca create \"Add new feature\"");
59
60    Ok(())
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use git2::{Repository, Signature};
67    use tempfile::TempDir;
68
69    async fn create_test_git_repo() -> (TempDir, std::path::PathBuf) {
70        let temp_dir = TempDir::new().unwrap();
71        let repo_path = temp_dir.path().to_path_buf();
72
73        // Initialize git repository
74        let repo = Repository::init(&repo_path).unwrap();
75
76        // Create initial commit
77        let signature = Signature::now("Test User", "test@example.com").unwrap();
78        let tree_id = {
79            let mut index = repo.index().unwrap();
80            index.write_tree().unwrap()
81        };
82        let tree = repo.find_tree(tree_id).unwrap();
83
84        repo.commit(
85            Some("HEAD"),
86            &signature,
87            &signature,
88            "Initial commit",
89            &tree,
90            &[],
91        )
92        .unwrap();
93
94        (temp_dir, repo_path)
95    }
96
97    #[tokio::test]
98    async fn test_init_in_git_repo() {
99        let (_temp_dir, repo_path) = create_test_git_repo().await;
100
101        // Test the core functionality directly using internal functions
102        // This verifies initialization logic without environment-dependent directory changes
103        assert!(is_git_repository(&repo_path));
104
105        // Initialize using internal function
106        crate::config::initialize_repo(
107            &repo_path,
108            Some("https://bitbucket.example.com".to_string()),
109        )
110        .unwrap();
111
112        // Verify it was initialized successfully
113        assert!(is_repo_initialized(&repo_path));
114
115        println!("āœ… Cascade initialization in Git repository tested successfully");
116    }
117
118    #[tokio::test]
119    async fn test_init_outside_git_repo() {
120        let temp_dir = TempDir::new().unwrap();
121        let non_git_path = temp_dir.path();
122
123        // Test validation logic directly - non-git directories should be detected
124        assert!(!is_git_repository(non_git_path));
125
126        // Attempting to find repository root should fail in non-git directory
127        let result = find_repository_root(non_git_path);
128        assert!(result.is_err());
129
130        println!("āœ… Non-Git directory correctly detected - initialization would be rejected");
131    }
132
133    #[tokio::test]
134    async fn test_init_already_initialized() {
135        let (_temp_dir, repo_path) = create_test_git_repo().await;
136
137        // Initialize repo directly using internal function
138        crate::config::initialize_repo(&repo_path, None).unwrap();
139        assert!(is_repo_initialized(&repo_path));
140
141        // Test the validation logic directly without changing directories
142        // This tests the same logic but avoids directory change issues
143        assert!(is_repo_initialized(&repo_path));
144
145        // Since we can't easily test the full run() function without directory issues,
146        // let's test the core logic that should fail when already initialized
147        let repo_root = crate::git::find_repository_root(&repo_path).unwrap();
148        assert!(is_repo_initialized(&repo_root));
149
150        // The logic in run() should detect this is already initialized
151        // and return an error unless force is used
152        println!("āœ… Repository correctly detected as already initialized");
153    }
154}