use std::path::PathBuf;
use std::process::Command;
pub fn get_git_user_name() -> Option<String> {
if let Ok(output) = Command::new("git")
.args(["config", "--global", "user.name"])
.output()
{
if output.status.success() {
let name = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !name.is_empty() {
return Some(name);
}
}
}
read_gitconfig_value("user", "name")
}
pub fn get_git_user_email() -> Option<String> {
if let Ok(output) = Command::new("git")
.args(["config", "--global", "user.email"])
.output()
{
if output.status.success() {
let email = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !email.is_empty() {
return Some(email);
}
}
}
read_gitconfig_value("user", "email")
}
fn read_gitconfig_value(section: &str, key: &str) -> Option<String> {
use std::fs;
let gitconfig_paths = get_gitconfig_paths();
for gitconfig_path in gitconfig_paths {
if let Ok(content) = fs::read_to_string(&gitconfig_path) {
if let Some(value) = parse_gitconfig(&content, section, key) {
return Some(value);
}
}
}
None
}
fn get_gitconfig_paths() -> Vec<PathBuf> {
let mut paths = Vec::new();
if let Some(user_config) = get_user_gitconfig_path() {
paths.push(user_config);
}
if let Some(system_config) = get_system_gitconfig_path() {
paths.push(system_config);
}
paths
}
fn get_user_gitconfig_path() -> Option<PathBuf> {
let home_dir = if let Ok(home) = std::env::var("HOME") {
PathBuf::from(home)
} else if let Ok(userprofile) = std::env::var("USERPROFILE") {
PathBuf::from(userprofile)
} else if let Ok(homedrive) = std::env::var("HOMEDRIVE") {
if let Ok(homepath) = std::env::var("HOMEPATH") {
PathBuf::from(homedrive).join(homepath)
} else {
return None;
}
} else {
return None;
};
Some(home_dir.join(".gitconfig"))
}
fn get_system_gitconfig_path() -> Option<PathBuf> {
if cfg!(windows) {
let possible_paths = vec![
PathBuf::from(r"C:\ProgramData\Git\config"),
PathBuf::from(r"C:\Program Files\Git\etc\gitconfig"),
PathBuf::from(r"C:\Program Files (x86)\Git\etc\gitconfig"),
];
for path in possible_paths {
if path.exists() {
return Some(path);
}
}
None
} else {
Some(PathBuf::from("/etc/gitconfig"))
}
}
fn parse_gitconfig(content: &str, target_section: &str, target_key: &str) -> Option<String> {
let mut in_target_section = false;
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') || line.starts_with(';') {
continue;
}
if line.starts_with('[') && line.ends_with(']') {
let section = &line[1..line.len() - 1];
in_target_section = section.trim().eq_ignore_ascii_case(target_section);
continue;
}
if in_target_section {
if let Some(eq_pos) = line.find('=') {
let key = line[..eq_pos].trim();
if key.eq_ignore_ascii_case(target_key) {
let value = line[eq_pos + 1..].trim();
let value = if (value.starts_with('"') && value.ends_with('"'))
|| (value.starts_with('\'') && value.ends_with('\''))
{
&value[1..value.len() - 1]
} else {
value
};
return Some(value.to_string());
}
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_gitconfig_basic() {
let config = r#"
[user]
name = John Doe
email = john@example.com
[core]
editor = vim
"#;
assert_eq!(
parse_gitconfig(config, "user", "name"),
Some("John Doe".to_string())
);
assert_eq!(
parse_gitconfig(config, "user", "email"),
Some("john@example.com".to_string())
);
assert_eq!(
parse_gitconfig(config, "core", "editor"),
Some("vim".to_string())
);
assert_eq!(parse_gitconfig(config, "user", "nonexistent"), None);
assert_eq!(parse_gitconfig(config, "nonexistent", "name"), None);
}
#[test]
fn test_parse_gitconfig_with_quotes() {
let config = r#"
[user]
name = "John Doe"
email = 'john@example.com'
"#;
assert_eq!(
parse_gitconfig(config, "user", "name"),
Some("John Doe".to_string())
);
assert_eq!(
parse_gitconfig(config, "user", "email"),
Some("john@example.com".to_string())
);
}
#[test]
fn test_parse_gitconfig_case_insensitive() {
let config = r#"
[USER]
NAME = John Doe
EMAIL = john@example.com
"#;
assert_eq!(
parse_gitconfig(config, "user", "name"),
Some("John Doe".to_string())
);
assert_eq!(
parse_gitconfig(config, "user", "email"),
Some("john@example.com".to_string())
);
}
#[test]
fn test_parse_gitconfig_with_comments() {
let config = r#"
# This is a comment
[user]
name = John Doe # inline comment
; This is also a comment
email = john@example.com
"#;
assert_eq!(
parse_gitconfig(config, "user", "name"),
Some("John Doe # inline comment".to_string())
);
assert_eq!(
parse_gitconfig(config, "user", "email"),
Some("john@example.com".to_string())
);
}
#[test]
fn test_parse_gitconfig_empty_values() {
let config = r#"
[user]
name =
email = john@example.com
"#;
assert_eq!(
parse_gitconfig(config, "user", "name"),
Some("".to_string())
);
assert_eq!(
parse_gitconfig(config, "user", "email"),
Some("john@example.com".to_string())
);
}
#[test]
fn test_get_git_user_functions_exist() {
let _name = get_git_user_name();
let _email = get_git_user_email();
}
#[test]
fn test_get_user_gitconfig_path() {
let path = get_user_gitconfig_path();
assert!(path.is_some(), "Should return a gitconfig path");
let path = path.unwrap();
assert!(
path.ends_with(".gitconfig"),
"Path should end with .gitconfig"
);
}
#[test]
fn test_get_gitconfig_paths() {
let paths = get_gitconfig_paths();
assert!(
!paths.is_empty(),
"Should return at least one gitconfig path"
);
assert!(
paths[0].ends_with(".gitconfig"),
"First path should be user config"
);
}
#[test]
fn test_cross_platform_home_detection() {
use std::env;
let original_home = env::var("HOME").ok();
let original_userprofile = env::var("USERPROFILE").ok();
let original_homedrive = env::var("HOMEDRIVE").ok();
let original_homepath = env::var("HOMEPATH").ok();
env::remove_var("USERPROFILE");
env::remove_var("HOMEDRIVE");
env::remove_var("HOMEPATH");
env::set_var("HOME", "/tmp/test-home");
let path = get_user_gitconfig_path();
assert!(path.is_some());
let path_buf = path.unwrap();
let path_str = path_buf.to_string_lossy();
assert!(path_str.contains("test-home"));
assert!(path_str.ends_with(".gitconfig"));
env::remove_var("HOME");
env::set_var("USERPROFILE", r"C:\Users\TestUser");
let path = get_user_gitconfig_path();
assert!(path.is_some());
let path_buf = path.unwrap();
let path_str = path_buf.to_string_lossy();
assert!(path_str.contains("TestUser"));
assert!(path_str.ends_with(".gitconfig"));
env::remove_var("USERPROFILE");
env::set_var("HOMEDRIVE", "C:");
env::set_var("HOMEPATH", r"\Users\TestUser2");
let path = get_user_gitconfig_path();
assert!(path.is_some());
let path_buf = path.unwrap();
let path_str = path_buf.to_string_lossy();
assert!(path_str.contains("TestUser2"));
assert!(path_str.ends_with(".gitconfig"));
env::remove_var("HOME");
env::remove_var("USERPROFILE");
env::remove_var("HOMEDRIVE");
env::remove_var("HOMEPATH");
if let Some(home) = original_home {
env::set_var("HOME", home);
}
if let Some(userprofile) = original_userprofile {
env::set_var("USERPROFILE", userprofile);
}
if let Some(homedrive) = original_homedrive {
env::set_var("HOMEDRIVE", homedrive);
}
if let Some(homepath) = original_homepath {
env::set_var("HOMEPATH", homepath);
}
}
#[test]
fn test_system_gitconfig_path_logic() {
let _path = get_system_gitconfig_path();
}
}