git_editor/utils/
git_config.rs1use std::path::PathBuf;
2use std::process::Command;
3
4pub fn get_git_user_name() -> Option<String> {
6 if let Ok(output) = Command::new("git")
8 .args(["config", "--global", "user.name"])
9 .output()
10 {
11 if output.status.success() {
12 let name = String::from_utf8_lossy(&output.stdout).trim().to_string();
13 if !name.is_empty() {
14 return Some(name);
15 }
16 }
17 }
18
19 read_gitconfig_value("user", "name")
21}
22
23pub fn get_git_user_email() -> Option<String> {
25 if let Ok(output) = Command::new("git")
27 .args(["config", "--global", "user.email"])
28 .output()
29 {
30 if output.status.success() {
31 let email = String::from_utf8_lossy(&output.stdout).trim().to_string();
32 if !email.is_empty() {
33 return Some(email);
34 }
35 }
36 }
37
38 read_gitconfig_value("user", "email")
40}
41
42fn read_gitconfig_value(section: &str, key: &str) -> Option<String> {
44 use std::fs;
45
46 let gitconfig_paths = get_gitconfig_paths();
48
49 for gitconfig_path in gitconfig_paths {
51 if let Ok(content) = fs::read_to_string(&gitconfig_path) {
52 if let Some(value) = parse_gitconfig(&content, section, key) {
53 return Some(value);
54 }
55 }
56 }
57
58 None
59}
60
61fn get_gitconfig_paths() -> Vec<PathBuf> {
63 let mut paths = Vec::new();
64
65 if let Some(user_config) = get_user_gitconfig_path() {
67 paths.push(user_config);
68 }
69
70 if let Some(system_config) = get_system_gitconfig_path() {
72 paths.push(system_config);
73 }
74
75 paths
76}
77
78fn get_user_gitconfig_path() -> Option<PathBuf> {
80 let home_dir = if let Ok(home) = std::env::var("HOME") {
82 PathBuf::from(home)
84 } else if let Ok(userprofile) = std::env::var("USERPROFILE") {
85 PathBuf::from(userprofile)
87 } else if let Ok(homedrive) = std::env::var("HOMEDRIVE") {
88 if let Ok(homepath) = std::env::var("HOMEPATH") {
89 PathBuf::from(homedrive).join(homepath)
91 } else {
92 return None;
93 }
94 } else {
95 return None;
96 };
97
98 Some(home_dir.join(".gitconfig"))
99}
100
101fn get_system_gitconfig_path() -> Option<PathBuf> {
103 if cfg!(windows) {
104 let possible_paths = vec![
106 PathBuf::from(r"C:\ProgramData\Git\config"),
107 PathBuf::from(r"C:\Program Files\Git\etc\gitconfig"),
108 PathBuf::from(r"C:\Program Files (x86)\Git\etc\gitconfig"),
109 ];
110
111 for path in possible_paths {
113 if path.exists() {
114 return Some(path);
115 }
116 }
117 None
118 } else {
119 Some(PathBuf::from("/etc/gitconfig"))
121 }
122}
123
124fn parse_gitconfig(content: &str, target_section: &str, target_key: &str) -> Option<String> {
126 let mut in_target_section = false;
127
128 for line in content.lines() {
129 let line = line.trim();
130
131 if line.is_empty() || line.starts_with('#') || line.starts_with(';') {
133 continue;
134 }
135
136 if line.starts_with('[') && line.ends_with(']') {
138 let section = &line[1..line.len() - 1];
139 in_target_section = section.trim().eq_ignore_ascii_case(target_section);
140 continue;
141 }
142
143 if in_target_section {
145 if let Some(eq_pos) = line.find('=') {
146 let key = line[..eq_pos].trim();
147 if key.eq_ignore_ascii_case(target_key) {
148 let value = line[eq_pos + 1..].trim();
149 let value = if (value.starts_with('"') && value.ends_with('"'))
151 || (value.starts_with('\'') && value.ends_with('\''))
152 {
153 &value[1..value.len() - 1]
154 } else {
155 value
156 };
157 return Some(value.to_string());
158 }
159 }
160 }
161 }
162
163 None
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_parse_gitconfig_basic() {
172 let config = r#"
173[user]
174 name = John Doe
175 email = john@example.com
176
177[core]
178 editor = vim
179"#;
180
181 assert_eq!(
182 parse_gitconfig(config, "user", "name"),
183 Some("John Doe".to_string())
184 );
185 assert_eq!(
186 parse_gitconfig(config, "user", "email"),
187 Some("john@example.com".to_string())
188 );
189 assert_eq!(
190 parse_gitconfig(config, "core", "editor"),
191 Some("vim".to_string())
192 );
193 assert_eq!(parse_gitconfig(config, "user", "nonexistent"), None);
194 assert_eq!(parse_gitconfig(config, "nonexistent", "name"), None);
195 }
196
197 #[test]
198 fn test_parse_gitconfig_with_quotes() {
199 let config = r#"
200[user]
201 name = "John Doe"
202 email = 'john@example.com'
203"#;
204
205 assert_eq!(
206 parse_gitconfig(config, "user", "name"),
207 Some("John Doe".to_string())
208 );
209 assert_eq!(
210 parse_gitconfig(config, "user", "email"),
211 Some("john@example.com".to_string())
212 );
213 }
214
215 #[test]
216 fn test_parse_gitconfig_case_insensitive() {
217 let config = r#"
218[USER]
219 NAME = John Doe
220 EMAIL = john@example.com
221"#;
222
223 assert_eq!(
224 parse_gitconfig(config, "user", "name"),
225 Some("John Doe".to_string())
226 );
227 assert_eq!(
228 parse_gitconfig(config, "user", "email"),
229 Some("john@example.com".to_string())
230 );
231 }
232
233 #[test]
234 fn test_parse_gitconfig_with_comments() {
235 let config = r#"
236# This is a comment
237[user]
238 name = John Doe # inline comment
239 ; This is also a comment
240 email = john@example.com
241"#;
242
243 assert_eq!(
244 parse_gitconfig(config, "user", "name"),
245 Some("John Doe # inline comment".to_string())
246 );
247 assert_eq!(
248 parse_gitconfig(config, "user", "email"),
249 Some("john@example.com".to_string())
250 );
251 }
252
253 #[test]
254 fn test_parse_gitconfig_empty_values() {
255 let config = r#"
256[user]
257 name =
258 email = john@example.com
259"#;
260
261 assert_eq!(
262 parse_gitconfig(config, "user", "name"),
263 Some("".to_string())
264 );
265 assert_eq!(
266 parse_gitconfig(config, "user", "email"),
267 Some("john@example.com".to_string())
268 );
269 }
270
271 #[test]
272 fn test_get_git_user_functions_exist() {
273 let _name = get_git_user_name();
275 let _email = get_git_user_email();
276 }
277
278 #[test]
279 fn test_get_user_gitconfig_path() {
280 let path = get_user_gitconfig_path();
282
283 assert!(path.is_some(), "Should return a gitconfig path");
285
286 let path = path.unwrap();
287 assert!(
288 path.ends_with(".gitconfig"),
289 "Path should end with .gitconfig"
290 );
291 }
292
293 #[test]
294 fn test_get_gitconfig_paths() {
295 let paths = get_gitconfig_paths();
297 assert!(
298 !paths.is_empty(),
299 "Should return at least one gitconfig path"
300 );
301
302 assert!(
304 paths[0].ends_with(".gitconfig"),
305 "First path should be user config"
306 );
307 }
308
309 #[test]
310 fn test_cross_platform_home_detection() {
311 use std::env;
313
314 let original_home = env::var("HOME").ok();
316 let original_userprofile = env::var("USERPROFILE").ok();
317 let original_homedrive = env::var("HOMEDRIVE").ok();
318 let original_homepath = env::var("HOMEPATH").ok();
319
320 env::remove_var("USERPROFILE");
322 env::remove_var("HOMEDRIVE");
323 env::remove_var("HOMEPATH");
324 env::set_var("HOME", "/tmp/test-home");
325
326 let path = get_user_gitconfig_path();
327 assert!(path.is_some());
328 let path_buf = path.unwrap();
329 let path_str = path_buf.to_string_lossy();
330 assert!(path_str.contains("test-home"));
331 assert!(path_str.ends_with(".gitconfig"));
332
333 env::remove_var("HOME");
335 env::set_var("USERPROFILE", r"C:\Users\TestUser");
336
337 let path = get_user_gitconfig_path();
338 assert!(path.is_some());
339 let path_buf = path.unwrap();
340 let path_str = path_buf.to_string_lossy();
341 assert!(path_str.contains("TestUser"));
342 assert!(path_str.ends_with(".gitconfig"));
343
344 env::remove_var("USERPROFILE");
346 env::set_var("HOMEDRIVE", "C:");
347 env::set_var("HOMEPATH", r"\Users\TestUser2");
348
349 let path = get_user_gitconfig_path();
350 assert!(path.is_some());
351 let path_buf = path.unwrap();
352 let path_str = path_buf.to_string_lossy();
353 assert!(path_str.contains("TestUser2"));
354 assert!(path_str.ends_with(".gitconfig"));
355
356 env::remove_var("HOME");
358 env::remove_var("USERPROFILE");
359 env::remove_var("HOMEDRIVE");
360 env::remove_var("HOMEPATH");
361
362 if let Some(home) = original_home {
363 env::set_var("HOME", home);
364 }
365 if let Some(userprofile) = original_userprofile {
366 env::set_var("USERPROFILE", userprofile);
367 }
368 if let Some(homedrive) = original_homedrive {
369 env::set_var("HOMEDRIVE", homedrive);
370 }
371 if let Some(homepath) = original_homepath {
372 env::set_var("HOMEPATH", homepath);
373 }
374 }
375
376 #[test]
377 fn test_system_gitconfig_path_logic() {
378 let _path = get_system_gitconfig_path();
380 }
381}