use anyhow::Result;
use colored::*;
use console::style;
use inquire::{Confirm, Select};
use std::path::PathBuf;
use crate::git::bulk_clone::{BulkCloneCommand, BulkCloneOptions};
use crate::git::provider::github_cli::GitHubCliProvider;
use crate::git::{GitConfig, Repository};
use crate::workspace::install::RepositoryInstaller;
use crate::workspace::manager::WorkspaceManager;
pub struct CloneCommand;
impl CloneCommand {
pub async fn execute(
url: String,
path: Option<PathBuf>,
open: bool,
install: bool,
workspace_manager: &mut WorkspaceManager,
git_config: &GitConfig,
) -> Result<PathBuf> {
let workspace_root = workspace_manager.config().workspace.root.clone();
let installer = RepositoryInstaller::new(workspace_root, git_config.clone());
let installed = installer
.install_from_url_with_options(&url, path, open, install)
.await?;
workspace_manager
.add_repository(installed.repository.clone())
.await?;
if !installed.post_install_actions.is_empty() {
installer
.execute_post_install_actions(&installed.post_install_actions, &installed.path)
.await?;
}
println!(
"\n{} Repository successfully added to workspace!",
"đ".green()
);
println!("Path: {}", installed.path.display().to_string().cyan());
Ok(installed.path)
}
pub async fn execute_interactive(
url: String,
path: Option<PathBuf>,
workspace_manager: &mut WorkspaceManager,
git_config: &GitConfig,
) -> Result<PathBuf> {
let cloned_path = Self::execute(
url.clone(),
path,
false,
false,
workspace_manager,
git_config,
)
.await?;
let repo_name = cloned_path
.file_name()
.and_then(|n| n.to_str())
.ok_or_else(|| anyhow::anyhow!("Could not determine repository name"))?;
Self::interactive_post_clone_workflow(repo_name, workspace_manager).await?;
Ok(cloned_path)
}
pub async fn clone_from_search_result(
repo: Repository,
workspace_manager: &mut WorkspaceManager,
git_config: &GitConfig,
) -> Result<()> {
println!(
"\n{} Selected: {}",
"â
".green(),
repo.full_name.cyan().bold()
);
let _cloned_path =
Self::execute(repo.url, None, false, false, workspace_manager, git_config).await?;
Self::interactive_post_clone_workflow(&repo.name, workspace_manager).await?;
Ok(())
}
pub async fn interactive_post_clone_workflow(
repo_name: &str,
workspace_manager: &mut WorkspaceManager,
) -> Result<()> {
println!("\n{} Repository cloned successfully!", style("đ").green());
let configure_apps = Confirm::new(&format!(
"Would you like to configure apps for '{}'?",
style(repo_name).cyan().bold()
))
.with_default(true)
.with_help_message(
"Configure which applications can open this repository (VS Code, Warp, etc.)",
)
.prompt()?;
if configure_apps {
Self::configure_repository_apps(repo_name, workspace_manager).await?;
}
let open_now = Confirm::new(&format!(
"Would you like to open '{}' now?",
style(repo_name).cyan().bold()
))
.with_default(true)
.with_help_message("Open the repository with your configured app")
.prompt()?;
if open_now {
Self::open_repository_interactive(repo_name, workspace_manager).await?;
}
Ok(())
}
async fn configure_repository_apps(
repo_name: &str,
workspace_manager: &mut WorkspaceManager,
) -> Result<()> {
let available_apps = [
("vscode", "Visual Studio Code - Code editor"),
("warp", "Warp - Modern terminal"),
("iterm2", "iTerm2 - Terminal emulator"),
];
println!(
"\n{} Select an application to configure for this repository:",
style("đą").green()
);
let app_choices: Vec<String> = available_apps
.iter()
.map(|(name, desc)| format!("{name} - {desc}"))
.collect();
let selected_display = Select::new("Choose an application:", app_choices)
.with_help_message("Select an application to configure for this repository")
.prompt()?;
let app_name = selected_display
.split(" - ")
.next()
.unwrap_or(&selected_display);
workspace_manager
.configure_app_for_repo(repo_name, app_name, "default")
.await?;
println!(
"{} Configured {} for {}",
style("â
").green(),
style(app_name).blue(),
style(repo_name).cyan()
);
Ok(())
}
async fn open_repository_interactive(
repo_name: &str,
workspace_manager: &mut WorkspaceManager,
) -> Result<()> {
if let Some(repo_info) = workspace_manager.get_repository(repo_name) {
if repo_info.apps.is_empty() {
println!(
"{} No apps configured for this repository",
style("â ī¸").yellow()
);
println!(" Configure apps first using the configuration workflow");
return Ok(());
}
let app_to_use = if repo_info.apps.len() == 1 {
repo_info.apps.keys().next().unwrap().clone()
} else {
let app_choices: Vec<String> = repo_info.apps.keys().cloned().collect();
Select::new("Choose an app to open with:", app_choices)
.with_help_message("Select which application to use")
.prompt()?
};
workspace_manager
.open_repo_with_app(repo_name, &app_to_use)
.await?;
println!(
"{} Opened {} with {}",
style("đ").green(),
style(repo_name).cyan().bold(),
style(&app_to_use).blue()
);
} else {
println!(
"{} Repository '{}' not found in workspace",
style("â").red(),
repo_name
);
}
Ok(())
}
}
pub struct EnhancedCloneCommand;
impl EnhancedCloneCommand {
pub async fn execute_with_detection(
url_or_target: String,
app: Option<String>,
no_configure: bool,
no_open: bool,
workspace_manager: &mut WorkspaceManager,
git_config: &GitConfig,
) -> Result<()> {
let contains_slash = url_or_target.contains('/');
let is_url = url_or_target.starts_with("http") || url_or_target.starts_with("git@");
match (contains_slash, is_url) {
(true, _) | (false, true) => {
Self::single_repository_workflow(
url_or_target,
app,
no_configure,
no_open,
workspace_manager,
git_config,
)
.await
}
(false, false) => {
Self::detect_and_route(
url_or_target,
app,
no_configure,
no_open,
workspace_manager,
git_config,
)
.await
}
}
}
async fn detect_and_route(
target: String,
app: Option<String>,
no_configure: bool,
no_open: bool,
workspace_manager: &mut WorkspaceManager,
git_config: &GitConfig,
) -> Result<()> {
println!("đ Analyzing '{}'...", style(&target).cyan());
let github_cli = match GitHubCliProvider::new() {
Ok(cli) => cli,
Err(_) => {
println!(
"{} GitHub CLI not available, searching repositories...",
style("â ī¸").yellow()
);
return Self::fallback_to_search(target, workspace_manager, git_config).await;
}
};
match github_cli.user_or_org_exists(&target).await {
Ok(true) => {
match github_cli.count_repositories(&target).await {
Ok(0) => {
println!(
"{} '{}' has no public repositories.",
style("âšī¸").blue(),
style(&target).cyan()
);
Self::fallback_to_search(target, workspace_manager, git_config).await
}
Ok(count) => {
Self::interactive_clone_selection(
target,
count,
app,
no_configure,
no_open,
workspace_manager,
git_config,
)
.await
}
Err(_) => {
println!(
"{} Failed to count repositories for '{}', searching instead...",
style("â ī¸").yellow(),
style(&target).cyan()
);
Self::fallback_to_search(target, workspace_manager, git_config).await
}
}
}
Ok(false) => {
println!(
"đ '{}' not found as a GitHub user or organization.",
&target
);
println!("đ Searching repositories for '{}'...", &target);
Self::fallback_to_search(target, workspace_manager, git_config).await
}
Err(_) => {
println!(
"{} Failed to check GitHub, searching repositories instead...",
style("â ī¸").yellow()
);
Self::fallback_to_search(target, workspace_manager, git_config).await
}
}
}
async fn interactive_clone_selection(
target: String,
repo_count: usize,
_app: Option<String>,
_no_configure: bool,
_no_open: bool,
workspace_manager: &mut WorkspaceManager,
git_config: &GitConfig,
) -> Result<()> {
println!(
"â
Found GitHub target '{}' with {} repositories",
style(&target).cyan().bold(),
style(repo_count).green().bold()
);
let options = vec![
format!("Clone all {} repositories", repo_count),
"Search for specific repository".to_string(),
"Cancel".to_string(),
];
let selection = Select::new("What would you like to do?", options)
.with_help_message("Choose how to proceed with this GitHub target")
.prompt()?;
match selection.as_str() {
s if s.starts_with("Clone all") => {
Self::bulk_clone_workflow(target, workspace_manager, git_config).await
}
"Search for specific repository" => {
Self::fallback_to_search(target, workspace_manager, git_config).await
}
_ => {
println!("{} Operation cancelled", style("âšī¸").blue());
Ok(())
}
}
}
async fn bulk_clone_workflow(
target: String,
workspace_manager: &mut WorkspaceManager,
git_config: &GitConfig,
) -> Result<()> {
let options = BulkCloneOptions {
exclude_patterns: Vec::new(),
include_patterns: Vec::new(),
skip_existing: true,
custom_path: None,
force: false, };
match BulkCloneCommand::execute(target, options, workspace_manager, git_config).await {
Ok(result) => {
println!(
"{} Bulk clone completed: {} successful, {} failed",
style("â
").green().bold(),
result.total_cloned,
result.failed.len()
);
Ok(())
}
Err(e) => {
println!("{} Bulk clone failed: {}", style("â").red(), e);
Err(e)
}
}
}
async fn fallback_to_search(
target: String,
workspace_manager: &mut WorkspaceManager,
git_config: &GitConfig,
) -> Result<()> {
use crate::git::SearchCommand;
SearchCommand::execute_with_query(&target, workspace_manager, git_config).await
}
async fn single_repository_workflow(
url: String,
app: Option<String>,
no_configure: bool,
no_open: bool,
workspace_manager: &mut WorkspaceManager,
git_config: &GitConfig,
) -> Result<()> {
use crate::ui::workflows::{execute_workflow, CloneWorkflow};
if !no_configure || !no_open {
let workflow = Box::new(CloneWorkflow {
url: url.clone(),
app: app.clone(),
});
execute_workflow(workflow, workspace_manager).await?;
} else {
let _cloned_path =
CloneCommand::execute(url, None, false, false, workspace_manager, git_config)
.await?;
}
Ok(())
}
}