From: RISHAV DEWAN <rishavdewan10@gmail.com>
To: git@vger.kernel.org
Cc: Junio C Hamano <gitster@pobox.com>,
RISHAV DEWAN <rishavdewan10@gmail.com>
Subject: [PATCH v1] config: fix case-insensitive match for old-style [section.subsection]
Date: Tue, 30 Jun 2026 18:18:50 +0530 [thread overview]
Message-ID: <20260630124850.2498-1-rishavdewan10@gmail.com> (raw)
When writing to an old-style "[section.subsection]" header, the
config file parser always folds the subsection to lower case
(get_base_var() lowercases unconditionally), while the key given on
the command line keeps whatever case the caller typed
(do_parse_config_key() deliberately leaves the subsection segment
untouched). matches() compared these two forms with a plain strcmp(),
so a caller passing an upper-cased subsection (e.g. 'git config
section.Subsection.key value2' against a file containing
'[section.subsection]') never matched the existing key and a
duplicate line was appended instead of replacing the value. This was
a known, documented limitation (see the now-removed BUGS section of
git-config.adoc).
Track whether the currently active section was parsed case-
insensitively (old-style) or case-sensitively (new-style, quoted) in
struct config_store_data, and have matches() use that information to
compare the subsection segment of the key accordingly, instead of an
unconditional case-sensitive strcmp().
Signed-off-by: RISHAV DEWAN <rishavdewan10@gmail.com>
---
Documentation/git-config.adoc | 21 ---------------------
config.c | 18 +++++++++++++++++-
t/t1300-config.sh | 2 --
3 files changed, 17 insertions(+), 24 deletions(-)
diff --git a/Documentation/git-config.adoc b/Documentation/git-config.adoc
index 57af010ade..11dfd802b4 100644
--- a/Documentation/git-config.adoc
+++ b/Documentation/git-config.adoc
@@ -634,27 +634,6 @@ http.sslverify false
include::config.adoc[]
-BUGS
-----
-When using the deprecated `[section.subsection]` syntax, changing a value
-will result in adding a multi-line key instead of a change, if the subsection
-is given with at least one uppercase character. For example when the config
-looks like
-
---------
- [section.subsection]
- key = value1
---------
-
-and running `git config section.Subsection.key value2` will result in
-
---------
- [section.subsection]
- key = value1
- key = value2
---------
-
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/config.c b/config.c
index 6a0de86e3a..7f086fcd75 100644
--- a/config.c
+++ b/config.c
@@ -2594,6 +2594,7 @@ struct config_store_data {
} *parsed;
unsigned int parsed_nr, parsed_alloc, *seen, seen_nr, seen_alloc;
unsigned int key_seen:1, section_seen:1, is_keys_section:1;
+ unsigned int subsection_case_sensitive:1;
};
#define CONFIG_STORE_INIT { 0 }
@@ -2613,7 +2614,21 @@ static void config_store_data_clear(struct config_store_data *store)
static int matches(const char *key, const char *value,
const struct config_store_data *store)
{
- if (strcmp(key, store->key))
+ /*
+ * The subsection part of "key" (key[0..store->baselen)) was parsed
+ * out of the config file using the case sensitivity of whichever
+ * section header it came from (see store_aux_event()): old-style
+ * "[section.subsection]" headers are folded to lower case while
+ * parsing, so they must be compared case-insensitively against
+ * store->key, which preserves whatever case the caller passed on
+ * the command line. New-style "[section "Subsection"]" headers keep
+ * their case, so they need an exact, case-sensitive comparison.
+ */
+ int (*cmpfn)(const char *, const char *, size_t) =
+ store->subsection_case_sensitive ? strncasecmp : strncmp;
+
+ if (cmpfn(key, store->key, store->baselen) ||
+ strcmp(key + store->baselen, store->key + store->baselen))
return 0; /* not ours */
if (store->fixed_value && value)
return !strcmp(store->fixed_value, value);
@@ -2654,6 +2669,7 @@ static int store_aux_event(enum config_event_t type, size_t begin, size_t end,
!cmpfn(cs->var.buf, store->key, store->baselen);
if (store->is_keys_section) {
store->section_seen = 1;
+ store->subsection_case_sensitive = cs->subsection_case_sensitive;
ALLOC_GROW(store->seen, store->seen_nr + 1,
store->seen_alloc);
store->seen[store->seen_nr] = store->parsed_nr;
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 87ca11a127..eaa3b83990 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1499,7 +1499,6 @@ test_expect_success 'old-fashioned settings are case insensitive' '
EOF
q_to_tab >testConfig_expect <<-EOF &&
[V.A]
- r = value1
Qr = value2
EOF
git config -f testConfig_actual "V.A.r" value2 &&
@@ -1511,7 +1510,6 @@ test_expect_success 'old-fashioned settings are case insensitive' '
EOF
q_to_tab >testConfig_expect <<-EOF &&
[V.A]
- r = value1
Qr = value2
EOF
git config -f testConfig_actual "v.A.r" value2 &&
--
2.50.1 (Apple Git-155)
reply other threads:[~2026-06-30 12:49 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260630124850.2498-1-rishavdewan10@gmail.com \
--to=rishavdewan10@gmail.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox