{"thread":{"id":"63881","subject":"[PATCH 0/5] string_list_split*() updates","startedAt":"2025-07-31T06:39:52Z","lastAt":"2025-08-04T06:24:17Z","messageCount":72,"participants":["Junio C Hamano","Eric Sunshine","shejialuo","Patrick Steinhardt","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":5},"messages":[{"id":"523046","messageId":"20250731063949.1601669-1-gitster@pobox.com","threadId":"63881","inReplyTo":null,"subject":"[PATCH 0/5] string_list_split*() updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-31T06:39:44Z","receivedAt":"2025-07-31T06:39:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Two related string-list API functions, string_list_split() and\nstring_list_split_in_place(), more or less duplicates their\nimplementations.  They both take a single string, and split the\nstring at the delimiter and stuff the result into a string list.\n\nHowever, there is one subtle and unnecessary difference.  The non\n\"in-place\" variant only allows a single byte value as delimiter,\nwhile the \"in-place\" variant can take multiple delimiters (e.g.,\n\"split at either a comma or a space\").\n\nThis series first updates the string_list_split() to allow multiple\ndelimiters like string_list_split_in_place() does, unifies their\nimplementations into one.  This refactoring allows us to give new\nfeature to these two functions with a single chnage.\n\nThen these functions learn to optionally trim the split string\npieces before placing them in the resulting string list.\n\nAn existing caller of string_list_split() in diff.c trims the\nelements in the resulting string list before it uses them, which is\nsimplified by taking advantage of this new feature.\n\nJunio C Hamano (5):\n  string-list: report programming error with BUG\n  string-list: align string_list_split() with its _in_place()\n    counterpart\n  string-list: unify string_list_split* functions\n  string-list: optionally trim string pieces split by\n    string_list_split()\n  diff: simplify parsing of diff.colormovedws\n\n builtin/blame.c              |   2 +-\n builtin/merge.c              |   2 +-\n builtin/var.c                |   2 +-\n connect.c                    |   2 +-\n diff.c                       |  20 +++----\n fetch-pack.c                 |   2 +-\n notes.c                      |   2 +-\n parse-options.c              |   2 +-\n pathspec.c                   |   2 +-\n protocol.c                   |   2 +-\n ref-filter.c                 |   4 +-\n setup.c                      |   3 +-\n string-list.c                | 113 +++++++++++++++++++++++------------\n string-list.h                |  26 +++++---\n t/helper/test-path-utils.c   |   3 +-\n t/helper/test-ref-store.c    |   2 +-\n t/unit-tests/u-string-list.c |  80 ++++++++++++++++++++++---\n transport.c                  |   2 +-\n upload-pack.c                |   2 +-\n 19 files changed, 190 insertions(+), 83 deletions(-)\n\n-- \n2.50.1-612-g4756c59422\n\n"},{"id":"523047","messageId":"20250731063949.1601669-2-gitster@pobox.com","threadId":"63881","inReplyTo":"20250731063949.1601669-1-gitster@pobox.com","subject":"[PATCH 1/5] string-list: report programming error with BUG","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-31T06:39:45Z","receivedAt":"2025-07-31T06:39:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Passing a string list that has .strdup_strings bit unset to\nstring_list_split(), orone that has .strdup_strings bit set to\nstring_list_split_in_place(), is a programmer error.  Do not use\ndie() to abort the execution.  Use BUG() instead.\n\nAs a developer-facing message, the message string itself should\nbe a lot more concise, but let's keep the original one for now.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n string-list.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/string-list.c b/string-list.c\nindex 53faaa8420..0cb920e9b0 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -283,7 +283,7 @@ int string_list_split(struct string_list *list, const char *string,\n \tconst char *p = string, *end;\n \n \tif (!list->strdup_strings)\n-\t\tdie(\"internal error in string_list_split(): \"\n+\t\tBUG(\"internal error in string_list_split(): \"\n \t\t    \"list->strdup_strings must be set\");\n \tfor (;;) {\n \t\tcount++;\n@@ -309,7 +309,7 @@ int string_list_split_in_place(struct string_list *list, char *string,\n \tchar *p = string, *end;\n \n \tif (list->strdup_strings)\n-\t\tdie(\"internal error in string_list_split_in_place(): \"\n+\t\tBUG(\"internal error in string_list_split_in_place(): \"\n \t\t    \"list->strdup_strings must not be set\");\n \tfor (;;) {\n \t\tcount++;\n-- \n2.50.1-612-g4756c59422\n\n"},{"id":"523048","messageId":"20250731063949.1601669-3-gitster@pobox.com","threadId":"63881","inReplyTo":"20250731063949.1601669-1-gitster@pobox.com","subject":"[PATCH 2/5] string-list: align string_list_split() with its _in_place() counterpart","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-31T06:39:46Z","receivedAt":"2025-07-31T06:39:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"For some unknown reason, unlike string_list_split_in_place(),\nstring_list_split() took only a single character as a field\ndelimiter.  Before giving both functions more features in future\ncommits, allow stirng_list_split() to take more than one delimiter\ncharacters to make them closer to each other.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/blame.c              |  2 +-\n builtin/merge.c              |  2 +-\n builtin/var.c                |  2 +-\n connect.c                    |  2 +-\n diff.c                       |  2 +-\n fetch-pack.c                 |  2 +-\n notes.c                      |  2 +-\n parse-options.c              |  2 +-\n pathspec.c                   |  2 +-\n protocol.c                   |  2 +-\n ref-filter.c                 |  4 ++--\n setup.c                      |  3 ++-\n string-list.c                |  4 ++--\n string-list.h                | 16 ++++++++--------\n t/helper/test-path-utils.c   |  3 ++-\n t/helper/test-ref-store.c    |  2 +-\n t/unit-tests/u-string-list.c | 16 ++++++++--------\n transport.c                  |  2 +-\n upload-pack.c                |  2 +-\n 19 files changed, 37 insertions(+), 35 deletions(-)\n\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 91586e6852..70a6460401 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -420,7 +420,7 @@ static void parse_color_fields(const char *s)\n \tcolorfield_nr = 0;\n \n \t/* Ideally this would be stripped and split at the same time? */\n-\tstring_list_split(&l, s, ',', -1);\n+\tstring_list_split(&l, s, \",\", -1);\n \tALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc);\n \n \tfor_each_string_list_item(item, &l) {\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 18b22c0a26..893f8950bf 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -875,7 +875,7 @@ static void add_strategies(const char *string, unsigned attr)\n \tif (string) {\n \t\tstruct string_list list = STRING_LIST_INIT_DUP;\n \t\tstruct string_list_item *item;\n-\t\tstring_list_split(&list, string, ' ', -1);\n+\t\tstring_list_split(&list, string, \" \", -1);\n \t\tfor_each_string_list_item(item, &list)\n \t\t\tappend_strategy(get_strategy(item->string));\n \t\tstring_list_clear(&list, 0);\ndiff --git a/builtin/var.c b/builtin/var.c\nindex ada642a9fe..4ae7af0eff 100644\n--- a/builtin/var.c\n+++ b/builtin/var.c\n@@ -181,7 +181,7 @@ static void list_vars(void)\n \t\t\tif (ptr->multivalued && *val) {\n \t\t\t\tstruct string_list list = STRING_LIST_INIT_DUP;\n \n-\t\t\t\tstring_list_split(&list, val, '\\n', -1);\n+\t\t\t\tstring_list_split(&list, val, \"\\n\", -1);\n \t\t\t\tfor (size_t i = 0; i < list.nr; i++)\n \t\t\t\t\tprintf(\"%s=%s\\n\", ptr->name, list.items[i].string);\n \t\t\t\tstring_list_clear(&list, 0);\ndiff --git a/connect.c b/connect.c\nindex e77287f426..867b12bde5 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -407,7 +407,7 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list,\n \t * name.  Subsequent fields (symref-target and peeled) are optional and\n \t * don't have a particular order.\n \t */\n-\tif (string_list_split(&line_sections, line, ' ', -1) < 2) {\n+\tif (string_list_split(&line_sections, line, \" \", -1) < 2) {\n \t\tret = 0;\n \t\tgoto out;\n \t}\ndiff --git a/diff.c b/diff.c\nindex dca87e164f..a81949a422 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -327,7 +327,7 @@ static unsigned parse_color_moved_ws(const char *arg)\n \tstruct string_list l = STRING_LIST_INIT_DUP;\n \tstruct string_list_item *i;\n \n-\tstring_list_split(&l, arg, ',', -1);\n+\tstring_list_split(&l, arg, \",\", -1);\n \n \tfor_each_string_list_item(i, &l) {\n \t\tstruct strbuf sb = STRBUF_INIT;\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex c1be9b76eb..9866270696 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1914,7 +1914,7 @@ static void fetch_pack_config(void)\n \t\tchar *str;\n \n \t\tif (!git_config_get_string(\"fetch.uriprotocols\", &str) && str) {\n-\t\t\tstring_list_split(&uri_protocols, str, ',', -1);\n+\t\t\tstring_list_split(&uri_protocols, str, \",\", -1);\n \t\t\tfree(str);\n \t\t}\n \t}\ndiff --git a/notes.c b/notes.c\nindex 97b995f3f2..6afcf088b9 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -892,7 +892,7 @@ static int string_list_add_note_lines(struct string_list *list,\n \t * later, along with any empty strings that came from empty\n \t * lines within the file.\n \t */\n-\tstring_list_split(list, data, '\\n', -1);\n+\tstring_list_split(list, data, \"\\n\", -1);\n \tfree(data);\n \treturn 0;\n }\ndiff --git a/parse-options.c b/parse-options.c\nindex 5224203ffe..9e7cb75192 100644\n--- a/parse-options.c\n+++ b/parse-options.c\n@@ -1338,7 +1338,7 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t\n \t\tif (!saw_empty_line && !*str)\n \t\t\tsaw_empty_line = 1;\n \n-\t\tstring_list_split(&list, str, '\\n', -1);\n+\t\tstring_list_split(&list, str, \"\\n\", -1);\n \t\tfor (j = 0; j < list.nr; j++) {\n \t\t\tconst char *line = list.items[j].string;\n \ndiff --git a/pathspec.c b/pathspec.c\nindex a3ddd701c7..de325f7ef9 100644\n--- a/pathspec.c\n+++ b/pathspec.c\n@@ -201,7 +201,7 @@ static void parse_pathspec_attr_match(struct pathspec_item *item, const char *va\n \tif (!value || !*value)\n \t\tdie(_(\"attr spec must not be empty\"));\n \n-\tstring_list_split(&list, value, ' ', -1);\n+\tstring_list_split(&list, value, \" \", -1);\n \tstring_list_remove_empty_items(&list, 0);\n \n \titem->attr_check = attr_check_alloc();\ndiff --git a/protocol.c b/protocol.c\nindex bae7226ff4..54b9f49c01 100644\n--- a/protocol.c\n+++ b/protocol.c\n@@ -61,7 +61,7 @@ enum protocol_version determine_protocol_version_server(void)\n \tif (git_protocol) {\n \t\tstruct string_list list = STRING_LIST_INIT_DUP;\n \t\tconst struct string_list_item *item;\n-\t\tstring_list_split(&list, git_protocol, ':', -1);\n+\t\tstring_list_split(&list, git_protocol, \":\", -1);\n \n \t\tfor_each_string_list_item(item, &list) {\n \t\t\tconst char *value;\ndiff --git a/ref-filter.c b/ref-filter.c\nindex f9f2c512a8..4edfb9c83b 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -435,7 +435,7 @@ static int remote_ref_atom_parser(struct ref_format *format UNUSED,\n \t}\n \n \tatom->u.remote_ref.nobracket = 0;\n-\tstring_list_split(&params, arg, ',', -1);\n+\tstring_list_split(&params, arg, \",\", -1);\n \n \tfor (i = 0; i < params.nr; i++) {\n \t\tconst char *s = params.items[i].string;\n@@ -831,7 +831,7 @@ static int align_atom_parser(struct ref_format *format UNUSED,\n \n \talign->position = ALIGN_LEFT;\n \n-\tstring_list_split(&params, arg, ',', -1);\n+\tstring_list_split(&params, arg, \",\", -1);\n \tfor (i = 0; i < params.nr; i++) {\n \t\tconst char *s = params.items[i].string;\n \t\tint position;\ndiff --git a/setup.c b/setup.c\nindex 6f52dab64c..b9f5eb8b51 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1460,8 +1460,9 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n \n \tif (env_ceiling_dirs) {\n \t\tint empty_entry_found = 0;\n+\t\tstatic const char path_sep[] = { PATH_SEP, '\\0' };\n \n-\t\tstring_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);\n+\t\tstring_list_split(&ceiling_dirs, env_ceiling_dirs, path_sep, -1);\n \t\tfilter_string_list(&ceiling_dirs, 0,\n \t\t\t\t   canonicalize_ceiling_entry, &empty_entry_found);\n \t\tceil_offset = longest_ancestor_length(dir->buf, &ceiling_dirs);\ndiff --git a/string-list.c b/string-list.c\nindex 0cb920e9b0..2284a009cb 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -277,7 +277,7 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_\n }\n \n int string_list_split(struct string_list *list, const char *string,\n-\t\t      int delim, int maxsplit)\n+\t\t      const char *delim, int maxsplit)\n {\n \tint count = 0;\n \tconst char *p = string, *end;\n@@ -291,7 +291,7 @@ int string_list_split(struct string_list *list, const char *string,\n \t\t\tstring_list_append(list, p);\n \t\t\treturn count;\n \t\t}\n-\t\tend = strchr(p, delim);\n+\t\tend = strpbrk(p, delim);\n \t\tif (end) {\n \t\t\tstring_list_append_nodup(list, xmemdupz(p, end - p));\n \t\t\tp = end + 1;\ndiff --git a/string-list.h b/string-list.h\nindex 122b318641..6c8650efde 100644\n--- a/string-list.h\n+++ b/string-list.h\n@@ -254,7 +254,7 @@ struct string_list_item *unsorted_string_list_lookup(struct string_list *list,\n void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util);\n \n /**\n- * Split string into substrings on character `delim` and append the\n+ * Split string into substrings on characters in `delim` and append the\n  * substrings to `list`.  The input string is not modified.\n  * list->strdup_strings must be set, as new memory needs to be\n  * allocated to hold the substrings.  If maxsplit is non-negative,\n@@ -262,15 +262,15 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_\n  * appended to list.\n  *\n  * Examples:\n- *   string_list_split(l, \"foo:bar:baz\", ':', -1) -> [\"foo\", \"bar\", \"baz\"]\n- *   string_list_split(l, \"foo:bar:baz\", ':', 0) -> [\"foo:bar:baz\"]\n- *   string_list_split(l, \"foo:bar:baz\", ':', 1) -> [\"foo\", \"bar:baz\"]\n- *   string_list_split(l, \"foo:bar:\", ':', -1) -> [\"foo\", \"bar\", \"\"]\n- *   string_list_split(l, \"\", ':', -1) -> [\"\"]\n- *   string_list_split(l, \":\", ':', -1) -> [\"\", \"\"]\n+ *   string_list_split(l, \"foo:bar:baz\", \":\", -1) -> [\"foo\", \"bar\", \"baz\"]\n+ *   string_list_split(l, \"foo:bar:baz\", \":\", 0) -> [\"foo:bar:baz\"]\n+ *   string_list_split(l, \"foo:bar:baz\", \":\", 1) -> [\"foo\", \"bar:baz\"]\n+ *   string_list_split(l, \"foo:bar:\", \":\", -1) -> [\"foo\", \"bar\", \"\"]\n+ *   string_list_split(l, \"\", \":\", -1) -> [\"\"]\n+ *   string_list_split(l, \":\", \":\", -1) -> [\"\", \"\"]\n  */\n int string_list_split(struct string_list *list, const char *string,\n-\t\t      int delim, int maxsplit);\n+\t\t      const char *delim, int maxsplit);\n \n /*\n  * Like string_list_split(), except that string is split in-place: the\ndiff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c\nindex 086238c826..f5f33751da 100644\n--- a/t/helper/test-path-utils.c\n+++ b/t/helper/test-path-utils.c\n@@ -348,6 +348,7 @@ int cmd__path_utils(int argc, const char **argv)\n \tif (argc == 4 && !strcmp(argv[1], \"longest_ancestor_length\")) {\n \t\tint len;\n \t\tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n+\t\tconst char path_sep[] = { PATH_SEP, '\\0' };\n \t\tchar *path = xstrdup(argv[2]);\n \n \t\t/*\n@@ -362,7 +363,7 @@ int cmd__path_utils(int argc, const char **argv)\n \t\t */\n \t\tif (normalize_path_copy(path, path))\n \t\t\tdie(\"Path \\\"%s\\\" could not be normalized\", argv[2]);\n-\t\tstring_list_split(&ceiling_dirs, argv[3], PATH_SEP, -1);\n+\t\tstring_list_split(&ceiling_dirs, argv[3], path_sep, -1);\n \t\tfilter_string_list(&ceiling_dirs, 0,\n \t\t\t\t   normalize_ceiling_entry, NULL);\n \t\tlen = longest_ancestor_length(path, &ceiling_dirs);\ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex 8d9a271845..aa1cb9b4ac 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -29,7 +29,7 @@ static unsigned int parse_flags(const char *str, struct flag_definition *defs)\n \tif (!strcmp(str, \"0\"))\n \t\treturn 0;\n \n-\tstring_list_split(&masks, str, ',', 64);\n+\tstring_list_split(&masks, str, \",\", 64);\n \tfor (size_t i = 0; i < masks.nr; i++) {\n \t\tconst char *name = masks.items[i].string;\n \t\tstruct flag_definition *def = defs;\ndiff --git a/t/unit-tests/u-string-list.c b/t/unit-tests/u-string-list.c\nindex d4ba5f9fa5..150a5f505f 100644\n--- a/t/unit-tests/u-string-list.c\n+++ b/t/unit-tests/u-string-list.c\n@@ -43,7 +43,7 @@ static void t_string_list_equal(struct string_list *list,\n \t\t\t\t  expected_strings->items[i].string);\n }\n \n-static void t_string_list_split(const char *data, int delim, int maxsplit, ...)\n+static void t_string_list_split(const char *data, const char *delim, int maxsplit, ...)\n {\n \tstruct string_list expected_strings = STRING_LIST_INIT_DUP;\n \tstruct string_list list = STRING_LIST_INIT_DUP;\n@@ -65,13 +65,13 @@ static void t_string_list_split(const char *data, int delim, int maxsplit, ...)\n \n void test_string_list__split(void)\n {\n-\tt_string_list_split(\"foo:bar:baz\", ':', -1, \"foo\", \"bar\", \"baz\", NULL);\n-\tt_string_list_split(\"foo:bar:baz\", ':', 0, \"foo:bar:baz\", NULL);\n-\tt_string_list_split(\"foo:bar:baz\", ':', 1, \"foo\", \"bar:baz\", NULL);\n-\tt_string_list_split(\"foo:bar:baz\", ':', 2, \"foo\", \"bar\", \"baz\", NULL);\n-\tt_string_list_split(\"foo:bar:\", ':', -1, \"foo\", \"bar\", \"\", NULL);\n-\tt_string_list_split(\"\", ':', -1, \"\", NULL);\n-\tt_string_list_split(\":\", ':', -1, \"\", \"\", NULL);\n+\tt_string_list_split(\"foo:bar:baz\", \":\", -1, \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split(\"foo:bar:baz\", \":\", 0, \"foo:bar:baz\", NULL);\n+\tt_string_list_split(\"foo:bar:baz\", \":\", 1, \"foo\", \"bar:baz\", NULL);\n+\tt_string_list_split(\"foo:bar:baz\", \":\", 2, \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split(\"foo:bar:\", \":\", -1, \"foo\", \"bar\", \"\", NULL);\n+\tt_string_list_split(\"\", \":\", -1, \"\", NULL);\n+\tt_string_list_split(\":\", \":\", -1, \"\", \"\", NULL);\n }\n \n static void t_string_list_split_in_place(const char *data, const char *delim,\ndiff --git a/transport.c b/transport.c\nindex c123ac1e38..76487b5453 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1042,7 +1042,7 @@ static const struct string_list *protocol_allow_list(void)\n \tif (enabled < 0) {\n \t\tconst char *v = getenv(\"GIT_ALLOW_PROTOCOL\");\n \t\tif (v) {\n-\t\t\tstring_list_split(&allowed, v, ':', -1);\n+\t\t\tstring_list_split(&allowed, v, \":\", -1);\n \t\t\tstring_list_sort(&allowed);\n \t\t\tenabled = 1;\n \t\t} else {\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 4f26f6afc7..91fcdcad9b 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1685,7 +1685,7 @@ static void process_args(struct packet_reader *request,\n \t\t\tif (data->uri_protocols.nr)\n \t\t\t\tsend_err_and_die(data,\n \t\t\t\t\t\t \"multiple packfile-uris lines forbidden\");\n-\t\t\tstring_list_split(&data->uri_protocols, p, ',', -1);\n+\t\t\tstring_list_split(&data->uri_protocols, p, \",\", -1);\n \t\t\tcontinue;\n \t\t}\n \n-- \n2.50.1-612-g4756c59422\n\n"},{"id":"523049","messageId":"20250731063949.1601669-4-gitster@pobox.com","threadId":"63881","inReplyTo":"20250731063949.1601669-1-gitster@pobox.com","subject":"[PATCH 3/5] string-list: unify string_list_split* functions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-31T06:39:47Z","receivedAt":"2025-07-31T06:39:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thanks to the previous step, the only difference between these two\nrelated functions is that string_list_split() works on a string\nwithout modifying its contents (i.e. taking \"const char *\") and the\nresulting pieces of strings are their own copies in a string list,\nwhile string_list_split_in_place() works on a mutable string and the\nresulting pieces of strings come from the original string.\n\nConsolidate their implementations into a single helper function, and\nmake them a thin wrapper around it.  We can later add an extra flags\nparameter to extend both of these functions by updating only the\ninternal helper function.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n string-list.c | 90 +++++++++++++++++++++++++++++----------------------\n 1 file changed, 51 insertions(+), 39 deletions(-)\n\ndiff --git a/string-list.c b/string-list.c\nindex 2284a009cb..893e82be49 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -276,55 +276,67 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_\n \tlist->nr--;\n }\n \n-int string_list_split(struct string_list *list, const char *string,\n-\t\t      const char *delim, int maxsplit)\n+static void append_one(struct string_list *list,\n+\t\t       const char *p, const char *end,\n+\t\t       int in_place)\n+{\n+\tif (!end)\n+\t\tend = p + strlen(p);\n+\n+\tif (in_place) {\n+\t\t*((char *)end) = '\\0';\n+\t\tstring_list_append(list, p);\n+\t} else {\n+\t\tstring_list_append_nodup(list, xmemdupz(p, end - p));\n+\t}\n+}\n+\n+/*\n+ * Unfortunately this cannot become a public interface, as _in_place()\n+ * wants to have \"const char *string\" while the other variant wants to\n+ * have \"char *string\" for type safety.\n+ *\n+ * This accepts \"const char *string\" to allow both wrappers to use it;\n+ * it internally casts away the constness when in_place is true by\n+ * taking advantage of strpbrk() that takes a \"const char *\" arg and\n+ * returns \"char *\" pointer into that const string.  Yucky but works ;-).\n+ */\n+static int split_string(struct string_list *list, const char *string, const char *delim,\n+\t\t\tint maxsplit, int in_place)\n {\n \tint count = 0;\n-\tconst char *p = string, *end;\n+\tconst char *p = string;\n+\n+\tif (in_place && list->strdup_strings)\n+\t\tBUG(\"string_list_split_in_place() called with strdup_strings\");\n+\telse if (!in_place && !list->strdup_strings)\n+\t\tBUG(\"string_list_split() called without strdup_strings\");\n \n-\tif (!list->strdup_strings)\n-\t\tBUG(\"internal error in string_list_split(): \"\n-\t\t    \"list->strdup_strings must be set\");\n \tfor (;;) {\n+\t\tchar *end;\n+\n \t\tcount++;\n-\t\tif (maxsplit >= 0 && count > maxsplit) {\n-\t\t\tstring_list_append(list, p);\n-\t\t\treturn count;\n-\t\t}\n-\t\tend = strpbrk(p, delim);\n-\t\tif (end) {\n-\t\t\tstring_list_append_nodup(list, xmemdupz(p, end - p));\n-\t\t\tp = end + 1;\n-\t\t} else {\n-\t\t\tstring_list_append(list, p);\n+\t\tif (maxsplit >= 0 && count > maxsplit)\n+\t\t\tend = NULL;\n+\t\telse\n+\t\t\tend = strpbrk(p, delim);\n+\n+\t\tappend_one(list, p, end, in_place);\n+\n+\t\tif (!end)\n \t\t\treturn count;\n-\t\t}\n+\t\tp = end + 1;\n \t}\n }\n \n+int string_list_split(struct string_list *list, const char *string,\n+\t\t      const char *delim, int maxsplit)\n+{\n+\treturn split_string(list, string, delim, maxsplit, 0);\n+}\n+\n int string_list_split_in_place(struct string_list *list, char *string,\n \t\t\t       const char *delim, int maxsplit)\n {\n-\tint count = 0;\n-\tchar *p = string, *end;\n-\n-\tif (list->strdup_strings)\n-\t\tBUG(\"internal error in string_list_split_in_place(): \"\n-\t\t    \"list->strdup_strings must not be set\");\n-\tfor (;;) {\n-\t\tcount++;\n-\t\tif (maxsplit >= 0 && count > maxsplit) {\n-\t\t\tstring_list_append(list, p);\n-\t\t\treturn count;\n-\t\t}\n-\t\tend = strpbrk(p, delim);\n-\t\tif (end) {\n-\t\t\t*end = '\\0';\n-\t\t\tstring_list_append(list, p);\n-\t\t\tp = end + 1;\n-\t\t} else {\n-\t\t\tstring_list_append(list, p);\n-\t\t\treturn count;\n-\t\t}\n-\t}\n+\treturn split_string(list, string, delim, maxsplit, 1);\n }\n-- \n2.50.1-612-g4756c59422\n\n"},{"id":"523050","messageId":"20250731063949.1601669-5-gitster@pobox.com","threadId":"63881","inReplyTo":"20250731063949.1601669-1-gitster@pobox.com","subject":"[PATCH 4/5] string-list: optionally trim string pieces split by string_list_split()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-31T06:39:48Z","receivedAt":"2025-07-31T06:39:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Teach the unified split_string() to take an optional \"flags\" word,\nand define the first flag STRING_LIST_SPLIT_TRIM to cause the split\npieces to be trimmed before they are placed in the string list.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n string-list.c                | 35 +++++++++++++++++---\n string-list.h                | 10 ++++++\n t/unit-tests/u-string-list.c | 64 ++++++++++++++++++++++++++++++++++++\n 3 files changed, 104 insertions(+), 5 deletions(-)\n\ndiff --git a/string-list.c b/string-list.c\nindex 893e82be49..c6a3afb15a 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -278,11 +278,18 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_\n \n static void append_one(struct string_list *list,\n \t\t       const char *p, const char *end,\n-\t\t       int in_place)\n+\t\t       int in_place, unsigned flags)\n {\n \tif (!end)\n \t\tend = p + strlen(p);\n \n+\tif ((flags & STRING_LIST_SPLIT_TRIM)) {\n+\t\t/* rtrim */\n+\t\tfor (; p < end; end--)\n+\t\t\tif (!isspace(end[-1]))\n+\t\t\t\tbreak;\n+\t}\n+\n \tif (in_place) {\n \t\t*((char *)end) = '\\0';\n \t\tstring_list_append(list, p);\n@@ -302,7 +309,7 @@ static void append_one(struct string_list *list,\n  * returns \"char *\" pointer into that const string.  Yucky but works ;-).\n  */\n static int split_string(struct string_list *list, const char *string, const char *delim,\n-\t\t\tint maxsplit, int in_place)\n+\t\t\tint maxsplit, int in_place, unsigned flags)\n {\n \tint count = 0;\n \tconst char *p = string;\n@@ -315,13 +322,19 @@ static int split_string(struct string_list *list, const char *string, const char\n \tfor (;;) {\n \t\tchar *end;\n \n+\t\tif (flags & STRING_LIST_SPLIT_TRIM) {\n+\t\t\t/* ltrim */\n+\t\t\twhile (*p && isspace(*p))\n+\t\t\t\tp++;\n+\t\t}\n+\n \t\tcount++;\n \t\tif (maxsplit >= 0 && count > maxsplit)\n \t\t\tend = NULL;\n \t\telse\n \t\t\tend = strpbrk(p, delim);\n \n-\t\tappend_one(list, p, end, in_place);\n+\t\tappend_one(list, p, end, in_place, flags);\n \n \t\tif (!end)\n \t\t\treturn count;\n@@ -332,11 +345,23 @@ static int split_string(struct string_list *list, const char *string, const char\n int string_list_split(struct string_list *list, const char *string,\n \t\t      const char *delim, int maxsplit)\n {\n-\treturn split_string(list, string, delim, maxsplit, 0);\n+\treturn split_string(list, string, delim, maxsplit, 0, 0);\n }\n \n int string_list_split_in_place(struct string_list *list, char *string,\n \t\t\t       const char *delim, int maxsplit)\n {\n-\treturn split_string(list, string, delim, maxsplit, 1);\n+\treturn split_string(list, string, delim, maxsplit, 1, 0);\n+}\n+\n+int string_list_split_f(struct string_list *list, const char *string,\n+\t\t\tconst char *delim, int maxsplit, unsigned flags)\n+{\n+\treturn split_string(list, string, delim, maxsplit, 0, flags);\n+}\n+\n+int string_list_split_in_place_f(struct string_list *list, char *string,\n+\t\t\t       const char *delim, int maxsplit, unsigned flags)\n+{\n+\treturn split_string(list, string, delim, maxsplit, 1, flags);\n }\ndiff --git a/string-list.h b/string-list.h\nindex 6c8650efde..ee9922af67 100644\n--- a/string-list.h\n+++ b/string-list.h\n@@ -281,4 +281,14 @@ int string_list_split(struct string_list *list, const char *string,\n  */\n int string_list_split_in_place(struct string_list *list, char *string,\n \t\t\t       const char *delim, int maxsplit);\n+\n+/* trim() resulting string piece before adding it to the list */\n+#define STRING_LIST_SPLIT_TRIM 01\n+\n+int string_list_split_f(struct string_list *, const char *string,\n+\t\t\tconst char *delim, int maxsplit, unsigned flags);\n+\n+int string_list_split_in_place_f(struct string_list *, char *string,\n+\t\t\t\t const char *delim, int maxsplit, unsigned flags);\n+\n #endif /* STRING_LIST_H */\ndiff --git a/t/unit-tests/u-string-list.c b/t/unit-tests/u-string-list.c\nindex 150a5f505f..daa9307e45 100644\n--- a/t/unit-tests/u-string-list.c\n+++ b/t/unit-tests/u-string-list.c\n@@ -63,6 +63,70 @@ static void t_string_list_split(const char *data, const char *delim, int maxspli\n \tstring_list_clear(&list, 0);\n }\n \n+static void t_string_list_split_f(const char *data, const char *delim,\n+\t\t\t\t  int maxsplit, unsigned flags, ...)\n+{\n+\tstruct string_list expected_strings = STRING_LIST_INIT_DUP;\n+\tstruct string_list list = STRING_LIST_INIT_DUP;\n+\tva_list ap;\n+\tint len;\n+\n+\tva_start(ap, flags);\n+\tt_vcreate_string_list_dup(&expected_strings, 0, ap);\n+\tva_end(ap);\n+\n+\tstring_list_clear(&list, 0);\n+\tlen = string_list_split_f(&list, data, delim, maxsplit, flags);\n+\tcl_assert_equal_i(len, expected_strings.nr);\n+\tt_string_list_equal(&list, &expected_strings);\n+\n+\tstring_list_clear(&expected_strings, 0);\n+\tstring_list_clear(&list, 0);\n+}\n+\n+void test_string_list__split_f(void)\n+{\n+\tt_string_list_split_f(\"::foo:bar:baz:\", \":\", -1, 0,\n+\t\t\t      \"\", \"\", \"foo\", \"bar\", \"baz\", \"\", NULL);\n+\tt_string_list_split_f(\" foo:bar : baz\", \":\", -1, STRING_LIST_SPLIT_TRIM,\n+\t\t\t      \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split_f(\"  a  b c  \", \" \", 1, STRING_LIST_SPLIT_TRIM,\n+\t\t\t      \"a\", \"b c\", NULL);\n+}\n+\n+static void t_string_list_split_in_place_f(const char *data_, const char *delim,\n+\t\t\t\t\t   int maxsplit, unsigned flags, ...)\n+{\n+\tstruct string_list expected_strings = STRING_LIST_INIT_DUP;\n+\tstruct string_list list = STRING_LIST_INIT_NODUP;\n+\tchar *data = xstrdup(data_);\n+\tva_list ap;\n+\tint len;\n+\n+\tva_start(ap, flags);\n+\tt_vcreate_string_list_dup(&expected_strings, 0, ap);\n+\tva_end(ap);\n+\n+\tstring_list_clear(&list, 0);\n+\tlen = string_list_split_in_place_f(&list, data, delim, maxsplit, flags);\n+\tcl_assert_equal_i(len, expected_strings.nr);\n+\tt_string_list_equal(&list, &expected_strings);\n+\n+\tfree(data);\n+\tstring_list_clear(&expected_strings, 0);\n+\tstring_list_clear(&list, 0);\n+}\n+\n+void test_string_list__split_in_place_f(void)\n+{\n+\tt_string_list_split_in_place_f(\"::foo:bar:baz:\", \":\", -1, 0,\n+\t\t\t\t       \"\", \"\", \"foo\", \"bar\", \"baz\", \"\", NULL);\n+\tt_string_list_split_in_place_f(\" foo:bar : baz\", \":\", -1, STRING_LIST_SPLIT_TRIM,\n+\t\t\t\t       \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split_in_place_f(\"  a  b c  \", \" \", 1, STRING_LIST_SPLIT_TRIM,\n+\t\t\t\t       \"a\", \"b c\", NULL);\n+}\n+\n void test_string_list__split(void)\n {\n \tt_string_list_split(\"foo:bar:baz\", \":\", -1, \"foo\", \"bar\", \"baz\", NULL);\n-- \n2.50.1-612-g4756c59422\n\n"},{"id":"523051","messageId":"20250731063949.1601669-6-gitster@pobox.com","threadId":"63881","inReplyTo":"20250731063949.1601669-1-gitster@pobox.com","subject":"[PATCH 5/5] diff: simplify parsing of diff.colormovedws","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-31T06:39:49Z","receivedAt":"2025-07-31T06:40:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The code to parse this configuration variable, whose value is a\ncomma separated known tokens like \"ignore-space-change\" and\n\"ignore-all-space\", uses string_list_split() to split the value int\npieces, and then places each piece of string in a strbuf to trim,\nbefore comparing the result with the list of known tokens.\n\nThanks to the previous steps, now string_list_split() knows to trim\nthe resulting pieces in the string list.  Use it to simplify the\ncode.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n diff.c | 20 +++++++-------------\n 1 file changed, 7 insertions(+), 13 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex a81949a422..70666ad2cd 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -327,29 +327,23 @@ static unsigned parse_color_moved_ws(const char *arg)\n \tstruct string_list l = STRING_LIST_INIT_DUP;\n \tstruct string_list_item *i;\n \n-\tstring_list_split(&l, arg, \",\", -1);\n+\tstring_list_split_f(&l, arg, \",\", -1, STRING_LIST_SPLIT_TRIM);\n \n \tfor_each_string_list_item(i, &l) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\tstrbuf_addstr(&sb, i->string);\n-\t\tstrbuf_trim(&sb);\n-\n-\t\tif (!strcmp(sb.buf, \"no\"))\n+\t\tif (!strcmp(i->string, \"no\"))\n \t\t\tret = 0;\n-\t\telse if (!strcmp(sb.buf, \"ignore-space-change\"))\n+\t\telse if (!strcmp(i->string, \"ignore-space-change\"))\n \t\t\tret |= XDF_IGNORE_WHITESPACE_CHANGE;\n-\t\telse if (!strcmp(sb.buf, \"ignore-space-at-eol\"))\n+\t\telse if (!strcmp(i->string, \"ignore-space-at-eol\"))\n \t\t\tret |= XDF_IGNORE_WHITESPACE_AT_EOL;\n-\t\telse if (!strcmp(sb.buf, \"ignore-all-space\"))\n+\t\telse if (!strcmp(i->string, \"ignore-all-space\"))\n \t\t\tret |= XDF_IGNORE_WHITESPACE;\n-\t\telse if (!strcmp(sb.buf, \"allow-indentation-change\"))\n+\t\telse if (!strcmp(i->string, \"allow-indentation-change\"))\n \t\t\tret |= COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE;\n \t\telse {\n \t\t\tret |= COLOR_MOVED_WS_ERROR;\n-\t\t\terror(_(\"unknown color-moved-ws mode '%s', possible values are 'ignore-space-change', 'ignore-space-at-eol', 'ignore-all-space', 'allow-indentation-change'\"), sb.buf);\n+\t\t\terror(_(\"unknown color-moved-ws mode '%s', possible values are 'ignore-space-change', 'ignore-space-at-eol', 'ignore-all-space', 'allow-indentation-change'\"), i->string);\n \t\t}\n-\n-\t\tstrbuf_release(&sb);\n \t}\n \n \tif ((ret & COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) &&\n-- \n2.50.1-612-g4756c59422\n\n"},{"id":"523117","messageId":"CAPig+cT9tBSo1od8iuCf+88rvDO9GEikKm38WLcXHemM-emhOw@mail.gmail.com","threadId":"63881","inReplyTo":"20250731063949.1601669-2-gitster@pobox.com","subject":"Re: [PATCH 1/5] string-list: report programming error with BUG","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-07-31T19:33:27Z","receivedAt":"2025-07-31T19:33:39Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, Jul 31, 2025 at 2:40 AM Junio C Hamano <gitster@pobox.com> wrote:\n> Passing a string list that has .strdup_strings bit unset to\n> string_list_split(), orone that has .strdup_strings bit set to\n> string_list_split_in_place(), is a programmer error.  Do not use\n> die() to abort the execution.  Use BUG() instead.\n\ns/orone/or one/\n\n> As a developer-facing message, the message string itself should\n> be a lot more concise, but let's keep the original one for now.\n>\n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n"},{"id":"523118","messageId":"CAPig+cSh9gFOd6hyFSJTL99pRMU294P7xnWxuko_nqz26M8DWg@mail.gmail.com","threadId":"63881","inReplyTo":"20250731063949.1601669-3-gitster@pobox.com","subject":"Re: [PATCH 2/5] string-list: align string_list_split() with its _in_place() counterpart","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-07-31T19:36:57Z","receivedAt":"2025-07-31T19:37:09Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, Jul 31, 2025 at 2:40 AM Junio C Hamano <gitster@pobox.com> wrote:\n> For some unknown reason, unlike string_list_split_in_place(),\n> string_list_split() took only a single character as a field\n> delimiter.  Before giving both functions more features in future\n> commits, allow stirng_list_split() to take more than one delimiter\n> characters to make them closer to each other.\n\ns/stirng/string/\n\n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n"},{"id":"523120","messageId":"CAPig+cSed8ZOfLxDGYVeWhA_46FQ8LZ4E0+1KQu=V7NQZmLZxw@mail.gmail.com","threadId":"63881","inReplyTo":"20250731063949.1601669-6-gitster@pobox.com","subject":"Re: [PATCH 5/5] diff: simplify parsing of diff.colormovedws","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-07-31T19:45:21Z","receivedAt":"2025-07-31T19:45:33Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, Jul 31, 2025 at 2:40 AM Junio C Hamano <gitster@pobox.com> wrote:\n> The code to parse this configuration variable, whose value is a\n> comma separated known tokens like \"ignore-space-change\" and\n> \"ignore-all-space\", uses string_list_split() to split the value int\n> pieces, and then places each piece of string in a strbuf to trim,\n> before comparing the result with the list of known tokens.\n\ns/int/into/\n\n> Thanks to the previous steps, now string_list_split() knows to trim\n> the resulting pieces in the string list.  Use it to simplify the\n> code.\n>\n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> ---\n> diff --git a/diff.c b/diff.c\n> @@ -327,29 +327,23 @@ static unsigned parse_color_moved_ws(const char *arg)\n> -       string_list_split(&l, arg, \",\", -1);\n> +       string_list_split_f(&l, arg, \",\", -1, STRING_LIST_SPLIT_TRIM);\n>\n>         for_each_string_list_item(i, &l) {\n> -               struct strbuf sb = STRBUF_INIT;\n> -               strbuf_addstr(&sb, i->string);\n> -               strbuf_trim(&sb);\n> -\n> -               if (!strcmp(sb.buf, \"no\"))\n> +               if (!strcmp(i->string, \"no\"))\n>                         ret = 0;\n> -               else if (!strcmp(sb.buf, \"ignore-space-change\"))\n> +               else if (!strcmp(i->string, \"ignore-space-change\"))\n>                         ret |= XDF_IGNORE_WHITESPACE_CHANGE;\n> -               else if (!strcmp(sb.buf, \"ignore-space-at-eol\"))\n> +               else if (!strcmp(i->string, \"ignore-space-at-eol\"))\n>                         ret |= XDF_IGNORE_WHITESPACE_AT_EOL;\n> -               else if (!strcmp(sb.buf, \"ignore-all-space\"))\n> +               else if (!strcmp(i->string, \"ignore-all-space\"))\n>                         ret |= XDF_IGNORE_WHITESPACE;\n> -               else if (!strcmp(sb.buf, \"allow-indentation-change\"))\n> +               else if (!strcmp(i->string, \"allow-indentation-change\"))\n>                         ret |= COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE;\n>                 else {\n>                         ret |= COLOR_MOVED_WS_ERROR;\n> -                       error(_(\"unknown color-moved-ws mode '%s', possible values are 'ignore-space-change', 'ignore-space-at-eol', 'ignore-all-space', 'allow-indentation-change'\"), sb.buf);\n> +                       error(_(\"unknown color-moved-ws mode '%s', possible values are 'ignore-space-change', 'ignore-space-at-eol', 'ignore-all-space', 'allow-indentation-change'\"), i->string);\n>                 }\n> -\n> -               strbuf_release(&sb);\n>         }\n\nAn unfortunately noisy diff, but it can't be helped. The end result is\na pleasant improvement.\n"},{"id":"523129","messageId":"xmqq5xf8ovko.fsf@gitster.g","threadId":"63881","inReplyTo":"CAPig+cT9tBSo1od8iuCf+88rvDO9GEikKm38WLcXHemM-emhOw@mail.gmail.com","subject":"Re: [PATCH 1/5] string-list: report programming error with BUG","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-31T22:16:07Z","receivedAt":"2025-07-31T22:16:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> On Thu, Jul 31, 2025 at 2:40 AM Junio C Hamano <gitster@pobox.com> wrote:\n>> Passing a string list that has .strdup_strings bit unset to\n>> string_list_split(), orone that has .strdup_strings bit set to\n>> string_list_split_in_place(), is a programmer error.  Do not use\n>> die() to abort the execution.  Use BUG() instead.\n>\n> s/orone/or one/\n\nThanks, as always, for typofixes.  Not just this step but for other\nsteps in the series.  Will use them when I update them.\n\n"},{"id":"523132","messageId":"20250731224607.3942417-1-gitster@pobox.com","threadId":"63881","inReplyTo":"20250731063949.1601669-1-gitster@pobox.com","subject":"[PATCH v2 0/7] string_list_split*() updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-31T22:45:59Z","receivedAt":"2025-07-31T22:46:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Two related string-list API functions, string_list_split() and\nstring_list_split_in_place(), more or less duplicates their\nimplementations.  They both take a single string, and split the\nstring at the delimiter and stuff the result into a string list.\n\nHowever, there is one subtle and unnecessary difference.  The non\n\"in-place\" variant only allows a single byte value as delimiter,\nwhile the \"in-place\" variant can take multiple delimiters (e.g.,\n\"split at either a comma or a space\").\n\nThis series first updates the string_list_split() to allow multiple\ndelimiters like string_list_split_in_place() does, by unifying their\nimplementations into one.  This refactoring allows us to give new\nfeatures to these two functions more easily.\n\nThen these functions learn to optionally\n\n - trim the split string pieces before placing them in the resulting\n   string list.\n\n - omit empty string pieces from the resulting string list.\n\nAn existing caller of string_list_split() in diff.c trims the\nelements in the resulting string list before it uses them, which is\nsimplified by taking advantage of this new feature.\n\nA handful of code paths call string_list_split*(), immediately\nfollowed by string_list_remove_empty_items().  They are simplified\nby not placing empty items in the list in the first place.\n\nJunio C Hamano (7):\n  string-list: report programming error with BUG\n  string-list: align string_list_split() with its _in_place()\n    counterpart\n  string-list: unify string_list_split* functions\n  string-list: optionally trim string pieces split by\n    string_list_split*()\n  diff: simplify parsing of diff.colormovedws\n  string-list: optionally omit empty string pieces in\n    string_list_split*()\n  string-list: split-then-remove-empty can be done while splitting\n\n builtin/blame.c              |   2 +-\n builtin/merge.c              |   2 +-\n builtin/var.c                |   2 +-\n connect.c                    |   2 +-\n diff.c                       |  20 ++----\n fetch-pack.c                 |   2 +-\n notes.c                      |   6 +-\n parse-options.c              |   2 +-\n pathspec.c                   |   3 +-\n protocol.c                   |   2 +-\n ref-filter.c                 |   4 +-\n setup.c                      |   3 +-\n string-list.c                | 120 ++++++++++++++++++++++++-----------\n string-list.h                |  29 ++++++---\n t/helper/test-hashmap.c      |   4 +-\n t/helper/test-json-writer.c  |   4 +-\n t/helper/test-path-utils.c   |   3 +-\n t/helper/test-ref-store.c    |   2 +-\n t/unit-tests/u-string-list.c |  95 ++++++++++++++++++++++++---\n transport.c                  |   2 +-\n upload-pack.c                |   2 +-\n 21 files changed, 221 insertions(+), 90 deletions(-)\n\n\n1:  e56dc89249 ! 1:  1c2b222eec string-list: report programming error with BUG\n    @@ Commit message\n         string-list: report programming error with BUG\n     \n         Passing a string list that has .strdup_strings bit unset to\n    -    string_list_split(), orone that has .strdup_strings bit set to\n    +    string_list_split(), or one that has .strdup_strings bit set to\n         string_list_split_in_place(), is a programmer error.  Do not use\n         die() to abort the execution.  Use BUG() instead.\n     \n2:  1bd3506fad ! 2:  a7e07b94ef string-list: align string_list_split() with its _in_place() counterpart\n    @@ Commit message\n         For some unknown reason, unlike string_list_split_in_place(),\n         string_list_split() took only a single character as a field\n         delimiter.  Before giving both functions more features in future\n    -    commits, allow stirng_list_split() to take more than one delimiter\n    +    commits, allow string_list_split() to take more than one delimiter\n         characters to make them closer to each other.\n     \n         Signed-off-by: Junio C Hamano <gitster@pobox.com>\n3:  52c3b694d2 ! 3:  b7a7fbb975 string-list: unify string_list_split* functions\n    @@ string-list.c: void unsorted_string_list_delete_item(struct string_list *list, i\n      \n     -int string_list_split(struct string_list *list, const char *string,\n     -\t\t      const char *delim, int maxsplit)\n    -+static void append_one(struct string_list *list,\n    -+\t\t       const char *p, const char *end,\n    -+\t\t       int in_place)\n    ++/*\n    ++ * append a substring [p..end] to list; return number of things it\n    ++ * appended to the list.\n    ++ */\n    ++static int append_one(struct string_list *list,\n    ++\t\t      const char *p, const char *end,\n    ++\t\t      int in_place)\n     +{\n     +\tif (!end)\n     +\t\tend = p + strlen(p);\n    @@ string-list.c: void unsorted_string_list_delete_item(struct string_list *list, i\n     +\t} else {\n     +\t\tstring_list_append_nodup(list, xmemdupz(p, end - p));\n     +\t}\n    ++\treturn 1;\n     +}\n     +\n     +/*\n    @@ string-list.c: void unsorted_string_list_delete_item(struct string_list *list, i\n     -\t\tBUG(\"internal error in string_list_split(): \"\n     -\t\t    \"list->strdup_strings must be set\");\n      \tfor (;;) {\n    -+\t\tchar *end;\n    -+\n    - \t\tcount++;\n    +-\t\tcount++;\n     -\t\tif (maxsplit >= 0 && count > maxsplit) {\n     -\t\t\tstring_list_append(list, p);\n     -\t\t\treturn count;\n    @@ string-list.c: void unsorted_string_list_delete_item(struct string_list *list, i\n     -\t\t\tp = end + 1;\n     -\t\t} else {\n     -\t\t\tstring_list_append(list, p);\n    -+\t\tif (maxsplit >= 0 && count > maxsplit)\n    ++\t\tchar *end;\n    ++\n    ++\t\tif (0 <= maxsplit && maxsplit <= count)\n     +\t\t\tend = NULL;\n     +\t\telse\n     +\t\t\tend = strpbrk(p, delim);\n     +\n    -+\t\tappend_one(list, p, end, in_place);\n    ++\t\tcount += append_one(list, p, end, in_place);\n     +\n     +\t\tif (!end)\n      \t\t\treturn count;\n4:  13e3d9fbaf ! 4:  c566d88c28 string-list: optionally trim string pieces split by string_list_split()\n    @@ Metadata\n     Author: Junio C Hamano <gitster@pobox.com>\n     \n      ## Commit message ##\n    -    string-list: optionally trim string pieces split by string_list_split()\n    +    string-list: optionally trim string pieces split by string_list_split*()\n     \n         Teach the unified split_string() to take an optional \"flags\" word,\n         and define the first flag STRING_LIST_SPLIT_TRIM to cause the split\n    @@ Commit message\n     \n      ## string-list.c ##\n     @@ string-list.c: void unsorted_string_list_delete_item(struct string_list *list, int i, int free_\n    - \n    - static void append_one(struct string_list *list,\n    - \t\t       const char *p, const char *end,\n    --\t\t       int in_place)\n    -+\t\t       int in_place, unsigned flags)\n    +  */\n    + static int append_one(struct string_list *list,\n    + \t\t      const char *p, const char *end,\n    +-\t\t      int in_place)\n    ++\t\t      int in_place, unsigned flags)\n      {\n      \tif (!end)\n      \t\tend = p + strlen(p);\n    @@ string-list.c: void unsorted_string_list_delete_item(struct string_list *list, i\n      \tif (in_place) {\n      \t\t*((char *)end) = '\\0';\n      \t\tstring_list_append(list, p);\n    -@@ string-list.c: static void append_one(struct string_list *list,\n    +@@ string-list.c: static int append_one(struct string_list *list,\n       * returns \"char *\" pointer into that const string.  Yucky but works ;-).\n       */\n      static int split_string(struct string_list *list, const char *string, const char *delim,\n    @@ string-list.c: static int split_string(struct string_list *list, const char *str\n     +\t\t\t\tp++;\n     +\t\t}\n     +\n    - \t\tcount++;\n    - \t\tif (maxsplit >= 0 && count > maxsplit)\n    + \t\tif (0 <= maxsplit && maxsplit <= count)\n      \t\t\tend = NULL;\n      \t\telse\n      \t\t\tend = strpbrk(p, delim);\n      \n    --\t\tappend_one(list, p, end, in_place);\n    -+\t\tappend_one(list, p, end, in_place, flags);\n    +-\t\tcount += append_one(list, p, end, in_place);\n    ++\t\tcount += append_one(list, p, end, in_place, flags);\n      \n      \t\tif (!end)\n      \t\t\treturn count;\n5:  912c6ee193 ! 5:  eb272e0f22 diff: simplify parsing of diff.colormovedws\n    @@ Commit message\n     \n         The code to parse this configuration variable, whose value is a\n         comma separated known tokens like \"ignore-space-change\" and\n    -    \"ignore-all-space\", uses string_list_split() to split the value int\n    +    \"ignore-all-space\", uses string_list_split() to split the value into\n         pieces, and then places each piece of string in a strbuf to trim,\n         before comparing the result with the list of known tokens.\n     \n    -    Thanks to the previous steps, now string_list_split() knows to trim\n    -    the resulting pieces in the string list.  Use it to simplify the\n    -    code.\n    +    Thanks to the previous steps, now string_list_split() can trim the\n    +    resulting pieces before it places them in the string list.  Use it\n    +    to simplify the code.\n     \n         Signed-off-by: Junio C Hamano <gitster@pobox.com>\n     \n-:  ---------- > 6:  d418078a84 string-list: optionally omit empty string pieces in string_list_split*()\n-:  ---------- > 7:  12c1189a08 string-list: split-then-remove-empty can be done while splitting\n"},{"id":"523133","messageId":"20250731224607.3942417-2-gitster@pobox.com","threadId":"63881","inReplyTo":"20250731224607.3942417-1-gitster@pobox.com","subject":"[PATCH v2 1/7] string-list: report programming error with BUG","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-31T22:46:00Z","receivedAt":"2025-07-31T22:46:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Passing a string list that has .strdup_strings bit unset to\nstring_list_split(), or one that has .strdup_strings bit set to\nstring_list_split_in_place(), is a programmer error.  Do not use\ndie() to abort the execution.  Use BUG() instead.\n\nAs a developer-facing message, the message string itself should\nbe a lot more concise, but let's keep the original one for now.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n string-list.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/string-list.c b/string-list.c\nindex 53faaa8420..0cb920e9b0 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -283,7 +283,7 @@ int string_list_split(struct string_list *list, const char *string,\n \tconst char *p = string, *end;\n \n \tif (!list->strdup_strings)\n-\t\tdie(\"internal error in string_list_split(): \"\n+\t\tBUG(\"internal error in string_list_split(): \"\n \t\t    \"list->strdup_strings must be set\");\n \tfor (;;) {\n \t\tcount++;\n@@ -309,7 +309,7 @@ int string_list_split_in_place(struct string_list *list, char *string,\n \tchar *p = string, *end;\n \n \tif (list->strdup_strings)\n-\t\tdie(\"internal error in string_list_split_in_place(): \"\n+\t\tBUG(\"internal error in string_list_split_in_place(): \"\n \t\t    \"list->strdup_strings must not be set\");\n \tfor (;;) {\n \t\tcount++;\n-- \n2.50.1-618-g45d530d26b\n\n"},{"id":"523134","messageId":"20250731224607.3942417-3-gitster@pobox.com","threadId":"63881","inReplyTo":"20250731224607.3942417-1-gitster@pobox.com","subject":"[PATCH v2 2/7] string-list: align string_list_split() with its _in_place() counterpart","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-31T22:46:01Z","receivedAt":"2025-07-31T22:46:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"For some unknown reason, unlike string_list_split_in_place(),\nstring_list_split() took only a single character as a field\ndelimiter.  Before giving both functions more features in future\ncommits, allow string_list_split() to take more than one delimiter\ncharacters to make them closer to each other.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/blame.c              |  2 +-\n builtin/merge.c              |  2 +-\n builtin/var.c                |  2 +-\n connect.c                    |  2 +-\n diff.c                       |  2 +-\n fetch-pack.c                 |  2 +-\n notes.c                      |  2 +-\n parse-options.c              |  2 +-\n pathspec.c                   |  2 +-\n protocol.c                   |  2 +-\n ref-filter.c                 |  4 ++--\n setup.c                      |  3 ++-\n string-list.c                |  4 ++--\n string-list.h                | 16 ++++++++--------\n t/helper/test-path-utils.c   |  3 ++-\n t/helper/test-ref-store.c    |  2 +-\n t/unit-tests/u-string-list.c | 16 ++++++++--------\n transport.c                  |  2 +-\n upload-pack.c                |  2 +-\n 19 files changed, 37 insertions(+), 35 deletions(-)\n\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 91586e6852..70a6460401 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -420,7 +420,7 @@ static void parse_color_fields(const char *s)\n \tcolorfield_nr = 0;\n \n \t/* Ideally this would be stripped and split at the same time? */\n-\tstring_list_split(&l, s, ',', -1);\n+\tstring_list_split(&l, s, \",\", -1);\n \tALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc);\n \n \tfor_each_string_list_item(item, &l) {\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 18b22c0a26..893f8950bf 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -875,7 +875,7 @@ static void add_strategies(const char *string, unsigned attr)\n \tif (string) {\n \t\tstruct string_list list = STRING_LIST_INIT_DUP;\n \t\tstruct string_list_item *item;\n-\t\tstring_list_split(&list, string, ' ', -1);\n+\t\tstring_list_split(&list, string, \" \", -1);\n \t\tfor_each_string_list_item(item, &list)\n \t\t\tappend_strategy(get_strategy(item->string));\n \t\tstring_list_clear(&list, 0);\ndiff --git a/builtin/var.c b/builtin/var.c\nindex ada642a9fe..4ae7af0eff 100644\n--- a/builtin/var.c\n+++ b/builtin/var.c\n@@ -181,7 +181,7 @@ static void list_vars(void)\n \t\t\tif (ptr->multivalued && *val) {\n \t\t\t\tstruct string_list list = STRING_LIST_INIT_DUP;\n \n-\t\t\t\tstring_list_split(&list, val, '\\n', -1);\n+\t\t\t\tstring_list_split(&list, val, \"\\n\", -1);\n \t\t\t\tfor (size_t i = 0; i < list.nr; i++)\n \t\t\t\t\tprintf(\"%s=%s\\n\", ptr->name, list.items[i].string);\n \t\t\t\tstring_list_clear(&list, 0);\ndiff --git a/connect.c b/connect.c\nindex e77287f426..867b12bde5 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -407,7 +407,7 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list,\n \t * name.  Subsequent fields (symref-target and peeled) are optional and\n \t * don't have a particular order.\n \t */\n-\tif (string_list_split(&line_sections, line, ' ', -1) < 2) {\n+\tif (string_list_split(&line_sections, line, \" \", -1) < 2) {\n \t\tret = 0;\n \t\tgoto out;\n \t}\ndiff --git a/diff.c b/diff.c\nindex dca87e164f..a81949a422 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -327,7 +327,7 @@ static unsigned parse_color_moved_ws(const char *arg)\n \tstruct string_list l = STRING_LIST_INIT_DUP;\n \tstruct string_list_item *i;\n \n-\tstring_list_split(&l, arg, ',', -1);\n+\tstring_list_split(&l, arg, \",\", -1);\n \n \tfor_each_string_list_item(i, &l) {\n \t\tstruct strbuf sb = STRBUF_INIT;\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex c1be9b76eb..9866270696 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1914,7 +1914,7 @@ static void fetch_pack_config(void)\n \t\tchar *str;\n \n \t\tif (!git_config_get_string(\"fetch.uriprotocols\", &str) && str) {\n-\t\t\tstring_list_split(&uri_protocols, str, ',', -1);\n+\t\t\tstring_list_split(&uri_protocols, str, \",\", -1);\n \t\t\tfree(str);\n \t\t}\n \t}\ndiff --git a/notes.c b/notes.c\nindex 97b995f3f2..6afcf088b9 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -892,7 +892,7 @@ static int string_list_add_note_lines(struct string_list *list,\n \t * later, along with any empty strings that came from empty\n \t * lines within the file.\n \t */\n-\tstring_list_split(list, data, '\\n', -1);\n+\tstring_list_split(list, data, \"\\n\", -1);\n \tfree(data);\n \treturn 0;\n }\ndiff --git a/parse-options.c b/parse-options.c\nindex 5224203ffe..9e7cb75192 100644\n--- a/parse-options.c\n+++ b/parse-options.c\n@@ -1338,7 +1338,7 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t\n \t\tif (!saw_empty_line && !*str)\n \t\t\tsaw_empty_line = 1;\n \n-\t\tstring_list_split(&list, str, '\\n', -1);\n+\t\tstring_list_split(&list, str, \"\\n\", -1);\n \t\tfor (j = 0; j < list.nr; j++) {\n \t\t\tconst char *line = list.items[j].string;\n \ndiff --git a/pathspec.c b/pathspec.c\nindex a3ddd701c7..de325f7ef9 100644\n--- a/pathspec.c\n+++ b/pathspec.c\n@@ -201,7 +201,7 @@ static void parse_pathspec_attr_match(struct pathspec_item *item, const char *va\n \tif (!value || !*value)\n \t\tdie(_(\"attr spec must not be empty\"));\n \n-\tstring_list_split(&list, value, ' ', -1);\n+\tstring_list_split(&list, value, \" \", -1);\n \tstring_list_remove_empty_items(&list, 0);\n \n \titem->attr_check = attr_check_alloc();\ndiff --git a/protocol.c b/protocol.c\nindex bae7226ff4..54b9f49c01 100644\n--- a/protocol.c\n+++ b/protocol.c\n@@ -61,7 +61,7 @@ enum protocol_version determine_protocol_version_server(void)\n \tif (git_protocol) {\n \t\tstruct string_list list = STRING_LIST_INIT_DUP;\n \t\tconst struct string_list_item *item;\n-\t\tstring_list_split(&list, git_protocol, ':', -1);\n+\t\tstring_list_split(&list, git_protocol, \":\", -1);\n \n \t\tfor_each_string_list_item(item, &list) {\n \t\t\tconst char *value;\ndiff --git a/ref-filter.c b/ref-filter.c\nindex f9f2c512a8..4edfb9c83b 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -435,7 +435,7 @@ static int remote_ref_atom_parser(struct ref_format *format UNUSED,\n \t}\n \n \tatom->u.remote_ref.nobracket = 0;\n-\tstring_list_split(&params, arg, ',', -1);\n+\tstring_list_split(&params, arg, \",\", -1);\n \n \tfor (i = 0; i < params.nr; i++) {\n \t\tconst char *s = params.items[i].string;\n@@ -831,7 +831,7 @@ static int align_atom_parser(struct ref_format *format UNUSED,\n \n \talign->position = ALIGN_LEFT;\n \n-\tstring_list_split(&params, arg, ',', -1);\n+\tstring_list_split(&params, arg, \",\", -1);\n \tfor (i = 0; i < params.nr; i++) {\n \t\tconst char *s = params.items[i].string;\n \t\tint position;\ndiff --git a/setup.c b/setup.c\nindex 6f52dab64c..b9f5eb8b51 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1460,8 +1460,9 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n \n \tif (env_ceiling_dirs) {\n \t\tint empty_entry_found = 0;\n+\t\tstatic const char path_sep[] = { PATH_SEP, '\\0' };\n \n-\t\tstring_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);\n+\t\tstring_list_split(&ceiling_dirs, env_ceiling_dirs, path_sep, -1);\n \t\tfilter_string_list(&ceiling_dirs, 0,\n \t\t\t\t   canonicalize_ceiling_entry, &empty_entry_found);\n \t\tceil_offset = longest_ancestor_length(dir->buf, &ceiling_dirs);\ndiff --git a/string-list.c b/string-list.c\nindex 0cb920e9b0..2284a009cb 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -277,7 +277,7 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_\n }\n \n int string_list_split(struct string_list *list, const char *string,\n-\t\t      int delim, int maxsplit)\n+\t\t      const char *delim, int maxsplit)\n {\n \tint count = 0;\n \tconst char *p = string, *end;\n@@ -291,7 +291,7 @@ int string_list_split(struct string_list *list, const char *string,\n \t\t\tstring_list_append(list, p);\n \t\t\treturn count;\n \t\t}\n-\t\tend = strchr(p, delim);\n+\t\tend = strpbrk(p, delim);\n \t\tif (end) {\n \t\t\tstring_list_append_nodup(list, xmemdupz(p, end - p));\n \t\t\tp = end + 1;\ndiff --git a/string-list.h b/string-list.h\nindex 122b318641..6c8650efde 100644\n--- a/string-list.h\n+++ b/string-list.h\n@@ -254,7 +254,7 @@ struct string_list_item *unsorted_string_list_lookup(struct string_list *list,\n void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util);\n \n /**\n- * Split string into substrings on character `delim` and append the\n+ * Split string into substrings on characters in `delim` and append the\n  * substrings to `list`.  The input string is not modified.\n  * list->strdup_strings must be set, as new memory needs to be\n  * allocated to hold the substrings.  If maxsplit is non-negative,\n@@ -262,15 +262,15 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_\n  * appended to list.\n  *\n  * Examples:\n- *   string_list_split(l, \"foo:bar:baz\", ':', -1) -> [\"foo\", \"bar\", \"baz\"]\n- *   string_list_split(l, \"foo:bar:baz\", ':', 0) -> [\"foo:bar:baz\"]\n- *   string_list_split(l, \"foo:bar:baz\", ':', 1) -> [\"foo\", \"bar:baz\"]\n- *   string_list_split(l, \"foo:bar:\", ':', -1) -> [\"foo\", \"bar\", \"\"]\n- *   string_list_split(l, \"\", ':', -1) -> [\"\"]\n- *   string_list_split(l, \":\", ':', -1) -> [\"\", \"\"]\n+ *   string_list_split(l, \"foo:bar:baz\", \":\", -1) -> [\"foo\", \"bar\", \"baz\"]\n+ *   string_list_split(l, \"foo:bar:baz\", \":\", 0) -> [\"foo:bar:baz\"]\n+ *   string_list_split(l, \"foo:bar:baz\", \":\", 1) -> [\"foo\", \"bar:baz\"]\n+ *   string_list_split(l, \"foo:bar:\", \":\", -1) -> [\"foo\", \"bar\", \"\"]\n+ *   string_list_split(l, \"\", \":\", -1) -> [\"\"]\n+ *   string_list_split(l, \":\", \":\", -1) -> [\"\", \"\"]\n  */\n int string_list_split(struct string_list *list, const char *string,\n-\t\t      int delim, int maxsplit);\n+\t\t      const char *delim, int maxsplit);\n \n /*\n  * Like string_list_split(), except that string is split in-place: the\ndiff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c\nindex 086238c826..f5f33751da 100644\n--- a/t/helper/test-path-utils.c\n+++ b/t/helper/test-path-utils.c\n@@ -348,6 +348,7 @@ int cmd__path_utils(int argc, const char **argv)\n \tif (argc == 4 && !strcmp(argv[1], \"longest_ancestor_length\")) {\n \t\tint len;\n \t\tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n+\t\tconst char path_sep[] = { PATH_SEP, '\\0' };\n \t\tchar *path = xstrdup(argv[2]);\n \n \t\t/*\n@@ -362,7 +363,7 @@ int cmd__path_utils(int argc, const char **argv)\n \t\t */\n \t\tif (normalize_path_copy(path, path))\n \t\t\tdie(\"Path \\\"%s\\\" could not be normalized\", argv[2]);\n-\t\tstring_list_split(&ceiling_dirs, argv[3], PATH_SEP, -1);\n+\t\tstring_list_split(&ceiling_dirs, argv[3], path_sep, -1);\n \t\tfilter_string_list(&ceiling_dirs, 0,\n \t\t\t\t   normalize_ceiling_entry, NULL);\n \t\tlen = longest_ancestor_length(path, &ceiling_dirs);\ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex 8d9a271845..aa1cb9b4ac 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -29,7 +29,7 @@ static unsigned int parse_flags(const char *str, struct flag_definition *defs)\n \tif (!strcmp(str, \"0\"))\n \t\treturn 0;\n \n-\tstring_list_split(&masks, str, ',', 64);\n+\tstring_list_split(&masks, str, \",\", 64);\n \tfor (size_t i = 0; i < masks.nr; i++) {\n \t\tconst char *name = masks.items[i].string;\n \t\tstruct flag_definition *def = defs;\ndiff --git a/t/unit-tests/u-string-list.c b/t/unit-tests/u-string-list.c\nindex d4ba5f9fa5..150a5f505f 100644\n--- a/t/unit-tests/u-string-list.c\n+++ b/t/unit-tests/u-string-list.c\n@@ -43,7 +43,7 @@ static void t_string_list_equal(struct string_list *list,\n \t\t\t\t  expected_strings->items[i].string);\n }\n \n-static void t_string_list_split(const char *data, int delim, int maxsplit, ...)\n+static void t_string_list_split(const char *data, const char *delim, int maxsplit, ...)\n {\n \tstruct string_list expected_strings = STRING_LIST_INIT_DUP;\n \tstruct string_list list = STRING_LIST_INIT_DUP;\n@@ -65,13 +65,13 @@ static void t_string_list_split(const char *data, int delim, int maxsplit, ...)\n \n void test_string_list__split(void)\n {\n-\tt_string_list_split(\"foo:bar:baz\", ':', -1, \"foo\", \"bar\", \"baz\", NULL);\n-\tt_string_list_split(\"foo:bar:baz\", ':', 0, \"foo:bar:baz\", NULL);\n-\tt_string_list_split(\"foo:bar:baz\", ':', 1, \"foo\", \"bar:baz\", NULL);\n-\tt_string_list_split(\"foo:bar:baz\", ':', 2, \"foo\", \"bar\", \"baz\", NULL);\n-\tt_string_list_split(\"foo:bar:\", ':', -1, \"foo\", \"bar\", \"\", NULL);\n-\tt_string_list_split(\"\", ':', -1, \"\", NULL);\n-\tt_string_list_split(\":\", ':', -1, \"\", \"\", NULL);\n+\tt_string_list_split(\"foo:bar:baz\", \":\", -1, \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split(\"foo:bar:baz\", \":\", 0, \"foo:bar:baz\", NULL);\n+\tt_string_list_split(\"foo:bar:baz\", \":\", 1, \"foo\", \"bar:baz\", NULL);\n+\tt_string_list_split(\"foo:bar:baz\", \":\", 2, \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split(\"foo:bar:\", \":\", -1, \"foo\", \"bar\", \"\", NULL);\n+\tt_string_list_split(\"\", \":\", -1, \"\", NULL);\n+\tt_string_list_split(\":\", \":\", -1, \"\", \"\", NULL);\n }\n \n static void t_string_list_split_in_place(const char *data, const char *delim,\ndiff --git a/transport.c b/transport.c\nindex c123ac1e38..76487b5453 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1042,7 +1042,7 @@ static const struct string_list *protocol_allow_list(void)\n \tif (enabled < 0) {\n \t\tconst char *v = getenv(\"GIT_ALLOW_PROTOCOL\");\n \t\tif (v) {\n-\t\t\tstring_list_split(&allowed, v, ':', -1);\n+\t\t\tstring_list_split(&allowed, v, \":\", -1);\n \t\t\tstring_list_sort(&allowed);\n \t\t\tenabled = 1;\n \t\t} else {\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 4f26f6afc7..91fcdcad9b 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1685,7 +1685,7 @@ static void process_args(struct packet_reader *request,\n \t\t\tif (data->uri_protocols.nr)\n \t\t\t\tsend_err_and_die(data,\n \t\t\t\t\t\t \"multiple packfile-uris lines forbidden\");\n-\t\t\tstring_list_split(&data->uri_protocols, p, ',', -1);\n+\t\t\tstring_list_split(&data->uri_protocols, p, \",\", -1);\n \t\t\tcontinue;\n \t\t}\n \n-- \n2.50.1-618-g45d530d26b\n\n"},{"id":"523135","messageId":"20250731224607.3942417-4-gitster@pobox.com","threadId":"63881","inReplyTo":"20250731224607.3942417-1-gitster@pobox.com","subject":"[PATCH v2 3/7] string-list: unify string_list_split* functions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-31T22:46:02Z","receivedAt":"2025-07-31T22:46:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thanks to the previous step, the only difference between these two\nrelated functions is that string_list_split() works on a string\nwithout modifying its contents (i.e. taking \"const char *\") and the\nresulting pieces of strings are their own copies in a string list,\nwhile string_list_split_in_place() works on a mutable string and the\nresulting pieces of strings come from the original string.\n\nConsolidate their implementations into a single helper function, and\nmake them a thin wrapper around it.  We can later add an extra flags\nparameter to extend both of these functions by updating only the\ninternal helper function.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n string-list.c | 96 ++++++++++++++++++++++++++++++---------------------\n 1 file changed, 56 insertions(+), 40 deletions(-)\n\ndiff --git a/string-list.c b/string-list.c\nindex 2284a009cb..65b6ceb259 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -276,55 +276,71 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_\n \tlist->nr--;\n }\n \n-int string_list_split(struct string_list *list, const char *string,\n-\t\t      const char *delim, int maxsplit)\n+/*\n+ * append a substring [p..end] to list; return number of things it\n+ * appended to the list.\n+ */\n+static int append_one(struct string_list *list,\n+\t\t      const char *p, const char *end,\n+\t\t      int in_place)\n+{\n+\tif (!end)\n+\t\tend = p + strlen(p);\n+\n+\tif (in_place) {\n+\t\t*((char *)end) = '\\0';\n+\t\tstring_list_append(list, p);\n+\t} else {\n+\t\tstring_list_append_nodup(list, xmemdupz(p, end - p));\n+\t}\n+\treturn 1;\n+}\n+\n+/*\n+ * Unfortunately this cannot become a public interface, as _in_place()\n+ * wants to have \"const char *string\" while the other variant wants to\n+ * have \"char *string\" for type safety.\n+ *\n+ * This accepts \"const char *string\" to allow both wrappers to use it;\n+ * it internally casts away the constness when in_place is true by\n+ * taking advantage of strpbrk() that takes a \"const char *\" arg and\n+ * returns \"char *\" pointer into that const string.  Yucky but works ;-).\n+ */\n+static int split_string(struct string_list *list, const char *string, const char *delim,\n+\t\t\tint maxsplit, int in_place)\n {\n \tint count = 0;\n-\tconst char *p = string, *end;\n+\tconst char *p = string;\n+\n+\tif (in_place && list->strdup_strings)\n+\t\tBUG(\"string_list_split_in_place() called with strdup_strings\");\n+\telse if (!in_place && !list->strdup_strings)\n+\t\tBUG(\"string_list_split() called without strdup_strings\");\n \n-\tif (!list->strdup_strings)\n-\t\tBUG(\"internal error in string_list_split(): \"\n-\t\t    \"list->strdup_strings must be set\");\n \tfor (;;) {\n-\t\tcount++;\n-\t\tif (maxsplit >= 0 && count > maxsplit) {\n-\t\t\tstring_list_append(list, p);\n-\t\t\treturn count;\n-\t\t}\n-\t\tend = strpbrk(p, delim);\n-\t\tif (end) {\n-\t\t\tstring_list_append_nodup(list, xmemdupz(p, end - p));\n-\t\t\tp = end + 1;\n-\t\t} else {\n-\t\t\tstring_list_append(list, p);\n+\t\tchar *end;\n+\n+\t\tif (0 <= maxsplit && maxsplit <= count)\n+\t\t\tend = NULL;\n+\t\telse\n+\t\t\tend = strpbrk(p, delim);\n+\n+\t\tcount += append_one(list, p, end, in_place);\n+\n+\t\tif (!end)\n \t\t\treturn count;\n-\t\t}\n+\t\tp = end + 1;\n \t}\n }\n \n+int string_list_split(struct string_list *list, const char *string,\n+\t\t      const char *delim, int maxsplit)\n+{\n+\treturn split_string(list, string, delim, maxsplit, 0);\n+}\n+\n int string_list_split_in_place(struct string_list *list, char *string,\n \t\t\t       const char *delim, int maxsplit)\n {\n-\tint count = 0;\n-\tchar *p = string, *end;\n-\n-\tif (list->strdup_strings)\n-\t\tBUG(\"internal error in string_list_split_in_place(): \"\n-\t\t    \"list->strdup_strings must not be set\");\n-\tfor (;;) {\n-\t\tcount++;\n-\t\tif (maxsplit >= 0 && count > maxsplit) {\n-\t\t\tstring_list_append(list, p);\n-\t\t\treturn count;\n-\t\t}\n-\t\tend = strpbrk(p, delim);\n-\t\tif (end) {\n-\t\t\t*end = '\\0';\n-\t\t\tstring_list_append(list, p);\n-\t\t\tp = end + 1;\n-\t\t} else {\n-\t\t\tstring_list_append(list, p);\n-\t\t\treturn count;\n-\t\t}\n-\t}\n+\treturn split_string(list, string, delim, maxsplit, 1);\n }\n-- \n2.50.1-618-g45d530d26b\n\n"},{"id":"523137","messageId":"20250731224607.3942417-5-gitster@pobox.com","threadId":"63881","inReplyTo":"20250731224607.3942417-1-gitster@pobox.com","subject":"[PATCH v2 4/7] string-list: optionally trim string pieces split by string_list_split*()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-31T22:46:03Z","receivedAt":"2025-07-31T22:46:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Teach the unified split_string() to take an optional \"flags\" word,\nand define the first flag STRING_LIST_SPLIT_TRIM to cause the split\npieces to be trimmed before they are placed in the string list.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n string-list.c                | 35 +++++++++++++++++---\n string-list.h                | 10 ++++++\n t/unit-tests/u-string-list.c | 64 ++++++++++++++++++++++++++++++++++++\n 3 files changed, 104 insertions(+), 5 deletions(-)\n\ndiff --git a/string-list.c b/string-list.c\nindex 65b6ceb259..86a309f8fb 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -282,11 +282,18 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_\n  */\n static int append_one(struct string_list *list,\n \t\t      const char *p, const char *end,\n-\t\t      int in_place)\n+\t\t      int in_place, unsigned flags)\n {\n \tif (!end)\n \t\tend = p + strlen(p);\n \n+\tif ((flags & STRING_LIST_SPLIT_TRIM)) {\n+\t\t/* rtrim */\n+\t\tfor (; p < end; end--)\n+\t\t\tif (!isspace(end[-1]))\n+\t\t\t\tbreak;\n+\t}\n+\n \tif (in_place) {\n \t\t*((char *)end) = '\\0';\n \t\tstring_list_append(list, p);\n@@ -307,7 +314,7 @@ static int append_one(struct string_list *list,\n  * returns \"char *\" pointer into that const string.  Yucky but works ;-).\n  */\n static int split_string(struct string_list *list, const char *string, const char *delim,\n-\t\t\tint maxsplit, int in_place)\n+\t\t\tint maxsplit, int in_place, unsigned flags)\n {\n \tint count = 0;\n \tconst char *p = string;\n@@ -320,12 +327,18 @@ static int split_string(struct string_list *list, const char *string, const char\n \tfor (;;) {\n \t\tchar *end;\n \n+\t\tif (flags & STRING_LIST_SPLIT_TRIM) {\n+\t\t\t/* ltrim */\n+\t\t\twhile (*p && isspace(*p))\n+\t\t\t\tp++;\n+\t\t}\n+\n \t\tif (0 <= maxsplit && maxsplit <= count)\n \t\t\tend = NULL;\n \t\telse\n \t\t\tend = strpbrk(p, delim);\n \n-\t\tcount += append_one(list, p, end, in_place);\n+\t\tcount += append_one(list, p, end, in_place, flags);\n \n \t\tif (!end)\n \t\t\treturn count;\n@@ -336,11 +349,23 @@ static int split_string(struct string_list *list, const char *string, const char\n int string_list_split(struct string_list *list, const char *string,\n \t\t      const char *delim, int maxsplit)\n {\n-\treturn split_string(list, string, delim, maxsplit, 0);\n+\treturn split_string(list, string, delim, maxsplit, 0, 0);\n }\n \n int string_list_split_in_place(struct string_list *list, char *string,\n \t\t\t       const char *delim, int maxsplit)\n {\n-\treturn split_string(list, string, delim, maxsplit, 1);\n+\treturn split_string(list, string, delim, maxsplit, 1, 0);\n+}\n+\n+int string_list_split_f(struct string_list *list, const char *string,\n+\t\t\tconst char *delim, int maxsplit, unsigned flags)\n+{\n+\treturn split_string(list, string, delim, maxsplit, 0, flags);\n+}\n+\n+int string_list_split_in_place_f(struct string_list *list, char *string,\n+\t\t\t       const char *delim, int maxsplit, unsigned flags)\n+{\n+\treturn split_string(list, string, delim, maxsplit, 1, flags);\n }\ndiff --git a/string-list.h b/string-list.h\nindex 6c8650efde..ee9922af67 100644\n--- a/string-list.h\n+++ b/string-list.h\n@@ -281,4 +281,14 @@ int string_list_split(struct string_list *list, const char *string,\n  */\n int string_list_split_in_place(struct string_list *list, char *string,\n \t\t\t       const char *delim, int maxsplit);\n+\n+/* trim() resulting string piece before adding it to the list */\n+#define STRING_LIST_SPLIT_TRIM 01\n+\n+int string_list_split_f(struct string_list *, const char *string,\n+\t\t\tconst char *delim, int maxsplit, unsigned flags);\n+\n+int string_list_split_in_place_f(struct string_list *, char *string,\n+\t\t\t\t const char *delim, int maxsplit, unsigned flags);\n+\n #endif /* STRING_LIST_H */\ndiff --git a/t/unit-tests/u-string-list.c b/t/unit-tests/u-string-list.c\nindex 150a5f505f..daa9307e45 100644\n--- a/t/unit-tests/u-string-list.c\n+++ b/t/unit-tests/u-string-list.c\n@@ -63,6 +63,70 @@ static void t_string_list_split(const char *data, const char *delim, int maxspli\n \tstring_list_clear(&list, 0);\n }\n \n+static void t_string_list_split_f(const char *data, const char *delim,\n+\t\t\t\t  int maxsplit, unsigned flags, ...)\n+{\n+\tstruct string_list expected_strings = STRING_LIST_INIT_DUP;\n+\tstruct string_list list = STRING_LIST_INIT_DUP;\n+\tva_list ap;\n+\tint len;\n+\n+\tva_start(ap, flags);\n+\tt_vcreate_string_list_dup(&expected_strings, 0, ap);\n+\tva_end(ap);\n+\n+\tstring_list_clear(&list, 0);\n+\tlen = string_list_split_f(&list, data, delim, maxsplit, flags);\n+\tcl_assert_equal_i(len, expected_strings.nr);\n+\tt_string_list_equal(&list, &expected_strings);\n+\n+\tstring_list_clear(&expected_strings, 0);\n+\tstring_list_clear(&list, 0);\n+}\n+\n+void test_string_list__split_f(void)\n+{\n+\tt_string_list_split_f(\"::foo:bar:baz:\", \":\", -1, 0,\n+\t\t\t      \"\", \"\", \"foo\", \"bar\", \"baz\", \"\", NULL);\n+\tt_string_list_split_f(\" foo:bar : baz\", \":\", -1, STRING_LIST_SPLIT_TRIM,\n+\t\t\t      \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split_f(\"  a  b c  \", \" \", 1, STRING_LIST_SPLIT_TRIM,\n+\t\t\t      \"a\", \"b c\", NULL);\n+}\n+\n+static void t_string_list_split_in_place_f(const char *data_, const char *delim,\n+\t\t\t\t\t   int maxsplit, unsigned flags, ...)\n+{\n+\tstruct string_list expected_strings = STRING_LIST_INIT_DUP;\n+\tstruct string_list list = STRING_LIST_INIT_NODUP;\n+\tchar *data = xstrdup(data_);\n+\tva_list ap;\n+\tint len;\n+\n+\tva_start(ap, flags);\n+\tt_vcreate_string_list_dup(&expected_strings, 0, ap);\n+\tva_end(ap);\n+\n+\tstring_list_clear(&list, 0);\n+\tlen = string_list_split_in_place_f(&list, data, delim, maxsplit, flags);\n+\tcl_assert_equal_i(len, expected_strings.nr);\n+\tt_string_list_equal(&list, &expected_strings);\n+\n+\tfree(data);\n+\tstring_list_clear(&expected_strings, 0);\n+\tstring_list_clear(&list, 0);\n+}\n+\n+void test_string_list__split_in_place_f(void)\n+{\n+\tt_string_list_split_in_place_f(\"::foo:bar:baz:\", \":\", -1, 0,\n+\t\t\t\t       \"\", \"\", \"foo\", \"bar\", \"baz\", \"\", NULL);\n+\tt_string_list_split_in_place_f(\" foo:bar : baz\", \":\", -1, STRING_LIST_SPLIT_TRIM,\n+\t\t\t\t       \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split_in_place_f(\"  a  b c  \", \" \", 1, STRING_LIST_SPLIT_TRIM,\n+\t\t\t\t       \"a\", \"b c\", NULL);\n+}\n+\n void test_string_list__split(void)\n {\n \tt_string_list_split(\"foo:bar:baz\", \":\", -1, \"foo\", \"bar\", \"baz\", NULL);\n-- \n2.50.1-618-g45d530d26b\n\n"},{"id":"523136","messageId":"20250731224607.3942417-6-gitster@pobox.com","threadId":"63881","inReplyTo":"20250731224607.3942417-1-gitster@pobox.com","subject":"[PATCH v2 5/7] diff: simplify parsing of diff.colormovedws","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-31T22:46:04Z","receivedAt":"2025-07-31T22:46:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The code to parse this configuration variable, whose value is a\ncomma separated known tokens like \"ignore-space-change\" and\n\"ignore-all-space\", uses string_list_split() to split the value into\npieces, and then places each piece of string in a strbuf to trim,\nbefore comparing the result with the list of known tokens.\n\nThanks to the previous steps, now string_list_split() can trim the\nresulting pieces before it places them in the string list.  Use it\nto simplify the code.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n diff.c | 20 +++++++-------------\n 1 file changed, 7 insertions(+), 13 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex a81949a422..70666ad2cd 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -327,29 +327,23 @@ static unsigned parse_color_moved_ws(const char *arg)\n \tstruct string_list l = STRING_LIST_INIT_DUP;\n \tstruct string_list_item *i;\n \n-\tstring_list_split(&l, arg, \",\", -1);\n+\tstring_list_split_f(&l, arg, \",\", -1, STRING_LIST_SPLIT_TRIM);\n \n \tfor_each_string_list_item(i, &l) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\tstrbuf_addstr(&sb, i->string);\n-\t\tstrbuf_trim(&sb);\n-\n-\t\tif (!strcmp(sb.buf, \"no\"))\n+\t\tif (!strcmp(i->string, \"no\"))\n \t\t\tret = 0;\n-\t\telse if (!strcmp(sb.buf, \"ignore-space-change\"))\n+\t\telse if (!strcmp(i->string, \"ignore-space-change\"))\n \t\t\tret |= XDF_IGNORE_WHITESPACE_CHANGE;\n-\t\telse if (!strcmp(sb.buf, \"ignore-space-at-eol\"))\n+\t\telse if (!strcmp(i->string, \"ignore-space-at-eol\"))\n \t\t\tret |= XDF_IGNORE_WHITESPACE_AT_EOL;\n-\t\telse if (!strcmp(sb.buf, \"ignore-all-space\"))\n+\t\telse if (!strcmp(i->string, \"ignore-all-space\"))\n \t\t\tret |= XDF_IGNORE_WHITESPACE;\n-\t\telse if (!strcmp(sb.buf, \"allow-indentation-change\"))\n+\t\telse if (!strcmp(i->string, \"allow-indentation-change\"))\n \t\t\tret |= COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE;\n \t\telse {\n \t\t\tret |= COLOR_MOVED_WS_ERROR;\n-\t\t\terror(_(\"unknown color-moved-ws mode '%s', possible values are 'ignore-space-change', 'ignore-space-at-eol', 'ignore-all-space', 'allow-indentation-change'\"), sb.buf);\n+\t\t\terror(_(\"unknown color-moved-ws mode '%s', possible values are 'ignore-space-change', 'ignore-space-at-eol', 'ignore-all-space', 'allow-indentation-change'\"), i->string);\n \t\t}\n-\n-\t\tstrbuf_release(&sb);\n \t}\n \n \tif ((ret & COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) &&\n-- \n2.50.1-618-g45d530d26b\n\n"},{"id":"523138","messageId":"20250731224607.3942417-7-gitster@pobox.com","threadId":"63881","inReplyTo":"20250731224607.3942417-1-gitster@pobox.com","subject":"[PATCH v2 6/7] string-list: optionally omit empty string pieces in string_list_split*()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-31T22:46:05Z","receivedAt":"2025-07-31T22:46:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Teach the unified split_string() machinery a new flag bit,\nSTRING_LIST_SPLIT_NONEMPTY, to cause empty split pieces omitted from\nthe resulting string list.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n string-list.c                |  3 +++\n string-list.h                |  3 +++\n t/unit-tests/u-string-list.c | 15 +++++++++++++++\n 3 files changed, 21 insertions(+)\n\ndiff --git a/string-list.c b/string-list.c\nindex 86a309f8fb..343cf1ca90 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -294,6 +294,9 @@ static int append_one(struct string_list *list,\n \t\t\t\tbreak;\n \t}\n \n+\tif ((flags & STRING_LIST_SPLIT_NONEMPTY) && (end <= p))\n+\t\treturn 0;\n+\n \tif (in_place) {\n \t\t*((char *)end) = '\\0';\n \t\tstring_list_append(list, p);\ndiff --git a/string-list.h b/string-list.h\nindex ee9922af67..0f73064fd1 100644\n--- a/string-list.h\n+++ b/string-list.h\n@@ -285,6 +285,9 @@ int string_list_split_in_place(struct string_list *list, char *string,\n /* trim() resulting string piece before adding it to the list */\n #define STRING_LIST_SPLIT_TRIM 01\n \n+/* omit adding empty string piece to the resulting list */\n+#define STRING_LIST_SPLIT_NONEMPTY 02\n+\n int string_list_split_f(struct string_list *, const char *string,\n \t\t\tconst char *delim, int maxsplit, unsigned flags);\n \ndiff --git a/t/unit-tests/u-string-list.c b/t/unit-tests/u-string-list.c\nindex daa9307e45..a2457d7b1e 100644\n--- a/t/unit-tests/u-string-list.c\n+++ b/t/unit-tests/u-string-list.c\n@@ -92,6 +92,13 @@ void test_string_list__split_f(void)\n \t\t\t      \"foo\", \"bar\", \"baz\", NULL);\n \tt_string_list_split_f(\"  a  b c  \", \" \", 1, STRING_LIST_SPLIT_TRIM,\n \t\t\t      \"a\", \"b c\", NULL);\n+\tt_string_list_split_f(\"::foo::bar:baz:\", \":\", -1, STRING_LIST_SPLIT_NONEMPTY,\n+\t\t\t      \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split_f(\"foo:baz\", \":\", -1, STRING_LIST_SPLIT_NONEMPTY,\n+\t\t\t      \"foo\", \"baz\", NULL);\n+\tt_string_list_split_f(\"foo :: : baz\", \":\", -1,\n+\t\t\t      STRING_LIST_SPLIT_NONEMPTY | STRING_LIST_SPLIT_TRIM,\n+\t\t\t      \"foo\", \"baz\", NULL);\n }\n \n static void t_string_list_split_in_place_f(const char *data_, const char *delim,\n@@ -125,6 +132,14 @@ void test_string_list__split_in_place_f(void)\n \t\t\t\t       \"foo\", \"bar\", \"baz\", NULL);\n \tt_string_list_split_in_place_f(\"  a  b c  \", \" \", 1, STRING_LIST_SPLIT_TRIM,\n \t\t\t\t       \"a\", \"b c\", NULL);\n+\tt_string_list_split_in_place_f(\"::foo::bar:baz:\", \":\", -1,\n+\t\t\t\t       STRING_LIST_SPLIT_NONEMPTY,\n+\t\t\t\t       \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split_in_place_f(\"foo:baz\", \":\", -1, STRING_LIST_SPLIT_NONEMPTY,\n+\t\t\t\t       \"foo\", \"baz\", NULL);\n+\tt_string_list_split_in_place_f(\"foo :: : baz\", \":\", -1,\n+\t\t\t\t       STRING_LIST_SPLIT_NONEMPTY | STRING_LIST_SPLIT_TRIM,\n+\t\t\t\t       \"foo\", \"baz\", NULL);\n }\n \n void test_string_list__split(void)\n-- \n2.50.1-618-g45d530d26b\n\n"},{"id":"523139","messageId":"20250731224607.3942417-8-gitster@pobox.com","threadId":"63881","inReplyTo":"20250731224607.3942417-1-gitster@pobox.com","subject":"[PATCH v2 7/7] string-list: split-then-remove-empty can be done while splitting","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-31T22:46:06Z","receivedAt":"2025-07-31T22:46:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thanks to the new STRING_LIST_SPLIT_NONEMPTY flag, a common pattern\nto split a string into a string list and then remove empty items in\nthe resulting list is no longer needed.  Instead, just tell the\nstring_list_split*() to omit empty ones while splitting.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n notes.c                     | 4 ++--\n pathspec.c                  | 3 +--\n t/helper/test-hashmap.c     | 4 ++--\n t/helper/test-json-writer.c | 4 ++--\n 4 files changed, 7 insertions(+), 8 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex 6afcf088b9..3603c4a42b 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -970,8 +970,8 @@ void string_list_add_refs_from_colon_sep(struct string_list *list,\n \tchar *globs_copy = xstrdup(globs);\n \tint i;\n \n-\tstring_list_split_in_place(&split, globs_copy, \":\", -1);\n-\tstring_list_remove_empty_items(&split, 0);\n+\tstring_list_split_in_place_f(&split, globs_copy, \":\", -1,\n+\t\t\t\t     STRING_LIST_SPLIT_NONEMPTY);\n \n \tfor (i = 0; i < split.nr; i++)\n \t\tstring_list_add_refs_by_glob(list, split.items[i].string);\ndiff --git a/pathspec.c b/pathspec.c\nindex de325f7ef9..5993c4afa0 100644\n--- a/pathspec.c\n+++ b/pathspec.c\n@@ -201,8 +201,7 @@ static void parse_pathspec_attr_match(struct pathspec_item *item, const char *va\n \tif (!value || !*value)\n \t\tdie(_(\"attr spec must not be empty\"));\n \n-\tstring_list_split(&list, value, \" \", -1);\n-\tstring_list_remove_empty_items(&list, 0);\n+\tstring_list_split_f(&list, value, \" \", -1, STRING_LIST_SPLIT_NONEMPTY);\n \n \titem->attr_check = attr_check_alloc();\n \tCALLOC_ARRAY(item->attr_match, list.nr);\ndiff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c\nindex 7782ae585e..e4dc02bd7a 100644\n--- a/t/helper/test-hashmap.c\n+++ b/t/helper/test-hashmap.c\n@@ -149,8 +149,8 @@ int cmd__hashmap(int argc UNUSED, const char **argv UNUSED)\n \n \t\t/* break line into command and up to two parameters */\n \t\tstring_list_setlen(&parts, 0);\n-\t\tstring_list_split_in_place(&parts, line.buf, DELIM, 2);\n-\t\tstring_list_remove_empty_items(&parts, 0);\n+\t\tstring_list_split_in_place_f(&parts, line.buf, DELIM, 2,\n+\t\t\t\t\t     STRING_LIST_SPLIT_NONEMPTY);\n \n \t\t/* ignore empty lines */\n \t\tif (!parts.nr)\ndiff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c\nindex a288069b04..f8316a7d29 100644\n--- a/t/helper/test-json-writer.c\n+++ b/t/helper/test-json-writer.c\n@@ -492,8 +492,8 @@ static int scripted(void)\n \n \t\t/* break line into command and zero or more tokens */\n \t\tstring_list_setlen(&parts, 0);\n-\t\tstring_list_split_in_place(&parts, line, \" \", -1);\n-\t\tstring_list_remove_empty_items(&parts, 0);\n+\t\tstring_list_split_in_place_f(&parts, line, \" \", -1,\n+\t\t\t\t\t     STRING_LIST_SPLIT_NONEMPTY);\n \n \t\t/* ignore empty lines */\n \t\tif (!parts.nr || !*parts.items[0].string)\n-- \n2.50.1-618-g45d530d26b\n\n"},{"id":"523152","messageId":"CAPig+cQs=fCd8DYsuGyrK=e6xGpZE8zdyE5BTTCX1jpN-TpcgA@mail.gmail.com","threadId":"63881","inReplyTo":"20250731224607.3942417-7-gitster@pobox.com","subject":"Re: [PATCH v2 6/7] string-list: optionally omit empty string pieces in string_list_split*()","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-07-31T22:54:47Z","receivedAt":"2025-07-31T22:54:59Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, Jul 31, 2025 at 6:46 PM Junio C Hamano <gitster@pobox.com> wrote:\n> Teach the unified split_string() machinery a new flag bit,\n> STRING_LIST_SPLIT_NONEMPTY, to cause empty split pieces omitted from\n> the resulting string list.\n\ns/pieces/& to be/\n\n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n"},{"id":"523157","messageId":"aIwndQtEoKNXRG5z@mbp","threadId":"63881","inReplyTo":"20250731224607.3942417-3-gitster@pobox.com","subject":"Re: [PATCH v2 2/7] string-list: align string_list_split() with its _in_place() counterpart","fromName":"shejialuo","fromEmail":"shejialuo@gmail.com","sentAt":"2025-08-01T02:33:25Z","receivedAt":"2025-08-01T02:33:30Z","isPatch":true,"sender":{"key":"shejialuo@gmail.com","avatar":"https://avatars.githubusercontent.com/u/56911263?v=4"},"body":"On Thu, Jul 31, 2025 at 03:46:01PM -0700, Junio C Hamano wrote:\n> diff --git a/setup.c b/setup.c\n> index 6f52dab64c..b9f5eb8b51 100644\n> --- a/setup.c\n> +++ b/setup.c\n> @@ -1460,8 +1460,9 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n>  \n>  \tif (env_ceiling_dirs) {\n>  \t\tint empty_entry_found = 0;\n> +\t\tstatic const char path_sep[] = { PATH_SEP, '\\0' };\n>  \n\nI am a little confused why we need to use `static`? Would this function\nbe called many times?\n\nAnd I have a design question: by using \"PATH_SEP\", we need to convert\nthis character to be string. Should we create a new variable named\n\"PATH_SEP_STR\" or whatever to do that?\n\n> -\t\tstring_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);\n> +\t\tstring_list_split(&ceiling_dirs, env_ceiling_dirs, path_sep, -1);\n>  \t\tfilter_string_list(&ceiling_dirs, 0,\n>  \t\t\t\t   canonicalize_ceiling_entry, &empty_entry_found);\n>  \t\tceil_offset = longest_ancestor_length(dir->buf, &ceiling_dirs);\n\nThanks,\nJialuo\n"},{"id":"523158","messageId":"aIwtzHdDQPzl56KU@mbp","threadId":"63881","inReplyTo":"20250731224607.3942417-4-gitster@pobox.com","subject":"Re: [PATCH v2 3/7] string-list: unify string_list_split* functions","fromName":"shejialuo","fromEmail":"shejialuo@gmail.com","sentAt":"2025-08-01T03:00:28Z","receivedAt":"2025-08-01T03:00:32Z","isPatch":true,"sender":{"key":"shejialuo@gmail.com","avatar":"https://avatars.githubusercontent.com/u/56911263?v=4"},"body":"On Thu, Jul 31, 2025 at 03:46:02PM -0700, Junio C Hamano wrote:\n\n[snip]\n\n> +/*\n> + * append a substring [p..end] to list; return number of things it\n> + * appended to the list.\n> + */\n\nIn the following function, we would always return 1. So, I guess in the\nfollowing commits, there would be a case where we won't append the\nstring. And it is, in [PATCH v2 6/7], we would simply skip and return 0.\n\nAnd I have a design question, should we make \"append_one\" pure? It would\nsimply attend a string where start is `p` and end is `end`? Let's see in\nthe later patches whether we could do this.\n\n> +static int append_one(struct string_list *list,\n> +\t\t      const char *p, const char *end,\n> +\t\t      int in_place)\n> +{\n> +\tif (!end)\n> +\t\tend = p + strlen(p);\n> +\n> +\tif (in_place) {\n> +\t\t*((char *)end) = '\\0';\n> +\t\tstring_list_append(list, p);\n> +\t} else {\n> +\t\tstring_list_append_nodup(list, xmemdupz(p, end - p));\n> +\t}\n> +\treturn 1;\n\nThanks,\nJialuo\n"},{"id":"523159","messageId":"aIwyIJqEIdSdKINz@mbp","threadId":"63881","inReplyTo":"20250731224607.3942417-5-gitster@pobox.com","subject":"Re: [PATCH v2 4/7] string-list: optionally trim string pieces split by string_list_split*()","fromName":"shejialuo","fromEmail":"shejialuo@gmail.com","sentAt":"2025-08-01T03:18:56Z","receivedAt":"2025-08-01T03:19:00Z","isPatch":true,"sender":{"key":"shejialuo@gmail.com","avatar":"https://avatars.githubusercontent.com/u/56911263?v=4"},"body":"On Thu, Jul 31, 2025 at 03:46:03PM -0700, Junio C Hamano wrote:\n>  static int split_string(struct string_list *list, const char *string, const char *delim,\n> -\t\t\tint maxsplit, int in_place)\n> +\t\t\tint maxsplit, int in_place, unsigned flags)\n>  {\n>  \tint count = 0;\n>  \tconst char *p = string;\n> @@ -320,12 +327,18 @@ static int split_string(struct string_list *list, const char *string, const char\n>  \tfor (;;) {\n>  \t\tchar *end;\n>  \n> +\t\tif (flags & STRING_LIST_SPLIT_TRIM) {\n> +\t\t\t/* ltrim */\n> +\t\t\twhile (*p && isspace(*p))\n> +\t\t\t\tp++;\n> +\t\t}\n> +\n>  \t\tif (0 <= maxsplit && maxsplit <= count)\n>  \t\t\tend = NULL;\n>  \t\telse\n>  \t\t\tend = strpbrk(p, delim);\n>  \n\nIn `append_one`, we would tell whether `end` is NULL. I somehow feel\nstrange why we need to do that in `append_one`. Should we just set `end`\nto be `p + strlen(p)` when `end` is NULL. And then we could do rtrim\ninside this function instead of `append_one` to avoid passing \"flags\" to\n`append_one`.\n\n> -\t\tcount += append_one(list, p, end, in_place);\n> +\t\tcount += append_one(list, p, end, in_place, flags);\n>  \n>  \t\tif (!end)\n>  \t\t\treturn count;\n\nThanks,\nJialuo\n"},{"id":"523160","messageId":"aIw1niPrhuvt8QoQ@mbp","threadId":"63881","inReplyTo":"20250731224607.3942417-7-gitster@pobox.com","subject":"Re: [PATCH v2 6/7] string-list: optionally omit empty string pieces in string_list_split*()","fromName":"shejialuo","fromEmail":"shejialuo@gmail.com","sentAt":"2025-08-01T03:33:50Z","receivedAt":"2025-08-01T03:33:55Z","isPatch":true,"sender":{"key":"shejialuo@gmail.com","avatar":"https://avatars.githubusercontent.com/u/56911263?v=4"},"body":"On Thu, Jul 31, 2025 at 03:46:05PM -0700, Junio C Hamano wrote:\n> diff --git a/string-list.c b/string-list.c\n> index 86a309f8fb..343cf1ca90 100644\n> --- a/string-list.c\n> +++ b/string-list.c\n> @@ -294,6 +294,9 @@ static int append_one(struct string_list *list,\n>  \t\t\t\tbreak;\n>  \t}\n>  \n> +\tif ((flags & STRING_LIST_SPLIT_NONEMPTY) && (end <= p))\n> +\t\treturn 0;\n> +\n\nI somehow think we should do this directly in `split_string` function.\nAnd should we use `end == p`?\n\nThanks,\nJialuo\n"},{"id":"523161","messageId":"xmqqikj7ogf7.fsf@gitster.g","threadId":"63881","inReplyTo":"aIwndQtEoKNXRG5z@mbp","subject":"Re: [PATCH v2 2/7] string-list: align string_list_split() with its _in_place() counterpart","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-01T03:43:24Z","receivedAt":"2025-08-01T03:43:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"shejialuo <shejialuo@gmail.com> writes:\n\n> On Thu, Jul 31, 2025 at 03:46:01PM -0700, Junio C Hamano wrote:\n>> diff --git a/setup.c b/setup.c\n>> index 6f52dab64c..b9f5eb8b51 100644\n>> --- a/setup.c\n>> +++ b/setup.c\n>> @@ -1460,8 +1460,9 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n>>  \n>>  \tif (env_ceiling_dirs) {\n>>  \t\tint empty_entry_found = 0;\n>> +\t\tstatic const char path_sep[] = { PATH_SEP, '\\0' };\n>>  \n>\n> I am a little confused why we need to use `static`? Would this function\n> be called many times?\n\nI actually am confused why you would want anything other than static\nhere.  Writing this way would allow the compiler to realize that the\narray can be prepared at compile time, without need to do anything\nat runtime.  If you made it non static, the runtime code would\nallocate two bytes worth of memory on stack, and stuff these two\nbyte values there, each time this block is entered, which would be\nat least once.\n\n> And I have a design question: by using \"PATH_SEP\", we need to convert\n> this character to be string. Should we create a new variable named\n> \"PATH_SEP_STR\" or whatever to do that?\n\nSorry, but I do not understand the question.  You want to see\nsomething like\n\n\t#define PATH_SEP_STR \"/\"\n\nyou mean?  I do not offhand see why anybody would want to do so.\n\n>> -\t\tstring_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);\n>> +\t\tstring_list_split(&ceiling_dirs, env_ceiling_dirs, path_sep, -1);\n>>  \t\tfilter_string_list(&ceiling_dirs, 0,\n>>  \t\t\t\t   canonicalize_ceiling_entry, &empty_entry_found);\n>>  \t\tceil_offset = longest_ancestor_length(dir->buf, &ceiling_dirs);\n>\n> Thanks,\n> Jialuo\n"},{"id":"523162","messageId":"xmqqcy9fog8x.fsf@gitster.g","threadId":"63881","inReplyTo":"aIwyIJqEIdSdKINz@mbp","subject":"Re: [PATCH v2 4/7] string-list: optionally trim string pieces split by string_list_split*()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-01T03:47:10Z","receivedAt":"2025-08-01T03:47:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"shejialuo <shejialuo@gmail.com> writes:\n\n> On Thu, Jul 31, 2025 at 03:46:03PM -0700, Junio C Hamano wrote:\n>>  static int split_string(struct string_list *list, const char *string, const char *delim,\n>> -\t\t\tint maxsplit, int in_place)\n>> +\t\t\tint maxsplit, int in_place, unsigned flags)\n>>  {\n>>  \tint count = 0;\n>>  \tconst char *p = string;\n>> @@ -320,12 +327,18 @@ static int split_string(struct string_list *list, const char *string, const char\n>>  \tfor (;;) {\n>>  \t\tchar *end;\n>>  \n>> +\t\tif (flags & STRING_LIST_SPLIT_TRIM) {\n>> +\t\t\t/* ltrim */\n>> +\t\t\twhile (*p && isspace(*p))\n>> +\t\t\t\tp++;\n>> +\t\t}\n>> +\n>>  \t\tif (0 <= maxsplit && maxsplit <= count)\n>>  \t\t\tend = NULL;\n>>  \t\telse\n>>  \t\t\tend = strpbrk(p, delim);\n>>  \n>\n> In `append_one`, we would tell whether `end` is NULL. I somehow feel\n> strange why we need to do that in `append_one`. Should we just set `end`\n> to be `p + strlen(p)` when `end` is NULL. And then we could do rtrim\n> inside this function instead of `append_one` to avoid passing \"flags\" to\n> `append_one`.\n\nSorry, but I do not see why such an alternative design is a better\nidea.  The helper function's purpose is to stuff the substring at\n[p..end), possibly after rtrimming, to the list.  You could compute\nrtrim in the caller, but that would make the logic here more complex\n(at least, you'd need to introduce yet another variable similar to\n\"end\" that points at the real tail of the string, and you cannot\nreuse \"end\" for it, because of the exit condition you see below).\n\n>> -\t\tcount += append_one(list, p, end, in_place);\n>> +\t\tcount += append_one(list, p, end, in_place, flags);\n>>  \n>>  \t\tif (!end)\n>>  \t\t\treturn count;\n>\n> Thanks,\n> Jialuo\n"},{"id":"523164","messageId":"aIw6ojvvDWLPllG7@mbp","threadId":"63881","inReplyTo":"xmqqikj7ogf7.fsf@gitster.g","subject":"Re: [PATCH v2 2/7] string-list: align string_list_split() with its _in_place() counterpart","fromName":"shejialuo","fromEmail":"shejialuo@gmail.com","sentAt":"2025-08-01T03:55:14Z","receivedAt":"2025-08-01T03:55:18Z","isPatch":true,"sender":{"key":"shejialuo@gmail.com","avatar":"https://avatars.githubusercontent.com/u/56911263?v=4"},"body":"On Thu, Jul 31, 2025 at 08:43:24PM -0700, Junio C Hamano wrote:\n> shejialuo <shejialuo@gmail.com> writes:\n> \n> > On Thu, Jul 31, 2025 at 03:46:01PM -0700, Junio C Hamano wrote:\n> >> diff --git a/setup.c b/setup.c\n> >> index 6f52dab64c..b9f5eb8b51 100644\n> >> --- a/setup.c\n> >> +++ b/setup.c\n> >> @@ -1460,8 +1460,9 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n> >>  \n> >>  \tif (env_ceiling_dirs) {\n> >>  \t\tint empty_entry_found = 0;\n> >> +\t\tstatic const char path_sep[] = { PATH_SEP, '\\0' };\n> >>  \n> >\n> > I am a little confused why we need to use `static`? Would this function\n> > be called many times?\n> \n> I actually am confused why you would want anything other than static\n> here.  Writing this way would allow the compiler to realize that the\n> array can be prepared at compile time, without need to do anything\n> at runtime.  If you made it non static, the runtime code would\n> allocate two bytes worth of memory on stack, and stuff these two\n> byte values there, each time this block is entered, which would be\n> at least once.\n> \n\nSorry to make you confused. Because there are some other changes where\nyou don't use `static`. That's the main reason why I ask this question.\n\n--- a/t/helper/test-path-utils.c\n+++ b/t/helper/test-path-utils.c\n@@ -348,6 +348,7 @@ int cmd__path_utils(int argc, const char **argv)\n \tif (argc == 4 && !strcmp(argv[1], \"longest_ancestor_length\")) {\n \t\tint len;\n \t\tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n+\t\tconst char path_sep[] = { PATH_SEP, '\\0' };\n \t\tchar *path = xstrdup(argv[2]);\n\n> > And I have a design question: by using \"PATH_SEP\", we need to convert\n> > this character to be string. Should we create a new variable named\n> > \"PATH_SEP_STR\" or whatever to do that?\n> \n> Sorry, but I do not understand the question.  You want to see\n> something like\n> \n> \t#define PATH_SEP_STR \"/\"\n> \n> you mean?  I do not offhand see why anybody would want to do so.\n> \n\nYes, that's my question. Because I see that we would define `path_sep`\narray in many place, so I wonder whether we could use such macro.\n"},{"id":"523165","messageId":"aIw85gIurfEhvuPC@mbp","threadId":"63881","inReplyTo":"xmqqcy9fog8x.fsf@gitster.g","subject":"Re: [PATCH v2 4/7] string-list: optionally trim string pieces split by string_list_split*()","fromName":"shejialuo","fromEmail":"shejialuo@gmail.com","sentAt":"2025-08-01T04:04:54Z","receivedAt":"2025-08-01T04:04:59Z","isPatch":true,"sender":{"key":"shejialuo@gmail.com","avatar":"https://avatars.githubusercontent.com/u/56911263?v=4"},"body":"On Thu, Jul 31, 2025 at 08:47:10PM -0700, Junio C Hamano wrote:\n> shejialuo <shejialuo@gmail.com> writes:\n> \n> > On Thu, Jul 31, 2025 at 03:46:03PM -0700, Junio C Hamano wrote:\n> >>  static int split_string(struct string_list *list, const char *string, const char *delim,\n> >> -\t\t\tint maxsplit, int in_place)\n> >> +\t\t\tint maxsplit, int in_place, unsigned flags)\n> >>  {\n> >>  \tint count = 0;\n> >>  \tconst char *p = string;\n> >> @@ -320,12 +327,18 @@ static int split_string(struct string_list *list, const char *string, const char\n> >>  \tfor (;;) {\n> >>  \t\tchar *end;\n> >>  \n> >> +\t\tif (flags & STRING_LIST_SPLIT_TRIM) {\n> >> +\t\t\t/* ltrim */\n> >> +\t\t\twhile (*p && isspace(*p))\n> >> +\t\t\t\tp++;\n> >> +\t\t}\n> >> +\n> >>  \t\tif (0 <= maxsplit && maxsplit <= count)\n> >>  \t\t\tend = NULL;\n> >>  \t\telse\n> >>  \t\t\tend = strpbrk(p, delim);\n> >>  \n> >\n> > In `append_one`, we would tell whether `end` is NULL. I somehow feel\n> > strange why we need to do that in `append_one`. Should we just set `end`\n> > to be `p + strlen(p)` when `end` is NULL. And then we could do rtrim\n> > inside this function instead of `append_one` to avoid passing \"flags\" to\n> > `append_one`.\n> \n> Sorry, but I do not see why such an alternative design is a better\n> idea.  The helper function's purpose is to stuff the substring at\n> [p..end), possibly after rtrimming, to the list.  You could compute\n> rtrim in the caller, but that would make the logic here more complex\n> (at least, you'd need to introduce yet another variable similar to\n> \"end\" that points at the real tail of the string, and you cannot\n> reuse \"end\" for it, because of the exit condition you see below).\n> \n\nI agree with you that we would introduce another variable. However, the\nthing I quite dislike is that we do ltrim inside the current function\nand we do rtrim inside `append_one`.\n\nMy thinking is that we should handle the [p, end) string in the same\nplace. We could either decide to drop the string or change the string in\nthe same place. However, at now, the logic happens at two different\nplaces, which is my concern.\n"},{"id":"523178","messageId":"aIx_HIjWEDx9pNW2@pks.im","threadId":"63881","inReplyTo":"20250731224607.3942417-5-gitster@pobox.com","subject":"Re: [PATCH v2 4/7] string-list: optionally trim string pieces split by string_list_split*()","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-08-01T08:47:24Z","receivedAt":"2025-08-01T08:47:30Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jul 31, 2025 at 03:46:03PM -0700, Junio C Hamano wrote:\n> diff --git a/string-list.c b/string-list.c\n> index 65b6ceb259..86a309f8fb 100644\n> --- a/string-list.c\n> +++ b/string-list.c\n> @@ -336,11 +349,23 @@ static int split_string(struct string_list *list, const char *string, const char\n>  int string_list_split(struct string_list *list, const char *string,\n>  \t\t      const char *delim, int maxsplit)\n>  {\n> -\treturn split_string(list, string, delim, maxsplit, 0);\n> +\treturn split_string(list, string, delim, maxsplit, 0, 0);\n>  }\n>  \n>  int string_list_split_in_place(struct string_list *list, char *string,\n>  \t\t\t       const char *delim, int maxsplit)\n>  {\n> -\treturn split_string(list, string, delim, maxsplit, 1);\n> +\treturn split_string(list, string, delim, maxsplit, 1, 0);\n> +}\n> +\n> +int string_list_split_f(struct string_list *list, const char *string,\n> +\t\t\tconst char *delim, int maxsplit, unsigned flags)\n> +{\n> +\treturn split_string(list, string, delim, maxsplit, 0, flags);\n> +}\n> +\n> +int string_list_split_in_place_f(struct string_list *list, char *string,\n> +\t\t\t       const char *delim, int maxsplit, unsigned flags)\n> +{\n> +\treturn split_string(list, string, delim, maxsplit, 1, flags);\n>  }\n\nOne issue I have with the `_f` suffix is that I immediately jumped\nto \"formatting string\". I think in other places we use `_ext` as a\nsuffix.\n\n> diff --git a/string-list.h b/string-list.h\n> index 6c8650efde..ee9922af67 100644\n> --- a/string-list.h\n> +++ b/string-list.h\n> @@ -281,4 +281,14 @@ int string_list_split(struct string_list *list, const char *string,\n>   */\n>  int string_list_split_in_place(struct string_list *list, char *string,\n>  \t\t\t       const char *delim, int maxsplit);\n> +\n> +/* trim() resulting string piece before adding it to the list */\n> +#define STRING_LIST_SPLIT_TRIM 01\n\nAnother nit: I think nowadays we more often use enums to introduce such\nflags, where the benefit is improved grouping. Also, I think having\n`(1 << 0)` as value is slightly more readable.\n\nPatrick\n"},{"id":"523179","messageId":"aIx_InP6HRn7bO6k@pks.im","threadId":"63881","inReplyTo":"20250731224607.3942417-6-gitster@pobox.com","subject":"Re: [PATCH v2 5/7] diff: simplify parsing of diff.colormovedws","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-08-01T08:47:30Z","receivedAt":"2025-08-01T08:47:35Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jul 31, 2025 at 03:46:04PM -0700, Junio C Hamano wrote:\n> The code to parse this configuration variable, whose value is a\n> comma separated known tokens like \"ignore-space-change\" and\n\nShould this read \"comma-separated list of known tokens\"?\n\n> \"ignore-all-space\", uses string_list_split() to split the value into\n> pieces, and then places each piece of string in a strbuf to trim,\n> before comparing the result with the list of known tokens.\n> \n> Thanks to the previous steps, now string_list_split() can trim the\n> resulting pieces before it places them in the string list.  Use it\n> to simplify the code.\n\nThe change itself makes sense.\n\nPatrick\n"},{"id":"523180","messageId":"aIx_KAhKhGedQ0mV@pks.im","threadId":"63881","inReplyTo":"20250731224607.3942417-7-gitster@pobox.com","subject":"Re: [PATCH v2 6/7] string-list: optionally omit empty string pieces in string_list_split*()","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-08-01T08:47:36Z","receivedAt":"2025-08-01T08:47:41Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jul 31, 2025 at 03:46:05PM -0700, Junio C Hamano wrote:\n> Teach the unified split_string() machinery a new flag bit,\n> STRING_LIST_SPLIT_NONEMPTY, to cause empty split pieces omitted from\n\ns/omitted/to be &/\n\n> the resulting string list.\n> \n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> ---\n>  string-list.c                |  3 +++\n>  string-list.h                |  3 +++\n>  t/unit-tests/u-string-list.c | 15 +++++++++++++++\n>  3 files changed, 21 insertions(+)\n> \n> diff --git a/string-list.c b/string-list.c\n> index 86a309f8fb..343cf1ca90 100644\n> --- a/string-list.c\n> +++ b/string-list.c\n> @@ -294,6 +294,9 @@ static int append_one(struct string_list *list,\n>  \t\t\t\tbreak;\n>  \t}\n>  \n> +\tif ((flags & STRING_LIST_SPLIT_NONEMPTY) && (end <= p))\n> +\t\treturn 0;\n\nOkay, this is where the return value of `append_one()` starts to make\nsense.\n\nThe condition for `end <= p` is probably overly defensive, as it\nshouldn't ever happen that `end < p`. We could make that a `BUG()`, but\nI'm not sure that's really worth it.\n\nPatrick\n"},{"id":"523181","messageId":"aIx_Lp67GZC9fHaF@pks.im","threadId":"63881","inReplyTo":"20250731224607.3942417-8-gitster@pobox.com","subject":"Re: [PATCH v2 7/7] string-list: split-then-remove-empty can be done while splitting","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-08-01T08:47:42Z","receivedAt":"2025-08-01T08:47:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jul 31, 2025 at 03:46:06PM -0700, Junio C Hamano wrote:\n> Thanks to the new STRING_LIST_SPLIT_NONEMPTY flag, a common pattern\n> to split a string into a string list and then remove empty items in\n> the resulting list is no longer needed.  Instead, just tell the\n> string_list_split*() to omit empty ones while splitting.\n\nNeat.\n\nPatrick\n"},{"id":"523220","messageId":"xmqq4iurnh36.fsf@gitster.g","threadId":"63881","inReplyTo":"aIx_HIjWEDx9pNW2@pks.im","subject":"Re: [PATCH v2 4/7] string-list: optionally trim string pieces split by string_list_split*()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-01T16:26:37Z","receivedAt":"2025-08-01T16:26:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> One issue I have with the `_f` suffix is that I immediately jumped\n> to \"formatting string\". I think in other places we use `_ext` as a\n> suffix.\n\nIt stands for \"with flags\".  I've seen _with_options and _extended\nalso used.  This is like oid_object_info_extended() that has a\nvariant oid_object_info() that is a simpler and less capable wrapper\nfor common use cases.  None of these overly long names are my\nfavourites X-<.\n\nWith brief inspection, many _ext() in midx.c are more like helpers\nthat deal with a class of files with .$ext for various extensions;\nthey are not the primary interface to external callers, and many are\nextern only because the code is spread across midx. and midx-write.c\ninstead of being in a single compilation unit.\n\n>> diff --git a/string-list.h b/string-list.h\n>> index 6c8650efde..ee9922af67 100644\n>> --- a/string-list.h\n>> +++ b/string-list.h\n>> @@ -281,4 +281,14 @@ int string_list_split(struct string_list *list, const char *string,\n>>   */\n>>  int string_list_split_in_place(struct string_list *list, char *string,\n>>  \t\t\t       const char *delim, int maxsplit);\n>> +\n>> +/* trim() resulting string piece before adding it to the list */\n>> +#define STRING_LIST_SPLIT_TRIM 01\n>\n> Another nit: I think nowadays we more often use enums to introduce such\n> flags, where the benefit is improved grouping. Also, I think having\n> `(1 << 0)` as value is slightly more readable.\n\nOK, let's update that.  Thanks.\n"},{"id":"523223","messageId":"xmqqzfcjm1za.fsf@gitster.g","threadId":"63881","inReplyTo":"aIx_KAhKhGedQ0mV@pks.im","subject":"Re: [PATCH v2 6/7] string-list: optionally omit empty string pieces in string_list_split*()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-01T16:38:17Z","receivedAt":"2025-08-01T16:38:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n>> +\tif ((flags & STRING_LIST_SPLIT_NONEMPTY) && (end <= p))\n>> +\t\treturn 0;\n>\n> Okay, this is where the return value of `append_one()` starts to make\n> sense.\n>\n> The condition for `end <= p` is probably overly defensive, as it\n> shouldn't ever happen that `end < p`. We could make that a `BUG()`, but\n> I'm not sure that's really worth it.\n\nCorrect.  I'd leave it to be defensive but without overly\npessimistic BUG, as this is a leaf function that, once carefully\nvetted, is unlikely to become buggy (which is famous last words).\n"},{"id":"523246","messageId":"20250801220423.1230969-1-gitster@pobox.com","threadId":"63881","inReplyTo":"20250731224607.3942417-1-gitster@pobox.com","subject":"[PATCH v3 0/7] string_list_split*() updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-01T22:04:16Z","receivedAt":"2025-08-01T22:04:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Two related string-list API functions, string_list_split() and\nstring_list_split_in_place(), more or less duplicates their\nimplementations.  They both take a single string, and split the\nstring at the delimiter and stuff the result into a string list.\n\nHowever, there is one subtle and unnecessary difference.  The non\n\"in-place\" variant only allows a single byte value as delimiter,\nwhile the \"in-place\" variant can take multiple delimiters (e.g.,\n\"split at either a comma or a space\").\n\nThis series first updates the string_list_split() to allow multiple\ndelimiters like string_list_split_in_place() does, by unifying their\nimplementations into one.  This refactoring allows us to give new\nfeatures to these two functions more easily.\n\nThen these functions learn to optionally\n\n - trim the split string pieces before placing them in the resulting\n   string list.\n\n - omit empty string pieces from the resulting string list.\n\nAn existing caller of string_list_split() in diff.c trims the\nelements in the resulting string list before it uses them, which is\nsimplified by taking advantage of this new feature.\n\nA handful of code paths call string_list_split*(), immediately\nfollowed by string_list_remove_empty_items().  They are simplified\nby not placing empty items in the list in the first place.\n\n\n\nRelative to the v2 iteration, the v3 iteration switches from CPP\nmacros to enum for flag bits, and corrects a handful of typos.\n\nJunio C Hamano (7):\n  string-list: report programming error with BUG\n  string-list: align string_list_split() with its _in_place()\n    counterpart\n  string-list: unify string_list_split* functions\n  string-list: optionally trim string pieces split by\n    string_list_split*()\n  diff: simplify parsing of diff.colormovedws\n  string-list: optionally omit empty string pieces in\n    string_list_split*()\n  string-list: split-then-remove-empty can be done while splitting\n\n builtin/blame.c              |   2 +-\n builtin/merge.c              |   2 +-\n builtin/var.c                |   2 +-\n connect.c                    |   2 +-\n diff.c                       |  20 ++----\n fetch-pack.c                 |   2 +-\n notes.c                      |   6 +-\n parse-options.c              |   2 +-\n pathspec.c                   |   3 +-\n protocol.c                   |   2 +-\n ref-filter.c                 |   4 +-\n setup.c                      |   3 +-\n string-list.c                | 120 ++++++++++++++++++++++++-----------\n string-list.h                |  30 ++++++---\n t/helper/test-hashmap.c      |   4 +-\n t/helper/test-json-writer.c  |   4 +-\n t/helper/test-path-utils.c   |   3 +-\n t/helper/test-ref-store.c    |   2 +-\n t/unit-tests/u-string-list.c |  95 ++++++++++++++++++++++++---\n transport.c                  |   2 +-\n upload-pack.c                |   2 +-\n 21 files changed, 222 insertions(+), 90 deletions(-)\n\nRange-diff against v2:\n1:  1c2b222eec = 1:  442ed679bb string-list: report programming error with BUG\n2:  a7e07b94ef = 2:  cc80bac8c2 string-list: align string_list_split() with its _in_place() counterpart\n3:  b7a7fbb975 = 3:  c7922b3e14 string-list: unify string_list_split* functions\n4:  c566d88c28 ! 4:  9d7d22e8ef string-list: optionally trim string pieces split by string_list_split*()\n    @@ string-list.h: int string_list_split(struct string_list *list, const char *strin\n      int string_list_split_in_place(struct string_list *list, char *string,\n      \t\t\t       const char *delim, int maxsplit);\n     +\n    -+/* trim() resulting string piece before adding it to the list */\n    -+#define STRING_LIST_SPLIT_TRIM 01\n    ++/* flag bits for split_f and split_in_place_f functions */\n    ++enum {\n    ++\t/* trim() resulting string piece before adding it to the list */\n    ++\tSTRING_LIST_SPLIT_TRIM = (1 << 0),\n    ++};\n     +\n     +int string_list_split_f(struct string_list *, const char *string,\n     +\t\t\tconst char *delim, int maxsplit, unsigned flags);\n     +\n     +int string_list_split_in_place_f(struct string_list *, char *string,\n     +\t\t\t\t const char *delim, int maxsplit, unsigned flags);\n    -+\n      #endif /* STRING_LIST_H */\n     \n      ## t/unit-tests/u-string-list.c ##\n5:  eb272e0f22 ! 5:  ad8b425bc5 diff: simplify parsing of diff.colormovedws\n    @@ Commit message\n         diff: simplify parsing of diff.colormovedws\n     \n         The code to parse this configuration variable, whose value is a\n    -    comma separated known tokens like \"ignore-space-change\" and\n    +    comma-separated list of known tokens like \"ignore-space-change\" and\n         \"ignore-all-space\", uses string_list_split() to split the value into\n         pieces, and then places each piece of string in a strbuf to trim,\n         before comparing the result with the list of known tokens.\n6:  d418078a84 ! 6:  d03f443878 string-list: optionally omit empty string pieces in string_list_split*()\n    @@ Commit message\n         string-list: optionally omit empty string pieces in string_list_split*()\n     \n         Teach the unified split_string() machinery a new flag bit,\n    -    STRING_LIST_SPLIT_NONEMPTY, to cause empty split pieces omitted from\n    -    the resulting string list.\n    +    STRING_LIST_SPLIT_NONEMPTY, to cause empty split pieces to be\n    +    omitted from the resulting string list.\n     \n         Signed-off-by: Junio C Hamano <gitster@pobox.com>\n     \n    @@ string-list.c: static int append_one(struct string_list *list,\n     \n      ## string-list.h ##\n     @@ string-list.h: int string_list_split_in_place(struct string_list *list, char *string,\n    - /* trim() resulting string piece before adding it to the list */\n    - #define STRING_LIST_SPLIT_TRIM 01\n    + enum {\n    + \t/* trim() resulting string piece before adding it to the list */\n    + \tSTRING_LIST_SPLIT_TRIM = (1 << 0),\n    ++\t/* omit adding empty string piece to the resulting list */\n    ++\tSTRING_LIST_SPLIT_NONEMPTY = (1 << 1),\n    + };\n      \n    -+/* omit adding empty string piece to the resulting list */\n    -+#define STRING_LIST_SPLIT_NONEMPTY 02\n    -+\n      int string_list_split_f(struct string_list *, const char *string,\n    - \t\t\tconst char *delim, int maxsplit, unsigned flags);\n    - \n     \n      ## t/unit-tests/u-string-list.c ##\n     @@ t/unit-tests/u-string-list.c: void test_string_list__split_f(void)\n7:  12c1189a08 = 7:  9eb8d87d62 string-list: split-then-remove-empty can be done while splitting\n-- \n2.50.1-633-g85c5610de3\n\n"},{"id":"523247","messageId":"20250801220423.1230969-2-gitster@pobox.com","threadId":"63881","inReplyTo":"20250801220423.1230969-1-gitster@pobox.com","subject":"[PATCH v3 1/7] string-list: report programming error with BUG","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-01T22:04:17Z","receivedAt":"2025-08-01T22:04:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Passing a string list that has .strdup_strings bit unset to\nstring_list_split(), or one that has .strdup_strings bit set to\nstring_list_split_in_place(), is a programmer error.  Do not use\ndie() to abort the execution.  Use BUG() instead.\n\nAs a developer-facing message, the message string itself should\nbe a lot more concise, but let's keep the original one for now.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n string-list.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/string-list.c b/string-list.c\nindex 53faaa8420..0cb920e9b0 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -283,7 +283,7 @@ int string_list_split(struct string_list *list, const char *string,\n \tconst char *p = string, *end;\n \n \tif (!list->strdup_strings)\n-\t\tdie(\"internal error in string_list_split(): \"\n+\t\tBUG(\"internal error in string_list_split(): \"\n \t\t    \"list->strdup_strings must be set\");\n \tfor (;;) {\n \t\tcount++;\n@@ -309,7 +309,7 @@ int string_list_split_in_place(struct string_list *list, char *string,\n \tchar *p = string, *end;\n \n \tif (list->strdup_strings)\n-\t\tdie(\"internal error in string_list_split_in_place(): \"\n+\t\tBUG(\"internal error in string_list_split_in_place(): \"\n \t\t    \"list->strdup_strings must not be set\");\n \tfor (;;) {\n \t\tcount++;\n-- \n2.50.1-633-g85c5610de3\n\n"},{"id":"523248","messageId":"20250801220423.1230969-3-gitster@pobox.com","threadId":"63881","inReplyTo":"20250801220423.1230969-1-gitster@pobox.com","subject":"[PATCH v3 2/7] string-list: align string_list_split() with its _in_place() counterpart","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-01T22:04:18Z","receivedAt":"2025-08-01T22:04:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"For some unknown reason, unlike string_list_split_in_place(),\nstring_list_split() took only a single character as a field\ndelimiter.  Before giving both functions more features in future\ncommits, allow string_list_split() to take more than one delimiter\ncharacters to make them closer to each other.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/blame.c              |  2 +-\n builtin/merge.c              |  2 +-\n builtin/var.c                |  2 +-\n connect.c                    |  2 +-\n diff.c                       |  2 +-\n fetch-pack.c                 |  2 +-\n notes.c                      |  2 +-\n parse-options.c              |  2 +-\n pathspec.c                   |  2 +-\n protocol.c                   |  2 +-\n ref-filter.c                 |  4 ++--\n setup.c                      |  3 ++-\n string-list.c                |  4 ++--\n string-list.h                | 16 ++++++++--------\n t/helper/test-path-utils.c   |  3 ++-\n t/helper/test-ref-store.c    |  2 +-\n t/unit-tests/u-string-list.c | 16 ++++++++--------\n transport.c                  |  2 +-\n upload-pack.c                |  2 +-\n 19 files changed, 37 insertions(+), 35 deletions(-)\n\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 91586e6852..70a6460401 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -420,7 +420,7 @@ static void parse_color_fields(const char *s)\n \tcolorfield_nr = 0;\n \n \t/* Ideally this would be stripped and split at the same time? */\n-\tstring_list_split(&l, s, ',', -1);\n+\tstring_list_split(&l, s, \",\", -1);\n \tALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc);\n \n \tfor_each_string_list_item(item, &l) {\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 18b22c0a26..893f8950bf 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -875,7 +875,7 @@ static void add_strategies(const char *string, unsigned attr)\n \tif (string) {\n \t\tstruct string_list list = STRING_LIST_INIT_DUP;\n \t\tstruct string_list_item *item;\n-\t\tstring_list_split(&list, string, ' ', -1);\n+\t\tstring_list_split(&list, string, \" \", -1);\n \t\tfor_each_string_list_item(item, &list)\n \t\t\tappend_strategy(get_strategy(item->string));\n \t\tstring_list_clear(&list, 0);\ndiff --git a/builtin/var.c b/builtin/var.c\nindex ada642a9fe..4ae7af0eff 100644\n--- a/builtin/var.c\n+++ b/builtin/var.c\n@@ -181,7 +181,7 @@ static void list_vars(void)\n \t\t\tif (ptr->multivalued && *val) {\n \t\t\t\tstruct string_list list = STRING_LIST_INIT_DUP;\n \n-\t\t\t\tstring_list_split(&list, val, '\\n', -1);\n+\t\t\t\tstring_list_split(&list, val, \"\\n\", -1);\n \t\t\t\tfor (size_t i = 0; i < list.nr; i++)\n \t\t\t\t\tprintf(\"%s=%s\\n\", ptr->name, list.items[i].string);\n \t\t\t\tstring_list_clear(&list, 0);\ndiff --git a/connect.c b/connect.c\nindex e77287f426..867b12bde5 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -407,7 +407,7 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list,\n \t * name.  Subsequent fields (symref-target and peeled) are optional and\n \t * don't have a particular order.\n \t */\n-\tif (string_list_split(&line_sections, line, ' ', -1) < 2) {\n+\tif (string_list_split(&line_sections, line, \" \", -1) < 2) {\n \t\tret = 0;\n \t\tgoto out;\n \t}\ndiff --git a/diff.c b/diff.c\nindex dca87e164f..a81949a422 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -327,7 +327,7 @@ static unsigned parse_color_moved_ws(const char *arg)\n \tstruct string_list l = STRING_LIST_INIT_DUP;\n \tstruct string_list_item *i;\n \n-\tstring_list_split(&l, arg, ',', -1);\n+\tstring_list_split(&l, arg, \",\", -1);\n \n \tfor_each_string_list_item(i, &l) {\n \t\tstruct strbuf sb = STRBUF_INIT;\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex c1be9b76eb..9866270696 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1914,7 +1914,7 @@ static void fetch_pack_config(void)\n \t\tchar *str;\n \n \t\tif (!git_config_get_string(\"fetch.uriprotocols\", &str) && str) {\n-\t\t\tstring_list_split(&uri_protocols, str, ',', -1);\n+\t\t\tstring_list_split(&uri_protocols, str, \",\", -1);\n \t\t\tfree(str);\n \t\t}\n \t}\ndiff --git a/notes.c b/notes.c\nindex 97b995f3f2..6afcf088b9 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -892,7 +892,7 @@ static int string_list_add_note_lines(struct string_list *list,\n \t * later, along with any empty strings that came from empty\n \t * lines within the file.\n \t */\n-\tstring_list_split(list, data, '\\n', -1);\n+\tstring_list_split(list, data, \"\\n\", -1);\n \tfree(data);\n \treturn 0;\n }\ndiff --git a/parse-options.c b/parse-options.c\nindex 5224203ffe..9e7cb75192 100644\n--- a/parse-options.c\n+++ b/parse-options.c\n@@ -1338,7 +1338,7 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t\n \t\tif (!saw_empty_line && !*str)\n \t\t\tsaw_empty_line = 1;\n \n-\t\tstring_list_split(&list, str, '\\n', -1);\n+\t\tstring_list_split(&list, str, \"\\n\", -1);\n \t\tfor (j = 0; j < list.nr; j++) {\n \t\t\tconst char *line = list.items[j].string;\n \ndiff --git a/pathspec.c b/pathspec.c\nindex a3ddd701c7..de325f7ef9 100644\n--- a/pathspec.c\n+++ b/pathspec.c\n@@ -201,7 +201,7 @@ static void parse_pathspec_attr_match(struct pathspec_item *item, const char *va\n \tif (!value || !*value)\n \t\tdie(_(\"attr spec must not be empty\"));\n \n-\tstring_list_split(&list, value, ' ', -1);\n+\tstring_list_split(&list, value, \" \", -1);\n \tstring_list_remove_empty_items(&list, 0);\n \n \titem->attr_check = attr_check_alloc();\ndiff --git a/protocol.c b/protocol.c\nindex bae7226ff4..54b9f49c01 100644\n--- a/protocol.c\n+++ b/protocol.c\n@@ -61,7 +61,7 @@ enum protocol_version determine_protocol_version_server(void)\n \tif (git_protocol) {\n \t\tstruct string_list list = STRING_LIST_INIT_DUP;\n \t\tconst struct string_list_item *item;\n-\t\tstring_list_split(&list, git_protocol, ':', -1);\n+\t\tstring_list_split(&list, git_protocol, \":\", -1);\n \n \t\tfor_each_string_list_item(item, &list) {\n \t\t\tconst char *value;\ndiff --git a/ref-filter.c b/ref-filter.c\nindex f9f2c512a8..4edfb9c83b 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -435,7 +435,7 @@ static int remote_ref_atom_parser(struct ref_format *format UNUSED,\n \t}\n \n \tatom->u.remote_ref.nobracket = 0;\n-\tstring_list_split(&params, arg, ',', -1);\n+\tstring_list_split(&params, arg, \",\", -1);\n \n \tfor (i = 0; i < params.nr; i++) {\n \t\tconst char *s = params.items[i].string;\n@@ -831,7 +831,7 @@ static int align_atom_parser(struct ref_format *format UNUSED,\n \n \talign->position = ALIGN_LEFT;\n \n-\tstring_list_split(&params, arg, ',', -1);\n+\tstring_list_split(&params, arg, \",\", -1);\n \tfor (i = 0; i < params.nr; i++) {\n \t\tconst char *s = params.items[i].string;\n \t\tint position;\ndiff --git a/setup.c b/setup.c\nindex 6f52dab64c..b9f5eb8b51 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1460,8 +1460,9 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n \n \tif (env_ceiling_dirs) {\n \t\tint empty_entry_found = 0;\n+\t\tstatic const char path_sep[] = { PATH_SEP, '\\0' };\n \n-\t\tstring_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);\n+\t\tstring_list_split(&ceiling_dirs, env_ceiling_dirs, path_sep, -1);\n \t\tfilter_string_list(&ceiling_dirs, 0,\n \t\t\t\t   canonicalize_ceiling_entry, &empty_entry_found);\n \t\tceil_offset = longest_ancestor_length(dir->buf, &ceiling_dirs);\ndiff --git a/string-list.c b/string-list.c\nindex 0cb920e9b0..2284a009cb 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -277,7 +277,7 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_\n }\n \n int string_list_split(struct string_list *list, const char *string,\n-\t\t      int delim, int maxsplit)\n+\t\t      const char *delim, int maxsplit)\n {\n \tint count = 0;\n \tconst char *p = string, *end;\n@@ -291,7 +291,7 @@ int string_list_split(struct string_list *list, const char *string,\n \t\t\tstring_list_append(list, p);\n \t\t\treturn count;\n \t\t}\n-\t\tend = strchr(p, delim);\n+\t\tend = strpbrk(p, delim);\n \t\tif (end) {\n \t\t\tstring_list_append_nodup(list, xmemdupz(p, end - p));\n \t\t\tp = end + 1;\ndiff --git a/string-list.h b/string-list.h\nindex 122b318641..6c8650efde 100644\n--- a/string-list.h\n+++ b/string-list.h\n@@ -254,7 +254,7 @@ struct string_list_item *unsorted_string_list_lookup(struct string_list *list,\n void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util);\n \n /**\n- * Split string into substrings on character `delim` and append the\n+ * Split string into substrings on characters in `delim` and append the\n  * substrings to `list`.  The input string is not modified.\n  * list->strdup_strings must be set, as new memory needs to be\n  * allocated to hold the substrings.  If maxsplit is non-negative,\n@@ -262,15 +262,15 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_\n  * appended to list.\n  *\n  * Examples:\n- *   string_list_split(l, \"foo:bar:baz\", ':', -1) -> [\"foo\", \"bar\", \"baz\"]\n- *   string_list_split(l, \"foo:bar:baz\", ':', 0) -> [\"foo:bar:baz\"]\n- *   string_list_split(l, \"foo:bar:baz\", ':', 1) -> [\"foo\", \"bar:baz\"]\n- *   string_list_split(l, \"foo:bar:\", ':', -1) -> [\"foo\", \"bar\", \"\"]\n- *   string_list_split(l, \"\", ':', -1) -> [\"\"]\n- *   string_list_split(l, \":\", ':', -1) -> [\"\", \"\"]\n+ *   string_list_split(l, \"foo:bar:baz\", \":\", -1) -> [\"foo\", \"bar\", \"baz\"]\n+ *   string_list_split(l, \"foo:bar:baz\", \":\", 0) -> [\"foo:bar:baz\"]\n+ *   string_list_split(l, \"foo:bar:baz\", \":\", 1) -> [\"foo\", \"bar:baz\"]\n+ *   string_list_split(l, \"foo:bar:\", \":\", -1) -> [\"foo\", \"bar\", \"\"]\n+ *   string_list_split(l, \"\", \":\", -1) -> [\"\"]\n+ *   string_list_split(l, \":\", \":\", -1) -> [\"\", \"\"]\n  */\n int string_list_split(struct string_list *list, const char *string,\n-\t\t      int delim, int maxsplit);\n+\t\t      const char *delim, int maxsplit);\n \n /*\n  * Like string_list_split(), except that string is split in-place: the\ndiff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c\nindex 086238c826..f5f33751da 100644\n--- a/t/helper/test-path-utils.c\n+++ b/t/helper/test-path-utils.c\n@@ -348,6 +348,7 @@ int cmd__path_utils(int argc, const char **argv)\n \tif (argc == 4 && !strcmp(argv[1], \"longest_ancestor_length\")) {\n \t\tint len;\n \t\tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n+\t\tconst char path_sep[] = { PATH_SEP, '\\0' };\n \t\tchar *path = xstrdup(argv[2]);\n \n \t\t/*\n@@ -362,7 +363,7 @@ int cmd__path_utils(int argc, const char **argv)\n \t\t */\n \t\tif (normalize_path_copy(path, path))\n \t\t\tdie(\"Path \\\"%s\\\" could not be normalized\", argv[2]);\n-\t\tstring_list_split(&ceiling_dirs, argv[3], PATH_SEP, -1);\n+\t\tstring_list_split(&ceiling_dirs, argv[3], path_sep, -1);\n \t\tfilter_string_list(&ceiling_dirs, 0,\n \t\t\t\t   normalize_ceiling_entry, NULL);\n \t\tlen = longest_ancestor_length(path, &ceiling_dirs);\ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex 8d9a271845..aa1cb9b4ac 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -29,7 +29,7 @@ static unsigned int parse_flags(const char *str, struct flag_definition *defs)\n \tif (!strcmp(str, \"0\"))\n \t\treturn 0;\n \n-\tstring_list_split(&masks, str, ',', 64);\n+\tstring_list_split(&masks, str, \",\", 64);\n \tfor (size_t i = 0; i < masks.nr; i++) {\n \t\tconst char *name = masks.items[i].string;\n \t\tstruct flag_definition *def = defs;\ndiff --git a/t/unit-tests/u-string-list.c b/t/unit-tests/u-string-list.c\nindex d4ba5f9fa5..150a5f505f 100644\n--- a/t/unit-tests/u-string-list.c\n+++ b/t/unit-tests/u-string-list.c\n@@ -43,7 +43,7 @@ static void t_string_list_equal(struct string_list *list,\n \t\t\t\t  expected_strings->items[i].string);\n }\n \n-static void t_string_list_split(const char *data, int delim, int maxsplit, ...)\n+static void t_string_list_split(const char *data, const char *delim, int maxsplit, ...)\n {\n \tstruct string_list expected_strings = STRING_LIST_INIT_DUP;\n \tstruct string_list list = STRING_LIST_INIT_DUP;\n@@ -65,13 +65,13 @@ static void t_string_list_split(const char *data, int delim, int maxsplit, ...)\n \n void test_string_list__split(void)\n {\n-\tt_string_list_split(\"foo:bar:baz\", ':', -1, \"foo\", \"bar\", \"baz\", NULL);\n-\tt_string_list_split(\"foo:bar:baz\", ':', 0, \"foo:bar:baz\", NULL);\n-\tt_string_list_split(\"foo:bar:baz\", ':', 1, \"foo\", \"bar:baz\", NULL);\n-\tt_string_list_split(\"foo:bar:baz\", ':', 2, \"foo\", \"bar\", \"baz\", NULL);\n-\tt_string_list_split(\"foo:bar:\", ':', -1, \"foo\", \"bar\", \"\", NULL);\n-\tt_string_list_split(\"\", ':', -1, \"\", NULL);\n-\tt_string_list_split(\":\", ':', -1, \"\", \"\", NULL);\n+\tt_string_list_split(\"foo:bar:baz\", \":\", -1, \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split(\"foo:bar:baz\", \":\", 0, \"foo:bar:baz\", NULL);\n+\tt_string_list_split(\"foo:bar:baz\", \":\", 1, \"foo\", \"bar:baz\", NULL);\n+\tt_string_list_split(\"foo:bar:baz\", \":\", 2, \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split(\"foo:bar:\", \":\", -1, \"foo\", \"bar\", \"\", NULL);\n+\tt_string_list_split(\"\", \":\", -1, \"\", NULL);\n+\tt_string_list_split(\":\", \":\", -1, \"\", \"\", NULL);\n }\n \n static void t_string_list_split_in_place(const char *data, const char *delim,\ndiff --git a/transport.c b/transport.c\nindex c123ac1e38..76487b5453 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1042,7 +1042,7 @@ static const struct string_list *protocol_allow_list(void)\n \tif (enabled < 0) {\n \t\tconst char *v = getenv(\"GIT_ALLOW_PROTOCOL\");\n \t\tif (v) {\n-\t\t\tstring_list_split(&allowed, v, ':', -1);\n+\t\t\tstring_list_split(&allowed, v, \":\", -1);\n \t\t\tstring_list_sort(&allowed);\n \t\t\tenabled = 1;\n \t\t} else {\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 4f26f6afc7..91fcdcad9b 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1685,7 +1685,7 @@ static void process_args(struct packet_reader *request,\n \t\t\tif (data->uri_protocols.nr)\n \t\t\t\tsend_err_and_die(data,\n \t\t\t\t\t\t \"multiple packfile-uris lines forbidden\");\n-\t\t\tstring_list_split(&data->uri_protocols, p, ',', -1);\n+\t\t\tstring_list_split(&data->uri_protocols, p, \",\", -1);\n \t\t\tcontinue;\n \t\t}\n \n-- \n2.50.1-633-g85c5610de3\n\n"},{"id":"523249","messageId":"20250801220423.1230969-4-gitster@pobox.com","threadId":"63881","inReplyTo":"20250801220423.1230969-1-gitster@pobox.com","subject":"[PATCH v3 3/7] string-list: unify string_list_split* functions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-01T22:04:19Z","receivedAt":"2025-08-01T22:04:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thanks to the previous step, the only difference between these two\nrelated functions is that string_list_split() works on a string\nwithout modifying its contents (i.e. taking \"const char *\") and the\nresulting pieces of strings are their own copies in a string list,\nwhile string_list_split_in_place() works on a mutable string and the\nresulting pieces of strings come from the original string.\n\nConsolidate their implementations into a single helper function, and\nmake them a thin wrapper around it.  We can later add an extra flags\nparameter to extend both of these functions by updating only the\ninternal helper function.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n string-list.c | 96 ++++++++++++++++++++++++++++++---------------------\n 1 file changed, 56 insertions(+), 40 deletions(-)\n\ndiff --git a/string-list.c b/string-list.c\nindex 2284a009cb..65b6ceb259 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -276,55 +276,71 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_\n \tlist->nr--;\n }\n \n-int string_list_split(struct string_list *list, const char *string,\n-\t\t      const char *delim, int maxsplit)\n+/*\n+ * append a substring [p..end] to list; return number of things it\n+ * appended to the list.\n+ */\n+static int append_one(struct string_list *list,\n+\t\t      const char *p, const char *end,\n+\t\t      int in_place)\n+{\n+\tif (!end)\n+\t\tend = p + strlen(p);\n+\n+\tif (in_place) {\n+\t\t*((char *)end) = '\\0';\n+\t\tstring_list_append(list, p);\n+\t} else {\n+\t\tstring_list_append_nodup(list, xmemdupz(p, end - p));\n+\t}\n+\treturn 1;\n+}\n+\n+/*\n+ * Unfortunately this cannot become a public interface, as _in_place()\n+ * wants to have \"const char *string\" while the other variant wants to\n+ * have \"char *string\" for type safety.\n+ *\n+ * This accepts \"const char *string\" to allow both wrappers to use it;\n+ * it internally casts away the constness when in_place is true by\n+ * taking advantage of strpbrk() that takes a \"const char *\" arg and\n+ * returns \"char *\" pointer into that const string.  Yucky but works ;-).\n+ */\n+static int split_string(struct string_list *list, const char *string, const char *delim,\n+\t\t\tint maxsplit, int in_place)\n {\n \tint count = 0;\n-\tconst char *p = string, *end;\n+\tconst char *p = string;\n+\n+\tif (in_place && list->strdup_strings)\n+\t\tBUG(\"string_list_split_in_place() called with strdup_strings\");\n+\telse if (!in_place && !list->strdup_strings)\n+\t\tBUG(\"string_list_split() called without strdup_strings\");\n \n-\tif (!list->strdup_strings)\n-\t\tBUG(\"internal error in string_list_split(): \"\n-\t\t    \"list->strdup_strings must be set\");\n \tfor (;;) {\n-\t\tcount++;\n-\t\tif (maxsplit >= 0 && count > maxsplit) {\n-\t\t\tstring_list_append(list, p);\n-\t\t\treturn count;\n-\t\t}\n-\t\tend = strpbrk(p, delim);\n-\t\tif (end) {\n-\t\t\tstring_list_append_nodup(list, xmemdupz(p, end - p));\n-\t\t\tp = end + 1;\n-\t\t} else {\n-\t\t\tstring_list_append(list, p);\n+\t\tchar *end;\n+\n+\t\tif (0 <= maxsplit && maxsplit <= count)\n+\t\t\tend = NULL;\n+\t\telse\n+\t\t\tend = strpbrk(p, delim);\n+\n+\t\tcount += append_one(list, p, end, in_place);\n+\n+\t\tif (!end)\n \t\t\treturn count;\n-\t\t}\n+\t\tp = end + 1;\n \t}\n }\n \n+int string_list_split(struct string_list *list, const char *string,\n+\t\t      const char *delim, int maxsplit)\n+{\n+\treturn split_string(list, string, delim, maxsplit, 0);\n+}\n+\n int string_list_split_in_place(struct string_list *list, char *string,\n \t\t\t       const char *delim, int maxsplit)\n {\n-\tint count = 0;\n-\tchar *p = string, *end;\n-\n-\tif (list->strdup_strings)\n-\t\tBUG(\"internal error in string_list_split_in_place(): \"\n-\t\t    \"list->strdup_strings must not be set\");\n-\tfor (;;) {\n-\t\tcount++;\n-\t\tif (maxsplit >= 0 && count > maxsplit) {\n-\t\t\tstring_list_append(list, p);\n-\t\t\treturn count;\n-\t\t}\n-\t\tend = strpbrk(p, delim);\n-\t\tif (end) {\n-\t\t\t*end = '\\0';\n-\t\t\tstring_list_append(list, p);\n-\t\t\tp = end + 1;\n-\t\t} else {\n-\t\t\tstring_list_append(list, p);\n-\t\t\treturn count;\n-\t\t}\n-\t}\n+\treturn split_string(list, string, delim, maxsplit, 1);\n }\n-- \n2.50.1-633-g85c5610de3\n\n"},{"id":"523250","messageId":"20250801220423.1230969-5-gitster@pobox.com","threadId":"63881","inReplyTo":"20250801220423.1230969-1-gitster@pobox.com","subject":"[PATCH v3 4/7] string-list: optionally trim string pieces split by string_list_split*()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-01T22:04:20Z","receivedAt":"2025-08-01T22:04:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Teach the unified split_string() to take an optional \"flags\" word,\nand define the first flag STRING_LIST_SPLIT_TRIM to cause the split\npieces to be trimmed before they are placed in the string list.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n string-list.c                | 35 +++++++++++++++++---\n string-list.h                | 12 +++++++\n t/unit-tests/u-string-list.c | 64 ++++++++++++++++++++++++++++++++++++\n 3 files changed, 106 insertions(+), 5 deletions(-)\n\ndiff --git a/string-list.c b/string-list.c\nindex 65b6ceb259..86a309f8fb 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -282,11 +282,18 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_\n  */\n static int append_one(struct string_list *list,\n \t\t      const char *p, const char *end,\n-\t\t      int in_place)\n+\t\t      int in_place, unsigned flags)\n {\n \tif (!end)\n \t\tend = p + strlen(p);\n \n+\tif ((flags & STRING_LIST_SPLIT_TRIM)) {\n+\t\t/* rtrim */\n+\t\tfor (; p < end; end--)\n+\t\t\tif (!isspace(end[-1]))\n+\t\t\t\tbreak;\n+\t}\n+\n \tif (in_place) {\n \t\t*((char *)end) = '\\0';\n \t\tstring_list_append(list, p);\n@@ -307,7 +314,7 @@ static int append_one(struct string_list *list,\n  * returns \"char *\" pointer into that const string.  Yucky but works ;-).\n  */\n static int split_string(struct string_list *list, const char *string, const char *delim,\n-\t\t\tint maxsplit, int in_place)\n+\t\t\tint maxsplit, int in_place, unsigned flags)\n {\n \tint count = 0;\n \tconst char *p = string;\n@@ -320,12 +327,18 @@ static int split_string(struct string_list *list, const char *string, const char\n \tfor (;;) {\n \t\tchar *end;\n \n+\t\tif (flags & STRING_LIST_SPLIT_TRIM) {\n+\t\t\t/* ltrim */\n+\t\t\twhile (*p && isspace(*p))\n+\t\t\t\tp++;\n+\t\t}\n+\n \t\tif (0 <= maxsplit && maxsplit <= count)\n \t\t\tend = NULL;\n \t\telse\n \t\t\tend = strpbrk(p, delim);\n \n-\t\tcount += append_one(list, p, end, in_place);\n+\t\tcount += append_one(list, p, end, in_place, flags);\n \n \t\tif (!end)\n \t\t\treturn count;\n@@ -336,11 +349,23 @@ static int split_string(struct string_list *list, const char *string, const char\n int string_list_split(struct string_list *list, const char *string,\n \t\t      const char *delim, int maxsplit)\n {\n-\treturn split_string(list, string, delim, maxsplit, 0);\n+\treturn split_string(list, string, delim, maxsplit, 0, 0);\n }\n \n int string_list_split_in_place(struct string_list *list, char *string,\n \t\t\t       const char *delim, int maxsplit)\n {\n-\treturn split_string(list, string, delim, maxsplit, 1);\n+\treturn split_string(list, string, delim, maxsplit, 1, 0);\n+}\n+\n+int string_list_split_f(struct string_list *list, const char *string,\n+\t\t\tconst char *delim, int maxsplit, unsigned flags)\n+{\n+\treturn split_string(list, string, delim, maxsplit, 0, flags);\n+}\n+\n+int string_list_split_in_place_f(struct string_list *list, char *string,\n+\t\t\t       const char *delim, int maxsplit, unsigned flags)\n+{\n+\treturn split_string(list, string, delim, maxsplit, 1, flags);\n }\ndiff --git a/string-list.h b/string-list.h\nindex 6c8650efde..87ccc5f1e6 100644\n--- a/string-list.h\n+++ b/string-list.h\n@@ -281,4 +281,16 @@ int string_list_split(struct string_list *list, const char *string,\n  */\n int string_list_split_in_place(struct string_list *list, char *string,\n \t\t\t       const char *delim, int maxsplit);\n+\n+/* flag bits for split_f and split_in_place_f functions */\n+enum {\n+\t/* trim() resulting string piece before adding it to the list */\n+\tSTRING_LIST_SPLIT_TRIM = (1 << 0),\n+};\n+\n+int string_list_split_f(struct string_list *, const char *string,\n+\t\t\tconst char *delim, int maxsplit, unsigned flags);\n+\n+int string_list_split_in_place_f(struct string_list *, char *string,\n+\t\t\t\t const char *delim, int maxsplit, unsigned flags);\n #endif /* STRING_LIST_H */\ndiff --git a/t/unit-tests/u-string-list.c b/t/unit-tests/u-string-list.c\nindex 150a5f505f..daa9307e45 100644\n--- a/t/unit-tests/u-string-list.c\n+++ b/t/unit-tests/u-string-list.c\n@@ -63,6 +63,70 @@ static void t_string_list_split(const char *data, const char *delim, int maxspli\n \tstring_list_clear(&list, 0);\n }\n \n+static void t_string_list_split_f(const char *data, const char *delim,\n+\t\t\t\t  int maxsplit, unsigned flags, ...)\n+{\n+\tstruct string_list expected_strings = STRING_LIST_INIT_DUP;\n+\tstruct string_list list = STRING_LIST_INIT_DUP;\n+\tva_list ap;\n+\tint len;\n+\n+\tva_start(ap, flags);\n+\tt_vcreate_string_list_dup(&expected_strings, 0, ap);\n+\tva_end(ap);\n+\n+\tstring_list_clear(&list, 0);\n+\tlen = string_list_split_f(&list, data, delim, maxsplit, flags);\n+\tcl_assert_equal_i(len, expected_strings.nr);\n+\tt_string_list_equal(&list, &expected_strings);\n+\n+\tstring_list_clear(&expected_strings, 0);\n+\tstring_list_clear(&list, 0);\n+}\n+\n+void test_string_list__split_f(void)\n+{\n+\tt_string_list_split_f(\"::foo:bar:baz:\", \":\", -1, 0,\n+\t\t\t      \"\", \"\", \"foo\", \"bar\", \"baz\", \"\", NULL);\n+\tt_string_list_split_f(\" foo:bar : baz\", \":\", -1, STRING_LIST_SPLIT_TRIM,\n+\t\t\t      \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split_f(\"  a  b c  \", \" \", 1, STRING_LIST_SPLIT_TRIM,\n+\t\t\t      \"a\", \"b c\", NULL);\n+}\n+\n+static void t_string_list_split_in_place_f(const char *data_, const char *delim,\n+\t\t\t\t\t   int maxsplit, unsigned flags, ...)\n+{\n+\tstruct string_list expected_strings = STRING_LIST_INIT_DUP;\n+\tstruct string_list list = STRING_LIST_INIT_NODUP;\n+\tchar *data = xstrdup(data_);\n+\tva_list ap;\n+\tint len;\n+\n+\tva_start(ap, flags);\n+\tt_vcreate_string_list_dup(&expected_strings, 0, ap);\n+\tva_end(ap);\n+\n+\tstring_list_clear(&list, 0);\n+\tlen = string_list_split_in_place_f(&list, data, delim, maxsplit, flags);\n+\tcl_assert_equal_i(len, expected_strings.nr);\n+\tt_string_list_equal(&list, &expected_strings);\n+\n+\tfree(data);\n+\tstring_list_clear(&expected_strings, 0);\n+\tstring_list_clear(&list, 0);\n+}\n+\n+void test_string_list__split_in_place_f(void)\n+{\n+\tt_string_list_split_in_place_f(\"::foo:bar:baz:\", \":\", -1, 0,\n+\t\t\t\t       \"\", \"\", \"foo\", \"bar\", \"baz\", \"\", NULL);\n+\tt_string_list_split_in_place_f(\" foo:bar : baz\", \":\", -1, STRING_LIST_SPLIT_TRIM,\n+\t\t\t\t       \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split_in_place_f(\"  a  b c  \", \" \", 1, STRING_LIST_SPLIT_TRIM,\n+\t\t\t\t       \"a\", \"b c\", NULL);\n+}\n+\n void test_string_list__split(void)\n {\n \tt_string_list_split(\"foo:bar:baz\", \":\", -1, \"foo\", \"bar\", \"baz\", NULL);\n-- \n2.50.1-633-g85c5610de3\n\n"},{"id":"523251","messageId":"20250801220423.1230969-6-gitster@pobox.com","threadId":"63881","inReplyTo":"20250801220423.1230969-1-gitster@pobox.com","subject":"[PATCH v3 5/7] diff: simplify parsing of diff.colormovedws","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-01T22:04:21Z","receivedAt":"2025-08-01T22:04:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The code to parse this configuration variable, whose value is a\ncomma-separated list of known tokens like \"ignore-space-change\" and\n\"ignore-all-space\", uses string_list_split() to split the value into\npieces, and then places each piece of string in a strbuf to trim,\nbefore comparing the result with the list of known tokens.\n\nThanks to the previous steps, now string_list_split() can trim the\nresulting pieces before it places them in the string list.  Use it\nto simplify the code.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n diff.c | 20 +++++++-------------\n 1 file changed, 7 insertions(+), 13 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex a81949a422..70666ad2cd 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -327,29 +327,23 @@ static unsigned parse_color_moved_ws(const char *arg)\n \tstruct string_list l = STRING_LIST_INIT_DUP;\n \tstruct string_list_item *i;\n \n-\tstring_list_split(&l, arg, \",\", -1);\n+\tstring_list_split_f(&l, arg, \",\", -1, STRING_LIST_SPLIT_TRIM);\n \n \tfor_each_string_list_item(i, &l) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\tstrbuf_addstr(&sb, i->string);\n-\t\tstrbuf_trim(&sb);\n-\n-\t\tif (!strcmp(sb.buf, \"no\"))\n+\t\tif (!strcmp(i->string, \"no\"))\n \t\t\tret = 0;\n-\t\telse if (!strcmp(sb.buf, \"ignore-space-change\"))\n+\t\telse if (!strcmp(i->string, \"ignore-space-change\"))\n \t\t\tret |= XDF_IGNORE_WHITESPACE_CHANGE;\n-\t\telse if (!strcmp(sb.buf, \"ignore-space-at-eol\"))\n+\t\telse if (!strcmp(i->string, \"ignore-space-at-eol\"))\n \t\t\tret |= XDF_IGNORE_WHITESPACE_AT_EOL;\n-\t\telse if (!strcmp(sb.buf, \"ignore-all-space\"))\n+\t\telse if (!strcmp(i->string, \"ignore-all-space\"))\n \t\t\tret |= XDF_IGNORE_WHITESPACE;\n-\t\telse if (!strcmp(sb.buf, \"allow-indentation-change\"))\n+\t\telse if (!strcmp(i->string, \"allow-indentation-change\"))\n \t\t\tret |= COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE;\n \t\telse {\n \t\t\tret |= COLOR_MOVED_WS_ERROR;\n-\t\t\terror(_(\"unknown color-moved-ws mode '%s', possible values are 'ignore-space-change', 'ignore-space-at-eol', 'ignore-all-space', 'allow-indentation-change'\"), sb.buf);\n+\t\t\terror(_(\"unknown color-moved-ws mode '%s', possible values are 'ignore-space-change', 'ignore-space-at-eol', 'ignore-all-space', 'allow-indentation-change'\"), i->string);\n \t\t}\n-\n-\t\tstrbuf_release(&sb);\n \t}\n \n \tif ((ret & COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) &&\n-- \n2.50.1-633-g85c5610de3\n\n"},{"id":"523252","messageId":"20250801220423.1230969-7-gitster@pobox.com","threadId":"63881","inReplyTo":"20250801220423.1230969-1-gitster@pobox.com","subject":"[PATCH v3 6/7] string-list: optionally omit empty string pieces in string_list_split*()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-01T22:04:22Z","receivedAt":"2025-08-01T22:04:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Teach the unified split_string() machinery a new flag bit,\nSTRING_LIST_SPLIT_NONEMPTY, to cause empty split pieces to be\nomitted from the resulting string list.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n string-list.c                |  3 +++\n string-list.h                |  2 ++\n t/unit-tests/u-string-list.c | 15 +++++++++++++++\n 3 files changed, 20 insertions(+)\n\ndiff --git a/string-list.c b/string-list.c\nindex 86a309f8fb..343cf1ca90 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -294,6 +294,9 @@ static int append_one(struct string_list *list,\n \t\t\t\tbreak;\n \t}\n \n+\tif ((flags & STRING_LIST_SPLIT_NONEMPTY) && (end <= p))\n+\t\treturn 0;\n+\n \tif (in_place) {\n \t\t*((char *)end) = '\\0';\n \t\tstring_list_append(list, p);\ndiff --git a/string-list.h b/string-list.h\nindex 87ccc5f1e6..c25f28e9a3 100644\n--- a/string-list.h\n+++ b/string-list.h\n@@ -286,6 +286,8 @@ int string_list_split_in_place(struct string_list *list, char *string,\n enum {\n \t/* trim() resulting string piece before adding it to the list */\n \tSTRING_LIST_SPLIT_TRIM = (1 << 0),\n+\t/* omit adding empty string piece to the resulting list */\n+\tSTRING_LIST_SPLIT_NONEMPTY = (1 << 1),\n };\n \n int string_list_split_f(struct string_list *, const char *string,\ndiff --git a/t/unit-tests/u-string-list.c b/t/unit-tests/u-string-list.c\nindex daa9307e45..a2457d7b1e 100644\n--- a/t/unit-tests/u-string-list.c\n+++ b/t/unit-tests/u-string-list.c\n@@ -92,6 +92,13 @@ void test_string_list__split_f(void)\n \t\t\t      \"foo\", \"bar\", \"baz\", NULL);\n \tt_string_list_split_f(\"  a  b c  \", \" \", 1, STRING_LIST_SPLIT_TRIM,\n \t\t\t      \"a\", \"b c\", NULL);\n+\tt_string_list_split_f(\"::foo::bar:baz:\", \":\", -1, STRING_LIST_SPLIT_NONEMPTY,\n+\t\t\t      \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split_f(\"foo:baz\", \":\", -1, STRING_LIST_SPLIT_NONEMPTY,\n+\t\t\t      \"foo\", \"baz\", NULL);\n+\tt_string_list_split_f(\"foo :: : baz\", \":\", -1,\n+\t\t\t      STRING_LIST_SPLIT_NONEMPTY | STRING_LIST_SPLIT_TRIM,\n+\t\t\t      \"foo\", \"baz\", NULL);\n }\n \n static void t_string_list_split_in_place_f(const char *data_, const char *delim,\n@@ -125,6 +132,14 @@ void test_string_list__split_in_place_f(void)\n \t\t\t\t       \"foo\", \"bar\", \"baz\", NULL);\n \tt_string_list_split_in_place_f(\"  a  b c  \", \" \", 1, STRING_LIST_SPLIT_TRIM,\n \t\t\t\t       \"a\", \"b c\", NULL);\n+\tt_string_list_split_in_place_f(\"::foo::bar:baz:\", \":\", -1,\n+\t\t\t\t       STRING_LIST_SPLIT_NONEMPTY,\n+\t\t\t\t       \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split_in_place_f(\"foo:baz\", \":\", -1, STRING_LIST_SPLIT_NONEMPTY,\n+\t\t\t\t       \"foo\", \"baz\", NULL);\n+\tt_string_list_split_in_place_f(\"foo :: : baz\", \":\", -1,\n+\t\t\t\t       STRING_LIST_SPLIT_NONEMPTY | STRING_LIST_SPLIT_TRIM,\n+\t\t\t\t       \"foo\", \"baz\", NULL);\n }\n \n void test_string_list__split(void)\n-- \n2.50.1-633-g85c5610de3\n\n"},{"id":"523253","messageId":"20250801220423.1230969-8-gitster@pobox.com","threadId":"63881","inReplyTo":"20250801220423.1230969-1-gitster@pobox.com","subject":"[PATCH v3 7/7] string-list: split-then-remove-empty can be done while splitting","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-01T22:04:23Z","receivedAt":"2025-08-01T22:04:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thanks to the new STRING_LIST_SPLIT_NONEMPTY flag, a common pattern\nto split a string into a string list and then remove empty items in\nthe resulting list is no longer needed.  Instead, just tell the\nstring_list_split*() to omit empty ones while splitting.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n notes.c                     | 4 ++--\n pathspec.c                  | 3 +--\n t/helper/test-hashmap.c     | 4 ++--\n t/helper/test-json-writer.c | 4 ++--\n 4 files changed, 7 insertions(+), 8 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex 6afcf088b9..3603c4a42b 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -970,8 +970,8 @@ void string_list_add_refs_from_colon_sep(struct string_list *list,\n \tchar *globs_copy = xstrdup(globs);\n \tint i;\n \n-\tstring_list_split_in_place(&split, globs_copy, \":\", -1);\n-\tstring_list_remove_empty_items(&split, 0);\n+\tstring_list_split_in_place_f(&split, globs_copy, \":\", -1,\n+\t\t\t\t     STRING_LIST_SPLIT_NONEMPTY);\n \n \tfor (i = 0; i < split.nr; i++)\n \t\tstring_list_add_refs_by_glob(list, split.items[i].string);\ndiff --git a/pathspec.c b/pathspec.c\nindex de325f7ef9..5993c4afa0 100644\n--- a/pathspec.c\n+++ b/pathspec.c\n@@ -201,8 +201,7 @@ static void parse_pathspec_attr_match(struct pathspec_item *item, const char *va\n \tif (!value || !*value)\n \t\tdie(_(\"attr spec must not be empty\"));\n \n-\tstring_list_split(&list, value, \" \", -1);\n-\tstring_list_remove_empty_items(&list, 0);\n+\tstring_list_split_f(&list, value, \" \", -1, STRING_LIST_SPLIT_NONEMPTY);\n \n \titem->attr_check = attr_check_alloc();\n \tCALLOC_ARRAY(item->attr_match, list.nr);\ndiff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c\nindex 7782ae585e..e4dc02bd7a 100644\n--- a/t/helper/test-hashmap.c\n+++ b/t/helper/test-hashmap.c\n@@ -149,8 +149,8 @@ int cmd__hashmap(int argc UNUSED, const char **argv UNUSED)\n \n \t\t/* break line into command and up to two parameters */\n \t\tstring_list_setlen(&parts, 0);\n-\t\tstring_list_split_in_place(&parts, line.buf, DELIM, 2);\n-\t\tstring_list_remove_empty_items(&parts, 0);\n+\t\tstring_list_split_in_place_f(&parts, line.buf, DELIM, 2,\n+\t\t\t\t\t     STRING_LIST_SPLIT_NONEMPTY);\n \n \t\t/* ignore empty lines */\n \t\tif (!parts.nr)\ndiff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c\nindex a288069b04..f8316a7d29 100644\n--- a/t/helper/test-json-writer.c\n+++ b/t/helper/test-json-writer.c\n@@ -492,8 +492,8 @@ static int scripted(void)\n \n \t\t/* break line into command and zero or more tokens */\n \t\tstring_list_setlen(&parts, 0);\n-\t\tstring_list_split_in_place(&parts, line, \" \", -1);\n-\t\tstring_list_remove_empty_items(&parts, 0);\n+\t\tstring_list_split_in_place_f(&parts, line, \" \", -1,\n+\t\t\t\t\t     STRING_LIST_SPLIT_NONEMPTY);\n \n \t\t/* ignore empty lines */\n \t\tif (!parts.nr || !*parts.items[0].string)\n-- \n2.50.1-633-g85c5610de3\n\n"},{"id":"523254","messageId":"xmqqa54iljw1.fsf@gitster.g","threadId":"63881","inReplyTo":"aIw85gIurfEhvuPC@mbp","subject":"Re: [PATCH v2 4/7] string-list: optionally trim string pieces split by string_list_split*()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-01T23:09:02Z","receivedAt":"2025-08-01T23:09:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"shejialuo <shejialuo@gmail.com> writes:\n\n> I agree with you that we would introduce another variable. However, the\n> thing I quite dislike is that we do ltrim inside the current function\n> and we do rtrim inside `append_one`.\n\nYour \"quite dislike\" does not matter unless backed by a reason why\nit is not good, and for that, you need to think a bit deeper.  Then\nyou will hopefully appreciate why the current arrangement is more\noptimal ;-)\n\nThere is a clear separation of tasks between this caller-callee\npair.  The caller is responsible for finding where the current token\nends, and the callee is responsible for massaging the current token\ninto the resulting list.\n\nBut ltrim needs to be done in the caller for this to be efficient.\n\nImagine the case where you want to allow both non-empty and trim\nbehaviour at the same time, and use a whitespace character as\ndelimiter.  If your token has leading whitespaces, instead of\nchopping them into zero-length ranges and feeding it to append_one()\none by one, only to have them discarded (due to non-empty being\nset), ltrimming in the caller before it decides where the next token\n(i.e. \"end\") starts is far more efficient.  It may be more\nconceptually cleaner, but cleanliness is more subjective ;-)\n\n> My thinking is that we should handle the [p, end) string in the same\n> place.\n\nAgain this sounds no more than a subjective \"quite dislike\".  Is\nthere a reason why anybody would want to insist these two things be\ndone at the same location?\n\nYou could satisfy the subjective \"same place requirement\" by\ninlining the helper function into its sole caller and still keep the\ncurrent arrangement to ltrim before finding \"end\", of course.  But\nat that point, I would have to say that it is a tail wagging the\ndog.  You are making the code worse by destroying the caller-callee\ndivision of responsibilities, only to satisfy a subjetive \"quite\ndislike\" criteria.\n\n"},{"id":"523255","messageId":"xmqq5xf6lju9.fsf@gitster.g","threadId":"63881","inReplyTo":"aIw6ojvvDWLPllG7@mbp","subject":"Re: [PATCH v2 2/7] string-list: align string_list_split() with its _in_place() counterpart","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-01T23:10:06Z","receivedAt":"2025-08-01T23:10:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"shejialuo <shejialuo@gmail.com> writes:\n\n> Sorry to make you confused. Because there are some other changes where\n> you don't use `static`. That's the main reason why I ask this question.\n\nHmph, are there new things that are not static but have good reasons\nto be?  I am not aware of them offhand.\n\n> --- a/t/helper/test-path-utils.c\n> +++ b/t/helper/test-path-utils.c\n> @@ -348,6 +348,7 @@ int cmd__path_utils(int argc, const char **argv)\n>  \tif (argc == 4 && !strcmp(argv[1], \"longest_ancestor_length\")) {\n>  \t\tint len;\n>  \t\tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n> +\t\tconst char path_sep[] = { PATH_SEP, '\\0' };\n>  \t\tchar *path = xstrdup(argv[2]);\n>\n>> > And I have a design question: by using \"PATH_SEP\", we need to convert\n>> > this character to be string. Should we create a new variable named\n>> > \"PATH_SEP_STR\" or whatever to do that?\n>> \n>> Sorry, but I do not understand the question.  You want to see\n>> something like\n>> \n>> \t#define PATH_SEP_STR \"/\"\n>> \n>> you mean?  I do not offhand see why anybody would want to do so.\n>> \n>\n> Yes, that's my question. Because I see that we would define `path_sep`\n> array in many place, so I wonder whether we could use such macro.\n\nWhy would we define path_sep[] in many places?  There is only one\nhere, and no existing code outside this helper needs one.\n\nIf it becomes necessary, I suspect that a global variable, i.e.\n\n    === in some header file, perhaps git-compat-util.h ===\n    extern const char PATH_SEP_STR[];\n\n    === in a C source file, perhaps dir.c ===\n    const char PATH_SEP_STR[] = { PATH_SEP, '\\0' };\n\nwould be more efficient than having such #define and use site across\nthe codebase.\n\nUsing PATH_SEP_STR defined as a literal string \"/\" and sprinkling\nmany copies of it in the source files everywhere gives compilers\nopportunity to consolidate those that appear in each file into one\ncopy (as these are literals and constant, they can be shared), but\nwould not give opportunity to do so across compilation units so\neasily.  With an explicit singleton global instance, that is\ntrivial.\n\nIf we end up using great many number of them to matter, that is.  As\nI said above, we have no evidence that would be the case, and that\nis why I didn't make such a change in the first place.\n\nAlso, if many code paths need to split a path into pieces, it is\nmuch more likely for us to give them a single well-designed helper\nfunction for doing exactly that, than to have PATH_SEP_STR[] that\ncan be used by them from everywhere and have them use it to split\ntheir paths individually.\n"},{"id":"523256","messageId":"aI1vEzuD2DeiFSzW@mbp","threadId":"63881","inReplyTo":"xmqqa54iljw1.fsf@gitster.g","subject":"Re: [PATCH v2 4/7] string-list: optionally trim string pieces split by string_list_split*()","fromName":"shejialuo","fromEmail":"shejialuo@gmail.com","sentAt":"2025-08-02T01:51:15Z","receivedAt":"2025-08-02T01:51:20Z","isPatch":true,"sender":{"key":"shejialuo@gmail.com","avatar":"https://avatars.githubusercontent.com/u/56911263?v=4"},"body":"On Fri, Aug 01, 2025 at 04:09:02PM -0700, Junio C Hamano wrote:\n> shejialuo <shejialuo@gmail.com> writes:\n> \n> > I agree with you that we would introduce another variable. However, the\n> > thing I quite dislike is that we do ltrim inside the current function\n> > and we do rtrim inside `append_one`.\n> \n> Your \"quite dislike\" does not matter unless backed by a reason why\n> it is not good, and for that, you need to think a bit deeper.  Then\n> you will hopefully appreciate why the current arrangement is more\n> optimal ;-)\n> \n> There is a clear separation of tasks between this caller-callee\n> pair.  The caller is responsible for finding where the current token\n> ends, and the callee is responsible for massaging the current token\n> into the resulting list.\n> \n> But ltrim needs to be done in the caller for this to be efficient.\n> \n> Imagine the case where you want to allow both non-empty and trim\n> behaviour at the same time, and use a whitespace character as\n> delimiter.  If your token has leading whitespaces, instead of\n> chopping them into zero-length ranges and feeding it to append_one()\n> one by one, only to have them discarded (due to non-empty being\n> set), ltrimming in the caller before it decides where the next token\n> (i.e. \"end\") starts is far more efficient.  It may be more\n> conceptually cleaner, but cleanliness is more subjective ;-)\n> \n\nYes, I agree. Thanks for the wonderful explanation.\n\n> > My thinking is that we should handle the [p, end) string in the same\n> > place.\n> \n> Again this sounds no more than a subjective \"quite dislike\".  Is\n> there a reason why anybody would want to insist these two things be\n> done at the same location?\n> \n> You could satisfy the subjective \"same place requirement\" by\n> inlining the helper function into its sole caller and still keep the\n> current arrangement to ltrim before finding \"end\", of course.  But\n> at that point, I would have to say that it is a tail wagging the\n> dog.  You are making the code worse by destroying the caller-callee\n> division of responsibilities, only to satisfy a subjetive \"quite\n> dislike\" criteria.\n> \n\nI get your point. In the later review, I think I should avoid commenting\nthings using a subjetive idea. Really thanks for your suggestion.\n\nThanks,\n"},{"id":"523259","messageId":"20250802082257.GA3711639@coredump.intra.peff.net","threadId":"63881","inReplyTo":"20250801220423.1230969-3-gitster@pobox.com","subject":"Re: [PATCH v3 2/7] string-list: align string_list_split() with its _in_place() counterpart","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-08-02T08:22:57Z","receivedAt":"2025-08-02T08:23:06Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Aug 01, 2025 at 03:04:18PM -0700, Junio C Hamano wrote:\n\n> For some unknown reason, unlike string_list_split_in_place(),\n> string_list_split() took only a single character as a field\n> delimiter.  Before giving both functions more features in future\n> commits, allow string_list_split() to take more than one delimiter\n> characters to make them closer to each other.\n\nYou must know by now that writing \"some unknown reason\" in a commit\nmessage is the best way to nerd-snipe me. ;)\n\nIt looks like 52acddf36c (string-list: multi-delimiter\n`string_list_split_in_place()`, 2023-04-24) modified the in-place\nvariant, but left the original alone. It was needed for the in-place one\nto replace strtok(). Probably the original _should_ have been updated\nthen too for consistency, but wasn't. The motivation isn't given there,\nbut I'd assume it was some combination of \"didn't think of it\",\nlaziness, and not wanting to update all of the callers.\n\nI don't think there's a need to re-roll for this. Mostly I was curious\nand thought I'd share my finding on the list.\n\n-Peff\n"},{"id":"523260","messageId":"20250802082659.GB3711639@coredump.intra.peff.net","threadId":"63881","inReplyTo":"20250801220423.1230969-5-gitster@pobox.com","subject":"Re: [PATCH v3 4/7] string-list: optionally trim string pieces split by string_list_split*()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-08-02T08:26:59Z","receivedAt":"2025-08-02T08:27:01Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Aug 01, 2025 at 03:04:20PM -0700, Junio C Hamano wrote:\n\n> +/* flag bits for split_f and split_in_place_f functions */\n> +enum {\n> +\t/* trim() resulting string piece before adding it to the list */\n> +\tSTRING_LIST_SPLIT_TRIM = (1 << 0),\n> +};\n\nIt might be worth defining here what \"trim\" means. I can think of two\nobvious definitions:\n\n  1. trim whitespace from each split piece\n\n  2. trim excess delimiters from each split piece (which in turn depends\n     on how we handle multiple delimiters; do we make empty pieces, or\n     do we collapse them? I think the former, which would make this type\n     of trimming impossible?).\n\nIt looks like the patch does (1).\n\n-Peff\n"},{"id":"523277","messageId":"xmqq4iupk7i7.fsf@gitster.g","threadId":"63881","inReplyTo":"20250802082257.GA3711639@coredump.intra.peff.net","subject":"Re: [PATCH v3 2/7] string-list: align string_list_split() with its _in_place() counterpart","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-02T16:34:08Z","receivedAt":"2025-08-02T16:34:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> You must know by now that writing \"some unknown reason\" in a commit\n> message is the best way to nerd-snipe me. ;)\n>\n> It looks like 52acddf36c (string-list: multi-delimiter\n> `string_list_split_in_place()`, 2023-04-24) modified the in-place\n> variant, but left the original alone. It was needed for the in-place one\n> to replace strtok(). Probably the original _should_ have been updated\n> then too for consistency, but wasn't. The motivation isn't given there,\n> but I'd assume it was some combination of \"didn't think of it\",\n> laziness, and not wanting to update all of the callers.\n\nIOW, it wasn't done for some reason that is still unknown ;-).\n\n> I don't think there's a need to re-roll for this. Mostly I was curious\n> and thought I'd share my finding on the list.\n\nThanks.  IIRC, the multi-delimiter capability was used only in one\nunit test or test helper that wanted to split at any whitespace.  As\nit did not look so important, I could have unified them in the other\ndirection to only support a single delimiter (i.e. reverting that\ncommit you found), but I think the end result of this series would\ngive us more ergonomic API that we can apply at more places than\nbefore, so I am happy I didn't.\n\n"},{"id":"523279","messageId":"xmqqzfchispv.fsf@gitster.g","threadId":"63881","inReplyTo":"20250802082659.GB3711639@coredump.intra.peff.net","subject":"Re: [PATCH v3 4/7] string-list: optionally trim string pieces split by string_list_split*()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-02T16:38:52Z","receivedAt":"2025-08-02T16:38:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Fri, Aug 01, 2025 at 03:04:20PM -0700, Junio C Hamano wrote:\n>\n>> +/* flag bits for split_f and split_in_place_f functions */\n>> +enum {\n>> +\t/* trim() resulting string piece before adding it to the list */\n>> +\tSTRING_LIST_SPLIT_TRIM = (1 << 0),\n>> +};\n>\n> It might be worth defining here what \"trim\" means. I can think of two\n> obvious definitions:\n>\n>   1. trim whitespace from each split piece\n>\n>   2. trim excess delimiters from each split piece (which in turn depends\n>      on how we handle multiple delimiters; do we make empty pieces, or\n>      do we collapse them? I think the former, which would make this type\n>      of trimming impossible?).\n>\n> It looks like the patch does (1).\n\nTrue.  \"nm git | grep trim\" tells us that we most of the time use\nthe word to mean removing whitespaces, but there are exceptions.\n\nIt certainly is a good idea to rewrite \"trim()\" in that comment to\n\"trim whitespaces around\" or something like that.\n\nThanks.\n\n\n"},{"id":"523287","messageId":"20250802183832.GA1773585@coredump.intra.peff.net","threadId":"63881","inReplyTo":"xmqq4iupk7i7.fsf@gitster.g","subject":"Re: [PATCH v3 2/7] string-list: align string_list_split() with its _in_place() counterpart","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-08-02T18:38:32Z","receivedAt":"2025-08-02T18:38:34Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Aug 02, 2025 at 09:34:08AM -0700, Junio C Hamano wrote:\n\n> > I don't think there's a need to re-roll for this. Mostly I was curious\n> > and thought I'd share my finding on the list.\n> \n> Thanks.  IIRC, the multi-delimiter capability was used only in one\n> unit test or test helper that wanted to split at any whitespace.  As\n> it did not look so important, I could have unified them in the other\n> direction to only support a single delimiter (i.e. reverting that\n> commit you found), but I think the end result of this series would\n> give us more ergonomic API that we can apply at more places than\n> before, so I am happy I didn't.\n\nAgreed.\n\nThe commit I mentioned earlier said it was trying to get rid of\nstrtok(), so I assumed there were some real uses. But running \"log\n-Sstrtok\", all of those strtok calls were in test helpers!\n\nWell, at least it speaks well of us that we did not let strtok creep\ninto actual git code. ;)\n\n-Peff\n"},{"id":"523288","messageId":"20250802183916.GB1773585@coredump.intra.peff.net","threadId":"63881","inReplyTo":"xmqqzfchispv.fsf@gitster.g","subject":"Re: [PATCH v3 4/7] string-list: optionally trim string pieces split by string_list_split*()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-08-02T18:39:16Z","receivedAt":"2025-08-02T18:39:17Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Aug 02, 2025 at 09:38:52AM -0700, Junio C Hamano wrote:\n\n> > It might be worth defining here what \"trim\" means. I can think of two\n> > obvious definitions:\n> >\n> >   1. trim whitespace from each split piece\n> >\n> >   2. trim excess delimiters from each split piece (which in turn depends\n> >      on how we handle multiple delimiters; do we make empty pieces, or\n> >      do we collapse them? I think the former, which would make this type\n> >      of trimming impossible?).\n> >\n> > It looks like the patch does (1).\n> \n> True.  \"nm git | grep trim\" tells us that we most of the time use\n> the word to mean removing whitespaces, but there are exceptions.\n> \n> It certainly is a good idea to rewrite \"trim()\" in that comment to\n> \"trim whitespaces around\" or something like that.\n\nYep, ordinarily I'd assume it means whitespace. But since the function\ntakes a different delimiter, a hint of doubt crept into my mind. The\ntext you suggest would have made that go away.\n\n-Peff\n"},{"id":"523308","messageId":"20250803065223.3325111-1-gitster@pobox.com","threadId":"63881","inReplyTo":"20250801220423.1230969-1-gitster@pobox.com","subject":"[PATCH v4 0/7] string_list_split*() updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:52:16Z","receivedAt":"2025-08-03T06:52:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Two related string-list API functions, string_list_split() and\nstring_list_split_in_place(), more or less duplicates their\nimplementations.  They both take a single string, and split the\nstring at the delimiter and stuff the result into a string list.\n\nHowever, there is one subtle and unnecessary difference.  The non\n\"in-place\" variant only allows a single byte value as delimiter,\nwhile the \"in-place\" variant can take multiple delimiters (e.g.,\n\"split at either a comma or a space\").\n\nThis series first updates the string_list_split() to allow multiple\ndelimiters like string_list_split_in_place() does, by unifying their\nimplementations into one.  This refactoring allows us to give new\nfeatures to these two functions more easily.\n\nThen these functions learn to optionally\n\n - trim the split string pieces before placing them in the resulting\n   string list.\n\n - omit empty string pieces from the resulting string list.\n\nAn existing caller of string_list_split() in diff.c trims the\nelements in the resulting string list before it uses them, which is\nsimplified by taking advantage of this new feature.\n\nA handful of code paths call string_list_split*(), immediately\nfollowed by string_list_remove_empty_items().  They are simplified\nby not placing empty items in the list in the first place.\n\n\nRelative to the v3 iteration, the v4 iteration explains the history\nbehind string_list_split_in_place() in a bit more detail, and\nexpands in-code comment to clarify what the verb \"trim\" means in the\ncontext of STRING_LIST_SPLIT_TRIM.\n\nJunio C Hamano (7):\n  string-list: report programming error with BUG\n  string-list: align string_list_split() with its _in_place()\n    counterpart\n  string-list: unify string_list_split* functions\n  string-list: optionally trim string pieces split by\n    string_list_split*()\n  diff: simplify parsing of diff.colormovedws\n  string-list: optionally omit empty string pieces in\n    string_list_split*()\n  string-list: split-then-remove-empty can be done while splitting\n\n builtin/blame.c              |   2 +-\n builtin/merge.c              |   2 +-\n builtin/var.c                |   2 +-\n connect.c                    |   2 +-\n diff.c                       |  20 ++----\n fetch-pack.c                 |   2 +-\n notes.c                      |   6 +-\n parse-options.c              |   2 +-\n pathspec.c                   |   3 +-\n protocol.c                   |   2 +-\n ref-filter.c                 |   4 +-\n setup.c                      |   3 +-\n string-list.c                | 120 ++++++++++++++++++++++++-----------\n string-list.h                |  33 +++++++---\n t/helper/test-hashmap.c      |   4 +-\n t/helper/test-json-writer.c  |   4 +-\n t/helper/test-path-utils.c   |   3 +-\n t/helper/test-ref-store.c    |   2 +-\n t/unit-tests/u-string-list.c |  95 ++++++++++++++++++++++++---\n transport.c                  |   2 +-\n upload-pack.c                |   2 +-\n 21 files changed, 225 insertions(+), 90 deletions(-)\n\nRange-diff against v3:\n1:  442ed679bb = 1:  4f9c8d8963 string-list: report programming error with BUG\n2:  cc80bac8c2 ! 2:  9f6dfe43c8 string-list: align string_list_split() with its _in_place() counterpart\n    @@ Metadata\n      ## Commit message ##\n         string-list: align string_list_split() with its _in_place() counterpart\n     \n    -    For some unknown reason, unlike string_list_split_in_place(),\n    -    string_list_split() took only a single character as a field\n    -    delimiter.  Before giving both functions more features in future\n    -    commits, allow string_list_split() to take more than one delimiter\n    -    characters to make them closer to each other.\n    +    The string_list_split_in_place() function was updated by 52acddf3\n    +    (string-list: multi-delimiter `string_list_split_in_place()`,\n    +    2023-04-24) to take more than one delimiter characters, hoping that\n    +    we can later use it to replace our uses of strtok().  We however did\n    +    not make a matching change to the string_list_split() function,\n    +    which is very similar.\n    +\n    +    Before giving both functions more features in future commits, allow\n    +    string_list_split() to also take more than one delimiter characters\n    +    to make them closer to each other.\n     \n         Signed-off-by: Junio C Hamano <gitster@pobox.com>\n     \n3:  c7922b3e14 = 3:  527535fcdd string-list: unify string_list_split* functions\n4:  9d7d22e8ef ! 4:  5764549741 string-list: optionally trim string pieces split by string_list_split*()\n    @@ string-list.h: int string_list_split(struct string_list *list, const char *strin\n      int string_list_split_in_place(struct string_list *list, char *string,\n      \t\t\t       const char *delim, int maxsplit);\n     +\n    -+/* flag bits for split_f and split_in_place_f functions */\n    ++/* Flag bits for split_f and split_in_place_f functions */\n     +enum {\n    -+\t/* trim() resulting string piece before adding it to the list */\n    ++\t/*\n    ++\t * trim whitespaces around resulting string piece before adding\n    ++\t * it to the list\n    ++\t */\n     +\tSTRING_LIST_SPLIT_TRIM = (1 << 0),\n     +};\n     +\n5:  ad8b425bc5 = 5:  f3a303aef0 diff: simplify parsing of diff.colormovedws\n6:  d03f443878 ! 6:  27531efa41 string-list: optionally omit empty string pieces in string_list_split*()\n    @@ string-list.c: static int append_one(struct string_list *list,\n      \t\tstring_list_append(list, p);\n     \n      ## string-list.h ##\n    -@@ string-list.h: int string_list_split_in_place(struct string_list *list, char *string,\n    - enum {\n    - \t/* trim() resulting string piece before adding it to the list */\n    +@@ string-list.h: enum {\n    + \t * it to the list\n    + \t */\n      \tSTRING_LIST_SPLIT_TRIM = (1 << 0),\n     +\t/* omit adding empty string piece to the resulting list */\n     +\tSTRING_LIST_SPLIT_NONEMPTY = (1 << 1),\n7:  9eb8d87d62 = 7:  2ab2aac73d string-list: split-then-remove-empty can be done while splitting\n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523309","messageId":"20250803065223.3325111-2-gitster@pobox.com","threadId":"63881","inReplyTo":"20250803065223.3325111-1-gitster@pobox.com","subject":"[PATCH v4 1/7] string-list: report programming error with BUG","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:52:17Z","receivedAt":"2025-08-03T06:52:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Passing a string list that has .strdup_strings bit unset to\nstring_list_split(), or one that has .strdup_strings bit set to\nstring_list_split_in_place(), is a programmer error.  Do not use\ndie() to abort the execution.  Use BUG() instead.\n\nAs a developer-facing message, the message string itself should\nbe a lot more concise, but let's keep the original one for now.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n string-list.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/string-list.c b/string-list.c\nindex 53faaa8420..0cb920e9b0 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -283,7 +283,7 @@ int string_list_split(struct string_list *list, const char *string,\n \tconst char *p = string, *end;\n \n \tif (!list->strdup_strings)\n-\t\tdie(\"internal error in string_list_split(): \"\n+\t\tBUG(\"internal error in string_list_split(): \"\n \t\t    \"list->strdup_strings must be set\");\n \tfor (;;) {\n \t\tcount++;\n@@ -309,7 +309,7 @@ int string_list_split_in_place(struct string_list *list, char *string,\n \tchar *p = string, *end;\n \n \tif (list->strdup_strings)\n-\t\tdie(\"internal error in string_list_split_in_place(): \"\n+\t\tBUG(\"internal error in string_list_split_in_place(): \"\n \t\t    \"list->strdup_strings must not be set\");\n \tfor (;;) {\n \t\tcount++;\n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523310","messageId":"20250803065223.3325111-3-gitster@pobox.com","threadId":"63881","inReplyTo":"20250803065223.3325111-1-gitster@pobox.com","subject":"[PATCH v4 2/7] string-list: align string_list_split() with its _in_place() counterpart","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:52:18Z","receivedAt":"2025-08-03T06:52:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The string_list_split_in_place() function was updated by 52acddf3\n(string-list: multi-delimiter `string_list_split_in_place()`,\n2023-04-24) to take more than one delimiter characters, hoping that\nwe can later use it to replace our uses of strtok().  We however did\nnot make a matching change to the string_list_split() function,\nwhich is very similar.\n\nBefore giving both functions more features in future commits, allow\nstring_list_split() to also take more than one delimiter characters\nto make them closer to each other.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/blame.c              |  2 +-\n builtin/merge.c              |  2 +-\n builtin/var.c                |  2 +-\n connect.c                    |  2 +-\n diff.c                       |  2 +-\n fetch-pack.c                 |  2 +-\n notes.c                      |  2 +-\n parse-options.c              |  2 +-\n pathspec.c                   |  2 +-\n protocol.c                   |  2 +-\n ref-filter.c                 |  4 ++--\n setup.c                      |  3 ++-\n string-list.c                |  4 ++--\n string-list.h                | 16 ++++++++--------\n t/helper/test-path-utils.c   |  3 ++-\n t/helper/test-ref-store.c    |  2 +-\n t/unit-tests/u-string-list.c | 16 ++++++++--------\n transport.c                  |  2 +-\n upload-pack.c                |  2 +-\n 19 files changed, 37 insertions(+), 35 deletions(-)\n\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 91586e6852..70a6460401 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -420,7 +420,7 @@ static void parse_color_fields(const char *s)\n \tcolorfield_nr = 0;\n \n \t/* Ideally this would be stripped and split at the same time? */\n-\tstring_list_split(&l, s, ',', -1);\n+\tstring_list_split(&l, s, \",\", -1);\n \tALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc);\n \n \tfor_each_string_list_item(item, &l) {\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 18b22c0a26..893f8950bf 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -875,7 +875,7 @@ static void add_strategies(const char *string, unsigned attr)\n \tif (string) {\n \t\tstruct string_list list = STRING_LIST_INIT_DUP;\n \t\tstruct string_list_item *item;\n-\t\tstring_list_split(&list, string, ' ', -1);\n+\t\tstring_list_split(&list, string, \" \", -1);\n \t\tfor_each_string_list_item(item, &list)\n \t\t\tappend_strategy(get_strategy(item->string));\n \t\tstring_list_clear(&list, 0);\ndiff --git a/builtin/var.c b/builtin/var.c\nindex ada642a9fe..4ae7af0eff 100644\n--- a/builtin/var.c\n+++ b/builtin/var.c\n@@ -181,7 +181,7 @@ static void list_vars(void)\n \t\t\tif (ptr->multivalued && *val) {\n \t\t\t\tstruct string_list list = STRING_LIST_INIT_DUP;\n \n-\t\t\t\tstring_list_split(&list, val, '\\n', -1);\n+\t\t\t\tstring_list_split(&list, val, \"\\n\", -1);\n \t\t\t\tfor (size_t i = 0; i < list.nr; i++)\n \t\t\t\t\tprintf(\"%s=%s\\n\", ptr->name, list.items[i].string);\n \t\t\t\tstring_list_clear(&list, 0);\ndiff --git a/connect.c b/connect.c\nindex e77287f426..867b12bde5 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -407,7 +407,7 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list,\n \t * name.  Subsequent fields (symref-target and peeled) are optional and\n \t * don't have a particular order.\n \t */\n-\tif (string_list_split(&line_sections, line, ' ', -1) < 2) {\n+\tif (string_list_split(&line_sections, line, \" \", -1) < 2) {\n \t\tret = 0;\n \t\tgoto out;\n \t}\ndiff --git a/diff.c b/diff.c\nindex dca87e164f..a81949a422 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -327,7 +327,7 @@ static unsigned parse_color_moved_ws(const char *arg)\n \tstruct string_list l = STRING_LIST_INIT_DUP;\n \tstruct string_list_item *i;\n \n-\tstring_list_split(&l, arg, ',', -1);\n+\tstring_list_split(&l, arg, \",\", -1);\n \n \tfor_each_string_list_item(i, &l) {\n \t\tstruct strbuf sb = STRBUF_INIT;\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex c1be9b76eb..9866270696 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1914,7 +1914,7 @@ static void fetch_pack_config(void)\n \t\tchar *str;\n \n \t\tif (!git_config_get_string(\"fetch.uriprotocols\", &str) && str) {\n-\t\t\tstring_list_split(&uri_protocols, str, ',', -1);\n+\t\t\tstring_list_split(&uri_protocols, str, \",\", -1);\n \t\t\tfree(str);\n \t\t}\n \t}\ndiff --git a/notes.c b/notes.c\nindex 97b995f3f2..6afcf088b9 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -892,7 +892,7 @@ static int string_list_add_note_lines(struct string_list *list,\n \t * later, along with any empty strings that came from empty\n \t * lines within the file.\n \t */\n-\tstring_list_split(list, data, '\\n', -1);\n+\tstring_list_split(list, data, \"\\n\", -1);\n \tfree(data);\n \treturn 0;\n }\ndiff --git a/parse-options.c b/parse-options.c\nindex 5224203ffe..9e7cb75192 100644\n--- a/parse-options.c\n+++ b/parse-options.c\n@@ -1338,7 +1338,7 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t\n \t\tif (!saw_empty_line && !*str)\n \t\t\tsaw_empty_line = 1;\n \n-\t\tstring_list_split(&list, str, '\\n', -1);\n+\t\tstring_list_split(&list, str, \"\\n\", -1);\n \t\tfor (j = 0; j < list.nr; j++) {\n \t\t\tconst char *line = list.items[j].string;\n \ndiff --git a/pathspec.c b/pathspec.c\nindex a3ddd701c7..de325f7ef9 100644\n--- a/pathspec.c\n+++ b/pathspec.c\n@@ -201,7 +201,7 @@ static void parse_pathspec_attr_match(struct pathspec_item *item, const char *va\n \tif (!value || !*value)\n \t\tdie(_(\"attr spec must not be empty\"));\n \n-\tstring_list_split(&list, value, ' ', -1);\n+\tstring_list_split(&list, value, \" \", -1);\n \tstring_list_remove_empty_items(&list, 0);\n \n \titem->attr_check = attr_check_alloc();\ndiff --git a/protocol.c b/protocol.c\nindex bae7226ff4..54b9f49c01 100644\n--- a/protocol.c\n+++ b/protocol.c\n@@ -61,7 +61,7 @@ enum protocol_version determine_protocol_version_server(void)\n \tif (git_protocol) {\n \t\tstruct string_list list = STRING_LIST_INIT_DUP;\n \t\tconst struct string_list_item *item;\n-\t\tstring_list_split(&list, git_protocol, ':', -1);\n+\t\tstring_list_split(&list, git_protocol, \":\", -1);\n \n \t\tfor_each_string_list_item(item, &list) {\n \t\t\tconst char *value;\ndiff --git a/ref-filter.c b/ref-filter.c\nindex f9f2c512a8..4edfb9c83b 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -435,7 +435,7 @@ static int remote_ref_atom_parser(struct ref_format *format UNUSED,\n \t}\n \n \tatom->u.remote_ref.nobracket = 0;\n-\tstring_list_split(&params, arg, ',', -1);\n+\tstring_list_split(&params, arg, \",\", -1);\n \n \tfor (i = 0; i < params.nr; i++) {\n \t\tconst char *s = params.items[i].string;\n@@ -831,7 +831,7 @@ static int align_atom_parser(struct ref_format *format UNUSED,\n \n \talign->position = ALIGN_LEFT;\n \n-\tstring_list_split(&params, arg, ',', -1);\n+\tstring_list_split(&params, arg, \",\", -1);\n \tfor (i = 0; i < params.nr; i++) {\n \t\tconst char *s = params.items[i].string;\n \t\tint position;\ndiff --git a/setup.c b/setup.c\nindex 6f52dab64c..b9f5eb8b51 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1460,8 +1460,9 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n \n \tif (env_ceiling_dirs) {\n \t\tint empty_entry_found = 0;\n+\t\tstatic const char path_sep[] = { PATH_SEP, '\\0' };\n \n-\t\tstring_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);\n+\t\tstring_list_split(&ceiling_dirs, env_ceiling_dirs, path_sep, -1);\n \t\tfilter_string_list(&ceiling_dirs, 0,\n \t\t\t\t   canonicalize_ceiling_entry, &empty_entry_found);\n \t\tceil_offset = longest_ancestor_length(dir->buf, &ceiling_dirs);\ndiff --git a/string-list.c b/string-list.c\nindex 0cb920e9b0..2284a009cb 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -277,7 +277,7 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_\n }\n \n int string_list_split(struct string_list *list, const char *string,\n-\t\t      int delim, int maxsplit)\n+\t\t      const char *delim, int maxsplit)\n {\n \tint count = 0;\n \tconst char *p = string, *end;\n@@ -291,7 +291,7 @@ int string_list_split(struct string_list *list, const char *string,\n \t\t\tstring_list_append(list, p);\n \t\t\treturn count;\n \t\t}\n-\t\tend = strchr(p, delim);\n+\t\tend = strpbrk(p, delim);\n \t\tif (end) {\n \t\t\tstring_list_append_nodup(list, xmemdupz(p, end - p));\n \t\t\tp = end + 1;\ndiff --git a/string-list.h b/string-list.h\nindex 122b318641..6c8650efde 100644\n--- a/string-list.h\n+++ b/string-list.h\n@@ -254,7 +254,7 @@ struct string_list_item *unsorted_string_list_lookup(struct string_list *list,\n void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util);\n \n /**\n- * Split string into substrings on character `delim` and append the\n+ * Split string into substrings on characters in `delim` and append the\n  * substrings to `list`.  The input string is not modified.\n  * list->strdup_strings must be set, as new memory needs to be\n  * allocated to hold the substrings.  If maxsplit is non-negative,\n@@ -262,15 +262,15 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_\n  * appended to list.\n  *\n  * Examples:\n- *   string_list_split(l, \"foo:bar:baz\", ':', -1) -> [\"foo\", \"bar\", \"baz\"]\n- *   string_list_split(l, \"foo:bar:baz\", ':', 0) -> [\"foo:bar:baz\"]\n- *   string_list_split(l, \"foo:bar:baz\", ':', 1) -> [\"foo\", \"bar:baz\"]\n- *   string_list_split(l, \"foo:bar:\", ':', -1) -> [\"foo\", \"bar\", \"\"]\n- *   string_list_split(l, \"\", ':', -1) -> [\"\"]\n- *   string_list_split(l, \":\", ':', -1) -> [\"\", \"\"]\n+ *   string_list_split(l, \"foo:bar:baz\", \":\", -1) -> [\"foo\", \"bar\", \"baz\"]\n+ *   string_list_split(l, \"foo:bar:baz\", \":\", 0) -> [\"foo:bar:baz\"]\n+ *   string_list_split(l, \"foo:bar:baz\", \":\", 1) -> [\"foo\", \"bar:baz\"]\n+ *   string_list_split(l, \"foo:bar:\", \":\", -1) -> [\"foo\", \"bar\", \"\"]\n+ *   string_list_split(l, \"\", \":\", -1) -> [\"\"]\n+ *   string_list_split(l, \":\", \":\", -1) -> [\"\", \"\"]\n  */\n int string_list_split(struct string_list *list, const char *string,\n-\t\t      int delim, int maxsplit);\n+\t\t      const char *delim, int maxsplit);\n \n /*\n  * Like string_list_split(), except that string is split in-place: the\ndiff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c\nindex 086238c826..f5f33751da 100644\n--- a/t/helper/test-path-utils.c\n+++ b/t/helper/test-path-utils.c\n@@ -348,6 +348,7 @@ int cmd__path_utils(int argc, const char **argv)\n \tif (argc == 4 && !strcmp(argv[1], \"longest_ancestor_length\")) {\n \t\tint len;\n \t\tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n+\t\tconst char path_sep[] = { PATH_SEP, '\\0' };\n \t\tchar *path = xstrdup(argv[2]);\n \n \t\t/*\n@@ -362,7 +363,7 @@ int cmd__path_utils(int argc, const char **argv)\n \t\t */\n \t\tif (normalize_path_copy(path, path))\n \t\t\tdie(\"Path \\\"%s\\\" could not be normalized\", argv[2]);\n-\t\tstring_list_split(&ceiling_dirs, argv[3], PATH_SEP, -1);\n+\t\tstring_list_split(&ceiling_dirs, argv[3], path_sep, -1);\n \t\tfilter_string_list(&ceiling_dirs, 0,\n \t\t\t\t   normalize_ceiling_entry, NULL);\n \t\tlen = longest_ancestor_length(path, &ceiling_dirs);\ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex 8d9a271845..aa1cb9b4ac 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -29,7 +29,7 @@ static unsigned int parse_flags(const char *str, struct flag_definition *defs)\n \tif (!strcmp(str, \"0\"))\n \t\treturn 0;\n \n-\tstring_list_split(&masks, str, ',', 64);\n+\tstring_list_split(&masks, str, \",\", 64);\n \tfor (size_t i = 0; i < masks.nr; i++) {\n \t\tconst char *name = masks.items[i].string;\n \t\tstruct flag_definition *def = defs;\ndiff --git a/t/unit-tests/u-string-list.c b/t/unit-tests/u-string-list.c\nindex d4ba5f9fa5..150a5f505f 100644\n--- a/t/unit-tests/u-string-list.c\n+++ b/t/unit-tests/u-string-list.c\n@@ -43,7 +43,7 @@ static void t_string_list_equal(struct string_list *list,\n \t\t\t\t  expected_strings->items[i].string);\n }\n \n-static void t_string_list_split(const char *data, int delim, int maxsplit, ...)\n+static void t_string_list_split(const char *data, const char *delim, int maxsplit, ...)\n {\n \tstruct string_list expected_strings = STRING_LIST_INIT_DUP;\n \tstruct string_list list = STRING_LIST_INIT_DUP;\n@@ -65,13 +65,13 @@ static void t_string_list_split(const char *data, int delim, int maxsplit, ...)\n \n void test_string_list__split(void)\n {\n-\tt_string_list_split(\"foo:bar:baz\", ':', -1, \"foo\", \"bar\", \"baz\", NULL);\n-\tt_string_list_split(\"foo:bar:baz\", ':', 0, \"foo:bar:baz\", NULL);\n-\tt_string_list_split(\"foo:bar:baz\", ':', 1, \"foo\", \"bar:baz\", NULL);\n-\tt_string_list_split(\"foo:bar:baz\", ':', 2, \"foo\", \"bar\", \"baz\", NULL);\n-\tt_string_list_split(\"foo:bar:\", ':', -1, \"foo\", \"bar\", \"\", NULL);\n-\tt_string_list_split(\"\", ':', -1, \"\", NULL);\n-\tt_string_list_split(\":\", ':', -1, \"\", \"\", NULL);\n+\tt_string_list_split(\"foo:bar:baz\", \":\", -1, \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split(\"foo:bar:baz\", \":\", 0, \"foo:bar:baz\", NULL);\n+\tt_string_list_split(\"foo:bar:baz\", \":\", 1, \"foo\", \"bar:baz\", NULL);\n+\tt_string_list_split(\"foo:bar:baz\", \":\", 2, \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split(\"foo:bar:\", \":\", -1, \"foo\", \"bar\", \"\", NULL);\n+\tt_string_list_split(\"\", \":\", -1, \"\", NULL);\n+\tt_string_list_split(\":\", \":\", -1, \"\", \"\", NULL);\n }\n \n static void t_string_list_split_in_place(const char *data, const char *delim,\ndiff --git a/transport.c b/transport.c\nindex c123ac1e38..76487b5453 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1042,7 +1042,7 @@ static const struct string_list *protocol_allow_list(void)\n \tif (enabled < 0) {\n \t\tconst char *v = getenv(\"GIT_ALLOW_PROTOCOL\");\n \t\tif (v) {\n-\t\t\tstring_list_split(&allowed, v, ':', -1);\n+\t\t\tstring_list_split(&allowed, v, \":\", -1);\n \t\t\tstring_list_sort(&allowed);\n \t\t\tenabled = 1;\n \t\t} else {\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 4f26f6afc7..91fcdcad9b 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1685,7 +1685,7 @@ static void process_args(struct packet_reader *request,\n \t\t\tif (data->uri_protocols.nr)\n \t\t\t\tsend_err_and_die(data,\n \t\t\t\t\t\t \"multiple packfile-uris lines forbidden\");\n-\t\t\tstring_list_split(&data->uri_protocols, p, ',', -1);\n+\t\t\tstring_list_split(&data->uri_protocols, p, \",\", -1);\n \t\t\tcontinue;\n \t\t}\n \n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523311","messageId":"20250803065223.3325111-4-gitster@pobox.com","threadId":"63881","inReplyTo":"20250803065223.3325111-1-gitster@pobox.com","subject":"[PATCH v4 3/7] string-list: unify string_list_split* functions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:52:19Z","receivedAt":"2025-08-03T06:52:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thanks to the previous step, the only difference between these two\nrelated functions is that string_list_split() works on a string\nwithout modifying its contents (i.e. taking \"const char *\") and the\nresulting pieces of strings are their own copies in a string list,\nwhile string_list_split_in_place() works on a mutable string and the\nresulting pieces of strings come from the original string.\n\nConsolidate their implementations into a single helper function, and\nmake them a thin wrapper around it.  We can later add an extra flags\nparameter to extend both of these functions by updating only the\ninternal helper function.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n string-list.c | 96 ++++++++++++++++++++++++++++++---------------------\n 1 file changed, 56 insertions(+), 40 deletions(-)\n\ndiff --git a/string-list.c b/string-list.c\nindex 2284a009cb..65b6ceb259 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -276,55 +276,71 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_\n \tlist->nr--;\n }\n \n-int string_list_split(struct string_list *list, const char *string,\n-\t\t      const char *delim, int maxsplit)\n+/*\n+ * append a substring [p..end] to list; return number of things it\n+ * appended to the list.\n+ */\n+static int append_one(struct string_list *list,\n+\t\t      const char *p, const char *end,\n+\t\t      int in_place)\n+{\n+\tif (!end)\n+\t\tend = p + strlen(p);\n+\n+\tif (in_place) {\n+\t\t*((char *)end) = '\\0';\n+\t\tstring_list_append(list, p);\n+\t} else {\n+\t\tstring_list_append_nodup(list, xmemdupz(p, end - p));\n+\t}\n+\treturn 1;\n+}\n+\n+/*\n+ * Unfortunately this cannot become a public interface, as _in_place()\n+ * wants to have \"const char *string\" while the other variant wants to\n+ * have \"char *string\" for type safety.\n+ *\n+ * This accepts \"const char *string\" to allow both wrappers to use it;\n+ * it internally casts away the constness when in_place is true by\n+ * taking advantage of strpbrk() that takes a \"const char *\" arg and\n+ * returns \"char *\" pointer into that const string.  Yucky but works ;-).\n+ */\n+static int split_string(struct string_list *list, const char *string, const char *delim,\n+\t\t\tint maxsplit, int in_place)\n {\n \tint count = 0;\n-\tconst char *p = string, *end;\n+\tconst char *p = string;\n+\n+\tif (in_place && list->strdup_strings)\n+\t\tBUG(\"string_list_split_in_place() called with strdup_strings\");\n+\telse if (!in_place && !list->strdup_strings)\n+\t\tBUG(\"string_list_split() called without strdup_strings\");\n \n-\tif (!list->strdup_strings)\n-\t\tBUG(\"internal error in string_list_split(): \"\n-\t\t    \"list->strdup_strings must be set\");\n \tfor (;;) {\n-\t\tcount++;\n-\t\tif (maxsplit >= 0 && count > maxsplit) {\n-\t\t\tstring_list_append(list, p);\n-\t\t\treturn count;\n-\t\t}\n-\t\tend = strpbrk(p, delim);\n-\t\tif (end) {\n-\t\t\tstring_list_append_nodup(list, xmemdupz(p, end - p));\n-\t\t\tp = end + 1;\n-\t\t} else {\n-\t\t\tstring_list_append(list, p);\n+\t\tchar *end;\n+\n+\t\tif (0 <= maxsplit && maxsplit <= count)\n+\t\t\tend = NULL;\n+\t\telse\n+\t\t\tend = strpbrk(p, delim);\n+\n+\t\tcount += append_one(list, p, end, in_place);\n+\n+\t\tif (!end)\n \t\t\treturn count;\n-\t\t}\n+\t\tp = end + 1;\n \t}\n }\n \n+int string_list_split(struct string_list *list, const char *string,\n+\t\t      const char *delim, int maxsplit)\n+{\n+\treturn split_string(list, string, delim, maxsplit, 0);\n+}\n+\n int string_list_split_in_place(struct string_list *list, char *string,\n \t\t\t       const char *delim, int maxsplit)\n {\n-\tint count = 0;\n-\tchar *p = string, *end;\n-\n-\tif (list->strdup_strings)\n-\t\tBUG(\"internal error in string_list_split_in_place(): \"\n-\t\t    \"list->strdup_strings must not be set\");\n-\tfor (;;) {\n-\t\tcount++;\n-\t\tif (maxsplit >= 0 && count > maxsplit) {\n-\t\t\tstring_list_append(list, p);\n-\t\t\treturn count;\n-\t\t}\n-\t\tend = strpbrk(p, delim);\n-\t\tif (end) {\n-\t\t\t*end = '\\0';\n-\t\t\tstring_list_append(list, p);\n-\t\t\tp = end + 1;\n-\t\t} else {\n-\t\t\tstring_list_append(list, p);\n-\t\t\treturn count;\n-\t\t}\n-\t}\n+\treturn split_string(list, string, delim, maxsplit, 1);\n }\n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523312","messageId":"20250803065223.3325111-5-gitster@pobox.com","threadId":"63881","inReplyTo":"20250803065223.3325111-1-gitster@pobox.com","subject":"[PATCH v4 4/7] string-list: optionally trim string pieces split by string_list_split*()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:52:20Z","receivedAt":"2025-08-03T06:52:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Teach the unified split_string() to take an optional \"flags\" word,\nand define the first flag STRING_LIST_SPLIT_TRIM to cause the split\npieces to be trimmed before they are placed in the string list.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n string-list.c                | 35 +++++++++++++++++---\n string-list.h                | 15 +++++++++\n t/unit-tests/u-string-list.c | 64 ++++++++++++++++++++++++++++++++++++\n 3 files changed, 109 insertions(+), 5 deletions(-)\n\ndiff --git a/string-list.c b/string-list.c\nindex 65b6ceb259..86a309f8fb 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -282,11 +282,18 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_\n  */\n static int append_one(struct string_list *list,\n \t\t      const char *p, const char *end,\n-\t\t      int in_place)\n+\t\t      int in_place, unsigned flags)\n {\n \tif (!end)\n \t\tend = p + strlen(p);\n \n+\tif ((flags & STRING_LIST_SPLIT_TRIM)) {\n+\t\t/* rtrim */\n+\t\tfor (; p < end; end--)\n+\t\t\tif (!isspace(end[-1]))\n+\t\t\t\tbreak;\n+\t}\n+\n \tif (in_place) {\n \t\t*((char *)end) = '\\0';\n \t\tstring_list_append(list, p);\n@@ -307,7 +314,7 @@ static int append_one(struct string_list *list,\n  * returns \"char *\" pointer into that const string.  Yucky but works ;-).\n  */\n static int split_string(struct string_list *list, const char *string, const char *delim,\n-\t\t\tint maxsplit, int in_place)\n+\t\t\tint maxsplit, int in_place, unsigned flags)\n {\n \tint count = 0;\n \tconst char *p = string;\n@@ -320,12 +327,18 @@ static int split_string(struct string_list *list, const char *string, const char\n \tfor (;;) {\n \t\tchar *end;\n \n+\t\tif (flags & STRING_LIST_SPLIT_TRIM) {\n+\t\t\t/* ltrim */\n+\t\t\twhile (*p && isspace(*p))\n+\t\t\t\tp++;\n+\t\t}\n+\n \t\tif (0 <= maxsplit && maxsplit <= count)\n \t\t\tend = NULL;\n \t\telse\n \t\t\tend = strpbrk(p, delim);\n \n-\t\tcount += append_one(list, p, end, in_place);\n+\t\tcount += append_one(list, p, end, in_place, flags);\n \n \t\tif (!end)\n \t\t\treturn count;\n@@ -336,11 +349,23 @@ static int split_string(struct string_list *list, const char *string, const char\n int string_list_split(struct string_list *list, const char *string,\n \t\t      const char *delim, int maxsplit)\n {\n-\treturn split_string(list, string, delim, maxsplit, 0);\n+\treturn split_string(list, string, delim, maxsplit, 0, 0);\n }\n \n int string_list_split_in_place(struct string_list *list, char *string,\n \t\t\t       const char *delim, int maxsplit)\n {\n-\treturn split_string(list, string, delim, maxsplit, 1);\n+\treturn split_string(list, string, delim, maxsplit, 1, 0);\n+}\n+\n+int string_list_split_f(struct string_list *list, const char *string,\n+\t\t\tconst char *delim, int maxsplit, unsigned flags)\n+{\n+\treturn split_string(list, string, delim, maxsplit, 0, flags);\n+}\n+\n+int string_list_split_in_place_f(struct string_list *list, char *string,\n+\t\t\t       const char *delim, int maxsplit, unsigned flags)\n+{\n+\treturn split_string(list, string, delim, maxsplit, 1, flags);\n }\ndiff --git a/string-list.h b/string-list.h\nindex 6c8650efde..40e148712d 100644\n--- a/string-list.h\n+++ b/string-list.h\n@@ -281,4 +281,19 @@ int string_list_split(struct string_list *list, const char *string,\n  */\n int string_list_split_in_place(struct string_list *list, char *string,\n \t\t\t       const char *delim, int maxsplit);\n+\n+/* Flag bits for split_f and split_in_place_f functions */\n+enum {\n+\t/*\n+\t * trim whitespaces around resulting string piece before adding\n+\t * it to the list\n+\t */\n+\tSTRING_LIST_SPLIT_TRIM = (1 << 0),\n+};\n+\n+int string_list_split_f(struct string_list *, const char *string,\n+\t\t\tconst char *delim, int maxsplit, unsigned flags);\n+\n+int string_list_split_in_place_f(struct string_list *, char *string,\n+\t\t\t\t const char *delim, int maxsplit, unsigned flags);\n #endif /* STRING_LIST_H */\ndiff --git a/t/unit-tests/u-string-list.c b/t/unit-tests/u-string-list.c\nindex 150a5f505f..daa9307e45 100644\n--- a/t/unit-tests/u-string-list.c\n+++ b/t/unit-tests/u-string-list.c\n@@ -63,6 +63,70 @@ static void t_string_list_split(const char *data, const char *delim, int maxspli\n \tstring_list_clear(&list, 0);\n }\n \n+static void t_string_list_split_f(const char *data, const char *delim,\n+\t\t\t\t  int maxsplit, unsigned flags, ...)\n+{\n+\tstruct string_list expected_strings = STRING_LIST_INIT_DUP;\n+\tstruct string_list list = STRING_LIST_INIT_DUP;\n+\tva_list ap;\n+\tint len;\n+\n+\tva_start(ap, flags);\n+\tt_vcreate_string_list_dup(&expected_strings, 0, ap);\n+\tva_end(ap);\n+\n+\tstring_list_clear(&list, 0);\n+\tlen = string_list_split_f(&list, data, delim, maxsplit, flags);\n+\tcl_assert_equal_i(len, expected_strings.nr);\n+\tt_string_list_equal(&list, &expected_strings);\n+\n+\tstring_list_clear(&expected_strings, 0);\n+\tstring_list_clear(&list, 0);\n+}\n+\n+void test_string_list__split_f(void)\n+{\n+\tt_string_list_split_f(\"::foo:bar:baz:\", \":\", -1, 0,\n+\t\t\t      \"\", \"\", \"foo\", \"bar\", \"baz\", \"\", NULL);\n+\tt_string_list_split_f(\" foo:bar : baz\", \":\", -1, STRING_LIST_SPLIT_TRIM,\n+\t\t\t      \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split_f(\"  a  b c  \", \" \", 1, STRING_LIST_SPLIT_TRIM,\n+\t\t\t      \"a\", \"b c\", NULL);\n+}\n+\n+static void t_string_list_split_in_place_f(const char *data_, const char *delim,\n+\t\t\t\t\t   int maxsplit, unsigned flags, ...)\n+{\n+\tstruct string_list expected_strings = STRING_LIST_INIT_DUP;\n+\tstruct string_list list = STRING_LIST_INIT_NODUP;\n+\tchar *data = xstrdup(data_);\n+\tva_list ap;\n+\tint len;\n+\n+\tva_start(ap, flags);\n+\tt_vcreate_string_list_dup(&expected_strings, 0, ap);\n+\tva_end(ap);\n+\n+\tstring_list_clear(&list, 0);\n+\tlen = string_list_split_in_place_f(&list, data, delim, maxsplit, flags);\n+\tcl_assert_equal_i(len, expected_strings.nr);\n+\tt_string_list_equal(&list, &expected_strings);\n+\n+\tfree(data);\n+\tstring_list_clear(&expected_strings, 0);\n+\tstring_list_clear(&list, 0);\n+}\n+\n+void test_string_list__split_in_place_f(void)\n+{\n+\tt_string_list_split_in_place_f(\"::foo:bar:baz:\", \":\", -1, 0,\n+\t\t\t\t       \"\", \"\", \"foo\", \"bar\", \"baz\", \"\", NULL);\n+\tt_string_list_split_in_place_f(\" foo:bar : baz\", \":\", -1, STRING_LIST_SPLIT_TRIM,\n+\t\t\t\t       \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split_in_place_f(\"  a  b c  \", \" \", 1, STRING_LIST_SPLIT_TRIM,\n+\t\t\t\t       \"a\", \"b c\", NULL);\n+}\n+\n void test_string_list__split(void)\n {\n \tt_string_list_split(\"foo:bar:baz\", \":\", -1, \"foo\", \"bar\", \"baz\", NULL);\n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523313","messageId":"20250803065223.3325111-6-gitster@pobox.com","threadId":"63881","inReplyTo":"20250803065223.3325111-1-gitster@pobox.com","subject":"[PATCH v4 5/7] diff: simplify parsing of diff.colormovedws","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:52:21Z","receivedAt":"2025-08-03T06:52:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The code to parse this configuration variable, whose value is a\ncomma-separated list of known tokens like \"ignore-space-change\" and\n\"ignore-all-space\", uses string_list_split() to split the value into\npieces, and then places each piece of string in a strbuf to trim,\nbefore comparing the result with the list of known tokens.\n\nThanks to the previous steps, now string_list_split() can trim the\nresulting pieces before it places them in the string list.  Use it\nto simplify the code.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n diff.c | 20 +++++++-------------\n 1 file changed, 7 insertions(+), 13 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex a81949a422..70666ad2cd 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -327,29 +327,23 @@ static unsigned parse_color_moved_ws(const char *arg)\n \tstruct string_list l = STRING_LIST_INIT_DUP;\n \tstruct string_list_item *i;\n \n-\tstring_list_split(&l, arg, \",\", -1);\n+\tstring_list_split_f(&l, arg, \",\", -1, STRING_LIST_SPLIT_TRIM);\n \n \tfor_each_string_list_item(i, &l) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\tstrbuf_addstr(&sb, i->string);\n-\t\tstrbuf_trim(&sb);\n-\n-\t\tif (!strcmp(sb.buf, \"no\"))\n+\t\tif (!strcmp(i->string, \"no\"))\n \t\t\tret = 0;\n-\t\telse if (!strcmp(sb.buf, \"ignore-space-change\"))\n+\t\telse if (!strcmp(i->string, \"ignore-space-change\"))\n \t\t\tret |= XDF_IGNORE_WHITESPACE_CHANGE;\n-\t\telse if (!strcmp(sb.buf, \"ignore-space-at-eol\"))\n+\t\telse if (!strcmp(i->string, \"ignore-space-at-eol\"))\n \t\t\tret |= XDF_IGNORE_WHITESPACE_AT_EOL;\n-\t\telse if (!strcmp(sb.buf, \"ignore-all-space\"))\n+\t\telse if (!strcmp(i->string, \"ignore-all-space\"))\n \t\t\tret |= XDF_IGNORE_WHITESPACE;\n-\t\telse if (!strcmp(sb.buf, \"allow-indentation-change\"))\n+\t\telse if (!strcmp(i->string, \"allow-indentation-change\"))\n \t\t\tret |= COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE;\n \t\telse {\n \t\t\tret |= COLOR_MOVED_WS_ERROR;\n-\t\t\terror(_(\"unknown color-moved-ws mode '%s', possible values are 'ignore-space-change', 'ignore-space-at-eol', 'ignore-all-space', 'allow-indentation-change'\"), sb.buf);\n+\t\t\terror(_(\"unknown color-moved-ws mode '%s', possible values are 'ignore-space-change', 'ignore-space-at-eol', 'ignore-all-space', 'allow-indentation-change'\"), i->string);\n \t\t}\n-\n-\t\tstrbuf_release(&sb);\n \t}\n \n \tif ((ret & COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) &&\n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523314","messageId":"20250803065223.3325111-7-gitster@pobox.com","threadId":"63881","inReplyTo":"20250803065223.3325111-1-gitster@pobox.com","subject":"[PATCH v4 6/7] string-list: optionally omit empty string pieces in string_list_split*()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:52:22Z","receivedAt":"2025-08-03T06:52:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Teach the unified split_string() machinery a new flag bit,\nSTRING_LIST_SPLIT_NONEMPTY, to cause empty split pieces to be\nomitted from the resulting string list.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n string-list.c                |  3 +++\n string-list.h                |  2 ++\n t/unit-tests/u-string-list.c | 15 +++++++++++++++\n 3 files changed, 20 insertions(+)\n\ndiff --git a/string-list.c b/string-list.c\nindex 86a309f8fb..343cf1ca90 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -294,6 +294,9 @@ static int append_one(struct string_list *list,\n \t\t\t\tbreak;\n \t}\n \n+\tif ((flags & STRING_LIST_SPLIT_NONEMPTY) && (end <= p))\n+\t\treturn 0;\n+\n \tif (in_place) {\n \t\t*((char *)end) = '\\0';\n \t\tstring_list_append(list, p);\ndiff --git a/string-list.h b/string-list.h\nindex 40e148712d..2b438c7733 100644\n--- a/string-list.h\n+++ b/string-list.h\n@@ -289,6 +289,8 @@ enum {\n \t * it to the list\n \t */\n \tSTRING_LIST_SPLIT_TRIM = (1 << 0),\n+\t/* omit adding empty string piece to the resulting list */\n+\tSTRING_LIST_SPLIT_NONEMPTY = (1 << 1),\n };\n \n int string_list_split_f(struct string_list *, const char *string,\ndiff --git a/t/unit-tests/u-string-list.c b/t/unit-tests/u-string-list.c\nindex daa9307e45..a2457d7b1e 100644\n--- a/t/unit-tests/u-string-list.c\n+++ b/t/unit-tests/u-string-list.c\n@@ -92,6 +92,13 @@ void test_string_list__split_f(void)\n \t\t\t      \"foo\", \"bar\", \"baz\", NULL);\n \tt_string_list_split_f(\"  a  b c  \", \" \", 1, STRING_LIST_SPLIT_TRIM,\n \t\t\t      \"a\", \"b c\", NULL);\n+\tt_string_list_split_f(\"::foo::bar:baz:\", \":\", -1, STRING_LIST_SPLIT_NONEMPTY,\n+\t\t\t      \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split_f(\"foo:baz\", \":\", -1, STRING_LIST_SPLIT_NONEMPTY,\n+\t\t\t      \"foo\", \"baz\", NULL);\n+\tt_string_list_split_f(\"foo :: : baz\", \":\", -1,\n+\t\t\t      STRING_LIST_SPLIT_NONEMPTY | STRING_LIST_SPLIT_TRIM,\n+\t\t\t      \"foo\", \"baz\", NULL);\n }\n \n static void t_string_list_split_in_place_f(const char *data_, const char *delim,\n@@ -125,6 +132,14 @@ void test_string_list__split_in_place_f(void)\n \t\t\t\t       \"foo\", \"bar\", \"baz\", NULL);\n \tt_string_list_split_in_place_f(\"  a  b c  \", \" \", 1, STRING_LIST_SPLIT_TRIM,\n \t\t\t\t       \"a\", \"b c\", NULL);\n+\tt_string_list_split_in_place_f(\"::foo::bar:baz:\", \":\", -1,\n+\t\t\t\t       STRING_LIST_SPLIT_NONEMPTY,\n+\t\t\t\t       \"foo\", \"bar\", \"baz\", NULL);\n+\tt_string_list_split_in_place_f(\"foo:baz\", \":\", -1, STRING_LIST_SPLIT_NONEMPTY,\n+\t\t\t\t       \"foo\", \"baz\", NULL);\n+\tt_string_list_split_in_place_f(\"foo :: : baz\", \":\", -1,\n+\t\t\t\t       STRING_LIST_SPLIT_NONEMPTY | STRING_LIST_SPLIT_TRIM,\n+\t\t\t\t       \"foo\", \"baz\", NULL);\n }\n \n void test_string_list__split(void)\n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523315","messageId":"20250803065223.3325111-8-gitster@pobox.com","threadId":"63881","inReplyTo":"20250803065223.3325111-1-gitster@pobox.com","subject":"[PATCH v4 7/7] string-list: split-then-remove-empty can be done while splitting","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:52:23Z","receivedAt":"2025-08-03T06:52:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thanks to the new STRING_LIST_SPLIT_NONEMPTY flag, a common pattern\nto split a string into a string list and then remove empty items in\nthe resulting list is no longer needed.  Instead, just tell the\nstring_list_split*() to omit empty ones while splitting.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n notes.c                     | 4 ++--\n pathspec.c                  | 3 +--\n t/helper/test-hashmap.c     | 4 ++--\n t/helper/test-json-writer.c | 4 ++--\n 4 files changed, 7 insertions(+), 8 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex 6afcf088b9..3603c4a42b 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -970,8 +970,8 @@ void string_list_add_refs_from_colon_sep(struct string_list *list,\n \tchar *globs_copy = xstrdup(globs);\n \tint i;\n \n-\tstring_list_split_in_place(&split, globs_copy, \":\", -1);\n-\tstring_list_remove_empty_items(&split, 0);\n+\tstring_list_split_in_place_f(&split, globs_copy, \":\", -1,\n+\t\t\t\t     STRING_LIST_SPLIT_NONEMPTY);\n \n \tfor (i = 0; i < split.nr; i++)\n \t\tstring_list_add_refs_by_glob(list, split.items[i].string);\ndiff --git a/pathspec.c b/pathspec.c\nindex de325f7ef9..5993c4afa0 100644\n--- a/pathspec.c\n+++ b/pathspec.c\n@@ -201,8 +201,7 @@ static void parse_pathspec_attr_match(struct pathspec_item *item, const char *va\n \tif (!value || !*value)\n \t\tdie(_(\"attr spec must not be empty\"));\n \n-\tstring_list_split(&list, value, \" \", -1);\n-\tstring_list_remove_empty_items(&list, 0);\n+\tstring_list_split_f(&list, value, \" \", -1, STRING_LIST_SPLIT_NONEMPTY);\n \n \titem->attr_check = attr_check_alloc();\n \tCALLOC_ARRAY(item->attr_match, list.nr);\ndiff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c\nindex 7782ae585e..e4dc02bd7a 100644\n--- a/t/helper/test-hashmap.c\n+++ b/t/helper/test-hashmap.c\n@@ -149,8 +149,8 @@ int cmd__hashmap(int argc UNUSED, const char **argv UNUSED)\n \n \t\t/* break line into command and up to two parameters */\n \t\tstring_list_setlen(&parts, 0);\n-\t\tstring_list_split_in_place(&parts, line.buf, DELIM, 2);\n-\t\tstring_list_remove_empty_items(&parts, 0);\n+\t\tstring_list_split_in_place_f(&parts, line.buf, DELIM, 2,\n+\t\t\t\t\t     STRING_LIST_SPLIT_NONEMPTY);\n \n \t\t/* ignore empty lines */\n \t\tif (!parts.nr)\ndiff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c\nindex a288069b04..f8316a7d29 100644\n--- a/t/helper/test-json-writer.c\n+++ b/t/helper/test-json-writer.c\n@@ -492,8 +492,8 @@ static int scripted(void)\n \n \t\t/* break line into command and zero or more tokens */\n \t\tstring_list_setlen(&parts, 0);\n-\t\tstring_list_split_in_place(&parts, line, \" \", -1);\n-\t\tstring_list_remove_empty_items(&parts, 0);\n+\t\tstring_list_split_in_place_f(&parts, line, \" \", -1,\n+\t\t\t\t\t     STRING_LIST_SPLIT_NONEMPTY);\n \n \t\t/* ignore empty lines */\n \t\tif (!parts.nr || !*parts.items[0].string)\n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523316","messageId":"20250803065304.3325286-1-gitster@pobox.com","threadId":"63881","inReplyTo":"20250801220423.1230969-1-gitster@pobox.com","subject":"[PATCH v3 00/12] do not overuse strbuf_split*()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:52:52Z","receivedAt":"2025-08-03T06:53:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"strbuf is a very good data structure to work with string data\nwithout having to worry about running past the end of the string.\n\nBut an array of strbuf is often a wrong data structure.  You rarely\nhave need to be able to edit multiple strings represented by such an\narray simultaneously.  And strbuf_split*() that produces result in\nsuch a shape is a misdesigned API function.\n\nThe most common use case of strbuf_split*() family of functions\nseems to be to trim away the whitespaces around each piece of split\nstring.  With modern string_list_split*(), it is often no longer\nnecessary.\n\nThis series builds on top of the other series that extends string\nlist API to allow string_list_split() to take more than one delimiter\nbytes, and to optionally trim the resulting string pieces.\n\nI do not plan to eradicate all the uses of strbuf_split*() myself,\nnot because I found some valid use cases in the existing code (I\nhaven't yet), but these patches would give interested others enough\nmaterial to study and mimic to continue the effort and I can safely\nleave it as #leftoverbits to rewrite them.\n\nRelative to v2, this iteration v3 adds one more clean-up step to\ncorrect a callee that insists on taking a while strbuf when it can\nwork with any NUL-terminated strings, and comes with a handful of\ntypofixes.\n\nJunio C Hamano (12):\n  wt-status: avoid strbuf_split*()\n  clean: do not pass strbuf by value\n  clean: do not use strbuf_split*() [part 1]\n  clean: do not pass the whole structure when it is not necessary\n  clean: do not use strbuf_split*() [part 2]\n  merge-tree: do not use strbuf_split*()\n  notes: do not use strbuf_split*()\n  config: do not use strbuf_split()\n  environment: do not use strbuf_split*()\n  sub-process: do not use strbuf_split*()\n  trace2: trim_trailing_newline followed by trim is a no-op\n  trace2: do not use strbuf_split*()\n\n builtin/clean.c      | 74 ++++++++++++++++++++--------------------\n builtin/merge-tree.c | 30 +++++++++--------\n builtin/notes.c      | 23 +++++++------\n config.c             | 23 ++++++-------\n environment.c        | 19 +++++++----\n sub-process.c        | 15 ++++-----\n trace2/tr2_cfg.c     | 80 +++++++++++++++-----------------------------\n wt-status.c          | 31 ++++++-----------\n 8 files changed, 129 insertions(+), 166 deletions(-)\n\nRange-diff against v2:\n 1:  27de3d9a92 =  1:  2efe707054 wt-status: avoid strbuf_split*()\n 2:  8f096e5a2d =  2:  899ff9c175 clean: do not pass strbuf by value\n 3:  768b08907e =  3:  7a4acc3607 clean: do not use strbuf_split*() [part 1]\n -:  ---------- >  4:  4985f72ea5 clean: do not pass the whole structure when it is not necessary\n 4:  0f8583e798 =  5:  4f60672f6f clean: do not use strbuf_split*() [part 2]\n 5:  cefc2ec9f5 =  6:  d33091220d merge-tree: do not use strbuf_split*()\n 6:  1c8ea097f6 !  7:  566e910495 notes: do not use strbuf_split*()\n    @@ Metadata\n      ## Commit message ##\n         notes: do not use strbuf_split*()\n     \n    -    When reading the copy instruction from the standard input, the\n    -    program reads a line, splits it into tokens at whitespace, and trims\n    -    each of the tokens before using.  We no longer need to use strbuf\n    -    just to be able to trimming, as string_list_split*() family now can\n    -    trim while splitting a string.\n    +    When reading copy instructions from the standard input, the program\n    +    reads a line, splits it into tokens at whitespace, and trims each of\n    +    the tokens before using.  We no longer need to use strbuf just to be\n    +    able to trim, as string_list_split*() family now can trim while\n    +    splitting a string.\n     \n    -    Retire the use of strbuf_split().\n    +    Retire the use of strbuf_split() from this code path.\n     \n         Note that this loop is a bit sloppy in that it ensures at least\n         there are two tokens on each line, but ignores if there are extra\n 7:  a472688ec1 !  8:  dcecac2580 config: do not use strbuf_split()\n    @@ Commit message\n         config: do not use strbuf_split()\n     \n         When parsing an old-style GIT_CONFIG_PARAMETERS environment\n    -    variable, the code parses the key=value pair by spliting them at '='\n    -    into an array of strbuf's.  As strbuf_split() leafes the delimiter\n    +    variable, the code parses key=value pairs by splitting them at '='\n    +    into an array of strbuf's.  As strbuf_split() leaves the delimiter\n         at the end of the split piece, the code has to manually trim it.\n     \n         If we split with string_list_split(), that becomes unnecessary.\n    -    Retire the use of strbuf_split().\n    +    Retire the use of strbuf_split() from this code path.\n     \n         Note that the max parameter of string_list_split() is of\n         an ergonomically iffy design---it specifies the maximum number of\n 8:  2b9957f31c =  9:  b894d4481f environment: do not use strbuf_split*()\n 9:  4a5599836d = 10:  d6fd08bd76 sub-process: do not use strbuf_split*()\n10:  cf6ecd2090 ! 11:  cb8e82a641 trace2: trim_trailing_newline followed by trim is a no-op\n    @@ Commit message\n         of a string.  If the code plans to call strbuf_trim() immediately\n         after doing so, the code is better off skipping the EOL trimming in\n         the first place.  After all, LF/CRLF at the end is a mere special\n    -    case of whitespaces at the right end of the string, which will be\n    -    removed by strbuf_rtrim().\n    +    case of whitespaces at the end of the string, which will be removed\n    +    by strbuf_rtrim() anyway.\n     \n         Signed-off-by: Junio C Hamano <gitster@pobox.com>\n     \n11:  c2578b6b1c ! 12:  838fe56920 trace2: do not use strbuf_split*()\n    @@ Metadata\n      ## Commit message ##\n         trace2: do not use strbuf_split*()\n     \n    -    tr2_cfg_load_patterns() and tr2_load_env_vars() functions are copied\n    -    and pasted pair of functions that each reads an environment\n    -    variable, split the value at ',' boundaries and trims the resulting\n    -    string pieces into an array of strbufs.  But the code paths that\n    -    later use these strbufs take no advantage of the strbuf-ness of the\n    -    result (they do not benefit from <ptr,len> representation to avoid\n    -    having to run strlne(<ptr>), for example).\n    +    tr2_cfg_load_patterns() and tr2_load_env_vars() functions are\n    +    functions with very similar structure that each reads an environment\n    +    variable, splits its value at the ',' boundaries, and trims the\n    +    resulting string pieces into an array of strbufs.\n    +\n    +    But the code paths that later use these strbufs take no advantage of\n    +    the strbuf-ness of the result (they do not benefit from <ptr,len>\n    +    representation to avoid having to run strlen(<ptr>), for example).\n     \n         Simplify the code by teaching these functions to split into a string\n    -    list instead.\n    +    list instead; even the trimming comes for free ;-).\n     \n         Signed-off-by: Junio C Hamano <gitster@pobox.com>\n     \n     \n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523317","messageId":"20250803065304.3325286-2-gitster@pobox.com","threadId":"63881","inReplyTo":"20250803065304.3325286-1-gitster@pobox.com","subject":"[PATCH v3 01/12] wt-status: avoid strbuf_split*()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:52:53Z","receivedAt":"2025-08-03T06:53:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"strbuf is a very good data structure to work with string data\nwithout having to worry about running past the end of the string,\nbut strbuf_split() is a wrong API and an array of strbuf that the\nfunction produces is a wrong thing to use in general.  You do not\nedit these N strings split out of a single strbuf simultaneously.\nOften it is much better off to split a string into string_list and\nwork with the resulting strings.\n\nwt-status.c:abbrev_oid_in_line() takes one line of rebase todo list\n(like \"pick e813a0200a7121b97fec535f0d0b460b0a33356c title\"), and\nfor instructions that has an object name as the second token on the\nline, replace the object name with its unique abbreviation.  After\nsplitting these tokens out of a single line, no simultaneous edit on\nany of these pieces of string that takes advantage of strbuf API\ntakes place.  The final string is composed with strbuf API, but\nthese split pieces are merely used as pieces of strings and there is\nno need for them to be stored in individual strbuf.\n\nInstead, split the line into a string_list, and compose the final\nstring using these pieces.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n wt-status.c | 31 ++++++++++---------------------\n 1 file changed, 10 insertions(+), 21 deletions(-)\n\ndiff --git a/wt-status.c b/wt-status.c\nindex 454601afa1..a34dc144ee 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -1351,8 +1351,8 @@ static int split_commit_in_progress(struct wt_status *s)\n  */\n static void abbrev_oid_in_line(struct strbuf *line)\n {\n-\tstruct strbuf **split;\n-\tint i;\n+\tstruct string_list split = STRING_LIST_INIT_DUP;\n+\tstruct object_id oid;\n \n \tif (starts_with(line->buf, \"exec \") ||\n \t    starts_with(line->buf, \"x \") ||\n@@ -1360,26 +1360,15 @@ static void abbrev_oid_in_line(struct strbuf *line)\n \t    starts_with(line->buf, \"l \"))\n \t\treturn;\n \n-\tsplit = strbuf_split_max(line, ' ', 3);\n-\tif (split[0] && split[1]) {\n-\t\tstruct object_id oid;\n-\n-\t\t/*\n-\t\t * strbuf_split_max left a space. Trim it and re-add\n-\t\t * it after abbreviation.\n-\t\t */\n-\t\tstrbuf_trim(split[1]);\n-\t\tif (!repo_get_oid(the_repository, split[1]->buf, &oid)) {\n-\t\t\tstrbuf_reset(split[1]);\n-\t\t\tstrbuf_add_unique_abbrev(split[1], &oid,\n-\t\t\t\t\t\t DEFAULT_ABBREV);\n-\t\t\tstrbuf_addch(split[1], ' ');\n-\t\t\tstrbuf_reset(line);\n-\t\t\tfor (i = 0; split[i]; i++)\n-\t\t\t\tstrbuf_addbuf(line, split[i]);\n-\t\t}\n+\tif ((2 <= string_list_split(&split, line->buf, \" \", 2)) &&\n+\t    !repo_get_oid(the_repository, split.items[1].string, &oid)) {\n+\t\tstrbuf_reset(line);\n+\t\tstrbuf_addf(line, \"%s \", split.items[0].string);\n+\t\tstrbuf_add_unique_abbrev(line, &oid, DEFAULT_ABBREV);\n+\t\tfor (size_t i = 2; i < split.nr; i++)\n+\t\t\tstrbuf_addf(line, \" %s\", split.items[i].string);\n \t}\n-\tstrbuf_list_free(split);\n+\tstring_list_clear(&split, 0);\n }\n \n static int read_rebase_todolist(const char *fname, struct string_list *lines)\n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523318","messageId":"20250803065304.3325286-3-gitster@pobox.com","threadId":"63881","inReplyTo":"20250803065304.3325286-1-gitster@pobox.com","subject":"[PATCH v3 02/12] clean: do not pass strbuf by value","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:52:54Z","receivedAt":"2025-08-03T06:53:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"When you pass a structure by value, the callee can modify the\ncontents of the structure that was passed in without having to worry\nabout changing the structure the caller has.  Passing structure by\nvalue sometimes (but not very often) can be a valid way to give\ncallee a temporary variable it can freely modify.\n\nBut not a structure with members that are pointers, like a strbuf.\n\nbuiltin/clean.c:list_and_choose() reads a line interactively from\nthe user, and passes the line (in a strbuf) to parse_choice() by\nvalue, which then munges by replacing ',' with ' ' (to accept both\ncomma and space separated list of choices).  But because the strbuf\npassed by value still shares the underlying character array buf[],\nthis ends up munging the caller's strbuf contents.\n\nThis is a catastrophe waiting to happen.  If the callee causes the\nstrbuf to be reallocated, the buf[] the caller has will become\ndangling, and when the caller does strbuf_release(), it would result\nin double-free.\n\nStop calling the function with misleading call-by-value with strbuf.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/clean.c | 10 +++++-----\n 1 file changed, 5 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 053c94fc6b..224551537e 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -477,7 +477,7 @@ static int find_unique(const char *choice, struct menu_stuff *menu_stuff)\n  */\n static int parse_choice(struct menu_stuff *menu_stuff,\n \t\t\tint is_single,\n-\t\t\tstruct strbuf input,\n+\t\t\tstruct strbuf *input,\n \t\t\tint **chosen)\n {\n \tstruct strbuf **choice_list, **ptr;\n@@ -485,14 +485,14 @@ static int parse_choice(struct menu_stuff *menu_stuff,\n \tint i;\n \n \tif (is_single) {\n-\t\tchoice_list = strbuf_split_max(&input, '\\n', 0);\n+\t\tchoice_list = strbuf_split_max(input, '\\n', 0);\n \t} else {\n-\t\tchar *p = input.buf;\n+\t\tchar *p = input->buf;\n \t\tdo {\n \t\t\tif (*p == ',')\n \t\t\t\t*p = ' ';\n \t\t} while (*p++);\n-\t\tchoice_list = strbuf_split_max(&input, ' ', 0);\n+\t\tchoice_list = strbuf_split_max(input, ' ', 0);\n \t}\n \n \tfor (ptr = choice_list; *ptr; ptr++) {\n@@ -630,7 +630,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)\n \n \t\tnr = parse_choice(stuff,\n \t\t\t\t  opts->flags & MENU_OPTS_SINGLETON,\n-\t\t\t\t  choice,\n+\t\t\t\t  &choice,\n \t\t\t\t  &chosen);\n \n \t\tif (opts->flags & MENU_OPTS_SINGLETON) {\n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523319","messageId":"20250803065304.3325286-4-gitster@pobox.com","threadId":"63881","inReplyTo":"20250803065304.3325286-1-gitster@pobox.com","subject":"[PATCH v3 03/12] clean: do not use strbuf_split*() [part 1]","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:52:55Z","receivedAt":"2025-08-03T06:53:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"builtin/clean.c:parse_choice() is fed a single line of input, which\nis space or comma separated list of tokens, and a list of menu\nitems.  It parses the tokens into number ranges (e.g. 1-3 that means\nthe first three items) or string prefix (e.g. 's' to choose the menu\nitem \"(s)elect\") that specify the elements in the menu item list,\nand tells the caller which ones are chosen.\n\nFor parsing the input string, it uses strbuf_split() to split it\ninto bunch of strbufs.  Instead use string_list_split_in_place(),\nfor a few reasons.\n\n * strbuf_split() is a bad API function to use, that yields an array\n   of strbuf that is a bad data structure to use in general.\n\n * string_list_split_in_place() allows you to split with \"comma or\n   space\"; the current code has to preprocess the input string to\n   replace comma with space because strbuf_split() does not allow\n   this.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/clean.c | 50 +++++++++++++++++++++++--------------------------\n 1 file changed, 23 insertions(+), 27 deletions(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 224551537e..708cd9344c 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -480,40 +480,36 @@ static int parse_choice(struct menu_stuff *menu_stuff,\n \t\t\tstruct strbuf *input,\n \t\t\tint **chosen)\n {\n-\tstruct strbuf **choice_list, **ptr;\n+\tstruct string_list choice = STRING_LIST_INIT_NODUP;\n+\tstruct string_list_item *item;\n \tint nr = 0;\n \tint i;\n \n-\tif (is_single) {\n-\t\tchoice_list = strbuf_split_max(input, '\\n', 0);\n-\t} else {\n-\t\tchar *p = input->buf;\n-\t\tdo {\n-\t\t\tif (*p == ',')\n-\t\t\t\t*p = ' ';\n-\t\t} while (*p++);\n-\t\tchoice_list = strbuf_split_max(input, ' ', 0);\n-\t}\n+\tstring_list_split_in_place_f(&choice, input->buf,\n+\t\t\t\t     is_single ? \"\\n\" : \", \", -1,\n+\t\t\t\t     STRING_LIST_SPLIT_TRIM);\n \n-\tfor (ptr = choice_list; *ptr; ptr++) {\n-\t\tchar *p;\n-\t\tint choose = 1;\n+\tfor_each_string_list_item(item, &choice) {\n+\t\tconst char *string;\n+\t\tint choose;\n \t\tint bottom = 0, top = 0;\n \t\tint is_range, is_number;\n \n-\t\tstrbuf_trim(*ptr);\n-\t\tif (!(*ptr)->len)\n+\t\tstring = item->string;\n+\t\tif (!*string)\n \t\t\tcontinue;\n \n \t\t/* Input that begins with '-'; unchoose */\n-\t\tif (*(*ptr)->buf == '-') {\n+\t\tif (string[0] == '-') {\n \t\t\tchoose = 0;\n-\t\t\tstrbuf_remove((*ptr), 0, 1);\n+\t\t\tstring++;\n+\t\t} else {\n+\t\t\tchoose = 1;\n \t\t}\n \n \t\tis_range = 0;\n \t\tis_number = 1;\n-\t\tfor (p = (*ptr)->buf; *p; p++) {\n+\t\tfor (const char *p = string; *p; p++) {\n \t\t\tif ('-' == *p) {\n \t\t\t\tif (!is_range) {\n \t\t\t\t\tis_range = 1;\n@@ -531,27 +527,27 @@ static int parse_choice(struct menu_stuff *menu_stuff,\n \t\t}\n \n \t\tif (is_number) {\n-\t\t\tbottom = atoi((*ptr)->buf);\n+\t\t\tbottom = atoi(string);\n \t\t\ttop = bottom;\n \t\t} else if (is_range) {\n-\t\t\tbottom = atoi((*ptr)->buf);\n+\t\t\tbottom = atoi(string);\n \t\t\t/* a range can be specified like 5-7 or 5- */\n-\t\t\tif (!*(strchr((*ptr)->buf, '-') + 1))\n+\t\t\tif (!*(strchr(string, '-') + 1))\n \t\t\t\ttop = menu_stuff->nr;\n \t\t\telse\n-\t\t\t\ttop = atoi(strchr((*ptr)->buf, '-') + 1);\n-\t\t} else if (!strcmp((*ptr)->buf, \"*\")) {\n+\t\t\t\ttop = atoi(strchr(string, '-') + 1);\n+\t\t} else if (!strcmp(string, \"*\")) {\n \t\t\tbottom = 1;\n \t\t\ttop = menu_stuff->nr;\n \t\t} else {\n-\t\t\tbottom = find_unique((*ptr)->buf, menu_stuff);\n+\t\t\tbottom = find_unique(string, menu_stuff);\n \t\t\ttop = bottom;\n \t\t}\n \n \t\tif (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top ||\n \t\t    (is_single && bottom != top)) {\n \t\t\tclean_print_color(CLEAN_COLOR_ERROR);\n-\t\t\tprintf(_(\"Huh (%s)?\\n\"), (*ptr)->buf);\n+\t\t\tprintf(_(\"Huh (%s)?\\n\"), string);\n \t\t\tclean_print_color(CLEAN_COLOR_RESET);\n \t\t\tcontinue;\n \t\t}\n@@ -560,7 +556,7 @@ static int parse_choice(struct menu_stuff *menu_stuff,\n \t\t\t(*chosen)[i-1] = choose;\n \t}\n \n-\tstrbuf_list_free(choice_list);\n+\tstring_list_clear(&choice, 0);\n \n \tfor (i = 0; i < menu_stuff->nr; i++)\n \t\tnr += (*chosen)[i];\n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523320","messageId":"20250803065304.3325286-5-gitster@pobox.com","threadId":"63881","inReplyTo":"20250803065304.3325286-1-gitster@pobox.com","subject":"[PATCH v3 04/12] clean: do not pass the whole structure when it is not necessary","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:52:56Z","receivedAt":"2025-08-03T06:53:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The callee parse_choice() only needs to access a NUL-terminated\nstring; instead of insisting to take a pointer to a strbuf, just\ntake a pointer to a character array.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/clean.c | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 708cd9344c..9bb920e7fd 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -477,7 +477,7 @@ static int find_unique(const char *choice, struct menu_stuff *menu_stuff)\n  */\n static int parse_choice(struct menu_stuff *menu_stuff,\n \t\t\tint is_single,\n-\t\t\tstruct strbuf *input,\n+\t\t\tchar *input,\n \t\t\tint **chosen)\n {\n \tstruct string_list choice = STRING_LIST_INIT_NODUP;\n@@ -485,7 +485,7 @@ static int parse_choice(struct menu_stuff *menu_stuff,\n \tint nr = 0;\n \tint i;\n \n-\tstring_list_split_in_place_f(&choice, input->buf,\n+\tstring_list_split_in_place_f(&choice, input,\n \t\t\t\t     is_single ? \"\\n\" : \", \", -1,\n \t\t\t\t     STRING_LIST_SPLIT_TRIM);\n \n@@ -626,7 +626,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)\n \n \t\tnr = parse_choice(stuff,\n \t\t\t\t  opts->flags & MENU_OPTS_SINGLETON,\n-\t\t\t\t  &choice,\n+\t\t\t\t  choice.buf,\n \t\t\t\t  &chosen);\n \n \t\tif (opts->flags & MENU_OPTS_SINGLETON) {\n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523321","messageId":"20250803065304.3325286-6-gitster@pobox.com","threadId":"63881","inReplyTo":"20250803065304.3325286-1-gitster@pobox.com","subject":"[PATCH v3 05/12] clean: do not use strbuf_split*() [part 2]","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:52:57Z","receivedAt":"2025-08-03T06:53:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"builtin/clean.c:filter_by_patterns_cmd() interactively reads a line\nthat has exclude patterns from the user and splits the line into a\nlist of patterns.  It uses the strbuf_split() so that each split\npiece can then trimmed.\n\nThere is no need to use strbuf anymore, thanks to the recent\nenhancement to string_list_split*() family that allows us to trim\nthe pieces split into a string_list.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/clean.c | 20 +++++++++++---------\n 1 file changed, 11 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 9bb920e7fd..38780edc39 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -674,12 +674,13 @@ static int filter_by_patterns_cmd(void)\n {\n \tstruct dir_struct dir = DIR_INIT;\n \tstruct strbuf confirm = STRBUF_INIT;\n-\tstruct strbuf **ignore_list;\n-\tstruct string_list_item *item;\n \tstruct pattern_list *pl;\n \tint changed = -1, i;\n \n \tfor (;;) {\n+\t\tstruct string_list ignore_list = STRING_LIST_INIT_NODUP;\n+\t\tstruct string_list_item *item;\n+\n \t\tif (!del_list.nr)\n \t\t\tbreak;\n \n@@ -697,14 +698,15 @@ static int filter_by_patterns_cmd(void)\n \t\t\tbreak;\n \n \t\tpl = add_pattern_list(&dir, EXC_CMDL, \"manual exclude\");\n-\t\tignore_list = strbuf_split_max(&confirm, ' ', 0);\n \n-\t\tfor (i = 0; ignore_list[i]; i++) {\n-\t\t\tstrbuf_trim(ignore_list[i]);\n-\t\t\tif (!ignore_list[i]->len)\n-\t\t\t\tcontinue;\n+\t\tstring_list_split_in_place_f(&ignore_list, confirm.buf, \" \", -1,\n+\t\t\t\t\t     STRING_LIST_SPLIT_TRIM);\n \n-\t\t\tadd_pattern(ignore_list[i]->buf, \"\", 0, pl, -(i+1));\n+\t\tfor (i = 0; i < ignore_list.nr; i++) {\n+\t\t\titem = &ignore_list.items[i];\n+\t\t\tif (!*item->string)\n+\t\t\t\tcontinue;\n+\t\t\tadd_pattern(item->string, \"\", 0, pl, -(i+1));\n \t\t}\n \n \t\tchanged = 0;\n@@ -725,7 +727,7 @@ static int filter_by_patterns_cmd(void)\n \t\t\tclean_print_color(CLEAN_COLOR_RESET);\n \t\t}\n \n-\t\tstrbuf_list_free(ignore_list);\n+\t\tstring_list_clear(&ignore_list, 0);\n \t\tdir_clear(&dir);\n \t}\n \n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523322","messageId":"20250803065304.3325286-7-gitster@pobox.com","threadId":"63881","inReplyTo":"20250803065304.3325286-1-gitster@pobox.com","subject":"[PATCH v3 06/12] merge-tree: do not use strbuf_split*()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:52:58Z","receivedAt":"2025-08-03T06:53:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"When reading merge instructions from the standard input, the program\nreads from the standard input, splits the line into tokens at\nwhitespace, and trims each of them before using.  We no longer need\nto use strbuf just for trimming, as string_list_split*() family can\ntrim while splitting a string.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/merge-tree.c | 30 ++++++++++++++++--------------\n 1 file changed, 16 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex cf8b06cadc..70235856d7 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -618,32 +618,34 @@ int cmd_merge_tree(int argc,\n \t\t\t    \"--merge-base\", \"--stdin\");\n \t\tline_termination = '\\0';\n \t\twhile (strbuf_getline_lf(&buf, stdin) != EOF) {\n-\t\t\tstruct strbuf **split;\n+\t\t\tstruct string_list split = STRING_LIST_INIT_NODUP;\n \t\t\tconst char *input_merge_base = NULL;\n \n-\t\t\tsplit = strbuf_split(&buf, ' ');\n-\t\t\tif (!split[0] || !split[1])\n+\t\t\tstring_list_split_in_place_f(&split, buf.buf, \" \", -1,\n+\t\t\t\t\t\t     STRING_LIST_SPLIT_TRIM);\n+\n+\t\t\tif (split.nr < 2)\n \t\t\t\tdie(_(\"malformed input line: '%s'.\"), buf.buf);\n-\t\t\tstrbuf_rtrim(split[0]);\n-\t\t\tstrbuf_rtrim(split[1]);\n \n \t\t\t/* parse the merge-base */\n-\t\t\tif (!strcmp(split[1]->buf, \"--\")) {\n-\t\t\t\tinput_merge_base = split[0]->buf;\n+\t\t\tif (!strcmp(split.items[1].string, \"--\")) {\n+\t\t\t\tinput_merge_base = split.items[0].string;\n \t\t\t}\n \n-\t\t\tif (input_merge_base && split[2] && split[3] && !split[4]) {\n-\t\t\t\tstrbuf_rtrim(split[2]);\n-\t\t\t\tstrbuf_rtrim(split[3]);\n-\t\t\t\treal_merge(&o, input_merge_base, split[2]->buf, split[3]->buf, prefix);\n-\t\t\t} else if (!input_merge_base && !split[2]) {\n-\t\t\t\treal_merge(&o, NULL, split[0]->buf, split[1]->buf, prefix);\n+\t\t\tif (input_merge_base && split.nr == 4) {\n+\t\t\t\treal_merge(&o, input_merge_base,\n+\t\t\t\t\t   split.items[2].string, split.items[3].string,\n+\t\t\t\t\t   prefix);\n+\t\t\t} else if (!input_merge_base && split.nr == 2) {\n+\t\t\t\treal_merge(&o, NULL,\n+\t\t\t\t\t   split.items[0].string, split.items[1].string,\n+\t\t\t\t\t   prefix);\n \t\t\t} else {\n \t\t\t\tdie(_(\"malformed input line: '%s'.\"), buf.buf);\n \t\t\t}\n \t\t\tmaybe_flush_or_die(stdout, \"stdout\");\n \n-\t\t\tstrbuf_list_free(split);\n+\t\t\tstring_list_clear(&split, 0);\n \t\t}\n \t\tstrbuf_release(&buf);\n \n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523323","messageId":"20250803065304.3325286-8-gitster@pobox.com","threadId":"63881","inReplyTo":"20250803065304.3325286-1-gitster@pobox.com","subject":"[PATCH v3 07/12] notes: do not use strbuf_split*()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:52:59Z","receivedAt":"2025-08-03T06:53:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"When reading copy instructions from the standard input, the program\nreads a line, splits it into tokens at whitespace, and trims each of\nthe tokens before using.  We no longer need to use strbuf just to be\nable to trim, as string_list_split*() family now can trim while\nsplitting a string.\n\nRetire the use of strbuf_split() from this code path.\n\nNote that this loop is a bit sloppy in that it ensures at least\nthere are two tokens on each line, but ignores if there are extra\ntokens on the line.  Tightening it is outside the scope of this\nseries.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/notes.c | 23 ++++++++++++-----------\n 1 file changed, 12 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex a9529b1696..4fb36a743c 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -375,18 +375,19 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd)\n \n \twhile (strbuf_getline_lf(&buf, stdin) != EOF) {\n \t\tstruct object_id from_obj, to_obj;\n-\t\tstruct strbuf **split;\n+\t\tstruct string_list split = STRING_LIST_INIT_NODUP;\n \t\tint err;\n \n-\t\tsplit = strbuf_split(&buf, ' ');\n-\t\tif (!split[0] || !split[1])\n+\t\tstring_list_split_in_place_f(&split, buf.buf, \" \", -1,\n+\t\t\t\t\t     STRING_LIST_SPLIT_TRIM);\n+\t\tif (split.nr < 2)\n \t\t\tdie(_(\"malformed input line: '%s'.\"), buf.buf);\n-\t\tstrbuf_rtrim(split[0]);\n-\t\tstrbuf_rtrim(split[1]);\n-\t\tif (repo_get_oid(the_repository, split[0]->buf, &from_obj))\n-\t\t\tdie(_(\"failed to resolve '%s' as a valid ref.\"), split[0]->buf);\n-\t\tif (repo_get_oid(the_repository, split[1]->buf, &to_obj))\n-\t\t\tdie(_(\"failed to resolve '%s' as a valid ref.\"), split[1]->buf);\n+\t\tif (repo_get_oid(the_repository, split.items[0].string, &from_obj))\n+\t\t\tdie(_(\"failed to resolve '%s' as a valid ref.\"),\n+\t\t\t    split.items[0].string);\n+\t\tif (repo_get_oid(the_repository, split.items[1].string, &to_obj))\n+\t\t\tdie(_(\"failed to resolve '%s' as a valid ref.\"),\n+\t\t\t    split.items[1].string);\n \n \t\tif (rewrite_cmd)\n \t\t\terr = copy_note_for_rewrite(c, &from_obj, &to_obj);\n@@ -396,11 +397,11 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd)\n \n \t\tif (err) {\n \t\t\terror(_(\"failed to copy notes from '%s' to '%s'\"),\n-\t\t\t      split[0]->buf, split[1]->buf);\n+\t\t\t      split.items[0].string, split.items[1].string);\n \t\t\tret = 1;\n \t\t}\n \n-\t\tstrbuf_list_free(split);\n+\t\tstring_list_clear(&split, 0);\n \t}\n \n \tif (!rewrite_cmd) {\n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523324","messageId":"20250803065304.3325286-9-gitster@pobox.com","threadId":"63881","inReplyTo":"20250803065304.3325286-1-gitster@pobox.com","subject":"[PATCH v3 08/12] config: do not use strbuf_split()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:53:00Z","receivedAt":"2025-08-03T06:53:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"When parsing an old-style GIT_CONFIG_PARAMETERS environment\nvariable, the code parses key=value pairs by splitting them at '='\ninto an array of strbuf's.  As strbuf_split() leaves the delimiter\nat the end of the split piece, the code has to manually trim it.\n\nIf we split with string_list_split(), that becomes unnecessary.\nRetire the use of strbuf_split() from this code path.\n\nNote that the max parameter of string_list_split() is of\nan ergonomically iffy design---it specifies the maximum number of\ntimes the function is allowed to split, which means that in order to\nsplit a text into up to 2 pieces, you have to pass 1, not 2.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n config.c | 23 ++++++++++-------------\n 1 file changed, 10 insertions(+), 13 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 8a2d0b7916..1769f15ee3 100644\n--- a/config.c\n+++ b/config.c\n@@ -638,31 +638,28 @@ int git_config_parse_parameter(const char *text,\n \t\t\t       config_fn_t fn, void *data)\n {\n \tconst char *value;\n-\tstruct strbuf **pair;\n+\tstruct string_list pair = STRING_LIST_INIT_DUP;\n \tint ret;\n \tstruct key_value_info kvi = KVI_INIT;\n \n \tkvi_from_param(&kvi);\n \n-\tpair = strbuf_split_str(text, '=', 2);\n-\tif (!pair[0])\n+\tstring_list_split(&pair, text, \"=\", 1);\n+\tif (!pair.nr)\n \t\treturn error(_(\"bogus config parameter: %s\"), text);\n \n-\tif (pair[0]->len && pair[0]->buf[pair[0]->len - 1] == '=') {\n-\t\tstrbuf_setlen(pair[0], pair[0]->len - 1);\n-\t\tvalue = pair[1] ? pair[1]->buf : \"\";\n-\t} else {\n+\tif (pair.nr == 1)\n \t\tvalue = NULL;\n-\t}\n+\telse\n+\t\tvalue = pair.items[1].string;\n \n-\tstrbuf_trim(pair[0]);\n-\tif (!pair[0]->len) {\n-\t\tstrbuf_list_free(pair);\n+\tif (!*pair.items[0].string) {\n+\t\tstring_list_clear(&pair, 0);\n \t\treturn error(_(\"bogus config parameter: %s\"), text);\n \t}\n \n-\tret = config_parse_pair(pair[0]->buf, value, &kvi, fn, data);\n-\tstrbuf_list_free(pair);\n+\tret = config_parse_pair(pair.items[0].string, value, &kvi, fn, data);\n+\tstring_list_clear(&pair, 0);\n \treturn ret;\n }\n \n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523325","messageId":"20250803065304.3325286-10-gitster@pobox.com","threadId":"63881","inReplyTo":"20250803065304.3325286-1-gitster@pobox.com","subject":"[PATCH v3 09/12] environment: do not use strbuf_split*()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:53:01Z","receivedAt":"2025-08-03T06:53:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"environment.c:get_git_namespace() learns the raw namespace from an\nenvironment variable, splits it at \"/\", and appends them after\n\"refs/namespaces/\"; the reason why it splits first is so that an\nempty string resulting from double slashes can be omitted.\n\nThe split pieces do not need to be edited in any way, so an array of\nstrbufs is a wrong data structure to use.  Instead split into a\nstring list and use the pieces from there.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n environment.c | 19 ++++++++++++-------\n 1 file changed, 12 insertions(+), 7 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex 7c2480b22e..ab3ed08433 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -163,10 +163,10 @@ int have_git_dir(void)\n const char *get_git_namespace(void)\n {\n \tstatic const char *namespace;\n-\n \tstruct strbuf buf = STRBUF_INIT;\n-\tstruct strbuf **components, **c;\n \tconst char *raw_namespace;\n+\tstruct string_list components = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n \n \tif (namespace)\n \t\treturn namespace;\n@@ -178,12 +178,17 @@ const char *get_git_namespace(void)\n \t}\n \n \tstrbuf_addstr(&buf, raw_namespace);\n-\tcomponents = strbuf_split(&buf, '/');\n+\n+\tstring_list_split(&components, buf.buf, \"/\", -1);\n \tstrbuf_reset(&buf);\n-\tfor (c = components; *c; c++)\n-\t\tif (strcmp((*c)->buf, \"/\") != 0)\n-\t\t\tstrbuf_addf(&buf, \"refs/namespaces/%s\", (*c)->buf);\n-\tstrbuf_list_free(components);\n+\n+\tfor_each_string_list_item(item, &components) {\n+\t\tif (item->string[0])\n+\t\t\tstrbuf_addf(&buf, \"refs/namespaces/%s/\", item->string);\n+\t}\n+\tstring_list_clear(&components, 0);\n+\n+\tstrbuf_trim_trailing_dir_sep(&buf);\n \tif (check_refname_format(buf.buf, 0))\n \t\tdie(_(\"bad git namespace path \\\"%s\\\"\"), raw_namespace);\n \tstrbuf_addch(&buf, '/');\n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523326","messageId":"20250803065304.3325286-11-gitster@pobox.com","threadId":"63881","inReplyTo":"20250803065304.3325286-1-gitster@pobox.com","subject":"[PATCH v3 10/12] sub-process: do not use strbuf_split*()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:53:02Z","receivedAt":"2025-08-03T06:53:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The code to read status from subprocess reads one packet line and\ntries to find \"status=<foo>\".  It is way overkill to split the line\ninto an array of two strbufs to extract <foo>.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n sub-process.c | 15 ++++++---------\n 1 file changed, 6 insertions(+), 9 deletions(-)\n\ndiff --git a/sub-process.c b/sub-process.c\nindex 1daf5a9752..83bf0a0e82 100644\n--- a/sub-process.c\n+++ b/sub-process.c\n@@ -30,23 +30,20 @@ struct subprocess_entry *subprocess_find_entry(struct hashmap *hashmap, const ch\n \n int subprocess_read_status(int fd, struct strbuf *status)\n {\n-\tstruct strbuf **pair;\n-\tchar *line;\n \tint len;\n \n \tfor (;;) {\n+\t\tchar *line;\n+\t\tconst char *value;\n+\n \t\tlen = packet_read_line_gently(fd, NULL, &line);\n \t\tif ((len < 0) || !line)\n \t\t\tbreak;\n-\t\tpair = strbuf_split_str(line, '=', 2);\n-\t\tif (pair[0] && pair[0]->len && pair[1]) {\n+\t\tif (skip_prefix(line, \"status=\", &value)) {\n \t\t\t/* the last \"status=<foo>\" line wins */\n-\t\t\tif (!strcmp(pair[0]->buf, \"status=\")) {\n-\t\t\t\tstrbuf_reset(status);\n-\t\t\t\tstrbuf_addbuf(status, pair[1]);\n-\t\t\t}\n+\t\t\tstrbuf_reset(status);\n+\t\t\tstrbuf_addstr(status, value);\n \t\t}\n-\t\tstrbuf_list_free(pair);\n \t}\n \n \treturn (len < 0) ? len : 0;\n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523327","messageId":"20250803065304.3325286-12-gitster@pobox.com","threadId":"63881","inReplyTo":"20250803065304.3325286-1-gitster@pobox.com","subject":"[PATCH v3 11/12] trace2: trim_trailing_newline followed by trim is a no-op","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:53:03Z","receivedAt":"2025-08-03T06:53:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"strbuf_trim_trailing_newline() removes a LF or a CRLF from the tail\nof a string.  If the code plans to call strbuf_trim() immediately\nafter doing so, the code is better off skipping the EOL trimming in\nthe first place.  After all, LF/CRLF at the end is a mere special\ncase of whitespaces at the end of the string, which will be removed\nby strbuf_rtrim() anyway.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n trace2/tr2_cfg.c | 2 --\n 1 file changed, 2 deletions(-)\n\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nindex 22a99a0682..2b7cfcd10c 100644\n--- a/trace2/tr2_cfg.c\n+++ b/trace2/tr2_cfg.c\n@@ -39,7 +39,6 @@ static int tr2_cfg_load_patterns(void)\n \n \t\tif (buf->len && buf->buf[buf->len - 1] == ',')\n \t\t\tstrbuf_setlen(buf, buf->len - 1);\n-\t\tstrbuf_trim_trailing_newline(*s);\n \t\tstrbuf_trim(*s);\n \t}\n \n@@ -78,7 +77,6 @@ static int tr2_load_env_vars(void)\n \n \t\tif (buf->len && buf->buf[buf->len - 1] == ',')\n \t\t\tstrbuf_setlen(buf, buf->len - 1);\n-\t\tstrbuf_trim_trailing_newline(*s);\n \t\tstrbuf_trim(*s);\n \t}\n \n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523328","messageId":"20250803065304.3325286-13-gitster@pobox.com","threadId":"63881","inReplyTo":"20250803065304.3325286-1-gitster@pobox.com","subject":"[PATCH v3 12/12] trace2: do not use strbuf_split*()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-03T06:53:04Z","receivedAt":"2025-08-03T06:53:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"tr2_cfg_load_patterns() and tr2_load_env_vars() functions are\nfunctions with very similar structure that each reads an environment\nvariable, splits its value at the ',' boundaries, and trims the\nresulting string pieces into an array of strbufs.\n\nBut the code paths that later use these strbufs take no advantage of\nthe strbuf-ness of the result (they do not benefit from <ptr,len>\nrepresentation to avoid having to run strlen(<ptr>), for example).\n\nSimplify the code by teaching these functions to split into a string\nlist instead; even the trimming comes for free ;-).\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n trace2/tr2_cfg.c | 78 +++++++++++++++++-------------------------------\n 1 file changed, 27 insertions(+), 51 deletions(-)\n\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nindex 2b7cfcd10c..bbcfeda60a 100644\n--- a/trace2/tr2_cfg.c\n+++ b/trace2/tr2_cfg.c\n@@ -8,87 +8,65 @@\n #include \"trace2/tr2_sysenv.h\"\n #include \"wildmatch.h\"\n \n-static struct strbuf **tr2_cfg_patterns;\n-static int tr2_cfg_count_patterns;\n+static struct string_list tr2_cfg_patterns = STRING_LIST_INIT_DUP;\n static int tr2_cfg_loaded;\n \n-static struct strbuf **tr2_cfg_env_vars;\n-static int tr2_cfg_env_vars_count;\n+static struct string_list tr2_cfg_env_vars = STRING_LIST_INIT_DUP;\n static int tr2_cfg_env_vars_loaded;\n \n /*\n  * Parse a string containing a comma-delimited list of config keys\n- * or wildcard patterns into a list of strbufs.\n+ * or wildcard patterns into a string list.\n  */\n-static int tr2_cfg_load_patterns(void)\n+static size_t tr2_cfg_load_patterns(void)\n {\n-\tstruct strbuf **s;\n \tconst char *envvar;\n \n \tif (tr2_cfg_loaded)\n-\t\treturn tr2_cfg_count_patterns;\n+\t\treturn tr2_cfg_patterns.nr;\n \ttr2_cfg_loaded = 1;\n \n \tenvvar = tr2_sysenv_get(TR2_SYSENV_CFG_PARAM);\n \tif (!envvar || !*envvar)\n-\t\treturn tr2_cfg_count_patterns;\n+\t\treturn tr2_cfg_patterns.nr;\n \n-\ttr2_cfg_patterns = strbuf_split_buf(envvar, strlen(envvar), ',', -1);\n-\tfor (s = tr2_cfg_patterns; *s; s++) {\n-\t\tstruct strbuf *buf = *s;\n-\n-\t\tif (buf->len && buf->buf[buf->len - 1] == ',')\n-\t\t\tstrbuf_setlen(buf, buf->len - 1);\n-\t\tstrbuf_trim(*s);\n-\t}\n-\n-\ttr2_cfg_count_patterns = s - tr2_cfg_patterns;\n-\treturn tr2_cfg_count_patterns;\n+\tstring_list_split_f(&tr2_cfg_patterns, envvar, \",\", -1,\n+\t\t\t    STRING_LIST_SPLIT_TRIM);\n+\treturn tr2_cfg_patterns.nr;\n }\n \n void tr2_cfg_free_patterns(void)\n {\n-\tif (tr2_cfg_patterns)\n-\t\tstrbuf_list_free(tr2_cfg_patterns);\n-\ttr2_cfg_count_patterns = 0;\n+\tif (tr2_cfg_patterns.nr)\n+\t\tstring_list_clear(&tr2_cfg_patterns, 0);\n \ttr2_cfg_loaded = 0;\n }\n \n /*\n  * Parse a string containing a comma-delimited list of environment variable\n- * names into a list of strbufs.\n+ * names into a string list.\n  */\n-static int tr2_load_env_vars(void)\n+static size_t tr2_load_env_vars(void)\n {\n-\tstruct strbuf **s;\n \tconst char *varlist;\n \n \tif (tr2_cfg_env_vars_loaded)\n-\t\treturn tr2_cfg_env_vars_count;\n+\t\treturn tr2_cfg_env_vars.nr;\n \ttr2_cfg_env_vars_loaded = 1;\n \n \tvarlist = tr2_sysenv_get(TR2_SYSENV_ENV_VARS);\n \tif (!varlist || !*varlist)\n-\t\treturn tr2_cfg_env_vars_count;\n-\n-\ttr2_cfg_env_vars = strbuf_split_buf(varlist, strlen(varlist), ',', -1);\n-\tfor (s = tr2_cfg_env_vars; *s; s++) {\n-\t\tstruct strbuf *buf = *s;\n-\n-\t\tif (buf->len && buf->buf[buf->len - 1] == ',')\n-\t\t\tstrbuf_setlen(buf, buf->len - 1);\n-\t\tstrbuf_trim(*s);\n-\t}\n+\t\treturn tr2_cfg_env_vars.nr;\n \n-\ttr2_cfg_env_vars_count = s - tr2_cfg_env_vars;\n-\treturn tr2_cfg_env_vars_count;\n+\tstring_list_split_f(&tr2_cfg_env_vars, varlist, \",\", -1,\n+\t\t\t    STRING_LIST_SPLIT_TRIM);\n+\treturn tr2_cfg_env_vars.nr;\n }\n \n void tr2_cfg_free_env_vars(void)\n {\n-\tif (tr2_cfg_env_vars)\n-\t\tstrbuf_list_free(tr2_cfg_env_vars);\n-\ttr2_cfg_env_vars_count = 0;\n+\tif (tr2_cfg_env_vars.nr)\n+\t\tstring_list_clear(&tr2_cfg_env_vars, 0);\n \ttr2_cfg_env_vars_loaded = 0;\n }\n \n@@ -103,12 +81,11 @@ struct tr2_cfg_data {\n static int tr2_cfg_cb(const char *key, const char *value,\n \t\t      const struct config_context *ctx, void *d)\n {\n-\tstruct strbuf **s;\n+\tstruct string_list_item *item;\n \tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\n \n-\tfor (s = tr2_cfg_patterns; *s; s++) {\n-\t\tstruct strbuf *buf = *s;\n-\t\tint wm = wildmatch(buf->buf, key, WM_CASEFOLD);\n+\tfor_each_string_list_item(item, &tr2_cfg_patterns) {\n+\t\tint wm = wildmatch(item->string, key, WM_CASEFOLD);\n \t\tif (wm == WM_MATCH) {\n \t\t\ttrace2_def_param_fl(data->file, data->line, key, value,\n \t\t\t\t\t    ctx->kvi);\n@@ -130,17 +107,16 @@ void tr2_cfg_list_config_fl(const char *file, int line)\n void tr2_list_env_vars_fl(const char *file, int line)\n {\n \tstruct key_value_info kvi = KVI_INIT;\n-\tstruct strbuf **s;\n+\tstruct string_list_item *item;\n \n \tkvi_from_param(&kvi);\n \tif (tr2_load_env_vars() <= 0)\n \t\treturn;\n \n-\tfor (s = tr2_cfg_env_vars; *s; s++) {\n-\t\tstruct strbuf *buf = *s;\n-\t\tconst char *val = getenv(buf->buf);\n+\tfor_each_string_list_item(item, &tr2_cfg_env_vars) {\n+\t\tconst char *val = getenv(item->string);\n \t\tif (val && *val)\n-\t\t\ttrace2_def_param_fl(file, line, buf->buf, val, &kvi);\n+\t\t\ttrace2_def_param_fl(file, line, item->string, val, &kvi);\n \t}\n }\n \n-- \n2.50.1-633-g69dfdd50af\n\n"},{"id":"523373","messageId":"aJBSCpPRSJoPN7EC@pks.im","threadId":"63881","inReplyTo":"20250803065223.3325111-1-gitster@pobox.com","subject":"Re: [PATCH v4 0/7] string_list_split*() updates","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-08-04T06:24:10Z","receivedAt":"2025-08-04T06:24:17Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sat, Aug 02, 2025 at 11:52:16PM -0700, Junio C Hamano wrote:\n> Relative to the v3 iteration, the v4 iteration explains the history\n> behind string_list_split_in_place() in a bit more detail, and\n> expands in-code comment to clarify what the verb \"trim\" means in the\n> context of STRING_LIST_SPLIT_TRIM.\n\nThanks, this version looks good to me.\n\nPatrick\n"}]}