git_worktree_cli/commands/
init.rs

1use colored::Colorize;
2use std::fs;
3use std::path::{Path, PathBuf};
4
5use crate::cli::Provider;
6use crate::config::{GitWorktreeConfig, CONFIG_FILENAME};
7use crate::error::{Error, Result};
8use crate::git;
9use crate::{bitbucket_api, github};
10
11pub fn run(repo_url: &str, provider: Option<Provider>, force: bool) -> Result<()> {
12    // Detect or validate the repository provider
13    let detected_provider = detect_repository_provider(repo_url, provider)?;
14
15    println!("{}", format!("✓ Detected provider: {:?}", detected_provider).green());
16
17    // Extract repository name from URL
18    let repo_name = extract_repo_name(repo_url)?;
19    let project_root = std::env::current_dir()?;
20
21    // Check for existing clone directory
22    if Path::new(&repo_name).exists() {
23        if !force {
24            return Err(Error::msg(format!(
25                "Directory '{}' already exists. Use --force to overwrite or remove it manually.",
26                repo_name
27            )));
28        }
29        fs::remove_dir_all(&repo_name)
30            .map_err(|e| Error::msg(format!("Failed to remove existing directory: {}", e)))?;
31    }
32
33    // Clone the repository with streaming output (this is the key improvement!)
34    git::clone(repo_url, &repo_name)?;
35
36    // Get the default branch name
37    let repo_path = PathBuf::from(&repo_name);
38    let default_branch =
39        git::get_default_branch(&repo_path).map_err(|e| Error::git(format!("Failed to get default branch: {}", e)))?;
40
41    // Sanitize branch name for use as directory name
42    let final_dir_name = default_branch.replace(['/', '\\'], "-");
43
44    // Check for existing branch directory
45    if Path::new(&final_dir_name).exists() {
46        if !force {
47            return Err(Error::msg(format!(
48                "Directory '{}' already exists. Use --force to overwrite or remove it manually.",
49                final_dir_name
50            )));
51        }
52        fs::remove_dir_all(&final_dir_name)
53            .map_err(|e| Error::msg(format!("Failed to remove existing directory: {}", e)))?;
54    }
55
56    fs::rename(&repo_name, &final_dir_name).map_err(|e| Error::msg(format!("Failed to rename directory: {}", e)))?;
57
58    // Create configuration file
59    let config = GitWorktreeConfig::new(repo_url.to_string(), default_branch.clone(), detected_provider);
60    let config_path = project_root.join(CONFIG_FILENAME);
61    config
62        .save(&config_path)
63        .map_err(|e| Error::config(format!("Failed to save configuration: {}", e)))?;
64
65    // Print success messages
66    println!("{}", format!("✓ Repository cloned to: {}", final_dir_name).green());
67    println!("{}", format!("✓ Default branch: {}", default_branch).green());
68    println!("{}", format!("✓ Config saved to: {}", config_path.display()).green());
69
70    // Post-init hooks removed - no longer needed
71
72    Ok(())
73}
74
75fn extract_repo_name(repo_url: &str) -> Result<String> {
76    let name = repo_url
77        .split('/')
78        .next_back()
79        .ok_or_else(|| Error::msg("Invalid repository URL"))?
80        .strip_suffix(".git")
81        .unwrap_or_else(|| repo_url.split('/').next_back().unwrap());
82
83    Ok(name.to_string())
84}
85
86fn detect_repository_provider(repo_url: &str, provider: Option<Provider>) -> Result<Provider> {
87    let auto_detected = detect_provider_from_url(repo_url);
88
89    match provider {
90        // Use explicit provider if provided
91        Some(explicit) => {
92            if let Some(detected) = auto_detected {
93                if !providers_match(&detected, &explicit) {
94                    warn_provider_mismatch(&detected, &explicit);
95                }
96            }
97            Ok(explicit)
98        }
99
100        // Use auto-detected if no explicit provider
101        None => match auto_detected {
102            Some(detected) => Ok(detected),
103            None => Err(create_provider_error(repo_url)),
104        },
105    }
106}
107
108fn detect_provider_from_url(repo_url: &str) -> Option<Provider> {
109    if github::GitHubClient::parse_github_url(repo_url).is_some() {
110        Some(Provider::Github)
111    } else if bitbucket_api::is_bitbucket_repository(repo_url) {
112        Some(Provider::BitbucketCloud)
113    } else {
114        None
115    }
116}
117
118fn providers_match(a: &Provider, b: &Provider) -> bool {
119    std::mem::discriminant(a) == std::mem::discriminant(b)
120}
121
122fn warn_provider_mismatch(detected: &Provider, explicit: &Provider) {
123    println!(
124        "{}",
125        format!(
126            "⚠ URL suggests {:?} but --provider {:?} specified. Using {:?}.",
127            detected, explicit, explicit
128        )
129        .yellow()
130    );
131}
132
133fn create_provider_error(repo_url: &str) -> Error {
134    Error::provider(format!(
135        "Could not detect repository provider from URL: {}\n\
136         Please specify the provider using --provider:\n\
137         - For GitHub: --provider github\n\
138         - For Bitbucket Cloud: --provider bitbucket-cloud\n\
139         - For Bitbucket Data Center: --provider bitbucket-data-center",
140        repo_url
141    ))
142}