1use std::fmt::Debug;
2
3use command_error::CommandExt;
4use command_error::OutputContext;
5use miette::miette;
6use tracing::instrument;
7use utf8_command::Utf8Output;
8
9use super::GitLike;
10
11#[repr(transparent)]
13pub struct GitConfig<'a, G>(&'a G);
14
15impl<G> Debug for GitConfig<'_, G>
16where
17 G: GitLike,
18{
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 f.debug_tuple("GitConfig")
21 .field(&self.0.get_current_dir().as_ref())
22 .finish()
23 }
24}
25
26impl<'a, G> GitConfig<'a, G>
27where
28 G: GitLike,
29{
30 pub fn new(git: &'a G) -> Self {
31 Self(git)
32 }
33
34 pub fn get_and<R>(
36 &self,
37 key: &str,
38 parser: impl Fn(OutputContext<Utf8Output>, Option<String>) -> Result<R, command_error::Error>,
39 ) -> miette::Result<R> {
40 Ok(self
41 .0
42 .command()
43 .args(["config", "get", "--null", key])
44 .output_checked_as(|context: OutputContext<Utf8Output>| {
45 if context.status().success() {
46 match context.output().stdout.as_str().split_once('\0') {
48 Some((value, rest)) => {
49 if !rest.is_empty() {
50 tracing::warn!(
51 %key,
52 data=rest,
53 "Trailing data in `git config` output"
54 );
55 }
56 let value = value.to_owned();
57 parser(context, Some(value))
58 }
59 None => Err(context.error_msg("Output didn't contain any null bytes")),
60 }
61 } else if let Some(1) = context.status().code() {
62 parser(context, None)
63 } else {
64 Err(context.error())
65 }
66 })?)
67 }
68
69 #[instrument(level = "trace")]
71 pub fn get(&self, key: &str) -> miette::Result<Option<String>> {
72 self.get_and(key, |_, value| Ok(value))
73 }
74
75 #[instrument(level = "trace")]
77 pub fn is_bare(&self) -> miette::Result<bool> {
78 self.get_and("core.bare", |context, value| {
79 match value {
80 None => {
81 Ok(false)
83 }
84 Some(value) => match value.as_str() {
85 "true" => Ok(true),
86 "false" => Ok(false),
87 _ => Err(context.error_msg(miette!(
88 "Unexpected Git config value for `core.bare`: {value}"
89 ))),
90 },
91 }
92 })
93 }
94
95 #[instrument(level = "trace")]
97 pub fn set(&self, key: &str, value: &str) -> miette::Result<()> {
98 self.0
99 .command()
100 .args(["config", "set", key, value])
101 .output_checked_utf8()?;
102 Ok(())
103 }
104}