Git development
 help / color / mirror / Atom feed
* [PATCH v5 03/11] config: add ctx arg to config_fn_t
From: Glen Choo via GitGitGadget @ 2023-06-28 19:26 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Ævar Arnfjörð Bjarmason,
	Emily Shaffer, Phillip Wood, Jeff King, Glen Choo, Glen Choo
In-Reply-To: <pull.1497.v5.git.git.1687980390.gitgitgadget@gmail.com>

From: Glen Choo <chooglen@google.com>

Add a new "const struct config_context *ctx" arg to config_fn_t to hold
additional information about the config iteration operation.
config_context has a "struct key_value_info kvi" member that holds
metadata about the config source being read (e.g. what kind of config
source it is, the filename, etc). In this series, we're only interested
in .kvi, so we could have just used "struct key_value_info" as an arg,
but config_context makes it possible to add/adjust members in the future
without changing the config_fn_t signature. We could also consider other
ways of organizing the args (e.g. moving the config name and value into
config_context or key_value_info), but in my experiments, the
incremental benefit doesn't justify the added complexity (e.g. a
config_fn_t will sometimes invoke another config_fn_t but with a
different config value).

In subsequent commits, the .kvi member will replace the global "struct
config_reader" in config.c, making config iteration a global-free
operation. It requires much more work for the machinery to provide
meaningful values of .kvi, so for now, merely change the signature and
call sites, pass NULL as a placeholder value, and don't rely on the arg
in any meaningful way.

Most of the changes are performed by
contrib/coccinelle/config_fn_ctx.pending.cocci, which, for every
config_fn_t:

- Modifies the signature to accept "const struct config_context *ctx"
- Passes "ctx" to any inner config_fn_t, if needed
- Adds UNUSED attributes to "ctx", if needed

Most config_fn_t instances are easily identified by seeing if they are
called by the various config functions. Most of the remaining ones are
manually named in the .cocci patch. Manual cleanups are still needed,
but the majority of it is trivial; it's either adjusting config_fn_t
that the .cocci patch didn't catch, or adding forward declarations of
"struct config_context ctx" to make the signatures make sense.

The non-trivial changes are in cases where we are invoking a config_fn_t
outside of config machinery, and we now need to decide what value of
"ctx" to pass. These cases are:

- trace2/tr2_cfg.c:tr2_cfg_set_fl()

  This is indirectly called by git_config_set() so that the trace2
  machinery can notice the new config values and update its settings
  using the tr2 config parsing function, i.e. tr2_cfg_cb().

- builtin/checkout.c:checkout_main()

  This calls git_xmerge_config() as a shorthand for parsing a CLI arg.
  This might be worth refactoring away in the future, since
  git_xmerge_config() can call git_default_config(), which can do much
  more than just parsing.

Handle them by creating a KVI_INIT macro that initializes "struct
key_value_info" to a reasonable default, and use that to construct the
"ctx" arg.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 alias.c                                       |   3 +-
 archive-tar.c                                 |   3 +-
 archive-zip.c                                 |   1 +
 builtin/add.c                                 |   5 +-
 builtin/blame.c                               |   5 +-
 builtin/branch.c                              |   5 +-
 builtin/cat-file.c                            |   5 +-
 builtin/checkout.c                            |  12 +-
 builtin/clean.c                               |   5 +-
 builtin/clone.c                               |  11 +-
 builtin/column.c                              |   3 +-
 builtin/commit-graph.c                        |   1 +
 builtin/commit.c                              |  10 +-
 builtin/config.c                              |  10 +-
 builtin/difftool.c                            |   5 +-
 builtin/fetch.c                               |   9 +-
 builtin/fsmonitor--daemon.c                   |   5 +-
 builtin/grep.c                                |   7 +-
 builtin/help.c                                |   5 +-
 builtin/index-pack.c                          |   5 +-
 builtin/log.c                                 |  10 +-
 builtin/merge.c                               |   7 +-
 builtin/multi-pack-index.c                    |   1 +
 builtin/pack-objects.c                        |   5 +-
 builtin/patch-id.c                            |   5 +-
 builtin/pull.c                                |   5 +-
 builtin/push.c                                |   5 +-
 builtin/read-tree.c                           |   5 +-
 builtin/rebase.c                              |   5 +-
 builtin/receive-pack.c                        |   5 +-
 builtin/reflog.c                              |   7 +-
 builtin/remote.c                              |   7 +-
 builtin/repack.c                              |   5 +-
 builtin/reset.c                               |   5 +-
 builtin/send-pack.c                           |   5 +-
 builtin/show-branch.c                         |   5 +-
 builtin/stash.c                               |   5 +-
 builtin/submodule--helper.c                   |   1 +
 builtin/tag.c                                 |   5 +-
 builtin/var.c                                 |   5 +-
 builtin/worktree.c                            |   5 +-
 bundle-uri.c                                  |   8 +-
 compat/mingw.c                                |   3 +-
 compat/mingw.h                                |   4 +-
 config.c                                      |  38 +++--
 config.h                                      |  41 +++--
 connect.c                                     |   4 +-
 .../coccinelle/config_fn_ctx.pending.cocci    | 144 ++++++++++++++++++
 convert.c                                     |   4 +-
 credential.c                                  |   1 +
 delta-islands.c                               |   4 +-
 diff.c                                        |  10 +-
 diff.h                                        |   7 +-
 fetch-pack.c                                  |   5 +-
 fmt-merge-msg.c                               |   5 +-
 fmt-merge-msg.h                               |   3 +-
 fsck.c                                        |   9 +-
 fsck.h                                        |   4 +-
 git-compat-util.h                             |   2 +
 gpg-interface.c                               |   7 +-
 grep.c                                        |   7 +-
 grep.h                                        |   4 +-
 help.c                                        |   7 +-
 http.c                                        |   5 +-
 ident.c                                       |   4 +-
 ident.h                                       |   4 +-
 imap-send.c                                   |   5 +-
 ll-merge.c                                    |   1 +
 ls-refs.c                                     |   1 +
 mailinfo.c                                    |   5 +-
 notes-utils.c                                 |   4 +-
 notes.c                                       |   4 +-
 pager.c                                       |   5 +-
 pretty.c                                      |   1 +
 promisor-remote.c                             |   4 +-
 remote.c                                      |   3 +-
 revision.c                                    |   4 +-
 scalar.c                                      |   4 +-
 sequencer.c                                   |   9 +-
 setup.c                                       |  16 +-
 submodule-config.c                            |  17 ++-
 t/helper/test-config.c                        |  11 +-
 t/helper/test-userdiff.c                      |   4 +-
 trace2/tr2_cfg.c                              |   9 +-
 trace2/tr2_sysenv.c                           |   3 +-
 trailer.c                                     |   2 +
 upload-pack.c                                 |   8 +-
 urlmatch.c                                    |   7 +-
 urlmatch.h                                    |   3 +-
 xdiff-interface.c                             |   5 +-
 xdiff-interface.h                             |   4 +-
 91 files changed, 515 insertions(+), 181 deletions(-)
 create mode 100644 contrib/coccinelle/config_fn_ctx.pending.cocci

diff --git a/alias.c b/alias.c
index 54a1a23d2cf..910dd252a01 100644
--- a/alias.c
+++ b/alias.c
@@ -12,7 +12,8 @@ struct config_alias_data {
 	struct string_list *list;
 };
 
