git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v2 08/15] config: introduce an optional event stream while parsing

From
Johannes Schindelin <johannes.schindelin@gmx.de>
Date
Apr 3, 2018, 16:28 UTC
Message-ID
<a38025bcbc07b83b578ff2f1cde786bdca0c918c.1522772789.git.johannes.schindelin@gmx.de>
In-Reply-To
<cover.1522772789.git.johannes.schindelin@gmx.de>

This extends our config parser so that it can optionally produce an event stream via callback function, where it reports e.g. when a comment was parsed, or a section header, etc.

This parser will be used subsequently to handle the scenarios better where removing config entries would make sections empty, or where a new entry could be added to an already-existing, empty section.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 config.c | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
 config.h |  25 ++++++++++++++++
 2 files changed, 115 insertions(+), 12 deletions(-)
diff --git a/config.c b/config.c
index f10f8c6f52f..4cd745f6628 100644
--- a/config.c
+++ b/config.c
@@ -653,7 +653,46 @@ static int get_base_var(struct strbuf *name)
 	}
 }
 
-static int git_parse_source(config_fn_t fn, void *data)
+struct parse_event_data {
+	enum config_event_t previous_type;
+	size_t previous_offset;
+	const struct config_options *opts;
+};
+
+static inline int do_event(enum config_event_t type,
+			   struct parse_event_data *data)
+{
+	size_t offset;
+
+	if (!data->opts || !data->opts->event_fn)
+		return 0;
+
+	if (type == CONFIG_EVENT_WHITESPACE &&
+	    data->previous_type == type)
+		return 0;
+
+	offset = cf->do_ftell(cf);
+	/*
+	 * At EOF, the parser always "inserts" an extra '\n', therefore
+	 * the end offset of the event is the current file position, otherwise
+	 * we will already have advanced to the next event.
+	 */
+	if (type != CONFIG_EVENT_EOF)
+		offset--;
+
+	if (data->previous_type != CONFIG_EVENT_EOF &&
+	    data->opts->event_fn(data->previous_type, data->previous_offset,
+				 offset, data->opts->event_fn_data) < 0)
+		return -1;
+
+	data->previous_type = type;
+	data->previous_offset = offset;
+
+	return 0;
+}
+
+static int git_parse_source(config_fn_t fn, void *data,
+			    const struct config_options *opts)
 {
 	int comment = 0;
 	int baselen = 0;
@@ -664,8 +703,15 @@ static int git_parse_source(config_fn_t fn, void *data)
 	/* U+FEFF Byte Order Mark in UTF8 */
 	const char *bomptr = utf8_bom;
 
+	/* For the parser event callback */
+	struct parse_event_data event_data = {
+		CONFIG_EVENT_EOF, 0, opts
+	};
+
 	for (;;) {
-		int c = get_next_char();
+		int c;
+
+		c = get_next_char();
 		if (bomptr && *bomptr) {
 			/* We are at the file beginning; skip UTF8-encoded BOM
 			 * if present. Sane editors won't put this in on their
@@ -682,18 +728,33 @@ static int git_parse_source(config_fn_t fn, void *data)
 			}
 		}
 		if (c == '\n') {
-			if (cf->eof)
+			if (cf->eof) {
+				if (do_event(CONFIG_EVENT_EOF, &event_data) < 0)
+					return -1;
 				return 0;
+			}
+			if (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)
+				return -1;
 			comment = 0;
 			continue;
 		}
-		if (comment || isspace(c))
+		if (comment)
 			continue;
+		if (isspace(c)) {
+			if (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)
+					return -1;
+			continue;
+		}
 		if (c == '#' || c == ';') {
+			if (do_event(CONFIG_EVENT_COMMENT, &event_data) < 0)
+					return -1;
 			comment = 1;
 			continue;
 		}
 		if (c == '[') {
+			if (do_event(CONFIG_EVENT_SECTION, &event_data) < 0)
+					return -1;
+
 			/* Reset prior to determining a new stem */
 			strbuf_reset(var);
 			if (get_base_var(var) < 0 || var->len < 1)
@@ -704,6 +765,10 @@ static int git_parse_source(config_fn_t fn, void *data)
 		}
 		if (!isalpha(c))
 			break;
+
+		if (do_event(CONFIG_EVENT_ENTRY, &event_data) < 0)
+			return -1;
+
 		/*
 		 * Truncate the var name back to the section header
 		 * stem prior to grabbing the suffix part of the name
@@ -715,6 +780,9 @@ static int git_parse_source(config_fn_t fn, void *data)
 			break;
 	}
 
+	if (do_event(CONFIG_EVENT_ERROR, &event_data) < 0)
+		return -1;
+
 	switch (cf->origin_type) {
 	case CONFIG_ORIGIN_BLOB:
 		error_msg = xstrfmt(_("bad config line %d in blob %s"),
@@ -1398,7 +1466,8 @@ int git_default_config(const char *var, const char *value, void *dummy)
  * fgetc, ungetc, ftell of top need to be initialized before calling
  * this function.
  */
-static int do_config_from(struct config_source *top, config_fn_t fn, void *data)
+static int do_config_from(struct config_source *top, config_fn_t fn, void *data,
+			  const struct config_options *opts)
 {
 	int ret;
 
@@ -1410,7 +1479,7 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data)
 	strbuf_init(&top->var, 1024);
 	cf = top;
 
-	ret = git_parse_source(fn, data);
+	ret = git_parse_source(fn, data, opts);
 
 	/* pop config-file parsing state stack */
 	strbuf_release(&top->value);
@@ -1423,7 +1492,7 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data)
 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)
