git_worktree_cli/
config.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::fs;
4use std::path::{Path, PathBuf};
5
6use crate::cli::Provider;
7use crate::error::{Error, Result};
8
9#[derive(Debug, Serialize, Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub struct GitWorktreeConfig {
12 pub repository_url: String,
13 pub main_branch: String,
14 pub created_at: DateTime<Utc>,
15 pub source_control: String,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub bitbucket_email: Option<String>,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub hooks: Option<Hooks>,
20}
21
22#[derive(Debug, Serialize, Deserialize)]
23#[serde(rename_all = "camelCase")]
24pub struct Hooks {
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub post_add: Option<Vec<String>>,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub pre_remove: Option<Vec<String>>,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub post_remove: Option<Vec<String>>,
31}
32
33impl GitWorktreeConfig {
34 pub fn new(repository_url: String, main_branch: String, provider: Provider) -> Self {
35 let source_control = match provider {
37 Provider::Github => "github".to_string(),
38 Provider::BitbucketCloud => "bitbucket-cloud".to_string(),
39 Provider::BitbucketDataCenter => "bitbucket-data-center".to_string(),
40 };
41
42 Self {
43 repository_url,
44 main_branch,
45 created_at: Utc::now(),
46 source_control,
47 bitbucket_email: None,
48 hooks: Some(Hooks {
49 post_add: Some(vec![]),
50 pre_remove: Some(vec![]),
51 post_remove: Some(vec![]),
52 }),
53 }
54 }
55
56 pub fn save(&self, path: &Path) -> Result<()> {
57 let json_string = serde_json::to_string_pretty(self)?;
58
59 fs::write(path, json_string).map_err(|e| Error::config(format!("Failed to write config file: {}", e)))?;
60
61 Ok(())
62 }
63
64 pub fn load(path: &Path) -> Result<Self> {
65 let content =
66 fs::read_to_string(path).map_err(|e| Error::config(format!("Failed to read config file: {}", e)))?;
67
68 let config: Self = json5::from_str(&content)?;
69
70 Ok(config)
71 }
72
73 pub fn find_config() -> Result<Option<(PathBuf, Self)>> {
74 let mut current_dir = std::env::current_dir()?;
75
76 loop {
77 let config_path = current_dir.join("git-worktree-config.jsonc");
79 if config_path.exists() {
80 let config = Self::load(&config_path)?;
81 return Ok(Some((config_path, config)));
82 }
83
84 let main_config_path = current_dir.join("main").join("git-worktree-config.jsonc");
86 if main_config_path.exists() {
87 let config = Self::load(&main_config_path)?;
88 return Ok(Some((main_config_path, config)));
89 }
90
91 if !current_dir.pop() {
92 break;
93 }
94 }
95
96 Ok(None)
97 }
98}
99
100pub const CONFIG_FILENAME: &str = "git-worktree-config.jsonc";
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use tempfile::tempdir;
106
107 #[test]
108 fn test_config_creation() {
109 let config = GitWorktreeConfig::new(
110 "git@github.com:test/repo.git".to_string(),
111 "main".to_string(),
112 Provider::Github,
113 );
114
115 assert_eq!(config.repository_url, "git@github.com:test/repo.git");
116 assert_eq!(config.main_branch, "main");
117 assert_eq!(config.source_control, "github");
118 assert_eq!(config.bitbucket_email, None);
119 assert!(config.hooks.is_some());
120
121 let hooks = config.hooks.unwrap();
122 assert!(hooks.post_add.is_some());
123 assert!(hooks.pre_remove.is_some());
124 assert!(hooks.post_remove.is_some());
125 }
126
127 #[test]
128 fn test_config_creation_bitbucket() {
129 let config = GitWorktreeConfig::new(
130 "https://bitbucket.org/workspace/repo.git".to_string(),
131 "main".to_string(),
132 Provider::BitbucketCloud,
133 );
134
135 assert_eq!(config.repository_url, "https://bitbucket.org/workspace/repo.git");
136 assert_eq!(config.main_branch, "main");
137 assert_eq!(config.source_control, "bitbucket-cloud");
138 assert_eq!(config.bitbucket_email, None);
139 }
140
141 #[test]
142 fn test_config_creation_bitbucket_data_center() {
143 let config = GitWorktreeConfig::new(
144 "https://bitbucket.company.com/scm/project/repo.git".to_string(),
145 "main".to_string(),
146 Provider::BitbucketDataCenter,
147 );
148
149 assert_eq!(
150 config.repository_url,
151 "https://bitbucket.company.com/scm/project/repo.git"
152 );
153 assert_eq!(config.main_branch, "main");
154 assert_eq!(config.source_control, "bitbucket-data-center");
155 assert_eq!(config.bitbucket_email, None);
156 }
157
158 #[test]
159 fn test_config_save_and_load() {
160 let temp_dir = tempdir().unwrap();
161 let config_path = temp_dir.path().join("test-config.jsonc");
162
163 let original_config = GitWorktreeConfig::new(
164 "git@github.com:test/repo.git".to_string(),
165 "develop".to_string(),
166 Provider::Github,
167 );
168
169 original_config.save(&config_path).unwrap();
171 assert!(config_path.exists());
172
173 let loaded_config = GitWorktreeConfig::load(&config_path).unwrap();
175 assert_eq!(loaded_config.repository_url, original_config.repository_url);
176 assert_eq!(loaded_config.main_branch, original_config.main_branch);
177 }
178
179 #[test]
180 fn test_config_find_in_current_dir() {
181 let temp_dir = tempdir().unwrap();
182 let original_cwd = std::env::current_dir().unwrap();
183
184 let config = GitWorktreeConfig::new(
186 "git@github.com:test/repo.git".to_string(),
187 "main".to_string(),
188 Provider::Github,
189 );
190 config.save(&temp_dir.path().join(CONFIG_FILENAME)).unwrap();
191
192 std::env::set_current_dir(temp_dir.path()).unwrap();
194
195 let result = GitWorktreeConfig::find_config().unwrap();
197 assert!(result.is_some());
198
199 let (_found_path, found_config) = result.unwrap();
200 assert_eq!(found_config.repository_url, "git@github.com:test/repo.git");
201 assert_eq!(found_config.main_branch, "main");
202
203 if original_cwd.exists() {
206 std::env::set_current_dir(&original_cwd).unwrap();
207 } else {
208 std::env::set_current_dir("/").unwrap();
210 }
211 }
212
213 #[test]
214 fn test_config_not_found() {
215 let temp_dir = tempdir().unwrap();
216 let original_cwd = std::env::current_dir().unwrap();
217
218 std::env::set_current_dir(temp_dir.path()).unwrap();
220
221 let result = GitWorktreeConfig::find_config().unwrap();
223 assert!(result.is_none());
224
225 if original_cwd.exists() {
228 std::env::set_current_dir(&original_cwd).unwrap();
229 } else {
230 std::env::set_current_dir("/").unwrap();
232 }
233 }
234}