Skip to content

Commit 08c50a4

Browse files
committed
fix: Properly handle boolean values such that a is true but a= is false. (#450)
This is even consistent when no booleans are used, such that `a` has no value as if it is not present, it's only available for booleans which must be specified.
1 parent 7a871c2 commit 08c50a4

File tree

6 files changed

+23
-23
lines changed

6 files changed

+23
-23
lines changed

git-config/src/file/access/read_only.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ impl<'event> File<'event> {
4040
/// let config = r#"
4141
/// [core]
4242
/// a = 10k
43-
/// c
43+
/// c = false
4444
/// "#;
4545
/// let git_config = git_config::File::try_from(config)?;
4646
/// // You can either use the turbofish to determine the type...

git-config/src/file/mutable/section.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ impl<'a, 'event> SectionMut<'a, 'event> {
9292
self.push(key, Some(value.into()));
9393
None
9494
}
95-
Some((_, value_range)) => {
95+
Some((key_range, value_range)) => {
96+
let value_range = value_range.unwrap_or(key_range.end - 1..key_range.end);
9697
let range_start = value_range.start;
9798
let ret = self.remove_internal(value_range);
9899
self.section

git-config/src/file/section/body.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ impl<'event> Body<'event> {
2020
pub fn value(&self, key: impl AsRef<str>) -> Option<Cow<'_, BStr>> {
2121
let key = Key::from_str_unchecked(key.as_ref());
2222
let (_key_range, range) = self.key_and_value_range_by(&key)?;
23+
let range = range?;
2324
let mut concatenated = BString::default();
2425

2526
for event in &self.0[range] {
@@ -109,9 +110,10 @@ impl<'event> Body<'event> {
109110
&self.0
110111
}
111112

112-
/// Returns the the range containing the value events for the `key`.
113-
/// If the value is not found, then this returns an empty range.
114-
pub(crate) fn key_and_value_range_by(&self, key: &Key<'_>) -> Option<(Range<usize>, Range<usize>)> {
113+
/// Returns the the range containing the value events for the `key`, with value range being `None` if there is no key-value separator
114+
/// and only a 'fake' Value event with an empty string in side.
115+
/// If the value is not found, `None` is returned.
116+
pub(crate) fn key_and_value_range_by(&self, key: &Key<'_>) -> Option<(Range<usize>, Option<Range<usize>>)> {
115117
let mut value_range = Range::default();
116118
let mut key_start = None;
117119
for (i, e) in self.0.iter().enumerate().rev() {
@@ -140,7 +142,8 @@ impl<'event> Body<'event> {
140142
// value end needs to be offset by one so that the last value's index
141143
// is included in the range
142144
let value_range = value_range.start..value_range.end + 1;
143-
(key_start..value_range.end, value_range)
145+
let key_range = key_start..value_range.end;
146+
(key_range, (value_range.start != key_start + 1).then(|| value_range))
144147
})
145148
}
146149
}

git-config/src/lib.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,6 @@
2222
//! - Legacy headers like `[section.subsection]` are supposed to be turned into to lower case and compared
2323
//! case-sensitively. We keep its case and compare case-insensitively.
2424
//!
25-
//! # Limitations
26-
//!
27-
//! - Keys like `a.b` are interpreted as true but `a.b = ` are interpreted as false in `git`.
28-
//! This library, however, considers both to be true due to its inability to differentiate them at present.
29-
//! Even to though the information is available, the API doesn't expose it.
30-
//!
3125
//! [^1]: When read values do not need normalization and it wasn't parsed in 'owned' mode.
3226
//!
3327
//! [`git-config` files]: https://git-scm.com/docs/git-config#_configuration_file

git-config/src/parse/nom/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,9 @@ fn config_value<'a>(i: &'a [u8], dispatch: &mut impl FnMut(Event<'a>)) -> IResul
288288
let (i, newlines) = value_impl(i, dispatch)?;
289289
Ok((i, newlines))
290290
} else {
291-
// TODO: remove this and fix everything, or else we can't fix the boolean limitation
291+
// This is a special way of denoting 'empty' values which a lot of code depends on.
292+
// Hence, rather to fix this everywhere else, leave it here and fix it where it matters, namely
293+
// when it's about differentiating between a missing key-vaue separator, and one followed by emptiness.
292294
dispatch(Event::Value(Cow::Borrowed("".into())));
293295
Ok((i, 0))
294296
}

git-config/tests/file/access/read_only.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@ fn get_value_for_all_provided_values() -> crate::Result {
4141
assert!(!config.boolean("core", None, "bool-explicit").expect("exists")?);
4242

4343
assert!(
44-
!config.value::<Boolean>("core", None, "bool-implicit")?.0,
44+
config.value::<Boolean>("core", None, "bool-implicit").is_err(),
4545
"this cannot work like in git as the value isn't there for us"
4646
);
4747
assert!(
48-
!config.boolean("core", None, "bool-implicit").expect("present")?,
49-
"this should work, but doesn't yet"
48+
config.boolean("core", None, "bool-implicit").expect("present")?,
49+
"this should work"
5050
);
5151

5252
assert_eq!(config.string("doesnt", None, "exist"), None);
@@ -177,11 +177,11 @@ fn get_value_looks_up_all_sections_before_failing() -> crate::Result {
177177

178178
#[test]
179179
fn section_names_are_case_insensitive() -> crate::Result {
180-
let config = "[core] bool-implicit";
180+
let config = "[core] a=true";
181181
let file = File::try_from(config)?;
182182
assert_eq!(
183-
file.value::<Boolean>("core", None, "bool-implicit").unwrap(),
184-
file.value::<Boolean>("CORE", None, "bool-implicit").unwrap()
183+
file.value::<Boolean>("core", None, "a").unwrap(),
184+
file.value::<Boolean>("CORE", None, "a").unwrap()
185185
);
186186

187187
Ok(())
@@ -209,13 +209,13 @@ fn single_section() {
209209
assert_eq!(first_value, cow_str("b"));
210210

211211
assert!(
212-
config.raw_value("core", None, "c").is_ok(),
213-
"value is considered false as it is without '=', so it's like not present, BUT this parses strangely which needs fixing (see TODO nom parse)"
212+
config.raw_value("core", None, "c").is_err(),
213+
"value is considered false as it is without '=', so it's like not present"
214214
);
215215

216216
assert!(
217-
!config.boolean("core", None, "c").expect("present").unwrap(),
218-
"asking for a boolean is true true, as per git rules, but doesn't work yet"
217+
config.boolean("core", None, "c").expect("present").unwrap(),
218+
"asking for a boolean is true true, as per git rules"
219219
);
220220
}
221221

0 commit comments

Comments
 (0)