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, always uses 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    // Check if we're in a worktree by comparing commondir to the repo path.
32    // commondir() points to the shared .git dir; canonicalize to strip any
33    // trailing slash, then take parent() to get the main repo root.
34    if let Ok(repo) = git2::Repository::discover(repo_path) {
35        let commondir = repo.commondir().to_path_buf();
36        let commondir_clean = commondir.canonicalize().unwrap_or(commondir);
37        if let Some(main_root) = commondir_clean.parent() {
38            let main_canonical = main_root.canonicalize().unwrap_or(main_root.to_path_buf());
39            if main_canonical != canonical_repo {
40                // We're in a worktree — always use the main repo's .cascade/
41                let main_config_dir = main_canonical.join(".cascade");
42                return crate::utils::path_validation::validate_config_path(
43                    &main_config_dir,
44                    &main_canonical,
45                );
46            }
47        }
48    }
49
50    // Not a worktree — use the repo's own .cascade/
51    let config_dir = canonical_repo.join(".cascade");
52    crate::utils::path_validation::validate_config_path(&config_dir, &canonical_repo)
53}
54
55/// Ensure the configuration directory exists
56pub fn ensure_config_dir(config_dir: &Path) -> Result<()> {
57    if !config_dir.exists() {
58        fs::create_dir_all(config_dir)
59            .map_err(|e| CascadeError::config(format!("Failed to create config directory: {e}")))?;
60    }
61
62    // Create subdirectories
63    let stacks_dir = config_dir.join("stacks");
64    if !stacks_dir.exists() {
65        fs::create_dir_all(&stacks_dir)
66            .map_err(|e| CascadeError::config(format!("Failed to create stacks directory: {e}")))?;
67    }
68
69    let cache_dir = config_dir.join("cache");
70    if !cache_dir.exists() {
71        fs::create_dir_all(&cache_dir)
72            .map_err(|e| CascadeError::config(format!("Failed to create cache directory: {e}")))?;
73    }
74
75    Ok(())
76}
77
78/// Check if a repository is initialized for Cascade
79pub fn is_repo_initialized(repo_path: &Path) -> bool {
80    match get_repo_config_dir(repo_path) {
81        Ok(config_dir) => config_dir.exists() && config_dir.join("config.json").exists(),
82        Err(_) => false,
83    }
84}
85
86/// Initialize a repository for Cascade
87pub fn initialize_repo(repo_path: &Path, bitbucket_url: Option<String>) -> Result<()> {
88    let config_dir = get_repo_config_dir(repo_path)?;
89    ensure_config_dir(&config_dir)?;
90
91    // Create default configuration with detected default branch
92    let mut settings = Settings::default_for_repo(bitbucket_url);
93
94    // Detect the actual default branch from the repository
95    if let Ok(git_repo) = GitRepository::open(repo_path) {
96        if let Ok(detected_branch) = git_repo.detect_main_branch() {
97            tracing::debug!("Detected default branch: {}", detected_branch);
98            settings.git.default_branch = detected_branch;
99        } else {
100            tracing::debug!(
101                "Could not detect default branch, using fallback: {}",
102                settings.git.default_branch
103            );
104        }
105    } else {
106        tracing::debug!(
107            "Could not open git repository, using fallback default branch: {}",
108            settings.git.default_branch
109        );
110    }
111
112    settings.save_to_file(&config_dir.join("config.json"))?;
113
114    tracing::debug!("Initialized Cascade repository at {}", repo_path.display());
115    Ok(())
116}