This RFC is preparation for config.[ch] to be libified as as part of the libification effort that Emily described in [1]. One of the first goals is to read config from a file, but the trouble with how config.c is written today is that all reading operations rely on global state, so before turning that into a library, we'd want to make that state non-global. This series gets us about halfway there; it does enough plumbing for a workable-but-kinda-ugly library interface, but with a little bit more work, I think we can get rid of global state in-tree as well. That requires a fair amount of work though, so I'd like to get thoughts on that before starting work. = Description This series extracts the global config reading state into "struct config_reader" and plumbs it through the config reading machinery. It's very similar to how we've plumbed "struct repository" and other 'context objects' in the past, except: * The global state (named "the_reader") for the git process lives in a config.c static variable, and not on "the_repository". See 3/6 for the rationale. * I've stopped short of adding "struct config_reader" to config.h public functions, since that would affect non-config.c callers. If we stop right here, it's quite easy to extend it to a future config-lib.h without having to adjust the config.h interface: * Move the core config reading functionality from config.c to config-lib.c. * Have config-lib.h accept "struct config_reader" as an arg. * Have config.h call config-lib.h while passing "the_reader". and I have some WIP patches that do just that [3], but I think they can be significantly improved if we go a bit further... = Leftover bits and RFC With a bit more work on the config machinery, we could make it so that config reading stops being global even without adjusting non-config.c callers. The idea is pretty simple: have the config machinery initialize an internal "struct config_reader" every time we read config and expose that state to the config callbacks (instead of, in this series, asking the caller to initialize "struct config_reader" themselves). I believe that only config callbacks are accessing this state, e.g. because they use the low-level information (like builtin/config.c printing the filename and scope of the value) or for error reporting (like git_parse_int() reporting the filename and line number of the value it failed to parse), and only config callbacks should be accessing this state anyway. The catch (aka the reason I stopped halfway through) is that I couldn't find a way to expose "struct config_reader" state without some fairly big changes, complexity-wise or LoC-wise, e.g. * We could add "struct config_reader" to "config_fn_t", i.e. -typedef int (*config_fn_t)(const char *var, const char *val, void *data); +typedef int (*config_fn_t)(const struct config_reader *reader, const char *var, const char *val, void *data); which isn't complex at all, except that there are ~100 config_fn_t implementations [3] and a good number of them may never reference "reader". If the churn is tolerable, I think this a good way forward. * We could create a new kind of "config_fn_t" that accepts "struct config_reader", e.g. typedef int (*config_fn_t)(const char *var, const char *val, void *data); +typedef int (*config_state_fn_t)(const struct config_reader *reader, const char *var, const char *val, void *data); and only adjust the callers that would actually reference "reader". This is less churn, but I couldn't find a great way to do this kind of 'switching between config callback types' elegantly. * We could smuggle "struct config_reader" to callback functions in a way that interested callers could see it, but uninterested callers could ignore. One trick that Jonathan Tan came up with (though not necessarily endorsed) would be to allocate a struct for the config value + "struct config_reader", then, interested callers could use "offset_of" to recover the "struct config_reader". It's a little hacky, but it's low-churn. = Questions * Is this worth merging without the extra work? There are some cleanups in this series that could make it valuable, but there are also some hacks (see 4/6) that aren't so great. * Is the extra work even worth it? * Do any of the ideas seem more promising than the others? Are there other ideas I'm missing? [1] https://lore.kernel.org/git/CAJoAoZ=Cig_kLocxKGax31sU7Xe4==BGzC__Bg2_pr7krNq6MA@xxxxxxxxxxxxxx [2] https://github.com/chooglen/git/compare/config/structify-reading...chooglen:git:config/read-without-globals [3] This is a rough estimate based on "git grep"-ing callers of the config.h functions. I vaguely recall callbacks being called "old-style", with the suggestion that we should replace them with the "new-style" constant time git_config_get_*() family of functions. That would decrease the number of config callbacks significantly. Glen Choo (6): config.c: plumb config_source through static fns config.c: don't assign to "cf" directly config.c: create config_reader and the_reader config.c: plumb the_reader through callbacks config.c: remove current_config_kvi config.c: remove current_parsing_scope config.c | 489 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 287 insertions(+), 202 deletions(-) base-commit: dadc8e6dacb629f46aee39bde90b6f09b73722eb Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1463%2Fchooglen%2Fconfig%2Fstructify-reading-v1 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1463/chooglen/config/structify-reading-v1 Pull-Request: https://github.com/git/git/pull/1463 -- gitgitgadget