+		void *data, const struct config_options *opts)
 {
 	struct config_source top;
 
@@ -1436,15 +1505,18 @@ static int do_config_from_file(config_fn_t fn,
 	top.do_ungetc = config_file_ungetc;
 	top.do_ftell = config_file_ftell;
 
-	return do_config_from(&top, fn, data);
+	return do_config_from(&top, fn, data, opts);
 }
 
 static int git_config_from_stdin(config_fn_t fn, void *data)
 {
-	return do_config_from_file(fn, CONFIG_ORIGIN_STDIN, "", NULL, stdin, data);
+	return do_config_from_file(fn, CONFIG_ORIGIN_STDIN, "", NULL, stdin,
+				   data, NULL);
 }
 
-int git_config_from_file(config_fn_t fn, const char *filename, void *data)
+int git_config_from_file_with_options(config_fn_t fn, const char *filename,
+				      void *data,
+				      const struct config_options *opts)
 {
 	int ret = -1;
 	FILE *f;
@@ -1452,13 +1524,19 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data)
 	f = fopen_or_warn(filename, "r");
 	if (f) {
 		flockfile(f);
-		ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename, filename, f, data);
+		ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename,
+					  filename, f, data, opts);
 		funlockfile(f);
 		fclose(f);
 	}
 	return ret;
 }
 
