Skip to main content

cascade_cli/config/
mod.rs

1pub mod auth;
2pub mod settings;
3
4pub use auth::{AuthConfig, AuthManager};
5pub use settings::{BitbucketConfig, CascadeConfig, CascadeSettings, GitConfig, Settings};
6
7use crate::errors::{CascadeError, Result};
8use crate::git::GitRepository;
9use std::fs;
10use std::path::{Path, PathBuf};
11
12/// Get the Cascade configuration directory (~/.cascade/)
13pub fn get_config_dir() -> Result<PathBuf> {
14    let home_dir =
15        dirs::home_dir().ok_or_else(|| CascadeError::config("Could not find home directory"))?;
16    let config_dir = home_dir.join(".cascade");
17
18    // Validate the path to ensure it's within the home directory
19    crate::utils::path_validation::validate_config_path(&config_dir, &home_dir)
20}
21
22/// Get the Cascade configuration directory for a specific repository.
23/// In a worktree, falls back to the main repo's `.cascade/` directory
24/// so that config, stacks, and credentials are shared across worktrees.
25pub fn get_repo_config_dir(repo_path: &Path) -> Result<PathBuf> {
26    // Validate that repo_path is a real directory
27    let canonical_repo = repo_path.canonicalize().map_err(|e| {
28        CascadeError::config(format!("Invalid repository path '{repo_path:?}': {e}"))
29    })?;
30
31    let config_dir = canonical_repo.join(".cascade");
32
33    // If .cascade exists here, use it directly
34    if config_dir.exists() {
35        return crate::utils::path_validation::validate_config_path(&config_dir, &canonical_repo);
36    }
37
38    // Fall back to the main repo root for worktrees
39    if let Ok(repo) = git2::Repository::discover(repo_path) {
40        if let Some(main_workdir) = repo.commondir().parent() {
41            let main_canonical = main_workdir
42                .canonicalize()
43                .unwrap_or(main_workdir.to_path_buf());
44            if main_canonical != canonical_repo {
45                let main_config_dir = main_canonical.join(".cascade");
46                if main_config_dir.exists() {
47                    return crate::utils::path_validation::validate_config_path(
48                        &main_config_dir,
49                        &main_canonical,
50                    );
51                }
52            }
53        }
54    }
55
56    // Default: return the workdir-relative path (for init or when no config exists yet)
57    crate::utils::path_validation::validate_config_path(&config_dir, &canonical_repo)
58}
59
60/// Ensure the configuration directory exists
61pub fn ensure_config_dir(config_dir: &Path) -> Result<()> {
62    if !config_dir.exists() {
63        fs::create_dir_all(config_dir)
64            .map_err(|e| CascadeError::config(format!("Failed to create config directory: {e}")))?;
65    }
66
67    // Create subdirectories
68    let stacks_dir = config_dir.join("stacks");
69    if !stacks_dir.exists() {
70        fs::create_dir_all(&stacks_dir)
71            .map_err(|e| CascadeError::config(format!("Failed to create stacks directory: {e}")))?;
72    }
73
74    let cache_dir = config_dir.join("cache");
75    if !cache_dir.exists() {
76        fs::create_dir_all(&cache_dir)
77            .map_err(|e| CascadeError::config(format!("Failed to create cache directory: {e}")))?;
78    }
79
80    Ok(())
81}
82
83/// Check if a repository is initialized for Cascade
84pub fn is_repo_initialized(repo_path: &Path) -> bool {
85    match get_repo_config_dir(repo_path) {
86        Ok(config_dir) => config_dir.exists() && config_dir.join("config.json").exists(),
87        Err(_) => false,
88    }
89}
90
91/// Initialize a repository for Cascade
92pub fn initialize_repo(repo_path: &Path, bitbucket_url: Option<String>) -> Result<()> {
93    let config_dir = get_repo_config_dir(repo_path)?;
94    ensure_config_dir(&config_dir)?;
95
96    // Create default configuration with detected default branch
97    let mut settings = Settings::default_for_repo(bitbucket_url);
98
99    // Detect the actual default branch from the repository
100    if let Ok(git_repo) = GitRepository::open(repo_path) {
101        if let Ok(detected_branch) = git_repo.detect_main_branch() {
102            tracing::debug!("Detected default branch: {}", detected_branch);
103            settings.git.default_branch = detected_branch;
104        } else {
105            tracing::debug!(
106                "Could not detect default branch, using fallback: {}",
107                settings.git.default_branch
108            );
109        }
110    } else {
111        tracing::debug!(
112            "Could not open git repository, using fallback default branch: {}",
113            settings.git.default_branch
114        );
115    }
116
117    settings.save_to_file(&config_dir.join("config.json"))?;
118
119    tracing::debug!("Initialized Cascade repository at {}", repo_path.display());
120    Ok(())
121}