Skip to main content

auths_cli/adapters/
git_config.rs

1use std::path::PathBuf;
2
3use auths_sdk::ports::git_config::{GitConfigError, GitConfigProvider};
4
5/// System adapter for git signing configuration.
6///
7/// Runs `git config <scope> <key> <value>` via `std::process::Command`.
8/// Construct with `SystemGitConfigProvider::global()` or
9/// `SystemGitConfigProvider::local(repo_path)`.
10///
11/// Usage:
12/// ```ignore
13/// let provider = SystemGitConfigProvider::global();
14/// provider.set("gpg.format", "ssh")?;
15/// ```
16pub struct SystemGitConfigProvider {
17    scope_flag: &'static str,
18    working_dir: Option<PathBuf>,
19}
20
21impl SystemGitConfigProvider {
22    /// Creates a provider that sets git config in global scope.
23    pub fn global() -> Self {
24        Self {
25            scope_flag: "--global",
26            working_dir: None,
27        }
28    }
29
30    /// Creates a provider that sets git config in local scope for the given repo.
31    ///
32    /// Args:
33    /// * `repo_path`: Path to the git repository to configure.
34    pub fn local(repo_path: PathBuf) -> Self {
35        Self {
36            scope_flag: "--local",
37            working_dir: Some(repo_path),
38        }
39    }
40}
41
42impl GitConfigProvider for SystemGitConfigProvider {
43    fn set(&self, key: &str, value: &str) -> Result<(), GitConfigError> {
44        let mut cmd = crate::subprocess::git_command(&["config", self.scope_flag, key, value]);
45        if let Some(dir) = &self.working_dir {
46            cmd.current_dir(dir);
47        }
48        let status = cmd
49            .status()
50            .map_err(|e| GitConfigError::CommandFailed(format!("failed to run git config: {e}")))?;
51        if !status.success() {
52            return Err(GitConfigError::CommandFailed(format!(
53                "git config {} = {} failed",
54                key, value
55            )));
56        }
57        Ok(())
58    }
59
60    fn get(&self, key: &str) -> Result<Option<String>, GitConfigError> {
61        let mut cmd = crate::subprocess::git_command(&["config", self.scope_flag, "--get", key]);
62        if let Some(dir) = &self.working_dir {
63            cmd.current_dir(dir);
64        }
65        let output = cmd
66            .output()
67            .map_err(|e| GitConfigError::CommandFailed(format!("failed to run git config: {e}")))?;
68        // Exit code 1 means the key is unset, which is not an error.
69        if !output.status.success() {
70            return Ok(None);
71        }
72        let value = String::from_utf8_lossy(&output.stdout).trim().to_string();
73        Ok((!value.is_empty()).then_some(value))
74    }
75
76    fn unset(&self, key: &str) -> Result<(), GitConfigError> {
77        let mut cmd = crate::subprocess::git_command(&["config", self.scope_flag, "--unset", key]);
78        if let Some(dir) = &self.working_dir {
79            cmd.current_dir(dir);
80        }
81        let output = cmd
82            .output()
83            .map_err(|e| GitConfigError::CommandFailed(format!("failed to run git config: {e}")))?;
84        // Exit code 5 means the key was not set — treat as success
85        if !output.status.success() && output.status.code() != Some(5) {
86            return Err(GitConfigError::CommandFailed(format!(
87                "git config --unset {} failed",
88                key
89            )));
90        }
91        Ok(())
92    }
93}