-static int config_alias_cb(const char *key, const char *value, void *d)
+static int config_alias_cb(const char *key, const char *value,
+			   const struct config_context *ctx UNUSED, void *d)
 {
 	struct config_alias_data *data = d;
 	const char *p;
diff --git a/archive-tar.c b/archive-tar.c
index 4cd81d8161e..ef06e516b1f 100644
--- a/archive-tar.c
+++ b/archive-tar.c
@@ -411,7 +411,8 @@ static int tar_filter_config(const char *var, const char *value,
 	return 0;
 }
 
-static int git_tar_config(const char *var, const char *value, void *cb)
+static int git_tar_config(const char *var, const char *value,
+			  const struct config_context *ctx UNUSED, void *cb)
 {
 	if (!strcmp(var, "tar.umask")) {
 		if (value && !strcmp(value, "user")) {
diff --git a/archive-zip.c b/archive-zip.c
index d0d065a312e..b6811951955 100644
--- a/archive-zip.c
+++ b/archive-zip.c
@@ -617,6 +617,7 @@ static void dos_time(timestamp_t *timestamp, int *dos_date, int *dos_time)
 }
 
 static int archive_zip_config(const char *var, const char *value,
+			      const struct config_context *ctx UNUSED,
 			      void *data UNUSED)
 {
 	return userdiff_config(var, value);
diff --git a/builtin/add.c b/builtin/add.c
index e01efdfc50d..1009ae13c13 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -357,7 +357,8 @@ static struct option builtin_add_options[] = {
 	OPT_END(),
 };
 
-static int add_config(const char *var, const char *value, void *cb)
+static int add_config(const char *var, const char *value,
+		      const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(var, "add.ignoreerrors") ||
 	    !strcmp(var, "add.ignore-errors")) {
@@ -368,7 +369,7 @@ static int add_config(const char *var, const char *value, void *cb)
 	if (git_color_config(var, value, cb) < 0)
 		return -1;
 
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 static const char embedded_advice[] = N_(
diff --git a/builtin/blame.c b/builtin/blame.c
index 2df6039a6e0..d0970a1ab13 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -694,7 +694,8 @@ static const char *add_prefix(const char *prefix, const char *path)
 	return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
 }
 
-static int git_blame_config(const char *var, const char *value, void *cb)
+static int git_blame_config(const char *var, const char *value,
+			    const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(var, "blame.showroot")) {
 		show_root = git_config_bool(var, value);
@@ -767,7 +768,7 @@ static int git_blame_config(const char *var, const char *value, void *cb)
 	if (userdiff_config(var, value) < 0)
 		return -1;
 
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 static int blame_copy_callback(const struct option *option, const char *arg, int unset)
diff --git a/builtin/branch.c b/builtin/branch.c
index 8337b9e71bb..af6d2e75fb0 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -83,7 +83,8 @@ static unsigned int colopts;
 
 define_list_config_array(color_branch_slots);
 
-static int git_branch_config(const char *var, const char *value, void *cb)
+static int git_branch_config(const char *var, const char *value,
+			     const struct config_context *ctx, void *cb)
 {
 	const char *slot_name;
 
@@ -120,7 +121,7 @@ static int git_branch_config(const char *var, const char *value, void *cb)
 	if (git_color_config(var, value, cb) < 0)
 		return -1;
 
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 static const char *branch_get_color(enum color_branch ix)
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 7ff56d5a781..b3c41b54c8d 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -873,12 +873,13 @@ static int batch_objects(struct batch_options *opt)
 	return retval;
 }
 
-static int git_cat_file_config(const char *var, const char *value, void *cb)
+static int git_cat_file_config(const char *var, const char *value,
+			       const struct config_context *ctx, void *cb)
 {
 	if (userdiff_config(var, value) < 0)
 		return -1;
 
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 static int batch_option_callback(const struct option *opt,
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 715eeb5048f..4e1f7dc26c2 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1186,7 +1186,8 @@ static int switch_branches(const struct checkout_opts *opts,
 	return ret || writeout_error;
 }
 
-static int git_checkout_config(const char *var, const char *value, void *cb)
+static int git_checkout_config(const char *var, const char *value,
+			       const struct config_context *ctx, void *cb)
 {
 	struct checkout_opts *opts = cb;
 
@@ -1202,7 +1203,7 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
 	if (starts_with(var, "submodule."))
 		return git_default_submodule_config(var, value, NULL);
 
-	return git_xmerge_config(var, value, NULL);
+	return git_xmerge_config(var, value, ctx, NULL);
 }
 
 static void setup_new_branch_info_and_source_tree(
@@ -1689,8 +1690,13 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 	}
 
 	if (opts->conflict_style) {
+		struct key_value_info kvi = KVI_INIT;
+		struct config_context ctx = {
+			.kvi = &kvi,
+		};
 		opts->merge = 1; /* implied */
-		git_xmerge_config("merge.conflictstyle", opts->conflict_style, NULL);
+		git_xmerge_config("merge.conflictstyle", opts->conflict_style,
+				  &ctx, NULL);
 	}
 	if (opts->force) {
 		opts->discard_changes = 1;
diff --git a/builtin/clean.c b/builtin/clean.c
index 57e7f7cac64..5eff1b802a7 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -103,7 +103,8 @@ struct menu_stuff {
 
 define_list_config_array(color_interactive_slots);
 
-static int git_clean_config(const char *var, const char *value, void *cb)
+static int git_clean_config(const char *var, const char *value,
+			    const struct config_context *ctx, void *cb)
 {
 	const char *slot_name;
 
@@ -133,7 +134,7 @@ static int git_clean_config(const char *var, const char *value, void *cb)
 	if (git_color_config(var, value, cb) < 0)
 		return -1;
 
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 static const char *clean_get_color(enum color_clean ix)
diff --git a/builtin/clone.c b/builtin/clone.c
index 15f9912b4ca..c0f6e067493 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -790,7 +790,8 @@ static int checkout(int submodule_progress, int filter_submodules)
 	return err;
 }
 
-static int git_clone_config(const char *k, const char *v, void *cb)
+static int git_clone_config(const char *k, const char *v,
+			    const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(k, "clone.defaultremotename")) {
 		free(remote_name);
@@ -801,17 +802,19 @@ static int git_clone_config(const char *k, const char *v, void *cb)
 	if (!strcmp(k, "clone.filtersubmodules"))
 		config_filter_submodules = git_config_bool(k, v);
 
-	return git_default_config(k, v, cb);
+	return git_default_config(k, v, ctx, cb);
 }
 
-static int write_one_config(const char *key, const char *value, void *data)
+static int write_one_config(const char *key, const char *value,
+			    const struct config_context *ctx,
+			    void *data)
 {
 	/*
 	 * give git_clone_config a chance to write config values back to the
 	 * environment, since git_config_set_multivar_gently only deals with
 	 * config-file writes
 	 */
-	int apply_failed = git_clone_config(key, value, data);
+	int apply_failed = git_clone_config(key, value, ctx, data);
 	if (apply_failed)
 		return apply_failed;
 
diff --git a/builtin/column.c b/builtin/column.c
index de623a16c2d..4a6148ca479 100644
--- a/builtin/column.c
+++ b/builtin/column.c
@@ -13,7 +13,8 @@ static const char * const builtin_column_usage[] = {
 };
 static unsigned int colopts;
 
-static int column_config(const char *var, const char *value, void *cb)
+static int column_config(const char *var, const char *value,
+			 const struct config_context *ctx UNUSED, void *cb)
 {
 	return git_column_config(var, value, cb, &colopts);
 }
diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c
index dd732b35348..1185c49239a 100644
--- a/builtin/commit-graph.c
+++ b/builtin/commit-graph.c
@@ -186,6 +186,7 @@ static int write_option_max_new_filters(const struct option *opt,
 }
 
 static int git_commit_graph_write_config(const char *var, const char *value,
+					 const struct config_context *ctx UNUSED,
 					 void *cb UNUSED)
 {
 	if (!strcmp(var, "commitgraph.maxnewfilters"))
diff --git a/builtin/commit.c b/builtin/commit.c
index 9ab57ea1aaa..6a2b2503328 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1405,7 +1405,8 @@ static int parse_status_slot(const char *slot)
 	return LOOKUP_CONFIG(color_status_slots, slot);
 }
 
-static int git_status_config(const char *k, const char *v, void *cb)
+static int git_status_config(const char *k, const char *v,
+			     const struct config_context *ctx, void *cb)
 {
 	struct wt_status *s = cb;
 	const char *slot_name;
@@ -1490,7 +1491,7 @@ static int git_status_config(const char *k, const char *v, void *cb)
 		s->detect_rename = git_config_rename(k, v);
 		return 0;
 	}
-	return git_diff_ui_config(k, v, NULL);
+	return git_diff_ui_config(k, v, ctx, NULL);
 }
 
 int cmd_status(int argc, const char **argv, const char *prefix)
@@ -1605,7 +1606,8 @@ int cmd_status(int argc, const char **argv, const char *prefix)
 	return 0;
 }
 
-static int git_commit_config(const char *k, const char *v, void *cb)
+static int git_commit_config(const char *k, const char *v,
+			     const struct config_context *ctx, void *cb)
 {
 	struct wt_status *s = cb;
 
@@ -1627,7 +1629,7 @@ static int git_commit_config(const char *k, const char *v, void *cb)
 		return 0;
 	}
 
-	return git_status_config(k, v, s);
+	return git_status_config(k, v, ctx, s);
 }
 
 int cmd_commit(int argc, const char **argv, const char *prefix)
diff --git a/builtin/config.c b/builtin/config.c
index d40fddb042a..f4fccf99cb8 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -217,6 +217,7 @@ static void show_config_scope(struct strbuf *buf)
 }
 
 static int show_all_config(const char *key_, const char *value_,
+			   const struct config_context *ctx UNUSED,
 			   void *cb UNUSED)
 {
 	if (show_origin || show_scope) {
@@ -301,7 +302,8 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value
 	return 0;
 }
 
-static int collect_config(const char *key_, const char *value_, void *cb)
+static int collect_config(const char *key_, const char *value_,
+			  const struct config_context *ctx UNUSED, void *cb)
 {
 	struct strbuf_list *values = cb;
 
@@ -470,6 +472,7 @@ static const char *get_colorbool_slot;
 static char parsed_color[COLOR_MAXLEN];
 
 static int git_get_color_config(const char *var, const char *value,
+				const struct config_context *ctx UNUSED,
 				void *cb UNUSED)
 {
 	if (!strcmp(var, get_color_slot)) {
@@ -503,6 +506,7 @@ static int get_colorbool_found;
 static int get_diff_color_found;
 static int get_color_ui_found;
 static int git_get_colorbool_config(const char *var, const char *value,
+				    const struct config_context *ctx UNUSED,
 				    void *data UNUSED)
 {
 	if (!strcmp(var, get_colorbool_slot))
@@ -561,7 +565,9 @@ struct urlmatch_current_candidate_value {
 	struct strbuf value;
 };
 
-static int urlmatch_collect_fn(const char *var, const char *value, void *cb)
+static int urlmatch_collect_fn(const char *var, const char *value,
+			       const struct config_context *ctx UNUSED,
+			       void *cb)
 {
 	struct string_list *values = cb;
 	struct string_list_item *item = string_list_insert(values, var);
diff --git a/builtin/difftool.c b/builtin/difftool.c
index 0049342f5c0..f289530068b 100644
--- a/builtin/difftool.c
+++ b/builtin/difftool.c
@@ -40,14 +40,15 @@ static const char *const builtin_difftool_usage[] = {
 	NULL
 };
 
-static int difftool_config(const char *var, const char *value, void *cb)
+static int difftool_config(const char *var, const char *value,
+			   const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(var, "difftool.trustexitcode")) {
 		trust_exit_code = git_config_bool(var, value);
 		return 0;
 	}
 
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 static int print_tool_help(void)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index e3871048cf6..a4aa0fbb8f5 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -110,7 +110,8 @@ struct fetch_config {
 	int submodule_fetch_jobs;
 };
 
-static int git_fetch_config(const char *k, const char *v, void *cb)
+static int git_fetch_config(const char *k, const char *v,
+			    const struct config_context *ctx, void *cb)
 {
 	struct fetch_config *fetch_config = cb;
 
@@ -164,7 +165,7 @@ static int git_fetch_config(const char *k, const char *v, void *cb)
 			    "fetch.output", v);
 	}
 
-	return git_default_config(k, v, cb);
+	return git_default_config(k, v, ctx, cb);
 }
 
 static int parse_refmap_arg(const struct option *opt, const char *arg, int unset)
@@ -1799,7 +1800,9 @@ struct remote_group_data {
 	struct string_list *list;
 };
 
-static int get_remote_group(const char *key, const char *value, void *priv)
+static int get_remote_group(const char *key, const char *value,
+			    const struct config_context *ctx UNUSED,
+			    void *priv)
 {
 	struct remote_group_data *g = priv;
 
diff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c
index f6dd9a784c1..91a776e2f17 100644
--- a/builtin/fsmonitor--daemon.c
+++ b/builtin/fsmonitor--daemon.c
@@ -37,7 +37,8 @@ static int fsmonitor__start_timeout_sec = 60;
 #define FSMONITOR__ANNOUNCE_STARTUP "fsmonitor.announcestartup"
 static int fsmonitor__announce_startup = 0;
 
-static int fsmonitor_config(const char *var, const char *value, void *cb)
+static int fsmonitor_config(const char *var, const char *value,
+			    const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(var, FSMONITOR__IPC_THREADS)) {
 		int i = git_config_int(var, value);
@@ -67,7 +68,7 @@ static int fsmonitor_config(const char *var, const char *value, void *cb)
 		return 0;
 	}
 
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 /*
diff --git a/builtin/grep.c b/builtin/grep.c
index 76cf999d310..757d52b94ec 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -290,13 +290,14 @@ static int wait_all(void)
 	return hit;
 }
 
-static int grep_cmd_config(const char *var, const char *value, void *cb)
+static int grep_cmd_config(const char *var, const char *value,
+			   const struct config_context *ctx, void *cb)
 {
-	int st = grep_config(var, value, cb);
+	int st = grep_config(var, value, ctx, cb);
 
 	if (git_color_config(var, value, cb) < 0)
 		st = -1;
-	else if (git_default_config(var, value, cb) < 0)
+	else if (git_default_config(var, value, ctx, cb) < 0)
 		st = -1;
 
 	if (!strcmp(var, "grep.threads")) {
diff --git a/builtin/help.c b/builtin/help.c
index d3cf4af3f6e..c348f201254 100644
--- a/builtin/help.c
+++ b/builtin/help.c
@@ -398,7 +398,8 @@ static int add_man_viewer_info(const char *var, const char *value)
 	return 0;
 }
 
-static int git_help_config(const char *var, const char *value, void *cb)
+static int git_help_config(const char *var, const char *value,
+			   const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(var, "help.format")) {
 		if (!value)
@@ -421,7 +422,7 @@ static int git_help_config(const char *var, const char *value, void *cb)
 	if (starts_with(var, "man."))
 		return add_man_viewer_info(var, value);
 
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 static struct cmdnames main_cmds, other_cmds;
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index d0d8067510b..de8884ea5c2 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -1581,7 +1581,8 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
 	strbuf_release(&pack_name);
 }
 
-static int git_index_pack_config(const char *k, const char *v, void *cb)
+static int git_index_pack_config(const char *k, const char *v,
+				 const struct config_context *ctx, void *cb)
 {
 	struct pack_idx_option *opts = cb;
 
@@ -1608,7 +1609,7 @@ static int git_index_pack_config(const char *k, const char *v, void *cb)
 		else
 			opts->flags &= ~WRITE_REV;
 	}
-	return git_default_config(k, v, cb);
+	return git_default_config(k, v, ctx, cb);
 }
 
 static int cmp_uint32(const void *a_, const void *b_)
diff --git a/builtin/log.c b/builtin/log.c
index c85f13a5d5e..09d6a13075b 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -564,7 +564,8 @@ static int cmd_log_walk(struct rev_info *rev)
 	return retval;
 }
 
-static int git_log_config(const char *var, const char *value, void *cb)
+static int git_log_config(const char *var, const char *value,
+			  const struct config_context *ctx, void *cb)
 {
 	const char *slot_name;
 
@@ -613,7 +614,7 @@ static int git_log_config(const char *var, const char *value, void *cb)
 		return 0;
 	}
 
-	return git_diff_ui_config(var, value, cb);
+	return git_diff_ui_config(var, value, ctx, cb);
 }
 
 int cmd_whatchanged(int argc, const char **argv, const char *prefix)
@@ -979,7 +980,8 @@ static enum cover_from_description parse_cover_from_description(const char *arg)
 		die(_("%s: invalid cover from description mode"), arg);
 }
 
-static int git_format_config(const char *var, const char *value, void *cb)
+static int git_format_config(const char *var, const char *value,
+			     const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(var, "format.headers")) {
 		if (!value)
@@ -1108,7 +1110,7 @@ static int git_format_config(const char *var, const char *value, void *cb)
 	if (!strcmp(var, "diff.noprefix"))
 		return 0;
 
-	return git_log_config(var, value, cb);
+	return git_log_config(var, value, ctx, cb);
 }
 
 static const char *output_directory = NULL;
diff --git a/builtin/merge.c b/builtin/merge.c
index 8da3e46abb0..cad624fb797 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -623,7 +623,8 @@ static void parse_branch_merge_options(char *bmo)
 	free(argv);
 }
 
-static int git_merge_config(const char *k, const char *v, void *cb)
+static int git_merge_config(const char *k, const char *v,
+			    const struct config_context *ctx, void *cb)
 {
 	int status;
 	const char *str;
@@ -668,10 +669,10 @@ static int git_merge_config(const char *k, const char *v, void *cb)
 		return 0;
 	}
 
-	status = fmt_merge_msg_config(k, v, cb);
+	status = fmt_merge_msg_config(k, v, ctx, cb);
 	if (status)
 		return status;
-	return git_diff_ui_config(k, v, cb);
+	return git_diff_ui_config(k, v, ctx, cb);
 }
 
 static int read_tree_trivial(struct object_id *common, struct object_id *head,
diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c
index 1b5083f8b26..a0a7b82cc69 100644
--- a/builtin/multi-pack-index.c
+++ b/builtin/multi-pack-index.c
@@ -82,6 +82,7 @@ static struct option *add_common_options(struct option *prev)
 }
 
 static int git_multi_pack_index_write_config(const char *var, const char *value,
+					     const struct config_context *ctx UNUSED,
 					     void *cb UNUSED)
 {
 	if (!strcmp(var, "pack.writebitmaphashcache")) {
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index 3af2d84f589..34aa0b483a0 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -3135,7 +3135,8 @@ static void prepare_pack(int window, int depth)
 	free(delta_list);
 }
 
-static int git_pack_config(const char *k, const char *v, void *cb)
+static int git_pack_config(const char *k, const char *v,
+			   const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(k, "pack.window")) {
 		window = git_config_int(k, v);
@@ -3227,7 +3228,7 @@ static int git_pack_config(const char *k, const char *v, void *cb)
 		ex->uri = xstrdup(pack_end + 1);
 		oidmap_put(&configured_exclusions, ex);
 	}
-	return git_default_config(k, v, cb);
+	return git_default_config(k, v, ctx, cb);
 }
 
 /* Counters for trace2 output when in --stdin-packs mode. */
diff --git a/builtin/patch-id.c b/builtin/patch-id.c
index 9d5585d3a72..03eddd0fb82 100644
--- a/builtin/patch-id.c
+++ b/builtin/patch-id.c
@@ -196,7 +196,8 @@ struct patch_id_opts {
 	int verbatim;
 };
 
-static int git_patch_id_config(const char *var, const char *value, void *cb)
+static int git_patch_id_config(const char *var, const char *value,
+			       const struct config_context *ctx, void *cb)
 {
 	struct patch_id_opts *opts = cb;
 
@@ -209,7 +210,7 @@ static int git_patch_id_config(const char *var, const char *value, void *cb)
 		return 0;
 	}
 
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 int cmd_patch_id(int argc, const char **argv, const char *prefix)
diff --git a/builtin/pull.c b/builtin/pull.c
index 0c7bac97b75..83fca5b1d46 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -361,7 +361,8 @@ static enum rebase_type config_get_rebase(int *rebase_unspecified)
 /**
  * Read config variables.
  */
-static int git_pull_config(const char *var, const char *value, void *cb)
+static int git_pull_config(const char *var, const char *value,
+			   const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(var, "rebase.autostash")) {
 		config_autostash = git_config_bool(var, value);
@@ -374,7 +375,7 @@ static int git_pull_config(const char *var, const char *value, void *cb)
 		check_trust_level = 0;
 	}
 
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 /**
diff --git a/builtin/push.c b/builtin/push.c
index dbdf609daf3..a2f68f77324 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -510,7 +510,8 @@ static void set_push_cert_flags(int *flags, int v)
 }
 
 
-static int git_push_config(const char *k, const char *v, void *cb)
+static int git_push_config(const char *k, const char *v,
+			   const struct config_context *ctx, void *cb)
 {
 	const char *slot_name;
 	int *flags = cb;
@@ -577,7 +578,7 @@ static int git_push_config(const char *k, const char *v, void *cb)
 		return 0;
 	}
 
-	return git_default_config(k, v, NULL);
+	return git_default_config(k, v, ctx, NULL);
 }
 
 int cmd_push(int argc, const char **argv, const char *prefix)
diff --git a/builtin/read-tree.c b/builtin/read-tree.c
index 440f19b1b87..8877dd6d4b5 100644
--- a/builtin/read-tree.c
+++ b/builtin/read-tree.c
@@ -102,12 +102,13 @@ static int debug_merge(const struct cache_entry * const *stages,
 	return 0;
 }
 
-static int git_read_tree_config(const char *var, const char *value, void *cb)
+static int git_read_tree_config(const char *var, const char *value,
+				const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(var, "submodule.recurse"))
 		return git_default_submodule_config(var, value, cb);
 
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
diff --git a/builtin/rebase.c b/builtin/rebase.c
index ace1d5e8d11..60930e2d8e0 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -772,7 +772,8 @@ static void parse_rebase_merges_value(struct rebase_options *options, const char
 		die(_("Unknown rebase-merges mode: %s"), value);
 }
 
-static int rebase_config(const char *var, const char *value, void *data)
+static int rebase_config(const char *var, const char *value,
+			 const struct config_context *ctx, void *data)
 {
 	struct rebase_options *opts = data;
 
@@ -831,7 +832,7 @@ static int rebase_config(const char *var, const char *value, void *data)
 		return git_config_string(&opts->default_backend, var, value);
 	}
 
-	return git_default_config(var, value, data);
+	return git_default_config(var, value, ctx, data);
 }
 
 static int checkout_up_to_date(struct rebase_options *options)
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 1a31a583674..94d9898aff7 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -139,7 +139,8 @@ static enum deny_action parse_deny_action(const char *var, const char *value)
 	return DENY_IGNORE;
 }
 
-static int receive_pack_config(const char *var, const char *value, void *cb)
+static int receive_pack_config(const char *var, const char *value,
+			       const struct config_context *ctx, void *cb)
 {
 	int status = parse_hide_refs_config(var, value, "receive", &hidden_refs);
 
@@ -266,7 +267,7 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
 		return 0;
 	}
 
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 static void show_ref(const char *path, const struct object_id *oid)
diff --git a/builtin/reflog.c b/builtin/reflog.c
index a1fa0c855f4..84251cc9517 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -108,7 +108,8 @@ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)
 #define EXPIRE_TOTAL   01
 #define EXPIRE_UNREACH 02
 
-static int reflog_expire_config(const char *var, const char *value, void *cb)
+static int reflog_expire_config(const char *var, const char *value,
+				const struct config_context *ctx, void *cb)
 {
 	const char *pattern, *key;
 	size_t pattern_len;
@@ -117,7 +118,7 @@ static int reflog_expire_config(const char *var, const char *value, void *cb)
 	struct reflog_expire_cfg *ent;
 
 	if (parse_config_key(var, "gc", &pattern, &pattern_len, &key) < 0)
-		return git_default_config(var, value, cb);
+		return git_default_config(var, value, ctx, cb);
 
 	if (!strcmp(key, "reflogexpire")) {
 		slot = EXPIRE_TOTAL;
@@ -128,7 +129,7 @@ static int reflog_expire_config(const char *var, const char *value, void *cb)
 		if (git_config_expiry_date(&expire, var, value))
 			return -1;
 	} else
-		return git_default_config(var, value, cb);
+		return git_default_config(var, value, ctx, cb);
 
 	if (!pattern) {
 		switch (slot) {
diff --git a/builtin/remote.c b/builtin/remote.c
index 1e0b137d977..87de81105e2 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -268,6 +268,7 @@ static const char *abbrev_ref(const char *name, const char *prefix)
 #define abbrev_branch(name) abbrev_ref((name), "refs/heads/")
 
 static int config_read_branches(const char *key, const char *value,
+				const struct config_context *ctx UNUSED,
 				void *data UNUSED)
 {
 	const char *orig_key = key;
@@ -645,7 +646,7 @@ struct push_default_info
 };
 
 static int config_read_push_default(const char *key, const char *value,
-	void *cb)
+	const struct config_context *ctx UNUSED, void *cb)
 {
 	struct push_default_info* info = cb;
 	if (strcmp(key, "remote.pushdefault") ||
@@ -1494,7 +1495,9 @@ static int prune(int argc, const char **argv, const char *prefix)
 	return result;
 }
 
-static int get_remote_default(const char *key, const char *value UNUSED, void *priv)
+static int get_remote_default(const char *key, const char *value UNUSED,
+			      const struct config_context *ctx UNUSED,
+			      void *priv)
 {
 	if (strcmp(key, "remotes.default") == 0) {
 		int *found = priv;
diff --git a/builtin/repack.c b/builtin/repack.c
index 0541c3ce157..6f74570bf94 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -59,7 +59,8 @@ struct pack_objects_args {
 	int local;
 };
 
-static int repack_config(const char *var, const char *value, void *cb)
+static int repack_config(const char *var, const char *value,
+			 const struct config_context *ctx, void *cb)
 {
 	struct pack_objects_args *cruft_po_args = cb;
 	if (!strcmp(var, "repack.usedeltabaseoffset")) {
@@ -91,7 +92,7 @@ static int repack_config(const char *var, const char *value, void *cb)
 		return git_config_string(&cruft_po_args->depth, var, value);
 	if (!strcmp(var, "repack.cruftthreads"))
 		return git_config_string(&cruft_po_args->threads, var, value);
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 /*
diff --git a/builtin/reset.c b/builtin/reset.c
index f99f32d5802..1ae82f2e89c 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -312,12 +312,13 @@ static int reset_refs(const char *rev, const struct object_id *oid)
 	return update_ref_status;
 }
 
-static int git_reset_config(const char *var, const char *value, void *cb)
+static int git_reset_config(const char *var, const char *value,
+			    const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(var, "submodule.recurse"))
 		return git_default_submodule_config(var, value, cb);
 
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 int cmd_reset(int argc, const char **argv, const char *prefix)
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 4784143004d..cd6d9e41129 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -131,7 +131,8 @@ static void print_helper_status(struct ref *ref)
 	strbuf_release(&buf);
 }
 
-static int send_pack_config(const char *k, const char *v, void *cb)
+static int send_pack_config(const char *k, const char *v,
+			    const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(k, "push.gpgsign")) {
 		const char *value;
@@ -151,7 +152,7 @@ static int send_pack_config(const char *k, const char *v, void *cb)
 			}
 		}
 	}
-	return git_default_config(k, v, cb);
+	return git_default_config(k, v, ctx, cb);
 }
 
 int cmd_send_pack(int argc, const char **argv, const char *prefix)
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index a2461270d4b..f2fd245b838 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -559,7 +559,8 @@ static void append_one_rev(const char *av)
 	die("bad sha1 reference %s", av);
 }
 
-static int git_show_branch_config(const char *var, const char *value, void *cb)
+static int git_show_branch_config(const char *var, const char *value,
+				  const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(var, "showbranch.default")) {
 		if (!value)
@@ -582,7 +583,7 @@ static int git_show_branch_config(const char *var, const char *value, void *cb)
 	if (git_color_config(var, value, cb) < 0)
 		return -1;
 
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 static int omit_in_dense(struct commit *commit, struct commit **rev, int n)
diff --git a/builtin/stash.c b/builtin/stash.c
index a7e17ffe384..e5c4246d2d4 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -837,7 +837,8 @@ static int show_stat = 1;
 static int show_patch;
 static int show_include_untracked;
 
-static int git_stash_config(const char *var, const char *value, void *cb)
+static int git_stash_config(const char *var, const char *value,
+			    const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(var, "stash.showstat")) {
 		show_stat = git_config_bool(var, value);
@@ -851,7 +852,7 @@ static int git_stash_config(const char *var, const char *value, void *cb)
 		show_include_untracked = git_config_bool(var, value);
 		return 0;
 	}
-	return git_diff_basic_config(var, value, cb);
+	return git_diff_basic_config(var, value, ctx, cb);
 }
 
 static void diff_include_untracked(const struct stash_info *info, struct diff_options *diff_opt)
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 6a16208e8a8..f8e9d85e77a 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2192,6 +2192,7 @@ static int update_clone_task_finished(int result,
 }
 
 static int git_update_clone_config(const char *var, const char *value,
+				   const struct config_context *ctx UNUSED,
 				   void *cb)
 {
 	int *max_jobs = cb;
diff --git a/builtin/tag.c b/builtin/tag.c
index 1acf5f7a59f..b7dfb5e2cad 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -188,7 +188,8 @@ static const char tag_template_nocleanup[] =
 	"Lines starting with '%c' will be kept; you may remove them"
 	" yourself if you want to.\n");
 
-static int git_tag_config(const char *var, const char *value, void *cb)
+static int git_tag_config(const char *var, const char *value,
+			  const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(var, "tag.gpgsign")) {
 		config_sign_tag = git_config_bool(var, value);
@@ -213,7 +214,7 @@ static int git_tag_config(const char *var, const char *value, void *cb)
 	if (git_color_config(var, value, cb) < 0)
 		return -1;
 
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 static void write_tag_body(int fd, const struct object_id *oid)
diff --git a/builtin/var.c b/builtin/var.c
index 21499989807..ae011bdf409 100644
--- a/builtin/var.c
+++ b/builtin/var.c
@@ -71,13 +71,14 @@ static const struct git_var *get_git_var(const char *var)
 	return NULL;
 }
 
-static int show_config(const char *var, const char *value, void *cb)
+static int show_config(const char *var, const char *value,
+		       const struct config_context *ctx, void *cb)
 {
 	if (value)
 		printf("%s=%s\n", var, value);
 	else
 		printf("%s\n", var);
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 int cmd_var(int argc, const char **argv, const char *prefix UNUSED)
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 5a9cf076ad2..a30d37a273f 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -128,14 +128,15 @@ static int verbose;
 static int guess_remote;
 static timestamp_t expire;
 
-static int git_worktree_config(const char *var, const char *value, void *cb)
+static int git_worktree_config(const char *var, const char *value,
+			       const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(var, "worktree.guessremote")) {
 		guess_remote = git_config_bool(var, value);
 		return 0;
 	}
 
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 static int delete_git_dir(const char *id)
diff --git a/bundle-uri.c b/bundle-uri.c
index 2a2db1a1d39..0d5acc3dc51 100644
--- a/bundle-uri.c
+++ b/bundle-uri.c
@@ -224,7 +224,9 @@ static int bundle_list_update(const char *key, const char *value,
 	return 0;
 }
 
-static int config_to_bundle_list(const char *key, const char *value, void *data)
+static int config_to_bundle_list(const char *key, const char *value,
+				 const struct config_context *ctx UNUSED,
+				 void *data)
 {
 	struct bundle_list *list = data;
 	return bundle_list_update(key, value, list);
@@ -871,7 +873,9 @@ cached:
 	return advertise_bundle_uri;
 }
 
-static int config_to_packet_line(const char *key, const char *value, void *data)
+static int config_to_packet_line(const char *key, const char *value,
+				 const struct config_context *ctx UNUSED,
+				 void *data)
 {
 	struct packet_reader *writer = data;
 
diff --git a/compat/mingw.c b/compat/mingw.c
index d06cdc6254f..4186bc7a417 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -244,7 +244,8 @@ static int core_restrict_inherited_handles = -1;
 static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
 static char *unset_environment_variables;
 
-int mingw_core_config(const char *var, const char *value, void *cb)
+int mingw_core_config(const char *var, const char *value,
+		      const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(var, "core.hidedotfiles")) {
 		if (value && !strcasecmp(value, "dotgitonly"))
diff --git a/compat/mingw.h b/compat/mingw.h
index 209cf7cebad..5e34c873473 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -11,7 +11,9 @@ typedef _sigset_t sigset_t;
 #undef _POSIX_THREAD_SAFE_FUNCTIONS
 #endif
 
-int mingw_core_config(const char *var, const char *value, void *cb);
+struct config_context;
+int mingw_core_config(const char *var, const char *value,
+		      const struct config_context *ctx, void *cb);
 #define platform_core_config mingw_core_config
 
 /*
diff --git a/config.c b/config.c
index 2eeb6621421..850e432e301 100644
--- a/config.c
+++ b/config.c
@@ -209,7 +209,8 @@ struct config_include_data {
 };
 #define CONFIG_INCLUDE_INIT { 0 }
 
-static int git_config_include(const char *var, const char *value, void *data);
+static int git_config_include(const char *var, const char *value,
+			      const struct config_context *ctx, void *data);
 
 #define MAX_INCLUDE_DEPTH 10
 static const char include_depth_advice[] = N_(
@@ -388,7 +389,8 @@ static int include_by_branch(const char *cond, size_t cond_len)
 	return ret;
 }
 
-static int add_remote_url(const char *var, const char *value, void *data)
+static int add_remote_url(const char *var, const char *value,
+			  const struct config_context *ctx UNUSED, void *data)
 {
 	struct string_list *remote_urls = data;
 	const char *remote_name;
@@ -423,6 +425,7 @@ static void populate_remote_urls(struct config_include_data *inc)
 }
 
 static int forbid_remote_url(const char *var, const char *value UNUSED,
+			     const struct config_context *ctx UNUSED,
 			     void *data UNUSED)
 {
 	const char *remote_name;
@@ -486,7 +489,9 @@ static int include_condition_is_true(struct config_source *cs,
 	return 0;
 }
 
-static int git_config_include(const char *var, const char *value, void *data)
+static int git_config_include(const char *var, const char *value,
+			      const struct config_context *ctx,
+			      void *data)
 {
 	struct config_include_data *inc = data;
 	struct config_source *cs = inc->config_reader->source;
@@ -498,7 +503,7 @@ static int git_config_include(const char *var, const char *value, void *data)
 	 * Pass along all values, including "include" directives; this makes it
 	 * possible to query information on the includes themselves.
 	 */
-	ret = inc->fn(var, value, inc->data);
+	ret = inc->fn(var, value, NULL, inc->data);
 	if (ret < 0)
 		return ret;
 
@@ -680,7 +685,7 @@ static int config_parse_pair(const char *key, const char *value,
 	if (git_config_parse_key(key, &canonical_name, NULL))
 		return -1;
 
-	ret = (fn(canonical_name, value, data) < 0) ? -1 : 0;
+	ret = (fn(canonical_name, value, NULL, data) < 0) ? -1 : 0;
 	free(canonical_name);
 	return ret;
 }
@@ -968,7 +973,7 @@ static int get_value(struct config_source *cs, config_fn_t fn, void *data,
 	 * accurate line number in error messages.
 	 */
 	cs->linenr--;
-	ret = fn(name->buf, value, data);
+	ret = fn(name->buf, value, NULL, data);
 	if (ret >= 0)
 		cs->linenr++;
 	return ret;
@@ -1562,7 +1567,8 @@ int git_config_color(char *dest, const char *var, const char *value)
 	return 0;
 }
 
-static int git_default_core_config(const char *var, const char *value, void *cb)
+static int git_default_core_config(const char *var, const char *value,
+				   const struct config_context *ctx, void *cb)
 {
 	/* This needs a better name */
 	if (!strcmp(var, "core.filemode")) {
@@ -1842,7 +1848,7 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
 	}
 
 	/* Add other config variables here and to Documentation/config.txt. */
-	return platform_core_config(var, value, cb);
+	return platform_core_config(var, value, ctx, cb);
 }
 
 static int git_default_sparse_config(const char *var, const char *value)
@@ -1944,15 +1950,16 @@ static int git_default_mailmap_config(const char *var, const char *value)
 	return 0;
 }
 
-int git_default_config(const char *var, const char *value, void *cb)
+int git_default_config(const char *var, const char *value,
+		       const struct config_context *ctx, void *cb)
 {
 	if (starts_with(var, "core."))
-		return git_default_core_config(var, value, cb);
+		return git_default_core_config(var, value, ctx, cb);
 
 	if (starts_with(var, "user.") ||
 	    starts_with(var, "author.") ||
 	    starts_with(var, "committer."))
-		return git_ident_config(var, value, cb);
+		return git_ident_config(var, value, ctx, cb);
 
 	if (starts_with(var, "i18n."))
 		return git_default_i18n_config(var, value);
@@ -2318,7 +2325,7 @@ static void configset_iter(struct config_reader *reader, struct config_set *set,
 
 		config_reader_set_kvi(reader, values->items[value_index].util);
 
-		if (fn(entry->key, values->items[value_index].string, data) < 0)
+		if (fn(entry->key, values->items[value_index].string, NULL, data) < 0)
 			git_die_config_linenr(entry->key,
 					      reader->config_kvi->filename,
 					      reader->config_kvi->linenr);
@@ -2496,7 +2503,9 @@ struct configset_add_data {
 };
 #define CONFIGSET_ADD_INIT { 0 }
 
-static int config_set_callback(const char *key, const char *value, void *cb)
+static int config_set_callback(const char *key, const char *value,
+			       const struct config_context *ctx UNUSED,
+			       void *cb)
 {
 	struct configset_add_data *data = cb;
 	configset_add_value(data->config_reader, data->config_set, key, value);
@@ -3106,7 +3115,8 @@ static int store_aux_event(enum config_event_t type,
 	return 0;
 }
 
-static int store_aux(const char *key, const char *value, void *cb)
+static int store_aux(const char *key, const char *value,
+		     const struct config_context *ctx UNUSED, void *cb)
 {
 	struct config_store_data *store = cb;
 
diff --git a/config.h b/config.h
index d1c5577589e..cd30125a8a4 100644
--- a/config.h
+++ b/config.h
@@ -110,8 +110,29 @@ struct config_options {
 	} error_action;
 };
 
+/* Config source metadata for a given config key-value pair */
+struct key_value_info {
+	const char *filename;
+	int linenr;
+	enum config_origin_type origin_type;
+	enum config_scope scope;
+};
+#define KVI_INIT { \
+	.filename = NULL, \
+	.linenr = -1, \
+	.origin_type = CONFIG_ORIGIN_UNKNOWN, \
+	.scope = CONFIG_SCOPE_UNKNOWN, \
+}
+
+/* Captures additional information that a config callback can use. */
+struct config_context {
+	/* Config source metadata for key and value. */
+	const struct key_value_info *kvi;
+};
+#define CONFIG_CONTEXT_INIT { 0 }
+
 /**
- * A config callback function takes three parameters:
+ * A config callback function takes four parameters:
  *
  * - the name of the parsed variable. This is in canonical "flat" form: the
  *   section, subsection, and variable segments will be separated by dots,
@@ -122,15 +143,22 @@ struct config_options {
  *   value specified, the value will be NULL (typically this means it
  *   should be interpreted as boolean true).
  *
+ * - the 'config context', that is, additional information about the config
+ *   iteration operation provided by the config machinery. For example, this
+ *   includes information about the config source being parsed (e.g. the
+ *   filename).
+ *
  * - a void pointer passed in by the caller of the config API; this can
  *   contain callback-specific data
  *
  * A config callback should return 0 for success, or -1 if the variable
  * could not be parsed properly.
  */
-typedef int (*config_fn_t)(const char *, const char *, void *);
+typedef int (*config_fn_t)(const char *, const char *,
+			   const struct config_context *, void *);
 
-int git_default_config(const char *, const char *, void *);
+int git_default_config(const char *, const char *,
+		       const struct config_context *, void *);
 
 /**
  * Read a specific file in git-config format.
@@ -667,13 +695,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
diff --git a/connect.c b/connect.c
index 3a0186280c4..cddac1d96b8 100644
--- a/connect.c
+++ b/connect.c
@@ -964,7 +964,7 @@ static struct child_process *git_tcp_connect(int fd[2], char *host, int flags)
 static char *git_proxy_command;
 
 static int git_proxy_command_options(const char *var, const char *value,
-		void *cb)
+		const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(var, "core.gitproxy")) {
 		const char *for_pos;
@@ -1010,7 +1010,7 @@ static int git_proxy_command_options(const char *var, const char *value,
 		return 0;
 	}
 
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 static int git_use_proxy(const char *host)
diff --git a/contrib/coccinelle/config_fn_ctx.pending.cocci b/contrib/coccinelle/config_fn_ctx.pending.cocci
new file mode 100644
index 00000000000..6d3d1000a96
--- /dev/null
+++ b/contrib/coccinelle/config_fn_ctx.pending.cocci
@@ -0,0 +1,144 @@
+@ get_fn @
+identifier fn, R;
+@@
+(
+(
+git_config_from_file
+|
+git_config_from_file_with_options
+|
+git_config_from_mem
+|
+git_config_from_blob_oid
+|
+read_early_config
+|
+read_very_early_config
+|
+config_with_options
+|
+git_config
+|
+git_protected_config
+|
+config_from_gitmodules
+)
+  (fn, ...)
+|
+repo_config(R, fn, ...)
+)
+
+@ extends get_fn @
+identifier C1, C2, D;
+@@
+int fn(const char *C1, const char *C2,
++ const struct config_context *ctx,
+  void *D);
+
+@ extends get_fn @
+@@
+int fn(const char *, const char *,
++ const struct config_context *,
+  void *);
+
+@ extends get_fn @
+// Don't change fns that look like callback fns but aren't
+identifier fn2 != tar_filter_config && != git_diff_heuristic_config &&
+  != git_default_submodule_config && != git_color_config &&
+  != bundle_list_update && != parse_object_filter_config;
+identifier C1, C2, D1, D2, S;
+attribute name UNUSED;
+@@
+int fn(const char *C1, const char *C2,
++ const struct config_context *ctx,
+  void *D1) {
+<+...
+(
+fn2(C1, C2
++ , ctx
+, D2);
+|
+if(fn2(C1, C2
++ , ctx
+, D2) < 0) { ... }
+|
+return fn2(C1, C2
++ , ctx
+, D2);
+|
+S = fn2(C1, C2
++ , ctx
+, D2);
+)
+...+>
+  }
+
+@ extends get_fn@
+identifier C1, C2, D;
+attribute name UNUSED;
+@@
+int fn(const char *C1, const char *C2,
++ const struct config_context *ctx UNUSED,
+  void *D) {...}
+
+
+// The previous rules don't catch all callbacks, especially if they're defined
+// in a separate file from the git_config() call. Fix these manually.
+@@
+identifier C1, C2, D;
+attribute name UNUSED;
+@@
+int
+(
+git_ident_config
+|
+urlmatch_collect_fn
+|
+write_one_config
+|
+forbid_remote_url
+|
+credential_config_callback
+)
+  (const char *C1, const char *C2,
++ const struct config_context *ctx UNUSED,
+  void *D) {...}
+
+@@
+identifier C1, C2, D, D2, S, fn2;
+@@
+int
+(
+http_options
+|
+git_status_config
+|
+git_commit_config
+|
+git_default_core_config
+|
+grep_config
+)
+  (const char *C1, const char *C2,
++ const struct config_context *ctx,
+  void *D) {
+<+...
+(
+fn2(C1, C2
++ , ctx
+, D2);
+|
+if(fn2(C1, C2
++ , ctx
+, D2) < 0) { ... }
+|
+return fn2(C1, C2
++ , ctx
+, D2);
+|
+S = fn2(C1, C2
++ , ctx
+, D2);
+)
+...+>
+  }
diff --git a/convert.c b/convert.c
index 9ee79fe4699..8e96cf83030 100644
--- a/convert.c
+++ b/convert.c
@@ -1015,7 +1015,9 @@ static int apply_filter(const char *path, const char *src, size_t len,
 	return 0;
 }
 
-static int read_convert_config(const char *var, const char *value, void *cb UNUSED)
+static int read_convert_config(const char *var, const char *value,
+			       const struct config_context *ctx UNUSED,
+			       void *cb UNUSED)
 {
 	const char *key, *name;
 	size_t namelen;
diff --git a/credential.c b/credential.c
index 8825c6f1320..d6647541634 100644
--- a/credential.c
+++ b/credential.c
@@ -49,6 +49,7 @@ static int credential_from_potentially_partial_url(struct credential *c,
 						   const char *url);
 
 static int credential_config_callback(const char *var, const char *value,
+				      const struct config_context *ctx UNUSED,
 				      void *data)
 {
 	struct credential *c = data;
diff --git a/delta-islands.c b/delta-islands.c
index c824a5f6a42..5fc6ea6ff55 100644
--- a/delta-islands.c
+++ b/delta-islands.c
@@ -341,7 +341,9 @@ static void free_remote_islands(kh_str_t *remote_islands)
 	kh_destroy_str(remote_islands);
 }
 
-static int island_config_callback(const char *k, const char *v, void *cb)
+static int island_config_callback(const char *k, const char *v,
+				  const struct config_context *ctx UNUSED,
+				  void *cb)
 {
 	struct island_load_data *ild = cb;
 
diff --git a/diff.c b/diff.c
index c106f8a4ffa..0e382c8f7f0 100644
--- a/diff.c
+++ b/diff.c
@@ -357,7 +357,8 @@ static unsigned parse_color_moved_ws(const char *arg)
 	return ret;
 }
 
-int git_diff_ui_config(const char *var, const char *value, void *cb)
+int git_diff_ui_config(const char *var, const char *value,
+		       const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
 		diff_use_color_default = git_config_colorbool(var, value);
@@ -440,10 +441,11 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
 	if (git_color_config(var, value, cb) < 0)
 		return -1;
 
-	return git_diff_basic_config(var, value, cb);
+	return git_diff_basic_config(var, value, ctx, cb);
 }
 
-int git_diff_basic_config(const char *var, const char *value, void *cb)
+int git_diff_basic_config(const char *var, const char *value,
+			  const struct config_context *ctx, void *cb)
 {
 	const char *name;
 
@@ -495,7 +497,7 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
 	if (git_diff_heuristic_config(var, value, cb) < 0)
 		return -1;
 
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 static char *quote_two(const char *one, const char *two)
diff --git a/diff.h b/diff.h
index 6c10ce289da..33a4f4d5988 100644
--- a/diff.h
+++ b/diff.h
@@ -531,10 +531,13 @@ void free_diffstat_info(struct diffstat_t *diffstat);
 int parse_long_opt(const char *opt, const char **argv,
 		   const char **optarg);
 
-int git_diff_basic_config(const char *var, const char *value, void *cb);
+struct config_context;
+int git_diff_basic_config(const char *var, const char *value,
+			  const struct config_context *ctx, void *cb);
 int git_diff_heuristic_config(const char *var, const char *value, void *cb);
 void init_diff_ui_defaults(void);
-int git_diff_ui_config(const char *var, const char *value, void *cb);
+int git_diff_ui_config(const char *var, const char *value,
+		       const struct config_context *ctx, void *cb);
 void repo_diff_setup(struct repository *, struct diff_options *);
 struct option *add_diff_options(const struct option *, struct diff_options *);
 int diff_opt_parse(struct diff_options *, const char **, int, const char *);
diff --git a/fetch-pack.c b/fetch-pack.c
index 0f71054fbae..5f3d40f3d09 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -1860,7 +1860,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
 	return ref;
 }
 
-static int fetch_pack_config_cb(const char *var, const char *value, void *cb)
+static int fetch_pack_config_cb(const char *var, const char *value,
+				const struct config_context *ctx, void *cb)
 {
 	if (strcmp(var, "fetch.fsck.skiplist") == 0) {
 		const char *path;
@@ -1882,7 +1883,7 @@ static int fetch_pack_config_cb(const char *var, const char *value, void *cb)
 		return 0;
 	}
 
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 static void fetch_pack_config(void)
diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c
index 5af0d4715ba..10137444321 100644
--- a/fmt-merge-msg.c
+++ b/fmt-merge-msg.c
@@ -20,7 +20,8 @@ static int use_branch_desc;
 static int suppress_dest_pattern_seen;
 static struct string_list suppress_dest_patterns = STRING_LIST_INIT_DUP;
 
-int fmt_merge_msg_config(const char *key, const char *value, void *cb)
+int fmt_merge_msg_config(const char *key, const char *value,
+			 const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) {
 		int is_bool;
@@ -40,7 +41,7 @@ int fmt_merge_msg_config(const char *key, const char *value, void *cb)
 			string_list_append(&suppress_dest_patterns, value);
 		suppress_dest_pattern_seen = 1;
 	} else {
-		return git_default_config(key, value, cb);
+		return git_default_config(key, value, ctx, cb);
 	}
 	return 0;
 }
diff --git a/fmt-merge-msg.h b/fmt-merge-msg.h
index 99054042dc5..73ca3e44652 100644
--- a/fmt-merge-msg.h
+++ b/fmt-merge-msg.h
@@ -13,7 +13,8 @@ struct fmt_merge_msg_opts {
 };
 
 extern int merge_log_config;
-int fmt_merge_msg_config(const char *key, const char *value, void *cb);
+int fmt_merge_msg_config(const char *key, const char *value,
+			 const struct config_context *ctx, void *cb);
 int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
 		  struct fmt_merge_msg_opts *);
 
diff --git a/fsck.c b/fsck.c
index 3261ef9ec28..55b6a694853 100644
--- a/fsck.c
+++ b/fsck.c
@@ -1163,7 +1163,9 @@ struct fsck_gitmodules_data {
 	int ret;
 };
 
-static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata)
+static int fsck_gitmodules_fn(const char *var, const char *value,
+			      const struct config_context *ctx UNUSED,
+			      void *vdata)
 {
 	struct fsck_gitmodules_data *data = vdata;
 	const char *subsection, *key;
@@ -1373,7 +1375,8 @@ int fsck_finish(struct fsck_options *options)
 	return ret;
 }
 
-int git_fsck_config(const char *var, const char *value, void *cb)
+int git_fsck_config(const char *var, const char *value,
+		    const struct config_context *ctx, void *cb)
 {
 	struct fsck_options *options = cb;
 	if (strcmp(var, "fsck.skiplist") == 0) {
@@ -1394,7 +1397,7 @@ int git_fsck_config(const char *var, const char *value, void *cb)
 		return 0;
 	}
 
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
 
 /*
diff --git a/fsck.h b/fsck.h
index e17730e9da9..6359ba359bd 100644
--- a/fsck.h
+++ b/fsck.h
@@ -233,10 +233,12 @@ void fsck_put_object_name(struct fsck_options *options,
 const char *fsck_describe_object(struct fsck_options *options,
 				 const struct object_id *oid);
 
+struct key_value_info;
 /*
  * git_config() callback for use by fsck-y tools that want to support
  * fsck.<msg> fsck.skipList etc.
  */
-int git_fsck_config(const char *var, const char *value, void *cb);
+int git_fsck_config(const char *var, const char *value,
+		    const struct config_context *ctx, void *cb);
 
 #endif
diff --git a/git-compat-util.h b/git-compat-util.h
index 5b2b99c17c5..14e8aacb957 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -440,8 +440,10 @@ typedef uintmax_t timestamp_t;
 #endif
 
 #ifndef platform_core_config
+struct config_context;
 static inline int noop_core_config(const char *var UNUSED,
 				   const char *value UNUSED,
+				   const struct config_context *ctx UNUSED,
 				   void *cb UNUSED)
 {
 	return 0;
diff --git a/gpg-interface.c b/gpg-interface.c
index 19a3471a0b5..57c862a3a22 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -14,7 +14,8 @@
 #include "alias.h"
 #include "wrapper.h"
 
-static int git_gpg_config(const char *, const char *, void *);
+static int git_gpg_config(const char *, const char *,
+			  const struct config_context *, void *);
 
 static void gpg_interface_lazy_init(void)
 {
@@ -720,7 +721,9 @@ void set_signing_key(const char *key)
 	configured_signing_key = xstrdup(key);
 }
 
-static int git_gpg_config(const char *var, const char *value, void *cb UNUSED)
+static int git_gpg_config(const char *var, const char *value,
+			  const struct config_context *ctx UNUSED,
+			  void *cb UNUSED)
 {
 	struct gpg_format *fmt = NULL;
 	char *fmtname = NULL;
diff --git a/grep.c b/grep.c
index f00986c451a..fc22c3e2afb 100644
--- a/grep.c
+++ b/grep.c
@@ -56,7 +56,8 @@ define_list_config_array_extra(color_grep_slots, {"match"});
  * Read the configuration file once and store it in
  * the grep_defaults template.
  */
-int grep_config(const char *var, const char *value, void *cb)
+int grep_config(const char *var, const char *value,
+		const struct config_context *ctx, void *cb)
 {
 	struct grep_opt *opt = cb;
 	const char *slot;
@@ -91,9 +92,9 @@ int grep_config(const char *var, const char *value, void *cb)
 	if (!strcmp(var, "color.grep"))
 		opt->color = git_config_colorbool(var, value);
 	if (!strcmp(var, "color.grep.match")) {
-		if (grep_config("color.grep.matchcontext", value, cb) < 0)
+		if (grep_config("color.grep.matchcontext", value, ctx, cb) < 0)
 			return -1;
-		if (grep_config("color.grep.matchselected", value, cb) < 0)
+		if (grep_config("color.grep.matchselected", value, ctx, cb) < 0)
 			return -1;
 	} else if (skip_prefix(var, "color.grep.", &slot)) {
 		int i = LOOKUP_CONFIG(color_grep_slots, slot);
diff --git a/grep.h b/grep.h
index c59592e3bdb..926c0875c42 100644
--- a/grep.h
+++ b/grep.h
@@ -202,7 +202,9 @@ struct grep_opt {
 	.output = std_output, \
 }
 
-int grep_config(const char *var, const char *value, void *);
+struct config_context;
+int grep_config(const char *var, const char *value,
+		const struct config_context *ctx, void *data);
 void grep_init(struct grep_opt *, struct repository *repo);
 
 void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, const char *origin, int no, enum grep_pat_token t);
diff --git a/help.c b/help.c
index 5d7637dce92..ac0ae5ac0dc 100644
--- a/help.c
+++ b/help.c
@@ -309,7 +309,8 @@ void load_command_list(const char *prefix,
 	exclude_cmds(other_cmds, main_cmds);
 }
 
-static int get_colopts(const char *var, const char *value, void *data)
+static int get_colopts(const char *var, const char *value,
+		       const struct config_context *ctx UNUSED, void *data)
 {
 	unsigned int *colopts = data;
 
@@ -459,7 +460,8 @@ void list_developer_interfaces_help(void)
 	putchar('\n');
 }
 
-static int get_alias(const char *var, const char *value, void *data)
+static int get_alias(const char *var, const char *value,
+		     const struct config_context *ctx UNUSED, void *data)
 {
 	struct string_list *list = data;
 
@@ -543,6 +545,7 @@ static struct cmdnames aliases;
 #define AUTOCORRECT_IMMEDIATELY (-1)
 
 static int git_unknown_cmd_config(const char *var, const char *value,
+				  const struct config_context *ctx UNUSED,
 				  void *cb UNUSED)
 {
 	const char *p;
diff --git a/http.c b/http.c
index bb58bb3e6a3..762502828c9 100644
--- a/http.c
+++ b/http.c
@@ -363,7 +363,8 @@ static void process_curl_messages(void)
 	}
 }
 
-static int http_options(const char *var, const char *value, void *cb)
+static int http_options(const char *var, const char *value,
+			const struct config_context *ctx, void *data)
 {
 	if (!strcmp("http.version", var)) {
 		return git_config_string(&curl_http_version, var, value);
@@ -534,7 +535,7 @@ static int http_options(const char *var, const char *value, void *cb)
 	}
 
 	/* Fall back on the default ones */
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, data);
 }
 
 static int curl_empty_auth_enabled(void)
diff --git a/ident.c b/ident.c
index 8fad92d7007..08be4d0747d 100644
--- a/ident.c
+++ b/ident.c
@@ -671,7 +671,9 @@ static int set_ident(const char *var, const char *value)
 	return 0;
 }
 
-int git_ident_config(const char *var, const char *value, void *data UNUSED)
+int git_ident_config(const char *var, const char *value,
+		     const struct config_context *ctx UNUSED,
+		     void *data UNUSED)
 {
 	if (!strcmp(var, "user.useconfigonly")) {
 		ident_use_config_only = git_config_bool(var, value);
diff --git a/ident.h b/ident.h
index 96a64896a01..6a79febba15 100644
--- a/ident.h
+++ b/ident.h
@@ -62,6 +62,8 @@ const char *fmt_name(enum want_ident);
 int committer_ident_sufficiently_given(void);
 int author_ident_sufficiently_given(void);
 
-int git_ident_config(const char *, const char *, void *);
+struct config_context;
+int git_ident_config(const char *, const char *, const struct config_context *,
+		     void *);
 
 #endif
diff --git a/imap-send.c b/imap-send.c
index 7f5426177a1..47777e76861 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -1323,7 +1323,8 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)
 	return 1;
 }
 
-static int git_imap_config(const char *var, const char *val, void *cb)
+static int git_imap_config(const char *var, const char *val,
+			   const struct config_context *ctx, void *cb)
 {
 
 	if (!strcmp("imap.sslverify", var))
@@ -1357,7 +1358,7 @@ static int git_imap_config(const char *var, const char *val, void *cb)
 			server.host = xstrdup(val);
 		}
 	} else
-		return git_default_config(var, val, cb);
+		return git_default_config(var, val, ctx, cb);
 
 	return 0;
 }
diff --git a/ll-merge.c b/ll-merge.c
index 07ec16e8e5b..3936d112e00 100644
--- a/ll-merge.c
+++ b/ll-merge.c
@@ -254,6 +254,7 @@ static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;
 static const char *default_ll_merge;
 
 static int read_merge_config(const char *var, const char *value,
+			     const struct config_context *ctx UNUSED,
 			     void *cb UNUSED)
 {
 	struct ll_merge_driver *fn;
diff --git a/ls-refs.c b/ls-refs.c
index f385938b64c..a29c2364a59 100644
--- a/ls-refs.c
+++ b/ls-refs.c
@@ -137,6 +137,7 @@ static void send_possibly_unborn_head(struct ls_refs_data *data)
 }
 
 static int ls_refs_config(const char *var, const char *value,
+			  const struct config_context *ctx UNUSED,
 			  void *cb_data)
 {
 	struct ls_refs_data *data = cb_data;
diff --git a/mailinfo.c b/mailinfo.c
index 2aeb20e5e62..931505363cd 100644
--- a/mailinfo.c
+++ b/mailinfo.c
@@ -1241,12 +1241,13 @@ int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action)
 	return 0;
 }
 
-static int git_mailinfo_config(const char *var, const char *value, void *mi_)
+static int git_mailinfo_config(const char *var, const char *value,
+			       const struct config_context *ctx, void *mi_)
 {
 	struct mailinfo *mi = mi_;
 
 	if (!starts_with(var, "mailinfo."))
-		return git_default_config(var, value, NULL);
+		return git_default_config(var, value, ctx, NULL);
 	if (!strcmp(var, "mailinfo.scissors")) {
 		mi->use_scissors = git_config_bool(var, value);
 		return 0;
diff --git a/notes-utils.c b/notes-utils.c
index 4a793eb347f..97c031c26ec 100644
--- a/notes-utils.c
+++ b/notes-utils.c
@@ -94,7 +94,9 @@ static combine_notes_fn parse_combine_notes_fn(const char *v)
 		return NULL;
 }
 
-static int notes_rewrite_config(const char *k, const char *v, void *cb)
+static int notes_rewrite_config(const char *k, const char *v,
+				const struct config_context *ctx UNUSED,
+				void *cb)
 {
 	struct notes_rewrite_cfg *c = cb;
 	if (starts_with(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) {
diff --git a/notes.c b/notes.c
index f51a2d3630e..e68645a4b89 100644
--- a/notes.c
+++ b/notes.c
@@ -974,7 +974,9 @@ void string_list_add_refs_from_colon_sep(struct string_list *list,
 	free(globs_copy);
 }
 
-static int notes_display_config(const char *k, const char *v, void *cb)
+static int notes_display_config(const char *k, const char *v,
+				const struct config_context *ctx UNUSED,
+				void *cb)
 {
 	int *load_refs = cb;
 
diff --git a/pager.c b/pager.c
index 63055d0873f..b8822a9381e 100644
--- a/pager.c
+++ b/pager.c
@@ -43,6 +43,7 @@ static void wait_for_pager_signal(int signo)
 }
 
 static int core_pager_config(const char *var, const char *value,
+			     const struct config_context *ctx UNUSED,
 			     void *data UNUSED)
 {
 	if (!strcmp(var, "core.pager"))
@@ -228,7 +229,9 @@ struct pager_command_config_data {
 	char *value;
 };
 
-static int pager_command_config(const char *var, const char *value, void *vdata)
+static int pager_command_config(const char *var, const char *value,
+				const struct config_context *ctx UNUSED,
+				void *vdata)
 {
 	struct pager_command_config_data *data = vdata;
 	const char *cmd;
diff --git a/pretty.c b/pretty.c
index 0bb938021ba..87245353452 100644
--- a/pretty.c
+++ b/pretty.c
@@ -56,6 +56,7 @@ static void save_user_format(struct rev_info *rev, const char *cp, int is_tforma
 }
 
 static int git_pretty_formats_config(const char *var, const char *value,
+				     const struct config_context *ctx UNUSED,
 				     void *cb UNUSED)
 {
 	struct cmt_fmt_map *commit_format = NULL;
diff --git a/promisor-remote.c b/promisor-remote.c
index 1adcd6fb0a5..c22abb85b15 100644
--- a/promisor-remote.c
+++ b/promisor-remote.c
@@ -100,7 +100,9 @@ static void promisor_remote_move_to_tail(struct promisor_remote_config *config,
 	config->promisors_tail = &r->next;
 }
 
-static int promisor_remote_config(const char *var, const char *value, void *data)
+static int promisor_remote_config(const char *var, const char *value,
+				  const struct config_context *ctx UNUSED,
+				  void *data)
 {
 	struct promisor_remote_config *config = data;
 	const char *name;
diff --git a/remote.c b/remote.c
index 1bcd36e358a..241999c2842 100644
--- a/remote.c
+++ b/remote.c
@@ -349,7 +349,8 @@ static void read_branches_file(struct remote_state *remote_state,
 	remote->fetch_tags = 1; /* always auto-follow */
 }
 
-static int handle_config(const char *key, const char *value, void *cb)
+static int handle_config(const char *key, const char *value,
+			 const struct config_context *ctx UNUSED, void *cb)
 {
 	const char *name;
 	size_t namelen;
diff --git a/revision.c b/revision.c
index b33cc1d106a..87ed8ccd444 100644
--- a/revision.c
+++ b/revision.c
@@ -1572,7 +1572,9 @@ struct exclude_hidden_refs_cb {
 	const char *section;
 };
 
-static int hide_refs_config(const char *var, const char *value, void *cb_data)
+static int hide_refs_config(const char *var, const char *value,
+			    const struct config_context *ctx UNUSED,
+			    void *cb_data)
 {
 	struct exclude_hidden_refs_cb *cb = cb_data;
 	cb->exclusions->hidden_refs_configured = 1;
diff --git a/scalar.c b/scalar.c
index 1326e1f6089..df7358f481c 100644
--- a/scalar.c
+++ b/scalar.c
@@ -594,7 +594,9 @@ static int cmd_register(int argc, const char **argv)
 	return register_dir();
 }
 
-static int get_scalar_repos(const char *key, const char *value, void *data)
+static int get_scalar_repos(const char *key, const char *value,
+			    const struct config_context *ctx UNUSED,
+			    void *data)
 {
 	struct string_list *list = data;
 
diff --git a/sequencer.c b/sequencer.c
index bceb6abcb6c..34754d17596 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -219,7 +219,8 @@ static struct update_ref_record *init_update_ref_record(const char *ref)
 	return rec;
 }
 
-static int git_sequencer_config(const char *k, const char *v, void *cb)
+static int git_sequencer_config(const char *k, const char *v,
+				const struct config_context *ctx, void *cb)
 {
 	struct replay_opts *opts = cb;
 	int status;
@@ -274,7 +275,7 @@ static int git_sequencer_config(const char *k, const char *v, void *cb)
 	if (opts->action == REPLAY_REVERT && !strcmp(k, "revert.reference"))
 		opts->commit_use_reference = git_config_bool(k, v);
 
-	return git_diff_basic_config(k, v, NULL);
+	return git_diff_basic_config(k, v, ctx, NULL);
 }
 
 void sequencer_init_config(struct replay_opts *opts)
@@ -2881,7 +2882,9 @@ static int git_config_string_dup(char **dest,
 	return 0;
 }
 
-static int populate_opts_cb(const char *key, const char *value, void *data)
+static int populate_opts_cb(const char *key, const char *value,
+			    const struct config_context *ctx UNUSED,
+			    void *data)
 {
 	struct replay_opts *opts = data;
 	int error_flag = 1;
diff --git a/setup.c b/setup.c
index 6f6e92b96be..fadba5bab4b 100644
--- a/setup.c
+++ b/setup.c
@@ -517,7 +517,9 @@ no_prevention_needed:
 	startup_info->original_cwd = NULL;
 }
 
-static int read_worktree_config(const char *var, const char *value, void *vdata)
+static int read_worktree_config(const char *var, const char *value,
+				const struct config_context *ctx UNUSED,
+				void *vdata)
 {
 	struct repository_format *data = vdata;
 
@@ -588,7 +590,8 @@ static enum extension_result handle_extension(const char *var,
 	return EXTENSION_UNKNOWN;
 }
 
-static int check_repo_format(const char *var, const char *value, void *vdata)
+static int check_repo_format(const char *var, const char *value,
+			     const struct config_context *ctx, void *vdata)
 {
 	struct repository_format *data = vdata;
 	const char *ext;
@@ -617,7 +620,7 @@ static int check_repo_format(const char *var, const char *value, void *vdata)
 		}
 	}
 
-	return read_worktree_config(var, value, vdata);
+	return read_worktree_config(var, value, ctx, vdata);
 }
 
 static int check_repository_format_gently(const char *gitdir, struct repository_format *candidate, int *nongit_ok)
@@ -1115,7 +1118,8 @@ struct safe_directory_data {
 	int is_safe;
 };
 
-static int safe_directory_cb(const char *key, const char *value, void *d)
+static int safe_directory_cb(const char *key, const char *value,
+			     const struct config_context *ctx UNUSED, void *d)
 {
 	struct safe_directory_data *data = d;
 
@@ -1171,7 +1175,9 @@ static int ensure_valid_ownership(const char *gitfile,
 	return data.is_safe;
 }
 
-static int allowed_bare_repo_cb(const char *key, const char *value, void *d)
+static int allowed_bare_repo_cb(const char *key, const char *value,
+				const struct config_context *ctx UNUSED,
+				void *d)
 {
 	enum allowed_bare_repo *allowed_bare_repo = d;
 
diff --git a/submodule-config.c b/submodule-config.c
index 7eb7a0d88d2..a38d4d49731 100644
--- a/submodule-config.c
+++ b/submodule-config.c
@@ -426,7 +426,8 @@ struct parse_config_parameter {
  * config store (.git/config, etc).  Callers are responsible for
  * checking for overrides in the main config store when appropriate.
  */
-static int parse_config(const char *var, const char *value, void *data)
+static int parse_config(const char *var, const char *value,
+			const struct config_context *ctx UNUSED, void *data)
 {
 	struct parse_config_parameter *me = data;
 	struct submodule *submodule;
@@ -674,7 +675,8 @@ out:
 	}
 }
 
-static int gitmodules_cb(const char *var, const char *value, void *data)
+static int gitmodules_cb(const char *var, const char *value,
+			 const struct config_context *ctx, void *data)
 {
 	struct repository *repo = data;
 	struct parse_config_parameter parameter;
@@ -684,7 +686,7 @@ static int gitmodules_cb(const char *var, const char *value, void *data)
 	parameter.gitmodules_oid = null_oid();
 	parameter.overwrite = 1;
 
-	return parse_config(var, value, &parameter);
+	return parse_config(var, value, ctx, &parameter);
 }
 
 void repo_read_gitmodules(struct repository *repo, int skip_if_read)
@@ -801,7 +803,9 @@ void submodule_free(struct repository *r)
 		submodule_cache_clear(r->submodule_cache);
 }
 
-static int config_print_callback(const char *var, const char *value, void *cb_data)
+static int config_print_callback(const char *var, const char *value,
+				 const struct config_context *ctx UNUSED,
+				 void *cb_data)
 {
 	char *wanted_key = cb_data;
 
@@ -843,7 +847,9 @@ struct fetch_config {
 	int *recurse_submodules;
 };
 
-static int gitmodules_fetch_config(const char *var, const char *value, void *cb)
+static int gitmodules_fetch_config(const char *var, const char *value,
+				   const struct config_context *ctx UNUSED,
+				   void *cb)
 {
 	struct fetch_config *config = cb;
 	if (!strcmp(var, "submodule.fetchjobs")) {
@@ -871,6 +877,7 @@ void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules)
 }
 
 static int gitmodules_update_clone_config(const char *var, const char *value,
+					  const struct config_context *ctx UNUSED,
 					  void *cb)
 {
 	int *max_jobs = cb;
diff --git a/t/helper/test-config.c b/t/helper/test-config.c
index ad78fc17683..85ad815358e 100644
--- a/t/helper/test-config.c
+++ b/t/helper/test-config.c
@@ -42,7 +42,9 @@
  *
  */
 
-static int iterate_cb(const char *var, const char *value, void *data UNUSED)
+static int iterate_cb(const char *var, const char *value,
+		      const struct config_context *ctx UNUSED,
+		      void *data UNUSED)
 {
 	static int nr;
 
@@ -59,7 +61,8 @@ 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)
+static int parse_int_cb(const char *var, const char *value,
+			const struct config_context *ctx UNUSED, void *data)
 {
 	const char *key_to_match = data;
 
@@ -70,7 +73,9 @@ static int parse_int_cb(const char *var, const char *value, void *data)
 	return 0;
 }
 
-static int early_config_cb(const char *var, const char *value, void *vdata)
+static int early_config_cb(const char *var, const char *value,
+			   const struct config_context *ctx UNUSED,
+			   void *vdata)
 {
 	const char *key = vdata;
 
diff --git a/t/helper/test-userdiff.c b/t/helper/test-userdiff.c
index 680124a6760..0ce31ce59f5 100644
--- a/t/helper/test-userdiff.c
+++ b/t/helper/test-userdiff.c
@@ -12,7 +12,9 @@ static int driver_cb(struct userdiff_driver *driver,
 	return 0;
 }
 
-static int cmd__userdiff_config(const char *var, const char *value, void *cb UNUSED)
+static int cmd__userdiff_config(const char *var, const char *value,
+				const struct config_context *ctx UNUSED,
+				void *cb UNUSED)
 {
 	if (userdiff_config(var, value) < 0)
 		return -1;
diff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c
index 78cfc15d52d..83bc4fd109c 100644
--- a/trace2/tr2_cfg.c
+++ b/trace2/tr2_cfg.c
@@ -99,7 +99,8 @@ struct tr2_cfg_data {
 /*
  * See if the given config key matches any of our patterns of interest.
  */
-static int tr2_cfg_cb(const char *key, const char *value, void *d)
+static int tr2_cfg_cb(const char *key, const char *value,
+		      const struct config_context *ctx UNUSED, void *d)
 {
 	struct strbuf **s;
 	struct tr2_cfg_data *data = (struct tr2_cfg_data *)d;
@@ -142,8 +143,12 @@ void tr2_list_env_vars_fl(const char *file, int line)
 void tr2_cfg_set_fl(const char *file, int line, const char *key,
 		    const char *value)
 {
+	struct key_value_info kvi = KVI_INIT;
+	struct config_context ctx = {
+		.kvi = &kvi,
+	};
 	struct tr2_cfg_data data = { file, line };
 
 	if (tr2_cfg_load_patterns() > 0)
-		tr2_cfg_cb(key, value, &data);
+		tr2_cfg_cb(key, value, &ctx, &data);
 }
diff --git a/trace2/tr2_sysenv.c b/trace2/tr2_sysenv.c
index 069786cb927..f26ec95ab4d 100644
--- a/trace2/tr2_sysenv.c
+++ b/trace2/tr2_sysenv.c
@@ -57,7 +57,8 @@ static struct tr2_sysenv_entry tr2_sysenv_settings[] = {
 };
 /* clang-format on */
 
-static int tr2_sysenv_cb(const char *key, const char *value, void *d)
+static int tr2_sysenv_cb(const char *key, const char *value,
+			 const struct config_context *ctx UNUSED, void *d)
 {
 	int k;
 
diff --git a/trailer.c b/trailer.c
index a2c3ed6f28c..06dc0b7f683 100644
--- a/trailer.c
+++ b/trailer.c
@@ -482,6 +482,7 @@ static struct {
 };
 
 static int git_trailer_default_config(const char *conf_key, const char *value,
+				      const struct config_context *ctx UNUSED,
 				      void *cb UNUSED)
 {
 	const char *trailer_item, *variable_name;
@@ -514,6 +515,7 @@ static int git_trailer_default_config(const char *conf_key, const char *value,
 }
 
 static int git_trailer_config(const char *conf_key, const char *value,
+			      const struct config_context *ctx UNUSED,
 			      void *cb UNUSED)
 {
 	const char *trailer_item, *variable_name;
diff --git a/upload-pack.c b/upload-pack.c
index d3312006a32..951fd1f9c25 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -1309,7 +1309,9 @@ static int parse_object_filter_config(const char *var, const char *value,
 	return 0;
 }
 
-static int upload_pack_config(const char *var, const char *value, void *cb_data)
+static int upload_pack_config(const char *var, const char *value,
+			      const struct config_context *ctx UNUSED,
+			      void *cb_data)
 {
 	struct upload_pack_data *data = cb_data;
 
@@ -1350,7 +1352,9 @@ static int upload_pack_config(const char *var, const char *value, void *cb_data)
 	return parse_hide_refs_config(var, value, "uploadpack", &data->hidden_refs);
 }
 
-static int upload_pack_protected_config(const char *var, const char *value, void *cb_data)
+static int upload_pack_protected_config(const char *var, const char *value,
+					const struct config_context *ctx UNUSED,
+					void *cb_data)
 {
 	struct upload_pack_data *data = cb_data;
 
diff --git a/urlmatch.c b/urlmatch.c
index eba0bdd77fe..1c45f23adf2 100644
--- a/urlmatch.c
+++ b/urlmatch.c
@@ -551,7 +551,8 @@ static int cmp_matches(const struct urlmatch_item *a,
 	return 0;
 }
 
-int urlmatch_config_entry(const char *var, const char *value, void *cb)
+int urlmatch_config_entry(const char *var, const char *value,
+			  const struct config_context *ctx, void *cb)
 {
 	struct string_list_item *item;
 	struct urlmatch_config *collect = cb;
@@ -565,7 +566,7 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb)
 
 	if (!skip_prefix(var, collect->section, &key) || *(key++) != '.') {
 		if (collect->cascade_fn)
-			return collect->cascade_fn(var, value, cb);
+			return collect->cascade_fn(var, value, ctx, cb);
 		return 0; /* not interested */
 	}
 	dot = strrchr(key, '.');
@@ -609,7 +610,7 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb)
 	strbuf_addstr(&synthkey, collect->section);
 	strbuf_addch(&synthkey, '.');
 	strbuf_addstr(&synthkey, key);
-	retval = collect->collect_fn(synthkey.buf, value, collect->cb);
+	retval = collect->collect_fn(synthkey.buf, value, ctx, collect->cb);
 
 	strbuf_release(&synthkey);
 	return retval;
diff --git a/urlmatch.h b/urlmatch.h
index bee374a642c..5ba85cea139 100644
--- a/urlmatch.h
+++ b/urlmatch.h
@@ -71,7 +71,8 @@ struct urlmatch_config {
 	.vars = STRING_LIST_INIT_DUP, \
 }
 
-int urlmatch_config_entry(const char *var, const char *value, void *cb);
+int urlmatch_config_entry(const char *var, const char *value,
+			  const struct config_context *ctx, void *cb);
 void urlmatch_config_release(struct urlmatch_config *config);
 
 #endif /* URL_MATCH_H */
diff --git a/xdiff-interface.c b/xdiff-interface.c
index 0460e03f5ed..dcbb5e09857 100644
--- a/xdiff-interface.c
+++ b/xdiff-interface.c
@@ -307,7 +307,8 @@ int xdiff_compare_lines(const char *l1, long s1,
 
 int git_xmerge_style = -1;
 
-int git_xmerge_config(const char *var, const char *value, void *cb)
+int git_xmerge_config(const char *var, const char *value,
+		      const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(var, "merge.conflictstyle")) {
 		if (!value)
@@ -327,5 +328,5 @@ int git_xmerge_config(const char *var, const char *value, void *cb)
 			    value, var);
 		return 0;
 	}
-	return git_default_config(var, value, cb);
+	return git_default_config(var, value, ctx, cb);
 }
diff --git a/xdiff-interface.h b/xdiff-interface.h
index 733c364d26c..e6f80df0462 100644
--- a/xdiff-interface.h
+++ b/xdiff-interface.h
@@ -50,7 +50,9 @@ int buffer_is_binary(const char *ptr, unsigned long size);
 
 void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);
 void xdiff_clear_find_func(xdemitconf_t *xecfg);
-int git_xmerge_config(const char *var, const char *value, void *cb);
+struct config_context;
+int git_xmerge_config(const char *var, const char *value,
+		      const struct config_context *ctx, void *cb);
 extern int git_xmerge_style;
 
 /*
-- 
gitgitgadget


^ permalink raw reply related

* [PATCH v5 09/11] config.c: remove config_reader from configsets
From: Glen Choo via GitGitGadget @ 2023-06-28 19:26 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Ævar Arnfjörð Bjarmason,
	Emily Shaffer, Phillip Wood, Jeff King, Glen Choo, Glen Choo
In-Reply-To: <pull.1497.v5.git.git.1687980390.gitgitgadget@gmail.com>

From: Glen Choo <chooglen@google.com>

Remove the last usage of "struct config_reader" from configsets by
copying the "kvi" arg instead of recomputing "kvi" from
config_reader.source. Since we no longer need to pass both "struct
config_reader" and "struct config_set" in a single "void *cb", remove
"struct configset_add_data" too.

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

diff --git a/config.c b/config.c
index 4e3c4dcf415..50dbd89a6d6 100644
--- a/config.c
+++ b/config.c
@@ -2311,8 +2311,7 @@ int config_with_options(config_fn_t fn, void *data,
 	return ret;
 }
 
-static void configset_iter(struct config_reader *reader, struct config_set *set,
-			   config_fn_t fn, void *data)
+static void configset_iter(struct config_set *set, config_fn_t fn, void *data)
 {
 	int i, value_index;
 	struct string_list *values;
@@ -2406,7 +2405,6 @@ static int configset_find_element(struct config_set *set, const char *key,
 }
 
 static int configset_add_value(const struct key_value_info *kvi_p,
-			       struct config_reader *reader,
 			       struct config_set *set, const char *key,
 			       const char *value)
 {
@@ -2437,13 +2435,7 @@ static int configset_add_value(const struct key_value_info *kvi_p,
 	l_item->e = e;
 	l_item->value_index = e->value_list.nr - 1;
 
-	if (!reader->source)
-		BUG("configset_add_value has no source");
-	if (reader->source->name) {
-		kvi_from_source(reader->source, kvi_p->scope, kv_info);
-	} else {
-		kvi_from_param(kv_info);
-	}
+	*kv_info = *kvi_p;
 	si->util = kv_info;
 
 	return 0;
@@ -2491,28 +2483,18 @@ void git_configset_clear(struct config_set *set)
 	set->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,
 			       const struct config_context *ctx,
 			       void *cb)
 {
-	struct configset_add_data *data = cb;
-	configset_add_value(ctx->kvi, data->config_reader, data->config_set,
-			    key, value);
+	struct config_set *set = cb;
+	configset_add_value(ctx->kvi, set, key, value);
 	return 0;
 }
 
 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 = set;
-	return git_config_from_file(config_set_callback, filename, &data);
+	return git_config_from_file(config_set_callback, filename, set);
 }
 
 int git_configset_get_value(struct config_set *set, const char *key,
@@ -2678,7 +2660,6 @@ int git_configset_get_pathname(struct config_set *set, const char *key, const ch
 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;
@@ -2690,10 +2671,8 @@ 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, &data, NULL, repo, &opts) < 0)
+	if (config_with_options(config_set_callback, repo->config, NULL,
+				repo, &opts) < 0)
 		/*
 		 * config_with_options() normally returns only
 		 * zero, as most errors are fatal, and
@@ -2725,7 +2704,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(&the_reader, repo->config, fn, data);
+	configset_iter(repo->config, fn, data);
 }
 
 int repo_config_get(struct repository *repo, const char *key)
@@ -2832,19 +2811,17 @@ 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);
-	data.config_set = &protected_config;
-	data.config_reader = &the_reader;
-	config_with_options(config_set_callback, &data, NULL, NULL, &opts);
+	config_with_options(config_set_callback, &protected_config, NULL,
+			    NULL, &opts);
 }
 
 void git_protected_config(config_fn_t fn, void *data)
 {
 	if (!protected_config.hash_initialized)
 		read_protected_config();
-	configset_iter(&the_reader, &protected_config, fn, data);
+	configset_iter(&protected_config, fn, data);
 }
 
 /* Functions used historically to read configuration from 'the_repository' */
-- 
gitgitgadget


^ permalink raw reply related

* [PATCH v5 10/11] config: add kvi.path, use it to evaluate includes
From: Glen Choo via GitGitGadget @ 2023-06-28 19:26 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Ævar Arnfjörð Bjarmason,
	Emily Shaffer, Phillip Wood, Jeff King, Glen Choo, Glen Choo
In-Reply-To: <pull.1497.v5.git.git.1687980390.gitgitgadget@gmail.com>

From: Glen Choo <chooglen@google.com>

Include directives are evaluated using the path of the config file. To
reduce the dependence on "config_reader.source", add a new
"key_value_info.path" member and use that instead of
"config_source.path". This allows us to remove a "struct config_reader
*" field from "struct config_include_data", which will subsequently
allow us to remove "struct config_reader" entirely.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 40 +++++++++++++++++++---------------------
 config.h |  2 ++
 2 files changed, 21 insertions(+), 21 deletions(-)

diff --git a/config.c b/config.c
index 50dbd89a6d6..8d342d84255 100644
--- a/config.c
+++ b/config.c
@@ -162,7 +162,6 @@ struct config_include_data {
 	const struct config_options *opts;
 	struct git_config_source *config_source;
 	struct repository *repo;
-	struct config_reader *config_reader;
 
 	/*
 	 * All remote URLs discovered when reading all config files.
@@ -181,8 +180,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 struct key_value_info *kvi,
+static int handle_path_include(const struct key_value_info *kvi,
 			       const char *path,
 			       struct config_include_data *inc)
 {
@@ -205,14 +203,14 @@ static int handle_path_include(struct config_source *cs,
 	if (!is_absolute_path(path)) {
 		char *slash;
 
-		if (!cs || !cs->path) {
+		if (!kvi || !kvi->path) {
 			ret = error(_("relative config includes must come from files"));
 			goto cleanup;
 		}
 
-		slash = find_last_dir_sep(cs->path);
+		slash = find_last_dir_sep(kvi->path);
 		if (slash)
-			strbuf_add(&buf, cs->path, slash - cs->path + 1);
+			strbuf_add(&buf, kvi->path, slash - kvi->path + 1);
 		strbuf_addstr(&buf, path);
 		path = buf.buf;
 	}
@@ -220,8 +218,8 @@ static int handle_path_include(struct config_source *cs,
 	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 :
+			    !kvi ? "<unknown>" :
+			    kvi->filename ? kvi->filename :
 			    "the command line");
 		ret = git_config_from_file_with_options(git_config_include, path, inc,
 							kvi->scope, NULL);
@@ -239,7 +237,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(const struct key_value_info *kvi,
 					     struct strbuf *pat)
 {
 	struct strbuf path = STRBUF_INIT;
@@ -256,11 +254,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 (!kvi || !kvi->path)
 			return error(_("relative config include "
 				       "conditionals must come from files"));
 
-		strbuf_realpath(&path, cs->path, 1);
+		strbuf_realpath(&path, kvi->path, 1);
 		slash = find_last_dir_sep(path.buf);
 		if (!slash)
 			BUG("how is this possible?");
@@ -275,7 +273,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(const struct key_value_info *kvi,
 			     const struct config_options *opts,
 			     const char *cond, size_t cond_len, int icase)
 {
@@ -292,7 +290,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(kvi, &pattern);
 
 again:
 	if (prefix < 0)
@@ -428,16 +426,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(const struct key_value_info *kvi,
 				     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(kvi, 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(kvi, 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,
@@ -453,7 +451,6 @@ 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;
@@ -467,16 +464,16 @@ static int git_config_include(const char *var, const char *value,
 		return ret;
 
 	if (!strcmp(var, "include.path"))
-		ret = handle_path_include(cs, ctx->kvi, value, inc);
+		ret = handle_path_include(ctx->kvi, 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(ctx->kvi, 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, ctx->kvi, value, inc);
+		ret = handle_path_include(ctx->kvi, value, inc);
 		inc->fn = old_fn;
 	}
 
@@ -661,6 +658,7 @@ void kvi_from_param(struct key_value_info *out)
 	out->linenr = -1;
 	out->origin_type = CONFIG_ORIGIN_CMDLINE;
 	out->scope = CONFIG_SCOPE_COMMAND;
+	out->path = NULL;
 }
 
 int git_config_parse_parameter(const char *text,
@@ -1064,6 +1062,7 @@ static void kvi_from_source(struct config_source *cs,
 	out->origin_type = cs->origin_type;
 	out->linenr = cs->linenr;
 	out->scope = scope;
+	out->path = cs->path;
 }
 
 static int git_parse_source(struct config_source *cs, config_fn_t fn,
@@ -2282,7 +2281,6 @@ int config_with_options(config_fn_t fn, void *data,
 		inc.opts = opts;
 		inc.repo = repo;
 		inc.config_source = config_source;
-		inc.config_reader = &the_reader;
 		fn = git_config_include;
 		data = &inc;
 	}
diff --git a/config.h b/config.h
index bd366ddb5ef..b5573e67a00 100644
--- a/config.h
+++ b/config.h
@@ -116,12 +116,14 @@ struct key_value_info {
 	int linenr;
 	enum config_origin_type origin_type;
 	enum config_scope scope;
+	const char *path;
 };
 #define KVI_INIT { \
 	.filename = NULL, \
 	.linenr = -1, \
 	.origin_type = CONFIG_ORIGIN_UNKNOWN, \
 	.scope = CONFIG_SCOPE_UNKNOWN, \
+	.path = NULL, \
 }
 
 /* Captures additional information that a config callback can use. */
-- 
gitgitgadget


^ permalink raw reply related

* [PATCH v5 11/11] config: pass source to config_parser_event_fn_t
From: Glen Choo via GitGitGadget @ 2023-06-28 19:26 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Ævar Arnfjörð Bjarmason,
	Emily Shaffer, Phillip Wood, Jeff King, Glen Choo, Glen Choo
In-Reply-To: <pull.1497.v5.git.git.1687980390.gitgitgadget@gmail.com>

From: Glen Choo <chooglen@google.com>

..so that the callback can use a "struct config_source" parameter
instead of "config_reader.source". "struct config_source" is internal to
config.c, so we are adding a pointer to a struct defined in config.c
into a public function signature defined in config.h, but this is okay
because this function has only ever been (and probably ever will be)
used internally by config.c.

As a result, the_reader isn't used anywhere, so "struct config_reader"
is obsolete (it was only intended to be used with the_reader). Remove
them.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 config.c | 77 ++++++++++----------------------------------------------
 config.h |  6 +++++
 2 files changed, 19 insertions(+), 64 deletions(-)

diff --git a/config.c b/config.c
index 8d342d84255..b3de01239d6 100644
--- a/config.c
+++ b/config.c
@@ -66,40 +66,6 @@ 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).
-	 */
-	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;
-
-static inline void config_reader_push_source(struct config_reader *reader,
-					     struct config_source *top)
-{
-	top->prev = reader->source;
-	reader->source = top;
-}
-
-static inline struct config_source *config_reader_pop_source(struct config_reader *reader)
-{
-	struct config_source *ret;
-	if (!reader->source)
-		BUG("tried to pop config source, but we weren't reading config");
-	ret = reader->source;
-	reader->source = reader->source->prev;
-	return ret;
-}
-
 static int pack_compression_seen;
 static int zlib_compression_seen;
 
@@ -752,14 +718,9 @@ 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 = CONFIG_SOURCE_INIT;
 	struct key_value_info kvi = KVI_INIT;
 
-	source.origin_type = CONFIG_ORIGIN_CMDLINE;
-	config_reader_push_source(&the_reader, &source);
-
 	kvi_from_param(&kvi);
-
 	env = getenv(CONFIG_COUNT_ENVIRONMENT);
 	if (env) {
 		unsigned long count;
@@ -816,7 +777,6 @@ out:
 	strbuf_release(&envvar);
 	strvec_clear(&to_free);
 	free(envw);
-	config_reader_pop_source(&the_reader);
 	return ret;
 }
 
@@ -1045,7 +1005,7 @@ static int do_event(struct config_source *cs, enum config_event_t type,
 
 	if (data->previous_type != CONFIG_EVENT_EOF &&
 	    data->opts->event_fn(data->previous_type, data->previous_offset,
-				 offset, data->opts->event_fn_data) < 0)
+				 offset, cs, data->opts->event_fn_data) < 0)
 		return -1;
 
 	data->previous_type = type;
@@ -2002,8 +1962,7 @@ int git_default_config(const char *var, const char *value,
  * fgetc, ungetc, ftell of top need to be initialized before calling
  * this function.
  */
-static int do_config_from(struct config_reader *reader,
-			  struct config_source *top, config_fn_t fn,
+static int do_config_from(struct config_source *top, config_fn_t fn,
 			  void *data, enum config_scope scope,
 			  const struct config_options *opts)
 {
@@ -2016,21 +1975,17 @@ static int do_config_from(struct config_reader *reader,
 	top->total_len = 0;
 	strbuf_init(&top->value, 1024);
 	strbuf_init(&top->var, 1024);
-	config_reader_push_source(reader, top);
 	kvi_from_source(top, scope, &kvi);
 
 	ret = git_parse_source(top, fn, &kvi, data, opts);
 
-	/* pop config-file parsing state stack */
 	strbuf_release(&top->value);
 	strbuf_release(&top->var);
-	config_reader_pop_source(reader);
 
 	return ret;
 }
 
-static int do_config_from_file(struct config_reader *reader,
-			       config_fn_t fn,
+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, enum config_scope scope,
@@ -2049,7 +2004,7 @@ static int do_config_from_file(struct config_reader *reader,
 	top.do_ftell = config_file_ftell;
 
 	flockfile(f);
-	ret = do_config_from(reader, &top, fn, data, scope, opts);
+	ret = do_config_from(&top, fn, data, scope, opts);
 	funlockfile(f);
 	return ret;
 }
@@ -2057,8 +2012,8 @@ static int do_config_from_file(struct config_reader *reader,
 static int git_config_from_stdin(config_fn_t fn, void *data,
 				 enum config_scope scope)
 {
-	return do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_STDIN, "",
-				   NULL, stdin, data, scope, NULL);
+	return do_config_from_file(fn, CONFIG_ORIGIN_STDIN, "", NULL, stdin,
+				   data, scope, NULL);
 }
 
 int git_config_from_file_with_options(config_fn_t fn, const char *filename,
@@ -2072,9 +2027,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(&the_reader, fn, CONFIG_ORIGIN_FILE,
-					  filename, filename, f, data, scope,
-					  opts);
+		ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename,
+					  filename, f, data, scope, opts);
 		fclose(f);
 	}
 	return ret;
@@ -2105,7 +2059,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(&the_reader, &top, fn, data, scope, opts);
+	return do_config_from(&top, fn, data, scope, opts);
 }
 
 int git_config_from_blob_oid(config_fn_t fn,
@@ -2198,8 +2152,7 @@ int git_config_system(void)
 	return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
 }
 
-static int do_git_config_sequence(struct config_reader *reader,
-				  const struct config_options *opts,
+static int do_git_config_sequence(const struct config_options *opts,
 				  const struct repository *repo,
 				  config_fn_t fn, void *data)
 {
@@ -2299,7 +2252,7 @@ int config_with_options(config_fn_t fn, void *data,
 		ret = git_config_from_blob_ref(fn, repo, config_source->blob,
 					       data, config_source->scope);
 	} else {
-		ret = do_git_config_sequence(&the_reader, opts, repo, fn, data);
+		ret = do_git_config_sequence(opts, repo, fn, data);
 	}
 
 	if (inc.remote_urls) {
@@ -3009,7 +2962,6 @@ 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;
@@ -3055,11 +3007,10 @@ static int matches(const char *key, const char *value,
 		(value && !regexec(store->value_pattern, value, 0, NULL, 0));
 }
 
-static int store_aux_event(enum config_event_t type,
-			   size_t begin, size_t end, void *data)
+static int store_aux_event(enum config_event_t type, size_t begin, size_t end,
+			   struct config_source *cs, 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;
@@ -3380,8 +3331,6 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
 	size_t contents_sz;
 	struct config_store_data store = CONFIG_STORE_INIT;
 
-	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);
 	if (ret)
diff --git a/config.h b/config.h
index b5573e67a00..6332d749047 100644
--- a/config.h
+++ b/config.h
@@ -72,6 +72,7 @@ enum config_event_t {
 	CONFIG_EVENT_ERROR
 };
 
+struct config_source;
 /*
  * The parser event function (if not NULL) is called with the event type and
  * the begin/end offsets of the parsed elements.
@@ -81,6 +82,7 @@ enum config_event_t {
  */
 typedef int (*config_parser_event_fn_t)(enum config_event_t type,
 					size_t begin_offset, size_t end_offset,
+					struct config_source *cs,
 					void *event_fn_data);
 
 struct config_options {
@@ -100,6 +102,10 @@ struct config_options {
 
 	const char *commondir;
 	const char *git_dir;
+	/*
+	 * event_fn and event_fn_data are for internal use only. Handles events
+	 * emitted by the config parser.
+	 */
 	config_parser_event_fn_t event_fn;
 	void *event_fn_data;
 	enum config_error_action {
-- 
gitgitgadget

^ permalink raw reply related

* [PATCH v5 08/11] config: pass kvi to die_bad_number()
From: Glen Choo via GitGitGadget @ 2023-06-28 19:26 UTC (permalink / raw)
  To: git
  Cc: Jonathan Tan, Ævar Arnfjörð Bjarmason,
	Emily Shaffer, Phillip Wood, Jeff King, Glen Choo, Glen Choo
In-Reply-To: <pull.1497.v5.git.git.1687980390.gitgitgadget@gmail.com>

From: Glen Choo <chooglen@google.com>

Plumb "struct key_value_info" through all code paths that end in
die_bad_number(), which lets us remove the helper functions that read
analogous values from "struct config_reader". As a result, nothing reads
config_reader.config_kvi any more, so remove that too.

In config.c, this requires changing the signature of
git_configset_get_value() to 'return' "kvi" in an out parameter so that
git_configset_get_<type>() can pass it to git_config_<type>(). Only
numeric types will use "kvi", so for non-numeric types (e.g.
git_configset_get_string()), pass NULL to indicate that the out
parameter isn't needed.

Outside of config.c, config callbacks now need to pass "ctx->kvi" to any
of the git_config_<type>() functions that parse a config string into a
number type. Included is a .cocci patch to make that refactor.

The only exceptional case is builtin/config.c, where git_config_<type>()
is called outside of a config callback (namely, on user-provided input),
so config source information has never been available. In this case,
die_bad_number() defaults to a generic, but perfectly descriptive
message. Let's provide a safe, non-NULL for "kvi" anyway, but make sure
not to change the message.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 archive-tar.c                              |   4 +-
 builtin/commit-graph.c                     |   4 +-
 builtin/commit.c                           |  10 +-
 builtin/config.c                           |  21 +--
 builtin/fetch.c                            |   4 +-
 builtin/fsmonitor--daemon.c                |   6 +-
 builtin/grep.c                             |   2 +-
 builtin/index-pack.c                       |   4 +-
 builtin/log.c                              |   2 +-
 builtin/pack-objects.c                     |  14 +-
 builtin/receive-pack.c                     |  10 +-
 builtin/submodule--helper.c                |   4 +-
 config.c                                   | 156 ++++++++-------------
 config.h                                   |  17 ++-
 contrib/coccinelle/git_config_number.cocci |  27 ++++
 diff.c                                     |   9 +-
 fmt-merge-msg.c                            |   2 +-
 help.c                                     |   4 +-
 http.c                                     |  10 +-
 imap-send.c                                |   2 +-
 sequencer.c                                |  22 +--
 setup.c                                    |   2 +-
 submodule-config.c                         |  13 +-
 submodule-config.h                         |   3 +-
 t/helper/test-config.c                     |   6 +-
 upload-pack.c                              |  12 +-
 worktree.c                                 |   2 +-
 27 files changed, 190 insertions(+), 182 deletions(-)
 create mode 100644 contrib/coccinelle/git_config_number.cocci

diff --git a/archive-tar.c b/archive-tar.c
index ef06e516b1f..3df8af6d1b1 100644
--- a/archive-tar.c
+++ b/archive-tar.c
@@ -412,14 +412,14 @@ static int tar_filter_config(const char *var, const char *value,
 }
 
 static int git_tar_config(const char *var, const char *value,
-			  const struct config_context *ctx UNUSED, void *cb)
+			  const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(var, "tar.umask")) {
 		if (value && !strcmp(value, "user")) {
 			tar_umask = umask(0);
 			umask(tar_umask);
 		} else {
-			tar_umask = git_config_int(var, value);
+			tar_umask = git_config_int(var, value, ctx->kvi);
 		}
 		return 0;
 	}
diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c
index 1185c49239a..b071c5ab646 100644
--- a/builtin/commit-graph.c
+++ b/builtin/commit-graph.c
@@ -186,11 +186,11 @@ static int write_option_max_new_filters(const struct option *opt,
 }
 
 static int git_commit_graph_write_config(const char *var, const char *value,
-					 const struct config_context *ctx UNUSED,
+					 const struct config_context *ctx,
 					 void *cb UNUSED)
 {
 	if (!strcmp(var, "commitgraph.maxnewfilters"))
-		write_opts.max_new_filters = git_config_int(var, value);
+		write_opts.max_new_filters = git_config_int(var, value, ctx->kvi);
 	/*
 	 * No need to fall-back to 'git_default_config', since this was already
 	 * called in 'cmd_commit_graph()'.
diff --git a/builtin/commit.c b/builtin/commit.c
index 6a2b2503328..9fe691470a3 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1415,7 +1415,8 @@ static int git_status_config(const char *k, const char *v,
 		return git_column_config(k, v, "status", &s->colopts);
 	if (!strcmp(k, "status.submodulesummary")) {
 		int is_bool;
-		s->submodule_summary = git_config_bool_or_int(k, v, &is_bool);
+		s->submodule_summary = git_config_bool_or_int(k, v, ctx->kvi,
+							      &is_bool);
 		if (is_bool && s->submodule_summary)
 			s->submodule_summary = -1;
 		return 0;
@@ -1475,11 +1476,11 @@ static int git_status_config(const char *k, const char *v,
 	}
 	if (!strcmp(k, "diff.renamelimit")) {
 		if (s->rename_limit == -1)
-			s->rename_limit = git_config_int(k, v);
+			s->rename_limit = git_config_int(k, v, ctx->kvi);
 		return 0;
 	}
 	if (!strcmp(k, "status.renamelimit")) {
-		s->rename_limit = git_config_int(k, v);
+		s->rename_limit = git_config_int(k, v, ctx->kvi);
 		return 0;
 	}
 	if (!strcmp(k, "diff.renames")) {
@@ -1625,7 +1626,8 @@ static int git_commit_config(const char *k, const char *v,
 	}
 	if (!strcmp(k, "commit.verbose")) {
 		int is_bool;
-		config_commit_verbose = git_config_bool_or_int(k, v, &is_bool);
+		config_commit_verbose = git_config_bool_or_int(k, v, ctx->kvi,
+							       &is_bool);
 		return 0;
 	}
 
diff --git a/builtin/config.c b/builtin/config.c
index 9b9f5527311..680269d263c 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -262,13 +262,14 @@ static int format_config(struct strbuf *buf, const char *key_,
 
 		if (type == TYPE_INT)
 			strbuf_addf(buf, "%"PRId64,
-				    git_config_int64(key_, value_ ? value_ : ""));
+				    git_config_int64(key_, value_ ? value_ : "", kvi));
 		else if (type == TYPE_BOOL)
 			strbuf_addstr(buf, git_config_bool(key_, value_) ?
 				      "true" : "false");
 		else if (type == TYPE_BOOL_OR_INT) {
 			int is_bool, v;
-			v = git_config_bool_or_int(key_, value_, &is_bool);
+			v = git_config_bool_or_int(key_, value_, kvi,
+						   &is_bool);
 			if (is_bool)
 				strbuf_addstr(buf, v ? "true" : "false");
 			else
@@ -424,7 +425,8 @@ free_strings:
 	return ret;
 }
 
-static char *normalize_value(const char *key, const char *value)
+static char *normalize_value(const char *key, const char *value,
+			     struct key_value_info *kvi)
 {
 	if (!value)
 		return NULL;
@@ -439,12 +441,12 @@ static char *normalize_value(const char *key, const char *value)
 		 */
 		return xstrdup(value);
 	if (type == TYPE_INT)
-		return xstrfmt("%"PRId64, git_config_int64(key, value));
+		return xstrfmt("%"PRId64, git_config_int64(key, value, kvi));
 	if (type == TYPE_BOOL)
 		return xstrdup(git_config_bool(key, value) ?  "true" : "false");
 	if (type == TYPE_BOOL_OR_INT) {
 		int is_bool, v;
-		v = git_config_bool_or_int(key, value, &is_bool);
+		v = git_config_bool_or_int(key, value, kvi, &is_bool);
 		if (!is_bool)
 			return xstrfmt("%d", v);
 		else
@@ -674,6 +676,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
 	char *value = NULL;
 	int flags = 0;
 	int ret = 0;
+	struct key_value_info default_kvi = KVI_INIT;
 
 	given_config_source.file = xstrdup_or_null(getenv(CONFIG_ENVIRONMENT));
 
@@ -891,7 +894,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
 	else if (actions == ACTION_SET) {
 		check_write();
 		check_argc(argc, 2, 2);
-		value = normalize_value(argv[0], argv[1]);
+		value = normalize_value(argv[0], argv[1], &default_kvi);
 		ret = git_config_set_in_file_gently(given_config_source.file, argv[0], value);
 		if (ret == CONFIG_NOTHING_SET)
 			error(_("cannot overwrite multiple values with a single value\n"
@@ -900,7 +903,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
 	else if (actions == ACTION_SET_ALL) {
 		check_write();
 		check_argc(argc, 2, 3);
-		value = normalize_value(argv[0], argv[1]);
+		value = normalize_value(argv[0], argv[1], &default_kvi);
 		ret = git_config_set_multivar_in_file_gently(given_config_source.file,
 							     argv[0], value, argv[2],
 							     flags);
@@ -908,7 +911,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
 	else if (actions == ACTION_ADD) {
 		check_write();
 		check_argc(argc, 2, 2);
-		value = normalize_value(argv[0], argv[1]);
+		value = normalize_value(argv[0], argv[1], &default_kvi);
 		ret = git_config_set_multivar_in_file_gently(given_config_source.file,
 							     argv[0], value,
 							     CONFIG_REGEX_NONE,
@@ -917,7 +920,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
 	else if (actions == ACTION_REPLACE_ALL) {
 		check_write();
 		check_argc(argc, 2, 3);
-		value = normalize_value(argv[0], argv[1]);
+		value = normalize_value(argv[0], argv[1], &default_kvi);
 		ret = git_config_set_multivar_in_file_gently(given_config_source.file,
 							     argv[0], value, argv[2],
 							     flags | CONFIG_FLAGS_MULTI_REPLACE);
diff --git a/builtin/fetch.c b/builtin/fetch.c
index a4aa0fbb8f5..fe86dfeb62a 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -137,7 +137,7 @@ static int git_fetch_config(const char *k, const char *v,
 	}
 
 	if (!strcmp(k, "submodule.fetchjobs")) {
-		fetch_config->submodule_fetch_jobs = parse_submodule_fetchjobs(k, v);
+		fetch_config->submodule_fetch_jobs = parse_submodule_fetchjobs(k, v, ctx->kvi);
 		return 0;
 	} else if (!strcmp(k, "fetch.recursesubmodules")) {
 		fetch_config->recurse_submodules = parse_fetch_recurse_submodules_arg(k, v);
@@ -145,7 +145,7 @@ static int git_fetch_config(const char *k, const char *v,
 	}
 
 	if (!strcmp(k, "fetch.parallel")) {
-		fetch_config->parallel = git_config_int(k, v);
+		fetch_config->parallel = git_config_int(k, v, ctx->kvi);
 		if (fetch_config->parallel < 0)
 			die(_("fetch.parallel cannot be negative"));
 		if (!fetch_config->parallel)
diff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c
index 91a776e2f17..6d2826b07da 100644
--- a/builtin/fsmonitor--daemon.c
+++ b/builtin/fsmonitor--daemon.c
@@ -41,7 +41,7 @@ static int fsmonitor_config(const char *var, const char *value,
 			    const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(var, FSMONITOR__IPC_THREADS)) {
-		int i = git_config_int(var, value);
+		int i = git_config_int(var, value, ctx->kvi);
 		if (i < 1)
 			return error(_("value of '%s' out of range: %d"),
 				     FSMONITOR__IPC_THREADS, i);
@@ -50,7 +50,7 @@ static int fsmonitor_config(const char *var, const char *value,
 	}
 
 	if (!strcmp(var, FSMONITOR__START_TIMEOUT)) {
-		int i = git_config_int(var, value);
+		int i = git_config_int(var, value, ctx->kvi);
 		if (i < 0)
 			return error(_("value of '%s' out of range: %d"),
 				     FSMONITOR__START_TIMEOUT, i);
@@ -60,7 +60,7 @@ static int fsmonitor_config(const char *var, const char *value,
 
 	if (!strcmp(var, FSMONITOR__ANNOUNCE_STARTUP)) {
 		int is_bool;
-		int i = git_config_bool_or_int(var, value, &is_bool);
+		int i = git_config_bool_or_int(var, value, ctx->kvi, &is_bool);
 		if (i < 0)
 			return error(_("value of '%s' not bool or int: %d"),
 				     var, i);
diff --git a/builtin/grep.c b/builtin/grep.c
index 757d52b94ec..3a464e6faab 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -301,7 +301,7 @@ static int grep_cmd_config(const char *var, const char *value,
 		st = -1;
 
 	if (!strcmp(var, "grep.threads")) {
-		num_threads = git_config_int(var, value);
+		num_threads = git_config_int(var, value, ctx->kvi);
 		if (num_threads < 0)
 			die(_("invalid number of threads specified (%d) for %s"),
 			    num_threads, var);
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index de8884ea5c2..e428f6d9a4a 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -1587,13 +1587,13 @@ static int git_index_pack_config(const char *k, const char *v,
 	struct pack_idx_option *opts = cb;
 
 	if (!strcmp(k, "pack.indexversion")) {
-		opts->version = git_config_int(k, v);
+		opts->version = git_config_int(k, v, ctx->kvi);
 		if (opts->version > 2)
 			die(_("bad pack.indexVersion=%"PRIu32), opts->version);
 		return 0;
 	}
 	if (!strcmp(k, "pack.threads")) {
-		nr_threads = git_config_int(k, v);
+		nr_threads = git_config_int(k, v, ctx->kvi);
 		if (nr_threads < 0)
 			die(_("invalid number of threads specified (%d)"),
 			    nr_threads);
diff --git a/builtin/log.c b/builtin/log.c
index 09d6a13075b..c7337354aaf 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -574,7 +574,7 @@ static int git_log_config(const char *var, const char *value,
 	if (!strcmp(var, "format.subjectprefix"))
 		return git_config_string(&fmt_patch_subject_prefix, var, value);
 	if (!strcmp(var, "format.filenamemaxlength")) {
-		fmt_patch_name_max = git_config_int(var, value);
+		fmt_patch_name_max = git_config_int(var, value, ctx->kvi);
 		return 0;
 	}
 	if (!strcmp(var, "format.encodeemailheaders")) {
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index 34aa0b483a0..38054a38b2b 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -3139,23 +3139,23 @@ static int git_pack_config(const char *k, const char *v,
 			   const struct config_context *ctx, void *cb)
 {
 	if (!strcmp(k, "pack.window")) {
-		window = git_config_int(k, v);
+		window = git_config_int(k, v, ctx->kvi);
 		return 0;
 	}
 	if (!strcmp(k, "pack.windowmemory")) {
-		window_memory_limit = git_config_ulong(k, v);
+		window_memory_limit = git_config_ulong(k, v, ctx->kvi);
 		return 0;
 	}
 	if (!strcmp(k, "pack.depth")) {
-		depth = git_config_int(k, v);
+		depth = git_config_int(k, v, ctx->kvi);
 		return 0;
 	}
 	if (!strcmp(k, "pack.deltacachesize")) {
-		max_delta_cache_size = git_config_int(k, v);
+		max_delta_cache_size = git_config_int(k, v, ctx->kvi);
 		return 0;
 	}
 	if (!strcmp(k, "pack.deltacachelimit")) {
-		cache_max_small_delta_size = git_config_int(k, v);
+		cache_max_small_delta_size = git_config_int(k, v, ctx->kvi);
 		return 0;
 	}
 	if (!strcmp(k, "pack.writebitmaphashcache")) {
@@ -3181,7 +3181,7 @@ static int git_pack_config(const char *k, const char *v,
 		return 0;
 	}
 	if (!strcmp(k, "pack.threads")) {
-		delta_search_threads = git_config_int(k, v);
+		delta_search_threads = git_config_int(k, v, ctx->kvi);
 		if (delta_search_threads < 0)
 			die(_("invalid number of threads specified (%d)"),
 			    delta_search_threads);
@@ -3192,7 +3192,7 @@ static int git_pack_config(const char *k, const char *v,
 		return 0;
 	}
 	if (!strcmp(k, "pack.indexversion")) {
-		pack_idx_opts.version = git_config_int(k, v);
+		pack_idx_opts.version = git_config_int(k, v, ctx->kvi);
 		if (pack_idx_opts.version > 2)
 			die(_("bad pack.indexVersion=%"PRIu32),
 			    pack_idx_opts.version);
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 94d9898aff7..98f6f0038f0 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -158,12 +158,12 @@ static int receive_pack_config(const char *var, const char *value,
 	}
 
 	if (strcmp(var, "receive.unpacklimit") == 0) {
-		receive_unpack_limit = git_config_int(var, value);
+		receive_unpack_limit = git_config_int(var, value, ctx->kvi);
 		return 0;
 	}
 
 	if (strcmp(var, "transfer.unpacklimit") == 0) {
-		transfer_unpack_limit = git_config_int(var, value);
+		transfer_unpack_limit = git_config_int(var, value, ctx->kvi);
 		return 0;
 	}
 
@@ -231,7 +231,7 @@ static int receive_pack_config(const char *var, const char *value,
 		return git_config_string(&cert_nonce_seed, var, value);
 
 	if (strcmp(var, "receive.certnonceslop") == 0) {
-		nonce_stamp_slop_limit = git_config_ulong(var, value);
+		nonce_stamp_slop_limit = git_config_ulong(var, value, ctx->kvi);
 		return 0;
 	}
 
@@ -246,12 +246,12 @@ static int receive_pack_config(const char *var, const char *value,
 	}
 
 	if (strcmp(var, "receive.keepalive") == 0) {
-		keepalive_in_sec = git_config_int(var, value);
+		keepalive_in_sec = git_config_int(var, value, ctx->kvi);
 		return 0;
 	}
 
 	if (strcmp(var, "receive.maxinputsize") == 0) {
-		max_input_size = git_config_int64(var, value);
+		max_input_size = git_config_int64(var, value, ctx->kvi);
 		return 0;
 	}
 
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index f8e9d85e77a..57185cf5f3d 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2192,13 +2192,13 @@ static int update_clone_task_finished(int result,
 }
 
 static int git_update_clone_config(const char *var, const char *value,
-				   const struct config_context *ctx UNUSED,
+				   const struct config_context *ctx,
 				   void *cb)
 {
 	int *max_jobs = cb;
 
 	if (!strcmp(var, "submodule.fetchjobs"))
-		*max_jobs = parse_submodule_fetchjobs(var, value);
+		*max_jobs = parse_submodule_fetchjobs(var, value, ctx->kvi);
 	return 0;
 }
 
diff --git a/config.c b/config.c
index d10faba56d3..4e3c4dcf415 100644
--- a/config.c
+++ b/config.c
@@ -73,18 +73,8 @@ struct config_reader {
 	 *
 	 * 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
@@ -96,8 +86,6 @@ 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 not be set while iterating a config set");
 	top->prev = reader->source;
 	reader->source = top;
 }
@@ -112,12 +100,6 @@ 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)
-{
-	reader->config_kvi = kvi;
-}
-
 static int pack_compression_seen;
 static int zlib_compression_seen;
 
@@ -1346,80 +1328,78 @@ 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_reader *reader, const char *name,
-			   const char *value)
+static void die_bad_number(const char *name, const char *value,
+			   const struct key_value_info *kvi)
 {
 	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 (!kvi)
+		BUG("kvi should not be NULL");
 
 	if (!value)
 		value = "";
 
-	/* 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)
+	if (!kvi->filename)
 		die(_(bad_numeric), value, name, _(error_type));
 
-	switch (config_origin) {
+	switch (kvi->origin_type) {
 	case CONFIG_ORIGIN_BLOB:
 		die(_("bad numeric config value '%s' for '%s' in blob %s: %s"),
-		    value, name, config_name, _(error_type));
+		    value, name, kvi->filename, _(error_type));
 	case CONFIG_ORIGIN_FILE:
 		die(_("bad numeric config value '%s' for '%s' in file %s: %s"),
-		    value, name, config_name, _(error_type));
+		    value, name, kvi->filename, _(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, config_name, _(error_type));
+		    value, name, kvi->filename, _(error_type));
 	case CONFIG_ORIGIN_CMDLINE:
 		die(_("bad numeric config value '%s' for '%s' in command line %s: %s"),
-		    value, name, config_name, _(error_type));
+		    value, name, kvi->filename, _(error_type));
 	default:
 		die(_("bad numeric config value '%s' for '%s' in %s: %s"),
-		    value, name, config_name, _(error_type));
+		    value, name, kvi->filename, _(error_type));
 	}
 }
 
-int git_config_int(const char *name, const char *value)
+int git_config_int(const char *name, const char *value,
+		   const struct key_value_info *kvi)
 {
 	int ret;
 	if (!git_parse_int(value, &ret))
-		die_bad_number(&the_reader, name, value);
+		die_bad_number(name, value, kvi);
 	return ret;
 }
 
-int64_t git_config_int64(const char *name, const char *value)
+int64_t git_config_int64(const char *name, const char *value,
+			 const struct key_value_info *kvi)
 {
 	int64_t ret;
 	if (!git_parse_int64(value, &ret))
-		die_bad_number(&the_reader, name, value);
+		die_bad_number(name, value, kvi);
 	return ret;
 }
 
-unsigned long git_config_ulong(const char *name, const char *value)
+unsigned long git_config_ulong(const char *name, const char *value,
+			       const struct key_value_info *kvi)
 {
 	unsigned long ret;
 	if (!git_parse_ulong(value, &ret))
-		die_bad_number(&the_reader, name, value);
+		die_bad_number(name, value, kvi);
 	return ret;
 }
 
-ssize_t git_config_ssize_t(const char *name, const char *value)
+ssize_t git_config_ssize_t(const char *name, const char *value,
+			   const struct key_value_info *kvi)
 {
 	ssize_t ret;
 	if (!git_parse_ssize_t(value, &ret))
-		die_bad_number(&the_reader, name, value);
+		die_bad_number(name, value, kvi);
 	return ret;
 }
 
@@ -1524,7 +1504,8 @@ int git_parse_maybe_bool(const char *value)
 	return -1;
 }
 
-int git_config_bool_or_int(const char *name, const char *value, int *is_bool)
+int git_config_bool_or_int(const char *name, const char *value,
+			   const struct key_value_info *kvi, int *is_bool)
 {
 	int v = git_parse_maybe_bool_text(value);
 	if (0 <= v) {
@@ -1532,7 +1513,7 @@ int git_config_bool_or_int(const char *name, const char *value, int *is_bool)
 		return v;
 	}
 	*is_bool = 0;
-	return git_config_int(name, value);
+	return git_config_int(name, value, kvi);
 }
 
 int git_config_bool(const char *name, const char *value)
@@ -1658,7 +1639,7 @@ static int git_default_core_config(const char *var, const char *value,
 		else if (!git_parse_maybe_bool_text(value))
 			default_abbrev = the_hash_algo->hexsz;
 		else {
-			int abbrev = git_config_int(var, value);
+			int abbrev = git_config_int(var, value, ctx->kvi);
 			if (abbrev < minimum_abbrev || abbrev > the_hash_algo->hexsz)
 				return error(_("abbrev length out of range: %d"), abbrev);
 			default_abbrev = abbrev;
@@ -1670,7 +1651,7 @@ static int git_default_core_config(const char *var, const char *value,
 		return set_disambiguate_hint_config(var, value);
 
 	if (!strcmp(var, "core.loosecompression")) {
-		int level = git_config_int(var, value);
+		int level = git_config_int(var, value, ctx->kvi);
 		if (level == -1)
 			level = Z_DEFAULT_COMPRESSION;
 		else if (level < 0 || level > Z_BEST_COMPRESSION)
@@ -1681,7 +1662,7 @@ static int git_default_core_config(const char *var, const char *value,
 	}
 
 	if (!strcmp(var, "core.compression")) {
-		int level = git_config_int(var, value);
+		int level = git_config_int(var, value, ctx->kvi);
 		if (level == -1)
 			level = Z_DEFAULT_COMPRESSION;
 		else if (level < 0 || level > Z_BEST_COMPRESSION)
@@ -1695,7 +1676,7 @@ static int git_default_core_config(const char *var, const char *value,
 
 	if (!strcmp(var, "core.packedgitwindowsize")) {
 		int pgsz_x2 = getpagesize() * 2;
-		packed_git_window_size = git_config_ulong(var, value);
+		packed_git_window_size = git_config_ulong(var, value, ctx->kvi);
 
 		/* This value must be multiple of (pagesize * 2) */
 		packed_git_window_size /= pgsz_x2;
@@ -1706,17 +1687,17 @@ static int git_default_core_config(const char *var, const char *value,
 	}
 
 	if (!strcmp(var, "core.bigfilethreshold")) {
-		big_file_threshold = git_config_ulong(var, value);
+		big_file_threshold = git_config_ulong(var, value, ctx->kvi);
 		return 0;
 	}
 
 	if (!strcmp(var, "core.packedgitlimit")) {
-		packed_git_limit = git_config_ulong(var, value);
+		packed_git_limit = git_config_ulong(var, value, ctx->kvi);
 		return 0;
 	}
 
 	if (!strcmp(var, "core.deltabasecachelimit")) {
-		delta_base_cache_limit = git_config_ulong(var, value);
+		delta_base_cache_limit = git_config_ulong(var, value, ctx->kvi);
 		return 0;
 	}
 
@@ -1995,12 +1976,12 @@ int git_default_config(const char *var, const char *value,
 	}
 
 	if (!strcmp(var, "pack.packsizelimit")) {
-		pack_size_limit_cfg = git_config_ulong(var, value);
+		pack_size_limit_cfg = git_config_ulong(var, value, ctx->kvi);
 		return 0;
 	}
 
 	if (!strcmp(var, "pack.compression")) {
-		int level = git_config_int(var, value);
+		int level = git_config_int(var, value, ctx->kvi);
 		if (level == -1)
 			level = Z_DEFAULT_COMPRESSION;
 		else if (level < 0 || level > Z_BEST_COMPRESSION)
@@ -2344,13 +2325,11 @@ static void configset_iter(struct config_reader *reader, struct config_set *set,
 		value_index = list->items[i].value_index;
 		values = &entry->value_list;
 
-		config_reader_set_kvi(reader, values->items[value_index].util);
 		ctx.kvi = values->items[value_index].util;
 		if (fn(entry->key, values->items[value_index].string, &ctx, data) < 0)
 			git_die_config_linenr(entry->key,
 					      ctx.kvi->filename,
 					      ctx.kvi->linenr);
-		config_reader_set_kvi(reader, NULL);
 	}
 }
 
@@ -2536,11 +2515,12 @@ int git_configset_add_file(struct config_set *set, const char *filename)
 	return git_config_from_file(config_set_callback, filename, &data);
 }
 
-int git_configset_get_value(struct config_set *set, const char *key, const char **value)
+int git_configset_get_value(struct config_set *set, const char *key,
+			    const char **value, struct key_value_info *kvi)
 {
 	const struct string_list *values = NULL;
 	int ret;
-
+	struct string_list_item item;
 	/*
 	 * Follows "last one wins" semantic, i.e., if there are multiple matches for the
 	 * queried key in the files of the configset, the value returned will be the last
@@ -2550,7 +2530,10 @@ int git_configset_get_value(struct config_set *set, const char *key, const char
 		return ret;
 
 	assert(values->nr > 0);
-	*value = values->items[values->nr - 1].string;
+	item = values->items[values->nr - 1];
+	*value = item.string;
+	if (kvi)
+		*kvi = *((struct key_value_info *)item.util);
 	return 0;
 }
 
@@ -2603,7 +2586,7 @@ int git_configset_get(struct config_set *set, const char *key)
 int git_configset_get_string(struct config_set *set, const char *key, char **dest)
 {
 	const char *value;
-	if (!git_configset_get_value(set, key, &value))
+	if (!git_configset_get_value(set, key, &value, NULL))
 		return git_config_string((const char **)dest, key, value);
 	else
 		return 1;
@@ -2613,7 +2596,7 @@ 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(set, key, &value)) {
+	if (!git_configset_get_value(set, key, &value, NULL)) {
 		if (!value)
 			return config_error_nonbool(key);
 		*dest = value;
@@ -2626,8 +2609,10 @@ static int git_configset_get_string_tmp(struct config_set *set, const char *key,
 int git_configset_get_int(struct config_set *set, const char *key, int *dest)
 {
 	const char *value;
-	if (!git_configset_get_value(set, key, &value)) {
-		*dest = git_config_int(key, value);
+	struct key_value_info kvi;
+
+	if (!git_configset_get_value(set, key, &value, &kvi)) {
+		*dest = git_config_int(key, value, &kvi);
 		return 0;
 	} else
 		return 1;
@@ -2636,8 +2621,10 @@ int git_configset_get_int(struct config_set *set, const char *key, int *dest)
 int git_configset_get_ulong(struct config_set *set, const char *key, unsigned long *dest)
 {
 	const char *value;
-	if (!git_configset_get_value(set, key, &value)) {
-		*dest = git_config_ulong(key, value);
+	struct key_value_info kvi;
+
+	if (!git_configset_get_value(set, key, &value, &kvi)) {
+		*dest = git_config_ulong(key, value, &kvi);
 		return 0;
 	} else
 		return 1;
@@ -2646,7 +2633,7 @@ int git_configset_get_ulong(struct config_set *set, const char *key, unsigned lo
 int git_configset_get_bool(struct config_set *set, const char *key, int *dest)
 {
 	const char *value;
-	if (!git_configset_get_value(set, key, &value)) {
+	if (!git_configset_get_value(set, key, &value, NULL)) {
 		*dest = git_config_bool(key, value);
 		return 0;
 	} else
@@ -2657,8 +2644,10 @@ 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(set, key, &value)) {
-		*dest = git_config_bool_or_int(key, value, is_bool);
+	struct key_value_info kvi;
+
+	if (!git_configset_get_value(set, key, &value, &kvi)) {
+		*dest = git_config_bool_or_int(key, value, &kvi, is_bool);
 		return 0;
 	} else
 		return 1;
@@ -2667,7 +2656,7 @@ int git_configset_get_bool_or_int(struct config_set *set, const char *key,
 int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *dest)
 {
 	const char *value;
-	if (!git_configset_get_value(set, key, &value)) {
+	if (!git_configset_get_value(set, key, &value, NULL)) {
 		*dest = git_parse_maybe_bool(value);
 		if (*dest == -1)
 			return -1;
@@ -2679,7 +2668,7 @@ int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *d
 int git_configset_get_pathname(struct config_set *set, const char *key, const char **dest)
 {
 	const char *value;
-	if (!git_configset_get_value(set, key, &value))
+	if (!git_configset_get_value(set, key, &value, NULL))
 		return git_config_pathname(dest, key, value);
 	else
 		return 1;
@@ -2749,7 +2738,7 @@ int repo_config_get_value(struct repository *repo,
 			  const char *key, const char **value)
 {
 	git_config_check_init(repo);
-	return git_configset_get_value(repo->config, key, value);
+	return git_configset_get_value(repo->config, key, value, NULL);
 }
 
 int repo_config_get_value_multi(struct repository *repo, const char *key,
@@ -3989,18 +3978,6 @@ int parse_config_key(const char *var,
 	return 0;
 }
 
-static int reader_origin_type(struct config_reader *reader,
-			      enum config_origin_type *type)
-{
-	if (the_reader.config_kvi)
-		*type = reader->config_kvi->origin_type;
-	else if(the_reader.source)
-		*type = reader->source->origin_type;
-	else
-		return 1;
-	return 0;
-}
-
 const char *config_origin_type_name(enum config_origin_type type)
 {
 	switch (type) {
@@ -4039,17 +4016,6 @@ const char *config_scope_name(enum config_scope scope)
 	}
 }
 
-static int reader_config_name(struct config_reader *reader, const char **out)
-{
-	if (the_reader.config_kvi)
-		*out = reader->config_kvi->filename;
-	else if (the_reader.source)
-		*out = reader->source->name;
-	else
-		return 1;
-	return 0;
-}
-
 int lookup_config(const char **mapping, int nr_mapping, const char *var)
 {
 	int i;
diff --git a/config.h b/config.h
index 82eeba94e71..bd366ddb5ef 100644
--- a/config.h
+++ b/config.h
@@ -249,22 +249,26 @@ int git_parse_maybe_bool(const char *);
  * Parse the string to an integer, including unit factors. Dies on error;
  * otherwise, returns the parsed result.
  */
-int git_config_int(const char *, const char *);
+int git_config_int(const char *, const char *, const struct key_value_info *);
 
-int64_t git_config_int64(const char *, const char *);
+int64_t git_config_int64(const char *, const char *,
+			 const struct key_value_info *);
 
 /**
  * Identical to `git_config_int`, but for unsigned longs.
  */
-unsigned long git_config_ulong(const char *, const char *);
+unsigned long git_config_ulong(const char *, const char *,
+			       const struct key_value_info *);
 
-ssize_t git_config_ssize_t(const char *, const char *);
+ssize_t git_config_ssize_t(const char *, const char *,
+			   const struct key_value_info *);
 
 /**
  * Same as `git_config_bool`, except that integers are returned as-is, and
  * an `is_bool` flag is unset.
  */
-int git_config_bool_or_int(const char *, const char *, int *);
+int git_config_bool_or_int(const char *, const char *,
+			   const struct key_value_info *, int *);
 
 /**
  * Parse a string into a boolean value, respecting keywords like "true" and
@@ -529,7 +533,8 @@ int git_configset_get(struct config_set *cs, const char *key);
  * touching `value`. The caller should not free or modify `value`, as it
  * is owned by the cache.
  */
-int git_configset_get_value(struct config_set *cs, const char *key, const char **dest);
+int git_configset_get_value(struct config_set *cs, const char *key,
+			    const char **dest, struct key_value_info *kvi);
 
 int git_configset_get_string(struct config_set *cs, const char *key, char **dest);
 int git_configset_get_int(struct config_set *cs, const char *key, int *dest);
diff --git a/contrib/coccinelle/git_config_number.cocci b/contrib/coccinelle/git_config_number.cocci
new file mode 100644
index 00000000000..7b57dceefe6
--- /dev/null
+++ b/contrib/coccinelle/git_config_number.cocci
@@ -0,0 +1,27 @@
+@@
+identifier C1, C2, C3;
+@@
+(
+(
+git_config_int
+|
+git_config_int64
+|
+git_config_ulong
+|
+git_config_ssize_t
+)
+  (C1, C2
++ , ctx->kvi
+  )
+|
+(
+git_configset_get_value
+|
+git_config_bool_or_int
+)
+  (C1, C2
++ , ctx->kvi
+ , C3
+  )
+)
diff --git a/diff.c b/diff.c
index 0e382c8f7f0..460211e7d40 100644
--- a/diff.c
+++ b/diff.c
@@ -379,13 +379,14 @@ int git_diff_ui_config(const char *var, const char *value,
 		return 0;
 	}
 	if (!strcmp(var, "diff.context")) {
-		diff_context_default = git_config_int(var, value);
+		diff_context_default = git_config_int(var, value, ctx->kvi);
 		if (diff_context_default < 0)
 			return -1;
 		return 0;
 	}
 	if (!strcmp(var, "diff.interhunkcontext")) {
-		diff_interhunk_context_default = git_config_int(var, value);
+		diff_interhunk_context_default = git_config_int(var, value,
+								ctx->kvi);
 		if (diff_interhunk_context_default < 0)
 			return -1;
 		return 0;
@@ -411,7 +412,7 @@ int git_diff_ui_config(const char *var, const char *value,
 		return 0;
 	}
 	if (!strcmp(var, "diff.statgraphwidth")) {
-		diff_stat_graph_width = git_config_int(var, value);
+		diff_stat_graph_width = git_config_int(var, value, ctx->kvi);
 		return 0;
 	}
 	if (!strcmp(var, "diff.external"))
@@ -450,7 +451,7 @@ int git_diff_basic_config(const char *var, const char *value,
 	const char *name;
 
 	if (!strcmp(var, "diff.renamelimit")) {
-		diff_rename_limit_default = git_config_int(var, value);
+		diff_rename_limit_default = git_config_int(var, value, ctx->kvi);
 		return 0;
 	}
 
diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c
index 10137444321..3ae59bde2f2 100644
--- a/fmt-merge-msg.c
+++ b/fmt-merge-msg.c
@@ -25,7 +25,7 @@ int fmt_merge_msg_config(const char *key, const char *value,
 {
 	if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) {
 		int is_bool;
-		merge_log_config = git_config_bool_or_int(key, value, &is_bool);
+		merge_log_config = git_config_bool_or_int(key, value, ctx->kvi, &is_bool);
 		if (!is_bool && merge_log_config < 0)
 			return error("%s: negative length %s", key, value);
 		if (is_bool && merge_log_config)
diff --git a/help.c b/help.c
index ac0ae5ac0dc..389382b1482 100644
--- a/help.c
+++ b/help.c
@@ -545,7 +545,7 @@ static struct cmdnames aliases;
 #define AUTOCORRECT_IMMEDIATELY (-1)
 
 static int git_unknown_cmd_config(const char *var, const char *value,
-				  const struct config_context *ctx UNUSED,
+				  const struct config_context *ctx,
 				  void *cb UNUSED)
 {
 	const char *p;
@@ -560,7 +560,7 @@ static int git_unknown_cmd_config(const char *var, const char *value,
 		} else if (!strcmp(value, "prompt")) {
 			autocorrect = AUTOCORRECT_PROMPT;
 		} else {
-			int v = git_config_int(var, value);
+			int v = git_config_int(var, value, ctx->kvi);
 			autocorrect = (v < 0)
 				? AUTOCORRECT_IMMEDIATELY : v;
 		}
diff --git a/http.c b/http.c
index 762502828c9..b12b234bde1 100644
--- a/http.c
+++ b/http.c
@@ -414,21 +414,21 @@ static int http_options(const char *var, const char *value,
 	}
 
 	if (!strcmp("http.minsessions", var)) {
-		min_curl_sessions = git_config_int(var, value);
+		min_curl_sessions = git_config_int(var, value, ctx->kvi);
 		if (min_curl_sessions > 1)
 			min_curl_sessions = 1;
 		return 0;
 	}
 	if (!strcmp("http.maxrequests", var)) {
-		max_requests = git_config_int(var, value);
+		max_requests = git_config_int(var, value, ctx->kvi);
 		return 0;
 	}
 	if (!strcmp("http.lowspeedlimit", var)) {
-		curl_low_speed_limit = (long)git_config_int(var, value);
+		curl_low_speed_limit = (long)git_config_int(var, value, ctx->kvi);
 		return 0;
 	}
 	if (!strcmp("http.lowspeedtime", var)) {
-		curl_low_speed_time = (long)git_config_int(var, value);
+		curl_low_speed_time = (long)git_config_int(var, value, ctx->kvi);
 		return 0;
 	}
 
@@ -464,7 +464,7 @@ static int http_options(const char *var, const char *value,
 	}
 
 	if (!strcmp("http.postbuffer", var)) {
-		http_post_buffer = git_config_ssize_t(var, value);
+		http_post_buffer = git_config_ssize_t(var, value, ctx->kvi);
 		if (http_post_buffer < 0)
 			warning(_("negative value for http.postBuffer; defaulting to %d"), LARGE_PACKET_MAX);
 		if (http_post_buffer < LARGE_PACKET_MAX)
diff --git a/imap-send.c b/imap-send.c
index 47777e76861..3518a4ace6c 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -1342,7 +1342,7 @@ static int git_imap_config(const char *var, const char *val,
 	else if (!strcmp("imap.authmethod", var))
 		return git_config_string(&server.auth_method, var, val);
 	else if (!strcmp("imap.port", var))
-		server.port = git_config_int(var, val);
+		server.port = git_config_int(var, val, ctx->kvi);
 	else if (!strcmp("imap.host", var)) {
 		if (!val) {
 			git_die_config("imap.host", "Missing value for 'imap.host'");
diff --git a/sequencer.c b/sequencer.c
index 34754d17596..a0aaee3056d 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -2883,7 +2883,7 @@ static int git_config_string_dup(char **dest,
 }
 
 static int populate_opts_cb(const char *key, const char *value,
-			    const struct config_context *ctx UNUSED,
+			    const struct config_context *ctx,
 			    void *data)
 {
 	struct replay_opts *opts = data;
@@ -2892,26 +2892,26 @@ static int populate_opts_cb(const char *key, const char *value,
 	if (!value)
 		error_flag = 0;
 	else if (!strcmp(key, "options.no-commit"))
-		opts->no_commit = git_config_bool_or_int(key, value, &error_flag);
+		opts->no_commit = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
 	else if (!strcmp(key, "options.edit"))
-		opts->edit = git_config_bool_or_int(key, value, &error_flag);
+		opts->edit = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
 	else if (!strcmp(key, "options.allow-empty"))
 		opts->allow_empty =
-			git_config_bool_or_int(key, value, &error_flag);
+			git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
 	else if (!strcmp(key, "options.allow-empty-message"))
 		opts->allow_empty_message =
-			git_config_bool_or_int(key, value, &error_flag);
+			git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
 	else if (!strcmp(key, "options.keep-redundant-commits"))
 		opts->keep_redundant_commits =
-			git_config_bool_or_int(key, value, &error_flag);
+			git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
 	else if (!strcmp(key, "options.signoff"))
-		opts->signoff = git_config_bool_or_int(key, value, &error_flag);
+		opts->signoff = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
 	else if (!strcmp(key, "options.record-origin"))
-		opts->record_origin = git_config_bool_or_int(key, value, &error_flag);
+		opts->record_origin = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
 	else if (!strcmp(key, "options.allow-ff"))
-		opts->allow_ff = git_config_bool_or_int(key, value, &error_flag);
+		opts->allow_ff = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
 	else if (!strcmp(key, "options.mainline"))
-		opts->mainline = git_config_int(key, value);
+		opts->mainline = git_config_int(key, value, ctx->kvi);
 	else if (!strcmp(key, "options.strategy"))
 		git_config_string_dup(&opts->strategy, key, value);
 	else if (!strcmp(key, "options.gpg-sign"))
@@ -2920,7 +2920,7 @@ static int populate_opts_cb(const char *key, const char *value,
 		strvec_push(&opts->xopts, value);
 	} else if (!strcmp(key, "options.allow-rerere-auto"))
 		opts->allow_rerere_auto =
-			git_config_bool_or_int(key, value, &error_flag) ?
+			git_config_bool_or_int(key, value, ctx->kvi, &error_flag) ?
 				RERERE_AUTOUPDATE : RERERE_NOAUTOUPDATE;
 	else if (!strcmp(key, "options.default-msg-cleanup")) {
 		opts->explicit_cleanup = 1;
diff --git a/setup.c b/setup.c
index fadba5bab4b..ca80fb464bb 100644
--- a/setup.c
+++ b/setup.c
@@ -597,7 +597,7 @@ static int check_repo_format(const char *var, const char *value,
 	const char *ext;
 
 	if (strcmp(var, "core.repositoryformatversion") == 0)
-		data->version = git_config_int(var, value);
+		data->version = git_config_int(var, value, ctx->kvi);
 	else if (skip_prefix(var, "extensions.", &ext)) {
 		switch (handle_extension_v0(var, value, ext, data)) {
 		case EXTENSION_ERROR:
diff --git a/submodule-config.c b/submodule-config.c
index 3f25bd13674..54be580e2a9 100644
--- a/submodule-config.c
+++ b/submodule-config.c
@@ -304,9 +304,10 @@ static int parse_fetch_recurse(const char *opt, const char *arg,
 	}
 }
 
-int parse_submodule_fetchjobs(const char *var, const char *value)
+int parse_submodule_fetchjobs(const char *var, const char *value,
+			      const struct key_value_info *kvi)
 {
-	int fetchjobs = git_config_int(var, value);
+	int fetchjobs = git_config_int(var, value, kvi);
 	if (fetchjobs < 0)
 		die(_("negative values not allowed for submodule.fetchJobs"));
 	if (!fetchjobs)
@@ -849,14 +850,14 @@ struct fetch_config {
 };
 
 static int gitmodules_fetch_config(const char *var, const char *value,
-				   const struct config_context *ctx UNUSED,
+				   const struct config_context *ctx,
 				   void *cb)
 {
 	struct fetch_config *config = cb;
 	if (!strcmp(var, "submodule.fetchjobs")) {
 		if (config->max_children)
 			*(config->max_children) =
-				parse_submodule_fetchjobs(var, value);
+				parse_submodule_fetchjobs(var, value, ctx->kvi);
 		return 0;
 	} else if (!strcmp(var, "fetch.recursesubmodules")) {
 		if (config->recurse_submodules)
@@ -878,12 +879,12 @@ void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules)
 }
 
 static int gitmodules_update_clone_config(const char *var, const char *value,
-					  const struct config_context *ctx UNUSED,
+					  const struct config_context *ctx,
 					  void *cb)
 {
 	int *max_jobs = cb;
 	if (!strcmp(var, "submodule.fetchjobs"))
-		*max_jobs = parse_submodule_fetchjobs(var, value);
+		*max_jobs = parse_submodule_fetchjobs(var, value, ctx->kvi);
 	return 0;
 }
 
diff --git a/submodule-config.h b/submodule-config.h
index c2045875bbb..2a37689cc27 100644
--- a/submodule-config.h
+++ b/submodule-config.h
@@ -50,7 +50,8 @@ struct repository;
 
 void submodule_cache_free(struct submodule_cache *cache);
 
-int parse_submodule_fetchjobs(const char *var, const char *value);
+int parse_submodule_fetchjobs(const char *var, const char *value,
+			      const struct key_value_info *kvi);
 int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg);
 struct option;
 int option_fetch_parse_recurse_submodules(const struct option *opt,
diff --git a/t/helper/test-config.c b/t/helper/test-config.c
index 3f4c3678318..ed444ca4c25 100644
--- a/t/helper/test-config.c
+++ b/t/helper/test-config.c
@@ -63,12 +63,12 @@ static int iterate_cb(const char *var, const char *value,
 }
 
 static int parse_int_cb(const char *var, const char *value,
-			const struct config_context *ctx UNUSED, void *data)
+			const struct config_context *ctx, void *data)
 {
 	const char *key_to_match = data;
 
 	if (!strcmp(key_to_match, var)) {
-		int parsed = git_config_int(value, value);
+		int parsed = git_config_int(value, value, ctx->kvi);
 		printf("%d\n", parsed);
 	}
 	return 0;
@@ -182,7 +182,7 @@ int cmd__config(int argc, const char **argv)
 				goto exit2;
 			}
 		}
-		if (!git_configset_get_value(&cs, argv[2], &v)) {
+		if (!git_configset_get_value(&cs, argv[2], &v, NULL)) {
 			if (!v)
 				printf("(NULL)\n");
 			else
diff --git a/upload-pack.c b/upload-pack.c
index 951fd1f9c25..c03415a5460 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -1275,7 +1275,8 @@ static int find_symref(const char *refname,
 }
 
 static int parse_object_filter_config(const char *var, const char *value,
-				       struct upload_pack_data *data)
+				      const struct key_value_info *kvi,
+				      struct upload_pack_data *data)
 {
 	struct strbuf buf = STRBUF_INIT;
 	const char *sub, *key;
@@ -1302,7 +1303,8 @@ static int parse_object_filter_config(const char *var, const char *value,
 		}
 		string_list_insert(&data->allowed_filters, buf.buf)->util =
 			(void *)(intptr_t)1;
-		data->tree_filter_max_depth = git_config_ulong(var, value);
+		data->tree_filter_max_depth = git_config_ulong(var, value,
+							       kvi);
 	}
 
 	strbuf_release(&buf);
@@ -1310,7 +1312,7 @@ static int parse_object_filter_config(const char *var, const char *value,
 }
 
 static int upload_pack_config(const char *var, const char *value,
-			      const struct config_context *ctx UNUSED,
+			      const struct config_context *ctx,
 			      void *cb_data)
 {
 	struct upload_pack_data *data = cb_data;
@@ -1331,7 +1333,7 @@ static int upload_pack_config(const char *var, const char *value,
 		else
 			data->allow_uor &= ~ALLOW_ANY_SHA1;
 	} else if (!strcmp("uploadpack.keepalive", var)) {
-		data->keepalive = git_config_int(var, value);
+		data->keepalive = git_config_int(var, value, ctx->kvi);
 		if (!data->keepalive)
 			data->keepalive = -1;
 	} else if (!strcmp("uploadpack.allowfilter", var)) {
@@ -1346,7 +1348,7 @@ static int upload_pack_config(const char *var, const char *value,
 		data->advertise_sid = git_config_bool(var, value);
 	}
 
-	if (parse_object_filter_config(var, value, data) < 0)
+	if (parse_object_filter_config(var, value, ctx->kvi, data) < 0)
 		return -1;
 
 	return parse_hide_refs_config(var, value, "uploadpack", &data->hidden_refs);
diff --git a/worktree.c b/worktree.c
index c448fecd4b3..af277ec9010 100644
--- a/worktree.c
+++ b/worktree.c
@@ -835,7 +835,7 @@ int init_worktree_config(struct repository *r)
 	 * Relocate that value to avoid breaking all worktrees with this
 	 * upgrade to worktree config.
 	 */
-	if (!git_configset_get_value(&cs, "core.worktree", &core_worktree)) {
+	if (!git_configset_get_value(&cs, "core.worktree", &core_worktree, NULL)) {
 		if ((res = move_config_setting("core.worktree", core_worktree,
 					       common_config_file,
 					       main_worktree_file)))
-- 
gitgitgadget


^ permalink raw reply related

* cw/header-compat-util-shuffle (was Re: What's cooking in git.git (Jun 2023, #07; Tue, 27))
From: Glen Choo @ 2023-06-28 19:29 UTC (permalink / raw)
  To: Junio C Hamano, git; +Cc: Calvin Wan
In-Reply-To: <xmqqcz1gftdn.fsf@gitster.g>

I notice that

  https://lore.kernel.org/git/20230606170711.912972-1-calvinwan@google.com/

is available as cw/header-compat-util-shuffle, but AFAICT that's not in
this announcement or 'seen'.

^ permalink raw reply

* Re: [GSoC] Blog
From: Kousik Sanagavarapu @ 2023-06-28 20:01 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Hariom Verma
In-Reply-To: <ZIdrQybUsjEcxMrb@five231003>

Hi,
Week 5 post is here

	Week 5: https://five-sh.github.io/2023/06/28/week5

Feel free to suggest any changes or comment on or off list.

Thanks

^ permalink raw reply

* Draft of Git Rev News edition 100
From: Christian Couder @ 2023-06-28 20:25 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jakub Narebski, Markus Jansen, Kaartic Sivaraam,
	Taylor Blau, Johannes Schindelin,
	Ævar Arnfjörð Bjarmason, Bruno Brito, Calvin Wan,
	Elijah Newren, Phillip Wood, Eric Sunshine, Jeff King

Hi everyone!

(Before the usual announcement, just a last reminder that our Reader Survey
is only up until next Friday, June 30th, at:

https://survs.com/survey/jo07oxsaom

Thanks for taking a bit of time to help us improve Git Rev News!)

A draft of a new Git Rev News edition is available here:

  https://github.com/git/git.github.io/blob/master/rev_news/drafts/edition-100.md

Everyone is welcome to contribute in any section either by editing the
above page on GitHub and sending a pull request, or by commenting on
this GitHub issue:

  https://github.com/git/git.github.io/issues/647

You can also reply to this email.

In general all kinds of contributions, for example proofreading,
suggestions for articles or links, help on the issues in GitHub,
volunteering for being interviewed and so on, are very much
appreciated.

I tried to Cc everyone who appears in this edition, but maybe I missed
some people, sorry about that.

Jakub, Markus, Kaartic and I plan to publish this edition late on
Friday June 30th.

Thanks,
Christian.

^ permalink raw reply

* Re: [PATCH 6/9] gitk: add keyboard bind for create and remove branch
From: Johannes Sixt @ 2023-06-28 20:30 UTC (permalink / raw)
  To: Jens Lideström; +Cc: Paul Mackerras [ ], git
In-Reply-To: <36b3ce90d4ecc9eb9fa5174aba0cf9d4@lidestrom.se>

Am 28.06.23 um 09:12 schrieb Jens Lideström:
> My intention is to always remove the first branch head that is displayed
> for a single commit in the GUI. This caters to the common use case, with
> only one branch for a single commit. If there are multiple branch heads
> on a commit and the users don't want to remove the first one then they
> need to use the mouse context menu to choose which one to delete.
> 
> I could change the implementation to display a dialog that lets the user
> choose in case of multiple branch heads.

IMO a selection dialog is the second-best solution. The best one is to
make the branch labels selectable somehow via keyboard navigation. I am
not a fan of the here implement behavior, because it is only a
half-solution.

Also, the order of branch labels on a line is not 100% deterministic:
when you create a branch, it goes last, but when you refresh the view,
branch labels are sorted alphabetically (I think). This means you can't
create a branch and delete it right away if there is already a branch on
the commit.

> In that case, should I do that as part of this PR, or as a follow up? I
> would prefer to finish this one first.

My preference would be to not implement this behavior until it is clear
what it should be.

-- Hannes


^ permalink raw reply

* Re: [PATCH 0/9] gitk: improve keyboard support
From: Johannes Sixt @ 2023-06-28 20:32 UTC (permalink / raw)
  To: Jens Lideström, Jens Lidestrom via GitGitGadget
  Cc: Paul Mackerras [ ], git
In-Reply-To: <102dabfc-595b-5f85-bc2c-ed9a003cd7bd@lidestrom.se>

Am 28.06.23 um 19:32 schrieb Jens Lideström:
> @Hannes: I choose key combinations with Ctrl+<another-key>.
> 
> Another possibility is to use Ctrl+Shift+<another-key>. That is more
> complicated to press, but it creates a nice distinction between two
> categories of commands:
> 
> Searching and navigation command (existing): Ctrl+<another-key>
> 
> Branch modification commands (new): Ctrl+Shift+<another-key>
> 
> Do you have any opinion on this? Only Ctrl, or Ctrl+Shift for the new
> commands?

I have no strong opinion, with a mild preference for the versions
without Shift, just because they are more ergonomic.

-- Hannes


^ permalink raw reply

* Re: [RFC PATCH 6/8] pager: remove pager_in_use()
From: Junio C Hamano @ 2023-06-28 20:58 UTC (permalink / raw)
  To: Glen Choo; +Cc: Calvin Wan, git, nasamuffin, jonathantanmy
In-Reply-To: <kl6lv8f8qvhd.fsf@chooglen-macbookpro.roam.corp.google.com>

Glen Choo <chooglen@google.com> writes:

> Having the function isn't just nice for typo prevention - it's also a
> reasonable boundary around the pager subsystem. We could imagine a
> world where we wanted to track the pager status using a static
> var instead of an env var (not that we'd even want that :P), and this
> inlining makes that harder.
>
> From the cover letter, it seems like we only need this to remove
> "#include pager.h" from date.c, and that's only used in
> parse_date_format(). Could we add a is_pager/pager_in_use to that
> function and push the pager.h dependency upwards?

Thanks---I think that may show a good direction.  parse_date_format()
reacts to "auto:foo" and as long as that feature needs to be there,
pager_in_use() must be available to the function.

^ permalink raw reply

* Re: cw/header-compat-util-shuffle (was Re: What's cooking in git.git (Jun 2023, #07; Tue, 27))
From: Junio C Hamano @ 2023-06-28 21:03 UTC (permalink / raw)
  To: Glen Choo; +Cc: git, Calvin Wan
In-Reply-To: <kl6lh6qrqtbj.fsf@chooglen-macbookpro.roam.corp.google.com>

Glen Choo <chooglen@google.com> writes:

> I notice that
>
>   https://lore.kernel.org/git/20230606170711.912972-1-calvinwan@google.com/
>
> is available as cw/header-compat-util-shuffle, but AFAICT that's not in
> this announcement or 'seen'.

Yes, I have found that it had too many bad interactions with topics
in flight and, keeping it out of 'seen' was very much deliberate.

Thanks.


^ permalink raw reply

* Re: [RFC PATCH 7/8] git-std-lib: introduce git standard library
From: Calvin Wan @ 2023-06-28 21:15 UTC (permalink / raw)
  To: phillip.wood; +Cc: git, nasamuffin, chooglen, Jonathan Tan
In-Reply-To: <d77f99df-f50a-39fa-a7c5-1bb958f196fb@gmail.com>

> On 27/06/2023 20:52, Calvin Wan wrote:
> > The Git Standard Library intends to serve as the foundational library
> > and root dependency that other libraries in Git will be built off of.
> > That is to say, suppose we have libraries X and Y; a user that wants to
> > use X and Y would need to include X, Y, and this Git Standard Library.
>
> I think having a library of commonly used functions and structures is a
> good idea. While I appreciate that we don't want to include everything
> I'm surprised to see it does not include things like "hashmap.c" and
> "string-list.c" that will be required by the config library as well as
> other code in "libgit.a". I don't think we want "libgitconfig.a" and
> "libgit.a" to both contain a copy of "hashmap.o" and "string-list.o"

I chose not to include hashmap and string-list in git-std-lib.a in the
first pass since they can exist as libraries built on top of
git-std-lib.a. There is no harm starting off with more libraries than
fewer besides having something like the config library be dependent on
lib-hashmap.a, lib-string-list.a, and git-std-lib.a rather than only
git-std-lib.a. They can always be added into git-std-lib.a in the
future. That being said, I do find it extremely unlikely that someone
would want to swap out the implementation for hashmap or string-list
so it is also very reasonable to include them into git-std-lib.a

>
> > diff --git a/Makefile b/Makefile
> > index e9ad9f9ef1..255bd10b82 100644
> > --- a/Makefile
> > +++ b/Makefile
> > @@ -2162,6 +2162,11 @@ ifdef FSMONITOR_OS_SETTINGS
> >       COMPAT_OBJS += compat/fsmonitor/fsm-path-utils-$(FSMONITOR_OS_SETTINGS).o
> >   endif
> >
> > +ifdef GIT_STD_LIB
> > +     BASIC_CFLAGS += -DGIT_STD_LIB
> > +     BASIC_CFLAGS += -DNO_GETTEXT
>
> I can see other projects may want to build git-std-lib without gettext
> support but if we're going to use git-std-lib within git it needs to be
> able to be built with that support. The same goes for the trace
> functions that you are redefining in usage.h

Taking a closer look at gettext.[ch], I believe I can also include it
into git-std-lib.a with a couple of minor changes. I'm currently
thinking about how the trace functions should interact with
git-std-lib.a since Victoria had similar comments on patch 1. I'll
reply to that thread when I come up with an answer.

>
> > diff --git a/git-compat-util.h b/git-compat-util.h
> > index 481dac22b0..75aa9b263e 100644
> > --- a/git-compat-util.h
> > +++ b/git-compat-util.h
> > @@ -396,8 +396,8 @@ static inline int noop_core_config(const char *var UNUSED,
> >   #define platform_core_config noop_core_config
> >   #endif
> >
> > +#if !defined(__MINGW32__) && !defined(_MSC_VER) && !defined(GIT_STD_LIB)
> >   int lstat_cache_aware_rmdir(const char *path);
> > -#if !defined(__MINGW32__) && !defined(_MSC_VER)
> >   #define rmdir lstat_cache_aware_rmdir
> >   #endif
>
> I'm not sure why the existing condition is being moved here

Ah I see that this changes behavior for callers of
lstat_cache_aware_rmdir if those conditions aren't satisfied. I
should've added an extra #if for GIT_STD_LIB instead of adding it to
the end of the current check and moving it. Thanks for spotting this.

^ permalink raw reply

* Re: [RFC PATCH 1/2] Add C TAP harness
From: Linus Arver @ 2023-06-28 21:17 UTC (permalink / raw)
  To: Phillip Wood, phillip.wood
  Cc: git, Ævar Arnfjörð Bjarmason, Josh Steadmon,
	Calvin Wan
In-Reply-To: <a6dff108-6946-71e7-99dc-ac3b70f7d7fa@gmail.com>


Phillip Wood <phillip.wood123@gmail.com> writes:
>> - Make the 'TEST' macro accept the test description first. Or, keep the
>>    'TEST' macro but also name a new macro 'IT' that accepts the
>>    description first, to encourage usage that reads in a
>>    behavior-driven-development (BDD) style, like 'IT("should accept foo",
>>    t_bar(...))'. I find some test descriptions easier to write this way.
>
> The test description is a printf style format string followed by 
> arguments. This allows parameterized tests to include the parameter 
> values in the description to aid debugging but it means the test 
> function must be the first parameter. We could have IT("should accept 
> %d", t(), i) but that would be a bit weird.

I see. I agree that

  IT("should accept %d", t(), i)

would look rather odd.

> I hope that we will end up with some guidelines for writing unit tests 
> that recommend testing the expect behavior and to avoid testing 
> implementation details. I'm don't think we necessarily need an IT() 
> macro to do that.

The IT() macro idea was only suggested as a stylistic choice. I also
agree that an IT() macro is not necessary to test the expected behavior.

>> - The 'check_int(result, ==, expected)' usage would be better without
>>    the commas, like 'check_int(result == expected)'. Is this possible?
>
> I don't particularly like the comma's either but in order to have decent 
> diagnostic messages we need to pass 'result' and 'expected' as separate 
> parameters so we can print them. We could have separate macros for 
> different comparisons check_int_eq(a, b), check_int_lt(a, b), ... I 
> don't have a strong preference either way.

I see. If it's a one-time cost to do this (enable comma-less usage) then I
strongly favor doing so. But I guess it's the kind of thing where we
could easily have a mechanical conversion from one style to the other in
the future, so it can certainly wait.

>> - It would be nice to report the test name for failing tests, so that
>>    this output can be parsed to check for the failing test name.
>
> I'm not quite sure what you mean here. The test name in printed in the 
> "not ok" lines for failing tests as shown in the output from your tests 
> below.

It would be nice if the failing output (in my example) also printed the
"t_same_token" function name.

>> There are other smaller changes I'd like (such as optional
>> colorization), but I think these bits can be sorted out much later.
>
> Sure
>
>> My patch can be found at
>> 
>>     git fetch https://github.com/listx/git trailer-unit-tests
>> 
>> and also inline at the bottom of this email message.
>> 
>> Build and test with
>> 
>>      make unit-tests && t/unit-tests/t-trailer
>> 
>> Sample output:
>> 
>>      $ make -j47 unit-tests && t/unit-tests/t-trailer
>>          CC t/unit-tests/t-trailer.o
>>          LINK t/unit-tests/t-trailer
>>      ok 1 - empty trailer_item and arg_item
>>      ok 2 - identical
>>      ok 3 - case should not matter
>>      ok 4 - arg_item is longer than trailer_item
>>      ok 5 - trailer_item is longer than arg_item
>>      ok 6 - no similarity
>>      ok 7 - accept WHERE_AFTER
>>      ok 8 - accept WHERE_END
>>      ok 9 - reject WHERE_END
>>      ok 10 - token with trailing punctuation (colon)
>>      ok 11 - token without trailing punctuation
>>      ok 12 - token with spaces with trailing punctuation (colon)
>>      ok 13 - token with spaces without trailing punctuation
>>      ok 14 - token with leading non-separator punctuation
>>      ok 15 - token with leading non-separator punctuation and space
>>      ok 16 - one-letter token
>>      ok 17 - token with punctuation in-between
>>      ok 18 - token with multiple leading punctuation chars
>>      # check "result == expected" failed at t/unit-tests/t-trailer.c:25
>>      #    left: 1
>>      #   right: 0
>>      not ok 19 - empty trailer_item
>
> This test fail because same_token() tests that the longer of its two 
> arguments starts with the shorter argument, not that the two arguments 
> are equal. As the shorter argument is empty in this case it returns 
> true, not false.

To me this behavior is surprising because this means that we could have,
for example,

    1. "" and "foo"
    2. "" and "bar"

and in both cases the empty string would be considered the "same token"
as "foo" and "bar". I think we should rename the function to something
other than same_token(), perhaps same_prefix_token() or similar.

>>      # check "result == expected" failed at t/unit-tests/t-trailer.c:25
>>      #    left: 1
>>      #   right: 0
>>      not ok 20 - empty arg_item
>>      1..20
>>
>> Note that some of the tests I expect to fail are passing.
>
> Which ones are those?

Oops. Disregard my comment here (it was from an earlier draft of the
email).

>> These may be
>> bugs but I am not sure because I don't fully understand the
>> interpret-trailers machinery yet.
>
> Thanks for sharing these tests, it is helpful to see examples of the 
> test framework being used. In this case the tests are checking static 
> functions from trailer.c. This mean that you have to export a number of 
> functions and structs that are really implementation details.

Indeed. I had a feeling that what I was doing was probably not the best
idea (exposing previously private internals of the trailer.c logic). I
agree with your analysis.

> Where 
> possible I think we want to concentrate our testing effort on the public 
> API as (a) that is what matters to callers and (b) it avoids having to 
> export internal functions and encourages people to test behavior rather 
> than implementation details.

Agreed. This is the "libification" work that needs to be done here so
that we can test the public API behaviors, not the internal functions. I
merely chose to sidestep that work in my example to keep the patch small.

> There may be some cases where there are 
> internal helpers that would benefit from tests. In those cases we can 
> either hide the export behind a #define so it is only visible to the 
> test code or just #include the file being tested in the test file (gross 
> but works).

My C is rusty so it would be great if such an example can be contributed
to the codebase in the future.

> An alternative to both of those is for the tests to live in 
> the implementation files and have them conditionally compiled (a bit 
> like rust).

Ditto.

> Thanks again for your comments, Best Wishes

You're welcome. Thank you for showing us what a minimal test framework
could look like.

Linus

> Phillip
>
>
>> Linus
>> 
>> ---- >8 ----
>>  From e5907b328d35eb37cfd1feb3a2431cb672beb4c8 Mon Sep 17 00:00:00 2001
>> From: Linus Arver <linusa@google.com>
>> Date: Thu, 15 Jun 2023 18:25:47 -0700
>> Subject: [PATCH] add some unit tests for interpret-trailers
>> 
>> Signed-off-by: Linus Arver <linusa@google.com>
>> ---
>>   Makefile                 |  5 +++
>>   t/unit-tests/t-trailer.c | 77 ++++++++++++++++++++++++++++++++++++++++
>>   trailer.c                | 33 ++---------------
>>   trailer.h                | 32 +++++++++++++++++
>>   4 files changed, 117 insertions(+), 30 deletions(-)
>>   create mode 100644 t/unit-tests/t-trailer.c
>> 
>> diff --git a/Makefile b/Makefile
>> index f0ca5bae4b..9d68518168 100644
>> --- a/Makefile
>> +++ b/Makefile
>> @@ -2668,6 +2668,7 @@ scalar-objs: $(SCALAR_OBJS)
>>   
>>   UNIT_TEST_PROGRAMS += t-basic
>>   UNIT_TEST_PROGRAMS += t-strbuf
>> +UNIT_TEST_PROGRAMS += t-trailer
>>   UNIT_TEST_PROGS = $(patsubst %,t/unit-tests/%$X,$(UNIT_TEST_PROGRAMS))
>>   UNIT_TEST_OBJS = $(patsubst %,t/unit-tests/%.o,$(UNIT_TEST_PROGRAMS))
>>   UNIT_TEST_OBJS += t/unit-tests/test-lib.o
>> @@ -3848,5 +3849,9 @@ t/unit-tests/t-strbuf$X: t/unit-tests/t-strbuf.o t/unit-tests/test-lib.o $(GITLI
>>   	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
>>   		 $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
>>   
>> +t/unit-tests/t-trailer$X: t/unit-tests/t-trailer.o t/unit-tests/test-lib.o builtin/interpret-trailers.o $(GITLIBS) GIT-LDFLAGS
>> +	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
>> +		 $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
>> +
>>   .PHONY: unit-tests
>>   unit-tests: $(UNIT_TEST_PROGS)
>> diff --git a/t/unit-tests/t-trailer.c b/t/unit-tests/t-trailer.c
>> new file mode 100644
>> index 0000000000..150b5606fa
>> --- /dev/null
>> +++ b/t/unit-tests/t-trailer.c
>> @@ -0,0 +1,77 @@
>> +#include "test-lib.h"
>> +#include "trailer.h"
>> +
>> +static void t_token_len_without_separator(const char *token, size_t expected)
>> +{
>> +	size_t result;
>> +	result = token_len_without_separator(token, strlen(token));
>> +	check_uint(result, ==, expected);
>> +}
>> +
>> +static void t_after_or_end(enum trailer_where where, int expected)
>> +{
>> +	size_t result;
>> +	result = after_or_end(where);
>> +	check_int(result, ==, expected);
>> +}
>> +
>> +
>> +static void t_same_token(char *a, char *b, int expected)
>> +{
>> +    struct trailer_item trailer_item = { .token = a };
>> +	struct arg_item arg_item = { .token = b };
>> +	size_t result;
>> +	result = same_token(&trailer_item, &arg_item);
>> +	check_int(result, ==, expected);
>> +}
>> +
>> +int cmd_main(int argc, const char **argv)
>> +{
>> +	TEST(t_same_token("", "", 1),
>> +		 "empty trailer_item and arg_item");
>> +	TEST(t_same_token("foo", "foo", 1),
>> +		 "identical");
>> +	TEST(t_same_token("foo", "FOO", 1),
>> +		 "case should not matter");
>> +	TEST(t_same_token("foo", "foobar", 1),
>> +		 "arg_item is longer than trailer_item");
>> +	TEST(t_same_token("foobar", "foo", 1),
>> +		 "trailer_item is longer than arg_item");
>> +	TEST(t_same_token("foo", "bar", 0),
>> +		 "no similarity");
>> +
>> +	TEST(t_after_or_end(WHERE_AFTER, 1), "accept WHERE_AFTER");
>> +	TEST(t_after_or_end(WHERE_END, 1), "accept WHERE_END");
>> +	TEST(t_after_or_end(WHERE_DEFAULT, 0), "reject WHERE_END");
>> +
>> +	TEST(t_token_len_without_separator("Signed-off-by:", 13),
>> +	     "token with trailing punctuation (colon)");
>> +	TEST(t_token_len_without_separator("Signed-off-by", 13),
>> +	     "token without trailing punctuation");
>> +	TEST(t_token_len_without_separator("Foo bar:", 7),
>> +	     "token with spaces with trailing punctuation (colon)");
>> +	TEST(t_token_len_without_separator("Foo bar", 7),
>> +	     "token with spaces without trailing punctuation");
>> +	TEST(t_token_len_without_separator("-Foo bar:", 8),
>> +	     "token with leading non-separator punctuation");
>> +	TEST(t_token_len_without_separator("- Foo bar:", 9),
>> +	     "token with leading non-separator punctuation and space");
>> +	TEST(t_token_len_without_separator("F:", 1),
>> +	     "one-letter token");
>> +	TEST(t_token_len_without_separator("abc%de#f@", 8),
>> +	     "token with punctuation in-between");
>> +	TEST(t_token_len_without_separator(":;*%_.#f@", 8),
>> +	     "token with multiple leading punctuation chars");
>> +
>> +	/*
>> +	 * These tests fail unexpectedly. They are probably bugs, although it may be
>> +	 * the case that these bugs never bubble up to the user because of other
>> +	 * checks we do elsewhere up the stack.
>> +	 */
>> +	TEST(t_same_token("", "foo", 0),
>> +		"empty trailer_item");
>> +	TEST(t_same_token("foo", "", 0),
>> +		"empty arg_item");
>> +
>> +	return test_done();
>> +}
>> diff --git a/trailer.c b/trailer.c
>> index a2c3ed6f28..9f59d8d7a6 100644
>> --- a/trailer.c
>> +++ b/trailer.c
>> @@ -13,35 +13,8 @@
>>    * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
>>    */
>>   
>> -struct conf_info {
>> -	char *name;
>> -	char *key;
>> -	char *command;
>> -	char *cmd;
>> -	enum trailer_where where;
>> -	enum trailer_if_exists if_exists;
>> -	enum trailer_if_missing if_missing;
>> -};
>> -
>>   static struct conf_info default_conf_info;
>>   
>> -struct trailer_item {
>> -	struct list_head list;
>> -	/*
>> -	 * If this is not a trailer line, the line is stored in value
>> -	 * (excluding the terminating newline) and token is NULL.
>> -	 */
>> -	char *token;
>> -	char *value;
>> -};
>> -
>> -struct arg_item {
>> -	struct list_head list;
>> -	char *token;
>> -	char *value;
>> -	struct conf_info conf;
>> -};
>> -
>>   static LIST_HEAD(conf_head);
>>   
>>   static char *separators = ":";
>> @@ -62,7 +35,7 @@ static const char *git_generated_prefixes[] = {
>>   		pos != (head); \
>>   		pos = is_reverse ? pos->prev : pos->next)
>>   
>> -static int after_or_end(enum trailer_where where)
>> +int after_or_end(enum trailer_where where)
>>   {
>>   	return (where == WHERE_AFTER) || (where == WHERE_END);
>>   }
>> @@ -73,14 +46,14 @@ static int after_or_end(enum trailer_where where)
>>    * 13, stripping the trailing punctuation but retaining
>>    * internal punctuation.
>>    */
>> -static size_t token_len_without_separator(const char *token, size_t len)
>> +size_t token_len_without_separator(const char *token, size_t len)
>>   {
>>   	while (len > 0 && !isalnum(token[len - 1]))
>>   		len--;
>>   	return len;
>>   }
>>   
>> -static int same_token(struct trailer_item *a, struct arg_item *b)
>> +int same_token(struct trailer_item *a, struct arg_item *b)
>>   {
>>   	size_t a_len, b_len, min_len;
>>   
>> diff --git a/trailer.h b/trailer.h
>> index 795d2fccfd..b2031eb305 100644
>> --- a/trailer.h
>> +++ b/trailer.h
>> @@ -146,4 +146,36 @@ int trailer_iterator_advance(struct trailer_iterator *iter);
>>    */
>>   void trailer_iterator_release(struct trailer_iterator *iter);
>>   
>> +int after_or_end(enum trailer_where where);
>> +size_t token_len_without_separator(const char *token, size_t len);
>> +
>> +struct conf_info {
>> +	char *name;
>> +	char *key;
>> +	char *command;
>> +	char *cmd;
>> +	enum trailer_where where;
>> +	enum trailer_if_exists if_exists;
>> +	enum trailer_if_missing if_missing;
>> +};
>> +
>> +struct trailer_item {
>> +	struct list_head list;
>> +	/*
>> +	 * If this is not a trailer line, the line is stored in value
>> +	 * (excluding the terminating newline) and token is NULL.
>> +	 */
>> +	char *token;
>> +	char *value;
>> +};
>> +
>> +struct arg_item {
>> +	struct list_head list;
>> +	char *token;
>> +	char *value;
>> +	struct conf_info conf;
>> +};
>> +
>> +int same_token(struct trailer_item *a, struct arg_item *b);
>> +
>>   #endif /* TRAILER_H */

^ permalink raw reply

* Re: cw/header-compat-util-shuffle (was Re: What's cooking in git.git (Jun 2023, #07; Tue, 27))
From: Glen Choo @ 2023-06-28 21:18 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Calvin Wan
In-Reply-To: <xmqqbkgzcnao.fsf@gitster.g>

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

> Glen Choo <chooglen@google.com> writes:
>
>> I notice that
>>
>>   https://lore.kernel.org/git/20230606170711.912972-1-calvinwan@google.com/
>>
>> is available as cw/header-compat-util-shuffle, but AFAICT that's not in
>> this announcement or 'seen'.
>
> Yes, I have found that it had too many bad interactions with topics
> in flight and, keeping it out of 'seen' was very much deliberate.

Thanks, that makes sense.

Perhaps it should still get a call out in What's Cooking? When Calvin
referenced that series, I went digging in What's Cooking for the topic
name, and when I couldn't find it, I manually listed the topic branches
starting with "cw/"; fortunately, Calvin's contribution history is short
enough to make that tenable ;)

^ permalink raw reply

* Re: [PATCH] t4205: correctly test %(describe:abbrev=...)
From: Junio C Hamano @ 2023-06-28 21:30 UTC (permalink / raw)
  To: Kousik Sanagavarapu; +Cc: git, Eli Schwartz, Christian Couder, Hariom Verma
In-Reply-To: <20230628181753.10384-1-five231003@gmail.com>

Kousik Sanagavarapu <five231003@gmail.com> writes:

> The pretty format %(describe:abbrev=<number>) tells describe to use only
> <number> characters of the oid to generate the human-readable format of
> the commit-ish.

Is that *only* correct?  I thought it was "at least <number> hexdigits"
to allow for future growth of the project.

> This is not apparent in the test for %(describe:abbrev=...) because we
> directly tag HEAD and use that, in which case the human-readable format
> is just the tag name. So, create a new commit and use that instead.

Nice.  How was this found, I have to wonder, and more importantly,
how would we have written this test in the first place to avoid
testing "the wrong thing", to learn from this experience?

>  test_expect_success '%(describe:abbrev=...) vs git describe --abbrev=...' '
> -	test_when_finished "git tag -d tagname" &&
> -	git tag -a -m tagged tagname &&
> +	test_commit --no-tag file &&
>  	git describe --abbrev=15 >expect &&
>  	git log -1 --format="%(describe:abbrev=15)" >actual &&
>  	test_cmp expect actual

The current test checks that the output in the case where the number
of commits since the tag is 0, and "describe --abbrev=15" and "log
--format='%(describe:abbrev=15)'" give exactly the same result.
Which is a good thing to test.

But we *also* want to test a more typical case where there are
commits between HEAD and the tag that is used to describe it.  

And we *also* want to make sure that the hexadecimal object name
suffix used in the description is at least 15 hexdigits long, if not
more.

The updated test drops test #1 (which is questionable), adds test #2
(which is good), and still omits test #3 (which is not so good).  

So, perhaps

    test_when_finished "git tag -d tagname" &&
-   git tag -a -m tagged tagname &&
    test_commit --no-tag file &&
    git describe --abbrev=15 >expect &&
    git log -1 --format="%(describe:abbrev=15)" >actual &&
    test_cmp expect actual &&
+   sed -e "s/^.*-g\([0-9a-f]*\)$/\1/" <actual >hexpart &&
+   test 16 -le $(wc -c <hexpart) &&
+
+   git tag -a -m tagged tagname &&
+   git describe --abbrev=15 >expect &&
+   git log -1 --format="%(describe:abbrev=15)" >actual &&
+   test_cmp expect actual &&
+   test tagname = $(cat actual)

or something along the line?  First we test with a commit that is
not tagged at all to have some commits between the tag and HEAD with
the original comparison (this is for #2), then we make sure the
length of the hexpart (new---this is for #3), and then we add the
tag to see the "exact" case also works (this is for #1).

Thanks.

^ permalink raw reply

* Re: [PATCH v5 00/11] config: remove global state from config iteration
From: Jonathan Tan @ 2023-06-28 22:23 UTC (permalink / raw)
  To: Glen Choo via GitGitGadget
  Cc: Jonathan Tan, git, Ævar Arnfjörð Bjarmason,
	Emily Shaffer, Phillip Wood, Jeff King, Glen Choo
In-Reply-To: <pull.1497.v5.git.git.1687980390.gitgitgadget@gmail.com>

"Glen Choo via GitGitGadget" <gitgitgadget@gmail.com> writes:
> As promised, this version addresses the comments on v3.
> 
> = Changes since v4
> 
> - Squash 6-7/12 since `test_must_fail` doesn't catch BUG()
> - Move a hunk to later in the series where it belongs
> - Replace a memcpy with `*a = *b`
> 
> = Changes since v3
> 
> - Rebase onto newer 'master'
> - Move the 'remove UNUSED from tr2_cfg_cb' hunk from 9/12 -> 8/12. It should
>   have been there all along; v3 8/12 didn't build at all.

Looks like all my comments have been addressed, so this patch set looks
good to me.

^ permalink raw reply

* Re: cw/header-compat-util-shuffle (was Re: What's cooking in git.git (Jun 2023, #07; Tue, 27))
From: Junio C Hamano @ 2023-06-28 22:31 UTC (permalink / raw)
  To: Glen Choo; +Cc: git, Calvin Wan
In-Reply-To: <kl6ledlvqo94.fsf@chooglen-macbookpro.roam.corp.google.com>

Glen Choo <chooglen@google.com> writes:

>> Yes, I have found that it had too many bad interactions with topics
>> in flight and, keeping it out of 'seen' was very much deliberate.
>
> Thanks, that makes sense.
>
> Perhaps it should still get a call out in What's Cooking? When Calvin
> referenced that series, I went digging in What's Cooking for the topic
> name, and when I couldn't find it, I manually listed the topic branches
> starting with "cw/"; fortunately, Calvin's contribution history is short
> enough to make that tenable ;)

Maybe.

As I only have limited bandwidth, I do not know how stale the posted
version got since other topics in flight also moved.  It may be that
other topics have graduated to 'master' and the conflicts got even
worse but it can be easier to resurrect on top of updated 'master'.
Or other topics that had interactions somehow magically stopped
being conflicting.  Or the series by itself may have remaining
issues pointed out during the review and is not ready to be picked
up.  I truly do not know.  Sorry.

One thing that many contributors are doing, which is very much
appreciated, is make sure that the series they are going to post
would work well with at least the tip of 'next' and also with
'seen', as well as 'master', before sending them out.  It would help
everybody: those who own topics that the series interacts with, the
maintainer, and those who may want to build on top of what the
series adds.

If the topic is too disruptive, however, they may have to synthesize
a good base (i.e. "merge on top of 'master' topics A, B and C, and
then queue these patches") but it may not be possible (e.g. among
the topics it interacts with, some topic may not yet be stable to
build on), in which case they must catch up quickly every time these
other topics it depends on are updated to avoid being left behind.

If the contributor cannot afford the time for quicker turnaround
that way, it would very much help everybody to announce that they
tentatively withdraw the topic until the other topics get more
stable, after which time they will send an updated version.  It
will help everybody, including those who want to build on top of
what the series adds.

Thanks.

^ permalink raw reply

* Re: cw/header-compat-util-shuffle (was Re: What's cooking in git.git (Jun 2023, #07; Tue, 27))
From: Junio C Hamano @ 2023-06-28 22:47 UTC (permalink / raw)
  To: Glen Choo; +Cc: git, Calvin Wan
In-Reply-To: <xmqqilb7b4mp.fsf@gitster.g>

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

> If the topic is too disruptive, however, they may have to synthesize
> a good base (i.e. "merge on top of 'master' topics A, B and C, and
> then queue these patches") but it may not be possible (e.g. among
> the topics it interacts with, some topic may not yet be stable to
> build on), in which case they must catch up quickly every time these
> other topics it depends on are updated to avoid being left behind.
>
> If the contributor cannot afford the time for quicker turnaround
> that way, it would very much help everybody to announce that they
> tentatively withdraw the topic until the other topics get more
> stable, after which time they will send an updated version.  It
> will help everybody, including those who want to build on top of
> what the series adds.

Oh, another thing.  Such a contributor who is blocked by topics by
others can help their own series by helping these other topics get
polished, i.e. with additional reviews, additional test reports,
etc.


^ permalink raw reply

* Re: [PATCH v5 00/11] config: remove global state from config iteration
From: Junio C Hamano @ 2023-06-28 22:47 UTC (permalink / raw)
  To: Jonathan Tan
  Cc: Glen Choo via GitGitGadget, git,
	Ævar Arnfjörð Bjarmason, Emily Shaffer,
	Phillip Wood, Jeff King, Glen Choo
In-Reply-To: <20230628222326.116535-1-jonathantanmy@google.com>

Jonathan Tan <jonathantanmy@google.com> writes:

> "Glen Choo via GitGitGadget" <gitgitgadget@gmail.com> writes:
>> As promised, this version addresses the comments on v3.
>> 
>> = Changes since v4
>> 
>> - Squash 6-7/12 since `test_must_fail` doesn't catch BUG()
>> - Move a hunk to later in the series where it belongs
>> - Replace a memcpy with `*a = *b`
>> 
>> = Changes since v3
>> 
>> - Rebase onto newer 'master'
>> - Move the 'remove UNUSED from tr2_cfg_cb' hunk from 9/12 -> 8/12. It should
>>   have been there all along; v3 8/12 didn't build at all.
>
> Looks like all my comments have been addressed, so this patch set looks
> good to me.

Thanks.  Queued.

^ permalink raw reply

* Re: What's cooking in git.git (Jun 2023, #07; Tue, 27)
From: Junio C Hamano @ 2023-06-28 23:58 UTC (permalink / raw)
  To: git
In-Reply-To: <xmqqcz1gftdn.fsf@gitster.g>

Summary of updates from today's integration cycle.

> * gc/config-context (2023-06-26) 12 commits
> ...
>  source: <pull.1497.v4.git.git.1687803083.gitgitgadget@gmail.com>

This is now in 'next', hopefully in 'master' in a week.

> * gc/config-partial-submodule-kvi-fix (2023-06-26) 1 commit
> ...
>  source: <pull.1535.git.git.1687801297404.gitgitgadget@gmail.com>

This is now in 'next', hopefully in 'master' in a week.

> * pw/apply-too-large (2023-06-26) 1 commit
> ...
>  source: <pull.1552.git.1687772253869.gitgitgadget@gmail.com>

Will merge to 'next'.

> * pw/diff-no-index-from-named-pipes (2023-06-27) 3 commits
> ...
>  source: <cover.1687874975.git.phillip.wood@dunelm.org.uk>

Expecting a reroll.

> * jc/doc-hash-object-types (2023-06-23) 1 commit
> ...
>  source: <pull.1533.v2.git.git.1687555504551.gitgitgadget@gmail.com>

Expecting a hopefully minor and final reroll.

> * rs/strbuf-expand-step (2023-06-18) 5 commits
> ...
>  source: <767baa64-20a6-daf2-d34b-d81f72363749@web.de>

Now in 'next'.

> * pb/complete-diff-options (2023-06-26) 24 commits
> ...
>  source: <pull.1543.v3.git.1687796688.gitgitgadget@gmail.com>

Now in 'next'.

> * cw/strbuf-cleanup (2023-06-12) 7 commits
> ...
>  source: <20230606194720.2053551-1-calvinwan@google.com>

Now in 'next'.

> * tl/notes-separator (2023-06-21) 7 commits
> ...
>  source: <cover.1685174011.git.dyroneteng@gmail.com>

Will merge to 'next'.

^ permalink raw reply

* Re: SHA256 support not experimental, or?
From: brian m. carlson @ 2023-06-29  1:59 UTC (permalink / raw)
  To: Adam Majer; +Cc: git
In-Reply-To: <2f5de416-04ba-c23d-1e0b-83bb655829a7@zombino.com>

[-- Attachment #1: Type: text/plain, Size: 923 bytes --]

On 2023-06-28 at 16:28:28, Adam Majer wrote:
> Hi all,
> 
> Is sha256 still considered experimental or can it be assumed to be stable?
> 
> The usecase here is we are planning on moving to sha256 repositories mostly
> due to integrity guarantees, hypothetical or otherwise. What is important is
> not the initial interop challenges with sha1 repos, but whether the on-disk
> format will remain compatible with future versions of git. At minimum, the
> on-disk format would be converted by some future version(s) of git into
> another one and not be an end-of-the-road because it was "experimental"
> where dataloss is an implied risk.

I have no intention of changing things at this point.  I think it should
be viewed as stable by now, and I'd support this patch, although to get
it picked up it will need a commit message and a sign-off.
-- 
brian m. carlson (he/him or they/them)
Toronto, Ontario, CA

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 263 bytes --]

^ permalink raw reply

* [PATCH v3] docs: add git hash-object -t option's possible values
From: John Cai via GitGitGadget @ 2023-06-29  2:07 UTC (permalink / raw)
  To: git; +Cc: brian m. carlson, Taylor Blau, John Cai, John Cai
In-Reply-To: <pull.1533.v2.git.git.1687555504551.gitgitgadget@gmail.com>

From: John Cai <johncai86@gmail.com>

The summary under the NAME section for git hash-object can mislead
readers to conclude that the command can only be used to create blobs,
whereas the description makes it clear that it can be used to create
objects, not just blobs. Let's clarify the one-line summary.

Further, the description for the option -t does not list out other types
that can be used when creating objects. Let's make this explicit by
listing out the different object types.

Signed-off-by: John Cai <johncai86@gmail.com>
---
    docs: add git-hash-object -t option's possible values
    
    For newer users of Git, the possible values of -t in git-hash-object may
    not be apparent. In fact the current verbiage under NAME could lead one
    to conclude that git-hash-object(1) can only be used to create blobs.
    
    Update the verbiage to make it clear the command can be used to write
    objects, not just blobs. Also add the possible values for -t.
    
    Changes since v2:
    
     * grammatical corrections in command documentation.
     * clarify -t text by specifying what "type" is.
    
    Changes since v1:
    
     * updated verbiage of commit message

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1533%2Fjohn-cai%2Fjc%2Fhash-object-documentation-update-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1533/john-cai/jc/hash-object-documentation-update-v3
Pull-Request: https://github.com/git/git/pull/1533

Range-diff vs v2:

 1:  2483375ecb8 ! 1:  9e0bec41285 docs: add git hash-object -t option's possible values
     @@ Metadata
       ## Commit message ##
          docs: add git hash-object -t option's possible values
      
     -    The verbiage under the NAME section for git hash-object could
     -    lead one to conclude that git hash-object can only be used to create
     -    blobs when in fact the description makes it clear that it can be used to
     -    create objects, not just blobs. Lets clarify this in the NAME text.
     +    The summary under the NAME section for git hash-object can mislead
     +    readers to conclude that the command can only be used to create blobs,
     +    whereas the description makes it clear that it can be used to create
     +    objects, not just blobs. Let's clarify the one-line summary.
      
          Further, the description for the option -t does not list out other types
     -    that can be used. Let's make this explicit by listing out the different
     -    object types.
     +    that can be used when creating objects. Let's make this explicit by
     +    listing out the different object types.
      
          Signed-off-by: John Cai <johncai86@gmail.com>
      
     @@ Documentation/git-hash-object.txt: git-hash-object(1)
       NAME
       ----
      -git-hash-object - Compute object ID and optionally creates a blob from a file
     -+git-hash-object - Compute object ID and optionally creates an object from a file
     ++git-hash-object - Compute object ID and optionally create an object from a file
       
       
       SYNOPSIS
     @@ Documentation/git-hash-object.txt: OPTIONS
       
       -t <type>::
      -	Specify the type (default: "blob").
     -+	Specify the type (default: "blob"). Possible values are `commit`,
     -+	`tree`, `blob`, and `tag`.
     ++	Specify the type of object to be created (default: "blob"). Possible
     ++	values are `commit`, `tree`, `blob`, and `tag`.
       
       -w::
       	Actually write the object into the object database.


 Documentation/git-hash-object.txt | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-hash-object.txt b/Documentation/git-hash-object.txt
index 472b5bb995b..8577f7a7d40 100644
--- a/Documentation/git-hash-object.txt
+++ b/Documentation/git-hash-object.txt
@@ -3,7 +3,7 @@ git-hash-object(1)
 
 NAME
 ----
-git-hash-object - Compute object ID and optionally creates a blob from a file
+git-hash-object - Compute object ID and optionally create an object from a file
 
 
 SYNOPSIS
@@ -25,7 +25,8 @@ OPTIONS
 -------
 
 -t <type>::
-	Specify the type (default: "blob").
+	Specify the type of object to be created (default: "blob"). Possible
+	values are `commit`, `tree`, `blob`, and `tag`.
 
 -w::
 	Actually write the object into the object database.

base-commit: 6640c2d06d112675426cf436f0594f0e8c614848
-- 
gitgitgadget

^ permalink raw reply related

* Re: [RFC PATCH 1/2] Add C TAP harness
From: Oswald Buddenhagen @ 2023-06-29  5:52 UTC (permalink / raw)
  To: phillip.wood
  Cc: Linus Arver, git, Ævar Arnfjörð Bjarmason,
	Josh Steadmon, Calvin Wan
In-Reply-To: <a6dff108-6946-71e7-99dc-ac3b70f7d7fa@gmail.com>

On Mon, Jun 26, 2023 at 02:15:39PM +0100, Phillip Wood wrote:
>On 21/06/2023 16:57, Linus Arver wrote:
>> - Make the 'TEST' macro accept the test description first. Or, keep 
>> the
>>    'TEST' macro but also name a new macro 'IT' that accepts the
>>    description first, to encourage usage that reads in a
>>    behavior-driven-development (BDD) style, like 'IT("should accept foo",
>>    t_bar(...))'. I find some test descriptions easier to write this way.
>
>The test description is a printf style format string followed by 
>arguments. This allows parameterized tests to include the parameter 
>values in the description to aid debugging but it means the test 
>function must be the first parameter. We could have IT("should accept 
>%d", t(), i) but that would be a bit weird.
>
with some minor preprocessor magic [1], you could make that

   IT(("should accept %d", i), t(i))

which would be somewhat more noisy, but arguably even somewhat clearer.
notably,

   IT("should accept foo", t())

would still work with the same macro.

[1] https://stackoverflow.com/a/62984543/3685191

somewhat on a tangent: it's also possible to overload macros on argument 
count [2], which may also come in handy.

[2] https://stackoverflow.com/a/24028231/3685191

regards,
ossi

^ permalink raw reply

* Re: SHA256 support not experimental, or?
From: Junio C Hamano @ 2023-06-29  5:59 UTC (permalink / raw)
  To: Adam Majer; +Cc: git
In-Reply-To: <2f5de416-04ba-c23d-1e0b-83bb655829a7@zombino.com>

Adam Majer <adamm@zombino.com> writes:

> Is sha256 still considered experimental or can it be assumed to be stable?

I do not think we would officially label SHA-256 support as "stable"
until we have good interoperability with SHA-1 repositories, but the
expectation is that we will make reasonable effort to keep migration
path for the current SHA-256 repositories, even if it turns out that
its on-disk format need to be updated, to keep the end-user data safe.

So while "no-longer-experimental" patch is probably a bit premature,
the warning in flashing red letters to caution against any use other
than testing may want to be toned down.

Thanks.


^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox