git_worktree_cli/commands/
init.rs1use 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 let detected_provider = detect_repository_provider(repo_url, provider)?;
14
15 println!("{}", format!("✓ Detected provider: {:?}", detected_provider).green());
16
17 let repo_name = extract_repo_name(repo_url)?;
19 let project_root = std::env::current_dir()?;
20
21 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 git::clone(repo_url, &repo_name)?;
35
36 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 let final_dir_name = default_branch.replace(['/', '\\'], "-");
43
44 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 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 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 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 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 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}