git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [PATCH 0/6] [RFC] config.c: use struct for config reading state
@ 2023-03-01  0:38 Glen Choo via GitGitGadget
  2023-03-01  0:38 ` [PATCH 1/6] config.c: plumb config_source through static fns Glen Choo via GitGitGadget
                   ` (8 more replies)
  0 siblings, 9 replies; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-01  0:38 UTC (permalink / raw)
  To: git; +Cc: Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee, Glen Choo

This RFC is preparation for config.[ch] to be libified as as part of the
libification effort that Emily described in [1]. One of the first goals is
to read config from a file, but the trouble with how config.c is written
today is that all reading operations rely on global state, so before turning
that into a library, we'd want to make that state non-global.

This series gets us about halfway there; it does enough plumbing for a
workable-but-kinda-ugly library interface, but with a little bit more work,
I think we can get rid of global state in-tree as well. That requires a fair
amount of work though, so I'd like to get thoughts on that before starting
work.

= Description

This series extracts the global config reading state into "struct
config_reader" and plumbs it through the config reading machinery. It's very
similar to how we've plumbed "struct repository" and other 'context objects'
in the past, except:

 * The global state (named "the_reader") for the git process lives in a
   config.c static variable, and not on "the_repository". See 3/6 for the
   rationale.

 * I've stopped short of adding "struct config_reader" to config.h public
   functions, since that would affect non-config.c callers.

If we stop right here, it's quite easy to extend it to a future config-lib.h
without having to adjust the config.h interface:

 * Move the core config reading functionality from config.c to config-lib.c.

 * Have config-lib.h accept "struct config_reader" as an arg.

 * Have config.h call config-lib.h while passing "the_reader".

and I have some WIP patches that do just that [3], but I think they can be
significantly improved if we go a bit further...

= Leftover bits and RFC

With a bit more work on the config machinery, we could make it so that
config reading stops being global even without adjusting non-config.c
callers. The idea is pretty simple: have the config machinery initialize an
internal "struct config_reader" every time we read config and expose that
state to the config callbacks (instead of, in this series, asking the caller
to initialize "struct config_reader" themselves). I believe that only config
callbacks are accessing this state, e.g. because they use the low-level
information (like builtin/config.c printing the filename and scope of the
value) or for error reporting (like git_parse_int() reporting the filename
and line number of the value it failed to parse), and only config callbacks
should be accessing this state anyway.

The catch (aka the reason I stopped halfway through) is that I couldn't find
a way to expose "struct config_reader" state without some fairly big
changes, complexity-wise or LoC-wise, e.g.

 * We could add "struct config_reader" to "config_fn_t", i.e.
   
   -typedef int (*config_fn_t)(const char *var, const char *val, void
   *data); +typedef int (*config_fn_t)(const struct config_reader *reader,
   const char *var, const char *val, void *data);
   
   which isn't complex at all, except that there are ~100 config_fn_t
   implementations [3] and a good number of them may never reference
   "reader". If the churn is tolerable, I think this a good way forward.

 * We could create a new kind of "config_fn_t" that accepts "struct
   config_reader", e.g.
   
   typedef int (*config_fn_t)(const char *var, const char *val, void *data);
   +typedef int (*config_state_fn_t)(const struct config_reader *reader,
   const char *var, const char *val, void *data);
   
   and only adjust the callers that would actually reference "reader". This
   is less churn, but I couldn't find a great way to do this kind of
   'switching between config callback types' elegantly.

 * We could smuggle "struct config_reader" to callback functions in a way
   that interested callers could see it, but uninterested callers could
   ignore. One trick that Jonathan Tan came up with (though not necessarily
   endorsed) would be to allocate a struct for the config value + "struct
   config_reader", then, interested callers could use "offset_of" to recover
   the "struct config_reader". It's a little hacky, but it's low-churn.

= Questions

 * Is this worth merging without the extra work? There are some cleanups in
   this series that could make it valuable, but there are also some hacks
   (see 4/6) that aren't so great.
 * Is the extra work even worth it?
 * Do any of the ideas seem more promising than the others? Are there other
   ideas I'm missing?

[1]
https://lore.kernel.org/git/CAJoAoZ=Cig_kLocxKGax31sU7Xe4==BGzC__Bg2_pr7krNq6MA@mail.gmail.com
[2]
https://github.com/chooglen/git/compare/config/structify-reading...chooglen:git:config/read-without-globals
[3] This is a rough estimate based on "git grep"-ing callers of the config.h
functions. I vaguely recall callbacks being called "old-style", with the
suggestion that we should replace them with the "new-style" constant time
git_config_get_*() family of functions. That would decrease the number of
config callbacks significantly.

Glen Choo (6):
  config.c: plumb config_source through static fns
  config.c: don't assign to "cf" directly
  config.c: create config_reader and the_reader
  config.c: plumb the_reader through callbacks
  config.c: remove current_config_kvi
  config.c: remove current_parsing_scope

 config.c | 489 ++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 287 insertions(+), 202 deletions(-)


base-commit: dadc8e6dacb629f46aee39bde90b6f09b73722eb
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1463%2Fchooglen%2Fconfig%2Fstructify-reading-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1463/chooglen/config/structify-reading-v1
Pull-Request: https://github.com/git/git/pull/1463
-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 72+ messages in thread

* [PATCH 1/6] config.c: plumb config_source through static fns
  2023-03-01  0:38 [PATCH 0/6] [RFC] config.c: use struct for config reading state Glen Choo via GitGitGadget
@ 2023-03-01  0:38 ` Glen Choo via GitGitGadget
  2023-03-03 18:02   ` Junio C Hamano
  2023-03-01  0:38 ` [PATCH 2/6] config.c: don't assign to "cf" directly Glen Choo via GitGitGadget
                   ` (7 subsequent siblings)
  8 siblings, 1 reply; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-01  0:38 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee, Glen Choo,
	Glen Choo

From: Glen Choo <chooglen@google.com>

This reduces the direct dependence on the "cf" static variable, which
will make it easier to remove in a later commit. The plumbed arg is
named "cs" to differentiate between it and the static variable.

In some cases (public functions and config callback functions), there
isn't an obvious way to plumb "struct config_source" through function
args. As a workaround, add references to "cf" that we'll address in
later commits.

The remaining references to "cf" are direct assignments to "cf", which
we'll also address in a later commit.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 192 +++++++++++++++++++++++++++++--------------------------
 1 file changed, 100 insertions(+), 92 deletions(-)

diff --git a/config.c b/config.c
index 00090a32fc3..84ae97741ac 100644
--- a/config.c
+++ b/config.c
@@ -156,7 +156,8 @@ static const char include_depth_advice[] = N_(
 "from\n"
 "	%s\n"
 "This might be due to circular includes.");
-static int handle_path_include(const char *path, struct config_include_data *inc)
+static int handle_path_include(struct config_source *cs, const char *path,
+			       struct config_include_data *inc)
 {
 	int ret = 0;
 	struct strbuf buf = STRBUF_INIT;
@@ -177,14 +178,14 @@ static int handle_path_include(const char *path, struct config_include_data *inc
 	if (!is_absolute_path(path)) {
 		char *slash;
 
-		if (!cf || !cf->path) {
+		if (!cs || !cs->path) {
 			ret = error(_("relative config includes must come from files"));
 			goto cleanup;
 		}
 
-		slash = find_last_dir_sep(cf->path);
+		slash = find_last_dir_sep(cs->path);
 		if (slash)
-			strbuf_add(&buf, cf->path, slash - cf->path + 1);
+			strbuf_add(&buf, cs->path, slash - cs->path + 1);
 		strbuf_addstr(&buf, path);
 		path = buf.buf;
 	}
@@ -192,8 +193,8 @@ static int handle_path_include(const char *path, struct config_include_data *inc
 	if (!access_or_die(path, R_OK, 0)) {
 		if (++inc->depth > MAX_INCLUDE_DEPTH)
 			die(_(include_depth_advice), MAX_INCLUDE_DEPTH, path,
-			    !cf ? "<unknown>" :
-			    cf->name ? cf->name :
+			    !cs ? "<unknown>" :
+			    cs->name ? cs->name :
 			    "the command line");
 		ret = git_config_from_file(git_config_include, path, inc);
 		inc->depth--;
@@ -210,7 +211,8 @@ static void add_trailing_starstar_for_dir(struct strbuf *pat)
 		strbuf_addstr(pat, "**");
 }
 
-static int prepare_include_condition_pattern(struct strbuf *pat)
+static int prepare_include_condition_pattern(struct config_source *cs,
+					     struct strbuf *pat)
 {
 	struct strbuf path = STRBUF_INIT;
 	char *expanded;
@@ -226,11 +228,11 @@ static int prepare_include_condition_pattern(struct strbuf *pat)
 	if (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {
 		const char *slash;
 
-		if (!cf || !cf->path)
+		if (!cs || !cs->path)
 			return error(_("relative config include "
 				       "conditionals must come from files"));
 
-		strbuf_realpath(&path, cf->path, 1);
+		strbuf_realpath(&path, cs->path, 1);
 		slash = find_last_dir_sep(path.buf);
 		if (!slash)
 			BUG("how is this possible?");
@@ -245,7 +247,8 @@ static int prepare_include_condition_pattern(struct strbuf *pat)
 	return prefix;
 }
 
-static int include_by_gitdir(const struct config_options *opts,
+static int include_by_gitdir(struct config_source *cs,
+			     const struct config_options *opts,
 			     const char *cond, size_t cond_len, int icase)
 {
 	struct strbuf text = STRBUF_INIT;
@@ -261,7 +264,7 @@ static int include_by_gitdir(const struct config_options *opts,
 
 	strbuf_realpath(&text, git_dir, 1);
 	strbuf_add(&pattern, cond, cond_len);
-	prefix = prepare_include_condition_pattern(&pattern);
+	prefix = prepare_include_condition_pattern(cs, &pattern);
 
 again:
 	if (prefix < 0)
@@ -406,15 +409,16 @@ static int include_by_remote_url(struct config_include_data *inc,
 					     inc->remote_urls);
 }
 
-static int include_condition_is_true(struct config_include_data *inc,
+static int include_condition_is_true(struct config_source *cs,
+				     struct config_include_data *inc,
 				     const char *cond, size_t cond_len)
 {
 	const struct config_options *opts = inc->opts;
 
 	if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
-		return include_by_gitdir(opts, cond, cond_len, 0);
+		return include_by_gitdir(cs, opts, cond, cond_len, 0);
 	else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
-		return include_by_gitdir(opts, cond, cond_len, 1);
+		return include_by_gitdir(cs, opts, cond, cond_len, 1);
 	else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
 		return include_by_branch(cond, cond_len);
 	else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,
@@ -441,16 +445,16 @@ static int git_config_include(const char *var, const char *value, void *data)
 		return ret;
 
 	if (!strcmp(var, "include.path"))
-		ret = handle_path_include(value, inc);
+		ret = handle_path_include(cf, value, inc);
 
 	if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
-	    cond && include_condition_is_true(inc, cond, cond_len) &&
+	    cond && include_condition_is_true(cf, inc, cond, cond_len) &&
 	    !strcmp(key, "path")) {
 		config_fn_t old_fn = inc->fn;
 
 		if (inc->opts->unconditional_remote_url)
 			inc->fn = forbid_remote_url;
-		ret = handle_path_include(value, inc);
+		ret = handle_path_include(cf, value, inc);
 		inc->fn = old_fn;
 	}
 
@@ -777,21 +781,21 @@ out:
 	return ret;
 }
 
-static int get_next_char(void)
+static int get_next_char(struct config_source *cs)
 {
-	int c = cf->do_fgetc(cf);
+	int c = cs->do_fgetc(cs);
 
 	if (c == '\r') {
 		/* DOS like systems */
-		c = cf->do_fgetc(cf);
+		c = cs->do_fgetc(cs);
 		if (c != '\n') {
 			if (c != EOF)
-				cf->do_ungetc(c, cf);
+				cs->do_ungetc(c, cs);
 			c = '\r';
 		}
 	}
 
-	if (c != EOF && ++cf->total_len > INT_MAX) {
+	if (c != EOF && ++cs->total_len > INT_MAX) {
 		/*
 		 * This is an absurdly long config file; refuse to parse
 		 * further in order to protect downstream code from integer
@@ -799,38 +803,38 @@ static int get_next_char(void)
 		 * but we can mark EOF and put trash in the return value,
 		 * which will trigger a parse error.
 		 */
-		cf->eof = 1;
+		cs->eof = 1;
 		return 0;
 	}
 
 	if (c == '\n')
-		cf->linenr++;
+		cs->linenr++;
 	if (c == EOF) {
-		cf->eof = 1;
-		cf->linenr++;
+		cs->eof = 1;
+		cs->linenr++;
 		c = '\n';
 	}
 	return c;
 }
 
-static char *parse_value(void)
+static char *parse_value(struct config_source *cs)
 {
 	int quote = 0, comment = 0, space = 0;
 
-	strbuf_reset(&cf->value);
+	strbuf_reset(&cs->value);
 	for (;;) {
-		int c = get_next_char();
+		int c = get_next_char(cs);
 		if (c == '\n') {
 			if (quote) {
-				cf->linenr--;
+				cs->linenr--;
 				return NULL;
 			}
-			return cf->value.buf;
+			return cs->value.buf;
 		}
 		if (comment)
 			continue;
 		if (isspace(c) && !quote) {
-			if (cf->value.len)
+			if (cs->value.len)
 				space++;
 			continue;
 		}
@@ -841,9 +845,9 @@ static char *parse_value(void)
 			}
 		}
 		for (; space; space--)
-			strbuf_addch(&cf->value, ' ');
+			strbuf_addch(&cs->value, ' ');
 		if (c == '\\') {
-			c = get_next_char();
+			c = get_next_char(cs);
 			switch (c) {
 			case '\n':
 				continue;
@@ -863,18 +867,19 @@ static char *parse_value(void)
 			default:
 				return NULL;
 			}
-			strbuf_addch(&cf->value, c);
+			strbuf_addch(&cs->value, c);
 			continue;
 		}
 		if (c == '"') {
 			quote = 1-quote;
 			continue;
 		}
-		strbuf_addch(&cf->value, c);
+		strbuf_addch(&cs->value, c);
 	}
 }
 
-static int get_value(config_fn_t fn, void *data, struct strbuf *name)
+static int get_value(struct config_source *cs, config_fn_t fn, void *data,
+		     struct strbuf *name)
 {
 	int c;
 	char *value;
@@ -882,8 +887,8 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
 
 	/* Get the full name */
 	for (;;) {
-		c = get_next_char();
-		if (cf->eof)
+		c = get_next_char(cs);
+		if (cs->eof)
 			break;
 		if (!iskeychar(c))
 			break;
@@ -891,13 +896,13 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
 	}
 
 	while (c == ' ' || c == '\t')
-		c = get_next_char();
+		c = get_next_char(cs);
 
 	value = NULL;
 	if (c != '\n') {
 		if (c != '=')
 			return -1;
-		value = parse_value();
+		value = parse_value(cs);
 		if (!value)
 			return -1;
 	}
@@ -906,20 +911,21 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
 	 * the line we just parsed during the call to fn to get
 	 * accurate line number in error messages.
 	 */
-	cf->linenr--;
+	cs->linenr--;
 	ret = fn(name->buf, value, data);
 	if (ret >= 0)
-		cf->linenr++;
+		cs->linenr++;
 	return ret;
 }
 
-static int get_extended_base_var(struct strbuf *name, int c)
+static int get_extended_base_var(struct config_source *cs, struct strbuf *name,
+				 int c)
 {
-	cf->subsection_case_sensitive = 0;
+	cs->subsection_case_sensitive = 0;
 	do {
 		if (c == '\n')
 			goto error_incomplete_line;
-		c = get_next_char();
+		c = get_next_char(cs);
 	} while (isspace(c));
 
 	/* We require the format to be '[base "extension"]' */
@@ -928,13 +934,13 @@ static int get_extended_base_var(struct strbuf *name, int c)
 	strbuf_addch(name, '.');
 
 	for (;;) {
-		int c = get_next_char();
+		int c = get_next_char(cs);
 		if (c == '\n')
 			goto error_incomplete_line;
 		if (c == '"')
 			break;
 		if (c == '\\') {
-			c = get_next_char();
+			c = get_next_char(cs);
 			if (c == '\n')
 				goto error_incomplete_line;
 		}
@@ -942,25 +948,25 @@ static int get_extended_base_var(struct strbuf *name, int c)
 	}
 
 	/* Final ']' */
-	if (get_next_char() != ']')
+	if (get_next_char(cs) != ']')
 		return -1;
 	return 0;
 error_incomplete_line:
-	cf->linenr--;
+	cs->linenr--;
 	return -1;
 }
 
-static int get_base_var(struct strbuf *name)
+static int get_base_var(struct config_source *cs, struct strbuf *name)
 {
-	cf->subsection_case_sensitive = 1;
+	cs->subsection_case_sensitive = 1;
 	for (;;) {
-		int c = get_next_char();
-		if (cf->eof)
+		int c = get_next_char(cs);
+		if (cs->eof)
 			return -1;
 		if (c == ']')
 			return 0;
 		if (isspace(c))
-			return get_extended_base_var(name, c);
+			return get_extended_base_var(cs, name, c);
 		if (!iskeychar(c) && c != '.')
 			return -1;
 		strbuf_addch(name, tolower(c));
@@ -973,7 +979,8 @@ struct parse_event_data {
 	const struct config_options *opts;
 };
 
-static int do_event(enum config_event_t type, struct parse_event_data *data)
+static int do_event(struct config_source *cs, enum config_event_t type,
+		    struct parse_event_data *data)
 {
 	size_t offset;
 
@@ -984,7 +991,7 @@ static int do_event(enum config_event_t type, struct parse_event_data *data)
 	    data->previous_type == type)
 		return 0;
 
-	offset = cf->do_ftell(cf);
+	offset = cs->do_ftell(cs);
 	/*
 	 * At EOF, the parser always "inserts" an extra '\n', therefore
 	 * the end offset of the event is the current file position, otherwise
@@ -1004,12 +1011,12 @@ static int do_event(enum config_event_t type, struct parse_event_data *data)
 	return 0;
 }
 
-static int git_parse_source(config_fn_t fn, void *data,
-			    const struct config_options *opts)
+static int git_parse_source(struct config_source *cs, config_fn_t fn,
+			    void *data, const struct config_options *opts)
 {
 	int comment = 0;
 	size_t baselen = 0;
-	struct strbuf *var = &cf->var;
+	struct strbuf *var = &cs->var;
 	int error_return = 0;
 	char *error_msg = NULL;
 
@@ -1024,7 +1031,7 @@ static int git_parse_source(config_fn_t fn, void *data,
 	for (;;) {
 		int c;
 
-		c = get_next_char();
+		c = get_next_char(cs);
 		if (bomptr && *bomptr) {
 			/* We are at the file beginning; skip UTF8-encoded BOM
 			 * if present. Sane editors won't put this in on their
@@ -1041,12 +1048,12 @@ static int git_parse_source(config_fn_t fn, void *data,
 			}
 		}
 		if (c == '\n') {
-			if (cf->eof) {
-				if (do_event(CONFIG_EVENT_EOF, &event_data) < 0)
+			if (cs->eof) {
+				if (do_event(cs, CONFIG_EVENT_EOF, &event_data) < 0)
 					return -1;
 				return 0;
 			}
-			if (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)
+			if (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
 				return -1;
 			comment = 0;
 			continue;
@@ -1054,23 +1061,23 @@ static int git_parse_source(config_fn_t fn, void *data,
 		if (comment)
 			continue;
 		if (isspace(c)) {
-			if (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)
+			if (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
 					return -1;
 			continue;
 		}
 		if (c == '#' || c == ';') {
-			if (do_event(CONFIG_EVENT_COMMENT, &event_data) < 0)
+			if (do_event(cs, CONFIG_EVENT_COMMENT, &event_data) < 0)
 					return -1;
 			comment = 1;
 			continue;
 		}
 		if (c == '[') {
-			if (do_event(CONFIG_EVENT_SECTION, &event_data) < 0)
+			if (do_event(cs, CONFIG_EVENT_SECTION, &event_data) < 0)
 					return -1;
 
 			/* Reset prior to determining a new stem */
 			strbuf_reset(var);
-			if (get_base_var(var) < 0 || var->len < 1)
+			if (get_base_var(cs, var) < 0 || var->len < 1)
 				break;
 			strbuf_addch(var, '.');
 			baselen = var->len;
@@ -1079,7 +1086,7 @@ static int git_parse_source(config_fn_t fn, void *data,
 		if (!isalpha(c))
 			break;
 
-		if (do_event(CONFIG_EVENT_ENTRY, &event_data) < 0)
+		if (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)
 			return -1;
 
 		/*
@@ -1089,42 +1096,42 @@ static int git_parse_source(config_fn_t fn, void *data,
 		 */
 		strbuf_setlen(var, baselen);
 		strbuf_addch(var, tolower(c));
-		if (get_value(fn, data, var) < 0)
+		if (get_value(cs, fn, data, var) < 0)
 			break;
 	}
 
-	if (do_event(CONFIG_EVENT_ERROR, &event_data) < 0)
+	if (do_event(cs, CONFIG_EVENT_ERROR, &event_data) < 0)
 		return -1;
 
-	switch (cf->origin_type) {
+	switch (cs->origin_type) {
 	case CONFIG_ORIGIN_BLOB:
 		error_msg = xstrfmt(_("bad config line %d in blob %s"),
-				      cf->linenr, cf->name);
+				      cs->linenr, cs->name);
 		break;
 	case CONFIG_ORIGIN_FILE:
 		error_msg = xstrfmt(_("bad config line %d in file %s"),
-				      cf->linenr, cf->name);
+				      cs->linenr, cs->name);
 		break;
 	case CONFIG_ORIGIN_STDIN:
 		error_msg = xstrfmt(_("bad config line %d in standard input"),
-				      cf->linenr);
+				      cs->linenr);
 		break;
 	case CONFIG_ORIGIN_SUBMODULE_BLOB:
 		error_msg = xstrfmt(_("bad config line %d in submodule-blob %s"),
-				       cf->linenr, cf->name);
+				       cs->linenr, cs->name);
 		break;
 	case CONFIG_ORIGIN_CMDLINE:
 		error_msg = xstrfmt(_("bad config line %d in command line %s"),
-				       cf->linenr, cf->name);
+				       cs->linenr, cs->name);
 		break;
 	default:
 		error_msg = xstrfmt(_("bad config line %d in %s"),
-				      cf->linenr, cf->name);
+				      cs->linenr, cs->name);
 	}
 
 	switch (opts && opts->error_action ?
 		opts->error_action :
-		cf->default_error_action) {
+		cs->default_error_action) {
 	case CONFIG_ERROR_DIE:
 		die("%s", error_msg);
 		break;
@@ -1266,7 +1273,8 @@ int git_parse_ssize_t(const char *value, ssize_t *ret)
 }
 
 NORETURN
-static void die_bad_number(const char *name, const char *value)
+static void die_bad_number(struct config_source *cs, const char *name,
+			   const char *value)
 {
 	const char *error_type = (errno == ERANGE) ?
 		N_("out of range") : N_("invalid unit");
@@ -1275,28 +1283,28 @@ static void die_bad_number(const char *name, const char *value)
 	if (!value)
 		value = "";
 
-	if (!(cf && cf->name))
+	if (!(cs && cs->name))
 		die(_(bad_numeric), value, name, _(error_type));
 
-	switch (cf->origin_type) {
+	switch (cs->origin_type) {
 	case CONFIG_ORIGIN_BLOB:
 		die(_("bad numeric config value '%s' for '%s' in blob %s: %s"),
-		    value, name, cf->name, _(error_type));
+		    value, name, cs->name, _(error_type));
 	case CONFIG_ORIGIN_FILE:
 		die(_("bad numeric config value '%s' for '%s' in file %s: %s"),
-		    value, name, cf->name, _(error_type));
+		    value, name, cs->name, _(error_type));
 	case CONFIG_ORIGIN_STDIN:
 		die(_("bad numeric config value '%s' for '%s' in standard input: %s"),
 		    value, name, _(error_type));
 	case CONFIG_ORIGIN_SUBMODULE_BLOB:
 		die(_("bad numeric config value '%s' for '%s' in submodule-blob %s: %s"),
-		    value, name, cf->name, _(error_type));
+		    value, name, cs->name, _(error_type));
 	case CONFIG_ORIGIN_CMDLINE:
 		die(_("bad numeric config value '%s' for '%s' in command line %s: %s"),
-		    value, name, cf->name, _(error_type));
+		    value, name, cs->name, _(error_type));
 	default:
 		die(_("bad numeric config value '%s' for '%s' in %s: %s"),
-		    value, name, cf->name, _(error_type));
+		    value, name, cs->name, _(error_type));
 	}
 }
 
@@ -1304,7 +1312,7 @@ int git_config_int(const char *name, const char *value)
 {
 	int ret;
 	if (!git_parse_int(value, &ret))
-		die_bad_number(name, value);
+		die_bad_number(cf, name, value);
 	return ret;
 }
 
@@ -1312,7 +1320,7 @@ int64_t git_config_int64(const char *name, const char *value)
 {
 	int64_t ret;
 	if (!git_parse_int64(value, &ret))
-		die_bad_number(name, value);
+		die_bad_number(cf, name, value);
 	return ret;
 }
 
@@ -1320,7 +1328,7 @@ unsigned long git_config_ulong(const char *name, const char *value)
 {
 	unsigned long ret;
 	if (!git_parse_ulong(value, &ret))
-		die_bad_number(name, value);
+		die_bad_number(cf, name, value);
 	return ret;
 }
 
@@ -1328,7 +1336,7 @@ ssize_t git_config_ssize_t(const char *name, const char *value)
 {
 	ssize_t ret;
 	if (!git_parse_ssize_t(value, &ret))
-		die_bad_number(name, value);
+		die_bad_number(cf, name, value);
 	return ret;
 }
 
@@ -1948,7 +1956,7 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data,
 	strbuf_init(&top->var, 1024);
 	cf = top;
 
-	ret = git_parse_source(fn, data, opts);
+	ret = git_parse_source(cf, fn, data, opts);
 
 	/* pop config-file parsing state stack */
 	strbuf_release(&top->value);
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 2/6] config.c: don't assign to "cf" directly
  2023-03-01  0:38 [PATCH 0/6] [RFC] config.c: use struct for config reading state Glen Choo via GitGitGadget
  2023-03-01  0:38 ` [PATCH 1/6] config.c: plumb config_source through static fns Glen Choo via GitGitGadget
@ 2023-03-01  0:38 ` Glen Choo via GitGitGadget
  2023-03-01  0:38 ` [PATCH 3/6] config.c: create config_reader and the_reader Glen Choo via GitGitGadget
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-01  0:38 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee, Glen Choo,
	Glen Choo

From: Glen Choo <chooglen@google.com>

To make "cf" easier to remove, replace all direct assignments to it with
function calls. This refactor has an additional maintainability benefit:
all of these functions were manually implementing stack pop/push
semantics on "struct config_source", so replacing them with function
calls allows us to only implement this logic once.

In this process, perform some now-obvious clean ups:

- Drop some unnecessary "cf" assignments in populate_remote_urls().
  Since it was introduced in 399b198489 (config: include file if remote
  URL matches a glob, 2022-01-18), it has stored and restored the value
  of "cf" to ensure that it doesn't get accidentally mutated. However,
  this was never necessary since "do_config_from()" already pushes/pops
  "cf" further down the call chain.

- Zero out every "struct config_source" with a dedicated initializer.
  This matters because the "struct config_source" is assigned to "cf"
  and we later 'pop the stack' by assigning "cf = cf->prev", but
  "cf->prev" could be pointing to uninitialized garbage.

  Fortunately, this has never bothered us since we never try to read
  "cf" except while iterating through config, in which case, "cf" is
  either set to a sensible value (when parsing a file), or it is ignored
  (when iterating a configset). Later in the series, zero-ing out memory
  will also let us enforce the constraint that "cf" and
  "current_config_kvi" are never non-NULL together.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 40 ++++++++++++++++++++++++++--------------
 1 file changed, 26 insertions(+), 14 deletions(-)

diff --git a/config.c b/config.c
index 84ae97741ac..1f89addc771 100644
--- a/config.c
+++ b/config.c
@@ -49,6 +49,7 @@ struct config_source {
 	int (*do_ungetc)(int c, struct config_source *conf);
 	long (*do_ftell)(struct config_source *c);
 };
+#define CONFIG_SOURCE_INIT { 0 }
 
 /*
  * These variables record the "current" config source, which
@@ -78,6 +79,23 @@ static struct key_value_info *current_config_kvi;
  */
 static enum config_scope current_parsing_scope;
 
+static inline void config_state_push_source(struct config_source *top)
+{
+	if (cf)
+		top->prev = cf;
+	cf = top;
+}
+
+static inline struct config_source *config_state_pop_source()
+{
+	struct config_source *ret;
+	if (!cf)
+		BUG("tried to pop config source, but we weren't reading config");
+	ret = cf;
+	cf = cf->prev;
+	return ret;
+}
+
 static int pack_compression_seen;
 static int zlib_compression_seen;
 
@@ -345,14 +363,12 @@ static void populate_remote_urls(struct config_include_data *inc)
 {
 	struct config_options opts;
 
-	struct config_source *store_cf = cf;
 	struct key_value_info *store_kvi = current_config_kvi;
 	enum config_scope store_scope = current_parsing_scope;
 
 	opts = *inc->opts;
 	opts.unconditional_remote_url = 1;
 
-	cf = NULL;
 	current_config_kvi = NULL;
 	current_parsing_scope = 0;
 
@@ -360,7 +376,6 @@ static void populate_remote_urls(struct config_include_data *inc)
 	string_list_init_dup(inc->remote_urls);
 	config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);
 
-	cf = store_cf;
 	current_config_kvi = store_kvi;
 	current_parsing_scope = store_scope;
 }
@@ -714,12 +729,10 @@ int git_config_from_parameters(config_fn_t fn, void *data)
 	struct strvec to_free = STRVEC_INIT;
 	int ret = 0;
 	char *envw = NULL;
-	struct config_source source;
+	struct config_source source = CONFIG_SOURCE_INIT;
 
-	memset(&source, 0, sizeof(source));
-	source.prev = cf;
 	source.origin_type = CONFIG_ORIGIN_CMDLINE;
-	cf = &source;
+	config_state_push_source(&source);
 
 	env = getenv(CONFIG_COUNT_ENVIRONMENT);
 	if (env) {
@@ -777,7 +790,7 @@ out:
 	strbuf_release(&envvar);
 	strvec_clear(&to_free);
 	free(envw);
-	cf = source.prev;
+	config_state_pop_source();
 	return ret;
 }
 
@@ -1948,20 +1961,19 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data,
 	int ret;
 
 	/* push config-file parsing state stack */
-	top->prev = cf;
 	top->linenr = 1;
 	top->eof = 0;
 	top->total_len = 0;
 	strbuf_init(&top->value, 1024);
 	strbuf_init(&top->var, 1024);
-	cf = top;
+	config_state_push_source(top);
 
-	ret = git_parse_source(cf, fn, data, opts);
+	ret = git_parse_source(top, fn, data, opts);
 
 	/* pop config-file parsing state stack */
 	strbuf_release(&top->value);
 	strbuf_release(&top->var);
-	cf = top->prev;
+	config_state_pop_source();
 
 	return ret;
 }
@@ -1971,7 +1983,7 @@ static int do_config_from_file(config_fn_t fn,
 		const char *name, const char *path, FILE *f,
 		void *data, const struct config_options *opts)
 {
-	struct config_source top;
+	struct config_source top = CONFIG_SOURCE_INIT;
 	int ret;
 
 	top.u.file = f;
@@ -2023,7 +2035,7 @@ int git_config_from_mem(config_fn_t fn,
 			const char *name, const char *buf, size_t len,
 			void *data, const struct config_options *opts)
 {
-	struct config_source top;
+	struct config_source top = CONFIG_SOURCE_INIT;
 
 	top.u.buf.buf = buf;
 	top.u.buf.len = len;
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 3/6] config.c: create config_reader and the_reader
  2023-03-01  0:38 [PATCH 0/6] [RFC] config.c: use struct for config reading state Glen Choo via GitGitGadget
  2023-03-01  0:38 ` [PATCH 1/6] config.c: plumb config_source through static fns Glen Choo via GitGitGadget
  2023-03-01  0:38 ` [PATCH 2/6] config.c: don't assign to "cf" directly Glen Choo via GitGitGadget
@ 2023-03-01  0:38 ` Glen Choo via GitGitGadget
  2023-03-03 18:05   ` Junio C Hamano
  2023-03-01  0:38 ` [PATCH 4/6] config.c: plumb the_reader through callbacks Glen Choo via GitGitGadget
                   ` (5 subsequent siblings)
  8 siblings, 1 reply; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-01  0:38 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee, Glen Choo,
	Glen Choo

From: Glen Choo <chooglen@google.com>

Create "struct config_reader" to hold the state of the config source
currently being read. Then, create a static instance of it,
"the_reader", and use "the_reader.source" to replace references to "cf"
in public functions.

This doesn't create much immediate benefit (since we're mostly replacing
static variables with a bigger static variable), but it prepares us for
a future where this state doesn't have to be global; the "struct
config_reader" could be provided by the caller, or constructed
internally by a function like "do_config_from()".

A more typical approach would be to put this struct on "the_repository",
but that's a worse fit for this use case since config reading is not
scoped to a repository. E.g. we can read config before the repository is
known ("read_very_early_config()"), blatantly ignore the repo
("read_protected_config()"), or read only from a file
("git_config_from_file()"). This is especially evident in t5318 and
t9210, where test-tool and scalar parse config but don't fully
initialize "the_repository".

We could have also replaced the references to "cf" in callback functions
(which are the only ones left), but we'll eventually plumb "the_reader"
through the callback "*data" arg, which will allow us to rename "cf" to
"cs" without changing line lengths. Until we remove "cf" altogether,
add logic to "config_reader_*_source()" to keep "cf" and
"the_reader.source" in sync.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 80 +++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 48 insertions(+), 32 deletions(-)

diff --git a/config.c b/config.c
index 1f89addc771..866cd54dd40 100644
--- a/config.c
+++ b/config.c
@@ -51,6 +51,12 @@ struct config_source {
 };
 #define CONFIG_SOURCE_INIT { 0 }
 
+struct config_reader {
+	struct config_source *source;
+};
+/* Only public functions should reference the_reader. */
+static struct config_reader the_reader;
+
 /*
  * These variables record the "current" config source, which
  * can be accessed by parsing callbacks.
@@ -66,6 +72,9 @@ struct config_source {
  * at the variables, it's either a bug for it to be called in the first place,
  * or it's a function which can be reused for non-config purposes, and should
  * fall back to some sane behavior).
+ *
+ * FIXME "cf" has been replaced by "the_reader.source", remove
+ * "cf" once we plumb "the_reader" through all of the callback functions.
  */
 static struct config_source *cf;
 static struct key_value_info *current_config_kvi;
@@ -79,20 +88,25 @@ static struct key_value_info *current_config_kvi;
  */
 static enum config_scope current_parsing_scope;
 
-static inline void config_state_push_source(struct config_source *top)
+static inline void config_reader_push_source(struct config_reader *reader,
+					     struct config_source *top)
 {
-	if (cf)
-		top->prev = cf;
-	cf = top;
+	if (reader->source)
+		top->prev = reader->source;
+	reader->source = top;
+	/* FIXME remove this when cf is removed. */
+	cf = reader->source;
 }
 
-static inline struct config_source *config_state_pop_source()
+static inline struct config_source *config_reader_pop_source(struct config_reader *reader)
 {
 	struct config_source *ret;
-	if (!cf)
+	if (!reader->source)
 		BUG("tried to pop config source, but we weren't reading config");
-	ret = cf;
-	cf = cf->prev;
+	ret = reader->source;
+	reader->source = reader->source->prev;
+	/* FIXME remove this when cf is removed. */
+	cf = reader->source;
 	return ret;
 }
 
@@ -732,7 +746,7 @@ int git_config_from_parameters(config_fn_t fn, void *data)
 	struct config_source source = CONFIG_SOURCE_INIT;
 
 	source.origin_type = CONFIG_ORIGIN_CMDLINE;
-	config_state_push_source(&source);
+	config_reader_push_source(&the_reader, &source);
 
 	env = getenv(CONFIG_COUNT_ENVIRONMENT);
 	if (env) {
@@ -790,7 +804,7 @@ out:
 	strbuf_release(&envvar);
 	strvec_clear(&to_free);
 	free(envw);
-	config_state_pop_source();
+	config_reader_pop_source(&the_reader);
 	return ret;
 }
 
@@ -1325,7 +1339,7 @@ int git_config_int(const char *name, const char *value)
 {
 	int ret;
 	if (!git_parse_int(value, &ret))
-		die_bad_number(cf, name, value);
+		die_bad_number(the_reader.source, name, value);
 	return ret;
 }
 
@@ -1333,7 +1347,7 @@ int64_t git_config_int64(const char *name, const char *value)
 {
 	int64_t ret;
 	if (!git_parse_int64(value, &ret))
-		die_bad_number(cf, name, value);
+		die_bad_number(the_reader.source, name, value);
 	return ret;
 }
 
@@ -1341,7 +1355,7 @@ unsigned long git_config_ulong(const char *name, const char *value)
 {
 	unsigned long ret;
 	if (!git_parse_ulong(value, &ret))
-		die_bad_number(cf, name, value);
+		die_bad_number(the_reader.source, name, value);
 	return ret;
 }
 
@@ -1349,7 +1363,7 @@ ssize_t git_config_ssize_t(const char *name, const char *value)
 {
 	ssize_t ret;
 	if (!git_parse_ssize_t(value, &ret))
-		die_bad_number(cf, name, value);
+		die_bad_number(the_reader.source, name, value);
 	return ret;
 }
 
@@ -1955,7 +1969,8 @@ int git_default_config(const char *var, const char *value, void *cb)
  * fgetc, ungetc, ftell of top need to be initialized before calling
  * this function.
  */
-static int do_config_from(struct config_source *top, config_fn_t fn, void *data,
+static int do_config_from(struct config_reader *reader,
+			  struct config_source *top, config_fn_t fn, void *data,
 			  const struct config_options *opts)
 {
 	int ret;
@@ -1966,22 +1981,23 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data,
 	top->total_len = 0;
 	strbuf_init(&top->value, 1024);
 	strbuf_init(&top->var, 1024);
-	config_state_push_source(top);
+	config_reader_push_source(reader, top);
 
 	ret = git_parse_source(top, fn, data, opts);
 
 	/* pop config-file parsing state stack */
 	strbuf_release(&top->value);
 	strbuf_release(&top->var);
-	config_state_pop_source();
+	config_reader_pop_source(reader);
 
 	return ret;
 }
 
-static int do_config_from_file(config_fn_t fn,
-		const enum config_origin_type origin_type,
-		const char *name, const char *path, FILE *f,
-		void *data, const struct config_options *opts)
+static int do_config_from_file(struct config_reader *reader,
+			       config_fn_t fn,
+			       const enum config_origin_type origin_type,
+			       const char *name, const char *path, FILE *f,
+			       void *data, const struct config_options *opts)
 {
 	struct config_source top = CONFIG_SOURCE_INIT;
 	int ret;
@@ -1996,15 +2012,15 @@ static int do_config_from_file(config_fn_t fn,
 	top.do_ftell = config_file_ftell;
 
 	flockfile(f);
-	ret = do_config_from(&top, fn, data, opts);
+	ret = do_config_from(reader, &top, fn, data, opts);
 	funlockfile(f);
 	return ret;
 }
 
 static int git_config_from_stdin(config_fn_t fn, void *data)
 {
-	return do_config_from_file(fn, CONFIG_ORIGIN_STDIN, "", NULL, stdin,
-				   data, NULL);
+	return do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_STDIN, "",
+				   NULL, stdin, data, NULL);
 }
 
 int git_config_from_file_with_options(config_fn_t fn, const char *filename,
@@ -2018,8 +2034,8 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,
 		BUG("filename cannot be NULL");
 	f = fopen_or_warn(filename, "r");
 	if (f) {
-		ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename,
-					  filename, f, data, opts);
+		ret = do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_FILE,
+					  filename, filename, f, data, opts);
 		fclose(f);
 	}
 	return ret;
@@ -2048,7 +2064,7 @@ int git_config_from_mem(config_fn_t fn,
 	top.do_ungetc = config_buf_ungetc;
 	top.do_ftell = config_buf_ftell;
 
-	return do_config_from(&top, fn, data, opts);
+	return do_config_from(&the_reader, &top, fn, data, opts);
 }
 
 int git_config_from_blob_oid(config_fn_t fn,
@@ -3791,8 +3807,8 @@ const char *current_config_origin_type(void)
 	int type;
 	if (current_config_kvi)
 		type = current_config_kvi->origin_type;
-	else if(cf)
-		type = cf->origin_type;
+	else if(the_reader.source)
+		type = the_reader.source->origin_type;
 	else
 		BUG("current_config_origin_type called outside config callback");
 
@@ -3837,8 +3853,8 @@ const char *current_config_name(void)
 	const char *name;
 	if (current_config_kvi)
 		name = current_config_kvi->filename;
-	else if (cf)
-		name = cf->name;
+	else if (the_reader.source)
+		name = the_reader.source->name;
 	else
 		BUG("current_config_name called outside config callback");
 	return name ? name : "";
@@ -3857,7 +3873,7 @@ int current_config_line(void)
 	if (current_config_kvi)
 		return current_config_kvi->linenr;
 	else
-		return cf->linenr;
+		return the_reader.source->linenr;
 }
 
 int lookup_config(const char **mapping, int nr_mapping, const char *var)
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 4/6] config.c: plumb the_reader through callbacks
  2023-03-01  0:38 [PATCH 0/6] [RFC] config.c: use struct for config reading state Glen Choo via GitGitGadget
                   ` (2 preceding siblings ...)
  2023-03-01  0:38 ` [PATCH 3/6] config.c: create config_reader and the_reader Glen Choo via GitGitGadget
@ 2023-03-01  0:38 ` Glen Choo via GitGitGadget
  2023-03-08  9:54   ` Ævar Arnfjörð Bjarmason
  2023-03-01  0:38 ` [PATCH 5/6] config.c: remove current_config_kvi Glen Choo via GitGitGadget
                   ` (4 subsequent siblings)
  8 siblings, 1 reply; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-01  0:38 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee, Glen Choo,
	Glen Choo

From: Glen Choo <chooglen@google.com>

The remaining references to "cf" are in config callback functions.
Remove them by plumbing "struct config_reader" via the "*data" arg.

**RFC NOTE** If we had a way to expose "struct config_reader" to the
config callback functions (the 'extra work' in the cover letter), we
wouldn't need to also pass it via the "*data" arg. This is more of a
hack to avoid doing that work now.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 78 ++++++++++++++++++++++++++++++++++----------------------
 1 file changed, 48 insertions(+), 30 deletions(-)

diff --git a/config.c b/config.c
index 866cd54dd40..9676734b1b7 100644
--- a/config.c
+++ b/config.c
@@ -58,6 +58,9 @@ struct config_reader {
 static struct config_reader the_reader;
 
 /*
+ * FIXME The comments are temporarily out of date since "cf" been moved to
+ * the_reader, but not current_*.
+ *
  * These variables record the "current" config source, which
  * can be accessed by parsing callbacks.
  *
@@ -72,11 +75,7 @@ static struct config_reader the_reader;
  * at the variables, it's either a bug for it to be called in the first place,
  * or it's a function which can be reused for non-config purposes, and should
  * fall back to some sane behavior).
- *
- * FIXME "cf" has been replaced by "the_reader.source", remove
- * "cf" once we plumb "the_reader" through all of the callback functions.
  */
-static struct config_source *cf;
 static struct key_value_info *current_config_kvi;
 
 /*
@@ -94,8 +93,6 @@ static inline void config_reader_push_source(struct config_reader *reader,
 	if (reader->source)
 		top->prev = reader->source;
 	reader->source = top;
-	/* FIXME remove this when cf is removed. */
-	cf = reader->source;
 }
 
 static inline struct config_source *config_reader_pop_source(struct config_reader *reader)
@@ -105,8 +102,6 @@ static inline struct config_source *config_reader_pop_source(struct config_reade
 		BUG("tried to pop config source, but we weren't reading config");
 	ret = reader->source;
 	reader->source = reader->source->prev;
-	/* FIXME remove this when cf is removed. */
-	cf = reader->source;
 	return ret;
 }
 
@@ -171,6 +166,7 @@ struct config_include_data {
 	void *data;
 	const struct config_options *opts;
 	struct git_config_source *config_source;
+	struct config_reader *config_reader;
 
 	/*
 	 * All remote URLs discovered when reading all config files.
@@ -461,6 +457,7 @@ static int include_condition_is_true(struct config_source *cs,
 static int git_config_include(const char *var, const char *value, void *data)
 {
 	struct config_include_data *inc = data;
+	struct config_source *cs = inc->config_reader->source;
 	const char *cond, *key;
 	size_t cond_len;
 	int ret;
@@ -474,16 +471,16 @@ static int git_config_include(const char *var, const char *value, void *data)
 		return ret;
 
 	if (!strcmp(var, "include.path"))
-		ret = handle_path_include(cf, value, inc);
+		ret = handle_path_include(cs, value, inc);
 
 	if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
-	    cond && include_condition_is_true(cf, inc, cond, cond_len) &&
+	    cond && include_condition_is_true(cs, inc, cond, cond_len) &&
 	    !strcmp(key, "path")) {
 		config_fn_t old_fn = inc->fn;
 
 		if (inc->opts->unconditional_remote_url)
 			inc->fn = forbid_remote_url;
-		ret = handle_path_include(cf, value, inc);
+		ret = handle_path_include(cs, value, inc);
 		inc->fn = old_fn;
 	}
 
@@ -2224,6 +2221,7 @@ int config_with_options(config_fn_t fn, void *data,
 		inc.data = data;
 		inc.opts = opts;
 		inc.config_source = config_source;
+		inc.config_reader = &the_reader;
 		fn = git_config_include;
 		data = &inc;
 	}
@@ -2344,7 +2342,9 @@ static struct config_set_element *configset_find_element(struct config_set *cs,
 	return found_entry;
 }
 
-static int configset_add_value(struct config_set *cs, const char *key, const char *value)
+static int configset_add_value(struct config_reader *reader,
+			       struct config_set *cs, const char *key,
+			       const char *value)
 {
 	struct config_set_element *e;
 	struct string_list_item *si;
@@ -2370,12 +2370,12 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha
 	l_item->e = e;
 	l_item->value_index = e->value_list.nr - 1;
 
-	if (!cf)
+	if (!reader->source)
 		BUG("configset_add_value has no source");
-	if (cf->name) {
-		kv_info->filename = strintern(cf->name);
-		kv_info->linenr = cf->linenr;
-		kv_info->origin_type = cf->origin_type;
+	if (reader->source->name) {
+		kv_info->filename = strintern(reader->source->name);
+		kv_info->linenr = reader->source->linenr;
+		kv_info->origin_type = reader->source->origin_type;
 	} else {
 		/* for values read from `git_config_from_parameters()` */
 		kv_info->filename = NULL;
@@ -2430,16 +2430,25 @@ void git_configset_clear(struct config_set *cs)
 	cs->list.items = NULL;
 }
 
+struct configset_add_data {
+	struct config_set *config_set;
+	struct config_reader *config_reader;
+};
+#define CONFIGSET_ADD_INIT { 0 }
+
 static int config_set_callback(const char *key, const char *value, void *cb)
 {
-	struct config_set *cs = cb;
-	configset_add_value(cs, key, value);
+	struct configset_add_data *data = cb;
+	configset_add_value(data->config_reader, data->config_set, key, value);
 	return 0;
 }
 
 int git_configset_add_file(struct config_set *cs, const char *filename)
 {
-	return git_config_from_file(config_set_callback, filename, cs);
+	struct configset_add_data data = CONFIGSET_ADD_INIT;
+	data.config_reader = &the_reader;
+	data.config_set = cs;
+	return git_config_from_file(config_set_callback, filename, &data);
 }
 
 int git_configset_get_value(struct config_set *cs, const char *key, const char **value)
@@ -2554,6 +2563,7 @@ int git_configset_get_pathname(struct config_set *cs, const char *key, const cha
 static void repo_read_config(struct repository *repo)
 {
 	struct config_options opts = { 0 };
+	struct configset_add_data data = CONFIGSET_ADD_INIT;
 
 	opts.respect_includes = 1;
 	opts.commondir = repo->commondir;
@@ -2565,8 +2575,10 @@ static void repo_read_config(struct repository *repo)
 		git_configset_clear(repo->config);
 
 	git_configset_init(repo->config);
+	data.config_set = repo->config;
+	data.config_reader = &the_reader;
 
-	if (config_with_options(config_set_callback, repo->config, NULL, &opts) < 0)
+	if (config_with_options(config_set_callback, &data, NULL, &opts) < 0)
 		/*
 		 * config_with_options() normally returns only
 		 * zero, as most errors are fatal, and
@@ -2692,9 +2704,12 @@ static void read_protected_config(void)
 		.ignore_worktree = 1,
 		.system_gently = 1,
 	};
+	struct configset_add_data data = CONFIGSET_ADD_INIT;
+
 	git_configset_init(&protected_config);
-	config_with_options(config_set_callback, &protected_config,
-			    NULL, &opts);
+	data.config_set = &protected_config;
+	data.config_reader = &the_reader;
+	config_with_options(config_set_callback, &data, NULL, &opts);
 }
 
 void git_protected_config(config_fn_t fn, void *data)
@@ -2879,6 +2894,7 @@ void git_die_config(const char *key, const char *err, ...)
  */
 
 struct config_store_data {
+	struct config_reader *config_reader;
 	size_t baselen;
 	char *key;
 	int do_not_match;
@@ -2893,6 +2909,7 @@ struct config_store_data {
 	unsigned int parsed_nr, parsed_alloc, *seen, seen_nr, seen_alloc;
 	unsigned int key_seen:1, section_seen:1, is_keys_section:1;
 };
+#define CONFIG_STORE_INIT { 0 }
 
 static void config_store_data_clear(struct config_store_data *store)
 {
@@ -2927,6 +2944,7 @@ static int store_aux_event(enum config_event_t type,
 			   size_t begin, size_t end, void *data)
 {
 	struct config_store_data *store = data;
+	struct config_source *cs = store->config_reader->source;
 
 	ALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc);
 	store->parsed[store->parsed_nr].begin = begin;
@@ -2936,10 +2954,10 @@ static int store_aux_event(enum config_event_t type,
 	if (type == CONFIG_EVENT_SECTION) {
 		int (*cmpfn)(const char *, const char *, size_t);
 
-		if (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')
-			return error(_("invalid section name '%s'"), cf->var.buf);
+		if (cs->var.len < 2 || cs->var.buf[cs->var.len - 1] != '.')
+			return error(_("invalid section name '%s'"), cs->var.buf);
 
-		if (cf->subsection_case_sensitive)
+		if (cs->subsection_case_sensitive)
 			cmpfn = strncasecmp;
 		else
 			cmpfn = strncmp;
@@ -2947,8 +2965,8 @@ static int store_aux_event(enum config_event_t type,
 		/* Is this the section we were looking for? */
 		store->is_keys_section =
 			store->parsed[store->parsed_nr].is_keys_section =
-			cf->var.len - 1 == store->baselen &&
-			!cmpfn(cf->var.buf, store->key, store->baselen);
+			cs->var.len - 1 == store->baselen &&
+			!cmpfn(cs->var.buf, store->key, store->baselen);
 		if (store->is_keys_section) {
 			store->section_seen = 1;
 			ALLOC_GROW(store->seen, store->seen_nr + 1,
@@ -3244,9 +3262,9 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
 	char *filename_buf = NULL;
 	char *contents = NULL;
 	size_t contents_sz;
-	struct config_store_data store;
+	struct config_store_data store = CONFIG_STORE_INIT;
 
-	memset(&store, 0, sizeof(store));
+	store.config_reader = &the_reader;
 
 	/* parse-key returns negative; flip the sign to feed exit(3) */
 	ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 5/6] config.c: remove current_config_kvi
  2023-03-01  0:38 [PATCH 0/6] [RFC] config.c: use struct for config reading state Glen Choo via GitGitGadget
                   ` (3 preceding siblings ...)
  2023-03-01  0:38 ` [PATCH 4/6] config.c: plumb the_reader through callbacks Glen Choo via GitGitGadget
@ 2023-03-01  0:38 ` Glen Choo via GitGitGadget
  2023-03-06 20:12   ` Calvin Wan
  2023-03-01  0:38 ` [PATCH 6/6] config.c: remove current_parsing_scope Glen Choo via GitGitGadget
                   ` (3 subsequent siblings)
  8 siblings, 1 reply; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-01  0:38 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee, Glen Choo,
	Glen Choo

From: Glen Choo <chooglen@google.com>

Add ".config_kvi" to "struct config_reader" and replace
"current_config_kvi" with "the_reader.config_kvi", either in-place (in
public functions) or by passing "the_reader" to the "*data" arg of
callback functions.

Also, introduce a setter function for ".config_kvi", which allows us to
enforce the contraint that only one of ".source" and ".config_kvi" can
be set at a time (as documented in the comments). Because of this
constraint, we know that "populate_remote_urls()" was never touching
"current_config_kvi" when iterating through config files, so it doesn't
need to store and restore that value.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 103 ++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 61 insertions(+), 42 deletions(-)

diff --git a/config.c b/config.c
index 9676734b1b7..c7995148165 100644
--- a/config.c
+++ b/config.c
@@ -52,32 +52,28 @@ struct config_source {
 #define CONFIG_SOURCE_INIT { 0 }
 
 struct config_reader {
+	/*
+	 * These members record the "current" config source, which can be
+	 * accessed by parsing callbacks.
+	 *
+	 * The "source" variable will be non-NULL only when we are actually
+	 * parsing a real config source (file, blob, cmdline, etc).
+	 *
+	 * The "config_kvi" variable will be non-NULL only when we are feeding
+	 * cached config from a configset into a callback.
+	 *
+	 * They cannot be non-NULL at the same time. If they are both NULL, then
+	 * we aren't parsing anything (and depending on the function looking at
+	 * the variables, it's either a bug for it to be called in the first
+	 * place, or it's a function which can be reused for non-config
+	 * purposes, and should fall back to some sane behavior).
+	 */
 	struct config_source *source;
+	struct key_value_info *config_kvi;
 };
 /* Only public functions should reference the_reader. */
 static struct config_reader the_reader;
 
-/*
- * FIXME The comments are temporarily out of date since "cf" been moved to
- * the_reader, but not current_*.
- *
- * These variables record the "current" config source, which
- * can be accessed by parsing callbacks.
- *
- * The "cf" variable will be non-NULL only when we are actually parsing a real
- * config source (file, blob, cmdline, etc).
- *
- * The "current_config_kvi" variable will be non-NULL only when we are feeding
- * cached config from a configset into a callback.
- *
- * They should generally never be non-NULL at the same time. If they are both
- * NULL, then we aren't parsing anything (and depending on the function looking
- * at the variables, it's either a bug for it to be called in the first place,
- * or it's a function which can be reused for non-config purposes, and should
- * fall back to some sane behavior).
- */
-static struct key_value_info *current_config_kvi;
-
 /*
  * Similar to the variables above, this gives access to the "scope" of the
  * current value (repo, global, etc). For cached values, it can be found via
@@ -90,6 +86,8 @@ static enum config_scope current_parsing_scope;
 static inline void config_reader_push_source(struct config_reader *reader,
 					     struct config_source *top)
 {
+	if (reader->config_kvi)
+		BUG("source should only be set when parsing a config source");
 	if (reader->source)
 		top->prev = reader->source;
 	reader->source = top;
@@ -105,6 +103,14 @@ static inline struct config_source *config_reader_pop_source(struct config_reade
 	return ret;
 }
 
+static inline void config_reader_set_kvi(struct config_reader *reader,
+					 struct key_value_info *kvi)
+{
+	if (kvi && reader->source)
+		BUG("kvi should only be set when iterating through configset");
+	reader->config_kvi = kvi;
+}
+
 static int pack_compression_seen;
 static int zlib_compression_seen;
 
@@ -373,20 +379,17 @@ static void populate_remote_urls(struct config_include_data *inc)
 {
 	struct config_options opts;
 
-	struct key_value_info *store_kvi = current_config_kvi;
 	enum config_scope store_scope = current_parsing_scope;
 
 	opts = *inc->opts;
 	opts.unconditional_remote_url = 1;
 
-	current_config_kvi = NULL;
 	current_parsing_scope = 0;
 
 	inc->remote_urls = xmalloc(sizeof(*inc->remote_urls));
 	string_list_init_dup(inc->remote_urls);
 	config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);
 
-	current_config_kvi = store_kvi;
 	current_parsing_scope = store_scope;
 }
 
@@ -2253,26 +2256,34 @@ int config_with_options(config_fn_t fn, void *data,
 	return ret;
 }
 
+struct configset_iter_data {
+	struct config_reader *config_reader;
+	void *inner;
+};
+#define CONFIGSET_ITER_INIT { 0 }
+
 static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
 {
 	int i, value_index;
 	struct string_list *values;
 	struct config_set_element *entry;
 	struct configset_list *list = &cs->list;
+	struct configset_iter_data *iter_data = data;
 
 	for (i = 0; i < list->nr; i++) {
+		struct key_value_info *kvi;
 		entry = list->items[i].e;
 		value_index = list->items[i].value_index;
 		values = &entry->value_list;
 
-		current_config_kvi = values->items[value_index].util;
+		kvi = values->items[value_index].util;
+		config_reader_set_kvi(iter_data->config_reader, kvi);
 
-		if (fn(entry->key, values->items[value_index].string, data) < 0)
-			git_die_config_linenr(entry->key,
-					      current_config_kvi->filename,
-					      current_config_kvi->linenr);
+		if (fn(entry->key, values->items[value_index].string, iter_data->inner) < 0)
+			git_die_config_linenr(entry->key, kvi->filename,
+					      kvi->linenr);
 
-		current_config_kvi = NULL;
+		config_reader_set_kvi(iter_data->config_reader, NULL);
 	}
 }
 
@@ -2607,10 +2618,14 @@ static void repo_config_clear(struct repository *repo)
 	git_configset_clear(repo->config);
 }
 
-void repo_config(struct repository *repo, config_fn_t fn, void *data)
+void repo_config(struct repository *repo, config_fn_t fn, void *data_inner)
 {
+	struct configset_iter_data data = CONFIGSET_ITER_INIT;
+	data.inner = data_inner;
+	data.config_reader = &the_reader;
+
 	git_config_check_init(repo);
-	configset_iter(repo->config, fn, data);
+	configset_iter(repo->config, fn, &data);
 }
 
 int repo_config_get_value(struct repository *repo,
@@ -2712,11 +2727,15 @@ static void read_protected_config(void)
 	config_with_options(config_set_callback, &data, NULL, &opts);
 }
 
-void git_protected_config(config_fn_t fn, void *data)
+void git_protected_config(config_fn_t fn, void *data_inner)
 {
+	struct configset_iter_data data = CONFIGSET_ITER_INIT;
 	if (!protected_config.hash_initialized)
 		read_protected_config();
-	configset_iter(&protected_config, fn, data);
+	data.inner = data_inner;
+	data.config_reader = &the_reader;
+
+	configset_iter(&protected_config, fn, &data);
 }
 
 /* Functions used historically to read configuration from 'the_repository' */
@@ -3823,8 +3842,8 @@ int parse_config_key(const char *var,
 const char *current_config_origin_type(void)
 {
 	int type;
-	if (current_config_kvi)
-		type = current_config_kvi->origin_type;
+	if (the_reader.config_kvi)
+		type = the_reader.config_kvi->origin_type;
 	else if(the_reader.source)
 		type = the_reader.source->origin_type;
 	else
@@ -3869,8 +3888,8 @@ const char *config_scope_name(enum config_scope scope)
 const char *current_config_name(void)
 {
 	const char *name;
-	if (current_config_kvi)
-		name = current_config_kvi->filename;
+	if (the_reader.config_kvi)
+		name = the_reader.config_kvi->filename;
 	else if (the_reader.source)
 		name = the_reader.source->name;
 	else
@@ -3880,16 +3899,16 @@ const char *current_config_name(void)
 
 enum config_scope current_config_scope(void)
 {
-	if (current_config_kvi)
-		return current_config_kvi->scope;
+	if (the_reader.config_kvi)
+		return the_reader.config_kvi->scope;
 	else
 		return current_parsing_scope;
 }
 
 int current_config_line(void)
 {
-	if (current_config_kvi)
-		return current_config_kvi->linenr;
+	if (the_reader.config_kvi)
+		return the_reader.config_kvi->linenr;
 	else
 		return the_reader.source->linenr;
 }
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 6/6] config.c: remove current_parsing_scope
  2023-03-01  0:38 [PATCH 0/6] [RFC] config.c: use struct for config reading state Glen Choo via GitGitGadget
                   ` (4 preceding siblings ...)
  2023-03-01  0:38 ` [PATCH 5/6] config.c: remove current_config_kvi Glen Choo via GitGitGadget
@ 2023-03-01  0:38 ` Glen Choo via GitGitGadget
  2023-03-06 19:57 ` [PATCH 0/6] [RFC] config.c: use struct for config reading state Jonathan Tan
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-01  0:38 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee, Glen Choo,
	Glen Choo

From: Glen Choo <chooglen@google.com>

Add ".parsing_scope" to "struct config_reader" and replace
"current_parsing_scope" with "the_reader.parsing_scope. Adjust the
comment slightly to make it clearer that the scope applies to the config
source (not the current value), and should only be set when parsing a
config source.

As such, ".parsing_scope" (only set when parsing config sources) and
".config_kvi" (only set when iterating a config set) should not be
set together, so enforce this with a setter function.

Unlike previous commits, "populate_remote_urls()" still needs to store
and restore the 'scope' value because it could have touched
"current_parsing_scope" ("config_with_options()" can set the scope).

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 62 +++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 37 insertions(+), 25 deletions(-)

diff --git a/config.c b/config.c
index c7995148165..19bab84c47f 100644
--- a/config.c
+++ b/config.c
@@ -70,19 +70,20 @@ struct config_reader {
 	 */
 	struct config_source *source;
 	struct key_value_info *config_kvi;
+	/*
+	 * The "scope" of the current config source being parsed (repo, global,
+	 * etc). Like "source", this is only set when parsing a config source.
+	 * It's not part of "source" because it transcends a single file (i.e.,
+	 * a file included from .git/config is still in "repo" scope).
+	 *
+	 * When iterating through a configset, the equivalent value is
+	 * "config_kvi.scope" (see above).
+	 */
+	enum config_scope parsing_scope;
 };
 /* Only public functions should reference the_reader. */
 static struct config_reader the_reader;
 
-/*
- * Similar to the variables above, this gives access to the "scope" of the
- * current value (repo, global, etc). For cached values, it can be found via
- * the current_config_kvi as above. During parsing, the current value can be
- * found in this variable. It's not part of "cf" because it transcends a single
- * file (i.e., a file included from .git/config is still in "repo" scope).
- */
-static enum config_scope current_parsing_scope;
-
 static inline void config_reader_push_source(struct config_reader *reader,
 					     struct config_source *top)
 {
@@ -106,11 +107,19 @@ static inline struct config_source *config_reader_pop_source(struct config_reade
 static inline void config_reader_set_kvi(struct config_reader *reader,
 					 struct key_value_info *kvi)
 {
-	if (kvi && reader->source)
+	if (kvi && (reader->source || reader->parsing_scope))
 		BUG("kvi should only be set when iterating through configset");
 	reader->config_kvi = kvi;
 }
 
+static inline void config_reader_set_scope(struct config_reader *reader,
+					   enum config_scope scope)
+{
+	if (scope && reader->config_kvi)
+		BUG("scope should only be set when iterating through a config source");
+	reader->parsing_scope = scope;
+}
+
 static int pack_compression_seen;
 static int zlib_compression_seen;
 
@@ -379,18 +388,18 @@ static void populate_remote_urls(struct config_include_data *inc)
 {
 	struct config_options opts;
 
-	enum config_scope store_scope = current_parsing_scope;
+	enum config_scope store_scope = inc->config_reader->parsing_scope;
 
 	opts = *inc->opts;
 	opts.unconditional_remote_url = 1;
 
-	current_parsing_scope = 0;
+	config_reader_set_scope(inc->config_reader, 0);
 
 	inc->remote_urls = xmalloc(sizeof(*inc->remote_urls));
 	string_list_init_dup(inc->remote_urls);
 	config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);
 
-	current_parsing_scope = store_scope;
+	config_reader_set_scope(inc->config_reader, store_scope);
 }
 
 static int forbid_remote_url(const char *var, const char *value UNUSED,
@@ -2155,7 +2164,8 @@ int git_config_system(void)
 	return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
 }
 
-static int do_git_config_sequence(const struct config_options *opts,
+static int do_git_config_sequence(struct config_reader *reader,
+				  const struct config_options *opts,
 				  config_fn_t fn, void *data)
 {
 	int ret = 0;
@@ -2163,7 +2173,7 @@ static int do_git_config_sequence(const struct config_options *opts,
 	char *xdg_config = NULL;
 	char *user_config = NULL;
 	char *repo_config;
-	enum config_scope prev_parsing_scope = current_parsing_scope;
+	enum config_scope prev_parsing_scope = reader->parsing_scope;
 
 	if (opts->commondir)
 		repo_config = mkpathdup("%s/config", opts->commondir);
@@ -2172,13 +2182,13 @@ static int do_git_config_sequence(const struct config_options *opts,
 	else
 		repo_config = NULL;
 
-	current_parsing_scope = CONFIG_SCOPE_SYSTEM;
+	config_reader_set_scope(reader, CONFIG_SCOPE_SYSTEM);
 	if (git_config_system() && system_config &&
 	    !access_or_die(system_config, R_OK,
 			   opts->system_gently ? ACCESS_EACCES_OK : 0))
 		ret += git_config_from_file(fn, system_config, data);
 
-	current_parsing_scope = CONFIG_SCOPE_GLOBAL;
+	config_reader_set_scope(reader, CONFIG_SCOPE_GLOBAL);
 	git_global_config(&user_config, &xdg_config);
 
 	if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))
@@ -2187,12 +2197,12 @@ static int do_git_config_sequence(const struct config_options *opts,
 	if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))
 		ret += git_config_from_file(fn, user_config, data);
 
-	current_parsing_scope = CONFIG_SCOPE_LOCAL;
+	config_reader_set_scope(reader, CONFIG_SCOPE_LOCAL);
 	if (!opts->ignore_repo && repo_config &&
 	    !access_or_die(repo_config, R_OK, 0))
 		ret += git_config_from_file(fn, repo_config, data);
 
-	current_parsing_scope = CONFIG_SCOPE_WORKTREE;
+	config_reader_set_scope(reader, CONFIG_SCOPE_WORKTREE);
 	if (!opts->ignore_worktree && repository_format_worktree_config) {
 		char *path = git_pathdup("config.worktree");
 		if (!access_or_die(path, R_OK, 0))
@@ -2200,11 +2210,11 @@ static int do_git_config_sequence(const struct config_options *opts,
 		free(path);
 	}
 
-	current_parsing_scope = CONFIG_SCOPE_COMMAND;
+	config_reader_set_scope(reader, CONFIG_SCOPE_COMMAND);
 	if (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)
 		die(_("unable to parse command-line config"));
 
-	current_parsing_scope = prev_parsing_scope;
+	config_reader_set_scope(reader, prev_parsing_scope);
 	free(system_config);
 	free(xdg_config);
 	free(user_config);
@@ -2217,6 +2227,7 @@ int config_with_options(config_fn_t fn, void *data,
 			const struct config_options *opts)
 {
 	struct config_include_data inc = CONFIG_INCLUDE_INIT;
+	enum config_scope prev_scope = the_reader.parsing_scope;
 	int ret;
 
 	if (opts->respect_includes) {
@@ -2230,7 +2241,7 @@ int config_with_options(config_fn_t fn, void *data,
 	}
 
 	if (config_source)
-		current_parsing_scope = config_source->scope;
+		config_reader_set_scope(&the_reader, config_source->scope);
 
 	/*
 	 * If we have a specific filename, use it. Otherwise, follow the
@@ -2246,13 +2257,14 @@ int config_with_options(config_fn_t fn, void *data,
 		ret = git_config_from_blob_ref(fn, repo, config_source->blob,
 						data);
 	} else {
-		ret = do_git_config_sequence(opts, fn, data);
+		ret = do_git_config_sequence(&the_reader, opts, fn, data);
 	}
 
 	if (inc.remote_urls) {
 		string_list_clear(inc.remote_urls, 0);
 		FREE_AND_NULL(inc.remote_urls);
 	}
+	config_reader_set_scope(&the_reader, prev_scope);
 	return ret;
 }
 
@@ -2393,7 +2405,7 @@ static int configset_add_value(struct config_reader *reader,
 		kv_info->linenr = -1;
 		kv_info->origin_type = CONFIG_ORIGIN_CMDLINE;
 	}
-	kv_info->scope = current_parsing_scope;
+	kv_info->scope = reader->parsing_scope;
 	si->util = kv_info;
 
 	return 0;
@@ -3902,7 +3914,7 @@ enum config_scope current_config_scope(void)
 	if (the_reader.config_kvi)
 		return the_reader.config_kvi->scope;
 	else
-		return current_parsing_scope;
+		return the_reader.parsing_scope;
 }
 
 int current_config_line(void)
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* Re: [PATCH 1/6] config.c: plumb config_source through static fns
  2023-03-01  0:38 ` [PATCH 1/6] config.c: plumb config_source through static fns Glen Choo via GitGitGadget
@ 2023-03-03 18:02   ` Junio C Hamano
  0 siblings, 0 replies; 72+ messages in thread
From: Junio C Hamano @ 2023-03-03 18:02 UTC (permalink / raw)
  To: Glen Choo via GitGitGadget
  Cc: git, Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee,
	Glen Choo

"Glen Choo via GitGitGadget" <gitgitgadget@gmail.com> writes:

> -static int handle_path_include(const char *path, struct config_include_data *inc)
> +static int handle_path_include(struct config_source *cs, const char *path,
> +			       struct config_include_data *inc)

Because handle_path_include() has no remaining reference to cf after
this patch, it may make sense to name the parameter "cf", instead of
"cs", taking advantage of the fact that it will cover/hide the global.

> -static int prepare_include_condition_pattern(struct strbuf *pat)
> +static int prepare_include_condition_pattern(struct config_source *cs,
> +					     struct strbuf *pat)

Ditto.

> -static int include_by_gitdir(const struct config_options *opts,
> +static int include_by_gitdir(struct config_source *cs,
> +			     const struct config_options *opts,
>  			     const char *cond, size_t cond_len, int icase)

Ditto.

> +static int include_condition_is_true(struct config_source *cs,
> +				     struct config_include_data *inc,
>  				     const char *cond, size_t cond_len)

Ditto.

> @@ -441,16 +445,16 @@ static int git_config_include(const char *var, const char *value, void *data)

Adding a member to the callback data struct to pass cf around would
be the natural next step, I presume.  I wonder if that makes the
result too big if it is done in this same commit.  I suspect that it
would be easier to grok the whole picture if it were done in the
same commit, though.

If not (IOW, if we deliberately leave some use of the global in the
callchains unfixed with this step), it may make the resulting patch
much easier to read if you (1) rename the global to a longer name
that stands out more, e.g. cf_global, and (2) add a new parameter
'cf' to these helper functions and pass 'cf_global' through to the
callchain.

> -static int get_next_char(void)
> +static int get_next_char(struct config_source *cs)

Ditto for "cs" -> "cf".

> -static char *parse_value(void)
> +static char *parse_value(struct config_source *cs)

Ditto.

> -static int get_value(config_fn_t fn, void *data, struct strbuf *name)
> +static int get_value(struct config_source *cs, config_fn_t fn, void *data,
> +		     struct strbuf *name)

Ditto.

> -static int get_extended_base_var(struct strbuf *name, int c)
> +static int get_extended_base_var(struct config_source *cs, struct strbuf *name,
> +				 int c)

Ditto.

> -static int get_base_var(struct strbuf *name)
> +static int get_base_var(struct config_source *cs, struct strbuf *name)

Ditto.

> -static int do_event(enum config_event_t type, struct parse_event_data *data)
> +static int do_event(struct config_source *cs, enum config_event_t type,
> +		    struct parse_event_data *data)

Ditto.

> -static int git_parse_source(config_fn_t fn, void *data,
> -			    const struct config_options *opts)
> +static int git_parse_source(struct config_source *cs, config_fn_t fn,
> +			    void *data, const struct config_options *opts)
>  {

Ditto.

> -static void die_bad_number(const char *name, const char *value)
> +static void die_bad_number(struct config_source *cs, const char *name,
> +			   const char *value)

Ditto.

> @@ -1304,7 +1312,7 @@ int git_config_int(const char *name, const char *value)
>  {
>  	int ret;
>  	if (!git_parse_int(value, &ret))
> -		die_bad_number(name, value);
> +		die_bad_number(cf, name, value);

And using a more visible name like cf_global will leave us a
reminder here what is remaining to be converted, like this place and
the callback function driven by config_with_options().



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH 3/6] config.c: create config_reader and the_reader
  2023-03-01  0:38 ` [PATCH 3/6] config.c: create config_reader and the_reader Glen Choo via GitGitGadget
@ 2023-03-03 18:05   ` Junio C Hamano
  0 siblings, 0 replies; 72+ messages in thread
From: Junio C Hamano @ 2023-03-03 18:05 UTC (permalink / raw)
  To: Glen Choo via GitGitGadget
  Cc: git, Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee,
	Glen Choo

"Glen Choo via GitGitGadget" <gitgitgadget@gmail.com> writes:

> A more typical approach would be to put this struct on "the_repository",
> but that's a worse fit for this use case since config reading is not
> scoped to a repository.

Good thinking.

> We could have also replaced the references to "cf" in callback functions
> (which are the only ones left), but we'll eventually plumb "the_reader"
> through the callback "*data" arg,...

Good.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state
  2023-03-01  0:38 [PATCH 0/6] [RFC] config.c: use struct for config reading state Glen Choo via GitGitGadget
                   ` (5 preceding siblings ...)
  2023-03-01  0:38 ` [PATCH 6/6] config.c: remove current_parsing_scope Glen Choo via GitGitGadget
@ 2023-03-06 19:57 ` Jonathan Tan
  2023-03-06 21:45   ` Glen Choo
  2023-03-07 11:57 ` Ævar Arnfjörð Bjarmason
  2023-03-16  0:11 ` [PATCH v2 0/8] " Glen Choo via GitGitGadget
  8 siblings, 1 reply; 72+ messages in thread
From: Jonathan Tan @ 2023-03-06 19:57 UTC (permalink / raw)
  To: Glen Choo via GitGitGadget
  Cc: Jonathan Tan, git, Emily Shaffer, Jeff King, Derrick Stolee,
	Glen Choo

"Glen Choo via GitGitGadget" <gitgitgadget@gmail.com> writes:
>  * We could add "struct config_reader" to "config_fn_t", i.e.
>    
>    -typedef int (*config_fn_t)(const char *var, const char *val, void
>    *data); +typedef int (*config_fn_t)(const struct config_reader *reader,
>    const char *var, const char *val, void *data);
>    
>    which isn't complex at all, except that there are ~100 config_fn_t
>    implementations [3] and a good number of them may never reference
>    "reader". If the churn is tolerable, I think this a good way forward.
> 
>  * We could create a new kind of "config_fn_t" that accepts "struct
>    config_reader", e.g.
>    
>    typedef int (*config_fn_t)(const char *var, const char *val, void *data);
>    +typedef int (*config_state_fn_t)(const struct config_reader *reader,
>    const char *var, const char *val, void *data);
>    
>    and only adjust the callers that would actually reference "reader". This
>    is less churn, but I couldn't find a great way to do this kind of
>    'switching between config callback types' elegantly.

To reduce churn, one thing that could be done alongside is to convert
config-using code (which is...practically the rest of Git) to start
using the configset interface (we seem to be using configsets internally
anyway, as evidenced by repo_config()). That way, we would reduce the
number of implementations of config_fn_t.

>  * We could smuggle "struct config_reader" to callback functions in a way
>    that interested callers could see it, but uninterested callers could
>    ignore. One trick that Jonathan Tan came up with (though not necessarily
>    endorsed) would be to allocate a struct for the config value + "struct
>    config_reader", then, interested callers could use "offset_of" to recover
>    the "struct config_reader". It's a little hacky, but it's low-churn.

Indeed, although we should probably use this as a last resort.

> = Questions
> 
>  * Is this worth merging without the extra work? There are some cleanups in
>    this series that could make it valuable, but there are also some hacks
>    (see 4/6) that aren't so great.

I'm leaning towards merging it now, but can go either way, since the
cost of churn is limited to one single file, but so are the benefits.
If it was up to me to decide, I would merge it now, because this opens
up a lot of work that other contributors could individually do (in
particular, converting individual config code paths so that we don't
need to reference the_reader as a global anymore).

I don't see 4/6 as a hack. It is true that the nature of the config_fn_t
callback could change so that passing the reader would end up being
done in yet another different way, but firstly, I don't think that will
happen for quite some time, and secondly, it might not happen at all
(right now, I think what's most likely to happen is that the rest of the
Git code moves to configsets and only a fraction of the Git code would
need to do low-level parsing, which would not have a problem passing the
reader through the data object since they would probably need to pass
other context anyway).

>  * Is the extra work even worth it?

Depends on which extra work, but I think that eliminating the the_reader
global would really be useful (and, as far as I know, the whole reason
for this effort). From the Git codebase's perspective, doing this would
(as far as I know) eliminate the need for pushing and popping cf, and
make multithreaded multi-repo operations less error-prone (e.g. we
can spawn a thread operating on a submodule and that thread can itself
read the configs of nested submodules without worrying about clobbering
global state...well, there is thread-local storage, but as far as I know
this is not portable).

>  * Do any of the ideas seem more promising than the others? Are there other
>    ideas I'm missing?

Hopefully I answered this in my answers to the other questions.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH 5/6] config.c: remove current_config_kvi
  2023-03-01  0:38 ` [PATCH 5/6] config.c: remove current_config_kvi Glen Choo via GitGitGadget
@ 2023-03-06 20:12   ` Calvin Wan
  0 siblings, 0 replies; 72+ messages in thread
From: Calvin Wan @ 2023-03-06 20:12 UTC (permalink / raw)
  To: Glen Choo via GitGitGadget
  Cc: Calvin Wan, git, Jonathan Tan, Emily Shaffer, Jeff King,
	Derrick Stolee, Glen Choo

"Glen Choo via GitGitGadget" <gitgitgadget@gmail.com> writes:
> From: Glen Choo <chooglen@google.com>
> 
> Add ".config_kvi" to "struct config_reader" and replace
> "current_config_kvi" with "the_reader.config_kvi", either in-place (in
> public functions) or by passing "the_reader" to the "*data" arg of
> callback functions.
> 
> Also, introduce a setter function for ".config_kvi", which allows us to
> enforce the contraint that only one of ".source" and ".config_kvi" can
> be set at a time (as documented in the comments). Because of this
> constraint, we know that "populate_remote_urls()" was never touching
> "current_config_kvi" when iterating through config files, so it doesn't
> need to store and restore that value.
> 
> Signed-off-by: Glen Choo <chooglen@google.com>
> ---
>  config.c | 103 ++++++++++++++++++++++++++++++++-----------------------
>  1 file changed, 61 insertions(+), 42 deletions(-)
> 
> diff --git a/config.c b/config.c
> index 9676734b1b7..c7995148165 100644
> --- a/config.c
> +++ b/config.c
> @@ -52,32 +52,28 @@ struct config_source {
>  #define CONFIG_SOURCE_INIT { 0 }
>  
>  struct config_reader {
> +	/*
> +	 * These members record the "current" config source, which can be
> +	 * accessed by parsing callbacks.
> +	 *
> +	 * The "source" variable will be non-NULL only when we are actually
> +	 * parsing a real config source (file, blob, cmdline, etc).
> +	 *
> +	 * The "config_kvi" variable will be non-NULL only when we are feeding
> +	 * cached config from a configset into a callback.
> +	 *
> +	 * They cannot be non-NULL at the same time. If they are both NULL, then
> +	 * we aren't parsing anything (and depending on the function looking at
> +	 * the variables, it's either a bug for it to be called in the first
> +	 * place, or it's a function which can be reused for non-config
> +	 * purposes, and should fall back to some sane behavior).
> +	 */
>  	struct config_source *source;
> +	struct key_value_info *config_kvi;
>  };
>  /* Only public functions should reference the_reader. */
>  static struct config_reader the_reader;
>  
> -/*
> - * FIXME The comments are temporarily out of date since "cf" been moved to
> - * the_reader, but not current_*.
> - *
> - * These variables record the "current" config source, which
> - * can be accessed by parsing callbacks.
> - *
> - * The "cf" variable will be non-NULL only when we are actually parsing a real
> - * config source (file, blob, cmdline, etc).
> - *
> - * The "current_config_kvi" variable will be non-NULL only when we are feeding
> - * cached config from a configset into a callback.
> - *
> - * They should generally never be non-NULL at the same time. If they are both
> - * NULL, then we aren't parsing anything (and depending on the function looking
> - * at the variables, it's either a bug for it to be called in the first place,
> - * or it's a function which can be reused for non-config purposes, and should
> - * fall back to some sane behavior).
> - */
> -static struct key_value_info *current_config_kvi;
> -
>  /*
>   * Similar to the variables above, this gives access to the "scope" of the
>   * current value (repo, global, etc). For cached values, it can be found via
> @@ -90,6 +86,8 @@ static enum config_scope current_parsing_scope;
>  static inline void config_reader_push_source(struct config_reader *reader,
>  					     struct config_source *top)
>  {
> +	if (reader->config_kvi)
> +		BUG("source should only be set when parsing a config source");
>  	if (reader->source)
>  		top->prev = reader->source;
>  	reader->source = top;

A developer who accidentally sets config_kvi probably has the right
intention of trying to push source. I think a clearer BUG message here
(which also doubly functions as a useful comment) would be something
along the lines of: "configset should not be set when parsing a config
source".

> @@ -105,6 +103,14 @@ static inline struct config_source *config_reader_pop_source(struct config_reade
>  	return ret;
>  }
>  
> +static inline void config_reader_set_kvi(struct config_reader *reader,
> +					 struct key_value_info *kvi)
> +{
> +	if (kvi && reader->source)
> +		BUG("kvi should only be set when iterating through configset");
> +	reader->config_kvi = kvi;
> +}
> +

ditto

>  static int pack_compression_seen;
>  static int zlib_compression_seen;
>  
> @@ -373,20 +379,17 @@ static void populate_remote_urls(struct config_include_data *inc)
>  {
>  	struct config_options opts;
>  
> -	struct key_value_info *store_kvi = current_config_kvi;
>  	enum config_scope store_scope = current_parsing_scope;
>  
>  	opts = *inc->opts;
>  	opts.unconditional_remote_url = 1;
>  
> -	current_config_kvi = NULL;
>  	current_parsing_scope = 0;
>  
>  	inc->remote_urls = xmalloc(sizeof(*inc->remote_urls));
>  	string_list_init_dup(inc->remote_urls);
>  	config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);
>  
> -	current_config_kvi = store_kvi;
>  	current_parsing_scope = store_scope;
>  }
>  
> @@ -2253,26 +2256,34 @@ int config_with_options(config_fn_t fn, void *data,
>  	return ret;
>  }
>  
> +struct configset_iter_data {
> +	struct config_reader *config_reader;
> +	void *inner;
> +};
> +#define CONFIGSET_ITER_INIT { 0 }
> +
>  static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
>  {
>  	int i, value_index;
>  	struct string_list *values;
>  	struct config_set_element *entry;
>  	struct configset_list *list = &cs->list;
> +	struct configset_iter_data *iter_data = data;
>  
>  	for (i = 0; i < list->nr; i++) {
> +		struct key_value_info *kvi;
>  		entry = list->items[i].e;
>  		value_index = list->items[i].value_index;
>  		values = &entry->value_list;
>  
> -		current_config_kvi = values->items[value_index].util;
> +		kvi = values->items[value_index].util;
> +		config_reader_set_kvi(iter_data->config_reader, kvi);
>  
> -		if (fn(entry->key, values->items[value_index].string, data) < 0)
> -			git_die_config_linenr(entry->key,
> -					      current_config_kvi->filename,
> -					      current_config_kvi->linenr);
> +		if (fn(entry->key, values->items[value_index].string, iter_data->inner) < 0)
> +			git_die_config_linenr(entry->key, kvi->filename,
> +					      kvi->linenr);
>  
> -		current_config_kvi = NULL;
> +		config_reader_set_kvi(iter_data->config_reader, NULL);
>  	}
>  }
>  
> @@ -2607,10 +2618,14 @@ static void repo_config_clear(struct repository *repo)
>  	git_configset_clear(repo->config);
>  }
>  
> -void repo_config(struct repository *repo, config_fn_t fn, void *data)
> +void repo_config(struct repository *repo, config_fn_t fn, void *data_inner)
>  {
> +	struct configset_iter_data data = CONFIGSET_ITER_INIT;
> +	data.inner = data_inner;
> +	data.config_reader = &the_reader;
> +
>  	git_config_check_init(repo);
> -	configset_iter(repo->config, fn, data);
> +	configset_iter(repo->config, fn, &data);
>  }

I'm not sure I agree with changing *data to *data_inner. An API caller
would be wondering why this function has a *data_inner signature vs
*data for other functions in config. You could instead change `struct
configset_iter_data data` to `struct configset_iter_data data_outer` to
preserve the function signature.

>  
>  int repo_config_get_value(struct repository *repo,
> @@ -2712,11 +2727,15 @@ static void read_protected_config(void)
>  	config_with_options(config_set_callback, &data, NULL, &opts);
>  }
>  
> -void git_protected_config(config_fn_t fn, void *data)
> +void git_protected_config(config_fn_t fn, void *data_inner)
>  {
> +	struct configset_iter_data data = CONFIGSET_ITER_INIT;
>  	if (!protected_config.hash_initialized)
>  		read_protected_config();
> -	configset_iter(&protected_config, fn, data);
> +	data.inner = data_inner;
> +	data.config_reader = &the_reader;
> +
> +	configset_iter(&protected_config, fn, &data);
>  }

ditto

>  
>  /* Functions used historically to read configuration from 'the_repository' */
> @@ -3823,8 +3842,8 @@ int parse_config_key(const char *var,
>  const char *current_config_origin_type(void)
>  {
>  	int type;
> -	if (current_config_kvi)
> -		type = current_config_kvi->origin_type;
> +	if (the_reader.config_kvi)
> +		type = the_reader.config_kvi->origin_type;
>  	else if(the_reader.source)
>  		type = the_reader.source->origin_type;
>  	else
> @@ -3869,8 +3888,8 @@ const char *config_scope_name(enum config_scope scope)
>  const char *current_config_name(void)
>  {
>  	const char *name;
> -	if (current_config_kvi)
> -		name = current_config_kvi->filename;
> +	if (the_reader.config_kvi)
> +		name = the_reader.config_kvi->filename;
>  	else if (the_reader.source)
>  		name = the_reader.source->name;
>  	else
> @@ -3880,16 +3899,16 @@ const char *current_config_name(void)
>  
>  enum config_scope current_config_scope(void)
>  {
> -	if (current_config_kvi)
> -		return current_config_kvi->scope;
> +	if (the_reader.config_kvi)
> +		return the_reader.config_kvi->scope;
>  	else
>  		return current_parsing_scope;
>  }
>  
>  int current_config_line(void)
>  {
> -	if (current_config_kvi)
> -		return current_config_kvi->linenr;
> +	if (the_reader.config_kvi)
> +		return the_reader.config_kvi->linenr;
>  	else
>  		return the_reader.source->linenr;
>  }
> -- 
> gitgitgadget
> 
> 
> 

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state
  2023-03-06 19:57 ` [PATCH 0/6] [RFC] config.c: use struct for config reading state Jonathan Tan
@ 2023-03-06 21:45   ` Glen Choo
  2023-03-06 22:38     ` Jonathan Tan
  0 siblings, 1 reply; 72+ messages in thread
From: Glen Choo @ 2023-03-06 21:45 UTC (permalink / raw)
  To: Jonathan Tan, Glen Choo via GitGitGadget
  Cc: Jonathan Tan, git, Emily Shaffer, Jeff King, Derrick Stolee

Jonathan Tan <jonathantanmy@google.com> writes:

> "Glen Choo via GitGitGadget" <gitgitgadget@gmail.com> writes:
>>  * We could create a new kind of "config_fn_t" that accepts "struct
>>    config_reader", e.g.
>>    
>>    typedef int (*config_fn_t)(const char *var, const char *val, void *data);
>>    +typedef int (*config_state_fn_t)(const struct config_reader *reader,
>>    const char *var, const char *val, void *data);
>>    
>>    and only adjust the callers that would actually reference "reader". This
>>    is less churn, but I couldn't find a great way to do this kind of
>>    'switching between config callback types' elegantly.
>
> To reduce churn, one thing that could be done alongside is to convert
> config-using code (which is...practically the rest of Git) to start
> using the configset interface (we seem to be using configsets internally
> anyway, as evidenced by repo_config()). That way, we would reduce the
> number of implementations of config_fn_t.

By configset interface, I believe you mean the O(1) lookup functions
like git_config_get_int() (which rely on the value being cached, but
don't necessarily accept "struct config_set" as an arg)? I think that
makes sense both from a performance and maintenance perspective.

>> = Questions
>> 
>>  * Is this worth merging without the extra work? There are some cleanups in
>>    this series that could make it valuable, but there are also some hacks
>>    (see 4/6) that aren't so great.
> I don't see 4/6 as a hack. It is true that the nature of the config_fn_t
> callback could change so that passing the reader would end up being
> done in yet another different way, but firstly, I don't think that will
> happen for quite some time, and secondly, it might not happen at all
> (right now, I think what's most likely to happen is that the rest of the
> Git code moves to configsets and only a fraction of the Git code would
> need to do low-level parsing, which would not have a problem passing the
> reader through the data object since they would probably need to pass
> other context anyway).

Given how painful it is to change the config_fn_t signature, I think it
is important to get as right as possible the first time. After I sent
this out, I thought of yet another possible config_fn_t signature
(since most callbacks only need diagnostic information, we could pass
"struct key_value_info" instead of the more privileged "struct
config_reader"), but given how many functions we'd have to change, it
seemed extremely difficult to even begin experimenting with this
different signature.

So I think you're right that we'd want to start by removing as many
config_fn_t implementations as possible. Perhaps we'd also want to
consider creating new abstractions for other situations where the key is
not known, e.g. "remote.<name>" and "branch.<name>".

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state
  2023-03-06 21:45   ` Glen Choo
@ 2023-03-06 22:38     ` Jonathan Tan
  2023-03-08 10:32       ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 72+ messages in thread
From: Jonathan Tan @ 2023-03-06 22:38 UTC (permalink / raw)
  To: Glen Choo
  Cc: Jonathan Tan, Glen Choo via GitGitGadget, git, Emily Shaffer,
	Jeff King, Derrick Stolee

Glen Choo <chooglen@google.com> writes:
> By configset interface, I believe you mean the O(1) lookup functions
> like git_config_get_int() (which rely on the value being cached, but
> don't necessarily accept "struct config_set" as an arg)? I think that
> makes sense both from a performance and maintenance perspective.

Ah, yes. (More precisely, not the one where you call something like
repo_config(), passing a callback function that.)

> Given how painful it is to change the config_fn_t signature, I think it
> is important to get as right as possible the first time. After I sent
> this out, I thought of yet another possible config_fn_t signature
> (since most callbacks only need diagnostic information, we could pass
> "struct key_value_info" instead of the more privileged "struct
> config_reader"), but given how many functions we'd have to change, it
> seemed extremely difficult to even begin experimenting with this
> different signature.

Yeah, the first change is the hardest. I think passing it a single
struct (so, instead of key, value, data, reader, and then any future
fields we would need) to which we can add fields later would mean that
we wouldn't need any changes beyond the first, though.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state
  2023-03-01  0:38 [PATCH 0/6] [RFC] config.c: use struct for config reading state Glen Choo via GitGitGadget
                   ` (6 preceding siblings ...)
  2023-03-06 19:57 ` [PATCH 0/6] [RFC] config.c: use struct for config reading state Jonathan Tan
@ 2023-03-07 11:57 ` Ævar Arnfjörð Bjarmason
  2023-03-07 18:22   ` Glen Choo
  2023-03-16  0:11 ` [PATCH v2 0/8] " Glen Choo via GitGitGadget
  8 siblings, 1 reply; 72+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2023-03-07 11:57 UTC (permalink / raw)
  To: Glen Choo via GitGitGadget
  Cc: git, Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee,
	Glen Choo


On Wed, Mar 01 2023, Glen Choo via GitGitGadget wrote:

> This RFC is preparation for config.[ch] to be libified as as part of the
> libification effort that Emily described in [1]. One of the first goals is
> to read config from a file, but the trouble with how config.c is written
> today is that all reading operations rely on global state, so before turning
> that into a library, we'd want to make that state non-global.
>
> This series gets us about halfway there; it does enough plumbing for a
> workable-but-kinda-ugly library interface, but with a little bit more work,
> I think we can get rid of global state in-tree as well. That requires a fair
> amount of work though, so I'd like to get thoughts on that before starting
> work.
>
> = Description
>
> This series extracts the global config reading state into "struct
> config_reader" and plumbs it through the config reading machinery. It's very
> similar to how we've plumbed "struct repository" and other 'context objects'
> in the past, except:
>
>  * The global state (named "the_reader") for the git process lives in a
>    config.c static variable, and not on "the_repository". See 3/6 for the
>    rationale.

I agree with the overall direction, but don't think that rationale in
3/6 is sufficient to go in this "the_reader" direction, as opposed to
sticking with and extending "the_repository" approach.

For orthagonal reasons (getting rid of some of the API duplication) I've
been carrying a patch to get rid of the "configset" part of the *public*
API, i.e. to have API users always use the "repo_config_*()" or
"git_config_*()" variants, that patch is at:
https://github.com/avar/git/commit/0233297a359bbda43a902dd0213aacdca82faa34

All of the rationale in your 3/6 is true now, but as that patch shows
the reason for why we have "the_repository" is for the trivial reason
that we want to access the repo's "config" member.

It's a bit distasteful, but that change argues that just mocking up a
"struct repository" with a "config" member pointing to a new configset
is better than maintaining an entirely different API just for those
cases where we need to parse a one-off file or whatever.

I think that going in that direction neatly solves the issues you're
noting here and in your 3/6, i.e. we'd always have this in the "repo"
object, so we'd just stick the persistent "reader" variables in the
"struct repository"'s "config" member.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state
  2023-03-07 11:57 ` Ævar Arnfjörð Bjarmason
@ 2023-03-07 18:22   ` Glen Choo
  2023-03-07 18:36     ` Ævar Arnfjörð Bjarmason
  2023-03-07 19:36     ` Junio C Hamano
  0 siblings, 2 replies; 72+ messages in thread
From: Glen Choo @ 2023-03-07 18:22 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason,
	Glen Choo via GitGitGadget
  Cc: git, Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee


Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> On Wed, Mar 01 2023, Glen Choo via GitGitGadget wrote:
>
>> This series extracts the global config reading state into "struct
>> config_reader" and plumbs it through the config reading machinery. It's very
>> similar to how we've plumbed "struct repository" and other 'context objects'
>> in the past, except:
>>
>>  * The global state (named "the_reader") for the git process lives in a
>>    config.c static variable, and not on "the_repository". See 3/6 for the
>>    rationale.
>
> I agree with the overall direction, but don't think that rationale in
> 3/6 is sufficient to go in this "the_reader" direction, as opposed to
> sticking with and extending "the_repository" approach.
>
> For orthagonal reasons (getting rid of some of the API duplication) I've
> been carrying a patch to get rid of the "configset" part of the *public*
> API, i.e. to have API users always use the "repo_config_*()" or
> "git_config_*()" variants, that patch is at:
> https://github.com/avar/git/commit/0233297a359bbda43a902dd0213aacdca82faa34

Those patches are probably worth sending, even if only as RFC. I found
it pretty hard to draft a substantial response without effectively doing
a full review of the patch.

> It's a bit distasteful, but that change argues that just mocking up a
> "struct repository" with a "config" member pointing to a new configset
> is better than maintaining an entirely different API just for those
> cases where we need to parse a one-off file or whatever.
>
> I think that going in that direction neatly solves the issues you're
> noting here and in your 3/6, i.e. we'd always have this in the "repo"
> object, so we'd just stick the persistent "reader" variables in the
> "struct repository"'s "config" member.

If I understand your proposal correctly, we would move the config
variables to the_repository. Then, any time a caller would like to work
with an individual file, it would init a new "struct repository" with a
clean set of config members (using repo_init_repo_blank_config() or
something) and reuse the repo_config_* API?

It is a workable solution, e.g. that approach would work around the
failures in test-tool and scalar that I observed. In the spirit of
libification, this feels like a kludge, though, since we'd be reverting
to using "struct repository" for more things instead of using more
well-scoped interfaces. IMO a better future for the config_set API would
be to move it into configset.c or something, where only users who want
the low level API would use it and everyone else would just pretend it
doesn't exist. This would be a little like libgit2's organization, where
'general config', 'config parsing' and 'in-memory config value
representations' are separate files, e.g.

  https://github.com/libgit2/libgit2/blob/main/src/libgit2/config.h
  https://github.com/libgit2/libgit2/blob/main/src/libgit2/config_parse.h
  https://github.com/libgit2/libgit2/blob/main/src/libgit2/config_entries.h

I also hesitate to put the config variables on the_repository, because
in the long term, I think "struct config_reader" can and should be
purely internal to config.c. But if we start advertising its existence
via the_repository, that might be an invitation to (ab)use that API and
make that transition harder.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state
  2023-03-07 18:22   ` Glen Choo
@ 2023-03-07 18:36     ` Ævar Arnfjörð Bjarmason
  2023-03-07 19:36     ` Junio C Hamano
  1 sibling, 0 replies; 72+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2023-03-07 18:36 UTC (permalink / raw)
  To: Glen Choo
  Cc: Glen Choo via GitGitGadget, git, Jonathan Tan, Emily Shaffer,
	Jeff King, Derrick Stolee


On Tue, Mar 07 2023, Glen Choo wrote:

> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>
>> On Wed, Mar 01 2023, Glen Choo via GitGitGadget wrote:
>>
>>> This series extracts the global config reading state into "struct
>>> config_reader" and plumbs it through the config reading machinery. It's very
>>> similar to how we've plumbed "struct repository" and other 'context objects'
>>> in the past, except:
>>>
>>>  * The global state (named "the_reader") for the git process lives in a
>>>    config.c static variable, and not on "the_repository". See 3/6 for the
>>>    rationale.
>>
>> I agree with the overall direction, but don't think that rationale in
>> 3/6 is sufficient to go in this "the_reader" direction, as opposed to
>> sticking with and extending "the_repository" approach.
>>
>> For orthagonal reasons (getting rid of some of the API duplication) I've
>> been carrying a patch to get rid of the "configset" part of the *public*
>> API, i.e. to have API users always use the "repo_config_*()" or
>> "git_config_*()" variants, that patch is at:
>> https://github.com/avar/git/commit/0233297a359bbda43a902dd0213aacdca82faa34
>
> Those patches are probably worth sending, even if only as RFC. I found
> it pretty hard to draft a substantial response without effectively doing
> a full review of the patch.

Yes, sorry. It's part of some changes on top of my outstanding config
API changes (just re-rolled at
https://lore.kernel.org/git/cover-v6-0.9-00000000000-20230307T180516Z-avarab@gmail.com/). Hopefully
those will land soon after the upcoming release, I'll try to submit this
(along with related changes) soon afterwards.

>> It's a bit distasteful, but that change argues that just mocking up a
>> "struct repository" with a "config" member pointing to a new
>> configset is better than maintaining an entirely different API just
>> for those cases where we need to parse a one-off file or whatever.
>>
>> I think that going in that direction neatly solves the issues you're
>> noting here and in your 3/6, i.e. we'd always have this in the "repo"
>> object, so we'd just stick the persistent "reader" variables in the
>> "struct repository"'s "config" member.
>
> If I understand your proposal correctly, we would move the config
> variables to the_repository. Then, any time a caller would like to work
> with an individual file, it would init a new "struct repository" with a
> clean set of config members (using repo_init_repo_blank_config() or
> something) and reuse the repo_config_* API?

It's certainly a hack, but so is introducing a new "the_reader"
singleton whose lifetime we need to manage seperately from
"the_repository" in the common case :)

I think a better argument for this is probably that if you try to change
repository.h so that we define "struct repository" thusly (The
"hash_algo" field being still there due to a very common macro):

	struct repository {
	        const struct git_hash_algo *hash_algo;
	        struct config_set *config;
	};

And then try to:

	make config.o

You'll get:
	
	$ make config.o
	    CC config.o
	config.c: In function ‘include_by_branch’:
	config.c:311:46: error: ‘struct repository’ has no member named ‘gitdir’
	  311 |         const char *refname = !the_repository->gitdir ?
	      |                                              ^~
	config.c: In function ‘repo_read_config’:
	config.c:2523:30: error: ‘struct repository’ has no member named ‘commondir’
	 2523 |         opts.commondir = repo->commondir;
	      |                              ^~
	config.c:2524:28: error: ‘struct repository’ has no member named ‘gitdir’
	 2524 |         opts.git_dir = repo->gitdir;
	      |                            ^~
	make: *** [Makefile:2719: config.o] Error 1

I.e. almost all of the config code doesn't care about the repository at
all, it just needs the "struct config_set" that's in the repository
struct.

With the linked-to change I'm arguing that just mocking it up sucks less
than carrying a duplicate set of API functions just for those rare cases
where we need to one-off read a config file.

But...

> It is a workable solution, e.g. that approach would work around the
> failures in test-tool and scalar that I observed. In the spirit of
> libification, this feels like a kludge, though, since we'd be reverting
> to using "struct repository" for more things instead of using more
> well-scoped interfaces. IMO a better future for the config_set API would
> be to move it into configset.c or something, where only users who want
> the low level API would use it and everyone else would just pretend it
> doesn't exist. This would be a little like libgit2's organization, where
> 'general config', 'config parsing' and 'in-memory config value
> representations' are separate files, e.g.
>
>   https://github.com/libgit2/libgit2/blob/main/src/libgit2/config.h
>   https://github.com/libgit2/libgit2/blob/main/src/libgit2/config_parse.h
>   https://github.com/libgit2/libgit2/blob/main/src/libgit2/config_entries.h
>
> I also hesitate to put the config variables on the_repository, because
> in the long term, I think "struct config_reader" can and should be
> purely internal to config.c. But if we start advertising its existence
> via the_repository, that might be an invitation to (ab)use that API and
> make that transition harder.

...yes it's a kludge, but I'd think if you're interested in more
generally fixing it that a better use of time would be to narrowly focus
on those cases where we don't have a "repository" now, i.e. the
configset API users my linked-to patch shows & amends.

Everything else that uses git_config_get_*(), repo_config_get_*() will
either use an already set up "the_repository", or lazily init it, see
the git_config_check_init() API users.

Or, we could have repo_config() and friends not take a "struct
repository", but another config-specific object which has the subset of
the three fields from that struct which it actually needs (config,
gitdir & commondir).

The mocking function I added in the linked-to commit was just a way of
getting to that via the shortest means.


^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state
  2023-03-07 18:22   ` Glen Choo
  2023-03-07 18:36     ` Ævar Arnfjörð Bjarmason
@ 2023-03-07 19:36     ` Junio C Hamano
  2023-03-07 22:53       ` Glen Choo
  1 sibling, 1 reply; 72+ messages in thread
From: Junio C Hamano @ 2023-03-07 19:36 UTC (permalink / raw)
  To: Glen Choo
  Cc: Ævar Arnfjörð Bjarmason,
	Glen Choo via GitGitGadget, git, Jonathan Tan, Emily Shaffer,
	Jeff King, Derrick Stolee

Glen Choo <chooglen@google.com> writes:

> ... In the spirit of
> libification, this feels like a kludge, though, since we'd be reverting
> to using "struct repository" for more things instead of using more
> well-scoped interfaces.

If you include "populate from system-wide, per-user, and repository
specific configuration files" as part of the API being libified,
your configsets cannot avoid being tied to a repository.  But I do
not think the reader needs to be in the repository.

> IMO a better future for the config_set API would
> be to move it into configset.c or something, where only users who want
> the low level API would use it and everyone else would just pretend it
> doesn't exist.

Isn't the use of the reader object purely transitory while you
populate the keys and values in a configset from a single file?  At
the layer to read and populate a configset from a single "source"
file, you still do not need repository.

Only when you say "I have a 'repo' instance and I want to read the
config variables from appropriate places", you call such a "read and
populate configset from a single source" helper three or four times
to populate repo->config.  Once a configset is populated, it or its
contents do not depend on the reader instance to function, so I do
not see how it benefits to have the reader in the repository object.


^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state
  2023-03-07 19:36     ` Junio C Hamano
@ 2023-03-07 22:53       ` Glen Choo
  2023-03-08  9:17         ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 72+ messages in thread
From: Glen Choo @ 2023-03-07 22:53 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Ævar Arnfjörð Bjarmason,
	Glen Choo via GitGitGadget, git, Jonathan Tan, Emily Shaffer,
	Jeff King, Derrick Stolee

Junio C Hamano <gitster@pobox.com> writes:

> If you include "populate from system-wide, per-user, and repository
> specific configuration files" as part of the API being libified,
> your configsets cannot avoid being tied to a repository.  But I do
> not think the reader needs to be in the repository.
>
> [...]
>
> Isn't the use of the reader object purely transitory while you
> populate the keys and values in a configset from a single file?  At
> the layer to read and populate a configset from a single "source"
> file, you still do not need repository.
>
> [...]
> Only when you say "I have a 'repo' instance and I want to read the
> config variables from appropriate places", you call such a "read and
> populate configset from a single source" helper three or four times
> to populate repo->config.  Once a configset is populated, it or its
> contents do not depend on the reader instance to function, so I do
> not see how it benefits to have the reader in the repository object.

Yes, exactly. Having a config_set on the repository makes sense, but I
don't see a good reason to have the reader on the repository.

If Ævar sends his series soon, it would be fruitful to see how that
series interacts with this one :)

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state
  2023-03-07 22:53       ` Glen Choo
@ 2023-03-08  9:17         ` Ævar Arnfjörð Bjarmason
  2023-03-08 23:18           ` Glen Choo
  0 siblings, 1 reply; 72+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2023-03-08  9:17 UTC (permalink / raw)
  To: Glen Choo
  Cc: Junio C Hamano, Glen Choo via GitGitGadget, git, Jonathan Tan,
	Emily Shaffer, Jeff King, Derrick Stolee


On Tue, Mar 07 2023, Glen Choo wrote:

> Junio C Hamano <gitster@pobox.com> writes:
>
>> If you include "populate from system-wide, per-user, and repository
>> specific configuration files" as part of the API being libified,
>> your configsets cannot avoid being tied to a repository.  But I do
>> not think the reader needs to be in the repository.
>>
>> [...]
>>
>> Isn't the use of the reader object purely transitory while you
>> populate the keys and values in a configset from a single file?  At
>> the layer to read and populate a configset from a single "source"
>> file, you still do not need repository.
>>
>> [...]
>> Only when you say "I have a 'repo' instance and I want to read the
>> config variables from appropriate places", you call such a "read and
>> populate configset from a single source" helper three or four times
>> to populate repo->config.  Once a configset is populated, it or its
>> contents do not depend on the reader instance to function, so I do
>> not see how it benefits to have the reader in the repository object.
>
> Yes, exactly. Having a config_set on the repository makes sense, but I
> don't see a good reason to have the reader on the repository.

Isn't Junio suggesting something different here?

I hadn't looked closely at this aspect of it, and just took it as a
given that we needed to persist this data outside of the configset
lifetime.

If that's not the case then we don't need it in the file scope, nor a
"struct repository" or whatever, and could just have it materialized by
git_config_check_init(), no? I.e. when we create the configset we'd
create it, and throw it away after the configset is created?

I.e. to address this note in your initial RFC:

	I think we can get rid of global state in-tree as well. That requires a fair
	amount of work though, so I'd like to get thoughts on that before starting
	work.

> If Ævar sends his series soon, it would be fruitful to see how that
> series interacts with this one :)

I tried merging this topic with that, and it didn't conflict textually
or semantically. I just raised it because I think with your 3/6 you're
needlessly tying yourself in knots, i.e. with this part:
	
	A more typical approach would be to put this struct on "the_repository",
	but that's a worse fit for this use case since config reading is not
	scoped to a repository. E.g. we can read config before the repository is
	known ("read_very_early_config()"), blatantly ignore the repo
	("read_protected_config()"), or read only from a file
	("git_config_from_file()"). This is especially evident in t5318 and
	t9210, where test-tool and scalar parse config but don't fully
	initialize "the_repository".

Out of those examples read_very_early_config() and
read_protected_config() already call config_with_options(), which
optionally uses a "struct repository" via the "repo" member of "struct
git_config_source".

I think we may have gotten lost in the weeds over what amounts to an
asthetic preference about how to do polymorphism in C. I'd be fine with
mocking up the "struct repository", but you could equally prepare and
pass some new "config_state" struct that would contain the required
information (including the configset).

As well as this in the CL:

	The catch (aka the reason I stopped halfway through) is that I
	couldn't find a way to expose "struct config_reader" state
	without some fairly big changes, complexity-wise or LoC-wise[...]

I didn't look into exactly why config_fn_t would need your new "reader",
but if you accept that we could stick such a thing into "the_repository"
then there's no catch or need for the churn to change all those
callbacks.

Of course something that wants to use the API as a "real" library would
need to use some alternate mechanism, as you reference in adding a new
"config_state_fn_t". You note:

	but I couldn't find a great way to do this kind of 'switching
	between config callback types' elegantly.

So, I don't know, but I was suggesting looking into doing that based on
"the_repository" in play...

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH 4/6] config.c: plumb the_reader through callbacks
  2023-03-01  0:38 ` [PATCH 4/6] config.c: plumb the_reader through callbacks Glen Choo via GitGitGadget
@ 2023-03-08  9:54   ` Ævar Arnfjörð Bjarmason
  2023-03-08 18:00     ` Glen Choo
  0 siblings, 1 reply; 72+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2023-03-08  9:54 UTC (permalink / raw)
  To: Glen Choo via GitGitGadget
  Cc: git, Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee,
	Glen Choo


On Wed, Mar 01 2023, Glen Choo via GitGitGadget wrote:

> From: Glen Choo <chooglen@google.com>
>
> The remaining references to "cf" are in config callback functions.
> Remove them by plumbing "struct config_reader" via the "*data" arg.
>
> [...]
>  	 * All remote URLs discovered when reading all config files.
> @@ -461,6 +457,7 @@ static int include_condition_is_true(struct config_source *cs,
>  static int git_config_include(const char *var, const char *value, void *data)
>  {
>  	struct config_include_data *inc = data;
> +	struct config_source *cs = inc->config_reader->source;
>  	const char *cond, *key;
>  	size_t cond_len;
>  	int ret;
> @@ -474,16 +471,16 @@ static int git_config_include(const char *var, const char *value, void *data)
>  		return ret;
>  
>  	if (!strcmp(var, "include.path"))
> -		ret = handle_path_include(cf, value, inc);
> +		ret = handle_path_include(cs, value, inc);

So, there's a lot of churn in this topic from renaming "cf" to "cs" all
over the place, I really wish that could be avoided for the size of the
overall diff, but haven't looked into in detail how easy that is, but...

>  
>  	if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
> -	    cond && include_condition_is_true(cf, inc, cond, cond_len) &&
> +	    cond && include_condition_is_true(cs, inc, cond, cond_len) &&
>  	    !strcmp(key, "path")) {
>  		config_fn_t old_fn = inc->fn;
>  
>  		if (inc->opts->unconditional_remote_url)
>  			inc->fn = forbid_remote_url;
> -		ret = handle_path_include(cf, value, inc);
> +		ret = handle_path_include(cs, value, inc);
>  		inc->fn = old_fn;
>  	}
>  
> @@ -2224,6 +2221,7 @@ int config_with_options(config_fn_t fn, void *data,
>  		inc.data = data;
>  		inc.opts = opts;
>  		inc.config_source = config_source;
> +		inc.config_reader = &the_reader;
>  		fn = git_config_include;
>  		data = &inc;
>  	}
> @@ -2344,7 +2342,9 @@ static struct config_set_element *configset_find_element(struct config_set *cs,
>  	return found_entry;
>  }
>  
> -static int configset_add_value(struct config_set *cs, const char *key, const char *value)
> +static int configset_add_value(struct config_reader *reader,
> +			       struct config_set *cs, const char *key,
> +			       const char *value)

...this is an existing name just seen in the context, but could we in
this topic at least avoid having a "cs" refer to both a "struct
config_set" and a "struct config_source" in the end-state?

I tried the below on top of this topic, and the overall diff looks much
nicer as a result:

diff --git a/config.c b/config.c
index 19bab84c47f..c27c128e3c0 100644
--- a/config.c
+++ b/config.c
@@ -199,7 +199,7 @@ static const char include_depth_advice[] = N_(
 "from\n"
 "	%s\n"
 "This might be due to circular includes.");
-static int handle_path_include(struct config_source *cs, const char *path,
+static int handle_path_include(struct config_source *cf, const char *path,
 			       struct config_include_data *inc)
 {
 	int ret = 0;
@@ -221,14 +221,14 @@ static int handle_path_include(struct config_source *cs, const char *path,
 	if (!is_absolute_path(path)) {
 		char *slash;
 
-		if (!cs || !cs->path) {
+		if (!cf || !cf->path) {
 			ret = error(_("relative config includes must come from files"));
 			goto cleanup;
 		}
 
-		slash = find_last_dir_sep(cs->path);
+		slash = find_last_dir_sep(cf->path);
 		if (slash)
-			strbuf_add(&buf, cs->path, slash - cs->path + 1);
+			strbuf_add(&buf, cf->path, slash - cf->path + 1);
 		strbuf_addstr(&buf, path);
 		path = buf.buf;
 	}
@@ -236,8 +236,8 @@ static int handle_path_include(struct config_source *cs, const char *path,
 	if (!access_or_die(path, R_OK, 0)) {
 		if (++inc->depth > MAX_INCLUDE_DEPTH)
 			die(_(include_depth_advice), MAX_INCLUDE_DEPTH, path,
-			    !cs ? "<unknown>" :
-			    cs->name ? cs->name :
+			    !cf ? "<unknown>" :
+			    cf->name ? cf->name :
 			    "the command line");
 		ret = git_config_from_file(git_config_include, path, inc);
 		inc->depth--;
@@ -254,7 +254,7 @@ static void add_trailing_starstar_for_dir(struct strbuf *pat)
 		strbuf_addstr(pat, "**");
 }
 
-static int prepare_include_condition_pattern(struct config_source *cs,
+static int prepare_include_condition_pattern(struct config_source *cf,
 					     struct strbuf *pat)
 {
 	struct strbuf path = STRBUF_INIT;
@@ -271,11 +271,11 @@ static int prepare_include_condition_pattern(struct config_source *cs,
 	if (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {
 		const char *slash;
 
-		if (!cs || !cs->path)
+		if (!cf || !cf->path)
 			return error(_("relative config include "
 				       "conditionals must come from files"));
 
-		strbuf_realpath(&path, cs->path, 1);
+		strbuf_realpath(&path, cf->path, 1);
 		slash = find_last_dir_sep(path.buf);
 		if (!slash)
 			BUG("how is this possible?");
@@ -290,7 +290,7 @@ static int prepare_include_condition_pattern(struct config_source *cs,
 	return prefix;
 }
 
-static int include_by_gitdir(struct config_source *cs,
+static int include_by_gitdir(struct config_source *cf,
 			     const struct config_options *opts,
 			     const char *cond, size_t cond_len, int icase)
 {
@@ -307,7 +307,7 @@ static int include_by_gitdir(struct config_source *cs,
 
 	strbuf_realpath(&text, git_dir, 1);
 	strbuf_add(&pattern, cond, cond_len);
-	prefix = prepare_include_condition_pattern(cs, &pattern);
+	prefix = prepare_include_condition_pattern(cf, &pattern);
 
 again:
 	if (prefix < 0)
@@ -446,16 +446,16 @@ static int include_by_remote_url(struct config_include_data *inc,
 					     inc->remote_urls);
 }
 
-static int include_condition_is_true(struct config_source *cs,
+static int include_condition_is_true(struct config_source *cf,
 				     struct config_include_data *inc,
 				     const char *cond, size_t cond_len)
 {
 	const struct config_options *opts = inc->opts;
 
 	if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
-		return include_by_gitdir(cs, opts, cond, cond_len, 0);
+		return include_by_gitdir(cf, opts, cond, cond_len, 0);
 	else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
-		return include_by_gitdir(cs, opts, cond, cond_len, 1);
+		return include_by_gitdir(cf, opts, cond, cond_len, 1);
 	else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
 		return include_by_branch(cond, cond_len);
 	else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,
@@ -469,7 +469,7 @@ static int include_condition_is_true(struct config_source *cs,
 static int git_config_include(const char *var, const char *value, void *data)
 {
 	struct config_include_data *inc = data;
-	struct config_source *cs = inc->config_reader->source;
+	struct config_source *cf = inc->config_reader->source;
 	const char *cond, *key;
 	size_t cond_len;
 	int ret;
@@ -483,16 +483,16 @@ static int git_config_include(const char *var, const char *value, void *data)
 		return ret;
 
 	if (!strcmp(var, "include.path"))
-		ret = handle_path_include(cs, value, inc);
+		ret = handle_path_include(cf, value, inc);
 
 	if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
-	    cond && include_condition_is_true(cs, inc, cond, cond_len) &&
+	    cond && include_condition_is_true(cf, inc, cond, cond_len) &&
 	    !strcmp(key, "path")) {
 		config_fn_t old_fn = inc->fn;
 
 		if (inc->opts->unconditional_remote_url)
 			inc->fn = forbid_remote_url;
-		ret = handle_path_include(cs, value, inc);
+		ret = handle_path_include(cf, value, inc);
 		inc->fn = old_fn;
 	}
 
@@ -817,21 +817,21 @@ int git_config_from_parameters(config_fn_t fn, void *data)
 	return ret;
 }
 
-static int get_next_char(struct config_source *cs)
+static int get_next_char(struct config_source *cf)
 {
-	int c = cs->do_fgetc(cs);
+	int c = cf->do_fgetc(cf);
 
 	if (c == '\r') {
 		/* DOS like systems */
-		c = cs->do_fgetc(cs);
+		c = cf->do_fgetc(cf);
 		if (c != '\n') {
 			if (c != EOF)
-				cs->do_ungetc(c, cs);
+				cf->do_ungetc(c, cf);
 			c = '\r';
 		}
 	}
 
-	if (c != EOF && ++cs->total_len > INT_MAX) {
+	if (c != EOF && ++cf->total_len > INT_MAX) {
 		/*
 		 * This is an absurdly long config file; refuse to parse
 		 * further in order to protect downstream code from integer
@@ -839,38 +839,38 @@ static int get_next_char(struct config_source *cs)
 		 * but we can mark EOF and put trash in the return value,
 		 * which will trigger a parse error.
 		 */
-		cs->eof = 1;
+		cf->eof = 1;
 		return 0;
 	}
 
 	if (c == '\n')
-		cs->linenr++;
+		cf->linenr++;
 	if (c == EOF) {
-		cs->eof = 1;
-		cs->linenr++;
+		cf->eof = 1;
+		cf->linenr++;
 		c = '\n';
 	}
 	return c;
 }
 
-static char *parse_value(struct config_source *cs)
+static char *parse_value(struct config_source *cf)
 {
 	int quote = 0, comment = 0, space = 0;
 
-	strbuf_reset(&cs->value);
+	strbuf_reset(&cf->value);
 	for (;;) {
-		int c = get_next_char(cs);
+		int c = get_next_char(cf);
 		if (c == '\n') {
 			if (quote) {
-				cs->linenr--;
+				cf->linenr--;
 				return NULL;
 			}
-			return cs->value.buf;
+			return cf->value.buf;
 		}
 		if (comment)
 			continue;
 		if (isspace(c) && !quote) {
-			if (cs->value.len)
+			if (cf->value.len)
 				space++;
 			continue;
 		}
@@ -881,9 +881,9 @@ static char *parse_value(struct config_source *cs)
 			}
 		}
 		for (; space; space--)
-			strbuf_addch(&cs->value, ' ');
+			strbuf_addch(&cf->value, ' ');
 		if (c == '\\') {
-			c = get_next_char(cs);
+			c = get_next_char(cf);
 			switch (c) {
 			case '\n':
 				continue;
@@ -903,18 +903,18 @@ static char *parse_value(struct config_source *cs)
 			default:
 				return NULL;
 			}
-			strbuf_addch(&cs->value, c);
+			strbuf_addch(&cf->value, c);
 			continue;
 		}
 		if (c == '"') {
 			quote = 1-quote;
 			continue;
 		}
-		strbuf_addch(&cs->value, c);
+		strbuf_addch(&cf->value, c);
 	}
 }
 
-static int get_value(struct config_source *cs, config_fn_t fn, void *data,
+static int get_value(struct config_source *cf, config_fn_t fn, void *data,
 		     struct strbuf *name)
 {
 	int c;
@@ -923,8 +923,8 @@ static int get_value(struct config_source *cs, config_fn_t fn, void *data,
 
 	/* Get the full name */
 	for (;;) {
-		c = get_next_char(cs);
-		if (cs->eof)
+		c = get_next_char(cf);
+		if (cf->eof)
 			break;
 		if (!iskeychar(c))
 			break;
@@ -932,13 +932,13 @@ static int get_value(struct config_source *cs, config_fn_t fn, void *data,
 	}
 
 	while (c == ' ' || c == '\t')
-		c = get_next_char(cs);
+		c = get_next_char(cf);
 
 	value = NULL;
 	if (c != '\n') {
 		if (c != '=')
 			return -1;
-		value = parse_value(cs);
+		value = parse_value(cf);
 		if (!value)
 			return -1;
 	}
@@ -947,21 +947,21 @@ static int get_value(struct config_source *cs, config_fn_t fn, void *data,
 	 * the line we just parsed during the call to fn to get
 	 * accurate line number in error messages.
 	 */
-	cs->linenr--;
+	cf->linenr--;
 	ret = fn(name->buf, value, data);
 	if (ret >= 0)
-		cs->linenr++;
+		cf->linenr++;
 	return ret;
 }
 
-static int get_extended_base_var(struct config_source *cs, struct strbuf *name,
+static int get_extended_base_var(struct config_source *cf, struct strbuf *name,
 				 int c)
 {
-	cs->subsection_case_sensitive = 0;
+	cf->subsection_case_sensitive = 0;
 	do {
 		if (c == '\n')
 			goto error_incomplete_line;
-		c = get_next_char(cs);
+		c = get_next_char(cf);
 	} while (isspace(c));
 
 	/* We require the format to be '[base "extension"]' */
@@ -970,13 +970,13 @@ static int get_extended_base_var(struct config_source *cs, struct strbuf *name,
 	strbuf_addch(name, '.');
 
 	for (;;) {
-		int c = get_next_char(cs);
+		int c = get_next_char(cf);
 		if (c == '\n')
 			goto error_incomplete_line;
 		if (c == '"')
 			break;
 		if (c == '\\') {
-			c = get_next_char(cs);
+			c = get_next_char(cf);
 			if (c == '\n')
 				goto error_incomplete_line;
 		}
@@ -984,25 +984,25 @@ static int get_extended_base_var(struct config_source *cs, struct strbuf *name,
 	}
 
 	/* Final ']' */
-	if (get_next_char(cs) != ']')
+	if (get_next_char(cf) != ']')
 		return -1;
 	return 0;
 error_incomplete_line:
-	cs->linenr--;
+	cf->linenr--;
 	return -1;
 }
 
-static int get_base_var(struct config_source *cs, struct strbuf *name)
+static int get_base_var(struct config_source *cf, struct strbuf *name)
 {
-	cs->subsection_case_sensitive = 1;
+	cf->subsection_case_sensitive = 1;
 	for (;;) {
-		int c = get_next_char(cs);
-		if (cs->eof)
+		int c = get_next_char(cf);
+		if (cf->eof)
 			return -1;
 		if (c == ']')
 			return 0;
 		if (isspace(c))
-			return get_extended_base_var(cs, name, c);
+			return get_extended_base_var(cf, name, c);
 		if (!iskeychar(c) && c != '.')
 			return -1;
 		strbuf_addch(name, tolower(c));
@@ -1015,7 +1015,7 @@ struct parse_event_data {
 	const struct config_options *opts;
 };
 
-static int do_event(struct config_source *cs, enum config_event_t type,
+static int do_event(struct config_source *cf, enum config_event_t type,
 		    struct parse_event_data *data)
 {
 	size_t offset;
@@ -1027,7 +1027,7 @@ static int do_event(struct config_source *cs, enum config_event_t type,
 	    data->previous_type == type)
 		return 0;
 
-	offset = cs->do_ftell(cs);
+	offset = cf->do_ftell(cf);
 	/*
 	 * At EOF, the parser always "inserts" an extra '\n', therefore
 	 * the end offset of the event is the current file position, otherwise
@@ -1047,12 +1047,12 @@ static int do_event(struct config_source *cs, enum config_event_t type,
 	return 0;
 }
 
-static int git_parse_source(struct config_source *cs, config_fn_t fn,
+static int git_parse_source(struct config_source *cf, config_fn_t fn,
 			    void *data, const struct config_options *opts)
 {
 	int comment = 0;
 	size_t baselen = 0;
-	struct strbuf *var = &cs->var;
+	struct strbuf *var = &cf->var;
 	int error_return = 0;
 	char *error_msg = NULL;
 
@@ -1067,7 +1067,7 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,
 	for (;;) {
 		int c;
 
-		c = get_next_char(cs);
+		c = get_next_char(cf);
 		if (bomptr && *bomptr) {
 			/* We are at the file beginning; skip UTF8-encoded BOM
 			 * if present. Sane editors won't put this in on their
@@ -1084,12 +1084,12 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,
 			}
 		}
 		if (c == '\n') {
-			if (cs->eof) {
-				if (do_event(cs, CONFIG_EVENT_EOF, &event_data) < 0)
+			if (cf->eof) {
+				if (do_event(cf, CONFIG_EVENT_EOF, &event_data) < 0)
 					return -1;
 				return 0;
 			}
-			if (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
+			if (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
 				return -1;
 			comment = 0;
 			continue;
@@ -1097,23 +1097,23 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,
 		if (comment)
 			continue;
 		if (isspace(c)) {
-			if (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
+			if (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
 					return -1;
 			continue;
 		}
 		if (c == '#' || c == ';') {
-			if (do_event(cs, CONFIG_EVENT_COMMENT, &event_data) < 0)
+			if (do_event(cf, CONFIG_EVENT_COMMENT, &event_data) < 0)
 					return -1;
 			comment = 1;
 			continue;
 		}
 		if (c == '[') {
-			if (do_event(cs, CONFIG_EVENT_SECTION, &event_data) < 0)
+			if (do_event(cf, CONFIG_EVENT_SECTION, &event_data) < 0)
 					return -1;
 
 			/* Reset prior to determining a new stem */
 			strbuf_reset(var);
-			if (get_base_var(cs, var) < 0 || var->len < 1)
+			if (get_base_var(cf, var) < 0 || var->len < 1)
 				break;
 			strbuf_addch(var, '.');
 			baselen = var->len;
@@ -1122,7 +1122,7 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,
 		if (!isalpha(c))
 			break;
 
-		if (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)
+		if (do_event(cf, CONFIG_EVENT_ENTRY, &event_data) < 0)
 			return -1;
 
 		/*
@@ -1132,42 +1132,42 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,
 		 */
 		strbuf_setlen(var, baselen);
 		strbuf_addch(var, tolower(c));
-		if (get_value(cs, fn, data, var) < 0)
+		if (get_value(cf, fn, data, var) < 0)
 			break;
 	}
 
-	if (do_event(cs, CONFIG_EVENT_ERROR, &event_data) < 0)
+	if (do_event(cf, CONFIG_EVENT_ERROR, &event_data) < 0)
 		return -1;
 
-	switch (cs->origin_type) {
+	switch (cf->origin_type) {
 	case CONFIG_ORIGIN_BLOB:
 		error_msg = xstrfmt(_("bad config line %d in blob %s"),
-				      cs->linenr, cs->name);
+				      cf->linenr, cf->name);
 		break;
 	case CONFIG_ORIGIN_FILE:
 		error_msg = xstrfmt(_("bad config line %d in file %s"),
-				      cs->linenr, cs->name);
+				      cf->linenr, cf->name);
 		break;
 	case CONFIG_ORIGIN_STDIN:
 		error_msg = xstrfmt(_("bad config line %d in standard input"),
-				      cs->linenr);
+				      cf->linenr);
 		break;
 	case CONFIG_ORIGIN_SUBMODULE_BLOB:
 		error_msg = xstrfmt(_("bad config line %d in submodule-blob %s"),
-				       cs->linenr, cs->name);
+				       cf->linenr, cf->name);
 		break;
 	case CONFIG_ORIGIN_CMDLINE:
 		error_msg = xstrfmt(_("bad config line %d in command line %s"),
-				       cs->linenr, cs->name);
+				       cf->linenr, cf->name);
 		break;
 	default:
 		error_msg = xstrfmt(_("bad config line %d in %s"),
-				      cs->linenr, cs->name);
+				      cf->linenr, cf->name);
 	}
 
 	switch (opts && opts->error_action ?
 		opts->error_action :
-		cs->default_error_action) {
+		cf->default_error_action) {
 	case CONFIG_ERROR_DIE:
 		die("%s", error_msg);
 		break;
@@ -1309,7 +1309,7 @@ int git_parse_ssize_t(const char *value, ssize_t *ret)
 }
 
 NORETURN
-static void die_bad_number(struct config_source *cs, const char *name,
+static void die_bad_number(struct config_source *cf, const char *name,
 			   const char *value)
 {
 	const char *error_type = (errno == ERANGE) ?
@@ -1319,28 +1319,28 @@ static void die_bad_number(struct config_source *cs, const char *name,
 	if (!value)
 		value = "";
 
-	if (!(cs && cs->name))
+	if (!(cf && cf->name))
 		die(_(bad_numeric), value, name, _(error_type));
 
-	switch (cs->origin_type) {
+	switch (cf->origin_type) {
 	case CONFIG_ORIGIN_BLOB:
 		die(_("bad numeric config value '%s' for '%s' in blob %s: %s"),
-		    value, name, cs->name, _(error_type));
+		    value, name, cf->name, _(error_type));
 	case CONFIG_ORIGIN_FILE:
 		die(_("bad numeric config value '%s' for '%s' in file %s: %s"),
-		    value, name, cs->name, _(error_type));
+		    value, name, cf->name, _(error_type));
 	case CONFIG_ORIGIN_STDIN:
 		die(_("bad numeric config value '%s' for '%s' in standard input: %s"),
 		    value, name, _(error_type));
 	case CONFIG_ORIGIN_SUBMODULE_BLOB:
 		die(_("bad numeric config value '%s' for '%s' in submodule-blob %s: %s"),
-		    value, name, cs->name, _(error_type));
+		    value, name, cf->name, _(error_type));
 	case CONFIG_ORIGIN_CMDLINE:
 		die(_("bad numeric config value '%s' for '%s' in command line %s: %s"),
-		    value, name, cs->name, _(error_type));
+		    value, name, cf->name, _(error_type));
 	default:
 		die(_("bad numeric config value '%s' for '%s' in %s: %s"),
-		    value, name, cs->name, _(error_type));
+		    value, name, cf->name, _(error_type));
 	}
 }
 
@@ -2274,12 +2274,12 @@ struct configset_iter_data {
 };
 #define CONFIGSET_ITER_INIT { 0 }
 
-static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
+static void configset_iter(struct config_set *cf, config_fn_t fn, void *data)
 {
 	int i, value_index;
 	struct string_list *values;
 	struct config_set_element *entry;
-	struct configset_list *list = &cs->list;
+	struct configset_list *list = &cf->list;
 	struct configset_iter_data *iter_data = data;
 
 	for (i = 0; i < list->nr; i++) {
@@ -2975,7 +2975,7 @@ static int store_aux_event(enum config_event_t type,
 			   size_t begin, size_t end, void *data)
 {
 	struct config_store_data *store = data;
-	struct config_source *cs = store->config_reader->source;
+	struct config_source *cf = store->config_reader->source;
 
 	ALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc);
 	store->parsed[store->parsed_nr].begin = begin;
@@ -2985,10 +2985,10 @@ static int store_aux_event(enum config_event_t type,
 	if (type == CONFIG_EVENT_SECTION) {
 		int (*cmpfn)(const char *, const char *, size_t);
 
-		if (cs->var.len < 2 || cs->var.buf[cs->var.len - 1] != '.')
-			return error(_("invalid section name '%s'"), cs->var.buf);
+		if (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')
+			return error(_("invalid section name '%s'"), cf->var.buf);
 
-		if (cs->subsection_case_sensitive)
+		if (cf->subsection_case_sensitive)
 			cmpfn = strncasecmp;
 		else
 			cmpfn = strncmp;
@@ -2996,8 +2996,8 @@ static int store_aux_event(enum config_event_t type,
 		/* Is this the section we were looking for? */
 		store->is_keys_section =
 			store->parsed[store->parsed_nr].is_keys_section =
-			cs->var.len - 1 == store->baselen &&
-			!cmpfn(cs->var.buf, store->key, store->baselen);
+			cf->var.len - 1 == store->baselen &&
+			!cmpfn(cf->var.buf, store->key, store->baselen);
 		if (store->is_keys_section) {
 			store->section_seen = 1;
 			ALLOC_GROW(store->seen, store->seen_nr + 1,
diff --git a/config.h b/config.h
index 7606246531a..5421c48e287 100644
--- a/config.h
+++ b/config.h
@@ -614,7 +614,7 @@ int git_config_get_expiry_in_days(const char *key, timestamp_t *, timestamp_t no
 
 struct key_value_info {
 	const char *filename;
-	int linenr;
+	int linenr2;
 	enum config_origin_type origin_type;
 	enum config_scope scope;
 };

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state
  2023-03-06 22:38     ` Jonathan Tan
@ 2023-03-08 10:32       ` Ævar Arnfjörð Bjarmason
  2023-03-08 23:09         ` Glen Choo
  0 siblings, 1 reply; 72+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2023-03-08 10:32 UTC (permalink / raw)
  To: Jonathan Tan
  Cc: Glen Choo, Glen Choo via GitGitGadget, git, Emily Shaffer,
	Jeff King, Derrick Stolee


On Mon, Mar 06 2023, Jonathan Tan wrote:

> Glen Choo <chooglen@google.com> writes:
>> By configset interface, I believe you mean the O(1) lookup functions
>> like git_config_get_int() (which rely on the value being cached, but
>> don't necessarily accept "struct config_set" as an arg)? I think that
>> makes sense both from a performance and maintenance perspective.
>
> Ah, yes. (More precisely, not the one where you call something like
> repo_config(), passing a callback function that.)
>
>> Given how painful it is to change the config_fn_t signature, I think it
>> is important to get as right as possible the first time. After I sent
>> this out, I thought of yet another possible config_fn_t signature
>> (since most callbacks only need diagnostic information, we could pass
>> "struct key_value_info" instead of the more privileged "struct
>> config_reader"), but given how many functions we'd have to change, it
>> seemed extremely difficult to even begin experimenting with this
>> different signature.
>
> Yeah, the first change is the hardest. I think passing it a single
> struct (so, instead of key, value, data, reader, and then any future
> fields we would need) to which we can add fields later would mean that
> we wouldn't need any changes beyond the first, though.

The more I've looked at this the more I'm convinced this is the wrong
direction.

For the configset API users we already have the line number, source
etc. in the "util" member, i.e. when we have an error in any API user
that uses the configset they can error about the specific line that
config came from.

I think this may have been conflated because e.g. for the configset to
get the "scope" we need to go from do_git_config_sequence(), which will
currently set "current_parsing_scope", all the way down to
configset_add_value(), and there we'll make use of the
"config_set_callback", which is a config_fn_t.

But that's all internal "static" functions, except
git_config_from_file() and git_config_from_file_with_options(), but
those have only a handful of callers.

But that's *different* than the user callbacks, which will be invoked
through a loop in configset_iter(), i.e. *after* we've parsed the
config, and are just getting the line number, scope etc. from the
configset.

There's other edge cases, e.g. current_config_line() will access the
global, but it only has two callers (one if we exclude the test
helper). But I think the answer there is to change the
config_read_push_default() code, not to give every current "config_fn_t"
implementation an extra parameter.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH 4/6] config.c: plumb the_reader through callbacks
  2023-03-08  9:54   ` Ævar Arnfjörð Bjarmason
@ 2023-03-08 18:00     ` Glen Choo
  2023-03-08 18:07       ` Junio C Hamano
  0 siblings, 1 reply; 72+ messages in thread
From: Glen Choo @ 2023-03-08 18:00 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason,
	Glen Choo via GitGitGadget
  Cc: git, Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> So, there's a lot of churn in this topic from renaming "cf" to "cs" all
> over the place, I really wish that could be avoided for the size of the
> overall diff, but haven't looked into in detail how easy that is, but...

Yeah, I think it can be avoided by taking Junio's suggestion to rename
the global "cf" to "cf_global" or something, then accepting a "cf"
parameter.

  https://lore.kernel.org/git/xmqqh6v1g1d3.fsf@gitster.g

>> -static int configset_add_value(struct config_set *cs, const char *key, const char *value)
>> +static int configset_add_value(struct config_reader *reader,
>> +			       struct config_set *cs, const char *key,
>> +			       const char *value)
>
> ...this is an existing name just seen in the context, but could we in
> this topic at least avoid having a "cs" refer to both a "struct
> config_set" and a "struct config_source" in the end-state?

Fair. This also irked me when I first saw it.

As an aside, I found "struct config_source cf" to be a bit of an eyesore
too. It's an unfortunate holdover from before 4d8dd1494e (config: make
parsing stack struct independent from actual data source, 2013-07-12)
when the struct was called "config_file". Unfortunately, "cs" is already
taken by at least one parameter, so maybe we'll never be able to
properly rename it...

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH 4/6] config.c: plumb the_reader through callbacks
  2023-03-08 18:00     ` Glen Choo
@ 2023-03-08 18:07       ` Junio C Hamano
  0 siblings, 0 replies; 72+ messages in thread
From: Junio C Hamano @ 2023-03-08 18:07 UTC (permalink / raw)
  To: Glen Choo
  Cc: Ævar Arnfjörð Bjarmason,
	Glen Choo via GitGitGadget, git, Jonathan Tan, Emily Shaffer,
	Jeff King, Derrick Stolee

Glen Choo <chooglen@google.com> writes:

> As an aside, I found "struct config_source cf" to be a bit of an eyesore
> too. It's an unfortunate holdover from before 4d8dd1494e (config: make
> parsing stack struct independent from actual data source, 2013-07-12)
> when the struct was called "config_file".

Ah, thanks for digging---I was wondering why it was called cf
myself.


^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state
  2023-03-08 10:32       ` Ævar Arnfjörð Bjarmason
@ 2023-03-08 23:09         ` Glen Choo
  0 siblings, 0 replies; 72+ messages in thread
From: Glen Choo @ 2023-03-08 23:09 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason, Jonathan Tan
  Cc: Glen Choo via GitGitGadget, git, Emily Shaffer, Jeff King,
	Derrick Stolee

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> On Mon, Mar 06 2023, Jonathan Tan wrote:
>
>> Glen Choo <chooglen@google.com> writes:
>>> By configset interface, I believe you mean the O(1) lookup functions
>>> like git_config_get_int() (which rely on the value being cached, but
>>> don't necessarily accept "struct config_set" as an arg)? I think that
>>> makes sense both from a performance and maintenance perspective.
>>
>> Ah, yes. (More precisely, not the one where you call something like
>> repo_config(), passing a callback function that.)
>>
>>> Given how painful it is to change the config_fn_t signature, I think it
>>> is important to get as right as possible the first time. After I sent
>>> this out, I thought of yet another possible config_fn_t signature
>>> (since most callbacks only need diagnostic information, we could pass
>>> "struct key_value_info" instead of the more privileged "struct
>>> config_reader"), but given how many functions we'd have to change, it
>>> seemed extremely difficult to even begin experimenting with this
>>> different signature.
>>
>> Yeah, the first change is the hardest. I think passing it a single
>> struct (so, instead of key, value, data, reader, and then any future
>> fields we would need) to which we can add fields later would mean that
>> we wouldn't need any changes beyond the first, though.
>
> For the configset API users we already have the line number, source
> etc. in the "util" member, i.e. when we have an error in any API user
> that uses the configset they can error about the specific line that
> config came from.

Yeah, and I think we should plumb the "util" (actually "struct
key_value_info") back to the users who need that diagnostic info, which
achieves the same purpose as plumbing the "config_reader" to the
callback functions (since the callback functions only need to read
diagnostic information), but it's better since it exposes fewer gory
details and we can refactor those using coccinelle. The difficult part
is replacing the callbacks with the configset API in the first place.

> I think this may have been conflated because e.g. for the configset to
> get the "scope" we need to go from do_git_config_sequence(), which will
> currently set "current_parsing_scope", all the way down to
> configset_add_value(), and there we'll make use of the
> "config_set_callback", which is a config_fn_t.

I think this is half-right (at least from a historical perspective). We
started by just reading auxiliary info like line number and file name
from the "current config source", but when we started caching values in
config sets, we had to find another way to share this auxiliary info.
The solution that 0d44a2dacc (config: return configset value for
current_config_ functions, 2016-05-26) gave us is to also cache the
auxiliary info, and then when we iterate the configset, we set the
global "current_config_kvi" to the cached value.

So they aren't conflated per se, since the reason for their existence is
so that API users don't have to care whether or not they are iterating a
file or iterating a configset. But as you've observed...

> But that's all internal "static" functions, except
> git_config_from_file() and git_config_from_file_with_options(), but
> those have only a handful of callers.
>
> But that's *different* than the user callbacks, which will be invoked
> through a loop in configset_iter(), i.e. *after* we've parsed the
> config, and are just getting the line number, scope etc. from the
> configset.

in practice, very few users read (and should be reading) config directly
from files. Most want the 'config for the whole repo' (which is handled
by the repo_* and git_* functions) and others will explicitly call
git_config_from_file*() so I don't think the config API needs to keep
users ignorant of 'whether the current config is cached or not'.

We could convert callbacks to the configset API, and if we then we plumb
the key_value_info to the configset API users, maybe we can retire
"current_config_kvi".

> There's other edge cases, e.g. current_config_line() will access the
> global, but it only has two callers (one if we exclude the test
> helper). But I think the answer there is to change the
> config_read_push_default() code, not to give every current "config_fn_t"
> implementation an extra parameter.

I agree that we should rewrite config_read_push_default(), but wouldn't
we still need to expose auxiliary information (line number, scope) to
users of the callback API? e.g. config.c:die_bad_number() uses the file
name [*], and builtin/config.c definitely needs it for 'git config -l'.
Also, an express purpose for this series is to prepare
git_config_from_file() to be used by callers out-of-tree, which would
definitely need that information.

I'm not sure if you're proposing to move state from "the_reader" to
something like "the_repository.config_state". I'd hesitate to take that
approach, since we're just swapping one global for another.

[*] config.c:die_bad_number() is actually a bit broken because it
doesn't use the cached kvi info from the config set. I'll probably send
a fixup patch to fix this.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state
  2023-03-08  9:17         ` Ævar Arnfjörð Bjarmason
@ 2023-03-08 23:18           ` Glen Choo
  0 siblings, 0 replies; 72+ messages in thread
From: Glen Choo @ 2023-03-08 23:18 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Junio C Hamano, Glen Choo via GitGitGadget, git, Jonathan Tan,
	Emily Shaffer, Jeff King, Derrick Stolee

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

>> Yes, exactly. Having a config_set on the repository makes sense, but I
>> don't see a good reason to have the reader on the repository.
>
> [...]
>
> I hadn't looked closely at this aspect of it, and just took it as a
> given that we needed to persist this data outside of the configset
> lifetime.

There really isn't a need to persist the "config_reader" state, we'd
only need it while reading the file, and as you've hinted, once we've
cached the info in the configset, we'd just use that instead.

> If that's not the case then we don't need it in the file scope, nor a
> "struct repository" or whatever, and could just have it materialized by
> git_config_check_init(), no? I.e. when we create the configset we'd
> create it, and throw it away after the configset is created?

Yes, except that I'm proposing that we should do it even lower in the
chain, like do_config_from().

> 	The catch (aka the reason I stopped halfway through) is that I
> 	couldn't find a way to expose "struct config_reader" state
> 	without some fairly big changes, complexity-wise or LoC-wise[...]
>
> I didn't look into exactly why config_fn_t would need your new "reader",

Ah, this is what I was referencing in the CL here:

  I believe that only config callbacks are accessing this [config
  reader] state, e.g. because they use the low-level information (like
  builtin/config.c printing the filename and scope of the value) or for
  error reporting (like git_parse_int() reporting the filename and line
  number of the value it failed to parse)

> but if you accept that we could stick such a thing into "the_repository"
> then there's no catch or need for the churn to change all those
> callbacks.
>
> Of course something that wants to use the API as a "real" library would
> need to use some alternate mechanism, as you reference in adding a new
> "config_state_fn_t". You note:
>
> 	but I couldn't find a great way to do this kind of 'switching
> 	between config callback types' elegantly.
>
> So, I don't know, but I was suggesting looking into doing that based on
> "the_repository" in play...

And yes, since the primary purpose is to make git_config_from_file()
usable by a library caller (and secondarily, prepare for a future where
reading config is thread-safer because of less global state, as Jonathan
discussed [1]), I'd prefer not to use the_repository.

1. https://lore.kernel.org/git/20230306195756.3399115-1-jonathantanmy@google.com

^ permalink raw reply	[flat|nested] 72+ messages in thread

* [PATCH v2 0/8] config.c: use struct for config reading state
  2023-03-01  0:38 [PATCH 0/6] [RFC] config.c: use struct for config reading state Glen Choo via GitGitGadget
                   ` (7 preceding siblings ...)
  2023-03-07 11:57 ` Ævar Arnfjörð Bjarmason
@ 2023-03-16  0:11 ` Glen Choo via GitGitGadget
  2023-03-16  0:11   ` [PATCH v2 1/8] config.c: plumb config_source through static fns Glen Choo via GitGitGadget
                     ` (11 more replies)
  8 siblings, 12 replies; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-16  0:11 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee,
	Calvin Wan, Ævar Arnfjörð Bjarmason, Glen Choo

Note to Junio: 8/8 (which renames "cs" -> "set") conflicts with
ab/config-multi-and-nonbool. The resolution is to rename "cs" -> "set" in
the conflicted hunks. This leaves a few "struct config_set cs" in
unconflicted hunks, but since 8/8 might be controversial, I don't think it's
worth your time to fix these. I'll reroll this and base it on top of
ab/config-multi-and-nonbool instead (since that is pretty stable and close
to being merged).

Thanks for the thoughtful review on v1, all. I'm sending this as a regular
series, not an RFC.

After reflecting on Ævar's responses on v1, I'm fairly convinced that
"struct config_reader" shouldn't exist in the long term. I've written my
thoughts on a good long term direction in the "Leftover bits" section. Based
on that, I've also updated my WIP libification patches [1] to remove "struct
config_reader" from the library interface, and think it looks a lot better
as a result.

= Changes in v2

 * To reduce churn, don't rename "struct config_source cf" to "cs" early in
   the series. Instead, rename the global "cf" to "cf_global", and leave the
   existing "cf"s untouched.
   
   Introduce 8/8 to get rid of the confusing acronym "struct config_source
   cf", but I don't mind ejecting it if it's too much churn.

 * Adjust 5/8 so to pass "struct config_reader" through args instead of
   "*data". v1 made the mistake of thinking "*data" was being passed to a
   callback, but it wasn't.

 * Add a 7/8 to fix a bug in die_bad_number(). I included this because it
   overlaps a little bit with the refactor here, but I don't mind ejecting
   this either.

 * Assorted BUG() message clarifications.

As a result of moving the rename, the range-diff is quite noisy. The diff
between the final commits is might be helpful instead [2] (I'll also send a
diff to the ML).

= Description

This series prepares for config.[ch] to be libified as as part of the
libification effort that Emily described in [3]. One of the first goals is
to read config from a file, but the trouble with how config.c is written
today is that all reading operations rely on global state, so before turning
that into a library, we'd want to make that state non-global.

This series doesn't remove all of the global state, but it gets us closer to
that goal by extracting the global config reading state into "struct
config_reader" and plumbing it through the config reading machinery. This
makes it possible to reuse the config machinery without global state, and to
enforce some constraints on "struct config_reader", which makes it more
predictable and easier to remove in the long run.

This process is very similar to how we've plumbed "struct repository" and
other 'context objects' in the past, except:

 * The global state (named "the_reader") for the git process lives in a
   config.c static variable, and not on "the_repository". See 3/6 for the
   rationale.

 * I've stopped short of adding "struct config_reader" to config.h public
   functions, since that would affect non-config.c callers.

Additionally, I've included a bugfix for die_bad_number() that became clear
as I did this refactor.

= Leftover bits

We still need a global "the_reader" because config callbacks are reading
auxiliary information about the config (e.g. line number, file name) via
global functions (e.g. current_config_line(), current_config_name()). This
is either because the callback uses this info directly (like
builtin/config.c printing the filename and scope of the value) or for error
reporting (like git_parse_int() reporting the filename of the value it
failed to parse).

If we had a way to plumb the state from "struct config_reader" to the config
callback functions, we could initialize "struct config_reader" in the config
machinery whenever we read config (instead of asking the caller to
initialize "struct config_reader" themselves), and config reading could
become a thread-safe operation. There isn't an obvious way to plumb this
state to config callbacks without adding an additional arg to config_fn_t
and incurring a lot of churn, but if we start replacing "config_fn_t" with
the configset API (which we've independently wanted for some time), this may
become feasible.

And if we do this, "struct config_reader" itself will probably become
obsolete, because we'd be able to plumb only the relevant state for the
current operation, e.g. if we are parsing a config file, we'd pass only the
config file parsing state, instead of "struct config_reader", which also
contains config set iterating state. In such a scenario, we'd probably want
to pass "struct key_value_info" to the config callback, since that's all the
callback should be interested in anyway. Interestingly, this was proposed by
Junio back in [4], and we didn't do this back then out of concern for the
churn (just like in v1).

[1]
https://github.com/git/git/compare/master...chooglen:git:config-lib-parsing
[2]
https://github.com/gitgitgadget/git/compare/pr-git-1463/chooglen/config/structify-reading-v1..chooglen:git:config/structify-reading
[3]
https://lore.kernel.org/git/CAJoAoZ=Cig_kLocxKGax31sU7Xe4==BGzC__Bg2_pr7krNq6MA@mail.gmail.com
[4]
https://lore.kernel.org/git/CAPc5daV6bdUKS-ExHmpT4Ppy2S832NXoyPw7aOLP7fG=WrBPgg@mail.gmail.com/

Glen Choo (8):
  config.c: plumb config_source through static fns
  config.c: don't assign to "cf_global" directly
  config.c: create config_reader and the_reader
  config.c: plumb the_reader through callbacks
  config.c: remove current_config_kvi
  config.c: remove current_parsing_scope
  config: report cached filenames in die_bad_number()
  config.c: rename "struct config_source cf"

 config.c               | 585 ++++++++++++++++++++++++-----------------
 config.h               |   1 +
 t/helper/test-config.c |  17 ++
 t/t1308-config-set.sh  |   9 +
 4 files changed, 369 insertions(+), 243 deletions(-)


base-commit: dadc8e6dacb629f46aee39bde90b6f09b73722eb
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1463%2Fchooglen%2Fconfig%2Fstructify-reading-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1463/chooglen/config/structify-reading-v2
Pull-Request: https://github.com/git/git/pull/1463

Range-diff vs v1:

 -:  ----------- > 1:  75d0f0efb79 config.c: plumb config_source through static fns
 2:  9f72cbb8d78 ! 2:  7555da0b0e0 config.c: don't assign to "cf" directly
     @@ Metadata
      Author: Glen Choo <chooglen@google.com>
      
       ## Commit message ##
     -    config.c: don't assign to "cf" directly
     +    config.c: don't assign to "cf_global" directly
      
     -    To make "cf" easier to remove, replace all direct assignments to it with
     -    function calls. This refactor has an additional maintainability benefit:
     -    all of these functions were manually implementing stack pop/push
     -    semantics on "struct config_source", so replacing them with function
     -    calls allows us to only implement this logic once.
     +    To make "cf_global" easier to remove, replace all direct assignments to
     +    it with function calls. This refactor has an additional maintainability
     +    benefit: all of these functions were manually implementing stack
     +    pop/push semantics on "struct config_source", so replacing them with
     +    function calls allows us to only implement this logic once.
      
          In this process, perform some now-obvious clean ups:
      
     -    - Drop some unnecessary "cf" assignments in populate_remote_urls().
     -      Since it was introduced in 399b198489 (config: include file if remote
     -      URL matches a glob, 2022-01-18), it has stored and restored the value
     -      of "cf" to ensure that it doesn't get accidentally mutated. However,
     -      this was never necessary since "do_config_from()" already pushes/pops
     -      "cf" further down the call chain.
     +    - Drop some unnecessary "cf_global" assignments in
     +      populate_remote_urls(). Since it was introduced in 399b198489 (config:
     +      include file if remote URL matches a glob, 2022-01-18), it has stored
     +      and restored the value of "cf_global" to ensure that it doesn't get
     +      accidentally mutated. However, this was never necessary since
     +      "do_config_from()" already pushes/pops "cf_global" further down the
     +      call chain.
      
          - Zero out every "struct config_source" with a dedicated initializer.
     -      This matters because the "struct config_source" is assigned to "cf"
     -      and we later 'pop the stack' by assigning "cf = cf->prev", but
     -      "cf->prev" could be pointing to uninitialized garbage.
     +      This matters because the "struct config_source" is assigned to
     +      "cf_global" and we later 'pop the stack' by assigning "cf_global =
     +      cf_global->prev", but "cf_global->prev" could be pointing to
     +      uninitialized garbage.
      
            Fortunately, this has never bothered us since we never try to read
     -      "cf" except while iterating through config, in which case, "cf" is
     -      either set to a sensible value (when parsing a file), or it is ignored
     -      (when iterating a configset). Later in the series, zero-ing out memory
     -      will also let us enforce the constraint that "cf" and
     -      "current_config_kvi" are never non-NULL together.
     +      "cf_global" except while iterating through config, in which case,
     +      "cf_global" is either set to a sensible value (when parsing a file),
     +      or it is ignored (when iterating a configset). Later in the series,
     +      zero-ing out memory will also let us enforce the constraint that
     +      "cf_global" and "current_config_kvi" are never non-NULL together.
      
          Signed-off-by: Glen Choo <chooglen@google.com>
      
     @@ config.c: static struct key_value_info *current_config_kvi;
        */
       static enum config_scope current_parsing_scope;
       
     -+static inline void config_state_push_source(struct config_source *top)
     ++static inline void config_reader_push_source(struct config_source *top)
      +{
     -+	if (cf)
     -+		top->prev = cf;
     -+	cf = top;
     ++	if (cf_global)
     ++		top->prev = cf_global;
     ++	cf_global = top;
      +}
      +
     -+static inline struct config_source *config_state_pop_source()
     ++static inline struct config_source *config_reader_pop_source()
      +{
      +	struct config_source *ret;
     -+	if (!cf)
     ++	if (!cf_global)
      +		BUG("tried to pop config source, but we weren't reading config");
     -+	ret = cf;
     -+	cf = cf->prev;
     ++	ret = cf_global;
     ++	cf_global = cf_global->prev;
      +	return ret;
      +}
      +
     @@ config.c: static void populate_remote_urls(struct config_include_data *inc)
       {
       	struct config_options opts;
       
     --	struct config_source *store_cf = cf;
     +-	struct config_source *store_cf = cf_global;
       	struct key_value_info *store_kvi = current_config_kvi;
       	enum config_scope store_scope = current_parsing_scope;
       
       	opts = *inc->opts;
       	opts.unconditional_remote_url = 1;
       
     --	cf = NULL;
     +-	cf_global = NULL;
       	current_config_kvi = NULL;
       	current_parsing_scope = 0;
       
     @@ config.c: static void populate_remote_urls(struct config_include_data *inc)
       	string_list_init_dup(inc->remote_urls);
       	config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);
       
     --	cf = store_cf;
     +-	cf_global = store_cf;
       	current_config_kvi = store_kvi;
       	current_parsing_scope = store_scope;
       }
     @@ config.c: int git_config_from_parameters(config_fn_t fn, void *data)
      +	struct config_source source = CONFIG_SOURCE_INIT;
       
      -	memset(&source, 0, sizeof(source));
     --	source.prev = cf;
     +-	source.prev = cf_global;
       	source.origin_type = CONFIG_ORIGIN_CMDLINE;
     --	cf = &source;
     -+	config_state_push_source(&source);
     +-	cf_global = &source;
     ++	config_reader_push_source(&source);
       
       	env = getenv(CONFIG_COUNT_ENVIRONMENT);
       	if (env) {
     @@ config.c: out:
       	strbuf_release(&envvar);
       	strvec_clear(&to_free);
       	free(envw);
     --	cf = source.prev;
     -+	config_state_pop_source();
     +-	cf_global = source.prev;
     ++	config_reader_pop_source();
       	return ret;
       }
       
     @@ config.c: static int do_config_from(struct config_source *top, config_fn_t fn, v
       	int ret;
       
       	/* push config-file parsing state stack */
     --	top->prev = cf;
     +-	top->prev = cf_global;
       	top->linenr = 1;
       	top->eof = 0;
       	top->total_len = 0;
       	strbuf_init(&top->value, 1024);
       	strbuf_init(&top->var, 1024);
     --	cf = top;
     -+	config_state_push_source(top);
     +-	cf_global = top;
     ++	config_reader_push_source(top);
       
     --	ret = git_parse_source(cf, fn, data, opts);
     -+	ret = git_parse_source(top, fn, data, opts);
     + 	ret = git_parse_source(top, fn, data, opts);
       
       	/* pop config-file parsing state stack */
       	strbuf_release(&top->value);
       	strbuf_release(&top->var);
     --	cf = top->prev;
     -+	config_state_pop_source();
     +-	cf_global = top->prev;
     ++	config_reader_pop_source();
       
       	return ret;
       }
 3:  751ce3e927d ! 3:  4347896f0a4 config.c: create config_reader and the_reader
     @@ Commit message
      
          Create "struct config_reader" to hold the state of the config source
          currently being read. Then, create a static instance of it,
     -    "the_reader", and use "the_reader.source" to replace references to "cf"
     -    in public functions.
     +    "the_reader", and use "the_reader.source" to replace references to
     +    "cf_global" in public functions.
      
          This doesn't create much immediate benefit (since we're mostly replacing
          static variables with a bigger static variable), but it prepares us for
     -    a future where this state doesn't have to be global; the "struct
     -    config_reader" could be provided by the caller, or constructed
     -    internally by a function like "do_config_from()".
     +    a future where this state doesn't have to be global; "struct
     +    config_reader" (or a similar struct) could be provided by the caller, or
     +    constructed internally by a function like "do_config_from()".
      
          A more typical approach would be to put this struct on "the_repository",
          but that's a worse fit for this use case since config reading is not
     @@ Commit message
          t9210, where test-tool and scalar parse config but don't fully
          initialize "the_repository".
      
     -    We could have also replaced the references to "cf" in callback functions
     -    (which are the only ones left), but we'll eventually plumb "the_reader"
     -    through the callback "*data" arg, which will allow us to rename "cf" to
     -    "cs" without changing line lengths. Until we remove "cf" altogether,
     -    add logic to "config_reader_*_source()" to keep "cf" and
     -    "the_reader.source" in sync.
     +    We could have also replaced the references to "cf_global" in callback
     +    functions (which are the only ones left), but we'll eventually plumb
     +    "the_reader" through the callback "*data" arg, so that would be
     +    unnecessary churn. Until we remove "cf_global" altogether, add logic to
     +    "config_reader_*_source()" to keep "cf_global" and "the_reader.source"
     +    in sync.
      
          Signed-off-by: Glen Choo <chooglen@google.com>
      
     @@ config.c: struct config_source {
      +struct config_reader {
      +	struct config_source *source;
      +};
     -+/* Only public functions should reference the_reader. */
     ++/*
     ++ * Where possible, prefer to accept "struct config_reader" as an arg than to use
     ++ * "the_reader". "the_reader" should only be used if that is infeasible, e.g. in
     ++ * a public function.
     ++ */
      +static struct config_reader the_reader;
      +
       /*
     @@ config.c: struct config_source {
        * or it's a function which can be reused for non-config purposes, and should
        * fall back to some sane behavior).
      + *
     -+ * FIXME "cf" has been replaced by "the_reader.source", remove
     -+ * "cf" once we plumb "the_reader" through all of the callback functions.
     ++ * FIXME "cf_global" has been replaced by "the_reader.source", remove
     ++ * "cf_global" once we plumb "the_reader" through all of the callback functions.
        */
     - static struct config_source *cf;
     + static struct config_source *cf_global;
       static struct key_value_info *current_config_kvi;
      @@ config.c: static struct key_value_info *current_config_kvi;
        */
       static enum config_scope current_parsing_scope;
       
     --static inline void config_state_push_source(struct config_source *top)
     +-static inline void config_reader_push_source(struct config_source *top)
      +static inline void config_reader_push_source(struct config_reader *reader,
      +					     struct config_source *top)
       {
     --	if (cf)
     --		top->prev = cf;
     --	cf = top;
     +-	if (cf_global)
     +-		top->prev = cf_global;
     +-	cf_global = top;
      +	if (reader->source)
      +		top->prev = reader->source;
      +	reader->source = top;
     -+	/* FIXME remove this when cf is removed. */
     -+	cf = reader->source;
     ++	/* FIXME remove this when cf_global is removed. */
     ++	cf_global = reader->source;
       }
       
     --static inline struct config_source *config_state_pop_source()
     +-static inline struct config_source *config_reader_pop_source()
      +static inline struct config_source *config_reader_pop_source(struct config_reader *reader)
       {
       	struct config_source *ret;
     --	if (!cf)
     +-	if (!cf_global)
      +	if (!reader->source)
       		BUG("tried to pop config source, but we weren't reading config");
     --	ret = cf;
     --	cf = cf->prev;
     +-	ret = cf_global;
     +-	cf_global = cf_global->prev;
      +	ret = reader->source;
      +	reader->source = reader->source->prev;
      +	/* FIXME remove this when cf is removed. */
     -+	cf = reader->source;
     ++	cf_global = reader->source;
       	return ret;
       }
       
     @@ config.c: int git_config_from_parameters(config_fn_t fn, void *data)
       	struct config_source source = CONFIG_SOURCE_INIT;
       
       	source.origin_type = CONFIG_ORIGIN_CMDLINE;
     --	config_state_push_source(&source);
     +-	config_reader_push_source(&source);
      +	config_reader_push_source(&the_reader, &source);
       
       	env = getenv(CONFIG_COUNT_ENVIRONMENT);
     @@ config.c: out:
       	strbuf_release(&envvar);
       	strvec_clear(&to_free);
       	free(envw);
     --	config_state_pop_source();
     +-	config_reader_pop_source();
      +	config_reader_pop_source(&the_reader);
       	return ret;
       }
     @@ config.c: int git_config_int(const char *name, const char *value)
       {
       	int ret;
       	if (!git_parse_int(value, &ret))
     --		die_bad_number(cf, name, value);
     +-		die_bad_number(cf_global, name, value);
      +		die_bad_number(the_reader.source, name, value);
       	return ret;
       }
     @@ config.c: int64_t git_config_int64(const char *name, const char *value)
       {
       	int64_t ret;
       	if (!git_parse_int64(value, &ret))
     --		die_bad_number(cf, name, value);
     +-		die_bad_number(cf_global, name, value);
      +		die_bad_number(the_reader.source, name, value);
       	return ret;
       }
     @@ config.c: unsigned long git_config_ulong(const char *name, const char *value)
       {
       	unsigned long ret;
       	if (!git_parse_ulong(value, &ret))
     --		die_bad_number(cf, name, value);
     +-		die_bad_number(cf_global, name, value);
      +		die_bad_number(the_reader.source, name, value);
       	return ret;
       }
     @@ config.c: ssize_t git_config_ssize_t(const char *name, const char *value)
       {
       	ssize_t ret;
       	if (!git_parse_ssize_t(value, &ret))
     --		die_bad_number(cf, name, value);
     +-		die_bad_number(cf_global, name, value);
      +		die_bad_number(the_reader.source, name, value);
       	return ret;
       }
     @@ config.c: static int do_config_from(struct config_source *top, config_fn_t fn, v
       	top->total_len = 0;
       	strbuf_init(&top->value, 1024);
       	strbuf_init(&top->var, 1024);
     --	config_state_push_source(top);
     +-	config_reader_push_source(top);
      +	config_reader_push_source(reader, top);
       
       	ret = git_parse_source(top, fn, data, opts);
     @@ config.c: static int do_config_from(struct config_source *top, config_fn_t fn, v
       	/* pop config-file parsing state stack */
       	strbuf_release(&top->value);
       	strbuf_release(&top->var);
     --	config_state_pop_source();
     +-	config_reader_pop_source();
      +	config_reader_pop_source(reader);
       
       	return ret;
     @@ config.c: const char *current_config_origin_type(void)
       	int type;
       	if (current_config_kvi)
       		type = current_config_kvi->origin_type;
     --	else if(cf)
     --		type = cf->origin_type;
     +-	else if(cf_global)
     +-		type = cf_global->origin_type;
      +	else if(the_reader.source)
      +		type = the_reader.source->origin_type;
       	else
     @@ config.c: const char *current_config_name(void)
       	const char *name;
       	if (current_config_kvi)
       		name = current_config_kvi->filename;
     --	else if (cf)
     --		name = cf->name;
     +-	else if (cf_global)
     +-		name = cf_global->name;
      +	else if (the_reader.source)
      +		name = the_reader.source->name;
       	else
     @@ config.c: int current_config_line(void)
       	if (current_config_kvi)
       		return current_config_kvi->linenr;
       	else
     --		return cf->linenr;
     +-		return cf_global->linenr;
      +		return the_reader.source->linenr;
       }
       
 4:  74a63fed705 ! 4:  22b69971749 config.c: plumb the_reader through callbacks
     @@ Metadata
       ## Commit message ##
          config.c: plumb the_reader through callbacks
      
     -    The remaining references to "cf" are in config callback functions.
     -    Remove them by plumbing "struct config_reader" via the "*data" arg.
     +    The remaining references to "cf_global" are in config callback
     +    functions. Remove them by plumbing "struct config_reader" via the
     +    "*data" arg.
      
     -    **RFC NOTE** If we had a way to expose "struct config_reader" to the
     -    config callback functions (the 'extra work' in the cover letter), we
     -    wouldn't need to also pass it via the "*data" arg. This is more of a
     -    hack to avoid doing that work now.
     +    In both of the callbacks here, we are only reading from
     +    "reader->source". So in the long run, if we had a way to expose readonly
     +    information from "reader->source" (probably in the form of "struct
     +    key_value_info"), we could undo this patch (i.e. remove "struct
     +    config_reader" fom "*data").
      
          Signed-off-by: Glen Choo <chooglen@google.com>
      
     @@ config.c: struct config_reader {
       static struct config_reader the_reader;
       
       /*
     -+ * FIXME The comments are temporarily out of date since "cf" been moved to
     -+ * the_reader, but not current_*.
     ++ * FIXME The comments are temporarily out of date since "cf_global" has been
     ++ * moved to the_reader, but not current_*.
      + *
        * These variables record the "current" config source, which
        * can be accessed by parsing callbacks.
     @@ config.c: static struct config_reader the_reader;
        * or it's a function which can be reused for non-config purposes, and should
        * fall back to some sane behavior).
      - *
     -- * FIXME "cf" has been replaced by "the_reader.source", remove
     -- * "cf" once we plumb "the_reader" through all of the callback functions.
     +- * FIXME "cf_global" has been replaced by "the_reader.source", remove
     +- * "cf_global" once we plumb "the_reader" through all of the callback functions.
        */
     --static struct config_source *cf;
     +-static struct config_source *cf_global;
       static struct key_value_info *current_config_kvi;
       
       /*
     @@ config.c: static inline void config_reader_push_source(struct config_reader *rea
       	if (reader->source)
       		top->prev = reader->source;
       	reader->source = top;
     --	/* FIXME remove this when cf is removed. */
     --	cf = reader->source;
     +-	/* FIXME remove this when cf_global is removed. */
     +-	cf_global = reader->source;
       }
       
       static inline struct config_source *config_reader_pop_source(struct config_reader *reader)
     @@ config.c: static inline struct config_source *config_reader_pop_source(struct co
       	ret = reader->source;
       	reader->source = reader->source->prev;
      -	/* FIXME remove this when cf is removed. */
     --	cf = reader->source;
     +-	cf_global = reader->source;
       	return ret;
       }
       
     @@ config.c: struct config_include_data {
       
       	/*
       	 * All remote URLs discovered when reading all config files.
     -@@ config.c: static int include_condition_is_true(struct config_source *cs,
     +@@ config.c: static int include_condition_is_true(struct config_source *cf,
       static int git_config_include(const char *var, const char *value, void *data)
       {
       	struct config_include_data *inc = data;
     -+	struct config_source *cs = inc->config_reader->source;
     ++	struct config_source *cf = inc->config_reader->source;
       	const char *cond, *key;
       	size_t cond_len;
       	int ret;
     @@ config.c: static int git_config_include(const char *var, const char *value, void
       		return ret;
       
       	if (!strcmp(var, "include.path"))
     --		ret = handle_path_include(cf, value, inc);
     -+		ret = handle_path_include(cs, value, inc);
     +-		ret = handle_path_include(cf_global, value, inc);
     ++		ret = handle_path_include(cf, value, inc);
       
       	if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
     --	    cond && include_condition_is_true(cf, inc, cond, cond_len) &&
     -+	    cond && include_condition_is_true(cs, inc, cond, cond_len) &&
     +-	    cond && include_condition_is_true(cf_global, inc, cond, cond_len) &&
     ++	    cond && include_condition_is_true(cf, inc, cond, cond_len) &&
       	    !strcmp(key, "path")) {
       		config_fn_t old_fn = inc->fn;
       
       		if (inc->opts->unconditional_remote_url)
       			inc->fn = forbid_remote_url;
     --		ret = handle_path_include(cf, value, inc);
     -+		ret = handle_path_include(cs, value, inc);
     +-		ret = handle_path_include(cf_global, value, inc);
     ++		ret = handle_path_include(cf, value, inc);
       		inc->fn = old_fn;
       	}
       
     @@ config.c: static int configset_add_value(struct config_set *cs, const char *key,
       	l_item->e = e;
       	l_item->value_index = e->value_list.nr - 1;
       
     --	if (!cf)
     +-	if (!cf_global)
      +	if (!reader->source)
       		BUG("configset_add_value has no source");
     --	if (cf->name) {
     --		kv_info->filename = strintern(cf->name);
     --		kv_info->linenr = cf->linenr;
     --		kv_info->origin_type = cf->origin_type;
     +-	if (cf_global->name) {
     +-		kv_info->filename = strintern(cf_global->name);
     +-		kv_info->linenr = cf_global->linenr;
     +-		kv_info->origin_type = cf_global->origin_type;
      +	if (reader->source->name) {
      +		kv_info->filename = strintern(reader->source->name);
      +		kv_info->linenr = reader->source->linenr;
     @@ config.c: static int store_aux_event(enum config_event_t type,
       			   size_t begin, size_t end, void *data)
       {
       	struct config_store_data *store = data;
     -+	struct config_source *cs = store->config_reader->source;
     +-	/*
     +-	 * FIXME Keep using "cf" so that we can avoid rewrapping a
     +-	 * really long line below. Remove this when "cf" gets plumbed
     +-	 * correctly.
     +-	 */
     +-	struct config_source *cf = cf_global;
     ++	struct config_source *cf = store->config_reader->source;
       
       	ALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc);
       	store->parsed[store->parsed_nr].begin = begin;
     -@@ config.c: static int store_aux_event(enum config_event_t type,
     - 	if (type == CONFIG_EVENT_SECTION) {
     - 		int (*cmpfn)(const char *, const char *, size_t);
     - 
     --		if (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')
     --			return error(_("invalid section name '%s'"), cf->var.buf);
     -+		if (cs->var.len < 2 || cs->var.buf[cs->var.len - 1] != '.')
     -+			return error(_("invalid section name '%s'"), cs->var.buf);
     - 
     --		if (cf->subsection_case_sensitive)
     -+		if (cs->subsection_case_sensitive)
     - 			cmpfn = strncasecmp;
     - 		else
     - 			cmpfn = strncmp;
     -@@ config.c: static int store_aux_event(enum config_event_t type,
     - 		/* Is this the section we were looking for? */
     - 		store->is_keys_section =
     - 			store->parsed[store->parsed_nr].is_keys_section =
     --			cf->var.len - 1 == store->baselen &&
     --			!cmpfn(cf->var.buf, store->key, store->baselen);
     -+			cs->var.len - 1 == store->baselen &&
     -+			!cmpfn(cs->var.buf, store->key, store->baselen);
     - 		if (store->is_keys_section) {
     - 			store->section_seen = 1;
     - 			ALLOC_GROW(store->seen, store->seen_nr + 1,
      @@ config.c: int git_config_set_multivar_in_file_gently(const char *config_filename,
       	char *filename_buf = NULL;
       	char *contents = NULL;
 5:  c05b33ab29d ! 5:  afb6e3e318d config.c: remove current_config_kvi
     @@ Commit message
          config.c: remove current_config_kvi
      
          Add ".config_kvi" to "struct config_reader" and replace
     -    "current_config_kvi" with "the_reader.config_kvi", either in-place (in
     -    public functions) or by passing "the_reader" to the "*data" arg of
     -    callback functions.
     +    "current_config_kvi" with "the_reader.config_kvi", plumbing "struct
     +    config_reader" where necesssary.
      
          Also, introduce a setter function for ".config_kvi", which allows us to
          enforce the contraint that only one of ".source" and ".config_kvi" can
     @@ config.c: struct config_source {
       	struct config_source *source;
      +	struct key_value_info *config_kvi;
       };
     - /* Only public functions should reference the_reader. */
     + /*
     +  * Where possible, prefer to accept "struct config_reader" as an arg than to use
     +@@ config.c: struct config_reader {
     +  */
       static struct config_reader the_reader;
       
      -/*
     -- * FIXME The comments are temporarily out of date since "cf" been moved to
     -- * the_reader, but not current_*.
     +- * FIXME The comments are temporarily out of date since "cf_global" has been
     +- * moved to the_reader, but not current_*.
      - *
      - * These variables record the "current" config source, which
      - * can be accessed by parsing callbacks.
      - *
     -- * The "cf" variable will be non-NULL only when we are actually parsing a real
     -- * config source (file, blob, cmdline, etc).
     +- * The "cf_global" variable will be non-NULL only when we are actually
     +- * parsing a real config source (file, blob, cmdline, etc).
      - *
      - * The "current_config_kvi" variable will be non-NULL only when we are feeding
      - * cached config from a configset into a callback.
     @@ config.c: static enum config_scope current_parsing_scope;
       					     struct config_source *top)
       {
      +	if (reader->config_kvi)
     -+		BUG("source should only be set when parsing a config source");
     ++		BUG("source should not be set while iterating a config set");
       	if (reader->source)
       		top->prev = reader->source;
       	reader->source = top;
     @@ config.c: static inline struct config_source *config_reader_pop_source(struct co
      +					 struct key_value_info *kvi)
      +{
      +	if (kvi && reader->source)
     -+		BUG("kvi should only be set when iterating through configset");
     ++		BUG("kvi should not be set while parsing a config source");
      +	reader->config_kvi = kvi;
      +}
      +
     @@ config.c: int config_with_options(config_fn_t fn, void *data,
       	return ret;
       }
       
     -+struct configset_iter_data {
     -+	struct config_reader *config_reader;
     -+	void *inner;
     -+};
     -+#define CONFIGSET_ITER_INIT { 0 }
     -+
     - static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
     +-static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
     ++static void configset_iter(struct config_reader *reader, struct config_set *cs,
     ++			   config_fn_t fn, void *data)
       {
       	int i, value_index;
       	struct string_list *values;
     - 	struct config_set_element *entry;
     - 	struct configset_list *list = &cs->list;
     -+	struct configset_iter_data *iter_data = data;
     - 
     - 	for (i = 0; i < list->nr; i++) {
     -+		struct key_value_info *kvi;
     - 		entry = list->items[i].e;
     +@@ config.c: static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
       		value_index = list->items[i].value_index;
       		values = &entry->value_list;
       
      -		current_config_kvi = values->items[value_index].util;
     -+		kvi = values->items[value_index].util;
     -+		config_reader_set_kvi(iter_data->config_reader, kvi);
     ++		config_reader_set_kvi(reader, values->items[value_index].util);
       
     --		if (fn(entry->key, values->items[value_index].string, data) < 0)
     --			git_die_config_linenr(entry->key,
     + 		if (fn(entry->key, values->items[value_index].string, data) < 0)
     + 			git_die_config_linenr(entry->key,
      -					      current_config_kvi->filename,
      -					      current_config_kvi->linenr);
     -+		if (fn(entry->key, values->items[value_index].string, iter_data->inner) < 0)
     -+			git_die_config_linenr(entry->key, kvi->filename,
     -+					      kvi->linenr);
     ++					      reader->config_kvi->filename,
     ++					      reader->config_kvi->linenr);
       
      -		current_config_kvi = NULL;
     -+		config_reader_set_kvi(iter_data->config_reader, NULL);
     ++		config_reader_set_kvi(reader, NULL);
       	}
       }
       
      @@ config.c: static void repo_config_clear(struct repository *repo)
     - 	git_configset_clear(repo->config);
     - }
     - 
     --void repo_config(struct repository *repo, config_fn_t fn, void *data)
     -+void repo_config(struct repository *repo, config_fn_t fn, void *data_inner)
     + void repo_config(struct repository *repo, config_fn_t fn, void *data)
       {
     -+	struct configset_iter_data data = CONFIGSET_ITER_INIT;
     -+	data.inner = data_inner;
     -+	data.config_reader = &the_reader;
     -+
       	git_config_check_init(repo);
      -	configset_iter(repo->config, fn, data);
     -+	configset_iter(repo->config, fn, &data);
     ++	configset_iter(&the_reader, repo->config, fn, data);
       }
       
       int repo_config_get_value(struct repository *repo,
     -@@ config.c: static void read_protected_config(void)
     - 	config_with_options(config_set_callback, &data, NULL, &opts);
     - }
     - 
     --void git_protected_config(config_fn_t fn, void *data)
     -+void git_protected_config(config_fn_t fn, void *data_inner)
     +@@ config.c: void git_protected_config(config_fn_t fn, void *data)
       {
     -+	struct configset_iter_data data = CONFIGSET_ITER_INIT;
       	if (!protected_config.hash_initialized)
       		read_protected_config();
      -	configset_iter(&protected_config, fn, data);
     -+	data.inner = data_inner;
     -+	data.config_reader = &the_reader;
     -+
     -+	configset_iter(&protected_config, fn, &data);
     ++	configset_iter(&the_reader, &protected_config, fn, data);
       }
       
       /* Functions used historically to read configuration from 'the_repository' */
 6:  b1e866f9216 ! 6:  a57e35163ae config.c: remove current_parsing_scope
     @@ config.c: struct config_reader {
      +	 */
      +	enum config_scope parsing_scope;
       };
     - /* Only public functions should reference the_reader. */
     + /*
     +  * Where possible, prefer to accept "struct config_reader" as an arg than to use
     +@@ config.c: struct config_reader {
     +  */
       static struct config_reader the_reader;
       
      -/*
      - * Similar to the variables above, this gives access to the "scope" of the
      - * current value (repo, global, etc). For cached values, it can be found via
      - * the current_config_kvi as above. During parsing, the current value can be
     -- * found in this variable. It's not part of "cf" because it transcends a single
     -- * file (i.e., a file included from .git/config is still in "repo" scope).
     +- * found in this variable. It's not part of "cf_global" because it transcends a
     +- * single file (i.e., a file included from .git/config is still in "repo"
     +- * scope).
      - */
      -static enum config_scope current_parsing_scope;
      -
     @@ config.c: static inline struct config_source *config_reader_pop_source(struct co
       {
      -	if (kvi && reader->source)
      +	if (kvi && (reader->source || reader->parsing_scope))
     - 		BUG("kvi should only be set when iterating through configset");
     + 		BUG("kvi should not be set while parsing a config source");
       	reader->config_kvi = kvi;
       }
       
 -:  ----------- > 7:  3c83d9535a0 config: report cached filenames in die_bad_number()
 1:  ad513d832d8 ! 8:  9aec9092fdf config.c: plumb config_source through static fns
     @@ Metadata
      Author: Glen Choo <chooglen@google.com>
      
       ## Commit message ##
     -    config.c: plumb config_source through static fns
     +    config.c: rename "struct config_source cf"
      
     -    This reduces the direct dependence on the "cf" static variable, which
     -    will make it easier to remove in a later commit. The plumbed arg is
     -    named "cs" to differentiate between it and the static variable.
     -
     -    In some cases (public functions and config callback functions), there
     -    isn't an obvious way to plumb "struct config_source" through function
     -    args. As a workaround, add references to "cf" that we'll address in
     -    later commits.
     -
     -    The remaining references to "cf" are direct assignments to "cf", which
     -    we'll also address in a later commit.
     +    The "cf" name is a holdover from before 4d8dd1494e (config: make parsing
     +    stack struct independent from actual data source, 2013-07-12), when the
     +    struct was named config_file. Since that acronym no longer makes sense,
     +    rename "cf" to "cs". In some places, we have "struct config_set cs", so
     +    to avoid conflict, rename those "cs" to "set" ("config_set" would be
     +    more descriptive, but it's much longer and would require us to rewrap
     +    several lines).
      
          Signed-off-by: Glen Choo <chooglen@google.com>
      
     @@ config.c: static const char include_depth_advice[] = N_(
       "from\n"
       "	%s\n"
       "This might be due to circular includes.");
     --static int handle_path_include(const char *path, struct config_include_data *inc)
     +-static int handle_path_include(struct config_source *cf, const char *path,
      +static int handle_path_include(struct config_source *cs, const char *path,
     -+			       struct config_include_data *inc)
     + 			       struct config_include_data *inc)
       {
       	int ret = 0;
     - 	struct strbuf buf = STRBUF_INIT;
     -@@ config.c: static int handle_path_include(const char *path, struct config_include_data *inc
     +@@ config.c: static int handle_path_include(struct config_source *cf, const char *path,
       	if (!is_absolute_path(path)) {
       		char *slash;
       
     @@ config.c: static int handle_path_include(const char *path, struct config_include
       		strbuf_addstr(&buf, path);
       		path = buf.buf;
       	}
     -@@ config.c: static int handle_path_include(const char *path, struct config_include_data *inc
     +@@ config.c: static int handle_path_include(struct config_source *cf, const char *path,
       	if (!access_or_die(path, R_OK, 0)) {
       		if (++inc->depth > MAX_INCLUDE_DEPTH)
       			die(_(include_depth_advice), MAX_INCLUDE_DEPTH, path,
     @@ config.c: static void add_trailing_starstar_for_dir(struct strbuf *pat)
       		strbuf_addstr(pat, "**");
       }
       
     --static int prepare_include_condition_pattern(struct strbuf *pat)
     +-static int prepare_include_condition_pattern(struct config_source *cf,
      +static int prepare_include_condition_pattern(struct config_source *cs,
     -+					     struct strbuf *pat)
     + 					     struct strbuf *pat)
       {
       	struct strbuf path = STRBUF_INIT;
     - 	char *expanded;
     -@@ config.c: static int prepare_include_condition_pattern(struct strbuf *pat)
     +@@ config.c: static int prepare_include_condition_pattern(struct config_source *cf,
       	if (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {
       		const char *slash;
       
     @@ config.c: static int prepare_include_condition_pattern(struct strbuf *pat)
       		slash = find_last_dir_sep(path.buf);
       		if (!slash)
       			BUG("how is this possible?");
     -@@ config.c: static int prepare_include_condition_pattern(struct strbuf *pat)
     +@@ config.c: static int prepare_include_condition_pattern(struct config_source *cf,
       	return prefix;
       }
       
     --static int include_by_gitdir(const struct config_options *opts,
     +-static int include_by_gitdir(struct config_source *cf,
      +static int include_by_gitdir(struct config_source *cs,
     -+			     const struct config_options *opts,
     + 			     const struct config_options *opts,
       			     const char *cond, size_t cond_len, int icase)
       {
     - 	struct strbuf text = STRBUF_INIT;
     -@@ config.c: static int include_by_gitdir(const struct config_options *opts,
     +@@ config.c: static int include_by_gitdir(struct config_source *cf,
       
       	strbuf_realpath(&text, git_dir, 1);
       	strbuf_add(&pattern, cond, cond_len);
     --	prefix = prepare_include_condition_pattern(&pattern);
     +-	prefix = prepare_include_condition_pattern(cf, &pattern);
      +	prefix = prepare_include_condition_pattern(cs, &pattern);
       
       again:
     @@ config.c: static int include_by_remote_url(struct config_include_data *inc,
       					     inc->remote_urls);
       }
       
     --static int include_condition_is_true(struct config_include_data *inc,
     +-static int include_condition_is_true(struct config_source *cf,
      +static int include_condition_is_true(struct config_source *cs,
     -+				     struct config_include_data *inc,
     + 				     struct config_include_data *inc,
       				     const char *cond, size_t cond_len)
       {
       	const struct config_options *opts = inc->opts;
       
       	if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
     --		return include_by_gitdir(opts, cond, cond_len, 0);
     +-		return include_by_gitdir(cf, opts, cond, cond_len, 0);
      +		return include_by_gitdir(cs, opts, cond, cond_len, 0);
       	else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
     --		return include_by_gitdir(opts, cond, cond_len, 1);
     +-		return include_by_gitdir(cf, opts, cond, cond_len, 1);
      +		return include_by_gitdir(cs, opts, cond, cond_len, 1);
       	else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
       		return include_by_branch(cond, cond_len);
       	else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,
     +@@ config.c: static int include_condition_is_true(struct config_source *cf,
     + static int git_config_include(const char *var, const char *value, void *data)
     + {
     + 	struct config_include_data *inc = data;
     +-	struct config_source *cf = inc->config_reader->source;
     ++	struct config_source *cs = inc->config_reader->source;
     + 	const char *cond, *key;
     + 	size_t cond_len;
     + 	int ret;
      @@ config.c: static int git_config_include(const char *var, const char *value, void *data)
       		return ret;
       
       	if (!strcmp(var, "include.path"))
     --		ret = handle_path_include(value, inc);
     -+		ret = handle_path_include(cf, value, inc);
     +-		ret = handle_path_include(cf, value, inc);
     ++		ret = handle_path_include(cs, value, inc);
       
       	if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
     --	    cond && include_condition_is_true(inc, cond, cond_len) &&
     -+	    cond && include_condition_is_true(cf, inc, cond, cond_len) &&
     +-	    cond && include_condition_is_true(cf, inc, cond, cond_len) &&
     ++	    cond && include_condition_is_true(cs, inc, cond, cond_len) &&
       	    !strcmp(key, "path")) {
       		config_fn_t old_fn = inc->fn;
       
       		if (inc->opts->unconditional_remote_url)
       			inc->fn = forbid_remote_url;
     --		ret = handle_path_include(value, inc);
     -+		ret = handle_path_include(cf, value, inc);
     +-		ret = handle_path_include(cf, value, inc);
     ++		ret = handle_path_include(cs, value, inc);
       		inc->fn = old_fn;
       	}
       
     @@ config.c: out:
       	return ret;
       }
       
     --static int get_next_char(void)
     +-static int get_next_char(struct config_source *cf)
      +static int get_next_char(struct config_source *cs)
       {
      -	int c = cf->do_fgetc(cf);
     @@ config.c: out:
       		/*
       		 * This is an absurdly long config file; refuse to parse
       		 * further in order to protect downstream code from integer
     -@@ config.c: static int get_next_char(void)
     +@@ config.c: static int get_next_char(struct config_source *cf)
       		 * but we can mark EOF and put trash in the return value,
       		 * which will trigger a parse error.
       		 */
     @@ config.c: static int get_next_char(void)
       	return c;
       }
       
     --static char *parse_value(void)
     +-static char *parse_value(struct config_source *cf)
      +static char *parse_value(struct config_source *cs)
       {
       	int quote = 0, comment = 0, space = 0;
     @@ config.c: static int get_next_char(void)
      -	strbuf_reset(&cf->value);
      +	strbuf_reset(&cs->value);
       	for (;;) {
     --		int c = get_next_char();
     +-		int c = get_next_char(cf);
      +		int c = get_next_char(cs);
       		if (c == '\n') {
       			if (quote) {
     @@ config.c: static int get_next_char(void)
       				space++;
       			continue;
       		}
     -@@ config.c: static char *parse_value(void)
     +@@ config.c: static char *parse_value(struct config_source *cf)
       			}
       		}
       		for (; space; space--)
      -			strbuf_addch(&cf->value, ' ');
      +			strbuf_addch(&cs->value, ' ');
       		if (c == '\\') {
     --			c = get_next_char();
     +-			c = get_next_char(cf);
      +			c = get_next_char(cs);
       			switch (c) {
       			case '\n':
       				continue;
     -@@ config.c: static char *parse_value(void)
     +@@ config.c: static char *parse_value(struct config_source *cf)
       			default:
       				return NULL;
       			}
     @@ config.c: static char *parse_value(void)
       	}
       }
       
     --static int get_value(config_fn_t fn, void *data, struct strbuf *name)
     +-static int get_value(struct config_source *cf, config_fn_t fn, void *data,
      +static int get_value(struct config_source *cs, config_fn_t fn, void *data,
     -+		     struct strbuf *name)
     + 		     struct strbuf *name)
       {
       	int c;
     - 	char *value;
     -@@ config.c: static int get_value(config_fn_t fn, void *data, struct strbuf *name)
     +@@ config.c: static int get_value(struct config_source *cf, config_fn_t fn, void *data,
       
       	/* Get the full name */
       	for (;;) {
     --		c = get_next_char();
     +-		c = get_next_char(cf);
      -		if (cf->eof)
      +		c = get_next_char(cs);
      +		if (cs->eof)
       			break;
       		if (!iskeychar(c))
       			break;
     -@@ config.c: static int get_value(config_fn_t fn, void *data, struct strbuf *name)
     +@@ config.c: static int get_value(struct config_source *cf, config_fn_t fn, void *data,
       	}
       
       	while (c == ' ' || c == '\t')
     --		c = get_next_char();
     +-		c = get_next_char(cf);
      +		c = get_next_char(cs);
       
       	value = NULL;
       	if (c != '\n') {
       		if (c != '=')
       			return -1;
     --		value = parse_value();
     +-		value = parse_value(cf);
      +		value = parse_value(cs);
       		if (!value)
       			return -1;
       	}
     -@@ config.c: static int get_value(config_fn_t fn, void *data, struct strbuf *name)
     +@@ config.c: static int get_value(struct config_source *cf, config_fn_t fn, void *data,
       	 * the line we just parsed during the call to fn to get
       	 * accurate line number in error messages.
       	 */
     @@ config.c: static int get_value(config_fn_t fn, void *data, struct strbuf *name)
       	return ret;
       }
       
     --static int get_extended_base_var(struct strbuf *name, int c)
     +-static int get_extended_base_var(struct config_source *cf, struct strbuf *name,
      +static int get_extended_base_var(struct config_source *cs, struct strbuf *name,
     -+				 int c)
     + 				 int c)
       {
      -	cf->subsection_case_sensitive = 0;
      +	cs->subsection_case_sensitive = 0;
       	do {
       		if (c == '\n')
       			goto error_incomplete_line;
     --		c = get_next_char();
     +-		c = get_next_char(cf);
      +		c = get_next_char(cs);
       	} while (isspace(c));
       
       	/* We require the format to be '[base "extension"]' */
     -@@ config.c: static int get_extended_base_var(struct strbuf *name, int c)
     +@@ config.c: static int get_extended_base_var(struct config_source *cf, struct strbuf *name,
       	strbuf_addch(name, '.');
       
       	for (;;) {
     --		int c = get_next_char();
     +-		int c = get_next_char(cf);
      +		int c = get_next_char(cs);
       		if (c == '\n')
       			goto error_incomplete_line;
       		if (c == '"')
       			break;
       		if (c == '\\') {
     --			c = get_next_char();
     +-			c = get_next_char(cf);
      +			c = get_next_char(cs);
       			if (c == '\n')
       				goto error_incomplete_line;
       		}
     -@@ config.c: static int get_extended_base_var(struct strbuf *name, int c)
     +@@ config.c: static int get_extended_base_var(struct config_source *cf, struct strbuf *name,
       	}
       
       	/* Final ']' */
     --	if (get_next_char() != ']')
     +-	if (get_next_char(cf) != ']')
      +	if (get_next_char(cs) != ']')
       		return -1;
       	return 0;
     @@ config.c: static int get_extended_base_var(struct strbuf *name, int c)
       	return -1;
       }
       
     --static int get_base_var(struct strbuf *name)
     +-static int get_base_var(struct config_source *cf, struct strbuf *name)
      +static int get_base_var(struct config_source *cs, struct strbuf *name)
       {
      -	cf->subsection_case_sensitive = 1;
      +	cs->subsection_case_sensitive = 1;
       	for (;;) {
     --		int c = get_next_char();
     +-		int c = get_next_char(cf);
      -		if (cf->eof)
      +		int c = get_next_char(cs);
      +		if (cs->eof)
     @@ config.c: static int get_extended_base_var(struct strbuf *name, int c)
       		if (c == ']')
       			return 0;
       		if (isspace(c))
     --			return get_extended_base_var(name, c);
     +-			return get_extended_base_var(cf, name, c);
      +			return get_extended_base_var(cs, name, c);
       		if (!iskeychar(c) && c != '.')
       			return -1;
     @@ config.c: struct parse_event_data {
       	const struct config_options *opts;
       };
       
     --static int do_event(enum config_event_t type, struct parse_event_data *data)
     +-static int do_event(struct config_source *cf, enum config_event_t type,
      +static int do_event(struct config_source *cs, enum config_event_t type,
     -+		    struct parse_event_data *data)
     + 		    struct parse_event_data *data)
       {
       	size_t offset;
     - 
     -@@ config.c: static int do_event(enum config_event_t type, struct parse_event_data *data)
     +@@ config.c: static int do_event(struct config_source *cf, enum config_event_t type,
       	    data->previous_type == type)
       		return 0;
       
     @@ config.c: static int do_event(enum config_event_t type, struct parse_event_data
       	/*
       	 * At EOF, the parser always "inserts" an extra '\n', therefore
       	 * the end offset of the event is the current file position, otherwise
     -@@ config.c: static int do_event(enum config_event_t type, struct parse_event_data *data)
     +@@ config.c: static int do_event(struct config_source *cf, enum config_event_t type,
       	return 0;
       }
       
     --static int git_parse_source(config_fn_t fn, void *data,
     --			    const struct config_options *opts)
     +-static int git_parse_source(struct config_source *cf, config_fn_t fn,
      +static int git_parse_source(struct config_source *cs, config_fn_t fn,
     -+			    void *data, const struct config_options *opts)
     + 			    void *data, const struct config_options *opts)
       {
       	int comment = 0;
       	size_t baselen = 0;
     @@ config.c: static int do_event(enum config_event_t type, struct parse_event_data
       	int error_return = 0;
       	char *error_msg = NULL;
       
     -@@ config.c: static int git_parse_source(config_fn_t fn, void *data,
     +@@ config.c: static int git_parse_source(struct config_source *cf, config_fn_t fn,
       	for (;;) {
       		int c;
       
     --		c = get_next_char();
     +-		c = get_next_char(cf);
      +		c = get_next_char(cs);
       		if (bomptr && *bomptr) {
       			/* We are at the file beginning; skip UTF8-encoded BOM
       			 * if present. Sane editors won't put this in on their
     -@@ config.c: static int git_parse_source(config_fn_t fn, void *data,
     +@@ config.c: static int git_parse_source(struct config_source *cf, config_fn_t fn,
       			}
       		}
       		if (c == '\n') {
      -			if (cf->eof) {
     --				if (do_event(CONFIG_EVENT_EOF, &event_data) < 0)
     +-				if (do_event(cf, CONFIG_EVENT_EOF, &event_data) < 0)
      +			if (cs->eof) {
      +				if (do_event(cs, CONFIG_EVENT_EOF, &event_data) < 0)
       					return -1;
       				return 0;
       			}
     --			if (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)
     +-			if (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
      +			if (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
       				return -1;
       			comment = 0;
       			continue;
     -@@ config.c: static int git_parse_source(config_fn_t fn, void *data,
     +@@ config.c: static int git_parse_source(struct config_source *cf, config_fn_t fn,
       		if (comment)
       			continue;
       		if (isspace(c)) {
     --			if (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)
     +-			if (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
      +			if (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
       					return -1;
       			continue;
       		}
       		if (c == '#' || c == ';') {
     --			if (do_event(CONFIG_EVENT_COMMENT, &event_data) < 0)
     +-			if (do_event(cf, CONFIG_EVENT_COMMENT, &event_data) < 0)
      +			if (do_event(cs, CONFIG_EVENT_COMMENT, &event_data) < 0)
       					return -1;
       			comment = 1;
       			continue;
       		}
       		if (c == '[') {
     --			if (do_event(CONFIG_EVENT_SECTION, &event_data) < 0)
     +-			if (do_event(cf, CONFIG_EVENT_SECTION, &event_data) < 0)
      +			if (do_event(cs, CONFIG_EVENT_SECTION, &event_data) < 0)
       					return -1;
       
       			/* Reset prior to determining a new stem */
       			strbuf_reset(var);
     --			if (get_base_var(var) < 0 || var->len < 1)
     +-			if (get_base_var(cf, var) < 0 || var->len < 1)
      +			if (get_base_var(cs, var) < 0 || var->len < 1)
       				break;
       			strbuf_addch(var, '.');
       			baselen = var->len;
     -@@ config.c: static int git_parse_source(config_fn_t fn, void *data,
     +@@ config.c: static int git_parse_source(struct config_source *cf, config_fn_t fn,
       		if (!isalpha(c))
       			break;
       
     --		if (do_event(CONFIG_EVENT_ENTRY, &event_data) < 0)
     +-		if (do_event(cf, CONFIG_EVENT_ENTRY, &event_data) < 0)
      +		if (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)
       			return -1;
       
       		/*
     -@@ config.c: static int git_parse_source(config_fn_t fn, void *data,
     +@@ config.c: static int git_parse_source(struct config_source *cf, config_fn_t fn,
       		 */
       		strbuf_setlen(var, baselen);
       		strbuf_addch(var, tolower(c));
     --		if (get_value(fn, data, var) < 0)
     +-		if (get_value(cf, fn, data, var) < 0)
      +		if (get_value(cs, fn, data, var) < 0)
       			break;
       	}
       
     --	if (do_event(CONFIG_EVENT_ERROR, &event_data) < 0)
     +-	if (do_event(cf, CONFIG_EVENT_ERROR, &event_data) < 0)
      +	if (do_event(cs, CONFIG_EVENT_ERROR, &event_data) < 0)
       		return -1;
       
     @@ config.c: static int git_parse_source(config_fn_t fn, void *data,
       	case CONFIG_ERROR_DIE:
       		die("%s", error_msg);
       		break;
     -@@ config.c: int git_parse_ssize_t(const char *value, ssize_t *ret)
     +@@ config.c: int config_with_options(config_fn_t fn, void *data,
     + 	return ret;
       }
       
     - NORETURN
     --static void die_bad_number(const char *name, const char *value)
     -+static void die_bad_number(struct config_source *cs, const char *name,
     -+			   const char *value)
     +-static void configset_iter(struct config_reader *reader, struct config_set *cs,
     ++static void configset_iter(struct config_reader *reader, struct config_set *set,
     + 			   config_fn_t fn, void *data)
       {
     - 	const char *error_type = (errno == ERANGE) ?
     - 		N_("out of range") : N_("invalid unit");
     -@@ config.c: static void die_bad_number(const char *name, const char *value)
     - 	if (!value)
     - 		value = "";
     + 	int i, value_index;
     + 	struct string_list *values;
     + 	struct config_set_element *entry;
     +-	struct configset_list *list = &cs->list;
     ++	struct configset_list *list = &set->list;
     + 
     + 	for (i = 0; i < list->nr; i++) {
     + 		entry = list->items[i].e;
     +@@ config.c: void read_very_early_config(config_fn_t cb, void *data)
     + 	config_with_options(cb, data, NULL, &opts);
     + }
       
     --	if (!(cf && cf->name))
     -+	if (!(cs && cs->name))
     - 		die(_(bad_numeric), value, name, _(error_type));
     +-static struct config_set_element *configset_find_element(struct config_set *cs, const char *key)
     ++static struct config_set_element *configset_find_element(struct config_set *set, const char *key)
     + {
     + 	struct config_set_element k;
     + 	struct config_set_element *found_entry;
     +@@ config.c: static struct config_set_element *configset_find_element(struct config_set *cs,
     + 
     + 	hashmap_entry_init(&k.ent, strhash(normalized_key));
     + 	k.key = normalized_key;
     +-	found_entry = hashmap_get_entry(&cs->config_hash, &k, ent, NULL);
     ++	found_entry = hashmap_get_entry(&set->config_hash, &k, ent, NULL);
     + 	free(normalized_key);
     + 	return found_entry;
     + }
       
     --	switch (cf->origin_type) {
     -+	switch (cs->origin_type) {
     - 	case CONFIG_ORIGIN_BLOB:
     - 		die(_("bad numeric config value '%s' for '%s' in blob %s: %s"),
     --		    value, name, cf->name, _(error_type));
     -+		    value, name, cs->name, _(error_type));
     - 	case CONFIG_ORIGIN_FILE:
     - 		die(_("bad numeric config value '%s' for '%s' in file %s: %s"),
     --		    value, name, cf->name, _(error_type));
     -+		    value, name, cs->name, _(error_type));
     - 	case CONFIG_ORIGIN_STDIN:
     - 		die(_("bad numeric config value '%s' for '%s' in standard input: %s"),
     - 		    value, name, _(error_type));
     - 	case CONFIG_ORIGIN_SUBMODULE_BLOB:
     - 		die(_("bad numeric config value '%s' for '%s' in submodule-blob %s: %s"),
     --		    value, name, cf->name, _(error_type));
     -+		    value, name, cs->name, _(error_type));
     - 	case CONFIG_ORIGIN_CMDLINE:
     - 		die(_("bad numeric config value '%s' for '%s' in command line %s: %s"),
     --		    value, name, cf->name, _(error_type));
     -+		    value, name, cs->name, _(error_type));
     - 	default:
     - 		die(_("bad numeric config value '%s' for '%s' in %s: %s"),
     --		    value, name, cf->name, _(error_type));
     -+		    value, name, cs->name, _(error_type));
     + static int configset_add_value(struct config_reader *reader,
     +-			       struct config_set *cs, const char *key,
     ++			       struct config_set *set, const char *key,
     + 			       const char *value)
     + {
     + 	struct config_set_element *e;
     +@@ config.c: static int configset_add_value(struct config_reader *reader,
     + 	struct configset_list_item *l_item;
     + 	struct key_value_info *kv_info = xmalloc(sizeof(*kv_info));
     + 
     +-	e = configset_find_element(cs, key);
     ++	e = configset_find_element(set, key);
     + 	/*
     + 	 * Since the keys are being fed by git_config*() callback mechanism, they
     + 	 * are already normalized. So simply add them without any further munging.
     +@@ config.c: static int configset_add_value(struct config_reader *reader,
     + 		hashmap_entry_init(&e->ent, strhash(key));
     + 		e->key = xstrdup(key);
     + 		string_list_init_dup(&e->value_list);
     +-		hashmap_add(&cs->config_hash, &e->ent);
     ++		hashmap_add(&set->config_hash, &e->ent);
       	}
     + 	si = string_list_append_nodup(&e->value_list, xstrdup_or_null(value));
     + 
     +-	ALLOC_GROW(cs->list.items, cs->list.nr + 1, cs->list.alloc);
     +-	l_item = &cs->list.items[cs->list.nr++];
     ++	ALLOC_GROW(set->list.items, set->list.nr + 1, set->list.alloc);
     ++	l_item = &set->list.items[set->list.nr++];
     + 	l_item->e = e;
     + 	l_item->value_index = e->value_list.nr - 1;
     + 
     +@@ config.c: static int config_set_element_cmp(const void *cmp_data UNUSED,
     + 	return strcmp(e1->key, e2->key);
       }
       
     -@@ config.c: int git_config_int(const char *name, const char *value)
     +-void git_configset_init(struct config_set *cs)
     ++void git_configset_init(struct config_set *set)
       {
     - 	int ret;
     - 	if (!git_parse_int(value, &ret))
     --		die_bad_number(name, value);
     -+		die_bad_number(cf, name, value);
     - 	return ret;
     +-	hashmap_init(&cs->config_hash, config_set_element_cmp, NULL, 0);
     +-	cs->hash_initialized = 1;
     +-	cs->list.nr = 0;
     +-	cs->list.alloc = 0;
     +-	cs->list.items = NULL;
     ++	hashmap_init(&set->config_hash, config_set_element_cmp, NULL, 0);
     ++	set->hash_initialized = 1;
     ++	set->list.nr = 0;
     ++	set->list.alloc = 0;
     ++	set->list.items = NULL;
       }
       
     -@@ config.c: int64_t git_config_int64(const char *name, const char *value)
     +-void git_configset_clear(struct config_set *cs)
     ++void git_configset_clear(struct config_set *set)
       {
     - 	int64_t ret;
     - 	if (!git_parse_int64(value, &ret))
     --		die_bad_number(name, value);
     -+		die_bad_number(cf, name, value);
     - 	return ret;
     + 	struct config_set_element *entry;
     + 	struct hashmap_iter iter;
     +-	if (!cs->hash_initialized)
     ++	if (!set->hash_initialized)
     + 		return;
     + 
     +-	hashmap_for_each_entry(&cs->config_hash, &iter, entry,
     ++	hashmap_for_each_entry(&set->config_hash, &iter, entry,
     + 				ent /* member name */) {
     + 		free(entry->key);
     + 		string_list_clear(&entry->value_list, 1);
     + 	}
     +-	hashmap_clear_and_free(&cs->config_hash, struct config_set_element, ent);
     +-	cs->hash_initialized = 0;
     +-	free(cs->list.items);
     +-	cs->list.nr = 0;
     +-	cs->list.alloc = 0;
     +-	cs->list.items = NULL;
     ++	hashmap_clear_and_free(&set->config_hash, struct config_set_element, ent);
     ++	set->hash_initialized = 0;
     ++	free(set->list.items);
     ++	set->list.nr = 0;
     ++	set->list.alloc = 0;
     ++	set->list.items = NULL;
     + }
     + 
     + struct configset_add_data {
     +@@ config.c: static int config_set_callback(const char *key, const char *value, void *cb)
     + 	return 0;
       }
       
     -@@ config.c: unsigned long git_config_ulong(const char *name, const char *value)
     +-int git_configset_add_file(struct config_set *cs, const char *filename)
     ++int git_configset_add_file(struct config_set *set, const char *filename)
       {
     - 	unsigned long ret;
     - 	if (!git_parse_ulong(value, &ret))
     --		die_bad_number(name, value);
     -+		die_bad_number(cf, name, value);
     - 	return ret;
     + 	struct configset_add_data data = CONFIGSET_ADD_INIT;
     + 	data.config_reader = &the_reader;
     +-	data.config_set = cs;
     ++	data.config_set = set;
     + 	return git_config_from_file(config_set_callback, filename, &data);
       }
       
     -@@ config.c: ssize_t git_config_ssize_t(const char *name, const char *value)
     +-int git_configset_get_value(struct config_set *cs, const char *key, const char **value)
     ++int git_configset_get_value(struct config_set *set, const char *key, const char **value)
       {
     - 	ssize_t ret;
     - 	if (!git_parse_ssize_t(value, &ret))
     --		die_bad_number(name, value);
     -+		die_bad_number(cf, name, value);
     - 	return ret;
     + 	const struct string_list *values = NULL;
     + 	/*
     +@@ config.c: int git_configset_get_value(struct config_set *cs, const char *key, const char *
     + 	 * queried key in the files of the configset, the value returned will be the last
     + 	 * value in the value list for that key.
     + 	 */
     +-	values = git_configset_get_value_multi(cs, key);
     ++	values = git_configset_get_value_multi(set, key);
     + 
     + 	if (!values)
     + 		return 1;
     +@@ config.c: int git_configset_get_value(struct config_set *cs, const char *key, const char *
     + 	return 0;
       }
       
     -@@ config.c: static int do_config_from(struct config_source *top, config_fn_t fn, void *data,
     - 	strbuf_init(&top->var, 1024);
     - 	cf = top;
     +-const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key)
     ++const struct string_list *git_configset_get_value_multi(struct config_set *set, const char *key)
     + {
     +-	struct config_set_element *e = configset_find_element(cs, key);
     ++	struct config_set_element *e = configset_find_element(set, key);
     + 	return e ? &e->value_list : NULL;
     + }
       
     --	ret = git_parse_source(fn, data, opts);
     -+	ret = git_parse_source(cf, fn, data, opts);
     +-int git_configset_get_string(struct config_set *cs, const char *key, char **dest)
     ++int git_configset_get_string(struct config_set *set, const char *key, char **dest)
     + {
     + 	const char *value;
     +-	if (!git_configset_get_value(cs, key, &value))
     ++	if (!git_configset_get_value(set, key, &value))
     + 		return git_config_string((const char **)dest, key, value);
     + 	else
     + 		return 1;
     + }
       
     - 	/* pop config-file parsing state stack */
     - 	strbuf_release(&top->value);
     +-static int git_configset_get_string_tmp(struct config_set *cs, const char *key,
     ++static int git_configset_get_string_tmp(struct config_set *set, const char *key,
     + 					const char **dest)
     + {
     + 	const char *value;
     +-	if (!git_configset_get_value(cs, key, &value)) {
     ++	if (!git_configset_get_value(set, key, &value)) {
     + 		if (!value)
     + 			return config_error_nonbool(key);
     + 		*dest = value;
     +@@ config.c: static int git_configset_get_string_tmp(struct config_set *cs, const char *key,
     + 	}
     + }
     + 
     +-int git_configset_get_int(struct config_set *cs, const char *key, int *dest)
     ++int git_configset_get_int(struct config_set *set, const char *key, int *dest)
     + {
     + 	const char *value;
     +-	if (!git_configset_get_value(cs, key, &value)) {
     ++	if (!git_configset_get_value(set, key, &value)) {
     + 		*dest = git_config_int(key, value);
     + 		return 0;
     + 	} else
     + 		return 1;
     + }
     + 
     +-int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest)
     ++int git_configset_get_ulong(struct config_set *set, const char *key, unsigned long *dest)
     + {
     + 	const char *value;
     +-	if (!git_configset_get_value(cs, key, &value)) {
     ++	if (!git_configset_get_value(set, key, &value)) {
     + 		*dest = git_config_ulong(key, value);
     + 		return 0;
     + 	} else
     + 		return 1;
     + }
     + 
     +-int git_configset_get_bool(struct config_set *cs, const char *key, int *dest)
     ++int git_configset_get_bool(struct config_set *set, const char *key, int *dest)
     + {
     + 	const char *value;
     +-	if (!git_configset_get_value(cs, key, &value)) {
     ++	if (!git_configset_get_value(set, key, &value)) {
     + 		*dest = git_config_bool(key, value);
     + 		return 0;
     + 	} else
     + 		return 1;
     + }
     + 
     +-int git_configset_get_bool_or_int(struct config_set *cs, const char *key,
     ++int git_configset_get_bool_or_int(struct config_set *set, const char *key,
     + 				int *is_bool, int *dest)
     + {
     + 	const char *value;
     +-	if (!git_configset_get_value(cs, key, &value)) {
     ++	if (!git_configset_get_value(set, key, &value)) {
     + 		*dest = git_config_bool_or_int(key, value, is_bool);
     + 		return 0;
     + 	} else
     + 		return 1;
     + }
     + 
     +-int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest)
     ++int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *dest)
     + {
     + 	const char *value;
     +-	if (!git_configset_get_value(cs, key, &value)) {
     ++	if (!git_configset_get_value(set, key, &value)) {
     + 		*dest = git_parse_maybe_bool(value);
     + 		if (*dest == -1)
     + 			return -1;
     +@@ config.c: int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *de
     + 		return 1;
     + }
     + 
     +-int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest)
     ++int git_configset_get_pathname(struct config_set *set, const char *key, const char **dest)
     + {
     + 	const char *value;
     +-	if (!git_configset_get_value(cs, key, &value))
     ++	if (!git_configset_get_value(set, key, &value))
     + 		return git_config_pathname(dest, key, value);
     + 	else
     + 		return 1;
     +@@ config.c: static int store_aux_event(enum config_event_t type,
     + 			   size_t begin, size_t end, void *data)
     + {
     + 	struct config_store_data *store = data;
     +-	struct config_source *cf = store->config_reader->source;
     ++	struct config_source *cs = store->config_reader->source;
     + 
     + 	ALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc);
     + 	store->parsed[store->parsed_nr].begin = begin;
     +@@ config.c: static int store_aux_event(enum config_event_t type,
     + 	if (type == CONFIG_EVENT_SECTION) {
     + 		int (*cmpfn)(const char *, const char *, size_t);
     + 
     +-		if (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')
     +-			return error(_("invalid section name '%s'"), cf->var.buf);
     ++		if (cs->var.len < 2 || cs->var.buf[cs->var.len - 1] != '.')
     ++			return error(_("invalid section name '%s'"), cs->var.buf);
     + 
     +-		if (cf->subsection_case_sensitive)
     ++		if (cs->subsection_case_sensitive)
     + 			cmpfn = strncasecmp;
     + 		else
     + 			cmpfn = strncmp;
     +@@ config.c: static int store_aux_event(enum config_event_t type,
     + 		/* Is this the section we were looking for? */
     + 		store->is_keys_section =
     + 			store->parsed[store->parsed_nr].is_keys_section =
     +-			cf->var.len - 1 == store->baselen &&
     +-			!cmpfn(cf->var.buf, store->key, store->baselen);
     ++			cs->var.len - 1 == store->baselen &&
     ++			!cmpfn(cs->var.buf, store->key, store->baselen);
     + 		if (store->is_keys_section) {
     + 			store->section_seen = 1;
     + 			ALLOC_GROW(store->seen, store->seen_nr + 1,

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 72+ messages in thread

* [PATCH v2 1/8] config.c: plumb config_source through static fns
  2023-03-16  0:11 ` [PATCH v2 0/8] " Glen Choo via GitGitGadget
@ 2023-03-16  0:11   ` Glen Choo via GitGitGadget
  2023-03-16 21:16     ` Jonathan Tan
  2023-03-16  0:11   ` [PATCH v2 2/8] config.c: don't assign to "cf_global" directly Glen Choo via GitGitGadget
                     ` (10 subsequent siblings)
  11 siblings, 1 reply; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-16  0:11 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee,
	Calvin Wan, Ævar Arnfjörð Bjarmason, Glen Choo,
	Glen Choo

From: Glen Choo <chooglen@google.com>

This reduces the direct dependence on the global "struct config_source",
which will make it easier to remove in a later commit.

To minimize the changes we need to make, we rename the current variable
from "cf" to "cf_global", and the plumbed arg uses the old name "cf".
This is a little unfortunate, since we now have the confusingly named
"struct config_source cf" everywhere (which is a holdover from before
4d8dd1494e (config: make parsing stack struct independent from actual
data source, 2013-07-12), when the struct used to be called
"config_file"), but we will rename "cf" to "cs" by the end of the
series.

In some cases (public functions and config callback functions), there
isn't an obvious way to plumb "struct config_source" through function
args. As a workaround, add references to "cf_global" that we'll address
in later commits.

The remaining references to "cf_global" are direct assignments to
"cf_global", which we'll also address in a later commit.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 153 ++++++++++++++++++++++++++++++-------------------------
 1 file changed, 84 insertions(+), 69 deletions(-)

diff --git a/config.c b/config.c
index 00090a32fc3..e4a76739365 100644
--- a/config.c
+++ b/config.c
@@ -54,8 +54,8 @@ struct config_source {
  * These variables record the "current" config source, which
  * can be accessed by parsing callbacks.
  *
- * The "cf" variable will be non-NULL only when we are actually parsing a real
- * config source (file, blob, cmdline, etc).
+ * The "cf_global" variable will be non-NULL only when we are actually
+ * parsing a real config source (file, blob, cmdline, etc).
  *
  * The "current_config_kvi" variable will be non-NULL only when we are feeding
  * cached config from a configset into a callback.
@@ -66,15 +66,16 @@ struct config_source {
  * or it's a function which can be reused for non-config purposes, and should
  * fall back to some sane behavior).
  */
-static struct config_source *cf;
+static struct config_source *cf_global;
 static struct key_value_info *current_config_kvi;
 
 /*
  * Similar to the variables above, this gives access to the "scope" of the
  * current value (repo, global, etc). For cached values, it can be found via
  * the current_config_kvi as above. During parsing, the current value can be
- * found in this variable. It's not part of "cf" because it transcends a single
- * file (i.e., a file included from .git/config is still in "repo" scope).
+ * found in this variable. It's not part of "cf_global" because it transcends a
+ * single file (i.e., a file included from .git/config is still in "repo"
+ * scope).
  */
 static enum config_scope current_parsing_scope;
 
@@ -156,7 +157,8 @@ static const char include_depth_advice[] = N_(
 "from\n"
 "	%s\n"
 "This might be due to circular includes.");
-static int handle_path_include(const char *path, struct config_include_data *inc)
+static int handle_path_include(struct config_source *cf, const char *path,
+			       struct config_include_data *inc)
 {
 	int ret = 0;
 	struct strbuf buf = STRBUF_INIT;
@@ -210,7 +212,8 @@ static void add_trailing_starstar_for_dir(struct strbuf *pat)
 		strbuf_addstr(pat, "**");
 }
 
-static int prepare_include_condition_pattern(struct strbuf *pat)
+static int prepare_include_condition_pattern(struct config_source *cf,
+					     struct strbuf *pat)
 {
 	struct strbuf path = STRBUF_INIT;
 	char *expanded;
@@ -245,7 +248,8 @@ static int prepare_include_condition_pattern(struct strbuf *pat)
 	return prefix;
 }
 
-static int include_by_gitdir(const struct config_options *opts,
+static int include_by_gitdir(struct config_source *cf,
+			     const struct config_options *opts,
 			     const char *cond, size_t cond_len, int icase)
 {
 	struct strbuf text = STRBUF_INIT;
@@ -261,7 +265,7 @@ static int include_by_gitdir(const struct config_options *opts,
 
 	strbuf_realpath(&text, git_dir, 1);
 	strbuf_add(&pattern, cond, cond_len);
-	prefix = prepare_include_condition_pattern(&pattern);
+	prefix = prepare_include_condition_pattern(cf, &pattern);
 
 again:
 	if (prefix < 0)
@@ -342,14 +346,14 @@ static void populate_remote_urls(struct config_include_data *inc)
 {
 	struct config_options opts;
 
-	struct config_source *store_cf = cf;
+	struct config_source *store_cf = cf_global;
 	struct key_value_info *store_kvi = current_config_kvi;
 	enum config_scope store_scope = current_parsing_scope;
 
 	opts = *inc->opts;
 	opts.unconditional_remote_url = 1;
 
-	cf = NULL;
+	cf_global = NULL;
 	current_config_kvi = NULL;
 	current_parsing_scope = 0;
 
@@ -357,7 +361,7 @@ static void populate_remote_urls(struct config_include_data *inc)
 	string_list_init_dup(inc->remote_urls);
 	config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);
 
-	cf = store_cf;
+	cf_global = store_cf;
 	current_config_kvi = store_kvi;
 	current_parsing_scope = store_scope;
 }
@@ -406,15 +410,16 @@ static int include_by_remote_url(struct config_include_data *inc,
 					     inc->remote_urls);
 }
 
-static int include_condition_is_true(struct config_include_data *inc,
+static int include_condition_is_true(struct config_source *cf,
+				     struct config_include_data *inc,
 				     const char *cond, size_t cond_len)
 {
 	const struct config_options *opts = inc->opts;
 
 	if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
-		return include_by_gitdir(opts, cond, cond_len, 0);
+		return include_by_gitdir(cf, opts, cond, cond_len, 0);
 	else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
-		return include_by_gitdir(opts, cond, cond_len, 1);
+		return include_by_gitdir(cf, opts, cond, cond_len, 1);
 	else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
 		return include_by_branch(cond, cond_len);
 	else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,
@@ -441,16 +446,16 @@ static int git_config_include(const char *var, const char *value, void *data)
 		return ret;
 
 	if (!strcmp(var, "include.path"))
-		ret = handle_path_include(value, inc);
+		ret = handle_path_include(cf_global, value, inc);
 
 	if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
-	    cond && include_condition_is_true(inc, cond, cond_len) &&
+	    cond && include_condition_is_true(cf_global, inc, cond, cond_len) &&
 	    !strcmp(key, "path")) {
 		config_fn_t old_fn = inc->fn;
 
 		if (inc->opts->unconditional_remote_url)
 			inc->fn = forbid_remote_url;
-		ret = handle_path_include(value, inc);
+		ret = handle_path_include(cf_global, value, inc);
 		inc->fn = old_fn;
 	}
 
@@ -713,9 +718,9 @@ int git_config_from_parameters(config_fn_t fn, void *data)
 	struct config_source source;
 
 	memset(&source, 0, sizeof(source));
-	source.prev = cf;
+	source.prev = cf_global;
 	source.origin_type = CONFIG_ORIGIN_CMDLINE;
-	cf = &source;
+	cf_global = &source;
 
 	env = getenv(CONFIG_COUNT_ENVIRONMENT);
 	if (env) {
@@ -773,11 +778,11 @@ out:
 	strbuf_release(&envvar);
 	strvec_clear(&to_free);
 	free(envw);
-	cf = source.prev;
+	cf_global = source.prev;
 	return ret;
 }
 
-static int get_next_char(void)
+static int get_next_char(struct config_source *cf)
 {
 	int c = cf->do_fgetc(cf);
 
@@ -813,13 +818,13 @@ static int get_next_char(void)
 	return c;
 }
 
-static char *parse_value(void)
+static char *parse_value(struct config_source *cf)
 {
 	int quote = 0, comment = 0, space = 0;
 
 	strbuf_reset(&cf->value);
 	for (;;) {
-		int c = get_next_char();
+		int c = get_next_char(cf);
 		if (c == '\n') {
 			if (quote) {
 				cf->linenr--;
@@ -843,7 +848,7 @@ static char *parse_value(void)
 		for (; space; space--)
 			strbuf_addch(&cf->value, ' ');
 		if (c == '\\') {
-			c = get_next_char();
+			c = get_next_char(cf);
 			switch (c) {
 			case '\n':
 				continue;
@@ -874,7 +879,8 @@ static char *parse_value(void)
 	}
 }
 
-static int get_value(config_fn_t fn, void *data, struct strbuf *name)
+static int get_value(struct config_source *cf, config_fn_t fn, void *data,
+		     struct strbuf *name)
 {
 	int c;
 	char *value;
@@ -882,7 +888,7 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
 
 	/* Get the full name */
 	for (;;) {
-		c = get_next_char();
+		c = get_next_char(cf);
 		if (cf->eof)
 			break;
 		if (!iskeychar(c))
@@ -891,13 +897,13 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
 	}
 
 	while (c == ' ' || c == '\t')
-		c = get_next_char();
+		c = get_next_char(cf);
 
 	value = NULL;
 	if (c != '\n') {
 		if (c != '=')
 			return -1;
-		value = parse_value();
+		value = parse_value(cf);
 		if (!value)
 			return -1;
 	}
@@ -913,13 +919,14 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
 	return ret;
 }
 
-static int get_extended_base_var(struct strbuf *name, int c)
+static int get_extended_base_var(struct config_source *cf, struct strbuf *name,
+				 int c)
 {
 	cf->subsection_case_sensitive = 0;
 	do {
 		if (c == '\n')
 			goto error_incomplete_line;
-		c = get_next_char();
+		c = get_next_char(cf);
 	} while (isspace(c));
 
 	/* We require the format to be '[base "extension"]' */
@@ -928,13 +935,13 @@ static int get_extended_base_var(struct strbuf *name, int c)
 	strbuf_addch(name, '.');
 
 	for (;;) {
-		int c = get_next_char();
+		int c = get_next_char(cf);
 		if (c == '\n')
 			goto error_incomplete_line;
 		if (c == '"')
 			break;
 		if (c == '\\') {
-			c = get_next_char();
+			c = get_next_char(cf);
 			if (c == '\n')
 				goto error_incomplete_line;
 		}
@@ -942,7 +949,7 @@ static int get_extended_base_var(struct strbuf *name, int c)
 	}
 
 	/* Final ']' */
-	if (get_next_char() != ']')
+	if (get_next_char(cf) != ']')
 		return -1;
 	return 0;
 error_incomplete_line:
@@ -950,17 +957,17 @@ error_incomplete_line:
 	return -1;
 }
 
-static int get_base_var(struct strbuf *name)
+static int get_base_var(struct config_source *cf, struct strbuf *name)
 {
 	cf->subsection_case_sensitive = 1;
 	for (;;) {
-		int c = get_next_char();
+		int c = get_next_char(cf);
 		if (cf->eof)
 			return -1;
 		if (c == ']')
 			return 0;
 		if (isspace(c))
-			return get_extended_base_var(name, c);
+			return get_extended_base_var(cf, name, c);
 		if (!iskeychar(c) && c != '.')
 			return -1;
 		strbuf_addch(name, tolower(c));
@@ -973,7 +980,8 @@ struct parse_event_data {
 	const struct config_options *opts;
 };
 
-static int do_event(enum config_event_t type, struct parse_event_data *data)
+static int do_event(struct config_source *cf, enum config_event_t type,
+		    struct parse_event_data *data)
 {
 	size_t offset;
 
@@ -1004,8 +1012,8 @@ static int do_event(enum config_event_t type, struct parse_event_data *data)
 	return 0;
 }
 
-static int git_parse_source(config_fn_t fn, void *data,
-			    const struct config_options *opts)
+static int git_parse_source(struct config_source *cf, config_fn_t fn,
+			    void *data, const struct config_options *opts)
 {
 	int comment = 0;
 	size_t baselen = 0;
@@ -1024,7 +1032,7 @@ static int git_parse_source(config_fn_t fn, void *data,
 	for (;;) {
 		int c;
 
-		c = get_next_char();
+		c = get_next_char(cf);
 		if (bomptr && *bomptr) {
 			/* We are at the file beginning; skip UTF8-encoded BOM
 			 * if present. Sane editors won't put this in on their
@@ -1042,11 +1050,11 @@ static int git_parse_source(config_fn_t fn, void *data,
 		}
 		if (c == '\n') {
 			if (cf->eof) {
-				if (do_event(CONFIG_EVENT_EOF, &event_data) < 0)
+				if (do_event(cf, CONFIG_EVENT_EOF, &event_data) < 0)
 					return -1;
 				return 0;
 			}
-			if (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)
+			if (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
 				return -1;
 			comment = 0;
 			continue;
@@ -1054,23 +1062,23 @@ static int git_parse_source(config_fn_t fn, void *data,
 		if (comment)
 			continue;
 		if (isspace(c)) {
-			if (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)
+			if (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
 					return -1;
 			continue;
 		}
 		if (c == '#' || c == ';') {
-			if (do_event(CONFIG_EVENT_COMMENT, &event_data) < 0)
+			if (do_event(cf, CONFIG_EVENT_COMMENT, &event_data) < 0)
 					return -1;
 			comment = 1;
 			continue;
 		}
 		if (c == '[') {
-			if (do_event(CONFIG_EVENT_SECTION, &event_data) < 0)
+			if (do_event(cf, CONFIG_EVENT_SECTION, &event_data) < 0)
 					return -1;
 
 			/* Reset prior to determining a new stem */
 			strbuf_reset(var);
-			if (get_base_var(var) < 0 || var->len < 1)
+			if (get_base_var(cf, var) < 0 || var->len < 1)
 				break;
 			strbuf_addch(var, '.');
 			baselen = var->len;
@@ -1079,7 +1087,7 @@ static int git_parse_source(config_fn_t fn, void *data,
 		if (!isalpha(c))
 			break;
 
-		if (do_event(CONFIG_EVENT_ENTRY, &event_data) < 0)
+		if (do_event(cf, CONFIG_EVENT_ENTRY, &event_data) < 0)
 			return -1;
 
 		/*
@@ -1089,11 +1097,11 @@ static int git_parse_source(config_fn_t fn, void *data,
 		 */
 		strbuf_setlen(var, baselen);
 		strbuf_addch(var, tolower(c));
-		if (get_value(fn, data, var) < 0)
+		if (get_value(cf, fn, data, var) < 0)
 			break;
 	}
 
-	if (do_event(CONFIG_EVENT_ERROR, &event_data) < 0)
+	if (do_event(cf, CONFIG_EVENT_ERROR, &event_data) < 0)
 		return -1;
 
 	switch (cf->origin_type) {
@@ -1266,7 +1274,8 @@ int git_parse_ssize_t(const char *value, ssize_t *ret)
 }
 
 NORETURN
-static void die_bad_number(const char *name, const char *value)
+static void die_bad_number(struct config_source *cf, const char *name,
+			   const char *value)
 {
 	const char *error_type = (errno == ERANGE) ?
 		N_("out of range") : N_("invalid unit");
@@ -1304,7 +1313,7 @@ int git_config_int(const char *name, const char *value)
 {
 	int ret;
 	if (!git_parse_int(value, &ret))
-		die_bad_number(name, value);
+		die_bad_number(cf_global, name, value);
 	return ret;
 }
 
@@ -1312,7 +1321,7 @@ int64_t git_config_int64(const char *name, const char *value)
 {
 	int64_t ret;
 	if (!git_parse_int64(value, &ret))
-		die_bad_number(name, value);
+		die_bad_number(cf_global, name, value);
 	return ret;
 }
 
@@ -1320,7 +1329,7 @@ unsigned long git_config_ulong(const char *name, const char *value)
 {
 	unsigned long ret;
 	if (!git_parse_ulong(value, &ret))
-		die_bad_number(name, value);
+		die_bad_number(cf_global, name, value);
 	return ret;
 }
 
@@ -1328,7 +1337,7 @@ ssize_t git_config_ssize_t(const char *name, const char *value)
 {
 	ssize_t ret;
 	if (!git_parse_ssize_t(value, &ret))
-		die_bad_number(name, value);
+		die_bad_number(cf_global, name, value);
 	return ret;
 }
 
@@ -1940,20 +1949,20 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data,
 	int ret;
 
 	/* push config-file parsing state stack */
-	top->prev = cf;
+	top->prev = cf_global;
 	top->linenr = 1;
 	top->eof = 0;
 	top->total_len = 0;
 	strbuf_init(&top->value, 1024);
 	strbuf_init(&top->var, 1024);
-	cf = top;
+	cf_global = top;
 
-	ret = git_parse_source(fn, data, opts);
+	ret = git_parse_source(top, fn, data, opts);
 
 	/* pop config-file parsing state stack */
 	strbuf_release(&top->value);
 	strbuf_release(&top->var);
-	cf = top->prev;
+	cf_global = top->prev;
 
 	return ret;
 }
@@ -2334,12 +2343,12 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha
 	l_item->e = e;
 	l_item->value_index = e->value_list.nr - 1;
 
-	if (!cf)
+	if (!cf_global)
 		BUG("configset_add_value has no source");
-	if (cf->name) {
-		kv_info->filename = strintern(cf->name);
-		kv_info->linenr = cf->linenr;
-		kv_info->origin_type = cf->origin_type;
+	if (cf_global->name) {
+		kv_info->filename = strintern(cf_global->name);
+		kv_info->linenr = cf_global->linenr;
+		kv_info->origin_type = cf_global->origin_type;
 	} else {
 		/* for values read from `git_config_from_parameters()` */
 		kv_info->filename = NULL;
@@ -2891,6 +2900,12 @@ static int store_aux_event(enum config_event_t type,
 			   size_t begin, size_t end, void *data)
 {
 	struct config_store_data *store = data;
+	/*
+	 * FIXME Keep using "cf" so that we can avoid rewrapping a
+	 * really long line below. Remove this when "cf" gets plumbed
+	 * correctly.
+	 */
+	struct config_source *cf = cf_global;
 
 	ALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc);
 	store->parsed[store->parsed_nr].begin = begin;
@@ -3771,8 +3786,8 @@ const char *current_config_origin_type(void)
 	int type;
 	if (current_config_kvi)
 		type = current_config_kvi->origin_type;
-	else if(cf)
-		type = cf->origin_type;
+	else if(cf_global)
+		type = cf_global->origin_type;
 	else
 		BUG("current_config_origin_type called outside config callback");
 
@@ -3817,8 +3832,8 @@ const char *current_config_name(void)
 	const char *name;
 	if (current_config_kvi)
 		name = current_config_kvi->filename;
-	else if (cf)
-		name = cf->name;
+	else if (cf_global)
+		name = cf_global->name;
 	else
 		BUG("current_config_name called outside config callback");
 	return name ? name : "";
@@ -3837,7 +3852,7 @@ int current_config_line(void)
 	if (current_config_kvi)
 		return current_config_kvi->linenr;
 	else
-		return cf->linenr;
+		return cf_global->linenr;
 }
 
 int lookup_config(const char **mapping, int nr_mapping, const char *var)
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH v2 2/8] config.c: don't assign to "cf_global" directly
  2023-03-16  0:11 ` [PATCH v2 0/8] " Glen Choo via GitGitGadget
  2023-03-16  0:11   ` [PATCH v2 1/8] config.c: plumb config_source through static fns Glen Choo via GitGitGadget
@ 2023-03-16  0:11   ` Glen Choo via GitGitGadget
  2023-03-16 21:18     ` Jonathan Tan
  2023-03-16  0:11   ` [PATCH v2 3/8] config.c: create config_reader and the_reader Glen Choo via GitGitGadget
                     ` (9 subsequent siblings)
  11 siblings, 1 reply; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-16  0:11 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee,
	Calvin Wan, Ævar Arnfjörð Bjarmason, Glen Choo,
	Glen Choo

From: Glen Choo <chooglen@google.com>

To make "cf_global" easier to remove, replace all direct assignments to
it with function calls. This refactor has an additional maintainability
benefit: all of these functions were manually implementing stack
pop/push semantics on "struct config_source", so replacing them with
function calls allows us to only implement this logic once.

In this process, perform some now-obvious clean ups:

- Drop some unnecessary "cf_global" assignments in
  populate_remote_urls(). Since it was introduced in 399b198489 (config:
  include file if remote URL matches a glob, 2022-01-18), it has stored
  and restored the value of "cf_global" to ensure that it doesn't get
  accidentally mutated. However, this was never necessary since
  "do_config_from()" already pushes/pops "cf_global" further down the
  call chain.

- Zero out every "struct config_source" with a dedicated initializer.
  This matters because the "struct config_source" is assigned to
  "cf_global" and we later 'pop the stack' by assigning "cf_global =
  cf_global->prev", but "cf_global->prev" could be pointing to
  uninitialized garbage.

  Fortunately, this has never bothered us since we never try to read
  "cf_global" except while iterating through config, in which case,
  "cf_global" is either set to a sensible value (when parsing a file),
  or it is ignored (when iterating a configset). Later in the series,
  zero-ing out memory will also let us enforce the constraint that
  "cf_global" and "current_config_kvi" are never non-NULL together.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 38 +++++++++++++++++++++++++-------------
 1 file changed, 25 insertions(+), 13 deletions(-)

diff --git a/config.c b/config.c
index e4a76739365..517b8f64038 100644
--- a/config.c
+++ b/config.c
@@ -49,6 +49,7 @@ struct config_source {
 	int (*do_ungetc)(int c, struct config_source *conf);
 	long (*do_ftell)(struct config_source *c);
 };
+#define CONFIG_SOURCE_INIT { 0 }
 
 /*
  * These variables record the "current" config source, which
@@ -79,6 +80,23 @@ static struct key_value_info *current_config_kvi;
  */
 static enum config_scope current_parsing_scope;
 
+static inline void config_reader_push_source(struct config_source *top)
+{
+	if (cf_global)
+		top->prev = cf_global;
+	cf_global = top;
+}
+
+static inline struct config_source *config_reader_pop_source()
+{
+	struct config_source *ret;
+	if (!cf_global)
+		BUG("tried to pop config source, but we weren't reading config");
+	ret = cf_global;
+	cf_global = cf_global->prev;
+	return ret;
+}
+
 static int pack_compression_seen;
 static int zlib_compression_seen;
 
@@ -346,14 +364,12 @@ static void populate_remote_urls(struct config_include_data *inc)
 {
 	struct config_options opts;
 
-	struct config_source *store_cf = cf_global;
 	struct key_value_info *store_kvi = current_config_kvi;
 	enum config_scope store_scope = current_parsing_scope;
 
 	opts = *inc->opts;
 	opts.unconditional_remote_url = 1;
 
-	cf_global = NULL;
 	current_config_kvi = NULL;
 	current_parsing_scope = 0;
 
@@ -361,7 +377,6 @@ static void populate_remote_urls(struct config_include_data *inc)
 	string_list_init_dup(inc->remote_urls);
 	config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);
 
-	cf_global = store_cf;
 	current_config_kvi = store_kvi;
 	current_parsing_scope = store_scope;
 }
@@ -715,12 +730,10 @@ int git_config_from_parameters(config_fn_t fn, void *data)
 	struct strvec to_free = STRVEC_INIT;
 	int ret = 0;
 	char *envw = NULL;
-	struct config_source source;
+	struct config_source source = CONFIG_SOURCE_INIT;
 
-	memset(&source, 0, sizeof(source));
-	source.prev = cf_global;
 	source.origin_type = CONFIG_ORIGIN_CMDLINE;
-	cf_global = &source;
+	config_reader_push_source(&source);
 
 	env = getenv(CONFIG_COUNT_ENVIRONMENT);
 	if (env) {
@@ -778,7 +791,7 @@ out:
 	strbuf_release(&envvar);
 	strvec_clear(&to_free);
 	free(envw);
-	cf_global = source.prev;
+	config_reader_pop_source();
 	return ret;
 }
 
@@ -1949,20 +1962,19 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data,
 	int ret;
 
 	/* push config-file parsing state stack */
-	top->prev = cf_global;
 	top->linenr = 1;
 	top->eof = 0;
 	top->total_len = 0;
 	strbuf_init(&top->value, 1024);
 	strbuf_init(&top->var, 1024);
-	cf_global = top;
+	config_reader_push_source(top);
 
 	ret = git_parse_source(top, fn, data, opts);
 
 	/* pop config-file parsing state stack */
 	strbuf_release(&top->value);
 	strbuf_release(&top->var);
-	cf_global = top->prev;
+	config_reader_pop_source();
 
 	return ret;
 }
@@ -1972,7 +1984,7 @@ static int do_config_from_file(config_fn_t fn,
 		const char *name, const char *path, FILE *f,
 		void *data, const struct config_options *opts)
 {
-	struct config_source top;
+	struct config_source top = CONFIG_SOURCE_INIT;
 	int ret;
 
 	top.u.file = f;
@@ -2024,7 +2036,7 @@ int git_config_from_mem(config_fn_t fn,
 			const char *name, const char *buf, size_t len,
 			void *data, const struct config_options *opts)
 {
-	struct config_source top;
+	struct config_source top = CONFIG_SOURCE_INIT;
 
 	top.u.buf.buf = buf;
 	top.u.buf.len = len;
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH v2 3/8] config.c: create config_reader and the_reader
  2023-03-16  0:11 ` [PATCH v2 0/8] " Glen Choo via GitGitGadget
  2023-03-16  0:11   ` [PATCH v2 1/8] config.c: plumb config_source through static fns Glen Choo via GitGitGadget
  2023-03-16  0:11   ` [PATCH v2 2/8] config.c: don't assign to "cf_global" directly Glen Choo via GitGitGadget
@ 2023-03-16  0:11   ` Glen Choo via GitGitGadget
  2023-03-16 21:22     ` Jonathan Tan
  2023-03-16  0:11   ` [PATCH v2 4/8] config.c: plumb the_reader through callbacks Glen Choo via GitGitGadget
                     ` (8 subsequent siblings)
  11 siblings, 1 reply; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-16  0:11 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee,
	Calvin Wan, Ævar Arnfjörð Bjarmason, Glen Choo,
	Glen Choo

From: Glen Choo <chooglen@google.com>

Create "struct config_reader" to hold the state of the config source
currently being read. Then, create a static instance of it,
"the_reader", and use "the_reader.source" to replace references to
"cf_global" in public functions.

This doesn't create much immediate benefit (since we're mostly replacing
static variables with a bigger static variable), but it prepares us for
a future where this state doesn't have to be global; "struct
config_reader" (or a similar struct) could be provided by the caller, or
constructed internally by a function like "do_config_from()".

A more typical approach would be to put this struct on "the_repository",
but that's a worse fit for this use case since config reading is not
scoped to a repository. E.g. we can read config before the repository is
known ("read_very_early_config()"), blatantly ignore the repo
("read_protected_config()"), or read only from a file
("git_config_from_file()"). This is especially evident in t5318 and
t9210, where test-tool and scalar parse config but don't fully
initialize "the_repository".

We could have also replaced the references to "cf_global" in callback
functions (which are the only ones left), but we'll eventually plumb
"the_reader" through the callback "*data" arg, so that would be
unnecessary churn. Until we remove "cf_global" altogether, add logic to
"config_reader_*_source()" to keep "cf_global" and "the_reader.source"
in sync.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 84 +++++++++++++++++++++++++++++++++++---------------------
 1 file changed, 52 insertions(+), 32 deletions(-)

diff --git a/config.c b/config.c
index 517b8f64038..7de25515818 100644
--- a/config.c
+++ b/config.c
@@ -51,6 +51,16 @@ struct config_source {
 };
 #define CONFIG_SOURCE_INIT { 0 }
 
+struct config_reader {
+	struct config_source *source;
+};
+/*
+ * Where possible, prefer to accept "struct config_reader" as an arg than to use
+ * "the_reader". "the_reader" should only be used if that is infeasible, e.g. in
+ * a public function.
+ */
+static struct config_reader the_reader;
+
 /*
  * These variables record the "current" config source, which
  * can be accessed by parsing callbacks.
@@ -66,6 +76,9 @@ struct config_source {
  * at the variables, it's either a bug for it to be called in the first place,
  * or it's a function which can be reused for non-config purposes, and should
  * fall back to some sane behavior).
+ *
+ * FIXME "cf_global" has been replaced by "the_reader.source", remove
+ * "cf_global" once we plumb "the_reader" through all of the callback functions.
  */
 static struct config_source *cf_global;
 static struct key_value_info *current_config_kvi;
@@ -80,20 +93,25 @@ static struct key_value_info *current_config_kvi;
  */
 static enum config_scope current_parsing_scope;
 
-static inline void config_reader_push_source(struct config_source *top)
+static inline void config_reader_push_source(struct config_reader *reader,
+					     struct config_source *top)
 {
-	if (cf_global)
-		top->prev = cf_global;
-	cf_global = top;
+	if (reader->source)
+		top->prev = reader->source;
+	reader->source = top;
+	/* FIXME remove this when cf_global is removed. */
+	cf_global = reader->source;
 }
 
-static inline struct config_source *config_reader_pop_source()
+static inline struct config_source *config_reader_pop_source(struct config_reader *reader)
 {
 	struct config_source *ret;
-	if (!cf_global)
+	if (!reader->source)
 		BUG("tried to pop config source, but we weren't reading config");
-	ret = cf_global;
-	cf_global = cf_global->prev;
+	ret = reader->source;
+	reader->source = reader->source->prev;
+	/* FIXME remove this when cf is removed. */
+	cf_global = reader->source;
 	return ret;
 }
 
@@ -733,7 +751,7 @@ int git_config_from_parameters(config_fn_t fn, void *data)
 	struct config_source source = CONFIG_SOURCE_INIT;
 
 	source.origin_type = CONFIG_ORIGIN_CMDLINE;
-	config_reader_push_source(&source);
+	config_reader_push_source(&the_reader, &source);
 
 	env = getenv(CONFIG_COUNT_ENVIRONMENT);
 	if (env) {
@@ -791,7 +809,7 @@ out:
 	strbuf_release(&envvar);
 	strvec_clear(&to_free);
 	free(envw);
-	config_reader_pop_source();
+	config_reader_pop_source(&the_reader);
 	return ret;
 }
 
@@ -1326,7 +1344,7 @@ int git_config_int(const char *name, const char *value)
 {
 	int ret;
 	if (!git_parse_int(value, &ret))
-		die_bad_number(cf_global, name, value);
+		die_bad_number(the_reader.source, name, value);
 	return ret;
 }
 
@@ -1334,7 +1352,7 @@ int64_t git_config_int64(const char *name, const char *value)
 {
 	int64_t ret;
 	if (!git_parse_int64(value, &ret))
-		die_bad_number(cf_global, name, value);
+		die_bad_number(the_reader.source, name, value);
 	return ret;
 }
 
@@ -1342,7 +1360,7 @@ unsigned long git_config_ulong(const char *name, const char *value)
 {
 	unsigned long ret;
 	if (!git_parse_ulong(value, &ret))
-		die_bad_number(cf_global, name, value);
+		die_bad_number(the_reader.source, name, value);
 	return ret;
 }
 
@@ -1350,7 +1368,7 @@ ssize_t git_config_ssize_t(const char *name, const char *value)
 {
 	ssize_t ret;
 	if (!git_parse_ssize_t(value, &ret))
-		die_bad_number(cf_global, name, value);
+		die_bad_number(the_reader.source, name, value);
 	return ret;
 }
 
@@ -1956,7 +1974,8 @@ int git_default_config(const char *var, const char *value, void *cb)
  * fgetc, ungetc, ftell of top need to be initialized before calling
  * this function.
  */
-static int do_config_from(struct config_source *top, config_fn_t fn, void *data,
+static int do_config_from(struct config_reader *reader,
+			  struct config_source *top, config_fn_t fn, void *data,
 			  const struct config_options *opts)
 {
 	int ret;
@@ -1967,22 +1986,23 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data,
 	top->total_len = 0;
 	strbuf_init(&top->value, 1024);
 	strbuf_init(&top->var, 1024);
-	config_reader_push_source(top);
+	config_reader_push_source(reader, top);
 
 	ret = git_parse_source(top, fn, data, opts);
 
 	/* pop config-file parsing state stack */
 	strbuf_release(&top->value);
 	strbuf_release(&top->var);
-	config_reader_pop_source();
+	config_reader_pop_source(reader);
 
 	return ret;
 }
 
-static int do_config_from_file(config_fn_t fn,
-		const enum config_origin_type origin_type,
-		const char *name, const char *path, FILE *f,
-		void *data, const struct config_options *opts)
+static int do_config_from_file(struct config_reader *reader,
+			       config_fn_t fn,
+			       const enum config_origin_type origin_type,
+			       const char *name, const char *path, FILE *f,
+			       void *data, const struct config_options *opts)
 {
 	struct config_source top = CONFIG_SOURCE_INIT;
 	int ret;
@@ -1997,15 +2017,15 @@ static int do_config_from_file(config_fn_t fn,
 	top.do_ftell = config_file_ftell;
 
 	flockfile(f);
-	ret = do_config_from(&top, fn, data, opts);
+	ret = do_config_from(reader, &top, fn, data, opts);
 	funlockfile(f);
 	return ret;
 }
 
 static int git_config_from_stdin(config_fn_t fn, void *data)
 {
-	return do_config_from_file(fn, CONFIG_ORIGIN_STDIN, "", NULL, stdin,
-				   data, NULL);
+	return do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_STDIN, "",
+				   NULL, stdin, data, NULL);
 }
 
 int git_config_from_file_with_options(config_fn_t fn, const char *filename,
@@ -2019,8 +2039,8 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,
 		BUG("filename cannot be NULL");
 	f = fopen_or_warn(filename, "r");
 	if (f) {
-		ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename,
-					  filename, f, data, opts);
+		ret = do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_FILE,
+					  filename, filename, f, data, opts);
 		fclose(f);
 	}
 	return ret;
@@ -2049,7 +2069,7 @@ int git_config_from_mem(config_fn_t fn,
 	top.do_ungetc = config_buf_ungetc;
 	top.do_ftell = config_buf_ftell;
 
-	return do_config_from(&top, fn, data, opts);
+	return do_config_from(&the_reader, &top, fn, data, opts);
 }
 
 int git_config_from_blob_oid(config_fn_t fn,
@@ -3798,8 +3818,8 @@ const char *current_config_origin_type(void)
 	int type;
 	if (current_config_kvi)
 		type = current_config_kvi->origin_type;
-	else if(cf_global)
-		type = cf_global->origin_type;
+	else if(the_reader.source)
+		type = the_reader.source->origin_type;
 	else
 		BUG("current_config_origin_type called outside config callback");
 
@@ -3844,8 +3864,8 @@ const char *current_config_name(void)
 	const char *name;
 	if (current_config_kvi)
 		name = current_config_kvi->filename;
-	else if (cf_global)
-		name = cf_global->name;
+	else if (the_reader.source)
+		name = the_reader.source->name;
 	else
 		BUG("current_config_name called outside config callback");
 	return name ? name : "";
@@ -3864,7 +3884,7 @@ int current_config_line(void)
 	if (current_config_kvi)
 		return current_config_kvi->linenr;
 	else
-		return cf_global->linenr;
+		return the_reader.source->linenr;
 }
 
 int lookup_config(const char **mapping, int nr_mapping, const char *var)
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH v2 4/8] config.c: plumb the_reader through callbacks
  2023-03-16  0:11 ` [PATCH v2 0/8] " Glen Choo via GitGitGadget
                     ` (2 preceding siblings ...)
  2023-03-16  0:11   ` [PATCH v2 3/8] config.c: create config_reader and the_reader Glen Choo via GitGitGadget
@ 2023-03-16  0:11   ` Glen Choo via GitGitGadget
  2023-03-16  0:11   ` [PATCH v2 5/8] config.c: remove current_config_kvi Glen Choo via GitGitGadget
                     ` (7 subsequent siblings)
  11 siblings, 0 replies; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-16  0:11 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee,
	Calvin Wan, Ævar Arnfjörð Bjarmason, Glen Choo,
	Glen Choo

From: Glen Choo <chooglen@google.com>

The remaining references to "cf_global" are in config callback
functions. Remove them by plumbing "struct config_reader" via the
"*data" arg.

In both of the callbacks here, we are only reading from
"reader->source". So in the long run, if we had a way to expose readonly
information from "reader->source" (probably in the form of "struct
key_value_info"), we could undo this patch (i.e. remove "struct
config_reader" fom "*data").

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 74 ++++++++++++++++++++++++++++++++------------------------
 1 file changed, 43 insertions(+), 31 deletions(-)

diff --git a/config.c b/config.c
index 7de25515818..b75cde42ca6 100644
--- a/config.c
+++ b/config.c
@@ -62,6 +62,9 @@ struct config_reader {
 static struct config_reader the_reader;
 
 /*
+ * FIXME The comments are temporarily out of date since "cf_global" has been
+ * moved to the_reader, but not current_*.
+ *
  * These variables record the "current" config source, which
  * can be accessed by parsing callbacks.
  *
@@ -76,11 +79,7 @@ static struct config_reader the_reader;
  * at the variables, it's either a bug for it to be called in the first place,
  * or it's a function which can be reused for non-config purposes, and should
  * fall back to some sane behavior).
- *
- * FIXME "cf_global" has been replaced by "the_reader.source", remove
- * "cf_global" once we plumb "the_reader" through all of the callback functions.
  */
-static struct config_source *cf_global;
 static struct key_value_info *current_config_kvi;
 
 /*
@@ -99,8 +98,6 @@ static inline void config_reader_push_source(struct config_reader *reader,
 	if (reader->source)
 		top->prev = reader->source;
 	reader->source = top;
-	/* FIXME remove this when cf_global is removed. */
-	cf_global = reader->source;
 }
 
 static inline struct config_source *config_reader_pop_source(struct config_reader *reader)
@@ -110,8 +107,6 @@ static inline struct config_source *config_reader_pop_source(struct config_reade
 		BUG("tried to pop config source, but we weren't reading config");
 	ret = reader->source;
 	reader->source = reader->source->prev;
-	/* FIXME remove this when cf is removed. */
-	cf_global = reader->source;
 	return ret;
 }
 
@@ -176,6 +171,7 @@ struct config_include_data {
 	void *data;
 	const struct config_options *opts;
 	struct git_config_source *config_source;
+	struct config_reader *config_reader;
 
 	/*
 	 * All remote URLs discovered when reading all config files.
@@ -466,6 +462,7 @@ static int include_condition_is_true(struct config_source *cf,
 static int git_config_include(const char *var, const char *value, void *data)
 {
 	struct config_include_data *inc = data;
+	struct config_source *cf = inc->config_reader->source;
 	const char *cond, *key;
 	size_t cond_len;
 	int ret;
@@ -479,16 +476,16 @@ static int git_config_include(const char *var, const char *value, void *data)
 		return ret;
 
 	if (!strcmp(var, "include.path"))
-		ret = handle_path_include(cf_global, value, inc);
+		ret = handle_path_include(cf, value, inc);
 
 	if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
-	    cond && include_condition_is_true(cf_global, inc, cond, cond_len) &&
+	    cond && include_condition_is_true(cf, inc, cond, cond_len) &&
 	    !strcmp(key, "path")) {
 		config_fn_t old_fn = inc->fn;
 
 		if (inc->opts->unconditional_remote_url)
 			inc->fn = forbid_remote_url;
-		ret = handle_path_include(cf_global, value, inc);
+		ret = handle_path_include(cf, value, inc);
 		inc->fn = old_fn;
 	}
 
@@ -2229,6 +2226,7 @@ int config_with_options(config_fn_t fn, void *data,
 		inc.data = data;
 		inc.opts = opts;
 		inc.config_source = config_source;
+		inc.config_reader = &the_reader;
 		fn = git_config_include;
 		data = &inc;
 	}
@@ -2349,7 +2347,9 @@ static struct config_set_element *configset_find_element(struct config_set *cs,
 	return found_entry;
 }
 
-static int configset_add_value(struct config_set *cs, const char *key, const char *value)
+static int configset_add_value(struct config_reader *reader,
+			       struct config_set *cs, const char *key,
+			       const char *value)
 {
 	struct config_set_element *e;
 	struct string_list_item *si;
@@ -2375,12 +2375,12 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha
 	l_item->e = e;
 	l_item->value_index = e->value_list.nr - 1;
 
-	if (!cf_global)
+	if (!reader->source)
 		BUG("configset_add_value has no source");
-	if (cf_global->name) {
-		kv_info->filename = strintern(cf_global->name);
-		kv_info->linenr = cf_global->linenr;
-		kv_info->origin_type = cf_global->origin_type;
+	if (reader->source->name) {
+		kv_info->filename = strintern(reader->source->name);
+		kv_info->linenr = reader->source->linenr;
+		kv_info->origin_type = reader->source->origin_type;
 	} else {
 		/* for values read from `git_config_from_parameters()` */
 		kv_info->filename = NULL;
@@ -2435,16 +2435,25 @@ void git_configset_clear(struct config_set *cs)
 	cs->list.items = NULL;
 }
 
+struct configset_add_data {
+	struct config_set *config_set;
+	struct config_reader *config_reader;
+};
+#define CONFIGSET_ADD_INIT { 0 }
+
 static int config_set_callback(const char *key, const char *value, void *cb)
 {
-	struct config_set *cs = cb;
-	configset_add_value(cs, key, value);
+	struct configset_add_data *data = cb;
+	configset_add_value(data->config_reader, data->config_set, key, value);
 	return 0;
 }
 
 int git_configset_add_file(struct config_set *cs, const char *filename)
 {
-	return git_config_from_file(config_set_callback, filename, cs);
+	struct configset_add_data data = CONFIGSET_ADD_INIT;
+	data.config_reader = &the_reader;
+	data.config_set = cs;
+	return git_config_from_file(config_set_callback, filename, &data);
 }
 
 int git_configset_get_value(struct config_set *cs, const char *key, const char **value)
@@ -2559,6 +2568,7 @@ int git_configset_get_pathname(struct config_set *cs, const char *key, const cha
 static void repo_read_config(struct repository *repo)
 {
 	struct config_options opts = { 0 };
+	struct configset_add_data data = CONFIGSET_ADD_INIT;
 
 	opts.respect_includes = 1;
 	opts.commondir = repo->commondir;
@@ -2570,8 +2580,10 @@ static void repo_read_config(struct repository *repo)
 		git_configset_clear(repo->config);
 
 	git_configset_init(repo->config);
+	data.config_set = repo->config;
+	data.config_reader = &the_reader;
 
-	if (config_with_options(config_set_callback, repo->config, NULL, &opts) < 0)
+	if (config_with_options(config_set_callback, &data, NULL, &opts) < 0)
 		/*
 		 * config_with_options() normally returns only
 		 * zero, as most errors are fatal, and
@@ -2697,9 +2709,12 @@ static void read_protected_config(void)
 		.ignore_worktree = 1,
 		.system_gently = 1,
 	};
+	struct configset_add_data data = CONFIGSET_ADD_INIT;
+
 	git_configset_init(&protected_config);
-	config_with_options(config_set_callback, &protected_config,
-			    NULL, &opts);
+	data.config_set = &protected_config;
+	data.config_reader = &the_reader;
+	config_with_options(config_set_callback, &data, NULL, &opts);
 }
 
 void git_protected_config(config_fn_t fn, void *data)
@@ -2884,6 +2899,7 @@ void git_die_config(const char *key, const char *err, ...)
  */
 
 struct config_store_data {
+	struct config_reader *config_reader;
 	size_t baselen;
 	char *key;
 	int do_not_match;
@@ -2898,6 +2914,7 @@ struct config_store_data {
 	unsigned int parsed_nr, parsed_alloc, *seen, seen_nr, seen_alloc;
 	unsigned int key_seen:1, section_seen:1, is_keys_section:1;
 };
+#define CONFIG_STORE_INIT { 0 }
 
 static void config_store_data_clear(struct config_store_data *store)
 {
@@ -2932,12 +2949,7 @@ static int store_aux_event(enum config_event_t type,
 			   size_t begin, size_t end, void *data)
 {
 	struct config_store_data *store = data;
-	/*
-	 * FIXME Keep using "cf" so that we can avoid rewrapping a
-	 * really long line below. Remove this when "cf" gets plumbed
-	 * correctly.
-	 */
-	struct config_source *cf = cf_global;
+	struct config_source *cf = store->config_reader->source;
 
 	ALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc);
 	store->parsed[store->parsed_nr].begin = begin;
@@ -3255,9 +3267,9 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
 	char *filename_buf = NULL;
 	char *contents = NULL;
 	size_t contents_sz;
-	struct config_store_data store;
+	struct config_store_data store = CONFIG_STORE_INIT;
 
-	memset(&store, 0, sizeof(store));
+	store.config_reader = &the_reader;
 
 	/* parse-key returns negative; flip the sign to feed exit(3) */
 	ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH v2 5/8] config.c: remove current_config_kvi
  2023-03-16  0:11 ` [PATCH v2 0/8] " Glen Choo via GitGitGadget
                     ` (3 preceding siblings ...)
  2023-03-16  0:11   ` [PATCH v2 4/8] config.c: plumb the_reader through callbacks Glen Choo via GitGitGadget
@ 2023-03-16  0:11   ` Glen Choo via GitGitGadget
  2023-03-16  0:11   ` [PATCH v2 6/8] config.c: remove current_parsing_scope Glen Choo via GitGitGadget
                     ` (6 subsequent siblings)
  11 siblings, 0 replies; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-16  0:11 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee,
	Calvin Wan, Ævar Arnfjörð Bjarmason, Glen Choo,
	Glen Choo

From: Glen Choo <chooglen@google.com>

Add ".config_kvi" to "struct config_reader" and replace
"current_config_kvi" with "the_reader.config_kvi", plumbing "struct
config_reader" where necesssary.

Also, introduce a setter function for ".config_kvi", which allows us to
enforce the contraint that only one of ".source" and ".config_kvi" can
be set at a time (as documented in the comments). Because of this
constraint, we know that "populate_remote_urls()" was never touching
"current_config_kvi" when iterating through config files, so it doesn't
need to store and restore that value.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 82 +++++++++++++++++++++++++++++---------------------------
 1 file changed, 43 insertions(+), 39 deletions(-)

diff --git a/config.c b/config.c
index b75cde42ca6..ba9b488c40d 100644
--- a/config.c
+++ b/config.c
@@ -52,7 +52,24 @@ struct config_source {
 #define CONFIG_SOURCE_INIT { 0 }
 
 struct config_reader {
+	/*
+	 * These members record the "current" config source, which can be
+	 * accessed by parsing callbacks.
+	 *
+	 * The "source" variable will be non-NULL only when we are actually
+	 * parsing a real config source (file, blob, cmdline, etc).
+	 *
+	 * The "config_kvi" variable will be non-NULL only when we are feeding
+	 * cached config from a configset into a callback.
+	 *
+	 * They cannot be non-NULL at the same time. If they are both NULL, then
+	 * we aren't parsing anything (and depending on the function looking at
+	 * the variables, it's either a bug for it to be called in the first
+	 * place, or it's a function which can be reused for non-config
+	 * purposes, and should fall back to some sane behavior).
+	 */
 	struct config_source *source;
+	struct key_value_info *config_kvi;
 };
 /*
  * Where possible, prefer to accept "struct config_reader" as an arg than to use
@@ -61,27 +78,6 @@ struct config_reader {
  */
 static struct config_reader the_reader;
 
-/*
- * FIXME The comments are temporarily out of date since "cf_global" has been
- * moved to the_reader, but not current_*.
- *
- * These variables record the "current" config source, which
- * can be accessed by parsing callbacks.
- *
- * The "cf_global" variable will be non-NULL only when we are actually
- * parsing a real config source (file, blob, cmdline, etc).
- *
- * The "current_config_kvi" variable will be non-NULL only when we are feeding
- * cached config from a configset into a callback.
- *
- * They should generally never be non-NULL at the same time. If they are both
- * NULL, then we aren't parsing anything (and depending on the function looking
- * at the variables, it's either a bug for it to be called in the first place,
- * or it's a function which can be reused for non-config purposes, and should
- * fall back to some sane behavior).
- */
-static struct key_value_info *current_config_kvi;
-
 /*
  * Similar to the variables above, this gives access to the "scope" of the
  * current value (repo, global, etc). For cached values, it can be found via
@@ -95,6 +91,8 @@ static enum config_scope current_parsing_scope;
 static inline void config_reader_push_source(struct config_reader *reader,
 					     struct config_source *top)
 {
+	if (reader->config_kvi)
+		BUG("source should not be set while iterating a config set");
 	if (reader->source)
 		top->prev = reader->source;
 	reader->source = top;
@@ -110,6 +108,14 @@ static inline struct config_source *config_reader_pop_source(struct config_reade
 	return ret;
 }
 
+static inline void config_reader_set_kvi(struct config_reader *reader,
+					 struct key_value_info *kvi)
+{
+	if (kvi && reader->source)
+		BUG("kvi should not be set while parsing a config source");
+	reader->config_kvi = kvi;
+}
+
 static int pack_compression_seen;
 static int zlib_compression_seen;
 
@@ -378,20 +384,17 @@ static void populate_remote_urls(struct config_include_data *inc)
 {
 	struct config_options opts;
 
-	struct key_value_info *store_kvi = current_config_kvi;
 	enum config_scope store_scope = current_parsing_scope;
 
 	opts = *inc->opts;
 	opts.unconditional_remote_url = 1;
 
-	current_config_kvi = NULL;
 	current_parsing_scope = 0;
 
 	inc->remote_urls = xmalloc(sizeof(*inc->remote_urls));
 	string_list_init_dup(inc->remote_urls);
 	config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);
 
-	current_config_kvi = store_kvi;
 	current_parsing_scope = store_scope;
 }
 
@@ -2258,7 +2261,8 @@ int config_with_options(config_fn_t fn, void *data,
 	return ret;
 }
 
-static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
+static void configset_iter(struct config_reader *reader, struct config_set *cs,
+			   config_fn_t fn, void *data)
 {
 	int i, value_index;
 	struct string_list *values;
@@ -2270,14 +2274,14 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
 		value_index = list->items[i].value_index;
 		values = &entry->value_list;
 
-		current_config_kvi = values->items[value_index].util;
+		config_reader_set_kvi(reader, values->items[value_index].util);
 
 		if (fn(entry->key, values->items[value_index].string, data) < 0)
 			git_die_config_linenr(entry->key,
-					      current_config_kvi->filename,
-					      current_config_kvi->linenr);
+					      reader->config_kvi->filename,
+					      reader->config_kvi->linenr);
 
-		current_config_kvi = NULL;
+		config_reader_set_kvi(reader, NULL);
 	}
 }
 
@@ -2615,7 +2619,7 @@ static void repo_config_clear(struct repository *repo)
 void repo_config(struct repository *repo, config_fn_t fn, void *data)
 {
 	git_config_check_init(repo);
-	configset_iter(repo->config, fn, data);
+	configset_iter(&the_reader, repo->config, fn, data);
 }
 
 int repo_config_get_value(struct repository *repo,
@@ -2721,7 +2725,7 @@ void git_protected_config(config_fn_t fn, void *data)
 {
 	if (!protected_config.hash_initialized)
 		read_protected_config();
-	configset_iter(&protected_config, fn, data);
+	configset_iter(&the_reader, &protected_config, fn, data);
 }
 
 /* Functions used historically to read configuration from 'the_repository' */
@@ -3828,8 +3832,8 @@ int parse_config_key(const char *var,
 const char *current_config_origin_type(void)
 {
 	int type;
-	if (current_config_kvi)
-		type = current_config_kvi->origin_type;
+	if (the_reader.config_kvi)
+		type = the_reader.config_kvi->origin_type;
 	else if(the_reader.source)
 		type = the_reader.source->origin_type;
 	else
@@ -3874,8 +3878,8 @@ const char *config_scope_name(enum config_scope scope)
 const char *current_config_name(void)
 {
 	const char *name;
-	if (current_config_kvi)
-		name = current_config_kvi->filename;
+	if (the_reader.config_kvi)
+		name = the_reader.config_kvi->filename;
 	else if (the_reader.source)
 		name = the_reader.source->name;
 	else
@@ -3885,16 +3889,16 @@ const char *current_config_name(void)
 
 enum config_scope current_config_scope(void)
 {
-	if (current_config_kvi)
-		return current_config_kvi->scope;
+	if (the_reader.config_kvi)
+		return the_reader.config_kvi->scope;
 	else
 		return current_parsing_scope;
 }
 
 int current_config_line(void)
 {
-	if (current_config_kvi)
-		return current_config_kvi->linenr;
+	if (the_reader.config_kvi)
+		return the_reader.config_kvi->linenr;
 	else
 		return the_reader.source->linenr;
 }
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH v2 6/8] config.c: remove current_parsing_scope
  2023-03-16  0:11 ` [PATCH v2 0/8] " Glen Choo via GitGitGadget
                     ` (4 preceding siblings ...)
  2023-03-16  0:11   ` [PATCH v2 5/8] config.c: remove current_config_kvi Glen Choo via GitGitGadget
@ 2023-03-16  0:11   ` Glen Choo via GitGitGadget
  2023-03-16  0:11   ` [PATCH v2 7/8] config: report cached filenames in die_bad_number() Glen Choo via GitGitGadget
                     ` (5 subsequent siblings)
  11 siblings, 0 replies; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-16  0:11 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee,
	Calvin Wan, Ævar Arnfjörð Bjarmason, Glen Choo,
	Glen Choo

From: Glen Choo <chooglen@google.com>

Add ".parsing_scope" to "struct config_reader" and replace
"current_parsing_scope" with "the_reader.parsing_scope. Adjust the
comment slightly to make it clearer that the scope applies to the config
source (not the current value), and should only be set when parsing a
config source.

As such, ".parsing_scope" (only set when parsing config sources) and
".config_kvi" (only set when iterating a config set) should not be
set together, so enforce this with a setter function.

Unlike previous commits, "populate_remote_urls()" still needs to store
and restore the 'scope' value because it could have touched
"current_parsing_scope" ("config_with_options()" can set the scope).

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 63 +++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 37 insertions(+), 26 deletions(-)

diff --git a/config.c b/config.c
index ba9b488c40d..460326ae21e 100644
--- a/config.c
+++ b/config.c
@@ -70,6 +70,16 @@ struct config_reader {
 	 */
 	struct config_source *source;
 	struct key_value_info *config_kvi;
+	/*
+	 * The "scope" of the current config source being parsed (repo, global,
+	 * etc). Like "source", this is only set when parsing a config source.
+	 * It's not part of "source" because it transcends a single file (i.e.,
+	 * a file included from .git/config is still in "repo" scope).
+	 *
+	 * When iterating through a configset, the equivalent value is
+	 * "config_kvi.scope" (see above).
+	 */
+	enum config_scope parsing_scope;
 };
 /*
  * Where possible, prefer to accept "struct config_reader" as an arg than to use
@@ -78,16 +88,6 @@ struct config_reader {
  */
 static struct config_reader the_reader;
 
-/*
- * Similar to the variables above, this gives access to the "scope" of the
- * current value (repo, global, etc). For cached values, it can be found via
- * the current_config_kvi as above. During parsing, the current value can be
- * found in this variable. It's not part of "cf_global" because it transcends a
- * single file (i.e., a file included from .git/config is still in "repo"
- * scope).
- */
-static enum config_scope current_parsing_scope;
-
 static inline void config_reader_push_source(struct config_reader *reader,
 					     struct config_source *top)
 {
@@ -111,11 +111,19 @@ static inline struct config_source *config_reader_pop_source(struct config_reade
 static inline void config_reader_set_kvi(struct config_reader *reader,
 					 struct key_value_info *kvi)
 {
-	if (kvi && reader->source)
+	if (kvi && (reader->source || reader->parsing_scope))
 		BUG("kvi should not be set while parsing a config source");
 	reader->config_kvi = kvi;
 }
 
+static inline void config_reader_set_scope(struct config_reader *reader,
+					   enum config_scope scope)
+{
+	if (scope && reader->config_kvi)
+		BUG("scope should only be set when iterating through a config source");
+	reader->parsing_scope = scope;
+}
+
 static int pack_compression_seen;
 static int zlib_compression_seen;
 
@@ -384,18 +392,18 @@ static void populate_remote_urls(struct config_include_data *inc)
 {
 	struct config_options opts;
 
-	enum config_scope store_scope = current_parsing_scope;
+	enum config_scope store_scope = inc->config_reader->parsing_scope;
 
 	opts = *inc->opts;
 	opts.unconditional_remote_url = 1;
 
-	current_parsing_scope = 0;
+	config_reader_set_scope(inc->config_reader, 0);
 
 	inc->remote_urls = xmalloc(sizeof(*inc->remote_urls));
 	string_list_init_dup(inc->remote_urls);
 	config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);
 
-	current_parsing_scope = store_scope;
+	config_reader_set_scope(inc->config_reader, store_scope);
 }
 
 static int forbid_remote_url(const char *var, const char *value UNUSED,
@@ -2160,7 +2168,8 @@ int git_config_system(void)
 	return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
 }
 
-static int do_git_config_sequence(const struct config_options *opts,
+static int do_git_config_sequence(struct config_reader *reader,
+				  const struct config_options *opts,
 				  config_fn_t fn, void *data)
 {
 	int ret = 0;
@@ -2168,7 +2177,7 @@ static int do_git_config_sequence(const struct config_options *opts,
 	char *xdg_config = NULL;
 	char *user_config = NULL;
 	char *repo_config;
-	enum config_scope prev_parsing_scope = current_parsing_scope;
+	enum config_scope prev_parsing_scope = reader->parsing_scope;
 
 	if (opts->commondir)
 		repo_config = mkpathdup("%s/config", opts->commondir);
@@ -2177,13 +2186,13 @@ static int do_git_config_sequence(const struct config_options *opts,
 	else
 		repo_config = NULL;
 
-	current_parsing_scope = CONFIG_SCOPE_SYSTEM;
+	config_reader_set_scope(reader, CONFIG_SCOPE_SYSTEM);
 	if (git_config_system() && system_config &&
 	    !access_or_die(system_config, R_OK,
 			   opts->system_gently ? ACCESS_EACCES_OK : 0))
 		ret += git_config_from_file(fn, system_config, data);
 
-	current_parsing_scope = CONFIG_SCOPE_GLOBAL;
+	config_reader_set_scope(reader, CONFIG_SCOPE_GLOBAL);
 	git_global_config(&user_config, &xdg_config);
 
 	if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))
@@ -2192,12 +2201,12 @@ static int do_git_config_sequence(const struct config_options *opts,
 	if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))
 		ret += git_config_from_file(fn, user_config, data);
 
-	current_parsing_scope = CONFIG_SCOPE_LOCAL;
+	config_reader_set_scope(reader, CONFIG_SCOPE_LOCAL);
 	if (!opts->ignore_repo && repo_config &&
 	    !access_or_die(repo_config, R_OK, 0))
 		ret += git_config_from_file(fn, repo_config, data);
 
-	current_parsing_scope = CONFIG_SCOPE_WORKTREE;
+	config_reader_set_scope(reader, CONFIG_SCOPE_WORKTREE);
 	if (!opts->ignore_worktree && repository_format_worktree_config) {
 		char *path = git_pathdup("config.worktree");
 		if (!access_or_die(path, R_OK, 0))
@@ -2205,11 +2214,11 @@ static int do_git_config_sequence(const struct config_options *opts,
 		free(path);
 	}
 
-	current_parsing_scope = CONFIG_SCOPE_COMMAND;
+	config_reader_set_scope(reader, CONFIG_SCOPE_COMMAND);
 	if (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)
 		die(_("unable to parse command-line config"));
 
-	current_parsing_scope = prev_parsing_scope;
+	config_reader_set_scope(reader, prev_parsing_scope);
 	free(system_config);
 	free(xdg_config);
 	free(user_config);
@@ -2222,6 +2231,7 @@ int config_with_options(config_fn_t fn, void *data,
 			const struct config_options *opts)
 {
 	struct config_include_data inc = CONFIG_INCLUDE_INIT;
+	enum config_scope prev_scope = the_reader.parsing_scope;
 	int ret;
 
 	if (opts->respect_includes) {
@@ -2235,7 +2245,7 @@ int config_with_options(config_fn_t fn, void *data,
 	}
 
 	if (config_source)
-		current_parsing_scope = config_source->scope;
+		config_reader_set_scope(&the_reader, config_source->scope);
 
 	/*
 	 * If we have a specific filename, use it. Otherwise, follow the
@@ -2251,13 +2261,14 @@ int config_with_options(config_fn_t fn, void *data,
 		ret = git_config_from_blob_ref(fn, repo, config_source->blob,
 						data);
 	} else {
-		ret = do_git_config_sequence(opts, fn, data);
+		ret = do_git_config_sequence(&the_reader, opts, fn, data);
 	}
 
 	if (inc.remote_urls) {
 		string_list_clear(inc.remote_urls, 0);
 		FREE_AND_NULL(inc.remote_urls);
 	}
+	config_reader_set_scope(&the_reader, prev_scope);
 	return ret;
 }
 
@@ -2391,7 +2402,7 @@ static int configset_add_value(struct config_reader *reader,
 		kv_info->linenr = -1;
 		kv_info->origin_type = CONFIG_ORIGIN_CMDLINE;
 	}
-	kv_info->scope = current_parsing_scope;
+	kv_info->scope = reader->parsing_scope;
 	si->util = kv_info;
 
 	return 0;
@@ -3892,7 +3903,7 @@ enum config_scope current_config_scope(void)
 	if (the_reader.config_kvi)
 		return the_reader.config_kvi->scope;
 	else
-		return current_parsing_scope;
+		return the_reader.parsing_scope;
 }
 
 int current_config_line(void)
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH v2 7/8] config: report cached filenames in die_bad_number()
  2023-03-16  0:11 ` [PATCH v2 0/8] " Glen Choo via GitGitGadget
                     ` (5 preceding siblings ...)
  2023-03-16  0:11   ` [PATCH v2 6/8] config.c: remove current_parsing_scope Glen Choo via GitGitGadget
@ 2023-03-16  0:11   ` Glen Choo via GitGitGadget
  2023-03-16 22:22     ` Jonathan Tan
  2023-03-16  0:11   ` [PATCH v2 8/8] config.c: rename "struct config_source cf" Glen Choo via GitGitGadget
                     ` (4 subsequent siblings)
  11 siblings, 1 reply; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-16  0:11 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee,
	Calvin Wan, Ævar Arnfjörð Bjarmason, Glen Choo,
	Glen Choo

From: Glen Choo <chooglen@google.com>

If, when parsing numbers from config, die_bad_number() is called, it
reports the filename and config source type if we were parsing a config
file, but not if we were iterating a config_set (it defaults to a less
specific error message). Most call sites don't parse config files
because config is typically read once and cached, so we only report
filename and config source type in "git config --type" (since "git
config" always parses config files).

This could have been fixed when we taught the current_config_*
functions to respect config_set values (0d44a2dacc (config: return
configset value for current_config_ functions, 2016-05-26), but it was
hard to spot then and we might have just missed it (I didn't find
mention of die_bad_number() in the original ML discussion [1].)

Fix this by refactoring the current_config_* functions into variants
that don't BUG() when we aren't reading config, and using the resulting
functions in die_bad_number(). Refactoring is needed because "git config
--get[-regexp] --type=int" parses the int value _after_ parsing the
config file, which will run into the BUG().

Also, plumb "struct config_reader" into the new functions. This isn't
necessary per se, but this generalizes better, so it might help us avoid
yet another refactor.

1. https://lore.kernel.org/git/20160518223712.GA18317@sigill.intra.peff.net/

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c               | 65 +++++++++++++++++++++++++++++-------------
 config.h               |  1 +
 t/helper/test-config.c | 17 +++++++++++
 t/t1308-config-set.sh  |  9 ++++++
 4 files changed, 72 insertions(+), 20 deletions(-)

diff --git a/config.c b/config.c
index 460326ae21e..da5f6381cde 100644
--- a/config.c
+++ b/config.c
@@ -1312,39 +1312,48 @@ int git_parse_ssize_t(const char *value, ssize_t *ret)
 	return 1;
 }
 
+static int reader_config_name(struct config_reader *reader, const char **out);
+static int reader_origin_type(struct config_reader *reader,
+			      enum config_origin_type *type);
 NORETURN
-static void die_bad_number(struct config_source *cf, const char *name,
+static void die_bad_number(struct config_reader *reader, const char *name,
 			   const char *value)
 {
 	const char *error_type = (errno == ERANGE) ?
 		N_("out of range") : N_("invalid unit");
 	const char *bad_numeric = N_("bad numeric config value '%s' for '%s': %s");
+	const char *config_name = NULL;
+	enum config_origin_type config_origin = CONFIG_ORIGIN_UNKNOWN;
 
 	if (!value)
 		value = "";
 
-	if (!(cf && cf->name))
+	/* Ignoring the return value is okay since we handle missing values. */
+	reader_config_name(reader, &config_name);
+	reader_origin_type(reader, &config_origin);
+
+	if (!config_name)
 		die(_(bad_numeric), value, name, _(error_type));
 
-	switch (cf->origin_type) {
+	switch (config_origin) {
 	case CONFIG_ORIGIN_BLOB:
 		die(_("bad numeric config value '%s' for '%s' in blob %s: %s"),
-		    value, name, cf->name, _(error_type));
+		    value, name, config_name, _(error_type));
 	case CONFIG_ORIGIN_FILE:
 		die(_("bad numeric config value '%s' for '%s' in file %s: %s"),
-		    value, name, cf->name, _(error_type));
+		    value, name, config_name, _(error_type));
 	case CONFIG_ORIGIN_STDIN:
 		die(_("bad numeric config value '%s' for '%s' in standard input: %s"),
 		    value, name, _(error_type));
 	case CONFIG_ORIGIN_SUBMODULE_BLOB:
 		die(_("bad numeric config value '%s' for '%s' in submodule-blob %s: %s"),
-		    value, name, cf->name, _(error_type));
+		    value, name, config_name, _(error_type));
 	case CONFIG_ORIGIN_CMDLINE:
 		die(_("bad numeric config value '%s' for '%s' in command line %s: %s"),
-		    value, name, cf->name, _(error_type));
+		    value, name, config_name, _(error_type));
 	default:
 		die(_("bad numeric config value '%s' for '%s' in %s: %s"),
-		    value, name, cf->name, _(error_type));
+		    value, name, config_name, _(error_type));
 	}
 }
 
@@ -1352,7 +1361,7 @@ int git_config_int(const char *name, const char *value)
 {
 	int ret;
 	if (!git_parse_int(value, &ret))
-		die_bad_number(the_reader.source, name, value);
+		die_bad_number(&the_reader, name, value);
 	return ret;
 }
 
@@ -1360,7 +1369,7 @@ int64_t git_config_int64(const char *name, const char *value)
 {
 	int64_t ret;
 	if (!git_parse_int64(value, &ret))
-		die_bad_number(the_reader.source, name, value);
+		die_bad_number(&the_reader, name, value);
 	return ret;
 }
 
@@ -1368,7 +1377,7 @@ unsigned long git_config_ulong(const char *name, const char *value)
 {
 	unsigned long ret;
 	if (!git_parse_ulong(value, &ret))
-		die_bad_number(the_reader.source, name, value);
+		die_bad_number(&the_reader, name, value);
 	return ret;
 }
 
@@ -1376,7 +1385,7 @@ ssize_t git_config_ssize_t(const char *name, const char *value)
 {
 	ssize_t ret;
 	if (!git_parse_ssize_t(value, &ret))
-		die_bad_number(the_reader.source, name, value);
+		die_bad_number(&the_reader, name, value);
 	return ret;
 }
 
@@ -3840,14 +3849,23 @@ int parse_config_key(const char *var,
 	return 0;
 }
 
-const char *current_config_origin_type(void)
+static int reader_origin_type(struct config_reader *reader,
+			      enum config_origin_type *type)
 {
-	int type;
 	if (the_reader.config_kvi)
-		type = the_reader.config_kvi->origin_type;
+		*type = reader->config_kvi->origin_type;
 	else if(the_reader.source)
-		type = the_reader.source->origin_type;
+		*type = reader->source->origin_type;
 	else
+		return 1;
+	return 0;
+}
+
+const char *current_config_origin_type(void)
+{
+	enum config_origin_type type = CONFIG_ORIGIN_UNKNOWN;
+
+	if (reader_origin_type(&the_reader, &type))
 		BUG("current_config_origin_type called outside config callback");
 
 	switch (type) {
@@ -3886,14 +3904,21 @@ const char *config_scope_name(enum config_scope scope)
 	}
 }
 
-const char *current_config_name(void)
+static int reader_config_name(struct config_reader *reader, const char **out)
 {
-	const char *name;
 	if (the_reader.config_kvi)
-		name = the_reader.config_kvi->filename;
+		*out = reader->config_kvi->filename;
 	else if (the_reader.source)
-		name = the_reader.source->name;
+		*out = reader->source->name;
 	else
+		return 1;
+	return 0;
+}
+
+const char *current_config_name(void)
+{
+	const char *name;
+	if (reader_config_name(&the_reader, &name))
 		BUG("current_config_name called outside config callback");
 	return name ? name : "";
 }
diff --git a/config.h b/config.h
index 7606246531a..66c8b996e15 100644
--- a/config.h
+++ b/config.h
@@ -56,6 +56,7 @@ struct git_config_source {
 };
 
 enum config_origin_type {
+	CONFIG_ORIGIN_UNKNOWN = 0,
 	CONFIG_ORIGIN_BLOB,
 	CONFIG_ORIGIN_FILE,
 	CONFIG_ORIGIN_STDIN,
diff --git a/t/helper/test-config.c b/t/helper/test-config.c
index 4ba9eb65606..26e79168f6a 100644
--- a/t/helper/test-config.c
+++ b/t/helper/test-config.c
@@ -30,6 +30,9 @@
  * iterate -> iterate over all values using git_config(), and print some
  *            data for each
  *
+ * git_config_int -> iterate over all values using git_config() and print the
+ *                   integer value for the entered key or die
+ *
  * Examples:
  *
  * To print the value with highest priority for key "foo.bAr Baz.rock":
@@ -54,6 +57,17 @@ static int iterate_cb(const char *var, const char *value, void *data UNUSED)
 	return 0;
 }
 
+static int parse_int_cb(const char *var, const char *value, void *data)
+{
+	const char *key_to_match = data;
+
+	if (!strcmp(key_to_match, var)) {
+		int parsed = git_config_int(value, value);
+		printf("%d\n", parsed);
+	}
+	return 0;
+}
+
 static int early_config_cb(const char *var, const char *value, void *vdata)
 {
 	const char *key = vdata;
@@ -176,6 +190,9 @@ int cmd__config(int argc, const char **argv)
 	} else if (!strcmp(argv[1], "iterate")) {
 		git_config(iterate_cb, NULL);
 		goto exit0;
+	} else if (argc == 3 && !strcmp(argv[1], "git_config_int")) {
+		git_config(parse_int_cb, (void *) argv[2]);
+		goto exit0;
 	}
 
 	die("%s: Please check the syntax and the function name", argv[0]);
diff --git a/t/t1308-config-set.sh b/t/t1308-config-set.sh
index b38e158d3b2..9733bed30a9 100755
--- a/t/t1308-config-set.sh
+++ b/t/t1308-config-set.sh
@@ -120,6 +120,10 @@ test_expect_success 'find integer value for a key' '
 	check_config get_int lamb.chop 65
 '
 
+test_expect_success 'parse integer value during iteration' '
+	check_config git_config_int lamb.chop 65
+'
+
 test_expect_success 'find string value for a key' '
 	check_config get_string case.baz hask &&
 	check_config expect_code 1 get_string case.ba "Value not found for \"case.ba\""
@@ -134,6 +138,11 @@ test_expect_success 'find integer if value is non parse-able' '
 	check_config expect_code 128 get_int lamb.head
 '
 
+test_expect_success 'non parse-able integer value during iteration' '
+	check_config expect_code 128 git_config_int lamb.head 2>result &&
+	grep "fatal: bad numeric config value .* in file \.git/config" result
+'
+
 test_expect_success 'find bool value for the entered key' '
 	check_config get_bool goat.head 1 &&
 	check_config get_bool goat.skin 0 &&
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH v2 8/8] config.c: rename "struct config_source cf"
  2023-03-16  0:11 ` [PATCH v2 0/8] " Glen Choo via GitGitGadget
                     ` (6 preceding siblings ...)
  2023-03-16  0:11   ` [PATCH v2 7/8] config: report cached filenames in die_bad_number() Glen Choo via GitGitGadget
@ 2023-03-16  0:11   ` Glen Choo via GitGitGadget
  2023-03-16  0:15   ` [PATCH v2 0/8] config.c: use struct for config reading state Glen Choo
                     ` (3 subsequent siblings)
  11 siblings, 0 replies; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-16  0:11 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee,
	Calvin Wan, Ævar Arnfjörð Bjarmason, Glen Choo,
	Glen Choo

From: Glen Choo <chooglen@google.com>

The "cf" name is a holdover from before 4d8dd1494e (config: make parsing
stack struct independent from actual data source, 2013-07-12), when the
struct was named config_file. Since that acronym no longer makes sense,
rename "cf" to "cs". In some places, we have "struct config_set cs", so
to avoid conflict, rename those "cs" to "set" ("config_set" would be
more descriptive, but it's much longer and would require us to rewrap
several lines).

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 262 +++++++++++++++++++++++++++----------------------------
 1 file changed, 131 insertions(+), 131 deletions(-)

diff --git a/config.c b/config.c
index da5f6381cde..ae171ab11c3 100644
--- a/config.c
+++ b/config.c
@@ -203,7 +203,7 @@ static const char include_depth_advice[] = N_(
 "from\n"
 "	%s\n"
 "This might be due to circular includes.");
-static int handle_path_include(struct config_source *cf, const char *path,
+static int handle_path_include(struct config_source *cs, const char *path,
 			       struct config_include_data *inc)
 {
 	int ret = 0;
@@ -225,14 +225,14 @@ static int handle_path_include(struct config_source *cf, const char *path,
 	if (!is_absolute_path(path)) {
 		char *slash;
 
-		if (!cf || !cf->path) {
+		if (!cs || !cs->path) {
 			ret = error(_("relative config includes must come from files"));
 			goto cleanup;
 		}
 
-		slash = find_last_dir_sep(cf->path);
+		slash = find_last_dir_sep(cs->path);
 		if (slash)
-			strbuf_add(&buf, cf->path, slash - cf->path + 1);
+			strbuf_add(&buf, cs->path, slash - cs->path + 1);
 		strbuf_addstr(&buf, path);
 		path = buf.buf;
 	}
@@ -240,8 +240,8 @@ static int handle_path_include(struct config_source *cf, const char *path,
 	if (!access_or_die(path, R_OK, 0)) {
 		if (++inc->depth > MAX_INCLUDE_DEPTH)
 			die(_(include_depth_advice), MAX_INCLUDE_DEPTH, path,
-			    !cf ? "<unknown>" :
-			    cf->name ? cf->name :
+			    !cs ? "<unknown>" :
+			    cs->name ? cs->name :
 			    "the command line");
 		ret = git_config_from_file(git_config_include, path, inc);
 		inc->depth--;
@@ -258,7 +258,7 @@ static void add_trailing_starstar_for_dir(struct strbuf *pat)
 		strbuf_addstr(pat, "**");
 }
 
-static int prepare_include_condition_pattern(struct config_source *cf,
+static int prepare_include_condition_pattern(struct config_source *cs,
 					     struct strbuf *pat)
 {
 	struct strbuf path = STRBUF_INIT;
@@ -275,11 +275,11 @@ static int prepare_include_condition_pattern(struct config_source *cf,
 	if (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {
 		const char *slash;
 
-		if (!cf || !cf->path)
+		if (!cs || !cs->path)
 			return error(_("relative config include "
 				       "conditionals must come from files"));
 
-		strbuf_realpath(&path, cf->path, 1);
+		strbuf_realpath(&path, cs->path, 1);
 		slash = find_last_dir_sep(path.buf);
 		if (!slash)
 			BUG("how is this possible?");
@@ -294,7 +294,7 @@ static int prepare_include_condition_pattern(struct config_source *cf,
 	return prefix;
 }
 
-static int include_by_gitdir(struct config_source *cf,
+static int include_by_gitdir(struct config_source *cs,
 			     const struct config_options *opts,
 			     const char *cond, size_t cond_len, int icase)
 {
@@ -311,7 +311,7 @@ static int include_by_gitdir(struct config_source *cf,
 
 	strbuf_realpath(&text, git_dir, 1);
 	strbuf_add(&pattern, cond, cond_len);
-	prefix = prepare_include_condition_pattern(cf, &pattern);
+	prefix = prepare_include_condition_pattern(cs, &pattern);
 
 again:
 	if (prefix < 0)
@@ -450,16 +450,16 @@ static int include_by_remote_url(struct config_include_data *inc,
 					     inc->remote_urls);
 }
 
-static int include_condition_is_true(struct config_source *cf,
+static int include_condition_is_true(struct config_source *cs,
 				     struct config_include_data *inc,
 				     const char *cond, size_t cond_len)
 {
 	const struct config_options *opts = inc->opts;
 
 	if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
-		return include_by_gitdir(cf, opts, cond, cond_len, 0);
+		return include_by_gitdir(cs, opts, cond, cond_len, 0);
 	else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
-		return include_by_gitdir(cf, opts, cond, cond_len, 1);
+		return include_by_gitdir(cs, opts, cond, cond_len, 1);
 	else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
 		return include_by_branch(cond, cond_len);
 	else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,
@@ -473,7 +473,7 @@ static int include_condition_is_true(struct config_source *cf,
 static int git_config_include(const char *var, const char *value, void *data)
 {
 	struct config_include_data *inc = data;
-	struct config_source *cf = inc->config_reader->source;
+	struct config_source *cs = inc->config_reader->source;
 	const char *cond, *key;
 	size_t cond_len;
 	int ret;
@@ -487,16 +487,16 @@ static int git_config_include(const char *var, const char *value, void *data)
 		return ret;
 
 	if (!strcmp(var, "include.path"))
-		ret = handle_path_include(cf, value, inc);
+		ret = handle_path_include(cs, value, inc);
 
 	if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
-	    cond && include_condition_is_true(cf, inc, cond, cond_len) &&
+	    cond && include_condition_is_true(cs, inc, cond, cond_len) &&
 	    !strcmp(key, "path")) {
 		config_fn_t old_fn = inc->fn;
 
 		if (inc->opts->unconditional_remote_url)
 			inc->fn = forbid_remote_url;
-		ret = handle_path_include(cf, value, inc);
+		ret = handle_path_include(cs, value, inc);
 		inc->fn = old_fn;
 	}
 
@@ -821,21 +821,21 @@ out:
 	return ret;
 }
 
-static int get_next_char(struct config_source *cf)
+static int get_next_char(struct config_source *cs)
 {
-	int c = cf->do_fgetc(cf);
+	int c = cs->do_fgetc(cs);
 
 	if (c == '\r') {
 		/* DOS like systems */
-		c = cf->do_fgetc(cf);
+		c = cs->do_fgetc(cs);
 		if (c != '\n') {
 			if (c != EOF)
-				cf->do_ungetc(c, cf);
+				cs->do_ungetc(c, cs);
 			c = '\r';
 		}
 	}
 
-	if (c != EOF && ++cf->total_len > INT_MAX) {
+	if (c != EOF && ++cs->total_len > INT_MAX) {
 		/*
 		 * This is an absurdly long config file; refuse to parse
 		 * further in order to protect downstream code from integer
@@ -843,38 +843,38 @@ static int get_next_char(struct config_source *cf)
 		 * but we can mark EOF and put trash in the return value,
 		 * which will trigger a parse error.
 		 */
-		cf->eof = 1;
+		cs->eof = 1;
 		return 0;
 	}
 
 	if (c == '\n')
-		cf->linenr++;
+		cs->linenr++;
 	if (c == EOF) {
-		cf->eof = 1;
-		cf->linenr++;
+		cs->eof = 1;
+		cs->linenr++;
 		c = '\n';
 	}
 	return c;
 }
 
-static char *parse_value(struct config_source *cf)
+static char *parse_value(struct config_source *cs)
 {
 	int quote = 0, comment = 0, space = 0;
 
-	strbuf_reset(&cf->value);
+	strbuf_reset(&cs->value);
 	for (;;) {
-		int c = get_next_char(cf);
+		int c = get_next_char(cs);
 		if (c == '\n') {
 			if (quote) {
-				cf->linenr--;
+				cs->linenr--;
 				return NULL;
 			}
-			return cf->value.buf;
+			return cs->value.buf;
 		}
 		if (comment)
 			continue;
 		if (isspace(c) && !quote) {
-			if (cf->value.len)
+			if (cs->value.len)
 				space++;
 			continue;
 		}
@@ -885,9 +885,9 @@ static char *parse_value(struct config_source *cf)
 			}
 		}
 		for (; space; space--)
-			strbuf_addch(&cf->value, ' ');
+			strbuf_addch(&cs->value, ' ');
 		if (c == '\\') {
-			c = get_next_char(cf);
+			c = get_next_char(cs);
 			switch (c) {
 			case '\n':
 				continue;
@@ -907,18 +907,18 @@ static char *parse_value(struct config_source *cf)
 			default:
 				return NULL;
 			}
-			strbuf_addch(&cf->value, c);
+			strbuf_addch(&cs->value, c);
 			continue;
 		}
 		if (c == '"') {
 			quote = 1-quote;
 			continue;
 		}
-		strbuf_addch(&cf->value, c);
+		strbuf_addch(&cs->value, c);
 	}
 }
 
-static int get_value(struct config_source *cf, config_fn_t fn, void *data,
+static int get_value(struct config_source *cs, config_fn_t fn, void *data,
 		     struct strbuf *name)
 {
 	int c;
@@ -927,8 +927,8 @@ static int get_value(struct config_source *cf, config_fn_t fn, void *data,
 
 	/* Get the full name */
 	for (;;) {
-		c = get_next_char(cf);
-		if (cf->eof)
+		c = get_next_char(cs);
+		if (cs->eof)
 			break;
 		if (!iskeychar(c))
 			break;
@@ -936,13 +936,13 @@ static int get_value(struct config_source *cf, config_fn_t fn, void *data,
 	}
 
 	while (c == ' ' || c == '\t')
-		c = get_next_char(cf);
+		c = get_next_char(cs);
 
 	value = NULL;
 	if (c != '\n') {
 		if (c != '=')
 			return -1;
-		value = parse_value(cf);
+		value = parse_value(cs);
 		if (!value)
 			return -1;
 	}
@@ -951,21 +951,21 @@ static int get_value(struct config_source *cf, config_fn_t fn, void *data,
 	 * the line we just parsed during the call to fn to get
 	 * accurate line number in error messages.
 	 */
-	cf->linenr--;
+	cs->linenr--;
 	ret = fn(name->buf, value, data);
 	if (ret >= 0)
-		cf->linenr++;
+		cs->linenr++;
 	return ret;
 }
 
-static int get_extended_base_var(struct config_source *cf, struct strbuf *name,
+static int get_extended_base_var(struct config_source *cs, struct strbuf *name,
 				 int c)
 {
-	cf->subsection_case_sensitive = 0;
+	cs->subsection_case_sensitive = 0;
 	do {
 		if (c == '\n')
 			goto error_incomplete_line;
-		c = get_next_char(cf);
+		c = get_next_char(cs);
 	} while (isspace(c));
 
 	/* We require the format to be '[base "extension"]' */
@@ -974,13 +974,13 @@ static int get_extended_base_var(struct config_source *cf, struct strbuf *name,
 	strbuf_addch(name, '.');
 
 	for (;;) {
-		int c = get_next_char(cf);
+		int c = get_next_char(cs);
 		if (c == '\n')
 			goto error_incomplete_line;
 		if (c == '"')
 			break;
 		if (c == '\\') {
-			c = get_next_char(cf);
+			c = get_next_char(cs);
 			if (c == '\n')
 				goto error_incomplete_line;
 		}
@@ -988,25 +988,25 @@ static int get_extended_base_var(struct config_source *cf, struct strbuf *name,
 	}
 
 	/* Final ']' */
-	if (get_next_char(cf) != ']')
+	if (get_next_char(cs) != ']')
 		return -1;
 	return 0;
 error_incomplete_line:
-	cf->linenr--;
+	cs->linenr--;
 	return -1;
 }
 
-static int get_base_var(struct config_source *cf, struct strbuf *name)
+static int get_base_var(struct config_source *cs, struct strbuf *name)
 {
-	cf->subsection_case_sensitive = 1;
+	cs->subsection_case_sensitive = 1;
 	for (;;) {
-		int c = get_next_char(cf);
-		if (cf->eof)
+		int c = get_next_char(cs);
+		if (cs->eof)
 			return -1;
 		if (c == ']')
 			return 0;
 		if (isspace(c))
-			return get_extended_base_var(cf, name, c);
+			return get_extended_base_var(cs, name, c);
 		if (!iskeychar(c) && c != '.')
 			return -1;
 		strbuf_addch(name, tolower(c));
@@ -1019,7 +1019,7 @@ struct parse_event_data {
 	const struct config_options *opts;
 };
 
-static int do_event(struct config_source *cf, enum config_event_t type,
+static int do_event(struct config_source *cs, enum config_event_t type,
 		    struct parse_event_data *data)
 {
 	size_t offset;
@@ -1031,7 +1031,7 @@ static int do_event(struct config_source *cf, enum config_event_t type,
 	    data->previous_type == type)
 		return 0;
 
-	offset = cf->do_ftell(cf);
+	offset = cs->do_ftell(cs);
 	/*
 	 * At EOF, the parser always "inserts" an extra '\n', therefore
 	 * the end offset of the event is the current file position, otherwise
@@ -1051,12 +1051,12 @@ static int do_event(struct config_source *cf, enum config_event_t type,
 	return 0;
 }
 
-static int git_parse_source(struct config_source *cf, config_fn_t fn,
+static int git_parse_source(struct config_source *cs, config_fn_t fn,
 			    void *data, const struct config_options *opts)
 {
 	int comment = 0;
 	size_t baselen = 0;
-	struct strbuf *var = &cf->var;
+	struct strbuf *var = &cs->var;
 	int error_return = 0;
 	char *error_msg = NULL;
 
@@ -1071,7 +1071,7 @@ static int git_parse_source(struct config_source *cf, config_fn_t fn,
 	for (;;) {
 		int c;
 
-		c = get_next_char(cf);
+		c = get_next_char(cs);
 		if (bomptr && *bomptr) {
 			/* We are at the file beginning; skip UTF8-encoded BOM
 			 * if present. Sane editors won't put this in on their
@@ -1088,12 +1088,12 @@ static int git_parse_source(struct config_source *cf, config_fn_t fn,
 			}
 		}
 		if (c == '\n') {
-			if (cf->eof) {
-				if (do_event(cf, CONFIG_EVENT_EOF, &event_data) < 0)
+			if (cs->eof) {
+				if (do_event(cs, CONFIG_EVENT_EOF, &event_data) < 0)
 					return -1;
 				return 0;
 			}
-			if (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
+			if (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
 				return -1;
 			comment = 0;
 			continue;
@@ -1101,23 +1101,23 @@ static int git_parse_source(struct config_source *cf, config_fn_t fn,
 		if (comment)
 			continue;
 		if (isspace(c)) {
-			if (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
+			if (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
 					return -1;
 			continue;
 		}
 		if (c == '#' || c == ';') {
-			if (do_event(cf, CONFIG_EVENT_COMMENT, &event_data) < 0)
+			if (do_event(cs, CONFIG_EVENT_COMMENT, &event_data) < 0)
 					return -1;
 			comment = 1;
 			continue;
 		}
 		if (c == '[') {
-			if (do_event(cf, CONFIG_EVENT_SECTION, &event_data) < 0)
+			if (do_event(cs, CONFIG_EVENT_SECTION, &event_data) < 0)
 					return -1;
 
 			/* Reset prior to determining a new stem */
 			strbuf_reset(var);
-			if (get_base_var(cf, var) < 0 || var->len < 1)
+			if (get_base_var(cs, var) < 0 || var->len < 1)
 				break;
 			strbuf_addch(var, '.');
 			baselen = var->len;
@@ -1126,7 +1126,7 @@ static int git_parse_source(struct config_source *cf, config_fn_t fn,
 		if (!isalpha(c))
 			break;
 
-		if (do_event(cf, CONFIG_EVENT_ENTRY, &event_data) < 0)
+		if (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)
 			return -1;
 
 		/*
@@ -1136,42 +1136,42 @@ static int git_parse_source(struct config_source *cf, config_fn_t fn,
 		 */
 		strbuf_setlen(var, baselen);
 		strbuf_addch(var, tolower(c));
-		if (get_value(cf, fn, data, var) < 0)
+		if (get_value(cs, fn, data, var) < 0)
 			break;
 	}
 
-	if (do_event(cf, CONFIG_EVENT_ERROR, &event_data) < 0)
+	if (do_event(cs, CONFIG_EVENT_ERROR, &event_data) < 0)
 		return -1;
 
-	switch (cf->origin_type) {
+	switch (cs->origin_type) {
 	case CONFIG_ORIGIN_BLOB:
 		error_msg = xstrfmt(_("bad config line %d in blob %s"),
-				      cf->linenr, cf->name);
+				      cs->linenr, cs->name);
 		break;
 	case CONFIG_ORIGIN_FILE:
 		error_msg = xstrfmt(_("bad config line %d in file %s"),
-				      cf->linenr, cf->name);
+				      cs->linenr, cs->name);
 		break;
 	case CONFIG_ORIGIN_STDIN:
 		error_msg = xstrfmt(_("bad config line %d in standard input"),
-				      cf->linenr);
+				      cs->linenr);
 		break;
 	case CONFIG_ORIGIN_SUBMODULE_BLOB:
 		error_msg = xstrfmt(_("bad config line %d in submodule-blob %s"),
-				       cf->linenr, cf->name);
+				       cs->linenr, cs->name);
 		break;
 	case CONFIG_ORIGIN_CMDLINE:
 		error_msg = xstrfmt(_("bad config line %d in command line %s"),
-				       cf->linenr, cf->name);
+				       cs->linenr, cs->name);
 		break;
 	default:
 		error_msg = xstrfmt(_("bad config line %d in %s"),
-				      cf->linenr, cf->name);
+				      cs->linenr, cs->name);
 	}
 
 	switch (opts && opts->error_action ?
 		opts->error_action :
-		cf->default_error_action) {
+		cs->default_error_action) {
 	case CONFIG_ERROR_DIE:
 		die("%s", error_msg);
 		break;
@@ -2281,13 +2281,13 @@ int config_with_options(config_fn_t fn, void *data,
 	return ret;
 }
 
-static void configset_iter(struct config_reader *reader, struct config_set *cs,
+static void configset_iter(struct config_reader *reader, struct config_set *set,
 			   config_fn_t fn, void *data)
 {
 	int i, value_index;
 	struct string_list *values;
 	struct config_set_element *entry;
-	struct configset_list *list = &cs->list;
+	struct configset_list *list = &set->list;
 
 	for (i = 0; i < list->nr; i++) {
 		entry = list->items[i].e;
@@ -2352,7 +2352,7 @@ void read_very_early_config(config_fn_t cb, void *data)
 	config_with_options(cb, data, NULL, &opts);
 }
 
-static struct config_set_element *configset_find_element(struct config_set *cs, const char *key)
+static struct config_set_element *configset_find_element(struct config_set *set, const char *key)
 {
 	struct config_set_element k;
 	struct config_set_element *found_entry;
@@ -2366,13 +2366,13 @@ static struct config_set_element *configset_find_element(struct config_set *cs,
 
 	hashmap_entry_init(&k.ent, strhash(normalized_key));
 	k.key = normalized_key;
-	found_entry = hashmap_get_entry(&cs->config_hash, &k, ent, NULL);
+	found_entry = hashmap_get_entry(&set->config_hash, &k, ent, NULL);
 	free(normalized_key);
 	return found_entry;
 }
 
 static int configset_add_value(struct config_reader *reader,
-			       struct config_set *cs, const char *key,
+			       struct config_set *set, const char *key,
 			       const char *value)
 {
 	struct config_set_element *e;
@@ -2380,7 +2380,7 @@ static int configset_add_value(struct config_reader *reader,
 	struct configset_list_item *l_item;
 	struct key_value_info *kv_info = xmalloc(sizeof(*kv_info));
 
-	e = configset_find_element(cs, key);
+	e = configset_find_element(set, key);
 	/*
 	 * Since the keys are being fed by git_config*() callback mechanism, they
 	 * are already normalized. So simply add them without any further munging.
@@ -2390,12 +2390,12 @@ static int configset_add_value(struct config_reader *reader,
 		hashmap_entry_init(&e->ent, strhash(key));
 		e->key = xstrdup(key);
 		string_list_init_dup(&e->value_list);
-		hashmap_add(&cs->config_hash, &e->ent);
+		hashmap_add(&set->config_hash, &e->ent);
 	}
 	si = string_list_append_nodup(&e->value_list, xstrdup_or_null(value));
 
-	ALLOC_GROW(cs->list.items, cs->list.nr + 1, cs->list.alloc);
-	l_item = &cs->list.items[cs->list.nr++];
+	ALLOC_GROW(set->list.items, set->list.nr + 1, set->list.alloc);
+	l_item = &set->list.items[set->list.nr++];
 	l_item->e = e;
 	l_item->value_index = e->value_list.nr - 1;
 
@@ -2430,33 +2430,33 @@ static int config_set_element_cmp(const void *cmp_data UNUSED,
 	return strcmp(e1->key, e2->key);
 }
 
-void git_configset_init(struct config_set *cs)
+void git_configset_init(struct config_set *set)
 {
-	hashmap_init(&cs->config_hash, config_set_element_cmp, NULL, 0);
-	cs->hash_initialized = 1;
-	cs->list.nr = 0;
-	cs->list.alloc = 0;
-	cs->list.items = NULL;
+	hashmap_init(&set->config_hash, config_set_element_cmp, NULL, 0);
+	set->hash_initialized = 1;
+	set->list.nr = 0;
+	set->list.alloc = 0;
+	set->list.items = NULL;
 }
 
-void git_configset_clear(struct config_set *cs)
+void git_configset_clear(struct config_set *set)
 {
 	struct config_set_element *entry;
 	struct hashmap_iter iter;
-	if (!cs->hash_initialized)
+	if (!set->hash_initialized)
 		return;
 
-	hashmap_for_each_entry(&cs->config_hash, &iter, entry,
+	hashmap_for_each_entry(&set->config_hash, &iter, entry,
 				ent /* member name */) {
 		free(entry->key);
 		string_list_clear(&entry->value_list, 1);
 	}
-	hashmap_clear_and_free(&cs->config_hash, struct config_set_element, ent);
-	cs->hash_initialized = 0;
-	free(cs->list.items);
-	cs->list.nr = 0;
-	cs->list.alloc = 0;
-	cs->list.items = NULL;
+	hashmap_clear_and_free(&set->config_hash, struct config_set_element, ent);
+	set->hash_initialized = 0;
+	free(set->list.items);
+	set->list.nr = 0;
+	set->list.alloc = 0;
+	set->list.items = NULL;
 }
 
 struct configset_add_data {
@@ -2472,15 +2472,15 @@ static int config_set_callback(const char *key, const char *value, void *cb)
 	return 0;
 }
 
-int git_configset_add_file(struct config_set *cs, const char *filename)
+int git_configset_add_file(struct config_set *set, const char *filename)
 {
 	struct configset_add_data data = CONFIGSET_ADD_INIT;
 	data.config_reader = &the_reader;
-	data.config_set = cs;
+	data.config_set = set;
 	return git_config_from_file(config_set_callback, filename, &data);
 }
 
-int git_configset_get_value(struct config_set *cs, const char *key, const char **value)
+int git_configset_get_value(struct config_set *set, const char *key, const char **value)
 {
 	const struct string_list *values = NULL;
 	/*
@@ -2488,7 +2488,7 @@ int git_configset_get_value(struct config_set *cs, const char *key, const char *
 	 * queried key in the files of the configset, the value returned will be the last
 	 * value in the value list for that key.
 	 */
-	values = git_configset_get_value_multi(cs, key);
+	values = git_configset_get_value_multi(set, key);
 
 	if (!values)
 		return 1;
@@ -2497,26 +2497,26 @@ int git_configset_get_value(struct config_set *cs, const char *key, const char *
 	return 0;
 }
 
-const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key)
+const struct string_list *git_configset_get_value_multi(struct config_set *set, const char *key)
 {
-	struct config_set_element *e = configset_find_element(cs, key);
+	struct config_set_element *e = configset_find_element(set, key);
 	return e ? &e->value_list : NULL;
 }
 
-int git_configset_get_string(struct config_set *cs, const char *key, char **dest)
+int git_configset_get_string(struct config_set *set, const char *key, char **dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value))
+	if (!git_configset_get_value(set, key, &value))
 		return git_config_string((const char **)dest, key, value);
 	else
 		return 1;
 }
 
-static int git_configset_get_string_tmp(struct config_set *cs, const char *key,
+static int git_configset_get_string_tmp(struct config_set *set, const char *key,
 					const char **dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value)) {
+	if (!git_configset_get_value(set, key, &value)) {
 		if (!value)
 			return config_error_nonbool(key);
 		*dest = value;
@@ -2526,51 +2526,51 @@ static int git_configset_get_string_tmp(struct config_set *cs, const char *key,
 	}
 }
 
-int git_configset_get_int(struct config_set *cs, const char *key, int *dest)
+int git_configset_get_int(struct config_set *set, const char *key, int *dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value)) {
+	if (!git_configset_get_value(set, key, &value)) {
 		*dest = git_config_int(key, value);
 		return 0;
 	} else
 		return 1;
 }
 
-int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest)
+int git_configset_get_ulong(struct config_set *set, const char *key, unsigned long *dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value)) {
+	if (!git_configset_get_value(set, key, &value)) {
 		*dest = git_config_ulong(key, value);
 		return 0;
 	} else
 		return 1;
 }
 
-int git_configset_get_bool(struct config_set *cs, const char *key, int *dest)
+int git_configset_get_bool(struct config_set *set, const char *key, int *dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value)) {
+	if (!git_configset_get_value(set, key, &value)) {
 		*dest = git_config_bool(key, value);
 		return 0;
 	} else
 		return 1;
 }
 
-int git_configset_get_bool_or_int(struct config_set *cs, const char *key,
+int git_configset_get_bool_or_int(struct config_set *set, const char *key,
 				int *is_bool, int *dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value)) {
+	if (!git_configset_get_value(set, key, &value)) {
 		*dest = git_config_bool_or_int(key, value, is_bool);
 		return 0;
 	} else
 		return 1;
 }
 
-int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest)
+int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value)) {
+	if (!git_configset_get_value(set, key, &value)) {
 		*dest = git_parse_maybe_bool(value);
 		if (*dest == -1)
 			return -1;
@@ -2579,10 +2579,10 @@ int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *de
 		return 1;
 }
 
-int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest)
+int git_configset_get_pathname(struct config_set *set, const char *key, const char **dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value))
+	if (!git_configset_get_value(set, key, &value))
 		return git_config_pathname(dest, key, value);
 	else
 		return 1;
@@ -2973,7 +2973,7 @@ static int store_aux_event(enum config_event_t type,
 			   size_t begin, size_t end, void *data)
 {
 	struct config_store_data *store = data;
-	struct config_source *cf = store->config_reader->source;
+	struct config_source *cs = store->config_reader->source;
 
 	ALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc);
 	store->parsed[store->parsed_nr].begin = begin;
@@ -2983,10 +2983,10 @@ static int store_aux_event(enum config_event_t type,
 	if (type == CONFIG_EVENT_SECTION) {
 		int (*cmpfn)(const char *, const char *, size_t);
 
-		if (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')
-			return error(_("invalid section name '%s'"), cf->var.buf);
+		if (cs->var.len < 2 || cs->var.buf[cs->var.len - 1] != '.')
+			return error(_("invalid section name '%s'"), cs->var.buf);
 
-		if (cf->subsection_case_sensitive)
+		if (cs->subsection_case_sensitive)
 			cmpfn = strncasecmp;
 		else
 			cmpfn = strncmp;
@@ -2994,8 +2994,8 @@ static int store_aux_event(enum config_event_t type,
 		/* Is this the section we were looking for? */
 		store->is_keys_section =
 			store->parsed[store->parsed_nr].is_keys_section =
-			cf->var.len - 1 == store->baselen &&
-			!cmpfn(cf->var.buf, store->key, store->baselen);
+			cs->var.len - 1 == store->baselen &&
+			!cmpfn(cs->var.buf, store->key, store->baselen);
 		if (store->is_keys_section) {
 			store->section_seen = 1;
 			ALLOC_GROW(store->seen, store->seen_nr + 1,
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* Re: [PATCH v2 0/8] config.c: use struct for config reading state
  2023-03-16  0:11 ` [PATCH v2 0/8] " Glen Choo via GitGitGadget
                     ` (7 preceding siblings ...)
  2023-03-16  0:11   ` [PATCH v2 8/8] config.c: rename "struct config_source cf" Glen Choo via GitGitGadget
@ 2023-03-16  0:15   ` Glen Choo
  2023-03-16 22:29   ` Jonathan Tan
                     ` (2 subsequent siblings)
  11 siblings, 0 replies; 72+ messages in thread
From: Glen Choo @ 2023-03-16  0:15 UTC (permalink / raw)
  To: Glen Choo via GitGitGadget, git
  Cc: Jonathan Tan, Emily Shaffer, Jeff King, Derrick Stolee,
	Calvin Wan, Ævar Arnfjörð Bjarmason

"Glen Choo via GitGitGadget" <gitgitgadget@gmail.com> writes:

> As a result of moving the rename, the range-diff is quite noisy. The diff
> between the final commits is might be helpful instead [2] (I'll also send a
> diff to the ML).

diff --git a/config.c b/config.c
index 19bab84c47..ae171ab11c 100644
--- a/config.c
+++ b/config.c
@@ -81,14 +81,18 @@ struct config_reader {
 	 */
 	enum config_scope parsing_scope;
 };
-/* Only public functions should reference the_reader. */
+/*
+ * Where possible, prefer to accept "struct config_reader" as an arg than to use
+ * "the_reader". "the_reader" should only be used if that is infeasible, e.g. in
+ * a public function.
+ */
 static struct config_reader the_reader;
 
 static inline void config_reader_push_source(struct config_reader *reader,
 					     struct config_source *top)
 {
 	if (reader->config_kvi)
-		BUG("source should only be set when parsing a config source");
+		BUG("source should not be set while iterating a config set");
 	if (reader->source)
 		top->prev = reader->source;
 	reader->source = top;
@@ -108,7 +112,7 @@ static inline void config_reader_set_kvi(struct config_reader *reader,
 					 struct key_value_info *kvi)
 {
 	if (kvi && (reader->source || reader->parsing_scope))
-		BUG("kvi should only be set when iterating through configset");
+		BUG("kvi should not be set while parsing a config source");
 	reader->config_kvi = kvi;
 }
 
@@ -1308,39 +1312,48 @@ int git_parse_ssize_t(const char *value, ssize_t *ret)
 	return 1;
 }
 
+static int reader_config_name(struct config_reader *reader, const char **out);
+static int reader_origin_type(struct config_reader *reader,
+			      enum config_origin_type *type);
 NORETURN
-static void die_bad_number(struct config_source *cs, const char *name,
+static void die_bad_number(struct config_reader *reader, const char *name,
 			   const char *value)
 {
 	const char *error_type = (errno == ERANGE) ?
 		N_("out of range") : N_("invalid unit");
 	const char *bad_numeric = N_("bad numeric config value '%s' for '%s': %s");
+	const char *config_name = NULL;
+	enum config_origin_type config_origin = CONFIG_ORIGIN_UNKNOWN;
 
 	if (!value)
 		value = "";
 
-	if (!(cs && cs->name))
+	/* Ignoring the return value is okay since we handle missing values. */
+	reader_config_name(reader, &config_name);
+	reader_origin_type(reader, &config_origin);
+
+	if (!config_name)
 		die(_(bad_numeric), value, name, _(error_type));
 
-	switch (cs->origin_type) {
+	switch (config_origin) {
 	case CONFIG_ORIGIN_BLOB:
 		die(_("bad numeric config value '%s' for '%s' in blob %s: %s"),
-		    value, name, cs->name, _(error_type));
+		    value, name, config_name, _(error_type));
 	case CONFIG_ORIGIN_FILE:
 		die(_("bad numeric config value '%s' for '%s' in file %s: %s"),
-		    value, name, cs->name, _(error_type));
+		    value, name, config_name, _(error_type));
 	case CONFIG_ORIGIN_STDIN:
 		die(_("bad numeric config value '%s' for '%s' in standard input: %s"),
 		    value, name, _(error_type));
 	case CONFIG_ORIGIN_SUBMODULE_BLOB:
 		die(_("bad numeric config value '%s' for '%s' in submodule-blob %s: %s"),
-		    value, name, cs->name, _(error_type));
+		    value, name, config_name, _(error_type));
 	case CONFIG_ORIGIN_CMDLINE:
 		die(_("bad numeric config value '%s' for '%s' in command line %s: %s"),
-		    value, name, cs->name, _(error_type));
+		    value, name, config_name, _(error_type));
 	default:
 		die(_("bad numeric config value '%s' for '%s' in %s: %s"),
-		    value, name, cs->name, _(error_type));
+		    value, name, config_name, _(error_type));
 	}
 }
 
@@ -1348,7 +1361,7 @@ int git_config_int(const char *name, const char *value)
 {
 	int ret;
 	if (!git_parse_int(value, &ret))
-		die_bad_number(the_reader.source, name, value);
+		die_bad_number(&the_reader, name, value);
 	return ret;
 }
 
@@ -1356,7 +1369,7 @@ int64_t git_config_int64(const char *name, const char *value)
 {
 	int64_t ret;
 	if (!git_parse_int64(value, &ret))
-		die_bad_number(the_reader.source, name, value);
+		die_bad_number(&the_reader, name, value);
 	return ret;
 }
 
@@ -1364,7 +1377,7 @@ unsigned long git_config_ulong(const char *name, const char *value)
 {
 	unsigned long ret;
 	if (!git_parse_ulong(value, &ret))
-		die_bad_number(the_reader.source, name, value);
+		die_bad_number(&the_reader, name, value);
 	return ret;
 }
 
@@ -1372,7 +1385,7 @@ ssize_t git_config_ssize_t(const char *name, const char *value)
 {
 	ssize_t ret;
 	if (!git_parse_ssize_t(value, &ret))
-		die_bad_number(the_reader.source, name, value);
+		die_bad_number(&the_reader, name, value);
 	return ret;
 }
 
@@ -2268,34 +2281,27 @@ int config_with_options(config_fn_t fn, void *data,
 	return ret;
 }
 
-struct configset_iter_data {
-	struct config_reader *config_reader;
-	void *inner;
-};
-#define CONFIGSET_ITER_INIT { 0 }
-
-static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
+static void configset_iter(struct config_reader *reader, struct config_set *set,
+			   config_fn_t fn, void *data)
 {
 	int i, value_index;
 	struct string_list *values;
 	struct config_set_element *entry;
-	struct configset_list *list = &cs->list;
-	struct configset_iter_data *iter_data = data;
+	struct configset_list *list = &set->list;
 
 	for (i = 0; i < list->nr; i++) {
-		struct key_value_info *kvi;
 		entry = list->items[i].e;
 		value_index = list->items[i].value_index;
 		values = &entry->value_list;
 
-		kvi = values->items[value_index].util;
-		config_reader_set_kvi(iter_data->config_reader, kvi);
+		config_reader_set_kvi(reader, values->items[value_index].util);
 
-		if (fn(entry->key, values->items[value_index].string, iter_data->inner) < 0)
-			git_die_config_linenr(entry->key, kvi->filename,
-					      kvi->linenr);
+		if (fn(entry->key, values->items[value_index].string, data) < 0)
+			git_die_config_linenr(entry->key,
+					      reader->config_kvi->filename,
+					      reader->config_kvi->linenr);
 
-		config_reader_set_kvi(iter_data->config_reader, NULL);
+		config_reader_set_kvi(reader, NULL);
 	}
 }
 
@@ -2346,7 +2352,7 @@ void read_very_early_config(config_fn_t cb, void *data)
 	config_with_options(cb, data, NULL, &opts);
 }
 
-static struct config_set_element *configset_find_element(struct config_set *cs, const char *key)
+static struct config_set_element *configset_find_element(struct config_set *set, const char *key)
 {
 	struct config_set_element k;
 	struct config_set_element *found_entry;
@@ -2360,13 +2366,13 @@ static struct config_set_element *configset_find_element(struct config_set *cs,
 
 	hashmap_entry_init(&k.ent, strhash(normalized_key));
 	k.key = normalized_key;
-	found_entry = hashmap_get_entry(&cs->config_hash, &k, ent, NULL);
+	found_entry = hashmap_get_entry(&set->config_hash, &k, ent, NULL);
 	free(normalized_key);
 	return found_entry;
 }
 
 static int configset_add_value(struct config_reader *reader,
-			       struct config_set *cs, const char *key,
+			       struct config_set *set, const char *key,
 			       const char *value)
 {
 	struct config_set_element *e;
@@ -2374,7 +2380,7 @@ static int configset_add_value(struct config_reader *reader,
 	struct configset_list_item *l_item;
 	struct key_value_info *kv_info = xmalloc(sizeof(*kv_info));
 
-	e = configset_find_element(cs, key);
+	e = configset_find_element(set, key);
 	/*
 	 * Since the keys are being fed by git_config*() callback mechanism, they
 	 * are already normalized. So simply add them without any further munging.
@@ -2384,12 +2390,12 @@ static int configset_add_value(struct config_reader *reader,
 		hashmap_entry_init(&e->ent, strhash(key));
 		e->key = xstrdup(key);
 		string_list_init_dup(&e->value_list);
-		hashmap_add(&cs->config_hash, &e->ent);
+		hashmap_add(&set->config_hash, &e->ent);
 	}
 	si = string_list_append_nodup(&e->value_list, xstrdup_or_null(value));
 
-	ALLOC_GROW(cs->list.items, cs->list.nr + 1, cs->list.alloc);
-	l_item = &cs->list.items[cs->list.nr++];
+	ALLOC_GROW(set->list.items, set->list.nr + 1, set->list.alloc);
+	l_item = &set->list.items[set->list.nr++];
 	l_item->e = e;
 	l_item->value_index = e->value_list.nr - 1;
 
@@ -2424,33 +2430,33 @@ static int config_set_element_cmp(const void *cmp_data UNUSED,
 	return strcmp(e1->key, e2->key);
 }
 
-void git_configset_init(struct config_set *cs)
+void git_configset_init(struct config_set *set)
 {
-	hashmap_init(&cs->config_hash, config_set_element_cmp, NULL, 0);
-	cs->hash_initialized = 1;
-	cs->list.nr = 0;
-	cs->list.alloc = 0;
-	cs->list.items = NULL;
+	hashmap_init(&set->config_hash, config_set_element_cmp, NULL, 0);
+	set->hash_initialized = 1;
+	set->list.nr = 0;
+	set->list.alloc = 0;
+	set->list.items = NULL;
 }
 
-void git_configset_clear(struct config_set *cs)
+void git_configset_clear(struct config_set *set)
 {
 	struct config_set_element *entry;
 	struct hashmap_iter iter;
-	if (!cs->hash_initialized)
+	if (!set->hash_initialized)
 		return;
 
-	hashmap_for_each_entry(&cs->config_hash, &iter, entry,
+	hashmap_for_each_entry(&set->config_hash, &iter, entry,
 				ent /* member name */) {
 		free(entry->key);
 		string_list_clear(&entry->value_list, 1);
 	}
-	hashmap_clear_and_free(&cs->config_hash, struct config_set_element, ent);
-	cs->hash_initialized = 0;
-	free(cs->list.items);
-	cs->list.nr = 0;
-	cs->list.alloc = 0;
-	cs->list.items = NULL;
+	hashmap_clear_and_free(&set->config_hash, struct config_set_element, ent);
+	set->hash_initialized = 0;
+	free(set->list.items);
+	set->list.nr = 0;
+	set->list.alloc = 0;
+	set->list.items = NULL;
 }
 
 struct configset_add_data {
@@ -2466,15 +2472,15 @@ static int config_set_callback(const char *key, const char *value, void *cb)
 	return 0;
 }
 
-int git_configset_add_file(struct config_set *cs, const char *filename)
+int git_configset_add_file(struct config_set *set, const char *filename)
 {
 	struct configset_add_data data = CONFIGSET_ADD_INIT;
 	data.config_reader = &the_reader;
-	data.config_set = cs;
+	data.config_set = set;
 	return git_config_from_file(config_set_callback, filename, &data);
 }
 
-int git_configset_get_value(struct config_set *cs, const char *key, const char **value)
+int git_configset_get_value(struct config_set *set, const char *key, const char **value)
 {
 	const struct string_list *values = NULL;
 	/*
@@ -2482,7 +2488,7 @@ int git_configset_get_value(struct config_set *cs, const char *key, const char *
 	 * queried key in the files of the configset, the value returned will be the last
 	 * value in the value list for that key.
 	 */
-	values = git_configset_get_value_multi(cs, key);
+	values = git_configset_get_value_multi(set, key);
 
 	if (!values)
 		return 1;
@@ -2491,26 +2497,26 @@ int git_configset_get_value(struct config_set *cs, const char *key, const char *
 	return 0;
 }
 
-const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key)
+const struct string_list *git_configset_get_value_multi(struct config_set *set, const char *key)
 {
-	struct config_set_element *e = configset_find_element(cs, key);
+	struct config_set_element *e = configset_find_element(set, key);
 	return e ? &e->value_list : NULL;
 }
 
-int git_configset_get_string(struct config_set *cs, const char *key, char **dest)
+int git_configset_get_string(struct config_set *set, const char *key, char **dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value))
+	if (!git_configset_get_value(set, key, &value))
 		return git_config_string((const char **)dest, key, value);
 	else
 		return 1;
 }
 
-static int git_configset_get_string_tmp(struct config_set *cs, const char *key,
+static int git_configset_get_string_tmp(struct config_set *set, const char *key,
 					const char **dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value)) {
+	if (!git_configset_get_value(set, key, &value)) {
 		if (!value)
 			return config_error_nonbool(key);
 		*dest = value;
@@ -2520,51 +2526,51 @@ static int git_configset_get_string_tmp(struct config_set *cs, const char *key,
 	}
 }
 
-int git_configset_get_int(struct config_set *cs, const char *key, int *dest)
+int git_configset_get_int(struct config_set *set, const char *key, int *dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value)) {
+	if (!git_configset_get_value(set, key, &value)) {
 		*dest = git_config_int(key, value);
 		return 0;
 	} else
 		return 1;
 }
 
-int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest)
+int git_configset_get_ulong(struct config_set *set, const char *key, unsigned long *dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value)) {
+	if (!git_configset_get_value(set, key, &value)) {
 		*dest = git_config_ulong(key, value);
 		return 0;
 	} else
 		return 1;
 }
 
-int git_configset_get_bool(struct config_set *cs, const char *key, int *dest)
+int git_configset_get_bool(struct config_set *set, const char *key, int *dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value)) {
+	if (!git_configset_get_value(set, key, &value)) {
 		*dest = git_config_bool(key, value);
 		return 0;
 	} else
 		return 1;
 }
 
-int git_configset_get_bool_or_int(struct config_set *cs, const char *key,
+int git_configset_get_bool_or_int(struct config_set *set, const char *key,
 				int *is_bool, int *dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value)) {
+	if (!git_configset_get_value(set, key, &value)) {
 		*dest = git_config_bool_or_int(key, value, is_bool);
 		return 0;
 	} else
 		return 1;
 }
 
-int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest)
+int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value)) {
+	if (!git_configset_get_value(set, key, &value)) {
 		*dest = git_parse_maybe_bool(value);
 		if (*dest == -1)
 			return -1;
@@ -2573,10 +2579,10 @@ int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *de
 		return 1;
 }
 
-int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest)
+int git_configset_get_pathname(struct config_set *set, const char *key, const char **dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value))
+	if (!git_configset_get_value(set, key, &value))
 		return git_config_pathname(dest, key, value);
 	else
 		return 1;
@@ -2630,14 +2636,10 @@ static void repo_config_clear(struct repository *repo)
 	git_configset_clear(repo->config);
 }
 
-void repo_config(struct repository *repo, config_fn_t fn, void *data_inner)
+void repo_config(struct repository *repo, config_fn_t fn, void *data)
 {
-	struct configset_iter_data data = CONFIGSET_ITER_INIT;
-	data.inner = data_inner;
-	data.config_reader = &the_reader;
-
 	git_config_check_init(repo);
-	configset_iter(repo->config, fn, &data);
+	configset_iter(&the_reader, repo->config, fn, data);
 }
 
 int repo_config_get_value(struct repository *repo,
@@ -2739,15 +2741,11 @@ static void read_protected_config(void)
 	config_with_options(config_set_callback, &data, NULL, &opts);
 }
 
-void git_protected_config(config_fn_t fn, void *data_inner)
+void git_protected_config(config_fn_t fn, void *data)
 {
-	struct configset_iter_data data = CONFIGSET_ITER_INIT;
 	if (!protected_config.hash_initialized)
 		read_protected_config();
-	data.inner = data_inner;
-	data.config_reader = &the_reader;
-
-	configset_iter(&protected_config, fn, &data);
+	configset_iter(&the_reader, &protected_config, fn, data);
 }
 
 /* Functions used historically to read configuration from 'the_repository' */
@@ -3851,14 +3849,23 @@ int parse_config_key(const char *var,
 	return 0;
 }
 
-const char *current_config_origin_type(void)
+static int reader_origin_type(struct config_reader *reader,
+			      enum config_origin_type *type)
 {
-	int type;
 	if (the_reader.config_kvi)
-		type = the_reader.config_kvi->origin_type;
+		*type = reader->config_kvi->origin_type;
 	else if(the_reader.source)
-		type = the_reader.source->origin_type;
+		*type = reader->source->origin_type;
 	else
+		return 1;
+	return 0;
+}
+
+const char *current_config_origin_type(void)
+{
+	enum config_origin_type type = CONFIG_ORIGIN_UNKNOWN;
+
+	if (reader_origin_type(&the_reader, &type))
 		BUG("current_config_origin_type called outside config callback");
 
 	switch (type) {
@@ -3897,14 +3904,21 @@ const char *config_scope_name(enum config_scope scope)
 	}
 }
 
-const char *current_config_name(void)
+static int reader_config_name(struct config_reader *reader, const char **out)
 {
-	const char *name;
 	if (the_reader.config_kvi)
-		name = the_reader.config_kvi->filename;
+		*out = reader->config_kvi->filename;
 	else if (the_reader.source)
-		name = the_reader.source->name;
+		*out = reader->source->name;
 	else
+		return 1;
+	return 0;
+}
+
+const char *current_config_name(void)
+{
+	const char *name;
+	if (reader_config_name(&the_reader, &name))
 		BUG("current_config_name called outside config callback");
 	return name ? name : "";
 }
diff --git a/config.h b/config.h
index 7606246531..66c8b996e1 100644
--- a/config.h
+++ b/config.h
@@ -56,6 +56,7 @@ struct git_config_source {
 };
 
 enum config_origin_type {
+	CONFIG_ORIGIN_UNKNOWN = 0,
 	CONFIG_ORIGIN_BLOB,
 	CONFIG_ORIGIN_FILE,
 	CONFIG_ORIGIN_STDIN,
diff --git a/t/helper/test-config.c b/t/helper/test-config.c
index 4ba9eb6560..26e79168f6 100644
--- a/t/helper/test-config.c
+++ b/t/helper/test-config.c
@@ -30,6 +30,9 @@
  * iterate -> iterate over all values using git_config(), and print some
  *            data for each
  *
+ * git_config_int -> iterate over all values using git_config() and print the
+ *                   integer value for the entered key or die
+ *
  * Examples:
  *
  * To print the value with highest priority for key "foo.bAr Baz.rock":
@@ -54,6 +57,17 @@ static int iterate_cb(const char *var, const char *value, void *data UNUSED)
 	return 0;
 }
 
+static int parse_int_cb(const char *var, const char *value, void *data)
+{
+	const char *key_to_match = data;
+
+	if (!strcmp(key_to_match, var)) {
+		int parsed = git_config_int(value, value);
+		printf("%d\n", parsed);
+	}
+	return 0;
+}
+
 static int early_config_cb(const char *var, const char *value, void *vdata)
 {
 	const char *key = vdata;
@@ -176,6 +190,9 @@ int cmd__config(int argc, const char **argv)
 	} else if (!strcmp(argv[1], "iterate")) {
 		git_config(iterate_cb, NULL);
 		goto exit0;
+	} else if (argc == 3 && !strcmp(argv[1], "git_config_int")) {
+		git_config(parse_int_cb, (void *) argv[2]);
+		goto exit0;
 	}
 
 	die("%s: Please check the syntax and the function name", argv[0]);
diff --git a/t/t1308-config-set.sh b/t/t1308-config-set.sh
index b38e158d3b..9733bed30a 100755
--- a/t/t1308-config-set.sh
+++ b/t/t1308-config-set.sh
@@ -120,6 +120,10 @@ test_expect_success 'find integer value for a key' '
 	check_config get_int lamb.chop 65
 '
 
+test_expect_success 'parse integer value during iteration' '
+	check_config git_config_int lamb.chop 65
+'
+
 test_expect_success 'find string value for a key' '
 	check_config get_string case.baz hask &&
 	check_config expect_code 1 get_string case.ba "Value not found for \"case.ba\""
@@ -134,6 +138,11 @@ test_expect_success 'find integer if value is non parse-able' '
 	check_config expect_code 128 get_int lamb.head
 '
 
+test_expect_success 'non parse-able integer value during iteration' '
+	check_config expect_code 128 git_config_int lamb.head 2>result &&
+	grep "fatal: bad numeric config value .* in file \.git/config" result
+'
+
 test_expect_success 'find bool value for the entered key' '
 	check_config get_bool goat.head 1 &&
 	check_config get_bool goat.skin 0 &&

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* Re: [PATCH v2 1/8] config.c: plumb config_source through static fns
  2023-03-16  0:11   ` [PATCH v2 1/8] config.c: plumb config_source through static fns Glen Choo via GitGitGadget
@ 2023-03-16 21:16     ` Jonathan Tan
  0 siblings, 0 replies; 72+ messages in thread
From: Jonathan Tan @ 2023-03-16 21:16 UTC (permalink / raw)
  To: Glen Choo via GitGitGadget
  Cc: Jonathan Tan, git, Emily Shaffer, Jeff King, Derrick Stolee,
	Calvin Wan, Ævar Arnfjörð Bjarmason, Glen Choo

"Glen Choo via GitGitGadget" <gitgitgadget@gmail.com> writes:
> but we will rename "cf" to "cs" by the end of the
> series.

Let's remember that this might not happen if we decide to drop the last
patch. Other than that, everything looks good in this patch.
 

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH v2 2/8] config.c: don't assign to "cf_global" directly
  2023-03-16  0:11   ` [PATCH v2 2/8] config.c: don't assign to "cf_global" directly Glen Choo via GitGitGadget
@ 2023-03-16 21:18     ` Jonathan Tan
  2023-03-16 21:31       ` Junio C Hamano
  2023-03-16 22:56       ` Glen Choo
  0 siblings, 2 replies; 72+ messages in thread
From: Jonathan Tan @ 2023-03-16 21:18 UTC (permalink / raw)
  To: Glen Choo via GitGitGadget
  Cc: Jonathan Tan, git, Emily Shaffer, Jeff King, Derrick Stolee,
	Calvin Wan, Ævar Arnfjörð Bjarmason, Glen Choo

"Glen Choo via GitGitGadget" <gitgitgadget@gmail.com> writes:
> +static inline void config_reader_push_source(struct config_source *top)
> +{
> +	if (cf_global)
> +		top->prev = cf_global;

Don't we want to set prev unconditionally here (i.e. set it to NULL if
cf_global was NULL)?

> +	cf_global = top;
> +}
> +
> +static inline struct config_source *config_reader_pop_source()
> +{
> +	struct config_source *ret;
> +	if (!cf_global)
> +		BUG("tried to pop config source, but we weren't reading config");
> +	ret = cf_global;
> +	cf_global = cf_global->prev;
> +	return ret;
> +}

...since we use it unconditionally here.

The rest looks good.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH v2 3/8] config.c: create config_reader and the_reader
  2023-03-16  0:11   ` [PATCH v2 3/8] config.c: create config_reader and the_reader Glen Choo via GitGitGadget
@ 2023-03-16 21:22     ` Jonathan Tan
  0 siblings, 0 replies; 72+ messages in thread
From: Jonathan Tan @ 2023-03-16 21:22 UTC (permalink / raw)
  To: Glen Choo via GitGitGadget
  Cc: Jonathan Tan, git, Emily Shaffer, Jeff King, Derrick Stolee,
	Calvin Wan, Ævar Arnfjörð Bjarmason, Glen Choo

"Glen Choo via GitGitGadget" <gitgitgadget@gmail.com> writes:
> -static inline struct config_source *config_reader_pop_source()
> +static inline struct config_source *config_reader_pop_source(struct config_reader *reader)
>  {
>  	struct config_source *ret;
> -	if (!cf_global)
> +	if (!reader->source)
>  		BUG("tried to pop config source, but we weren't reading config");
> -	ret = cf_global;
> -	cf_global = cf_global->prev;
> +	ret = reader->source;
> +	reader->source = reader->source->prev;
> +	/* FIXME remove this when cf is removed. */
> +	cf_global = reader->source;
>  	return ret;
>  }

In the FIXME, it's cf_global not cf.

Everything else looks good.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH v2 2/8] config.c: don't assign to "cf_global" directly
  2023-03-16 21:18     ` Jonathan Tan
@ 2023-03-16 21:31       ` Junio C Hamano
  2023-03-16 22:56       ` Glen Choo
  1 sibling, 0 replies; 72+ messages in thread
From: Junio C Hamano @ 2023-03-16 21:31 UTC (permalink / raw)
  To: Jonathan Tan
  Cc: Glen Choo via GitGitGadget, git, Emily Shaffer, Jeff King,
	Derrick Stolee, Calvin Wan,
	Ævar Arnfjörð Bjarmason, Glen Choo

Jonathan Tan <jonathantanmy@google.com> writes:

> "Glen Choo via GitGitGadget" <gitgitgadget@gmail.com> writes:
>> +static inline void config_reader_push_source(struct config_source *top)
>> +{
>> +	if (cf_global)
>> +		top->prev = cf_global;
>
> Don't we want to set prev unconditionally here (i.e. set it to NULL if
> cf_global was NULL)?

Good eyes.  You are absolutely right.
Thanks.

>
>> +	cf_global = top;
>> +}
>> +
>> +static inline struct config_source *config_reader_pop_source()
>> +{
>> +	struct config_source *ret;
>> +	if (!cf_global)
>> +		BUG("tried to pop config source, but we weren't reading config");
>> +	ret = cf_global;
>> +	cf_global = cf_global->prev;
>> +	return ret;
>> +}
>
> ...since we use it unconditionally here.
>
> The rest looks good.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH v2 7/8] config: report cached filenames in die_bad_number()
  2023-03-16  0:11   ` [PATCH v2 7/8] config: report cached filenames in die_bad_number() Glen Choo via GitGitGadget
@ 2023-03-16 22:22     ` Jonathan Tan
  2023-03-16 23:05       ` Glen Choo
  0 siblings, 1 reply; 72+ messages in thread
From: Jonathan Tan @ 2023-03-16 22:22 UTC (permalink / raw)
  To: Glen Choo via GitGitGadget
  Cc: Jonathan Tan, git, Emily Shaffer, Jeff King, Derrick Stolee,
	Calvin Wan, Ævar Arnfjörð Bjarmason, Glen Choo

Ah, thanks for spotting this bug! It is a minor one, but this now makes
me think that we should definitely do this refactoring of a struct
containing all the relevant config state and passing it to functions as
much as possible (as opposed to merely leaning towards the idea).

"Glen Choo via GitGitGadget" <gitgitgadget@gmail.com> writes:
> Fix this by refactoring the current_config_* functions into variants
> that don't BUG() when we aren't reading config, and using the resulting
> functions in die_bad_number(). Refactoring is needed because "git config
> --get[-regexp] --type=int" parses the int value _after_ parsing the
> config file, which will run into the BUG().

You say "fix this", but are there actually 2 bugs (so, "fix these")?
Firstly, that BUG() is run into when invoking "git config" the way
you describe, and secondly, die_bad_number() only reading cf and not
checking kvi to see if anything's there. (I'm not sure how to reproduce
the latter, though.)

> Also, plumb "struct config_reader" into the new functions. This isn't
> necessary per se, but this generalizes better, so it might help us avoid
> yet another refactor.

Hmm...I thought this would be desired because we don't want the_reader
to be used from non-public functions anyway, so we can just state
that that is the reason (and not worry about using future refactors as
a justification).

The code itself looks good.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH v2 0/8] config.c: use struct for config reading state
  2023-03-16  0:11 ` [PATCH v2 0/8] " Glen Choo via GitGitGadget
                     ` (8 preceding siblings ...)
  2023-03-16  0:15   ` [PATCH v2 0/8] config.c: use struct for config reading state Glen Choo
@ 2023-03-16 22:29   ` Jonathan Tan
  2023-03-17  5:01   ` [RFC PATCH 0/5] bypass config.c global state with configset Ævar Arnfjörð Bjarmason
  2023-03-28 17:51   ` [PATCH v3 0/8] config.c: use struct for config reading state Glen Choo via GitGitGadget
  11 siblings, 0 replies; 72+ messages in thread
From: Jonathan Tan @ 2023-03-16 22:29 UTC (permalink / raw)
  To: Glen Choo via GitGitGadget
  Cc: Jonathan Tan, git, Emily Shaffer, Jeff King, Derrick Stolee,
	Calvin Wan, Ævar Arnfjörð Bjarmason, Glen Choo

So overall what this series does is to create a struct that contains
config parsing state (roughly speaking...it's either parsing state or
a "kvi" that contains some of the parsing state that existed when its
value was parsed), starts a pattern in config.c in which that struct
is passed between functions as much as possible, and establishes a few
functions like reader_origin_type() that make it convenient to work
on the basis of this struct as opposed to on the config_source and kvi
individually. And following this pattern, I think, likely would have
enabled us to avoid the bug in patch 7/8 and enable us to avoid similar
bugs in the future, so overall I'm happy with this series.

"Glen Choo via GitGitGadget" <gitgitgadget@gmail.com> writes:
>    Introduce 8/8 to get rid of the confusing acronym "struct config_source
>    cf", but I don't mind ejecting it if it's too much churn.

I think patch 8/8 is not worth including, so I didn't leave comments on
it, but I'm fine if it ends up merged too.
 

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH v2 2/8] config.c: don't assign to "cf_global" directly
  2023-03-16 21:18     ` Jonathan Tan
  2023-03-16 21:31       ` Junio C Hamano
@ 2023-03-16 22:56       ` Glen Choo
  1 sibling, 0 replies; 72+ messages in thread
From: Glen Choo @ 2023-03-16 22:56 UTC (permalink / raw)
  To: Jonathan Tan, Glen Choo via GitGitGadget
  Cc: Jonathan Tan, git, Emily Shaffer, Jeff King, Derrick Stolee,
	Calvin Wan, Ævar Arnfjörð Bjarmason

Jonathan Tan <jonathantanmy@google.com> writes:

> "Glen Choo via GitGitGadget" <gitgitgadget@gmail.com> writes:
>> +static inline void config_reader_push_source(struct config_source *top)
>> +{
>> +	if (cf_global)
>> +		top->prev = cf_global;
>
> Don't we want to set prev unconditionally here (i.e. set it to NULL if
> cf_global was NULL)?
>
>> +	cf_global = top;
>> +}
>> +
>> +static inline struct config_source *config_reader_pop_source()
>> +{
>> +	struct config_source *ret;
>> +	if (!cf_global)
>> +		BUG("tried to pop config source, but we weren't reading config");
>> +	ret = cf_global;
>> +	cf_global = cf_global->prev;
>> +	return ret;
>> +}
>
> ...since we use it unconditionally here.

Ah, thanks. It's safe as of this patch, since we always zero-initialize
"struct config_source" and nothing else sets ".prev", but this asymmetry
is a bug waiting to happen.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH v2 7/8] config: report cached filenames in die_bad_number()
  2023-03-16 22:22     ` Jonathan Tan
@ 2023-03-16 23:05       ` Glen Choo
  0 siblings, 0 replies; 72+ messages in thread
From: Glen Choo @ 2023-03-16 23:05 UTC (permalink / raw)
  To: Jonathan Tan, Glen Choo via GitGitGadget
  Cc: Jonathan Tan, git, Emily Shaffer, Jeff King, Derrick Stolee,
	Calvin Wan, Ævar Arnfjörð Bjarmason

Jonathan Tan <jonathantanmy@google.com> writes:

>>                                Refactoring is needed because "git config
>> --get[-regexp] --type=int" parses the int value _after_ parsing the
>> config file, which will run into the BUG().
>
> You say "fix this", but are there actually 2 bugs (so, "fix these")?
> Firstly, that BUG() is run into when invoking "git config" the way
> you describe, and secondly, die_bad_number() only reading cf and not
> checking kvi to see if anything's there. (I'm not sure how to reproduce
> the latter, though.)

There is actually only one bug (the latter). That is tested by the new
test I added in this patch. To reproduce it, we need:

- To iterate a config_set (git_config() or repo_config() will suffice),
  in which case the config_kvi is set, but not cf.
- Then in the config_fn_t we pass to it, we call git_parse_int() on an
  invalid number, which will result in die_bad_number(), which prints
  the less specific message.

The former case isn't a bug. We never ran into the BUG() when invoking
"git config" because die_bad_number() doesn't use current_* prior to
this patch (which is where the BUG() is). t1300:'invalid unit'
demonstrates that we print the correct message (and that we don't
BUG()):

  test_expect_success 'invalid unit' '
    git config aninvalid.unit "1auto" &&
    test_cmp_config 1auto aninvalid.unit &&
    test_must_fail git config --int --get aninvalid.unit 2>actual &&
    test_i18ngrep "bad numeric config value .1auto. for .aninvalid.unit. in file .git/config: invalid unit" actual
  '


(which is a good signal that I should probably reword the commit
message)

>
>> Also, plumb "struct config_reader" into the new functions. This isn't
>> necessary per se, but this generalizes better, so it might help us avoid
>> yet another refactor.
>
> Hmm...I thought this would be desired because we don't want the_reader
> to be used from non-public functions anyway, so we can just state
> that that is the reason (and not worry about using future refactors as
> a justification).

Ah, good point, thanks.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* [RFC PATCH 0/5] bypass config.c global state with configset
  2023-03-16  0:11 ` [PATCH v2 0/8] " Glen Choo via GitGitGadget
                     ` (9 preceding siblings ...)
  2023-03-16 22:29   ` Jonathan Tan
@ 2023-03-17  5:01   ` Ævar Arnfjörð Bjarmason
  2023-03-17  5:01     ` [RFC PATCH 1/5] config.h: move up "struct key_value_info" Ævar Arnfjörð Bjarmason
                       ` (7 more replies)
  2023-03-28 17:51   ` [PATCH v3 0/8] config.c: use struct for config reading state Glen Choo via GitGitGadget
  11 siblings, 8 replies; 72+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2023-03-17  5:01 UTC (permalink / raw)
  To: git
  Cc: Glen Choo, Jonathan Tan, Junio C Hamano, Jeff King, Emily Shaffer,
	Derrick Stolee, Calvin Wan,
	Ævar Arnfjörð Bjarmason

On Thu, Mar 16 2023, Glen Choo via GitGitGadget wrote:

> After reflecting on Ævar's responses on v1, I'm fairly convinced that
> "struct config_reader" shouldn't exist in the long term. I've written my
> thoughts on a good long term direction in the "Leftover bits" section. Based
> on that, I've also updated my WIP libification patches [1] to remove "struct
> config_reader" from the library interface, and think it looks a lot better
> as a result.

That libification url
(https://github.com/git/git/compare/master...chooglen:git:config-lib-parsing)
doesn't work for me, and I didn't find a branch with that name in your
published repo. So maybe you've already done all this
post-libification work...

> = Leftover bits
>
> We still need a global "the_reader" because config callbacks are reading
> auxiliary information about the config (e.g. line number, file name) via
> global functions (e.g. current_config_line(), current_config_name()). This
> is either because the callback uses this info directly (like
> builtin/config.c printing the filename and scope of the value) or for error
> reporting (like git_parse_int() reporting the filename of the value it
> failed to parse).
>
> If we had a way to plumb the state from "struct config_reader" to the config
> callback functions, we could initialize "struct config_reader" in the config
> machinery whenever we read config (instead of asking the caller to
> initialize "struct config_reader" themselves), and config reading could
> become a thread-safe operation. There isn't an obvious way to plumb this
> state to config callbacks without adding an additional arg to config_fn_t
> and incurring a lot of churn, but if we start replacing "config_fn_t" with
> the configset API (which we've independently wanted for some time), this may
> become feasible.

...in any case. This RFC expands a bit on my comments on the v1 (at
[1] and upthread). It doesn't get all the way there, but with the
small change in 5/5 we've gotten rid of current_config_line(), the
1-4/5 are trivial pre-refactorings to make that diff smaller
(e.g. moving the "struct key_value_info" around in config.h).

Maybe it still makes sense to go for this "the_reader" intermediate
step, but I can't help but think that we could just go for it all in
one leap, and that you've just got stuck on thinking that you needed
to change "config_fn_t" for all its callers.

As the 5/5 here shows we have various orthagonal uses of the
"config_fn_t" in config.c, and can just implement a new callback type
for the edge cases where we need the file & line info.

This still leave the current_config_name() etc, which
e.g. builtin/config.c still uses. In your series you've needed to add
the new "reader" parameter for everything from do_config_from(), but
if we're doing that can't we instead just go straight to passing a
"struct key_value_info *" (perhaps with an added "name" field) all the
way down, replacing "cf->linenr" etc?

Instead you end up extending "the_reader" everywhere, including to
e.g. configset_iter, which I think as the 5/5 here shows isn't needed,
but maybe I've missed something.

Similarly, you mention git_parse_int() wanting to report a filename
and/or line number. I'm aware that it can do that, but it doesn't do
so in the common case, e.g.:

	git -c format.filenameMaxLength=abc log
	fatal: bad numeric config value 'abc' for 'format.filenamemaxlength': invalid unit

And the same goes for writing it to e.g. ~/.gitconfig. It's only if
you use "git config --file" or similar that we'll report a filename.

So just as with the current_config_line() I wonder if you just grepped
for e.g. git_config_int() and thought because we have a lot of users
of it that all of them would require this data, but for e.g. this
log.c caller (and most or all of the others) we'll be reading the
normal config, and aren't getting any useful info from
die_bad_number() that we wouldn't get from an error function that
didn't need the "linenr" etc.

> And if we do this, "struct config_reader" itself will probably become
> obsolete, because we'd be able to plumb only the relevant state for the
> current operation, e.g. if we are parsing a config file, we'd pass only the
> config file parsing state, instead of "struct config_reader", which also
> contains config set iterating state. In such a scenario, we'd probably want
> to pass "struct key_value_info" to the config callback, since that's all the
> callback should be interested in anyway. Interestingly, this was proposed by
> Junio back in [4], and we didn't do this back then out of concern for the
> churn (just like in v1).

I think we can make it even simpler than that, and from playing around
with builtin/config.c a bit after the 5/5 here I got a POC working
(but am not posting it here, didn't have time to clean it up).

We can just make config_set_callback() and configset_iter()
non-static, so e.g. the builtin/config.c caller that implements
"--show-origin" can keep its config_with_options(...) call, but
instead of "streaming" the config, it'll buffer it up into a
configset.

The advantage of that is that with the configset API we'll get a
"struct key_value_info *" for free on the other end. I.e. we'll
configset_iter() with a fn=NULL, but with a defined "config_kvi_fn_t",
which 5/5 is adding.

But I haven't done that work (and am not planning to finish this), but
maybe this helps.

We'll also need to track the equivalent of "cf->linenr" etc. while we
do the actual initial parse. I think it might be simpler to start by
converting those "linenr" to a "struct key_value_info" that's placed
in the "config_source" right away.

I.e. when we pass it to the error handlers we'll need to give them
access to the "linenr", but we don't want to provide e.g. "eof" (which
is internal-only state).

I wonder how much else you're converting here is actually dead code in
the end (or can trivially be made dead). E.g. the
current_config_line() change you make in 1/8 is never going to use the
"cf_global" if combined with the 5/5 change here.

1. https://lore.kernel.org/git/230308.867cvrziac.gmgdl@evledraar.gmail.com/

Ævar Arnfjörð Bjarmason (5):
  config.h: move up "struct key_value_info"
  config.c: use "enum config_origin_type", not "int"
  config API: add a config_origin_type_name() helper
  config.c: refactor configset_iter()
  config API: add and use a repo_config_kvi()

 builtin/remote.c       | 11 +++----
 config.c               | 67 ++++++++++++++++++++++++++----------------
 config.h               | 29 +++++++++++++-----
 t/helper/test-config.c | 13 ++++----
 t/t5505-remote.sh      |  7 +++--
 5 files changed, 80 insertions(+), 47 deletions(-)

-- 
2.40.0.rc1.1034.g5867a1b10c5


^ permalink raw reply	[flat|nested] 72+ messages in thread

* [RFC PATCH 1/5] config.h: move up "struct key_value_info"
  2023-03-17  5:01   ` [RFC PATCH 0/5] bypass config.c global state with configset Ævar Arnfjörð Bjarmason
@ 2023-03-17  5:01     ` Ævar Arnfjörð Bjarmason
  2023-03-17  5:01     ` [RFC PATCH 2/5] config.c: use "enum config_origin_type", not "int" Ævar Arnfjörð Bjarmason
                       ` (6 subsequent siblings)
  7 siblings, 0 replies; 72+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2023-03-17  5:01 UTC (permalink / raw)
  To: git
  Cc: Glen Choo, Jonathan Tan, Junio C Hamano, Jeff King, Emily Shaffer,
	Derrick Stolee, Calvin Wan,
	Ævar Arnfjörð Bjarmason

Move the declaration of the "struct key_value_info" earlier in the
file, in a subsequent commit we'll need it shortly after this line.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 config.h | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/config.h b/config.h
index 7606246531a..571b92d674a 100644
--- a/config.h
+++ b/config.h
@@ -110,6 +110,13 @@ struct config_options {
 	} error_action;
 };
 
+struct key_value_info {
+	const char *filename;
+	int linenr;
+	enum config_origin_type origin_type;
+	enum config_scope scope;
+};
+
 /**
  * A config callback function takes three parameters:
  *
@@ -612,13 +619,6 @@ int git_config_get_expiry(const char *key, const char **output);
 /* parse either "this many days" integer, or "5.days.ago" approxidate */
 int git_config_get_expiry_in_days(const char *key, timestamp_t *, timestamp_t now);
 
-struct key_value_info {
-	const char *filename;
-	int linenr;
-	enum config_origin_type origin_type;
-	enum config_scope scope;
-};
-
 /**
  * First prints the error message specified by the caller in `err` and then
  * dies printing the line number and the file name of the highest priority
-- 
2.40.0.rc1.1034.g5867a1b10c5


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [RFC PATCH 2/5] config.c: use "enum config_origin_type", not "int"
  2023-03-17  5:01   ` [RFC PATCH 0/5] bypass config.c global state with configset Ævar Arnfjörð Bjarmason
  2023-03-17  5:01     ` [RFC PATCH 1/5] config.h: move up "struct key_value_info" Ævar Arnfjörð Bjarmason
@ 2023-03-17  5:01     ` Ævar Arnfjörð Bjarmason
  2023-03-17  5:01     ` [RFC PATCH 3/5] config API: add a config_origin_type_name() helper Ævar Arnfjörð Bjarmason
                       ` (5 subsequent siblings)
  7 siblings, 0 replies; 72+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2023-03-17  5:01 UTC (permalink / raw)
  To: git
  Cc: Glen Choo, Jonathan Tan, Junio C Hamano, Jeff King, Emily Shaffer,
	Derrick Stolee, Calvin Wan,
	Ævar Arnfjörð Bjarmason

Change the current_config_origin_type() function to use the
appropriate enum type for its "type" variable.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 config.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/config.c b/config.c
index 00090a32fc3..a4105c456c3 100644
--- a/config.c
+++ b/config.c
@@ -3768,7 +3768,8 @@ int parse_config_key(const char *var,
 
 const char *current_config_origin_type(void)
 {
-	int type;
+	enum config_origin_type type;
+
 	if (current_config_kvi)
 		type = current_config_kvi->origin_type;
 	else if(cf)
-- 
2.40.0.rc1.1034.g5867a1b10c5


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [RFC PATCH 3/5] config API: add a config_origin_type_name() helper
  2023-03-17  5:01   ` [RFC PATCH 0/5] bypass config.c global state with configset Ævar Arnfjörð Bjarmason
  2023-03-17  5:01     ` [RFC PATCH 1/5] config.h: move up "struct key_value_info" Ævar Arnfjörð Bjarmason
  2023-03-17  5:01     ` [RFC PATCH 2/5] config.c: use "enum config_origin_type", not "int" Ævar Arnfjörð Bjarmason
@ 2023-03-17  5:01     ` Ævar Arnfjörð Bjarmason
  2023-03-17  5:01     ` [RFC PATCH 4/5] config.c: refactor configset_iter() Ævar Arnfjörð Bjarmason
                       ` (4 subsequent siblings)
  7 siblings, 0 replies; 72+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2023-03-17  5:01 UTC (permalink / raw)
  To: git
  Cc: Glen Choo, Jonathan Tan, Junio C Hamano, Jeff King, Emily Shaffer,
	Derrick Stolee, Calvin Wan,
	Ævar Arnfjörð Bjarmason

Add a config_origin_type_name() helper function. In a subsequent
commit we'll want to invoke this part of current_config_origin_type()
without requiring the global "current_config_kvi" or "cf" state.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 config.c | 25 +++++++++++++++----------
 config.h |  6 ++++++
 2 files changed, 21 insertions(+), 10 deletions(-)

diff --git a/config.c b/config.c
index a4105c456c3..a65e7bb36d3 100644
--- a/config.c
+++ b/config.c
@@ -3766,17 +3766,8 @@ int parse_config_key(const char *var,
 	return 0;
 }
 
-const char *current_config_origin_type(void)
+const char *config_origin_type_name(enum config_origin_type type)
 {
-	enum config_origin_type type;
-
-	if (current_config_kvi)
-		type = current_config_kvi->origin_type;
-	else if(cf)
-		type = cf->origin_type;
-	else
-		BUG("current_config_origin_type called outside config callback");
-
 	switch (type) {
 	case CONFIG_ORIGIN_BLOB:
 		return "blob";
@@ -3793,6 +3784,20 @@ const char *current_config_origin_type(void)
 	}
 }
 
+const char *current_config_origin_type(void)
+{
+	enum config_origin_type type;
+
+	if (current_config_kvi)
+		type = current_config_kvi->origin_type;
+	else if(cf)
+		type = cf->origin_type;
+	else
+		BUG("current_config_origin_type called outside config callback");
+
+	return config_origin_type_name(type);
+}
+
 const char *config_scope_name(enum config_scope scope)
 {
 	switch (scope) {
diff --git a/config.h b/config.h
index 571b92d674a..a9cb01e9405 100644
--- a/config.h
+++ b/config.h
@@ -117,6 +117,12 @@ struct key_value_info {
 	enum config_scope scope;
 };
 
+/**
+ * Given the "enum config_origin_type origin_type"
+ * (e.g. CONFIG_ORIGIN_BLOB) return a string (e.g. "blob").
+ */
+const char *config_origin_type_name(enum config_origin_type type);
+
 /**
  * A config callback function takes three parameters:
  *
-- 
2.40.0.rc1.1034.g5867a1b10c5


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [RFC PATCH 4/5] config.c: refactor configset_iter()
  2023-03-17  5:01   ` [RFC PATCH 0/5] bypass config.c global state with configset Ævar Arnfjörð Bjarmason
                       ` (2 preceding siblings ...)
  2023-03-17  5:01     ` [RFC PATCH 3/5] config API: add a config_origin_type_name() helper Ævar Arnfjörð Bjarmason
@ 2023-03-17  5:01     ` Ævar Arnfjörð Bjarmason
  2023-03-17  5:01     ` [RFC PATCH 5/5] config API: add and use a repo_config_kvi() Ævar Arnfjörð Bjarmason
                       ` (3 subsequent siblings)
  7 siblings, 0 replies; 72+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2023-03-17  5:01 UTC (permalink / raw)
  To: git
  Cc: Glen Choo, Jonathan Tan, Junio C Hamano, Jeff King, Emily Shaffer,
	Derrick Stolee, Calvin Wan,
	Ævar Arnfjörð Bjarmason

Use variables rather than long lines in configset_iter(), and use our
own "kvi" rather than relying on the global "current_config_kvi"
within this function.

There's no functional change here, but doing this will make a
subsequent functional change smaller.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 config.c | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/config.c b/config.c
index a65e7bb36d3..230a98b0631 100644
--- a/config.c
+++ b/config.c
@@ -2227,17 +2227,22 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
 	struct configset_list *list = &cs->list;
 
 	for (i = 0; i < list->nr; i++) {
+		const char *key;
+		const char *val;
+		struct key_value_info *kvi;
+
 		entry = list->items[i].e;
 		value_index = list->items[i].value_index;
 		values = &entry->value_list;
 
-		current_config_kvi = values->items[value_index].util;
-
-		if (fn(entry->key, values->items[value_index].string, data) < 0)
-			git_die_config_linenr(entry->key,
-					      current_config_kvi->filename,
-					      current_config_kvi->linenr);
+		key = entry->key;
+		val = values->items[value_index].string;
+		kvi = values->items[value_index].util;
 
+		current_config_kvi = kvi;
+		if (fn(key, val, data) < 0)
+			git_die_config_linenr(entry->key, kvi->filename,
+					      kvi->linenr);
 		current_config_kvi = NULL;
 	}
 }
-- 
2.40.0.rc1.1034.g5867a1b10c5


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [RFC PATCH 5/5] config API: add and use a repo_config_kvi()
  2023-03-17  5:01   ` [RFC PATCH 0/5] bypass config.c global state with configset Ævar Arnfjörð Bjarmason
                       ` (3 preceding siblings ...)
  2023-03-17  5:01     ` [RFC PATCH 4/5] config.c: refactor configset_iter() Ævar Arnfjörð Bjarmason
@ 2023-03-17  5:01     ` Ævar Arnfjörð Bjarmason
  2023-03-17 17:17       ` Junio C Hamano
  2023-03-17 20:59       ` Jonathan Tan
  2023-03-17 16:21     ` [RFC PATCH 0/5] bypass config.c global state with configset Junio C Hamano
                       ` (2 subsequent siblings)
  7 siblings, 2 replies; 72+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2023-03-17  5:01 UTC (permalink / raw)
  To: git
  Cc: Glen Choo, Jonathan Tan, Junio C Hamano, Jeff King, Emily Shaffer,
	Derrick Stolee, Calvin Wan,
	Ævar Arnfjörð Bjarmason

Introduce a repo_config_kvi(), which is a repo_config() which calls a
"config_kvi_fn_t", rather than the "config_fn_t" that repo_config()
uses.

This allows us to pass along the "struct key_value_info *" directly,
rather than having the callback grab it from the global
"current_config_kvi" that we've been maintaining in
"configset_iter()".

This change is an alternate direction to the topic at [1], this
expands on the vague suggestions I made in [2] to go in this
direction.

As this shows we can split apart the "config_fn_t", and thus avoid
having to change the hundreds of existing "config_fn_t" callers. By
doing this we can already get rid of the current_config_kvi()
function, as "builtin/remote.c" and "t/helper/test-config.c" were the
only users of it.

The change to "t/t5505-remote.sh" ensures that the change here to
config_read_push_default() isn't breaking things. It's the only test
that would go through that codepath, but nothing asserted that we'd
get the correct line number. Let's sanity check that, as well as the
other callback data.

This leaves the other current_config_*() functions. Subsequent commits
will need to deal with those.

1. https://lore.kernel.org/git/pull.1463.v2.git.git.1678925506.gitgitgadget@gmail.com/
2. https://lore.kernel.org/git/230307.86wn3szrzu.gmgdl@evledraar.gmail.com/
3. https://lore.kernel.org/git/230308.867cvrziac.gmgdl@evledraar.gmail.com/

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/remote.c       | 11 ++++++-----
 config.c               | 32 ++++++++++++++++++--------------
 config.h               |  9 ++++++++-
 t/helper/test-config.c | 13 +++++++------
 t/t5505-remote.sh      |  7 +++++--
 5 files changed, 44 insertions(+), 28 deletions(-)

diff --git a/builtin/remote.c b/builtin/remote.c
index 729f6f3643a..c65bce05034 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -644,17 +644,18 @@ struct push_default_info
 };
 
 static int config_read_push_default(const char *key, const char *value,
-	void *cb)
+				    struct key_value_info *kvi, void *cb)
 {
 	struct push_default_info* info = cb;
 	if (strcmp(key, "remote.pushdefault") ||
 	    !value || strcmp(value, info->old_name))
 		return 0;
 
-	info->scope = current_config_scope();
+	info->scope = kvi->scope;
 	strbuf_reset(&info->origin);
-	strbuf_addstr(&info->origin, current_config_name());
-	info->linenr = current_config_line();
+	if (kvi->filename)
+		strbuf_addstr(&info->origin, kvi->filename);
+	info->linenr = kvi->linenr;
 
 	return 0;
 }
@@ -663,7 +664,7 @@ static void handle_push_default(const char* old_name, const char* new_name)
 {
 	struct push_default_info push_default = {
 		old_name, CONFIG_SCOPE_UNKNOWN, STRBUF_INIT, -1 };
-	git_config(config_read_push_default, &push_default);
+	repo_config_kvi(the_repository, config_read_push_default, &push_default);
 	if (push_default.scope >= CONFIG_SCOPE_COMMAND)
 		; /* pass */
 	else if (push_default.scope >= CONFIG_SCOPE_LOCAL) {
diff --git a/config.c b/config.c
index 230a98b0631..1b3f534757c 100644
--- a/config.c
+++ b/config.c
@@ -2219,7 +2219,8 @@ int config_with_options(config_fn_t fn, void *data,
 	return ret;
 }
 
-static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
+static void configset_iter(struct config_set *cs, config_fn_t fn,
+			   config_kvi_fn_t fn_kvi, void *data)
 {
 	int i, value_index;
 	struct string_list *values;
@@ -2230,6 +2231,7 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
 		const char *key;
 		const char *val;
 		struct key_value_info *kvi;
+		int ret;
 
 		entry = list->items[i].e;
 		value_index = list->items[i].value_index;
@@ -2239,11 +2241,15 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
 		val = values->items[value_index].string;
 		kvi = values->items[value_index].util;
 
-		current_config_kvi = kvi;
-		if (fn(key, val, data) < 0)
+		if (!fn_kvi)
+			current_config_kvi = kvi;
+		ret = fn_kvi ? fn_kvi(key, val, kvi, data) :
+			fn(key, val, data);
+		current_config_kvi = NULL;
+
+		if (ret < 0)
 			git_die_config_linenr(entry->key, kvi->filename,
 					      kvi->linenr);
-		current_config_kvi = NULL;
 	}
 }
 
@@ -2567,7 +2573,13 @@ static void repo_config_clear(struct repository *repo)
 void repo_config(struct repository *repo, config_fn_t fn, void *data)
 {
 	git_config_check_init(repo);
-	configset_iter(repo->config, fn, data);
+	configset_iter(repo->config, fn, NULL, data);
+}
+
+void repo_config_kvi(struct repository *repo, config_kvi_fn_t fn, void *data)
+{
+	git_config_check_init(repo);
+	configset_iter(repo->config, NULL, fn, data);
 }
 
 int repo_config_get_value(struct repository *repo,
@@ -2670,7 +2682,7 @@ void git_protected_config(config_fn_t fn, void *data)
 {
 	if (!protected_config.hash_initialized)
 		read_protected_config();
-	configset_iter(&protected_config, fn, data);
+	configset_iter(&protected_config, fn, NULL, data);
 }
 
 /* Functions used historically to read configuration from 'the_repository' */
@@ -3843,14 +3855,6 @@ enum config_scope current_config_scope(void)
 		return current_parsing_scope;
 }
 
-int current_config_line(void)
-{
-	if (current_config_kvi)
-		return current_config_kvi->linenr;
-	else
-		return cf->linenr;
-}
-
 int lookup_config(const char **mapping, int nr_mapping, const char *var)
 {
 	int i;
diff --git a/config.h b/config.h
index a9cb01e9405..de5350dbee5 100644
--- a/config.h
+++ b/config.h
@@ -143,6 +143,13 @@ const char *config_origin_type_name(enum config_origin_type type);
  */
 typedef int (*config_fn_t)(const char *, const char *, void *);
 
+/**
+ * Like config_fn_t, but before the callback-specific data we'll get a
+ * "struct key_value_info" indicating the origin of the config.
+ */
+typedef int (*config_kvi_fn_t)(const char *key, const char *var,
+			       struct key_value_info *kvi, void *data);
+
 int git_default_config(const char *, const char *, void *);
 
 /**
@@ -371,7 +378,6 @@ int git_config_parse_parameter(const char *, config_fn_t fn, void *data);
 enum config_scope current_config_scope(void);
 const char *current_config_origin_type(void);
 const char *current_config_name(void);
-int current_config_line(void);
 
 /*
  * Match and parse a config key of the form:
@@ -498,6 +504,7 @@ int git_configset_get_pathname(struct config_set *cs, const char *key, const cha
 /* Functions for reading a repository's config */
 struct repository;
 void repo_config(struct repository *repo, config_fn_t fn, void *data);
+void repo_config_kvi(struct repository *repo, config_kvi_fn_t fn, void *data);
 int repo_config_get_value(struct repository *repo,
 			  const char *key, const char **value);
 const struct string_list *repo_config_get_value_multi(struct repository *repo,
diff --git a/t/helper/test-config.c b/t/helper/test-config.c
index 4ba9eb65606..2ef67b18a0b 100644
--- a/t/helper/test-config.c
+++ b/t/helper/test-config.c
@@ -37,7 +37,8 @@
  *
  */
 
-static int iterate_cb(const char *var, const char *value, void *data UNUSED)
+static int iterate_cb(const char *var, const char *value,
+		      struct key_value_info *kvi, void *data UNUSED)
 {
 	static int nr;
 
@@ -46,10 +47,10 @@ static int iterate_cb(const char *var, const char *value, void *data UNUSED)
 
 	printf("key=%s\n", var);
 	printf("value=%s\n", value ? value : "(null)");
-	printf("origin=%s\n", current_config_origin_type());
-	printf("name=%s\n", current_config_name());
-	printf("lno=%d\n", current_config_line());
-	printf("scope=%s\n", config_scope_name(current_config_scope()));
+	printf("origin=%s\n", config_origin_type_name(kvi->origin_type));
+	printf("name=%s\n", kvi->filename ? kvi->filename : "");
+	printf("lno=%d\n", kvi->linenr);
+	printf("scope=%s\n", config_scope_name(kvi->scope));
 
 	return 0;
 }
@@ -174,7 +175,7 @@ int cmd__config(int argc, const char **argv)
 			goto exit1;
 		}
 	} else if (!strcmp(argv[1], "iterate")) {
-		git_config(iterate_cb, NULL);
+		repo_config_kvi(the_repository, iterate_cb, NULL);
 		goto exit0;
 	}
 
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 43b7bcd7159..d9659b9c65e 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -853,8 +853,11 @@ test_expect_success 'rename a remote' '
 	(
 		cd four &&
 		git config branch.main.pushRemote origin &&
-		GIT_TRACE2_EVENT=$(pwd)/trace \
-			git remote rename --progress origin upstream &&
+		GIT_TRACE2_EVENT=$PWD/trace \
+			git remote rename --progress origin upstream 2>warn &&
+		grep -F "The global configuration remote.pushDefault" warn &&
+		grep "/\.gitconfig:2$" warn &&
+		grep "remote '\''origin'\''" warn &&
 		test_region progress "Renaming remote references" trace &&
 		grep "pushRemote" .git/config &&
 		test -z "$(git for-each-ref refs/remotes/origin)" &&
-- 
2.40.0.rc1.1034.g5867a1b10c5


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* Re: [RFC PATCH 0/5] bypass config.c global state with configset
  2023-03-17  5:01   ` [RFC PATCH 0/5] bypass config.c global state with configset Ævar Arnfjörð Bjarmason
                       ` (4 preceding siblings ...)
  2023-03-17  5:01     ` [RFC PATCH 5/5] config API: add and use a repo_config_kvi() Ævar Arnfjörð Bjarmason
@ 2023-03-17 16:21     ` Junio C Hamano
  2023-03-17 16:28     ` Glen Choo
  2023-03-17 19:20     ` Glen Choo
  7 siblings, 0 replies; 72+ messages in thread
From: Junio C Hamano @ 2023-03-17 16:21 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Glen Choo, Jonathan Tan, Jeff King, Emily Shaffer,
	Derrick Stolee, Calvin Wan

Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:

> Ævar Arnfjörð Bjarmason (5):
>   config.h: move up "struct key_value_info"
>   config.c: use "enum config_origin_type", not "int"
>   config API: add a config_origin_type_name() helper
>   config.c: refactor configset_iter()
>   config API: add and use a repo_config_kvi()

I haven't reached the end of 5/5 but it is a shame to see that these
obviously good and trivial clean-up patches like [1-4/5] have to be
buried in an RFC patch and benefit of applying them alone becomes
unclear only because it is done in an area that is actively worked
on by others.  The latter is unfortunately inherent to the approach
of "commenting by patches" but hopefully we can see them reappear
when the tree is quiescent.

Thanks.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [RFC PATCH 0/5] bypass config.c global state with configset
  2023-03-17  5:01   ` [RFC PATCH 0/5] bypass config.c global state with configset Ævar Arnfjörð Bjarmason
                       ` (5 preceding siblings ...)
  2023-03-17 16:21     ` [RFC PATCH 0/5] bypass config.c global state with configset Junio C Hamano
@ 2023-03-17 16:28     ` Glen Choo
  2023-03-17 19:20     ` Glen Choo
  7 siblings, 0 replies; 72+ messages in thread
From: Glen Choo @ 2023-03-17 16:28 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason, git
  Cc: Jonathan Tan, Junio C Hamano, Jeff King, Emily Shaffer,
	Derrick Stolee, Calvin Wan,
	Ævar Arnfjörð Bjarmason

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> That libification url
> (https://github.com/git/git/compare/master...chooglen:git:config-lib-parsing)
> doesn't work for me, and I didn't find a branch with that name in your
> published repo. So maybe you've already done all this
> post-libification work...

Argh, sorry, I renamed the branch.

  https://github.com/git/git/compare/master...chooglen:git:config/read-without-globals

> ...in any case. This RFC expands a bit on my comments on the v1 (at
> [1] and upthread). It doesn't get all the way there, but with the
> small change in 5/5 we've gotten rid of current_config_line(), the
> 1-4/5 are trivial pre-refactorings to make that diff smaller
> (e.g. moving the "struct key_value_info" around in config.h).

Thanks. I haven't read through it yet, but it sounds promising :)

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [RFC PATCH 5/5] config API: add and use a repo_config_kvi()
  2023-03-17  5:01     ` [RFC PATCH 5/5] config API: add and use a repo_config_kvi() Ævar Arnfjörð Bjarmason
@ 2023-03-17 17:17       ` Junio C Hamano
  2023-03-17 20:59       ` Jonathan Tan
  1 sibling, 0 replies; 72+ messages in thread
From: Junio C Hamano @ 2023-03-17 17:17 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Glen Choo, Jonathan Tan, Jeff King, Emily Shaffer,
	Derrick Stolee, Calvin Wan

Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:

> Introduce a repo_config_kvi(), which is a repo_config() which calls a
> "config_kvi_fn_t", rather than the "config_fn_t" that repo_config()
> uses.
>
> This allows us to pass along the "struct key_value_info *" directly,
> rather than having the callback grab it from the global
> "current_config_kvi" that we've been maintaining in
> "configset_iter()".

Nice that you can plumb kvi through the callchain but without having
to touch the end-user-visible callback data *cb or the function
signature of the cllaback functions.

> This leaves the other current_config_*() functions. Subsequent commits
> will need to deal with those.


^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [RFC PATCH 0/5] bypass config.c global state with configset
  2023-03-17  5:01   ` [RFC PATCH 0/5] bypass config.c global state with configset Ævar Arnfjörð Bjarmason
                       ` (6 preceding siblings ...)
  2023-03-17 16:28     ` Glen Choo
@ 2023-03-17 19:20     ` Glen Choo
  2023-03-17 23:32       ` Glen Choo
  2023-03-29 11:53       ` Ævar Arnfjörð Bjarmason
  7 siblings, 2 replies; 72+ messages in thread
From: Glen Choo @ 2023-03-17 19:20 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason, git
  Cc: Jonathan Tan, Junio C Hamano, Jeff King, Emily Shaffer,
	Derrick Stolee, Calvin Wan,
	Ævar Arnfjörð Bjarmason

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> Maybe it still makes sense to go for this "the_reader" intermediate
> step, but I can't help but think that we could just go for it all in
> one leap, and that you've just got stuck on thinking that you needed
> to change "config_fn_t" for all its callers.
>
> As the 5/5 here shows we have various orthagonal uses of the
> "config_fn_t" in config.c, and can just implement a new callback type
> for the edge cases where we need the file & line info.
>
> This still leave the current_config_name() etc, which
> e.g. builtin/config.c still uses. In your series you've needed to add
> the new "reader" parameter for everything from do_config_from(), but
> if we're doing that can't we instead just go straight to passing a
> "struct key_value_info *" (perhaps with an added "name" field) all the
> way down, replacing "cf->linenr" etc?

In the end state, I also think we should be passing "struct
key_value_info *" around instead of "cf", so I think we are seeing
"the_reader" in the same way (as a transitional state).

I considered the "repo_config_kvi() + config_fn_kvi_t" as well, but I
rejected it (before discussion on the list, whoops) because I didn't
want to add _yet another_ set of parallel config APIs, e.g. we already
have repo_config(), git_config(), configset*(),
git_config_from_file*(). Multiplying that by 2 to add *_kvi() seems like
way too much, especially when it seems clear that our current definition
of config_fn_t has some problems.

Maybe we could deprecate the non-*_kvi(), and have both as a
transitional state? It might work, but I think biting the bullet and
changing config_fn_t would be easier actually.

I'll try applying your series on top of my 1/8 (and maybe 7/8, see next
reply) and extending it to cover "cf" (instead of just
current_config_kvi()) to see whether the *_kvi() approach saves a lot of
unnecessary plumbing. I'd still feel very strongly about getting rid of
all of the non *_kvi() versions, though, but maybe that would happen in
a cleanup topic.

> Similarly, you mention git_parse_int() wanting to report a filename
> and/or line number. I'm aware that it can do that, but it doesn't do
> so in the common case, e.g.:
>
> 	git -c format.filenameMaxLength=abc log
> 	fatal: bad numeric config value 'abc' for 'format.filenamemaxlength': invalid unit
>
> And the same goes for writing it to e.g. ~/.gitconfig. It's only if
> you use "git config --file" or similar that we'll report a filename.

That's true, but I think that's a bug, not a feature. See 7/8 [1] where
I addressed it.

1.  https://lore.kernel.org/git/3c83d9535a037653c7de2d462a4df3a3c43a9308.1678925506.git.gitgitgadget@gmail.com/

> We can just make config_set_callback() and configset_iter()
> non-static, so e.g. the builtin/config.c caller that implements
> "--show-origin" can keep its config_with_options(...) call, but
> instead of "streaming" the config, it'll buffer it up into a
> configset.

Hm, so to extrapolate, we could make it so that nobody outside of
config.c uses the *config_from_file() APIs directly. Instead, all reads
get buffered up into a configset. That might not be a bad idea. It would
definitely help with some of your goals of config API surface reduction.

This would be friendlier in cases where we were already creating custom
configsets (I know we have some of those, but I don't recall where), but
in cases where we were reading the file directly (e.g.
builtin/config.c), we'd be taking a memory and runtime hit. I'm not sure
how I (or others) feel about that yet.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [RFC PATCH 5/5] config API: add and use a repo_config_kvi()
  2023-03-17  5:01     ` [RFC PATCH 5/5] config API: add and use a repo_config_kvi() Ævar Arnfjörð Bjarmason
  2023-03-17 17:17       ` Junio C Hamano
@ 2023-03-17 20:59       ` Jonathan Tan
  1 sibling, 0 replies; 72+ messages in thread
From: Jonathan Tan @ 2023-03-17 20:59 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Jonathan Tan, git, Glen Choo, Junio C Hamano, Jeff King,
	Emily Shaffer, Derrick Stolee, Calvin Wan

Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:
> This change is an alternate direction to the topic at [1], this
> expands on the vague suggestions I made in [2] to go in this
> direction.

Are you suggesting that we merge your patches instead of Glen's? (Or
maybe merge your patches and a subset of Glen's, but I'm not sure which
subset.)

> As this shows we can split apart the "config_fn_t", and thus avoid
> having to change the hundreds of existing "config_fn_t" callers. By
> doing this we can already get rid of the current_config_kvi()
> function, as "builtin/remote.c" and "t/helper/test-config.c" were the
> only users of it.

[snip]

> This leaves the other current_config_*() functions. Subsequent commits
> will need to deal with those.

I think we already know that splitting config_fn_t is possible but the
change will be quite widespread.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [RFC PATCH 0/5] bypass config.c global state with configset
  2023-03-17 19:20     ` Glen Choo
@ 2023-03-17 23:32       ` Glen Choo
  2023-03-29 11:53       ` Ævar Arnfjörð Bjarmason
  1 sibling, 0 replies; 72+ messages in thread
From: Glen Choo @ 2023-03-17 23:32 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason, git
  Cc: Jonathan Tan, Junio C Hamano, Jeff King, Emily Shaffer,
	Derrick Stolee, Calvin Wan,
	Ævar Arnfjörð Bjarmason

Glen Choo <chooglen@google.com> writes:

>> This still leave the current_config_name() etc, which
>> e.g. builtin/config.c still uses. In your series you've needed to add
>> the new "reader" parameter for everything from do_config_from(), but
>> if we're doing that can't we instead just go straight to passing a
>> "struct key_value_info *" (perhaps with an added "name" field) all the
>> way down, replacing "cf->linenr" etc?
>
> In the end state, I also think we should be passing "struct
> key_value_info *" around instead of "cf", so I think we are seeing
> "the_reader" in the same way (as a transitional state).
>
> I considered the "repo_config_kvi() + config_fn_kvi_t" as well, but I
> rejected it (before discussion on the list, whoops) because I didn't
> want to add _yet another_ set of parallel config APIs, e.g. we already
> have repo_config(), git_config(), configset*(),
> git_config_from_file*(). Multiplying that by 2 to add *_kvi() seems like
> way too much, especially when it seems clear that our current definition
> of config_fn_t has some problems.
>
> Maybe we could deprecate the non-*_kvi(), and have both as a
> transitional state? It might work, but I think biting the bullet and
> changing config_fn_t would be easier actually.
>
> I'll try applying your series on top of my 1/8 (and maybe 7/8, see next
> reply) and extending it to cover "cf" (instead of just
> current_config_kvi()) to see whether the *_kvi() approach saves a lot of
> unnecessary plumbing. I'd still feel very strongly about getting rid of
> all of the non *_kvi() versions, though, but maybe that would happen in
> a cleanup topic.

As mentioned above, I think having both "config_fn_t" and
config_kvi_fn_t" would make sense if we found a good way to extend it to
the functions that use "cf" (parsing config syntax), not the ones that
use "current_config_kvi" (iterating a config set).

I think technical difficulty is not a barrier here:

- Constructing a "struct key_value_info" out of "cf" is trivial

- Supporting both "config_fn_t" and "config_kvi_fn_t" with one
  implementation is also doable in theory. One approach would be to use
  only *_kvi() internally, and then adapt the external "config_fn_t"
  like:

    struct adapt_nonkvi {
          config_fn_t fn;
          void *data;
    };

    static int adapt_nonkvi_fn(const char *key, const char *value,
                              struct key_value_info *kvi UNUSED, void *cb)
    {
          struct adapt_nonkvi *adapt = cb;
          return adapt->fn(key, value, adapt->data);
    }

The real cost is that there are so many functions we'd need to adapt (I
counted 12 functions that accept config_fn_t in config.h). I think I got
through about 30% of it before thinking that it was too much work to try
to avoid adjusting config_fn_t.

I still strongly believe that we shouldn't have both config_fn_t and
config_kvi_fn_t in the long run, and we should converge on one.
It's plausible that if we support both as an intermediate state, we'll
never do the actual cleanup, so I think the extra cost is not worth it.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* [PATCH v3 0/8] config.c: use struct for config reading state
  2023-03-16  0:11 ` [PATCH v2 0/8] " Glen Choo via GitGitGadget
                     ` (10 preceding siblings ...)
  2023-03-17  5:01   ` [RFC PATCH 0/5] bypass config.c global state with configset Ævar Arnfjörð Bjarmason
@ 2023-03-28 17:51   ` Glen Choo via GitGitGadget
  2023-03-28 17:51     ` [PATCH v3 1/8] config.c: plumb config_source through static fns Glen Choo via GitGitGadget
                       ` (8 more replies)
  11 siblings, 9 replies; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-28 17:51 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Calvin Wan,
	Ævar Arnfjörð Bjarmason, Glen Choo

Note to Junio: 8/8 (which renames "cs" -> "set") conflicts with
ab/config-multi-and-nonbool. I previously said that I'd rebase this, but
presumably a remerge-diff is more ergonomic + flexible (let me know if I'm
mistaken), so I'll send a remerge-diff in a reply (I don't trust GGG not to
mangle the patch :/).

After sending this out, I'll see if cocci can make it easy enough to change
config_fn_t. If so, I'll probably go with that approach for the future
'libified' patches, which should also line up nicely with what Ævar
suggested.

= Changes in v3

 * Adjust the *_push() function to be unconditional (like the *_pop()
   function)

 * Various commit message and comment cleanups

= Changes in v2

 * To reduce churn, don't rename "struct config_source cf" to "cs" early in
   the series. Instead, rename the global "cf" to "cf_global", and leave the
   existing "cf"s untouched.
   
   Introduce 8/8 to get rid of the confusing acronym "struct config_source
   cf", but I don't mind ejecting it if it's too much churn.

 * Adjust 5/8 so to pass "struct config_reader" through args instead of
   "*data". v1 made the mistake of thinking "*data" was being passed to a
   callback, but it wasn't.

 * Add a 7/8 to fix a bug in die_bad_number(). I included this because it
   overlaps a little bit with the refactor here, but I don't mind ejecting
   this either.

 * Assorted BUG() message clarifications.

= Description

This series prepares for config.[ch] to be libified as as part of the
libification effort that Emily described in [1]. One of the first goals is
to read config from a file, but the trouble with how config.c is written
today is that all reading operations rely on global state, so before turning
that into a library, we'd want to make that state non-global.

This series doesn't remove all of the global state, but it gets us closer to
that goal by extracting the global config reading state into "struct
config_reader" and plumbing it through the config reading machinery. This
makes it possible to reuse the config machinery without global state, and to
enforce some constraints on "struct config_reader", which makes it more
predictable and easier to remove in the long run.

This process is very similar to how we've plumbed "struct repository" and
other 'context objects' in the past, except:

 * The global state (named "the_reader") for the git process lives in a
   config.c static variable, and not on "the_repository". See 3/6 for the
   rationale.

 * I've stopped short of adding "struct config_reader" to config.h public
   functions, since that would affect non-config.c callers.

Additionally, I've included a bugfix for die_bad_number() that became clear
as I did this refactor.

= Leftover bits

We still need a global "the_reader" because config callbacks are reading
auxiliary information about the config (e.g. line number, file name) via
global functions (e.g. current_config_line(), current_config_name()). This
is either because the callback uses this info directly (like
builtin/config.c printing the filename and scope of the value) or for error
reporting (like git_parse_int() reporting the filename of the value it
failed to parse).

If we had a way to plumb the state from "struct config_reader" to the config
callback functions, we could initialize "struct config_reader" in the config
machinery whenever we read config (instead of asking the caller to
initialize "struct config_reader" themselves), and config reading could
become a thread-safe operation. There isn't an obvious way to plumb this
state to config callbacks without adding an additional arg to config_fn_t
and incurring a lot of churn, but if we start replacing "config_fn_t" with
the configset API (which we've independently wanted for some time), this may
become feasible.

And if we do this, "struct config_reader" itself will probably become
obsolete, because we'd be able to plumb only the relevant state for the
current operation, e.g. if we are parsing a config file, we'd pass only the
config file parsing state, instead of "struct config_reader", which also
contains config set iterating state. In such a scenario, we'd probably want
to pass "struct key_value_info" to the config callback, since that's all the
callback should be interested in anyway. Interestingly, this was proposed by
Junio back in [2], and we didn't do this back then out of concern for the
churn (just like in v1).

[1]
https://lore.kernel.org/git/CAJoAoZ=Cig_kLocxKGax31sU7Xe4==BGzC__Bg2_pr7krNq6MA@mail.gmail.com
[2]
https://lore.kernel.org/git/CAPc5daV6bdUKS-ExHmpT4Ppy2S832NXoyPw7aOLP7fG=WrBPgg@mail.gmail.com/

Glen Choo (8):
  config.c: plumb config_source through static fns
  config.c: don't assign to "cf_global" directly
  config.c: create config_reader and the_reader
  config.c: plumb the_reader through callbacks
  config.c: remove current_config_kvi
  config.c: remove current_parsing_scope
  config: report cached filenames in die_bad_number()
  config.c: rename "struct config_source cf"

 config.c               | 584 ++++++++++++++++++++++++-----------------
 config.h               |   1 +
 t/helper/test-config.c |  17 ++
 t/t1308-config-set.sh  |   9 +
 4 files changed, 368 insertions(+), 243 deletions(-)


base-commit: dadc8e6dacb629f46aee39bde90b6f09b73722eb
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1463%2Fchooglen%2Fconfig%2Fstructify-reading-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1463/chooglen/config/structify-reading-v3
Pull-Request: https://github.com/git/git/pull/1463

Range-diff vs v2:

 1:  75d0f0efb79 = 1:  75d0f0efb79 config.c: plumb config_source through static fns
 2:  7555da0b0e0 ! 2:  39db7d8596a config.c: don't assign to "cf_global" directly
     @@ config.c: static struct key_value_info *current_config_kvi;
       
      +static inline void config_reader_push_source(struct config_source *top)
      +{
     -+	if (cf_global)
     -+		top->prev = cf_global;
     ++	top->prev = cf_global;
      +	cf_global = top;
      +}
      +
 3:  4347896f0a4 ! 3:  72774fd08f3 config.c: create config_reader and the_reader
     @@ config.c: static struct key_value_info *current_config_kvi;
      +static inline void config_reader_push_source(struct config_reader *reader,
      +					     struct config_source *top)
       {
     --	if (cf_global)
     --		top->prev = cf_global;
     +-	top->prev = cf_global;
      -	cf_global = top;
     -+	if (reader->source)
     -+		top->prev = reader->source;
     ++	top->prev = reader->source;
      +	reader->source = top;
      +	/* FIXME remove this when cf_global is removed. */
      +	cf_global = reader->source;
     @@ config.c: static struct key_value_info *current_config_kvi;
      -	cf_global = cf_global->prev;
      +	ret = reader->source;
      +	reader->source = reader->source->prev;
     -+	/* FIXME remove this when cf is removed. */
     ++	/* FIXME remove this when cf_global is removed. */
      +	cf_global = reader->source;
       	return ret;
       }
 4:  22b69971749 ! 4:  e02dddd560f config.c: plumb the_reader through callbacks
     @@ config.c: static struct config_reader the_reader;
       
       /*
      @@ config.c: static inline void config_reader_push_source(struct config_reader *reader,
     - 	if (reader->source)
     - 		top->prev = reader->source;
     + {
     + 	top->prev = reader->source;
       	reader->source = top;
      -	/* FIXME remove this when cf_global is removed. */
      -	cf_global = reader->source;
     @@ config.c: static inline struct config_source *config_reader_pop_source(struct co
       		BUG("tried to pop config source, but we weren't reading config");
       	ret = reader->source;
       	reader->source = reader->source->prev;
     --	/* FIXME remove this when cf is removed. */
     +-	/* FIXME remove this when cf_global is removed. */
      -	cf_global = reader->source;
       	return ret;
       }
 5:  afb6e3e318d ! 5:  c79eaf74f89 config.c: remove current_config_kvi
     @@ config.c: static enum config_scope current_parsing_scope;
       {
      +	if (reader->config_kvi)
      +		BUG("source should not be set while iterating a config set");
     - 	if (reader->source)
     - 		top->prev = reader->source;
     + 	top->prev = reader->source;
       	reader->source = top;
     + }
      @@ config.c: static inline struct config_source *config_reader_pop_source(struct config_reade
       	return ret;
       }
 6:  a57e35163ae = 6:  05d9ffa21f6 config.c: remove current_parsing_scope
 7:  3c83d9535a0 ! 7:  eb843e6f08d config: report cached filenames in die_bad_number()
     @@ Commit message
      
          Fix this by refactoring the current_config_* functions into variants
          that don't BUG() when we aren't reading config, and using the resulting
     -    functions in die_bad_number(). Refactoring is needed because "git config
     -    --get[-regexp] --type=int" parses the int value _after_ parsing the
     -    config file, which will run into the BUG().
     +    functions in die_bad_number(). "git config --get[-regexp] --type=int"
     +    cannot use the non-refactored version because it parses the int value
     +    _after_ parsing the config file, which would run into the BUG().
      
     -    Also, plumb "struct config_reader" into the new functions. This isn't
     -    necessary per se, but this generalizes better, so it might help us avoid
     -    yet another refactor.
     +    Since the refactored functions aren't public, they use "struct
     +    config_reader".
      
          1. https://lore.kernel.org/git/20160518223712.GA18317@sigill.intra.peff.net/
      
 8:  9aec9092fdf = 8:  ab800aa104c config.c: rename "struct config_source cf"

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 72+ messages in thread

* [PATCH v3 1/8] config.c: plumb config_source through static fns
  2023-03-28 17:51   ` [PATCH v3 0/8] config.c: use struct for config reading state Glen Choo via GitGitGadget
@ 2023-03-28 17:51     ` Glen Choo via GitGitGadget
  2023-03-28 17:51     ` [PATCH v3 2/8] config.c: don't assign to "cf_global" directly Glen Choo via GitGitGadget
                       ` (7 subsequent siblings)
  8 siblings, 0 replies; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-28 17:51 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Calvin Wan,
	Ævar Arnfjörð Bjarmason, Glen Choo, Glen Choo

From: Glen Choo <chooglen@google.com>

This reduces the direct dependence on the global "struct config_source",
which will make it easier to remove in a later commit.

To minimize the changes we need to make, we rename the current variable
from "cf" to "cf_global", and the plumbed arg uses the old name "cf".
This is a little unfortunate, since we now have the confusingly named
"struct config_source cf" everywhere (which is a holdover from before
4d8dd1494e (config: make parsing stack struct independent from actual
data source, 2013-07-12), when the struct used to be called
"config_file"), but we will rename "cf" to "cs" by the end of the
series.

In some cases (public functions and config callback functions), there
isn't an obvious way to plumb "struct config_source" through function
args. As a workaround, add references to "cf_global" that we'll address
in later commits.

The remaining references to "cf_global" are direct assignments to
"cf_global", which we'll also address in a later commit.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 153 ++++++++++++++++++++++++++++++-------------------------
 1 file changed, 84 insertions(+), 69 deletions(-)

diff --git a/config.c b/config.c
index 00090a32fc3..e4a76739365 100644
--- a/config.c
+++ b/config.c
@@ -54,8 +54,8 @@ struct config_source {
  * These variables record the "current" config source, which
  * can be accessed by parsing callbacks.
  *
- * The "cf" variable will be non-NULL only when we are actually parsing a real
- * config source (file, blob, cmdline, etc).
+ * The "cf_global" variable will be non-NULL only when we are actually
+ * parsing a real config source (file, blob, cmdline, etc).
  *
  * The "current_config_kvi" variable will be non-NULL only when we are feeding
  * cached config from a configset into a callback.
@@ -66,15 +66,16 @@ struct config_source {
  * or it's a function which can be reused for non-config purposes, and should
  * fall back to some sane behavior).
  */
-static struct config_source *cf;
+static struct config_source *cf_global;
 static struct key_value_info *current_config_kvi;
 
 /*
  * Similar to the variables above, this gives access to the "scope" of the
  * current value (repo, global, etc). For cached values, it can be found via
  * the current_config_kvi as above. During parsing, the current value can be
- * found in this variable. It's not part of "cf" because it transcends a single
- * file (i.e., a file included from .git/config is still in "repo" scope).
+ * found in this variable. It's not part of "cf_global" because it transcends a
+ * single file (i.e., a file included from .git/config is still in "repo"
+ * scope).
  */
 static enum config_scope current_parsing_scope;
 
@@ -156,7 +157,8 @@ static const char include_depth_advice[] = N_(
 "from\n"
 "	%s\n"
 "This might be due to circular includes.");
-static int handle_path_include(const char *path, struct config_include_data *inc)
+static int handle_path_include(struct config_source *cf, const char *path,
+			       struct config_include_data *inc)
 {
 	int ret = 0;
 	struct strbuf buf = STRBUF_INIT;
@@ -210,7 +212,8 @@ static void add_trailing_starstar_for_dir(struct strbuf *pat)
 		strbuf_addstr(pat, "**");
 }
 
-static int prepare_include_condition_pattern(struct strbuf *pat)
+static int prepare_include_condition_pattern(struct config_source *cf,
+					     struct strbuf *pat)
 {
 	struct strbuf path = STRBUF_INIT;
 	char *expanded;
@@ -245,7 +248,8 @@ static int prepare_include_condition_pattern(struct strbuf *pat)
 	return prefix;
 }
 
-static int include_by_gitdir(const struct config_options *opts,
+static int include_by_gitdir(struct config_source *cf,
+			     const struct config_options *opts,
 			     const char *cond, size_t cond_len, int icase)
 {
 	struct strbuf text = STRBUF_INIT;
@@ -261,7 +265,7 @@ static int include_by_gitdir(const struct config_options *opts,
 
 	strbuf_realpath(&text, git_dir, 1);
 	strbuf_add(&pattern, cond, cond_len);
-	prefix = prepare_include_condition_pattern(&pattern);
+	prefix = prepare_include_condition_pattern(cf, &pattern);
 
 again:
 	if (prefix < 0)
@@ -342,14 +346,14 @@ static void populate_remote_urls(struct config_include_data *inc)
 {
 	struct config_options opts;
 
-	struct config_source *store_cf = cf;
+	struct config_source *store_cf = cf_global;
 	struct key_value_info *store_kvi = current_config_kvi;
 	enum config_scope store_scope = current_parsing_scope;
 
 	opts = *inc->opts;
 	opts.unconditional_remote_url = 1;
 
-	cf = NULL;
+	cf_global = NULL;
 	current_config_kvi = NULL;
 	current_parsing_scope = 0;
 
@@ -357,7 +361,7 @@ static void populate_remote_urls(struct config_include_data *inc)
 	string_list_init_dup(inc->remote_urls);
 	config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);
 
-	cf = store_cf;
+	cf_global = store_cf;
 	current_config_kvi = store_kvi;
 	current_parsing_scope = store_scope;
 }
@@ -406,15 +410,16 @@ static int include_by_remote_url(struct config_include_data *inc,
 					     inc->remote_urls);
 }
 
-static int include_condition_is_true(struct config_include_data *inc,
+static int include_condition_is_true(struct config_source *cf,
+				     struct config_include_data *inc,
 				     const char *cond, size_t cond_len)
 {
 	const struct config_options *opts = inc->opts;
 
 	if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
-		return include_by_gitdir(opts, cond, cond_len, 0);
+		return include_by_gitdir(cf, opts, cond, cond_len, 0);
 	else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
-		return include_by_gitdir(opts, cond, cond_len, 1);
+		return include_by_gitdir(cf, opts, cond, cond_len, 1);
 	else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
 		return include_by_branch(cond, cond_len);
 	else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,
@@ -441,16 +446,16 @@ static int git_config_include(const char *var, const char *value, void *data)
 		return ret;
 
 	if (!strcmp(var, "include.path"))
-		ret = handle_path_include(value, inc);
+		ret = handle_path_include(cf_global, value, inc);
 
 	if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
-	    cond && include_condition_is_true(inc, cond, cond_len) &&
+	    cond && include_condition_is_true(cf_global, inc, cond, cond_len) &&
 	    !strcmp(key, "path")) {
 		config_fn_t old_fn = inc->fn;
 
 		if (inc->opts->unconditional_remote_url)
 			inc->fn = forbid_remote_url;
-		ret = handle_path_include(value, inc);
+		ret = handle_path_include(cf_global, value, inc);
 		inc->fn = old_fn;
 	}
 
@@ -713,9 +718,9 @@ int git_config_from_parameters(config_fn_t fn, void *data)
 	struct config_source source;
 
 	memset(&source, 0, sizeof(source));
-	source.prev = cf;
+	source.prev = cf_global;
 	source.origin_type = CONFIG_ORIGIN_CMDLINE;
-	cf = &source;
+	cf_global = &source;
 
 	env = getenv(CONFIG_COUNT_ENVIRONMENT);
 	if (env) {
@@ -773,11 +778,11 @@ out:
 	strbuf_release(&envvar);
 	strvec_clear(&to_free);
 	free(envw);
-	cf = source.prev;
+	cf_global = source.prev;
 	return ret;
 }
 
-static int get_next_char(void)
+static int get_next_char(struct config_source *cf)
 {
 	int c = cf->do_fgetc(cf);
 
@@ -813,13 +818,13 @@ static int get_next_char(void)
 	return c;
 }
 
-static char *parse_value(void)
+static char *parse_value(struct config_source *cf)
 {
 	int quote = 0, comment = 0, space = 0;
 
 	strbuf_reset(&cf->value);
 	for (;;) {
-		int c = get_next_char();
+		int c = get_next_char(cf);
 		if (c == '\n') {
 			if (quote) {
 				cf->linenr--;
@@ -843,7 +848,7 @@ static char *parse_value(void)
 		for (; space; space--)
 			strbuf_addch(&cf->value, ' ');
 		if (c == '\\') {
-			c = get_next_char();
+			c = get_next_char(cf);
 			switch (c) {
 			case '\n':
 				continue;
@@ -874,7 +879,8 @@ static char *parse_value(void)
 	}
 }
 
-static int get_value(config_fn_t fn, void *data, struct strbuf *name)
+static int get_value(struct config_source *cf, config_fn_t fn, void *data,
+		     struct strbuf *name)
 {
 	int c;
 	char *value;
@@ -882,7 +888,7 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
 
 	/* Get the full name */
 	for (;;) {
-		c = get_next_char();
+		c = get_next_char(cf);
 		if (cf->eof)
 			break;
 		if (!iskeychar(c))
@@ -891,13 +897,13 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
 	}
 
 	while (c == ' ' || c == '\t')
-		c = get_next_char();
+		c = get_next_char(cf);
 
 	value = NULL;
 	if (c != '\n') {
 		if (c != '=')
 			return -1;
-		value = parse_value();
+		value = parse_value(cf);
 		if (!value)
 			return -1;
 	}
@@ -913,13 +919,14 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
 	return ret;
 }
 
-static int get_extended_base_var(struct strbuf *name, int c)
+static int get_extended_base_var(struct config_source *cf, struct strbuf *name,
+				 int c)
 {
 	cf->subsection_case_sensitive = 0;
 	do {
 		if (c == '\n')
 			goto error_incomplete_line;
-		c = get_next_char();
+		c = get_next_char(cf);
 	} while (isspace(c));
 
 	/* We require the format to be '[base "extension"]' */
@@ -928,13 +935,13 @@ static int get_extended_base_var(struct strbuf *name, int c)
 	strbuf_addch(name, '.');
 
 	for (;;) {
-		int c = get_next_char();
+		int c = get_next_char(cf);
 		if (c == '\n')
 			goto error_incomplete_line;
 		if (c == '"')
 			break;
 		if (c == '\\') {
-			c = get_next_char();
+			c = get_next_char(cf);
 			if (c == '\n')
 				goto error_incomplete_line;
 		}
@@ -942,7 +949,7 @@ static int get_extended_base_var(struct strbuf *name, int c)
 	}
 
 	/* Final ']' */
-	if (get_next_char() != ']')
+	if (get_next_char(cf) != ']')
 		return -1;
 	return 0;
 error_incomplete_line:
@@ -950,17 +957,17 @@ error_incomplete_line:
 	return -1;
 }
 
-static int get_base_var(struct strbuf *name)
+static int get_base_var(struct config_source *cf, struct strbuf *name)
 {
 	cf->subsection_case_sensitive = 1;
 	for (;;) {
-		int c = get_next_char();
+		int c = get_next_char(cf);
 		if (cf->eof)
 			return -1;
 		if (c == ']')
 			return 0;
 		if (isspace(c))
-			return get_extended_base_var(name, c);
+			return get_extended_base_var(cf, name, c);
 		if (!iskeychar(c) && c != '.')
 			return -1;
 		strbuf_addch(name, tolower(c));
@@ -973,7 +980,8 @@ struct parse_event_data {
 	const struct config_options *opts;
 };
 
-static int do_event(enum config_event_t type, struct parse_event_data *data)
+static int do_event(struct config_source *cf, enum config_event_t type,
+		    struct parse_event_data *data)
 {
 	size_t offset;
 
@@ -1004,8 +1012,8 @@ static int do_event(enum config_event_t type, struct parse_event_data *data)
 	return 0;
 }
 
-static int git_parse_source(config_fn_t fn, void *data,
-			    const struct config_options *opts)
+static int git_parse_source(struct config_source *cf, config_fn_t fn,
+			    void *data, const struct config_options *opts)
 {
 	int comment = 0;
 	size_t baselen = 0;
@@ -1024,7 +1032,7 @@ static int git_parse_source(config_fn_t fn, void *data,
 	for (;;) {
 		int c;
 
-		c = get_next_char();
+		c = get_next_char(cf);
 		if (bomptr && *bomptr) {
 			/* We are at the file beginning; skip UTF8-encoded BOM
 			 * if present. Sane editors won't put this in on their
@@ -1042,11 +1050,11 @@ static int git_parse_source(config_fn_t fn, void *data,
 		}
 		if (c == '\n') {
 			if (cf->eof) {
-				if (do_event(CONFIG_EVENT_EOF, &event_data) < 0)
+				if (do_event(cf, CONFIG_EVENT_EOF, &event_data) < 0)
 					return -1;
 				return 0;
 			}
-			if (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)
+			if (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
 				return -1;
 			comment = 0;
 			continue;
@@ -1054,23 +1062,23 @@ static int git_parse_source(config_fn_t fn, void *data,
 		if (comment)
 			continue;
 		if (isspace(c)) {
-			if (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)
+			if (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
 					return -1;
 			continue;
 		}
 		if (c == '#' || c == ';') {
-			if (do_event(CONFIG_EVENT_COMMENT, &event_data) < 0)
+			if (do_event(cf, CONFIG_EVENT_COMMENT, &event_data) < 0)
 					return -1;
 			comment = 1;
 			continue;
 		}
 		if (c == '[') {
-			if (do_event(CONFIG_EVENT_SECTION, &event_data) < 0)
+			if (do_event(cf, CONFIG_EVENT_SECTION, &event_data) < 0)
 					return -1;
 
 			/* Reset prior to determining a new stem */
 			strbuf_reset(var);
-			if (get_base_var(var) < 0 || var->len < 1)
+			if (get_base_var(cf, var) < 0 || var->len < 1)
 				break;
 			strbuf_addch(var, '.');
 			baselen = var->len;
@@ -1079,7 +1087,7 @@ static int git_parse_source(config_fn_t fn, void *data,
 		if (!isalpha(c))
 			break;
 
-		if (do_event(CONFIG_EVENT_ENTRY, &event_data) < 0)
+		if (do_event(cf, CONFIG_EVENT_ENTRY, &event_data) < 0)
 			return -1;
 
 		/*
@@ -1089,11 +1097,11 @@ static int git_parse_source(config_fn_t fn, void *data,
 		 */
 		strbuf_setlen(var, baselen);
 		strbuf_addch(var, tolower(c));
-		if (get_value(fn, data, var) < 0)
+		if (get_value(cf, fn, data, var) < 0)
 			break;
 	}
 
-	if (do_event(CONFIG_EVENT_ERROR, &event_data) < 0)
+	if (do_event(cf, CONFIG_EVENT_ERROR, &event_data) < 0)
 		return -1;
 
 	switch (cf->origin_type) {
@@ -1266,7 +1274,8 @@ int git_parse_ssize_t(const char *value, ssize_t *ret)
 }
 
 NORETURN
-static void die_bad_number(const char *name, const char *value)
+static void die_bad_number(struct config_source *cf, const char *name,
+			   const char *value)
 {
 	const char *error_type = (errno == ERANGE) ?
 		N_("out of range") : N_("invalid unit");
@@ -1304,7 +1313,7 @@ int git_config_int(const char *name, const char *value)
 {
 	int ret;
 	if (!git_parse_int(value, &ret))
-		die_bad_number(name, value);
+		die_bad_number(cf_global, name, value);
 	return ret;
 }
 
@@ -1312,7 +1321,7 @@ int64_t git_config_int64(const char *name, const char *value)
 {
 	int64_t ret;
 	if (!git_parse_int64(value, &ret))
-		die_bad_number(name, value);
+		die_bad_number(cf_global, name, value);
 	return ret;
 }
 
@@ -1320,7 +1329,7 @@ unsigned long git_config_ulong(const char *name, const char *value)
 {
 	unsigned long ret;
 	if (!git_parse_ulong(value, &ret))
-		die_bad_number(name, value);
+		die_bad_number(cf_global, name, value);
 	return ret;
 }
 
@@ -1328,7 +1337,7 @@ ssize_t git_config_ssize_t(const char *name, const char *value)
 {
 	ssize_t ret;
 	if (!git_parse_ssize_t(value, &ret))
-		die_bad_number(name, value);
+		die_bad_number(cf_global, name, value);
 	return ret;
 }
 
@@ -1940,20 +1949,20 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data,
 	int ret;
 
 	/* push config-file parsing state stack */
-	top->prev = cf;
+	top->prev = cf_global;
 	top->linenr = 1;
 	top->eof = 0;
 	top->total_len = 0;
 	strbuf_init(&top->value, 1024);
 	strbuf_init(&top->var, 1024);
-	cf = top;
+	cf_global = top;
 
-	ret = git_parse_source(fn, data, opts);
+	ret = git_parse_source(top, fn, data, opts);
 
 	/* pop config-file parsing state stack */
 	strbuf_release(&top->value);
 	strbuf_release(&top->var);
-	cf = top->prev;
+	cf_global = top->prev;
 
 	return ret;
 }
@@ -2334,12 +2343,12 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha
 	l_item->e = e;
 	l_item->value_index = e->value_list.nr - 1;
 
-	if (!cf)
+	if (!cf_global)
 		BUG("configset_add_value has no source");
-	if (cf->name) {
-		kv_info->filename = strintern(cf->name);
-		kv_info->linenr = cf->linenr;
-		kv_info->origin_type = cf->origin_type;
+	if (cf_global->name) {
+		kv_info->filename = strintern(cf_global->name);
+		kv_info->linenr = cf_global->linenr;
+		kv_info->origin_type = cf_global->origin_type;
 	} else {
 		/* for values read from `git_config_from_parameters()` */
 		kv_info->filename = NULL;
@@ -2891,6 +2900,12 @@ static int store_aux_event(enum config_event_t type,
 			   size_t begin, size_t end, void *data)
 {
 	struct config_store_data *store = data;
+	/*
+	 * FIXME Keep using "cf" so that we can avoid rewrapping a
+	 * really long line below. Remove this when "cf" gets plumbed
+	 * correctly.
+	 */
+	struct config_source *cf = cf_global;
 
 	ALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc);
 	store->parsed[store->parsed_nr].begin = begin;
@@ -3771,8 +3786,8 @@ const char *current_config_origin_type(void)
 	int type;
 	if (current_config_kvi)
 		type = current_config_kvi->origin_type;
-	else if(cf)
-		type = cf->origin_type;
+	else if(cf_global)
+		type = cf_global->origin_type;
 	else
 		BUG("current_config_origin_type called outside config callback");
 
@@ -3817,8 +3832,8 @@ const char *current_config_name(void)
 	const char *name;
 	if (current_config_kvi)
 		name = current_config_kvi->filename;
-	else if (cf)
-		name = cf->name;
+	else if (cf_global)
+		name = cf_global->name;
 	else
 		BUG("current_config_name called outside config callback");
 	return name ? name : "";
@@ -3837,7 +3852,7 @@ int current_config_line(void)
 	if (current_config_kvi)
 		return current_config_kvi->linenr;
 	else
-		return cf->linenr;
+		return cf_global->linenr;
 }
 
 int lookup_config(const char **mapping, int nr_mapping, const char *var)
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH v3 2/8] config.c: don't assign to "cf_global" directly
  2023-03-28 17:51   ` [PATCH v3 0/8] config.c: use struct for config reading state Glen Choo via GitGitGadget
  2023-03-28 17:51     ` [PATCH v3 1/8] config.c: plumb config_source through static fns Glen Choo via GitGitGadget
@ 2023-03-28 17:51     ` Glen Choo via GitGitGadget
  2023-03-28 17:51     ` [PATCH v3 3/8] config.c: create config_reader and the_reader Glen Choo via GitGitGadget
                       ` (6 subsequent siblings)
  8 siblings, 0 replies; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-28 17:51 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Calvin Wan,
	Ævar Arnfjörð Bjarmason, Glen Choo, Glen Choo

From: Glen Choo <chooglen@google.com>

To make "cf_global" easier to remove, replace all direct assignments to
it with function calls. This refactor has an additional maintainability
benefit: all of these functions were manually implementing stack
pop/push semantics on "struct config_source", so replacing them with
function calls allows us to only implement this logic once.

In this process, perform some now-obvious clean ups:

- Drop some unnecessary "cf_global" assignments in
  populate_remote_urls(). Since it was introduced in 399b198489 (config:
  include file if remote URL matches a glob, 2022-01-18), it has stored
  and restored the value of "cf_global" to ensure that it doesn't get
  accidentally mutated. However, this was never necessary since
  "do_config_from()" already pushes/pops "cf_global" further down the
  call chain.

- Zero out every "struct config_source" with a dedicated initializer.
  This matters because the "struct config_source" is assigned to
  "cf_global" and we later 'pop the stack' by assigning "cf_global =
  cf_global->prev", but "cf_global->prev" could be pointing to
  uninitialized garbage.

  Fortunately, this has never bothered us since we never try to read
  "cf_global" except while iterating through config, in which case,
  "cf_global" is either set to a sensible value (when parsing a file),
  or it is ignored (when iterating a configset). Later in the series,
  zero-ing out memory will also let us enforce the constraint that
  "cf_global" and "current_config_kvi" are never non-NULL together.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 37 ++++++++++++++++++++++++-------------
 1 file changed, 24 insertions(+), 13 deletions(-)

diff --git a/config.c b/config.c
index e4a76739365..6627fad71cf 100644
--- a/config.c
+++ b/config.c
@@ -49,6 +49,7 @@ struct config_source {
 	int (*do_ungetc)(int c, struct config_source *conf);
 	long (*do_ftell)(struct config_source *c);
 };
+#define CONFIG_SOURCE_INIT { 0 }
 
 /*
  * These variables record the "current" config source, which
@@ -79,6 +80,22 @@ static struct key_value_info *current_config_kvi;
  */
 static enum config_scope current_parsing_scope;
 
+static inline void config_reader_push_source(struct config_source *top)
+{
+	top->prev = cf_global;
+	cf_global = top;
+}
+
+static inline struct config_source *config_reader_pop_source()
+{
+	struct config_source *ret;
+	if (!cf_global)
+		BUG("tried to pop config source, but we weren't reading config");
+	ret = cf_global;
+	cf_global = cf_global->prev;
+	return ret;
+}
+
 static int pack_compression_seen;
 static int zlib_compression_seen;
 
@@ -346,14 +363,12 @@ static void populate_remote_urls(struct config_include_data *inc)
 {
 	struct config_options opts;
 
-	struct config_source *store_cf = cf_global;
 	struct key_value_info *store_kvi = current_config_kvi;
 	enum config_scope store_scope = current_parsing_scope;
 
 	opts = *inc->opts;
 	opts.unconditional_remote_url = 1;
 
-	cf_global = NULL;
 	current_config_kvi = NULL;
 	current_parsing_scope = 0;
 
@@ -361,7 +376,6 @@ static void populate_remote_urls(struct config_include_data *inc)
 	string_list_init_dup(inc->remote_urls);
 	config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);
 
-	cf_global = store_cf;
 	current_config_kvi = store_kvi;
 	current_parsing_scope = store_scope;
 }
@@ -715,12 +729,10 @@ int git_config_from_parameters(config_fn_t fn, void *data)
 	struct strvec to_free = STRVEC_INIT;
 	int ret = 0;
 	char *envw = NULL;
-	struct config_source source;
+	struct config_source source = CONFIG_SOURCE_INIT;
 
-	memset(&source, 0, sizeof(source));
-	source.prev = cf_global;
 	source.origin_type = CONFIG_ORIGIN_CMDLINE;
-	cf_global = &source;
+	config_reader_push_source(&source);
 
 	env = getenv(CONFIG_COUNT_ENVIRONMENT);
 	if (env) {
@@ -778,7 +790,7 @@ out:
 	strbuf_release(&envvar);
 	strvec_clear(&to_free);
 	free(envw);
-	cf_global = source.prev;
+	config_reader_pop_source();
 	return ret;
 }
 
@@ -1949,20 +1961,19 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data,
 	int ret;
 
 	/* push config-file parsing state stack */
-	top->prev = cf_global;
 	top->linenr = 1;
 	top->eof = 0;
 	top->total_len = 0;
 	strbuf_init(&top->value, 1024);
 	strbuf_init(&top->var, 1024);
-	cf_global = top;
+	config_reader_push_source(top);
 
 	ret = git_parse_source(top, fn, data, opts);
 
 	/* pop config-file parsing state stack */
 	strbuf_release(&top->value);
 	strbuf_release(&top->var);
-	cf_global = top->prev;
+	config_reader_pop_source();
 
 	return ret;
 }
@@ -1972,7 +1983,7 @@ static int do_config_from_file(config_fn_t fn,
 		const char *name, const char *path, FILE *f,
 		void *data, const struct config_options *opts)
 {
-	struct config_source top;
+	struct config_source top = CONFIG_SOURCE_INIT;
 	int ret;
 
 	top.u.file = f;
@@ -2024,7 +2035,7 @@ int git_config_from_mem(config_fn_t fn,
 			const char *name, const char *buf, size_t len,
 			void *data, const struct config_options *opts)
 {
-	struct config_source top;
+	struct config_source top = CONFIG_SOURCE_INIT;
 
 	top.u.buf.buf = buf;
 	top.u.buf.len = len;
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH v3 3/8] config.c: create config_reader and the_reader
  2023-03-28 17:51   ` [PATCH v3 0/8] config.c: use struct for config reading state Glen Choo via GitGitGadget
  2023-03-28 17:51     ` [PATCH v3 1/8] config.c: plumb config_source through static fns Glen Choo via GitGitGadget
  2023-03-28 17:51     ` [PATCH v3 2/8] config.c: don't assign to "cf_global" directly Glen Choo via GitGitGadget
@ 2023-03-28 17:51     ` Glen Choo via GitGitGadget
  2023-03-29 10:41       ` Ævar Arnfjörð Bjarmason
  2023-03-28 17:51     ` [PATCH v3 4/8] config.c: plumb the_reader through callbacks Glen Choo via GitGitGadget
                       ` (5 subsequent siblings)
  8 siblings, 1 reply; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-28 17:51 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Calvin Wan,
	Ævar Arnfjörð Bjarmason, Glen Choo, Glen Choo

From: Glen Choo <chooglen@google.com>

Create "struct config_reader" to hold the state of the config source
currently being read. Then, create a static instance of it,
"the_reader", and use "the_reader.source" to replace references to
"cf_global" in public functions.

This doesn't create much immediate benefit (since we're mostly replacing
static variables with a bigger static variable), but it prepares us for
a future where this state doesn't have to be global; "struct
config_reader" (or a similar struct) could be provided by the caller, or
constructed internally by a function like "do_config_from()".

A more typical approach would be to put this struct on "the_repository",
but that's a worse fit for this use case since config reading is not
scoped to a repository. E.g. we can read config before the repository is
known ("read_very_early_config()"), blatantly ignore the repo
("read_protected_config()"), or read only from a file
("git_config_from_file()"). This is especially evident in t5318 and
t9210, where test-tool and scalar parse config but don't fully
initialize "the_repository".

We could have also replaced the references to "cf_global" in callback
functions (which are the only ones left), but we'll eventually plumb
"the_reader" through the callback "*data" arg, so that would be
unnecessary churn. Until we remove "cf_global" altogether, add logic to
"config_reader_*_source()" to keep "cf_global" and "the_reader.source"
in sync.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 82 +++++++++++++++++++++++++++++++++++---------------------
 1 file changed, 51 insertions(+), 31 deletions(-)

diff --git a/config.c b/config.c
index 6627fad71cf..3a28b397c4d 100644
--- a/config.c
+++ b/config.c
@@ -51,6 +51,16 @@ struct config_source {
 };
 #define CONFIG_SOURCE_INIT { 0 }
 
+struct config_reader {
+	struct config_source *source;
+};
+/*
+ * Where possible, prefer to accept "struct config_reader" as an arg than to use
+ * "the_reader". "the_reader" should only be used if that is infeasible, e.g. in
+ * a public function.
+ */
+static struct config_reader the_reader;
+
 /*
  * These variables record the "current" config source, which
  * can be accessed by parsing callbacks.
@@ -66,6 +76,9 @@ struct config_source {
  * at the variables, it's either a bug for it to be called in the first place,
  * or it's a function which can be reused for non-config purposes, and should
  * fall back to some sane behavior).
+ *
+ * FIXME "cf_global" has been replaced by "the_reader.source", remove
+ * "cf_global" once we plumb "the_reader" through all of the callback functions.
  */
 static struct config_source *cf_global;
 static struct key_value_info *current_config_kvi;
@@ -80,19 +93,24 @@ static struct key_value_info *current_config_kvi;
  */
 static enum config_scope current_parsing_scope;
 
-static inline void config_reader_push_source(struct config_source *top)
+static inline void config_reader_push_source(struct config_reader *reader,
+					     struct config_source *top)
 {
-	top->prev = cf_global;
-	cf_global = top;
+	top->prev = reader->source;
+	reader->source = top;
+	/* FIXME remove this when cf_global is removed. */
+	cf_global = reader->source;
 }
 
-static inline struct config_source *config_reader_pop_source()
+static inline struct config_source *config_reader_pop_source(struct config_reader *reader)
 {
 	struct config_source *ret;
-	if (!cf_global)
+	if (!reader->source)
 		BUG("tried to pop config source, but we weren't reading config");
-	ret = cf_global;
-	cf_global = cf_global->prev;
+	ret = reader->source;
+	reader->source = reader->source->prev;
+	/* FIXME remove this when cf_global is removed. */
+	cf_global = reader->source;
 	return ret;
 }
 
@@ -732,7 +750,7 @@ int git_config_from_parameters(config_fn_t fn, void *data)
 	struct config_source source = CONFIG_SOURCE_INIT;
 
 	source.origin_type = CONFIG_ORIGIN_CMDLINE;
-	config_reader_push_source(&source);
+	config_reader_push_source(&the_reader, &source);
 
 	env = getenv(CONFIG_COUNT_ENVIRONMENT);
 	if (env) {
@@ -790,7 +808,7 @@ out:
 	strbuf_release(&envvar);
 	strvec_clear(&to_free);
 	free(envw);
-	config_reader_pop_source();
+	config_reader_pop_source(&the_reader);
 	return ret;
 }
 
@@ -1325,7 +1343,7 @@ int git_config_int(const char *name, const char *value)
 {
 	int ret;
 	if (!git_parse_int(value, &ret))
-		die_bad_number(cf_global, name, value);
+		die_bad_number(the_reader.source, name, value);
 	return ret;
 }
 
@@ -1333,7 +1351,7 @@ int64_t git_config_int64(const char *name, const char *value)
 {
 	int64_t ret;
 	if (!git_parse_int64(value, &ret))
-		die_bad_number(cf_global, name, value);
+		die_bad_number(the_reader.source, name, value);
 	return ret;
 }
 
@@ -1341,7 +1359,7 @@ unsigned long git_config_ulong(const char *name, const char *value)
 {
 	unsigned long ret;
 	if (!git_parse_ulong(value, &ret))
-		die_bad_number(cf_global, name, value);
+		die_bad_number(the_reader.source, name, value);
 	return ret;
 }
 
@@ -1349,7 +1367,7 @@ ssize_t git_config_ssize_t(const char *name, const char *value)
 {
 	ssize_t ret;
 	if (!git_parse_ssize_t(value, &ret))
-		die_bad_number(cf_global, name, value);
+		die_bad_number(the_reader.source, name, value);
 	return ret;
 }
 
@@ -1955,7 +1973,8 @@ int git_default_config(const char *var, const char *value, void *cb)
  * fgetc, ungetc, ftell of top need to be initialized before calling
  * this function.
  */
-static int do_config_from(struct config_source *top, config_fn_t fn, void *data,
+static int do_config_from(struct config_reader *reader,
+			  struct config_source *top, config_fn_t fn, void *data,
 			  const struct config_options *opts)
 {
 	int ret;
@@ -1966,22 +1985,23 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data,
 	top->total_len = 0;
 	strbuf_init(&top->value, 1024);
 	strbuf_init(&top->var, 1024);
-	config_reader_push_source(top);
+	config_reader_push_source(reader, top);
 
 	ret = git_parse_source(top, fn, data, opts);
 
 	/* pop config-file parsing state stack */
 	strbuf_release(&top->value);
 	strbuf_release(&top->var);
-	config_reader_pop_source();
+	config_reader_pop_source(reader);
 
 	return ret;
 }
 
-static int do_config_from_file(config_fn_t fn,
-		const enum config_origin_type origin_type,
-		const char *name, const char *path, FILE *f,
-		void *data, const struct config_options *opts)
+static int do_config_from_file(struct config_reader *reader,
+			       config_fn_t fn,
+			       const enum config_origin_type origin_type,
+			       const char *name, const char *path, FILE *f,
+			       void *data, const struct config_options *opts)
 {
 	struct config_source top = CONFIG_SOURCE_INIT;
 	int ret;
@@ -1996,15 +2016,15 @@ static int do_config_from_file(config_fn_t fn,
 	top.do_ftell = config_file_ftell;
 
 	flockfile(f);
-	ret = do_config_from(&top, fn, data, opts);
+	ret = do_config_from(reader, &top, fn, data, opts);
 	funlockfile(f);
 	return ret;
 }
 
 static int git_config_from_stdin(config_fn_t fn, void *data)
 {
-	return do_config_from_file(fn, CONFIG_ORIGIN_STDIN, "", NULL, stdin,
-				   data, NULL);
+	return do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_STDIN, "",
+				   NULL, stdin, data, NULL);
 }
 
 int git_config_from_file_with_options(config_fn_t fn, const char *filename,
@@ -2018,8 +2038,8 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,
 		BUG("filename cannot be NULL");
 	f = fopen_or_warn(filename, "r");
 	if (f) {
-		ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename,
-					  filename, f, data, opts);
+		ret = do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_FILE,
+					  filename, filename, f, data, opts);
 		fclose(f);
 	}
 	return ret;
@@ -2048,7 +2068,7 @@ int git_config_from_mem(config_fn_t fn,
 	top.do_ungetc = config_buf_ungetc;
 	top.do_ftell = config_buf_ftell;
 
-	return do_config_from(&top, fn, data, opts);
+	return do_config_from(&the_reader, &top, fn, data, opts);
 }
 
 int git_config_from_blob_oid(config_fn_t fn,
@@ -3797,8 +3817,8 @@ const char *current_config_origin_type(void)
 	int type;
 	if (current_config_kvi)
 		type = current_config_kvi->origin_type;
-	else if(cf_global)
-		type = cf_global->origin_type;
+	else if(the_reader.source)
+		type = the_reader.source->origin_type;
 	else
 		BUG("current_config_origin_type called outside config callback");
 
@@ -3843,8 +3863,8 @@ const char *current_config_name(void)
 	const char *name;
 	if (current_config_kvi)
 		name = current_config_kvi->filename;
-	else if (cf_global)
-		name = cf_global->name;
+	else if (the_reader.source)
+		name = the_reader.source->name;
 	else
 		BUG("current_config_name called outside config callback");
 	return name ? name : "";
@@ -3863,7 +3883,7 @@ int current_config_line(void)
 	if (current_config_kvi)
 		return current_config_kvi->linenr;
 	else
-		return cf_global->linenr;
+		return the_reader.source->linenr;
 }
 
 int lookup_config(const char **mapping, int nr_mapping, const char *var)
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH v3 4/8] config.c: plumb the_reader through callbacks
  2023-03-28 17:51   ` [PATCH v3 0/8] config.c: use struct for config reading state Glen Choo via GitGitGadget
                       ` (2 preceding siblings ...)
  2023-03-28 17:51     ` [PATCH v3 3/8] config.c: create config_reader and the_reader Glen Choo via GitGitGadget
@ 2023-03-28 17:51     ` Glen Choo via GitGitGadget
  2023-03-28 17:51     ` [PATCH v3 5/8] config.c: remove current_config_kvi Glen Choo via GitGitGadget
                       ` (4 subsequent siblings)
  8 siblings, 0 replies; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-28 17:51 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Calvin Wan,
	Ævar Arnfjörð Bjarmason, Glen Choo, Glen Choo

From: Glen Choo <chooglen@google.com>

The remaining references to "cf_global" are in config callback
functions. Remove them by plumbing "struct config_reader" via the
"*data" arg.

In both of the callbacks here, we are only reading from
"reader->source". So in the long run, if we had a way to expose readonly
information from "reader->source" (probably in the form of "struct
key_value_info"), we could undo this patch (i.e. remove "struct
config_reader" fom "*data").

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 74 ++++++++++++++++++++++++++++++++------------------------
 1 file changed, 43 insertions(+), 31 deletions(-)

diff --git a/config.c b/config.c
index 3a28b397c4d..cb6ff134f5f 100644
--- a/config.c
+++ b/config.c
@@ -62,6 +62,9 @@ struct config_reader {
 static struct config_reader the_reader;
 
 /*
+ * FIXME The comments are temporarily out of date since "cf_global" has been
+ * moved to the_reader, but not current_*.
+ *
  * These variables record the "current" config source, which
  * can be accessed by parsing callbacks.
  *
@@ -76,11 +79,7 @@ static struct config_reader the_reader;
  * at the variables, it's either a bug for it to be called in the first place,
  * or it's a function which can be reused for non-config purposes, and should
  * fall back to some sane behavior).
- *
- * FIXME "cf_global" has been replaced by "the_reader.source", remove
- * "cf_global" once we plumb "the_reader" through all of the callback functions.
  */
-static struct config_source *cf_global;
 static struct key_value_info *current_config_kvi;
 
 /*
@@ -98,8 +97,6 @@ static inline void config_reader_push_source(struct config_reader *reader,
 {
 	top->prev = reader->source;
 	reader->source = top;
-	/* FIXME remove this when cf_global is removed. */
-	cf_global = reader->source;
 }
 
 static inline struct config_source *config_reader_pop_source(struct config_reader *reader)
@@ -109,8 +106,6 @@ static inline struct config_source *config_reader_pop_source(struct config_reade
 		BUG("tried to pop config source, but we weren't reading config");
 	ret = reader->source;
 	reader->source = reader->source->prev;
-	/* FIXME remove this when cf_global is removed. */
-	cf_global = reader->source;
 	return ret;
 }
 
@@ -175,6 +170,7 @@ struct config_include_data {
 	void *data;
 	const struct config_options *opts;
 	struct git_config_source *config_source;
+	struct config_reader *config_reader;
 
 	/*
 	 * All remote URLs discovered when reading all config files.
@@ -465,6 +461,7 @@ static int include_condition_is_true(struct config_source *cf,
 static int git_config_include(const char *var, const char *value, void *data)
 {
 	struct config_include_data *inc = data;
+	struct config_source *cf = inc->config_reader->source;
 	const char *cond, *key;
 	size_t cond_len;
 	int ret;
@@ -478,16 +475,16 @@ static int git_config_include(const char *var, const char *value, void *data)
 		return ret;
 
 	if (!strcmp(var, "include.path"))
-		ret = handle_path_include(cf_global, value, inc);
+		ret = handle_path_include(cf, value, inc);
 
 	if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
-	    cond && include_condition_is_true(cf_global, inc, cond, cond_len) &&
+	    cond && include_condition_is_true(cf, inc, cond, cond_len) &&
 	    !strcmp(key, "path")) {
 		config_fn_t old_fn = inc->fn;
 
 		if (inc->opts->unconditional_remote_url)
 			inc->fn = forbid_remote_url;
-		ret = handle_path_include(cf_global, value, inc);
+		ret = handle_path_include(cf, value, inc);
 		inc->fn = old_fn;
 	}
 
@@ -2228,6 +2225,7 @@ int config_with_options(config_fn_t fn, void *data,
 		inc.data = data;
 		inc.opts = opts;
 		inc.config_source = config_source;
+		inc.config_reader = &the_reader;
 		fn = git_config_include;
 		data = &inc;
 	}
@@ -2348,7 +2346,9 @@ static struct config_set_element *configset_find_element(struct config_set *cs,
 	return found_entry;
 }
 
-static int configset_add_value(struct config_set *cs, const char *key, const char *value)
+static int configset_add_value(struct config_reader *reader,
+			       struct config_set *cs, const char *key,
+			       const char *value)
 {
 	struct config_set_element *e;
 	struct string_list_item *si;
@@ -2374,12 +2374,12 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha
 	l_item->e = e;
 	l_item->value_index = e->value_list.nr - 1;
 
-	if (!cf_global)
+	if (!reader->source)
 		BUG("configset_add_value has no source");
-	if (cf_global->name) {
-		kv_info->filename = strintern(cf_global->name);
-		kv_info->linenr = cf_global->linenr;
-		kv_info->origin_type = cf_global->origin_type;
+	if (reader->source->name) {
+		kv_info->filename = strintern(reader->source->name);
+		kv_info->linenr = reader->source->linenr;
+		kv_info->origin_type = reader->source->origin_type;
 	} else {
 		/* for values read from `git_config_from_parameters()` */
 		kv_info->filename = NULL;
@@ -2434,16 +2434,25 @@ void git_configset_clear(struct config_set *cs)
 	cs->list.items = NULL;
 }
 
+struct configset_add_data {
+	struct config_set *config_set;
+	struct config_reader *config_reader;
+};
+#define CONFIGSET_ADD_INIT { 0 }
+
 static int config_set_callback(const char *key, const char *value, void *cb)
 {
-	struct config_set *cs = cb;
-	configset_add_value(cs, key, value);
+	struct configset_add_data *data = cb;
+	configset_add_value(data->config_reader, data->config_set, key, value);
 	return 0;
 }
 
 int git_configset_add_file(struct config_set *cs, const char *filename)
 {
-	return git_config_from_file(config_set_callback, filename, cs);
+	struct configset_add_data data = CONFIGSET_ADD_INIT;
+	data.config_reader = &the_reader;
+	data.config_set = cs;
+	return git_config_from_file(config_set_callback, filename, &data);
 }
 
 int git_configset_get_value(struct config_set *cs, const char *key, const char **value)
@@ -2558,6 +2567,7 @@ int git_configset_get_pathname(struct config_set *cs, const char *key, const cha
 static void repo_read_config(struct repository *repo)
 {
 	struct config_options opts = { 0 };
+	struct configset_add_data data = CONFIGSET_ADD_INIT;
 
 	opts.respect_includes = 1;
 	opts.commondir = repo->commondir;
@@ -2569,8 +2579,10 @@ static void repo_read_config(struct repository *repo)
 		git_configset_clear(repo->config);
 
 	git_configset_init(repo->config);
+	data.config_set = repo->config;
+	data.config_reader = &the_reader;
 
-	if (config_with_options(config_set_callback, repo->config, NULL, &opts) < 0)
+	if (config_with_options(config_set_callback, &data, NULL, &opts) < 0)
 		/*
 		 * config_with_options() normally returns only
 		 * zero, as most errors are fatal, and
@@ -2696,9 +2708,12 @@ static void read_protected_config(void)
 		.ignore_worktree = 1,
 		.system_gently = 1,
 	};
+	struct configset_add_data data = CONFIGSET_ADD_INIT;
+
 	git_configset_init(&protected_config);
-	config_with_options(config_set_callback, &protected_config,
-			    NULL, &opts);
+	data.config_set = &protected_config;
+	data.config_reader = &the_reader;
+	config_with_options(config_set_callback, &data, NULL, &opts);
 }
 
 void git_protected_config(config_fn_t fn, void *data)
@@ -2883,6 +2898,7 @@ void git_die_config(const char *key, const char *err, ...)
  */
 
 struct config_store_data {
+	struct config_reader *config_reader;
 	size_t baselen;
 	char *key;
 	int do_not_match;
@@ -2897,6 +2913,7 @@ struct config_store_data {
 	unsigned int parsed_nr, parsed_alloc, *seen, seen_nr, seen_alloc;
 	unsigned int key_seen:1, section_seen:1, is_keys_section:1;
 };
+#define CONFIG_STORE_INIT { 0 }
 
 static void config_store_data_clear(struct config_store_data *store)
 {
@@ -2931,12 +2948,7 @@ static int store_aux_event(enum config_event_t type,
 			   size_t begin, size_t end, void *data)
 {
 	struct config_store_data *store = data;
-	/*
-	 * FIXME Keep using "cf" so that we can avoid rewrapping a
-	 * really long line below. Remove this when "cf" gets plumbed
-	 * correctly.
-	 */
-	struct config_source *cf = cf_global;
+	struct config_source *cf = store->config_reader->source;
 
 	ALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc);
 	store->parsed[store->parsed_nr].begin = begin;
@@ -3254,9 +3266,9 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
 	char *filename_buf = NULL;
 	char *contents = NULL;
 	size_t contents_sz;
-	struct config_store_data store;
+	struct config_store_data store = CONFIG_STORE_INIT;
 
-	memset(&store, 0, sizeof(store));
+	store.config_reader = &the_reader;
 
 	/* parse-key returns negative; flip the sign to feed exit(3) */
 	ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH v3 5/8] config.c: remove current_config_kvi
  2023-03-28 17:51   ` [PATCH v3 0/8] config.c: use struct for config reading state Glen Choo via GitGitGadget
                       ` (3 preceding siblings ...)
  2023-03-28 17:51     ` [PATCH v3 4/8] config.c: plumb the_reader through callbacks Glen Choo via GitGitGadget
@ 2023-03-28 17:51     ` Glen Choo via GitGitGadget
  2023-03-28 17:51     ` [PATCH v3 6/8] config.c: remove current_parsing_scope Glen Choo via GitGitGadget
                       ` (3 subsequent siblings)
  8 siblings, 0 replies; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-28 17:51 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Calvin Wan,
	Ævar Arnfjörð Bjarmason, Glen Choo, Glen Choo

From: Glen Choo <chooglen@google.com>

Add ".config_kvi" to "struct config_reader" and replace
"current_config_kvi" with "the_reader.config_kvi", plumbing "struct
config_reader" where necesssary.

Also, introduce a setter function for ".config_kvi", which allows us to
enforce the contraint that only one of ".source" and ".config_kvi" can
be set at a time (as documented in the comments). Because of this
constraint, we know that "populate_remote_urls()" was never touching
"current_config_kvi" when iterating through config files, so it doesn't
need to store and restore that value.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 82 +++++++++++++++++++++++++++++---------------------------
 1 file changed, 43 insertions(+), 39 deletions(-)

diff --git a/config.c b/config.c
index cb6ff134f5f..71ee36f069b 100644
--- a/config.c
+++ b/config.c
@@ -52,7 +52,24 @@ struct config_source {
 #define CONFIG_SOURCE_INIT { 0 }
 
 struct config_reader {
+	/*
+	 * These members record the "current" config source, which can be
+	 * accessed by parsing callbacks.
+	 *
+	 * The "source" variable will be non-NULL only when we are actually
+	 * parsing a real config source (file, blob, cmdline, etc).
+	 *
+	 * The "config_kvi" variable will be non-NULL only when we are feeding
+	 * cached config from a configset into a callback.
+	 *
+	 * They cannot be non-NULL at the same time. If they are both NULL, then
+	 * we aren't parsing anything (and depending on the function looking at
+	 * the variables, it's either a bug for it to be called in the first
+	 * place, or it's a function which can be reused for non-config
+	 * purposes, and should fall back to some sane behavior).
+	 */
 	struct config_source *source;
+	struct key_value_info *config_kvi;
 };
 /*
  * Where possible, prefer to accept "struct config_reader" as an arg than to use
@@ -61,27 +78,6 @@ struct config_reader {
  */
 static struct config_reader the_reader;
 
-/*
- * FIXME The comments are temporarily out of date since "cf_global" has been
- * moved to the_reader, but not current_*.
- *
- * These variables record the "current" config source, which
- * can be accessed by parsing callbacks.
- *
- * The "cf_global" variable will be non-NULL only when we are actually
- * parsing a real config source (file, blob, cmdline, etc).
- *
- * The "current_config_kvi" variable will be non-NULL only when we are feeding
- * cached config from a configset into a callback.
- *
- * They should generally never be non-NULL at the same time. If they are both
- * NULL, then we aren't parsing anything (and depending on the function looking
- * at the variables, it's either a bug for it to be called in the first place,
- * or it's a function which can be reused for non-config purposes, and should
- * fall back to some sane behavior).
- */
-static struct key_value_info *current_config_kvi;
-
 /*
  * Similar to the variables above, this gives access to the "scope" of the
  * current value (repo, global, etc). For cached values, it can be found via
@@ -95,6 +91,8 @@ static enum config_scope current_parsing_scope;
 static inline void config_reader_push_source(struct config_reader *reader,
 					     struct config_source *top)
 {
+	if (reader->config_kvi)
+		BUG("source should not be set while iterating a config set");
 	top->prev = reader->source;
 	reader->source = top;
 }
@@ -109,6 +107,14 @@ static inline struct config_source *config_reader_pop_source(struct config_reade
 	return ret;
 }
 
+static inline void config_reader_set_kvi(struct config_reader *reader,
+					 struct key_value_info *kvi)
+{
+	if (kvi && reader->source)
+		BUG("kvi should not be set while parsing a config source");
+	reader->config_kvi = kvi;
+}
+
 static int pack_compression_seen;
 static int zlib_compression_seen;
 
@@ -377,20 +383,17 @@ static void populate_remote_urls(struct config_include_data *inc)
 {
 	struct config_options opts;
 
-	struct key_value_info *store_kvi = current_config_kvi;
 	enum config_scope store_scope = current_parsing_scope;
 
 	opts = *inc->opts;
 	opts.unconditional_remote_url = 1;
 
-	current_config_kvi = NULL;
 	current_parsing_scope = 0;
 
 	inc->remote_urls = xmalloc(sizeof(*inc->remote_urls));
 	string_list_init_dup(inc->remote_urls);
 	config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);
 
-	current_config_kvi = store_kvi;
 	current_parsing_scope = store_scope;
 }
 
@@ -2257,7 +2260,8 @@ int config_with_options(config_fn_t fn, void *data,
 	return ret;
 }
 
-static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
+static void configset_iter(struct config_reader *reader, struct config_set *cs,
+			   config_fn_t fn, void *data)
 {
 	int i, value_index;
 	struct string_list *values;
@@ -2269,14 +2273,14 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
 		value_index = list->items[i].value_index;
 		values = &entry->value_list;
 
-		current_config_kvi = values->items[value_index].util;
+		config_reader_set_kvi(reader, values->items[value_index].util);
 
 		if (fn(entry->key, values->items[value_index].string, data) < 0)
 			git_die_config_linenr(entry->key,
-					      current_config_kvi->filename,
-					      current_config_kvi->linenr);
+					      reader->config_kvi->filename,
+					      reader->config_kvi->linenr);
 
-		current_config_kvi = NULL;
+		config_reader_set_kvi(reader, NULL);
 	}
 }
 
@@ -2614,7 +2618,7 @@ static void repo_config_clear(struct repository *repo)
 void repo_config(struct repository *repo, config_fn_t fn, void *data)
 {
 	git_config_check_init(repo);
-	configset_iter(repo->config, fn, data);
+	configset_iter(&the_reader, repo->config, fn, data);
 }
 
 int repo_config_get_value(struct repository *repo,
@@ -2720,7 +2724,7 @@ void git_protected_config(config_fn_t fn, void *data)
 {
 	if (!protected_config.hash_initialized)
 		read_protected_config();
-	configset_iter(&protected_config, fn, data);
+	configset_iter(&the_reader, &protected_config, fn, data);
 }
 
 /* Functions used historically to read configuration from 'the_repository' */
@@ -3827,8 +3831,8 @@ int parse_config_key(const char *var,
 const char *current_config_origin_type(void)
 {
 	int type;
-	if (current_config_kvi)
-		type = current_config_kvi->origin_type;
+	if (the_reader.config_kvi)
+		type = the_reader.config_kvi->origin_type;
 	else if(the_reader.source)
 		type = the_reader.source->origin_type;
 	else
@@ -3873,8 +3877,8 @@ const char *config_scope_name(enum config_scope scope)
 const char *current_config_name(void)
 {
 	const char *name;
-	if (current_config_kvi)
-		name = current_config_kvi->filename;
+	if (the_reader.config_kvi)
+		name = the_reader.config_kvi->filename;
 	else if (the_reader.source)
 		name = the_reader.source->name;
 	else
@@ -3884,16 +3888,16 @@ const char *current_config_name(void)
 
 enum config_scope current_config_scope(void)
 {
-	if (current_config_kvi)
-		return current_config_kvi->scope;
+	if (the_reader.config_kvi)
+		return the_reader.config_kvi->scope;
 	else
 		return current_parsing_scope;
 }
 
 int current_config_line(void)
 {
-	if (current_config_kvi)
-		return current_config_kvi->linenr;
+	if (the_reader.config_kvi)
+		return the_reader.config_kvi->linenr;
 	else
 		return the_reader.source->linenr;
 }
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH v3 6/8] config.c: remove current_parsing_scope
  2023-03-28 17:51   ` [PATCH v3 0/8] config.c: use struct for config reading state Glen Choo via GitGitGadget
                       ` (4 preceding siblings ...)
  2023-03-28 17:51     ` [PATCH v3 5/8] config.c: remove current_config_kvi Glen Choo via GitGitGadget
@ 2023-03-28 17:51     ` Glen Choo via GitGitGadget
  2023-03-28 17:51     ` [PATCH v3 7/8] config: report cached filenames in die_bad_number() Glen Choo via GitGitGadget
                       ` (2 subsequent siblings)
  8 siblings, 0 replies; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-28 17:51 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Calvin Wan,
	Ævar Arnfjörð Bjarmason, Glen Choo, Glen Choo

From: Glen Choo <chooglen@google.com>

Add ".parsing_scope" to "struct config_reader" and replace
"current_parsing_scope" with "the_reader.parsing_scope. Adjust the
comment slightly to make it clearer that the scope applies to the config
source (not the current value), and should only be set when parsing a
config source.

As such, ".parsing_scope" (only set when parsing config sources) and
".config_kvi" (only set when iterating a config set) should not be
set together, so enforce this with a setter function.

Unlike previous commits, "populate_remote_urls()" still needs to store
and restore the 'scope' value because it could have touched
"current_parsing_scope" ("config_with_options()" can set the scope).

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 63 +++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 37 insertions(+), 26 deletions(-)

diff --git a/config.c b/config.c
index 71ee36f069b..3756322ec96 100644
--- a/config.c
+++ b/config.c
@@ -70,6 +70,16 @@ struct config_reader {
 	 */
 	struct config_source *source;
 	struct key_value_info *config_kvi;
+	/*
+	 * The "scope" of the current config source being parsed (repo, global,
+	 * etc). Like "source", this is only set when parsing a config source.
+	 * It's not part of "source" because it transcends a single file (i.e.,
+	 * a file included from .git/config is still in "repo" scope).
+	 *
+	 * When iterating through a configset, the equivalent value is
+	 * "config_kvi.scope" (see above).
+	 */
+	enum config_scope parsing_scope;
 };
 /*
  * Where possible, prefer to accept "struct config_reader" as an arg than to use
@@ -78,16 +88,6 @@ struct config_reader {
  */
 static struct config_reader the_reader;
 
-/*
- * Similar to the variables above, this gives access to the "scope" of the
- * current value (repo, global, etc). For cached values, it can be found via
- * the current_config_kvi as above. During parsing, the current value can be
- * found in this variable. It's not part of "cf_global" because it transcends a
- * single file (i.e., a file included from .git/config is still in "repo"
- * scope).
- */
-static enum config_scope current_parsing_scope;
-
 static inline void config_reader_push_source(struct config_reader *reader,
 					     struct config_source *top)
 {
@@ -110,11 +110,19 @@ static inline struct config_source *config_reader_pop_source(struct config_reade
 static inline void config_reader_set_kvi(struct config_reader *reader,
 					 struct key_value_info *kvi)
 {
-	if (kvi && reader->source)
+	if (kvi && (reader->source || reader->parsing_scope))
 		BUG("kvi should not be set while parsing a config source");
 	reader->config_kvi = kvi;
 }
 
+static inline void config_reader_set_scope(struct config_reader *reader,
+					   enum config_scope scope)
+{
+	if (scope && reader->config_kvi)
+		BUG("scope should only be set when iterating through a config source");
+	reader->parsing_scope = scope;
+}
+
 static int pack_compression_seen;
 static int zlib_compression_seen;
 
@@ -383,18 +391,18 @@ static void populate_remote_urls(struct config_include_data *inc)
 {
 	struct config_options opts;
 
-	enum config_scope store_scope = current_parsing_scope;
+	enum config_scope store_scope = inc->config_reader->parsing_scope;
 
 	opts = *inc->opts;
 	opts.unconditional_remote_url = 1;
 
-	current_parsing_scope = 0;
+	config_reader_set_scope(inc->config_reader, 0);
 
 	inc->remote_urls = xmalloc(sizeof(*inc->remote_urls));
 	string_list_init_dup(inc->remote_urls);
 	config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);
 
-	current_parsing_scope = store_scope;
+	config_reader_set_scope(inc->config_reader, store_scope);
 }
 
 static int forbid_remote_url(const char *var, const char *value UNUSED,
@@ -2159,7 +2167,8 @@ int git_config_system(void)
 	return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
 }
 
-static int do_git_config_sequence(const struct config_options *opts,
+static int do_git_config_sequence(struct config_reader *reader,
+				  const struct config_options *opts,
 				  config_fn_t fn, void *data)
 {
 	int ret = 0;
@@ -2167,7 +2176,7 @@ static int do_git_config_sequence(const struct config_options *opts,
 	char *xdg_config = NULL;
 	char *user_config = NULL;
 	char *repo_config;
-	enum config_scope prev_parsing_scope = current_parsing_scope;
+	enum config_scope prev_parsing_scope = reader->parsing_scope;
 
 	if (opts->commondir)
 		repo_config = mkpathdup("%s/config", opts->commondir);
@@ -2176,13 +2185,13 @@ static int do_git_config_sequence(const struct config_options *opts,
 	else
 		repo_config = NULL;
 
-	current_parsing_scope = CONFIG_SCOPE_SYSTEM;
+	config_reader_set_scope(reader, CONFIG_SCOPE_SYSTEM);
 	if (git_config_system() && system_config &&
 	    !access_or_die(system_config, R_OK,
 			   opts->system_gently ? ACCESS_EACCES_OK : 0))
 		ret += git_config_from_file(fn, system_config, data);
 
-	current_parsing_scope = CONFIG_SCOPE_GLOBAL;
+	config_reader_set_scope(reader, CONFIG_SCOPE_GLOBAL);
 	git_global_config(&user_config, &xdg_config);
 
 	if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))
@@ -2191,12 +2200,12 @@ static int do_git_config_sequence(const struct config_options *opts,
 	if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))
 		ret += git_config_from_file(fn, user_config, data);
 
-	current_parsing_scope = CONFIG_SCOPE_LOCAL;
+	config_reader_set_scope(reader, CONFIG_SCOPE_LOCAL);
 	if (!opts->ignore_repo && repo_config &&
 	    !access_or_die(repo_config, R_OK, 0))
 		ret += git_config_from_file(fn, repo_config, data);
 
-	current_parsing_scope = CONFIG_SCOPE_WORKTREE;
+	config_reader_set_scope(reader, CONFIG_SCOPE_WORKTREE);
 	if (!opts->ignore_worktree && repository_format_worktree_config) {
 		char *path = git_pathdup("config.worktree");
 		if (!access_or_die(path, R_OK, 0))
@@ -2204,11 +2213,11 @@ static int do_git_config_sequence(const struct config_options *opts,
 		free(path);
 	}
 
-	current_parsing_scope = CONFIG_SCOPE_COMMAND;
+	config_reader_set_scope(reader, CONFIG_SCOPE_COMMAND);
 	if (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)
 		die(_("unable to parse command-line config"));
 
-	current_parsing_scope = prev_parsing_scope;
+	config_reader_set_scope(reader, prev_parsing_scope);
 	free(system_config);
 	free(xdg_config);
 	free(user_config);
@@ -2221,6 +2230,7 @@ int config_with_options(config_fn_t fn, void *data,
 			const struct config_options *opts)
 {
 	struct config_include_data inc = CONFIG_INCLUDE_INIT;
+	enum config_scope prev_scope = the_reader.parsing_scope;
 	int ret;
 
 	if (opts->respect_includes) {
@@ -2234,7 +2244,7 @@ int config_with_options(config_fn_t fn, void *data,
 	}
 
 	if (config_source)
-		current_parsing_scope = config_source->scope;
+		config_reader_set_scope(&the_reader, config_source->scope);
 
 	/*
 	 * If we have a specific filename, use it. Otherwise, follow the
@@ -2250,13 +2260,14 @@ int config_with_options(config_fn_t fn, void *data,
 		ret = git_config_from_blob_ref(fn, repo, config_source->blob,
 						data);
 	} else {
-		ret = do_git_config_sequence(opts, fn, data);
+		ret = do_git_config_sequence(&the_reader, opts, fn, data);
 	}
 
 	if (inc.remote_urls) {
 		string_list_clear(inc.remote_urls, 0);
 		FREE_AND_NULL(inc.remote_urls);
 	}
+	config_reader_set_scope(&the_reader, prev_scope);
 	return ret;
 }
 
@@ -2390,7 +2401,7 @@ static int configset_add_value(struct config_reader *reader,
 		kv_info->linenr = -1;
 		kv_info->origin_type = CONFIG_ORIGIN_CMDLINE;
 	}
-	kv_info->scope = current_parsing_scope;
+	kv_info->scope = reader->parsing_scope;
 	si->util = kv_info;
 
 	return 0;
@@ -3891,7 +3902,7 @@ enum config_scope current_config_scope(void)
 	if (the_reader.config_kvi)
 		return the_reader.config_kvi->scope;
 	else
-		return current_parsing_scope;
+		return the_reader.parsing_scope;
 }
 
 int current_config_line(void)
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH v3 7/8] config: report cached filenames in die_bad_number()
  2023-03-28 17:51   ` [PATCH v3 0/8] config.c: use struct for config reading state Glen Choo via GitGitGadget
                       ` (5 preceding siblings ...)
  2023-03-28 17:51     ` [PATCH v3 6/8] config.c: remove current_parsing_scope Glen Choo via GitGitGadget
@ 2023-03-28 17:51     ` Glen Choo via GitGitGadget
  2023-03-28 17:51     ` [PATCH v3 8/8] config.c: rename "struct config_source cf" Glen Choo via GitGitGadget
  2023-03-28 18:00     ` [PATCH v3 0/8] config.c: use struct for config reading state Glen Choo
  8 siblings, 0 replies; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-28 17:51 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Calvin Wan,
	Ævar Arnfjörð Bjarmason, Glen Choo, Glen Choo

From: Glen Choo <chooglen@google.com>

If, when parsing numbers from config, die_bad_number() is called, it
reports the filename and config source type if we were parsing a config
file, but not if we were iterating a config_set (it defaults to a less
specific error message). Most call sites don't parse config files
because config is typically read once and cached, so we only report
filename and config source type in "git config --type" (since "git
config" always parses config files).

This could have been fixed when we taught the current_config_*
functions to respect config_set values (0d44a2dacc (config: return
configset value for current_config_ functions, 2016-05-26), but it was
hard to spot then and we might have just missed it (I didn't find
mention of die_bad_number() in the original ML discussion [1].)

Fix this by refactoring the current_config_* functions into variants
that don't BUG() when we aren't reading config, and using the resulting
functions in die_bad_number(). "git config --get[-regexp] --type=int"
cannot use the non-refactored version because it parses the int value
_after_ parsing the config file, which would run into the BUG().

Since the refactored functions aren't public, they use "struct
config_reader".

1. https://lore.kernel.org/git/20160518223712.GA18317@sigill.intra.peff.net/

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c               | 65 +++++++++++++++++++++++++++++-------------
 config.h               |  1 +
 t/helper/test-config.c | 17 +++++++++++
 t/t1308-config-set.sh  |  9 ++++++
 4 files changed, 72 insertions(+), 20 deletions(-)

diff --git a/config.c b/config.c
index 3756322ec96..46c5a9a7d51 100644
--- a/config.c
+++ b/config.c
@@ -1311,39 +1311,48 @@ int git_parse_ssize_t(const char *value, ssize_t *ret)
 	return 1;
 }
 
+static int reader_config_name(struct config_reader *reader, const char **out);
+static int reader_origin_type(struct config_reader *reader,
+			      enum config_origin_type *type);
 NORETURN
-static void die_bad_number(struct config_source *cf, const char *name,
+static void die_bad_number(struct config_reader *reader, const char *name,
 			   const char *value)
 {
 	const char *error_type = (errno == ERANGE) ?
 		N_("out of range") : N_("invalid unit");
 	const char *bad_numeric = N_("bad numeric config value '%s' for '%s': %s");
+	const char *config_name = NULL;
+	enum config_origin_type config_origin = CONFIG_ORIGIN_UNKNOWN;
 
 	if (!value)
 		value = "";
 
-	if (!(cf && cf->name))
+	/* Ignoring the return value is okay since we handle missing values. */
+	reader_config_name(reader, &config_name);
+	reader_origin_type(reader, &config_origin);
+
+	if (!config_name)
 		die(_(bad_numeric), value, name, _(error_type));
 
-	switch (cf->origin_type) {
+	switch (config_origin) {
 	case CONFIG_ORIGIN_BLOB:
 		die(_("bad numeric config value '%s' for '%s' in blob %s: %s"),
-		    value, name, cf->name, _(error_type));
+		    value, name, config_name, _(error_type));
 	case CONFIG_ORIGIN_FILE:
 		die(_("bad numeric config value '%s' for '%s' in file %s: %s"),
-		    value, name, cf->name, _(error_type));
+		    value, name, config_name, _(error_type));
 	case CONFIG_ORIGIN_STDIN:
 		die(_("bad numeric config value '%s' for '%s' in standard input: %s"),
 		    value, name, _(error_type));
 	case CONFIG_ORIGIN_SUBMODULE_BLOB:
 		die(_("bad numeric config value '%s' for '%s' in submodule-blob %s: %s"),
-		    value, name, cf->name, _(error_type));
+		    value, name, config_name, _(error_type));
 	case CONFIG_ORIGIN_CMDLINE:
 		die(_("bad numeric config value '%s' for '%s' in command line %s: %s"),
-		    value, name, cf->name, _(error_type));
+		    value, name, config_name, _(error_type));
 	default:
 		die(_("bad numeric config value '%s' for '%s' in %s: %s"),
-		    value, name, cf->name, _(error_type));
+		    value, name, config_name, _(error_type));
 	}
 }
 
@@ -1351,7 +1360,7 @@ int git_config_int(const char *name, const char *value)
 {
 	int ret;
 	if (!git_parse_int(value, &ret))
-		die_bad_number(the_reader.source, name, value);
+		die_bad_number(&the_reader, name, value);
 	return ret;
 }
 
@@ -1359,7 +1368,7 @@ int64_t git_config_int64(const char *name, const char *value)
 {
 	int64_t ret;
 	if (!git_parse_int64(value, &ret))
-		die_bad_number(the_reader.source, name, value);
+		die_bad_number(&the_reader, name, value);
 	return ret;
 }
 
@@ -1367,7 +1376,7 @@ unsigned long git_config_ulong(const char *name, const char *value)
 {
 	unsigned long ret;
 	if (!git_parse_ulong(value, &ret))
-		die_bad_number(the_reader.source, name, value);
+		die_bad_number(&the_reader, name, value);
 	return ret;
 }
 
@@ -1375,7 +1384,7 @@ ssize_t git_config_ssize_t(const char *name, const char *value)
 {
 	ssize_t ret;
 	if (!git_parse_ssize_t(value, &ret))
-		die_bad_number(the_reader.source, name, value);
+		die_bad_number(&the_reader, name, value);
 	return ret;
 }
 
@@ -3839,14 +3848,23 @@ int parse_config_key(const char *var,
 	return 0;
 }
 
-const char *current_config_origin_type(void)
+static int reader_origin_type(struct config_reader *reader,
+			      enum config_origin_type *type)
 {
-	int type;
 	if (the_reader.config_kvi)
-		type = the_reader.config_kvi->origin_type;
+		*type = reader->config_kvi->origin_type;
 	else if(the_reader.source)
-		type = the_reader.source->origin_type;
+		*type = reader->source->origin_type;
 	else
+		return 1;
+	return 0;
+}
+
+const char *current_config_origin_type(void)
+{
+	enum config_origin_type type = CONFIG_ORIGIN_UNKNOWN;
+
+	if (reader_origin_type(&the_reader, &type))
 		BUG("current_config_origin_type called outside config callback");
 
 	switch (type) {
@@ -3885,14 +3903,21 @@ const char *config_scope_name(enum config_scope scope)
 	}
 }
 
-const char *current_config_name(void)
+static int reader_config_name(struct config_reader *reader, const char **out)
 {
-	const char *name;
 	if (the_reader.config_kvi)
-		name = the_reader.config_kvi->filename;
+		*out = reader->config_kvi->filename;
 	else if (the_reader.source)
-		name = the_reader.source->name;
+		*out = reader->source->name;
 	else
+		return 1;
+	return 0;
+}
+
+const char *current_config_name(void)
+{
+	const char *name;
+	if (reader_config_name(&the_reader, &name))
 		BUG("current_config_name called outside config callback");
 	return name ? name : "";
 }
diff --git a/config.h b/config.h
index 7606246531a..66c8b996e15 100644
--- a/config.h
+++ b/config.h
@@ -56,6 +56,7 @@ struct git_config_source {
 };
 
 enum config_origin_type {
+	CONFIG_ORIGIN_UNKNOWN = 0,
 	CONFIG_ORIGIN_BLOB,
 	CONFIG_ORIGIN_FILE,
 	CONFIG_ORIGIN_STDIN,
diff --git a/t/helper/test-config.c b/t/helper/test-config.c
index 4ba9eb65606..26e79168f6a 100644
--- a/t/helper/test-config.c
+++ b/t/helper/test-config.c
@@ -30,6 +30,9 @@
  * iterate -> iterate over all values using git_config(), and print some
  *            data for each
  *
+ * git_config_int -> iterate over all values using git_config() and print the
+ *                   integer value for the entered key or die
+ *
  * Examples:
  *
  * To print the value with highest priority for key "foo.bAr Baz.rock":
@@ -54,6 +57,17 @@ static int iterate_cb(const char *var, const char *value, void *data UNUSED)
 	return 0;
 }
 
+static int parse_int_cb(const char *var, const char *value, void *data)
+{
+	const char *key_to_match = data;
+
+	if (!strcmp(key_to_match, var)) {
+		int parsed = git_config_int(value, value);
+		printf("%d\n", parsed);
+	}
+	return 0;
+}
+
 static int early_config_cb(const char *var, const char *value, void *vdata)
 {
 	const char *key = vdata;
@@ -176,6 +190,9 @@ int cmd__config(int argc, const char **argv)
 	} else if (!strcmp(argv[1], "iterate")) {
 		git_config(iterate_cb, NULL);
 		goto exit0;
+	} else if (argc == 3 && !strcmp(argv[1], "git_config_int")) {
+		git_config(parse_int_cb, (void *) argv[2]);
+		goto exit0;
 	}
 
 	die("%s: Please check the syntax and the function name", argv[0]);
diff --git a/t/t1308-config-set.sh b/t/t1308-config-set.sh
index b38e158d3b2..9733bed30a9 100755
--- a/t/t1308-config-set.sh
+++ b/t/t1308-config-set.sh
@@ -120,6 +120,10 @@ test_expect_success 'find integer value for a key' '
 	check_config get_int lamb.chop 65
 '
 
+test_expect_success 'parse integer value during iteration' '
+	check_config git_config_int lamb.chop 65
+'
+
 test_expect_success 'find string value for a key' '
 	check_config get_string case.baz hask &&
 	check_config expect_code 1 get_string case.ba "Value not found for \"case.ba\""
@@ -134,6 +138,11 @@ test_expect_success 'find integer if value is non parse-able' '
 	check_config expect_code 128 get_int lamb.head
 '
 
+test_expect_success 'non parse-able integer value during iteration' '
+	check_config expect_code 128 git_config_int lamb.head 2>result &&
+	grep "fatal: bad numeric config value .* in file \.git/config" result
+'
+
 test_expect_success 'find bool value for the entered key' '
 	check_config get_bool goat.head 1 &&
 	check_config get_bool goat.skin 0 &&
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH v3 8/8] config.c: rename "struct config_source cf"
  2023-03-28 17:51   ` [PATCH v3 0/8] config.c: use struct for config reading state Glen Choo via GitGitGadget
                       ` (6 preceding siblings ...)
  2023-03-28 17:51     ` [PATCH v3 7/8] config: report cached filenames in die_bad_number() Glen Choo via GitGitGadget
@ 2023-03-28 17:51     ` Glen Choo via GitGitGadget
  2023-03-28 18:00     ` [PATCH v3 0/8] config.c: use struct for config reading state Glen Choo
  8 siblings, 0 replies; 72+ messages in thread
From: Glen Choo via GitGitGadget @ 2023-03-28 17:51 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Emily Shaffer, Calvin Wan,
	Ævar Arnfjörð Bjarmason, Glen Choo, Glen Choo

From: Glen Choo <chooglen@google.com>

The "cf" name is a holdover from before 4d8dd1494e (config: make parsing
stack struct independent from actual data source, 2013-07-12), when the
struct was named config_file. Since that acronym no longer makes sense,
rename "cf" to "cs". In some places, we have "struct config_set cs", so
to avoid conflict, rename those "cs" to "set" ("config_set" would be
more descriptive, but it's much longer and would require us to rewrap
several lines).

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 262 +++++++++++++++++++++++++++----------------------------
 1 file changed, 131 insertions(+), 131 deletions(-)

diff --git a/config.c b/config.c
index 46c5a9a7d51..cf571d45282 100644
--- a/config.c
+++ b/config.c
@@ -202,7 +202,7 @@ static const char include_depth_advice[] = N_(
 "from\n"
 "	%s\n"
 "This might be due to circular includes.");
-static int handle_path_include(struct config_source *cf, const char *path,
+static int handle_path_include(struct config_source *cs, const char *path,
 			       struct config_include_data *inc)
 {
 	int ret = 0;
@@ -224,14 +224,14 @@ static int handle_path_include(struct config_source *cf, const char *path,
 	if (!is_absolute_path(path)) {
 		char *slash;
 
-		if (!cf || !cf->path) {
+		if (!cs || !cs->path) {
 			ret = error(_("relative config includes must come from files"));
 			goto cleanup;
 		}
 
-		slash = find_last_dir_sep(cf->path);
+		slash = find_last_dir_sep(cs->path);
 		if (slash)
-			strbuf_add(&buf, cf->path, slash - cf->path + 1);
+			strbuf_add(&buf, cs->path, slash - cs->path + 1);
 		strbuf_addstr(&buf, path);
 		path = buf.buf;
 	}
@@ -239,8 +239,8 @@ static int handle_path_include(struct config_source *cf, const char *path,
 	if (!access_or_die(path, R_OK, 0)) {
 		if (++inc->depth > MAX_INCLUDE_DEPTH)
 			die(_(include_depth_advice), MAX_INCLUDE_DEPTH, path,
-			    !cf ? "<unknown>" :
-			    cf->name ? cf->name :
+			    !cs ? "<unknown>" :
+			    cs->name ? cs->name :
 			    "the command line");
 		ret = git_config_from_file(git_config_include, path, inc);
 		inc->depth--;
@@ -257,7 +257,7 @@ static void add_trailing_starstar_for_dir(struct strbuf *pat)
 		strbuf_addstr(pat, "**");
 }
 
-static int prepare_include_condition_pattern(struct config_source *cf,
+static int prepare_include_condition_pattern(struct config_source *cs,
 					     struct strbuf *pat)
 {
 	struct strbuf path = STRBUF_INIT;
@@ -274,11 +274,11 @@ static int prepare_include_condition_pattern(struct config_source *cf,
 	if (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {
 		const char *slash;
 
-		if (!cf || !cf->path)
+		if (!cs || !cs->path)
 			return error(_("relative config include "
 				       "conditionals must come from files"));
 
-		strbuf_realpath(&path, cf->path, 1);
+		strbuf_realpath(&path, cs->path, 1);
 		slash = find_last_dir_sep(path.buf);
 		if (!slash)
 			BUG("how is this possible?");
@@ -293,7 +293,7 @@ static int prepare_include_condition_pattern(struct config_source *cf,
 	return prefix;
 }
 
-static int include_by_gitdir(struct config_source *cf,
+static int include_by_gitdir(struct config_source *cs,
 			     const struct config_options *opts,
 			     const char *cond, size_t cond_len, int icase)
 {
@@ -310,7 +310,7 @@ static int include_by_gitdir(struct config_source *cf,
 
 	strbuf_realpath(&text, git_dir, 1);
 	strbuf_add(&pattern, cond, cond_len);
-	prefix = prepare_include_condition_pattern(cf, &pattern);
+	prefix = prepare_include_condition_pattern(cs, &pattern);
 
 again:
 	if (prefix < 0)
@@ -449,16 +449,16 @@ static int include_by_remote_url(struct config_include_data *inc,
 					     inc->remote_urls);
 }
 
-static int include_condition_is_true(struct config_source *cf,
+static int include_condition_is_true(struct config_source *cs,
 				     struct config_include_data *inc,
 				     const char *cond, size_t cond_len)
 {
 	const struct config_options *opts = inc->opts;
 
 	if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
-		return include_by_gitdir(cf, opts, cond, cond_len, 0);
+		return include_by_gitdir(cs, opts, cond, cond_len, 0);
 	else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
-		return include_by_gitdir(cf, opts, cond, cond_len, 1);
+		return include_by_gitdir(cs, opts, cond, cond_len, 1);
 	else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
 		return include_by_branch(cond, cond_len);
 	else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,
@@ -472,7 +472,7 @@ static int include_condition_is_true(struct config_source *cf,
 static int git_config_include(const char *var, const char *value, void *data)
 {
 	struct config_include_data *inc = data;
-	struct config_source *cf = inc->config_reader->source;
+	struct config_source *cs = inc->config_reader->source;
 	const char *cond, *key;
 	size_t cond_len;
 	int ret;
@@ -486,16 +486,16 @@ static int git_config_include(const char *var, const char *value, void *data)
 		return ret;
 
 	if (!strcmp(var, "include.path"))
-		ret = handle_path_include(cf, value, inc);
+		ret = handle_path_include(cs, value, inc);
 
 	if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
-	    cond && include_condition_is_true(cf, inc, cond, cond_len) &&
+	    cond && include_condition_is_true(cs, inc, cond, cond_len) &&
 	    !strcmp(key, "path")) {
 		config_fn_t old_fn = inc->fn;
 
 		if (inc->opts->unconditional_remote_url)
 			inc->fn = forbid_remote_url;
-		ret = handle_path_include(cf, value, inc);
+		ret = handle_path_include(cs, value, inc);
 		inc->fn = old_fn;
 	}
 
@@ -820,21 +820,21 @@ out:
 	return ret;
 }
 
-static int get_next_char(struct config_source *cf)
+static int get_next_char(struct config_source *cs)
 {
-	int c = cf->do_fgetc(cf);
+	int c = cs->do_fgetc(cs);
 
 	if (c == '\r') {
 		/* DOS like systems */
-		c = cf->do_fgetc(cf);
+		c = cs->do_fgetc(cs);
 		if (c != '\n') {
 			if (c != EOF)
-				cf->do_ungetc(c, cf);
+				cs->do_ungetc(c, cs);
 			c = '\r';
 		}
 	}
 
-	if (c != EOF && ++cf->total_len > INT_MAX) {
+	if (c != EOF && ++cs->total_len > INT_MAX) {
 		/*
 		 * This is an absurdly long config file; refuse to parse
 		 * further in order to protect downstream code from integer
@@ -842,38 +842,38 @@ static int get_next_char(struct config_source *cf)
 		 * but we can mark EOF and put trash in the return value,
 		 * which will trigger a parse error.
 		 */
-		cf->eof = 1;
+		cs->eof = 1;
 		return 0;
 	}
 
 	if (c == '\n')
-		cf->linenr++;
+		cs->linenr++;
 	if (c == EOF) {
-		cf->eof = 1;
-		cf->linenr++;
+		cs->eof = 1;
+		cs->linenr++;
 		c = '\n';
 	}
 	return c;
 }
 
-static char *parse_value(struct config_source *cf)
+static char *parse_value(struct config_source *cs)
 {
 	int quote = 0, comment = 0, space = 0;
 
-	strbuf_reset(&cf->value);
+	strbuf_reset(&cs->value);
 	for (;;) {
-		int c = get_next_char(cf);
+		int c = get_next_char(cs);
 		if (c == '\n') {
 			if (quote) {
-				cf->linenr--;
+				cs->linenr--;
 				return NULL;
 			}
-			return cf->value.buf;
+			return cs->value.buf;
 		}
 		if (comment)
 			continue;
 		if (isspace(c) && !quote) {
-			if (cf->value.len)
+			if (cs->value.len)
 				space++;
 			continue;
 		}
@@ -884,9 +884,9 @@ static char *parse_value(struct config_source *cf)
 			}
 		}
 		for (; space; space--)
-			strbuf_addch(&cf->value, ' ');
+			strbuf_addch(&cs->value, ' ');
 		if (c == '\\') {
-			c = get_next_char(cf);
+			c = get_next_char(cs);
 			switch (c) {
 			case '\n':
 				continue;
@@ -906,18 +906,18 @@ static char *parse_value(struct config_source *cf)
 			default:
 				return NULL;
 			}
-			strbuf_addch(&cf->value, c);
+			strbuf_addch(&cs->value, c);
 			continue;
 		}
 		if (c == '"') {
 			quote = 1-quote;
 			continue;
 		}
-		strbuf_addch(&cf->value, c);
+		strbuf_addch(&cs->value, c);
 	}
 }
 
-static int get_value(struct config_source *cf, config_fn_t fn, void *data,
+static int get_value(struct config_source *cs, config_fn_t fn, void *data,
 		     struct strbuf *name)
 {
 	int c;
@@ -926,8 +926,8 @@ static int get_value(struct config_source *cf, config_fn_t fn, void *data,
 
 	/* Get the full name */
 	for (;;) {
-		c = get_next_char(cf);
-		if (cf->eof)
+		c = get_next_char(cs);
+		if (cs->eof)
 			break;
 		if (!iskeychar(c))
 			break;
@@ -935,13 +935,13 @@ static int get_value(struct config_source *cf, config_fn_t fn, void *data,
 	}
 
 	while (c == ' ' || c == '\t')
-		c = get_next_char(cf);
+		c = get_next_char(cs);
 
 	value = NULL;
 	if (c != '\n') {
 		if (c != '=')
 			return -1;
-		value = parse_value(cf);
+		value = parse_value(cs);
 		if (!value)
 			return -1;
 	}
@@ -950,21 +950,21 @@ static int get_value(struct config_source *cf, config_fn_t fn, void *data,
 	 * the line we just parsed during the call to fn to get
 	 * accurate line number in error messages.
 	 */
-	cf->linenr--;
+	cs->linenr--;
 	ret = fn(name->buf, value, data);
 	if (ret >= 0)
-		cf->linenr++;
+		cs->linenr++;
 	return ret;
 }
 
-static int get_extended_base_var(struct config_source *cf, struct strbuf *name,
+static int get_extended_base_var(struct config_source *cs, struct strbuf *name,
 				 int c)
 {
-	cf->subsection_case_sensitive = 0;
+	cs->subsection_case_sensitive = 0;
 	do {
 		if (c == '\n')
 			goto error_incomplete_line;
-		c = get_next_char(cf);
+		c = get_next_char(cs);
 	} while (isspace(c));
 
 	/* We require the format to be '[base "extension"]' */
@@ -973,13 +973,13 @@ static int get_extended_base_var(struct config_source *cf, struct strbuf *name,
 	strbuf_addch(name, '.');
 
 	for (;;) {
-		int c = get_next_char(cf);
+		int c = get_next_char(cs);
 		if (c == '\n')
 			goto error_incomplete_line;
 		if (c == '"')
 			break;
 		if (c == '\\') {
-			c = get_next_char(cf);
+			c = get_next_char(cs);
 			if (c == '\n')
 				goto error_incomplete_line;
 		}
@@ -987,25 +987,25 @@ static int get_extended_base_var(struct config_source *cf, struct strbuf *name,
 	}
 
 	/* Final ']' */
-	if (get_next_char(cf) != ']')
+	if (get_next_char(cs) != ']')
 		return -1;
 	return 0;
 error_incomplete_line:
-	cf->linenr--;
+	cs->linenr--;
 	return -1;
 }
 
-static int get_base_var(struct config_source *cf, struct strbuf *name)
+static int get_base_var(struct config_source *cs, struct strbuf *name)
 {
-	cf->subsection_case_sensitive = 1;
+	cs->subsection_case_sensitive = 1;
 	for (;;) {
-		int c = get_next_char(cf);
-		if (cf->eof)
+		int c = get_next_char(cs);
+		if (cs->eof)
 			return -1;
 		if (c == ']')
 			return 0;
 		if (isspace(c))
-			return get_extended_base_var(cf, name, c);
+			return get_extended_base_var(cs, name, c);
 		if (!iskeychar(c) && c != '.')
 			return -1;
 		strbuf_addch(name, tolower(c));
@@ -1018,7 +1018,7 @@ struct parse_event_data {
 	const struct config_options *opts;
 };
 
-static int do_event(struct config_source *cf, enum config_event_t type,
+static int do_event(struct config_source *cs, enum config_event_t type,
 		    struct parse_event_data *data)
 {
 	size_t offset;
@@ -1030,7 +1030,7 @@ static int do_event(struct config_source *cf, enum config_event_t type,
 	    data->previous_type == type)
 		return 0;
 
-	offset = cf->do_ftell(cf);
+	offset = cs->do_ftell(cs);
 	/*
 	 * At EOF, the parser always "inserts" an extra '\n', therefore
 	 * the end offset of the event is the current file position, otherwise
@@ -1050,12 +1050,12 @@ static int do_event(struct config_source *cf, enum config_event_t type,
 	return 0;
 }
 
-static int git_parse_source(struct config_source *cf, config_fn_t fn,
+static int git_parse_source(struct config_source *cs, config_fn_t fn,
 			    void *data, const struct config_options *opts)
 {
 	int comment = 0;
 	size_t baselen = 0;
-	struct strbuf *var = &cf->var;
+	struct strbuf *var = &cs->var;
 	int error_return = 0;
 	char *error_msg = NULL;
 
@@ -1070,7 +1070,7 @@ static int git_parse_source(struct config_source *cf, config_fn_t fn,
 	for (;;) {
 		int c;
 
-		c = get_next_char(cf);
+		c = get_next_char(cs);
 		if (bomptr && *bomptr) {
 			/* We are at the file beginning; skip UTF8-encoded BOM
 			 * if present. Sane editors won't put this in on their
@@ -1087,12 +1087,12 @@ static int git_parse_source(struct config_source *cf, config_fn_t fn,
 			}
 		}
 		if (c == '\n') {
-			if (cf->eof) {
-				if (do_event(cf, CONFIG_EVENT_EOF, &event_data) < 0)
+			if (cs->eof) {
+				if (do_event(cs, CONFIG_EVENT_EOF, &event_data) < 0)
 					return -1;
 				return 0;
 			}
-			if (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
+			if (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
 				return -1;
 			comment = 0;
 			continue;
@@ -1100,23 +1100,23 @@ static int git_parse_source(struct config_source *cf, config_fn_t fn,
 		if (comment)
 			continue;
 		if (isspace(c)) {
-			if (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
+			if (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
 					return -1;
 			continue;
 		}
 		if (c == '#' || c == ';') {
-			if (do_event(cf, CONFIG_EVENT_COMMENT, &event_data) < 0)
+			if (do_event(cs, CONFIG_EVENT_COMMENT, &event_data) < 0)
 					return -1;
 			comment = 1;
 			continue;
 		}
 		if (c == '[') {
-			if (do_event(cf, CONFIG_EVENT_SECTION, &event_data) < 0)
+			if (do_event(cs, CONFIG_EVENT_SECTION, &event_data) < 0)
 					return -1;
 
 			/* Reset prior to determining a new stem */
 			strbuf_reset(var);
-			if (get_base_var(cf, var) < 0 || var->len < 1)
+			if (get_base_var(cs, var) < 0 || var->len < 1)
 				break;
 			strbuf_addch(var, '.');
 			baselen = var->len;
@@ -1125,7 +1125,7 @@ static int git_parse_source(struct config_source *cf, config_fn_t fn,
 		if (!isalpha(c))
 			break;
 
-		if (do_event(cf, CONFIG_EVENT_ENTRY, &event_data) < 0)
+		if (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)
 			return -1;
 
 		/*
@@ -1135,42 +1135,42 @@ static int git_parse_source(struct config_source *cf, config_fn_t fn,
 		 */
 		strbuf_setlen(var, baselen);
 		strbuf_addch(var, tolower(c));
-		if (get_value(cf, fn, data, var) < 0)
+		if (get_value(cs, fn, data, var) < 0)
 			break;
 	}
 
-	if (do_event(cf, CONFIG_EVENT_ERROR, &event_data) < 0)
+	if (do_event(cs, CONFIG_EVENT_ERROR, &event_data) < 0)
 		return -1;
 
-	switch (cf->origin_type) {
+	switch (cs->origin_type) {
 	case CONFIG_ORIGIN_BLOB:
 		error_msg = xstrfmt(_("bad config line %d in blob %s"),
-				      cf->linenr, cf->name);
+				      cs->linenr, cs->name);
 		break;
 	case CONFIG_ORIGIN_FILE:
 		error_msg = xstrfmt(_("bad config line %d in file %s"),
-				      cf->linenr, cf->name);
+				      cs->linenr, cs->name);
 		break;
 	case CONFIG_ORIGIN_STDIN:
 		error_msg = xstrfmt(_("bad config line %d in standard input"),
-				      cf->linenr);
+				      cs->linenr);
 		break;
 	case CONFIG_ORIGIN_SUBMODULE_BLOB:
 		error_msg = xstrfmt(_("bad config line %d in submodule-blob %s"),
-				       cf->linenr, cf->name);
+				       cs->linenr, cs->name);
 		break;
 	case CONFIG_ORIGIN_CMDLINE:
 		error_msg = xstrfmt(_("bad config line %d in command line %s"),
-				       cf->linenr, cf->name);
+				       cs->linenr, cs->name);
 		break;
 	default:
 		error_msg = xstrfmt(_("bad config line %d in %s"),
-				      cf->linenr, cf->name);
+				      cs->linenr, cs->name);
 	}
 
 	switch (opts && opts->error_action ?
 		opts->error_action :
-		cf->default_error_action) {
+		cs->default_error_action) {
 	case CONFIG_ERROR_DIE:
 		die("%s", error_msg);
 		break;
@@ -2280,13 +2280,13 @@ int config_with_options(config_fn_t fn, void *data,
 	return ret;
 }
 
-static void configset_iter(struct config_reader *reader, struct config_set *cs,
+static void configset_iter(struct config_reader *reader, struct config_set *set,
 			   config_fn_t fn, void *data)
 {
 	int i, value_index;
 	struct string_list *values;
 	struct config_set_element *entry;
-	struct configset_list *list = &cs->list;
+	struct configset_list *list = &set->list;
 
 	for (i = 0; i < list->nr; i++) {
 		entry = list->items[i].e;
@@ -2351,7 +2351,7 @@ void read_very_early_config(config_fn_t cb, void *data)
 	config_with_options(cb, data, NULL, &opts);
 }
 
-static struct config_set_element *configset_find_element(struct config_set *cs, const char *key)
+static struct config_set_element *configset_find_element(struct config_set *set, const char *key)
 {
 	struct config_set_element k;
 	struct config_set_element *found_entry;
@@ -2365,13 +2365,13 @@ static struct config_set_element *configset_find_element(struct config_set *cs,
 
 	hashmap_entry_init(&k.ent, strhash(normalized_key));
 	k.key = normalized_key;
-	found_entry = hashmap_get_entry(&cs->config_hash, &k, ent, NULL);
+	found_entry = hashmap_get_entry(&set->config_hash, &k, ent, NULL);
 	free(normalized_key);
 	return found_entry;
 }
 
 static int configset_add_value(struct config_reader *reader,
-			       struct config_set *cs, const char *key,
+			       struct config_set *set, const char *key,
 			       const char *value)
 {
 	struct config_set_element *e;
@@ -2379,7 +2379,7 @@ static int configset_add_value(struct config_reader *reader,
 	struct configset_list_item *l_item;
 	struct key_value_info *kv_info = xmalloc(sizeof(*kv_info));
 
-	e = configset_find_element(cs, key);
+	e = configset_find_element(set, key);
 	/*
 	 * Since the keys are being fed by git_config*() callback mechanism, they
 	 * are already normalized. So simply add them without any further munging.
@@ -2389,12 +2389,12 @@ static int configset_add_value(struct config_reader *reader,
 		hashmap_entry_init(&e->ent, strhash(key));
 		e->key = xstrdup(key);
 		string_list_init_dup(&e->value_list);
-		hashmap_add(&cs->config_hash, &e->ent);
+		hashmap_add(&set->config_hash, &e->ent);
 	}
 	si = string_list_append_nodup(&e->value_list, xstrdup_or_null(value));
 
-	ALLOC_GROW(cs->list.items, cs->list.nr + 1, cs->list.alloc);
-	l_item = &cs->list.items[cs->list.nr++];
+	ALLOC_GROW(set->list.items, set->list.nr + 1, set->list.alloc);
+	l_item = &set->list.items[set->list.nr++];
 	l_item->e = e;
 	l_item->value_index = e->value_list.nr - 1;
 
@@ -2429,33 +2429,33 @@ static int config_set_element_cmp(const void *cmp_data UNUSED,
 	return strcmp(e1->key, e2->key);
 }
 
-void git_configset_init(struct config_set *cs)
+void git_configset_init(struct config_set *set)
 {
-	hashmap_init(&cs->config_hash, config_set_element_cmp, NULL, 0);
-	cs->hash_initialized = 1;
-	cs->list.nr = 0;
-	cs->list.alloc = 0;
-	cs->list.items = NULL;
+	hashmap_init(&set->config_hash, config_set_element_cmp, NULL, 0);
+	set->hash_initialized = 1;
+	set->list.nr = 0;
+	set->list.alloc = 0;
+	set->list.items = NULL;
 }
 
-void git_configset_clear(struct config_set *cs)
+void git_configset_clear(struct config_set *set)
 {
 	struct config_set_element *entry;
 	struct hashmap_iter iter;
-	if (!cs->hash_initialized)
+	if (!set->hash_initialized)
 		return;
 
-	hashmap_for_each_entry(&cs->config_hash, &iter, entry,
+	hashmap_for_each_entry(&set->config_hash, &iter, entry,
 				ent /* member name */) {
 		free(entry->key);
 		string_list_clear(&entry->value_list, 1);
 	}
-	hashmap_clear_and_free(&cs->config_hash, struct config_set_element, ent);
-	cs->hash_initialized = 0;
-	free(cs->list.items);
-	cs->list.nr = 0;
-	cs->list.alloc = 0;
-	cs->list.items = NULL;
+	hashmap_clear_and_free(&set->config_hash, struct config_set_element, ent);
+	set->hash_initialized = 0;
+	free(set->list.items);
+	set->list.nr = 0;
+	set->list.alloc = 0;
+	set->list.items = NULL;
 }
 
 struct configset_add_data {
@@ -2471,15 +2471,15 @@ static int config_set_callback(const char *key, const char *value, void *cb)
 	return 0;
 }
 
-int git_configset_add_file(struct config_set *cs, const char *filename)
+int git_configset_add_file(struct config_set *set, const char *filename)
 {
 	struct configset_add_data data = CONFIGSET_ADD_INIT;
 	data.config_reader = &the_reader;
-	data.config_set = cs;
+	data.config_set = set;
 	return git_config_from_file(config_set_callback, filename, &data);
 }
 
-int git_configset_get_value(struct config_set *cs, const char *key, const char **value)
+int git_configset_get_value(struct config_set *set, const char *key, const char **value)
 {
 	const struct string_list *values = NULL;
 	/*
@@ -2487,7 +2487,7 @@ int git_configset_get_value(struct config_set *cs, const char *key, const char *
 	 * queried key in the files of the configset, the value returned will be the last
 	 * value in the value list for that key.
 	 */
-	values = git_configset_get_value_multi(cs, key);
+	values = git_configset_get_value_multi(set, key);
 
 	if (!values)
 		return 1;
@@ -2496,26 +2496,26 @@ int git_configset_get_value(struct config_set *cs, const char *key, const char *
 	return 0;
 }
 
-const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key)
+const struct string_list *git_configset_get_value_multi(struct config_set *set, const char *key)
 {
-	struct config_set_element *e = configset_find_element(cs, key);
+	struct config_set_element *e = configset_find_element(set, key);
 	return e ? &e->value_list : NULL;
 }
 
-int git_configset_get_string(struct config_set *cs, const char *key, char **dest)
+int git_configset_get_string(struct config_set *set, const char *key, char **dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value))
+	if (!git_configset_get_value(set, key, &value))
 		return git_config_string((const char **)dest, key, value);
 	else
 		return 1;
 }
 
-static int git_configset_get_string_tmp(struct config_set *cs, const char *key,
+static int git_configset_get_string_tmp(struct config_set *set, const char *key,
 					const char **dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value)) {
+	if (!git_configset_get_value(set, key, &value)) {
 		if (!value)
 			return config_error_nonbool(key);
 		*dest = value;
@@ -2525,51 +2525,51 @@ static int git_configset_get_string_tmp(struct config_set *cs, const char *key,
 	}
 }
 
-int git_configset_get_int(struct config_set *cs, const char *key, int *dest)
+int git_configset_get_int(struct config_set *set, const char *key, int *dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value)) {
+	if (!git_configset_get_value(set, key, &value)) {
 		*dest = git_config_int(key, value);
 		return 0;
 	} else
 		return 1;
 }
 
-int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest)
+int git_configset_get_ulong(struct config_set *set, const char *key, unsigned long *dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value)) {
+	if (!git_configset_get_value(set, key, &value)) {
 		*dest = git_config_ulong(key, value);
 		return 0;
 	} else
 		return 1;
 }
 
-int git_configset_get_bool(struct config_set *cs, const char *key, int *dest)
+int git_configset_get_bool(struct config_set *set, const char *key, int *dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value)) {
+	if (!git_configset_get_value(set, key, &value)) {
 		*dest = git_config_bool(key, value);
 		return 0;
 	} else
 		return 1;
 }
 
-int git_configset_get_bool_or_int(struct config_set *cs, const char *key,
+int git_configset_get_bool_or_int(struct config_set *set, const char *key,
 				int *is_bool, int *dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value)) {
+	if (!git_configset_get_value(set, key, &value)) {
 		*dest = git_config_bool_or_int(key, value, is_bool);
 		return 0;
 	} else
 		return 1;
 }
 
-int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest)
+int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value)) {
+	if (!git_configset_get_value(set, key, &value)) {
 		*dest = git_parse_maybe_bool(value);
 		if (*dest == -1)
 			return -1;
@@ -2578,10 +2578,10 @@ int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *de
 		return 1;
 }
 
-int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest)
+int git_configset_get_pathname(struct config_set *set, const char *key, const char **dest)
 {
 	const char *value;
-	if (!git_configset_get_value(cs, key, &value))
+	if (!git_configset_get_value(set, key, &value))
 		return git_config_pathname(dest, key, value);
 	else
 		return 1;
@@ -2972,7 +2972,7 @@ static int store_aux_event(enum config_event_t type,
 			   size_t begin, size_t end, void *data)
 {
 	struct config_store_data *store = data;
-	struct config_source *cf = store->config_reader->source;
+	struct config_source *cs = store->config_reader->source;
 
 	ALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc);
 	store->parsed[store->parsed_nr].begin = begin;
@@ -2982,10 +2982,10 @@ static int store_aux_event(enum config_event_t type,
 	if (type == CONFIG_EVENT_SECTION) {
 		int (*cmpfn)(const char *, const char *, size_t);
 
-		if (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')
-			return error(_("invalid section name '%s'"), cf->var.buf);
+		if (cs->var.len < 2 || cs->var.buf[cs->var.len - 1] != '.')
+			return error(_("invalid section name '%s'"), cs->var.buf);
 
-		if (cf->subsection_case_sensitive)
+		if (cs->subsection_case_sensitive)
 			cmpfn = strncasecmp;
 		else
 			cmpfn = strncmp;
@@ -2993,8 +2993,8 @@ static int store_aux_event(enum config_event_t type,
 		/* Is this the section we were looking for? */
 		store->is_keys_section =
 			store->parsed[store->parsed_nr].is_keys_section =
-			cf->var.len - 1 == store->baselen &&
-			!cmpfn(cf->var.buf, store->key, store->baselen);
+			cs->var.len - 1 == store->baselen &&
+			!cmpfn(cs->var.buf, store->key, store->baselen);
 		if (store->is_keys_section) {
 			store->section_seen = 1;
 			ALLOC_GROW(store->seen, store->seen_nr + 1,
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* Re: [PATCH v3 0/8] config.c: use struct for config reading state
  2023-03-28 17:51   ` [PATCH v3 0/8] config.c: use struct for config reading state Glen Choo via GitGitGadget
                       ` (7 preceding siblings ...)
  2023-03-28 17:51     ` [PATCH v3 8/8] config.c: rename "struct config_source cf" Glen Choo via GitGitGadget
@ 2023-03-28 18:00     ` Glen Choo
  2023-03-28 20:14       ` Junio C Hamano
  8 siblings, 1 reply; 72+ messages in thread
From: Glen Choo @ 2023-03-28 18:00 UTC (permalink / raw)
  To: Glen Choo via GitGitGadget, git
  Cc: Jonathan Tan, Emily Shaffer, Calvin Wan,
	Ævar Arnfjörð Bjarmason

"Glen Choo via GitGitGadget" <gitgitgadget@gmail.com> writes:

> Note to Junio: 8/8 (which renames "cs" -> "set") conflicts with
> ab/config-multi-and-nonbool. I previously said that I'd rebase this, but
> presumably a remerge-diff is more ergonomic + flexible (let me know if I'm
> mistaken), so I'll send a remerge-diff in a reply (I don't trust GGG not to
> mangle the patch :/).


  diff --git a/config.c b/config.c
  remerge CONFLICT (content): Merge conflict in config.c
  index b17658e1ba..159c404d0c 100644
  --- a/config.c
  +++ b/config.c
  @@ -2351,15 +2351,9 @@ void read_very_early_config(config_fn_t cb, void *data)
    config_with_options(cb, data, NULL, &opts);
  }

  -<<<<<<< HEAD
  -static struct config_set_element *configset_find_element(struct config_set *set, const char *key)
  -||||||| c000d91638
  -static struct config_set_element *configset_find_element(struct config_set *cs, const char *key)
  -=======
  RESULT_MUST_BE_USED
  -static int configset_find_element(struct config_set *cs, const char *key,
  +static int configset_find_element(struct config_set *set, const char *key,
            struct config_set_element **dest)
  ->>>>>>> gitster/ab/config-multi-and-nonbool
  {
    struct config_set_element k;
    struct config_set_element *found_entry;
  @@ -2392,15 +2386,9 @@ static int configset_add_value(struct config_reader *reader,
    struct key_value_info *kv_info = xmalloc(sizeof(*kv_info));
    int ret;

  -<<<<<<< HEAD
  -	e = configset_find_element(set, key);
  -||||||| c000d91638
  -	e = configset_find_element(cs, key);
  -=======
  -	ret = configset_find_element(cs, key, &e);
  +	ret = configset_find_element(set, key, &e);
    if (ret)
      return ret;
  ->>>>>>> gitster/ab/config-multi-and-nonbool
    /*
    * Since the keys are being fed by git_config*() callback mechanism, they
    * are already normalized. So simply add them without any further munging.
  @@ -2510,40 +2498,21 @@ int git_configset_get_value(struct config_set *set, const char *key, const char
    * queried key in the files of the configset, the value returned will be the last
    * value in the value list for that key.
    */
  -<<<<<<< HEAD
  -	values = git_configset_get_value_multi(set, key);
  -||||||| c000d91638
  -	values = git_configset_get_value_multi(cs, key);
  -=======
  -	if ((ret = git_configset_get_value_multi(cs, key, &values)))
  +	if ((ret = git_configset_get_value_multi(set, key, &values)))
      return ret;
  ->>>>>>> gitster/ab/config-multi-and-nonbool

    assert(values->nr > 0);
    *value = values->items[values->nr - 1].string;
    return 0;
  }

  -<<<<<<< HEAD
  -const struct string_list *git_configset_get_value_multi(struct config_set *set, const char *key)
  -||||||| c000d91638
  -const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key)
  -=======
  -int git_configset_get_value_multi(struct config_set *cs, const char *key,
  +int git_configset_get_value_multi(struct config_set *set, const char *key,
            const struct string_list **dest)
  ->>>>>>> gitster/ab/config-multi-and-nonbool
  -{
  -<<<<<<< HEAD
  -	struct config_set_element *e = configset_find_element(set, key);
  -	return e ? &e->value_list : NULL;
  -||||||| c000d91638
  -	struct config_set_element *e = configset_find_element(cs, key);
  -	return e ? &e->value_list : NULL;
  -=======
  +{
    struct config_set_element *e;
    int ret;

  -	if ((ret = configset_find_element(cs, key, &e)))
  +	if ((ret = configset_find_element(set, key, &e)))
      return ret;
    else if (!e)
      return 1;
  @@ -2557,12 +2526,12 @@ static int check_multi_string(struct string_list_item *item, void *util)
    return item->string ? 0 : config_error_nonbool(util);
  }

  -int git_configset_get_string_multi(struct config_set *cs, const char *key,
  +int git_configset_get_string_multi(struct config_set *set, const char *key,
            const struct string_list **dest)
  {
    int ret;

  -	if ((ret = git_configset_get_value_multi(cs, key, dest)))
  +	if ((ret = git_configset_get_value_multi(set, key, dest)))
      return ret;
    if ((ret = for_each_string_list((struct string_list *)*dest,
            check_multi_string, (void *)key)))
  @@ -2571,17 +2540,16 @@ int git_configset_get_string_multi(struct config_set *cs, const char *key,
    return 0;
  }

  -int git_configset_get(struct config_set *cs, const char *key)
  +int git_configset_get(struct config_set *set, const char *key)
  {
    struct config_set_element *e;
    int ret;

  -	if ((ret = configset_find_element(cs, key, &e)))
  +	if ((ret = configset_find_element(set, key, &e)))
      return ret;
    else if (!e)
      return 1;
    return 0;
  ->>>>>>> gitster/ab/config-multi-and-nonbool
  }

  int git_configset_get_string(struct config_set *set, const char *key, char **dest)

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH v3 0/8] config.c: use struct for config reading state
  2023-03-28 18:00     ` [PATCH v3 0/8] config.c: use struct for config reading state Glen Choo
@ 2023-03-28 20:14       ` Junio C Hamano
  2023-03-28 21:24         ` Glen Choo
  0 siblings, 1 reply; 72+ messages in thread
From: Junio C Hamano @ 2023-03-28 20:14 UTC (permalink / raw)
  To: Glen Choo
  Cc: Glen Choo via GitGitGadget, git, Jonathan Tan, Emily Shaffer,
	Calvin Wan, Ævar Arnfjörð Bjarmason

Glen Choo <chooglen@google.com> writes:

> "Glen Choo via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
>> Note to Junio: 8/8 (which renames "cs" -> "set") conflicts with
>> ab/config-multi-and-nonbool. I previously said that I'd rebase this, but
>> presumably a remerge-diff is more ergonomic + flexible (let me know if I'm
>> mistaken), so I'll send a remerge-diff in a reply (I don't trust GGG not to
>> mangle the patch :/).

A patch that is indented by two places would not be mechanically
applicable anyway and even without such indentation, the hunk-side
markers like c000d91638, gitster/ab/config-multi-and-nonbool, etc.
you have in the patch would be different from conflicts I would see,
so it wouldn't be very useful.  There should probably be a mode
where remerge-diff would omit these labels to help mechanical
application of such a "patch".

In any case, remerge-diff certainly helps eyeballing the result.

In this case, because the difference between v2 and v3 does not
overlap with the area that v2 (or v3 for that matter) would conflict
with other topics in flight, my rerere database can clealy reuse the
recorded resolution for conflicts between this iteration and the
topics in flight just fine.  I'll push out the integration result
after I am done with other topics.

Thanks.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH v3 0/8] config.c: use struct for config reading state
  2023-03-28 20:14       ` Junio C Hamano
@ 2023-03-28 21:24         ` Glen Choo
  0 siblings, 0 replies; 72+ messages in thread
From: Glen Choo @ 2023-03-28 21:24 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Glen Choo via GitGitGadget, git, Jonathan Tan, Emily Shaffer,
	Calvin Wan, Ævar Arnfjörð Bjarmason

Junio C Hamano <gitster@pobox.com> writes:

> Glen Choo <chooglen@google.com> writes:
>
>> "Glen Choo via GitGitGadget" <gitgitgadget@gmail.com> writes:
>>
>>> Note to Junio: 8/8 (which renames "cs" -> "set") conflicts with
>>> ab/config-multi-and-nonbool. I previously said that I'd rebase this, but
>>> presumably a remerge-diff is more ergonomic + flexible (let me know if I'm
>>> mistaken), so I'll send a remerge-diff in a reply (I don't trust GGG not to
>>> mangle the patch :/).
>
> A patch that is indented by two places would not be mechanically
> applicable anyway and even without such indentation, the hunk-side
> markers like c000d91638, gitster/ab/config-multi-and-nonbool, etc.
> you have in the patch would be different from conflicts I would see,
> so it wouldn't be very useful.  There should probably be a mode
> where remerge-diff would omit these labels to help mechanical
> application of such a "patch".

Ah, bummer :(

I was thinking about that as well. remerge-diff actually outputs
left and right in "--format=reference", which I then manually modified
to reflect the conflicted version in the working tree, giving the final
result here. I wonder what remerge-diff or "git apply" could do instead.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH v3 3/8] config.c: create config_reader and the_reader
  2023-03-28 17:51     ` [PATCH v3 3/8] config.c: create config_reader and the_reader Glen Choo via GitGitGadget
@ 2023-03-29 10:41       ` Ævar Arnfjörð Bjarmason
  2023-03-29 18:57         ` Junio C Hamano
  2023-03-30 17:51         ` Glen Choo
  0 siblings, 2 replies; 72+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2023-03-29 10:41 UTC (permalink / raw)
  To: Glen Choo via GitGitGadget
  Cc: git, Jonathan Tan, Emily Shaffer, Calvin Wan, Glen Choo


On Tue, Mar 28 2023, Glen Choo via GitGitGadget wrote:

> From: Glen Choo <chooglen@google.com>
>
> Create "struct config_reader" to hold the state of the config source
> currently being read. Then, create a static instance of it,
> "the_reader", and use "the_reader.source" to replace references to
> "cf_global" in public functions.
>
> This doesn't create much immediate benefit (since we're mostly replacing
> static variables with a bigger static variable), but it prepares us for
> a future where this state doesn't have to be global; "struct
> config_reader" (or a similar struct) could be provided by the caller, or
> constructed internally by a function like "do_config_from()".
>
> A more typical approach would be to put this struct on "the_repository",
> but that's a worse fit for this use case since config reading is not
> scoped to a repository. E.g. we can read config before the repository is
> known ("read_very_early_config()"), blatantly ignore the repo
> ("read_protected_config()"), or read only from a file
> ("git_config_from_file()"). This is especially evident in t5318 and
> t9210, where test-tool and scalar parse config but don't fully
> initialize "the_repository".

I don't mean to just rehash previous discussion
(i.e. https://lore.kernel.org/git/230307.86wn3szrzu.gmgdl@evledraar.gmail.com/
and downthread). I get that you think sticking this in a "struct
repository *" here isn't clean, and would prefer to not conflate the
two.

Fair enough.

But I think this paragraph still does a bad job of justifying this
direction with reference to existing code.

Why? Because from reading it you get the impression that with
read_very_early_config() and read_protected_config() "config reading is
not scoped to a repository", but "scoped to" is doing a *lot* of work
here.

At the start of read_very_early_config() we do:

	struct config_options opts = { 0 };
	[...]
	opts.ignore_repo = 1;
	opts.ignore_worktree = 1;

And then call config_with_options(), which does:

	struct config_include_data inc = CONFIG_INCLUDE_INIT;

And that struct has:

	struct git_config_source *config_source;

Which in turn has:

	/* The repository if blob is not NULL; leave blank for the_repository */
	struct repository *repo;
	const char *blob;

The read_protected_config() is then another thin wrapper for
config_with_options().

So, so far the reader might be genuinely confused, since we already have
a "repo" in scope why can't we use it for this cache? Even if just
reading the system config etc.

For *those* cases I think what I *think* you're going for is that while
we have a "struct repository" already, we don't want to use it for our
"cache", and instead have a file-scoped one.

Personally, I don't see how it's cleaner to always use a file-scope
rather than piggy-back on the global we almost always have (or provide a
fallback), but let's not get on that topic again :)

Now, the case that *is* special on the other hand is
git_config_from_file(), there we really don't have a "repository" at
all, as it never gets the "struct config_include_data inc", or a
"git_config_source".

But if we dig a bit into those cases there's 3x users of
git_config_from_file() outside of config.c itself:

 * setup.c, to read only repo's "config.worktree"
 * setup.c, to read only repo "config"
 * sequencer.c, to read "sequencer/opts"

For the former two, I think the only thing that's needed is something
like this, along with a corresponding change to
do_git_config_sequence():

	diff --git a/config.h b/config.h
	index 7606246531a..b8a3de4eb93 100644
	--- a/config.h
	+++ b/config.h
	@@ -85,7 +85,10 @@ typedef int (*config_parser_event_fn_t)(enum config_event_t type,
	 
	 struct config_options {
	        unsigned int respect_includes : 1;
	+       unsigned int ignore_system : 1;
	+       unsigned int ignore_global : 1;
	        unsigned int ignore_repo : 1;
	+       unsigned int ignore_local : 1;
	        unsigned int ignore_worktree : 1;
	        unsigned int ignore_cmdline : 1;
	        unsigned int system_gently : 1;

I.e. we actually *do* have a repo there, we just haven't bridged the gap
of "ignore most of its config" so we can use config_with_options()
there.

The sequencer.c case is trickier, but presumably for such isolated
reading we could have a lower-level function which would return the
equivalent of a "key_value_info" on errors or whatever.


Anyway, I'm fine with this direction for now, but given the above & my
previous RFC
https://lore.kernel.org/git/RFC-cover-0.5-00000000000-20230317T042408Z-avarab@gmail.com/
I can't help but think we're taking two steps forward & one step
backwards for some of this.

I.e. are we assuming no "repo", but per the above we really do have one,
but we just don't pass it because we don't have a "read only the
worktree config part", or whatever?

Ditto the line number relaying for builtin/config.c, which as my RFC
showed we have one or two API users that care, which we can just
convert...

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [RFC PATCH 0/5] bypass config.c global state with configset
  2023-03-17 19:20     ` Glen Choo
  2023-03-17 23:32       ` Glen Choo
@ 2023-03-29 11:53       ` Ævar Arnfjörð Bjarmason
  1 sibling, 0 replies; 72+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2023-03-29 11:53 UTC (permalink / raw)
  To: Glen Choo
  Cc: git, Jonathan Tan, Junio C Hamano, Jeff King, Emily Shaffer,
	Derrick Stolee, Calvin Wan


On Fri, Mar 17 2023, Glen Choo wrote:

> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
> [...]
>> Similarly, you mention git_parse_int() wanting to report a filename
>> and/or line number. I'm aware that it can do that, but it doesn't do
>> so in the common case, e.g.:
>>
>> 	git -c format.filenameMaxLength=abc log
>> 	fatal: bad numeric config value 'abc' for 'format.filenamemaxlength': invalid unit
>>
>> And the same goes for writing it to e.g. ~/.gitconfig. It's only if
>> you use "git config --file" or similar that we'll report a filename.
>
> That's true, but I think that's a bug, not a feature. See 7/8 [1] where
> I addressed it.
>
> 1.  https://lore.kernel.org/git/3c83d9535a037653c7de2d462a4df3a3c43a9308.1678925506.git.gitgitgadget@gmail.com/
>
>> We can just make config_set_callback() and configset_iter()
>> non-static, so e.g. the builtin/config.c caller that implements
>> "--show-origin" can keep its config_with_options(...) call, but
>> instead of "streaming" the config, it'll buffer it up into a
>> configset.
>
> Hm, so to extrapolate, we could make it so that nobody outside of
> config.c uses the *config_from_file() APIs directly. Instead, all reads
> get buffered up into a configset. That might not be a bad idea. It would
> definitely help with some of your goals of config API surface reduction.
>
> This would be friendlier in cases where we were already creating custom
> configsets (I know we have some of those, but I don't recall where), but
> in cases where we were reading the file directly (e.g.
> builtin/config.c), we'd be taking a memory and runtime hit. I'm not sure
> how I (or others) feel about that yet.

I'm pretty sure that in the end this wouldn't matter, i.e. the time it
takes to parse the config is trivial, and the users of these APIs like
"git config -l --show-origin" aren't performance-senitive.

But for the general case & if you're concerned about this a trivial
addition on top of what I suggested would be to pass a streaming
callback to config_set_callback(), i.e. you could get the values you'd
get from configset_iter() as we parse them.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH v3 3/8] config.c: create config_reader and the_reader
  2023-03-29 10:41       ` Ævar Arnfjörð Bjarmason
@ 2023-03-29 18:57         ` Junio C Hamano
  2023-03-29 20:02           ` Glen Choo
  2023-03-30 17:51         ` Glen Choo
  1 sibling, 1 reply; 72+ messages in thread
From: Junio C Hamano @ 2023-03-29 18:57 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Glen Choo via GitGitGadget, git, Jonathan Tan, Emily Shaffer,
	Calvin Wan, Glen Choo

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> But I think this paragraph still does a bad job of justifying this
> direction with reference to existing code.

I thought it read reasonably well, if not perfect, and do not think
I am capable of rewriting it better, unfortunately.

Care to suggest a better rewrite?

> 	 struct config_options {
> 	        unsigned int respect_includes : 1;
> 	+       unsigned int ignore_system : 1;
> 	+       unsigned int ignore_global : 1;
> 	        unsigned int ignore_repo : 1;
> 	+       unsigned int ignore_local : 1;
> 	        unsigned int ignore_worktree : 1;
> 	        unsigned int ignore_cmdline : 1;
> 	        unsigned int system_gently : 1;

That does look (I am not sure about _local bit, though) well
organized, but I suspect that it can be left for a follow-on
clean-up series, perhaps?

Thanks.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH v3 3/8] config.c: create config_reader and the_reader
  2023-03-29 18:57         ` Junio C Hamano
@ 2023-03-29 20:02           ` Glen Choo
  0 siblings, 0 replies; 72+ messages in thread
From: Glen Choo @ 2023-03-29 20:02 UTC (permalink / raw)
  To: Junio C Hamano, Ævar Arnfjörð Bjarmason
  Cc: Glen Choo via GitGitGadget, git, Jonathan Tan, Emily Shaffer,
	Calvin Wan

Junio C Hamano <gitster@pobox.com> writes:

>>> I.e. we actually *do* have a repo there, we just haven't bridged the gap
>>> of "ignore most of its config" so we can use config_with_options()
>>> there.
>> 	 struct config_options {
>> 	        unsigned int respect_includes : 1;
>> 	+       unsigned int ignore_system : 1;
>> 	+       unsigned int ignore_global : 1;
>> 	        unsigned int ignore_repo : 1;
>> 	+       unsigned int ignore_local : 1;
>> 	        unsigned int ignore_worktree : 1;
>> 	        unsigned int ignore_cmdline : 1;
>> 	        unsigned int system_gently : 1;
>
> That does look (I am not sure about _local bit, though) well
> organized, but I suspect that it can be left for a follow-on
> clean-up series, perhaps?

Makes sense, I did suggest something similar previously:

  https://lore.kernel.org/git/kl6ly1oze7wb.fsf@chooglen-macbookpro.roam.corp.google.com

But I think that's a follow up series for sure.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [PATCH v3 3/8] config.c: create config_reader and the_reader
  2023-03-29 10:41       ` Ævar Arnfjörð Bjarmason
  2023-03-29 18:57         ` Junio C Hamano
@ 2023-03-30 17:51         ` Glen Choo
  1 sibling, 0 replies; 72+ messages in thread
From: Glen Choo @ 2023-03-30 17:51 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason,
	Glen Choo via GitGitGadget
  Cc: git, Jonathan Tan, Emily Shaffer, Calvin Wan

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> On Tue, Mar 28 2023, Glen Choo via GitGitGadget wrote:
>> A more typical approach would be to put this struct on "the_repository",
>> but that's a worse fit for this use case since config reading is not
>> scoped to a repository. E.g. we can read config before the repository is
>> known ("read_very_early_config()"), blatantly ignore the repo
>> ("read_protected_config()"), or read only from a file
>> ("git_config_from_file()"). This is especially evident in t5318 and
>> t9210, where test-tool and scalar parse config but don't fully
>> initialize "the_repository".
>
> [...]
>
> But I think this paragraph still does a bad job of justifying this
> direction with reference to existing code.
>
> Why? Because from reading it you get the impression that with
> read_very_early_config() and read_protected_config() "config reading is
> not scoped to a repository", but "scoped to" is doing a *lot* of work
> here.
>
> [...]
>
> So, so far the reader might be genuinely confused, since we already have
> a "repo" in scope why can't we use it for this cache? Even if just
> reading the system config etc.
>
> For *those* cases I think what I *think* you're going for is that while
> we have a "struct repository" already, we don't want to use it for our
> "cache", and instead have a file-scoped one.

I was probably unclear, bleh. I intended "repository" to mean 'the thing
users interact with', not "struct repository". At any rate, the major
use case I'm concerned with is 'reading config from a file', where the
repository really isn't relevant at all (more on that later).

> Personally, I don't see how it's cleaner to always use a file-scope
> rather than piggy-back on the global we almost always have (or provide a
> fallback), but let's not get on that topic again :)

Piggybacking is probably less intrusive, but I'm not sure it results in
a coherent interface. The _only_ use of git_config_source.repo is to
read config from blobs in config_with_options() (which we need to read
.gitmodules from commits, not the working copy). After that, we don't
actually propagate the "struct repository" at all (because it's not
needed), and I think it makes sense to keep it that way.

> Now, the case that *is* special on the other hand is
> git_config_from_file(), there we really don't have a "repository" at
> all, as it never gets the "struct config_include_data inc", or a
> "git_config_source".
>
> But if we dig a bit into those cases there's 3x users of
> git_config_from_file() outside of config.c itself:
>
>  * setup.c, to read only repo's "config.worktree"
>  * setup.c, to read only repo "config"
>  * sequencer.c, to read "sequencer/opts"

We should also include git_config_from_file_with_options() (which is
basically the same thing), which adds one more caller:

* bundle-uri.c, to read bundle URI files

> For the former two, I think the only thing that's needed is something
> like this, along with a corresponding change to
> do_git_config_sequence():
>
> 	diff --git a/config.h b/config.h
> 	index 7606246531a..b8a3de4eb93 100644
> 	--- a/config.h
> 	+++ b/config.h
> 	@@ -85,7 +85,10 @@ typedef int (*config_parser_event_fn_t)(enum config_event_t type,
> 	 
> 	 struct config_options {
> 	        unsigned int respect_includes : 1;
> 	+       unsigned int ignore_system : 1;
> 	+       unsigned int ignore_global : 1;
> 	        unsigned int ignore_repo : 1;
> 	+       unsigned int ignore_local : 1;
> 	        unsigned int ignore_worktree : 1;
> 	        unsigned int ignore_cmdline : 1;
> 	        unsigned int system_gently : 1;
>
> I.e. we actually *do* have a repo there, we just haven't bridged the gap
> of "ignore most of its config" so we can use config_with_options()
> there.

I'm ambivalent on this. On the one hand, you're not wrong to say that
there probably _is_ a repository that we just happen to not care about,
and maybe it makes sense for config_with_options() to see a "struct
repository". On the other, I'm still quite convinced that the "struct
repository" that we already have just happens to be there by accident
(because "struct git_config_source" is a union of unrelated things), and
I don't think we should be piggybacking onto that.

> The sequencer.c case is trickier, but presumably for such isolated
> reading we could have a lower-level function which would return the
> equivalent of a "key_value_info" on errors or whatever.

bundle-uri.c falls into this case of 'read a file in config syntax' too.
For this reason, I see at least two layers to the config API:

- Parsing a file in config syntax, i.e. the "lower level" API
- Reading Git-specific config (understanding where config is located,
  caching it, etc), i.e. the "higher level" API

We have in-tree callers for _both_ of these layers, and I think that's
appropriate. IOW I don't think we necessarily need to hide the "lower
level" API inside of config.c and expose only the "higher level" API
in-tree [1], which was the impression I got from some of your RFC
patches.

Separating the layers like this also makes it possible to expose the
"lower" level to out-of-tree callers in a sensible way. To parse a
file in a given syntax, a caller shouldn't need to know about
repositories and whatnot. That's exactly what this series is trying to
prepare for, and being principled about the 'config reading cache' is
essential to get this sort of separation.

> I.e. are we assuming no "repo", but per the above we really do have one,
> but we just don't pass it because we don't have a "read only the
> worktree config part", or whatever?

This was addressed above.

> Ditto the line number relaying for builtin/config.c, which as my RFC
> showed we have one or two API users that care, which we can just
> convert...

builtin/config.c is a weird case that I think needs some refactoring,
e.g. there's

- git config -l, which will list all of the git config ("higher level")
- git config -l -f <file>, which lists config from just a file ("lower
  level")

but it uses config_with_options() in both cases! It works because
config_with_options() can switch between "read a subset the git config"
and "read just this file", but it's pretty gross, and we sometimes get
it wrong. (see https://lore.kernel.org/git/xmqqzg9kew1q.fsf@gitster.g/
as an example of how --global is a bit broken).

Your suggestion to convert that (also made upthread, but I can't find
the link for some reason...) to something that uses config_set sounds
pretty reasonable [1].

> Anyway, I'm fine with this direction for now, but given the above & my
> previous RFC
> https://lore.kernel.org/git/RFC-cover-0.5-00000000000-20230317T042408Z-avarab@gmail.com/
> I can't help but think we're taking two steps forward & one step
> backwards for some of this.

Thanks. I appreciate the feedback, nevertheless; I think it's bringing
us closer to a good conclusion.

FWIW I'm working on a followup that will take _many_ steps forward by
adjusting config_fn_t. I don't know how that will pan out, so I
appreciate checking in this series, which is at least a marginal
improvement over the status quo.

[1] "struct config_set" doesn't fall very neatly into the "lower" and
"higher" level API discussion. It's useful to be able to read config
into some in-memory cache (in-tree and out-of-tree), though that isn't
as "low level" as parsing config (without caching). That will probably
be a good follow up to my work to _just_ parse config.

^ permalink raw reply	[flat|nested] 72+ messages in thread

end of thread, other threads:[~2023-03-30 17:51 UTC | newest]

Thread overview: 72+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-03-01  0:38 [PATCH 0/6] [RFC] config.c: use struct for config reading state Glen Choo via GitGitGadget
2023-03-01  0:38 ` [PATCH 1/6] config.c: plumb config_source through static fns Glen Choo via GitGitGadget
2023-03-03 18:02   ` Junio C Hamano
2023-03-01  0:38 ` [PATCH 2/6] config.c: don't assign to "cf" directly Glen Choo via GitGitGadget
2023-03-01  0:38 ` [PATCH 3/6] config.c: create config_reader and the_reader Glen Choo via GitGitGadget
2023-03-03 18:05   ` Junio C Hamano
2023-03-01  0:38 ` [PATCH 4/6] config.c: plumb the_reader through callbacks Glen Choo via GitGitGadget
2023-03-08  9:54   ` Ævar Arnfjörð Bjarmason
2023-03-08 18:00     ` Glen Choo
2023-03-08 18:07       ` Junio C Hamano
2023-03-01  0:38 ` [PATCH 5/6] config.c: remove current_config_kvi Glen Choo via GitGitGadget
2023-03-06 20:12   ` Calvin Wan
2023-03-01  0:38 ` [PATCH 6/6] config.c: remove current_parsing_scope Glen Choo via GitGitGadget
2023-03-06 19:57 ` [PATCH 0/6] [RFC] config.c: use struct for config reading state Jonathan Tan
2023-03-06 21:45   ` Glen Choo
2023-03-06 22:38     ` Jonathan Tan
2023-03-08 10:32       ` Ævar Arnfjörð Bjarmason
2023-03-08 23:09         ` Glen Choo
2023-03-07 11:57 ` Ævar Arnfjörð Bjarmason
2023-03-07 18:22   ` Glen Choo
2023-03-07 18:36     ` Ævar Arnfjörð Bjarmason
2023-03-07 19:36     ` Junio C Hamano
2023-03-07 22:53       ` Glen Choo
2023-03-08  9:17         ` Ævar Arnfjörð Bjarmason
2023-03-08 23:18           ` Glen Choo
2023-03-16  0:11 ` [PATCH v2 0/8] " Glen Choo via GitGitGadget
2023-03-16  0:11   ` [PATCH v2 1/8] config.c: plumb config_source through static fns Glen Choo via GitGitGadget
2023-03-16 21:16     ` Jonathan Tan
2023-03-16  0:11   ` [PATCH v2 2/8] config.c: don't assign to "cf_global" directly Glen Choo via GitGitGadget
2023-03-16 21:18     ` Jonathan Tan
2023-03-16 21:31       ` Junio C Hamano
2023-03-16 22:56       ` Glen Choo
2023-03-16  0:11   ` [PATCH v2 3/8] config.c: create config_reader and the_reader Glen Choo via GitGitGadget
2023-03-16 21:22     ` Jonathan Tan
2023-03-16  0:11   ` [PATCH v2 4/8] config.c: plumb the_reader through callbacks Glen Choo via GitGitGadget
2023-03-16  0:11   ` [PATCH v2 5/8] config.c: remove current_config_kvi Glen Choo via GitGitGadget
2023-03-16  0:11   ` [PATCH v2 6/8] config.c: remove current_parsing_scope Glen Choo via GitGitGadget
2023-03-16  0:11   ` [PATCH v2 7/8] config: report cached filenames in die_bad_number() Glen Choo via GitGitGadget
2023-03-16 22:22     ` Jonathan Tan
2023-03-16 23:05       ` Glen Choo
2023-03-16  0:11   ` [PATCH v2 8/8] config.c: rename "struct config_source cf" Glen Choo via GitGitGadget
2023-03-16  0:15   ` [PATCH v2 0/8] config.c: use struct for config reading state Glen Choo
2023-03-16 22:29   ` Jonathan Tan
2023-03-17  5:01   ` [RFC PATCH 0/5] bypass config.c global state with configset Ævar Arnfjörð Bjarmason
2023-03-17  5:01     ` [RFC PATCH 1/5] config.h: move up "struct key_value_info" Ævar Arnfjörð Bjarmason
2023-03-17  5:01     ` [RFC PATCH 2/5] config.c: use "enum config_origin_type", not "int" Ævar Arnfjörð Bjarmason
2023-03-17  5:01     ` [RFC PATCH 3/5] config API: add a config_origin_type_name() helper Ævar Arnfjörð Bjarmason
2023-03-17  5:01     ` [RFC PATCH 4/5] config.c: refactor configset_iter() Ævar Arnfjörð Bjarmason
2023-03-17  5:01     ` [RFC PATCH 5/5] config API: add and use a repo_config_kvi() Ævar Arnfjörð Bjarmason
2023-03-17 17:17       ` Junio C Hamano
2023-03-17 20:59       ` Jonathan Tan
2023-03-17 16:21     ` [RFC PATCH 0/5] bypass config.c global state with configset Junio C Hamano
2023-03-17 16:28     ` Glen Choo
2023-03-17 19:20     ` Glen Choo
2023-03-17 23:32       ` Glen Choo
2023-03-29 11:53       ` Ævar Arnfjörð Bjarmason
2023-03-28 17:51   ` [PATCH v3 0/8] config.c: use struct for config reading state Glen Choo via GitGitGadget
2023-03-28 17:51     ` [PATCH v3 1/8] config.c: plumb config_source through static fns Glen Choo via GitGitGadget
2023-03-28 17:51     ` [PATCH v3 2/8] config.c: don't assign to "cf_global" directly Glen Choo via GitGitGadget
2023-03-28 17:51     ` [PATCH v3 3/8] config.c: create config_reader and the_reader Glen Choo via GitGitGadget
2023-03-29 10:41       ` Ævar Arnfjörð Bjarmason
2023-03-29 18:57         ` Junio C Hamano
2023-03-29 20:02           ` Glen Choo
2023-03-30 17:51         ` Glen Choo
2023-03-28 17:51     ` [PATCH v3 4/8] config.c: plumb the_reader through callbacks Glen Choo via GitGitGadget
2023-03-28 17:51     ` [PATCH v3 5/8] config.c: remove current_config_kvi Glen Choo via GitGitGadget
2023-03-28 17:51     ` [PATCH v3 6/8] config.c: remove current_parsing_scope Glen Choo via GitGitGadget
2023-03-28 17:51     ` [PATCH v3 7/8] config: report cached filenames in die_bad_number() Glen Choo via GitGitGadget
2023-03-28 17:51     ` [PATCH v3 8/8] config.c: rename "struct config_source cf" Glen Choo via GitGitGadget
2023-03-28 18:00     ` [PATCH v3 0/8] config.c: use struct for config reading state Glen Choo
2023-03-28 20:14       ` Junio C Hamano
2023-03-28 21:24         ` Glen Choo

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).