+int git_config_from_file(config_fn_t fn, const char *filename, void *data)
+{
+	return git_config_from_file_with_options(fn, filename, data, NULL);
+}
+
 int git_config_from_mem(config_fn_t fn, const enum config_origin_type origin_type,
 			const char *name, const char *buf, size_t len, void *data)
 {
@@ -1475,7 +1553,7 @@ int git_config_from_mem(config_fn_t fn, const enum config_origin_type origin_typ
 	top.do_ungetc = config_buf_ungetc;
 	top.do_ftell = config_buf_ftell;
 
-	return do_config_from(&top, fn, data);
+	return do_config_from(&top, fn, data, NULL);
 }
 
 int git_config_from_blob_oid(config_fn_t fn,
diff --git a/config.h b/config.h
index ef70a9cac1e..5a2394daae2 100644
--- a/config.h
+++ b/config.h
@@ -28,15 +28,40 @@ enum config_origin_type {
 	CONFIG_ORIGIN_CMDLINE
 };
 
+enum config_event_t {
+	CONFIG_EVENT_SECTION,
+	CONFIG_EVENT_ENTRY,
+	CONFIG_EVENT_WHITESPACE,
+	CONFIG_EVENT_COMMENT,
+	CONFIG_EVENT_EOF,
+	CONFIG_EVENT_ERROR
+};
+
+/*
+ * The parser event function (if not NULL) is called with the event type and
+ * the begin/end offsets of the parsed elements.
+ *
+ * Note: for CONFIG_EVENT_ENTRY (i.e. config variables), the trailing newline
+ * character is considered part of the element.
+ */
+typedef int (*config_parser_event_fn_t)(enum config_event_t type,
+					size_t begin_offset, size_t end_offset,
+					void *event_fn_data);
+
 struct config_options {
 	unsigned int respect_includes : 1;
 	const char *commondir;
 	const char *git_dir;
+	config_parser_event_fn_t event_fn;
+	void *event_fn_data;
 };
 
 typedef int (*config_fn_t)(const char *, const char *, void *);
 extern int git_default_config(const char *, const char *, void *);
 extern int git_config_from_file(config_fn_t fn, const char *, void *);
+extern int git_config_from_file_with_options(config_fn_t fn, const char *,
+					     void *,
+					     const struct config_options *);
 extern int git_config_from_mem(config_fn_t fn, const enum config_origin_type,
 					const char *name, const char *buf, size_t len, void *data);
 extern int git_config_from_blob_oid(config_fn_t fn, const char *name,
-- 
2.16.2.windows.1.26.g2cc3565eb4b
Previous: Johannes SchindelinNext: Jeff King
Message 70 of 103 in “Assorted fixes for `git config` (including the "empty sections" bug)”
  1. 0/9 Assorted fixes for `git config` (including the "empty sections" bug)Johannes Schindelin, Mar 29, 2018
  2. 1/9 git_config_set: fix off-by-twoJohannes Schindelin, Mar 29, 2018
  3. Stefan BellerMar 29, 2018
  4. Jeff KingMar 29, 2018
  5. Johannes SchindelinMar 30, 2018
  6. Ævar Arnfjörð BjarmasonMar 30, 2018
  7. Junio C HamanoMar 30, 2018
  8. Johannes SchindelinMar 30, 2018
  9. Junio C HamanoMar 30, 2018
  10. Johannes SchindelinApr 3, 2018
  11. Duy NguyenApr 3, 2018
  12. Johannes SchindelinApr 3, 2018
  13. Junio C HamanoApr 8, 2018
  14. Duy NguyenMar 30, 2018
  15. Johannes SchindelinMar 30, 2018
  16. Duy NguyenMar 30, 2018
  17. A potential approach to making tests faster on WindowsÆvar Arnfjörð Bjarmason, Mar 30, 2018
  18. Junio C HamanoMar 30, 2018
  19. Jeff KingMar 30, 2018
  20. Johannes SchindelinApr 3, 2018
  21. Ævar Arnfjörð BjarmasonApr 3, 2018
  22. Johannes SchindelinApr 3, 2018
  23. Eric SunshineApr 3, 2018
  24. Johannes SchindelinApr 3, 2018
  25. Jeff KingApr 3, 2018
  26. Johannes SchindelinApr 3, 2018
  27. Jeff KingApr 6, 2018
  28. Stefan BellerApr 6, 2018
  29. 2/9 t1300: rename it to reflect that `repo-config` was deprecatedJohannes Schindelin, Mar 29, 2018
  30. Jeff KingMar 29, 2018
  31. Johannes SchindelinMar 30, 2018
  32. 3/9 t1300: avoid relying on a bugJohannes Schindelin, Mar 29, 2018
  33. Jeff KingMar 29, 2018
  34. Johannes SchindelinMar 30, 2018
  35. 5/9 t1300: `--unset-all` can leave an empty section behind (bug)Johannes Schindelin, Mar 29, 2018
  36. Jeff KingMar 29, 2018
  37. 4/9 t1300: remove unreasonable expectation from TODOJohannes Schindelin, Mar 29, 2018
  38. Jeff KingMar 29, 2018
  39. Junio C HamanoMar 29, 2018
  40. Johannes SchindelinMar 30, 2018
  41. 6/9 git_config_set: simplify the way the section name is rememberedJohannes Schindelin, Mar 29, 2018
  42. 7/9 git config --unset: remove empty sections (in normal situations)Johannes Schindelin, Mar 29, 2018
  43. Jeff KingMar 29, 2018
  44. Johannes SchindelinMar 30, 2018
  45. Jeff KingMar 30, 2018
  46. 8/9 git_config_set: use do_config_from_file() directlyJohannes Schindelin, Mar 29, 2018
  47. Jeff KingMar 29, 2018
  48. Johannes SchindelinMar 30, 2018
  49. Jeff KingMar 30, 2018
  50. Johannes SchindelinMar 30, 2018
  51. Jeff KingMar 30, 2018
  52. Johannes SchindelinMar 30, 2018
  53. 9/9 git_config_set: reuse empty sectionsJohannes Schindelin, Mar 29, 2018
  54. Jeff KingMar 29, 2018
  55. Johannes SchindelinMar 30, 2018
  56. Stefan BellerMar 29, 2018
  57. Johannes SchindelinMar 30, 2018
  58. Jeff KingMar 29, 2018
  59. Johannes SchindelinMar 30, 2018
  60. Ævar Arnfjörð BjarmasonMar 30, 2018
  61. Johannes SchindelinMar 30, 2018
  62. 00/15 Assorted fixes for `git config` (including the "empty sections" bug)Johannes Schindelin, Apr 3, 2018
  63. 01/15 git_config_set: fix off-by-twoJohannes Schindelin, Apr 3, 2018
  64. 02/15 t1300: rename it to reflect that `repo-config` was deprecatedJohannes Schindelin, Apr 3, 2018
  65. 03/15 t1300: demonstrate that --replace-all can "invent" newlinesJohannes Schindelin, Apr 3, 2018
  66. 04/15 config --replace-all: avoid extra line breaksJohannes Schindelin, Apr 3, 2018
  67. 05/15 t1300: avoid relying on a bugJohannes Schindelin, Apr 3, 2018
  68. 06/15 t1300: remove unreasonable expectation from TODOJohannes Schindelin, Apr 3, 2018
  69. 07/15 t1300: `--unset-all` can leave an empty section behind (bug)Johannes Schindelin, Apr 3, 2018
  70. 08/15 config: introduce an optional event stream while parsingJohannes Schindelin, Apr 3, 2018
  71. Jeff KingApr 6, 2018
  72. Johannes SchindelinApr 9, 2018
  73. 09/15 config: avoid using the global variable `store`Johannes Schindelin, Apr 3, 2018
  74. Jeff KingApr 6, 2018
  75. Johannes SchindelinApr 9, 2018
  76. 10/15 config_set_store: rename some fields for consistencyJohannes Schindelin, Apr 3, 2018
  77. 11/15 git_config_set: do not use a state machineJohannes Schindelin, Apr 3, 2018
  78. Jeff KingApr 6, 2018
  79. Johannes SchindelinApr 9, 2018
  80. 12/15 git_config_set: make use of the config parser's event streamJohannes Schindelin, Apr 3, 2018
  81. 13/15 git config --unset: remove empty sections (in the common case)Johannes Schindelin, Apr 3, 2018
  82. 14/15 git_config_set: reuse empty sectionsJohannes Schindelin, Apr 3, 2018
  83. Johannes SchindelinApr 3, 2018
  84. Jeff KingApr 6, 2018
  85. Johannes SchindelinApr 9, 2018
  86. 00/15 Assorted fixes for `git config` (including the "empty sections" bug)Johannes Schindelin, Apr 9, 2018
  87. 01/15 git_config_set: fix off-by-twoJohannes Schindelin, Apr 9, 2018
  88. 03/15 t1300: demonstrate that --replace-all can "invent" newlinesJohannes Schindelin, Apr 9, 2018
  89. 04/15 config --replace-all: avoid extra line breaksJohannes Schindelin, Apr 9, 2018
  90. 02/15 t1300: rename it to reflect that `repo-config` was deprecatedJohannes Schindelin, Apr 9, 2018
  91. 05/15 t1300: avoid relying on a bugJohannes Schindelin, Apr 9, 2018
  92. 06/15 t1300: remove unreasonable expectation from TODOJohannes Schindelin, Apr 9, 2018
  93. 07/15 t1300: add a few more hairy examples of sections becoming emptyJohannes Schindelin, Apr 9, 2018
  94. 08/15 t1300: `--unset-all` can leave an empty section behind (bug)Johannes Schindelin, Apr 9, 2018
  95. 09/15 config: introduce an optional event stream while parsingJohannes Schindelin, Apr 9, 2018
  96. 10/15 config: avoid using the global variable `store`Johannes Schindelin, Apr 9, 2018
  97. 11/15 config_set_store: rename some fields for consistencyJohannes Schindelin, Apr 9, 2018
  98. 12/15 git_config_set: do not use a state machineJohannes Schindelin, Apr 9, 2018
  99. 13/15 git_config_set: make use of the config parser's event streamJohannes Schindelin, Apr 9, 2018
  100. Jeff KingMay 8, 2018
  101. Jeff KingMay 8, 2018
  102. 14/15 git config --unset: remove empty sections (in the common case)Johannes Schindelin, Apr 9, 2018
  103. 15/15 git_config_set: reuse empty sectionsJohannes Schindelin, Apr 9, 2018

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.