git_worktree_cli/
bitbucket_data_center_auth.rs

1use std::env;
2
3use crate::error::{Error, Result};
4
5const TOKEN_ENV_VAR: &str = "BITBUCKET_DATA_CENTER_HTTP_ACCESS_TOKEN";
6
7pub struct BitbucketDataCenterAuth;
8
9impl BitbucketDataCenterAuth {
10    pub fn new(_project_key: String, _repo_slug: String, _base_url: String) -> Result<Self> {
11        Ok(BitbucketDataCenterAuth)
12    }
13
14    pub fn get_token(&self) -> Result<String> {
15        env::var(TOKEN_ENV_VAR)
16            .map_err(|_| {
17                Error::auth(format!(
18                    "No Bitbucket Data Center access token found. Please set the {} environment variable.\n\
19                Run 'gwt auth bitbucket-data-center setup' for instructions.",
20                    TOKEN_ENV_VAR
21                ))
22            })
23            .and_then(|token| {
24                if token.is_empty() {
25                    Err(Error::auth(format!(
26                        "Bitbucket Data Center access token is empty. Please set the {} environment variable.\n\
27                        Run 'gwt auth bitbucket-data-center setup' for instructions.",
28                        TOKEN_ENV_VAR
29                    )))
30                } else {
31                    Ok(token)
32                }
33            })
34    }
35}
36
37fn derive_api_base_url_from_repo_url(repo_url: &str) -> Option<String> {
38    // Extract domain from various URL patterns and construct API base URL
39
40    // Handle HTTPS URLs like https://github.com/owner/repo.git
41    if let Some(captures) = regex::Regex::new(r"https://([^/]+)").ok()?.captures(repo_url) {
42        let domain = captures.get(1)?.as_str();
43        return Some(format!("https://{}", domain));
44    }
45
46    // Handle SSH URLs like git@github.com:owner/repo.git
47    if let Some(captures) = regex::Regex::new(r"git@([^:]+):").ok()?.captures(repo_url) {
48        let domain = captures.get(1)?.as_str();
49        return Some(format!("https://{}", domain));
50    }
51
52    // Handle other SSH formats like ssh://git@domain/path
53    if let Some(captures) = regex::Regex::new(r"ssh://git@([^/]+)").ok()?.captures(repo_url) {
54        let domain = captures.get(1)?.as_str();
55        return Some(format!("https://{}", domain));
56    }
57
58    None
59}
60
61pub fn get_auth_from_config() -> Result<(String, String, String)> {
62    use crate::bitbucket_data_center_api::extract_bitbucket_data_center_info_from_url;
63    use crate::config::GitWorktreeConfig;
64    use crate::github;
65
66    let (_, config) =
67        GitWorktreeConfig::find_config()?.ok_or_else(|| Error::config("No git-worktree-config.jsonc found"))?;
68
69    // Check sourceControl field instead of URL pattern
70    if config.source_control != "bitbucket-data-center" {
71        return Err(Error::provider(format!(
72            "Repository is not configured for Bitbucket Data Center (sourceControl: {})",
73            config.source_control
74        )));
75    }
76
77    let repo_url = &config.repository_url;
78
79    // First try to extract from actual Bitbucket Data Center URL
80    if let Some((base_url, project_key, repo_slug)) = extract_bitbucket_data_center_info_from_url(repo_url) {
81        return Ok((base_url, project_key, repo_slug));
82    }
83
84    // If that fails, try to derive from other URL patterns (like GitHub URLs)
85    if let Some((owner, repo)) = github::GitHubClient::parse_github_url(repo_url) {
86        // For GitHub URLs with bitbucket-data-center config, derive API base URL from the domain
87        if let Some(base_url) = derive_api_base_url_from_repo_url(repo_url) {
88            return Ok((base_url, owner, repo));
89        }
90
91        return Err(Error::provider(format!(
92            "Failed to derive Bitbucket Data Center base URL from: {}",
93            repo_url
94        )));
95    }
96
97    Err(Error::provider(format!("Failed to parse repository URL: {}", repo_url)))
98}
99
100pub fn display_setup_instructions() {
101    println!("Setting up Bitbucket Data Center authentication\n");
102    println!("1. Create an HTTP Access Token in your Bitbucket Data Center instance:");
103    println!("   - Go to your Bitbucket Data Center instance");
104    println!("   - Navigate to your profile settings");
105    println!("   - Go to 'HTTP access tokens' or 'Personal access tokens'");
106    println!("   - Create a new token with repository permissions\n");
107    println!("2. Required permissions for the token:");
108    println!("   - Repository: Read");
109    println!("   - Pull requests: Read\n");
110    println!("3. Copy the generated token\n");
111    println!("4. Set the environment variable:");
112    println!("   export {}=YOUR_TOKEN", TOKEN_ENV_VAR);
113    println!("\nExample usage:");
114    println!("   curl -H \"Authorization: Bearer ${}\" \\", TOKEN_ENV_VAR);
115    println!("        \"https://git.acmeorg.com/rest/api/1.0/projects/PROJECT/repos/REPO/pull-requests\"");
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn test_bitbucket_data_center_auth_creation() {
124        let auth = BitbucketDataCenterAuth::new(
125            "PROJECT".to_string(),
126            "repository".to_string(),
127            "https://git.acmeorg.com".to_string(),
128        );
129        assert!(auth.is_ok());
130    }
131
132    #[test]
133    fn test_auth_structure() {
134        let auth = BitbucketDataCenterAuth::new(
135            "PROJ".to_string(),
136            "repo".to_string(),
137            "https://git.example.com".to_string(),
138        );
139        assert!(auth.is_ok());
140    }
141}