git_config_env/
param.rs

1use std::borrow::Cow;
2
3/// Read `GIT_CONFIG_PARAMETERS`
4///
5/// These are the `-c` parameters, passed from the `git` process to the subcommand.
6///
7/// See [`parse_parameter`] for how to parse the `-c` parameter.
8#[derive(Clone, Default, Debug, PartialEq, Eq)]
9pub struct ConfigParameters {
10    values: String,
11}
12
13impl ConfigParameters {
14    pub fn new() -> Self {
15        let values = std::env::var("GIT_CONFIG_PARAMETERS").unwrap_or_else(|_| Default::default());
16        Self { values }
17    }
18
19    pub fn iter(&self) -> ConfigParametersIter<'_> {
20        self.into_iter()
21    }
22}
23
24impl<'s> IntoIterator for &'s ConfigParameters {
25    type Item = (Cow<'s, str>, Option<Cow<'s, str>>);
26    type IntoIter = ConfigParametersIter<'s>;
27
28    fn into_iter(self) -> Self::IntoIter {
29        Self::IntoIter::new(&self.values)
30    }
31}
32
33/// Parse `GIT_CONFIG_PARAMETERS`
34///
35/// These are the `-c` parameters, passed from the `git` process to the subcommand.
36///
37/// See [`parse_parameter`] for how to parse the `-c` parameter.
38#[derive(Clone, Default, Debug, PartialEq, Eq)]
39pub struct ConfigParametersIter<'s> {
40    values: &'s str,
41}
42
43impl<'s> ConfigParametersIter<'s> {
44    pub fn new(values: &'s str) -> Self {
45        Self { values }
46    }
47
48    pub fn iter(&self) -> impl Iterator<Item = (Cow<'_, str>, Cow<'_, str>)> + '_ {
49        None.into_iter()
50    }
51}
52
53impl<'s> Iterator for ConfigParametersIter<'s> {
54    type Item = (Cow<'s, str>, Option<Cow<'s, str>>);
55
56    fn next(&mut self) -> Option<Self::Item> {
57        // See git's config.c's `parse_config_env_list`
58        self.values = self.values.trim_start();
59        let key = crate::quote::sq_dequote(&mut self.values).ok()?;
60
61        if let Some(values) = self.values.strip_prefix('=') {
62            // new-style 'key'='value'
63            self.values = values;
64
65            if self.values.is_empty() {
66                Some((key, None))
67            } else if let Some(values) = self.values.strip_prefix(' ') {
68                self.values = values;
69                Some((key, None))
70            } else {
71                let value = crate::quote::sq_dequote(&mut self.values).ok()?;
72                Some((key, Some(value)))
73            }
74        } else {
75            // old-style 'key=value'
76            if self.values.is_empty() {
77                Some(parse_parameter_cow(key))
78            } else if let Some(values) = self.values.strip_prefix(' ') {
79                self.values = values;
80                Some(parse_parameter_cow(key))
81            } else {
82                self.values = "";
83                None
84            }
85        }
86    }
87}
88
89#[cfg(test)]
90mod test_env {
91    use super::*;
92
93    #[test]
94    fn empty() {
95        let fixture = "";
96        let config = ConfigParametersIter::new(fixture);
97        let actual: Vec<_> = config.collect();
98        assert_eq!(actual, vec![]);
99    }
100
101    #[test]
102    fn test_old() {
103        let fixture = "'delta.plus-style=green'";
104        let config = ConfigParametersIter::new(fixture);
105        let actual: Vec<_> = config.collect();
106        assert_eq!(
107            actual,
108            vec![(
109                Cow::Borrowed("delta.plus-style"),
110                Some(Cow::Borrowed("green"))
111            )]
112        );
113    }
114
115    #[test]
116    fn test_old_bool() {
117        let fixture = "'delta.plus-style'";
118        let config = ConfigParametersIter::new(fixture);
119        let actual: Vec<_> = config.collect();
120        assert_eq!(actual, vec![(Cow::Borrowed("delta.plus-style"), None)]);
121    }
122
123    #[test]
124    fn test_old_multiple() {
125        let fixture = "'delta.plus-style=green' 'delta.plus-style' 'delta.plus-style=green'";
126        let config = ConfigParametersIter::new(fixture);
127        let actual: Vec<_> = config.collect();
128        assert_eq!(
129            actual,
130            vec![
131                (
132                    Cow::Borrowed("delta.plus-style"),
133                    Some(Cow::Borrowed("green"))
134                ),
135                (Cow::Borrowed("delta.plus-style"), None),
136                (
137                    Cow::Borrowed("delta.plus-style"),
138                    Some(Cow::Borrowed("green"))
139                ),
140            ]
141        );
142    }
143
144    #[test]
145    fn test_new() {
146        let fixture = "'delta.plus-style'='green'";
147        let config = ConfigParametersIter::new(fixture);
148        let actual: Vec<_> = config.collect();
149        assert_eq!(
150            actual,
151            vec![(
152                Cow::Borrowed("delta.plus-style"),
153                Some(Cow::Borrowed("green"))
154            )]
155        );
156    }
157
158    #[test]
159    fn test_new_bool() {
160        let fixture = "'delta.plus-style'=";
161        let config = ConfigParametersIter::new(fixture);
162        let actual: Vec<_> = config.collect();
163        assert_eq!(actual, vec![(Cow::Borrowed("delta.plus-style"), None)]);
164    }
165
166    #[test]
167    fn test_new_multiple() {
168        let fixture = "'delta.plus-style'='green' 'delta.plus-style'= 'delta.plus-style'='green'";
169        let config = ConfigParametersIter::new(fixture);
170        let actual: Vec<_> = config.collect();
171        assert_eq!(
172            actual,
173            vec![
174                (
175                    Cow::Borrowed("delta.plus-style"),
176                    Some(Cow::Borrowed("green"))
177                ),
178                (Cow::Borrowed("delta.plus-style"), None),
179                (
180                    Cow::Borrowed("delta.plus-style"),
181                    Some(Cow::Borrowed("green"))
182                ),
183            ]
184        );
185    }
186}
187
188/// Parse a command line argument into a key/value pair
189///
190/// When the `value` is `None`, it is implied to be a `true` boolean entry
191pub fn parse_parameter(arg: &str) -> (&str, Option<&str>) {
192    // When we see:
193    //
194    //   section.subsection=with=equals.key=value
195    //
196    // we cannot tell if it means:
197    //
198    //   [section "subsection=with=equals"]
199    //   key = value
200    //
201    // or:
202    //
203    //   [section]
204    //   subsection = with=equals.key=value
205    //
206    // We parse left-to-right for the first "=", meaning we'll prefer to
207    // keep the value intact over the subsection. This is historical, but
208    // also sensible since values are more likely to contain odd or
209    // untrusted input than a section name.
210    //
211    // A missing equals is explicitly allowed (as a bool-only entry).
212    //
213    // See git's config.c's `git_config_push_parameter`
214    arg.split_once('=')
215        .map(|(k, v)| (k, Some(v)))
216        .unwrap_or((arg, None))
217}
218
219fn parse_parameter_cow(arg: Cow<'_, str>) -> (Cow<'_, str>, Option<Cow<'_, str>>) {
220    match arg {
221        Cow::Borrowed(arg) => {
222            let (key, value) = parse_parameter(arg);
223            (Cow::Borrowed(key), value.map(Cow::Borrowed))
224        }
225        Cow::Owned(arg) => {
226            let (key, value) = parse_parameter(arg.as_str());
227            (
228                Cow::Owned(key.to_owned()),
229                value.map(|v| Cow::Owned(v.to_owned())),
230            )
231        }
232    }
233}
234
235#[cfg(test)]
236mod test_parse_parameter {
237    use super::*;
238
239    #[test]
240    fn basic() {
241        let fixture = "key=value";
242        let expected = ("key", Some("value"));
243        let actual = parse_parameter(fixture);
244        assert_eq!(actual, expected);
245    }
246
247    #[test]
248    fn implied_bool() {
249        let fixture = "key";
250        let expected = ("key", None);
251        let actual = parse_parameter(fixture);
252        assert_eq!(actual, expected);
253    }
254
255    #[test]
256    fn multiple_eq() {
257        let fixture = "section.subsection=with=equals.key=value";
258        let expected = ("section.subsection", Some("with=equals.key=value"));
259        let actual = parse_parameter(fixture);
260        assert_eq!(actual, expected);
261    }
262}