{"thread":{"id":"60715","subject":"[PATCH 00/10] Enrich Trailer API","startedAt":"2024-01-10T06:51:30Z","lastAt":"2024-03-15T05:32:01Z","messageCount":202,"participants":["Linus Arver via GitGitGadget","Junio C Hamano","Linus Arver","Josh Steadmon","Christian Couder","Kristoffer Haugsbakk"],"isPatch":true,"patchVersion":1,"patchTotal":10},"messages":[{"id":"486489","messageId":"pull.1632.git.1704869487.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":null,"subject":"[PATCH 00/10] Enrich Trailer API","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-10T06:51:16Z","receivedAt":"2024-01-10T06:51:30Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"This patch series is the first 10 patches of a much larger series I've been\nworking. The main goal of this series is to enrich the API in trailer.h. The\nlarger series brings a number of additional code simplifications and\ncleanups (exposing and fixing some bugs along the way), and builds on top of\nthis series. The goal of the larger series is to make the trailer interface\nready for unit testing. By \"trailer API\" I mean those functions exposed in\ntrailer.h.\n\nCurrently, we have \"process_trailers()\" in trailer.h which does many\ndifferent things (parse command-line arguments, create temporary files, etc)\nthat are independent of the concept of \"trailers\", whose interface trailer.h\ndefines. This makes unit-testing this function difficult. While there is no\ntechnical reason why we couldn't write unit tests for the smaller functions\nthat are called within process_trailers(), doing so would involve testing\nprivate (\"static\" in trailer.c) functions instead of the public functions\nexposed in trailer.h which authoritatively define the API.\n\nAs an alternative to this patch series, we could keep trailer.h intact and\ndecide to unit-test the existing \"trailer_info_get()\" function which does\nmost of the trailer parsing work. However this function wouldn't be easy to\ntest either, because the resulting data structure merely contains the\nunparsed \"trailers\" lines. So the unit test (if it wants to inspect the\nresult of parsing these lines) would have to invoke additional parsing\nfunctions itself. And at that point it would not longer be a unit test in\nthe traditional sense, because it would be invoking multiple functions at\nonce.\n\nThis series breaks up \"process_trailers()\" into smaller pieces, exposing\nmany of the parts relevant to trailer-related processing in trailer.h. This\nforces us to start writing unit tests for these now public functions, but\nthat is a good thing because those same unit tests should be easy to write\n(due to their small(er) sizes), but also, because those unit tests will now\nensure some degree of stability across new versions of trailer.h (we will\nstart noticing when the behavior of any of these API functions change).\n\nAnother benefit is that more clients (perhaps those outside of Git in the\nfuture) will be able to use the same trailer processing algorithms used by\nthe interpret-trailers builtin. For example, a web server may want to parse\ntrailers the same way that interpret-trailers would parse them, without\nhaving to shell out to interpret-trailers. Importing the new (richer)\ntrailer.h file that this series promotes would help them achieve that goal.\nThis use case was the original motivation behind my work in this area of\nGit.\n\nThanks to the aggressive refactoring in this series, I've been able to\nidentify and fix a couple bugs in our existing implementation. Those fixes\nbuild on top of this series but were not included here, in order to keep\nthis series small.\n\nLinus Arver (10):\n  trailer: move process_trailers() to interpret-trailers.c\n  trailer: include \"trailer\" term in API functions\n  trailer: unify trailer formatting machinery\n  trailer: delete obsolete formatting functions\n  sequencer: use the trailer iterator\n  trailer: make trailer_info struct private\n  trailer: spread usage of \"trailer_block\" language\n  trailer: prepare to move parse_trailers_from_command_line_args() to\n    builtin\n  trailer: move arg handling to interpret-trailers.c\n  trailer: delete obsolete argument handling code from API\n\n builtin/interpret-trailers.c | 169 ++++++++--\n builtin/shortlog.c           |   7 +-\n pretty.c                     |   2 +-\n ref-filter.c                 |   2 +-\n sequencer.c                  |  35 +--\n trailer.c                    | 579 ++++++++++++++++-------------------\n trailer.h                    | 106 ++++---\n 7 files changed, 482 insertions(+), 418 deletions(-)\n\n\nbase-commit: a54a84b333adbecf7bc4483c0e36ed5878cac17b\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1632%2Flistx%2Ftrailer-api-refactor-part-1-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1632/listx/trailer-api-refactor-part-1-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/1632\n-- \ngitgitgadget\n"},{"id":"486490","messageId":"2dc3279b37f3aa81ba20e3dd08b029ca655ac3d8.1704869487.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.git.1704869487.gitgitgadget@gmail.com","subject":"[PATCH 01/10] trailer: move process_trailers() to interpret-trailers.c","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-10T06:51:17Z","receivedAt":"2024-01-10T06:51:31Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThe interpret-trailers.c builtin is the only place we need to call\nprocess_trailers(). As it stands, process_trailers() is inherently tied\nto how the builtin behaves, so move its definition there.\n\nDelete the corresponding declaration from trailer.h, which then forces\nus to expose the working innards of that function. This enriches\ntrailer.h to include a more granular API, which can then be unit-tested\nin the future (because process_trailers() by itself does too many things\nto be able to be easily unit-tested).\n\nTake this opportunity to demote some file-handling functions out of the\ntrailer API implementation, as these have nothing to do with trailers.\n\nWhile we're at it, rename process_trailers() to interpret_trailers() in\nthe builtin for consistency with the existing cmd_interpret_trailers(),\nwhich wraps around this function.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  98 +++++++++++++++++++++++++++-\n trailer.c                    | 120 ++++-------------------------------\n trailer.h                    |  20 +++++-\n 3 files changed, 126 insertions(+), 112 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 033bd1556cf..444f8fb70c9 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -9,6 +9,7 @@\n #include \"gettext.h\"\n #include \"parse-options.h\"\n #include \"string-list.h\"\n+#include \"tempfile.h\"\n #include \"trailer.h\"\n #include \"config.h\"\n \n@@ -91,6 +92,99 @@ static int parse_opt_parse(const struct option *opt, const char *arg,\n \treturn 0;\n }\n \n+static struct tempfile *trailers_tempfile;\n+\n+static FILE *create_in_place_tempfile(const char *file)\n+{\n+\tstruct stat st;\n+\tstruct strbuf filename_template = STRBUF_INIT;\n+\tconst char *tail;\n+\tFILE *outfile;\n+\n+\tif (stat(file, &st))\n+\t\tdie_errno(_(\"could not stat %s\"), file);\n+\tif (!S_ISREG(st.st_mode))\n+\t\tdie(_(\"file %s is not a regular file\"), file);\n+\tif (!(st.st_mode & S_IWUSR))\n+\t\tdie(_(\"file %s is not writable by user\"), file);\n+\n+\t/* Create temporary file in the same directory as the original */\n+\ttail = strrchr(file, '/');\n+\tif (tail)\n+\t\tstrbuf_add(&filename_template, file, tail - file + 1);\n+\tstrbuf_addstr(&filename_template, \"git-interpret-trailers-XXXXXX\");\n+\n+\ttrailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode);\n+\tstrbuf_release(&filename_template);\n+\toutfile = fdopen_tempfile(trailers_tempfile, \"w\");\n+\tif (!outfile)\n+\t\tdie_errno(_(\"could not open temporary file\"));\n+\n+\treturn outfile;\n+}\n+\n+static void read_input_file(struct strbuf *sb, const char *file)\n+{\n+\tif (file) {\n+\t\tif (strbuf_read_file(sb, file, 0) < 0)\n+\t\t\tdie_errno(_(\"could not read input file '%s'\"), file);\n+\t} else {\n+\t\tif (strbuf_read(sb, fileno(stdin), 0) < 0)\n+\t\t\tdie_errno(_(\"could not read from stdin\"));\n+\t}\n+}\n+\n+static void interpret_trailers(const char *file,\n+\t\t\t       const struct process_trailer_options *opts,\n+\t\t\t       struct list_head *new_trailer_head)\n+{\n+\tLIST_HEAD(head);\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tstruct trailer_info info;\n+\tFILE *outfile = stdout;\n+\n+\tensure_configured();\n+\n+\tread_input_file(&sb, file);\n+\n+\tif (opts->in_place)\n+\t\toutfile = create_in_place_tempfile(file);\n+\n+\tparse_trailers(&info, sb.buf, &head, opts);\n+\n+\t/* Print the lines before the trailers */\n+\tif (!opts->only_trailers)\n+\t\tfwrite(sb.buf, 1, info.trailer_block_start, outfile);\n+\n+\tif (!opts->only_trailers && !info.blank_line_before_trailer)\n+\t\tfprintf(outfile, \"\\n\");\n+\n+\n+\tif (!opts->only_input) {\n+\t\tLIST_HEAD(config_head);\n+\t\tLIST_HEAD(arg_head);\n+\t\tparse_trailers_from_config(&config_head);\n+\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n+\t\tlist_splice(&config_head, &arg_head);\n+\t\tprocess_trailers_lists(&head, &arg_head);\n+\t}\n+\n+\tprint_all(outfile, &head, opts);\n+\n+\tfree_all(&head);\n+\ttrailer_info_release(&info);\n+\n+\t/* Print the lines after the trailers as is */\n+\tif (!opts->only_trailers)\n+\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n+\n+\tif (opts->in_place)\n+\t\tif (rename_tempfile(&trailers_tempfile, file))\n+\t\t\tdie_errno(_(\"could not rename temporary file to %s\"), file);\n+\n+\tstrbuf_release(&sb);\n+}\n+\n int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n {\n \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n@@ -132,11 +226,11 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n \tif (argc) {\n \t\tint i;\n \t\tfor (i = 0; i < argc; i++)\n-\t\t\tprocess_trailers(argv[i], &opts, &trailers);\n+\t\t\tinterpret_trailers(argv[i], &opts, &trailers);\n \t} else {\n \t\tif (opts.in_place)\n \t\t\tdie(_(\"no input file given for in-place editing\"));\n-\t\tprocess_trailers(NULL, &opts, &trailers);\n+\t\tinterpret_trailers(NULL, &opts, &trailers);\n \t}\n \n \tnew_trailers_clear(&trailers);\ndiff --git a/trailer.c b/trailer.c\nindex 3a0710a4583..9d70c9946bd 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -5,7 +5,6 @@\n #include \"string-list.h\"\n #include \"run-command.h\"\n #include \"commit.h\"\n-#include \"tempfile.h\"\n #include \"trailer.h\"\n #include \"list.h\"\n /*\n@@ -163,8 +162,8 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n-static void print_all(FILE *outfile, struct list_head *head,\n-\t\t      const struct process_trailer_options *opts)\n+void print_all(FILE *outfile, struct list_head *head,\n+\t       const struct process_trailer_options *opts)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *item;\n@@ -366,8 +365,8 @@ static int find_same_and_apply_arg(struct list_head *head,\n \treturn 0;\n }\n \n-static void process_trailers_lists(struct list_head *head,\n-\t\t\t\t   struct list_head *arg_head)\n+void process_trailers_lists(struct list_head *head,\n+\t\t\t    struct list_head *arg_head)\n {\n \tstruct list_head *pos, *p;\n \tstruct arg_item *arg_tok;\n@@ -589,7 +588,7 @@ static int git_trailer_config(const char *conf_key, const char *value,\n \treturn 0;\n }\n \n-static void ensure_configured(void)\n+void ensure_configured(void)\n {\n \tif (configured)\n \t\treturn;\n@@ -719,7 +718,7 @@ static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n \tlist_add_tail(&new_item->list, arg_head);\n }\n \n-static void parse_trailers_from_config(struct list_head *config_head)\n+void parse_trailers_from_config(struct list_head *config_head)\n {\n \tstruct arg_item *item;\n \tstruct list_head *pos;\n@@ -735,8 +734,8 @@ static void parse_trailers_from_config(struct list_head *config_head)\n \t}\n }\n \n-static void parse_trailers_from_command_line_args(struct list_head *arg_head,\n-\t\t\t\t\t\t  struct list_head *new_trailer_head)\n+void parse_trailers_from_command_line_args(struct list_head *arg_head,\n+\t\t\t\t\t   struct list_head *new_trailer_head)\n {\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n@@ -775,17 +774,6 @@ static void parse_trailers_from_command_line_args(struct list_head *arg_head,\n \tfree(cl_separators);\n }\n \n-static void read_input_file(struct strbuf *sb, const char *file)\n-{\n-\tif (file) {\n-\t\tif (strbuf_read_file(sb, file, 0) < 0)\n-\t\t\tdie_errno(_(\"could not read input file '%s'\"), file);\n-\t} else {\n-\t\tif (strbuf_read(sb, fileno(stdin), 0) < 0)\n-\t\t\tdie_errno(_(\"could not read from stdin\"));\n-\t}\n-}\n-\n static const char *next_line(const char *str)\n {\n \tconst char *nl = strchrnul(str, '\\n');\n@@ -1000,10 +988,10 @@ static void unfold_value(struct strbuf *val)\n  * Parse trailers in \"str\", populating the trailer info and \"head\"\n  * linked list structure.\n  */\n-static void parse_trailers(struct trailer_info *info,\n-\t\t\t     const char *str,\n-\t\t\t     struct list_head *head,\n-\t\t\t     const struct process_trailer_options *opts)\n+void parse_trailers(struct trailer_info *info,\n+\t\t    const char *str,\n+\t\t    struct list_head *head,\n+\t\t    const struct process_trailer_options *opts)\n {\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n@@ -1035,7 +1023,7 @@ static void parse_trailers(struct trailer_info *info,\n \t}\n }\n \n-static void free_all(struct list_head *head)\n+void free_all(struct list_head *head)\n {\n \tstruct list_head *pos, *p;\n \tlist_for_each_safe(pos, p, head) {\n@@ -1044,88 +1032,6 @@ static void free_all(struct list_head *head)\n \t}\n }\n \n-static struct tempfile *trailers_tempfile;\n-\n-static FILE *create_in_place_tempfile(const char *file)\n-{\n-\tstruct stat st;\n-\tstruct strbuf filename_template = STRBUF_INIT;\n-\tconst char *tail;\n-\tFILE *outfile;\n-\n-\tif (stat(file, &st))\n-\t\tdie_errno(_(\"could not stat %s\"), file);\n-\tif (!S_ISREG(st.st_mode))\n-\t\tdie(_(\"file %s is not a regular file\"), file);\n-\tif (!(st.st_mode & S_IWUSR))\n-\t\tdie(_(\"file %s is not writable by user\"), file);\n-\n-\t/* Create temporary file in the same directory as the original */\n-\ttail = strrchr(file, '/');\n-\tif (tail)\n-\t\tstrbuf_add(&filename_template, file, tail - file + 1);\n-\tstrbuf_addstr(&filename_template, \"git-interpret-trailers-XXXXXX\");\n-\n-\ttrailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode);\n-\tstrbuf_release(&filename_template);\n-\toutfile = fdopen_tempfile(trailers_tempfile, \"w\");\n-\tif (!outfile)\n-\t\tdie_errno(_(\"could not open temporary file\"));\n-\n-\treturn outfile;\n-}\n-\n-void process_trailers(const char *file,\n-\t\t      const struct process_trailer_options *opts,\n-\t\t      struct list_head *new_trailer_head)\n-{\n-\tLIST_HEAD(head);\n-\tstruct strbuf sb = STRBUF_INIT;\n-\tstruct trailer_info info;\n-\tFILE *outfile = stdout;\n-\n-\tensure_configured();\n-\n-\tread_input_file(&sb, file);\n-\n-\tif (opts->in_place)\n-\t\toutfile = create_in_place_tempfile(file);\n-\n-\tparse_trailers(&info, sb.buf, &head, opts);\n-\n-\t/* Print the lines before the trailers */\n-\tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf, 1, info.trailer_block_start, outfile);\n-\n-\tif (!opts->only_trailers && !info.blank_line_before_trailer)\n-\t\tfprintf(outfile, \"\\n\");\n-\n-\n-\tif (!opts->only_input) {\n-\t\tLIST_HEAD(config_head);\n-\t\tLIST_HEAD(arg_head);\n-\t\tparse_trailers_from_config(&config_head);\n-\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n-\t\tlist_splice(&config_head, &arg_head);\n-\t\tprocess_trailers_lists(&head, &arg_head);\n-\t}\n-\n-\tprint_all(outfile, &head, opts);\n-\n-\tfree_all(&head);\n-\ttrailer_info_release(&info);\n-\n-\t/* Print the lines after the trailers as is */\n-\tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n-\n-\tif (opts->in_place)\n-\t\tif (rename_tempfile(&trailers_tempfile, file))\n-\t\t\tdie_errno(_(\"could not rename temporary file to %s\"), file);\n-\n-\tstrbuf_release(&sb);\n-}\n-\n void trailer_info_get(struct trailer_info *info, const char *str,\n \t\t      const struct process_trailer_options *opts)\n {\ndiff --git a/trailer.h b/trailer.h\nindex 1644cd05f60..b3e4a5e127d 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -81,15 +81,29 @@ struct process_trailer_options {\n \n #define PROCESS_TRAILER_OPTIONS_INIT {0}\n \n-void process_trailers(const char *file,\n-\t\t      const struct process_trailer_options *opts,\n-\t\t      struct list_head *new_trailer_head);\n+void parse_trailers_from_config(struct list_head *config_head);\n+\n+void parse_trailers_from_command_line_args(struct list_head *arg_head,\n+\t\t\t\t\t   struct list_head *new_trailer_head);\n+\n+void process_trailers_lists(struct list_head *head,\n+\t\t\t    struct list_head *arg_head);\n+\n+void parse_trailers(struct trailer_info *info,\n+\t\t    const char *str,\n+\t\t    struct list_head *head,\n+\t\t    const struct process_trailer_options *opts);\n \n void trailer_info_get(struct trailer_info *info, const char *str,\n \t\t      const struct process_trailer_options *opts);\n \n void trailer_info_release(struct trailer_info *info);\n \n+void ensure_configured(void);\n+void print_all(FILE *outfile, struct list_head *head,\n+\t       const struct process_trailer_options *opts);\n+void free_all(struct list_head *head);\n+\n /*\n  * Format the trailers from the commit msg \"msg\" into the strbuf \"out\".\n  * Note two caveats about \"opts\":\n-- \ngitgitgadget\n\n"},{"id":"486491","messageId":"5f64718abfc2e61b4e259de700c137bc817fbb1c.1704869487.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.git.1704869487.gitgitgadget@gmail.com","subject":"[PATCH 02/10] trailer: include \"trailer\" term in API functions","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-10T06:51:18Z","receivedAt":"2024-01-10T06:51:32Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThese functions are exposed to clients and so they should include\n\"trailer\" in their names for easier identification, just like all the\nother functions already exposed by trailer.h.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  7 ++++---\n trailer.c                    | 10 +++++-----\n trailer.h                    | 10 +++++-----\n 3 files changed, 14 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 444f8fb70c9..adb74276281 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -143,7 +143,7 @@ static void interpret_trailers(const char *file,\n \tstruct trailer_info info;\n \tFILE *outfile = stdout;\n \n-\tensure_configured();\n+\ttrailer_config_init();\n \n \tread_input_file(&sb, file);\n \n@@ -169,9 +169,10 @@ static void interpret_trailers(const char *file,\n \t\tprocess_trailers_lists(&head, &arg_head);\n \t}\n \n-\tprint_all(outfile, &head, opts);\n+\t/* Print trailer block. */\n+\tformat_trailers(outfile, &head, opts);\n \n-\tfree_all(&head);\n+\tfree_trailers(&head);\n \ttrailer_info_release(&info);\n \n \t/* Print the lines after the trailers as is */\ndiff --git a/trailer.c b/trailer.c\nindex 9d70c9946bd..0ce7e9079ca 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -162,8 +162,8 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n-void print_all(FILE *outfile, struct list_head *head,\n-\t       const struct process_trailer_options *opts)\n+void format_trailers(FILE *outfile, struct list_head *head,\n+\t\t     const struct process_trailer_options *opts)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *item;\n@@ -588,7 +588,7 @@ static int git_trailer_config(const char *conf_key, const char *value,\n \treturn 0;\n }\n \n-void ensure_configured(void)\n+void trailer_config_init(void)\n {\n \tif (configured)\n \t\treturn;\n@@ -1023,7 +1023,7 @@ void parse_trailers(struct trailer_info *info,\n \t}\n }\n \n-void free_all(struct list_head *head)\n+void free_trailers(struct list_head *head)\n {\n \tstruct list_head *pos, *p;\n \tlist_for_each_safe(pos, p, head) {\n@@ -1041,7 +1041,7 @@ void trailer_info_get(struct trailer_info *info, const char *str,\n \tsize_t nr = 0, alloc = 0;\n \tchar **last = NULL;\n \n-\tensure_configured();\n+\ttrailer_config_init();\n \n \tend_of_log_message = find_end_of_log_message(str, opts->no_divider);\n \ttrailer_block_start = find_trailer_block_start(str, end_of_log_message);\ndiff --git a/trailer.h b/trailer.h\nindex b3e4a5e127d..0e4f0ece9b3 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -99,11 +99,11 @@ void trailer_info_get(struct trailer_info *info, const char *str,\n \n void trailer_info_release(struct trailer_info *info);\n \n-void ensure_configured(void);\n-void print_all(FILE *outfile, struct list_head *head,\n-\t       const struct process_trailer_options *opts);\n-void free_all(struct list_head *head);\n+void trailer_config_init(void);\n+void free_trailers(struct list_head *trailers);\n \n+void format_trailers(FILE *outfile, struct list_head *head,\n+\t\t     const struct process_trailer_options *opts);\n /*\n  * Format the trailers from the commit msg \"msg\" into the strbuf \"out\".\n  * Note two caveats about \"opts\":\n@@ -111,7 +111,7 @@ void free_all(struct list_head *head);\n  *   - this is primarily a helper for pretty.c, and not\n  *     all of the flags are supported.\n  *\n- *   - this differs from process_trailers slightly in that we always format\n+ *   - this differs from format_trailers slightly in that we always format\n  *     only the trailer block itself, even if the \"only_trailers\" option is not\n  *     set.\n  */\n-- \ngitgitgadget\n\n"},{"id":"486492","messageId":"d3326021fb6a63707e4ce56f166447143f4e55a2.1704869487.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.git.1704869487.gitgitgadget@gmail.com","subject":"[PATCH 03/10] trailer: unify trailer formatting machinery","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-10T06:51:19Z","receivedAt":"2024-01-10T06:51:33Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nCurrently have two functions for formatting trailers exposed in\ntrailer.h:\n\n    void format_trailers(FILE *outfile, struct list_head *head,\n                        const struct process_trailer_options *opts);\n\n    void format_trailers_from_commit(struct strbuf *out, const char *msg,\n                                    const struct process_trailer_options *opts);\n\nand previously these functions, although similar enough (even taking the\nsame process_trailer_options struct pointer), did not build on each\nother.\n\nMake format_trailers_from_commit() rely on format_trailers(). Teach\nformat_trailers() to process trailers with the additional\nprocess_trailer_options fields like opts->key_only which is only used by\nformat_trailers_from_commit() and not builtin/interpret-trailers.c.\n\nThis will allow us to delete the format_trailer_info() and\nprint_tok_val() functions in the next patch. They are not deleted here\nin order to keep the diff small.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |   5 +-\n pretty.c                     |   2 +-\n ref-filter.c                 |   2 +-\n trailer.c                    | 105 +++++++++++++++++++++++++++++------\n trailer.h                    |  21 +++----\n 5 files changed, 102 insertions(+), 33 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex adb74276281..934833a4645 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -140,6 +140,7 @@ static void interpret_trailers(const char *file,\n {\n \tLIST_HEAD(head);\n \tstruct strbuf sb = STRBUF_INIT;\n+\tstruct strbuf trailer_block = STRBUF_INIT;\n \tstruct trailer_info info;\n \tFILE *outfile = stdout;\n \n@@ -170,7 +171,9 @@ static void interpret_trailers(const char *file,\n \t}\n \n \t/* Print trailer block. */\n-\tformat_trailers(outfile, &head, opts);\n+\tformat_trailers(&head, opts, &trailer_block);\n+\tfwrite(trailer_block.buf, 1, trailer_block.len, outfile);\n+\tstrbuf_release(&trailer_block);\n \n \tfree_trailers(&head);\n \ttrailer_info_release(&info);\ndiff --git a/pretty.c b/pretty.c\nindex cf964b060cd..f0721a5214f 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1759,7 +1759,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \t\t\t\tgoto trailer_out;\n \t\t}\n \t\tif (*arg == ')') {\n-\t\t\tformat_trailers_from_commit(sb, msg + c->subject_off, &opts);\n+\t\t\tformat_trailers_from_commit(msg + c->subject_off, &opts, sb);\n \t\t\tret = arg - placeholder + 1;\n \t\t}\n \ttrailer_out:\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 35b989e1dfe..7fb13818686 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -1985,7 +1985,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct exp\n \t\t\tstruct strbuf s = STRBUF_INIT;\n \n \t\t\t/* Format the trailer info according to the trailer_opts given */\n-\t\t\tformat_trailers_from_commit(&s, subpos, &atom->u.contents.trailer_opts);\n+\t\t\tformat_trailers_from_commit(subpos, &atom->u.contents.trailer_opts, &s);\n \n \t\t\tv->s = strbuf_detach(&s, NULL);\n \t\t} else if (atom->u.contents.option == C_BARE)\ndiff --git a/trailer.c b/trailer.c\nindex 0ce7e9079ca..315d90ee1ab 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -162,19 +162,6 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n-void format_trailers(FILE *outfile, struct list_head *head,\n-\t\t     const struct process_trailer_options *opts)\n-{\n-\tstruct list_head *pos;\n-\tstruct trailer_item *item;\n-\tlist_for_each(pos, head) {\n-\t\titem = list_entry(pos, struct trailer_item, list);\n-\t\tif ((!opts->trim_empty || strlen(item->value) > 0) &&\n-\t\t    (!opts->only_trailers || item->token))\n-\t\t\tprint_tok_val(outfile, item->token, item->value);\n-\t}\n-}\n-\n static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)\n {\n \tstruct trailer_item *new_item = xcalloc(1, sizeof(*new_item));\n@@ -984,6 +971,78 @@ static void unfold_value(struct strbuf *val)\n \tstrbuf_release(&out);\n }\n \n+void format_trailers(struct list_head *head,\n+\t\t     const struct process_trailer_options *opts,\n+\t\t     struct strbuf *out)\n+{\n+\tstruct list_head *pos;\n+\tstruct trailer_item *item;\n+\tint need_separator = 0;\n+\n+\tlist_for_each(pos, head) {\n+\t\titem = list_entry(pos, struct trailer_item, list);\n+\t\tif (item->token) {\n+\t\t\tchar c;\n+\n+\t\t\tstruct strbuf tok = STRBUF_INIT;\n+\t\t\tstruct strbuf val = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&tok, item->token);\n+\t\t\tstrbuf_addstr(&val, item->value);\n+\n+\t\t\t/*\n+\t\t\t * Skip key/value pairs where the value was empty. This\n+\t\t\t * can happen from trailers specified without a\n+\t\t\t * separator, like `--trailer \"Reviewed-by\"` (no\n+\t\t\t * corresponding value).\n+\t\t\t */\n+\t\t\tif (opts->trim_empty && !strlen(item->value))\n+\t\t\t\tcontinue;\n+\n+\t\t\tif (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n+\t\t\t\tif (opts->unfold)\n+\t\t\t\t\tunfold_value(&val);\n+\n+\t\t\t\tif (opts->separator && need_separator)\n+\t\t\t\t\tstrbuf_addbuf(out, opts->separator);\n+\t\t\t\tif (!opts->value_only)\n+\t\t\t\t\tstrbuf_addbuf(out, &tok);\n+\t\t\t\tif (!opts->key_only && !opts->value_only) {\n+\t\t\t\t\tif (opts->key_value_separator)\n+\t\t\t\t\t\tstrbuf_addbuf(out, opts->key_value_separator);\n+\t\t\t\t\telse {\n+\t\t\t\t\t\tc = last_non_space_char(tok.buf);\n+\t\t\t\t\t\tif (c) {\n+\t\t\t\t\t\t\tif (!strchr(separators, c))\n+\t\t\t\t\t\t\t\tstrbuf_addf(out, \"%c \", separators[0]);\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t\tif (!opts->key_only)\n+\t\t\t\t\tstrbuf_addbuf(out, &val);\n+\t\t\t\tif (!opts->separator)\n+\t\t\t\t\tstrbuf_addch(out, '\\n');\n+\n+\t\t\t\tneed_separator = 1;\n+\t\t\t}\n+\n+\t\t\tstrbuf_release(&tok);\n+\t\t\tstrbuf_release(&val);\n+\t\t} else if (!opts->only_trailers) {\n+\t\t\tif (opts->separator && need_separator) {\n+\t\t\t\tstrbuf_addbuf(out, opts->separator);\n+\t\t\t}\n+\t\t\tstrbuf_addstr(out, item->value);\n+\t\t\tif (opts->separator)\n+\t\t\t\tstrbuf_rtrim(out);\n+\t\t\telse\n+\t\t\t\tstrbuf_addch(out, '\\n');\n+\n+\t\t\tneed_separator = 1;\n+\t\t}\n+\n+\t}\n+}\n+\n /*\n  * Parse trailers in \"str\", populating the trailer info and \"head\"\n  * linked list structure.\n@@ -1144,13 +1203,25 @@ static void format_trailer_info(struct strbuf *out,\n \n }\n \n-void format_trailers_from_commit(struct strbuf *out, const char *msg,\n-\t\t\t\t const struct process_trailer_options *opts)\n+void format_trailers_from_commit(const char *msg,\n+\t\t\t\t const struct process_trailer_options *opts,\n+\t\t\t\t struct strbuf *out)\n {\n+\tLIST_HEAD(head);\n \tstruct trailer_info info;\n \n-\ttrailer_info_get(&info, msg, opts);\n-\tformat_trailer_info(out, &info, msg, opts);\n+\tparse_trailers(&info, msg, &head, opts);\n+\n+\t/* If we want the whole block untouched, we can take the fast path. */\n+\tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n+\t    !opts->separator && !opts->key_only && !opts->value_only &&\n+\t    !opts->key_value_separator) {\n+\t\tstrbuf_add(out, msg + info.trailer_block_start,\n+\t\t\t   info.trailer_block_end - info.trailer_block_start);\n+\t} else\n+\t\tformat_trailers(&head, opts, out);\n+\n+\tfree_trailers(&head);\n \ttrailer_info_release(&info);\n }\n \ndiff --git a/trailer.h b/trailer.h\nindex 0e4f0ece9b3..50f70556302 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -102,21 +102,16 @@ void trailer_info_release(struct trailer_info *info);\n void trailer_config_init(void);\n void free_trailers(struct list_head *trailers);\n \n-void format_trailers(FILE *outfile, struct list_head *head,\n-\t\t     const struct process_trailer_options *opts);\n+void format_trailers(struct list_head *head,\n+\t\t     const struct process_trailer_options *opts,\n+\t\t     struct strbuf *out);\n /*\n- * Format the trailers from the commit msg \"msg\" into the strbuf \"out\".\n- * Note two caveats about \"opts\":\n- *\n- *   - this is primarily a helper for pretty.c, and not\n- *     all of the flags are supported.\n- *\n- *   - this differs from format_trailers slightly in that we always format\n- *     only the trailer block itself, even if the \"only_trailers\" option is not\n- *     set.\n+ * Convenience function to format the trailers from the commit msg \"msg\" into\n+ * the strbuf \"out\". Reuses format_trailers internally.\n  */\n-void format_trailers_from_commit(struct strbuf *out, const char *msg,\n-\t\t\t\t const struct process_trailer_options *opts);\n+void format_trailers_from_commit(const char *msg,\n+\t\t\t\t const struct process_trailer_options *opts,\n+\t\t\t\t struct strbuf *out);\n \n /*\n  * An interface for iterating over the trailers found in a particular commit\n-- \ngitgitgadget\n\n"},{"id":"486493","messageId":"8d86461475765ac04ad0ed207e46598eb90a45ba.1704869487.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.git.1704869487.gitgitgadget@gmail.com","subject":"[PATCH 04/10] trailer: delete obsolete formatting functions","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-10T06:51:20Z","receivedAt":"2024-01-10T06:51:34Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 79 -------------------------------------------------------\n 1 file changed, 79 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 315d90ee1ab..132f22b3dd7 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -144,24 +144,6 @@ static char last_non_space_char(const char *s)\n \treturn '\\0';\n }\n \n-static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n-{\n-\tchar c;\n-\n-\tif (!tok) {\n-\t\tfprintf(outfile, \"%s\\n\", val);\n-\t\treturn;\n-\t}\n-\n-\tc = last_non_space_char(tok);\n-\tif (!c)\n-\t\treturn;\n-\tif (strchr(separators, c))\n-\t\tfprintf(outfile, \"%s%s\\n\", tok, val);\n-\telse\n-\t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n-}\n-\n static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)\n {\n \tstruct trailer_item *new_item = xcalloc(1, sizeof(*new_item));\n@@ -1142,67 +1124,6 @@ void trailer_info_release(struct trailer_info *info)\n \tfree(info->trailers);\n }\n \n-static void format_trailer_info(struct strbuf *out,\n-\t\t\t\tconst struct trailer_info *info,\n-\t\t\t\tconst char *msg,\n-\t\t\t\tconst struct process_trailer_options *opts)\n-{\n-\tsize_t origlen = out->len;\n-\tsize_t i;\n-\n-\t/* If we want the whole block untouched, we can take the fast path. */\n-\tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n-\t    !opts->separator && !opts->key_only && !opts->value_only &&\n-\t    !opts->key_value_separator) {\n-\t\tstrbuf_add(out, msg + info->trailer_block_start,\n-\t\t\t   info->trailer_block_end - info->trailer_block_start);\n-\t\treturn;\n-\t}\n-\n-\tfor (i = 0; i < info->trailer_nr; i++) {\n-\t\tchar *trailer = info->trailers[i];\n-\t\tssize_t separator_pos = find_separator(trailer, separators);\n-\n-\t\tif (separator_pos >= 1) {\n-\t\t\tstruct strbuf tok = STRBUF_INIT;\n-\t\t\tstruct strbuf val = STRBUF_INIT;\n-\n-\t\t\tparse_trailer(&tok, &val, NULL, trailer, separator_pos);\n-\t\t\tif (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n-\t\t\t\tif (opts->unfold)\n-\t\t\t\t\tunfold_value(&val);\n-\n-\t\t\t\tif (opts->separator && out->len != origlen)\n-\t\t\t\t\tstrbuf_addbuf(out, opts->separator);\n-\t\t\t\tif (!opts->value_only)\n-\t\t\t\t\tstrbuf_addbuf(out, &tok);\n-\t\t\t\tif (!opts->key_only && !opts->value_only) {\n-\t\t\t\t\tif (opts->key_value_separator)\n-\t\t\t\t\t\tstrbuf_addbuf(out, opts->key_value_separator);\n-\t\t\t\t\telse\n-\t\t\t\t\t\tstrbuf_addstr(out, \": \");\n-\t\t\t\t}\n-\t\t\t\tif (!opts->key_only)\n-\t\t\t\t\tstrbuf_addbuf(out, &val);\n-\t\t\t\tif (!opts->separator)\n-\t\t\t\t\tstrbuf_addch(out, '\\n');\n-\t\t\t}\n-\t\t\tstrbuf_release(&tok);\n-\t\t\tstrbuf_release(&val);\n-\n-\t\t} else if (!opts->only_trailers) {\n-\t\t\tif (opts->separator && out->len != origlen) {\n-\t\t\t\tstrbuf_addbuf(out, opts->separator);\n-\t\t\t}\n-\t\t\tstrbuf_addstr(out, trailer);\n-\t\t\tif (opts->separator) {\n-\t\t\t\tstrbuf_rtrim(out);\n-\t\t\t}\n-\t\t}\n-\t}\n-\n-}\n-\n void format_trailers_from_commit(const char *msg,\n \t\t\t\t const struct process_trailer_options *opts,\n \t\t\t\t struct strbuf *out)\n-- \ngitgitgadget\n\n"},{"id":"486494","messageId":"fd4a9d54d9522973a4c22e43cb1d7964033d4837.1704869487.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.git.1704869487.gitgitgadget@gmail.com","subject":"[PATCH 05/10] sequencer: use the trailer iterator","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-10T06:51:21Z","receivedAt":"2024-01-10T06:51:35Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis patch allows for the removal of \"trailer_info_get()\" from the\ntrailer.h API, which will be in the next patch.\n\nInstead of calling \"trailer_info_get()\", which is a low-level function\nin the trailers implementation (trailer.c), call\ntrailer_iterator_advance(), which was specifically designed for public\nconsumption in f0939a0eb1 (trailer: add interface for iterating over\ncommit trailers, 2020-09-27).\n\nAvoiding \"trailer_info_get()\" means we don't have to worry about options\nlike \"no_divider\" (relevant for parsing trailers). We also don't have to\ncheck for things like \"info.trailer_start == info.trailer_end\" to see\nwhether there were any trailers (instead we can just check to see\nwhether the iterator advanced at all).\n\nAlso, teach the iterator about non-trailer lines, by adding a new field\ncalled \"raw\" to hold both trailer and non-trailer lines. This is\nnecessary because a \"trailer block\" is a list of trailer lines of at\nleast 25% trailers (see 146245063e (trailer: allow non-trailers in\ntrailer block, 2016-10-21)), such that it may hold non-trailer lines.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/shortlog.c |  7 +++++--\n sequencer.c        | 35 +++++++++++++++--------------------\n trailer.c          | 20 ++++++++++++--------\n trailer.h          | 13 +++++++++++++\n 4 files changed, 45 insertions(+), 30 deletions(-)\n\ndiff --git a/builtin/shortlog.c b/builtin/shortlog.c\nindex 1307ed2b88a..dc8fd5a5532 100644\n--- a/builtin/shortlog.c\n+++ b/builtin/shortlog.c\n@@ -172,7 +172,7 @@ static void insert_records_from_trailers(struct shortlog *log,\n \t\t\t\t\t const char *oneline)\n {\n \tstruct trailer_iterator iter;\n-\tconst char *commit_buffer, *body;\n+\tconst char *commit_buffer, *body, *value;\n \tstruct strbuf ident = STRBUF_INIT;\n \n \tif (!log->trailers.nr)\n@@ -190,7 +190,10 @@ static void insert_records_from_trailers(struct shortlog *log,\n \n \ttrailer_iterator_init(&iter, body);\n \twhile (trailer_iterator_advance(&iter)) {\n-\t\tconst char *value = iter.val.buf;\n+\t\tif (!iter.is_trailer)\n+\t\t\tcontinue;\n+\n+\t\tvalue = iter.val.buf;\n \n \t\tif (!string_list_has_string(&log->trailers, iter.key.buf))\n \t\t\tcontinue;\ndiff --git a/sequencer.c b/sequencer.c\nindex 3cc88d8a800..d199869cda9 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -319,37 +319,32 @@ static const char *get_todo_path(const struct replay_opts *opts)\n static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,\n \tsize_t ignore_footer)\n {\n-\tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n-\tstruct trailer_info info;\n-\tsize_t i;\n-\tint found_sob = 0, found_sob_last = 0;\n-\tchar saved_char;\n-\n-\topts.no_divider = 1;\n+\tstruct trailer_iterator iter;\n+\tsize_t i = 0, found_sob = 0;\n+\tchar saved_char = sb->buf[sb->len - ignore_footer];\n \n \tif (ignore_footer) {\n-\t\tsaved_char = sb->buf[sb->len - ignore_footer];\n \t\tsb->buf[sb->len - ignore_footer] = '\\0';\n \t}\n \n-\ttrailer_info_get(&info, sb->buf, &opts);\n+\ttrailer_iterator_init(&iter, sb->buf);\n+\twhile (trailer_iterator_advance(&iter)) {\n+\t\ti++;\n+\t\tif (sob &&\n+\t\t    iter.is_trailer &&\n+\t\t    !strncmp(iter.raw.buf, sob->buf, sob->len)) {\n+\t\t\tfound_sob = i;\n+\t\t}\n+\t}\n+\ttrailer_iterator_release(&iter);\n \n \tif (ignore_footer)\n \t\tsb->buf[sb->len - ignore_footer] = saved_char;\n \n-\tif (info.trailer_block_start == info.trailer_block_end)\n+\tif (!i)\n \t\treturn 0;\n \n-\tfor (i = 0; i < info.trailer_nr; i++)\n-\t\tif (sob && !strncmp(info.trailers[i], sob->buf, sob->len)) {\n-\t\t\tfound_sob = 1;\n-\t\t\tif (i == info.trailer_nr - 1)\n-\t\t\t\tfound_sob_last = 1;\n-\t\t}\n-\n-\ttrailer_info_release(&info);\n-\n-\tif (found_sob_last)\n+\tif (found_sob == i)\n \t\treturn 3;\n \tif (found_sob)\n \t\treturn 2;\ndiff --git a/trailer.c b/trailer.c\nindex 132f22b3dd7..593717fd56c 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1151,6 +1151,7 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n \tstrbuf_init(&iter->key, 0);\n \tstrbuf_init(&iter->val, 0);\n+\tstrbuf_init(&iter->raw, 0);\n \topts.no_divider = 1;\n \ttrailer_info_get(&iter->internal.info, msg, &opts);\n \titer->internal.cur = 0;\n@@ -1158,17 +1159,19 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n \n int trailer_iterator_advance(struct trailer_iterator *iter)\n {\n-\twhile (iter->internal.cur < iter->internal.info.trailer_nr) {\n-\t\tchar *trailer = iter->internal.info.trailers[iter->internal.cur++];\n-\t\tint separator_pos = find_separator(trailer, separators);\n-\n-\t\tif (separator_pos < 1)\n-\t\t\tcontinue; /* not a real trailer */\n-\n+\tchar *line;\n+\tint separator_pos;\n+\tif (iter->internal.cur < iter->internal.info.trailer_nr) {\n+\t\tline = iter->internal.info.trailers[iter->internal.cur++];\n+\t\tseparator_pos = find_separator(line, separators);\n+\t\titer->is_trailer = (separator_pos > 0);\n+\n+\t\tstrbuf_reset(&iter->raw);\n+\t\tstrbuf_addstr(&iter->raw, line);\n \t\tstrbuf_reset(&iter->key);\n \t\tstrbuf_reset(&iter->val);\n \t\tparse_trailer(&iter->key, &iter->val, NULL,\n-\t\t\t      trailer, separator_pos);\n+\t\t\t      line, separator_pos);\n \t\tunfold_value(&iter->val);\n \t\treturn 1;\n \t}\n@@ -1180,4 +1183,5 @@ void trailer_iterator_release(struct trailer_iterator *iter)\n \ttrailer_info_release(&iter->internal.info);\n \tstrbuf_release(&iter->val);\n \tstrbuf_release(&iter->key);\n+\tstrbuf_release(&iter->raw);\n }\ndiff --git a/trailer.h b/trailer.h\nindex 50f70556302..d50c9fd79b2 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -127,6 +127,19 @@ struct trailer_iterator {\n \tstruct strbuf key;\n \tstruct strbuf val;\n \n+\t/*\n+\t * Raw line (e.g., \"foo: bar baz\") before being parsed as a trailer\n+\t * key/val pair. This field can contain non-trailer lines because it's\n+\t * valid for a trailer block to contain such lines (i.e., we only\n+\t * require 25% of the lines in a trailer block to be trailer lines).\n+\t */\n+\tstruct strbuf raw;\n+\n+\t/*\n+\t * 1 if the raw line was parsed as a separate key/val pair.\n+\t */\n+\tint is_trailer;\n+\n \t/* private */\n \tstruct {\n \t\tstruct trailer_info info;\n-- \ngitgitgadget\n\n"},{"id":"486495","messageId":"0cbe96421c7bf573e8ddc97b2a0aecc894095399.1704869487.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.git.1704869487.gitgitgadget@gmail.com","subject":"[PATCH 06/10] trailer: make trailer_info struct private","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-10T06:51:22Z","receivedAt":"2024-01-10T06:51:35Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nIn 13211ae23f (trailer: separate public from internal portion of\ntrailer_iterator, 2023-09-09) we moved trailer_info behind an anonymous\nstruct to discourage use by trailer.h API users. However it still left\nopen the possibility of external use of trailer_info itself. Now that\nthere are no external users of trailer_info, we can make this struct\nprivate.\n\nMake this struct private by putting its definition inside trailer.c.\nThis has two benefits:\n\n(1) it makes the surface area of the public facing interface (trailer.h)\n    smaller, and\n\n(2) external API users are unable to peer inside this struct (because it\n    is only ever exposed as an opaque pointer).\n\nThis change exposes some deficiencies in the API, mainly with regard to\ninformation about the location of the trailer block that was parsed.\nExpose new API functions to access this information (needed by\nbuiltin/interpret-trailers.c).\n\nThe idea in this patch to hide implementation details behind an \"opaque\npointer\" is also known as the \"pimpl\" (pointer to implementation) idiom\nin C++ and is a common pattern in that language (where, for example,\nabstract classes only have pointers to concrete classes).\n\nHowever, the original inspiration to use this idiom does not come from\nC++, but instead the book \"C Interfaces and Implementations: Techniques\nfor Creating Reusable Software\" [1]. This book recommends opaque\npointers as a good design principle for designing C libraries, using the\nterm \"interface\" as the functions defined in *.h (header) files and\n\"implementation\" as the corresponding *.c file which define the\ninterfaces.\n\nThe book says this about opaque pointers:\n\n    ... clients can manipulate such pointers freely, but they can’t\n    dereference them; that is, they can’t look at the innards of the\n    structure pointed to by them. Only the implementation has that\n    privilege. Opaque pointers hide representation details and help\n    catch errors.\n\nIn our case, \"struct trailer_info\" is now hidden from clients, and the\nways in which this opaque pointer can be used is limited to the richness\nof the trailer.h file. In other words, trailer.h exclusively controls\nexactly how \"trailer_info\" pointers are to be used.\n\n[1] Hanson, David R. \"C Interfaces and Implementations: Techniques for\n    Creating Reusable Software\". Addison Wesley, 1997. p. 22\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  13 +--\n trailer.c                    | 154 +++++++++++++++++++++++------------\n trailer.h                    |  37 ++-------\n 3 files changed, 117 insertions(+), 87 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 934833a4645..0838a57e157 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -141,7 +141,7 @@ static void interpret_trailers(const char *file,\n \tLIST_HEAD(head);\n \tstruct strbuf sb = STRBUF_INIT;\n \tstruct strbuf trailer_block = STRBUF_INIT;\n-\tstruct trailer_info info;\n+\tstruct trailer_info *info;\n \tFILE *outfile = stdout;\n \n \ttrailer_config_init();\n@@ -151,13 +151,13 @@ static void interpret_trailers(const char *file,\n \tif (opts->in_place)\n \t\toutfile = create_in_place_tempfile(file);\n \n-\tparse_trailers(&info, sb.buf, &head, opts);\n+\tinfo = parse_trailers(sb.buf, &head, opts);\n \n \t/* Print the lines before the trailers */\n \tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf, 1, info.trailer_block_start, outfile);\n+\t\tfwrite(sb.buf, 1, trailer_block_start(info), outfile);\n \n-\tif (!opts->only_trailers && !info.blank_line_before_trailer)\n+\tif (!opts->only_trailers && !blank_line_before_trailer_block(info))\n \t\tfprintf(outfile, \"\\n\");\n \n \n@@ -176,11 +176,12 @@ static void interpret_trailers(const char *file,\n \tstrbuf_release(&trailer_block);\n \n \tfree_trailers(&head);\n-\ttrailer_info_release(&info);\n \n \t/* Print the lines after the trailers as is */\n \tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n+\t\tfwrite(sb.buf + trailer_block_end(info), 1, sb.len - trailer_block_end(info), outfile);\n+\n+\ttrailer_info_release(info);\n \n \tif (opts->in_place)\n \t\tif (rename_tempfile(&trailers_tempfile, file))\ndiff --git a/trailer.c b/trailer.c\nindex 593717fd56c..0c66e2d3812 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -11,6 +11,27 @@\n  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n  */\n \n+struct trailer_info {\n+\t/*\n+\t * True if there is a blank line before the location pointed to by\n+\t * trailer_block_start.\n+\t */\n+\tint blank_line_before_trailer;\n+\n+\t/*\n+\t * Offsets to the trailer block start and end positions in the input\n+\t * string. If no trailer block is found, these are both set to the\n+\t * \"true\" end of the input (find_end_of_log_message()).\n+\t */\n+\tsize_t trailer_block_start, trailer_block_end;\n+\n+\t/*\n+\t * Array of trailers found.\n+\t */\n+\tchar **trailers;\n+\tsize_t trailer_nr;\n+};\n+\n struct conf_info {\n \tchar *name;\n \tchar *key;\n@@ -1025,20 +1046,72 @@ void format_trailers(struct list_head *head,\n \t}\n }\n \n+static struct trailer_info *trailer_info_new(void)\n+{\n+\tstruct trailer_info *info = xcalloc(1, sizeof(*info));\n+\treturn info;\n+}\n+\n+static struct trailer_info *trailer_info_get(const char *str,\n+\t\t\t\t\t     const struct process_trailer_options *opts)\n+{\n+\tstruct trailer_info *info = trailer_info_new();\n+\tsize_t end_of_log_message = 0, trailer_block_start = 0;\n+\tstruct strbuf **trailer_lines, **ptr;\n+\tchar **trailer_strings = NULL;\n+\tsize_t nr = 0, alloc = 0;\n+\tchar **last = NULL;\n+\n+\ttrailer_config_init();\n+\n+\tend_of_log_message = find_end_of_log_message(str, opts->no_divider);\n+\ttrailer_block_start = find_trailer_block_start(str, end_of_log_message);\n+\n+\ttrailer_lines = strbuf_split_buf(str + trailer_block_start,\n+\t\t\t\t\t end_of_log_message - trailer_block_start,\n+\t\t\t\t\t '\\n',\n+\t\t\t\t\t 0);\n+\tfor (ptr = trailer_lines; *ptr; ptr++) {\n+\t\tif (last && isspace((*ptr)->buf[0])) {\n+\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\t\tstrbuf_attach(&sb, *last, strlen(*last), strlen(*last));\n+\t\t\tstrbuf_addbuf(&sb, *ptr);\n+\t\t\t*last = strbuf_detach(&sb, NULL);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tALLOC_GROW(trailer_strings, nr + 1, alloc);\n+\t\ttrailer_strings[nr] = strbuf_detach(*ptr, NULL);\n+\t\tlast = find_separator(trailer_strings[nr], separators) >= 1\n+\t\t\t? &trailer_strings[nr]\n+\t\t\t: NULL;\n+\t\tnr++;\n+\t}\n+\tstrbuf_list_free(trailer_lines);\n+\n+\tinfo->blank_line_before_trailer = ends_with_blank_line(str,\n+\t\t\t\t\t\t\t       trailer_block_start);\n+\tinfo->trailer_block_start = trailer_block_start;\n+\tinfo->trailer_block_end = end_of_log_message;\n+\tinfo->trailers = trailer_strings;\n+\tinfo->trailer_nr = nr;\n+\n+\treturn info;\n+}\n+\n /*\n  * Parse trailers in \"str\", populating the trailer info and \"head\"\n  * linked list structure.\n  */\n-void parse_trailers(struct trailer_info *info,\n-\t\t    const char *str,\n-\t\t    struct list_head *head,\n-\t\t    const struct process_trailer_options *opts)\n+struct trailer_info *parse_trailers(const char *str,\n+\t\t\t\t    struct list_head *head,\n+\t\t\t\t    const struct process_trailer_options *opts)\n {\n+\tstruct trailer_info *info;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n \tsize_t i;\n \n-\ttrailer_info_get(info, str, opts);\n+\tinfo = trailer_info_get(str, opts);\n \n \tfor (i = 0; i < info->trailer_nr; i++) {\n \t\tint separator_pos;\n@@ -1062,6 +1135,8 @@ void parse_trailers(struct trailer_info *info,\n \t\t\t\t\t strbuf_detach(&val, NULL));\n \t\t}\n \t}\n+\n+\treturn info;\n }\n \n void free_trailers(struct list_head *head)\n@@ -1073,47 +1148,19 @@ void free_trailers(struct list_head *head)\n \t}\n }\n \n-void trailer_info_get(struct trailer_info *info, const char *str,\n-\t\t      const struct process_trailer_options *opts)\n+size_t trailer_block_start(struct trailer_info *info)\n {\n-\tsize_t end_of_log_message = 0, trailer_block_start = 0;\n-\tstruct strbuf **trailer_lines, **ptr;\n-\tchar **trailer_strings = NULL;\n-\tsize_t nr = 0, alloc = 0;\n-\tchar **last = NULL;\n-\n-\ttrailer_config_init();\n-\n-\tend_of_log_message = find_end_of_log_message(str, opts->no_divider);\n-\ttrailer_block_start = find_trailer_block_start(str, end_of_log_message);\n+\treturn info->trailer_block_start;\n+}\n \n-\ttrailer_lines = strbuf_split_buf(str + trailer_block_start,\n-\t\t\t\t\t end_of_log_message - trailer_block_start,\n-\t\t\t\t\t '\\n',\n-\t\t\t\t\t 0);\n-\tfor (ptr = trailer_lines; *ptr; ptr++) {\n-\t\tif (last && isspace((*ptr)->buf[0])) {\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstrbuf_attach(&sb, *last, strlen(*last), strlen(*last));\n-\t\t\tstrbuf_addbuf(&sb, *ptr);\n-\t\t\t*last = strbuf_detach(&sb, NULL);\n-\t\t\tcontinue;\n-\t\t}\n-\t\tALLOC_GROW(trailer_strings, nr + 1, alloc);\n-\t\ttrailer_strings[nr] = strbuf_detach(*ptr, NULL);\n-\t\tlast = find_separator(trailer_strings[nr], separators) >= 1\n-\t\t\t? &trailer_strings[nr]\n-\t\t\t: NULL;\n-\t\tnr++;\n-\t}\n-\tstrbuf_list_free(trailer_lines);\n+size_t trailer_block_end(struct trailer_info *info)\n+{\n+\treturn info->trailer_block_end;\n+}\n \n-\tinfo->blank_line_before_trailer = ends_with_blank_line(str,\n-\t\t\t\t\t\t\t       trailer_block_start);\n-\tinfo->trailer_block_start = trailer_block_start;\n-\tinfo->trailer_block_end = end_of_log_message;\n-\tinfo->trailers = trailer_strings;\n-\tinfo->trailer_nr = nr;\n+int blank_line_before_trailer_block(struct trailer_info *info)\n+{\n+\treturn info->blank_line_before_trailer;\n }\n \n void trailer_info_release(struct trailer_info *info)\n@@ -1122,6 +1169,7 @@ void trailer_info_release(struct trailer_info *info)\n \tfor (i = 0; i < info->trailer_nr; i++)\n \t\tfree(info->trailers[i]);\n \tfree(info->trailers);\n+\tfree(info);\n }\n \n void format_trailers_from_commit(const char *msg,\n@@ -1129,31 +1177,31 @@ void format_trailers_from_commit(const char *msg,\n \t\t\t\t struct strbuf *out)\n {\n \tLIST_HEAD(head);\n-\tstruct trailer_info info;\n-\n-\tparse_trailers(&info, msg, &head, opts);\n+\tstruct trailer_info *info = parse_trailers(msg, &head, opts);\n \n \t/* If we want the whole block untouched, we can take the fast path. */\n \tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n \t    !opts->separator && !opts->key_only && !opts->value_only &&\n \t    !opts->key_value_separator) {\n-\t\tstrbuf_add(out, msg + info.trailer_block_start,\n-\t\t\t   info.trailer_block_end - info.trailer_block_start);\n+\t\tstrbuf_add(out, msg + info->trailer_block_start,\n+\t\t\t   info->trailer_block_end - info->trailer_block_start);\n \t} else\n \t\tformat_trailers(&head, opts, out);\n \n \tfree_trailers(&head);\n-\ttrailer_info_release(&info);\n+\ttrailer_info_release(info);\n }\n \n void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n {\n \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n+\tstruct trailer_info *internal = trailer_info_new();\n \tstrbuf_init(&iter->key, 0);\n \tstrbuf_init(&iter->val, 0);\n \tstrbuf_init(&iter->raw, 0);\n \topts.no_divider = 1;\n-\ttrailer_info_get(&iter->internal.info, msg, &opts);\n+\titer->internal.info = internal;\n+\titer->internal.info = trailer_info_get(msg, &opts);\n \titer->internal.cur = 0;\n }\n \n@@ -1161,8 +1209,8 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n {\n \tchar *line;\n \tint separator_pos;\n-\tif (iter->internal.cur < iter->internal.info.trailer_nr) {\n-\t\tline = iter->internal.info.trailers[iter->internal.cur++];\n+\tif (iter->internal.cur < iter->internal.info->trailer_nr) {\n+\t\tline = iter->internal.info->trailers[iter->internal.cur++];\n \t\tseparator_pos = find_separator(line, separators);\n \t\titer->is_trailer = (separator_pos > 0);\n \n@@ -1180,7 +1228,7 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n \n void trailer_iterator_release(struct trailer_iterator *iter)\n {\n-\ttrailer_info_release(&iter->internal.info);\n+\ttrailer_info_release(iter->internal.info);\n \tstrbuf_release(&iter->val);\n \tstrbuf_release(&iter->key);\n \tstrbuf_release(&iter->raw);\ndiff --git a/trailer.h b/trailer.h\nindex d50c9fd79b2..b06da1a7d3a 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -4,6 +4,8 @@\n #include \"list.h\"\n #include \"strbuf.h\"\n \n+struct trailer_info;\n+\n enum trailer_where {\n \tWHERE_DEFAULT,\n \tWHERE_END,\n@@ -29,27 +31,6 @@ int trailer_set_where(enum trailer_where *item, const char *value);\n int trailer_set_if_exists(enum trailer_if_exists *item, const char *value);\n int trailer_set_if_missing(enum trailer_if_missing *item, const char *value);\n \n-struct trailer_info {\n-\t/*\n-\t * True if there is a blank line before the location pointed to by\n-\t * trailer_block_start.\n-\t */\n-\tint blank_line_before_trailer;\n-\n-\t/*\n-\t * Offsets to the trailer block start and end positions in the input\n-\t * string. If no trailer block is found, these are both set to the\n-\t * \"true\" end of the input (find_end_of_log_message()).\n-\t */\n-\tsize_t trailer_block_start, trailer_block_end;\n-\n-\t/*\n-\t * Array of trailers found.\n-\t */\n-\tchar **trailers;\n-\tsize_t trailer_nr;\n-};\n-\n /*\n  * A list that represents newly-added trailers, such as those provided\n  * with the --trailer command line option of git-interpret-trailers.\n@@ -89,13 +70,13 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n void process_trailers_lists(struct list_head *head,\n \t\t\t    struct list_head *arg_head);\n \n-void parse_trailers(struct trailer_info *info,\n-\t\t    const char *str,\n-\t\t    struct list_head *head,\n-\t\t    const struct process_trailer_options *opts);\n+struct trailer_info *parse_trailers(const char *str,\n+\t\t\t\t    struct list_head *head,\n+\t\t\t\t    const struct process_trailer_options *opts);\n \n-void trailer_info_get(struct trailer_info *info, const char *str,\n-\t\t      const struct process_trailer_options *opts);\n+size_t trailer_block_start(struct trailer_info *info);\n+size_t trailer_block_end(struct trailer_info *info);\n+int blank_line_before_trailer_block(struct trailer_info *info);\n \n void trailer_info_release(struct trailer_info *info);\n \n@@ -142,7 +123,7 @@ struct trailer_iterator {\n \n \t/* private */\n \tstruct {\n-\t\tstruct trailer_info info;\n+\t\tstruct trailer_info *info;\n \t\tsize_t cur;\n \t} internal;\n };\n-- \ngitgitgadget\n\n"},{"id":"486496","messageId":"9183990583f9a591c79301a20fa327462bb50cf9.1704869487.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.git.1704869487.gitgitgadget@gmail.com","subject":"[PATCH 07/10] trailer: spread usage of \"trailer_block\" language","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-10T06:51:23Z","receivedAt":"2024-01-10T06:51:36Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nDeprecate the \"trailer_info\" struct name and replace it with\n\"trailer_block\". The main reason is to help readability, because\n\"trailer_info\" on the surface sounds like it's about a single trailer\nwhen in reality it is a collection of contiguous lines, at least 25% of\nwhich are trailers.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c | 26 +++++-----\n trailer.c                    | 99 ++++++++++++++++++------------------\n trailer.h                    | 18 +++----\n 3 files changed, 71 insertions(+), 72 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 0838a57e157..42d9ca07a56 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -140,8 +140,8 @@ static void interpret_trailers(const char *file,\n {\n \tLIST_HEAD(head);\n \tstruct strbuf sb = STRBUF_INIT;\n-\tstruct strbuf trailer_block = STRBUF_INIT;\n-\tstruct trailer_info *info;\n+\tstruct strbuf tb = STRBUF_INIT;\n+\tstruct trailer_block *trailer_block;\n \tFILE *outfile = stdout;\n \n \ttrailer_config_init();\n@@ -151,13 +151,13 @@ static void interpret_trailers(const char *file,\n \tif (opts->in_place)\n \t\toutfile = create_in_place_tempfile(file);\n \n-\tinfo = parse_trailers(sb.buf, &head, opts);\n+\ttrailer_block = parse_trailers(sb.buf, opts, &head);\n \n-\t/* Print the lines before the trailers */\n+\t/* Print the lines before the trailer block */\n \tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf, 1, trailer_block_start(info), outfile);\n+\t\tfwrite(sb.buf, 1, trailer_block_start(trailer_block), outfile);\n \n-\tif (!opts->only_trailers && !blank_line_before_trailer_block(info))\n+\tif (!opts->only_trailers && !blank_line_before_trailer_block(trailer_block))\n \t\tfprintf(outfile, \"\\n\");\n \n \n@@ -171,17 +171,17 @@ static void interpret_trailers(const char *file,\n \t}\n \n \t/* Print trailer block. */\n-\tformat_trailers(&head, opts, &trailer_block);\n-\tfwrite(trailer_block.buf, 1, trailer_block.len, outfile);\n-\tstrbuf_release(&trailer_block);\n+\tformat_trailers(&head, opts, &tb);\n+\tfwrite(tb.buf, 1, tb.len, outfile);\n+\tstrbuf_release(&tb);\n \n \tfree_trailers(&head);\n \n-\t/* Print the lines after the trailers as is */\n+\t/* Print the lines after the trailer block as is */\n \tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf + trailer_block_end(info), 1, sb.len - trailer_block_end(info), outfile);\n-\n-\ttrailer_info_release(info);\n+\t\tfwrite(sb.buf + trailer_block_end(trailer_block),\n+\t\t       1, sb.len - trailer_block_end(trailer_block), outfile);\n+\ttrailer_block_release(trailer_block);\n \n \tif (opts->in_place)\n \t\tif (rename_tempfile(&trailers_tempfile, file))\ndiff --git a/trailer.c b/trailer.c\nindex 0c66e2d3812..360e76376b8 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -11,19 +11,20 @@\n  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n  */\n \n-struct trailer_info {\n+struct trailer_block {\n \t/*\n \t * True if there is a blank line before the location pointed to by\n-\t * trailer_block_start.\n+\t * \"start\".\n \t */\n \tint blank_line_before_trailer;\n \n \t/*\n-\t * Offsets to the trailer block start and end positions in the input\n-\t * string. If no trailer block is found, these are both set to the\n-\t * \"true\" end of the input (find_end_of_log_message()).\n+\t * The locations of the start and end positions of the trailer block\n+\t * found, as offsets from the beginning of the source text from which\n+\t * this trailer block was parsed. If no trailer block is found, these\n+\t * are both set to 0.\n \t */\n-\tsize_t trailer_block_start, trailer_block_end;\n+\tsize_t start, end;\n \n \t/*\n \t * Array of trailers found.\n@@ -1046,16 +1047,16 @@ void format_trailers(struct list_head *head,\n \t}\n }\n \n-static struct trailer_info *trailer_info_new(void)\n+static struct trailer_block *trailer_block_new(void)\n {\n-\tstruct trailer_info *info = xcalloc(1, sizeof(*info));\n-\treturn info;\n+\tstruct trailer_block *trailer_block = xcalloc(1, sizeof(*trailer_block));\n+\treturn trailer_block;\n }\n \n-static struct trailer_info *trailer_info_get(const char *str,\n-\t\t\t\t\t     const struct process_trailer_options *opts)\n+static struct trailer_block *trailer_block_get(const char *str,\n+\t\t\t\t\t       const struct process_trailer_options *opts)\n {\n-\tstruct trailer_info *info = trailer_info_new();\n+\tstruct trailer_block *trailer_block = trailer_block_new();\n \tsize_t end_of_log_message = 0, trailer_block_start = 0;\n \tstruct strbuf **trailer_lines, **ptr;\n \tchar **trailer_strings = NULL;\n@@ -1088,34 +1089,34 @@ static struct trailer_info *trailer_info_get(const char *str,\n \t}\n \tstrbuf_list_free(trailer_lines);\n \n-\tinfo->blank_line_before_trailer = ends_with_blank_line(str,\n-\t\t\t\t\t\t\t       trailer_block_start);\n-\tinfo->trailer_block_start = trailer_block_start;\n-\tinfo->trailer_block_end = end_of_log_message;\n-\tinfo->trailers = trailer_strings;\n-\tinfo->trailer_nr = nr;\n+\ttrailer_block->blank_line_before_trailer = ends_with_blank_line(str,\n+\t\t\t\t\t\t\t\t\ttrailer_block_start);\n+\ttrailer_block->start = trailer_block_start;\n+\ttrailer_block->end = end_of_log_message;\n+\ttrailer_block->trailers = trailer_strings;\n+\ttrailer_block->trailer_nr = nr;\n \n-\treturn info;\n+\treturn trailer_block;\n }\n \n /*\n- * Parse trailers in \"str\", populating the trailer info and \"head\"\n- * linked list structure.\n+ * Parse trailers in \"str\", populating the trailer_block info and \"head\" linked\n+ * list structure.\n  */\n-struct trailer_info *parse_trailers(const char *str,\n-\t\t\t\t    struct list_head *head,\n-\t\t\t\t    const struct process_trailer_options *opts)\n+struct trailer_block *parse_trailers(const char *str,\n+\t\t\t\t     const struct process_trailer_options *opts,\n+\t\t\t\t     struct list_head *head)\n {\n-\tstruct trailer_info *info;\n+\tstruct trailer_block *trailer_block;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n \tsize_t i;\n \n-\tinfo = trailer_info_get(str, opts);\n+\ttrailer_block = trailer_block_get(str, opts);\n \n-\tfor (i = 0; i < info->trailer_nr; i++) {\n+\tfor (i = 0; i < trailer_block->trailer_nr; i++) {\n \t\tint separator_pos;\n-\t\tchar *trailer = info->trailers[i];\n+\t\tchar *trailer = trailer_block->trailers[i];\n \t\tif (trailer[0] == comment_line_char)\n \t\t\tcontinue;\n \t\tseparator_pos = find_separator(trailer, separators);\n@@ -1136,7 +1137,7 @@ struct trailer_info *parse_trailers(const char *str,\n \t\t}\n \t}\n \n-\treturn info;\n+\treturn trailer_block;\n }\n \n void free_trailers(struct list_head *head)\n@@ -1148,28 +1149,28 @@ void free_trailers(struct list_head *head)\n \t}\n }\n \n-size_t trailer_block_start(struct trailer_info *info)\n+size_t trailer_block_start(struct trailer_block *trailer_block)\n {\n-\treturn info->trailer_block_start;\n+\treturn trailer_block->start;\n }\n \n-size_t trailer_block_end(struct trailer_info *info)\n+size_t trailer_block_end(struct trailer_block *trailer_block)\n {\n-\treturn info->trailer_block_end;\n+\treturn trailer_block->end;\n }\n \n-int blank_line_before_trailer_block(struct trailer_info *info)\n+int blank_line_before_trailer_block(struct trailer_block *trailer_block)\n {\n-\treturn info->blank_line_before_trailer;\n+\treturn trailer_block->blank_line_before_trailer;\n }\n \n-void trailer_info_release(struct trailer_info *info)\n+void trailer_block_release(struct trailer_block *trailer_block)\n {\n \tsize_t i;\n-\tfor (i = 0; i < info->trailer_nr; i++)\n-\t\tfree(info->trailers[i]);\n-\tfree(info->trailers);\n-\tfree(info);\n+\tfor (i = 0; i < trailer_block->trailer_nr; i++)\n+\t\tfree(trailer_block->trailers[i]);\n+\tfree(trailer_block->trailers);\n+\tfree(trailer_block);\n }\n \n void format_trailers_from_commit(const char *msg,\n@@ -1177,31 +1178,29 @@ void format_trailers_from_commit(const char *msg,\n \t\t\t\t struct strbuf *out)\n {\n \tLIST_HEAD(head);\n-\tstruct trailer_info *info = parse_trailers(msg, &head, opts);\n+\tstruct trailer_block *trailer_block = parse_trailers(msg, opts, &head);\n \n \t/* If we want the whole block untouched, we can take the fast path. */\n \tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n \t    !opts->separator && !opts->key_only && !opts->value_only &&\n \t    !opts->key_value_separator) {\n-\t\tstrbuf_add(out, msg + info->trailer_block_start,\n-\t\t\t   info->trailer_block_end - info->trailer_block_start);\n+\t\tstrbuf_add(out, msg + trailer_block->start,\n+\t\t\t   trailer_block->end - trailer_block->start);\n \t} else\n \t\tformat_trailers(&head, opts, out);\n \n \tfree_trailers(&head);\n-\ttrailer_info_release(info);\n+\ttrailer_block_release(trailer_block);\n }\n \n void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n {\n \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n-\tstruct trailer_info *internal = trailer_info_new();\n \tstrbuf_init(&iter->key, 0);\n \tstrbuf_init(&iter->val, 0);\n \tstrbuf_init(&iter->raw, 0);\n \topts.no_divider = 1;\n-\titer->internal.info = internal;\n-\titer->internal.info = trailer_info_get(msg, &opts);\n+\titer->internal.trailer_block = trailer_block_get(msg, &opts);\n \titer->internal.cur = 0;\n }\n \n@@ -1209,8 +1208,8 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n {\n \tchar *line;\n \tint separator_pos;\n-\tif (iter->internal.cur < iter->internal.info->trailer_nr) {\n-\t\tline = iter->internal.info->trailers[iter->internal.cur++];\n+\tif (iter->internal.cur < iter->internal.trailer_block->trailer_nr) {\n+\t\tline = iter->internal.trailer_block->trailers[iter->internal.cur++];\n \t\tseparator_pos = find_separator(line, separators);\n \t\titer->is_trailer = (separator_pos > 0);\n \n@@ -1228,7 +1227,7 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n \n void trailer_iterator_release(struct trailer_iterator *iter)\n {\n-\ttrailer_info_release(iter->internal.info);\n+\ttrailer_block_release(iter->internal.trailer_block);\n \tstrbuf_release(&iter->val);\n \tstrbuf_release(&iter->key);\n \tstrbuf_release(&iter->raw);\ndiff --git a/trailer.h b/trailer.h\nindex b06da1a7d3a..5c8503ade78 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -4,7 +4,7 @@\n #include \"list.h\"\n #include \"strbuf.h\"\n \n-struct trailer_info;\n+struct trailer_block;\n \n enum trailer_where {\n \tWHERE_DEFAULT,\n@@ -70,15 +70,15 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n void process_trailers_lists(struct list_head *head,\n \t\t\t    struct list_head *arg_head);\n \n-struct trailer_info *parse_trailers(const char *str,\n-\t\t\t\t    struct list_head *head,\n-\t\t\t\t    const struct process_trailer_options *opts);\n+struct trailer_block *parse_trailers(const char *str,\n+\t\t\t\t     const struct process_trailer_options *opts,\n+\t\t\t\t     struct list_head *head);\n \n-size_t trailer_block_start(struct trailer_info *info);\n-size_t trailer_block_end(struct trailer_info *info);\n-int blank_line_before_trailer_block(struct trailer_info *info);\n+size_t trailer_block_start(struct trailer_block *trailer_block);\n+size_t trailer_block_end(struct trailer_block *trailer_block);\n+int blank_line_before_trailer_block(struct trailer_block *trailer_block);\n \n-void trailer_info_release(struct trailer_info *info);\n+void trailer_block_release(struct trailer_block *trailer_block);\n \n void trailer_config_init(void);\n void free_trailers(struct list_head *trailers);\n@@ -123,7 +123,7 @@ struct trailer_iterator {\n \n \t/* private */\n \tstruct {\n-\t\tstruct trailer_info *info;\n+\t\tstruct trailer_block *trailer_block;\n \t\tsize_t cur;\n \t} internal;\n };\n-- \ngitgitgadget\n\n"},{"id":"486497","messageId":"406725df46a81f485d7a74c11488e625d3026dc5.1704869487.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.git.1704869487.gitgitgadget@gmail.com","subject":"[PATCH 08/10] trailer: prepare to move parse_trailers_from_command_line_args() to builtin","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-10T06:51:24Z","receivedAt":"2024-01-10T06:51:37Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nExpose more functions in the trailer.h API, in preparation for moving\nout\n\n    parse_trailers_from_command_line_args()\n\nto interpret-trailer.c, because the trailer API should not be concerned\nwith command line arguments (as it has nothing to do with trailers\nthemselves). The interpret-trailers builtin is the only user of the\nabove function.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 66 +++++++++++++++++++++++++++----------------------------\n trailer.h | 10 +++++++++\n 2 files changed, 42 insertions(+), 34 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 360e76376b8..e2d541372a3 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -33,7 +33,7 @@ struct trailer_block {\n \tsize_t trailer_nr;\n };\n \n-struct conf_info {\n+struct trailer_conf {\n \tchar *name;\n \tchar *key;\n \tchar *command;\n@@ -43,7 +43,7 @@ struct conf_info {\n \tenum trailer_if_missing if_missing;\n };\n \n-static struct conf_info default_conf_info;\n+static struct trailer_conf default_trailer_conf;\n \n struct trailer_item {\n \tstruct list_head list;\n@@ -59,7 +59,7 @@ struct arg_item {\n \tstruct list_head list;\n \tchar *token;\n \tchar *value;\n-\tstruct conf_info conf;\n+\tstruct trailer_conf conf;\n };\n \n static LIST_HEAD(conf_head);\n@@ -210,7 +210,7 @@ static int check_if_different(struct trailer_item *in_tok,\n \treturn 1;\n }\n \n-static char *apply_command(struct conf_info *conf, const char *arg)\n+static char *apply_command(struct trailer_conf *conf, const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -424,7 +424,8 @@ int trailer_set_if_missing(enum trailer_if_missing *item, const char *value)\n \treturn 0;\n }\n \n-static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n+void duplicate_trailer_conf(struct trailer_conf *dst,\n+\t\t\t    const struct trailer_conf *src)\n {\n \t*dst = *src;\n \tdst->name = xstrdup_or_null(src->name);\n@@ -447,7 +448,7 @@ static struct arg_item *get_conf_item(const char *name)\n \n \t/* Item does not already exists, create it */\n \tCALLOC_ARRAY(item, 1);\n-\tduplicate_conf(&item->conf, &default_conf_info);\n+\tduplicate_trailer_conf(&item->conf, &default_trailer_conf);\n \titem->conf.name = xstrdup(name);\n \n \tlist_add_tail(&item->list, &conf_head);\n@@ -482,17 +483,17 @@ static int git_trailer_default_config(const char *conf_key, const char *value,\n \tvariable_name = strrchr(trailer_item, '.');\n \tif (!variable_name) {\n \t\tif (!strcmp(trailer_item, \"where\")) {\n-\t\t\tif (trailer_set_where(&default_conf_info.where,\n+\t\t\tif (trailer_set_where(&default_trailer_conf.where,\n \t\t\t\t\t      value) < 0)\n \t\t\t\twarning(_(\"unknown value '%s' for key '%s'\"),\n \t\t\t\t\tvalue, conf_key);\n \t\t} else if (!strcmp(trailer_item, \"ifexists\")) {\n-\t\t\tif (trailer_set_if_exists(&default_conf_info.if_exists,\n+\t\t\tif (trailer_set_if_exists(&default_trailer_conf.if_exists,\n \t\t\t\t\t\t  value) < 0)\n \t\t\t\twarning(_(\"unknown value '%s' for key '%s'\"),\n \t\t\t\t\tvalue, conf_key);\n \t\t} else if (!strcmp(trailer_item, \"ifmissing\")) {\n-\t\t\tif (trailer_set_if_missing(&default_conf_info.if_missing,\n+\t\t\tif (trailer_set_if_missing(&default_trailer_conf.if_missing,\n \t\t\t\t\t\t   value) < 0)\n \t\t\t\twarning(_(\"unknown value '%s' for key '%s'\"),\n \t\t\t\t\tvalue, conf_key);\n@@ -511,7 +512,7 @@ static int git_trailer_config(const char *conf_key, const char *value,\n {\n \tconst char *trailer_item, *variable_name;\n \tstruct arg_item *item;\n-\tstruct conf_info *conf;\n+\tstruct trailer_conf *conf;\n \tchar *name = NULL;\n \tenum trailer_info_type type;\n \tint i;\n@@ -585,9 +586,9 @@ void trailer_config_init(void)\n \t\treturn;\n \n \t/* Default config must be setup first */\n-\tdefault_conf_info.where = WHERE_END;\n-\tdefault_conf_info.if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;\n-\tdefault_conf_info.if_missing = MISSING_ADD;\n+\tdefault_trailer_conf.where = WHERE_END;\n+\tdefault_trailer_conf.if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;\n+\tdefault_trailer_conf.if_missing = MISSING_ADD;\n \tgit_config(git_trailer_default_config, NULL);\n \tgit_config(git_trailer_config, NULL);\n \tconfigured = 1;\n@@ -620,7 +621,7 @@ static int token_matches_item(const char *tok, struct arg_item *item, size_t tok\n  * distinguished from the non-well-formed-line case (in which this function\n  * returns -1) because some callers of this function need such a distinction.\n  */\n-static ssize_t find_separator(const char *line, const char *separators)\n+ssize_t find_separator(const char *line, const char *separators)\n {\n \tint whitespace_found = 0;\n \tconst char *c;\n@@ -645,28 +646,28 @@ static ssize_t find_separator(const char *line, const char *separators)\n  *\n  * If separator_pos is -1, interpret the whole trailer as a token.\n  */\n-static void parse_trailer(struct strbuf *tok, struct strbuf *val,\n-\t\t\t const struct conf_info **conf, const char *trailer,\n-\t\t\t ssize_t separator_pos)\n+void parse_trailer(const char *line, ssize_t separator_pos,\n+\t\t   struct strbuf *tok, struct strbuf *val,\n+\t\t   const struct trailer_conf **conf)\n {\n \tstruct arg_item *item;\n \tsize_t tok_len;\n \tstruct list_head *pos;\n \n \tif (separator_pos != -1) {\n-\t\tstrbuf_add(tok, trailer, separator_pos);\n+\t\tstrbuf_add(tok, line, separator_pos);\n \t\tstrbuf_trim(tok);\n-\t\tstrbuf_addstr(val, trailer + separator_pos + 1);\n+\t\tstrbuf_addstr(val, line + separator_pos + 1);\n \t\tstrbuf_trim(val);\n \t} else {\n-\t\tstrbuf_addstr(tok, trailer);\n+\t\tstrbuf_addstr(tok, line);\n \t\tstrbuf_trim(tok);\n \t}\n \n \t/* Lookup if the token matches something in the config */\n \ttok_len = token_len_without_separator(tok->buf, tok->len);\n \tif (conf)\n-\t\t*conf = &default_conf_info;\n+\t\t*conf = &default_trailer_conf;\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct arg_item, list);\n \t\tif (token_matches_item(tok->buf, item, tok_len)) {\n@@ -691,13 +692,13 @@ static struct trailer_item *add_trailer_item(struct list_head *head, char *tok,\n }\n \n static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n-\t\t\t const struct conf_info *conf,\n+\t\t\t const struct trailer_conf *conf,\n \t\t\t const struct new_trailer_item *new_trailer_item)\n {\n \tstruct arg_item *new_item = xcalloc(1, sizeof(*new_item));\n \tnew_item->token = tok;\n \tnew_item->value = val;\n-\tduplicate_conf(&new_item->conf, conf);\n+\tduplicate_trailer_conf(&new_item->conf, conf);\n \tif (new_trailer_item) {\n \t\tif (new_trailer_item->where != WHERE_DEFAULT)\n \t\t\tnew_item->conf.where = new_trailer_item->where;\n@@ -730,7 +731,7 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n {\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n-\tconst struct conf_info *conf;\n+\tconst struct trailer_conf *conf;\n \tstruct list_head *pos;\n \n \t/*\n@@ -753,8 +754,7 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n \t\t\t      (int) sb.len, sb.buf);\n \t\t\tstrbuf_release(&sb);\n \t\t} else {\n-\t\t\tparse_trailer(&tok, &val, &conf, tr->text,\n-\t\t\t\t      separator_pos);\n+\t\t\tparse_trailer(tr->text, separator_pos, &tok, &val, &conf);\n \t\t\tadd_arg_item(arg_head,\n \t\t\t\t     strbuf_detach(&tok, NULL),\n \t\t\t\t     strbuf_detach(&val, NULL),\n@@ -1116,20 +1116,19 @@ struct trailer_block *parse_trailers(const char *str,\n \n \tfor (i = 0; i < trailer_block->trailer_nr; i++) {\n \t\tint separator_pos;\n-\t\tchar *trailer = trailer_block->trailers[i];\n-\t\tif (trailer[0] == comment_line_char)\n+\t\tchar *line = trailer_block->trailers[i];\n+\t\tif (line[0] == comment_line_char)\n \t\t\tcontinue;\n-\t\tseparator_pos = find_separator(trailer, separators);\n+\t\tseparator_pos = find_separator(line, separators);\n \t\tif (separator_pos >= 1) {\n-\t\t\tparse_trailer(&tok, &val, NULL, trailer,\n-\t\t\t\t      separator_pos);\n+\t\t\tparse_trailer(line, separator_pos, &tok, &val, NULL);\n \t\t\tif (opts->unfold)\n \t\t\t\tunfold_value(&val);\n \t\t\tadd_trailer_item(head,\n \t\t\t\t\t strbuf_detach(&tok, NULL),\n \t\t\t\t\t strbuf_detach(&val, NULL));\n \t\t} else if (!opts->only_trailers) {\n-\t\t\tstrbuf_addstr(&val, trailer);\n+\t\t\tstrbuf_addstr(&val, line);\n \t\t\tstrbuf_strip_suffix(&val, \"\\n\");\n \t\t\tadd_trailer_item(head,\n \t\t\t\t\t NULL,\n@@ -1217,8 +1216,7 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n \t\tstrbuf_addstr(&iter->raw, line);\n \t\tstrbuf_reset(&iter->key);\n \t\tstrbuf_reset(&iter->val);\n-\t\tparse_trailer(&iter->key, &iter->val, NULL,\n-\t\t\t      line, separator_pos);\n+\t\tparse_trailer(line, separator_pos, &iter->key, &iter->val, NULL);\n \t\tunfold_value(&iter->val);\n \t\treturn 1;\n \t}\ndiff --git a/trailer.h b/trailer.h\nindex 5c8503ade78..fe49a9bad52 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -5,6 +5,7 @@\n #include \"strbuf.h\"\n \n struct trailer_block;\n+struct trailer_conf;\n \n enum trailer_where {\n \tWHERE_DEFAULT,\n@@ -45,6 +46,9 @@ struct new_trailer_item {\n \tenum trailer_if_missing if_missing;\n };\n \n+void duplicate_trailer_conf(struct trailer_conf *dst,\n+\t\t\t    const struct trailer_conf *src);\n+\n struct process_trailer_options {\n \tint in_place;\n \tint trim_empty;\n@@ -70,6 +74,12 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n void process_trailers_lists(struct list_head *head,\n \t\t\t    struct list_head *arg_head);\n \n+ssize_t find_separator(const char *line, const char *separators);\n+\n+void parse_trailer(const char *line, ssize_t separator_pos,\n+\t\t   struct strbuf *tok, struct strbuf *val,\n+\t\t   const struct trailer_conf **conf);\n+\n struct trailer_block *parse_trailers(const char *str,\n \t\t\t\t     const struct process_trailer_options *opts,\n \t\t\t\t     struct list_head *head);\n-- \ngitgitgadget\n\n"},{"id":"486498","messageId":"8a99d0fca21eca41d62dcd682c8b4ae545633bf7.1704869487.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.git.1704869487.gitgitgadget@gmail.com","subject":"[PATCH 09/10] trailer: move arg handling to interpret-trailers.c","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-10T06:51:25Z","receivedAt":"2024-01-10T06:51:38Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nWe don't move the \"arg_item\" struct to interpret-trailers.c, because it\nis now a struct that contains information about trailers that should be\ninjected into the input text's own trailers. We will rename this\nlanguage as such in a follow-up patch to keep the diff here small.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c | 88 ++++++++++++++++++++++--------------\n trailer.c                    | 63 +++++++++++++++++++-------\n trailer.h                    | 12 +++++\n 3 files changed, 113 insertions(+), 50 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 42d9ca07a56..4da4eac3b46 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -45,23 +45,17 @@ static int option_parse_if_missing(const struct option *opt,\n \treturn trailer_set_if_missing(opt->value, arg);\n }\n \n-static void new_trailers_clear(struct list_head *trailers)\n-{\n-\tstruct list_head *pos, *tmp;\n-\tstruct new_trailer_item *item;\n-\n-\tlist_for_each_safe(pos, tmp, trailers) {\n-\t\titem = list_entry(pos, struct new_trailer_item, list);\n-\t\tlist_del(pos);\n-\t\tfree(item);\n-\t}\n-}\n+static char *cl_separators;\n \n static int option_parse_trailer(const struct option *opt,\n \t\t\t\t   const char *arg, int unset)\n {\n \tstruct list_head *trailers = opt->value;\n-\tstruct new_trailer_item *item;\n+\tstruct strbuf tok = STRBUF_INIT;\n+\tstruct strbuf val = STRBUF_INIT;\n+\tconst struct trailer_conf *conf;\n+\tstruct trailer_conf *conf_current = new_trailer_conf();\n+\tssize_t separator_pos;\n \n \tif (unset) {\n \t\tnew_trailers_clear(trailers);\n@@ -71,12 +65,31 @@ static int option_parse_trailer(const struct option *opt,\n \tif (!arg)\n \t\treturn -1;\n \n-\titem = xmalloc(sizeof(*item));\n-\titem->text = arg;\n-\titem->where = where;\n-\titem->if_exists = if_exists;\n-\titem->if_missing = if_missing;\n-\tlist_add_tail(&item->list, trailers);\n+\tseparator_pos = find_separator(arg, cl_separators);\n+\tif (separator_pos) {\n+\t\tparse_trailer(arg, separator_pos, &tok, &val, &conf);\n+\t\tduplicate_trailer_conf(conf_current, conf);\n+\n+\t\t/*\n+\t\t * Override conf_current with settings specified via CLI flags.\n+\t\t */\n+\t\ttrailer_conf_set(where, if_exists, if_missing, conf_current);\n+\n+\t\tadd_arg_item(strbuf_detach(&tok, NULL),\n+\t\t\t     strbuf_detach(&val, NULL),\n+\t\t\t     conf_current,\n+\t\t\t     trailers);\n+\t} else {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_addstr(&sb, arg);\n+\t\tstrbuf_trim(&sb);\n+\t\terror(_(\"empty trailer token in trailer '%.*s'\"),\n+\t\t\t(int) sb.len, sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t}\n+\n+\tfree(conf_current);\n+\n \treturn 0;\n }\n \n@@ -136,7 +149,7 @@ static void read_input_file(struct strbuf *sb, const char *file)\n \n static void interpret_trailers(const char *file,\n \t\t\t       const struct process_trailer_options *opts,\n-\t\t\t       struct list_head *new_trailer_head)\n+\t\t\t       struct list_head *arg_trailers)\n {\n \tLIST_HEAD(head);\n \tstruct strbuf sb = STRBUF_INIT;\n@@ -144,8 +157,6 @@ static void interpret_trailers(const char *file,\n \tstruct trailer_block *trailer_block;\n \tFILE *outfile = stdout;\n \n-\ttrailer_config_init();\n-\n \tread_input_file(&sb, file);\n \n \tif (opts->in_place)\n@@ -162,12 +173,7 @@ static void interpret_trailers(const char *file,\n \n \n \tif (!opts->only_input) {\n-\t\tLIST_HEAD(config_head);\n-\t\tLIST_HEAD(arg_head);\n-\t\tparse_trailers_from_config(&config_head);\n-\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n-\t\tlist_splice(&config_head, &arg_head);\n-\t\tprocess_trailers_lists(&head, &arg_head);\n+\t\tprocess_trailers_lists(&head, arg_trailers);\n \t}\n \n \t/* Print trailer block. */\n@@ -193,7 +199,8 @@ static void interpret_trailers(const char *file,\n int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n {\n \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n-\tLIST_HEAD(trailers);\n+\tLIST_HEAD(configured_trailers);\n+\tLIST_HEAD(arg_trailers);\n \n \tstruct option options[] = {\n \t\tOPT_BOOL(0, \"in-place\", &opts.in_place, N_(\"edit files in place\")),\n@@ -212,33 +219,48 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK_F(0, \"parse\", &opts, NULL, N_(\"alias for --only-trailers --only-input --unfold\"),\n \t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse),\n \t\tOPT_BOOL(0, \"no-divider\", &opts.no_divider, N_(\"do not treat \\\"---\\\" as the end of input\")),\n-\t\tOPT_CALLBACK(0, \"trailer\", &trailers, N_(\"trailer\"),\n+\t\tOPT_CALLBACK(0, \"trailer\", &arg_trailers, N_(\"trailer\"),\n \t\t\t\tN_(\"trailer(s) to add\"), option_parse_trailer),\n \t\tOPT_END()\n \t};\n \n \tgit_config(git_default_config, NULL);\n+\ttrailer_config_init();\n+\n+\tif (!opts.only_input) {\n+\t\tparse_trailers_from_config(&configured_trailers);\n+\t}\n+\n+\t/*\n+\t* In command-line arguments, '=' is accepted (in addition to the\n+\t* separators that are defined).\n+\t*/\n+\tcl_separators = xstrfmt(\"=%s\", default_separators());\n \n \targc = parse_options(argc, argv, prefix, options,\n \t\t\t     git_interpret_trailers_usage, 0);\n \n-\tif (opts.only_input && !list_empty(&trailers))\n+\tfree(cl_separators);\n+\n+\tif (opts.only_input && !list_empty(&arg_trailers))\n \t\tusage_msg_opt(\n \t\t\t_(\"--trailer with --only-input does not make sense\"),\n \t\t\tgit_interpret_trailers_usage,\n \t\t\toptions);\n \n+\tlist_splice(&configured_trailers, &arg_trailers);\n+\n \tif (argc) {\n \t\tint i;\n \t\tfor (i = 0; i < argc; i++)\n-\t\t\tinterpret_trailers(argv[i], &opts, &trailers);\n+\t\t\tinterpret_trailers(argv[i], &opts, &arg_trailers);\n \t} else {\n \t\tif (opts.in_place)\n \t\t\tdie(_(\"no input file given for in-place editing\"));\n-\t\tinterpret_trailers(NULL, &opts, &trailers);\n+\t\tinterpret_trailers(NULL, &opts, &arg_trailers);\n \t}\n \n-\tnew_trailers_clear(&trailers);\n+\tnew_trailers_clear(&arg_trailers);\n \n \treturn 0;\n }\ndiff --git a/trailer.c b/trailer.c\nindex e2d541372a3..0a86e0d5afa 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -66,6 +66,11 @@ static LIST_HEAD(conf_head);\n \n static char *separators = \":\";\n \n+const char *default_separators(void)\n+{\n+\treturn separators;\n+}\n+\n static int configured;\n \n #define TRAILER_ARG_STRING \"$ARG\"\n@@ -424,6 +429,25 @@ int trailer_set_if_missing(enum trailer_if_missing *item, const char *value)\n \treturn 0;\n }\n \n+void trailer_conf_set(enum trailer_where where,\n+\t\t      enum trailer_if_exists if_exists,\n+\t\t      enum trailer_if_missing if_missing,\n+\t\t      struct trailer_conf *conf)\n+{\n+\tif (where != WHERE_DEFAULT)\n+\t\tconf->where = where;\n+\tif (if_exists != EXISTS_DEFAULT)\n+\t\tconf->if_exists = if_exists;\n+\tif (if_missing != MISSING_DEFAULT)\n+\t\tconf->if_missing = if_missing;\n+}\n+\n+struct trailer_conf *new_trailer_conf(void)\n+{\n+\tstruct trailer_conf *new = xcalloc(1, sizeof(*new));\n+\treturn new;\n+}\n+\n void duplicate_trailer_conf(struct trailer_conf *dst,\n \t\t\t    const struct trailer_conf *src)\n {\n@@ -642,6 +666,9 @@ ssize_t find_separator(const char *line, const char *separators)\n /*\n  * Obtain the token, value, and conf from the given trailer.\n  *\n+ * The conf needs special handling. We first read hardcoded defaults, and\n+ * override them if we find a matching trailer configuration in the config.\n+ *\n  * separator_pos must not be 0, since the token cannot be an empty string.\n  *\n  * If separator_pos is -1, interpret the whole trailer as a token.\n@@ -691,22 +718,14 @@ static struct trailer_item *add_trailer_item(struct list_head *head, char *tok,\n \treturn new_item;\n }\n \n-static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n-\t\t\t const struct trailer_conf *conf,\n-\t\t\t const struct new_trailer_item *new_trailer_item)\n+void add_arg_item(char *tok, char *val, const struct trailer_conf *conf,\n+\t\t  struct list_head *arg_head)\n+\n {\n \tstruct arg_item *new_item = xcalloc(1, sizeof(*new_item));\n \tnew_item->token = tok;\n \tnew_item->value = val;\n \tduplicate_trailer_conf(&new_item->conf, conf);\n-\tif (new_trailer_item) {\n-\t\tif (new_trailer_item->where != WHERE_DEFAULT)\n-\t\t\tnew_item->conf.where = new_trailer_item->where;\n-\t\tif (new_trailer_item->if_exists != EXISTS_DEFAULT)\n-\t\t\tnew_item->conf.if_exists = new_trailer_item->if_exists;\n-\t\tif (new_trailer_item->if_missing != MISSING_DEFAULT)\n-\t\t\tnew_item->conf.if_missing = new_trailer_item->if_missing;\n-\t}\n \tlist_add_tail(&new_item->list, arg_head);\n }\n \n@@ -719,10 +738,10 @@ void parse_trailers_from_config(struct list_head *config_head)\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct arg_item, list);\n \t\tif (item->conf.command)\n-\t\t\tadd_arg_item(config_head,\n-\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n+\t\t\tadd_arg_item(xstrdup(token_from_item(item, NULL)),\n \t\t\t\t     xstrdup(\"\"),\n-\t\t\t\t     &item->conf, NULL);\n+\t\t\t\t     &item->conf,\n+\t\t\t\t     config_head);\n \t}\n }\n \n@@ -755,10 +774,10 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n \t\t\tstrbuf_release(&sb);\n \t\t} else {\n \t\t\tparse_trailer(tr->text, separator_pos, &tok, &val, &conf);\n-\t\t\tadd_arg_item(arg_head,\n-\t\t\t\t     strbuf_detach(&tok, NULL),\n+\t\t\tadd_arg_item(strbuf_detach(&tok, NULL),\n \t\t\t\t     strbuf_detach(&val, NULL),\n-\t\t\t\t     conf, tr);\n+\t\t\t\t     conf,\n+\t\t\t\t     arg_head);\n \t\t}\n \t}\n \n@@ -1148,6 +1167,16 @@ void free_trailers(struct list_head *head)\n \t}\n }\n \n+void new_trailers_clear(struct list_head *trailers)\n+{\n+\tstruct list_head *pos, *p;\n+\n+\tlist_for_each_safe(pos, p, trailers) {\n+\t\tlist_del(pos);\n+\t\tfree_arg_item(list_entry(pos, struct arg_item, list));\n+\t}\n+}\n+\n size_t trailer_block_start(struct trailer_block *trailer_block)\n {\n \treturn trailer_block->start;\ndiff --git a/trailer.h b/trailer.h\nindex fe49a9bad52..9b86acfe2d4 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -46,9 +46,20 @@ struct new_trailer_item {\n \tenum trailer_if_missing if_missing;\n };\n \n+void trailer_conf_set(enum trailer_where where,\n+\t\t      enum trailer_if_exists if_exists,\n+\t\t      enum trailer_if_missing if_missing,\n+\t\t      struct trailer_conf *conf);\n+\n+struct trailer_conf *new_trailer_conf(void);\n void duplicate_trailer_conf(struct trailer_conf *dst,\n \t\t\t    const struct trailer_conf *src);\n \n+const char *default_separators(void);\n+\n+void add_arg_item(char *tok, char *val, const struct trailer_conf *conf,\n+\t\t  struct list_head *arg_head);\n+\n struct process_trailer_options {\n \tint in_place;\n \tint trim_empty;\n@@ -92,6 +103,7 @@ void trailer_block_release(struct trailer_block *trailer_block);\n \n void trailer_config_init(void);\n void free_trailers(struct list_head *trailers);\n+void new_trailers_clear(struct list_head *trailers);\n \n void format_trailers(struct list_head *head,\n \t\t     const struct process_trailer_options *opts,\n-- \ngitgitgadget\n\n"},{"id":"486499","messageId":"243eee730e4aae48fd53ba066c829a65cd5546f3.1704869487.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.git.1704869487.gitgitgadget@gmail.com","subject":"[PATCH 10/10] trailer: delete obsolete argument handling code from API","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-10T06:51:26Z","receivedAt":"2024-01-10T06:51:38Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis commit was not squashed with its parent in order to keep the diff\nseparate (to make the additive changes in the parent easier to read).\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 39 ---------------------------------------\n trailer.h | 17 -----------------\n 2 files changed, 56 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 0a86e0d5afa..27bb2195f53 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -745,45 +745,6 @@ void parse_trailers_from_config(struct list_head *config_head)\n \t}\n }\n \n-void parse_trailers_from_command_line_args(struct list_head *arg_head,\n-\t\t\t\t\t   struct list_head *new_trailer_head)\n-{\n-\tstruct strbuf tok = STRBUF_INIT;\n-\tstruct strbuf val = STRBUF_INIT;\n-\tconst struct trailer_conf *conf;\n-\tstruct list_head *pos;\n-\n-\t/*\n-\t * In command-line arguments, '=' is accepted (in addition to the\n-\t * separators that are defined).\n-\t */\n-\tchar *cl_separators = xstrfmt(\"=%s\", separators);\n-\n-\t/* Add an arg item for each trailer on the command line */\n-\tlist_for_each(pos, new_trailer_head) {\n-\t\tstruct new_trailer_item *tr =\n-\t\t\tlist_entry(pos, struct new_trailer_item, list);\n-\t\tssize_t separator_pos = find_separator(tr->text, cl_separators);\n-\n-\t\tif (separator_pos == 0) {\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstrbuf_addstr(&sb, tr->text);\n-\t\t\tstrbuf_trim(&sb);\n-\t\t\terror(_(\"empty trailer token in trailer '%.*s'\"),\n-\t\t\t      (int) sb.len, sb.buf);\n-\t\t\tstrbuf_release(&sb);\n-\t\t} else {\n-\t\t\tparse_trailer(tr->text, separator_pos, &tok, &val, &conf);\n-\t\t\tadd_arg_item(strbuf_detach(&tok, NULL),\n-\t\t\t\t     strbuf_detach(&val, NULL),\n-\t\t\t\t     conf,\n-\t\t\t\t     arg_head);\n-\t\t}\n-\t}\n-\n-\tfree(cl_separators);\n-}\n-\n static const char *next_line(const char *str)\n {\n \tconst char *nl = strchrnul(str, '\\n');\ndiff --git a/trailer.h b/trailer.h\nindex 9b86acfe2d4..8a89e95c171 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -32,20 +32,6 @@ int trailer_set_where(enum trailer_where *item, const char *value);\n int trailer_set_if_exists(enum trailer_if_exists *item, const char *value);\n int trailer_set_if_missing(enum trailer_if_missing *item, const char *value);\n \n-/*\n- * A list that represents newly-added trailers, such as those provided\n- * with the --trailer command line option of git-interpret-trailers.\n- */\n-struct new_trailer_item {\n-\tstruct list_head list;\n-\n-\tconst char *text;\n-\n-\tenum trailer_where where;\n-\tenum trailer_if_exists if_exists;\n-\tenum trailer_if_missing if_missing;\n-};\n-\n void trailer_conf_set(enum trailer_where where,\n \t\t      enum trailer_if_exists if_exists,\n \t\t      enum trailer_if_missing if_missing,\n@@ -79,9 +65,6 @@ struct process_trailer_options {\n \n void parse_trailers_from_config(struct list_head *config_head);\n \n-void parse_trailers_from_command_line_args(struct list_head *arg_head,\n-\t\t\t\t\t   struct list_head *new_trailer_head);\n-\n void process_trailers_lists(struct list_head *head,\n \t\t\t    struct list_head *arg_head);\n \n-- \ngitgitgadget\n"},{"id":"486560","messageId":"xmqqy1cx9dm4.fsf@gitster.g","threadId":"60715","inReplyTo":"pull.1632.git.1704869487.gitgitgadget@gmail.com","subject":"Re: [PATCH 00/10] Enrich Trailer API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-01-10T19:45:23Z","receivedAt":"2024-01-10T19:45:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> This patch series is the first 10 patches of a much larger series I've been\n> working. The main goal of this series is to enrich the API in trailer.h. The\n> larger series brings a number of additional code simplifications and\n> cleanups (exposing and fixing some bugs along the way), and builds on top of\n> this series. The goal of the larger series is to make the trailer interface\n> ready for unit testing. By \"trailer API\" I mean those functions exposed in\n> trailer.h.\n\nAre there places in the current code that deals with trailers but\ndoes not use the trailer API (e.g., manually parse and/or insert the\ntrailer in an in-core buffer)?  Is it part of the larger goal to\nupdate these places so that we will always use the trailer API to\ntouch trailers, and if so, have these places been identified?\n\nObviously the reason why I ask is that testing cannot be the goal by\nitself.  The \"alternative\" ...\n\n> As an alternative to this patch series, we could keep trailer.h intact and\n> decide to unit-test the existing \"trailer_info_get()\" function which does\n> most of the trailer parsing work.\n\n... may allow you to \"test\", but it would make it more difficult in\nthe future to revamp the trailer API, if it is needed, in order to\ncover code paths that ought to be using but currently bypassing the\ntrailer API.\n\n> This series breaks up \"process_trailers()\" into smaller pieces, exposing\n> many of the parts relevant to trailer-related processing in trailer.h. This\n> forces us to start writing unit tests for these now public functions, but\n> that is a good thing because those same unit tests should be easy to write\n> (due to their small(er) sizes), but also, because those unit tests will now\n> ensure some degree of stability across new versions of trailer.h (we will\n> start noticing when the behavior of any of these API functions change).\n\nAnd helper functions, each of which does one small thing well, may\nbe more applicable to other code paths that are currently bypassing\nthe API.\n\n> Thanks to the aggressive refactoring in this series, I've been able to\n> identify and fix a couple bugs in our existing implementation. Those fixes\n> build on top of this series but were not included here, in order to keep\n> this series small.\n\nIt would be nicer to have a concise list of these fixes (in the form\nof \"git shortlog\") as a teaser here ;-).  That would hopefully\nentice others into reviewing this part that forms the foundation.\n\nThanks.\n"},{"id":"486751","messageId":"owlyil3yhv70.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqqy1cx9dm4.fsf@gitster.g","subject":"Re: [PATCH 00/10] Enrich Trailer API","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-01-13T01:35:15Z","receivedAt":"2024-01-13T01:35:17Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> This patch series is the first 10 patches of a much larger series I've been\n>> working. The main goal of this series is to enrich the API in trailer.h. The\n>> larger series brings a number of additional code simplifications and\n>> cleanups (exposing and fixing some bugs along the way), and builds on top of\n>> this series. The goal of the larger series is to make the trailer interface\n>> ready for unit testing. By \"trailer API\" I mean those functions exposed in\n>> trailer.h.\n>\n> Are there places in the current code that deals with trailers but\n> does not use the trailer API (e.g., manually parse and/or insert the\n> trailer in an in-core buffer)?\n\nWhile working on this series I unfortunately did not search for such use\ncases (I limited the scope of my work to mainly the interpret-trailers\nbuiltin). But, a quick search just now on master branch turned up\nappend_signoff() in sequencer.c which constructs a Signed-off-by trailer\nmanually with a raw strbuf [1].\n\nThis is somewhat understandable though, because AFAICS the current\ntrailer API does not support creating and formatting single trailer\nobjects.\n\n> Is it part of the larger goal to\n> update these places so that we will always use the trailer API to\n> touch trailers\n\nThat sounds like The Right Thing to do.\n\n> , and if so, have these places been identified?\n\nNot yet, but, it should be easy to check any remaining cases other than\nthe one I identified up above.\n\n> Obviously the reason why I ask is that testing cannot be the goal by\n> itself.\n\nI now seem to recall an offline discussion where I said I wanted to\nenrich the API to make other parts of Git also use this new API. Somehow\nI've left that motivation out of the cover letter (will include in the\nnext reroll).\n\n> The \"alternative\" ...\n>\n>> As an alternative to this patch series, we could keep trailer.h intact and\n>> decide to unit-test the existing \"trailer_info_get()\" function which does\n>> most of the trailer parsing work.\n>\n> ... may allow you to \"test\", but it would make it more difficult in\n> the future to revamp the trailer API, if it is needed, in order to\n> cover code paths that ought to be using but currently bypassing the\n> trailer API.\n\nAgreed. I should include this rationale as well in the next cover\nletter, thanks.\n\n>> This series breaks up \"process_trailers()\" into smaller pieces, exposing\n>> many of the parts relevant to trailer-related processing in trailer.h. This\n>> forces us to start writing unit tests for these now public functions, but\n>> that is a good thing because those same unit tests should be easy to write\n>> (due to their small(er) sizes), but also, because those unit tests will now\n>> ensure some degree of stability across new versions of trailer.h (we will\n>> start noticing when the behavior of any of these API functions change).\n>\n> And helper functions, each of which does one small thing well, may\n> be more applicable to other code paths that are currently bypassing\n> the API.\n\nYep.\n\n>> Thanks to the aggressive refactoring in this series, I've been able to\n>> identify and fix a couple bugs in our existing implementation. Those fixes\n>> build on top of this series but were not included here, in order to keep\n>> this series small.\n>\n> It would be nicer to have a concise list of these fixes (in the form\n> of \"git shortlog\") as a teaser here ;-).  That would hopefully\n> entice others into reviewing this part that forms the foundation.\n\nAh, good idea. Here's a shortlog (with a short summary of each one) of\nbug fixes that are forthcoming:\n\n    trailer: trailer replacement should not change its position\n      (Summary: If we found a trailer we'd like to replace, preserve its\n      position relative to the other trailers found in the trailer\n      block, instead of always moving it to the beginning or end of the\n      entire trailer block.)\n\n    interpret-trailers: preserve trailers coming from the input\n      (Summary: Sometimes, the parsed trailers from the input will be\n      formatted differently depending on whether we provide\n      --only-trailers or not.  Make the trailers that were not modified\n      and are coming directly from the input get formatted the same way,\n      regardless of this flag.)\n\n    interpret-trailers: fail if given unrecognized arguments\n      (Summary: E.g., for \"--where\", only accept recognized WHERE_* enum\n      values. If we get something unrecognized, fail with an error\n      instead of silently doing nothing. Ditto for \"--if-exists\" and\n      \"--if-missing\".)\n\nThe last one is a different class of bug than the first two, and perhaps\nless interesting.\n\nThanks.\n\n[1] https://github.com/git/git/blob/d4dbce1db5cd227a57074bcfc7ec9f0655961bba/sequencer.c#L5299-L5301\n"},{"id":"486780","messageId":"owlycyu3ista.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"owlyil3yhv70.fsf@fine.c.googlers.com","subject":"Re: [PATCH 00/10] Enrich Trailer API","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-01-14T20:05:53Z","receivedAt":"2024-01-14T20:05:55Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Linus Arver <linusa@google.com> writes:\n\n>     interpret-trailers: fail if given unrecognized arguments\n>       (Summary: E.g., for \"--where\", only accept recognized WHERE_* enum\n>       values. If we get something unrecognized, fail with an error\n>       instead of silently doing nothing. Ditto for \"--if-exists\" and\n>       \"--if-missing\".)\n>\n> The last one is a different class of bug than the first two, and perhaps\n> less interesting.\n\nActually, upon closer inspection I realize that we already fail if given\nunrecognized arguments to --where, --if-exists, and --if-missing. But we\ndon't explain why to the user because no error message is printed. This\ncommit has been retitled to \"interpret-trailers: print error if given\nunrecognized arguments\".\n\nThanks.\n"},{"id":"487012","messageId":"xmqqle8mp9bx.fsf@gitster.g","threadId":"60715","inReplyTo":"2dc3279b37f3aa81ba20e3dd08b029ca655ac3d8.1704869487.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 01/10] trailer: move process_trailers() to interpret-trailers.c","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-01-18T22:26:10Z","receivedAt":"2024-01-18T22:26:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> diff --git a/trailer.h b/trailer.h\n> index 1644cd05f60..b3e4a5e127d 100644\n> --- a/trailer.h\n> +++ b/trailer.h\n> @@ -81,15 +81,29 @@ struct process_trailer_options {\n>  \n>  #define PROCESS_TRAILER_OPTIONS_INIT {0}\n>  \n> -void process_trailers(const char *file,\n> -\t\t      const struct process_trailer_options *opts,\n> -\t\t      struct list_head *new_trailer_head);\n> +void parse_trailers_from_config(struct list_head *config_head);\n> +\n> +void parse_trailers_from_command_line_args(struct list_head *arg_head,\n> +\t\t\t\t\t   struct list_head *new_trailer_head);\n> +\n> +void process_trailers_lists(struct list_head *head,\n> +\t\t\t    struct list_head *arg_head);\n> +\n> +void parse_trailers(struct trailer_info *info,\n> +\t\t    const char *str,\n> +\t\t    struct list_head *head,\n> +\t\t    const struct process_trailer_options *opts);\n\nOK.\n\n> +void ensure_configured(void);\n> +void print_all(FILE *outfile, struct list_head *head,\n> +\t       const struct process_trailer_options *opts);\n> +void free_all(struct list_head *head);\n\nAll of these names are way overly generic to live in the global\nnamespace, no?  Granted, they may only be visible to folks who\ninclude <trailer.h>, but sooner or later other subsystems would find\nneed to provide library-ish functions that allows their callers to\ndo these things to their subsystem: make sure the subsystem has been\ninitialized, print info on all \"things\" on a list that the subsystem\nworks on, and free all \"things\" on such a list.\n\nIt is sad to have a function that takes \"struct list_head *\" as its\nparameter without having any comment on what the elements on that\nlist are about.  free_trailer_items() might be a more appropriate\nname (or free_all_trailer_items(), if we foresee a need to just free\nthe first item on the list and give such a helper\nfree_trailer_item(), in which case, the distinction between \"item\"\nvs \"items\" might become too subtle.\n\nI am assuming (and did not verify more than seeing the \"git show\"\noutput with \"--color-moved\") that other changes in this step are all\npure moves?  If so, other than need to be careful about naming when\nmaking things from static to extern pointed out above, this step\nlooks OK to me.\n\nThanks.\n"},{"id":"487013","messageId":"xmqqfryup98u.fsf@gitster.g","threadId":"60715","inReplyTo":"5f64718abfc2e61b4e259de700c137bc817fbb1c.1704869487.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 02/10] trailer: include \"trailer\" term in API functions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-01-18T22:28:01Z","receivedAt":"2024-01-18T22:28:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Linus Arver <linusa@google.com>\n>\n> These functions are exposed to clients and so they should include\n> \"trailer\" in their names for easier identification, just like all the\n> other functions already exposed by trailer.h.\n\nOh, if you were to do this and code movement in two separate\npatches, doing the rename before the move in the previous step would\nhave made much more sense.  If we stopped after 01/10, the tree\nwould have been a very sorry state.  If this came first, even if we\nstop after these renaming of internal functions that are not extern,\nnobody wil be hurt by these new and improved names.\n"},{"id":"487014","messageId":"xmqqr0ientdb.fsf@gitster.g","threadId":"60715","inReplyTo":"d3326021fb6a63707e4ce56f166447143f4e55a2.1704869487.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 03/10] trailer: unify trailer formatting machinery","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-01-18T22:56:16Z","receivedAt":"2024-01-18T22:56:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Linus Arver <linusa@google.com>\n>\n> Currently have two functions for formatting trailers exposed in\n> trailer.h:\n>\n>     void format_trailers(FILE *outfile, struct list_head *head,\n>                         const struct process_trailer_options *opts);\n>\n>     void format_trailers_from_commit(struct strbuf *out, const char *msg,\n>                                     const struct process_trailer_options *opts);\n>\n> and previously these functions, although similar enough (even taking the\n> same process_trailer_options struct pointer), did not build on each\n> other.\n>\n> Make format_trailers_from_commit() rely on format_trailers(). Teach\n> format_trailers() to process trailers with the additional\n> process_trailer_options fields like opts->key_only which is only used by\n> format_trailers_from_commit() and not builtin/interpret-trailers.c.\n\nYay.  It feels a bit disappointing to see the diffstat and learn\nthat we are not deleting substantial number of lines.\n\n> ---\n>  builtin/interpret-trailers.c |   5 +-\n>  pretty.c                     |   2 +-\n>  ref-filter.c                 |   2 +-\n>  trailer.c                    | 105 +++++++++++++++++++++++++++++------\n>  trailer.h                    |  21 +++----\n>  5 files changed, 102 insertions(+), 33 deletions(-)\n\n> diff --git a/pretty.c b/pretty.c\n> index cf964b060cd..f0721a5214f 100644\n> --- a/pretty.c\n> +++ b/pretty.c\n> @@ -1759,7 +1759,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n>  \t\t\t\tgoto trailer_out;\n>  \t\t}\n>  \t\tif (*arg == ')') {\n> -\t\t\tformat_trailers_from_commit(sb, msg + c->subject_off, &opts);\n> +\t\t\tformat_trailers_from_commit(msg + c->subject_off, &opts, sb);\n\nI am curious (read: no objection---merely wondering if there is a\nguiding principle behind the choice of the new order) why this new\nparameter ordering.  I suspect it was originally written with a\nstrbuf-centric worldview and having sb at the beginning may have\nmade sense, but if we are moving them around, wouldn't it make more\nsense to have &opts as the first parameter, as this is primarily a\n\"trailers\" function?  Unsure until I read through to the end, but\nthat is my gut reaction.\n\n>  static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)\n>  {\n>  \tstruct trailer_item *new_item = xcalloc(1, sizeof(*new_item));\n> @@ -984,6 +971,78 @@ static void unfold_value(struct strbuf *val)\n>  \tstrbuf_release(&out);\n>  }\n>  \n> +void format_trailers(struct list_head *head,\n> +\t\t     const struct process_trailer_options *opts,\n> +\t\t     struct strbuf *out)\n> +{\n> +\tstruct list_head *pos;\n> +\tstruct trailer_item *item;\n> +\tint need_separator = 0;\n> +\n> +\tlist_for_each(pos, head) {\n> +\t\titem = list_entry(pos, struct trailer_item, list);\n> +\t\tif (item->token) {\n> +\t\t\tchar c;\n> + ...\n> +\t\t\tif (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n> +\t\t\t\tif (opts->unfold)\n> +\t\t\t\t\tunfold_value(&val);\n> +\n> +\t\t\t\tif (opts->separator && need_separator)\n> +\t\t\t\t\tstrbuf_addbuf(out, opts->separator);\n> +\t\t\t\tif (!opts->value_only)\n> +\t\t\t\t\tstrbuf_addbuf(out, &tok);\n> +\t\t\t\tif (!opts->key_only && !opts->value_only) {\n> +\t\t\t\t\tif (opts->key_value_separator)\n> +\t\t\t\t\t\tstrbuf_addbuf(out, opts->key_value_separator);\n> +\t\t\t\t\telse {\n> +\t\t\t\t\t\tc = last_non_space_char(tok.buf);\n> +\t\t\t\t\t\tif (c) {\n> +\t\t\t\t\t\t\tif (!strchr(separators, c))\n> +\t\t\t\t\t\t\t\tstrbuf_addf(out, \"%c \", separators[0]);\n> +\t\t\t\t\t\t}\n> +\t\t\t\t\t}\n\nThat's an overly deep nesting.  I wonder if a small file-scope\nhelper function is in order?\n\n\tstatic add_separator(struct process_trailer_options *opts,\n        \t\t     const char *token\n\t\t\t     struct strbuf *out)\n\t{\n\t\tif (opts->key_value_separator)\n\t\t\tstrbuf_addbuf(out, opts->key_value_separator);\n\t\telse\n\t\t\tstrbuf_addstr(out, \": \");\n\t}\n\nOr perhaps inside the context of the loop to go over the list of\ntrailer items, one iteration of the list_for_each() loop can become\none call to a small helper function format_one_trailer() and that\nmay make the result easier to follow?\n\nIn any case, I didn't see anything glaringly wrong so far in this\nseries.  Let me keep reading.\n\nThanks.\n"},{"id":"487018","messageId":"owlyv87qgozb.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqqfryup98u.fsf@gitster.g","subject":"Re: [PATCH 02/10] trailer: include \"trailer\" term in API functions","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-01-19T00:12:56Z","receivedAt":"2024-01-19T00:12:58Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> From: Linus Arver <linusa@google.com>\n>>\n>> These functions are exposed to clients and so they should include\n>> \"trailer\" in their names for easier identification, just like all the\n>> other functions already exposed by trailer.h.\n>\n> Oh, if you were to do this and code movement in two separate\n> patches, doing the rename before the move in the previous step would\n> have made much more sense.  If we stopped after 01/10, the tree\n> would have been a very sorry state.  If this came first, even if we\n> stop after these renaming of internal functions that are not extern,\n> nobody wil be hurt by these new and improved names.\n\nAh very true. I think I was thinking of the series as one atomic thing\n(all 10 patches or nothing), which is a bad habit I need to break. I'll\nreorder these two on the next reroll, because principles matter (and\nit's an easy mechanical change). Thanks.\n"},{"id":"487019","messageId":"xmqqbk9inpq3.fsf@gitster.g","threadId":"60715","inReplyTo":"owlyv87qgozb.fsf@fine.c.googlers.com","subject":"Re: [PATCH 02/10] trailer: include \"trailer\" term in API functions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-01-19T00:15:00Z","receivedAt":"2024-01-19T00:15:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Linus Arver <linusa@google.com> writes:\n\n> Ah very true. I think I was thinking of the series as one atomic thing\n> (all 10 patches or nothing), which is a bad habit I need to break. I'll\n> reorder these two on the next reroll, because principles matter (and\n> it's an easy mechanical change). Thanks.\n\nThanks.  I wouldn't have minded if these renames and moves were done\nin a single step, but because you already have them as separate two\nsteps, let's keep them separate.\n\n"},{"id":"487020","messageId":"owlysf2ugolv.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqqle8mp9bx.fsf@gitster.g","subject":"Re: [PATCH 01/10] trailer: move process_trailers() to interpret-trailers.c","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-01-19T00:21:00Z","receivedAt":"2024-01-19T00:21:02Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> I am assuming (and did not verify more than seeing the \"git show\"\n> output with \"--color-moved\") that other changes in this step are all\n> pure moves?\n\nYup.\n"},{"id":"487021","messageId":"xmqq7ck6noxy.fsf@gitster.g","threadId":"60715","inReplyTo":"8d86461475765ac04ad0ed207e46598eb90a45ba.1704869487.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 04/10] trailer: delete obsolete formatting functions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-01-19T00:31:53Z","receivedAt":"2024-01-19T00:32:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Linus Arver <linusa@google.com>\n>\n> Signed-off-by: Linus Arver <linusa@google.com>\n> ---\n>  trailer.c | 79 -------------------------------------------------------\n>  1 file changed, 79 deletions(-)\n\nOK, these unused functions are the reason why 03/10 weren't removing\nas many lines as I would have expected from the refactoring.\n\nLooking good.\n"},{"id":"487022","messageId":"xmqq34uunoag.fsf@gitster.g","threadId":"60715","inReplyTo":"fd4a9d54d9522973a4c22e43cb1d7964033d4837.1704869487.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 05/10] sequencer: use the trailer iterator","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-01-19T00:45:59Z","receivedAt":"2024-01-19T00:46:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Linus Arver <linusa@google.com>\n>\n> This patch allows for the removal of \"trailer_info_get()\" from the\n> trailer.h API, which will be in the next patch.\n>\n> Instead of calling \"trailer_info_get()\", which is a low-level function\n> in the trailers implementation (trailer.c), call\n> trailer_iterator_advance(), which was specifically designed for public\n> consumption in f0939a0eb1 (trailer: add interface for iterating over\n> commit trailers, 2020-09-27).\n>\n> Avoiding \"trailer_info_get()\" means we don't have to worry about options\n> like \"no_divider\" (relevant for parsing trailers). We also don't have to\n> check for things like \"info.trailer_start == info.trailer_end\" to see\n> whether there were any trailers (instead we can just check to see\n> whether the iterator advanced at all).\n\nNice.\n\n> Also, teach the iterator about non-trailer lines, by adding a new field\n> called \"raw\" to hold both trailer and non-trailer lines. This is\n> necessary because a \"trailer block\" is a list of trailer lines of at\n> least 25% trailers (see 146245063e (trailer: allow non-trailers in\n> trailer block, 2016-10-21)), such that it may hold non-trailer lines.\n\nOK.  This would change behaviour, wouldn't it, in the sense that we\nused to yield a non-trailer line from the old iterator but the new\none skips them?  Is that something we can demonstrate and protect in\ntests (presumably once we conclude these refactoring, we would be\nable to call into this machinery from unit-testing framework)?\n\nThanks.\n\n> Signed-off-by: Linus Arver <linusa@google.com>\n> ---\n>  builtin/shortlog.c |  7 +++++--\n>  sequencer.c        | 35 +++++++++++++++--------------------\n>  trailer.c          | 20 ++++++++++++--------\n>  trailer.h          | 13 +++++++++++++\n>  4 files changed, 45 insertions(+), 30 deletions(-)\n>\n> diff --git a/builtin/shortlog.c b/builtin/shortlog.c\n> index 1307ed2b88a..dc8fd5a5532 100644\n> --- a/builtin/shortlog.c\n> +++ b/builtin/shortlog.c\n> @@ -172,7 +172,7 @@ static void insert_records_from_trailers(struct shortlog *log,\n>  \t\t\t\t\t const char *oneline)\n>  {\n>  \tstruct trailer_iterator iter;\n> -\tconst char *commit_buffer, *body;\n> +\tconst char *commit_buffer, *body, *value;\n>  \tstruct strbuf ident = STRBUF_INIT;\n>  \n>  \tif (!log->trailers.nr)\n> @@ -190,7 +190,10 @@ static void insert_records_from_trailers(struct shortlog *log,\n>  \n>  \ttrailer_iterator_init(&iter, body);\n>  \twhile (trailer_iterator_advance(&iter)) {\n> -\t\tconst char *value = iter.val.buf;\n> +\t\tif (!iter.is_trailer)\n> +\t\t\tcontinue;\n> +\n> +\t\tvalue = iter.val.buf;\n>  \n>  \t\tif (!string_list_has_string(&log->trailers, iter.key.buf))\n>  \t\t\tcontinue;\n> diff --git a/sequencer.c b/sequencer.c\n> index 3cc88d8a800..d199869cda9 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -319,37 +319,32 @@ static const char *get_todo_path(const struct replay_opts *opts)\n>  static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,\n>  \tsize_t ignore_footer)\n>  {\n> -\tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n> -\tstruct trailer_info info;\n> -\tsize_t i;\n> -\tint found_sob = 0, found_sob_last = 0;\n> -\tchar saved_char;\n> -\n> -\topts.no_divider = 1;\n> +\tstruct trailer_iterator iter;\n> +\tsize_t i = 0, found_sob = 0;\n> +\tchar saved_char = sb->buf[sb->len - ignore_footer];\n>  \n>  \tif (ignore_footer) {\n> -\t\tsaved_char = sb->buf[sb->len - ignore_footer];\n>  \t\tsb->buf[sb->len - ignore_footer] = '\\0';\n>  \t}\n>  \n> -\ttrailer_info_get(&info, sb->buf, &opts);\n> +\ttrailer_iterator_init(&iter, sb->buf);\n> +\twhile (trailer_iterator_advance(&iter)) {\n> +\t\ti++;\n> +\t\tif (sob &&\n> +\t\t    iter.is_trailer &&\n> +\t\t    !strncmp(iter.raw.buf, sob->buf, sob->len)) {\n> +\t\t\tfound_sob = i;\n> +\t\t}\n> +\t}\n> +\ttrailer_iterator_release(&iter);\n>  \n>  \tif (ignore_footer)\n>  \t\tsb->buf[sb->len - ignore_footer] = saved_char;\n>  \n> -\tif (info.trailer_block_start == info.trailer_block_end)\n> +\tif (!i)\n>  \t\treturn 0;\n>  \n> -\tfor (i = 0; i < info.trailer_nr; i++)\n> -\t\tif (sob && !strncmp(info.trailers[i], sob->buf, sob->len)) {\n> -\t\t\tfound_sob = 1;\n> -\t\t\tif (i == info.trailer_nr - 1)\n> -\t\t\t\tfound_sob_last = 1;\n> -\t\t}\n> -\n> -\ttrailer_info_release(&info);\n> -\n> -\tif (found_sob_last)\n> +\tif (found_sob == i)\n>  \t\treturn 3;\n>  \tif (found_sob)\n>  \t\treturn 2;\n> diff --git a/trailer.c b/trailer.c\n> index 132f22b3dd7..593717fd56c 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> @@ -1151,6 +1151,7 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n>  \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n>  \tstrbuf_init(&iter->key, 0);\n>  \tstrbuf_init(&iter->val, 0);\n> +\tstrbuf_init(&iter->raw, 0);\n>  \topts.no_divider = 1;\n>  \ttrailer_info_get(&iter->internal.info, msg, &opts);\n>  \titer->internal.cur = 0;\n> @@ -1158,17 +1159,19 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n>  \n>  int trailer_iterator_advance(struct trailer_iterator *iter)\n>  {\n> -\twhile (iter->internal.cur < iter->internal.info.trailer_nr) {\n> -\t\tchar *trailer = iter->internal.info.trailers[iter->internal.cur++];\n> -\t\tint separator_pos = find_separator(trailer, separators);\n> -\n> -\t\tif (separator_pos < 1)\n> -\t\t\tcontinue; /* not a real trailer */\n> -\n> +\tchar *line;\n> +\tint separator_pos;\n> +\tif (iter->internal.cur < iter->internal.info.trailer_nr) {\n> +\t\tline = iter->internal.info.trailers[iter->internal.cur++];\n> +\t\tseparator_pos = find_separator(line, separators);\n> +\t\titer->is_trailer = (separator_pos > 0);\n> +\n> +\t\tstrbuf_reset(&iter->raw);\n> +\t\tstrbuf_addstr(&iter->raw, line);\n>  \t\tstrbuf_reset(&iter->key);\n>  \t\tstrbuf_reset(&iter->val);\n>  \t\tparse_trailer(&iter->key, &iter->val, NULL,\n> -\t\t\t      trailer, separator_pos);\n> +\t\t\t      line, separator_pos);\n>  \t\tunfold_value(&iter->val);\n>  \t\treturn 1;\n>  \t}\n> @@ -1180,4 +1183,5 @@ void trailer_iterator_release(struct trailer_iterator *iter)\n>  \ttrailer_info_release(&iter->internal.info);\n>  \tstrbuf_release(&iter->val);\n>  \tstrbuf_release(&iter->key);\n> +\tstrbuf_release(&iter->raw);\n>  }\n> diff --git a/trailer.h b/trailer.h\n> index 50f70556302..d50c9fd79b2 100644\n> --- a/trailer.h\n> +++ b/trailer.h\n> @@ -127,6 +127,19 @@ struct trailer_iterator {\n>  \tstruct strbuf key;\n>  \tstruct strbuf val;\n>  \n> +\t/*\n> +\t * Raw line (e.g., \"foo: bar baz\") before being parsed as a trailer\n> +\t * key/val pair. This field can contain non-trailer lines because it's\n> +\t * valid for a trailer block to contain such lines (i.e., we only\n> +\t * require 25% of the lines in a trailer block to be trailer lines).\n> +\t */\n> +\tstruct strbuf raw;\n> +\n> +\t/*\n> +\t * 1 if the raw line was parsed as a separate key/val pair.\n> +\t */\n> +\tint is_trailer;\n> +\n>  \t/* private */\n>  \tstruct {\n>  \t\tstruct trailer_info info;\n"},{"id":"487024","messageId":"xmqqy1cmm94h.fsf@gitster.g","threadId":"60715","inReplyTo":"0cbe96421c7bf573e8ddc97b2a0aecc894095399.1704869487.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 06/10] trailer: make trailer_info struct private","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-01-19T00:58:54Z","receivedAt":"2024-01-19T00:59:01Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Linus Arver <linusa@google.com>\n>\n> In 13211ae23f (trailer: separate public from internal portion of\n> trailer_iterator, 2023-09-09) we moved trailer_info behind an anonymous\n> struct to discourage use by trailer.h API users. However it still left\n> open the possibility of external use of trailer_info itself. Now that\n> there are no external users of trailer_info, we can make this struct\n> private.\n\nSurely, and looking good from a cursory glance.  I'll defer to\nChristian to comment on this step as an area expert, though.\n\nThanks.\n"},{"id":"487025","messageId":"xmqqttnam8wd.fsf@gitster.g","threadId":"60715","inReplyTo":"9183990583f9a591c79301a20fa327462bb50cf9.1704869487.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 07/10] trailer: spread usage of \"trailer_block\" language","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-01-19T01:03:46Z","receivedAt":"2024-01-19T01:03:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Linus Arver <linusa@google.com>\n>\n> Deprecate the \"trailer_info\" struct name and replace it with\n> \"trailer_block\". The main reason is to help readability, because\n> \"trailer_info\" on the surface sounds like it's about a single trailer\n> when in reality it is a collection of contiguous lines, at least 25% of\n> which are trailers.\n\nTogether with the introduction of trailer_block_{start,end}()\naccessor functions in the previous step, this sounds like the right\nthing to do.\n\n\"info\" is a word with so low information contents.  At least \"block\"\nwould hint that we are talking about a block of text that makes up\nthe trailer attached to a single commit.\n\n"},{"id":"487026","messageId":"owlyplxygm7q.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqqr0ientdb.fsf@gitster.g","subject":"Re: [PATCH 03/10] trailer: unify trailer formatting machinery","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-01-19T01:12:41Z","receivedAt":"2024-01-19T01:12:43Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> From: Linus Arver <linusa@google.com>\n>>\n>> Currently have two functions for formatting trailers exposed in\n>> trailer.h:\n>>\n>>     void format_trailers(FILE *outfile, struct list_head *head,\n>>                         const struct process_trailer_options *opts);\n>>\n>>     void format_trailers_from_commit(struct strbuf *out, const char *msg,\n>>                                     const struct process_trailer_options *opts);\n>>\n>> and previously these functions, although similar enough (even taking the\n>> same process_trailer_options struct pointer), did not build on each\n>> other.\n>>\n>> Make format_trailers_from_commit() rely on format_trailers(). Teach\n>> format_trailers() to process trailers with the additional\n>> process_trailer_options fields like opts->key_only which is only used by\n>> format_trailers_from_commit() and not builtin/interpret-trailers.c.\n>\n> Yay.  It feels a bit disappointing to see the diffstat and learn\n> that we are not deleting substantial number of lines.\n\nIndeed ;)\n\n>> ---\n>>  builtin/interpret-trailers.c |   5 +-\n>>  pretty.c                     |   2 +-\n>>  ref-filter.c                 |   2 +-\n>>  trailer.c                    | 105 +++++++++++++++++++++++++++++------\n>>  trailer.h                    |  21 +++----\n>>  5 files changed, 102 insertions(+), 33 deletions(-)\n>\n>> diff --git a/pretty.c b/pretty.c\n>> index cf964b060cd..f0721a5214f 100644\n>> --- a/pretty.c\n>> +++ b/pretty.c\n>> @@ -1759,7 +1759,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n>>  \t\t\t\tgoto trailer_out;\n>>  \t\t}\n>>  \t\tif (*arg == ')') {\n>> -\t\t\tformat_trailers_from_commit(sb, msg + c->subject_off, &opts);\n>> +\t\t\tformat_trailers_from_commit(msg + c->subject_off, &opts, sb);\n>\n> I am curious (read: no objection---merely wondering if there is a\n> guiding principle behind the choice of the new order) why this new\n> parameter ordering.\n\nGlen Choo told me a while ago [1] that we usually put \"out parameters\"\nat the end, and somehow that stuck with me.\n\n> I suspect it was originally written with a\n> strbuf-centric worldview and having sb at the beginning may have\n> made sense,\n\nHaving gotten more familiar with the strbuf.h functions since this patch\nwas originally written, I agree.\n\n> but if we are moving them around, wouldn't it make more\n> sense to have &opts as the first parameter, as this is primarily a\n> \"trailers\" function?  Unsure until I read through to the end, but\n> that is my gut reaction.\n\nI simply (mechanically) moved \"sb\" from the beginning of the parameters\nto the end, and didn't think much beyond that adjustment.\n\nYour ordering seems fine to me. Noted for the next reroll, but I will\nwait until you're done reviewing the other patches before going with the\nnew ordering (in case you change your mind).\n\n>>  static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)\n>>  {\n>>  \tstruct trailer_item *new_item = xcalloc(1, sizeof(*new_item));\n>> @@ -984,6 +971,78 @@ static void unfold_value(struct strbuf *val)\n>>  \tstrbuf_release(&out);\n>>  }\n>>  \n>> +void format_trailers(struct list_head *head,\n>> +\t\t     const struct process_trailer_options *opts,\n>> +\t\t     struct strbuf *out)\n>> +{\n>> +\tstruct list_head *pos;\n>> +\tstruct trailer_item *item;\n>> +\tint need_separator = 0;\n>> +\n>> +\tlist_for_each(pos, head) {\n>> +\t\titem = list_entry(pos, struct trailer_item, list);\n>> +\t\tif (item->token) {\n>> +\t\t\tchar c;\n>> + ...\n>> +\t\t\tif (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n>> +\t\t\t\tif (opts->unfold)\n>> +\t\t\t\t\tunfold_value(&val);\n>> +\n>> +\t\t\t\tif (opts->separator && need_separator)\n>> +\t\t\t\t\tstrbuf_addbuf(out, opts->separator);\n>> +\t\t\t\tif (!opts->value_only)\n>> +\t\t\t\t\tstrbuf_addbuf(out, &tok);\n>> +\t\t\t\tif (!opts->key_only && !opts->value_only) {\n>> +\t\t\t\t\tif (opts->key_value_separator)\n>> +\t\t\t\t\t\tstrbuf_addbuf(out, opts->key_value_separator);\n>> +\t\t\t\t\telse {\n>> +\t\t\t\t\t\tc = last_non_space_char(tok.buf);\n>> +\t\t\t\t\t\tif (c) {\n>> +\t\t\t\t\t\t\tif (!strchr(separators, c))\n>> +\t\t\t\t\t\t\t\tstrbuf_addf(out, \"%c \", separators[0]);\n>> +\t\t\t\t\t\t}\n>> +\t\t\t\t\t}\n>\n> That's an overly deep nesting.  I wonder if a small file-scope\n> helper function is in order?\n>\n> \tstatic add_separator(struct process_trailer_options *opts,\n>         \t\t     const char *token\n> \t\t\t     struct strbuf *out)\n> \t{\n> \t\tif (opts->key_value_separator)\n> \t\t\tstrbuf_addbuf(out, opts->key_value_separator);\n> \t\telse\n> \t\t\tstrbuf_addstr(out, \": \");\n> \t}\n>\n> Or perhaps inside the context of the loop to go over the list of\n> trailer items, one iteration of the list_for_each() loop can become\n> one call to a small helper function format_one_trailer() and that\n> may make the result easier to follow?\n\nThis is actually what I've already done (introducing helper functions to\nmake this more readable and reduce nesting), but in the larger series\n(not in this patch series). I think in this patch I tried to avoid\nintroducing new functions in order to keep the original \"shape\" of the\nexisting refactored functions, including all of the nesting that they\noriginally had. I wanted to keep the original shape because I thought\nthat would make review simpler.\n\n> In any case, I didn't see anything glaringly wrong so far in this\n> series.  Let me keep reading.\n>\n> Thanks.\n\nThank you for reviewing!\n\n[1] https://lore.kernel.org/git/kl6l5y5qa34v.fsf@chooglen-macbookpro.roam.corp.google.com/ \n"},{"id":"487027","messageId":"xmqqo7dim8dz.fsf@gitster.g","threadId":"60715","inReplyTo":"8a99d0fca21eca41d62dcd682c8b4ae545633bf7.1704869487.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 09/10] trailer: move arg handling to interpret-trailers.c","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-01-19T01:14:48Z","receivedAt":"2024-01-19T01:14:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> diff --git a/trailer.c b/trailer.c\n> index e2d541372a3..0a86e0d5afa 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> @@ -66,6 +66,11 @@ static LIST_HEAD(conf_head);\n>  \n>  static char *separators = \":\";\n>  \n> +const char *default_separators(void)\n> +{\n> +\treturn separators;\n> +}\n\nThis allows API users to peek into the current set of separator\nbytes (either the default \":\" or specified by the configuration\nvaraible \"trailer.separators\"), which is an improvement over\ndirectly exposing the \"separators\" variable, but in a longer term,\ndo we need to have some \"trailer context\" object that holds this\nand possibly other global variables like this?\n\nI do not demand such further abstraction in this series, but I'd\nprefer to see if we all have shared vision into the future.\n\nThanks.\n"},{"id":"487148","messageId":"owlyle8jhiv3.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqq34uunoag.fsf@gitster.g","subject":"Re: [PATCH 05/10] sequencer: use the trailer iterator","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-01-20T20:04:16Z","receivedAt":"2024-01-20T20:04:19Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n>> From: Linus Arver <linusa@google.com>\n>> Also, teach the iterator about non-trailer lines, by adding a new field\n>> called \"raw\" to hold both trailer and non-trailer lines. This is\n>> necessary because a \"trailer block\" is a list of trailer lines of at\n>> least 25% trailers (see 146245063e (trailer: allow non-trailers in\n>> trailer block, 2016-10-21)), such that it may hold non-trailer lines.\n>\n> OK.  This would change behaviour, wouldn't it, in the sense that we\n> used to yield a non-trailer line from the old iterator but the new\n> one skips them?\n\nI think it's the other way; the old iterator only iterated over trailer\nlines, skipping over non-trailer lines (see the \"not a real trailer\"\ndeleted bit for trailer_iterator_advance()). The new one iterates over\nall lines found in the trailer block, whether they are trailer or\nnon-trailer lines.\n\nThe function insert_records_from_trailers() from shortlog.c uses the new\niterator, but has to be careful because the new iterator goes over\nnon-trailer lines too. That's why it now does\n\n    if (!iter.is_trailer)\n            continue;\n\nto do the skipping itself.\n\n> Is that something we can demonstrate and protect in\n> tests (presumably once we conclude these refactoring, we would be\n> able to call into this machinery from unit-testing framework)?\n\nYup, that is exactly what I want to do once the dust around the trailer\nAPI settles down after this series (and also the larger series). :)\n"},{"id":"487149","messageId":"owlyil3nhim9.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqqttnam8wd.fsf@gitster.g","subject":"Re: [PATCH 07/10] trailer: spread usage of \"trailer_block\" language","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-01-20T20:09:34Z","receivedAt":"2024-01-20T20:09:37Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"info\" is a word with so low information contents.  At least \"block\"\n> would hint that we are talking about a block of text that makes up\n> the trailer attached to a single commit.\n\nThere is also \"conf_info\" and the whole \"key\"/\"key alias\" vs \"token\"\nlingo which (if we are going to rename things for consistency) needs to\nbe updated to match documentation. In the larger series I've cleaned\nthis up as well.\n"},{"id":"487150","messageId":"owlyfryrhid8.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqqo7dim8dz.fsf@gitster.g","subject":"Re: [PATCH 09/10] trailer: move arg handling to interpret-trailers.c","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-01-20T20:14:59Z","receivedAt":"2024-01-20T20:15:02Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> diff --git a/trailer.c b/trailer.c\n>> index e2d541372a3..0a86e0d5afa 100644\n>> --- a/trailer.c\n>> +++ b/trailer.c\n>> @@ -66,6 +66,11 @@ static LIST_HEAD(conf_head);\n>>  \n>>  static char *separators = \":\";\n>>  \n>> +const char *default_separators(void)\n>> +{\n>> +\treturn separators;\n>> +}\n>\n> This allows API users to peek into the current set of separator\n> bytes (either the default \":\" or specified by the configuration\n> varaible \"trailer.separators\"), which is an improvement over\n> directly exposing the \"separators\" variable, but in a longer term,\n> do we need to have some \"trailer context\" object that holds this\n> and possibly other global variables like this?\n\nYes, and I've implemented a \"trailer_subsystem_conf\" struct to hold all\nconfiguration-related bits (and hardcoded defaults) relating to trailers\nin one place, in the larger series.\n\n> I do not demand such further abstraction in this series, but I'd\n> prefer to see if we all have shared vision into the future.\n\nMakes sense. Thanks.\n"},{"id":"487236","messageId":"owlyjzo1vtqs.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"fd4a9d54d9522973a4c22e43cb1d7964033d4837.1704869487.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 05/10] sequencer: use the trailer iterator","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-01-22T23:22:19Z","receivedAt":"2024-01-22T23:22:22Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> diff --git a/trailer.h b/trailer.h\n> index 50f70556302..d50c9fd79b2 100644\n> --- a/trailer.h\n> +++ b/trailer.h\n> @@ -127,6 +127,19 @@ struct trailer_iterator {\n>  \tstruct strbuf key;\n>  \tstruct strbuf val;\n>  \n> +\t/*\n> +\t * Raw line (e.g., \"foo: bar baz\") before being parsed as a trailer\n> +\t * key/val pair. This field can contain non-trailer lines because it's\n> +\t * valid for a trailer block to contain such lines (i.e., we only\n> +\t * require 25% of the lines in a trailer block to be trailer lines).\n> +\t */\n> +\tstruct strbuf raw;\n\nOriginally I used a strbuf here for consistency with the other strbufs\nused in the iterator for the key and val members. But now I've realized\nthat there's no need to make \"raw\" a strbuf at all, because iterator\nusers will never need to manipulate the string that this points to. Will\nchange to just \"const char *\" in the reroll.\n"},{"id":"487379","messageId":"ZbK4BcESMfljw7Ym@google.com","threadId":"60715","inReplyTo":"0cbe96421c7bf573e8ddc97b2a0aecc894095399.1704869487.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 06/10] trailer: make trailer_info struct private","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2024-01-25T19:35:33Z","receivedAt":"2024-01-25T19:35:39Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2024.01.10 06:51, Linus Arver via GitGitGadget wrote:\n> From: Linus Arver <linusa@google.com>\n> \n> In 13211ae23f (trailer: separate public from internal portion of\n> trailer_iterator, 2023-09-09) we moved trailer_info behind an anonymous\n> struct to discourage use by trailer.h API users. However it still left\n> open the possibility of external use of trailer_info itself. Now that\n> there are no external users of trailer_info, we can make this struct\n> private.\n> \n> Make this struct private by putting its definition inside trailer.c.\n> This has two benefits:\n> \n> (1) it makes the surface area of the public facing interface (trailer.h)\n>     smaller, and\n> \n> (2) external API users are unable to peer inside this struct (because it\n>     is only ever exposed as an opaque pointer).\n> \n> This change exposes some deficiencies in the API, mainly with regard to\n> information about the location of the trailer block that was parsed.\n> Expose new API functions to access this information (needed by\n> builtin/interpret-trailers.c).\n> \n> The idea in this patch to hide implementation details behind an \"opaque\n> pointer\" is also known as the \"pimpl\" (pointer to implementation) idiom\n> in C++ and is a common pattern in that language (where, for example,\n> abstract classes only have pointers to concrete classes).\n> \n> However, the original inspiration to use this idiom does not come from\n> C++, but instead the book \"C Interfaces and Implementations: Techniques\n> for Creating Reusable Software\" [1]. This book recommends opaque\n> pointers as a good design principle for designing C libraries, using the\n> term \"interface\" as the functions defined in *.h (header) files and\n> \"implementation\" as the corresponding *.c file which define the\n> interfaces.\n> \n> The book says this about opaque pointers:\n> \n>     ... clients can manipulate such pointers freely, but they can’t\n>     dereference them; that is, they can’t look at the innards of the\n>     structure pointed to by them. Only the implementation has that\n>     privilege. Opaque pointers hide representation details and help\n>     catch errors.\n> \n> In our case, \"struct trailer_info\" is now hidden from clients, and the\n> ways in which this opaque pointer can be used is limited to the richness\n> of the trailer.h file. In other words, trailer.h exclusively controls\n> exactly how \"trailer_info\" pointers are to be used.\n> \n> [1] Hanson, David R. \"C Interfaces and Implementations: Techniques for\n>     Creating Reusable Software\". Addison Wesley, 1997. p. 22\n> \n> Signed-off-by: Linus Arver <linusa@google.com>\n> ---\n>  builtin/interpret-trailers.c |  13 +--\n>  trailer.c                    | 154 +++++++++++++++++++++++------------\n>  trailer.h                    |  37 ++-------\n>  3 files changed, 117 insertions(+), 87 deletions(-)\n\nLooks like a pretty straightforward change. I think the only point worth\ndiscussing is using the \"pimpl\" idiom. I think it's harder to see the\nvalue because the trailer_info struct is fairly simple, but I can\ndefinitely see this pattern being useful as we libify more complex parts\nof Git where the struct internals have more complicated logic involved.\nThe pimpl pattern also seems like it will force us to think harder about\nproviding a useful interface, so I am in favor of using it here as\npractice for future libification.\n"},{"id":"487383","messageId":"xmqq7cjxw3wb.fsf@gitster.g","threadId":"60715","inReplyTo":"ZbK4BcESMfljw7Ym@google.com","subject":"Re: [PATCH 06/10] trailer: make trailer_info struct private","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-01-25T20:32:04Z","receivedAt":"2024-01-25T20:32:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> The pimpl pattern also seems like it will force us to think harder about\n> providing a useful interface, so I am in favor of using it here as\n> practice for future libification.\n\nSounds good.\n\n"},{"id":"487390","messageId":"ZbLxG8zVZAnrCQea@google.com","threadId":"60715","inReplyTo":"406725df46a81f485d7a74c11488e625d3026dc5.1704869487.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 08/10] trailer: prepare to move parse_trailers_from_command_line_args() to builtin","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2024-01-25T23:39:07Z","receivedAt":"2024-01-25T23:39:13Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2024.01.10 06:51, Linus Arver via GitGitGadget wrote:\n> From: Linus Arver <linusa@google.com>\n> \n> Expose more functions in the trailer.h API, in preparation for moving\n> out\n> \n>     parse_trailers_from_command_line_args()\n> \n> to interpret-trailer.c, because the trailer API should not be concerned\n> with command line arguments (as it has nothing to do with trailers\n> themselves). The interpret-trailers builtin is the only user of the\n> above function.\n> \n> Signed-off-by: Linus Arver <linusa@google.com>\n> ---\n>  trailer.c | 66 +++++++++++++++++++++++++++----------------------------\n>  trailer.h | 10 +++++++++\n>  2 files changed, 42 insertions(+), 34 deletions(-)\n> \n> diff --git a/trailer.c b/trailer.c\n> index 360e76376b8..e2d541372a3 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> @@ -33,7 +33,7 @@ struct trailer_block {\n>  \tsize_t trailer_nr;\n>  };\n>  \n> -struct conf_info {\n> +struct trailer_conf {\n\nCan you also add a note about this conf_info -> trailer_conf rename? I\nagree that it's an improvement but I think it should be mentioned in the\ncommit message.\n"},{"id":"487391","messageId":"ZbL0nyOnUKqIwgsg@google.com","threadId":"60715","inReplyTo":"pull.1632.git.1704869487.gitgitgadget@gmail.com","subject":"Re: [PATCH 00/10] Enrich Trailer API","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2024-01-25T23:54:07Z","receivedAt":"2024-01-25T23:54:13Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2024.01.10 06:51, Linus Arver via GitGitGadget wrote:\n> This patch series is the first 10 patches of a much larger series I've been\n> working. The main goal of this series is to enrich the API in trailer.h. The\n> larger series brings a number of additional code simplifications and\n> cleanups (exposing and fixing some bugs along the way), and builds on top of\n> this series. The goal of the larger series is to make the trailer interface\n> ready for unit testing. By \"trailer API\" I mean those functions exposed in\n> trailer.h.\n\nI agree with Junio's points, and I added a small nitpick about patch 8's\ncommit message. But apart from that, this all looks good to me and I'm\ninterested in seeing the followup series as well. Thanks!\n\nReviewed-by: Josh Steadmon <steadmon@google.com>\n"},{"id":"487392","messageId":"owlysf2l2bnj.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"ZbLxG8zVZAnrCQea@google.com","subject":"Re: [PATCH 08/10] trailer: prepare to move parse_trailers_from_command_line_args() to builtin","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-01-26T00:14:56Z","receivedAt":"2024-01-26T00:14:58Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> On 2024.01.10 06:51, Linus Arver via GitGitGadget wrote:\n>> diff --git a/trailer.c b/trailer.c\n>> index 360e76376b8..e2d541372a3 100644\n>> --- a/trailer.c\n>> +++ b/trailer.c\n>> @@ -33,7 +33,7 @@ struct trailer_block {\n>>  \tsize_t trailer_nr;\n>>  };\n>>  \n>> -struct conf_info {\n>> +struct trailer_conf {\n>\n> Can you also add a note about this conf_info -> trailer_conf rename? I\n> agree that it's an improvement but I think it should be mentioned in the\n> commit message.\n\nWill do.\n"},{"id":"487435","messageId":"pull.1632.v2.git.1706308737.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.git.1704869487.gitgitgadget@gmail.com","subject":"[PATCH v2 00/10] Enrich Trailer API","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-26T22:38:47Z","receivedAt":"2024-01-26T22:39:01Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"This patch series is the first 10 patches of a larger cleanup/bugfix series\n(henceforth \"larger series\") I've been working on. The main goal of this\nseries is to begin the process of \"libifying\" the trailer API. By \"API\" I\nmean the interface exposed in trailer.h. The larger series brings a number\nof additional cleanups (exposing and fixing some bugs along the way), and\nbuilds on top of this series.\n\nWhen the larger series is merged, we will be in a good state to additionally\npursue the following goals:\n\n 1. \"API reuse inside Git\": make the API expressive enough to eliminate any\n    need by other parts of Git to use the interpret-trailers builtin as a\n    subprocess (instead they could just use the API directly);\n 2. \"API stability\": add unit tests to codify the expected behavior of API\n    functions; and\n 3. \"API documentation\": create developer-focused documentation to explain\n    how to use the API effectively, noting any API limitations or\n    anti-patterns.\n\nThe reason why the larger series itself doesn't tackle these goals directly\nis because I believe that API code should be thought from the ground up with\na libification-focused perspective. Some structs and functions exposed in\nthe API today should probably not be libified (read: kept in trailer.h) as\nis. For example, the \"trailer_iterator\" struct has a \"private\" member and it\nfeels wrong to allow API users to peek inside here (and take at face value\nour future API users' pinky promise that they won't depend on those private\ninternals not meant for public consumption).\n\nOne pattern we could use here to cleanly separate \"what is the API\"\n(publicly exposed) and \"what is the implementation\" (private) is the\npointer-to-implementation (\"pimpl\") idiom. There may be other appropriate\npatterns, but I've chosen this one because it's a simple, low-level concept\n(put structs in foo.c instead of foo.h), which has far-reaching high-level\nconsequences (API users must rely exclusively on the API to make use of such\nprivate structs, via opaque pointers). The pimpl idiom for C comes from the\nbook \"C Interfaces and Implementations\" (see patch \"trailer: make\ntrailer_info struct private\").\n\nThe idea of turning a public struct into a private one is a fundamental\nquestion of libification because it forces us to reconsider all of the data\nstructures we have and how they're actually used by already existing users.\nFor the trailer API, those existing users are the \"interpret-trailers\"\nbuiltin command, and anything else that includes the \"trailer.h\" header file\n(e.g., sequencer.c). One advantage of this idiom is that even the compiler\nunderstands it --- the compiler will loudly complain if you try to access\nthe innards of a private struct through an opaque pointer.\n\nAnother advantage of this idiom is that it helps to reduce the probability\nof breaking changes in the API. Because a private struct's members are out\nof view from our users (they only know about opaque pointers to the private\nstruct, not its members), we are free to modify the members of the struct at\nany time, as much as we like, as long as we don't break the semantics of the\nexposed API functions (which is why unit-testing these API functions will be\ncrucial long-term).\n\nIf this pimpl idiom turns out to be a mistake, undoing it is easy --- just\nmove the relevant struct definition from foo.c to the header file. So it's a\ngreat way to try things out without digging ourselves into a pit of despair\nthat will be difficult to get out of.\n\nWith the libification-focused goals out of the way, let's turn to this patch\nseries in more detail.\n\nCurrently, we have \"process_trailers()\" in trailer.h which does many\ndifferent things (parse command-line arguments, create temporary files, etc)\nthat are independent of the concept of \"trailers\". Keeping this function as\nan API function would make unit-testing it difficult. While there is no\ntechnical reason why we couldn't write unit tests for the smaller functions\nthat are called within process_trailers(), doing so would involve testing\nprivate (\"static\" in trailer.c) functions instead of API functions, which\ndefeats the goal of \"API stability\" mentioned earlier above.\n\nAs an alternative to how things are done in this patch series, we could keep\ntrailer.h intact and decide to unit-test the existing \"trailer_info_get()\"\nfunction which does most of the trailer parsing work (and is used by\nsequencer.c). However this function wouldn't be easy to test either, because\nthe resulting \"trailer_info\" struct merely contains the unparsed \"trailers\"\nlines. So the unit test (if it wants to inspect the result of parsing these\nlines) would have to invoke additional parsing functions itself. And at that\npoint it would no longer be a unit test in the traditional sense, because it\nwould be invoking multiple functions at once.\n\nIn summary this series breaks up \"process_trailers()\" into smaller pieces,\nexposing many of the parts relevant to trailer-related processing in\ntrailer.h. This will force us to eventually introduce unit tests for these\nAPI functions, but that is a good thing for API stability.\n\nIn the future after libification is \"complete\", users external to Git will\nbe able to use the same trailer processing API used by the\ninterpret-trailers builtin. For example, a web server may want to parse\ntrailers the same way that Git would parse them, without having to call\ninterpret-trailers as a subprocess. This use case was the original\nmotivation behind my work in this area.\n\nThanks to the aggressive refactoring in this series, I've been able to\nidentify and fix several bugs in our existing implementation. Those fixes\nbuild on top of this series but were not included here, in order to keep\nthis series small. Below is a \"shortlog\" of those fixes I have locally:\n\n * \"trailer: trailer replacement should not change its position\" (If we\n   found a trailer we'd like to replace, preserve its position relative to\n   the other trailers found in the trailer block, instead of always moving\n   it to the beginning or end of the entire trailer block.)\n * \"interpret-trailers: preserve trailers coming from the input\" (Sometimes,\n   the parsed trailers from the input will be formatted differently\n   depending on whether we provide --only-trailers or not. Make the trailers\n   that were not modified and which are coming directly from the input get\n   formatted the same way, regardless of this flag.)\n * \"interpret-trailers: do not modify the input if NOP\" (Refrain from\n   subtracting or adding a newline around the patch divider \"---\" if we are\n   not adding new trailers.)\n * \"trailer formatter: split up format_trailer() monolith\" (Fix a bug in\n   git-log where we still printed a blank newline even if we didn't want to\n   format anything.)\n * \"interpret-trailers: fail if given unrecognized arguments\" (E.g., for\n   \"--where\", only accept recognized WHERE_* enum values. If we get\n   something unrecognized, fail with an error instead of silently doing\n   nothing. Ditto for \"--if-exists\" and \"--if-missing\".)\n\n\nNotable changes in v2\n=====================\n\n * Reorder function parameters to have trailer options at the beginning (and\n   out parameters toward the end)\n * \"sequencer: use the trailer iterator\": prefer C string instead of strbuf\n   for new \"raw\" field\n * Patch 1 (was Patch 2) also renames ensure_configured() to\n   trailer_config_init() (forgot to rename this one previously)\n\nLinus Arver (10):\n  trailer: prepare to expose functions as part of API\n  trailer: move interpret_trailers() to interpret-trailers.c\n  trailer: unify trailer formatting machinery\n  trailer: delete obsolete formatting functions\n  sequencer: use the trailer iterator\n  trailer: make trailer_info struct private\n  trailer: spread usage of \"trailer_block\" language\n  trailer: prepare to move parse_trailers_from_command_line_args() to\n    builtin\n  trailer: move arg handling to interpret-trailers.c\n  trailer: delete obsolete argument handling code from API\n\n builtin/interpret-trailers.c | 169 ++++++++--\n builtin/shortlog.c           |   7 +-\n pretty.c                     |   2 +-\n ref-filter.c                 |   2 +-\n sequencer.c                  |  35 +--\n trailer.c                    | 576 ++++++++++++++++-------------------\n trailer.h                    | 105 ++++---\n 7 files changed, 478 insertions(+), 418 deletions(-)\n\n\nbase-commit: a54a84b333adbecf7bc4483c0e36ed5878cac17b\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1632%2Flistx%2Ftrailer-api-refactor-part-1-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1632/listx/trailer-api-refactor-part-1-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/1632\n\nRange-diff vs v1:\n\n  2:  5f64718abfc !  1:  e2d3ed9b5b6 trailer: include \"trailer\" term in API functions\n     @@ Metadata\n      Author: Linus Arver <linusa@google.com>\n      \n       ## Commit message ##\n     -    trailer: include \"trailer\" term in API functions\n     +    trailer: prepare to expose functions as part of API\n      \n     -    These functions are exposed to clients and so they should include\n     -    \"trailer\" in their names for easier identification, just like all the\n     -    other functions already exposed by trailer.h.\n     +    In the next patch, we will move \"process_trailers\" from trailer.c to\n     +    builtin/interpret-trailers.c. That move will necessitate the growth of\n     +    the trailer.h API, forcing us to expose some additional functions in\n     +    trailer.h.\n      \n     +    Rename relevant functions so that they include the term \"trailer\" in\n     +    their name, so that clients of the API will be able to easily identify\n     +    them by their \"trailer\" moniker, just like all the other functions\n     +    already exposed by trailer.h.\n     +\n     +    The the opportunity to start putting trailer processions options (opts)\n     +    as the first parameter. This will be the pattern going forward in this\n     +    series.\n     +\n     +    Helped-by: Junio C Hamano <gitster@pobox.com>\n          Signed-off-by: Linus Arver <linusa@google.com>\n      \n       ## builtin/interpret-trailers.c ##\n     -@@ builtin/interpret-trailers.c: static void interpret_trailers(const char *file,\n     - \tstruct trailer_info info;\n     - \tFILE *outfile = stdout;\n     - \n     --\tensure_configured();\n     -+\ttrailer_config_init();\n     - \n     - \tread_input_file(&sb, file);\n     - \n     -@@ builtin/interpret-trailers.c: static void interpret_trailers(const char *file,\n     - \t\tprocess_trailers_lists(&head, &arg_head);\n     +@@ builtin/interpret-trailers.c: int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n     + \tif (argc) {\n     + \t\tint i;\n     + \t\tfor (i = 0; i < argc; i++)\n     +-\t\t\tprocess_trailers(argv[i], &opts, &trailers);\n     ++\t\t\tinterpret_trailers(&opts, &trailers, argv[i]);\n     + \t} else {\n     + \t\tif (opts.in_place)\n     + \t\t\tdie(_(\"no input file given for in-place editing\"));\n     +-\t\tprocess_trailers(NULL, &opts, &trailers);\n     ++\t\tinterpret_trailers(&opts, &trailers, NULL);\n       \t}\n       \n     --\tprint_all(outfile, &head, opts);\n     -+\t/* Print trailer block. */\n     -+\tformat_trailers(outfile, &head, opts);\n     - \n     --\tfree_all(&head);\n     -+\tfree_trailers(&head);\n     - \ttrailer_info_release(&info);\n     - \n     - \t/* Print the lines after the trailers as is */\n     + \tnew_trailers_clear(&trailers);\n      \n       ## trailer.c ##\n      @@ trailer.c: static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n       \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n       }\n       \n     --void print_all(FILE *outfile, struct list_head *head,\n     --\t       const struct process_trailer_options *opts)\n     -+void format_trailers(FILE *outfile, struct list_head *head,\n     -+\t\t     const struct process_trailer_options *opts)\n     +-static void print_all(FILE *outfile, struct list_head *head,\n     +-\t\t      const struct process_trailer_options *opts)\n     ++static void format_trailers(const struct process_trailer_options *opts,\n     ++\t\t\t    struct list_head *trailers, FILE *outfile)\n       {\n       \tstruct list_head *pos;\n       \tstruct trailer_item *item;\n     +-\tlist_for_each(pos, head) {\n     ++\tlist_for_each(pos, trailers) {\n     + \t\titem = list_entry(pos, struct trailer_item, list);\n     + \t\tif ((!opts->trim_empty || strlen(item->value) > 0) &&\n     + \t\t    (!opts->only_trailers || item->token))\n      @@ trailer.c: static int git_trailer_config(const char *conf_key, const char *value,\n       \treturn 0;\n       }\n       \n     --void ensure_configured(void)\n     -+void trailer_config_init(void)\n     +-static void ensure_configured(void)\n     ++static void trailer_config_init(void)\n       {\n       \tif (configured)\n       \t\treturn;\n     -@@ trailer.c: void parse_trailers(struct trailer_info *info,\n     +@@ trailer.c: static void parse_trailers(struct trailer_info *info,\n       \t}\n       }\n       \n     --void free_all(struct list_head *head)\n     -+void free_trailers(struct list_head *head)\n     +-static void free_all(struct list_head *head)\n     ++static void free_trailers(struct list_head *trailers)\n       {\n       \tstruct list_head *pos, *p;\n     - \tlist_for_each_safe(pos, p, head) {\n     +-\tlist_for_each_safe(pos, p, head) {\n     ++\tlist_for_each_safe(pos, p, trailers) {\n     + \t\tlist_del(pos);\n     + \t\tfree_trailer_item(list_entry(pos, struct trailer_item, list));\n     + \t}\n     +@@ trailer.c: static FILE *create_in_place_tempfile(const char *file)\n     + \treturn outfile;\n     + }\n     + \n     +-void process_trailers(const char *file,\n     +-\t\t      const struct process_trailer_options *opts,\n     +-\t\t      struct list_head *new_trailer_head)\n     ++void interpret_trailers(const struct process_trailer_options *opts,\n     ++\t\t\tstruct list_head *new_trailer_head,\n     ++\t\t\tconst char *file)\n     + {\n     + \tLIST_HEAD(head);\n     + \tstruct strbuf sb = STRBUF_INIT;\n     + \tstruct trailer_info info;\n     + \tFILE *outfile = stdout;\n     + \n     +-\tensure_configured();\n     ++\ttrailer_config_init();\n     + \n     + \tread_input_file(&sb, file);\n     + \n     +@@ trailer.c: void process_trailers(const char *file,\n     + \t\tprocess_trailers_lists(&head, &arg_head);\n     + \t}\n     + \n     +-\tprint_all(outfile, &head, opts);\n     ++\tformat_trailers(opts, &head, outfile);\n     + \n     +-\tfree_all(&head);\n     ++\tfree_trailers(&head);\n     + \ttrailer_info_release(&info);\n     + \n     + \t/* Print the lines after the trailers as is */\n      @@ trailer.c: void trailer_info_get(struct trailer_info *info, const char *str,\n       \tsize_t nr = 0, alloc = 0;\n       \tchar **last = NULL;\n     @@ trailer.c: void trailer_info_get(struct trailer_info *info, const char *str,\n       \ttrailer_block_start = find_trailer_block_start(str, end_of_log_message);\n      \n       ## trailer.h ##\n     -@@ trailer.h: void trailer_info_get(struct trailer_info *info, const char *str,\n     - \n     - void trailer_info_release(struct trailer_info *info);\n     - \n     --void ensure_configured(void);\n     --void print_all(FILE *outfile, struct list_head *head,\n     --\t       const struct process_trailer_options *opts);\n     --void free_all(struct list_head *head);\n     -+void trailer_config_init(void);\n     -+void free_trailers(struct list_head *trailers);\n     - \n     -+void format_trailers(FILE *outfile, struct list_head *head,\n     -+\t\t     const struct process_trailer_options *opts);\n     - /*\n     -  * Format the trailers from the commit msg \"msg\" into the strbuf \"out\".\n     -  * Note two caveats about \"opts\":\n     -@@ trailer.h: void free_all(struct list_head *head);\n     -  *   - this is primarily a helper for pretty.c, and not\n     -  *     all of the flags are supported.\n     -  *\n     -- *   - this differs from process_trailers slightly in that we always format\n     -+ *   - this differs from format_trailers slightly in that we always format\n     -  *     only the trailer block itself, even if the \"only_trailers\" option is not\n     -  *     set.\n     -  */\n     +@@ trailer.h: struct process_trailer_options {\n     + \n     + #define PROCESS_TRAILER_OPTIONS_INIT {0}\n     + \n     +-void process_trailers(const char *file,\n     +-\t\t      const struct process_trailer_options *opts,\n     +-\t\t      struct list_head *new_trailer_head);\n     ++void interpret_trailers(const struct process_trailer_options *opts,\n     ++\t\t\tstruct list_head *new_trailer_head,\n     ++\t\t\tconst char *file);\n     + \n     + void trailer_info_get(struct trailer_info *info, const char *str,\n     + \t\t      const struct process_trailer_options *opts);\n  1:  2dc3279b37f !  2:  eaca39fd7ea trailer: move process_trailers() to interpret-trailers.c\n     @@ Metadata\n      Author: Linus Arver <linusa@google.com>\n      \n       ## Commit message ##\n     -    trailer: move process_trailers() to interpret-trailers.c\n     +    trailer: move interpret_trailers() to interpret-trailers.c\n      \n          The interpret-trailers.c builtin is the only place we need to call\n     -    process_trailers(). As it stands, process_trailers() is inherently tied\n     -    to how the builtin behaves, so move its definition there.\n     +    interpret_trailers(), so move its definition there.\n      \n          Delete the corresponding declaration from trailer.h, which then forces\n          us to expose the working innards of that function. This enriches\n     -    trailer.h to include a more granular API, which can then be unit-tested\n     -    in the future (because process_trailers() by itself does too many things\n     -    to be able to be easily unit-tested).\n     +    trailer.h with a more granular API, which can then be unit-tested in the\n     +    future (because interpret_trailers() by itself does too many things to\n     +    be able to be easily unit-tested).\n      \n          Take this opportunity to demote some file-handling functions out of the\n          trailer API implementation, as these have nothing to do with trailers.\n      \n     -    While we're at it, rename process_trailers() to interpret_trailers() in\n     -    the builtin for consistency with the existing cmd_interpret_trailers(),\n     -    which wraps around this function.\n     -\n          Signed-off-by: Linus Arver <linusa@google.com>\n      \n       ## builtin/interpret-trailers.c ##\n     @@ builtin/interpret-trailers.c: static int parse_opt_parse(const struct option *op\n      +\t}\n      +}\n      +\n     -+static void interpret_trailers(const char *file,\n     -+\t\t\t       const struct process_trailer_options *opts,\n     -+\t\t\t       struct list_head *new_trailer_head)\n     ++static void interpret_trailers(const struct process_trailer_options *opts,\n     ++\t\t\t       struct list_head *new_trailer_head,\n     ++\t\t\t       const char *file)\n      +{\n      +\tLIST_HEAD(head);\n      +\tstruct strbuf sb = STRBUF_INIT;\n      +\tstruct trailer_info info;\n      +\tFILE *outfile = stdout;\n      +\n     -+\tensure_configured();\n     ++\ttrailer_config_init();\n      +\n      +\tread_input_file(&sb, file);\n      +\n      +\tif (opts->in_place)\n      +\t\toutfile = create_in_place_tempfile(file);\n      +\n     -+\tparse_trailers(&info, sb.buf, &head, opts);\n     ++\tparse_trailers(opts, &info, sb.buf, &head);\n      +\n      +\t/* Print the lines before the trailers */\n      +\tif (!opts->only_trailers)\n     @@ builtin/interpret-trailers.c: static int parse_opt_parse(const struct option *op\n      +\t\tprocess_trailers_lists(&head, &arg_head);\n      +\t}\n      +\n     -+\tprint_all(outfile, &head, opts);\n     ++\tformat_trailers(opts, &head, outfile);\n      +\n     -+\tfree_all(&head);\n     ++\tfree_trailers(&head);\n      +\ttrailer_info_release(&info);\n      +\n      +\t/* Print the lines after the trailers as is */\n     @@ builtin/interpret-trailers.c: static int parse_opt_parse(const struct option *op\n       int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n       {\n       \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n     -@@ builtin/interpret-trailers.c: int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n     - \tif (argc) {\n     - \t\tint i;\n     - \t\tfor (i = 0; i < argc; i++)\n     --\t\t\tprocess_trailers(argv[i], &opts, &trailers);\n     -+\t\t\tinterpret_trailers(argv[i], &opts, &trailers);\n     - \t} else {\n     - \t\tif (opts.in_place)\n     - \t\t\tdie(_(\"no input file given for in-place editing\"));\n     --\t\tprocess_trailers(NULL, &opts, &trailers);\n     -+\t\tinterpret_trailers(NULL, &opts, &trailers);\n     - \t}\n     - \n     - \tnew_trailers_clear(&trailers);\n      \n       ## trailer.c ##\n      @@\n     @@ trailer.c: static void print_tok_val(FILE *outfile, const char *tok, const char\n       \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n       }\n       \n     --static void print_all(FILE *outfile, struct list_head *head,\n     --\t\t      const struct process_trailer_options *opts)\n     -+void print_all(FILE *outfile, struct list_head *head,\n     -+\t       const struct process_trailer_options *opts)\n     +-static void format_trailers(const struct process_trailer_options *opts,\n     +-\t\t\t    struct list_head *trailers, FILE *outfile)\n     ++void format_trailers(const struct process_trailer_options *opts,\n     ++\t\t     struct list_head *trailers, FILE *outfile)\n       {\n       \tstruct list_head *pos;\n       \tstruct trailer_item *item;\n     @@ trailer.c: static int git_trailer_config(const char *conf_key, const char *value\n       \treturn 0;\n       }\n       \n     --static void ensure_configured(void)\n     -+void ensure_configured(void)\n     +-static void trailer_config_init(void)\n     ++void trailer_config_init(void)\n       {\n       \tif (configured)\n       \t\treturn;\n     @@ trailer.c: static void unfold_value(struct strbuf *val)\n      -\t\t\t     const char *str,\n      -\t\t\t     struct list_head *head,\n      -\t\t\t     const struct process_trailer_options *opts)\n     -+void parse_trailers(struct trailer_info *info,\n     ++void parse_trailers(const struct process_trailer_options *opts,\n     ++\t\t    struct trailer_info *info,\n      +\t\t    const char *str,\n     -+\t\t    struct list_head *head,\n     -+\t\t    const struct process_trailer_options *opts)\n     ++\t\t    struct list_head *head)\n       {\n       \tstruct strbuf tok = STRBUF_INIT;\n       \tstruct strbuf val = STRBUF_INIT;\n     @@ trailer.c: static void parse_trailers(struct trailer_info *info,\n       \t}\n       }\n       \n     --static void free_all(struct list_head *head)\n     -+void free_all(struct list_head *head)\n     +-static void free_trailers(struct list_head *trailers)\n     ++void free_trailers(struct list_head *trailers)\n       {\n       \tstruct list_head *pos, *p;\n     - \tlist_for_each_safe(pos, p, head) {\n     -@@ trailer.c: static void free_all(struct list_head *head)\n     + \tlist_for_each_safe(pos, p, trailers) {\n     +@@ trailer.c: static void free_trailers(struct list_head *trailers)\n       \t}\n       }\n       \n     @@ trailer.c: static void free_all(struct list_head *head)\n      -\treturn outfile;\n      -}\n      -\n     --void process_trailers(const char *file,\n     --\t\t      const struct process_trailer_options *opts,\n     --\t\t      struct list_head *new_trailer_head)\n     +-void interpret_trailers(const struct process_trailer_options *opts,\n     +-\t\t\tstruct list_head *new_trailer_head,\n     +-\t\t\tconst char *file)\n      -{\n      -\tLIST_HEAD(head);\n      -\tstruct strbuf sb = STRBUF_INIT;\n      -\tstruct trailer_info info;\n      -\tFILE *outfile = stdout;\n      -\n     --\tensure_configured();\n     +-\ttrailer_config_init();\n      -\n      -\tread_input_file(&sb, file);\n      -\n     @@ trailer.c: static void free_all(struct list_head *head)\n      -\t\tprocess_trailers_lists(&head, &arg_head);\n      -\t}\n      -\n     --\tprint_all(outfile, &head, opts);\n     +-\tformat_trailers(opts, &head, outfile);\n      -\n     --\tfree_all(&head);\n     +-\tfree_trailers(&head);\n      -\ttrailer_info_release(&info);\n      -\n      -\t/* Print the lines after the trailers as is */\n     @@ trailer.h: struct process_trailer_options {\n       \n       #define PROCESS_TRAILER_OPTIONS_INIT {0}\n       \n     --void process_trailers(const char *file,\n     --\t\t      const struct process_trailer_options *opts,\n     --\t\t      struct list_head *new_trailer_head);\n     +-void interpret_trailers(const struct process_trailer_options *opts,\n     +-\t\t\tstruct list_head *new_trailer_head,\n     +-\t\t\tconst char *file);\n      +void parse_trailers_from_config(struct list_head *config_head);\n      +\n      +void parse_trailers_from_command_line_args(struct list_head *arg_head,\n     @@ trailer.h: struct process_trailer_options {\n      +void process_trailers_lists(struct list_head *head,\n      +\t\t\t    struct list_head *arg_head);\n      +\n     -+void parse_trailers(struct trailer_info *info,\n     ++void parse_trailers(const struct process_trailer_options *opts,\n     ++\t\t    struct trailer_info *info,\n      +\t\t    const char *str,\n     -+\t\t    struct list_head *head,\n     -+\t\t    const struct process_trailer_options *opts);\n     ++\t\t    struct list_head *head);\n       \n       void trailer_info_get(struct trailer_info *info, const char *str,\n       \t\t      const struct process_trailer_options *opts);\n       \n       void trailer_info_release(struct trailer_info *info);\n       \n     -+void ensure_configured(void);\n     -+void print_all(FILE *outfile, struct list_head *head,\n     -+\t       const struct process_trailer_options *opts);\n     -+void free_all(struct list_head *head);\n     ++void trailer_config_init(void);\n     ++void format_trailers(const struct process_trailer_options *opts,\n     ++\t\t     struct list_head *trailers, FILE *outfile);\n     ++void free_trailers(struct list_head *trailers);\n      +\n       /*\n        * Format the trailers from the commit msg \"msg\" into the strbuf \"out\".\n  3:  d3326021fb6 !  3:  9b7747d550e trailer: unify trailer formatting machinery\n     @@ Commit message\n          format_trailers() to process trailers with the additional\n          process_trailer_options fields like opts->key_only which is only used by\n          format_trailers_from_commit() and not builtin/interpret-trailers.c.\n     +    While we're at it, reorder parameters to put the trailer processing\n     +    options first, and the out parameter (strbuf we write into) at the end.\n      \n     -    This will allow us to delete the format_trailer_info() and\n     +    This unification will allow us to delete the format_trailer_info() and\n          print_tok_val() functions in the next patch. They are not deleted here\n          in order to keep the diff small.\n      \n     +    Helped-by: Junio C Hamano <gitster@pobox.com>\n          Signed-off-by: Linus Arver <linusa@google.com>\n      \n       ## builtin/interpret-trailers.c ##\n     -@@ builtin/interpret-trailers.c: static void interpret_trailers(const char *file,\n     +@@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n       {\n       \tLIST_HEAD(head);\n       \tstruct strbuf sb = STRBUF_INIT;\n     @@ builtin/interpret-trailers.c: static void interpret_trailers(const char *file,\n       \tstruct trailer_info info;\n       \tFILE *outfile = stdout;\n       \n     -@@ builtin/interpret-trailers.c: static void interpret_trailers(const char *file,\n     +@@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n     + \t\tprocess_trailers_lists(&head, &arg_head);\n       \t}\n       \n     - \t/* Print trailer block. */\n     --\tformat_trailers(outfile, &head, opts);\n     -+\tformat_trailers(&head, opts, &trailer_block);\n     +-\tformat_trailers(opts, &head, outfile);\n     ++\t/* Print trailer block. */\n     ++\tformat_trailers(opts, &head, &trailer_block);\n      +\tfwrite(trailer_block.buf, 1, trailer_block.len, outfile);\n      +\tstrbuf_release(&trailer_block);\n       \n     @@ pretty.c: static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n       \t\t}\n       \t\tif (*arg == ')') {\n      -\t\t\tformat_trailers_from_commit(sb, msg + c->subject_off, &opts);\n     -+\t\t\tformat_trailers_from_commit(msg + c->subject_off, &opts, sb);\n     ++\t\t\tformat_trailers_from_commit(&opts, msg + c->subject_off, sb);\n       \t\t\tret = arg - placeholder + 1;\n       \t\t}\n       \ttrailer_out:\n     @@ ref-filter.c: static void grab_sub_body_contents(struct atom_value *val, int der\n       \n       \t\t\t/* Format the trailer info according to the trailer_opts given */\n      -\t\t\tformat_trailers_from_commit(&s, subpos, &atom->u.contents.trailer_opts);\n     -+\t\t\tformat_trailers_from_commit(subpos, &atom->u.contents.trailer_opts, &s);\n     ++\t\t\tformat_trailers_from_commit(&atom->u.contents.trailer_opts, subpos, &s);\n       \n       \t\t\tv->s = strbuf_detach(&s, NULL);\n       \t\t} else if (atom->u.contents.option == C_BARE)\n     @@ trailer.c: static void print_tok_val(FILE *outfile, const char *tok, const char\n       \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n       }\n       \n     --void format_trailers(FILE *outfile, struct list_head *head,\n     --\t\t     const struct process_trailer_options *opts)\n     +-void format_trailers(const struct process_trailer_options *opts,\n     +-\t\t     struct list_head *trailers, FILE *outfile)\n      -{\n      -\tstruct list_head *pos;\n      -\tstruct trailer_item *item;\n     --\tlist_for_each(pos, head) {\n     +-\tlist_for_each(pos, trailers) {\n      -\t\titem = list_entry(pos, struct trailer_item, list);\n      -\t\tif ((!opts->trim_empty || strlen(item->value) > 0) &&\n      -\t\t    (!opts->only_trailers || item->token))\n     @@ trailer.c: static void unfold_value(struct strbuf *val)\n       \tstrbuf_release(&out);\n       }\n       \n     -+void format_trailers(struct list_head *head,\n     -+\t\t     const struct process_trailer_options *opts,\n     ++void format_trailers(const struct process_trailer_options *opts,\n     ++\t\t     struct list_head *trailers,\n      +\t\t     struct strbuf *out)\n      +{\n      +\tstruct list_head *pos;\n      +\tstruct trailer_item *item;\n      +\tint need_separator = 0;\n      +\n     -+\tlist_for_each(pos, head) {\n     ++\tlist_for_each(pos, trailers) {\n      +\t\titem = list_entry(pos, struct trailer_item, list);\n      +\t\tif (item->token) {\n      +\t\t\tchar c;\n     @@ trailer.c: static void format_trailer_info(struct strbuf *out,\n       \n      -void format_trailers_from_commit(struct strbuf *out, const char *msg,\n      -\t\t\t\t const struct process_trailer_options *opts)\n     -+void format_trailers_from_commit(const char *msg,\n     -+\t\t\t\t const struct process_trailer_options *opts,\n     ++void format_trailers_from_commit(const struct process_trailer_options *opts,\n     ++\t\t\t\t const char *msg,\n      +\t\t\t\t struct strbuf *out)\n       {\n      +\tLIST_HEAD(head);\n     @@ trailer.c: static void format_trailer_info(struct strbuf *out,\n       \n      -\ttrailer_info_get(&info, msg, opts);\n      -\tformat_trailer_info(out, &info, msg, opts);\n     -+\tparse_trailers(&info, msg, &head, opts);\n     ++\tparse_trailers(opts, &info, msg, &head);\n      +\n      +\t/* If we want the whole block untouched, we can take the fast path. */\n      +\tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n     @@ trailer.c: static void format_trailer_info(struct strbuf *out,\n      +\t\tstrbuf_add(out, msg + info.trailer_block_start,\n      +\t\t\t   info.trailer_block_end - info.trailer_block_start);\n      +\t} else\n     -+\t\tformat_trailers(&head, opts, out);\n     ++\t\tformat_trailers(opts, &head, out);\n      +\n      +\tfree_trailers(&head);\n       \ttrailer_info_release(&info);\n     @@ trailer.c: static void format_trailer_info(struct strbuf *out,\n      \n       ## trailer.h ##\n      @@ trailer.h: void trailer_info_release(struct trailer_info *info);\n     + \n       void trailer_config_init(void);\n     + void format_trailers(const struct process_trailer_options *opts,\n     +-\t\t     struct list_head *trailers, FILE *outfile);\n     ++\t\t     struct list_head *trailers,\n     ++\t\t     struct strbuf *out);\n       void free_trailers(struct list_head *trailers);\n       \n     --void format_trailers(FILE *outfile, struct list_head *head,\n     --\t\t     const struct process_trailer_options *opts);\n     -+void format_trailers(struct list_head *head,\n     -+\t\t     const struct process_trailer_options *opts,\n     -+\t\t     struct strbuf *out);\n       /*\n      - * Format the trailers from the commit msg \"msg\" into the strbuf \"out\".\n      - * Note two caveats about \"opts\":\n     @@ trailer.h: void trailer_info_release(struct trailer_info *info);\n      - *   - this is primarily a helper for pretty.c, and not\n      - *     all of the flags are supported.\n      - *\n     -- *   - this differs from format_trailers slightly in that we always format\n     +- *   - this differs from process_trailers slightly in that we always format\n      - *     only the trailer block itself, even if the \"only_trailers\" option is not\n      - *     set.\n      + * Convenience function to format the trailers from the commit msg \"msg\" into\n     @@ trailer.h: void trailer_info_release(struct trailer_info *info);\n        */\n      -void format_trailers_from_commit(struct strbuf *out, const char *msg,\n      -\t\t\t\t const struct process_trailer_options *opts);\n     -+void format_trailers_from_commit(const char *msg,\n     -+\t\t\t\t const struct process_trailer_options *opts,\n     ++void format_trailers_from_commit(const struct process_trailer_options *opts,\n     ++\t\t\t\t const char *msg,\n      +\t\t\t\t struct strbuf *out);\n       \n       /*\n  4:  8d864614757 !  4:  f1171f5202f trailer: delete obsolete formatting functions\n     @@ trailer.c: void trailer_info_release(struct trailer_info *info)\n      -\n      -}\n      -\n     - void format_trailers_from_commit(const char *msg,\n     - \t\t\t\t const struct process_trailer_options *opts,\n     + void format_trailers_from_commit(const struct process_trailer_options *opts,\n     + \t\t\t\t const char *msg,\n       \t\t\t\t struct strbuf *out)\n  5:  fd4a9d54d95 !  5:  5ba842b5005 sequencer: use the trailer iterator\n     @@ sequencer.c: static const char *get_todo_path(const struct replay_opts *opts)\n      +\t\ti++;\n      +\t\tif (sob &&\n      +\t\t    iter.is_trailer &&\n     -+\t\t    !strncmp(iter.raw.buf, sob->buf, sob->len)) {\n     ++\t\t    !strncmp(iter.raw, sob->buf, sob->len)) {\n      +\t\t\tfound_sob = i;\n      +\t\t}\n      +\t}\n     @@ sequencer.c: static const char *get_todo_path(const struct replay_opts *opts)\n      \n       ## trailer.c ##\n      @@ trailer.c: void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n     - \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n     - \tstrbuf_init(&iter->key, 0);\n     - \tstrbuf_init(&iter->val, 0);\n     -+\tstrbuf_init(&iter->raw, 0);\n     - \topts.no_divider = 1;\n     - \ttrailer_info_get(&iter->internal.info, msg, &opts);\n     - \titer->internal.cur = 0;\n     -@@ trailer.c: void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n       \n       int trailer_iterator_advance(struct trailer_iterator *iter)\n       {\n     @@ trailer.c: void trailer_iterator_init(struct trailer_iterator *iter, const char\n      +\t\tseparator_pos = find_separator(line, separators);\n      +\t\titer->is_trailer = (separator_pos > 0);\n      +\n     -+\t\tstrbuf_reset(&iter->raw);\n     -+\t\tstrbuf_addstr(&iter->raw, line);\n     ++\t\titer->raw = line;\n       \t\tstrbuf_reset(&iter->key);\n       \t\tstrbuf_reset(&iter->val);\n       \t\tparse_trailer(&iter->key, &iter->val, NULL,\n     @@ trailer.c: void trailer_iterator_init(struct trailer_iterator *iter, const char\n       \t\tunfold_value(&iter->val);\n       \t\treturn 1;\n       \t}\n     -@@ trailer.c: void trailer_iterator_release(struct trailer_iterator *iter)\n     - \ttrailer_info_release(&iter->internal.info);\n     - \tstrbuf_release(&iter->val);\n     - \tstrbuf_release(&iter->key);\n     -+\tstrbuf_release(&iter->raw);\n     - }\n      \n       ## trailer.h ##\n      @@ trailer.h: struct trailer_iterator {\n     @@ trailer.h: struct trailer_iterator {\n       \n      +\t/*\n      +\t * Raw line (e.g., \"foo: bar baz\") before being parsed as a trailer\n     -+\t * key/val pair. This field can contain non-trailer lines because it's\n     -+\t * valid for a trailer block to contain such lines (i.e., we only\n     -+\t * require 25% of the lines in a trailer block to be trailer lines).\n     ++\t * key/val pair as part of a trailer block. A trailer block can be\n     ++\t * either 100% trailer lines, or mixed in with non-trailer lines (in\n     ++\t * which case at least 25% must be trailer lines).\n      +\t */\n     -+\tstruct strbuf raw;\n     ++\tconst char *raw;\n      +\n      +\t/*\n     -+\t * 1 if the raw line was parsed as a separate key/val pair.\n     ++\t * 1 if the raw line was parsed as a trailer line (key/val pair).\n      +\t */\n      +\tint is_trailer;\n      +\n  6:  0cbe96421c7 !  6:  f0ac2f6c4b9 trailer: make trailer_info struct private\n     @@ Commit message\n          Make this struct private by putting its definition inside trailer.c.\n          This has two benefits:\n      \n     -    (1) it makes the surface area of the public facing interface (trailer.h)\n     -        smaller, and\n     +      (1) it makes the surface area of the public facing\n     +          interface (trailer.h) smaller, and\n      \n     -    (2) external API users are unable to peer inside this struct (because it\n     -        is only ever exposed as an opaque pointer).\n     +      (2) external API users are unable to peer inside this struct (because\n     +          it is only ever exposed as an opaque pointer).\n      \n          This change exposes some deficiencies in the API, mainly with regard to\n          information about the location of the trailer block that was parsed.\n     @@ Commit message\n          Signed-off-by: Linus Arver <linusa@google.com>\n      \n       ## builtin/interpret-trailers.c ##\n     -@@ builtin/interpret-trailers.c: static void interpret_trailers(const char *file,\n     +@@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n       \tLIST_HEAD(head);\n       \tstruct strbuf sb = STRBUF_INIT;\n       \tstruct strbuf trailer_block = STRBUF_INIT;\n     @@ builtin/interpret-trailers.c: static void interpret_trailers(const char *file,\n       \tFILE *outfile = stdout;\n       \n       \ttrailer_config_init();\n     -@@ builtin/interpret-trailers.c: static void interpret_trailers(const char *file,\n     +@@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n       \tif (opts->in_place)\n       \t\toutfile = create_in_place_tempfile(file);\n       \n     --\tparse_trailers(&info, sb.buf, &head, opts);\n     -+\tinfo = parse_trailers(sb.buf, &head, opts);\n     +-\tparse_trailers(opts, &info, sb.buf, &head);\n     ++\tinfo = parse_trailers(opts, sb.buf, &head);\n       \n       \t/* Print the lines before the trailers */\n       \tif (!opts->only_trailers)\n     @@ builtin/interpret-trailers.c: static void interpret_trailers(const char *file,\n       \t\tfprintf(outfile, \"\\n\");\n       \n       \n     -@@ builtin/interpret-trailers.c: static void interpret_trailers(const char *file,\n     +@@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n       \tstrbuf_release(&trailer_block);\n       \n       \tfree_trailers(&head);\n     @@ trailer.c\n       struct conf_info {\n       \tchar *name;\n       \tchar *key;\n     -@@ trailer.c: void format_trailers(struct list_head *head,\n     +@@ trailer.c: void format_trailers(const struct process_trailer_options *opts,\n       \t}\n       }\n       \n     @@ trailer.c: void format_trailers(struct list_head *head,\n      +\treturn info;\n      +}\n      +\n     -+static struct trailer_info *trailer_info_get(const char *str,\n     -+\t\t\t\t\t     const struct process_trailer_options *opts)\n     ++static struct trailer_info *trailer_info_get(const struct process_trailer_options *opts,\n     ++\t\t\t\t\t     const char *str)\n      +{\n      +\tstruct trailer_info *info = trailer_info_new();\n      +\tsize_t end_of_log_message = 0, trailer_block_start = 0;\n     @@ trailer.c: void format_trailers(struct list_head *head,\n        * Parse trailers in \"str\", populating the trailer info and \"head\"\n        * linked list structure.\n        */\n     --void parse_trailers(struct trailer_info *info,\n     +-void parse_trailers(const struct process_trailer_options *opts,\n     +-\t\t    struct trailer_info *info,\n      -\t\t    const char *str,\n     --\t\t    struct list_head *head,\n     --\t\t    const struct process_trailer_options *opts)\n     -+struct trailer_info *parse_trailers(const char *str,\n     -+\t\t\t\t    struct list_head *head,\n     -+\t\t\t\t    const struct process_trailer_options *opts)\n     +-\t\t    struct list_head *head)\n     ++struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n     ++\t\t\t\t    const char *str,\n     ++\t\t\t\t    struct list_head *head)\n       {\n      +\tstruct trailer_info *info;\n       \tstruct strbuf tok = STRBUF_INIT;\n     @@ trailer.c: void format_trailers(struct list_head *head,\n       \tsize_t i;\n       \n      -\ttrailer_info_get(info, str, opts);\n     -+\tinfo = trailer_info_get(str, opts);\n     ++\tinfo = trailer_info_get(opts, str);\n       \n       \tfor (i = 0; i < info->trailer_nr; i++) {\n       \t\tint separator_pos;\n     -@@ trailer.c: void parse_trailers(struct trailer_info *info,\n     +@@ trailer.c: void parse_trailers(const struct process_trailer_options *opts,\n       \t\t\t\t\t strbuf_detach(&val, NULL));\n       \t\t}\n       \t}\n     @@ trailer.c: void parse_trailers(struct trailer_info *info,\n      +\treturn info;\n       }\n       \n     - void free_trailers(struct list_head *head)\n     -@@ trailer.c: void free_trailers(struct list_head *head)\n     + void free_trailers(struct list_head *trailers)\n     +@@ trailer.c: void free_trailers(struct list_head *trailers)\n       \t}\n       }\n       \n     @@ trailer.c: void trailer_info_release(struct trailer_info *info)\n      +\tfree(info);\n       }\n       \n     - void format_trailers_from_commit(const char *msg,\n     -@@ trailer.c: void format_trailers_from_commit(const char *msg,\n     + void format_trailers_from_commit(const struct process_trailer_options *opts,\n     +@@ trailer.c: void format_trailers_from_commit(const struct process_trailer_options *opts,\n       \t\t\t\t struct strbuf *out)\n       {\n       \tLIST_HEAD(head);\n      -\tstruct trailer_info info;\n      -\n     --\tparse_trailers(&info, msg, &head, opts);\n     -+\tstruct trailer_info *info = parse_trailers(msg, &head, opts);\n     +-\tparse_trailers(opts, &info, msg, &head);\n     ++\tstruct trailer_info *info = parse_trailers(opts, msg, &head);\n       \n       \t/* If we want the whole block untouched, we can take the fast path. */\n       \tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n     @@ trailer.c: void format_trailers_from_commit(const char *msg,\n      +\t\tstrbuf_add(out, msg + info->trailer_block_start,\n      +\t\t\t   info->trailer_block_end - info->trailer_block_start);\n       \t} else\n     - \t\tformat_trailers(&head, opts, out);\n     + \t\tformat_trailers(opts, &head, out);\n       \n       \tfree_trailers(&head);\n      -\ttrailer_info_release(&info);\n     @@ trailer.c: void format_trailers_from_commit(const char *msg,\n      +\tstruct trailer_info *internal = trailer_info_new();\n       \tstrbuf_init(&iter->key, 0);\n       \tstrbuf_init(&iter->val, 0);\n     - \tstrbuf_init(&iter->raw, 0);\n       \topts.no_divider = 1;\n      -\ttrailer_info_get(&iter->internal.info, msg, &opts);\n      +\titer->internal.info = internal;\n     -+\titer->internal.info = trailer_info_get(msg, &opts);\n     ++\titer->internal.info = trailer_info_get(&opts, msg);\n       \titer->internal.cur = 0;\n       }\n       \n     @@ trailer.c: int trailer_iterator_advance(struct trailer_iterator *iter)\n      +\ttrailer_info_release(iter->internal.info);\n       \tstrbuf_release(&iter->val);\n       \tstrbuf_release(&iter->key);\n     - \tstrbuf_release(&iter->raw);\n     + }\n      \n       ## trailer.h ##\n      @@\n     @@ trailer.h: void parse_trailers_from_command_line_args(struct list_head *arg_head\n       void process_trailers_lists(struct list_head *head,\n       \t\t\t    struct list_head *arg_head);\n       \n     --void parse_trailers(struct trailer_info *info,\n     +-void parse_trailers(const struct process_trailer_options *opts,\n     +-\t\t    struct trailer_info *info,\n      -\t\t    const char *str,\n     --\t\t    struct list_head *head,\n     --\t\t    const struct process_trailer_options *opts);\n     -+struct trailer_info *parse_trailers(const char *str,\n     -+\t\t\t\t    struct list_head *head,\n     -+\t\t\t\t    const struct process_trailer_options *opts);\n     +-\t\t    struct list_head *head);\n     ++struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n     ++\t\t\t\t    const char *str,\n     ++\t\t\t\t    struct list_head *head);\n       \n      -void trailer_info_get(struct trailer_info *info, const char *str,\n      -\t\t      const struct process_trailer_options *opts);\n  7:  9183990583f !  7:  291aa83af55 trailer: spread usage of \"trailer_block\" language\n     @@ Commit message\n          Signed-off-by: Linus Arver <linusa@google.com>\n      \n       ## builtin/interpret-trailers.c ##\n     -@@ builtin/interpret-trailers.c: static void interpret_trailers(const char *file,\n     +@@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n       {\n       \tLIST_HEAD(head);\n       \tstruct strbuf sb = STRBUF_INIT;\n     @@ builtin/interpret-trailers.c: static void interpret_trailers(const char *file,\n       \tFILE *outfile = stdout;\n       \n       \ttrailer_config_init();\n     -@@ builtin/interpret-trailers.c: static void interpret_trailers(const char *file,\n     +@@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n       \tif (opts->in_place)\n       \t\toutfile = create_in_place_tempfile(file);\n       \n     --\tinfo = parse_trailers(sb.buf, &head, opts);\n     -+\ttrailer_block = parse_trailers(sb.buf, opts, &head);\n     +-\tinfo = parse_trailers(opts, sb.buf, &head);\n     ++\ttrailer_block = parse_trailers(opts, sb.buf, &head);\n       \n      -\t/* Print the lines before the trailers */\n      +\t/* Print the lines before the trailer block */\n     @@ builtin/interpret-trailers.c: static void interpret_trailers(const char *file,\n       \t\tfprintf(outfile, \"\\n\");\n       \n       \n     -@@ builtin/interpret-trailers.c: static void interpret_trailers(const char *file,\n     +@@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n       \t}\n       \n       \t/* Print trailer block. */\n     --\tformat_trailers(&head, opts, &trailer_block);\n     +-\tformat_trailers(opts, &head, &trailer_block);\n      -\tfwrite(trailer_block.buf, 1, trailer_block.len, outfile);\n      -\tstrbuf_release(&trailer_block);\n     -+\tformat_trailers(&head, opts, &tb);\n     ++\tformat_trailers(opts, &head, &tb);\n      +\tfwrite(tb.buf, 1, tb.len, outfile);\n      +\tstrbuf_release(&tb);\n       \n     @@ trailer.c\n       \n       \t/*\n       \t * Array of trailers found.\n     -@@ trailer.c: void format_trailers(struct list_head *head,\n     +@@ trailer.c: void format_trailers(const struct process_trailer_options *opts,\n       \t}\n       }\n       \n     @@ trailer.c: void format_trailers(struct list_head *head,\n      +\treturn trailer_block;\n       }\n       \n     --static struct trailer_info *trailer_info_get(const char *str,\n     --\t\t\t\t\t     const struct process_trailer_options *opts)\n     -+static struct trailer_block *trailer_block_get(const char *str,\n     -+\t\t\t\t\t       const struct process_trailer_options *opts)\n     +-static struct trailer_info *trailer_info_get(const struct process_trailer_options *opts,\n     +-\t\t\t\t\t     const char *str)\n     ++static struct trailer_block *trailer_block_get(const struct process_trailer_options *opts,\n     ++\t\t\t\t\t       const char *str)\n       {\n      -\tstruct trailer_info *info = trailer_info_new();\n      +\tstruct trailer_block *trailer_block = trailer_block_new();\n       \tsize_t end_of_log_message = 0, trailer_block_start = 0;\n       \tstruct strbuf **trailer_lines, **ptr;\n       \tchar **trailer_strings = NULL;\n     -@@ trailer.c: static struct trailer_info *trailer_info_get(const char *str,\n     +@@ trailer.c: static struct trailer_info *trailer_info_get(const struct process_trailer_option\n       \t}\n       \tstrbuf_list_free(trailer_lines);\n       \n     @@ trailer.c: static struct trailer_info *trailer_info_get(const char *str,\n      + * Parse trailers in \"str\", populating the trailer_block info and \"head\" linked\n      + * list structure.\n        */\n     --struct trailer_info *parse_trailers(const char *str,\n     --\t\t\t\t    struct list_head *head,\n     --\t\t\t\t    const struct process_trailer_options *opts)\n     -+struct trailer_block *parse_trailers(const char *str,\n     -+\t\t\t\t     const struct process_trailer_options *opts,\n     +-struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n     +-\t\t\t\t    const char *str,\n     +-\t\t\t\t    struct list_head *head)\n     ++struct trailer_block *parse_trailers(const struct process_trailer_options *opts,\n     ++\t\t\t\t     const char *str,\n      +\t\t\t\t     struct list_head *head)\n       {\n      -\tstruct trailer_info *info;\n     @@ trailer.c: static struct trailer_info *trailer_info_get(const char *str,\n       \tstruct strbuf val = STRBUF_INIT;\n       \tsize_t i;\n       \n     --\tinfo = trailer_info_get(str, opts);\n     -+\ttrailer_block = trailer_block_get(str, opts);\n     +-\tinfo = trailer_info_get(opts, str);\n     ++\ttrailer_block = trailer_block_get(opts, str);\n       \n      -\tfor (i = 0; i < info->trailer_nr; i++) {\n      +\tfor (i = 0; i < trailer_block->trailer_nr; i++) {\n     @@ trailer.c: static struct trailer_info *trailer_info_get(const char *str,\n       \t\tif (trailer[0] == comment_line_char)\n       \t\t\tcontinue;\n       \t\tseparator_pos = find_separator(trailer, separators);\n     -@@ trailer.c: struct trailer_info *parse_trailers(const char *str,\n     +@@ trailer.c: struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n       \t\t}\n       \t}\n       \n     @@ trailer.c: struct trailer_info *parse_trailers(const char *str,\n      +\treturn trailer_block;\n       }\n       \n     - void free_trailers(struct list_head *head)\n     -@@ trailer.c: void free_trailers(struct list_head *head)\n     + void free_trailers(struct list_head *trailers)\n     +@@ trailer.c: void free_trailers(struct list_head *trailers)\n       \t}\n       }\n       \n     @@ trailer.c: void free_trailers(struct list_head *head)\n      +\tfree(trailer_block);\n       }\n       \n     - void format_trailers_from_commit(const char *msg,\n     -@@ trailer.c: void format_trailers_from_commit(const char *msg,\n     + void format_trailers_from_commit(const struct process_trailer_options *opts,\n     +@@ trailer.c: void format_trailers_from_commit(const struct process_trailer_options *opts,\n       \t\t\t\t struct strbuf *out)\n       {\n       \tLIST_HEAD(head);\n     --\tstruct trailer_info *info = parse_trailers(msg, &head, opts);\n     -+\tstruct trailer_block *trailer_block = parse_trailers(msg, opts, &head);\n     +-\tstruct trailer_info *info = parse_trailers(opts, msg, &head);\n     ++\tstruct trailer_block *trailer_block = parse_trailers(opts, msg, &head);\n       \n       \t/* If we want the whole block untouched, we can take the fast path. */\n       \tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n     @@ trailer.c: void format_trailers_from_commit(const char *msg,\n      +\t\tstrbuf_add(out, msg + trailer_block->start,\n      +\t\t\t   trailer_block->end - trailer_block->start);\n       \t} else\n     - \t\tformat_trailers(&head, opts, out);\n     + \t\tformat_trailers(opts, &head, out);\n       \n       \tfree_trailers(&head);\n      -\ttrailer_info_release(info);\n     @@ trailer.c: void format_trailers_from_commit(const char *msg,\n      -\tstruct trailer_info *internal = trailer_info_new();\n       \tstrbuf_init(&iter->key, 0);\n       \tstrbuf_init(&iter->val, 0);\n     - \tstrbuf_init(&iter->raw, 0);\n       \topts.no_divider = 1;\n      -\titer->internal.info = internal;\n     --\titer->internal.info = trailer_info_get(msg, &opts);\n     -+\titer->internal.trailer_block = trailer_block_get(msg, &opts);\n     +-\titer->internal.info = trailer_info_get(&opts, msg);\n     ++\titer->internal.trailer_block = trailer_block_get(&opts, msg);\n       \titer->internal.cur = 0;\n       }\n       \n     @@ trailer.c: int trailer_iterator_advance(struct trailer_iterator *iter)\n      +\ttrailer_block_release(iter->internal.trailer_block);\n       \tstrbuf_release(&iter->val);\n       \tstrbuf_release(&iter->key);\n     - \tstrbuf_release(&iter->raw);\n     + }\n      \n       ## trailer.h ##\n      @@\n     @@ trailer.h: void parse_trailers_from_command_line_args(struct list_head *arg_head\n       void process_trailers_lists(struct list_head *head,\n       \t\t\t    struct list_head *arg_head);\n       \n     --struct trailer_info *parse_trailers(const char *str,\n     --\t\t\t\t    struct list_head *head,\n     --\t\t\t\t    const struct process_trailer_options *opts);\n     -+struct trailer_block *parse_trailers(const char *str,\n     -+\t\t\t\t     const struct process_trailer_options *opts,\n     +-struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n     +-\t\t\t\t    const char *str,\n     +-\t\t\t\t    struct list_head *head);\n     ++struct trailer_block *parse_trailers(const struct process_trailer_options *opts,\n     ++\t\t\t\t     const char *str,\n      +\t\t\t\t     struct list_head *head);\n       \n      -size_t trailer_block_start(struct trailer_info *info);\n     @@ trailer.h: void parse_trailers_from_command_line_args(struct list_head *arg_head\n      +void trailer_block_release(struct trailer_block *trailer_block);\n       \n       void trailer_config_init(void);\n     - void free_trailers(struct list_head *trailers);\n     + void format_trailers(const struct process_trailer_options *opts,\n      @@ trailer.h: struct trailer_iterator {\n       \n       \t/* private */\n  8:  406725df46a !  8:  64ee07d0b53 trailer: prepare to move parse_trailers_from_command_line_args() to builtin\n     @@ Commit message\n              parse_trailers_from_command_line_args()\n      \n          to interpret-trailer.c, because the trailer API should not be concerned\n     -    with command line arguments (as it has nothing to do with trailers\n     -    themselves). The interpret-trailers builtin is the only user of the\n     -    above function.\n     +    with command line arguments (as they have nothing to do with trailers\n     +    themselves). The interpret-trailers builtin is the only caller of this\n     +    function.\n      \n     +    Also rename \"conf_info\" to \"trailer_conf\" for readability, dropping the\n     +    low-value \"_info\" suffix as we did earlier in this series for\n     +    \"trailer_info\" to \"trailer_block\".\n     +\n     +    Helped-by: Josh Steadmon <steadmon@google.com>\n          Signed-off-by: Linus Arver <linusa@google.com>\n      \n       ## trailer.c ##\n     @@ trailer.c: void parse_trailers_from_command_line_args(struct list_head *arg_head\n       \t\t\tadd_arg_item(arg_head,\n       \t\t\t\t     strbuf_detach(&tok, NULL),\n       \t\t\t\t     strbuf_detach(&val, NULL),\n     -@@ trailer.c: struct trailer_block *parse_trailers(const char *str,\n     +@@ trailer.c: struct trailer_block *parse_trailers(const struct process_trailer_options *opts,\n       \n       \tfor (i = 0; i < trailer_block->trailer_nr; i++) {\n       \t\tint separator_pos;\n     @@ trailer.c: struct trailer_block *parse_trailers(const char *str,\n       \t\t\tadd_trailer_item(head,\n       \t\t\t\t\t NULL,\n      @@ trailer.c: int trailer_iterator_advance(struct trailer_iterator *iter)\n     - \t\tstrbuf_addstr(&iter->raw, line);\n     + \t\titer->raw = line;\n       \t\tstrbuf_reset(&iter->key);\n       \t\tstrbuf_reset(&iter->val);\n      -\t\tparse_trailer(&iter->key, &iter->val, NULL,\n     @@ trailer.h: void parse_trailers_from_command_line_args(struct list_head *arg_head\n      +\t\t   struct strbuf *tok, struct strbuf *val,\n      +\t\t   const struct trailer_conf **conf);\n      +\n     - struct trailer_block *parse_trailers(const char *str,\n     - \t\t\t\t     const struct process_trailer_options *opts,\n     + struct trailer_block *parse_trailers(const struct process_trailer_options *opts,\n     + \t\t\t\t     const char *str,\n       \t\t\t\t     struct list_head *head);\n  9:  8a99d0fca21 !  9:  1b4bdde65bc trailer: move arg handling to interpret-trailers.c\n     @@ builtin/interpret-trailers.c: static int option_parse_trailer(const struct optio\n       }\n       \n      @@ builtin/interpret-trailers.c: static void read_input_file(struct strbuf *sb, const char *file)\n     + }\n       \n     - static void interpret_trailers(const char *file,\n     - \t\t\t       const struct process_trailer_options *opts,\n     --\t\t\t       struct list_head *new_trailer_head)\n     -+\t\t\t       struct list_head *arg_trailers)\n     + static void interpret_trailers(const struct process_trailer_options *opts,\n     +-\t\t\t       struct list_head *new_trailer_head,\n     ++\t\t\t       struct list_head *arg_trailers,\n     + \t\t\t       const char *file)\n       {\n       \tLIST_HEAD(head);\n     - \tstruct strbuf sb = STRBUF_INIT;\n     -@@ builtin/interpret-trailers.c: static void interpret_trailers(const char *file,\n     +@@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n       \tstruct trailer_block *trailer_block;\n       \tFILE *outfile = stdout;\n       \n     @@ builtin/interpret-trailers.c: static void interpret_trailers(const char *file,\n       \tread_input_file(&sb, file);\n       \n       \tif (opts->in_place)\n     -@@ builtin/interpret-trailers.c: static void interpret_trailers(const char *file,\n     +@@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n       \n       \n       \tif (!opts->only_input) {\n     @@ builtin/interpret-trailers.c: static void interpret_trailers(const char *file,\n       \t}\n       \n       \t/* Print trailer block. */\n     -@@ builtin/interpret-trailers.c: static void interpret_trailers(const char *file,\n     +@@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n       int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n       {\n       \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n     @@ builtin/interpret-trailers.c: int cmd_interpret_trailers(int argc, const char **\n       \tif (argc) {\n       \t\tint i;\n       \t\tfor (i = 0; i < argc; i++)\n     --\t\t\tinterpret_trailers(argv[i], &opts, &trailers);\n     -+\t\t\tinterpret_trailers(argv[i], &opts, &arg_trailers);\n     +-\t\t\tinterpret_trailers(&opts, &trailers, argv[i]);\n     ++\t\t\tinterpret_trailers(&opts, &arg_trailers, argv[i]);\n       \t} else {\n       \t\tif (opts.in_place)\n       \t\t\tdie(_(\"no input file given for in-place editing\"));\n     --\t\tinterpret_trailers(NULL, &opts, &trailers);\n     -+\t\tinterpret_trailers(NULL, &opts, &arg_trailers);\n     +-\t\tinterpret_trailers(&opts, &trailers, NULL);\n     ++\t\tinterpret_trailers(&opts, &arg_trailers, NULL);\n       \t}\n       \n      -\tnew_trailers_clear(&trailers);\n     @@ trailer.c: void parse_trailers_from_command_line_args(struct list_head *arg_head\n       \t\t}\n       \t}\n       \n     -@@ trailer.c: void free_trailers(struct list_head *head)\n     +@@ trailer.c: void free_trailers(struct list_head *trailers)\n       \t}\n       }\n       \n     @@ trailer.h: struct new_trailer_item {\n       struct process_trailer_options {\n       \tint in_place;\n       \tint trim_empty;\n     -@@ trailer.h: void trailer_block_release(struct trailer_block *trailer_block);\n     - \n     - void trailer_config_init(void);\n     +@@ trailer.h: void format_trailers(const struct process_trailer_options *opts,\n     + \t\t     struct list_head *trailers,\n     + \t\t     struct strbuf *out);\n       void free_trailers(struct list_head *trailers);\n     -+void new_trailers_clear(struct list_head *trailers);\n     - \n     - void format_trailers(struct list_head *head,\n     - \t\t     const struct process_trailer_options *opts,\n     +-\n     ++void new_trailers_clear(struct list_head *new_trailers);\n     + /*\n     +  * Convenience function to format the trailers from the commit msg \"msg\" into\n     +  * the strbuf \"out\". Reuses format_trailers internally.\n 10:  243eee730e4 = 10:  ed67ebf8647 trailer: delete obsolete argument handling code from API\n\n-- \ngitgitgadget\n"},{"id":"487433","messageId":"e2d3ed9b5b6d67273c22671374daf7695c67709f.1706308737.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v2.git.1706308737.gitgitgadget@gmail.com","subject":"[PATCH v2 01/10] trailer: prepare to expose functions as part of API","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-26T22:38:48Z","receivedAt":"2024-01-26T22:39:02Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nIn the next patch, we will move \"process_trailers\" from trailer.c to\nbuiltin/interpret-trailers.c. That move will necessitate the growth of\nthe trailer.h API, forcing us to expose some additional functions in\ntrailer.h.\n\nRename relevant functions so that they include the term \"trailer\" in\ntheir name, so that clients of the API will be able to easily identify\nthem by their \"trailer\" moniker, just like all the other functions\nalready exposed by trailer.h.\n\nThe the opportunity to start putting trailer processions options (opts)\nas the first parameter. This will be the pattern going forward in this\nseries.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  4 ++--\n trailer.c                    | 26 +++++++++++++-------------\n trailer.h                    |  6 +++---\n 3 files changed, 18 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 033bd1556cf..85a3413baf5 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -132,11 +132,11 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n \tif (argc) {\n \t\tint i;\n \t\tfor (i = 0; i < argc; i++)\n-\t\t\tprocess_trailers(argv[i], &opts, &trailers);\n+\t\t\tinterpret_trailers(&opts, &trailers, argv[i]);\n \t} else {\n \t\tif (opts.in_place)\n \t\t\tdie(_(\"no input file given for in-place editing\"));\n-\t\tprocess_trailers(NULL, &opts, &trailers);\n+\t\tinterpret_trailers(&opts, &trailers, NULL);\n \t}\n \n \tnew_trailers_clear(&trailers);\ndiff --git a/trailer.c b/trailer.c\nindex 3a0710a4583..66b6660f5a4 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -163,12 +163,12 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n-static void print_all(FILE *outfile, struct list_head *head,\n-\t\t      const struct process_trailer_options *opts)\n+static void format_trailers(const struct process_trailer_options *opts,\n+\t\t\t    struct list_head *trailers, FILE *outfile)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *item;\n-\tlist_for_each(pos, head) {\n+\tlist_for_each(pos, trailers) {\n \t\titem = list_entry(pos, struct trailer_item, list);\n \t\tif ((!opts->trim_empty || strlen(item->value) > 0) &&\n \t\t    (!opts->only_trailers || item->token))\n@@ -589,7 +589,7 @@ static int git_trailer_config(const char *conf_key, const char *value,\n \treturn 0;\n }\n \n-static void ensure_configured(void)\n+static void trailer_config_init(void)\n {\n \tif (configured)\n \t\treturn;\n@@ -1035,10 +1035,10 @@ static void parse_trailers(struct trailer_info *info,\n \t}\n }\n \n-static void free_all(struct list_head *head)\n+static void free_trailers(struct list_head *trailers)\n {\n \tstruct list_head *pos, *p;\n-\tlist_for_each_safe(pos, p, head) {\n+\tlist_for_each_safe(pos, p, trailers) {\n \t\tlist_del(pos);\n \t\tfree_trailer_item(list_entry(pos, struct trailer_item, list));\n \t}\n@@ -1075,16 +1075,16 @@ static FILE *create_in_place_tempfile(const char *file)\n \treturn outfile;\n }\n \n-void process_trailers(const char *file,\n-\t\t      const struct process_trailer_options *opts,\n-\t\t      struct list_head *new_trailer_head)\n+void interpret_trailers(const struct process_trailer_options *opts,\n+\t\t\tstruct list_head *new_trailer_head,\n+\t\t\tconst char *file)\n {\n \tLIST_HEAD(head);\n \tstruct strbuf sb = STRBUF_INIT;\n \tstruct trailer_info info;\n \tFILE *outfile = stdout;\n \n-\tensure_configured();\n+\ttrailer_config_init();\n \n \tread_input_file(&sb, file);\n \n@@ -1110,9 +1110,9 @@ void process_trailers(const char *file,\n \t\tprocess_trailers_lists(&head, &arg_head);\n \t}\n \n-\tprint_all(outfile, &head, opts);\n+\tformat_trailers(opts, &head, outfile);\n \n-\tfree_all(&head);\n+\tfree_trailers(&head);\n \ttrailer_info_release(&info);\n \n \t/* Print the lines after the trailers as is */\n@@ -1135,7 +1135,7 @@ void trailer_info_get(struct trailer_info *info, const char *str,\n \tsize_t nr = 0, alloc = 0;\n \tchar **last = NULL;\n \n-\tensure_configured();\n+\ttrailer_config_init();\n \n \tend_of_log_message = find_end_of_log_message(str, opts->no_divider);\n \ttrailer_block_start = find_trailer_block_start(str, end_of_log_message);\ndiff --git a/trailer.h b/trailer.h\nindex 1644cd05f60..37033e631a1 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -81,9 +81,9 @@ struct process_trailer_options {\n \n #define PROCESS_TRAILER_OPTIONS_INIT {0}\n \n-void process_trailers(const char *file,\n-\t\t      const struct process_trailer_options *opts,\n-\t\t      struct list_head *new_trailer_head);\n+void interpret_trailers(const struct process_trailer_options *opts,\n+\t\t\tstruct list_head *new_trailer_head,\n+\t\t\tconst char *file);\n \n void trailer_info_get(struct trailer_info *info, const char *str,\n \t\t      const struct process_trailer_options *opts);\n-- \ngitgitgadget\n\n"},{"id":"487434","messageId":"eaca39fd7eaa2c02ef2798cbb0304924bbc12b39.1706308737.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v2.git.1706308737.gitgitgadget@gmail.com","subject":"[PATCH v2 02/10] trailer: move interpret_trailers() to interpret-trailers.c","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-26T22:38:49Z","receivedAt":"2024-01-26T22:39:03Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThe interpret-trailers.c builtin is the only place we need to call\ninterpret_trailers(), so move its definition there.\n\nDelete the corresponding declaration from trailer.h, which then forces\nus to expose the working innards of that function. This enriches\ntrailer.h with a more granular API, which can then be unit-tested in the\nfuture (because interpret_trailers() by itself does too many things to\nbe able to be easily unit-tested).\n\nTake this opportunity to demote some file-handling functions out of the\ntrailer API implementation, as these have nothing to do with trailers.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  94 +++++++++++++++++++++++++++\n trailer.c                    | 120 ++++-------------------------------\n trailer.h                    |  20 +++++-\n 3 files changed, 124 insertions(+), 110 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 85a3413baf5..8556acde4aa 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -9,6 +9,7 @@\n #include \"gettext.h\"\n #include \"parse-options.h\"\n #include \"string-list.h\"\n+#include \"tempfile.h\"\n #include \"trailer.h\"\n #include \"config.h\"\n \n@@ -91,6 +92,99 @@ static int parse_opt_parse(const struct option *opt, const char *arg,\n \treturn 0;\n }\n \n+static struct tempfile *trailers_tempfile;\n+\n+static FILE *create_in_place_tempfile(const char *file)\n+{\n+\tstruct stat st;\n+\tstruct strbuf filename_template = STRBUF_INIT;\n+\tconst char *tail;\n+\tFILE *outfile;\n+\n+\tif (stat(file, &st))\n+\t\tdie_errno(_(\"could not stat %s\"), file);\n+\tif (!S_ISREG(st.st_mode))\n+\t\tdie(_(\"file %s is not a regular file\"), file);\n+\tif (!(st.st_mode & S_IWUSR))\n+\t\tdie(_(\"file %s is not writable by user\"), file);\n+\n+\t/* Create temporary file in the same directory as the original */\n+\ttail = strrchr(file, '/');\n+\tif (tail)\n+\t\tstrbuf_add(&filename_template, file, tail - file + 1);\n+\tstrbuf_addstr(&filename_template, \"git-interpret-trailers-XXXXXX\");\n+\n+\ttrailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode);\n+\tstrbuf_release(&filename_template);\n+\toutfile = fdopen_tempfile(trailers_tempfile, \"w\");\n+\tif (!outfile)\n+\t\tdie_errno(_(\"could not open temporary file\"));\n+\n+\treturn outfile;\n+}\n+\n+static void read_input_file(struct strbuf *sb, const char *file)\n+{\n+\tif (file) {\n+\t\tif (strbuf_read_file(sb, file, 0) < 0)\n+\t\t\tdie_errno(_(\"could not read input file '%s'\"), file);\n+\t} else {\n+\t\tif (strbuf_read(sb, fileno(stdin), 0) < 0)\n+\t\t\tdie_errno(_(\"could not read from stdin\"));\n+\t}\n+}\n+\n+static void interpret_trailers(const struct process_trailer_options *opts,\n+\t\t\t       struct list_head *new_trailer_head,\n+\t\t\t       const char *file)\n+{\n+\tLIST_HEAD(head);\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tstruct trailer_info info;\n+\tFILE *outfile = stdout;\n+\n+\ttrailer_config_init();\n+\n+\tread_input_file(&sb, file);\n+\n+\tif (opts->in_place)\n+\t\toutfile = create_in_place_tempfile(file);\n+\n+\tparse_trailers(opts, &info, sb.buf, &head);\n+\n+\t/* Print the lines before the trailers */\n+\tif (!opts->only_trailers)\n+\t\tfwrite(sb.buf, 1, info.trailer_block_start, outfile);\n+\n+\tif (!opts->only_trailers && !info.blank_line_before_trailer)\n+\t\tfprintf(outfile, \"\\n\");\n+\n+\n+\tif (!opts->only_input) {\n+\t\tLIST_HEAD(config_head);\n+\t\tLIST_HEAD(arg_head);\n+\t\tparse_trailers_from_config(&config_head);\n+\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n+\t\tlist_splice(&config_head, &arg_head);\n+\t\tprocess_trailers_lists(&head, &arg_head);\n+\t}\n+\n+\tformat_trailers(opts, &head, outfile);\n+\n+\tfree_trailers(&head);\n+\ttrailer_info_release(&info);\n+\n+\t/* Print the lines after the trailers as is */\n+\tif (!opts->only_trailers)\n+\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n+\n+\tif (opts->in_place)\n+\t\tif (rename_tempfile(&trailers_tempfile, file))\n+\t\t\tdie_errno(_(\"could not rename temporary file to %s\"), file);\n+\n+\tstrbuf_release(&sb);\n+}\n+\n int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n {\n \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\ndiff --git a/trailer.c b/trailer.c\nindex 66b6660f5a4..d3899195876 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -5,7 +5,6 @@\n #include \"string-list.h\"\n #include \"run-command.h\"\n #include \"commit.h\"\n-#include \"tempfile.h\"\n #include \"trailer.h\"\n #include \"list.h\"\n /*\n@@ -163,8 +162,8 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n-static void format_trailers(const struct process_trailer_options *opts,\n-\t\t\t    struct list_head *trailers, FILE *outfile)\n+void format_trailers(const struct process_trailer_options *opts,\n+\t\t     struct list_head *trailers, FILE *outfile)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *item;\n@@ -366,8 +365,8 @@ static int find_same_and_apply_arg(struct list_head *head,\n \treturn 0;\n }\n \n-static void process_trailers_lists(struct list_head *head,\n-\t\t\t\t   struct list_head *arg_head)\n+void process_trailers_lists(struct list_head *head,\n+\t\t\t    struct list_head *arg_head)\n {\n \tstruct list_head *pos, *p;\n \tstruct arg_item *arg_tok;\n@@ -589,7 +588,7 @@ static int git_trailer_config(const char *conf_key, const char *value,\n \treturn 0;\n }\n \n-static void trailer_config_init(void)\n+void trailer_config_init(void)\n {\n \tif (configured)\n \t\treturn;\n@@ -719,7 +718,7 @@ static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n \tlist_add_tail(&new_item->list, arg_head);\n }\n \n-static void parse_trailers_from_config(struct list_head *config_head)\n+void parse_trailers_from_config(struct list_head *config_head)\n {\n \tstruct arg_item *item;\n \tstruct list_head *pos;\n@@ -735,8 +734,8 @@ static void parse_trailers_from_config(struct list_head *config_head)\n \t}\n }\n \n-static void parse_trailers_from_command_line_args(struct list_head *arg_head,\n-\t\t\t\t\t\t  struct list_head *new_trailer_head)\n+void parse_trailers_from_command_line_args(struct list_head *arg_head,\n+\t\t\t\t\t   struct list_head *new_trailer_head)\n {\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n@@ -775,17 +774,6 @@ static void parse_trailers_from_command_line_args(struct list_head *arg_head,\n \tfree(cl_separators);\n }\n \n-static void read_input_file(struct strbuf *sb, const char *file)\n-{\n-\tif (file) {\n-\t\tif (strbuf_read_file(sb, file, 0) < 0)\n-\t\t\tdie_errno(_(\"could not read input file '%s'\"), file);\n-\t} else {\n-\t\tif (strbuf_read(sb, fileno(stdin), 0) < 0)\n-\t\t\tdie_errno(_(\"could not read from stdin\"));\n-\t}\n-}\n-\n static const char *next_line(const char *str)\n {\n \tconst char *nl = strchrnul(str, '\\n');\n@@ -1000,10 +988,10 @@ static void unfold_value(struct strbuf *val)\n  * Parse trailers in \"str\", populating the trailer info and \"head\"\n  * linked list structure.\n  */\n-static void parse_trailers(struct trailer_info *info,\n-\t\t\t     const char *str,\n-\t\t\t     struct list_head *head,\n-\t\t\t     const struct process_trailer_options *opts)\n+void parse_trailers(const struct process_trailer_options *opts,\n+\t\t    struct trailer_info *info,\n+\t\t    const char *str,\n+\t\t    struct list_head *head)\n {\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n@@ -1035,7 +1023,7 @@ static void parse_trailers(struct trailer_info *info,\n \t}\n }\n \n-static void free_trailers(struct list_head *trailers)\n+void free_trailers(struct list_head *trailers)\n {\n \tstruct list_head *pos, *p;\n \tlist_for_each_safe(pos, p, trailers) {\n@@ -1044,88 +1032,6 @@ static void free_trailers(struct list_head *trailers)\n \t}\n }\n \n-static struct tempfile *trailers_tempfile;\n-\n-static FILE *create_in_place_tempfile(const char *file)\n-{\n-\tstruct stat st;\n-\tstruct strbuf filename_template = STRBUF_INIT;\n-\tconst char *tail;\n-\tFILE *outfile;\n-\n-\tif (stat(file, &st))\n-\t\tdie_errno(_(\"could not stat %s\"), file);\n-\tif (!S_ISREG(st.st_mode))\n-\t\tdie(_(\"file %s is not a regular file\"), file);\n-\tif (!(st.st_mode & S_IWUSR))\n-\t\tdie(_(\"file %s is not writable by user\"), file);\n-\n-\t/* Create temporary file in the same directory as the original */\n-\ttail = strrchr(file, '/');\n-\tif (tail)\n-\t\tstrbuf_add(&filename_template, file, tail - file + 1);\n-\tstrbuf_addstr(&filename_template, \"git-interpret-trailers-XXXXXX\");\n-\n-\ttrailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode);\n-\tstrbuf_release(&filename_template);\n-\toutfile = fdopen_tempfile(trailers_tempfile, \"w\");\n-\tif (!outfile)\n-\t\tdie_errno(_(\"could not open temporary file\"));\n-\n-\treturn outfile;\n-}\n-\n-void interpret_trailers(const struct process_trailer_options *opts,\n-\t\t\tstruct list_head *new_trailer_head,\n-\t\t\tconst char *file)\n-{\n-\tLIST_HEAD(head);\n-\tstruct strbuf sb = STRBUF_INIT;\n-\tstruct trailer_info info;\n-\tFILE *outfile = stdout;\n-\n-\ttrailer_config_init();\n-\n-\tread_input_file(&sb, file);\n-\n-\tif (opts->in_place)\n-\t\toutfile = create_in_place_tempfile(file);\n-\n-\tparse_trailers(&info, sb.buf, &head, opts);\n-\n-\t/* Print the lines before the trailers */\n-\tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf, 1, info.trailer_block_start, outfile);\n-\n-\tif (!opts->only_trailers && !info.blank_line_before_trailer)\n-\t\tfprintf(outfile, \"\\n\");\n-\n-\n-\tif (!opts->only_input) {\n-\t\tLIST_HEAD(config_head);\n-\t\tLIST_HEAD(arg_head);\n-\t\tparse_trailers_from_config(&config_head);\n-\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n-\t\tlist_splice(&config_head, &arg_head);\n-\t\tprocess_trailers_lists(&head, &arg_head);\n-\t}\n-\n-\tformat_trailers(opts, &head, outfile);\n-\n-\tfree_trailers(&head);\n-\ttrailer_info_release(&info);\n-\n-\t/* Print the lines after the trailers as is */\n-\tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n-\n-\tif (opts->in_place)\n-\t\tif (rename_tempfile(&trailers_tempfile, file))\n-\t\t\tdie_errno(_(\"could not rename temporary file to %s\"), file);\n-\n-\tstrbuf_release(&sb);\n-}\n-\n void trailer_info_get(struct trailer_info *info, const char *str,\n \t\t      const struct process_trailer_options *opts)\n {\ndiff --git a/trailer.h b/trailer.h\nindex 37033e631a1..4f603e03ceb 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -81,15 +81,29 @@ struct process_trailer_options {\n \n #define PROCESS_TRAILER_OPTIONS_INIT {0}\n \n-void interpret_trailers(const struct process_trailer_options *opts,\n-\t\t\tstruct list_head *new_trailer_head,\n-\t\t\tconst char *file);\n+void parse_trailers_from_config(struct list_head *config_head);\n+\n+void parse_trailers_from_command_line_args(struct list_head *arg_head,\n+\t\t\t\t\t   struct list_head *new_trailer_head);\n+\n+void process_trailers_lists(struct list_head *head,\n+\t\t\t    struct list_head *arg_head);\n+\n+void parse_trailers(const struct process_trailer_options *opts,\n+\t\t    struct trailer_info *info,\n+\t\t    const char *str,\n+\t\t    struct list_head *head);\n \n void trailer_info_get(struct trailer_info *info, const char *str,\n \t\t      const struct process_trailer_options *opts);\n \n void trailer_info_release(struct trailer_info *info);\n \n+void trailer_config_init(void);\n+void format_trailers(const struct process_trailer_options *opts,\n+\t\t     struct list_head *trailers, FILE *outfile);\n+void free_trailers(struct list_head *trailers);\n+\n /*\n  * Format the trailers from the commit msg \"msg\" into the strbuf \"out\".\n  * Note two caveats about \"opts\":\n-- \ngitgitgadget\n\n"},{"id":"487436","messageId":"9b7747d550e87457195c40a49347bc749a7290d0.1706308737.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v2.git.1706308737.gitgitgadget@gmail.com","subject":"[PATCH v2 03/10] trailer: unify trailer formatting machinery","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-26T22:38:50Z","receivedAt":"2024-01-26T22:39:04Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nCurrently have two functions for formatting trailers exposed in\ntrailer.h:\n\n    void format_trailers(FILE *outfile, struct list_head *head,\n                        const struct process_trailer_options *opts);\n\n    void format_trailers_from_commit(struct strbuf *out, const char *msg,\n                                    const struct process_trailer_options *opts);\n\nand previously these functions, although similar enough (even taking the\nsame process_trailer_options struct pointer), did not build on each\nother.\n\nMake format_trailers_from_commit() rely on format_trailers(). Teach\nformat_trailers() to process trailers with the additional\nprocess_trailer_options fields like opts->key_only which is only used by\nformat_trailers_from_commit() and not builtin/interpret-trailers.c.\nWhile we're at it, reorder parameters to put the trailer processing\noptions first, and the out parameter (strbuf we write into) at the end.\n\nThis unification will allow us to delete the format_trailer_info() and\nprint_tok_val() functions in the next patch. They are not deleted here\nin order to keep the diff small.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |   6 +-\n pretty.c                     |   2 +-\n ref-filter.c                 |   2 +-\n trailer.c                    | 105 +++++++++++++++++++++++++++++------\n trailer.h                    |  19 +++----\n 5 files changed, 102 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 8556acde4aa..5352ee65bd1 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -140,6 +140,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n {\n \tLIST_HEAD(head);\n \tstruct strbuf sb = STRBUF_INIT;\n+\tstruct strbuf trailer_block = STRBUF_INIT;\n \tstruct trailer_info info;\n \tFILE *outfile = stdout;\n \n@@ -169,7 +170,10 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \t\tprocess_trailers_lists(&head, &arg_head);\n \t}\n \n-\tformat_trailers(opts, &head, outfile);\n+\t/* Print trailer block. */\n+\tformat_trailers(opts, &head, &trailer_block);\n+\tfwrite(trailer_block.buf, 1, trailer_block.len, outfile);\n+\tstrbuf_release(&trailer_block);\n \n \tfree_trailers(&head);\n \ttrailer_info_release(&info);\ndiff --git a/pretty.c b/pretty.c\nindex cf964b060cd..bdbed4295aa 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1759,7 +1759,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \t\t\t\tgoto trailer_out;\n \t\t}\n \t\tif (*arg == ')') {\n-\t\t\tformat_trailers_from_commit(sb, msg + c->subject_off, &opts);\n+\t\t\tformat_trailers_from_commit(&opts, msg + c->subject_off, sb);\n \t\t\tret = arg - placeholder + 1;\n \t\t}\n \ttrailer_out:\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 35b989e1dfe..d358953b0ce 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -1985,7 +1985,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct exp\n \t\t\tstruct strbuf s = STRBUF_INIT;\n \n \t\t\t/* Format the trailer info according to the trailer_opts given */\n-\t\t\tformat_trailers_from_commit(&s, subpos, &atom->u.contents.trailer_opts);\n+\t\t\tformat_trailers_from_commit(&atom->u.contents.trailer_opts, subpos, &s);\n \n \t\t\tv->s = strbuf_detach(&s, NULL);\n \t\t} else if (atom->u.contents.option == C_BARE)\ndiff --git a/trailer.c b/trailer.c\nindex d3899195876..7692bf9eb40 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -162,19 +162,6 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n-void format_trailers(const struct process_trailer_options *opts,\n-\t\t     struct list_head *trailers, FILE *outfile)\n-{\n-\tstruct list_head *pos;\n-\tstruct trailer_item *item;\n-\tlist_for_each(pos, trailers) {\n-\t\titem = list_entry(pos, struct trailer_item, list);\n-\t\tif ((!opts->trim_empty || strlen(item->value) > 0) &&\n-\t\t    (!opts->only_trailers || item->token))\n-\t\t\tprint_tok_val(outfile, item->token, item->value);\n-\t}\n-}\n-\n static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)\n {\n \tstruct trailer_item *new_item = xcalloc(1, sizeof(*new_item));\n@@ -984,6 +971,78 @@ static void unfold_value(struct strbuf *val)\n \tstrbuf_release(&out);\n }\n \n+void format_trailers(const struct process_trailer_options *opts,\n+\t\t     struct list_head *trailers,\n+\t\t     struct strbuf *out)\n+{\n+\tstruct list_head *pos;\n+\tstruct trailer_item *item;\n+\tint need_separator = 0;\n+\n+\tlist_for_each(pos, trailers) {\n+\t\titem = list_entry(pos, struct trailer_item, list);\n+\t\tif (item->token) {\n+\t\t\tchar c;\n+\n+\t\t\tstruct strbuf tok = STRBUF_INIT;\n+\t\t\tstruct strbuf val = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&tok, item->token);\n+\t\t\tstrbuf_addstr(&val, item->value);\n+\n+\t\t\t/*\n+\t\t\t * Skip key/value pairs where the value was empty. This\n+\t\t\t * can happen from trailers specified without a\n+\t\t\t * separator, like `--trailer \"Reviewed-by\"` (no\n+\t\t\t * corresponding value).\n+\t\t\t */\n+\t\t\tif (opts->trim_empty && !strlen(item->value))\n+\t\t\t\tcontinue;\n+\n+\t\t\tif (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n+\t\t\t\tif (opts->unfold)\n+\t\t\t\t\tunfold_value(&val);\n+\n+\t\t\t\tif (opts->separator && need_separator)\n+\t\t\t\t\tstrbuf_addbuf(out, opts->separator);\n+\t\t\t\tif (!opts->value_only)\n+\t\t\t\t\tstrbuf_addbuf(out, &tok);\n+\t\t\t\tif (!opts->key_only && !opts->value_only) {\n+\t\t\t\t\tif (opts->key_value_separator)\n+\t\t\t\t\t\tstrbuf_addbuf(out, opts->key_value_separator);\n+\t\t\t\t\telse {\n+\t\t\t\t\t\tc = last_non_space_char(tok.buf);\n+\t\t\t\t\t\tif (c) {\n+\t\t\t\t\t\t\tif (!strchr(separators, c))\n+\t\t\t\t\t\t\t\tstrbuf_addf(out, \"%c \", separators[0]);\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t\tif (!opts->key_only)\n+\t\t\t\t\tstrbuf_addbuf(out, &val);\n+\t\t\t\tif (!opts->separator)\n+\t\t\t\t\tstrbuf_addch(out, '\\n');\n+\n+\t\t\t\tneed_separator = 1;\n+\t\t\t}\n+\n+\t\t\tstrbuf_release(&tok);\n+\t\t\tstrbuf_release(&val);\n+\t\t} else if (!opts->only_trailers) {\n+\t\t\tif (opts->separator && need_separator) {\n+\t\t\t\tstrbuf_addbuf(out, opts->separator);\n+\t\t\t}\n+\t\t\tstrbuf_addstr(out, item->value);\n+\t\t\tif (opts->separator)\n+\t\t\t\tstrbuf_rtrim(out);\n+\t\t\telse\n+\t\t\t\tstrbuf_addch(out, '\\n');\n+\n+\t\t\tneed_separator = 1;\n+\t\t}\n+\n+\t}\n+}\n+\n /*\n  * Parse trailers in \"str\", populating the trailer info and \"head\"\n  * linked list structure.\n@@ -1144,13 +1203,25 @@ static void format_trailer_info(struct strbuf *out,\n \n }\n \n-void format_trailers_from_commit(struct strbuf *out, const char *msg,\n-\t\t\t\t const struct process_trailer_options *opts)\n+void format_trailers_from_commit(const struct process_trailer_options *opts,\n+\t\t\t\t const char *msg,\n+\t\t\t\t struct strbuf *out)\n {\n+\tLIST_HEAD(head);\n \tstruct trailer_info info;\n \n-\ttrailer_info_get(&info, msg, opts);\n-\tformat_trailer_info(out, &info, msg, opts);\n+\tparse_trailers(opts, &info, msg, &head);\n+\n+\t/* If we want the whole block untouched, we can take the fast path. */\n+\tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n+\t    !opts->separator && !opts->key_only && !opts->value_only &&\n+\t    !opts->key_value_separator) {\n+\t\tstrbuf_add(out, msg + info.trailer_block_start,\n+\t\t\t   info.trailer_block_end - info.trailer_block_start);\n+\t} else\n+\t\tformat_trailers(opts, &head, out);\n+\n+\tfree_trailers(&head);\n \ttrailer_info_release(&info);\n }\n \ndiff --git a/trailer.h b/trailer.h\nindex 4f603e03ceb..c309b01323d 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -101,22 +101,17 @@ void trailer_info_release(struct trailer_info *info);\n \n void trailer_config_init(void);\n void format_trailers(const struct process_trailer_options *opts,\n-\t\t     struct list_head *trailers, FILE *outfile);\n+\t\t     struct list_head *trailers,\n+\t\t     struct strbuf *out);\n void free_trailers(struct list_head *trailers);\n \n /*\n- * Format the trailers from the commit msg \"msg\" into the strbuf \"out\".\n- * Note two caveats about \"opts\":\n- *\n- *   - this is primarily a helper for pretty.c, and not\n- *     all of the flags are supported.\n- *\n- *   - this differs from process_trailers slightly in that we always format\n- *     only the trailer block itself, even if the \"only_trailers\" option is not\n- *     set.\n+ * Convenience function to format the trailers from the commit msg \"msg\" into\n+ * the strbuf \"out\". Reuses format_trailers internally.\n  */\n-void format_trailers_from_commit(struct strbuf *out, const char *msg,\n-\t\t\t\t const struct process_trailer_options *opts);\n+void format_trailers_from_commit(const struct process_trailer_options *opts,\n+\t\t\t\t const char *msg,\n+\t\t\t\t struct strbuf *out);\n \n /*\n  * An interface for iterating over the trailers found in a particular commit\n-- \ngitgitgadget\n\n"},{"id":"487437","messageId":"f1171f5202f576fdc73719d0ab184b0557733ec2.1706308737.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v2.git.1706308737.gitgitgadget@gmail.com","subject":"[PATCH v2 04/10] trailer: delete obsolete formatting functions","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-26T22:38:51Z","receivedAt":"2024-01-26T22:39:06Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 79 -------------------------------------------------------\n 1 file changed, 79 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 7692bf9eb40..71ea2bb67f8 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -144,24 +144,6 @@ static char last_non_space_char(const char *s)\n \treturn '\\0';\n }\n \n-static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n-{\n-\tchar c;\n-\n-\tif (!tok) {\n-\t\tfprintf(outfile, \"%s\\n\", val);\n-\t\treturn;\n-\t}\n-\n-\tc = last_non_space_char(tok);\n-\tif (!c)\n-\t\treturn;\n-\tif (strchr(separators, c))\n-\t\tfprintf(outfile, \"%s%s\\n\", tok, val);\n-\telse\n-\t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n-}\n-\n static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)\n {\n \tstruct trailer_item *new_item = xcalloc(1, sizeof(*new_item));\n@@ -1142,67 +1124,6 @@ void trailer_info_release(struct trailer_info *info)\n \tfree(info->trailers);\n }\n \n-static void format_trailer_info(struct strbuf *out,\n-\t\t\t\tconst struct trailer_info *info,\n-\t\t\t\tconst char *msg,\n-\t\t\t\tconst struct process_trailer_options *opts)\n-{\n-\tsize_t origlen = out->len;\n-\tsize_t i;\n-\n-\t/* If we want the whole block untouched, we can take the fast path. */\n-\tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n-\t    !opts->separator && !opts->key_only && !opts->value_only &&\n-\t    !opts->key_value_separator) {\n-\t\tstrbuf_add(out, msg + info->trailer_block_start,\n-\t\t\t   info->trailer_block_end - info->trailer_block_start);\n-\t\treturn;\n-\t}\n-\n-\tfor (i = 0; i < info->trailer_nr; i++) {\n-\t\tchar *trailer = info->trailers[i];\n-\t\tssize_t separator_pos = find_separator(trailer, separators);\n-\n-\t\tif (separator_pos >= 1) {\n-\t\t\tstruct strbuf tok = STRBUF_INIT;\n-\t\t\tstruct strbuf val = STRBUF_INIT;\n-\n-\t\t\tparse_trailer(&tok, &val, NULL, trailer, separator_pos);\n-\t\t\tif (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n-\t\t\t\tif (opts->unfold)\n-\t\t\t\t\tunfold_value(&val);\n-\n-\t\t\t\tif (opts->separator && out->len != origlen)\n-\t\t\t\t\tstrbuf_addbuf(out, opts->separator);\n-\t\t\t\tif (!opts->value_only)\n-\t\t\t\t\tstrbuf_addbuf(out, &tok);\n-\t\t\t\tif (!opts->key_only && !opts->value_only) {\n-\t\t\t\t\tif (opts->key_value_separator)\n-\t\t\t\t\t\tstrbuf_addbuf(out, opts->key_value_separator);\n-\t\t\t\t\telse\n-\t\t\t\t\t\tstrbuf_addstr(out, \": \");\n-\t\t\t\t}\n-\t\t\t\tif (!opts->key_only)\n-\t\t\t\t\tstrbuf_addbuf(out, &val);\n-\t\t\t\tif (!opts->separator)\n-\t\t\t\t\tstrbuf_addch(out, '\\n');\n-\t\t\t}\n-\t\t\tstrbuf_release(&tok);\n-\t\t\tstrbuf_release(&val);\n-\n-\t\t} else if (!opts->only_trailers) {\n-\t\t\tif (opts->separator && out->len != origlen) {\n-\t\t\t\tstrbuf_addbuf(out, opts->separator);\n-\t\t\t}\n-\t\t\tstrbuf_addstr(out, trailer);\n-\t\t\tif (opts->separator) {\n-\t\t\t\tstrbuf_rtrim(out);\n-\t\t\t}\n-\t\t}\n-\t}\n-\n-}\n-\n void format_trailers_from_commit(const struct process_trailer_options *opts,\n \t\t\t\t const char *msg,\n \t\t\t\t struct strbuf *out)\n-- \ngitgitgadget\n\n"},{"id":"487438","messageId":"5ba842b5005ce2099a4de2884d48bfc894ce4c2c.1706308737.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v2.git.1706308737.gitgitgadget@gmail.com","subject":"[PATCH v2 05/10] sequencer: use the trailer iterator","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-26T22:38:52Z","receivedAt":"2024-01-26T22:39:06Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis patch allows for the removal of \"trailer_info_get()\" from the\ntrailer.h API, which will be in the next patch.\n\nInstead of calling \"trailer_info_get()\", which is a low-level function\nin the trailers implementation (trailer.c), call\ntrailer_iterator_advance(), which was specifically designed for public\nconsumption in f0939a0eb1 (trailer: add interface for iterating over\ncommit trailers, 2020-09-27).\n\nAvoiding \"trailer_info_get()\" means we don't have to worry about options\nlike \"no_divider\" (relevant for parsing trailers). We also don't have to\ncheck for things like \"info.trailer_start == info.trailer_end\" to see\nwhether there were any trailers (instead we can just check to see\nwhether the iterator advanced at all).\n\nAlso, teach the iterator about non-trailer lines, by adding a new field\ncalled \"raw\" to hold both trailer and non-trailer lines. This is\nnecessary because a \"trailer block\" is a list of trailer lines of at\nleast 25% trailers (see 146245063e (trailer: allow non-trailers in\ntrailer block, 2016-10-21)), such that it may hold non-trailer lines.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/shortlog.c |  7 +++++--\n sequencer.c        | 35 +++++++++++++++--------------------\n trailer.c          | 17 +++++++++--------\n trailer.h          | 13 +++++++++++++\n 4 files changed, 42 insertions(+), 30 deletions(-)\n\ndiff --git a/builtin/shortlog.c b/builtin/shortlog.c\nindex 1307ed2b88a..dc8fd5a5532 100644\n--- a/builtin/shortlog.c\n+++ b/builtin/shortlog.c\n@@ -172,7 +172,7 @@ static void insert_records_from_trailers(struct shortlog *log,\n \t\t\t\t\t const char *oneline)\n {\n \tstruct trailer_iterator iter;\n-\tconst char *commit_buffer, *body;\n+\tconst char *commit_buffer, *body, *value;\n \tstruct strbuf ident = STRBUF_INIT;\n \n \tif (!log->trailers.nr)\n@@ -190,7 +190,10 @@ static void insert_records_from_trailers(struct shortlog *log,\n \n \ttrailer_iterator_init(&iter, body);\n \twhile (trailer_iterator_advance(&iter)) {\n-\t\tconst char *value = iter.val.buf;\n+\t\tif (!iter.is_trailer)\n+\t\t\tcontinue;\n+\n+\t\tvalue = iter.val.buf;\n \n \t\tif (!string_list_has_string(&log->trailers, iter.key.buf))\n \t\t\tcontinue;\ndiff --git a/sequencer.c b/sequencer.c\nindex 3cc88d8a800..bc7c82c5271 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -319,37 +319,32 @@ static const char *get_todo_path(const struct replay_opts *opts)\n static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,\n \tsize_t ignore_footer)\n {\n-\tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n-\tstruct trailer_info info;\n-\tsize_t i;\n-\tint found_sob = 0, found_sob_last = 0;\n-\tchar saved_char;\n-\n-\topts.no_divider = 1;\n+\tstruct trailer_iterator iter;\n+\tsize_t i = 0, found_sob = 0;\n+\tchar saved_char = sb->buf[sb->len - ignore_footer];\n \n \tif (ignore_footer) {\n-\t\tsaved_char = sb->buf[sb->len - ignore_footer];\n \t\tsb->buf[sb->len - ignore_footer] = '\\0';\n \t}\n \n-\ttrailer_info_get(&info, sb->buf, &opts);\n+\ttrailer_iterator_init(&iter, sb->buf);\n+\twhile (trailer_iterator_advance(&iter)) {\n+\t\ti++;\n+\t\tif (sob &&\n+\t\t    iter.is_trailer &&\n+\t\t    !strncmp(iter.raw, sob->buf, sob->len)) {\n+\t\t\tfound_sob = i;\n+\t\t}\n+\t}\n+\ttrailer_iterator_release(&iter);\n \n \tif (ignore_footer)\n \t\tsb->buf[sb->len - ignore_footer] = saved_char;\n \n-\tif (info.trailer_block_start == info.trailer_block_end)\n+\tif (!i)\n \t\treturn 0;\n \n-\tfor (i = 0; i < info.trailer_nr; i++)\n-\t\tif (sob && !strncmp(info.trailers[i], sob->buf, sob->len)) {\n-\t\t\tfound_sob = 1;\n-\t\t\tif (i == info.trailer_nr - 1)\n-\t\t\t\tfound_sob_last = 1;\n-\t\t}\n-\n-\ttrailer_info_release(&info);\n-\n-\tif (found_sob_last)\n+\tif (found_sob == i)\n \t\treturn 3;\n \tif (found_sob)\n \t\treturn 2;\ndiff --git a/trailer.c b/trailer.c\nindex 71ea2bb67f8..5bcc9b0006c 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1158,17 +1158,18 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n \n int trailer_iterator_advance(struct trailer_iterator *iter)\n {\n-\twhile (iter->internal.cur < iter->internal.info.trailer_nr) {\n-\t\tchar *trailer = iter->internal.info.trailers[iter->internal.cur++];\n-\t\tint separator_pos = find_separator(trailer, separators);\n-\n-\t\tif (separator_pos < 1)\n-\t\t\tcontinue; /* not a real trailer */\n-\n+\tchar *line;\n+\tint separator_pos;\n+\tif (iter->internal.cur < iter->internal.info.trailer_nr) {\n+\t\tline = iter->internal.info.trailers[iter->internal.cur++];\n+\t\tseparator_pos = find_separator(line, separators);\n+\t\titer->is_trailer = (separator_pos > 0);\n+\n+\t\titer->raw = line;\n \t\tstrbuf_reset(&iter->key);\n \t\tstrbuf_reset(&iter->val);\n \t\tparse_trailer(&iter->key, &iter->val, NULL,\n-\t\t\t      trailer, separator_pos);\n+\t\t\t      line, separator_pos);\n \t\tunfold_value(&iter->val);\n \t\treturn 1;\n \t}\ndiff --git a/trailer.h b/trailer.h\nindex c309b01323d..a47f18b1525 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -127,6 +127,19 @@ struct trailer_iterator {\n \tstruct strbuf key;\n \tstruct strbuf val;\n \n+\t/*\n+\t * Raw line (e.g., \"foo: bar baz\") before being parsed as a trailer\n+\t * key/val pair as part of a trailer block. A trailer block can be\n+\t * either 100% trailer lines, or mixed in with non-trailer lines (in\n+\t * which case at least 25% must be trailer lines).\n+\t */\n+\tconst char *raw;\n+\n+\t/*\n+\t * 1 if the raw line was parsed as a trailer line (key/val pair).\n+\t */\n+\tint is_trailer;\n+\n \t/* private */\n \tstruct {\n \t\tstruct trailer_info info;\n-- \ngitgitgadget\n\n"},{"id":"487439","messageId":"f0ac2f6c4b90912c8f1a78f3c59098b21134560f.1706308737.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v2.git.1706308737.gitgitgadget@gmail.com","subject":"[PATCH v2 06/10] trailer: make trailer_info struct private","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-26T22:38:53Z","receivedAt":"2024-01-26T22:39:07Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nIn 13211ae23f (trailer: separate public from internal portion of\ntrailer_iterator, 2023-09-09) we moved trailer_info behind an anonymous\nstruct to discourage use by trailer.h API users. However it still left\nopen the possibility of external use of trailer_info itself. Now that\nthere are no external users of trailer_info, we can make this struct\nprivate.\n\nMake this struct private by putting its definition inside trailer.c.\nThis has two benefits:\n\n  (1) it makes the surface area of the public facing\n      interface (trailer.h) smaller, and\n\n  (2) external API users are unable to peer inside this struct (because\n      it is only ever exposed as an opaque pointer).\n\nThis change exposes some deficiencies in the API, mainly with regard to\ninformation about the location of the trailer block that was parsed.\nExpose new API functions to access this information (needed by\nbuiltin/interpret-trailers.c).\n\nThe idea in this patch to hide implementation details behind an \"opaque\npointer\" is also known as the \"pimpl\" (pointer to implementation) idiom\nin C++ and is a common pattern in that language (where, for example,\nabstract classes only have pointers to concrete classes).\n\nHowever, the original inspiration to use this idiom does not come from\nC++, but instead the book \"C Interfaces and Implementations: Techniques\nfor Creating Reusable Software\" [1]. This book recommends opaque\npointers as a good design principle for designing C libraries, using the\nterm \"interface\" as the functions defined in *.h (header) files and\n\"implementation\" as the corresponding *.c file which define the\ninterfaces.\n\nThe book says this about opaque pointers:\n\n    ... clients can manipulate such pointers freely, but they can’t\n    dereference them; that is, they can’t look at the innards of the\n    structure pointed to by them. Only the implementation has that\n    privilege. Opaque pointers hide representation details and help\n    catch errors.\n\nIn our case, \"struct trailer_info\" is now hidden from clients, and the\nways in which this opaque pointer can be used is limited to the richness\nof the trailer.h file. In other words, trailer.h exclusively controls\nexactly how \"trailer_info\" pointers are to be used.\n\n[1] Hanson, David R. \"C Interfaces and Implementations: Techniques for\n    Creating Reusable Software\". Addison Wesley, 1997. p. 22\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  13 +--\n trailer.c                    | 154 +++++++++++++++++++++++------------\n trailer.h                    |  37 ++-------\n 3 files changed, 117 insertions(+), 87 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 5352ee65bd1..9e6ed6b65e2 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -141,7 +141,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \tLIST_HEAD(head);\n \tstruct strbuf sb = STRBUF_INIT;\n \tstruct strbuf trailer_block = STRBUF_INIT;\n-\tstruct trailer_info info;\n+\tstruct trailer_info *info;\n \tFILE *outfile = stdout;\n \n \ttrailer_config_init();\n@@ -151,13 +151,13 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \tif (opts->in_place)\n \t\toutfile = create_in_place_tempfile(file);\n \n-\tparse_trailers(opts, &info, sb.buf, &head);\n+\tinfo = parse_trailers(opts, sb.buf, &head);\n \n \t/* Print the lines before the trailers */\n \tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf, 1, info.trailer_block_start, outfile);\n+\t\tfwrite(sb.buf, 1, trailer_block_start(info), outfile);\n \n-\tif (!opts->only_trailers && !info.blank_line_before_trailer)\n+\tif (!opts->only_trailers && !blank_line_before_trailer_block(info))\n \t\tfprintf(outfile, \"\\n\");\n \n \n@@ -176,11 +176,12 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \tstrbuf_release(&trailer_block);\n \n \tfree_trailers(&head);\n-\ttrailer_info_release(&info);\n \n \t/* Print the lines after the trailers as is */\n \tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n+\t\tfwrite(sb.buf + trailer_block_end(info), 1, sb.len - trailer_block_end(info), outfile);\n+\n+\ttrailer_info_release(info);\n \n \tif (opts->in_place)\n \t\tif (rename_tempfile(&trailers_tempfile, file))\ndiff --git a/trailer.c b/trailer.c\nindex 5bcc9b0006c..63774cd068d 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -11,6 +11,27 @@\n  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n  */\n \n+struct trailer_info {\n+\t/*\n+\t * True if there is a blank line before the location pointed to by\n+\t * trailer_block_start.\n+\t */\n+\tint blank_line_before_trailer;\n+\n+\t/*\n+\t * Offsets to the trailer block start and end positions in the input\n+\t * string. If no trailer block is found, these are both set to the\n+\t * \"true\" end of the input (find_end_of_log_message()).\n+\t */\n+\tsize_t trailer_block_start, trailer_block_end;\n+\n+\t/*\n+\t * Array of trailers found.\n+\t */\n+\tchar **trailers;\n+\tsize_t trailer_nr;\n+};\n+\n struct conf_info {\n \tchar *name;\n \tchar *key;\n@@ -1025,20 +1046,72 @@ void format_trailers(const struct process_trailer_options *opts,\n \t}\n }\n \n+static struct trailer_info *trailer_info_new(void)\n+{\n+\tstruct trailer_info *info = xcalloc(1, sizeof(*info));\n+\treturn info;\n+}\n+\n+static struct trailer_info *trailer_info_get(const struct process_trailer_options *opts,\n+\t\t\t\t\t     const char *str)\n+{\n+\tstruct trailer_info *info = trailer_info_new();\n+\tsize_t end_of_log_message = 0, trailer_block_start = 0;\n+\tstruct strbuf **trailer_lines, **ptr;\n+\tchar **trailer_strings = NULL;\n+\tsize_t nr = 0, alloc = 0;\n+\tchar **last = NULL;\n+\n+\ttrailer_config_init();\n+\n+\tend_of_log_message = find_end_of_log_message(str, opts->no_divider);\n+\ttrailer_block_start = find_trailer_block_start(str, end_of_log_message);\n+\n+\ttrailer_lines = strbuf_split_buf(str + trailer_block_start,\n+\t\t\t\t\t end_of_log_message - trailer_block_start,\n+\t\t\t\t\t '\\n',\n+\t\t\t\t\t 0);\n+\tfor (ptr = trailer_lines; *ptr; ptr++) {\n+\t\tif (last && isspace((*ptr)->buf[0])) {\n+\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\t\tstrbuf_attach(&sb, *last, strlen(*last), strlen(*last));\n+\t\t\tstrbuf_addbuf(&sb, *ptr);\n+\t\t\t*last = strbuf_detach(&sb, NULL);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tALLOC_GROW(trailer_strings, nr + 1, alloc);\n+\t\ttrailer_strings[nr] = strbuf_detach(*ptr, NULL);\n+\t\tlast = find_separator(trailer_strings[nr], separators) >= 1\n+\t\t\t? &trailer_strings[nr]\n+\t\t\t: NULL;\n+\t\tnr++;\n+\t}\n+\tstrbuf_list_free(trailer_lines);\n+\n+\tinfo->blank_line_before_trailer = ends_with_blank_line(str,\n+\t\t\t\t\t\t\t       trailer_block_start);\n+\tinfo->trailer_block_start = trailer_block_start;\n+\tinfo->trailer_block_end = end_of_log_message;\n+\tinfo->trailers = trailer_strings;\n+\tinfo->trailer_nr = nr;\n+\n+\treturn info;\n+}\n+\n /*\n  * Parse trailers in \"str\", populating the trailer info and \"head\"\n  * linked list structure.\n  */\n-void parse_trailers(const struct process_trailer_options *opts,\n-\t\t    struct trailer_info *info,\n-\t\t    const char *str,\n-\t\t    struct list_head *head)\n+struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n+\t\t\t\t    const char *str,\n+\t\t\t\t    struct list_head *head)\n {\n+\tstruct trailer_info *info;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n \tsize_t i;\n \n-\ttrailer_info_get(info, str, opts);\n+\tinfo = trailer_info_get(opts, str);\n \n \tfor (i = 0; i < info->trailer_nr; i++) {\n \t\tint separator_pos;\n@@ -1062,6 +1135,8 @@ void parse_trailers(const struct process_trailer_options *opts,\n \t\t\t\t\t strbuf_detach(&val, NULL));\n \t\t}\n \t}\n+\n+\treturn info;\n }\n \n void free_trailers(struct list_head *trailers)\n@@ -1073,47 +1148,19 @@ void free_trailers(struct list_head *trailers)\n \t}\n }\n \n-void trailer_info_get(struct trailer_info *info, const char *str,\n-\t\t      const struct process_trailer_options *opts)\n+size_t trailer_block_start(struct trailer_info *info)\n {\n-\tsize_t end_of_log_message = 0, trailer_block_start = 0;\n-\tstruct strbuf **trailer_lines, **ptr;\n-\tchar **trailer_strings = NULL;\n-\tsize_t nr = 0, alloc = 0;\n-\tchar **last = NULL;\n-\n-\ttrailer_config_init();\n-\n-\tend_of_log_message = find_end_of_log_message(str, opts->no_divider);\n-\ttrailer_block_start = find_trailer_block_start(str, end_of_log_message);\n+\treturn info->trailer_block_start;\n+}\n \n-\ttrailer_lines = strbuf_split_buf(str + trailer_block_start,\n-\t\t\t\t\t end_of_log_message - trailer_block_start,\n-\t\t\t\t\t '\\n',\n-\t\t\t\t\t 0);\n-\tfor (ptr = trailer_lines; *ptr; ptr++) {\n-\t\tif (last && isspace((*ptr)->buf[0])) {\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstrbuf_attach(&sb, *last, strlen(*last), strlen(*last));\n-\t\t\tstrbuf_addbuf(&sb, *ptr);\n-\t\t\t*last = strbuf_detach(&sb, NULL);\n-\t\t\tcontinue;\n-\t\t}\n-\t\tALLOC_GROW(trailer_strings, nr + 1, alloc);\n-\t\ttrailer_strings[nr] = strbuf_detach(*ptr, NULL);\n-\t\tlast = find_separator(trailer_strings[nr], separators) >= 1\n-\t\t\t? &trailer_strings[nr]\n-\t\t\t: NULL;\n-\t\tnr++;\n-\t}\n-\tstrbuf_list_free(trailer_lines);\n+size_t trailer_block_end(struct trailer_info *info)\n+{\n+\treturn info->trailer_block_end;\n+}\n \n-\tinfo->blank_line_before_trailer = ends_with_blank_line(str,\n-\t\t\t\t\t\t\t       trailer_block_start);\n-\tinfo->trailer_block_start = trailer_block_start;\n-\tinfo->trailer_block_end = end_of_log_message;\n-\tinfo->trailers = trailer_strings;\n-\tinfo->trailer_nr = nr;\n+int blank_line_before_trailer_block(struct trailer_info *info)\n+{\n+\treturn info->blank_line_before_trailer;\n }\n \n void trailer_info_release(struct trailer_info *info)\n@@ -1122,6 +1169,7 @@ void trailer_info_release(struct trailer_info *info)\n \tfor (i = 0; i < info->trailer_nr; i++)\n \t\tfree(info->trailers[i]);\n \tfree(info->trailers);\n+\tfree(info);\n }\n \n void format_trailers_from_commit(const struct process_trailer_options *opts,\n@@ -1129,30 +1177,30 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \t\t\t\t struct strbuf *out)\n {\n \tLIST_HEAD(head);\n-\tstruct trailer_info info;\n-\n-\tparse_trailers(opts, &info, msg, &head);\n+\tstruct trailer_info *info = parse_trailers(opts, msg, &head);\n \n \t/* If we want the whole block untouched, we can take the fast path. */\n \tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n \t    !opts->separator && !opts->key_only && !opts->value_only &&\n \t    !opts->key_value_separator) {\n-\t\tstrbuf_add(out, msg + info.trailer_block_start,\n-\t\t\t   info.trailer_block_end - info.trailer_block_start);\n+\t\tstrbuf_add(out, msg + info->trailer_block_start,\n+\t\t\t   info->trailer_block_end - info->trailer_block_start);\n \t} else\n \t\tformat_trailers(opts, &head, out);\n \n \tfree_trailers(&head);\n-\ttrailer_info_release(&info);\n+\ttrailer_info_release(info);\n }\n \n void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n {\n \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n+\tstruct trailer_info *internal = trailer_info_new();\n \tstrbuf_init(&iter->key, 0);\n \tstrbuf_init(&iter->val, 0);\n \topts.no_divider = 1;\n-\ttrailer_info_get(&iter->internal.info, msg, &opts);\n+\titer->internal.info = internal;\n+\titer->internal.info = trailer_info_get(&opts, msg);\n \titer->internal.cur = 0;\n }\n \n@@ -1160,8 +1208,8 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n {\n \tchar *line;\n \tint separator_pos;\n-\tif (iter->internal.cur < iter->internal.info.trailer_nr) {\n-\t\tline = iter->internal.info.trailers[iter->internal.cur++];\n+\tif (iter->internal.cur < iter->internal.info->trailer_nr) {\n+\t\tline = iter->internal.info->trailers[iter->internal.cur++];\n \t\tseparator_pos = find_separator(line, separators);\n \t\titer->is_trailer = (separator_pos > 0);\n \n@@ -1178,7 +1226,7 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n \n void trailer_iterator_release(struct trailer_iterator *iter)\n {\n-\ttrailer_info_release(&iter->internal.info);\n+\ttrailer_info_release(iter->internal.info);\n \tstrbuf_release(&iter->val);\n \tstrbuf_release(&iter->key);\n }\ndiff --git a/trailer.h b/trailer.h\nindex a47f18b1525..fff16a29a55 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -4,6 +4,8 @@\n #include \"list.h\"\n #include \"strbuf.h\"\n \n+struct trailer_info;\n+\n enum trailer_where {\n \tWHERE_DEFAULT,\n \tWHERE_END,\n@@ -29,27 +31,6 @@ int trailer_set_where(enum trailer_where *item, const char *value);\n int trailer_set_if_exists(enum trailer_if_exists *item, const char *value);\n int trailer_set_if_missing(enum trailer_if_missing *item, const char *value);\n \n-struct trailer_info {\n-\t/*\n-\t * True if there is a blank line before the location pointed to by\n-\t * trailer_block_start.\n-\t */\n-\tint blank_line_before_trailer;\n-\n-\t/*\n-\t * Offsets to the trailer block start and end positions in the input\n-\t * string. If no trailer block is found, these are both set to the\n-\t * \"true\" end of the input (find_end_of_log_message()).\n-\t */\n-\tsize_t trailer_block_start, trailer_block_end;\n-\n-\t/*\n-\t * Array of trailers found.\n-\t */\n-\tchar **trailers;\n-\tsize_t trailer_nr;\n-};\n-\n /*\n  * A list that represents newly-added trailers, such as those provided\n  * with the --trailer command line option of git-interpret-trailers.\n@@ -89,13 +70,13 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n void process_trailers_lists(struct list_head *head,\n \t\t\t    struct list_head *arg_head);\n \n-void parse_trailers(const struct process_trailer_options *opts,\n-\t\t    struct trailer_info *info,\n-\t\t    const char *str,\n-\t\t    struct list_head *head);\n+struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n+\t\t\t\t    const char *str,\n+\t\t\t\t    struct list_head *head);\n \n-void trailer_info_get(struct trailer_info *info, const char *str,\n-\t\t      const struct process_trailer_options *opts);\n+size_t trailer_block_start(struct trailer_info *info);\n+size_t trailer_block_end(struct trailer_info *info);\n+int blank_line_before_trailer_block(struct trailer_info *info);\n \n void trailer_info_release(struct trailer_info *info);\n \n@@ -142,7 +123,7 @@ struct trailer_iterator {\n \n \t/* private */\n \tstruct {\n-\t\tstruct trailer_info info;\n+\t\tstruct trailer_info *info;\n \t\tsize_t cur;\n \t} internal;\n };\n-- \ngitgitgadget\n\n"},{"id":"487440","messageId":"291aa83af5583b696eaa003625b3c4c27e1ae8ba.1706308737.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v2.git.1706308737.gitgitgadget@gmail.com","subject":"[PATCH v2 07/10] trailer: spread usage of \"trailer_block\" language","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-26T22:38:54Z","receivedAt":"2024-01-26T22:39:07Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nDeprecate the \"trailer_info\" struct name and replace it with\n\"trailer_block\". The main reason is to help readability, because\n\"trailer_info\" on the surface sounds like it's about a single trailer\nwhen in reality it is a collection of contiguous lines, at least 25% of\nwhich are trailers.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c | 26 +++++-----\n trailer.c                    | 99 ++++++++++++++++++------------------\n trailer.h                    | 18 +++----\n 3 files changed, 71 insertions(+), 72 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 9e6ed6b65e2..9e41fa20b5f 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -140,8 +140,8 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n {\n \tLIST_HEAD(head);\n \tstruct strbuf sb = STRBUF_INIT;\n-\tstruct strbuf trailer_block = STRBUF_INIT;\n-\tstruct trailer_info *info;\n+\tstruct strbuf tb = STRBUF_INIT;\n+\tstruct trailer_block *trailer_block;\n \tFILE *outfile = stdout;\n \n \ttrailer_config_init();\n@@ -151,13 +151,13 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \tif (opts->in_place)\n \t\toutfile = create_in_place_tempfile(file);\n \n-\tinfo = parse_trailers(opts, sb.buf, &head);\n+\ttrailer_block = parse_trailers(opts, sb.buf, &head);\n \n-\t/* Print the lines before the trailers */\n+\t/* Print the lines before the trailer block */\n \tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf, 1, trailer_block_start(info), outfile);\n+\t\tfwrite(sb.buf, 1, trailer_block_start(trailer_block), outfile);\n \n-\tif (!opts->only_trailers && !blank_line_before_trailer_block(info))\n+\tif (!opts->only_trailers && !blank_line_before_trailer_block(trailer_block))\n \t\tfprintf(outfile, \"\\n\");\n \n \n@@ -171,17 +171,17 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \t}\n \n \t/* Print trailer block. */\n-\tformat_trailers(opts, &head, &trailer_block);\n-\tfwrite(trailer_block.buf, 1, trailer_block.len, outfile);\n-\tstrbuf_release(&trailer_block);\n+\tformat_trailers(opts, &head, &tb);\n+\tfwrite(tb.buf, 1, tb.len, outfile);\n+\tstrbuf_release(&tb);\n \n \tfree_trailers(&head);\n \n-\t/* Print the lines after the trailers as is */\n+\t/* Print the lines after the trailer block as is */\n \tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf + trailer_block_end(info), 1, sb.len - trailer_block_end(info), outfile);\n-\n-\ttrailer_info_release(info);\n+\t\tfwrite(sb.buf + trailer_block_end(trailer_block),\n+\t\t       1, sb.len - trailer_block_end(trailer_block), outfile);\n+\ttrailer_block_release(trailer_block);\n \n \tif (opts->in_place)\n \t\tif (rename_tempfile(&trailers_tempfile, file))\ndiff --git a/trailer.c b/trailer.c\nindex 63774cd068d..e2a48bea0ae 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -11,19 +11,20 @@\n  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n  */\n \n-struct trailer_info {\n+struct trailer_block {\n \t/*\n \t * True if there is a blank line before the location pointed to by\n-\t * trailer_block_start.\n+\t * \"start\".\n \t */\n \tint blank_line_before_trailer;\n \n \t/*\n-\t * Offsets to the trailer block start and end positions in the input\n-\t * string. If no trailer block is found, these are both set to the\n-\t * \"true\" end of the input (find_end_of_log_message()).\n+\t * The locations of the start and end positions of the trailer block\n+\t * found, as offsets from the beginning of the source text from which\n+\t * this trailer block was parsed. If no trailer block is found, these\n+\t * are both set to 0.\n \t */\n-\tsize_t trailer_block_start, trailer_block_end;\n+\tsize_t start, end;\n \n \t/*\n \t * Array of trailers found.\n@@ -1046,16 +1047,16 @@ void format_trailers(const struct process_trailer_options *opts,\n \t}\n }\n \n-static struct trailer_info *trailer_info_new(void)\n+static struct trailer_block *trailer_block_new(void)\n {\n-\tstruct trailer_info *info = xcalloc(1, sizeof(*info));\n-\treturn info;\n+\tstruct trailer_block *trailer_block = xcalloc(1, sizeof(*trailer_block));\n+\treturn trailer_block;\n }\n \n-static struct trailer_info *trailer_info_get(const struct process_trailer_options *opts,\n-\t\t\t\t\t     const char *str)\n+static struct trailer_block *trailer_block_get(const struct process_trailer_options *opts,\n+\t\t\t\t\t       const char *str)\n {\n-\tstruct trailer_info *info = trailer_info_new();\n+\tstruct trailer_block *trailer_block = trailer_block_new();\n \tsize_t end_of_log_message = 0, trailer_block_start = 0;\n \tstruct strbuf **trailer_lines, **ptr;\n \tchar **trailer_strings = NULL;\n@@ -1088,34 +1089,34 @@ static struct trailer_info *trailer_info_get(const struct process_trailer_option\n \t}\n \tstrbuf_list_free(trailer_lines);\n \n-\tinfo->blank_line_before_trailer = ends_with_blank_line(str,\n-\t\t\t\t\t\t\t       trailer_block_start);\n-\tinfo->trailer_block_start = trailer_block_start;\n-\tinfo->trailer_block_end = end_of_log_message;\n-\tinfo->trailers = trailer_strings;\n-\tinfo->trailer_nr = nr;\n+\ttrailer_block->blank_line_before_trailer = ends_with_blank_line(str,\n+\t\t\t\t\t\t\t\t\ttrailer_block_start);\n+\ttrailer_block->start = trailer_block_start;\n+\ttrailer_block->end = end_of_log_message;\n+\ttrailer_block->trailers = trailer_strings;\n+\ttrailer_block->trailer_nr = nr;\n \n-\treturn info;\n+\treturn trailer_block;\n }\n \n /*\n- * Parse trailers in \"str\", populating the trailer info and \"head\"\n- * linked list structure.\n+ * Parse trailers in \"str\", populating the trailer_block info and \"head\" linked\n+ * list structure.\n  */\n-struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n-\t\t\t\t    const char *str,\n-\t\t\t\t    struct list_head *head)\n+struct trailer_block *parse_trailers(const struct process_trailer_options *opts,\n+\t\t\t\t     const char *str,\n+\t\t\t\t     struct list_head *head)\n {\n-\tstruct trailer_info *info;\n+\tstruct trailer_block *trailer_block;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n \tsize_t i;\n \n-\tinfo = trailer_info_get(opts, str);\n+\ttrailer_block = trailer_block_get(opts, str);\n \n-\tfor (i = 0; i < info->trailer_nr; i++) {\n+\tfor (i = 0; i < trailer_block->trailer_nr; i++) {\n \t\tint separator_pos;\n-\t\tchar *trailer = info->trailers[i];\n+\t\tchar *trailer = trailer_block->trailers[i];\n \t\tif (trailer[0] == comment_line_char)\n \t\t\tcontinue;\n \t\tseparator_pos = find_separator(trailer, separators);\n@@ -1136,7 +1137,7 @@ struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n \t\t}\n \t}\n \n-\treturn info;\n+\treturn trailer_block;\n }\n \n void free_trailers(struct list_head *trailers)\n@@ -1148,28 +1149,28 @@ void free_trailers(struct list_head *trailers)\n \t}\n }\n \n-size_t trailer_block_start(struct trailer_info *info)\n+size_t trailer_block_start(struct trailer_block *trailer_block)\n {\n-\treturn info->trailer_block_start;\n+\treturn trailer_block->start;\n }\n \n-size_t trailer_block_end(struct trailer_info *info)\n+size_t trailer_block_end(struct trailer_block *trailer_block)\n {\n-\treturn info->trailer_block_end;\n+\treturn trailer_block->end;\n }\n \n-int blank_line_before_trailer_block(struct trailer_info *info)\n+int blank_line_before_trailer_block(struct trailer_block *trailer_block)\n {\n-\treturn info->blank_line_before_trailer;\n+\treturn trailer_block->blank_line_before_trailer;\n }\n \n-void trailer_info_release(struct trailer_info *info)\n+void trailer_block_release(struct trailer_block *trailer_block)\n {\n \tsize_t i;\n-\tfor (i = 0; i < info->trailer_nr; i++)\n-\t\tfree(info->trailers[i]);\n-\tfree(info->trailers);\n-\tfree(info);\n+\tfor (i = 0; i < trailer_block->trailer_nr; i++)\n+\t\tfree(trailer_block->trailers[i]);\n+\tfree(trailer_block->trailers);\n+\tfree(trailer_block);\n }\n \n void format_trailers_from_commit(const struct process_trailer_options *opts,\n@@ -1177,30 +1178,28 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \t\t\t\t struct strbuf *out)\n {\n \tLIST_HEAD(head);\n-\tstruct trailer_info *info = parse_trailers(opts, msg, &head);\n+\tstruct trailer_block *trailer_block = parse_trailers(opts, msg, &head);\n \n \t/* If we want the whole block untouched, we can take the fast path. */\n \tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n \t    !opts->separator && !opts->key_only && !opts->value_only &&\n \t    !opts->key_value_separator) {\n-\t\tstrbuf_add(out, msg + info->trailer_block_start,\n-\t\t\t   info->trailer_block_end - info->trailer_block_start);\n+\t\tstrbuf_add(out, msg + trailer_block->start,\n+\t\t\t   trailer_block->end - trailer_block->start);\n \t} else\n \t\tformat_trailers(opts, &head, out);\n \n \tfree_trailers(&head);\n-\ttrailer_info_release(info);\n+\ttrailer_block_release(trailer_block);\n }\n \n void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n {\n \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n-\tstruct trailer_info *internal = trailer_info_new();\n \tstrbuf_init(&iter->key, 0);\n \tstrbuf_init(&iter->val, 0);\n \topts.no_divider = 1;\n-\titer->internal.info = internal;\n-\titer->internal.info = trailer_info_get(&opts, msg);\n+\titer->internal.trailer_block = trailer_block_get(&opts, msg);\n \titer->internal.cur = 0;\n }\n \n@@ -1208,8 +1207,8 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n {\n \tchar *line;\n \tint separator_pos;\n-\tif (iter->internal.cur < iter->internal.info->trailer_nr) {\n-\t\tline = iter->internal.info->trailers[iter->internal.cur++];\n+\tif (iter->internal.cur < iter->internal.trailer_block->trailer_nr) {\n+\t\tline = iter->internal.trailer_block->trailers[iter->internal.cur++];\n \t\tseparator_pos = find_separator(line, separators);\n \t\titer->is_trailer = (separator_pos > 0);\n \n@@ -1226,7 +1225,7 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n \n void trailer_iterator_release(struct trailer_iterator *iter)\n {\n-\ttrailer_info_release(iter->internal.info);\n+\ttrailer_block_release(iter->internal.trailer_block);\n \tstrbuf_release(&iter->val);\n \tstrbuf_release(&iter->key);\n }\ndiff --git a/trailer.h b/trailer.h\nindex fff16a29a55..be14dd17185 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -4,7 +4,7 @@\n #include \"list.h\"\n #include \"strbuf.h\"\n \n-struct trailer_info;\n+struct trailer_block;\n \n enum trailer_where {\n \tWHERE_DEFAULT,\n@@ -70,15 +70,15 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n void process_trailers_lists(struct list_head *head,\n \t\t\t    struct list_head *arg_head);\n \n-struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n-\t\t\t\t    const char *str,\n-\t\t\t\t    struct list_head *head);\n+struct trailer_block *parse_trailers(const struct process_trailer_options *opts,\n+\t\t\t\t     const char *str,\n+\t\t\t\t     struct list_head *head);\n \n-size_t trailer_block_start(struct trailer_info *info);\n-size_t trailer_block_end(struct trailer_info *info);\n-int blank_line_before_trailer_block(struct trailer_info *info);\n+size_t trailer_block_start(struct trailer_block *trailer_block);\n+size_t trailer_block_end(struct trailer_block *trailer_block);\n+int blank_line_before_trailer_block(struct trailer_block *trailer_block);\n \n-void trailer_info_release(struct trailer_info *info);\n+void trailer_block_release(struct trailer_block *trailer_block);\n \n void trailer_config_init(void);\n void format_trailers(const struct process_trailer_options *opts,\n@@ -123,7 +123,7 @@ struct trailer_iterator {\n \n \t/* private */\n \tstruct {\n-\t\tstruct trailer_info *info;\n+\t\tstruct trailer_block *trailer_block;\n \t\tsize_t cur;\n \t} internal;\n };\n-- \ngitgitgadget\n\n"},{"id":"487441","messageId":"64ee07d0b53cf46d8f1aa1b7d26528f35a26b662.1706308737.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v2.git.1706308737.gitgitgadget@gmail.com","subject":"[PATCH v2 08/10] trailer: prepare to move parse_trailers_from_command_line_args() to builtin","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-26T22:38:55Z","receivedAt":"2024-01-26T22:39:08Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nExpose more functions in the trailer.h API, in preparation for moving\nout\n\n    parse_trailers_from_command_line_args()\n\nto interpret-trailer.c, because the trailer API should not be concerned\nwith command line arguments (as they have nothing to do with trailers\nthemselves). The interpret-trailers builtin is the only caller of this\nfunction.\n\nAlso rename \"conf_info\" to \"trailer_conf\" for readability, dropping the\nlow-value \"_info\" suffix as we did earlier in this series for\n\"trailer_info\" to \"trailer_block\".\n\nHelped-by: Josh Steadmon <steadmon@google.com>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 66 +++++++++++++++++++++++++++----------------------------\n trailer.h | 10 +++++++++\n 2 files changed, 42 insertions(+), 34 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex e2a48bea0ae..d362b9a604e 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -33,7 +33,7 @@ struct trailer_block {\n \tsize_t trailer_nr;\n };\n \n-struct conf_info {\n+struct trailer_conf {\n \tchar *name;\n \tchar *key;\n \tchar *command;\n@@ -43,7 +43,7 @@ struct conf_info {\n \tenum trailer_if_missing if_missing;\n };\n \n-static struct conf_info default_conf_info;\n+static struct trailer_conf default_trailer_conf;\n \n struct trailer_item {\n \tstruct list_head list;\n@@ -59,7 +59,7 @@ struct arg_item {\n \tstruct list_head list;\n \tchar *token;\n \tchar *value;\n-\tstruct conf_info conf;\n+\tstruct trailer_conf conf;\n };\n \n static LIST_HEAD(conf_head);\n@@ -210,7 +210,7 @@ static int check_if_different(struct trailer_item *in_tok,\n \treturn 1;\n }\n \n-static char *apply_command(struct conf_info *conf, const char *arg)\n+static char *apply_command(struct trailer_conf *conf, const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -424,7 +424,8 @@ int trailer_set_if_missing(enum trailer_if_missing *item, const char *value)\n \treturn 0;\n }\n \n-static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n+void duplicate_trailer_conf(struct trailer_conf *dst,\n+\t\t\t    const struct trailer_conf *src)\n {\n \t*dst = *src;\n \tdst->name = xstrdup_or_null(src->name);\n@@ -447,7 +448,7 @@ static struct arg_item *get_conf_item(const char *name)\n \n \t/* Item does not already exists, create it */\n \tCALLOC_ARRAY(item, 1);\n-\tduplicate_conf(&item->conf, &default_conf_info);\n+\tduplicate_trailer_conf(&item->conf, &default_trailer_conf);\n \titem->conf.name = xstrdup(name);\n \n \tlist_add_tail(&item->list, &conf_head);\n@@ -482,17 +483,17 @@ static int git_trailer_default_config(const char *conf_key, const char *value,\n \tvariable_name = strrchr(trailer_item, '.');\n \tif (!variable_name) {\n \t\tif (!strcmp(trailer_item, \"where\")) {\n-\t\t\tif (trailer_set_where(&default_conf_info.where,\n+\t\t\tif (trailer_set_where(&default_trailer_conf.where,\n \t\t\t\t\t      value) < 0)\n \t\t\t\twarning(_(\"unknown value '%s' for key '%s'\"),\n \t\t\t\t\tvalue, conf_key);\n \t\t} else if (!strcmp(trailer_item, \"ifexists\")) {\n-\t\t\tif (trailer_set_if_exists(&default_conf_info.if_exists,\n+\t\t\tif (trailer_set_if_exists(&default_trailer_conf.if_exists,\n \t\t\t\t\t\t  value) < 0)\n \t\t\t\twarning(_(\"unknown value '%s' for key '%s'\"),\n \t\t\t\t\tvalue, conf_key);\n \t\t} else if (!strcmp(trailer_item, \"ifmissing\")) {\n-\t\t\tif (trailer_set_if_missing(&default_conf_info.if_missing,\n+\t\t\tif (trailer_set_if_missing(&default_trailer_conf.if_missing,\n \t\t\t\t\t\t   value) < 0)\n \t\t\t\twarning(_(\"unknown value '%s' for key '%s'\"),\n \t\t\t\t\tvalue, conf_key);\n@@ -511,7 +512,7 @@ static int git_trailer_config(const char *conf_key, const char *value,\n {\n \tconst char *trailer_item, *variable_name;\n \tstruct arg_item *item;\n-\tstruct conf_info *conf;\n+\tstruct trailer_conf *conf;\n \tchar *name = NULL;\n \tenum trailer_info_type type;\n \tint i;\n@@ -585,9 +586,9 @@ void trailer_config_init(void)\n \t\treturn;\n \n \t/* Default config must be setup first */\n-\tdefault_conf_info.where = WHERE_END;\n-\tdefault_conf_info.if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;\n-\tdefault_conf_info.if_missing = MISSING_ADD;\n+\tdefault_trailer_conf.where = WHERE_END;\n+\tdefault_trailer_conf.if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;\n+\tdefault_trailer_conf.if_missing = MISSING_ADD;\n \tgit_config(git_trailer_default_config, NULL);\n \tgit_config(git_trailer_config, NULL);\n \tconfigured = 1;\n@@ -620,7 +621,7 @@ static int token_matches_item(const char *tok, struct arg_item *item, size_t tok\n  * distinguished from the non-well-formed-line case (in which this function\n  * returns -1) because some callers of this function need such a distinction.\n  */\n-static ssize_t find_separator(const char *line, const char *separators)\n+ssize_t find_separator(const char *line, const char *separators)\n {\n \tint whitespace_found = 0;\n \tconst char *c;\n@@ -645,28 +646,28 @@ static ssize_t find_separator(const char *line, const char *separators)\n  *\n  * If separator_pos is -1, interpret the whole trailer as a token.\n  */\n-static void parse_trailer(struct strbuf *tok, struct strbuf *val,\n-\t\t\t const struct conf_info **conf, const char *trailer,\n-\t\t\t ssize_t separator_pos)\n+void parse_trailer(const char *line, ssize_t separator_pos,\n+\t\t   struct strbuf *tok, struct strbuf *val,\n+\t\t   const struct trailer_conf **conf)\n {\n \tstruct arg_item *item;\n \tsize_t tok_len;\n \tstruct list_head *pos;\n \n \tif (separator_pos != -1) {\n-\t\tstrbuf_add(tok, trailer, separator_pos);\n+\t\tstrbuf_add(tok, line, separator_pos);\n \t\tstrbuf_trim(tok);\n-\t\tstrbuf_addstr(val, trailer + separator_pos + 1);\n+\t\tstrbuf_addstr(val, line + separator_pos + 1);\n \t\tstrbuf_trim(val);\n \t} else {\n-\t\tstrbuf_addstr(tok, trailer);\n+\t\tstrbuf_addstr(tok, line);\n \t\tstrbuf_trim(tok);\n \t}\n \n \t/* Lookup if the token matches something in the config */\n \ttok_len = token_len_without_separator(tok->buf, tok->len);\n \tif (conf)\n-\t\t*conf = &default_conf_info;\n+\t\t*conf = &default_trailer_conf;\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct arg_item, list);\n \t\tif (token_matches_item(tok->buf, item, tok_len)) {\n@@ -691,13 +692,13 @@ static struct trailer_item *add_trailer_item(struct list_head *head, char *tok,\n }\n \n static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n-\t\t\t const struct conf_info *conf,\n+\t\t\t const struct trailer_conf *conf,\n \t\t\t const struct new_trailer_item *new_trailer_item)\n {\n \tstruct arg_item *new_item = xcalloc(1, sizeof(*new_item));\n \tnew_item->token = tok;\n \tnew_item->value = val;\n-\tduplicate_conf(&new_item->conf, conf);\n+\tduplicate_trailer_conf(&new_item->conf, conf);\n \tif (new_trailer_item) {\n \t\tif (new_trailer_item->where != WHERE_DEFAULT)\n \t\t\tnew_item->conf.where = new_trailer_item->where;\n@@ -730,7 +731,7 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n {\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n-\tconst struct conf_info *conf;\n+\tconst struct trailer_conf *conf;\n \tstruct list_head *pos;\n \n \t/*\n@@ -753,8 +754,7 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n \t\t\t      (int) sb.len, sb.buf);\n \t\t\tstrbuf_release(&sb);\n \t\t} else {\n-\t\t\tparse_trailer(&tok, &val, &conf, tr->text,\n-\t\t\t\t      separator_pos);\n+\t\t\tparse_trailer(tr->text, separator_pos, &tok, &val, &conf);\n \t\t\tadd_arg_item(arg_head,\n \t\t\t\t     strbuf_detach(&tok, NULL),\n \t\t\t\t     strbuf_detach(&val, NULL),\n@@ -1116,20 +1116,19 @@ struct trailer_block *parse_trailers(const struct process_trailer_options *opts,\n \n \tfor (i = 0; i < trailer_block->trailer_nr; i++) {\n \t\tint separator_pos;\n-\t\tchar *trailer = trailer_block->trailers[i];\n-\t\tif (trailer[0] == comment_line_char)\n+\t\tchar *line = trailer_block->trailers[i];\n+\t\tif (line[0] == comment_line_char)\n \t\t\tcontinue;\n-\t\tseparator_pos = find_separator(trailer, separators);\n+\t\tseparator_pos = find_separator(line, separators);\n \t\tif (separator_pos >= 1) {\n-\t\t\tparse_trailer(&tok, &val, NULL, trailer,\n-\t\t\t\t      separator_pos);\n+\t\t\tparse_trailer(line, separator_pos, &tok, &val, NULL);\n \t\t\tif (opts->unfold)\n \t\t\t\tunfold_value(&val);\n \t\t\tadd_trailer_item(head,\n \t\t\t\t\t strbuf_detach(&tok, NULL),\n \t\t\t\t\t strbuf_detach(&val, NULL));\n \t\t} else if (!opts->only_trailers) {\n-\t\t\tstrbuf_addstr(&val, trailer);\n+\t\t\tstrbuf_addstr(&val, line);\n \t\t\tstrbuf_strip_suffix(&val, \"\\n\");\n \t\t\tadd_trailer_item(head,\n \t\t\t\t\t NULL,\n@@ -1215,8 +1214,7 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n \t\titer->raw = line;\n \t\tstrbuf_reset(&iter->key);\n \t\tstrbuf_reset(&iter->val);\n-\t\tparse_trailer(&iter->key, &iter->val, NULL,\n-\t\t\t      line, separator_pos);\n+\t\tparse_trailer(line, separator_pos, &iter->key, &iter->val, NULL);\n \t\tunfold_value(&iter->val);\n \t\treturn 1;\n \t}\ndiff --git a/trailer.h b/trailer.h\nindex be14dd17185..c6aa96dd6be 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -5,6 +5,7 @@\n #include \"strbuf.h\"\n \n struct trailer_block;\n+struct trailer_conf;\n \n enum trailer_where {\n \tWHERE_DEFAULT,\n@@ -45,6 +46,9 @@ struct new_trailer_item {\n \tenum trailer_if_missing if_missing;\n };\n \n+void duplicate_trailer_conf(struct trailer_conf *dst,\n+\t\t\t    const struct trailer_conf *src);\n+\n struct process_trailer_options {\n \tint in_place;\n \tint trim_empty;\n@@ -70,6 +74,12 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n void process_trailers_lists(struct list_head *head,\n \t\t\t    struct list_head *arg_head);\n \n+ssize_t find_separator(const char *line, const char *separators);\n+\n+void parse_trailer(const char *line, ssize_t separator_pos,\n+\t\t   struct strbuf *tok, struct strbuf *val,\n+\t\t   const struct trailer_conf **conf);\n+\n struct trailer_block *parse_trailers(const struct process_trailer_options *opts,\n \t\t\t\t     const char *str,\n \t\t\t\t     struct list_head *head);\n-- \ngitgitgadget\n\n"},{"id":"487442","messageId":"1b4bdde65bcb375ce9ef2345814330df92e6d932.1706308737.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v2.git.1706308737.gitgitgadget@gmail.com","subject":"[PATCH v2 09/10] trailer: move arg handling to interpret-trailers.c","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-26T22:38:56Z","receivedAt":"2024-01-26T22:39:09Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nWe don't move the \"arg_item\" struct to interpret-trailers.c, because it\nis now a struct that contains information about trailers that should be\ninjected into the input text's own trailers. We will rename this\nlanguage as such in a follow-up patch to keep the diff here small.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c | 88 ++++++++++++++++++++++--------------\n trailer.c                    | 63 +++++++++++++++++++-------\n trailer.h                    | 13 +++++-\n 3 files changed, 113 insertions(+), 51 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 9e41fa20b5f..ad9b9a9f9ef 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -45,23 +45,17 @@ static int option_parse_if_missing(const struct option *opt,\n \treturn trailer_set_if_missing(opt->value, arg);\n }\n \n-static void new_trailers_clear(struct list_head *trailers)\n-{\n-\tstruct list_head *pos, *tmp;\n-\tstruct new_trailer_item *item;\n-\n-\tlist_for_each_safe(pos, tmp, trailers) {\n-\t\titem = list_entry(pos, struct new_trailer_item, list);\n-\t\tlist_del(pos);\n-\t\tfree(item);\n-\t}\n-}\n+static char *cl_separators;\n \n static int option_parse_trailer(const struct option *opt,\n \t\t\t\t   const char *arg, int unset)\n {\n \tstruct list_head *trailers = opt->value;\n-\tstruct new_trailer_item *item;\n+\tstruct strbuf tok = STRBUF_INIT;\n+\tstruct strbuf val = STRBUF_INIT;\n+\tconst struct trailer_conf *conf;\n+\tstruct trailer_conf *conf_current = new_trailer_conf();\n+\tssize_t separator_pos;\n \n \tif (unset) {\n \t\tnew_trailers_clear(trailers);\n@@ -71,12 +65,31 @@ static int option_parse_trailer(const struct option *opt,\n \tif (!arg)\n \t\treturn -1;\n \n-\titem = xmalloc(sizeof(*item));\n-\titem->text = arg;\n-\titem->where = where;\n-\titem->if_exists = if_exists;\n-\titem->if_missing = if_missing;\n-\tlist_add_tail(&item->list, trailers);\n+\tseparator_pos = find_separator(arg, cl_separators);\n+\tif (separator_pos) {\n+\t\tparse_trailer(arg, separator_pos, &tok, &val, &conf);\n+\t\tduplicate_trailer_conf(conf_current, conf);\n+\n+\t\t/*\n+\t\t * Override conf_current with settings specified via CLI flags.\n+\t\t */\n+\t\ttrailer_conf_set(where, if_exists, if_missing, conf_current);\n+\n+\t\tadd_arg_item(strbuf_detach(&tok, NULL),\n+\t\t\t     strbuf_detach(&val, NULL),\n+\t\t\t     conf_current,\n+\t\t\t     trailers);\n+\t} else {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_addstr(&sb, arg);\n+\t\tstrbuf_trim(&sb);\n+\t\terror(_(\"empty trailer token in trailer '%.*s'\"),\n+\t\t\t(int) sb.len, sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t}\n+\n+\tfree(conf_current);\n+\n \treturn 0;\n }\n \n@@ -135,7 +148,7 @@ static void read_input_file(struct strbuf *sb, const char *file)\n }\n \n static void interpret_trailers(const struct process_trailer_options *opts,\n-\t\t\t       struct list_head *new_trailer_head,\n+\t\t\t       struct list_head *arg_trailers,\n \t\t\t       const char *file)\n {\n \tLIST_HEAD(head);\n@@ -144,8 +157,6 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \tstruct trailer_block *trailer_block;\n \tFILE *outfile = stdout;\n \n-\ttrailer_config_init();\n-\n \tread_input_file(&sb, file);\n \n \tif (opts->in_place)\n@@ -162,12 +173,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \n \n \tif (!opts->only_input) {\n-\t\tLIST_HEAD(config_head);\n-\t\tLIST_HEAD(arg_head);\n-\t\tparse_trailers_from_config(&config_head);\n-\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n-\t\tlist_splice(&config_head, &arg_head);\n-\t\tprocess_trailers_lists(&head, &arg_head);\n+\t\tprocess_trailers_lists(&head, arg_trailers);\n \t}\n \n \t/* Print trailer block. */\n@@ -193,7 +199,8 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n {\n \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n-\tLIST_HEAD(trailers);\n+\tLIST_HEAD(configured_trailers);\n+\tLIST_HEAD(arg_trailers);\n \n \tstruct option options[] = {\n \t\tOPT_BOOL(0, \"in-place\", &opts.in_place, N_(\"edit files in place\")),\n@@ -212,33 +219,48 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK_F(0, \"parse\", &opts, NULL, N_(\"alias for --only-trailers --only-input --unfold\"),\n \t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse),\n \t\tOPT_BOOL(0, \"no-divider\", &opts.no_divider, N_(\"do not treat \\\"---\\\" as the end of input\")),\n-\t\tOPT_CALLBACK(0, \"trailer\", &trailers, N_(\"trailer\"),\n+\t\tOPT_CALLBACK(0, \"trailer\", &arg_trailers, N_(\"trailer\"),\n \t\t\t\tN_(\"trailer(s) to add\"), option_parse_trailer),\n \t\tOPT_END()\n \t};\n \n \tgit_config(git_default_config, NULL);\n+\ttrailer_config_init();\n+\n+\tif (!opts.only_input) {\n+\t\tparse_trailers_from_config(&configured_trailers);\n+\t}\n+\n+\t/*\n+\t* In command-line arguments, '=' is accepted (in addition to the\n+\t* separators that are defined).\n+\t*/\n+\tcl_separators = xstrfmt(\"=%s\", default_separators());\n \n \targc = parse_options(argc, argv, prefix, options,\n \t\t\t     git_interpret_trailers_usage, 0);\n \n-\tif (opts.only_input && !list_empty(&trailers))\n+\tfree(cl_separators);\n+\n+\tif (opts.only_input && !list_empty(&arg_trailers))\n \t\tusage_msg_opt(\n \t\t\t_(\"--trailer with --only-input does not make sense\"),\n \t\t\tgit_interpret_trailers_usage,\n \t\t\toptions);\n \n+\tlist_splice(&configured_trailers, &arg_trailers);\n+\n \tif (argc) {\n \t\tint i;\n \t\tfor (i = 0; i < argc; i++)\n-\t\t\tinterpret_trailers(&opts, &trailers, argv[i]);\n+\t\t\tinterpret_trailers(&opts, &arg_trailers, argv[i]);\n \t} else {\n \t\tif (opts.in_place)\n \t\t\tdie(_(\"no input file given for in-place editing\"));\n-\t\tinterpret_trailers(&opts, &trailers, NULL);\n+\t\tinterpret_trailers(&opts, &arg_trailers, NULL);\n \t}\n \n-\tnew_trailers_clear(&trailers);\n+\tnew_trailers_clear(&arg_trailers);\n \n \treturn 0;\n }\ndiff --git a/trailer.c b/trailer.c\nindex d362b9a604e..1865d04bdf2 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -66,6 +66,11 @@ static LIST_HEAD(conf_head);\n \n static char *separators = \":\";\n \n+const char *default_separators(void)\n+{\n+\treturn separators;\n+}\n+\n static int configured;\n \n #define TRAILER_ARG_STRING \"$ARG\"\n@@ -424,6 +429,25 @@ int trailer_set_if_missing(enum trailer_if_missing *item, const char *value)\n \treturn 0;\n }\n \n+void trailer_conf_set(enum trailer_where where,\n+\t\t      enum trailer_if_exists if_exists,\n+\t\t      enum trailer_if_missing if_missing,\n+\t\t      struct trailer_conf *conf)\n+{\n+\tif (where != WHERE_DEFAULT)\n+\t\tconf->where = where;\n+\tif (if_exists != EXISTS_DEFAULT)\n+\t\tconf->if_exists = if_exists;\n+\tif (if_missing != MISSING_DEFAULT)\n+\t\tconf->if_missing = if_missing;\n+}\n+\n+struct trailer_conf *new_trailer_conf(void)\n+{\n+\tstruct trailer_conf *new = xcalloc(1, sizeof(*new));\n+\treturn new;\n+}\n+\n void duplicate_trailer_conf(struct trailer_conf *dst,\n \t\t\t    const struct trailer_conf *src)\n {\n@@ -642,6 +666,9 @@ ssize_t find_separator(const char *line, const char *separators)\n /*\n  * Obtain the token, value, and conf from the given trailer.\n  *\n+ * The conf needs special handling. We first read hardcoded defaults, and\n+ * override them if we find a matching trailer configuration in the config.\n+ *\n  * separator_pos must not be 0, since the token cannot be an empty string.\n  *\n  * If separator_pos is -1, interpret the whole trailer as a token.\n@@ -691,22 +718,14 @@ static struct trailer_item *add_trailer_item(struct list_head *head, char *tok,\n \treturn new_item;\n }\n \n-static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n-\t\t\t const struct trailer_conf *conf,\n-\t\t\t const struct new_trailer_item *new_trailer_item)\n+void add_arg_item(char *tok, char *val, const struct trailer_conf *conf,\n+\t\t  struct list_head *arg_head)\n+\n {\n \tstruct arg_item *new_item = xcalloc(1, sizeof(*new_item));\n \tnew_item->token = tok;\n \tnew_item->value = val;\n \tduplicate_trailer_conf(&new_item->conf, conf);\n-\tif (new_trailer_item) {\n-\t\tif (new_trailer_item->where != WHERE_DEFAULT)\n-\t\t\tnew_item->conf.where = new_trailer_item->where;\n-\t\tif (new_trailer_item->if_exists != EXISTS_DEFAULT)\n-\t\t\tnew_item->conf.if_exists = new_trailer_item->if_exists;\n-\t\tif (new_trailer_item->if_missing != MISSING_DEFAULT)\n-\t\t\tnew_item->conf.if_missing = new_trailer_item->if_missing;\n-\t}\n \tlist_add_tail(&new_item->list, arg_head);\n }\n \n@@ -719,10 +738,10 @@ void parse_trailers_from_config(struct list_head *config_head)\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct arg_item, list);\n \t\tif (item->conf.command)\n-\t\t\tadd_arg_item(config_head,\n-\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n+\t\t\tadd_arg_item(xstrdup(token_from_item(item, NULL)),\n \t\t\t\t     xstrdup(\"\"),\n-\t\t\t\t     &item->conf, NULL);\n+\t\t\t\t     &item->conf,\n+\t\t\t\t     config_head);\n \t}\n }\n \n@@ -755,10 +774,10 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n \t\t\tstrbuf_release(&sb);\n \t\t} else {\n \t\t\tparse_trailer(tr->text, separator_pos, &tok, &val, &conf);\n-\t\t\tadd_arg_item(arg_head,\n-\t\t\t\t     strbuf_detach(&tok, NULL),\n+\t\t\tadd_arg_item(strbuf_detach(&tok, NULL),\n \t\t\t\t     strbuf_detach(&val, NULL),\n-\t\t\t\t     conf, tr);\n+\t\t\t\t     conf,\n+\t\t\t\t     arg_head);\n \t\t}\n \t}\n \n@@ -1148,6 +1167,16 @@ void free_trailers(struct list_head *trailers)\n \t}\n }\n \n+void new_trailers_clear(struct list_head *trailers)\n+{\n+\tstruct list_head *pos, *p;\n+\n+\tlist_for_each_safe(pos, p, trailers) {\n+\t\tlist_del(pos);\n+\t\tfree_arg_item(list_entry(pos, struct arg_item, list));\n+\t}\n+}\n+\n size_t trailer_block_start(struct trailer_block *trailer_block)\n {\n \treturn trailer_block->start;\ndiff --git a/trailer.h b/trailer.h\nindex c6aa96dd6be..e01437160cf 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -46,9 +46,20 @@ struct new_trailer_item {\n \tenum trailer_if_missing if_missing;\n };\n \n+void trailer_conf_set(enum trailer_where where,\n+\t\t      enum trailer_if_exists if_exists,\n+\t\t      enum trailer_if_missing if_missing,\n+\t\t      struct trailer_conf *conf);\n+\n+struct trailer_conf *new_trailer_conf(void);\n void duplicate_trailer_conf(struct trailer_conf *dst,\n \t\t\t    const struct trailer_conf *src);\n \n+const char *default_separators(void);\n+\n+void add_arg_item(char *tok, char *val, const struct trailer_conf *conf,\n+\t\t  struct list_head *arg_head);\n+\n struct process_trailer_options {\n \tint in_place;\n \tint trim_empty;\n@@ -95,7 +106,7 @@ void format_trailers(const struct process_trailer_options *opts,\n \t\t     struct list_head *trailers,\n \t\t     struct strbuf *out);\n void free_trailers(struct list_head *trailers);\n-\n+void new_trailers_clear(struct list_head *new_trailers);\n /*\n  * Convenience function to format the trailers from the commit msg \"msg\" into\n  * the strbuf \"out\". Reuses format_trailers internally.\n-- \ngitgitgadget\n\n"},{"id":"487443","messageId":"ed67ebf8647bf7ed7d3effb65b4759cde0ebf759.1706308737.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v2.git.1706308737.gitgitgadget@gmail.com","subject":"[PATCH v2 10/10] trailer: delete obsolete argument handling code from API","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-26T22:38:57Z","receivedAt":"2024-01-26T22:39:10Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis commit was not squashed with its parent in order to keep the diff\nseparate (to make the additive changes in the parent easier to read).\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 39 ---------------------------------------\n trailer.h | 17 -----------------\n 2 files changed, 56 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 1865d04bdf2..8016908039f 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -745,45 +745,6 @@ void parse_trailers_from_config(struct list_head *config_head)\n \t}\n }\n \n-void parse_trailers_from_command_line_args(struct list_head *arg_head,\n-\t\t\t\t\t   struct list_head *new_trailer_head)\n-{\n-\tstruct strbuf tok = STRBUF_INIT;\n-\tstruct strbuf val = STRBUF_INIT;\n-\tconst struct trailer_conf *conf;\n-\tstruct list_head *pos;\n-\n-\t/*\n-\t * In command-line arguments, '=' is accepted (in addition to the\n-\t * separators that are defined).\n-\t */\n-\tchar *cl_separators = xstrfmt(\"=%s\", separators);\n-\n-\t/* Add an arg item for each trailer on the command line */\n-\tlist_for_each(pos, new_trailer_head) {\n-\t\tstruct new_trailer_item *tr =\n-\t\t\tlist_entry(pos, struct new_trailer_item, list);\n-\t\tssize_t separator_pos = find_separator(tr->text, cl_separators);\n-\n-\t\tif (separator_pos == 0) {\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstrbuf_addstr(&sb, tr->text);\n-\t\t\tstrbuf_trim(&sb);\n-\t\t\terror(_(\"empty trailer token in trailer '%.*s'\"),\n-\t\t\t      (int) sb.len, sb.buf);\n-\t\t\tstrbuf_release(&sb);\n-\t\t} else {\n-\t\t\tparse_trailer(tr->text, separator_pos, &tok, &val, &conf);\n-\t\t\tadd_arg_item(strbuf_detach(&tok, NULL),\n-\t\t\t\t     strbuf_detach(&val, NULL),\n-\t\t\t\t     conf,\n-\t\t\t\t     arg_head);\n-\t\t}\n-\t}\n-\n-\tfree(cl_separators);\n-}\n-\n static const char *next_line(const char *str)\n {\n \tconst char *nl = strchrnul(str, '\\n');\ndiff --git a/trailer.h b/trailer.h\nindex e01437160cf..a71b770c564 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -32,20 +32,6 @@ int trailer_set_where(enum trailer_where *item, const char *value);\n int trailer_set_if_exists(enum trailer_if_exists *item, const char *value);\n int trailer_set_if_missing(enum trailer_if_missing *item, const char *value);\n \n-/*\n- * A list that represents newly-added trailers, such as those provided\n- * with the --trailer command line option of git-interpret-trailers.\n- */\n-struct new_trailer_item {\n-\tstruct list_head list;\n-\n-\tconst char *text;\n-\n-\tenum trailer_where where;\n-\tenum trailer_if_exists if_exists;\n-\tenum trailer_if_missing if_missing;\n-};\n-\n void trailer_conf_set(enum trailer_where where,\n \t\t      enum trailer_if_exists if_exists,\n \t\t      enum trailer_if_missing if_missing,\n@@ -79,9 +65,6 @@ struct process_trailer_options {\n \n void parse_trailers_from_config(struct list_head *config_head);\n \n-void parse_trailers_from_command_line_args(struct list_head *arg_head,\n-\t\t\t\t\t   struct list_head *new_trailer_head);\n-\n void process_trailers_lists(struct list_head *head,\n \t\t\t    struct list_head *arg_head);\n \n-- \ngitgitgadget\n"},{"id":"487464","messageId":"owlyh6iy2grk.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"1b4bdde65bcb375ce9ef2345814330df92e6d932.1706308737.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 09/10] trailer: move arg handling to interpret-trailers.c","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-01-28T05:01:19Z","receivedAt":"2024-01-28T05:01:21Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> diff --git a/trailer.h b/trailer.h\n> index c6aa96dd6be..e01437160cf 100644\n> --- a/trailer.h\n> +++ b/trailer.h\n> @@ -46,9 +46,20 @@ struct new_trailer_item {\n>  \tenum trailer_if_missing if_missing;\n>  };\n>\n> +void trailer_conf_set(enum trailer_where where,\n> +\t\t      enum trailer_if_exists if_exists,\n> +\t\t      enum trailer_if_missing if_missing,\n> +\t\t      struct trailer_conf *conf);\n> +\n> +struct trailer_conf *new_trailer_conf(void);\n>  void duplicate_trailer_conf(struct trailer_conf *dst,\n>  \t\t\t    const struct trailer_conf *src);\n>\n> +const char *default_separators(void);\n> +\n> +void add_arg_item(char *tok, char *val, const struct trailer_conf *conf,\n> +\t\t  struct list_head *arg_head);\n\nLooks like I forgot to add a \"trailer\" keyword for these API functions.\nWill rename to \"trailer_default_separators()\" and\n\"trailer_add_arg_item()\" in next reroll.\n"},{"id":"487465","messageId":"owlyede22c8e.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"1b4bdde65bcb375ce9ef2345814330df92e6d932.1706308737.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 09/10] trailer: move arg handling to interpret-trailers.c","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-01-28T06:39:13Z","receivedAt":"2024-01-28T06:39:16Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Linus Arver <linusa@google.com>\n>\n> We don't move the \"arg_item\" struct to interpret-trailers.c, because it\n> is now a struct that contains information about trailers that should be\n> injected into the input text's own trailers. We will rename this\n> language as such in a follow-up patch to keep the diff here small.\n\nI forgot to add this follow-up patch in this series. That will be patch\n11 in v3, which renames \"arg_item\" to \"trailer_template\" (the idea being\nthat trailer templates are used to create new trailers, using the\ntemplate as a guide).\n\n> diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\n> index 9e41fa20b5f..ad9b9a9f9ef 100644\n> --- a/builtin/interpret-trailers.c\n> +++ b/builtin/interpret-trailers.c\n> @@ -45,23 +45,17 @@ static int option_parse_if_missing(const struct option *opt,\n>  \treturn trailer_set_if_missing(opt->value, arg);\n>  }\n>  \n> -static void new_trailers_clear(struct list_head *trailers)\n> -{\n> -\tstruct list_head *pos, *tmp;\n> -\tstruct new_trailer_item *item;\n> -\n> -\tlist_for_each_safe(pos, tmp, trailers) {\n> -\t\titem = list_entry(pos, struct new_trailer_item, list);\n> -\t\tlist_del(pos);\n> -\t\tfree(item);\n> -\t}\n> -}\n\nThis function will also get promoted to the API, and will be renamed to\n\"free_new_trailers()\" initially in patch 9, but renamed to\n\"free_trailer_templates()\" in patch 11 (we wait until patch 11 to\nintroduce the term \"template\").\n"},{"id":"487565","messageId":"ZbhByped3D0-NBAs@google.com","threadId":"60715","inReplyTo":"9b7747d550e87457195c40a49347bc749a7290d0.1706308737.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 03/10] trailer: unify trailer formatting machinery","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2024-01-30T00:24:42Z","receivedAt":"2024-01-30T00:24:47Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2024.01.26 22:38, Linus Arver via GitGitGadget wrote:\n> From: Linus Arver <linusa@google.com>\n> \n> Currently have two functions for formatting trailers exposed in\n> trailer.h:\n> \n>     void format_trailers(FILE *outfile, struct list_head *head,\n>                         const struct process_trailer_options *opts);\n> \n>     void format_trailers_from_commit(struct strbuf *out, const char *msg,\n>                                     const struct process_trailer_options *opts);\n> \n> and previously these functions, although similar enough (even taking the\n> same process_trailer_options struct pointer), did not build on each\n> other.\n> \n> Make format_trailers_from_commit() rely on format_trailers(). Teach\n> format_trailers() to process trailers with the additional\n> process_trailer_options fields like opts->key_only which is only used by\n> format_trailers_from_commit() and not builtin/interpret-trailers.c.\n> While we're at it, reorder parameters to put the trailer processing\n> options first, and the out parameter (strbuf we write into) at the end.\n> \n> This unification will allow us to delete the format_trailer_info() and\n> print_tok_val() functions in the next patch. They are not deleted here\n> in order to keep the diff small.\n\nUnfortunately this breaks the build:\n\ntrailer.c:1145:13: error: ‘format_trailer_info’ defined but not used [-Werror=unused-function]\n\nand\n\ntrailer.c:147:13: error: ‘print_tok_val’ defined but not used [-Werror=unused-function]\n\nWhile separating this patch from the deletion does make it easier to\nreview, it may make bisection more difficult.\n"},{"id":"487567","messageId":"ZbhGUYcxEaeOXPAi@google.com","threadId":"60715","inReplyTo":"e2d3ed9b5b6d67273c22671374daf7695c67709f.1706308737.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 01/10] trailer: prepare to expose functions as part of API","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2024-01-30T00:44:01Z","receivedAt":"2024-01-30T00:44:07Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2024.01.26 22:38, Linus Arver via GitGitGadget wrote:\n> From: Linus Arver <linusa@google.com>\n> \n> In the next patch, we will move \"process_trailers\" from trailer.c to\n> builtin/interpret-trailers.c. That move will necessitate the growth of\n> the trailer.h API, forcing us to expose some additional functions in\n> trailer.h.\n> \n> Rename relevant functions so that they include the term \"trailer\" in\n> their name, so that clients of the API will be able to easily identify\n> them by their \"trailer\" moniker, just like all the other functions\n> already exposed by trailer.h.\n> \n> The the opportunity to start putting trailer processions options (opts)\n\nNitpick: typo in the commit message.\ns/The the opportunity/Take the opportunity/ ?\n\n> as the first parameter. This will be the pattern going forward in this\n> series.\n> \n> Helped-by: Junio C Hamano <gitster@pobox.com>\n> Signed-off-by: Linus Arver <linusa@google.com>\n> ---\n>  builtin/interpret-trailers.c |  4 ++--\n>  trailer.c                    | 26 +++++++++++++-------------\n>  trailer.h                    |  6 +++---\n>  3 files changed, 18 insertions(+), 18 deletions(-)\n"},{"id":"487570","messageId":"owlybk9335i5.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"ZbhGUYcxEaeOXPAi@google.com","subject":"Re: [PATCH v2 01/10] trailer: prepare to expose functions as part of API","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-01-30T02:43:46Z","receivedAt":"2024-01-30T02:43:48Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> On 2024.01.26 22:38, Linus Arver via GitGitGadget wrote:\n>> From: Linus Arver <linusa@google.com>\n>> \n>> In the next patch, we will move \"process_trailers\" from trailer.c to\n>> builtin/interpret-trailers.c. That move will necessitate the growth of\n>> the trailer.h API, forcing us to expose some additional functions in\n>> trailer.h.\n>> \n>> Rename relevant functions so that they include the term \"trailer\" in\n>> their name, so that clients of the API will be able to easily identify\n>> them by their \"trailer\" moniker, just like all the other functions\n>> already exposed by trailer.h.\n>> \n>> The the opportunity to start putting trailer processions options (opts)\n>\n> Nitpick: typo in the commit message.\n> s/The the opportunity/Take the opportunity/ ?\n\nAck, will update in reroll.\n"},{"id":"487571","messageId":"owly8r4734sz.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"ZbhByped3D0-NBAs@google.com","subject":"Re: [PATCH v2 03/10] trailer: unify trailer formatting machinery","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-01-30T02:58:52Z","receivedAt":"2024-01-30T02:58:54Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> On 2024.01.26 22:38, Linus Arver via GitGitGadget wrote:\n>> From: Linus Arver <linusa@google.com>\n>> \n>> Currently have two functions for formatting trailers exposed in\n>> trailer.h:\n>> \n>>     void format_trailers(FILE *outfile, struct list_head *head,\n>>                         const struct process_trailer_options *opts);\n>> \n>>     void format_trailers_from_commit(struct strbuf *out, const char *msg,\n>>                                     const struct process_trailer_options *opts);\n>> \n>> and previously these functions, although similar enough (even taking the\n>> same process_trailer_options struct pointer), did not build on each\n>> other.\n>> \n>> Make format_trailers_from_commit() rely on format_trailers(). Teach\n>> format_trailers() to process trailers with the additional\n>> process_trailer_options fields like opts->key_only which is only used by\n>> format_trailers_from_commit() and not builtin/interpret-trailers.c.\n>> While we're at it, reorder parameters to put the trailer processing\n>> options first, and the out parameter (strbuf we write into) at the end.\n>> \n>> This unification will allow us to delete the format_trailer_info() and\n>> print_tok_val() functions in the next patch. They are not deleted here\n>> in order to keep the diff small.\n>\n> Unfortunately this breaks the build:\n>\n> trailer.c:1145:13: error: ‘format_trailer_info’ defined but not used [-Werror=unused-function]\n>\n> and\n>\n> trailer.c:147:13: error: ‘print_tok_val’ defined but not used [-Werror=unused-function]\n>\n> While separating this patch from the deletion does make it easier to\n> review, it may make bisection more difficult.\n\nFTR I've tried a preview version of squashing the deletion into this\npatch with \"/preview\" in GitGitGadget, and it generated a clean-enough\ndiff where the deletions weren't intermixed with additions (maybe it\nuses the patience diff algorithm). But I didn't squash them for v2\nbecause I was concerned about the range diff becoming even more\ndifficult to read for reviewers.\n\nI'm OK with squashing them for v3, but I'm also not sure that's\nnecessary. For example, during bisection you could use DEVOPTS=no-error\n(or similar) in config.mak to skip over harmless errors like\n\"unused-function\". Personally I'd prefer to keep the patches separate\nbecause they started separately on the list.\n\nUltimately, I don't have a strong opinion on this. Maybe Junio or\nsomeone else can cast the tie-breaking vote? To squash or not to squash?\nI will take lazy consensus to mean \"squash\" for v3 if no one has\nobjections. Thanks.\n"},{"id":"487617","messageId":"72cc12a306660dde0a09c042160df20b624f785a.1706664145.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v3.git.1706664144.gitgitgadget@gmail.com","subject":"[PATCH v3 01/10] trailer: prepare to expose functions as part of API","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-31T01:22:15Z","receivedAt":"2024-01-31T01:22:29Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nIn the next patch, we will move \"process_trailers\" from trailer.c to\nbuiltin/interpret-trailers.c. That move will necessitate the growth of\nthe trailer.h API, forcing us to expose some additional functions in\ntrailer.h.\n\nRename relevant functions so that they include the term \"trailer\" in\ntheir name, so that clients of the API will be able to easily identify\nthem by their \"trailer\" moniker, just like all the other functions\nalready exposed by trailer.h.\n\nTake the opportunity to start putting trailer processions options (opts)\nas the first parameter. This will be the pattern going forward in this\nseries.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  4 ++--\n trailer.c                    | 26 +++++++++++++-------------\n trailer.h                    |  6 +++---\n 3 files changed, 18 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 033bd1556cf..85a3413baf5 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -132,11 +132,11 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n \tif (argc) {\n \t\tint i;\n \t\tfor (i = 0; i < argc; i++)\n-\t\t\tprocess_trailers(argv[i], &opts, &trailers);\n+\t\t\tinterpret_trailers(&opts, &trailers, argv[i]);\n \t} else {\n \t\tif (opts.in_place)\n \t\t\tdie(_(\"no input file given for in-place editing\"));\n-\t\tprocess_trailers(NULL, &opts, &trailers);\n+\t\tinterpret_trailers(&opts, &trailers, NULL);\n \t}\n \n \tnew_trailers_clear(&trailers);\ndiff --git a/trailer.c b/trailer.c\nindex 3a0710a4583..66b6660f5a4 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -163,12 +163,12 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n-static void print_all(FILE *outfile, struct list_head *head,\n-\t\t      const struct process_trailer_options *opts)\n+static void format_trailers(const struct process_trailer_options *opts,\n+\t\t\t    struct list_head *trailers, FILE *outfile)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *item;\n-\tlist_for_each(pos, head) {\n+\tlist_for_each(pos, trailers) {\n \t\titem = list_entry(pos, struct trailer_item, list);\n \t\tif ((!opts->trim_empty || strlen(item->value) > 0) &&\n \t\t    (!opts->only_trailers || item->token))\n@@ -589,7 +589,7 @@ static int git_trailer_config(const char *conf_key, const char *value,\n \treturn 0;\n }\n \n-static void ensure_configured(void)\n+static void trailer_config_init(void)\n {\n \tif (configured)\n \t\treturn;\n@@ -1035,10 +1035,10 @@ static void parse_trailers(struct trailer_info *info,\n \t}\n }\n \n-static void free_all(struct list_head *head)\n+static void free_trailers(struct list_head *trailers)\n {\n \tstruct list_head *pos, *p;\n-\tlist_for_each_safe(pos, p, head) {\n+\tlist_for_each_safe(pos, p, trailers) {\n \t\tlist_del(pos);\n \t\tfree_trailer_item(list_entry(pos, struct trailer_item, list));\n \t}\n@@ -1075,16 +1075,16 @@ static FILE *create_in_place_tempfile(const char *file)\n \treturn outfile;\n }\n \n-void process_trailers(const char *file,\n-\t\t      const struct process_trailer_options *opts,\n-\t\t      struct list_head *new_trailer_head)\n+void interpret_trailers(const struct process_trailer_options *opts,\n+\t\t\tstruct list_head *new_trailer_head,\n+\t\t\tconst char *file)\n {\n \tLIST_HEAD(head);\n \tstruct strbuf sb = STRBUF_INIT;\n \tstruct trailer_info info;\n \tFILE *outfile = stdout;\n \n-\tensure_configured();\n+\ttrailer_config_init();\n \n \tread_input_file(&sb, file);\n \n@@ -1110,9 +1110,9 @@ void process_trailers(const char *file,\n \t\tprocess_trailers_lists(&head, &arg_head);\n \t}\n \n-\tprint_all(outfile, &head, opts);\n+\tformat_trailers(opts, &head, outfile);\n \n-\tfree_all(&head);\n+\tfree_trailers(&head);\n \ttrailer_info_release(&info);\n \n \t/* Print the lines after the trailers as is */\n@@ -1135,7 +1135,7 @@ void trailer_info_get(struct trailer_info *info, const char *str,\n \tsize_t nr = 0, alloc = 0;\n \tchar **last = NULL;\n \n-\tensure_configured();\n+\ttrailer_config_init();\n \n \tend_of_log_message = find_end_of_log_message(str, opts->no_divider);\n \ttrailer_block_start = find_trailer_block_start(str, end_of_log_message);\ndiff --git a/trailer.h b/trailer.h\nindex 1644cd05f60..37033e631a1 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -81,9 +81,9 @@ struct process_trailer_options {\n \n #define PROCESS_TRAILER_OPTIONS_INIT {0}\n \n-void process_trailers(const char *file,\n-\t\t      const struct process_trailer_options *opts,\n-\t\t      struct list_head *new_trailer_head);\n+void interpret_trailers(const struct process_trailer_options *opts,\n+\t\t\tstruct list_head *new_trailer_head,\n+\t\t\tconst char *file);\n \n void trailer_info_get(struct trailer_info *info, const char *str,\n \t\t      const struct process_trailer_options *opts);\n-- \ngitgitgadget\n\n"},{"id":"487618","messageId":"pull.1632.v3.git.1706664144.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v2.git.1706308737.gitgitgadget@gmail.com","subject":"[PATCH v3 00/10] Enrich Trailer API","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-31T01:22:14Z","receivedAt":"2024-01-31T01:22:29Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"This patch series is the first 10 patches of a larger cleanup/bugfix series\n(henceforth \"larger series\") I've been working on. The main goal of this\nseries is to begin the process of \"libifying\" the trailer API. By \"API\" I\nmean the interface exposed in trailer.h. The larger series brings a number\nof additional cleanups (exposing and fixing some bugs along the way), and\nbuilds on top of this series.\n\nWhen the larger series is merged, we will be in a good state to additionally\npursue the following goals:\n\n 1. \"API reuse inside Git\": make the API expressive enough to eliminate any\n    need by other parts of Git to use the interpret-trailers builtin as a\n    subprocess (instead they could just use the API directly);\n 2. \"API stability\": add unit tests to codify the expected behavior of API\n    functions; and\n 3. \"API documentation\": create developer-focused documentation to explain\n    how to use the API effectively, noting any API limitations or\n    anti-patterns.\n\nThe reason why the larger series itself doesn't tackle these goals directly\nis because I believe that API code should be thought from the ground up with\na libification-focused perspective. Some structs and functions exposed in\nthe API today should probably not be libified (read: kept in trailer.h) as\nis. For example, the \"trailer_iterator\" struct has a \"private\" member and it\nfeels wrong to allow API users to peek inside here (and take at face value\nour future API users' pinky promise that they won't depend on those private\ninternals not meant for public consumption).\n\nOne pattern we could use here to cleanly separate \"what is the API\"\n(publicly exposed) and \"what is the implementation\" (private) is the\npointer-to-implementation (\"pimpl\") idiom. There may be other appropriate\npatterns, but I've chosen this one because it's a simple, low-level concept\n(put structs in foo.c instead of foo.h), which has far-reaching high-level\nconsequences (API users must rely exclusively on the API to make use of such\nprivate structs, via opaque pointers). The pimpl idiom for C comes from the\nbook \"C Interfaces and Implementations\" (see patch \"trailer: make\ntrailer_info struct private\").\n\nThe idea of turning a public struct into a private one is a fundamental\nquestion of libification because it forces us to reconsider all of the data\nstructures we have and how they're actually used by already existing users.\nFor the trailer API, those existing users are the \"interpret-trailers\"\nbuiltin command, and anything else that includes the \"trailer.h\" header file\n(e.g., sequencer.c). One advantage of this idiom is that even the compiler\nunderstands it --- the compiler will loudly complain if you try to access\nthe innards of a private struct through an opaque pointer.\n\nAnother advantage of this idiom is that it helps to reduce the probability\nof breaking changes in the API. Because a private struct's members are out\nof view from our users (they only know about opaque pointers to the private\nstruct, not its members), we are free to modify the members of the struct at\nany time, as much as we like, as long as we don't break the semantics of the\nexposed API functions (which is why unit-testing these API functions will be\ncrucial long-term).\n\nIf this pimpl idiom turns out to be a mistake, undoing it is easy --- just\nmove the relevant struct definition from foo.c to the header file. So it's a\ngreat way to try things out without digging ourselves into a pit of despair\nthat will be difficult to get out of.\n\nWith the libification-focused goals out of the way, let's turn to this patch\nseries in more detail.\n\nCurrently, we have \"process_trailers()\" in trailer.h which does many\ndifferent things (parse command-line arguments, create temporary files, etc)\nthat are independent of the concept of \"trailers\". Keeping this function as\nan API function would make unit-testing it difficult. While there is no\ntechnical reason why we couldn't write unit tests for the smaller functions\nthat are called within process_trailers(), doing so would involve testing\nprivate (\"static\" in trailer.c) functions instead of API functions, which\ndefeats the goal of \"API stability\" mentioned earlier above.\n\nAs an alternative to how things are done in this patch series, we could keep\ntrailer.h intact and decide to unit-test the existing \"trailer_info_get()\"\nfunction which does most of the trailer parsing work (and is used by\nsequencer.c). However this function wouldn't be easy to test either, because\nthe resulting \"trailer_info\" struct merely contains the unparsed \"trailers\"\nlines. So the unit test (if it wants to inspect the result of parsing these\nlines) would have to invoke additional parsing functions itself. And at that\npoint it would no longer be a unit test in the traditional sense, because it\nwould be invoking multiple functions at once.\n\nIn summary this series breaks up \"process_trailers()\" into smaller pieces,\nexposing many of the parts relevant to trailer-related processing in\ntrailer.h. This will force us to eventually introduce unit tests for these\nAPI functions, but that is a good thing for API stability.\n\nIn the future after libification is \"complete\", users external to Git will\nbe able to use the same trailer processing API used by the\ninterpret-trailers builtin. For example, a web server may want to parse\ntrailers the same way that Git would parse them, without having to call\ninterpret-trailers as a subprocess. This use case was the original\nmotivation behind my work in this area.\n\nThanks to the aggressive refactoring in this series, I've been able to\nidentify and fix several bugs in our existing implementation. Those fixes\nbuild on top of this series but were not included here, in order to keep\nthis series small. Below is a \"shortlog\" of those fixes I have locally:\n\n * \"trailer: trailer replacement should not change its position\" (If we\n   found a trailer we'd like to replace, preserve its position relative to\n   the other trailers found in the trailer block, instead of always moving\n   it to the beginning or end of the entire trailer block.)\n * \"interpret-trailers: preserve trailers coming from the input\" (Sometimes,\n   the parsed trailers from the input will be formatted differently\n   depending on whether we provide --only-trailers or not. Make the trailers\n   that were not modified and which are coming directly from the input get\n   formatted the same way, regardless of this flag.)\n * \"interpret-trailers: do not modify the input if NOP\" (Refrain from\n   subtracting or adding a newline around the patch divider \"---\" if we are\n   not adding new trailers.)\n * \"trailer formatter: split up format_trailer() monolith\" (Fix a bug in\n   git-log where we still printed a blank newline even if we didn't want to\n   format anything.)\n * \"interpret-trailers: fail if given unrecognized arguments\" (E.g., for\n   \"--where\", only accept recognized WHERE_* enum values. If we get\n   something unrecognized, fail with an error instead of silently doing\n   nothing. Ditto for \"--if-exists\" and \"--if-missing\".)\n\n\nNotable changes in v3\n=====================\n\n * Squashed Patch 4 into Patch 3 (\"trailer: unify trailer formatting\n   machinery\"), to avoid breaking the build (\"-Werror=unused-function\"\n   violations)\n * NEW (Patch 10): Introduce \"trailer template\" terminology for readability\n   (no behavioral change)\n * (API function) Rename default_separators() to\n   trailer_default_separators()\n * (API function) Rename new_trailers_clear() to free_trailer_templates()\n * trailer.h: for single-parameter functions, anonymize the parameter name\n   to reduce verbosity\n\n\nNotable changes in v2\n=====================\n\n * (cover letter) Discuss goals of the larger series in more detail,\n   especially the pimpl idiom\n * (cover letter) List bug fixes pending in the larger series that depend on\n   this series\n * Reorder function parameters to have trailer options at the beginning (and\n   out parameters toward the end)\n * \"sequencer: use the trailer iterator\": prefer C string instead of strbuf\n   for new \"raw\" field\n * Patch 1 (was Patch 2) also renames ensure_configured() to\n   trailer_config_init() (forgot to rename this one previously)\n\nLinus Arver (10):\n  trailer: prepare to expose functions as part of API\n  trailer: move interpret_trailers() to interpret-trailers.c\n  trailer: unify trailer formatting machinery\n  sequencer: use the trailer iterator\n  trailer: make trailer_info struct private\n  trailer: spread usage of \"trailer_block\" language\n  trailer: prepare to move parse_trailers_from_command_line_args() to\n    builtin\n  trailer: move arg handling to interpret-trailers.c\n  trailer: delete obsolete argument handling code from API\n  trailer: introduce \"template\" term for readability\n\n builtin/interpret-trailers.c | 183 ++++++--\n builtin/shortlog.c           |   7 +-\n pretty.c                     |   2 +-\n ref-filter.c                 |   2 +-\n sequencer.c                  |  35 +-\n trailer.c                    | 835 +++++++++++++++++------------------\n trailer.h                    | 105 +++--\n 7 files changed, 631 insertions(+), 538 deletions(-)\n\n\nbase-commit: a54a84b333adbecf7bc4483c0e36ed5878cac17b\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1632%2Flistx%2Ftrailer-api-refactor-part-1-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1632/listx/trailer-api-refactor-part-1-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/1632\n\nRange-diff vs v2:\n\n  1:  e2d3ed9b5b6 !  1:  72cc12a3066 trailer: prepare to expose functions as part of API\n     @@ Commit message\n          them by their \"trailer\" moniker, just like all the other functions\n          already exposed by trailer.h.\n      \n     -    The the opportunity to start putting trailer processions options (opts)\n     +    Take the opportunity to start putting trailer processions options (opts)\n          as the first parameter. This will be the pattern going forward in this\n          series.\n      \n  2:  eaca39fd7ea !  2:  cafa39d1048 trailer: move interpret_trailers() to interpret-trailers.c\n     @@ trailer.h: struct process_trailer_options {\n      +void trailer_config_init(void);\n      +void format_trailers(const struct process_trailer_options *opts,\n      +\t\t     struct list_head *trailers, FILE *outfile);\n     -+void free_trailers(struct list_head *trailers);\n     ++void free_trailers(struct list_head *);\n      +\n       /*\n        * Format the trailers from the commit msg \"msg\" into the strbuf \"out\".\n  3:  9b7747d550e !  3:  5c7a2354df0 trailer: unify trailer formatting machinery\n     @@ ref-filter.c: static void grab_sub_body_contents(struct atom_value *val, int der\n       \t\t} else if (atom->u.contents.option == C_BARE)\n      \n       ## trailer.c ##\n     -@@ trailer.c: static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n     - \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n     +@@ trailer.c: static char last_non_space_char(const char *s)\n     + \treturn '\\0';\n       }\n       \n     +-static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n     +-{\n     +-\tchar c;\n     +-\n     +-\tif (!tok) {\n     +-\t\tfprintf(outfile, \"%s\\n\", val);\n     +-\t\treturn;\n     +-\t}\n     +-\n     +-\tc = last_non_space_char(tok);\n     +-\tif (!c)\n     +-\t\treturn;\n     +-\tif (strchr(separators, c))\n     +-\t\tfprintf(outfile, \"%s%s\\n\", tok, val);\n     +-\telse\n     +-\t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n     +-}\n     +-\n      -void format_trailers(const struct process_trailer_options *opts,\n      -\t\t     struct list_head *trailers, FILE *outfile)\n      -{\n     @@ trailer.c: static void unfold_value(struct strbuf *val)\n       /*\n        * Parse trailers in \"str\", populating the trailer info and \"head\"\n        * linked list structure.\n     -@@ trailer.c: static void format_trailer_info(struct strbuf *out,\n     - \n     +@@ trailer.c: void trailer_info_release(struct trailer_info *info)\n     + \tfree(info->trailers);\n       }\n       \n     --void format_trailers_from_commit(struct strbuf *out, const char *msg,\n     --\t\t\t\t const struct process_trailer_options *opts)\n     +-static void format_trailer_info(struct strbuf *out,\n     +-\t\t\t\tconst struct trailer_info *info,\n     +-\t\t\t\tconst char *msg,\n     +-\t\t\t\tconst struct process_trailer_options *opts)\n      +void format_trailers_from_commit(const struct process_trailer_options *opts,\n      +\t\t\t\t const char *msg,\n      +\t\t\t\t struct strbuf *out)\n       {\n     +-\tsize_t origlen = out->len;\n     +-\tsize_t i;\n      +\tLIST_HEAD(head);\n     - \tstruct trailer_info info;\n     - \n     --\ttrailer_info_get(&info, msg, opts);\n     --\tformat_trailer_info(out, &info, msg, opts);\n     -+\tparse_trailers(opts, &info, msg, &head);\n     ++\tstruct trailer_info info;\n      +\n     -+\t/* If we want the whole block untouched, we can take the fast path. */\n     -+\tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n     -+\t    !opts->separator && !opts->key_only && !opts->value_only &&\n     -+\t    !opts->key_value_separator) {\n     ++\tparse_trailers(opts, &info, msg, &head);\n     + \n     + \t/* If we want the whole block untouched, we can take the fast path. */\n     + \tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n     + \t    !opts->separator && !opts->key_only && !opts->value_only &&\n     + \t    !opts->key_value_separator) {\n     +-\t\tstrbuf_add(out, msg + info->trailer_block_start,\n     +-\t\t\t   info->trailer_block_end - info->trailer_block_start);\n     +-\t\treturn;\n     +-\t}\n     +-\n     +-\tfor (i = 0; i < info->trailer_nr; i++) {\n     +-\t\tchar *trailer = info->trailers[i];\n     +-\t\tssize_t separator_pos = find_separator(trailer, separators);\n     +-\n     +-\t\tif (separator_pos >= 1) {\n     +-\t\t\tstruct strbuf tok = STRBUF_INIT;\n     +-\t\t\tstruct strbuf val = STRBUF_INIT;\n     +-\n     +-\t\t\tparse_trailer(&tok, &val, NULL, trailer, separator_pos);\n     +-\t\t\tif (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n     +-\t\t\t\tif (opts->unfold)\n     +-\t\t\t\t\tunfold_value(&val);\n     +-\n     +-\t\t\t\tif (opts->separator && out->len != origlen)\n     +-\t\t\t\t\tstrbuf_addbuf(out, opts->separator);\n     +-\t\t\t\tif (!opts->value_only)\n     +-\t\t\t\t\tstrbuf_addbuf(out, &tok);\n     +-\t\t\t\tif (!opts->key_only && !opts->value_only) {\n     +-\t\t\t\t\tif (opts->key_value_separator)\n     +-\t\t\t\t\t\tstrbuf_addbuf(out, opts->key_value_separator);\n     +-\t\t\t\t\telse\n     +-\t\t\t\t\t\tstrbuf_addstr(out, \": \");\n     +-\t\t\t\t}\n     +-\t\t\t\tif (!opts->key_only)\n     +-\t\t\t\t\tstrbuf_addbuf(out, &val);\n     +-\t\t\t\tif (!opts->separator)\n     +-\t\t\t\t\tstrbuf_addch(out, '\\n');\n     +-\t\t\t}\n     +-\t\t\tstrbuf_release(&tok);\n     +-\t\t\tstrbuf_release(&val);\n     +-\n     +-\t\t} else if (!opts->only_trailers) {\n     +-\t\t\tif (opts->separator && out->len != origlen) {\n     +-\t\t\t\tstrbuf_addbuf(out, opts->separator);\n     +-\t\t\t}\n     +-\t\t\tstrbuf_addstr(out, trailer);\n     +-\t\t\tif (opts->separator) {\n     +-\t\t\t\tstrbuf_rtrim(out);\n     +-\t\t\t}\n     +-\t\t}\n     +-\t}\n     +-\n     +-}\n     +-\n     +-void format_trailers_from_commit(struct strbuf *out, const char *msg,\n     +-\t\t\t\t const struct process_trailer_options *opts)\n     +-{\n     +-\tstruct trailer_info info;\n      +\t\tstrbuf_add(out, msg + info.trailer_block_start,\n      +\t\t\t   info.trailer_block_end - info.trailer_block_start);\n      +\t} else\n      +\t\tformat_trailers(opts, &head, out);\n     -+\n     + \n     +-\ttrailer_info_get(&info, msg, opts);\n     +-\tformat_trailer_info(out, &info, msg, opts);\n      +\tfree_trailers(&head);\n       \ttrailer_info_release(&info);\n       }\n     @@ trailer.h: void trailer_info_release(struct trailer_info *info);\n      -\t\t     struct list_head *trailers, FILE *outfile);\n      +\t\t     struct list_head *trailers,\n      +\t\t     struct strbuf *out);\n     - void free_trailers(struct list_head *trailers);\n     + void free_trailers(struct list_head *);\n       \n       /*\n      - * Format the trailers from the commit msg \"msg\" into the strbuf \"out\".\n  4:  f1171f5202f <  -:  ----------- trailer: delete obsolete formatting functions\n  5:  5ba842b5005 =  4:  bf2b8e1a3c4 sequencer: use the trailer iterator\n  6:  f0ac2f6c4b9 =  5:  c19c1dcc592 trailer: make trailer_info struct private\n  7:  291aa83af55 =  6:  0a9a7438c3f trailer: spread usage of \"trailer_block\" language\n  8:  64ee07d0b53 !  7:  97e5d86ddf0 trailer: prepare to move parse_trailers_from_command_line_args() to builtin\n     @@ Commit message\n          themselves). The interpret-trailers builtin is the only caller of this\n          function.\n      \n     -    Also rename \"conf_info\" to \"trailer_conf\" for readability, dropping the\n     -    low-value \"_info\" suffix as we did earlier in this series for\n     -    \"trailer_info\" to \"trailer_block\".\n     +    Rename add_arg_item() to trailer_add_arg_item() because it will have to\n     +    be exposed as an API function in the next patch. Rename\n     +    new_trailers_clear() to free_new_trailers() because it will be promoted\n     +    into an API function; the API already has free_trailers(), so using the\n     +    \"free_*\" naming style will keep it consistent. Also rename \"conf_info\"\n     +    to \"trailer_conf\" for readability, dropping the low-value \"_info\" suffix\n     +    as we did earlier in this series for \"trailer_info\" to \"trailer_block\".\n      \n          Helped-by: Josh Steadmon <steadmon@google.com>\n          Signed-off-by: Linus Arver <linusa@google.com>\n      \n     + ## builtin/interpret-trailers.c ##\n     +@@ builtin/interpret-trailers.c: static int option_parse_if_missing(const struct option *opt,\n     + \treturn trailer_set_if_missing(opt->value, arg);\n     + }\n     + \n     +-static void new_trailers_clear(struct list_head *trailers)\n     ++static void free_new_trailers(struct list_head *trailers)\n     + {\n     + \tstruct list_head *pos, *tmp;\n     + \tstruct new_trailer_item *item;\n     +@@ builtin/interpret-trailers.c: static int option_parse_trailer(const struct option *opt,\n     + \tstruct new_trailer_item *item;\n     + \n     + \tif (unset) {\n     +-\t\tnew_trailers_clear(trailers);\n     ++\t\tfree_new_trailers(trailers);\n     + \t\treturn 0;\n     + \t}\n     + \n     +@@ builtin/interpret-trailers.c: int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n     + \t\tinterpret_trailers(&opts, &trailers, NULL);\n     + \t}\n     + \n     +-\tnew_trailers_clear(&trailers);\n     ++\tfree_new_trailers(&trailers);\n     + \n     + \treturn 0;\n     + }\n     +\n       ## trailer.c ##\n      @@ trailer.c: struct trailer_block {\n       \tsize_t trailer_nr;\n     @@ trailer.c: static ssize_t find_separator(const char *line, const char *separator\n       \t\titem = list_entry(pos, struct arg_item, list);\n       \t\tif (token_matches_item(tok->buf, item, tok_len)) {\n      @@ trailer.c: static struct trailer_item *add_trailer_item(struct list_head *head, char *tok,\n     + \treturn new_item;\n       }\n       \n     - static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n     +-static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n      -\t\t\t const struct conf_info *conf,\n     -+\t\t\t const struct trailer_conf *conf,\n     - \t\t\t const struct new_trailer_item *new_trailer_item)\n     +-\t\t\t const struct new_trailer_item *new_trailer_item)\n     ++static void trailer_add_arg_item(struct list_head *arg_head, char *tok, char *val,\n     ++\t\t\t\t const struct trailer_conf *conf,\n     ++\t\t\t\t const struct new_trailer_item *new_trailer_item)\n       {\n       \tstruct arg_item *new_item = xcalloc(1, sizeof(*new_item));\n       \tnew_item->token = tok;\n     @@ trailer.c: static struct trailer_item *add_trailer_item(struct list_head *head,\n       \tif (new_trailer_item) {\n       \t\tif (new_trailer_item->where != WHERE_DEFAULT)\n       \t\t\tnew_item->conf.where = new_trailer_item->where;\n     +@@ trailer.c: void parse_trailers_from_config(struct list_head *config_head)\n     + \tlist_for_each(pos, &conf_head) {\n     + \t\titem = list_entry(pos, struct arg_item, list);\n     + \t\tif (item->conf.command)\n     +-\t\t\tadd_arg_item(config_head,\n     +-\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n     +-\t\t\t\t     xstrdup(\"\"),\n     +-\t\t\t\t     &item->conf, NULL);\n     ++\t\t\ttrailer_add_arg_item(config_head,\n     ++\t\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n     ++\t\t\t\t\t     xstrdup(\"\"),\n     ++\t\t\t\t\t     &item->conf, NULL);\n     + \t}\n     + }\n     + \n      @@ trailer.c: void parse_trailers_from_command_line_args(struct list_head *arg_head,\n       {\n       \tstruct strbuf tok = STRBUF_INIT;\n     @@ trailer.c: void parse_trailers_from_command_line_args(struct list_head *arg_head\n       \t\t} else {\n      -\t\t\tparse_trailer(&tok, &val, &conf, tr->text,\n      -\t\t\t\t      separator_pos);\n     +-\t\t\tadd_arg_item(arg_head,\n     +-\t\t\t\t     strbuf_detach(&tok, NULL),\n     +-\t\t\t\t     strbuf_detach(&val, NULL),\n     +-\t\t\t\t     conf, tr);\n      +\t\t\tparse_trailer(tr->text, separator_pos, &tok, &val, &conf);\n     - \t\t\tadd_arg_item(arg_head,\n     - \t\t\t\t     strbuf_detach(&tok, NULL),\n     - \t\t\t\t     strbuf_detach(&val, NULL),\n     ++\t\t\ttrailer_add_arg_item(arg_head,\n     ++\t\t\t\t\t     strbuf_detach(&tok, NULL),\n     ++\t\t\t\t\t     strbuf_detach(&val, NULL),\n     ++\t\t\t\t\t     conf, tr);\n     + \t\t}\n     + \t}\n     + \n      @@ trailer.c: struct trailer_block *parse_trailers(const struct process_trailer_options *opts,\n       \n       \tfor (i = 0; i < trailer_block->trailer_nr; i++) {\n  9:  1b4bdde65bc !  8:  465dc51cdcb trailer: move arg handling to interpret-trailers.c\n     @@ Commit message\n          trailer: move arg handling to interpret-trailers.c\n      \n          We don't move the \"arg_item\" struct to interpret-trailers.c, because it\n     -    is now a struct that contains information about trailers that should be\n     -    injected into the input text's own trailers. We will rename this\n     -    language as such in a follow-up patch to keep the diff here small.\n     +    is now a struct that contains information about trailers that could be\n     +    added into the input text's own trailers. This is a generic concept that\n     +    extends beyond trailers defined as CLI arguments (it applies to trailers\n     +    defined in configuration as well). We will rename \"arg_item\" to\n     +    \"trailer_template\" in a follow-up patch to keep the diff here small.\n      \n          Signed-off-by: Linus Arver <linusa@google.com>\n      \n     @@ builtin/interpret-trailers.c: static int option_parse_if_missing(const struct op\n       \treturn trailer_set_if_missing(opt->value, arg);\n       }\n       \n     --static void new_trailers_clear(struct list_head *trailers)\n     +-static void free_new_trailers(struct list_head *trailers)\n      -{\n      -\tstruct list_head *pos, *tmp;\n      -\tstruct new_trailer_item *item;\n     @@ builtin/interpret-trailers.c: static int option_parse_if_missing(const struct op\n      +\tssize_t separator_pos;\n       \n       \tif (unset) {\n     - \t\tnew_trailers_clear(trailers);\n     + \t\tfree_new_trailers(trailers);\n      @@ builtin/interpret-trailers.c: static int option_parse_trailer(const struct option *opt,\n       \tif (!arg)\n       \t\treturn -1;\n     @@ builtin/interpret-trailers.c: static int option_parse_trailer(const struct optio\n      +\t\t */\n      +\t\ttrailer_conf_set(where, if_exists, if_missing, conf_current);\n      +\n     -+\t\tadd_arg_item(strbuf_detach(&tok, NULL),\n     -+\t\t\t     strbuf_detach(&val, NULL),\n     -+\t\t\t     conf_current,\n     -+\t\t\t     trailers);\n     ++\t\ttrailer_add_arg_item(strbuf_detach(&tok, NULL),\n     ++\t\t\t\t     strbuf_detach(&val, NULL),\n     ++\t\t\t\t     conf_current,\n     ++\t\t\t\t     trailers);\n      +\t} else {\n      +\t\tstruct strbuf sb = STRBUF_INIT;\n      +\t\tstrbuf_addstr(&sb, arg);\n     @@ builtin/interpret-trailers.c: int cmd_interpret_trailers(int argc, const char **\n      +\t* In command-line arguments, '=' is accepted (in addition to the\n      +\t* separators that are defined).\n      +\t*/\n     -+\tcl_separators = xstrfmt(\"=%s\", default_separators());\n     ++\tcl_separators = xstrfmt(\"=%s\", trailer_default_separators());\n       \n       \targc = parse_options(argc, argv, prefix, options,\n       \t\t\t     git_interpret_trailers_usage, 0);\n     @@ builtin/interpret-trailers.c: int cmd_interpret_trailers(int argc, const char **\n      +\t\tinterpret_trailers(&opts, &arg_trailers, NULL);\n       \t}\n       \n     --\tnew_trailers_clear(&trailers);\n     -+\tnew_trailers_clear(&arg_trailers);\n     +-\tfree_new_trailers(&trailers);\n     ++\tfree_new_trailers(&arg_trailers);\n       \n       \treturn 0;\n       }\n     @@ trailer.c: static LIST_HEAD(conf_head);\n       \n       static char *separators = \":\";\n       \n     -+const char *default_separators(void)\n     ++const char *trailer_default_separators(void)\n      +{\n      +\treturn separators;\n      +}\n     @@ trailer.c: ssize_t find_separator(const char *line, const char *separators)\n        * Obtain the token, value, and conf from the given trailer.\n        *\n      + * The conf needs special handling. We first read hardcoded defaults, and\n     -+ * override them if we find a matching trailer configuration in the config.\n     ++ * override them if we find a matching trailer configuration.\n      + *\n        * separator_pos must not be 0, since the token cannot be an empty string.\n        *\n     @@ trailer.c: static struct trailer_item *add_trailer_item(struct list_head *head,\n       \treturn new_item;\n       }\n       \n     --static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n     --\t\t\t const struct trailer_conf *conf,\n     --\t\t\t const struct new_trailer_item *new_trailer_item)\n     -+void add_arg_item(char *tok, char *val, const struct trailer_conf *conf,\n     -+\t\t  struct list_head *arg_head)\n     -+\n     +-static void trailer_add_arg_item(struct list_head *arg_head, char *tok, char *val,\n     +-\t\t\t\t const struct trailer_conf *conf,\n     +-\t\t\t\t const struct new_trailer_item *new_trailer_item)\n     ++void trailer_add_arg_item(char *tok, char *val, const struct trailer_conf *conf,\n     ++\t\t\t  struct list_head *arg_head)\n       {\n       \tstruct arg_item *new_item = xcalloc(1, sizeof(*new_item));\n       \tnew_item->token = tok;\n     @@ trailer.c: void parse_trailers_from_config(struct list_head *config_head)\n       \tlist_for_each(pos, &conf_head) {\n       \t\titem = list_entry(pos, struct arg_item, list);\n       \t\tif (item->conf.command)\n     --\t\t\tadd_arg_item(config_head,\n     --\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n     -+\t\t\tadd_arg_item(xstrdup(token_from_item(item, NULL)),\n     - \t\t\t\t     xstrdup(\"\"),\n     --\t\t\t\t     &item->conf, NULL);\n     -+\t\t\t\t     &item->conf,\n     -+\t\t\t\t     config_head);\n     +-\t\t\ttrailer_add_arg_item(config_head,\n     +-\t\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n     ++\t\t\ttrailer_add_arg_item(xstrdup(token_from_item(item, NULL)),\n     + \t\t\t\t\t     xstrdup(\"\"),\n     +-\t\t\t\t\t     &item->conf, NULL);\n     ++\t\t\t\t\t     &item->conf,\n     ++\t\t\t\t\t     config_head);\n       \t}\n       }\n       \n     @@ trailer.c: void parse_trailers_from_command_line_args(struct list_head *arg_head\n       \t\t\tstrbuf_release(&sb);\n       \t\t} else {\n       \t\t\tparse_trailer(tr->text, separator_pos, &tok, &val, &conf);\n     --\t\t\tadd_arg_item(arg_head,\n     --\t\t\t\t     strbuf_detach(&tok, NULL),\n     -+\t\t\tadd_arg_item(strbuf_detach(&tok, NULL),\n     - \t\t\t\t     strbuf_detach(&val, NULL),\n     --\t\t\t\t     conf, tr);\n     -+\t\t\t\t     conf,\n     -+\t\t\t\t     arg_head);\n     +-\t\t\ttrailer_add_arg_item(arg_head,\n     +-\t\t\t\t\t     strbuf_detach(&tok, NULL),\n     ++\t\t\ttrailer_add_arg_item(strbuf_detach(&tok, NULL),\n     + \t\t\t\t\t     strbuf_detach(&val, NULL),\n     +-\t\t\t\t\t     conf, tr);\n     ++\t\t\t\t\t     conf,\n     ++\t\t\t\t\t     arg_head);\n       \t\t}\n       \t}\n       \n     @@ trailer.c: void free_trailers(struct list_head *trailers)\n       \t}\n       }\n       \n     -+void new_trailers_clear(struct list_head *trailers)\n     ++void free_new_trailers(struct list_head *trailers)\n      +{\n      +\tstruct list_head *pos, *p;\n      +\n     @@ trailer.h: struct new_trailer_item {\n       void duplicate_trailer_conf(struct trailer_conf *dst,\n       \t\t\t    const struct trailer_conf *src);\n       \n     -+const char *default_separators(void);\n     ++const char *trailer_default_separators(void);\n      +\n     -+void add_arg_item(char *tok, char *val, const struct trailer_conf *conf,\n     -+\t\t  struct list_head *arg_head);\n     ++void trailer_add_arg_item(char *tok, char *val, const struct trailer_conf *conf,\n     ++\t\t\t  struct list_head *arg_head);\n      +\n       struct process_trailer_options {\n       \tint in_place;\n     @@ trailer.h: struct new_trailer_item {\n      @@ trailer.h: void format_trailers(const struct process_trailer_options *opts,\n       \t\t     struct list_head *trailers,\n       \t\t     struct strbuf *out);\n     - void free_trailers(struct list_head *trailers);\n     --\n     -+void new_trailers_clear(struct list_head *new_trailers);\n     + void free_trailers(struct list_head *);\n     ++void free_new_trailers(struct list_head *);\n     + \n       /*\n        * Convenience function to format the trailers from the commit msg \"msg\" into\n     -  * the strbuf \"out\". Reuses format_trailers internally.\n 10:  ed67ebf8647 !  9:  885ac87a544 trailer: delete obsolete argument handling code from API\n     @@ Commit message\n          This commit was not squashed with its parent in order to keep the diff\n          separate (to make the additive changes in the parent easier to read).\n      \n     +    Note that we remove the \"new_trailer_item\" struct, because it has some\n     +    overlap with \"arg_item\" struct that also deals with trailers coming from\n     +    the command line. The latter will be renamed to \"trailer_template\" in\n     +    the next patch.\n     +\n          Signed-off-by: Linus Arver <linusa@google.com>\n      \n       ## trailer.c ##\n     @@ trailer.c: void parse_trailers_from_config(struct list_head *config_head)\n      -\t\t\tstrbuf_release(&sb);\n      -\t\t} else {\n      -\t\t\tparse_trailer(tr->text, separator_pos, &tok, &val, &conf);\n     --\t\t\tadd_arg_item(strbuf_detach(&tok, NULL),\n     --\t\t\t\t     strbuf_detach(&val, NULL),\n     --\t\t\t\t     conf,\n     --\t\t\t\t     arg_head);\n     +-\t\t\ttrailer_add_arg_item(strbuf_detach(&tok, NULL),\n     +-\t\t\t\t\t     strbuf_detach(&val, NULL),\n     +-\t\t\t\t\t     conf,\n     +-\t\t\t\t\t     arg_head);\n      -\t\t}\n      -\t}\n      -\n  -:  ----------- > 10:  bcd3fc9660e trailer: introduce \"template\" term for readability\n\n-- \ngitgitgadget\n"},{"id":"487619","messageId":"cafa39d10489f90897227a244e012457989a7710.1706664145.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v3.git.1706664144.gitgitgadget@gmail.com","subject":"[PATCH v3 02/10] trailer: move interpret_trailers() to interpret-trailers.c","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-31T01:22:16Z","receivedAt":"2024-01-31T01:22:30Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThe interpret-trailers.c builtin is the only place we need to call\ninterpret_trailers(), so move its definition there.\n\nDelete the corresponding declaration from trailer.h, which then forces\nus to expose the working innards of that function. This enriches\ntrailer.h with a more granular API, which can then be unit-tested in the\nfuture (because interpret_trailers() by itself does too many things to\nbe able to be easily unit-tested).\n\nTake this opportunity to demote some file-handling functions out of the\ntrailer API implementation, as these have nothing to do with trailers.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  94 +++++++++++++++++++++++++++\n trailer.c                    | 120 ++++-------------------------------\n trailer.h                    |  20 +++++-\n 3 files changed, 124 insertions(+), 110 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 85a3413baf5..8556acde4aa 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -9,6 +9,7 @@\n #include \"gettext.h\"\n #include \"parse-options.h\"\n #include \"string-list.h\"\n+#include \"tempfile.h\"\n #include \"trailer.h\"\n #include \"config.h\"\n \n@@ -91,6 +92,99 @@ static int parse_opt_parse(const struct option *opt, const char *arg,\n \treturn 0;\n }\n \n+static struct tempfile *trailers_tempfile;\n+\n+static FILE *create_in_place_tempfile(const char *file)\n+{\n+\tstruct stat st;\n+\tstruct strbuf filename_template = STRBUF_INIT;\n+\tconst char *tail;\n+\tFILE *outfile;\n+\n+\tif (stat(file, &st))\n+\t\tdie_errno(_(\"could not stat %s\"), file);\n+\tif (!S_ISREG(st.st_mode))\n+\t\tdie(_(\"file %s is not a regular file\"), file);\n+\tif (!(st.st_mode & S_IWUSR))\n+\t\tdie(_(\"file %s is not writable by user\"), file);\n+\n+\t/* Create temporary file in the same directory as the original */\n+\ttail = strrchr(file, '/');\n+\tif (tail)\n+\t\tstrbuf_add(&filename_template, file, tail - file + 1);\n+\tstrbuf_addstr(&filename_template, \"git-interpret-trailers-XXXXXX\");\n+\n+\ttrailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode);\n+\tstrbuf_release(&filename_template);\n+\toutfile = fdopen_tempfile(trailers_tempfile, \"w\");\n+\tif (!outfile)\n+\t\tdie_errno(_(\"could not open temporary file\"));\n+\n+\treturn outfile;\n+}\n+\n+static void read_input_file(struct strbuf *sb, const char *file)\n+{\n+\tif (file) {\n+\t\tif (strbuf_read_file(sb, file, 0) < 0)\n+\t\t\tdie_errno(_(\"could not read input file '%s'\"), file);\n+\t} else {\n+\t\tif (strbuf_read(sb, fileno(stdin), 0) < 0)\n+\t\t\tdie_errno(_(\"could not read from stdin\"));\n+\t}\n+}\n+\n+static void interpret_trailers(const struct process_trailer_options *opts,\n+\t\t\t       struct list_head *new_trailer_head,\n+\t\t\t       const char *file)\n+{\n+\tLIST_HEAD(head);\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tstruct trailer_info info;\n+\tFILE *outfile = stdout;\n+\n+\ttrailer_config_init();\n+\n+\tread_input_file(&sb, file);\n+\n+\tif (opts->in_place)\n+\t\toutfile = create_in_place_tempfile(file);\n+\n+\tparse_trailers(opts, &info, sb.buf, &head);\n+\n+\t/* Print the lines before the trailers */\n+\tif (!opts->only_trailers)\n+\t\tfwrite(sb.buf, 1, info.trailer_block_start, outfile);\n+\n+\tif (!opts->only_trailers && !info.blank_line_before_trailer)\n+\t\tfprintf(outfile, \"\\n\");\n+\n+\n+\tif (!opts->only_input) {\n+\t\tLIST_HEAD(config_head);\n+\t\tLIST_HEAD(arg_head);\n+\t\tparse_trailers_from_config(&config_head);\n+\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n+\t\tlist_splice(&config_head, &arg_head);\n+\t\tprocess_trailers_lists(&head, &arg_head);\n+\t}\n+\n+\tformat_trailers(opts, &head, outfile);\n+\n+\tfree_trailers(&head);\n+\ttrailer_info_release(&info);\n+\n+\t/* Print the lines after the trailers as is */\n+\tif (!opts->only_trailers)\n+\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n+\n+\tif (opts->in_place)\n+\t\tif (rename_tempfile(&trailers_tempfile, file))\n+\t\t\tdie_errno(_(\"could not rename temporary file to %s\"), file);\n+\n+\tstrbuf_release(&sb);\n+}\n+\n int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n {\n \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\ndiff --git a/trailer.c b/trailer.c\nindex 66b6660f5a4..d3899195876 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -5,7 +5,6 @@\n #include \"string-list.h\"\n #include \"run-command.h\"\n #include \"commit.h\"\n-#include \"tempfile.h\"\n #include \"trailer.h\"\n #include \"list.h\"\n /*\n@@ -163,8 +162,8 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n-static void format_trailers(const struct process_trailer_options *opts,\n-\t\t\t    struct list_head *trailers, FILE *outfile)\n+void format_trailers(const struct process_trailer_options *opts,\n+\t\t     struct list_head *trailers, FILE *outfile)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *item;\n@@ -366,8 +365,8 @@ static int find_same_and_apply_arg(struct list_head *head,\n \treturn 0;\n }\n \n-static void process_trailers_lists(struct list_head *head,\n-\t\t\t\t   struct list_head *arg_head)\n+void process_trailers_lists(struct list_head *head,\n+\t\t\t    struct list_head *arg_head)\n {\n \tstruct list_head *pos, *p;\n \tstruct arg_item *arg_tok;\n@@ -589,7 +588,7 @@ static int git_trailer_config(const char *conf_key, const char *value,\n \treturn 0;\n }\n \n-static void trailer_config_init(void)\n+void trailer_config_init(void)\n {\n \tif (configured)\n \t\treturn;\n@@ -719,7 +718,7 @@ static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n \tlist_add_tail(&new_item->list, arg_head);\n }\n \n-static void parse_trailers_from_config(struct list_head *config_head)\n+void parse_trailers_from_config(struct list_head *config_head)\n {\n \tstruct arg_item *item;\n \tstruct list_head *pos;\n@@ -735,8 +734,8 @@ static void parse_trailers_from_config(struct list_head *config_head)\n \t}\n }\n \n-static void parse_trailers_from_command_line_args(struct list_head *arg_head,\n-\t\t\t\t\t\t  struct list_head *new_trailer_head)\n+void parse_trailers_from_command_line_args(struct list_head *arg_head,\n+\t\t\t\t\t   struct list_head *new_trailer_head)\n {\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n@@ -775,17 +774,6 @@ static void parse_trailers_from_command_line_args(struct list_head *arg_head,\n \tfree(cl_separators);\n }\n \n-static void read_input_file(struct strbuf *sb, const char *file)\n-{\n-\tif (file) {\n-\t\tif (strbuf_read_file(sb, file, 0) < 0)\n-\t\t\tdie_errno(_(\"could not read input file '%s'\"), file);\n-\t} else {\n-\t\tif (strbuf_read(sb, fileno(stdin), 0) < 0)\n-\t\t\tdie_errno(_(\"could not read from stdin\"));\n-\t}\n-}\n-\n static const char *next_line(const char *str)\n {\n \tconst char *nl = strchrnul(str, '\\n');\n@@ -1000,10 +988,10 @@ static void unfold_value(struct strbuf *val)\n  * Parse trailers in \"str\", populating the trailer info and \"head\"\n  * linked list structure.\n  */\n-static void parse_trailers(struct trailer_info *info,\n-\t\t\t     const char *str,\n-\t\t\t     struct list_head *head,\n-\t\t\t     const struct process_trailer_options *opts)\n+void parse_trailers(const struct process_trailer_options *opts,\n+\t\t    struct trailer_info *info,\n+\t\t    const char *str,\n+\t\t    struct list_head *head)\n {\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n@@ -1035,7 +1023,7 @@ static void parse_trailers(struct trailer_info *info,\n \t}\n }\n \n-static void free_trailers(struct list_head *trailers)\n+void free_trailers(struct list_head *trailers)\n {\n \tstruct list_head *pos, *p;\n \tlist_for_each_safe(pos, p, trailers) {\n@@ -1044,88 +1032,6 @@ static void free_trailers(struct list_head *trailers)\n \t}\n }\n \n-static struct tempfile *trailers_tempfile;\n-\n-static FILE *create_in_place_tempfile(const char *file)\n-{\n-\tstruct stat st;\n-\tstruct strbuf filename_template = STRBUF_INIT;\n-\tconst char *tail;\n-\tFILE *outfile;\n-\n-\tif (stat(file, &st))\n-\t\tdie_errno(_(\"could not stat %s\"), file);\n-\tif (!S_ISREG(st.st_mode))\n-\t\tdie(_(\"file %s is not a regular file\"), file);\n-\tif (!(st.st_mode & S_IWUSR))\n-\t\tdie(_(\"file %s is not writable by user\"), file);\n-\n-\t/* Create temporary file in the same directory as the original */\n-\ttail = strrchr(file, '/');\n-\tif (tail)\n-\t\tstrbuf_add(&filename_template, file, tail - file + 1);\n-\tstrbuf_addstr(&filename_template, \"git-interpret-trailers-XXXXXX\");\n-\n-\ttrailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode);\n-\tstrbuf_release(&filename_template);\n-\toutfile = fdopen_tempfile(trailers_tempfile, \"w\");\n-\tif (!outfile)\n-\t\tdie_errno(_(\"could not open temporary file\"));\n-\n-\treturn outfile;\n-}\n-\n-void interpret_trailers(const struct process_trailer_options *opts,\n-\t\t\tstruct list_head *new_trailer_head,\n-\t\t\tconst char *file)\n-{\n-\tLIST_HEAD(head);\n-\tstruct strbuf sb = STRBUF_INIT;\n-\tstruct trailer_info info;\n-\tFILE *outfile = stdout;\n-\n-\ttrailer_config_init();\n-\n-\tread_input_file(&sb, file);\n-\n-\tif (opts->in_place)\n-\t\toutfile = create_in_place_tempfile(file);\n-\n-\tparse_trailers(&info, sb.buf, &head, opts);\n-\n-\t/* Print the lines before the trailers */\n-\tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf, 1, info.trailer_block_start, outfile);\n-\n-\tif (!opts->only_trailers && !info.blank_line_before_trailer)\n-\t\tfprintf(outfile, \"\\n\");\n-\n-\n-\tif (!opts->only_input) {\n-\t\tLIST_HEAD(config_head);\n-\t\tLIST_HEAD(arg_head);\n-\t\tparse_trailers_from_config(&config_head);\n-\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n-\t\tlist_splice(&config_head, &arg_head);\n-\t\tprocess_trailers_lists(&head, &arg_head);\n-\t}\n-\n-\tformat_trailers(opts, &head, outfile);\n-\n-\tfree_trailers(&head);\n-\ttrailer_info_release(&info);\n-\n-\t/* Print the lines after the trailers as is */\n-\tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n-\n-\tif (opts->in_place)\n-\t\tif (rename_tempfile(&trailers_tempfile, file))\n-\t\t\tdie_errno(_(\"could not rename temporary file to %s\"), file);\n-\n-\tstrbuf_release(&sb);\n-}\n-\n void trailer_info_get(struct trailer_info *info, const char *str,\n \t\t      const struct process_trailer_options *opts)\n {\ndiff --git a/trailer.h b/trailer.h\nindex 37033e631a1..ca701c04f3b 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -81,15 +81,29 @@ struct process_trailer_options {\n \n #define PROCESS_TRAILER_OPTIONS_INIT {0}\n \n-void interpret_trailers(const struct process_trailer_options *opts,\n-\t\t\tstruct list_head *new_trailer_head,\n-\t\t\tconst char *file);\n+void parse_trailers_from_config(struct list_head *config_head);\n+\n+void parse_trailers_from_command_line_args(struct list_head *arg_head,\n+\t\t\t\t\t   struct list_head *new_trailer_head);\n+\n+void process_trailers_lists(struct list_head *head,\n+\t\t\t    struct list_head *arg_head);\n+\n+void parse_trailers(const struct process_trailer_options *opts,\n+\t\t    struct trailer_info *info,\n+\t\t    const char *str,\n+\t\t    struct list_head *head);\n \n void trailer_info_get(struct trailer_info *info, const char *str,\n \t\t      const struct process_trailer_options *opts);\n \n void trailer_info_release(struct trailer_info *info);\n \n+void trailer_config_init(void);\n+void format_trailers(const struct process_trailer_options *opts,\n+\t\t     struct list_head *trailers, FILE *outfile);\n+void free_trailers(struct list_head *);\n+\n /*\n  * Format the trailers from the commit msg \"msg\" into the strbuf \"out\".\n  * Note two caveats about \"opts\":\n-- \ngitgitgadget\n\n"},{"id":"487620","messageId":"5c7a2354df0f4a29841f9ab8294ead0e1c3b9cf5.1706664145.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v3.git.1706664144.gitgitgadget@gmail.com","subject":"[PATCH v3 03/10] trailer: unify trailer formatting machinery","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-31T01:22:17Z","receivedAt":"2024-01-31T01:22:31Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nCurrently have two functions for formatting trailers exposed in\ntrailer.h:\n\n    void format_trailers(FILE *outfile, struct list_head *head,\n                        const struct process_trailer_options *opts);\n\n    void format_trailers_from_commit(struct strbuf *out, const char *msg,\n                                    const struct process_trailer_options *opts);\n\nand previously these functions, although similar enough (even taking the\nsame process_trailer_options struct pointer), did not build on each\nother.\n\nMake format_trailers_from_commit() rely on format_trailers(). Teach\nformat_trailers() to process trailers with the additional\nprocess_trailer_options fields like opts->key_only which is only used by\nformat_trailers_from_commit() and not builtin/interpret-trailers.c.\nWhile we're at it, reorder parameters to put the trailer processing\noptions first, and the out parameter (strbuf we write into) at the end.\n\nThis unification will allow us to delete the format_trailer_info() and\nprint_tok_val() functions in the next patch. They are not deleted here\nin order to keep the diff small.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |   6 +-\n pretty.c                     |   2 +-\n ref-filter.c                 |   2 +-\n trailer.c                    | 176 +++++++++++++++++------------------\n trailer.h                    |  19 ++--\n 5 files changed, 98 insertions(+), 107 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 8556acde4aa..5352ee65bd1 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -140,6 +140,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n {\n \tLIST_HEAD(head);\n \tstruct strbuf sb = STRBUF_INIT;\n+\tstruct strbuf trailer_block = STRBUF_INIT;\n \tstruct trailer_info info;\n \tFILE *outfile = stdout;\n \n@@ -169,7 +170,10 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \t\tprocess_trailers_lists(&head, &arg_head);\n \t}\n \n-\tformat_trailers(opts, &head, outfile);\n+\t/* Print trailer block. */\n+\tformat_trailers(opts, &head, &trailer_block);\n+\tfwrite(trailer_block.buf, 1, trailer_block.len, outfile);\n+\tstrbuf_release(&trailer_block);\n \n \tfree_trailers(&head);\n \ttrailer_info_release(&info);\ndiff --git a/pretty.c b/pretty.c\nindex cf964b060cd..bdbed4295aa 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1759,7 +1759,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \t\t\t\tgoto trailer_out;\n \t\t}\n \t\tif (*arg == ')') {\n-\t\t\tformat_trailers_from_commit(sb, msg + c->subject_off, &opts);\n+\t\t\tformat_trailers_from_commit(&opts, msg + c->subject_off, sb);\n \t\t\tret = arg - placeholder + 1;\n \t\t}\n \ttrailer_out:\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 35b989e1dfe..d358953b0ce 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -1985,7 +1985,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct exp\n \t\t\tstruct strbuf s = STRBUF_INIT;\n \n \t\t\t/* Format the trailer info according to the trailer_opts given */\n-\t\t\tformat_trailers_from_commit(&s, subpos, &atom->u.contents.trailer_opts);\n+\t\t\tformat_trailers_from_commit(&atom->u.contents.trailer_opts, subpos, &s);\n \n \t\t\tv->s = strbuf_detach(&s, NULL);\n \t\t} else if (atom->u.contents.option == C_BARE)\ndiff --git a/trailer.c b/trailer.c\nindex d3899195876..71ea2bb67f8 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -144,37 +144,6 @@ static char last_non_space_char(const char *s)\n \treturn '\\0';\n }\n \n-static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n-{\n-\tchar c;\n-\n-\tif (!tok) {\n-\t\tfprintf(outfile, \"%s\\n\", val);\n-\t\treturn;\n-\t}\n-\n-\tc = last_non_space_char(tok);\n-\tif (!c)\n-\t\treturn;\n-\tif (strchr(separators, c))\n-\t\tfprintf(outfile, \"%s%s\\n\", tok, val);\n-\telse\n-\t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n-}\n-\n-void format_trailers(const struct process_trailer_options *opts,\n-\t\t     struct list_head *trailers, FILE *outfile)\n-{\n-\tstruct list_head *pos;\n-\tstruct trailer_item *item;\n-\tlist_for_each(pos, trailers) {\n-\t\titem = list_entry(pos, struct trailer_item, list);\n-\t\tif ((!opts->trim_empty || strlen(item->value) > 0) &&\n-\t\t    (!opts->only_trailers || item->token))\n-\t\t\tprint_tok_val(outfile, item->token, item->value);\n-\t}\n-}\n-\n static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)\n {\n \tstruct trailer_item *new_item = xcalloc(1, sizeof(*new_item));\n@@ -984,6 +953,78 @@ static void unfold_value(struct strbuf *val)\n \tstrbuf_release(&out);\n }\n \n+void format_trailers(const struct process_trailer_options *opts,\n+\t\t     struct list_head *trailers,\n+\t\t     struct strbuf *out)\n+{\n+\tstruct list_head *pos;\n+\tstruct trailer_item *item;\n+\tint need_separator = 0;\n+\n+\tlist_for_each(pos, trailers) {\n+\t\titem = list_entry(pos, struct trailer_item, list);\n+\t\tif (item->token) {\n+\t\t\tchar c;\n+\n+\t\t\tstruct strbuf tok = STRBUF_INIT;\n+\t\t\tstruct strbuf val = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&tok, item->token);\n+\t\t\tstrbuf_addstr(&val, item->value);\n+\n+\t\t\t/*\n+\t\t\t * Skip key/value pairs where the value was empty. This\n+\t\t\t * can happen from trailers specified without a\n+\t\t\t * separator, like `--trailer \"Reviewed-by\"` (no\n+\t\t\t * corresponding value).\n+\t\t\t */\n+\t\t\tif (opts->trim_empty && !strlen(item->value))\n+\t\t\t\tcontinue;\n+\n+\t\t\tif (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n+\t\t\t\tif (opts->unfold)\n+\t\t\t\t\tunfold_value(&val);\n+\n+\t\t\t\tif (opts->separator && need_separator)\n+\t\t\t\t\tstrbuf_addbuf(out, opts->separator);\n+\t\t\t\tif (!opts->value_only)\n+\t\t\t\t\tstrbuf_addbuf(out, &tok);\n+\t\t\t\tif (!opts->key_only && !opts->value_only) {\n+\t\t\t\t\tif (opts->key_value_separator)\n+\t\t\t\t\t\tstrbuf_addbuf(out, opts->key_value_separator);\n+\t\t\t\t\telse {\n+\t\t\t\t\t\tc = last_non_space_char(tok.buf);\n+\t\t\t\t\t\tif (c) {\n+\t\t\t\t\t\t\tif (!strchr(separators, c))\n+\t\t\t\t\t\t\t\tstrbuf_addf(out, \"%c \", separators[0]);\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t\tif (!opts->key_only)\n+\t\t\t\t\tstrbuf_addbuf(out, &val);\n+\t\t\t\tif (!opts->separator)\n+\t\t\t\t\tstrbuf_addch(out, '\\n');\n+\n+\t\t\t\tneed_separator = 1;\n+\t\t\t}\n+\n+\t\t\tstrbuf_release(&tok);\n+\t\t\tstrbuf_release(&val);\n+\t\t} else if (!opts->only_trailers) {\n+\t\t\tif (opts->separator && need_separator) {\n+\t\t\t\tstrbuf_addbuf(out, opts->separator);\n+\t\t\t}\n+\t\t\tstrbuf_addstr(out, item->value);\n+\t\t\tif (opts->separator)\n+\t\t\t\tstrbuf_rtrim(out);\n+\t\t\telse\n+\t\t\t\tstrbuf_addch(out, '\\n');\n+\n+\t\t\tneed_separator = 1;\n+\t\t}\n+\n+\t}\n+}\n+\n /*\n  * Parse trailers in \"str\", populating the trailer info and \"head\"\n  * linked list structure.\n@@ -1083,74 +1124,25 @@ void trailer_info_release(struct trailer_info *info)\n \tfree(info->trailers);\n }\n \n-static void format_trailer_info(struct strbuf *out,\n-\t\t\t\tconst struct trailer_info *info,\n-\t\t\t\tconst char *msg,\n-\t\t\t\tconst struct process_trailer_options *opts)\n+void format_trailers_from_commit(const struct process_trailer_options *opts,\n+\t\t\t\t const char *msg,\n+\t\t\t\t struct strbuf *out)\n {\n-\tsize_t origlen = out->len;\n-\tsize_t i;\n+\tLIST_HEAD(head);\n+\tstruct trailer_info info;\n+\n+\tparse_trailers(opts, &info, msg, &head);\n \n \t/* If we want the whole block untouched, we can take the fast path. */\n \tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n \t    !opts->separator && !opts->key_only && !opts->value_only &&\n \t    !opts->key_value_separator) {\n-\t\tstrbuf_add(out, msg + info->trailer_block_start,\n-\t\t\t   info->trailer_block_end - info->trailer_block_start);\n-\t\treturn;\n-\t}\n-\n-\tfor (i = 0; i < info->trailer_nr; i++) {\n-\t\tchar *trailer = info->trailers[i];\n-\t\tssize_t separator_pos = find_separator(trailer, separators);\n-\n-\t\tif (separator_pos >= 1) {\n-\t\t\tstruct strbuf tok = STRBUF_INIT;\n-\t\t\tstruct strbuf val = STRBUF_INIT;\n-\n-\t\t\tparse_trailer(&tok, &val, NULL, trailer, separator_pos);\n-\t\t\tif (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n-\t\t\t\tif (opts->unfold)\n-\t\t\t\t\tunfold_value(&val);\n-\n-\t\t\t\tif (opts->separator && out->len != origlen)\n-\t\t\t\t\tstrbuf_addbuf(out, opts->separator);\n-\t\t\t\tif (!opts->value_only)\n-\t\t\t\t\tstrbuf_addbuf(out, &tok);\n-\t\t\t\tif (!opts->key_only && !opts->value_only) {\n-\t\t\t\t\tif (opts->key_value_separator)\n-\t\t\t\t\t\tstrbuf_addbuf(out, opts->key_value_separator);\n-\t\t\t\t\telse\n-\t\t\t\t\t\tstrbuf_addstr(out, \": \");\n-\t\t\t\t}\n-\t\t\t\tif (!opts->key_only)\n-\t\t\t\t\tstrbuf_addbuf(out, &val);\n-\t\t\t\tif (!opts->separator)\n-\t\t\t\t\tstrbuf_addch(out, '\\n');\n-\t\t\t}\n-\t\t\tstrbuf_release(&tok);\n-\t\t\tstrbuf_release(&val);\n-\n-\t\t} else if (!opts->only_trailers) {\n-\t\t\tif (opts->separator && out->len != origlen) {\n-\t\t\t\tstrbuf_addbuf(out, opts->separator);\n-\t\t\t}\n-\t\t\tstrbuf_addstr(out, trailer);\n-\t\t\tif (opts->separator) {\n-\t\t\t\tstrbuf_rtrim(out);\n-\t\t\t}\n-\t\t}\n-\t}\n-\n-}\n-\n-void format_trailers_from_commit(struct strbuf *out, const char *msg,\n-\t\t\t\t const struct process_trailer_options *opts)\n-{\n-\tstruct trailer_info info;\n+\t\tstrbuf_add(out, msg + info.trailer_block_start,\n+\t\t\t   info.trailer_block_end - info.trailer_block_start);\n+\t} else\n+\t\tformat_trailers(opts, &head, out);\n \n-\ttrailer_info_get(&info, msg, opts);\n-\tformat_trailer_info(out, &info, msg, opts);\n+\tfree_trailers(&head);\n \ttrailer_info_release(&info);\n }\n \ndiff --git a/trailer.h b/trailer.h\nindex ca701c04f3b..244f29fc91f 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -101,22 +101,17 @@ void trailer_info_release(struct trailer_info *info);\n \n void trailer_config_init(void);\n void format_trailers(const struct process_trailer_options *opts,\n-\t\t     struct list_head *trailers, FILE *outfile);\n+\t\t     struct list_head *trailers,\n+\t\t     struct strbuf *out);\n void free_trailers(struct list_head *);\n \n /*\n- * Format the trailers from the commit msg \"msg\" into the strbuf \"out\".\n- * Note two caveats about \"opts\":\n- *\n- *   - this is primarily a helper for pretty.c, and not\n- *     all of the flags are supported.\n- *\n- *   - this differs from process_trailers slightly in that we always format\n- *     only the trailer block itself, even if the \"only_trailers\" option is not\n- *     set.\n+ * Convenience function to format the trailers from the commit msg \"msg\" into\n+ * the strbuf \"out\". Reuses format_trailers internally.\n  */\n-void format_trailers_from_commit(struct strbuf *out, const char *msg,\n-\t\t\t\t const struct process_trailer_options *opts);\n+void format_trailers_from_commit(const struct process_trailer_options *opts,\n+\t\t\t\t const char *msg,\n+\t\t\t\t struct strbuf *out);\n \n /*\n  * An interface for iterating over the trailers found in a particular commit\n-- \ngitgitgadget\n\n"},{"id":"487621","messageId":"bf2b8e1a3c4bc77022fab1ebaa0fc89a7813b4c4.1706664145.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v3.git.1706664144.gitgitgadget@gmail.com","subject":"[PATCH v3 04/10] sequencer: use the trailer iterator","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-31T01:22:18Z","receivedAt":"2024-01-31T01:22:32Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis patch allows for the removal of \"trailer_info_get()\" from the\ntrailer.h API, which will be in the next patch.\n\nInstead of calling \"trailer_info_get()\", which is a low-level function\nin the trailers implementation (trailer.c), call\ntrailer_iterator_advance(), which was specifically designed for public\nconsumption in f0939a0eb1 (trailer: add interface for iterating over\ncommit trailers, 2020-09-27).\n\nAvoiding \"trailer_info_get()\" means we don't have to worry about options\nlike \"no_divider\" (relevant for parsing trailers). We also don't have to\ncheck for things like \"info.trailer_start == info.trailer_end\" to see\nwhether there were any trailers (instead we can just check to see\nwhether the iterator advanced at all).\n\nAlso, teach the iterator about non-trailer lines, by adding a new field\ncalled \"raw\" to hold both trailer and non-trailer lines. This is\nnecessary because a \"trailer block\" is a list of trailer lines of at\nleast 25% trailers (see 146245063e (trailer: allow non-trailers in\ntrailer block, 2016-10-21)), such that it may hold non-trailer lines.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/shortlog.c |  7 +++++--\n sequencer.c        | 35 +++++++++++++++--------------------\n trailer.c          | 17 +++++++++--------\n trailer.h          | 13 +++++++++++++\n 4 files changed, 42 insertions(+), 30 deletions(-)\n\ndiff --git a/builtin/shortlog.c b/builtin/shortlog.c\nindex 1307ed2b88a..dc8fd5a5532 100644\n--- a/builtin/shortlog.c\n+++ b/builtin/shortlog.c\n@@ -172,7 +172,7 @@ static void insert_records_from_trailers(struct shortlog *log,\n \t\t\t\t\t const char *oneline)\n {\n \tstruct trailer_iterator iter;\n-\tconst char *commit_buffer, *body;\n+\tconst char *commit_buffer, *body, *value;\n \tstruct strbuf ident = STRBUF_INIT;\n \n \tif (!log->trailers.nr)\n@@ -190,7 +190,10 @@ static void insert_records_from_trailers(struct shortlog *log,\n \n \ttrailer_iterator_init(&iter, body);\n \twhile (trailer_iterator_advance(&iter)) {\n-\t\tconst char *value = iter.val.buf;\n+\t\tif (!iter.is_trailer)\n+\t\t\tcontinue;\n+\n+\t\tvalue = iter.val.buf;\n \n \t\tif (!string_list_has_string(&log->trailers, iter.key.buf))\n \t\t\tcontinue;\ndiff --git a/sequencer.c b/sequencer.c\nindex 3cc88d8a800..bc7c82c5271 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -319,37 +319,32 @@ static const char *get_todo_path(const struct replay_opts *opts)\n static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,\n \tsize_t ignore_footer)\n {\n-\tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n-\tstruct trailer_info info;\n-\tsize_t i;\n-\tint found_sob = 0, found_sob_last = 0;\n-\tchar saved_char;\n-\n-\topts.no_divider = 1;\n+\tstruct trailer_iterator iter;\n+\tsize_t i = 0, found_sob = 0;\n+\tchar saved_char = sb->buf[sb->len - ignore_footer];\n \n \tif (ignore_footer) {\n-\t\tsaved_char = sb->buf[sb->len - ignore_footer];\n \t\tsb->buf[sb->len - ignore_footer] = '\\0';\n \t}\n \n-\ttrailer_info_get(&info, sb->buf, &opts);\n+\ttrailer_iterator_init(&iter, sb->buf);\n+\twhile (trailer_iterator_advance(&iter)) {\n+\t\ti++;\n+\t\tif (sob &&\n+\t\t    iter.is_trailer &&\n+\t\t    !strncmp(iter.raw, sob->buf, sob->len)) {\n+\t\t\tfound_sob = i;\n+\t\t}\n+\t}\n+\ttrailer_iterator_release(&iter);\n \n \tif (ignore_footer)\n \t\tsb->buf[sb->len - ignore_footer] = saved_char;\n \n-\tif (info.trailer_block_start == info.trailer_block_end)\n+\tif (!i)\n \t\treturn 0;\n \n-\tfor (i = 0; i < info.trailer_nr; i++)\n-\t\tif (sob && !strncmp(info.trailers[i], sob->buf, sob->len)) {\n-\t\t\tfound_sob = 1;\n-\t\t\tif (i == info.trailer_nr - 1)\n-\t\t\t\tfound_sob_last = 1;\n-\t\t}\n-\n-\ttrailer_info_release(&info);\n-\n-\tif (found_sob_last)\n+\tif (found_sob == i)\n \t\treturn 3;\n \tif (found_sob)\n \t\treturn 2;\ndiff --git a/trailer.c b/trailer.c\nindex 71ea2bb67f8..5bcc9b0006c 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1158,17 +1158,18 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n \n int trailer_iterator_advance(struct trailer_iterator *iter)\n {\n-\twhile (iter->internal.cur < iter->internal.info.trailer_nr) {\n-\t\tchar *trailer = iter->internal.info.trailers[iter->internal.cur++];\n-\t\tint separator_pos = find_separator(trailer, separators);\n-\n-\t\tif (separator_pos < 1)\n-\t\t\tcontinue; /* not a real trailer */\n-\n+\tchar *line;\n+\tint separator_pos;\n+\tif (iter->internal.cur < iter->internal.info.trailer_nr) {\n+\t\tline = iter->internal.info.trailers[iter->internal.cur++];\n+\t\tseparator_pos = find_separator(line, separators);\n+\t\titer->is_trailer = (separator_pos > 0);\n+\n+\t\titer->raw = line;\n \t\tstrbuf_reset(&iter->key);\n \t\tstrbuf_reset(&iter->val);\n \t\tparse_trailer(&iter->key, &iter->val, NULL,\n-\t\t\t      trailer, separator_pos);\n+\t\t\t      line, separator_pos);\n \t\tunfold_value(&iter->val);\n \t\treturn 1;\n \t}\ndiff --git a/trailer.h b/trailer.h\nindex 244f29fc91f..a7599067acc 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -127,6 +127,19 @@ struct trailer_iterator {\n \tstruct strbuf key;\n \tstruct strbuf val;\n \n+\t/*\n+\t * Raw line (e.g., \"foo: bar baz\") before being parsed as a trailer\n+\t * key/val pair as part of a trailer block. A trailer block can be\n+\t * either 100% trailer lines, or mixed in with non-trailer lines (in\n+\t * which case at least 25% must be trailer lines).\n+\t */\n+\tconst char *raw;\n+\n+\t/*\n+\t * 1 if the raw line was parsed as a trailer line (key/val pair).\n+\t */\n+\tint is_trailer;\n+\n \t/* private */\n \tstruct {\n \t\tstruct trailer_info info;\n-- \ngitgitgadget\n\n"},{"id":"487622","messageId":"c19c1dcc592186d8da2857692f4ebbfe35adfac0.1706664145.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v3.git.1706664144.gitgitgadget@gmail.com","subject":"[PATCH v3 05/10] trailer: make trailer_info struct private","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-31T01:22:19Z","receivedAt":"2024-01-31T01:22:33Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nIn 13211ae23f (trailer: separate public from internal portion of\ntrailer_iterator, 2023-09-09) we moved trailer_info behind an anonymous\nstruct to discourage use by trailer.h API users. However it still left\nopen the possibility of external use of trailer_info itself. Now that\nthere are no external users of trailer_info, we can make this struct\nprivate.\n\nMake this struct private by putting its definition inside trailer.c.\nThis has two benefits:\n\n  (1) it makes the surface area of the public facing\n      interface (trailer.h) smaller, and\n\n  (2) external API users are unable to peer inside this struct (because\n      it is only ever exposed as an opaque pointer).\n\nThis change exposes some deficiencies in the API, mainly with regard to\ninformation about the location of the trailer block that was parsed.\nExpose new API functions to access this information (needed by\nbuiltin/interpret-trailers.c).\n\nThe idea in this patch to hide implementation details behind an \"opaque\npointer\" is also known as the \"pimpl\" (pointer to implementation) idiom\nin C++ and is a common pattern in that language (where, for example,\nabstract classes only have pointers to concrete classes).\n\nHowever, the original inspiration to use this idiom does not come from\nC++, but instead the book \"C Interfaces and Implementations: Techniques\nfor Creating Reusable Software\" [1]. This book recommends opaque\npointers as a good design principle for designing C libraries, using the\nterm \"interface\" as the functions defined in *.h (header) files and\n\"implementation\" as the corresponding *.c file which define the\ninterfaces.\n\nThe book says this about opaque pointers:\n\n    ... clients can manipulate such pointers freely, but they can’t\n    dereference them; that is, they can’t look at the innards of the\n    structure pointed to by them. Only the implementation has that\n    privilege. Opaque pointers hide representation details and help\n    catch errors.\n\nIn our case, \"struct trailer_info\" is now hidden from clients, and the\nways in which this opaque pointer can be used is limited to the richness\nof the trailer.h file. In other words, trailer.h exclusively controls\nexactly how \"trailer_info\" pointers are to be used.\n\n[1] Hanson, David R. \"C Interfaces and Implementations: Techniques for\n    Creating Reusable Software\". Addison Wesley, 1997. p. 22\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  13 +--\n trailer.c                    | 154 +++++++++++++++++++++++------------\n trailer.h                    |  37 ++-------\n 3 files changed, 117 insertions(+), 87 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 5352ee65bd1..9e6ed6b65e2 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -141,7 +141,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \tLIST_HEAD(head);\n \tstruct strbuf sb = STRBUF_INIT;\n \tstruct strbuf trailer_block = STRBUF_INIT;\n-\tstruct trailer_info info;\n+\tstruct trailer_info *info;\n \tFILE *outfile = stdout;\n \n \ttrailer_config_init();\n@@ -151,13 +151,13 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \tif (opts->in_place)\n \t\toutfile = create_in_place_tempfile(file);\n \n-\tparse_trailers(opts, &info, sb.buf, &head);\n+\tinfo = parse_trailers(opts, sb.buf, &head);\n \n \t/* Print the lines before the trailers */\n \tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf, 1, info.trailer_block_start, outfile);\n+\t\tfwrite(sb.buf, 1, trailer_block_start(info), outfile);\n \n-\tif (!opts->only_trailers && !info.blank_line_before_trailer)\n+\tif (!opts->only_trailers && !blank_line_before_trailer_block(info))\n \t\tfprintf(outfile, \"\\n\");\n \n \n@@ -176,11 +176,12 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \tstrbuf_release(&trailer_block);\n \n \tfree_trailers(&head);\n-\ttrailer_info_release(&info);\n \n \t/* Print the lines after the trailers as is */\n \tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n+\t\tfwrite(sb.buf + trailer_block_end(info), 1, sb.len - trailer_block_end(info), outfile);\n+\n+\ttrailer_info_release(info);\n \n \tif (opts->in_place)\n \t\tif (rename_tempfile(&trailers_tempfile, file))\ndiff --git a/trailer.c b/trailer.c\nindex 5bcc9b0006c..63774cd068d 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -11,6 +11,27 @@\n  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n  */\n \n+struct trailer_info {\n+\t/*\n+\t * True if there is a blank line before the location pointed to by\n+\t * trailer_block_start.\n+\t */\n+\tint blank_line_before_trailer;\n+\n+\t/*\n+\t * Offsets to the trailer block start and end positions in the input\n+\t * string. If no trailer block is found, these are both set to the\n+\t * \"true\" end of the input (find_end_of_log_message()).\n+\t */\n+\tsize_t trailer_block_start, trailer_block_end;\n+\n+\t/*\n+\t * Array of trailers found.\n+\t */\n+\tchar **trailers;\n+\tsize_t trailer_nr;\n+};\n+\n struct conf_info {\n \tchar *name;\n \tchar *key;\n@@ -1025,20 +1046,72 @@ void format_trailers(const struct process_trailer_options *opts,\n \t}\n }\n \n+static struct trailer_info *trailer_info_new(void)\n+{\n+\tstruct trailer_info *info = xcalloc(1, sizeof(*info));\n+\treturn info;\n+}\n+\n+static struct trailer_info *trailer_info_get(const struct process_trailer_options *opts,\n+\t\t\t\t\t     const char *str)\n+{\n+\tstruct trailer_info *info = trailer_info_new();\n+\tsize_t end_of_log_message = 0, trailer_block_start = 0;\n+\tstruct strbuf **trailer_lines, **ptr;\n+\tchar **trailer_strings = NULL;\n+\tsize_t nr = 0, alloc = 0;\n+\tchar **last = NULL;\n+\n+\ttrailer_config_init();\n+\n+\tend_of_log_message = find_end_of_log_message(str, opts->no_divider);\n+\ttrailer_block_start = find_trailer_block_start(str, end_of_log_message);\n+\n+\ttrailer_lines = strbuf_split_buf(str + trailer_block_start,\n+\t\t\t\t\t end_of_log_message - trailer_block_start,\n+\t\t\t\t\t '\\n',\n+\t\t\t\t\t 0);\n+\tfor (ptr = trailer_lines; *ptr; ptr++) {\n+\t\tif (last && isspace((*ptr)->buf[0])) {\n+\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\t\tstrbuf_attach(&sb, *last, strlen(*last), strlen(*last));\n+\t\t\tstrbuf_addbuf(&sb, *ptr);\n+\t\t\t*last = strbuf_detach(&sb, NULL);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tALLOC_GROW(trailer_strings, nr + 1, alloc);\n+\t\ttrailer_strings[nr] = strbuf_detach(*ptr, NULL);\n+\t\tlast = find_separator(trailer_strings[nr], separators) >= 1\n+\t\t\t? &trailer_strings[nr]\n+\t\t\t: NULL;\n+\t\tnr++;\n+\t}\n+\tstrbuf_list_free(trailer_lines);\n+\n+\tinfo->blank_line_before_trailer = ends_with_blank_line(str,\n+\t\t\t\t\t\t\t       trailer_block_start);\n+\tinfo->trailer_block_start = trailer_block_start;\n+\tinfo->trailer_block_end = end_of_log_message;\n+\tinfo->trailers = trailer_strings;\n+\tinfo->trailer_nr = nr;\n+\n+\treturn info;\n+}\n+\n /*\n  * Parse trailers in \"str\", populating the trailer info and \"head\"\n  * linked list structure.\n  */\n-void parse_trailers(const struct process_trailer_options *opts,\n-\t\t    struct trailer_info *info,\n-\t\t    const char *str,\n-\t\t    struct list_head *head)\n+struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n+\t\t\t\t    const char *str,\n+\t\t\t\t    struct list_head *head)\n {\n+\tstruct trailer_info *info;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n \tsize_t i;\n \n-\ttrailer_info_get(info, str, opts);\n+\tinfo = trailer_info_get(opts, str);\n \n \tfor (i = 0; i < info->trailer_nr; i++) {\n \t\tint separator_pos;\n@@ -1062,6 +1135,8 @@ void parse_trailers(const struct process_trailer_options *opts,\n \t\t\t\t\t strbuf_detach(&val, NULL));\n \t\t}\n \t}\n+\n+\treturn info;\n }\n \n void free_trailers(struct list_head *trailers)\n@@ -1073,47 +1148,19 @@ void free_trailers(struct list_head *trailers)\n \t}\n }\n \n-void trailer_info_get(struct trailer_info *info, const char *str,\n-\t\t      const struct process_trailer_options *opts)\n+size_t trailer_block_start(struct trailer_info *info)\n {\n-\tsize_t end_of_log_message = 0, trailer_block_start = 0;\n-\tstruct strbuf **trailer_lines, **ptr;\n-\tchar **trailer_strings = NULL;\n-\tsize_t nr = 0, alloc = 0;\n-\tchar **last = NULL;\n-\n-\ttrailer_config_init();\n-\n-\tend_of_log_message = find_end_of_log_message(str, opts->no_divider);\n-\ttrailer_block_start = find_trailer_block_start(str, end_of_log_message);\n+\treturn info->trailer_block_start;\n+}\n \n-\ttrailer_lines = strbuf_split_buf(str + trailer_block_start,\n-\t\t\t\t\t end_of_log_message - trailer_block_start,\n-\t\t\t\t\t '\\n',\n-\t\t\t\t\t 0);\n-\tfor (ptr = trailer_lines; *ptr; ptr++) {\n-\t\tif (last && isspace((*ptr)->buf[0])) {\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstrbuf_attach(&sb, *last, strlen(*last), strlen(*last));\n-\t\t\tstrbuf_addbuf(&sb, *ptr);\n-\t\t\t*last = strbuf_detach(&sb, NULL);\n-\t\t\tcontinue;\n-\t\t}\n-\t\tALLOC_GROW(trailer_strings, nr + 1, alloc);\n-\t\ttrailer_strings[nr] = strbuf_detach(*ptr, NULL);\n-\t\tlast = find_separator(trailer_strings[nr], separators) >= 1\n-\t\t\t? &trailer_strings[nr]\n-\t\t\t: NULL;\n-\t\tnr++;\n-\t}\n-\tstrbuf_list_free(trailer_lines);\n+size_t trailer_block_end(struct trailer_info *info)\n+{\n+\treturn info->trailer_block_end;\n+}\n \n-\tinfo->blank_line_before_trailer = ends_with_blank_line(str,\n-\t\t\t\t\t\t\t       trailer_block_start);\n-\tinfo->trailer_block_start = trailer_block_start;\n-\tinfo->trailer_block_end = end_of_log_message;\n-\tinfo->trailers = trailer_strings;\n-\tinfo->trailer_nr = nr;\n+int blank_line_before_trailer_block(struct trailer_info *info)\n+{\n+\treturn info->blank_line_before_trailer;\n }\n \n void trailer_info_release(struct trailer_info *info)\n@@ -1122,6 +1169,7 @@ void trailer_info_release(struct trailer_info *info)\n \tfor (i = 0; i < info->trailer_nr; i++)\n \t\tfree(info->trailers[i]);\n \tfree(info->trailers);\n+\tfree(info);\n }\n \n void format_trailers_from_commit(const struct process_trailer_options *opts,\n@@ -1129,30 +1177,30 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \t\t\t\t struct strbuf *out)\n {\n \tLIST_HEAD(head);\n-\tstruct trailer_info info;\n-\n-\tparse_trailers(opts, &info, msg, &head);\n+\tstruct trailer_info *info = parse_trailers(opts, msg, &head);\n \n \t/* If we want the whole block untouched, we can take the fast path. */\n \tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n \t    !opts->separator && !opts->key_only && !opts->value_only &&\n \t    !opts->key_value_separator) {\n-\t\tstrbuf_add(out, msg + info.trailer_block_start,\n-\t\t\t   info.trailer_block_end - info.trailer_block_start);\n+\t\tstrbuf_add(out, msg + info->trailer_block_start,\n+\t\t\t   info->trailer_block_end - info->trailer_block_start);\n \t} else\n \t\tformat_trailers(opts, &head, out);\n \n \tfree_trailers(&head);\n-\ttrailer_info_release(&info);\n+\ttrailer_info_release(info);\n }\n \n void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n {\n \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n+\tstruct trailer_info *internal = trailer_info_new();\n \tstrbuf_init(&iter->key, 0);\n \tstrbuf_init(&iter->val, 0);\n \topts.no_divider = 1;\n-\ttrailer_info_get(&iter->internal.info, msg, &opts);\n+\titer->internal.info = internal;\n+\titer->internal.info = trailer_info_get(&opts, msg);\n \titer->internal.cur = 0;\n }\n \n@@ -1160,8 +1208,8 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n {\n \tchar *line;\n \tint separator_pos;\n-\tif (iter->internal.cur < iter->internal.info.trailer_nr) {\n-\t\tline = iter->internal.info.trailers[iter->internal.cur++];\n+\tif (iter->internal.cur < iter->internal.info->trailer_nr) {\n+\t\tline = iter->internal.info->trailers[iter->internal.cur++];\n \t\tseparator_pos = find_separator(line, separators);\n \t\titer->is_trailer = (separator_pos > 0);\n \n@@ -1178,7 +1226,7 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n \n void trailer_iterator_release(struct trailer_iterator *iter)\n {\n-\ttrailer_info_release(&iter->internal.info);\n+\ttrailer_info_release(iter->internal.info);\n \tstrbuf_release(&iter->val);\n \tstrbuf_release(&iter->key);\n }\ndiff --git a/trailer.h b/trailer.h\nindex a7599067acc..e19ddf84e64 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -4,6 +4,8 @@\n #include \"list.h\"\n #include \"strbuf.h\"\n \n+struct trailer_info;\n+\n enum trailer_where {\n \tWHERE_DEFAULT,\n \tWHERE_END,\n@@ -29,27 +31,6 @@ int trailer_set_where(enum trailer_where *item, const char *value);\n int trailer_set_if_exists(enum trailer_if_exists *item, const char *value);\n int trailer_set_if_missing(enum trailer_if_missing *item, const char *value);\n \n-struct trailer_info {\n-\t/*\n-\t * True if there is a blank line before the location pointed to by\n-\t * trailer_block_start.\n-\t */\n-\tint blank_line_before_trailer;\n-\n-\t/*\n-\t * Offsets to the trailer block start and end positions in the input\n-\t * string. If no trailer block is found, these are both set to the\n-\t * \"true\" end of the input (find_end_of_log_message()).\n-\t */\n-\tsize_t trailer_block_start, trailer_block_end;\n-\n-\t/*\n-\t * Array of trailers found.\n-\t */\n-\tchar **trailers;\n-\tsize_t trailer_nr;\n-};\n-\n /*\n  * A list that represents newly-added trailers, such as those provided\n  * with the --trailer command line option of git-interpret-trailers.\n@@ -89,13 +70,13 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n void process_trailers_lists(struct list_head *head,\n \t\t\t    struct list_head *arg_head);\n \n-void parse_trailers(const struct process_trailer_options *opts,\n-\t\t    struct trailer_info *info,\n-\t\t    const char *str,\n-\t\t    struct list_head *head);\n+struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n+\t\t\t\t    const char *str,\n+\t\t\t\t    struct list_head *head);\n \n-void trailer_info_get(struct trailer_info *info, const char *str,\n-\t\t      const struct process_trailer_options *opts);\n+size_t trailer_block_start(struct trailer_info *info);\n+size_t trailer_block_end(struct trailer_info *info);\n+int blank_line_before_trailer_block(struct trailer_info *info);\n \n void trailer_info_release(struct trailer_info *info);\n \n@@ -142,7 +123,7 @@ struct trailer_iterator {\n \n \t/* private */\n \tstruct {\n-\t\tstruct trailer_info info;\n+\t\tstruct trailer_info *info;\n \t\tsize_t cur;\n \t} internal;\n };\n-- \ngitgitgadget\n\n"},{"id":"487623","messageId":"0a9a7438c3ff39f1434087bf3ed6a9865758c803.1706664145.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v3.git.1706664144.gitgitgadget@gmail.com","subject":"[PATCH v3 06/10] trailer: spread usage of \"trailer_block\" language","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-31T01:22:20Z","receivedAt":"2024-01-31T01:22:34Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nDeprecate the \"trailer_info\" struct name and replace it with\n\"trailer_block\". The main reason is to help readability, because\n\"trailer_info\" on the surface sounds like it's about a single trailer\nwhen in reality it is a collection of contiguous lines, at least 25% of\nwhich are trailers.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c | 26 +++++-----\n trailer.c                    | 99 ++++++++++++++++++------------------\n trailer.h                    | 18 +++----\n 3 files changed, 71 insertions(+), 72 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 9e6ed6b65e2..9e41fa20b5f 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -140,8 +140,8 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n {\n \tLIST_HEAD(head);\n \tstruct strbuf sb = STRBUF_INIT;\n-\tstruct strbuf trailer_block = STRBUF_INIT;\n-\tstruct trailer_info *info;\n+\tstruct strbuf tb = STRBUF_INIT;\n+\tstruct trailer_block *trailer_block;\n \tFILE *outfile = stdout;\n \n \ttrailer_config_init();\n@@ -151,13 +151,13 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \tif (opts->in_place)\n \t\toutfile = create_in_place_tempfile(file);\n \n-\tinfo = parse_trailers(opts, sb.buf, &head);\n+\ttrailer_block = parse_trailers(opts, sb.buf, &head);\n \n-\t/* Print the lines before the trailers */\n+\t/* Print the lines before the trailer block */\n \tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf, 1, trailer_block_start(info), outfile);\n+\t\tfwrite(sb.buf, 1, trailer_block_start(trailer_block), outfile);\n \n-\tif (!opts->only_trailers && !blank_line_before_trailer_block(info))\n+\tif (!opts->only_trailers && !blank_line_before_trailer_block(trailer_block))\n \t\tfprintf(outfile, \"\\n\");\n \n \n@@ -171,17 +171,17 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \t}\n \n \t/* Print trailer block. */\n-\tformat_trailers(opts, &head, &trailer_block);\n-\tfwrite(trailer_block.buf, 1, trailer_block.len, outfile);\n-\tstrbuf_release(&trailer_block);\n+\tformat_trailers(opts, &head, &tb);\n+\tfwrite(tb.buf, 1, tb.len, outfile);\n+\tstrbuf_release(&tb);\n \n \tfree_trailers(&head);\n \n-\t/* Print the lines after the trailers as is */\n+\t/* Print the lines after the trailer block as is */\n \tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf + trailer_block_end(info), 1, sb.len - trailer_block_end(info), outfile);\n-\n-\ttrailer_info_release(info);\n+\t\tfwrite(sb.buf + trailer_block_end(trailer_block),\n+\t\t       1, sb.len - trailer_block_end(trailer_block), outfile);\n+\ttrailer_block_release(trailer_block);\n \n \tif (opts->in_place)\n \t\tif (rename_tempfile(&trailers_tempfile, file))\ndiff --git a/trailer.c b/trailer.c\nindex 63774cd068d..e2a48bea0ae 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -11,19 +11,20 @@\n  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n  */\n \n-struct trailer_info {\n+struct trailer_block {\n \t/*\n \t * True if there is a blank line before the location pointed to by\n-\t * trailer_block_start.\n+\t * \"start\".\n \t */\n \tint blank_line_before_trailer;\n \n \t/*\n-\t * Offsets to the trailer block start and end positions in the input\n-\t * string. If no trailer block is found, these are both set to the\n-\t * \"true\" end of the input (find_end_of_log_message()).\n+\t * The locations of the start and end positions of the trailer block\n+\t * found, as offsets from the beginning of the source text from which\n+\t * this trailer block was parsed. If no trailer block is found, these\n+\t * are both set to 0.\n \t */\n-\tsize_t trailer_block_start, trailer_block_end;\n+\tsize_t start, end;\n \n \t/*\n \t * Array of trailers found.\n@@ -1046,16 +1047,16 @@ void format_trailers(const struct process_trailer_options *opts,\n \t}\n }\n \n-static struct trailer_info *trailer_info_new(void)\n+static struct trailer_block *trailer_block_new(void)\n {\n-\tstruct trailer_info *info = xcalloc(1, sizeof(*info));\n-\treturn info;\n+\tstruct trailer_block *trailer_block = xcalloc(1, sizeof(*trailer_block));\n+\treturn trailer_block;\n }\n \n-static struct trailer_info *trailer_info_get(const struct process_trailer_options *opts,\n-\t\t\t\t\t     const char *str)\n+static struct trailer_block *trailer_block_get(const struct process_trailer_options *opts,\n+\t\t\t\t\t       const char *str)\n {\n-\tstruct trailer_info *info = trailer_info_new();\n+\tstruct trailer_block *trailer_block = trailer_block_new();\n \tsize_t end_of_log_message = 0, trailer_block_start = 0;\n \tstruct strbuf **trailer_lines, **ptr;\n \tchar **trailer_strings = NULL;\n@@ -1088,34 +1089,34 @@ static struct trailer_info *trailer_info_get(const struct process_trailer_option\n \t}\n \tstrbuf_list_free(trailer_lines);\n \n-\tinfo->blank_line_before_trailer = ends_with_blank_line(str,\n-\t\t\t\t\t\t\t       trailer_block_start);\n-\tinfo->trailer_block_start = trailer_block_start;\n-\tinfo->trailer_block_end = end_of_log_message;\n-\tinfo->trailers = trailer_strings;\n-\tinfo->trailer_nr = nr;\n+\ttrailer_block->blank_line_before_trailer = ends_with_blank_line(str,\n+\t\t\t\t\t\t\t\t\ttrailer_block_start);\n+\ttrailer_block->start = trailer_block_start;\n+\ttrailer_block->end = end_of_log_message;\n+\ttrailer_block->trailers = trailer_strings;\n+\ttrailer_block->trailer_nr = nr;\n \n-\treturn info;\n+\treturn trailer_block;\n }\n \n /*\n- * Parse trailers in \"str\", populating the trailer info and \"head\"\n- * linked list structure.\n+ * Parse trailers in \"str\", populating the trailer_block info and \"head\" linked\n+ * list structure.\n  */\n-struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n-\t\t\t\t    const char *str,\n-\t\t\t\t    struct list_head *head)\n+struct trailer_block *parse_trailers(const struct process_trailer_options *opts,\n+\t\t\t\t     const char *str,\n+\t\t\t\t     struct list_head *head)\n {\n-\tstruct trailer_info *info;\n+\tstruct trailer_block *trailer_block;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n \tsize_t i;\n \n-\tinfo = trailer_info_get(opts, str);\n+\ttrailer_block = trailer_block_get(opts, str);\n \n-\tfor (i = 0; i < info->trailer_nr; i++) {\n+\tfor (i = 0; i < trailer_block->trailer_nr; i++) {\n \t\tint separator_pos;\n-\t\tchar *trailer = info->trailers[i];\n+\t\tchar *trailer = trailer_block->trailers[i];\n \t\tif (trailer[0] == comment_line_char)\n \t\t\tcontinue;\n \t\tseparator_pos = find_separator(trailer, separators);\n@@ -1136,7 +1137,7 @@ struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n \t\t}\n \t}\n \n-\treturn info;\n+\treturn trailer_block;\n }\n \n void free_trailers(struct list_head *trailers)\n@@ -1148,28 +1149,28 @@ void free_trailers(struct list_head *trailers)\n \t}\n }\n \n-size_t trailer_block_start(struct trailer_info *info)\n+size_t trailer_block_start(struct trailer_block *trailer_block)\n {\n-\treturn info->trailer_block_start;\n+\treturn trailer_block->start;\n }\n \n-size_t trailer_block_end(struct trailer_info *info)\n+size_t trailer_block_end(struct trailer_block *trailer_block)\n {\n-\treturn info->trailer_block_end;\n+\treturn trailer_block->end;\n }\n \n-int blank_line_before_trailer_block(struct trailer_info *info)\n+int blank_line_before_trailer_block(struct trailer_block *trailer_block)\n {\n-\treturn info->blank_line_before_trailer;\n+\treturn trailer_block->blank_line_before_trailer;\n }\n \n-void trailer_info_release(struct trailer_info *info)\n+void trailer_block_release(struct trailer_block *trailer_block)\n {\n \tsize_t i;\n-\tfor (i = 0; i < info->trailer_nr; i++)\n-\t\tfree(info->trailers[i]);\n-\tfree(info->trailers);\n-\tfree(info);\n+\tfor (i = 0; i < trailer_block->trailer_nr; i++)\n+\t\tfree(trailer_block->trailers[i]);\n+\tfree(trailer_block->trailers);\n+\tfree(trailer_block);\n }\n \n void format_trailers_from_commit(const struct process_trailer_options *opts,\n@@ -1177,30 +1178,28 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \t\t\t\t struct strbuf *out)\n {\n \tLIST_HEAD(head);\n-\tstruct trailer_info *info = parse_trailers(opts, msg, &head);\n+\tstruct trailer_block *trailer_block = parse_trailers(opts, msg, &head);\n \n \t/* If we want the whole block untouched, we can take the fast path. */\n \tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n \t    !opts->separator && !opts->key_only && !opts->value_only &&\n \t    !opts->key_value_separator) {\n-\t\tstrbuf_add(out, msg + info->trailer_block_start,\n-\t\t\t   info->trailer_block_end - info->trailer_block_start);\n+\t\tstrbuf_add(out, msg + trailer_block->start,\n+\t\t\t   trailer_block->end - trailer_block->start);\n \t} else\n \t\tformat_trailers(opts, &head, out);\n \n \tfree_trailers(&head);\n-\ttrailer_info_release(info);\n+\ttrailer_block_release(trailer_block);\n }\n \n void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n {\n \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n-\tstruct trailer_info *internal = trailer_info_new();\n \tstrbuf_init(&iter->key, 0);\n \tstrbuf_init(&iter->val, 0);\n \topts.no_divider = 1;\n-\titer->internal.info = internal;\n-\titer->internal.info = trailer_info_get(&opts, msg);\n+\titer->internal.trailer_block = trailer_block_get(&opts, msg);\n \titer->internal.cur = 0;\n }\n \n@@ -1208,8 +1207,8 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n {\n \tchar *line;\n \tint separator_pos;\n-\tif (iter->internal.cur < iter->internal.info->trailer_nr) {\n-\t\tline = iter->internal.info->trailers[iter->internal.cur++];\n+\tif (iter->internal.cur < iter->internal.trailer_block->trailer_nr) {\n+\t\tline = iter->internal.trailer_block->trailers[iter->internal.cur++];\n \t\tseparator_pos = find_separator(line, separators);\n \t\titer->is_trailer = (separator_pos > 0);\n \n@@ -1226,7 +1225,7 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n \n void trailer_iterator_release(struct trailer_iterator *iter)\n {\n-\ttrailer_info_release(iter->internal.info);\n+\ttrailer_block_release(iter->internal.trailer_block);\n \tstrbuf_release(&iter->val);\n \tstrbuf_release(&iter->key);\n }\ndiff --git a/trailer.h b/trailer.h\nindex e19ddf84e64..e74f9189c0a 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -4,7 +4,7 @@\n #include \"list.h\"\n #include \"strbuf.h\"\n \n-struct trailer_info;\n+struct trailer_block;\n \n enum trailer_where {\n \tWHERE_DEFAULT,\n@@ -70,15 +70,15 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n void process_trailers_lists(struct list_head *head,\n \t\t\t    struct list_head *arg_head);\n \n-struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n-\t\t\t\t    const char *str,\n-\t\t\t\t    struct list_head *head);\n+struct trailer_block *parse_trailers(const struct process_trailer_options *opts,\n+\t\t\t\t     const char *str,\n+\t\t\t\t     struct list_head *head);\n \n-size_t trailer_block_start(struct trailer_info *info);\n-size_t trailer_block_end(struct trailer_info *info);\n-int blank_line_before_trailer_block(struct trailer_info *info);\n+size_t trailer_block_start(struct trailer_block *trailer_block);\n+size_t trailer_block_end(struct trailer_block *trailer_block);\n+int blank_line_before_trailer_block(struct trailer_block *trailer_block);\n \n-void trailer_info_release(struct trailer_info *info);\n+void trailer_block_release(struct trailer_block *trailer_block);\n \n void trailer_config_init(void);\n void format_trailers(const struct process_trailer_options *opts,\n@@ -123,7 +123,7 @@ struct trailer_iterator {\n \n \t/* private */\n \tstruct {\n-\t\tstruct trailer_info *info;\n+\t\tstruct trailer_block *trailer_block;\n \t\tsize_t cur;\n \t} internal;\n };\n-- \ngitgitgadget\n\n"},{"id":"487625","messageId":"97e5d86ddf0ea25df1b512dfbf620910298c1b02.1706664145.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v3.git.1706664144.gitgitgadget@gmail.com","subject":"[PATCH v3 07/10] trailer: prepare to move parse_trailers_from_command_line_args() to builtin","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-31T01:22:21Z","receivedAt":"2024-01-31T01:22:35Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nExpose more functions in the trailer.h API, in preparation for moving\nout\n\n    parse_trailers_from_command_line_args()\n\nto interpret-trailer.c, because the trailer API should not be concerned\nwith command line arguments (as they have nothing to do with trailers\nthemselves). The interpret-trailers builtin is the only caller of this\nfunction.\n\nRename add_arg_item() to trailer_add_arg_item() because it will have to\nbe exposed as an API function in the next patch. Rename\nnew_trailers_clear() to free_new_trailers() because it will be promoted\ninto an API function; the API already has free_trailers(), so using the\n\"free_*\" naming style will keep it consistent. Also rename \"conf_info\"\nto \"trailer_conf\" for readability, dropping the low-value \"_info\" suffix\nas we did earlier in this series for \"trailer_info\" to \"trailer_block\".\n\nHelped-by: Josh Steadmon <steadmon@google.com>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  6 +--\n trailer.c                    | 86 ++++++++++++++++++------------------\n trailer.h                    | 10 +++++\n 3 files changed, 55 insertions(+), 47 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 9e41fa20b5f..9f0ba39b317 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -45,7 +45,7 @@ static int option_parse_if_missing(const struct option *opt,\n \treturn trailer_set_if_missing(opt->value, arg);\n }\n \n-static void new_trailers_clear(struct list_head *trailers)\n+static void free_new_trailers(struct list_head *trailers)\n {\n \tstruct list_head *pos, *tmp;\n \tstruct new_trailer_item *item;\n@@ -64,7 +64,7 @@ static int option_parse_trailer(const struct option *opt,\n \tstruct new_trailer_item *item;\n \n \tif (unset) {\n-\t\tnew_trailers_clear(trailers);\n+\t\tfree_new_trailers(trailers);\n \t\treturn 0;\n \t}\n \n@@ -238,7 +238,7 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n \t\tinterpret_trailers(&opts, &trailers, NULL);\n \t}\n \n-\tnew_trailers_clear(&trailers);\n+\tfree_new_trailers(&trailers);\n \n \treturn 0;\n }\ndiff --git a/trailer.c b/trailer.c\nindex e2a48bea0ae..c16f552b463 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -33,7 +33,7 @@ struct trailer_block {\n \tsize_t trailer_nr;\n };\n \n-struct conf_info {\n+struct trailer_conf {\n \tchar *name;\n \tchar *key;\n \tchar *command;\n@@ -43,7 +43,7 @@ struct conf_info {\n \tenum trailer_if_missing if_missing;\n };\n \n-static struct conf_info default_conf_info;\n+static struct trailer_conf default_trailer_conf;\n \n struct trailer_item {\n \tstruct list_head list;\n@@ -59,7 +59,7 @@ struct arg_item {\n \tstruct list_head list;\n \tchar *token;\n \tchar *value;\n-\tstruct conf_info conf;\n+\tstruct trailer_conf conf;\n };\n \n static LIST_HEAD(conf_head);\n@@ -210,7 +210,7 @@ static int check_if_different(struct trailer_item *in_tok,\n \treturn 1;\n }\n \n-static char *apply_command(struct conf_info *conf, const char *arg)\n+static char *apply_command(struct trailer_conf *conf, const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -424,7 +424,8 @@ int trailer_set_if_missing(enum trailer_if_missing *item, const char *value)\n \treturn 0;\n }\n \n-static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n+void duplicate_trailer_conf(struct trailer_conf *dst,\n+\t\t\t    const struct trailer_conf *src)\n {\n \t*dst = *src;\n \tdst->name = xstrdup_or_null(src->name);\n@@ -447,7 +448,7 @@ static struct arg_item *get_conf_item(const char *name)\n \n \t/* Item does not already exists, create it */\n \tCALLOC_ARRAY(item, 1);\n-\tduplicate_conf(&item->conf, &default_conf_info);\n+\tduplicate_trailer_conf(&item->conf, &default_trailer_conf);\n \titem->conf.name = xstrdup(name);\n \n \tlist_add_tail(&item->list, &conf_head);\n@@ -482,17 +483,17 @@ static int git_trailer_default_config(const char *conf_key, const char *value,\n \tvariable_name = strrchr(trailer_item, '.');\n \tif (!variable_name) {\n \t\tif (!strcmp(trailer_item, \"where\")) {\n-\t\t\tif (trailer_set_where(&default_conf_info.where,\n+\t\t\tif (trailer_set_where(&default_trailer_conf.where,\n \t\t\t\t\t      value) < 0)\n \t\t\t\twarning(_(\"unknown value '%s' for key '%s'\"),\n \t\t\t\t\tvalue, conf_key);\n \t\t} else if (!strcmp(trailer_item, \"ifexists\")) {\n-\t\t\tif (trailer_set_if_exists(&default_conf_info.if_exists,\n+\t\t\tif (trailer_set_if_exists(&default_trailer_conf.if_exists,\n \t\t\t\t\t\t  value) < 0)\n \t\t\t\twarning(_(\"unknown value '%s' for key '%s'\"),\n \t\t\t\t\tvalue, conf_key);\n \t\t} else if (!strcmp(trailer_item, \"ifmissing\")) {\n-\t\t\tif (trailer_set_if_missing(&default_conf_info.if_missing,\n+\t\t\tif (trailer_set_if_missing(&default_trailer_conf.if_missing,\n \t\t\t\t\t\t   value) < 0)\n \t\t\t\twarning(_(\"unknown value '%s' for key '%s'\"),\n \t\t\t\t\tvalue, conf_key);\n@@ -511,7 +512,7 @@ static int git_trailer_config(const char *conf_key, const char *value,\n {\n \tconst char *trailer_item, *variable_name;\n \tstruct arg_item *item;\n-\tstruct conf_info *conf;\n+\tstruct trailer_conf *conf;\n \tchar *name = NULL;\n \tenum trailer_info_type type;\n \tint i;\n@@ -585,9 +586,9 @@ void trailer_config_init(void)\n \t\treturn;\n \n \t/* Default config must be setup first */\n-\tdefault_conf_info.where = WHERE_END;\n-\tdefault_conf_info.if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;\n-\tdefault_conf_info.if_missing = MISSING_ADD;\n+\tdefault_trailer_conf.where = WHERE_END;\n+\tdefault_trailer_conf.if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;\n+\tdefault_trailer_conf.if_missing = MISSING_ADD;\n \tgit_config(git_trailer_default_config, NULL);\n \tgit_config(git_trailer_config, NULL);\n \tconfigured = 1;\n@@ -620,7 +621,7 @@ static int token_matches_item(const char *tok, struct arg_item *item, size_t tok\n  * distinguished from the non-well-formed-line case (in which this function\n  * returns -1) because some callers of this function need such a distinction.\n  */\n-static ssize_t find_separator(const char *line, const char *separators)\n+ssize_t find_separator(const char *line, const char *separators)\n {\n \tint whitespace_found = 0;\n \tconst char *c;\n@@ -645,28 +646,28 @@ static ssize_t find_separator(const char *line, const char *separators)\n  *\n  * If separator_pos is -1, interpret the whole trailer as a token.\n  */\n-static void parse_trailer(struct strbuf *tok, struct strbuf *val,\n-\t\t\t const struct conf_info **conf, const char *trailer,\n-\t\t\t ssize_t separator_pos)\n+void parse_trailer(const char *line, ssize_t separator_pos,\n+\t\t   struct strbuf *tok, struct strbuf *val,\n+\t\t   const struct trailer_conf **conf)\n {\n \tstruct arg_item *item;\n \tsize_t tok_len;\n \tstruct list_head *pos;\n \n \tif (separator_pos != -1) {\n-\t\tstrbuf_add(tok, trailer, separator_pos);\n+\t\tstrbuf_add(tok, line, separator_pos);\n \t\tstrbuf_trim(tok);\n-\t\tstrbuf_addstr(val, trailer + separator_pos + 1);\n+\t\tstrbuf_addstr(val, line + separator_pos + 1);\n \t\tstrbuf_trim(val);\n \t} else {\n-\t\tstrbuf_addstr(tok, trailer);\n+\t\tstrbuf_addstr(tok, line);\n \t\tstrbuf_trim(tok);\n \t}\n \n \t/* Lookup if the token matches something in the config */\n \ttok_len = token_len_without_separator(tok->buf, tok->len);\n \tif (conf)\n-\t\t*conf = &default_conf_info;\n+\t\t*conf = &default_trailer_conf;\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct arg_item, list);\n \t\tif (token_matches_item(tok->buf, item, tok_len)) {\n@@ -690,14 +691,14 @@ static struct trailer_item *add_trailer_item(struct list_head *head, char *tok,\n \treturn new_item;\n }\n \n-static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n-\t\t\t const struct conf_info *conf,\n-\t\t\t const struct new_trailer_item *new_trailer_item)\n+static void trailer_add_arg_item(struct list_head *arg_head, char *tok, char *val,\n+\t\t\t\t const struct trailer_conf *conf,\n+\t\t\t\t const struct new_trailer_item *new_trailer_item)\n {\n \tstruct arg_item *new_item = xcalloc(1, sizeof(*new_item));\n \tnew_item->token = tok;\n \tnew_item->value = val;\n-\tduplicate_conf(&new_item->conf, conf);\n+\tduplicate_trailer_conf(&new_item->conf, conf);\n \tif (new_trailer_item) {\n \t\tif (new_trailer_item->where != WHERE_DEFAULT)\n \t\t\tnew_item->conf.where = new_trailer_item->where;\n@@ -718,10 +719,10 @@ void parse_trailers_from_config(struct list_head *config_head)\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct arg_item, list);\n \t\tif (item->conf.command)\n-\t\t\tadd_arg_item(config_head,\n-\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n-\t\t\t\t     xstrdup(\"\"),\n-\t\t\t\t     &item->conf, NULL);\n+\t\t\ttrailer_add_arg_item(config_head,\n+\t\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n+\t\t\t\t\t     xstrdup(\"\"),\n+\t\t\t\t\t     &item->conf, NULL);\n \t}\n }\n \n@@ -730,7 +731,7 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n {\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n-\tconst struct conf_info *conf;\n+\tconst struct trailer_conf *conf;\n \tstruct list_head *pos;\n \n \t/*\n@@ -753,12 +754,11 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n \t\t\t      (int) sb.len, sb.buf);\n \t\t\tstrbuf_release(&sb);\n \t\t} else {\n-\t\t\tparse_trailer(&tok, &val, &conf, tr->text,\n-\t\t\t\t      separator_pos);\n-\t\t\tadd_arg_item(arg_head,\n-\t\t\t\t     strbuf_detach(&tok, NULL),\n-\t\t\t\t     strbuf_detach(&val, NULL),\n-\t\t\t\t     conf, tr);\n+\t\t\tparse_trailer(tr->text, separator_pos, &tok, &val, &conf);\n+\t\t\ttrailer_add_arg_item(arg_head,\n+\t\t\t\t\t     strbuf_detach(&tok, NULL),\n+\t\t\t\t\t     strbuf_detach(&val, NULL),\n+\t\t\t\t\t     conf, tr);\n \t\t}\n \t}\n \n@@ -1116,20 +1116,19 @@ struct trailer_block *parse_trailers(const struct process_trailer_options *opts,\n \n \tfor (i = 0; i < trailer_block->trailer_nr; i++) {\n \t\tint separator_pos;\n-\t\tchar *trailer = trailer_block->trailers[i];\n-\t\tif (trailer[0] == comment_line_char)\n+\t\tchar *line = trailer_block->trailers[i];\n+\t\tif (line[0] == comment_line_char)\n \t\t\tcontinue;\n-\t\tseparator_pos = find_separator(trailer, separators);\n+\t\tseparator_pos = find_separator(line, separators);\n \t\tif (separator_pos >= 1) {\n-\t\t\tparse_trailer(&tok, &val, NULL, trailer,\n-\t\t\t\t      separator_pos);\n+\t\t\tparse_trailer(line, separator_pos, &tok, &val, NULL);\n \t\t\tif (opts->unfold)\n \t\t\t\tunfold_value(&val);\n \t\t\tadd_trailer_item(head,\n \t\t\t\t\t strbuf_detach(&tok, NULL),\n \t\t\t\t\t strbuf_detach(&val, NULL));\n \t\t} else if (!opts->only_trailers) {\n-\t\t\tstrbuf_addstr(&val, trailer);\n+\t\t\tstrbuf_addstr(&val, line);\n \t\t\tstrbuf_strip_suffix(&val, \"\\n\");\n \t\t\tadd_trailer_item(head,\n \t\t\t\t\t NULL,\n@@ -1215,8 +1214,7 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n \t\titer->raw = line;\n \t\tstrbuf_reset(&iter->key);\n \t\tstrbuf_reset(&iter->val);\n-\t\tparse_trailer(&iter->key, &iter->val, NULL,\n-\t\t\t      line, separator_pos);\n+\t\tparse_trailer(line, separator_pos, &iter->key, &iter->val, NULL);\n \t\tunfold_value(&iter->val);\n \t\treturn 1;\n \t}\ndiff --git a/trailer.h b/trailer.h\nindex e74f9189c0a..d724263e4f6 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -5,6 +5,7 @@\n #include \"strbuf.h\"\n \n struct trailer_block;\n+struct trailer_conf;\n \n enum trailer_where {\n \tWHERE_DEFAULT,\n@@ -45,6 +46,9 @@ struct new_trailer_item {\n \tenum trailer_if_missing if_missing;\n };\n \n+void duplicate_trailer_conf(struct trailer_conf *dst,\n+\t\t\t    const struct trailer_conf *src);\n+\n struct process_trailer_options {\n \tint in_place;\n \tint trim_empty;\n@@ -70,6 +74,12 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n void process_trailers_lists(struct list_head *head,\n \t\t\t    struct list_head *arg_head);\n \n+ssize_t find_separator(const char *line, const char *separators);\n+\n+void parse_trailer(const char *line, ssize_t separator_pos,\n+\t\t   struct strbuf *tok, struct strbuf *val,\n+\t\t   const struct trailer_conf **conf);\n+\n struct trailer_block *parse_trailers(const struct process_trailer_options *opts,\n \t\t\t\t     const char *str,\n \t\t\t\t     struct list_head *head);\n-- \ngitgitgadget\n\n"},{"id":"487624","messageId":"465dc51cdcba28d235241021bc52369f6082d329.1706664145.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v3.git.1706664144.gitgitgadget@gmail.com","subject":"[PATCH v3 08/10] trailer: move arg handling to interpret-trailers.c","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-31T01:22:22Z","receivedAt":"2024-01-31T01:22:36Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nWe don't move the \"arg_item\" struct to interpret-trailers.c, because it\nis now a struct that contains information about trailers that could be\nadded into the input text's own trailers. This is a generic concept that\nextends beyond trailers defined as CLI arguments (it applies to trailers\ndefined in configuration as well). We will rename \"arg_item\" to\n\"trailer_template\" in a follow-up patch to keep the diff here small.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c | 88 ++++++++++++++++++++++--------------\n trailer.c                    | 62 ++++++++++++++++++-------\n trailer.h                    | 12 +++++\n 3 files changed, 112 insertions(+), 50 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 9f0ba39b317..9a902012912 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -45,23 +45,17 @@ static int option_parse_if_missing(const struct option *opt,\n \treturn trailer_set_if_missing(opt->value, arg);\n }\n \n-static void free_new_trailers(struct list_head *trailers)\n-{\n-\tstruct list_head *pos, *tmp;\n-\tstruct new_trailer_item *item;\n-\n-\tlist_for_each_safe(pos, tmp, trailers) {\n-\t\titem = list_entry(pos, struct new_trailer_item, list);\n-\t\tlist_del(pos);\n-\t\tfree(item);\n-\t}\n-}\n+static char *cl_separators;\n \n static int option_parse_trailer(const struct option *opt,\n \t\t\t\t   const char *arg, int unset)\n {\n \tstruct list_head *trailers = opt->value;\n-\tstruct new_trailer_item *item;\n+\tstruct strbuf tok = STRBUF_INIT;\n+\tstruct strbuf val = STRBUF_INIT;\n+\tconst struct trailer_conf *conf;\n+\tstruct trailer_conf *conf_current = new_trailer_conf();\n+\tssize_t separator_pos;\n \n \tif (unset) {\n \t\tfree_new_trailers(trailers);\n@@ -71,12 +65,31 @@ static int option_parse_trailer(const struct option *opt,\n \tif (!arg)\n \t\treturn -1;\n \n-\titem = xmalloc(sizeof(*item));\n-\titem->text = arg;\n-\titem->where = where;\n-\titem->if_exists = if_exists;\n-\titem->if_missing = if_missing;\n-\tlist_add_tail(&item->list, trailers);\n+\tseparator_pos = find_separator(arg, cl_separators);\n+\tif (separator_pos) {\n+\t\tparse_trailer(arg, separator_pos, &tok, &val, &conf);\n+\t\tduplicate_trailer_conf(conf_current, conf);\n+\n+\t\t/*\n+\t\t * Override conf_current with settings specified via CLI flags.\n+\t\t */\n+\t\ttrailer_conf_set(where, if_exists, if_missing, conf_current);\n+\n+\t\ttrailer_add_arg_item(strbuf_detach(&tok, NULL),\n+\t\t\t\t     strbuf_detach(&val, NULL),\n+\t\t\t\t     conf_current,\n+\t\t\t\t     trailers);\n+\t} else {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_addstr(&sb, arg);\n+\t\tstrbuf_trim(&sb);\n+\t\terror(_(\"empty trailer token in trailer '%.*s'\"),\n+\t\t\t(int) sb.len, sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t}\n+\n+\tfree(conf_current);\n+\n \treturn 0;\n }\n \n@@ -135,7 +148,7 @@ static void read_input_file(struct strbuf *sb, const char *file)\n }\n \n static void interpret_trailers(const struct process_trailer_options *opts,\n-\t\t\t       struct list_head *new_trailer_head,\n+\t\t\t       struct list_head *arg_trailers,\n \t\t\t       const char *file)\n {\n \tLIST_HEAD(head);\n@@ -144,8 +157,6 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \tstruct trailer_block *trailer_block;\n \tFILE *outfile = stdout;\n \n-\ttrailer_config_init();\n-\n \tread_input_file(&sb, file);\n \n \tif (opts->in_place)\n@@ -162,12 +173,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \n \n \tif (!opts->only_input) {\n-\t\tLIST_HEAD(config_head);\n-\t\tLIST_HEAD(arg_head);\n-\t\tparse_trailers_from_config(&config_head);\n-\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n-\t\tlist_splice(&config_head, &arg_head);\n-\t\tprocess_trailers_lists(&head, &arg_head);\n+\t\tprocess_trailers_lists(&head, arg_trailers);\n \t}\n \n \t/* Print trailer block. */\n@@ -193,7 +199,8 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n {\n \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n-\tLIST_HEAD(trailers);\n+\tLIST_HEAD(configured_trailers);\n+\tLIST_HEAD(arg_trailers);\n \n \tstruct option options[] = {\n \t\tOPT_BOOL(0, \"in-place\", &opts.in_place, N_(\"edit files in place\")),\n@@ -212,33 +219,48 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK_F(0, \"parse\", &opts, NULL, N_(\"alias for --only-trailers --only-input --unfold\"),\n \t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse),\n \t\tOPT_BOOL(0, \"no-divider\", &opts.no_divider, N_(\"do not treat \\\"---\\\" as the end of input\")),\n-\t\tOPT_CALLBACK(0, \"trailer\", &trailers, N_(\"trailer\"),\n+\t\tOPT_CALLBACK(0, \"trailer\", &arg_trailers, N_(\"trailer\"),\n \t\t\t\tN_(\"trailer(s) to add\"), option_parse_trailer),\n \t\tOPT_END()\n \t};\n \n \tgit_config(git_default_config, NULL);\n+\ttrailer_config_init();\n+\n+\tif (!opts.only_input) {\n+\t\tparse_trailers_from_config(&configured_trailers);\n+\t}\n+\n+\t/*\n+\t* In command-line arguments, '=' is accepted (in addition to the\n+\t* separators that are defined).\n+\t*/\n+\tcl_separators = xstrfmt(\"=%s\", trailer_default_separators());\n \n \targc = parse_options(argc, argv, prefix, options,\n \t\t\t     git_interpret_trailers_usage, 0);\n \n-\tif (opts.only_input && !list_empty(&trailers))\n+\tfree(cl_separators);\n+\n+\tif (opts.only_input && !list_empty(&arg_trailers))\n \t\tusage_msg_opt(\n \t\t\t_(\"--trailer with --only-input does not make sense\"),\n \t\t\tgit_interpret_trailers_usage,\n \t\t\toptions);\n \n+\tlist_splice(&configured_trailers, &arg_trailers);\n+\n \tif (argc) {\n \t\tint i;\n \t\tfor (i = 0; i < argc; i++)\n-\t\t\tinterpret_trailers(&opts, &trailers, argv[i]);\n+\t\t\tinterpret_trailers(&opts, &arg_trailers, argv[i]);\n \t} else {\n \t\tif (opts.in_place)\n \t\t\tdie(_(\"no input file given for in-place editing\"));\n-\t\tinterpret_trailers(&opts, &trailers, NULL);\n+\t\tinterpret_trailers(&opts, &arg_trailers, NULL);\n \t}\n \n-\tfree_new_trailers(&trailers);\n+\tfree_new_trailers(&arg_trailers);\n \n \treturn 0;\n }\ndiff --git a/trailer.c b/trailer.c\nindex c16f552b463..19637ff295d 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -66,6 +66,11 @@ static LIST_HEAD(conf_head);\n \n static char *separators = \":\";\n \n+const char *trailer_default_separators(void)\n+{\n+\treturn separators;\n+}\n+\n static int configured;\n \n #define TRAILER_ARG_STRING \"$ARG\"\n@@ -424,6 +429,25 @@ int trailer_set_if_missing(enum trailer_if_missing *item, const char *value)\n \treturn 0;\n }\n \n+void trailer_conf_set(enum trailer_where where,\n+\t\t      enum trailer_if_exists if_exists,\n+\t\t      enum trailer_if_missing if_missing,\n+\t\t      struct trailer_conf *conf)\n+{\n+\tif (where != WHERE_DEFAULT)\n+\t\tconf->where = where;\n+\tif (if_exists != EXISTS_DEFAULT)\n+\t\tconf->if_exists = if_exists;\n+\tif (if_missing != MISSING_DEFAULT)\n+\t\tconf->if_missing = if_missing;\n+}\n+\n+struct trailer_conf *new_trailer_conf(void)\n+{\n+\tstruct trailer_conf *new = xcalloc(1, sizeof(*new));\n+\treturn new;\n+}\n+\n void duplicate_trailer_conf(struct trailer_conf *dst,\n \t\t\t    const struct trailer_conf *src)\n {\n@@ -642,6 +666,9 @@ ssize_t find_separator(const char *line, const char *separators)\n /*\n  * Obtain the token, value, and conf from the given trailer.\n  *\n+ * The conf needs special handling. We first read hardcoded defaults, and\n+ * override them if we find a matching trailer configuration.\n+ *\n  * separator_pos must not be 0, since the token cannot be an empty string.\n  *\n  * If separator_pos is -1, interpret the whole trailer as a token.\n@@ -691,22 +718,13 @@ static struct trailer_item *add_trailer_item(struct list_head *head, char *tok,\n \treturn new_item;\n }\n \n-static void trailer_add_arg_item(struct list_head *arg_head, char *tok, char *val,\n-\t\t\t\t const struct trailer_conf *conf,\n-\t\t\t\t const struct new_trailer_item *new_trailer_item)\n+void trailer_add_arg_item(char *tok, char *val, const struct trailer_conf *conf,\n+\t\t\t  struct list_head *arg_head)\n {\n \tstruct arg_item *new_item = xcalloc(1, sizeof(*new_item));\n \tnew_item->token = tok;\n \tnew_item->value = val;\n \tduplicate_trailer_conf(&new_item->conf, conf);\n-\tif (new_trailer_item) {\n-\t\tif (new_trailer_item->where != WHERE_DEFAULT)\n-\t\t\tnew_item->conf.where = new_trailer_item->where;\n-\t\tif (new_trailer_item->if_exists != EXISTS_DEFAULT)\n-\t\t\tnew_item->conf.if_exists = new_trailer_item->if_exists;\n-\t\tif (new_trailer_item->if_missing != MISSING_DEFAULT)\n-\t\t\tnew_item->conf.if_missing = new_trailer_item->if_missing;\n-\t}\n \tlist_add_tail(&new_item->list, arg_head);\n }\n \n@@ -719,10 +737,10 @@ void parse_trailers_from_config(struct list_head *config_head)\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct arg_item, list);\n \t\tif (item->conf.command)\n-\t\t\ttrailer_add_arg_item(config_head,\n-\t\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n+\t\t\ttrailer_add_arg_item(xstrdup(token_from_item(item, NULL)),\n \t\t\t\t\t     xstrdup(\"\"),\n-\t\t\t\t\t     &item->conf, NULL);\n+\t\t\t\t\t     &item->conf,\n+\t\t\t\t\t     config_head);\n \t}\n }\n \n@@ -755,10 +773,10 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n \t\t\tstrbuf_release(&sb);\n \t\t} else {\n \t\t\tparse_trailer(tr->text, separator_pos, &tok, &val, &conf);\n-\t\t\ttrailer_add_arg_item(arg_head,\n-\t\t\t\t\t     strbuf_detach(&tok, NULL),\n+\t\t\ttrailer_add_arg_item(strbuf_detach(&tok, NULL),\n \t\t\t\t\t     strbuf_detach(&val, NULL),\n-\t\t\t\t\t     conf, tr);\n+\t\t\t\t\t     conf,\n+\t\t\t\t\t     arg_head);\n \t\t}\n \t}\n \n@@ -1148,6 +1166,16 @@ void free_trailers(struct list_head *trailers)\n \t}\n }\n \n+void free_new_trailers(struct list_head *trailers)\n+{\n+\tstruct list_head *pos, *p;\n+\n+\tlist_for_each_safe(pos, p, trailers) {\n+\t\tlist_del(pos);\n+\t\tfree_arg_item(list_entry(pos, struct arg_item, list));\n+\t}\n+}\n+\n size_t trailer_block_start(struct trailer_block *trailer_block)\n {\n \treturn trailer_block->start;\ndiff --git a/trailer.h b/trailer.h\nindex d724263e4f6..8fcf1969a3c 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -46,9 +46,20 @@ struct new_trailer_item {\n \tenum trailer_if_missing if_missing;\n };\n \n+void trailer_conf_set(enum trailer_where where,\n+\t\t      enum trailer_if_exists if_exists,\n+\t\t      enum trailer_if_missing if_missing,\n+\t\t      struct trailer_conf *conf);\n+\n+struct trailer_conf *new_trailer_conf(void);\n void duplicate_trailer_conf(struct trailer_conf *dst,\n \t\t\t    const struct trailer_conf *src);\n \n+const char *trailer_default_separators(void);\n+\n+void trailer_add_arg_item(char *tok, char *val, const struct trailer_conf *conf,\n+\t\t\t  struct list_head *arg_head);\n+\n struct process_trailer_options {\n \tint in_place;\n \tint trim_empty;\n@@ -95,6 +106,7 @@ void format_trailers(const struct process_trailer_options *opts,\n \t\t     struct list_head *trailers,\n \t\t     struct strbuf *out);\n void free_trailers(struct list_head *);\n+void free_new_trailers(struct list_head *);\n \n /*\n  * Convenience function to format the trailers from the commit msg \"msg\" into\n-- \ngitgitgadget\n\n"},{"id":"487626","messageId":"885ac87a5447e54139171fb3eda62055ffd517cd.1706664145.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v3.git.1706664144.gitgitgadget@gmail.com","subject":"[PATCH v3 09/10] trailer: delete obsolete argument handling code from API","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-31T01:22:23Z","receivedAt":"2024-01-31T01:22:36Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis commit was not squashed with its parent in order to keep the diff\nseparate (to make the additive changes in the parent easier to read).\n\nNote that we remove the \"new_trailer_item\" struct, because it has some\noverlap with \"arg_item\" struct that also deals with trailers coming from\nthe command line. The latter will be renamed to \"trailer_template\" in\nthe next patch.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 39 ---------------------------------------\n trailer.h | 17 -----------------\n 2 files changed, 56 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 19637ff295d..bf1d2eee550 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -744,45 +744,6 @@ void parse_trailers_from_config(struct list_head *config_head)\n \t}\n }\n \n-void parse_trailers_from_command_line_args(struct list_head *arg_head,\n-\t\t\t\t\t   struct list_head *new_trailer_head)\n-{\n-\tstruct strbuf tok = STRBUF_INIT;\n-\tstruct strbuf val = STRBUF_INIT;\n-\tconst struct trailer_conf *conf;\n-\tstruct list_head *pos;\n-\n-\t/*\n-\t * In command-line arguments, '=' is accepted (in addition to the\n-\t * separators that are defined).\n-\t */\n-\tchar *cl_separators = xstrfmt(\"=%s\", separators);\n-\n-\t/* Add an arg item for each trailer on the command line */\n-\tlist_for_each(pos, new_trailer_head) {\n-\t\tstruct new_trailer_item *tr =\n-\t\t\tlist_entry(pos, struct new_trailer_item, list);\n-\t\tssize_t separator_pos = find_separator(tr->text, cl_separators);\n-\n-\t\tif (separator_pos == 0) {\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstrbuf_addstr(&sb, tr->text);\n-\t\t\tstrbuf_trim(&sb);\n-\t\t\terror(_(\"empty trailer token in trailer '%.*s'\"),\n-\t\t\t      (int) sb.len, sb.buf);\n-\t\t\tstrbuf_release(&sb);\n-\t\t} else {\n-\t\t\tparse_trailer(tr->text, separator_pos, &tok, &val, &conf);\n-\t\t\ttrailer_add_arg_item(strbuf_detach(&tok, NULL),\n-\t\t\t\t\t     strbuf_detach(&val, NULL),\n-\t\t\t\t\t     conf,\n-\t\t\t\t\t     arg_head);\n-\t\t}\n-\t}\n-\n-\tfree(cl_separators);\n-}\n-\n static const char *next_line(const char *str)\n {\n \tconst char *nl = strchrnul(str, '\\n');\ndiff --git a/trailer.h b/trailer.h\nindex 8fcf1969a3c..5d4bacd9931 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -32,20 +32,6 @@ int trailer_set_where(enum trailer_where *item, const char *value);\n int trailer_set_if_exists(enum trailer_if_exists *item, const char *value);\n int trailer_set_if_missing(enum trailer_if_missing *item, const char *value);\n \n-/*\n- * A list that represents newly-added trailers, such as those provided\n- * with the --trailer command line option of git-interpret-trailers.\n- */\n-struct new_trailer_item {\n-\tstruct list_head list;\n-\n-\tconst char *text;\n-\n-\tenum trailer_where where;\n-\tenum trailer_if_exists if_exists;\n-\tenum trailer_if_missing if_missing;\n-};\n-\n void trailer_conf_set(enum trailer_where where,\n \t\t      enum trailer_if_exists if_exists,\n \t\t      enum trailer_if_missing if_missing,\n@@ -79,9 +65,6 @@ struct process_trailer_options {\n \n void parse_trailers_from_config(struct list_head *config_head);\n \n-void parse_trailers_from_command_line_args(struct list_head *arg_head,\n-\t\t\t\t\t   struct list_head *new_trailer_head);\n-\n void process_trailers_lists(struct list_head *head,\n \t\t\t    struct list_head *arg_head);\n \n-- \ngitgitgadget\n\n"},{"id":"487627","messageId":"bcd3fc9660e08f5e8fea7c8c85bdd69aae3b1e93.1706664145.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v3.git.1706664144.gitgitgadget@gmail.com","subject":"[PATCH v3 10/10] trailer: introduce \"template\" term for readability","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-01-31T01:22:24Z","receivedAt":"2024-01-31T01:22:38Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThe term \"arg_item\" is ambiguous because we use it to hold data for\n\n  (1) trailers specified as command line arguments (in\n      builtin/interpret-trailers.c), and\n\n  (2) trailers specified in configuration,\n\nand these are both used to ultimately insert new trailers (based\non the contents of arg_item, acting as a kind of template) into some\nother set of existing trailers (such as those found in a trailer block\ninside a log message) that have already been parsed.\n\nRename \"arg_item\" to \"trailer_template\". This necessitates further\nrenames to make the functions that act on these templates match the data\nstructures (parameters) they act on:\n\n  - [*] add_arg_to_input_list()      to apply_template_to_trailers()\n  - [*] apply_arg_if_exists()        to maybe_add_if_exists()\n  - [*] apply_arg_if_missing()       to maybe_add_if_missing()\n  -     apply_command()              to run_command_from_template()\n  - [*] apply_item_command()         to populate_template_value()\n  -     free_arg_item()              to free_template() (non-API function)\n  -     free_new_trailers()          to free_trailer_templates() (API function)\n  -     get_conf_item()              to get_or_add_template_by()\n  -     option_parse_trailer()       to option_parse_trailer_template()\n  -     parse_trailers_from_config() to parse_trailer_templates_from_config()\n  - [*] process_trailers_lists()     to apply_trailer_templates()\n  -     token_from_item()            to token_from_template()\n  -     token_matches_item           to token_matches_template\n  - [*] trailer_add_arg_item()       to add_trailer_template()\n  -     trailer_from_arg()           to trailer_from()\n  - [*] check_if_different()         (reorder parameters only)\n  - [*] find_same_and_apply_arg()    (reorder parameters only)\n\nReorder parameters where appropriate; these functions have been marked\nwith an asterisk ([*]).\n\nThis removes the \"arg\" terminology (standing for \"CLI arguments\") from\nthe trailer implementation, which makes sense because trailers\nthemselves have nothing to do with CLI argument handling.\n\nAlso note that these renames expose the previously liberal use of\n\"trailer\" to mean both trailers we read from the input text (trailer\nblock) and trailer templates that are defined as CLI args or\nconfigurations. Some functions implied a single action when they could\ndo two different things, so introduce words like \"maybe\" and \"or\" to\nunmask their behavior.\n\nIn summary this patch renames and reorders parameters for readability,\nwithout any behavioral change. We don't rename\nfind_same_and_apply_arg(), because it will be refactored soon. As an\nadded benefit, we no longer use the term \"arg\" to mean \"CLI arguments\"\nin the trailer implementation, because trailers themselves should not be\nconcerned about CLI arguments (this is the domain of the\ninterpret-trailers builtin).\n\nFor parse_trailers_from_config() (renamed to\nparse_trailer_templates_from_config()), add a NEEDSWORK discussion about\nhow the deprecated trailer.*.command configuration option is oddly more\nfeatureful than trailer.*.cmd (if we were to remove support for\ntrailer.*.command, users would not be able to replicate the behavior\nwith trailer.*.cmd and lose out on functionality).\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  48 +++---\n trailer.c                    | 292 +++++++++++++++++++----------------\n trailer.h                    |  11 +-\n 3 files changed, 192 insertions(+), 159 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 9a902012912..aae3f3119df 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -47,10 +47,14 @@ static int option_parse_if_missing(const struct option *opt,\n \n static char *cl_separators;\n \n-static int option_parse_trailer(const struct option *opt,\n-\t\t\t\t   const char *arg, int unset)\n+/*\n+ * Interpret \"--trailer ...\" as trailer templates (trailers we want to add into\n+ * the input text).\n+ */\n+static int option_parse_trailer_template(const struct option *opt,\n+\t\t\t\t\t const char *arg, int unset)\n {\n-\tstruct list_head *trailers = opt->value;\n+\tstruct list_head *templates = opt->value;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n \tconst struct trailer_conf *conf;\n@@ -58,7 +62,7 @@ static int option_parse_trailer(const struct option *opt,\n \tssize_t separator_pos;\n \n \tif (unset) {\n-\t\tfree_new_trailers(trailers);\n+\t\tfree_trailer_templates(templates);\n \t\treturn 0;\n \t}\n \n@@ -75,10 +79,10 @@ static int option_parse_trailer(const struct option *opt,\n \t\t */\n \t\ttrailer_conf_set(where, if_exists, if_missing, conf_current);\n \n-\t\ttrailer_add_arg_item(strbuf_detach(&tok, NULL),\n+\t\tadd_trailer_template(strbuf_detach(&tok, NULL),\n \t\t\t\t     strbuf_detach(&val, NULL),\n \t\t\t\t     conf_current,\n-\t\t\t\t     trailers);\n+\t\t\t\t     templates);\n \t} else {\n \t\tstruct strbuf sb = STRBUF_INIT;\n \t\tstrbuf_addstr(&sb, arg);\n@@ -148,10 +152,10 @@ static void read_input_file(struct strbuf *sb, const char *file)\n }\n \n static void interpret_trailers(const struct process_trailer_options *opts,\n-\t\t\t       struct list_head *arg_trailers,\n+\t\t\t       struct list_head *templates,\n \t\t\t       const char *file)\n {\n-\tLIST_HEAD(head);\n+\tLIST_HEAD(trailers_from_sb);\n \tstruct strbuf sb = STRBUF_INIT;\n \tstruct strbuf tb = STRBUF_INIT;\n \tstruct trailer_block *trailer_block;\n@@ -162,7 +166,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \tif (opts->in_place)\n \t\toutfile = create_in_place_tempfile(file);\n \n-\ttrailer_block = parse_trailers(opts, sb.buf, &head);\n+\ttrailer_block = parse_trailers(opts, sb.buf, &trailers_from_sb);\n \n \t/* Print the lines before the trailer block */\n \tif (!opts->only_trailers)\n@@ -173,15 +177,15 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \n \n \tif (!opts->only_input) {\n-\t\tprocess_trailers_lists(&head, arg_trailers);\n+\t\tapply_trailer_templates(templates, &trailers_from_sb);\n \t}\n \n \t/* Print trailer block. */\n-\tformat_trailers(opts, &head, &tb);\n+\tformat_trailers(opts, &trailers_from_sb, &tb);\n \tfwrite(tb.buf, 1, tb.len, outfile);\n \tstrbuf_release(&tb);\n \n-\tfree_trailers(&head);\n+\tfree_trailers(&trailers_from_sb);\n \n \t/* Print the lines after the trailer block as is */\n \tif (!opts->only_trailers)\n@@ -199,8 +203,8 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n {\n \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n-\tLIST_HEAD(configured_trailers);\n-\tLIST_HEAD(arg_trailers);\n+\tLIST_HEAD(configured_templates);\n+\tLIST_HEAD(templates);\n \n \tstruct option options[] = {\n \t\tOPT_BOOL(0, \"in-place\", &opts.in_place, N_(\"edit files in place\")),\n@@ -219,8 +223,8 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK_F(0, \"parse\", &opts, NULL, N_(\"alias for --only-trailers --only-input --unfold\"),\n \t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse),\n \t\tOPT_BOOL(0, \"no-divider\", &opts.no_divider, N_(\"do not treat \\\"---\\\" as the end of input\")),\n-\t\tOPT_CALLBACK(0, \"trailer\", &arg_trailers, N_(\"trailer\"),\n-\t\t\t\tN_(\"trailer(s) to add\"), option_parse_trailer),\n+\t\tOPT_CALLBACK(0, \"trailer\", &templates, N_(\"trailer\"),\n+\t\t\t\tN_(\"trailer(s) to add\"), option_parse_trailer_template),\n \t\tOPT_END()\n \t};\n \n@@ -228,7 +232,7 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n \ttrailer_config_init();\n \n \tif (!opts.only_input) {\n-\t\tparse_trailers_from_config(&configured_trailers);\n+\t\tparse_trailer_templates_from_config(&configured_templates);\n \t}\n \n \t/*\n@@ -242,25 +246,25 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n \n \tfree(cl_separators);\n \n-\tif (opts.only_input && !list_empty(&arg_trailers))\n+\tif (opts.only_input && !list_empty(&templates))\n \t\tusage_msg_opt(\n \t\t\t_(\"--trailer with --only-input does not make sense\"),\n \t\t\tgit_interpret_trailers_usage,\n \t\t\toptions);\n \n-\tlist_splice(&configured_trailers, &arg_trailers);\n+\tlist_splice(&configured_templates, &templates);\n \n \tif (argc) {\n \t\tint i;\n \t\tfor (i = 0; i < argc; i++)\n-\t\t\tinterpret_trailers(&opts, &arg_trailers, argv[i]);\n+\t\t\tinterpret_trailers(&opts, &templates, argv[i]);\n \t} else {\n \t\tif (opts.in_place)\n \t\t\tdie(_(\"no input file given for in-place editing\"));\n-\t\tinterpret_trailers(&opts, &arg_trailers, NULL);\n+\t\tinterpret_trailers(&opts, &templates, NULL);\n \t}\n \n-\tfree_new_trailers(&arg_trailers);\n+\tfree_trailer_templates(&templates);\n \n \treturn 0;\n }\ndiff --git a/trailer.c b/trailer.c\nindex bf1d2eee550..a739e2ada86 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -55,14 +55,14 @@ struct trailer_item {\n \tchar *value;\n };\n \n-struct arg_item {\n+struct trailer_template {\n \tstruct list_head list;\n \tchar *token;\n \tchar *value;\n \tstruct trailer_conf conf;\n };\n \n-static LIST_HEAD(conf_head);\n+static LIST_HEAD(templates_from_conf);\n \n static char *separators = \":\";\n \n@@ -105,7 +105,7 @@ static size_t token_len_without_separator(const char *token, size_t len)\n \treturn len;\n }\n \n-static int same_token(struct trailer_item *a, struct arg_item *b)\n+static int same_token(struct trailer_item *a, struct trailer_template *b)\n {\n \tsize_t a_len, b_len, min_len;\n \n@@ -119,12 +119,12 @@ static int same_token(struct trailer_item *a, struct arg_item *b)\n \treturn !strncasecmp(a->token, b->token, min_len);\n }\n \n-static int same_value(struct trailer_item *a, struct arg_item *b)\n+static int same_value(struct trailer_item *a, struct trailer_template *b)\n {\n \treturn !strcasecmp(a->value, b->value);\n }\n \n-static int same_trailer(struct trailer_item *a, struct arg_item *b)\n+static int same_trailer(struct trailer_item *a, struct trailer_template *b)\n {\n \treturn same_token(a, b) && same_value(a, b);\n }\n@@ -151,15 +151,15 @@ static void free_trailer_item(struct trailer_item *item)\n \tfree(item);\n }\n \n-static void free_arg_item(struct arg_item *item)\n+static void free_template(struct trailer_template *template)\n {\n-\tfree(item->conf.name);\n-\tfree(item->conf.key);\n-\tfree(item->conf.command);\n-\tfree(item->conf.cmd);\n-\tfree(item->token);\n-\tfree(item->value);\n-\tfree(item);\n+\tfree(template->conf.name);\n+\tfree(template->conf.key);\n+\tfree(template->conf.command);\n+\tfree(template->conf.cmd);\n+\tfree(template->token);\n+\tfree(template->value);\n+\tfree(template);\n }\n \n static char last_non_space_char(const char *s)\n@@ -171,36 +171,36 @@ static char last_non_space_char(const char *s)\n \treturn '\\0';\n }\n \n-static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)\n+static struct trailer_item *trailer_from(struct trailer_template *template)\n {\n \tstruct trailer_item *new_item = xcalloc(1, sizeof(*new_item));\n-\tnew_item->token = arg_tok->token;\n-\tnew_item->value = arg_tok->value;\n-\targ_tok->token = arg_tok->value = NULL;\n-\tfree_arg_item(arg_tok);\n+\tnew_item->token = template->token;\n+\tnew_item->value = template->value;\n+\ttemplate->token = template->value = NULL;\n+\tfree_template(template);\n \treturn new_item;\n }\n \n-static void add_arg_to_input_list(struct trailer_item *on_tok,\n-\t\t\t\t  struct arg_item *arg_tok)\n+static void apply_template_to_trailers(struct trailer_template *template,\n+\t\t\t\t       struct trailer_item *on_tok)\n {\n-\tint aoe = after_or_end(arg_tok->conf.where);\n-\tstruct trailer_item *to_add = trailer_from_arg(arg_tok);\n+\tint aoe = after_or_end(template->conf.where);\n+\tstruct trailer_item *to_add = trailer_from(template);\n \tif (aoe)\n \t\tlist_add(&to_add->list, &on_tok->list);\n \telse\n \t\tlist_add_tail(&to_add->list, &on_tok->list);\n }\n \n-static int check_if_different(struct trailer_item *in_tok,\n-\t\t\t      struct arg_item *arg_tok,\n-\t\t\t      int check_all,\n-\t\t\t      struct list_head *head)\n+static int check_if_different(struct trailer_template *template,\n+\t\t\t      struct trailer_item *in_tok,\n+\t\t\t      struct list_head *head,\n+\t\t\t      int check_all)\n {\n-\tenum trailer_where where = arg_tok->conf.where;\n+\tenum trailer_where where = template->conf.where;\n \tstruct list_head *next_head;\n \tdo {\n-\t\tif (same_trailer(in_tok, arg_tok))\n+\t\tif (same_trailer(in_tok, template))\n \t\t\treturn 0;\n \t\t/*\n \t\t * if we want to add a trailer after another one,\n@@ -215,7 +215,8 @@ static int check_if_different(struct trailer_item *in_tok,\n \treturn 1;\n }\n \n-static char *apply_command(struct trailer_conf *conf, const char *arg)\n+static char *run_command_from_template(struct trailer_conf *conf,\n+\t\t\t\t       const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -250,133 +251,142 @@ static char *apply_command(struct trailer_conf *conf, const char *arg)\n \treturn result;\n }\n \n-static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n+/*\n+ * Prepare the template by running the command (if any) requested by the\n+ * template in order to populate the template's value field.\n+ */\n+static void populate_template_value(struct trailer_template *template,\n+\t\t\t\t    struct trailer_item *in_tok)\n {\n-\tif (arg_tok->conf.command || arg_tok->conf.cmd) {\n+\tif (template->conf.command || template->conf.cmd) {\n+\t\t/*\n+\t\t * Determine argument to pass into the command.\n+\t\t */\n \t\tconst char *arg;\n-\t\tif (arg_tok->value && arg_tok->value[0]) {\n-\t\t\targ = arg_tok->value;\n+\t\tif (template->value && template->value[0]) {\n+\t\t\targ = template->value;\n \t\t} else {\n \t\t\tif (in_tok && in_tok->value)\n \t\t\t\targ = xstrdup(in_tok->value);\n \t\t\telse\n \t\t\t\targ = xstrdup(\"\");\n \t\t}\n-\t\targ_tok->value = apply_command(&arg_tok->conf, arg);\n+\t\ttemplate->value = run_command_from_template(&template->conf,\n+\t\t\t\t\t\t\t    arg);\n \t\tfree((char *)arg);\n \t}\n }\n \n-static void apply_arg_if_exists(struct trailer_item *in_tok,\n-\t\t\t\tstruct arg_item *arg_tok,\n+static void maybe_add_if_exists(struct trailer_template *template,\n+\t\t\t\tstruct trailer_item *in_tok,\n \t\t\t\tstruct trailer_item *on_tok,\n-\t\t\t\tstruct list_head *head)\n+\t\t\t\tstruct list_head *trailers)\n {\n-\tswitch (arg_tok->conf.if_exists) {\n+\tswitch (template->conf.if_exists) {\n \tcase EXISTS_DO_NOTHING:\n-\t\tfree_arg_item(arg_tok);\n+\t\tfree_template(template);\n \t\tbreak;\n \tcase EXISTS_REPLACE:\n-\t\tapply_item_command(in_tok, arg_tok);\n-\t\tadd_arg_to_input_list(on_tok, arg_tok);\n+\t\tpopulate_template_value(template, in_tok);\n+\t\tapply_template_to_trailers(template, on_tok);\n \t\tlist_del(&in_tok->list);\n \t\tfree_trailer_item(in_tok);\n \t\tbreak;\n \tcase EXISTS_ADD:\n-\t\tapply_item_command(in_tok, arg_tok);\n-\t\tadd_arg_to_input_list(on_tok, arg_tok);\n+\t\tpopulate_template_value(template, in_tok);\n+\t\tapply_template_to_trailers(template, on_tok);\n \t\tbreak;\n \tcase EXISTS_ADD_IF_DIFFERENT:\n-\t\tapply_item_command(in_tok, arg_tok);\n-\t\tif (check_if_different(in_tok, arg_tok, 1, head))\n-\t\t\tadd_arg_to_input_list(on_tok, arg_tok);\n+\t\tpopulate_template_value(template, in_tok);\n+\t\tif (check_if_different(template, in_tok, trailers, 1))\n+\t\t\tapply_template_to_trailers(template, on_tok);\n \t\telse\n-\t\t\tfree_arg_item(arg_tok);\n+\t\t\tfree_template(template);\n \t\tbreak;\n \tcase EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:\n-\t\tapply_item_command(in_tok, arg_tok);\n-\t\tif (check_if_different(on_tok, arg_tok, 0, head))\n-\t\t\tadd_arg_to_input_list(on_tok, arg_tok);\n+\t\tpopulate_template_value(template, in_tok);\n+\t\tif (check_if_different(template, on_tok, trailers, 0))\n+\t\t\tapply_template_to_trailers(template, on_tok);\n \t\telse\n-\t\t\tfree_arg_item(arg_tok);\n+\t\t\tfree_template(template);\n \t\tbreak;\n \tdefault:\n \t\tBUG(\"trailer.c: unhandled value %d\",\n-\t\t    arg_tok->conf.if_exists);\n+\t\t    template->conf.if_exists);\n \t}\n }\n \n-static void apply_arg_if_missing(struct list_head *head,\n-\t\t\t\t struct arg_item *arg_tok)\n+static void maybe_add_if_missing(struct trailer_template *template,\n+\t\t\t\t struct list_head *trailers)\n {\n \tenum trailer_where where;\n \tstruct trailer_item *to_add;\n \n-\tswitch (arg_tok->conf.if_missing) {\n+\tswitch (template->conf.if_missing) {\n \tcase MISSING_DO_NOTHING:\n-\t\tfree_arg_item(arg_tok);\n+\t\tfree_template(template);\n \t\tbreak;\n \tcase MISSING_ADD:\n-\t\twhere = arg_tok->conf.where;\n-\t\tapply_item_command(NULL, arg_tok);\n-\t\tto_add = trailer_from_arg(arg_tok);\n+\t\twhere = template->conf.where;\n+\t\tpopulate_template_value(template, NULL);\n+\t\tto_add = trailer_from(template);\n \t\tif (after_or_end(where))\n-\t\t\tlist_add_tail(&to_add->list, head);\n+\t\t\tlist_add_tail(&to_add->list, trailers);\n \t\telse\n-\t\t\tlist_add(&to_add->list, head);\n+\t\t\tlist_add(&to_add->list, trailers);\n \t\tbreak;\n \tdefault:\n \t\tBUG(\"trailer.c: unhandled value %d\",\n-\t\t    arg_tok->conf.if_missing);\n+\t\t    template->conf.if_missing);\n \t}\n }\n \n-static int find_same_and_apply_arg(struct list_head *head,\n-\t\t\t\t   struct arg_item *arg_tok)\n+static int find_same_and_apply_arg(struct trailer_template *template,\n+\t\t\t\t   struct list_head *trailers)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *in_tok;\n \tstruct trailer_item *on_tok;\n \n-\tenum trailer_where where = arg_tok->conf.where;\n+\tenum trailer_where where = template->conf.where;\n \tint middle = (where == WHERE_AFTER) || (where == WHERE_BEFORE);\n \tint backwards = after_or_end(where);\n \tstruct trailer_item *start_tok;\n \n-\tif (list_empty(head))\n+\tif (list_empty(trailers))\n \t\treturn 0;\n \n-\tstart_tok = list_entry(backwards ? head->prev : head->next,\n+\tstart_tok = list_entry(backwards ? trailers->prev : trailers->next,\n \t\t\t       struct trailer_item,\n \t\t\t       list);\n \n-\tlist_for_each_dir(pos, head, backwards) {\n+\tlist_for_each_dir(pos, trailers, backwards) {\n \t\tin_tok = list_entry(pos, struct trailer_item, list);\n-\t\tif (!same_token(in_tok, arg_tok))\n+\t\tif (!same_token(in_tok, template))\n \t\t\tcontinue;\n \t\ton_tok = middle ? in_tok : start_tok;\n-\t\tapply_arg_if_exists(in_tok, arg_tok, on_tok, head);\n+\t\tmaybe_add_if_exists(template, in_tok, on_tok, trailers);\n \t\treturn 1;\n \t}\n \treturn 0;\n }\n \n-void process_trailers_lists(struct list_head *head,\n-\t\t\t    struct list_head *arg_head)\n+void apply_trailer_templates(struct list_head *templates,\n+\t\t\t     struct list_head *trailers)\n {\n \tstruct list_head *pos, *p;\n-\tstruct arg_item *arg_tok;\n+\tstruct trailer_template *template;\n \n-\tlist_for_each_safe(pos, p, arg_head) {\n+\tlist_for_each_safe(pos, p, templates) {\n \t\tint applied = 0;\n-\t\targ_tok = list_entry(pos, struct arg_item, list);\n+\t\ttemplate = list_entry(pos, struct trailer_template, list);\n \n \t\tlist_del(pos);\n \n-\t\tapplied = find_same_and_apply_arg(head, arg_tok);\n+\t\tapplied = find_same_and_apply_arg(template, trailers);\n \n \t\tif (!applied)\n-\t\t\tapply_arg_if_missing(head, arg_tok);\n+\t\t\tmaybe_add_if_missing(template, trailers);\n \t}\n }\n \n@@ -458,26 +468,26 @@ void duplicate_trailer_conf(struct trailer_conf *dst,\n \tdst->cmd = xstrdup_or_null(src->cmd);\n }\n \n-static struct arg_item *get_conf_item(const char *name)\n+static struct trailer_template *get_or_add_template_by(const char *name)\n {\n \tstruct list_head *pos;\n-\tstruct arg_item *item;\n+\tstruct trailer_template *template;\n \n-\t/* Look up item with same name */\n-\tlist_for_each(pos, &conf_head) {\n-\t\titem = list_entry(pos, struct arg_item, list);\n-\t\tif (!strcasecmp(item->conf.name, name))\n-\t\t\treturn item;\n+\t/* Look up template with same name. */\n+\tlist_for_each(pos, &templates_from_conf) {\n+\t\ttemplate = list_entry(pos, struct trailer_template, list);\n+\t\tif (!strcasecmp(template->conf.name, name))\n+\t\t\treturn template;\n \t}\n \n-\t/* Item does not already exists, create it */\n-\tCALLOC_ARRAY(item, 1);\n-\tduplicate_trailer_conf(&item->conf, &default_trailer_conf);\n-\titem->conf.name = xstrdup(name);\n+\t/* Template does not already exist; create it. */\n+\tCALLOC_ARRAY(template, 1);\n+\tduplicate_trailer_conf(&template->conf, &default_trailer_conf);\n+\ttemplate->conf.name = xstrdup(name);\n \n-\tlist_add_tail(&item->list, &conf_head);\n+\tlist_add_tail(&template->list, &templates_from_conf);\n \n-\treturn item;\n+\treturn template;\n }\n \n enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_CMD,\n@@ -535,7 +545,7 @@ static int git_trailer_config(const char *conf_key, const char *value,\n \t\t\t      void *cb UNUSED)\n {\n \tconst char *trailer_item, *variable_name;\n-\tstruct arg_item *item;\n+\tstruct trailer_template *template;\n \tstruct trailer_conf *conf;\n \tchar *name = NULL;\n \tenum trailer_info_type type;\n@@ -560,8 +570,8 @@ static int git_trailer_config(const char *conf_key, const char *value,\n \tif (!name)\n \t\treturn 0;\n \n-\titem = get_conf_item(name);\n-\tconf = &item->conf;\n+\ttemplate = get_or_add_template_by(name);\n+\tconf = &template->conf;\n \tfree(name);\n \n \tswitch (type) {\n@@ -618,20 +628,22 @@ void trailer_config_init(void)\n \tconfigured = 1;\n }\n \n-static const char *token_from_item(struct arg_item *item, char *tok)\n+static const char *token_from_template(struct trailer_template *template, char *tok)\n {\n-\tif (item->conf.key)\n-\t\treturn item->conf.key;\n+\tif (template->conf.key)\n+\t\treturn template->conf.key;\n \tif (tok)\n \t\treturn tok;\n-\treturn item->conf.name;\n+\treturn template->conf.name;\n }\n \n-static int token_matches_item(const char *tok, struct arg_item *item, size_t tok_len)\n+static int token_matches_template(const char *tok,\n+\t\t\t\t  struct trailer_template *template,\n+\t\t\t\t  size_t tok_len)\n {\n-\tif (!strncasecmp(tok, item->conf.name, tok_len))\n+\tif (!strncasecmp(tok, template->conf.name, tok_len))\n \t\treturn 1;\n-\treturn item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0;\n+\treturn template->conf.key ? !strncasecmp(tok, template->conf.key, tok_len) : 0;\n }\n \n /*\n@@ -677,7 +689,7 @@ void parse_trailer(const char *line, ssize_t separator_pos,\n \t\t   struct strbuf *tok, struct strbuf *val,\n \t\t   const struct trailer_conf **conf)\n {\n-\tstruct arg_item *item;\n+\tstruct trailer_template *template;\n \tsize_t tok_len;\n \tstruct list_head *pos;\n \n@@ -695,13 +707,13 @@ void parse_trailer(const char *line, ssize_t separator_pos,\n \ttok_len = token_len_without_separator(tok->buf, tok->len);\n \tif (conf)\n \t\t*conf = &default_trailer_conf;\n-\tlist_for_each(pos, &conf_head) {\n-\t\titem = list_entry(pos, struct arg_item, list);\n-\t\tif (token_matches_item(tok->buf, item, tok_len)) {\n+\tlist_for_each(pos, &templates_from_conf) {\n+\t\ttemplate = list_entry(pos, struct trailer_template, list);\n+\t\tif (token_matches_template(tok->buf, template, tok_len)) {\n \t\t\tchar *tok_buf = strbuf_detach(tok, NULL);\n \t\t\tif (conf)\n-\t\t\t\t*conf = &item->conf;\n-\t\t\tstrbuf_addstr(tok, token_from_item(item, tok_buf));\n+\t\t\t\t*conf = &template->conf;\n+\t\t\tstrbuf_addstr(tok, token_from_template(template, tok_buf));\n \t\t\tfree(tok_buf);\n \t\t\tbreak;\n \t\t}\n@@ -718,29 +730,41 @@ static struct trailer_item *add_trailer_item(struct list_head *head, char *tok,\n \treturn new_item;\n }\n \n-void trailer_add_arg_item(char *tok, char *val, const struct trailer_conf *conf,\n-\t\t\t  struct list_head *arg_head)\n+void add_trailer_template(char *tok, char *val, const struct trailer_conf *conf,\n+\t\t\t  struct list_head *templates)\n {\n-\tstruct arg_item *new_item = xcalloc(1, sizeof(*new_item));\n-\tnew_item->token = tok;\n-\tnew_item->value = val;\n-\tduplicate_trailer_conf(&new_item->conf, conf);\n-\tlist_add_tail(&new_item->list, arg_head);\n+\tstruct trailer_template *template = xcalloc(1, sizeof(*template));\n+\ttemplate->token = tok;\n+\ttemplate->value = val;\n+\tduplicate_trailer_conf(&template->conf, conf);\n+\tlist_add_tail(&template->list, templates);\n }\n \n-void parse_trailers_from_config(struct list_head *config_head)\n+void parse_trailer_templates_from_config(struct list_head *config_head)\n {\n-\tstruct arg_item *item;\n+\tstruct trailer_template *template;\n \tstruct list_head *pos;\n \n-\t/* Add an arg item for each configured trailer with a command */\n-\tlist_for_each(pos, &conf_head) {\n-\t\titem = list_entry(pos, struct arg_item, list);\n-\t\tif (item->conf.command)\n-\t\t\ttrailer_add_arg_item(xstrdup(token_from_item(item, NULL)),\n-\t\t\t\t\t     xstrdup(\"\"),\n-\t\t\t\t\t     &item->conf,\n-\t\t\t\t\t     config_head);\n+\t/*\n+\t * Get configured templates with a \".command\" option.\n+\t *\n+\t * NEEDSWORK: If the interpret-trailers builtin sees a\n+\t * \"trailer.foo.command = ...\" setting, then the \"foo\" trailer will\n+\t * always be inserted, even if \"--trailer foo\" is not provided.\n+\t * Considering how \".command\" is deprecated, it is a bit strange to see\n+\t * it getting special treatment like this over \".cmd\". Instead, we\n+\t * should add a new option that explicitly lets the user decide if the\n+\t * configured trailer should always be added automatically, or if it\n+\t * should only be added if \"--trailer foo\" is provided (default).\n+\t * Then we can collect configured trailers that have either \".command\"\n+\t * or \".cmd\" below, instead of just \".command\".\n+\t */\n+\tlist_for_each(pos, &templates_from_conf) {\n+\t\ttemplate = list_entry(pos, struct trailer_template, list);\n+\t\tif (template->conf.command)\n+\t\t\tadd_trailer_template(xstrdup(token_from_template(template,\n+\t\t\t\t\t\t\t\t\t NULL)),\n+\t\t\t\t\t     xstrdup(\"\"), &template->conf, config_head);\n \t}\n }\n \n@@ -852,7 +876,7 @@ static size_t find_trailer_block_start(const char *buf, size_t len)\n \t * Get the start of the trailers by looking starting from the end for a\n \t * blank line before a set of non-blank lines that (i) are all\n \t * trailers, or (ii) contains at least one Git-generated trailer and\n-\t * consists of at least 25% trailers.\n+\t * consists of at least 25% configured trailers.\n \t */\n \tfor (l = last_line(buf, len);\n \t     l >= end_of_title;\n@@ -896,10 +920,16 @@ static size_t find_trailer_block_start(const char *buf, size_t len)\n \t\t\tpossible_continuation_lines = 0;\n \t\t\tif (recognized_prefix)\n \t\t\t\tcontinue;\n-\t\t\tlist_for_each(pos, &conf_head) {\n-\t\t\t\tstruct arg_item *item;\n-\t\t\t\titem = list_entry(pos, struct arg_item, list);\n-\t\t\t\tif (token_matches_item(bol, item,\n+\t\t\t/*\n+\t\t\t * The templates here are not used for actually\n+\t\t\t * adding trailers anywhere, but instead to help us\n+\t\t\t * identify trailer lines by comparing their keys with\n+\t\t\t * those found in configured templates.\n+\t\t\t */\n+\t\t\tlist_for_each(pos, &templates_from_conf) {\n+\t\t\t\tstruct trailer_template *template;\n+\t\t\t\ttemplate = list_entry(pos, struct trailer_template, list);\n+\t\t\t\tif (token_matches_template(bol, template,\n \t\t\t\t\t\t       separator_pos)) {\n \t\t\t\t\trecognized_prefix = 1;\n \t\t\t\t\tbreak;\n@@ -1127,13 +1157,13 @@ void free_trailers(struct list_head *trailers)\n \t}\n }\n \n-void free_new_trailers(struct list_head *trailers)\n+void free_trailer_templates(struct list_head *trailer_templates)\n {\n \tstruct list_head *pos, *p;\n \n-\tlist_for_each_safe(pos, p, trailers) {\n+\tlist_for_each_safe(pos, p, trailer_templates) {\n \t\tlist_del(pos);\n-\t\tfree_arg_item(list_entry(pos, struct arg_item, list));\n+\t\tfree_template(list_entry(pos, struct trailer_template, list));\n \t}\n }\n \ndiff --git a/trailer.h b/trailer.h\nindex 5d4bacd9931..79aa123478d 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -43,8 +43,8 @@ void duplicate_trailer_conf(struct trailer_conf *dst,\n \n const char *trailer_default_separators(void);\n \n-void trailer_add_arg_item(char *tok, char *val, const struct trailer_conf *conf,\n-\t\t\t  struct list_head *arg_head);\n+void add_trailer_template(char *tok, char *val, const struct trailer_conf *conf,\n+\t\t\t  struct list_head *templates);\n \n struct process_trailer_options {\n \tint in_place;\n@@ -63,10 +63,9 @@ struct process_trailer_options {\n \n #define PROCESS_TRAILER_OPTIONS_INIT {0}\n \n-void parse_trailers_from_config(struct list_head *config_head);\n+void parse_trailer_templates_from_config(struct list_head *config_head);\n \n-void process_trailers_lists(struct list_head *head,\n-\t\t\t    struct list_head *arg_head);\n+void apply_trailer_templates(struct list_head *templates, struct list_head *trailers_head);\n \n ssize_t find_separator(const char *line, const char *separators);\n \n@@ -89,7 +88,7 @@ void format_trailers(const struct process_trailer_options *opts,\n \t\t     struct list_head *trailers,\n \t\t     struct strbuf *out);\n void free_trailers(struct list_head *);\n-void free_new_trailers(struct list_head *);\n+void free_trailer_templates(struct list_head *);\n \n /*\n  * Convenience function to format the trailers from the commit msg \"msg\" into\n-- \ngitgitgadget\n"},{"id":"487658","messageId":"xmqqy1c55o6a.fsf@gitster.g","threadId":"60715","inReplyTo":"cafa39d10489f90897227a244e012457989a7710.1706664145.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 02/10] trailer: move interpret_trailers() to interpret-trailers.c","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-01-31T18:54:21Z","receivedAt":"2024-01-31T18:54:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Linus Arver <linusa@google.com>\n>\n> The interpret-trailers.c builtin is the only place we need to call\n> interpret_trailers(), so move its definition there.\n\nA few helper functions that are only called by interpret_trailers()\nare also moved, naturally.  I would have less surprised to see\nthe addtion near the beginning of builtin/interpret-trailers.c if\nthis part said:\n\n    ..., so move its definition there, together with a few helper\n    functions called only by it, and remove its external declaration\n    from <trailer.h>.\n\n> Delete the corresponding declaration from trailer.h, which then forces\n> us to expose the working innards of that function.\n\nThis was a bit confusing, at least to me.  The reason why several\nother helper functions that are called by interpret_trailers() need\nto be declared in trailer.h is not because the declaration of\ninterpret_trailers() is deleted from trailer.h but that is how I\nread the above.\n\n    Several helper functions that are called by interpret_trailers()\n    remain in trailer.c because other callers in the same file still\n    call them, so add declaration for them to <trailer.h>.\n\n> This enriches\n> trailer.h with a more granular API, which can then be unit-tested in the\n> future (because interpret_trailers() by itself does too many things to\n> be able to be easily unit-tested).\n>\n> Take this opportunity to demote some file-handling functions out of the\n> trailer API implementation, as these have nothing to do with trailers.\n>\n> Signed-off-by: Linus Arver <linusa@google.com>\n> ---\n>  builtin/interpret-trailers.c |  94 +++++++++++++++++++++++++++\n>  trailer.c                    | 120 ++++-------------------------------\n>  trailer.h                    |  20 +++++-\n\nTogether with the preparatory renaming in the previous step, this\nmade quite a pleasant read.  Thanks.\n"},{"id":"487662","messageId":"ZbqnSNPjIQW3Durz@google.com","threadId":"60715","inReplyTo":"5c7a2354df0f4a29841f9ab8294ead0e1c3b9cf5.1706664145.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 03/10] trailer: unify trailer formatting machinery","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2024-01-31T20:02:16Z","receivedAt":"2024-01-31T20:02:22Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2024.01.31 01:22, Linus Arver via GitGitGadget wrote:\n> This unification will allow us to delete the format_trailer_info() and\n> print_tok_val() functions in the next patch. They are not deleted here\n> in order to keep the diff small.\n\nNeeds to be removed after squashing v2 patch 4 :)\n"},{"id":"487663","messageId":"xmqqy1c545y0.fsf@gitster.g","threadId":"60715","inReplyTo":"5c7a2354df0f4a29841f9ab8294ead0e1c3b9cf5.1706664145.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 03/10] trailer: unify trailer formatting machinery","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-01-31T20:13:27Z","receivedAt":"2024-01-31T20:13:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Linus Arver <linusa@google.com>\n>\n> Currently have two functions for formatting trailers exposed in\n> trailer.h:\n>\n>     void format_trailers(FILE *outfile, struct list_head *head,\n>                         const struct process_trailer_options *opts);\n>\n>     void format_trailers_from_commit(struct strbuf *out, const char *msg,\n>                                     const struct process_trailer_options *opts);\n>\n> and previously these functions, although similar enough (even taking the\n> same process_trailer_options struct pointer), did not build on each\n> other.\n>\n> Make format_trailers_from_commit() rely on format_trailers(). Teach\n> format_trailers() to process trailers with the additional\n> process_trailer_options fields like opts->key_only which is only used by\n> format_trailers_from_commit() and not builtin/interpret-trailers.c.\n> While we're at it, reorder parameters to put the trailer processing\n> options first, and the out parameter (strbuf we write into) at the end.\n>\n> This unification will allow us to delete the format_trailer_info() and\n> print_tok_val() functions in the next patch. They are not deleted here\n> in order to keep the diff small.\n>\n> Helped-by: Junio C Hamano <gitster@pobox.com>\n> Signed-off-by: Linus Arver <linusa@google.com>\n> ---\n\nI am not sure how I helped this particularly (say, compared to all\nthe other patches on the list from everybody), though.\n\nThe updated format_trailers() does so much more than the original\nand does so quite differently, it is hard to tell if the existing\ncaller is getting the same behaviour (modulo obviously it now needs\nto prepare a strbuf and print the resulting string in the strbuf,\nwhich is expected) as before.\n\nThe conversion for the sole existing caller looks like this, ...\n\n>  \tLIST_HEAD(head);\n>  \tstruct strbuf sb = STRBUF_INIT;\n> +\tstruct strbuf trailer_block = STRBUF_INIT;\n>  \tstruct trailer_info info;\n> ...\n> -\tformat_trailers(opts, &head, outfile);\n> +\t/* Print trailer block. */\n> +\tformat_trailers(opts, &head, &trailer_block);\n> +\tfwrite(trailer_block.buf, 1, trailer_block.len, outfile);\n> +\tstrbuf_release(&trailer_block);\n\n... and it is very straight-forward.\n\n\nBut what it called was this:\n\n> -static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n> -{\n> -\tchar c;\n> -\n> -\tif (!tok) {\n> -\t\tfprintf(outfile, \"%s\\n\", val);\n> -\t\treturn;\n> -\t}\n> -\n> -\tc = last_non_space_char(tok);\n> -\tif (!c)\n> -\t\treturn;\n> -\tif (strchr(separators, c))\n> -\t\tfprintf(outfile, \"%s%s\\n\", tok, val);\n> -\telse\n> -\t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n> -}\n> -\n> -void format_trailers(const struct process_trailer_options *opts,\n> -\t\t     struct list_head *trailers, FILE *outfile)\n> -{\n> -\tstruct list_head *pos;\n> -\tstruct trailer_item *item;\n> -\tlist_for_each(pos, trailers) {\n> -\t\titem = list_entry(pos, struct trailer_item, list);\n> -\t\tif ((!opts->trim_empty || strlen(item->value) > 0) &&\n> -\t\t    (!opts->only_trailers || item->token))\n> -\t\t\tprint_tok_val(outfile, item->token, item->value);\n> -\t}\n> -}\n\ni.e. iterate over trailers, and sometimes call print_tok_val(),\nwhich does not do all that much.  Print only the val, or when tok is\nnot empty, show \"tok: val\" while taking into account the possibility\nthat the last byte of tok may already be the separator.\n\nBut the new thing is way more complex.\n\n> +void format_trailers(const struct process_trailer_options *opts,\n> +\t\t     struct list_head *trailers,\n> +\t\t     struct strbuf *out)\n> +{\n> +\tstruct list_head *pos;\n> +\tstruct trailer_item *item;\n> +\tint need_separator = 0;\n> +\n> +\tlist_for_each(pos, trailers) {\n> +\t\titem = list_entry(pos, struct trailer_item, list);\n> +\t\tif (item->token) {\n> +\t\t\tchar c;\n> +\n> +\t\t\tstruct strbuf tok = STRBUF_INIT;\n> +\t\t\tstruct strbuf val = STRBUF_INIT;\n> +\t\t\tstrbuf_addstr(&tok, item->token);\n> +\t\t\tstrbuf_addstr(&val, item->value);\n> +\n> +\t\t\t/*\n> +\t\t\t * Skip key/value pairs where the value was empty. This\n> +\t\t\t * can happen from trailers specified without a\n> +\t\t\t * separator, like `--trailer \"Reviewed-by\"` (no\n> +\t\t\t * corresponding value).\n> +\t\t\t */\n> +\t\t\tif (opts->trim_empty && !strlen(item->value))\n> +\t\t\t\tcontinue;\n\nOK, this matches the first condition to stay silent in the original.\n\n> +\t\t\tif (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n\nFor the original caller of format_trailers(), which is the\ninterpret_trailers(), .filter member is never set, so we always come\nhere, I presume.  cmd_interpret_trailers(), before calling\ninterpret_trailers() could affect the following members:\n\n    .in_place .trim_empty .only_trailers .only_input .unfold .no_divider\n\nso we should make sure this new implementation does not change the\nbehaviour from the original with various combination of these\noptions.\n\n> +\t\t\t\tif (opts->unfold)\n> +\t\t\t\t\tunfold_value(&val);\n\nSo, ... how would this affect an invocation of\n\n    $ git interpret-trailers --unfold\n\nwhich would have set opts.unfold to true in cmd_interpret_trailers()\nand eventually called process_trailers() with it, which eventually\ncalled print_all(), which conditionally called print_tok_val() but\nnever gave the val to unfold_value()?\n\n ... goes and digs a bit more ...\n\nAhhhh, original process_trailers() did this by calling\nparse_trailers() that munged the value.  So its print_all()\ncodepath only had to print what has already been munged.\n\nBut then in this new code, do we unfold only here?  I thought that\nin the new code you moved around, the caller, whose name is now\ninterpret_trailers(), still calls parse_trailers() and that calls\nthe unfold_value().  So, are we doing redundant work that may merely\nbe unneeded (if we are lucky) or could be harmful (if things, other\nthan the unfold thing I just noticed, that are done both in\nparse_trailers() and this function are not idempotent)?\n\nIt could be that a bit more preparatory refactoring would clarify.\nI dunno.\n\n> +\t\t\t\tif (opts->separator && need_separator)\n> +\t\t\t\t\tstrbuf_addbuf(out, opts->separator);\n> +\t\t\t\tif (!opts->value_only)\n> +\t\t\t\t\tstrbuf_addbuf(out, &tok);\n> +\t\t\t\tif (!opts->key_only && !opts->value_only) {\n> +\t\t\t\t\tif (opts->key_value_separator)\n> +\t\t\t\t\t\tstrbuf_addbuf(out, opts->key_value_separator);\n> +\t\t\t\t\telse {\n> +\t\t\t\t\t\tc = last_non_space_char(tok.buf);\n> +\t\t\t\t\t\tif (c) {\n> +\t\t\t\t\t\t\tif (!strchr(separators, c))\n> +\t\t\t\t\t\t\t\tstrbuf_addf(out, \"%c \", separators[0]);\n> +\t\t\t\t\t\t}\n> +\t\t\t\t\t}\n> +\t\t\t\t}\n> +\t\t\t\tif (!opts->key_only)\n> +\t\t\t\t\tstrbuf_addbuf(out, &val);\n> +\t\t\t\tif (!opts->separator)\n> +\t\t\t\t\tstrbuf_addch(out, '\\n');\n> +\n> +\t\t\t\tneed_separator = 1;\n> +\t\t\t}\n> +\n> +\t\t\tstrbuf_release(&tok);\n> +\t\t\tstrbuf_release(&val);\n> +\t\t} else if (!opts->only_trailers) {\n> +\t\t\tif (opts->separator && need_separator) {\n> +\t\t\t\tstrbuf_addbuf(out, opts->separator);\n> +\t\t\t}\n> +\t\t\tstrbuf_addstr(out, item->value);\n> +\t\t\tif (opts->separator)\n> +\t\t\t\tstrbuf_rtrim(out);\n> +\t\t\telse\n> +\t\t\t\tstrbuf_addch(out, '\\n');\n> +\n> +\t\t\tneed_separator = 1;\n> +\t\t}\n> +\n> +\t}\n> +}\n\n"},{"id":"487666","messageId":"xmqqa5ol409k.fsf@gitster.g","threadId":"60715","inReplyTo":"xmqqy1c545y0.fsf@gitster.g","subject":"Re: [PATCH v3 03/10] trailer: unify trailer formatting machinery","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-01-31T22:16:07Z","receivedAt":"2024-01-31T22:16:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n>> +\t\t\t\tif (opts->unfold)\n>> +\t\t\t\t\tunfold_value(&val);\n>\n> So, ... how would this affect an invocation of\n>\n>     $ git interpret-trailers --unfold\n>\n> which would have set opts.unfold to true in cmd_interpret_trailers()\n> and eventually called process_trailers() with it, which eventually\n> called print_all(), which conditionally called print_tok_val() but\n> never gave the val to unfold_value()?\n>\n>  ... goes and digs a bit more ...\n>\n> Ahhhh, original process_trailers() did this by calling\n> parse_trailers() that munged the value.  So its print_all()\n> codepath only had to print what has already been munged.\n>\n> But then in this new code, do we unfold only here?  I thought that\n> in the new code you moved around, the caller, whose name is now\n> interpret_trailers(), still calls parse_trailers() and that calls\n> the unfold_value().  So, are we doing redundant work that may merely\n> be unneeded (if we are lucky) or could be harmful (if things, other\n> than the unfold thing I just noticed, that are done both in\n> parse_trailers() and this function are not idempotent)?\n\nI was too confused by the code flow and resorted to tracing what\nhappens when \"git interpret-trailers --unfold\" is run in a way\nsimilar to how t7513 does in gdb.  Shame shame.\n\nIn any case, the updated code does try to call unfold_value() in\nformat_trailers() on \"val\" that has already been unfolded in\nparse_trailers().  This may not produce an incorrect result if\nunfold_value() is truly idempotent (I do not know), but even if it\nis correct for its handling of .unfold bit, this episode lowered my\nconfidence level on the new code significantly, as I do not know\nwhat unintended effects [*] all the other new things done in this\nfunction have, or if none of these unintended effects are\nerror-free.\n\n> It could be that a bit more preparatory refactoring would clarify.\n> I dunno.\n\nThanks.\n\n[Footnote] \n\n * I am assuming that calling unfold twice on the same value and\n   relying for its idempotent-ness for correctness was not your\n   intention.\n"},{"id":"487689","messageId":"owly4jet2ipd.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqqy1c55o6a.fsf@gitster.g","subject":"Re: [PATCH v3 02/10] trailer: move interpret_trailers() to interpret-trailers.c","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-01-31T23:20:46Z","receivedAt":"2024-01-31T23:20:48Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> From: Linus Arver <linusa@google.com>\n>>\n>> The interpret-trailers.c builtin is the only place we need to call\n>> interpret_trailers(), so move its definition there.\n>\n> A few helper functions that are only called by interpret_trailers()\n> are also moved, naturally.  I would have less surprised to see\n> the addtion near the beginning of builtin/interpret-trailers.c if\n> this part said:\n>\n>     ..., so move its definition there, together with a few helper\n>     functions called only by it, and remove its external declaration\n>     from <trailer.h>.\n\nWill add in next reroll.\n\n>> Delete the corresponding declaration from trailer.h, which then forces\n>> us to expose the working innards of that function.\n>\n> This was a bit confusing, at least to me.  The reason why several\n> other helper functions that are called by interpret_trailers() need\n> to be declared in trailer.h is not because the declaration of\n> interpret_trailers() is deleted from trailer.h but that is how I\n> read the above.\n>\n>     Several helper functions that are called by interpret_trailers()\n>     remain in trailer.c because other callers in the same file still\n>     call them, so add declaration for them to <trailer.h>.\n\nI've adopted most of this language in the next reroll to make the\nreasoning a bit easier to follow. Thanks.\n"},{"id":"487690","messageId":"owly1q9x2io6.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"ZbqnSNPjIQW3Durz@google.com","subject":"Re: [PATCH v3 03/10] trailer: unify trailer formatting machinery","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-01-31T23:21:29Z","receivedAt":"2024-01-31T23:21:31Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> On 2024.01.31 01:22, Linus Arver via GitGitGadget wrote:\n>> This unification will allow us to delete the format_trailer_info() and\n>> print_tok_val() functions in the next patch. They are not deleted here\n>> in order to keep the diff small.\n>\n> Needs to be removed after squashing v2 patch 4 :)\n\nOops. Will update in next reroll, thanks.\n"},{"id":"487691","messageId":"owlyy1c513ps.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqqy1c545y0.fsf@gitster.g","subject":"Re: [PATCH v3 03/10] trailer: unify trailer formatting machinery","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-01-31T23:29:51Z","receivedAt":"2024-01-31T23:29:54Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> From: Linus Arver <linusa@google.com>\n>>\n>> Currently have two functions for formatting trailers exposed in\n>> trailer.h:\n>>\n>>     void format_trailers(FILE *outfile, struct list_head *head,\n>>                         const struct process_trailer_options *opts);\n>>\n>>     void format_trailers_from_commit(struct strbuf *out, const char *msg,\n>>                                     const struct process_trailer_options *opts);\n>>\n>> and previously these functions, although similar enough (even taking the\n>> same process_trailer_options struct pointer), did not build on each\n>> other.\n>>\n>> Make format_trailers_from_commit() rely on format_trailers(). Teach\n>> format_trailers() to process trailers with the additional\n>> process_trailer_options fields like opts->key_only which is only used by\n>> format_trailers_from_commit() and not builtin/interpret-trailers.c.\n>> While we're at it, reorder parameters to put the trailer processing\n>> options first, and the out parameter (strbuf we write into) at the end.\n>>\n>> This unification will allow us to delete the format_trailer_info() and\n>> print_tok_val() functions in the next patch. They are not deleted here\n>> in order to keep the diff small.\n>>\n>> Helped-by: Junio C Hamano <gitster@pobox.com>\n>> Signed-off-by: Linus Arver <linusa@google.com>\n>> ---\n>\n> I am not sure how I helped this particularly (say, compared to all\n> the other patches on the list from everybody), though.\n\nYou made a suggestion to put trailer processing options first for\nfunction parameters, which I've adopted in this series (but also in the\nlarger series).\n"},{"id":"487692","messageId":"owlyv879106s.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqqa5ol409k.fsf@gitster.g","subject":"Re: [PATCH v3 03/10] trailer: unify trailer formatting machinery","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-01T00:46:03Z","receivedAt":"2024-02-01T00:46:05Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>>> +\t\t\t\tif (opts->unfold)\n>>> +\t\t\t\t\tunfold_value(&val);\n>>\n>> So, ... how would this affect an invocation of\n>>\n>>     $ git interpret-trailers --unfold\n>>\n>> which would have set opts.unfold to true in cmd_interpret_trailers()\n>> and eventually called process_trailers() with it, which eventually\n>> called print_all(), which conditionally called print_tok_val() but\n>> never gave the val to unfold_value()?\n>>\n>>  ... goes and digs a bit more ...\n>>\n>> Ahhhh, original process_trailers() did this by calling\n>> parse_trailers() that munged the value.  So its print_all()\n>> codepath only had to print what has already been munged.\n>>\n>> But then in this new code, do we unfold only here?  I thought that\n>> in the new code you moved around, the caller, whose name is now\n>> interpret_trailers(), still calls parse_trailers() and that calls\n>> the unfold_value().  So, are we doing redundant work that may merely\n>> be unneeded (if we are lucky) or could be harmful (if things, other\n>> than the unfold thing I just noticed, that are done both in\n>> parse_trailers() and this function are not idempotent)?\n>\n> I was too confused by the code flow and resorted to tracing what\n> happens when \"git interpret-trailers --unfold\" is run in a way\n> similar to how t7513 does in gdb.  Shame shame.\n\nIn my larger series I've made the parser and formater much simpler\n(removing nesting, calling helper functions, _only parsing once and only\nonce_, etc) to make both parsing and formatting much easier to\nunderstand. While I am biased as the author of these refactors, I do\nthink they make the code simpler.\n\n> In any case, the updated code does try to call unfold_value() in\n> format_trailers() on \"val\" that has already been unfolded in\n> parse_trailers().\n\nCorrect. But this was already the case before this series. IOW the\nexisting code assumes that this function is idempotent: we call\nunfold_value() in parse_trailers() and again in format_trailer_info().\n\nIn my tests if I remove the redundant call to unfold_value() in\nthe new formatter (so that unfolding only happens during\nparse_trailers()), all trailer-related tests still pass:\n\n    prove -j47 t{7513,3507,7502,4205,3511,3428,6300}*.sh\n\nFWIW in the larger series we prohibit the parser from making mutations\nto the input (unfolding is one such mutation), and allow multiline\n(folded) strings to be stored as is in \"val\", only unfolding the value\nif we need to during formatting.\n\n> This may not produce an incorrect result if\n> unfold_value() is truly idempotent (I do not know), but even if it\n> is correct for its handling of .unfold bit, this episode lowered my\n> confidence level on the new code significantly, as I do not know\n> what unintended effects [*] all the other new things done in this\n> function have, or if none of these unintended effects are\n> error-free.\n\nI think dropping the redundant call to unfold_value() would help address\nsome of your concerns. Of course, all of the existing test cases pass\n(with and without the redundant call). As for addressing _all_ (not just\nsome) concerns, I am not confident I can deliver that as an additional\npatch or two to this series because ...\n\n>> It could be that a bit more preparatory refactoring would clarify.\n>> I dunno.\n\n... this is basically what I've done in the larger series, because there\nare many areas that could be simplified (not to mention addressing some\nbugs some bugs are lurking around). Granted, those are more aggressive\nrefactorings, and are not \"preparatory refactoring\" at all.\n\nI could just grow this series with another ~22 patches to include those\nadditional refactors, but I am hesitant about doing so, simply due to\nthe sheer number of them.\n\nHow would you like me to proceed, other than deleting the redundant\nunfold_value() call in the next reroll?\n\nFor reference, here's a list of those 22 commits that build on this\nseries as a preview (roughly grouped on themes):\n\n    trailer formatter: deprecate format_trailers()\n    trailer formatter: introduce format_trailer_block()\n    trailer parser: parse_trailer_v2() for '--trailer <...>' CLI arguments\n    trailer parser: parse_trailer_v2() for configuration\n    trailer parser: parse_trailer_v2() for trailer blocks\n    trailer parser: introduce parse_trailer_v2()\n    interpret-trailers: preserve trailers coming from the input\n    trailer_block: remove trailer_strings member\n    trailer_block: parse trailers in one place\n    trailer_block: prepare to remove trailer_strings member\n    trailer: make find_same_and_apply_arg() do one thing\n    trailer: unify global configuration\n    interpret-trailers: print error if given unrecognized arguments\n    trailer: make entire iterator struct private\n    trailer: rename terms for consistency\n    trailer: use create_new_target_for() everywhere\n    trailer: capture behavior of addIfDifferentNeighbor\n    trailer: EXISTS_REPLACE preserves original position\n    trailer: capture replacement behavior for multiple matches\n    trailer: split template application into 2 steps\n    trailer: free templates in one place\n    trailer: template's final value does not depend on in_tok\n\nand \"trailer formatter: introduce format_trailer_block()\" is the one\nthat refactors the unified formatter introduced in this patch to have\nmultiple smaller helper functions.\n"},{"id":"487693","messageId":"xmqqfryd2drm.fsf@gitster.g","threadId":"60715","inReplyTo":"owlyv879106s.fsf@fine.c.googlers.com","subject":"Re: [PATCH v3 03/10] trailer: unify trailer formatting machinery","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-01T01:07:25Z","receivedAt":"2024-02-01T01:07:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Linus Arver <linusa@google.com> writes:\n\n>> In any case, the updated code does try to call unfold_value() in\n>> format_trailers() on \"val\" that has already been unfolded in\n>> parse_trailers().\n>\n> Correct. But this was already the case before this series. IOW the\n> existing code assumes that this function is idempotent: we call\n> unfold_value() in parse_trailers() and again in format_trailer_info().\n\nBut not in format_trailers(), which is the theme of this step.  In\nother words, the behaviour before and after this step of the\nfunction are not the same (modulo that the new version stores the\noutput in a strbuf), and as the way the changes are presented here,\nit is almost impossible to make sure that we are not introducing\nregressions, without making such assumptions like \"unfold_value() is\nidempotent and we already rely on that fact elsewhere in the code\".\n\nIt is not just \"unfold\", which was the first thing I happened to\nnotice, by the way.  There are many more lines in the new lines of\nthat function, doing things that the original version did not appear\nto do, or doing in very different ways.\n\n> I could just grow this series with another ~22 patches to include those\n> additional refactors, but I am hesitant about doing so, simply due to\n> the sheer number of them.\n\nI actually do not mind at all if you started with a preliminary\nclean-up series, and stopped the first batch somewhere in the middle\nof the 20+ patches before even reaching any of these 10 patches we\nsee here, if that gives us a readable set of bite-sized changes that\nprepare a solid foundation to rebuild things on top.  I am having a\nfeeling that not even a single person has reviewed them on list even\nthough we are already at the third iteration, which is quite\nfrustrating (and I would imagine that it would be frustrating for\nyou, too), and I suspect that the step like [v3 03/10] that makes\ntoo large a change with too little explanation (and perhaps a bit of\n\"trust me, this does not change the behaviour\") is one contributing\nfactor why people are afraid of touching it.\n\n"},{"id":"487750","messageId":"xmqq4jes2l36.fsf@gitster.g","threadId":"60715","inReplyTo":"xmqqfryd2drm.fsf@gitster.g","subject":"Re: [PATCH v3 03/10] trailer: unify trailer formatting machinery","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-01T16:41:33Z","receivedAt":"2024-02-01T16:41:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> ..., if that gives us a readable set of bite-sized changes that\n> prepare a solid foundation to rebuild things on top.  I am having a\n> feeling that not even a single person has reviewed them on list even\n> though we are already at the third iteration, which is quite\n> frustrating (and I would imagine that it would be frustrating for\n> you, too), ...\n\nI guess \"not even a single person\" was a bit unfair, given that we\ndid see some minor comments from me and also Josh during the first\nround.\n\nBut neither of two rounds saw any in-depth reviews that question \"is\nthe code doing the right thing?\", which would take a real reading of\nand a comparison between the code before and after the patches, with\nsome understanding of how things have been working, how things were\nenvisioned to evolve, and how the patch author would want to change\nthe course of the evolution of the code in the longer term.  \n\nChristian, you've been touching the code in this area the longuest.\nCan we have some of your time reviewing these?\n\nThanks.\n"},{"id":"487760","messageId":"xmqqjzno13ev.fsf@gitster.g","threadId":"60715","inReplyTo":"owly1q9x2io6.fsf@fine.c.googlers.com","subject":"Re: [PATCH v3 03/10] trailer: unify trailer formatting machinery","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-01T17:48:40Z","receivedAt":"2024-02-01T17:48:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Linus Arver <linusa@google.com> writes:\n\n> Josh Steadmon <steadmon@google.com> writes:\n>\n>> On 2024.01.31 01:22, Linus Arver via GitGitGadget wrote:\n>>> This unification will allow us to delete the format_trailer_info() and\n>>> print_tok_val() functions in the next patch. They are not deleted here\n>>> in order to keep the diff small.\n>>\n>> Needs to be removed after squashing v2 patch 4 :)\n>\n> Oops. Will update in next reroll, thanks.\n\nFWIW, by the way, having them in the same patch made it a lot easier\nto compare what the original did (with these removed functions) and\nwhat the updated code would do.  When a change is supposed to be a\nclean-up of an existing code without changing the behaviour, it helps\nto make the before and after versions visible in the patch.\n\nThanks.\n"},{"id":"487762","messageId":"xmqqa5ok12lt.fsf@gitster.g","threadId":"60715","inReplyTo":"bf2b8e1a3c4bc77022fab1ebaa0fc89a7813b4c4.1706664145.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 04/10] sequencer: use the trailer iterator","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-01T18:06:06Z","receivedAt":"2024-02-01T18:06:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Linus Arver <linusa@google.com>\n>\n> This patch allows for the removal of \"trailer_info_get()\" from the\n> trailer.h API, which will be in the next patch.\n\nHmph, do you mean \"shortlog\" and the sequencer were the only two\nexternal callers and with this we can make it file-scope static to\ntrailer.c?  Or do you mean the next step will be more than a removal\nof a declaration from trailer.h plus adding \"static\" in front of its\ndefinition in trailer.c, because there need other adjustments before\nthat happens?\n\n> Instead of calling \"trailer_info_get()\", which is a low-level function\n> in the trailers implementation (trailer.c), call\n> trailer_iterator_advance(), which was specifically designed for public\n> consumption in f0939a0eb1 (trailer: add interface for iterating over\n> commit trailers, 2020-09-27).\n\n;-).\n\n> Also, teach the iterator about non-trailer lines, by adding a new field\n> called \"raw\" to hold both trailer and non-trailer lines. This is\n> necessary because a \"trailer block\" is a list of trailer lines of at\n> least 25% trailers (see 146245063e (trailer: allow non-trailers in\n> trailer block, 2016-10-21)), such that it may hold non-trailer lines.\n\nThat sounds like a task larger than something we would want in a\npatch that focuses on another task (e.g. update sequencer not to\ncall trailer_info_get()) while at it.  It seems from a casual glance\nthat the change to shortlog.c is to accomodate this change in the\nsemantics of what the iterator could return?  It smells that this\npatch does two more or less unrelated things at the same time?\n\n> Signed-off-by: Linus Arver <linusa@google.com>\n> ---\n>  builtin/shortlog.c |  7 +++++--\n>  sequencer.c        | 35 +++++++++++++++--------------------\n>  trailer.c          | 17 +++++++++--------\n>  trailer.h          | 13 +++++++++++++\n>  4 files changed, 42 insertions(+), 30 deletions(-)\n>\n> diff --git a/builtin/shortlog.c b/builtin/shortlog.c\n> index 1307ed2b88a..dc8fd5a5532 100644\n> --- a/builtin/shortlog.c\n> +++ b/builtin/shortlog.c\n> @@ -172,7 +172,7 @@ static void insert_records_from_trailers(struct shortlog *log,\n>  \t\t\t\t\t const char *oneline)\n>  {\n>  \tstruct trailer_iterator iter;\n> -\tconst char *commit_buffer, *body;\n> +\tconst char *commit_buffer, *body, *value;\n>  \tstruct strbuf ident = STRBUF_INIT;\n>  \n>  \tif (!log->trailers.nr)\n> @@ -190,7 +190,10 @@ static void insert_records_from_trailers(struct shortlog *log,\n>  \n>  \ttrailer_iterator_init(&iter, body);\n>  \twhile (trailer_iterator_advance(&iter)) {\n> -\t\tconst char *value = iter.val.buf;\n> +\t\tif (!iter.is_trailer)\n> +\t\t\tcontinue;\n> +\n> +\t\tvalue = iter.val.buf;\n>  \n>  \t\tif (!string_list_has_string(&log->trailers, iter.key.buf))\n>  \t\t\tcontinue;\n> diff --git a/sequencer.c b/sequencer.c\n> index 3cc88d8a800..bc7c82c5271 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -319,37 +319,32 @@ static const char *get_todo_path(const struct replay_opts *opts)\n>  static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,\n>  \tsize_t ignore_footer)\n>  {\n> -\tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n> -\tstruct trailer_info info;\n> -\tsize_t i;\n> -\tint found_sob = 0, found_sob_last = 0;\n> -\tchar saved_char;\n> -\n> -\topts.no_divider = 1;\n> +\tstruct trailer_iterator iter;\n> +\tsize_t i = 0, found_sob = 0;\n> +\tchar saved_char = sb->buf[sb->len - ignore_footer];\n>  \n>  \tif (ignore_footer) {\n> -\t\tsaved_char = sb->buf[sb->len - ignore_footer];\n>  \t\tsb->buf[sb->len - ignore_footer] = '\\0';\n>  \t}\n>  \n> -\ttrailer_info_get(&info, sb->buf, &opts);\n> +\ttrailer_iterator_init(&iter, sb->buf);\n> +\twhile (trailer_iterator_advance(&iter)) {\n> +\t\ti++;\n> +\t\tif (sob &&\n> +\t\t    iter.is_trailer &&\n> +\t\t    !strncmp(iter.raw, sob->buf, sob->len)) {\n> +\t\t\tfound_sob = i;\n> +\t\t}\n> +\t}\n> +\ttrailer_iterator_release(&iter);\n>  \n>  \tif (ignore_footer)\n>  \t\tsb->buf[sb->len - ignore_footer] = saved_char;\n>  \n> -\tif (info.trailer_block_start == info.trailer_block_end)\n> +\tif (!i)\n>  \t\treturn 0;\n>  \n> -\tfor (i = 0; i < info.trailer_nr; i++)\n> -\t\tif (sob && !strncmp(info.trailers[i], sob->buf, sob->len)) {\n> -\t\t\tfound_sob = 1;\n> -\t\t\tif (i == info.trailer_nr - 1)\n> -\t\t\t\tfound_sob_last = 1;\n> -\t\t}\n> -\n> -\ttrailer_info_release(&info);\n> -\n> -\tif (found_sob_last)\n> +\tif (found_sob == i)\n>  \t\treturn 3;\n>  \tif (found_sob)\n>  \t\treturn 2;\n> diff --git a/trailer.c b/trailer.c\n> index 71ea2bb67f8..5bcc9b0006c 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> @@ -1158,17 +1158,18 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n>  \n>  int trailer_iterator_advance(struct trailer_iterator *iter)\n>  {\n> -\twhile (iter->internal.cur < iter->internal.info.trailer_nr) {\n> -\t\tchar *trailer = iter->internal.info.trailers[iter->internal.cur++];\n> -\t\tint separator_pos = find_separator(trailer, separators);\n> -\n> -\t\tif (separator_pos < 1)\n> -\t\t\tcontinue; /* not a real trailer */\n> -\n> +\tchar *line;\n> +\tint separator_pos;\n> +\tif (iter->internal.cur < iter->internal.info.trailer_nr) {\n> +\t\tline = iter->internal.info.trailers[iter->internal.cur++];\n> +\t\tseparator_pos = find_separator(line, separators);\n> +\t\titer->is_trailer = (separator_pos > 0);\n> +\n> +\t\titer->raw = line;\n>  \t\tstrbuf_reset(&iter->key);\n>  \t\tstrbuf_reset(&iter->val);\n>  \t\tparse_trailer(&iter->key, &iter->val, NULL,\n> -\t\t\t      trailer, separator_pos);\n> +\t\t\t      line, separator_pos);\n>  \t\tunfold_value(&iter->val);\n>  \t\treturn 1;\n>  \t}\n> diff --git a/trailer.h b/trailer.h\n> index 244f29fc91f..a7599067acc 100644\n> --- a/trailer.h\n> +++ b/trailer.h\n> @@ -127,6 +127,19 @@ struct trailer_iterator {\n>  \tstruct strbuf key;\n>  \tstruct strbuf val;\n>  \n> +\t/*\n> +\t * Raw line (e.g., \"foo: bar baz\") before being parsed as a trailer\n> +\t * key/val pair as part of a trailer block. A trailer block can be\n> +\t * either 100% trailer lines, or mixed in with non-trailer lines (in\n> +\t * which case at least 25% must be trailer lines).\n> +\t */\n> +\tconst char *raw;\n> +\n> +\t/*\n> +\t * 1 if the raw line was parsed as a trailer line (key/val pair).\n> +\t */\n> +\tint is_trailer;\n> +\n>  \t/* private */\n>  \tstruct {\n>  \t\tstruct trailer_info info;\n"},{"id":"487763","messageId":"owlysf2c11ug.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqqjzno13ev.fsf@gitster.g","subject":"Re: [PATCH v3 03/10] trailer: unify trailer formatting machinery","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-01T18:22:31Z","receivedAt":"2024-02-01T18:22:33Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Linus Arver <linusa@google.com> writes:\n>\n>> Josh Steadmon <steadmon@google.com> writes:\n>>\n>>> On 2024.01.31 01:22, Linus Arver via GitGitGadget wrote:\n>>>> This unification will allow us to delete the format_trailer_info() and\n>>>> print_tok_val() functions in the next patch. They are not deleted here\n>>>> in order to keep the diff small.\n>>>\n>>> Needs to be removed after squashing v2 patch 4 :)\n>>\n>> Oops. Will update in next reroll, thanks.\n>\n> FWIW, by the way, having them in the same patch made it a lot easier\n> to compare what the original did (with these removed functions) and\n> what the updated code would do.  When a change is supposed to be a\n> clean-up of an existing code without changing the behaviour, it helps\n> to make the before and after versions visible in the patch.\n>\n> Thanks.\n\nAck, will try to keep that in mind.\n"},{"id":"487764","messageId":"owlyo7d011no.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqqfryd2drm.fsf@gitster.g","subject":"Re: [PATCH v3 03/10] trailer: unify trailer formatting machinery","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-01T18:26:35Z","receivedAt":"2024-02-01T18:26:37Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Linus Arver <linusa@google.com> writes:\n>\n>> I could just grow this series with another ~22 patches to include those\n>> additional refactors, but I am hesitant about doing so, simply due to\n>> the sheer number of them.\n>\n> I actually do not mind at all if you started with a preliminary\n> clean-up series, and stopped the first batch somewhere in the middle\n> of the 20+ patches before even reaching any of these 10 patches we\n> see here, if that gives us a readable set of bite-sized changes that\n> prepare a solid foundation to rebuild things on top.  I am having a\n> feeling that not even a single person has reviewed them on list even\n> though we are already at the third iteration, which is quite\n> frustrating (and I would imagine that it would be frustrating for\n> you, too), and I suspect that the step like [v3 03/10] that makes\n> too large a change with too little explanation (and perhaps a bit of\n> \"trust me, this does not change the behaviour\") is one contributing\n> factor why people are afraid of touching it.\n\nI am planning to spend today trying to break up this patch into smaller\npreparatory chunks that still end up at the same state as this patch.\n\nWill post another update on how this goes by EOD. Thanks.\n"},{"id":"487766","messageId":"xmqqplxgyq7f.fsf@gitster.g","threadId":"60715","inReplyTo":"c19c1dcc592186d8da2857692f4ebbfe35adfac0.1706664145.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 05/10] trailer: make trailer_info struct private","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-01T18:49:56Z","receivedAt":"2024-02-01T18:50:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Make this struct private by putting its definition inside trailer.c.\n> This has two benefits:\n>\n>   (1) it makes the surface area of the public facing\n>       interface (trailer.h) smaller, and\n>\n>   (2) external API users are unable to peer inside this struct (because\n>       it is only ever exposed as an opaque pointer).\n\nAt the cost of an extra pointer dereference every time the member of\nthe struct is accessed, plus the cost of allocation/deallocation.\n\nWhich may not be a huge deal, but I wonder if the approach to name\nthe member of the outer struct with \"private\" that seems to be used\nin other parts of the code (e.g. the .private_size member in the\nhashmap structure, the .refs_private member in the repository\nstructure) or even a documented convention (e.g. raw_object_store),\nmight be more appropriate here.  If Coccinelle works well (which we\nmay be having some trouble with --- cf. <xmqq1q9ybsnl.fsf@gitster.g>),\nwe should be able to catch external accesses without having to hide\nthe implementation details via an extra pointer dereference.\n\n> @@ -176,11 +176,12 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n>  \tstrbuf_release(&trailer_block);\n>  \n>  \tfree_trailers(&head);\n> -\ttrailer_info_release(&info);\n>  \n>  \t/* Print the lines after the trailers as is */\n>  \tif (!opts->only_trailers)\n> -\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n> +\t\tfwrite(sb.buf + trailer_block_end(info), 1, sb.len - trailer_block_end(info), outfile);\n> +\n> +\ttrailer_info_release(info);\n\nInteresting.  Is this an indenendent bugfix even if we decided not\nto take this patch?  No, I have not fully decided to be negative on\nthe move this entire patch makes (even though I am leaning towards\nsaying so).  Just hypothetically, even if we wanted to keep \"info\"\nhere as a structure and not a pointer to an opaque structure,\ndoesn't this hunk fix a real bug?\n\nWell, technically, not quite, because the members referenced in that\nif (.only_trailers) block are still live in the info struct.  But it\nstill smells wrong to access info.* after calling _release() on it,\nand this fix should come before \"info\" is turned from an instance to\na pointer, I would say.\n\n> diff --git a/trailer.h b/trailer.h\n> index a7599067acc..e19ddf84e64 100644\n> --- a/trailer.h\n> +++ b/trailer.h\n> @@ -4,6 +4,8 @@\n>  #include \"list.h\"\n>  #include \"strbuf.h\"\n>  \n> +struct trailer_info;\n> +\n>  enum trailer_where {\n>  \tWHERE_DEFAULT,\n>  \tWHERE_END,\n> @@ -29,27 +31,6 @@ int trailer_set_where(enum trailer_where *item, const char *value);\n>  int trailer_set_if_exists(enum trailer_if_exists *item, const char *value);\n>  int trailer_set_if_missing(enum trailer_if_missing *item, const char *value);\n>  \n> +size_t trailer_block_start(struct trailer_info *info);\n> +size_t trailer_block_end(struct trailer_info *info);\n> +int blank_line_before_trailer_block(struct trailer_info *info);\n\nAnd we need new accessors, which is a good change regardless of the\nanswer to the \"do we really want an extra pointer dereference?\nShouldn't the existing 'private' and 'internal' label we see below\nsufficient?\" question.\n\n> @@ -142,7 +123,7 @@ struct trailer_iterator {\n>  \n>  \t/* private */\n>  \tstruct {\n> -\t\tstruct trailer_info info;\n> +\t\tstruct trailer_info *info;\n>  \t\tsize_t cur;\n>  \t} internal;\n>  };\n"},{"id":"487767","messageId":"xmqqil38ypuk.fsf@gitster.g","threadId":"60715","inReplyTo":"0a9a7438c3ff39f1434087bf3ed6a9865758c803.1706664145.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 06/10] trailer: spread usage of \"trailer_block\" language","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-01T18:57:39Z","receivedAt":"2024-02-01T18:57:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Linus Arver <linusa@google.com>\n>\n> Deprecate the \"trailer_info\" struct name and replace it with\n> \"trailer_block\". The main reason is to help readability, because\n> \"trailer_info\" on the surface sounds like it's about a single trailer\n> when in reality it is a collection of contiguous lines, at least 25% of\n> which are trailers.\n\nYup, \"info\" is usually a fairly meaningless word.  At least \"block\"\nmay imply it is a collection of trailers.\n\nThe naming would not matter as much to the API users, if the thing\nis now opaque, though.\n\n\n"},{"id":"487769","messageId":"xmqqeddwypf5.fsf@gitster.g","threadId":"60715","inReplyTo":"97e5d86ddf0ea25df1b512dfbf620910298c1b02.1706664145.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 07/10] trailer: prepare to move parse_trailers_from_command_line_args() to builtin","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-01T19:06:54Z","receivedAt":"2024-02-01T19:06:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Rename add_arg_item() to trailer_add_arg_item() because it will have to\n> be exposed as an API function in the next patch. Rename\n> new_trailers_clear() to free_new_trailers() because it will be promoted\n> into an API function; the API already has free_trailers(), so using the\n> \"free_*\" naming style will keep it consistent. Also rename \"conf_info\"\n> to \"trailer_conf\" for readability, dropping the low-value \"_info\" suffix\n> as we did earlier in this series for \"trailer_info\" to \"trailer_block\".\n\nMakes sense.\n\nThanks.\n"},{"id":"487770","messageId":"owlyle840zfo.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqqa5ok12lt.fsf@gitster.g","subject":"Re: [PATCH v3 04/10] sequencer: use the trailer iterator","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-01T19:14:35Z","receivedAt":"2024-02-01T19:14:39Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> From: Linus Arver <linusa@google.com>\n>>\n>> This patch allows for the removal of \"trailer_info_get()\" from the\n>> trailer.h API, which will be in the next patch.\n>\n> Hmph, do you mean \"shortlog\" and the sequencer were the only two\n> external callers and with this we can make it file-scope static to\n> trailer.c?\n\nThis was what I meant (originally ...\n\n> Or do you mean the next step will be more than a removal\n> of a declaration from trailer.h plus adding \"static\" in front of its\n> definition in trailer.c, because there need other adjustments before\n> that happens?\n\n... but now I realize that the operation adds a few small tweaks, such\nas tweaking the parameters it expects and also what it returns). In the\nspirit of breaking up patch 3, I will also break this up into\npreparatory patches as well.\n\n>> Also, teach the iterator about non-trailer lines, by adding a new field\n>> called \"raw\" to hold both trailer and non-trailer lines. This is\n>> necessary because a \"trailer block\" is a list of trailer lines of at\n>> least 25% trailers (see 146245063e (trailer: allow non-trailers in\n>> trailer block, 2016-10-21)), such that it may hold non-trailer lines.\n>\n> That sounds like a task larger than something we would want in a\n> patch that focuses on another task (e.g. update sequencer not to\n> call trailer_info_get()) while at it.  It seems from a casual glance\n> that the change to shortlog.c is to accomodate this change in the\n> semantics of what the iterator could return?  It smells that this\n> patch does two more or less unrelated things at the same time?\n\nI think you're correct. Hopefully breaking this up will make things\neasier to review.\n\nI am learning very quickly from your review comments in patch 3 and in\nhere that, in the absence of area experts, the existing tests/CI tests\ncannot be trusted alone (after all some tests could be missing), which\nmakes it more important to do so-called \"micro-commits\".\n\nBut overall, breaking things up is a good thing anyway as a general\npractice, so, I think this is a good lesson. TBH I have a (bad) habit of\nsaying \"is the diff ~100 lines?\" and if so I don't spend any time\nthinking of breaking these up. X-<\n\nThanks.\n"},{"id":"487771","messageId":"xmqqa5okyori.fsf@gitster.g","threadId":"60715","inReplyTo":"owlyo7d011no.fsf@fine.c.googlers.com","subject":"Re: [PATCH v3 03/10] trailer: unify trailer formatting machinery","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-01T19:21:05Z","receivedAt":"2024-02-01T19:21:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Linus Arver <linusa@google.com> writes:\n\n> I am planning to spend today trying to break up this patch into smaller\n> preparatory chunks that still end up at the same state as this patch.\n>\n> Will post another update on how this goes by EOD. Thanks.\n\nI stopped reading the function after noticing the double unfolding,\nso there may be similar \"why do we do this unexplained new thing in\nthe function that the original didn't?\" issues in the \"same state\",\n\nIn any case, if I understood your plan I heard from you in the\ndiscussion yesterday correctly, the unfolding should not be added to\nformat (to make it double), but would be moved from parse to format\nin a single step.  It would avoid making it double, and would make\nthe parse step about purely parsing without modification, which is a\nvery worthwhile thing to do.  So I am not sure if we want to end up\nwith the same state in the first place, though.\n\nTHanks.\n\n"},{"id":"487779","messageId":"xmqqttmrx1ro.fsf@gitster.g","threadId":"60715","inReplyTo":"465dc51cdcba28d235241021bc52369f6082d329.1706664145.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 08/10] trailer: move arg handling to interpret-trailers.c","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-01T22:23:07Z","receivedAt":"2024-02-01T22:23:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Linus Arver <linusa@google.com>\n>\n> We don't move the \"arg_item\" struct to interpret-trailers.c, because it\n> is now a struct that contains information about trailers that could be\n> added into the input text's own trailers. This is a generic concept that\n> extends beyond trailers defined as CLI arguments (it applies to trailers\n> defined in configuration as well). We will rename \"arg_item\" to\n> \"trailer_template\" in a follow-up patch to keep the diff here small.\n>\n> Signed-off-by: Linus Arver <linusa@google.com>\n> ---\n>  builtin/interpret-trailers.c | 88 ++++++++++++++++++++++--------------\n>  trailer.c                    | 62 ++++++++++++++++++-------\n>  trailer.h                    | 12 +++++\n>  3 files changed, 112 insertions(+), 50 deletions(-)\n>\n> diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\n> index 9f0ba39b317..9a902012912 100644\n> --- a/builtin/interpret-trailers.c\n> +++ b/builtin/interpret-trailers.c\n> @@ -45,23 +45,17 @@ static int option_parse_if_missing(const struct option *opt,\n>  \treturn trailer_set_if_missing(opt->value, arg);\n>  }\n>  \n> -static void free_new_trailers(struct list_head *trailers)\n> -{\n> -\tstruct list_head *pos, *tmp;\n> -\tstruct new_trailer_item *item;\n> -\n> -\tlist_for_each_safe(pos, tmp, trailers) {\n> -\t\titem = list_entry(pos, struct new_trailer_item, list);\n> -\t\tlist_del(pos);\n> -\t\tfree(item);\n> -\t}\n> -}\n> +static char *cl_separators;\n\nIt somehow feels ugly and goes backward to depend on a new global,\nespecially when you are aiming to libify more things.  If we are\ndoing something like ...\n\n>  static int option_parse_trailer(const struct option *opt,\n>  \t\t\t\t   const char *arg, int unset)\n>  {\n>  \tstruct list_head *trailers = opt->value;\n> -\tstruct new_trailer_item *item;\n> +\tstruct strbuf tok = STRBUF_INIT;\n> +\tstruct strbuf val = STRBUF_INIT;\n> +\tconst struct trailer_conf *conf;\n> +\tstruct trailer_conf *conf_current = new_trailer_conf();\n> +\tssize_t separator_pos;\n>  \n>  \tif (unset) {\n>  \t\tfree_new_trailers(trailers);\n> @@ -71,12 +65,31 @@ static int option_parse_trailer(const struct option *opt,\n>  \tif (!arg)\n>  \t\treturn -1;\n>  \n> -\titem = xmalloc(sizeof(*item));\n> -\titem->text = arg;\n> -\titem->where = where;\n> -\titem->if_exists = if_exists;\n> -\titem->if_missing = if_missing;\n> -\tlist_add_tail(&item->list, trailers);\n> +\tseparator_pos = find_separator(arg, cl_separators);\n> +\tif (separator_pos) {\n> +\t\tparse_trailer(arg, separator_pos, &tok, &val, &conf);\n> +\t\tduplicate_trailer_conf(conf_current, conf);\n> +\n> +\t\t/*\n> +\t\t * Override conf_current with settings specified via CLI flags.\n> +\t\t */\n\n... this, which apparently needs to know that we are dealing with\ninput from CLI, shouldn't we be able to do the \"CLI allows '=' as\nwell as whatever is the default\" here, instead of mucking with the\nglobal variable in the caller?\n\nEven if you plan to later make this function callable from outside\nthe context of parse_options() callback, and if you do not want \"CLI\nallows '=' as well\" for such new callers, we should be able to have\na shared helper function that is the bulk of this function but takes\nadditional parameter to tweak its behaviour slightly depending on\nthe needs of the callers?\n\n> +\t\ttrailer_conf_set(where, if_exists, if_missing, conf_current);\n\nI am getting an impression that the use of and the introduction of\nthe new helper, mixed with movement of the responsibility, makes\nreviewing the change unnecessarily more cumbersome.  An equivalent\nseries with a few more steps, each of them focusing on a small\nchange (like, \"updating the conf_current members is done with\nassignments that appear as a pattern very often---introduce a helper\nto reduce boilerplate\") would have been more enticing to reviewers.\n\n> +\t\ttrailer_add_arg_item(strbuf_detach(&tok, NULL),\n> +\t\t\t\t     strbuf_detach(&val, NULL),\n> +\t\t\t\t     conf_current,\n> +\t\t\t\t     trailers);\n> +\t} else {\n> +\t\tstruct strbuf sb = STRBUF_INIT;\n> +\t\tstrbuf_addstr(&sb, arg);\n> +\t\tstrbuf_trim(&sb);\n> +\t\terror(_(\"empty trailer token in trailer '%.*s'\"),\n> +\t\t\t(int) sb.len, sb.buf);\n> +\t\tstrbuf_release(&sb);\n> +\t}\n> +\n> +\tfree(conf_current);\n> +\n>  \treturn 0;\n>  }\n>  \n> @@ -135,7 +148,7 @@ static void read_input_file(struct strbuf *sb, const char *file)\n>  }\n>  \n>  static void interpret_trailers(const struct process_trailer_options *opts,\n> -\t\t\t       struct list_head *new_trailer_head,\n> +\t\t\t       struct list_head *arg_trailers,\n>  \t\t\t       const char *file)\n>  {\n>  \tLIST_HEAD(head);\n> @@ -144,8 +157,6 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n>  \tstruct trailer_block *trailer_block;\n>  \tFILE *outfile = stdout;\n>  \n> -\ttrailer_config_init();\n> -\n>  \tread_input_file(&sb, file);\n>  \n>  \tif (opts->in_place)\n> @@ -162,12 +173,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n>  \n>  \n>  \tif (!opts->only_input) {\n> -\t\tLIST_HEAD(config_head);\n> -\t\tLIST_HEAD(arg_head);\n> -\t\tparse_trailers_from_config(&config_head);\n> -\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n> -\t\tlist_splice(&config_head, &arg_head);\n> -\t\tprocess_trailers_lists(&head, &arg_head);\n> +\t\tprocess_trailers_lists(&head, arg_trailers);\n>  \t}\n\nSo, the bulk of the parsing is no longer responsibility of this\nfunction.  Instead, the caller (e.g., cmd_interpret_trailers()) is\nexpected to do that before they call us.\n\n> ...\n>  \tgit_config(git_default_config, NULL);\n> +\ttrailer_config_init();\n> +\n> +\tif (!opts.only_input) {\n> +\t\tparse_trailers_from_config(&configured_trailers);\n> +\t}\n\nBy the way, until we add more statements in this block, lose the\nunnecessary {} around it.\n\n> +\t/*\n> +\t* In command-line arguments, '=' is accepted (in addition to the\n> +\t* separators that are defined).\n> +\t*/\n> +\tcl_separators = xstrfmt(\"=%s\", trailer_default_separators());\n>  \n>  \targc = parse_options(argc, argv, prefix, options,\n>  \t\t\t     git_interpret_trailers_usage, 0);\n>  \n> -\tif (opts.only_input && !list_empty(&trailers))\n> +\tfree(cl_separators);\n> +\n> +\tif (opts.only_input && !list_empty(&arg_trailers))\n>  \t\tusage_msg_opt(\n>  \t\t\t_(\"--trailer with --only-input does not make sense\"),\n>  \t\t\tgit_interpret_trailers_usage,\n>  \t\t\toptions);\n>  \n> +\tlist_splice(&configured_trailers, &arg_trailers);\n\nThis corresponds to what the old code did in interpret_trailers(),\nOK.\n\nThe move of the responsibility may make sense.\n\n>  \tif (argc) {\n>  \t\tint i;\n>  \t\tfor (i = 0; i < argc; i++)\n> -\t\t\tinterpret_trailers(&opts, &trailers, argv[i]);\n> +\t\t\tinterpret_trailers(&opts, &arg_trailers, argv[i]);\n>  \t} else {\n>  \t\tif (opts.in_place)\n>  \t\t\tdie(_(\"no input file given for in-place editing\"));\n> -\t\tinterpret_trailers(&opts, &trailers, NULL);\n> +\t\tinterpret_trailers(&opts, &arg_trailers, NULL);\n>  \t}\n>  \n> -\tfree_new_trailers(&trailers);\n> +\tfree_new_trailers(&arg_trailers);\n>  \n>  \treturn 0;\n>  }\n> diff --git a/trailer.c b/trailer.c\n> index c16f552b463..19637ff295d 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> @@ -66,6 +66,11 @@ static LIST_HEAD(conf_head);\n>  \n>  static char *separators = \":\";\n>  \n> +const char *trailer_default_separators(void)\n> +{\n> +\treturn separators;\n> +}\n> +\n>  static int configured;\n>  \n>  #define TRAILER_ARG_STRING \"$ARG\"\n> @@ -424,6 +429,25 @@ int trailer_set_if_missing(enum trailer_if_missing *item, const char *value)\n>  \treturn 0;\n>  }\n>  \n> +void trailer_conf_set(enum trailer_where where,\n> +\t\t      enum trailer_if_exists if_exists,\n> +\t\t      enum trailer_if_missing if_missing,\n> +\t\t      struct trailer_conf *conf)\n> +{\n> +\tif (where != WHERE_DEFAULT)\n> +\t\tconf->where = where;\n> +\tif (if_exists != EXISTS_DEFAULT)\n> +\t\tconf->if_exists = if_exists;\n> +\tif (if_missing != MISSING_DEFAULT)\n> +\t\tconf->if_missing = if_missing;\n> +}\n\nSo, this is such a helper function. It is curious that it\ndeliberately lacks the ability to reset what has already been\nconfigured back to the default.\n\nFor example, we earlier saw this original code ...\n\n> -\titem = xmalloc(sizeof(*item));\n> -\titem->text = arg;\n> -\titem->where = where;\n> -\titem->if_exists = if_exists;\n> -\titem->if_missing = if_missing;\n\n... gets replaced with this call.\n\n> +\tstruct trailer_conf *conf_current = new_trailer_conf();\n> ...\n> +\t\ttrailer_conf_set(where, if_exists, if_missing, conf_current);\n\nFor this conversion to be correct, we require that the value of the\n*_DEFAULT macro to be 0 and/or these three values getting assigned\nare not *_DEFAULT values.  Maybe that may happen to be the case in\ntoday's code, but such an unwritten assumption makes me feel nervous.\n\nI am not sure if it is worth the confusion factor to make a function\nwhose name is $anything_set() to be making a conditional assignment.\nIf such a conditional assignment *also* happens often and deserves\nto have its own helper, it is fine to have such a helper for\nconditional assignment, but calling it $anything_set() is probably a\nmistake.\n\nOther than that, the main thrust of this step seems sensible.\n\nThanks.\n"},{"id":"487780","messageId":"xmqqplxfx1nk.fsf@gitster.g","threadId":"60715","inReplyTo":"885ac87a5447e54139171fb3eda62055ffd517cd.1706664145.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 09/10] trailer: delete obsolete argument handling code from API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-01T22:25:35Z","receivedAt":"2024-02-01T22:25:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Linus Arver <linusa@google.com>\n>\n> This commit was not squashed with its parent in order to keep the diff\n> separate (to make the additive changes in the parent easier to read).\n\nHopefully we won't see such an artificial separation in the new\niteration.  As we saw, being able to compare before and after images\nin a single patch is useful while reviewing a change that is supposed\nto be making things cleaner without altering what they do.\n"},{"id":"487791","messageId":"owlyil371g96.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqqa5okyori.fsf@gitster.g","subject":"Re: [PATCH v3 03/10] trailer: unify trailer formatting machinery","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-02T07:23:33Z","receivedAt":"2024-02-02T07:23:35Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Linus Arver <linusa@google.com> writes:\n>\n>> I am planning to spend today trying to break up this patch into smaller\n>> preparatory chunks that still end up at the same state as this patch.\n>>\n>> Will post another update on how this goes by EOD. Thanks.\n\nI'm happy to report that this breaking-up of things is going a lot\nsmoother than expected. The hard part is not so much the rearranging of\ncode but rather thinking up of good commit messages to explain the\nintent of each smaller step. I have most of this patch broken up into\nmuch smaller steps with all tests passing.\n\n> I stopped reading the function after noticing the double unfolding,\n> so there may be similar \"why do we do this unexplained new thing in\n> the function that the original didn't?\" issues in the \"same state\",\n\nYep, and these unexplained new things are the things I am recalling\n(from a few weeks back when I first started this series) and am now\ntrying to find the right commit messages for.\n\n> In any case, if I understood your plan I heard from you in the\n> discussion yesterday correctly, the unfolding should not be added to\n> format (to make it double),\n\nYes, it will be done during parsing for now. This is because of\nadditional refactors that are coming in the later series, which ...\n\n> but would be moved from parse to format\n> in a single step.  It would avoid making it double, and would make\n> the parse step about purely parsing without modification, which is a\n> very worthwhile thing to do.\n\n... achieve this exact thing (unfolding is done \"lazily\" during\nformatting only, not \"eagerly\" as we do now during parsing).\n\n> So I am not sure if we want to end up\n> with the same state in the first place, though.\n>\n> THanks.\n\nSo far the breaking-up of this patch has not revealed a need to change\nthe end result significantly from what I have here. But I admit I am not\nsure this will be the case for the other patches that need similar\ntreatment in this series.\n\nConsidering how it took me most of the day to break up this single\npatch, I am not sure I can get a v4 ready by tomorrow. Might need a few\ndays for that.\n\nThank you for your very helpful review comments for this series so far.\nThey have been quite illuminating. :)\n"},{"id":"487830","messageId":"xmqq8r42u68y.fsf@gitster.g","threadId":"60715","inReplyTo":"owlyil371g96.fsf@fine.c.googlers.com","subject":"Re: [PATCH v3 03/10] trailer: unify trailer formatting machinery","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-02T17:26:53Z","receivedAt":"2024-02-02T17:26:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Linus Arver <linusa@google.com> writes:\n\n> The hard part is not so much the rearranging of\n> code but rather thinking up of good commit messages to explain the\n> intent of each smaller step.\n\nGood.\n\nComing up with a good explanation of what you did forces you to\nthink it thorough again.  People often discover bugs in the code\nthey just wrote, or different and better ways to solve the problem,\nwhile explaining the change to their colleagues, and I found it has\nvery similar effect to try to write a good explanation in the commit\nlog.  Time to write such a good explanation is time worth investing\nin.\n\nIncidentally, this is why we value clearly written proposed log\nmessage while reviewing.  A commit that is a product of \"I tried\nthis, which did not work, and then tried something else, which did\nnot work either, and after some more random tries, this seems to\nwork\" would not have a clear thinking behind it, but if such\niterations of random tries happened to hit the best solution,\nthinking backwards from the result to the original problem and\nexplaining how the solution works would result in a good\ndescription.\n\nThanks.\n"},{"id":"487850","messageId":"owlyfrya1ivk.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"owlyle840zfo.fsf@fine.c.googlers.com","subject":"Re: [PATCH v3 04/10] sequencer: use the trailer iterator","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-03T00:39:11Z","receivedAt":"2024-02-03T00:39:13Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Linus Arver <linusa@google.com> writes:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> \"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>>\n>>> From: Linus Arver <linusa@google.com>\n>>>\n>>> Also, teach the iterator about non-trailer lines, by adding a new field\n>>> called \"raw\" to hold both trailer and non-trailer lines. This is\n>>> necessary because a \"trailer block\" is a list of trailer lines of at\n>>> least 25% trailers (see 146245063e (trailer: allow non-trailers in\n>>> trailer block, 2016-10-21)), such that it may hold non-trailer lines.\n>>\n>> That sounds like a task larger than something we would want in a\n>> patch that focuses on another task (e.g. update sequencer not to\n>> call trailer_info_get()) while at it.  It seems from a casual glance\n>> that the change to shortlog.c is to accomodate this change in the\n>> semantics of what the iterator could return?  It smells that this\n>> patch does two more or less unrelated things at the same time?\n>\n> I think you're correct. Hopefully breaking this up will make things\n> easier to review.\n\nAnd now that I've broken it up locally, I can say that the change I made\nto shortlog was unnecessary (shortlog already has a check to see if the\ntrailer key is empty) which makes the \"is_trailer\" check I added to it\nhere redundant (because non-trailer lines, which the new iterator can\niterate over, have empty keys).\n\nWill remove the shortlog change in v4. Thanks.\n"},{"id":"487859","messageId":"owlycyte1hhj.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqqplxgyq7f.fsf@gitster.g","subject":"Re: [PATCH v3 05/10] trailer: make trailer_info struct private","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-03T01:09:12Z","receivedAt":"2024-02-03T01:09:14Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> Make this struct private by putting its definition inside trailer.c.\n>> This has two benefits:\n>>\n>>   (1) it makes the surface area of the public facing\n>>       interface (trailer.h) smaller, and\n>>\n>>   (2) external API users are unable to peer inside this struct (because\n>>       it is only ever exposed as an opaque pointer).\n>\n> At the cost of an extra pointer dereference every time the member of\n> the struct is accessed, plus the cost of allocation/deallocation.\n\nAdded to commit message for v4, thanks.\n\n> Which may not be a huge deal, but I wonder if the approach to name\n> the member of the outer struct with \"private\" that seems to be used\n> in other parts of the code (e.g. the .private_size member in the\n> hashmap structure, the .refs_private member in the repository\n> structure) or even a documented convention (e.g. raw_object_store),\n> might be more appropriate here.\n\nHaving gotten my hands dirty with the pimpl idiom, I think possibly the\nbest thing about it is that the compiler can tell me exactly where\nevery Git subsystem outside of trailer.c is accessing members inside the\n(newly private) struct. So it's great for checking existing usage of how\nthings are used already, day-to-day. Not being well-versed in shortlog\nor sequencer internals, these warnings from the compiler for these\nfunctions (trying to peer through the opaque pointer) have been very\ninformative.\n\nI think it would be reasonable to drop the idiom if the additional\nperformance costs (pointer dereference, allocation/deallocation) are too\nmuch to bear. For the trailer subsystem I don't see an immediate need to\nfocus on performance though, so I think it's fine for now.\n\n> If Coccinelle works well (which we\n> may be having some trouble with --- cf. <xmqq1q9ybsnl.fsf@gitster.g>),\n> we should be able to catch external accesses without having to hide\n> the implementation details via an extra pointer dereference.\n\nThat's true. But that would only work for trailer API users inside the\nGit codebase. Eventually I envision projects external to Git using\n<trailer.h>, and in that scenario we would _really_ not want to let\nthese external users peek inside structs with members named \"private\" or\n\"internal\" in name only. Murphy's law suggests those external users will\n(naughtily) depend on the internals, and that would place an undue\nburden on us if we ever wanted to make large changes to these (public)\nstructs because we might break them.\n\nAnother thing to consider is that if our API users ever want to get more\nflexibility out of it, they would immediately come to us if we used the\npimpl idiom (our users are bound by exactly how we define the API). If\nthe structs were public, then they would be free to implement new\nfunctionality to extend the API on their own, without ever letting us\nknow. If there are folks making small (unofficial) extensions to the\nAPI, I want that to happen _in_ this project, not outside.\n\nIn summary what I'm saying is that this idiom gives very strong guidance\nfor desining an API for both Git-internal and external-to-Git usage. I\nthink for at least the trailer subsystem it's worth trying out. I've\npointed out some of the same points in the cover letter also.\n\n>> @@ -176,11 +176,12 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n>>  \tstrbuf_release(&trailer_block);\n>>  \n>>  \tfree_trailers(&head);\n>> -\ttrailer_info_release(&info);\n>>  \n>>  \t/* Print the lines after the trailers as is */\n>>  \tif (!opts->only_trailers)\n>> -\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n>> +\t\tfwrite(sb.buf + trailer_block_end(info), 1, sb.len - trailer_block_end(info), outfile);\n>> +\n>> +\ttrailer_info_release(info);\n>\n> Interesting.  Is this an indenendent bugfix even if we decided not\n> to take this patch?\n\nNice catch!\n\n> No, I have not fully decided to be negative on\n> the move this entire patch makes (even though I am leaning towards\n> saying so).  Just hypothetically, even if we wanted to keep \"info\"\n> here as a structure and not a pointer to an opaque structure,\n> doesn't this hunk fix a real bug?\n>\n> Well, technically, not quite, because the members referenced in that\n> if (.only_trailers) block are still live in the info struct.  But it\n> still smells wrong to access info.* after calling _release() on it,\n> and this fix should come before \"info\" is turned from an instance to\n> a pointer, I would say.\n\nI've promoted this change into a bugfix patch as the very first patch\nfor the next reroll. Thanks.\n\n>> diff --git a/trailer.h b/trailer.h\n>> index a7599067acc..e19ddf84e64 100644\n>> --- a/trailer.h\n>> +++ b/trailer.h\n>> @@ -4,6 +4,8 @@\n>>  #include \"list.h\"\n>>  #include \"strbuf.h\"\n>>  \n>> +struct trailer_info;\n>> +\n>>  enum trailer_where {\n>>  \tWHERE_DEFAULT,\n>>  \tWHERE_END,\n>> @@ -29,27 +31,6 @@ int trailer_set_where(enum trailer_where *item, const char *value);\n>>  int trailer_set_if_exists(enum trailer_if_exists *item, const char *value);\n>>  int trailer_set_if_missing(enum trailer_if_missing *item, const char *value);\n>>  \n>> +size_t trailer_block_start(struct trailer_info *info);\n>> +size_t trailer_block_end(struct trailer_info *info);\n>> +int blank_line_before_trailer_block(struct trailer_info *info);\n>\n> And we need new accessors, which is a good change regardless of the\n> answer to the \"do we really want an extra pointer dereference?\n> Shouldn't the existing 'private' and 'internal' label we see below\n> sufficient?\" question.\n\nI am very surprised with your response here, because I was expecting the\ncomplete opposite reaction from reviewers --- something like\n\n    Hmph, we have to write accessor functions now when before we could\n    just reach inside the struct directly? Isn't this just adding\n    needless verbosity?\n\n(perhaps with more dissatisfaction). Something tells me you meant \"bad\nchange\" but accidentally wrote \"good change\". Am I correct?\n"},{"id":"487861","messageId":"owly8r421g6g.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqqil38ypuk.fsf@gitster.g","subject":"Re: [PATCH v3 06/10] trailer: spread usage of \"trailer_block\" language","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-03T01:37:27Z","receivedAt":"2024-02-03T01:37:29Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> From: Linus Arver <linusa@google.com>\n>>\n>> Deprecate the \"trailer_info\" struct name and replace it with\n>> \"trailer_block\". The main reason is to help readability, because\n>> \"trailer_info\" on the surface sounds like it's about a single trailer\n>> when in reality it is a collection of contiguous lines, at least 25% of\n>> which are trailers.\n>\n> Yup, \"info\" is usually a fairly meaningless word.  At least \"block\"\n> may imply it is a collection of trailers.\n>\n> The naming would not matter as much to the API users, if the thing\n> is now opaque, though.\n\nYou make an interesting point (and I agree).\n\nIf we were to provide additional private structs named \"trailer_<foo>\"\nat that point we should think about how these <foo> parts \"interact\"\nwith each other (if you will) in terms of names. For example we probably\nwould never want to name a struct \"trailer_block_parser\" if it has no\nrelationship to the existing \"trailer_block\" struct. More elaborate APIs\nmight design a particular naming scheme (with some rules about certain\nsuffixes, perhaps, for opaque pointers that all behave a certain way).\n\nBut such naming considerations would naturally come into view\nindependent of this public vs public struct discussion, so I think the\nabove paragraph is moot.\n"},{"id":"487862","messageId":"owly5xz61g0u.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqqplxfx1nk.fsf@gitster.g","subject":"Re: [PATCH v3 09/10] trailer: delete obsolete argument handling code from API","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-03T01:40:49Z","receivedAt":"2024-02-03T01:40:51Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> From: Linus Arver <linusa@google.com>\n>>\n>> This commit was not squashed with its parent in order to keep the diff\n>> separate (to make the additive changes in the parent easier to read).\n>\n> Hopefully we won't see such an artificial separation in the new\n> iteration.  As we saw, being able to compare before and after images\n> in a single patch is useful while reviewing a change that is supposed\n> to be making things cleaner without altering what they do.\n\nTotally agree (and I'm reaping the benefits of this as well in the\nsmaller preparatory refactoring changes I've made for the next reroll).\n"},{"id":"487863","messageId":"owly1q9u1foe.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqqttmrx1ro.fsf@gitster.g","subject":"Re: [PATCH v3 08/10] trailer: move arg handling to interpret-trailers.c","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-03T01:48:17Z","receivedAt":"2024-02-03T01:48:20Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> [...]\n>\n> Other than that, the main thrust of this step seems sensible.\n>\n> Thanks.\n\nQuick update: I've broken down patches 3 and 4 into smaller pieces, and\nam now ready to break this one down, too.\n\nHopefully sometime over the weekend I'll have some answers ready for the\nmany good questions you've raised. Thanks.\n"},{"id":"487864","messageId":"xmqq4jeqtaxx.fsf@gitster.g","threadId":"60715","inReplyTo":"owlycyte1hhj.fsf@fine.c.googlers.com","subject":"Re: [PATCH v3 05/10] trailer: make trailer_info struct private","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-03T04:43:06Z","receivedAt":"2024-02-03T04:43:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Linus Arver <linusa@google.com> writes:\n\n>>> +size_t trailer_block_start(struct trailer_info *info);\n>>> +size_t trailer_block_end(struct trailer_info *info);\n>>> +int blank_line_before_trailer_block(struct trailer_info *info);\n>>\n>> And we need new accessors, which is a good change regardless of the\n>> answer to the \"do we really want an extra pointer dereference?\n>> Shouldn't the existing 'private' and 'internal' label we see below\n>> sufficient?\" question.\n>\n> I am very surprised with your response here, because I was expecting the\n> complete opposite reaction from reviewers --- something like\n>\n>     Hmph, we have to write accessor functions now when before we could\n>     just reach inside the struct directly? Isn't this just adding\n>     needless verbosity?\n>\n> (perhaps with more dissatisfaction). Something tells me you meant \"bad\n> change\" but accidentally wrote \"good change\". Am I correct?\n\nI often make typoes and screw up my double negations, but not this\ntime.  While I still suspect that the extra secrecy afforded by\nusing a pointer to an opaque structure is something unnecessary\nbetween friends, as coding convention, it would be a good change to\nintroduce accessors and have them used by the API users, i.e. code\noutside the API implementation.\n\nYou can still \"git grep\" for references to the \".trailer_private\"\n(or whatever the \"private\" members are named by convention) member\noutside the trailer \"client code\" to see if there are people who are\ntoo intimate than they should be that way, as they should all be\nusing the published accessors.\n\n"},{"id":"487974","messageId":"owlyzfwemmm8.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqqttmrx1ro.fsf@gitster.g","subject":"Re: [PATCH v3 08/10] trailer: move arg handling to interpret-trailers.c","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-06T01:01:51Z","receivedAt":"2024-02-06T01:01:54Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\n>> index 9f0ba39b317..9a902012912 100644\n>> --- a/builtin/interpret-trailers.c\n>> +++ b/builtin/interpret-trailers.c\n>> @@ -45,23 +45,17 @@ static int option_parse_if_missing(const struct option *opt,\n>>  \treturn trailer_set_if_missing(opt->value, arg);\n>>  }\n>>  \n>> -static void free_new_trailers(struct list_head *trailers)\n>> -{\n>> -\tstruct list_head *pos, *tmp;\n>> -\tstruct new_trailer_item *item;\n>> -\n>> -\tlist_for_each_safe(pos, tmp, trailers) {\n>> -\t\titem = list_entry(pos, struct new_trailer_item, list);\n>> -\t\tlist_del(pos);\n>> -\t\tfree(item);\n>> -\t}\n>> -}\n>> +static char *cl_separators;\n>\n> It somehow feels ugly and goes backward to depend on a new global,\n> especially when you are aiming to libify more things.\n\nRemoved in the next reroll (we can just use a regular stack variable,\nnot a global).\n\n> Even if you plan to later make this function callable from outside\n> the context of parse_options() callback, and if you do not want \"CLI\n> allows '=' as well\" for such new callers, we should be able to have\n> a shared helper function that is the bulk of this function but takes\n> additional parameter to tweak its behaviour slightly depending on\n> the needs of the callers?\n\nI'll refrain from exploring this possibility in this series (and just\nmake do with the non-global variable as described above) because of the\nadditional pending changes around trailer configuration handling in the\nlarger series. FWIW even in the larger series I don't think there's a\nneed to have such a helper function.\n\n>> +\t\ttrailer_conf_set(where, if_exists, if_missing, conf_current);\n>\n> I am getting an impression that the use of and the introduction of\n> the new helper, mixed with movement of the responsibility, makes\n> reviewing the change unnecessarily more cumbersome.  An equivalent\n> series with a few more steps, each of them focusing on a small\n> change (like, \"updating the conf_current members is done with\n> assignments that appear as a pattern very often---introduce a helper\n> to reduce boilerplate\") would have been more enticing to reviewers.\n\nI've broken this patch down into smaller steps (along with the other\nnotable patches in this series).\n\n>> +\t\ttrailer_add_arg_item(strbuf_detach(&tok, NULL),\n>> +\t\t\t\t     strbuf_detach(&val, NULL),\n>> +\t\t\t\t     conf_current,\n>> +\t\t\t\t     trailers);\n>> +\t} else {\n>> +\t\tstruct strbuf sb = STRBUF_INIT;\n>> +\t\tstrbuf_addstr(&sb, arg);\n>> +\t\tstrbuf_trim(&sb);\n>> +\t\terror(_(\"empty trailer token in trailer '%.*s'\"),\n>> +\t\t\t(int) sb.len, sb.buf);\n>> +\t\tstrbuf_release(&sb);\n>> +\t}\n>> +\n>> +\tfree(conf_current);\n>> +\n>>  \treturn 0;\n>>  }\n>>  \n>> @@ -135,7 +148,7 @@ static void read_input_file(struct strbuf *sb, const char *file)\n>>  }\n>>  \n>>  static void interpret_trailers(const struct process_trailer_options *opts,\n>> -\t\t\t       struct list_head *new_trailer_head,\n>> +\t\t\t       struct list_head *arg_trailers,\n>>  \t\t\t       const char *file)\n>>  {\n>>  \tLIST_HEAD(head);\n>> @@ -144,8 +157,6 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n>>  \tstruct trailer_block *trailer_block;\n>>  \tFILE *outfile = stdout;\n>>  \n>> -\ttrailer_config_init();\n>> -\n>>  \tread_input_file(&sb, file);\n>>  \n>>  \tif (opts->in_place)\n>> @@ -162,12 +173,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n>>  \n>>  \n>>  \tif (!opts->only_input) {\n>> -\t\tLIST_HEAD(config_head);\n>> -\t\tLIST_HEAD(arg_head);\n>> -\t\tparse_trailers_from_config(&config_head);\n>> -\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n>> -\t\tlist_splice(&config_head, &arg_head);\n>> -\t\tprocess_trailers_lists(&head, &arg_head);\n>> +\t\tprocess_trailers_lists(&head, arg_trailers);\n>>  \t}\n>\n> So, the bulk of the parsing is no longer responsibility of this\n> function.  Instead, the caller (e.g., cmd_interpret_trailers()) is\n> expected to do that before they call us.\n\nYup. This movement of \"parsing responsibility\" logic should be easy to\nsee in the next reroll (as it will be broken out separately).\n\n>> ...\n>>  \tgit_config(git_default_config, NULL);\n>> +\ttrailer_config_init();\n>> +\n>> +\tif (!opts.only_input) {\n>> +\t\tparse_trailers_from_config(&configured_trailers);\n>> +\t}\n>\n> By the way, until we add more statements in this block, lose the\n> unnecessary {} around it.\n\nOops. Done.\n\n>> @@ -424,6 +429,25 @@ int trailer_set_if_missing(enum trailer_if_missing *item, const char *value)\n>>  \treturn 0;\n>>  }\n>>  \n>> +void trailer_conf_set(enum trailer_where where,\n>> +\t\t      enum trailer_if_exists if_exists,\n>> +\t\t      enum trailer_if_missing if_missing,\n>> +\t\t      struct trailer_conf *conf)\n>> +{\n>> +\tif (where != WHERE_DEFAULT)\n>> +\t\tconf->where = where;\n>> +\tif (if_exists != EXISTS_DEFAULT)\n>> +\t\tconf->if_exists = if_exists;\n>> +\tif (if_missing != MISSING_DEFAULT)\n>> +\t\tconf->if_missing = if_missing;\n>> +}\n>\n> So, this is such a helper function. It is curious that it\n> deliberately lacks the ability to reset what has already been\n> configured back to the default.\n>\n> For example, we earlier saw this original code ...\n>\n>> -\titem = xmalloc(sizeof(*item));\n>> -\titem->text = arg;\n>> -\titem->where = where;\n>> -\titem->if_exists = if_exists;\n>> -\titem->if_missing = if_missing;\n>\n> ... gets replaced with this call.\n>\n>> +\tstruct trailer_conf *conf_current = new_trailer_conf();\n>> ...\n>> +\t\ttrailer_conf_set(where, if_exists, if_missing, conf_current);\n>\n> For this conversion to be correct, we require that the value of the\n> *_DEFAULT macro to be 0 and/or these three values getting assigned\n> are not *_DEFAULT values.  Maybe that may happen to be the case in\n> today's code, but such an unwritten assumption makes me feel nervous.\n>\n> I am not sure if it is worth the confusion factor to make a function\n> whose name is $anything_set() to be making a conditional assignment.\n> If such a conditional assignment *also* happens often and deserves\n> to have its own helper, it is fine to have such a helper for\n> conditional assignment, but calling it $anything_set() is probably a\n> mistake.\n\nAgreed. I've split this out into separate helpers that do not have\nconditionals built into them.\n\n> Other than that, the main thrust of this step seems sensible.\n\nAck.\n\nOverall, for the next reroll I have the commits broken down and it is\nmostly the same. I just need to wait for CI to pass and update the cover\nletter, before sending it (as v4). Should be ready later tonight.\nThanks.\n"},{"id":"487992","messageId":"652df25f30ecc8227bef5109b0f7b47817386b13.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 01/28] trailer: free trailer_info _after_ all related usage","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:01Z","receivedAt":"2024-02-06T05:12:33Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nIn de7c27a186 (trailer: use offsets for trailer_start/trailer_end,\n2023-10-20), we started using trailer block offsets in trailer_info. In\nparticular, we dropped the use of a separate stack variable \"size_t\ntrailer_end\", in favor of accessing the new \"trailer_block_end\" member\nof trailer_info (as \"info.trailer_block_end\").\n\nAt that time, we forgot to also move the\n\n   trailer_info_release(&info);\n\nline to be _after_ this new use of the trailer_info struct. Move it now.\n\nNote that even without this patch, we didn't have leaks or any other\nproblems because trailer_info_release() only frees memory allocated on\nthe heap. The \"trailer_block_end\" member was allocated on the stack back\nthen (as it is now) so it was still safe to use for all this time.\n\nReported-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 3 +--\n 1 file changed, 1 insertion(+), 2 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 3a0710a4583..e1d83390b66 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1111,13 +1111,12 @@ void process_trailers(const char *file,\n \t}\n \n \tprint_all(outfile, &head, opts);\n-\n \tfree_all(&head);\n-\ttrailer_info_release(&info);\n \n \t/* Print the lines after the trailers as is */\n \tif (!opts->only_trailers)\n \t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n+\ttrailer_info_release(&info);\n \n \tif (opts->in_place)\n \t\tif (rename_tempfile(&trailers_tempfile, file))\n-- \ngitgitgadget\n\n"},{"id":"487995","messageId":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v3.git.1706664144.gitgitgadget@gmail.com","subject":"[PATCH v4 00/28] Enrich Trailer API","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:00Z","receivedAt":"2024-02-06T05:12:33Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"This patch series is the first 10 patches of a larger cleanup/bugfix series\n(henceforth \"larger series\") I've been working on. The main goal of this\nseries is to begin the process of \"libifying\" the trailer API. By \"API\" I\nmean the interface exposed in trailer.h. The larger series brings a number\nof additional cleanups (exposing and fixing some bugs along the way), and\nbuilds on top of this series.\n\nWhen the larger series is merged, we will be in a good state to additionally\npursue the following goals:\n\n 1. \"API reuse inside Git\": make the API expressive enough to eliminate any\n    need by other parts of Git to use the interpret-trailers builtin as a\n    subprocess (instead they could just use the API directly);\n 2. \"API stability\": add unit tests to codify the expected behavior of API\n    functions; and\n 3. \"API documentation\": create developer-focused documentation to explain\n    how to use the API effectively, noting any API limitations or\n    anti-patterns.\n\nThe reason why the larger series itself doesn't tackle these goals directly\nis because I believe that API code should be thought from the ground up with\na libification-focused perspective. Some structs and functions exposed in\nthe API today should probably not be libified (read: kept in trailer.h) as\nis. For example, the \"trailer_iterator\" struct has a \"private\" member and it\nfeels wrong to allow API users to peek inside here (and take at face value\nour future API users' pinky promise that they won't depend on those private\ninternals not meant for public consumption).\n\nOne pattern we could use here to cleanly separate \"what is the API\"\n(publicly exposed) and \"what is the implementation\" (private) is the\npointer-to-implementation (\"pimpl\") idiom. There may be other appropriate\npatterns, but I've chosen this one because it's a simple, low-level concept\n(put structs in foo.c instead of foo.h), which has far-reaching high-level\nconsequences (API users must rely exclusively on the API to make use of such\nprivate structs, via opaque pointers). The pimpl idiom for C comes from the\nbook \"C Interfaces and Implementations\" (see patch \"trailer: make\ntrailer_info struct private\").\n\nThe idea of turning a public struct into a private one is a fundamental\nquestion of libification because it forces us to reconsider all of the data\nstructures we have and how they're actually used by already existing users.\nFor the trailer API, those existing users are the \"interpret-trailers\"\nbuiltin command, and anything else that includes the \"trailer.h\" header file\n(e.g., sequencer.c). One advantage of this idiom is that even the compiler\nunderstands it --- the compiler will loudly complain if you try to access\nthe innards of a private struct through an opaque pointer.\n\nAnother advantage of this idiom is that it helps to reduce the probability\nof breaking changes in the API. Because a private struct's members are out\nof view from our users (they only know about opaque pointers to the private\nstruct, not its members), we are free to modify the members of the struct at\nany time, as much as we like, as long as we don't break the semantics of the\nexposed API functions (which is why unit-testing these API functions will be\ncrucial long-term).\n\nIf this pimpl idiom turns out to be a mistake, undoing it is easy --- just\nmove the relevant struct definition from foo.c to the header file. So it's a\ngreat way to try things out without digging ourselves into a pit of despair\nthat will be difficult to get out of.\n\nWith the libification-focused goals out of the way, let's turn to this patch\nseries in more detail.\n\nCurrently, we have \"process_trailers()\" in trailer.h which does many\ndifferent things (parse command-line arguments, create temporary files, etc)\nthat are independent of the concept of \"trailers\". Keeping this function as\nan API function would make unit-testing it difficult. While there is no\ntechnical reason why we couldn't write unit tests for the smaller functions\nthat are called within process_trailers(), doing so would involve testing\nprivate (\"static\" in trailer.c) functions instead of API functions, which\ndefeats the goal of \"API stability\" mentioned earlier above.\n\nAs an alternative to how things are done in this patch series, we could keep\ntrailer.h intact and decide to unit-test the existing \"trailer_info_get()\"\nfunction which does most of the trailer parsing work (and is used by\nsequencer.c). However this function wouldn't be easy to test either, because\nthe resulting \"trailer_info\" struct merely contains the unparsed \"trailers\"\nlines. So the unit test (if it wants to inspect the result of parsing these\nlines) would have to invoke additional parsing functions itself. And at that\npoint it would no longer be a unit test in the traditional sense, because it\nwould be invoking multiple functions at once.\n\nIn summary this series breaks up \"process_trailers()\" into smaller pieces,\nexposing many of the parts relevant to trailer-related processing in\ntrailer.h. This will force us to eventually introduce unit tests for these\nAPI functions, but that is a good thing for API stability.\n\nIn the future after libification is \"complete\", users external to Git will\nbe able to use the same trailer processing API used by the\ninterpret-trailers builtin. For example, a web server may want to parse\ntrailers the same way that Git would parse them, without having to call\ninterpret-trailers as a subprocess. This use case was the original\nmotivation behind my work in this area.\n\nThanks to the aggressive refactoring in this series, I've been able to\nidentify and fix several bugs in our existing implementation. Those fixes\nbuild on top of this series but were not included here, in order to keep\nthis series small. Below is a \"shortlog\" of those fixes I have locally:\n\n * \"trailer: trailer replacement should not change its position\" (If we\n   found a trailer we'd like to replace, preserve its position relative to\n   the other trailers found in the trailer block, instead of always moving\n   it to the beginning or end of the entire trailer block.)\n * \"interpret-trailers: preserve trailers coming from the input\" (Sometimes,\n   the parsed trailers from the input will be formatted differently\n   depending on whether we provide --only-trailers or not. Make the trailers\n   that were not modified and which are coming directly from the input get\n   formatted the same way, regardless of this flag.)\n * \"interpret-trailers: do not modify the input if NOP\" (Refrain from\n   subtracting or adding a newline around the patch divider \"---\" if we are\n   not adding new trailers.)\n * \"trailer formatter: split up format_trailer() monolith\" (Fix a bug in\n   git-log where we still printed a blank newline even if we didn't want to\n   format anything.)\n * \"interpret-trailers: fail if given unrecognized arguments\" (E.g., for\n   \"--where\", only accept recognized WHERE_* enum values. If we get\n   something unrecognized, fail with an error instead of silently doing\n   nothing. Ditto for \"--if-exists\" and \"--if-missing\".)\n\n\nNotable changes in v4\n=====================\n\n * Patches 3, 4, 5, and 8 have been broken up into smaller steps. There are\n   28 instead of 10 patches now, but these 28 should be much easier to\n   review than the (previously condensed) 10.\n * NEW Patch 1: \"trailer: free trailer_info after all related usage\" fixes\n   awkward use-after-free coding style\n * NEW Patch 2: \"shortlog: add test for de-duplicating folded trailers\"\n   increases test coverage related to trailer iterators and \"unfold_value()\"\n * NEW Patch 27: \"trailer_set_*(): put out parameter at the end\" is a small\n   refactor to reorder parameters.\n * Patches 5-16: These smaller patches make up Patch 3 from v3.\n * Patches 17-18: These smaller patches make up Patch 4 from v3.\n * Patches 19-20: These smaller patches make up Patch 5 from v3.\n * Patches 23-26: These smaller patches make up Patch 8 from v3.\n * Anonymize unambiguous parameters in <trailer.h>.\n\n\nNotable changes in v3\n=====================\n\n * Squashed Patch 4 into Patch 3 (\"trailer: unify trailer formatting\n   machinery\"), to avoid breaking the build (\"-Werror=unused-function\"\n   violations)\n * NEW (Patch 10): Introduce \"trailer template\" terminology for readability\n   (no behavioral change)\n * (API function) Rename default_separators() to\n   trailer_default_separators()\n * (API function) Rename new_trailers_clear() to free_trailer_templates()\n * trailer.h: for single-parameter functions, anonymize the parameter name\n   to reduce verbosity\n\n\nNotable changes in v2\n=====================\n\n * (cover letter) Discuss goals of the larger series in more detail,\n   especially the pimpl idiom\n * (cover letter) List bug fixes pending in the larger series that depend on\n   this series\n * Reorder function parameters to have trailer options at the beginning (and\n   out parameters toward the end)\n * \"sequencer: use the trailer iterator\": prefer C string instead of strbuf\n   for new \"raw\" field\n * Patch 1 (was Patch 2) also renames ensure_configured() to\n   trailer_config_init() (forgot to rename this one previously)\n\nLinus Arver (28):\n  trailer: free trailer_info _after_ all related usage\n  shortlog: add test for de-duplicating folded trailers\n  trailer: prepare to expose functions as part of API\n  trailer: move interpret_trailers() to interpret-trailers.c\n  trailer: start preparing for formatting unification\n  trailer_info_get(): reorder parameters\n  format_trailers(): use strbuf instead of FILE\n  format_trailer_info(): move \"fast path\" to caller\n  format_trailers_from_commit(): indirectly call trailer_info_get()\n  format_trailer_info(): use trailer_item objects\n  format_trailer_info(): drop redundant unfold_value()\n  format_trailer_info(): append newline for non-trailer lines\n  trailer: begin formatting unification\n  format_trailer_info(): teach it about opts->trim_empty\n  format_trailer_info(): avoid double-printing the separator\n  trailer: finish formatting unification\n  trailer: teach iterator about non-trailer lines\n  sequencer: use the trailer iterator\n  trailer: make trailer_info struct private\n  trailer: retire trailer_info_get() from API\n  trailer: spread usage of \"trailer_block\" language\n  trailer: prepare to delete \"parse_trailers_from_command_line_args()\"\n  trailer: add new helper functions to API\n  trailer_add_arg_item(): drop new_trailer_item usage\n  trailer: deprecate \"new_trailer_item\" struct from API\n  trailer: unify \"--trailer ...\" arg handling\n  trailer_set_*(): put out parameter at the end\n  trailer: introduce \"template\" term for readability\n\n builtin/interpret-trailers.c | 189 ++++++--\n pretty.c                     |   2 +-\n ref-filter.c                 |   2 +-\n sequencer.c                  |  27 +-\n t/t4201-shortlog.sh          |  32 ++\n trailer.c                    | 811 +++++++++++++++++------------------\n trailer.h                    | 109 ++---\n 7 files changed, 642 insertions(+), 530 deletions(-)\n\n\nbase-commit: a54a84b333adbecf7bc4483c0e36ed5878cac17b\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1632%2Flistx%2Ftrailer-api-refactor-part-1-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1632/listx/trailer-api-refactor-part-1-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/1632\n\nRange-diff vs v3:\n\n  -:  ----------- >  1:  652df25f30e trailer: free trailer_info _after_ all related usage\n  -:  ----------- >  2:  fdccaca2ba0 shortlog: add test for de-duplicating folded trailers\n  1:  72cc12a3066 !  3:  4372af244f0 trailer: prepare to expose functions as part of API\n     @@ Commit message\n          them by their \"trailer\" moniker, just like all the other functions\n          already exposed by trailer.h.\n      \n     -    Take the opportunity to start putting trailer processions options (opts)\n     +    Take the opportunity to start putting trailer processing options (opts)\n          as the first parameter. This will be the pattern going forward in this\n          series.\n      \n     @@ trailer.c: void process_trailers(const char *file,\n       \t}\n       \n      -\tprint_all(outfile, &head, opts);\n     -+\tformat_trailers(opts, &head, outfile);\n     - \n      -\tfree_all(&head);\n     ++\tformat_trailers(opts, &head, outfile);\n      +\tfree_trailers(&head);\n     - \ttrailer_info_release(&info);\n       \n       \t/* Print the lines after the trailers as is */\n     + \tif (!opts->only_trailers)\n      @@ trailer.c: void trailer_info_get(struct trailer_info *info, const char *str,\n       \tsize_t nr = 0, alloc = 0;\n       \tchar **last = NULL;\n  2:  cafa39d1048 !  4:  4073b8eb510 trailer: move interpret_trailers() to interpret-trailers.c\n     @@ Commit message\n          trailer: move interpret_trailers() to interpret-trailers.c\n      \n          The interpret-trailers.c builtin is the only place we need to call\n     -    interpret_trailers(), so move its definition there.\n     +    interpret_trailers(), so move its definition there (together with a few\n     +    helper functions called only by it) and remove its external declaration\n     +    from <trailer.h>.\n      \n     -    Delete the corresponding declaration from trailer.h, which then forces\n     -    us to expose the working innards of that function. This enriches\n     -    trailer.h with a more granular API, which can then be unit-tested in the\n     -    future (because interpret_trailers() by itself does too many things to\n     -    be able to be easily unit-tested).\n     +    Several helper functions that are called by interpret_trailers() remain\n     +    in trailer.c because other callers in the same file still call them.\n     +    Declare them in <trailer.h> so that interpret_trailers() (now in\n     +    builtin/interpret-trailers.c) can continue calling them as a trailer API\n     +    user.\n     +\n     +    This enriches <trailer.h> with a more granular API, which can then be\n     +    unit-tested in the future (because interpret_trailers() by itself does\n     +    too many things to be able to be easily unit-tested).\n      \n          Take this opportunity to demote some file-handling functions out of the\n          trailer API implementation, as these have nothing to do with trailers.\n      \n     +    Helped-by: Junio C Hamano <gitster@pobox.com>\n          Signed-off-by: Linus Arver <linusa@google.com>\n      \n       ## builtin/interpret-trailers.c ##\n     @@ builtin/interpret-trailers.c: static int parse_opt_parse(const struct option *op\n      +\t}\n      +\n      +\tformat_trailers(opts, &head, outfile);\n     -+\n      +\tfree_trailers(&head);\n     -+\ttrailer_info_release(&info);\n      +\n      +\t/* Print the lines after the trailers as is */\n      +\tif (!opts->only_trailers)\n      +\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n     ++\ttrailer_info_release(&info);\n      +\n      +\tif (opts->in_place)\n      +\t\tif (rename_tempfile(&trailers_tempfile, file))\n     @@ trailer.c: static void free_trailers(struct list_head *trailers)\n      -\t}\n      -\n      -\tformat_trailers(opts, &head, outfile);\n     --\n      -\tfree_trailers(&head);\n     --\ttrailer_info_release(&info);\n      -\n      -\t/* Print the lines after the trailers as is */\n      -\tif (!opts->only_trailers)\n      -\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n     +-\ttrailer_info_release(&info);\n      -\n      -\tif (opts->in_place)\n      -\t\tif (rename_tempfile(&trailers_tempfile, file))\n     @@ trailer.h: struct process_trailer_options {\n      +void process_trailers_lists(struct list_head *head,\n      +\t\t\t    struct list_head *arg_head);\n      +\n     -+void parse_trailers(const struct process_trailer_options *opts,\n     -+\t\t    struct trailer_info *info,\n     ++void parse_trailers(const struct process_trailer_options *,\n     ++\t\t    struct trailer_info *,\n      +\t\t    const char *str,\n      +\t\t    struct list_head *head);\n       \n     @@ trailer.h: struct process_trailer_options {\n       void trailer_info_release(struct trailer_info *info);\n       \n      +void trailer_config_init(void);\n     -+void format_trailers(const struct process_trailer_options *opts,\n     ++void format_trailers(const struct process_trailer_options *,\n      +\t\t     struct list_head *trailers, FILE *outfile);\n      +void free_trailers(struct list_head *);\n      +\n  3:  5c7a2354df0 !  5:  b2a0f7829a1 trailer: unify trailer formatting machinery\n     @@ Metadata\n      Author: Linus Arver <linusa@google.com>\n      \n       ## Commit message ##\n     -    trailer: unify trailer formatting machinery\n     +    trailer: start preparing for formatting unification\n      \n     -    Currently have two functions for formatting trailers exposed in\n     -    trailer.h:\n     +    Currently there are two functions for formatting trailers in\n     +    <trailer.h>:\n      \n     -        void format_trailers(FILE *outfile, struct list_head *head,\n     -                            const struct process_trailer_options *opts);\n     +        void format_trailers(const struct process_trailer_options *,\n     +                             struct list_head *trailers, FILE *outfile);\n      \n              void format_trailers_from_commit(struct strbuf *out, const char *msg,\n     -                                        const struct process_trailer_options *opts);\n     +                                         const struct process_trailer_options *opts);\n      \n     -    and previously these functions, although similar enough (even taking the\n     -    same process_trailer_options struct pointer), did not build on each\n     -    other.\n     +    and although they are similar enough (even taking the same\n     +    process_trailer_options struct pointer) they are used quite differently.\n     +    One might intuitively think that format_trailers_from_commit() builds on\n     +    top of format_trailers(), but this is not the case. Instead\n     +    format_trailers_from_commit() calls format_trailer_info() and\n     +    format_trailers() is never called in that codepath.\n      \n     -    Make format_trailers_from_commit() rely on format_trailers(). Teach\n     -    format_trailers() to process trailers with the additional\n     -    process_trailer_options fields like opts->key_only which is only used by\n     -    format_trailers_from_commit() and not builtin/interpret-trailers.c.\n     -    While we're at it, reorder parameters to put the trailer processing\n     -    options first, and the out parameter (strbuf we write into) at the end.\n     +    This is a preparatory refactor to help us deprecate format_trailers() in\n     +    favor of format_trailer_info() (at which point we can rename the latter\n     +    to the former). When the deprecation is complete, both\n     +    format_trailers_from_commit(), and the interpret-trailers builtin will\n     +    be able to call into the same helper function (instead of\n     +    format_trailers() and format_trailer_info(), respectively). Unifying the\n     +    formatters is desirable because it simplifies the API.\n      \n     -    This unification will allow us to delete the format_trailer_info() and\n     -    print_tok_val() functions in the next patch. They are not deleted here\n     -    in order to keep the diff small.\n     +    Reorder parameters for format_trailers_from_commit() to prefer\n      \n     -    Helped-by: Junio C Hamano <gitster@pobox.com>\n     -    Signed-off-by: Linus Arver <linusa@google.com>\n     +        const struct process_trailer_options *opts\n      \n     - ## builtin/interpret-trailers.c ##\n     -@@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n     - {\n     - \tLIST_HEAD(head);\n     - \tstruct strbuf sb = STRBUF_INIT;\n     -+\tstruct strbuf trailer_block = STRBUF_INIT;\n     - \tstruct trailer_info info;\n     - \tFILE *outfile = stdout;\n     - \n     -@@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n     - \t\tprocess_trailers_lists(&head, &arg_head);\n     - \t}\n     - \n     --\tformat_trailers(opts, &head, outfile);\n     -+\t/* Print trailer block. */\n     -+\tformat_trailers(opts, &head, &trailer_block);\n     -+\tfwrite(trailer_block.buf, 1, trailer_block.len, outfile);\n     -+\tstrbuf_release(&trailer_block);\n     - \n     - \tfree_trailers(&head);\n     - \ttrailer_info_release(&info);\n     +    as the first parameter, because these options are intimately tied to\n     +    formatting trailers. And take\n     +\n     +        struct strbuf *out\n     +\n     +    last, because it's an \"out parameter\" (something that the caller wants\n     +    to use as the output of this function).\n     +\n     +    Signed-off-by: Linus Arver <linusa@google.com>\n      \n       ## pretty.c ##\n      @@ pretty.c: static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n     @@ ref-filter.c: static void grab_sub_body_contents(struct atom_value *val, int der\n       \t\t} else if (atom->u.contents.option == C_BARE)\n      \n       ## trailer.c ##\n     -@@ trailer.c: static char last_non_space_char(const char *s)\n     - \treturn '\\0';\n     - }\n     - \n     --static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n     --{\n     --\tchar c;\n     --\n     --\tif (!tok) {\n     --\t\tfprintf(outfile, \"%s\\n\", val);\n     --\t\treturn;\n     --\t}\n     --\n     --\tc = last_non_space_char(tok);\n     --\tif (!c)\n     --\t\treturn;\n     --\tif (strchr(separators, c))\n     --\t\tfprintf(outfile, \"%s%s\\n\", tok, val);\n     --\telse\n     --\t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n     --}\n     --\n     --void format_trailers(const struct process_trailer_options *opts,\n     --\t\t     struct list_head *trailers, FILE *outfile)\n     --{\n     --\tstruct list_head *pos;\n     --\tstruct trailer_item *item;\n     --\tlist_for_each(pos, trailers) {\n     --\t\titem = list_entry(pos, struct trailer_item, list);\n     --\t\tif ((!opts->trim_empty || strlen(item->value) > 0) &&\n     --\t\t    (!opts->only_trailers || item->token))\n     --\t\t\tprint_tok_val(outfile, item->token, item->value);\n     --\t}\n     --}\n     --\n     - static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)\n     - {\n     - \tstruct trailer_item *new_item = xcalloc(1, sizeof(*new_item));\n     -@@ trailer.c: static void unfold_value(struct strbuf *val)\n     - \tstrbuf_release(&out);\n     - }\n     - \n     -+void format_trailers(const struct process_trailer_options *opts,\n     -+\t\t     struct list_head *trailers,\n     -+\t\t     struct strbuf *out)\n     -+{\n     -+\tstruct list_head *pos;\n     -+\tstruct trailer_item *item;\n     -+\tint need_separator = 0;\n     -+\n     -+\tlist_for_each(pos, trailers) {\n     -+\t\titem = list_entry(pos, struct trailer_item, list);\n     -+\t\tif (item->token) {\n     -+\t\t\tchar c;\n     -+\n     -+\t\t\tstruct strbuf tok = STRBUF_INIT;\n     -+\t\t\tstruct strbuf val = STRBUF_INIT;\n     -+\t\t\tstrbuf_addstr(&tok, item->token);\n     -+\t\t\tstrbuf_addstr(&val, item->value);\n     -+\n     -+\t\t\t/*\n     -+\t\t\t * Skip key/value pairs where the value was empty. This\n     -+\t\t\t * can happen from trailers specified without a\n     -+\t\t\t * separator, like `--trailer \"Reviewed-by\"` (no\n     -+\t\t\t * corresponding value).\n     -+\t\t\t */\n     -+\t\t\tif (opts->trim_empty && !strlen(item->value))\n     -+\t\t\t\tcontinue;\n     -+\n     -+\t\t\tif (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n     -+\t\t\t\tif (opts->unfold)\n     -+\t\t\t\t\tunfold_value(&val);\n     -+\n     -+\t\t\t\tif (opts->separator && need_separator)\n     -+\t\t\t\t\tstrbuf_addbuf(out, opts->separator);\n     -+\t\t\t\tif (!opts->value_only)\n     -+\t\t\t\t\tstrbuf_addbuf(out, &tok);\n     -+\t\t\t\tif (!opts->key_only && !opts->value_only) {\n     -+\t\t\t\t\tif (opts->key_value_separator)\n     -+\t\t\t\t\t\tstrbuf_addbuf(out, opts->key_value_separator);\n     -+\t\t\t\t\telse {\n     -+\t\t\t\t\t\tc = last_non_space_char(tok.buf);\n     -+\t\t\t\t\t\tif (c) {\n     -+\t\t\t\t\t\t\tif (!strchr(separators, c))\n     -+\t\t\t\t\t\t\t\tstrbuf_addf(out, \"%c \", separators[0]);\n     -+\t\t\t\t\t\t}\n     -+\t\t\t\t\t}\n     -+\t\t\t\t}\n     -+\t\t\t\tif (!opts->key_only)\n     -+\t\t\t\t\tstrbuf_addbuf(out, &val);\n     -+\t\t\t\tif (!opts->separator)\n     -+\t\t\t\t\tstrbuf_addch(out, '\\n');\n     -+\n     -+\t\t\t\tneed_separator = 1;\n     -+\t\t\t}\n     -+\n     -+\t\t\tstrbuf_release(&tok);\n     -+\t\t\tstrbuf_release(&val);\n     -+\t\t} else if (!opts->only_trailers) {\n     -+\t\t\tif (opts->separator && need_separator) {\n     -+\t\t\t\tstrbuf_addbuf(out, opts->separator);\n     -+\t\t\t}\n     -+\t\t\tstrbuf_addstr(out, item->value);\n     -+\t\t\tif (opts->separator)\n     -+\t\t\t\tstrbuf_rtrim(out);\n     -+\t\t\telse\n     -+\t\t\t\tstrbuf_addch(out, '\\n');\n     -+\n     -+\t\t\tneed_separator = 1;\n     -+\t\t}\n     -+\n     -+\t}\n     -+}\n     -+\n     - /*\n     -  * Parse trailers in \"str\", populating the trailer info and \"head\"\n     -  * linked list structure.\n      @@ trailer.c: void trailer_info_release(struct trailer_info *info)\n       \tfree(info->trailers);\n       }\n       \n      -static void format_trailer_info(struct strbuf *out,\n     --\t\t\t\tconst struct trailer_info *info,\n     --\t\t\t\tconst char *msg,\n     ++static void format_trailer_info(const struct process_trailer_options *opts,\n     + \t\t\t\tconst struct trailer_info *info,\n     + \t\t\t\tconst char *msg,\n      -\t\t\t\tconst struct process_trailer_options *opts)\n     -+void format_trailers_from_commit(const struct process_trailer_options *opts,\n     -+\t\t\t\t const char *msg,\n     -+\t\t\t\t struct strbuf *out)\n     ++\t\t\t\tstruct strbuf *out)\n       {\n     --\tsize_t origlen = out->len;\n     --\tsize_t i;\n     -+\tLIST_HEAD(head);\n     -+\tstruct trailer_info info;\n     -+\n     -+\tparse_trailers(opts, &info, msg, &head);\n     + \tsize_t origlen = out->len;\n     + \tsize_t i;\n     +@@ trailer.c: static void format_trailer_info(struct strbuf *out,\n     + \n     + }\n       \n     - \t/* If we want the whole block untouched, we can take the fast path. */\n     - \tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n     - \t    !opts->separator && !opts->key_only && !opts->value_only &&\n     - \t    !opts->key_value_separator) {\n     --\t\tstrbuf_add(out, msg + info->trailer_block_start,\n     --\t\t\t   info->trailer_block_end - info->trailer_block_start);\n     --\t\treturn;\n     --\t}\n     --\n     --\tfor (i = 0; i < info->trailer_nr; i++) {\n     --\t\tchar *trailer = info->trailers[i];\n     --\t\tssize_t separator_pos = find_separator(trailer, separators);\n     --\n     --\t\tif (separator_pos >= 1) {\n     --\t\t\tstruct strbuf tok = STRBUF_INIT;\n     --\t\t\tstruct strbuf val = STRBUF_INIT;\n     --\n     --\t\t\tparse_trailer(&tok, &val, NULL, trailer, separator_pos);\n     --\t\t\tif (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n     --\t\t\t\tif (opts->unfold)\n     --\t\t\t\t\tunfold_value(&val);\n     --\n     --\t\t\t\tif (opts->separator && out->len != origlen)\n     --\t\t\t\t\tstrbuf_addbuf(out, opts->separator);\n     --\t\t\t\tif (!opts->value_only)\n     --\t\t\t\t\tstrbuf_addbuf(out, &tok);\n     --\t\t\t\tif (!opts->key_only && !opts->value_only) {\n     --\t\t\t\t\tif (opts->key_value_separator)\n     --\t\t\t\t\t\tstrbuf_addbuf(out, opts->key_value_separator);\n     --\t\t\t\t\telse\n     --\t\t\t\t\t\tstrbuf_addstr(out, \": \");\n     --\t\t\t\t}\n     --\t\t\t\tif (!opts->key_only)\n     --\t\t\t\t\tstrbuf_addbuf(out, &val);\n     --\t\t\t\tif (!opts->separator)\n     --\t\t\t\t\tstrbuf_addch(out, '\\n');\n     --\t\t\t}\n     --\t\t\tstrbuf_release(&tok);\n     --\t\t\tstrbuf_release(&val);\n     --\n     --\t\t} else if (!opts->only_trailers) {\n     --\t\t\tif (opts->separator && out->len != origlen) {\n     --\t\t\t\tstrbuf_addbuf(out, opts->separator);\n     --\t\t\t}\n     --\t\t\tstrbuf_addstr(out, trailer);\n     --\t\t\tif (opts->separator) {\n     --\t\t\t\tstrbuf_rtrim(out);\n     --\t\t\t}\n     --\t\t}\n     --\t}\n     --\n     --}\n     --\n      -void format_trailers_from_commit(struct strbuf *out, const char *msg,\n      -\t\t\t\t const struct process_trailer_options *opts)\n     --{\n     --\tstruct trailer_info info;\n     -+\t\tstrbuf_add(out, msg + info.trailer_block_start,\n     -+\t\t\t   info.trailer_block_end - info.trailer_block_start);\n     -+\t} else\n     -+\t\tformat_trailers(opts, &head, out);\n     ++void format_trailers_from_commit(const struct process_trailer_options *opts,\n     ++\t\t\t\t const char *msg,\n     ++\t\t\t\t struct strbuf *out)\n     + {\n     + \tstruct trailer_info info;\n       \n     --\ttrailer_info_get(&info, msg, opts);\n     + \ttrailer_info_get(&info, msg, opts);\n      -\tformat_trailer_info(out, &info, msg, opts);\n     -+\tfree_trailers(&head);\n     ++\tformat_trailer_info(opts, &info, msg, out);\n       \ttrailer_info_release(&info);\n       }\n       \n      \n       ## trailer.h ##\n     -@@ trailer.h: void trailer_info_release(struct trailer_info *info);\n     - \n     - void trailer_config_init(void);\n     - void format_trailers(const struct process_trailer_options *opts,\n     --\t\t     struct list_head *trailers, FILE *outfile);\n     -+\t\t     struct list_head *trailers,\n     -+\t\t     struct strbuf *out);\n     - void free_trailers(struct list_head *);\n     - \n     - /*\n     -- * Format the trailers from the commit msg \"msg\" into the strbuf \"out\".\n     -- * Note two caveats about \"opts\":\n     -- *\n     -- *   - this is primarily a helper for pretty.c, and not\n     -- *     all of the flags are supported.\n     -- *\n     -- *   - this differs from process_trailers slightly in that we always format\n     -- *     only the trailer block itself, even if the \"only_trailers\" option is not\n     -- *     set.\n     -+ * Convenience function to format the trailers from the commit msg \"msg\" into\n     -+ * the strbuf \"out\". Reuses format_trailers internally.\n     +@@ trailer.h: void free_trailers(struct list_head *);\n     +  *     only the trailer block itself, even if the \"only_trailers\" option is not\n     +  *     set.\n        */\n      -void format_trailers_from_commit(struct strbuf *out, const char *msg,\n      -\t\t\t\t const struct process_trailer_options *opts);\n  -:  ----------- >  6:  c1760f80356 trailer_info_get(): reorder parameters\n  -:  ----------- >  7:  9dc912b5bc5 format_trailers(): use strbuf instead of FILE\n  -:  ----------- >  8:  b97c06d8bc3 format_trailer_info(): move \"fast path\" to caller\n  -:  ----------- >  9:  6906910417a format_trailers_from_commit(): indirectly call trailer_info_get()\n  -:  ----------- > 10:  f5b7ba08aa7 format_trailer_info(): use trailer_item objects\n  -:  ----------- > 11:  457f2a839d5 format_trailer_info(): drop redundant unfold_value()\n  -:  ----------- > 12:  a72eca301f7 format_trailer_info(): append newline for non-trailer lines\n  -:  ----------- > 13:  ad77c33e457 trailer: begin formatting unification\n  -:  ----------- > 14:  11f854399db format_trailer_info(): teach it about opts->trim_empty\n  -:  ----------- > 15:  ba1f387747b format_trailer_info(): avoid double-printing the separator\n  -:  ----------- > 16:  31725832224 trailer: finish formatting unification\n  4:  bf2b8e1a3c4 ! 17:  6f17c022b15 sequencer: use the trailer iterator\n     @@ Metadata\n      Author: Linus Arver <linusa@google.com>\n      \n       ## Commit message ##\n     -    sequencer: use the trailer iterator\n     +    trailer: teach iterator about non-trailer lines\n      \n     -    This patch allows for the removal of \"trailer_info_get()\" from the\n     -    trailer.h API, which will be in the next patch.\n     +    Previously the iterator did not iterate over non-trailer lines. This was\n     +    somewhat unfortunate, because trailer blocks could have non-trailer\n     +    lines in them since 146245063e (trailer: allow non-trailers in trailer\n     +    block, 2016-10-21), which was before the iterator was created in\n     +    f0939a0eb1 (trailer: add interface for iterating over commit trailers,\n     +    2020-09-27).\n      \n     -    Instead of calling \"trailer_info_get()\", which is a low-level function\n     -    in the trailers implementation (trailer.c), call\n     -    trailer_iterator_advance(), which was specifically designed for public\n     -    consumption in f0939a0eb1 (trailer: add interface for iterating over\n     -    commit trailers, 2020-09-27).\n     +    So if trailer API users wanted to iterate over all lines in a trailer\n     +    block (including non-trailer lines), they could not use the iterator and\n     +    were forced to use the lower-level trailer_info struct directly (which\n     +    provides a raw string array that includes all lines in the trailer\n     +    block).\n      \n     -    Avoiding \"trailer_info_get()\" means we don't have to worry about options\n     -    like \"no_divider\" (relevant for parsing trailers). We also don't have to\n     -    check for things like \"info.trailer_start == info.trailer_end\" to see\n     -    whether there were any trailers (instead we can just check to see\n     -    whether the iterator advanced at all).\n     +    Change the iterator's behavior so that we also iterate over non-trailer\n     +    lines, instead of skipping over them. The new \"raw\" member of the\n     +    iterator allows API users to access previously inaccessible non-trailer\n     +    lines. Reword the variable \"trailer\" to just \"line\" because this\n     +    variable can now hold both trailer lines _and_ non-trailer lines.\n      \n     -    Also, teach the iterator about non-trailer lines, by adding a new field\n     -    called \"raw\" to hold both trailer and non-trailer lines. This is\n     -    necessary because a \"trailer block\" is a list of trailer lines of at\n     -    least 25% trailers (see 146245063e (trailer: allow non-trailers in\n     -    trailer block, 2016-10-21)), such that it may hold non-trailer lines.\n     +    The new \"raw\" member is important because anyone currently not using the\n     +    iterator is using trailer_info's raw string array directly to access\n     +    lines to check what the combined key + value looks like. If we didn't\n     +    provide a \"raw\" member here, iterator users would have to re-construct\n     +    the unparsed line by concatenating the key and value back together again\n     +    --- which places an undue burden for iterator users.\n      \n     -    Signed-off-by: Linus Arver <linusa@google.com>\n     +    The next patch demonstrates the use of the iterator in sequencer.c as an\n     +    example of where \"raw\" will be useful, so that it can start using the\n     +    iterator.\n      \n     - ## builtin/shortlog.c ##\n     -@@ builtin/shortlog.c: static void insert_records_from_trailers(struct shortlog *log,\n     - \t\t\t\t\t const char *oneline)\n     - {\n     - \tstruct trailer_iterator iter;\n     --\tconst char *commit_buffer, *body;\n     -+\tconst char *commit_buffer, *body, *value;\n     - \tstruct strbuf ident = STRBUF_INIT;\n     - \n     - \tif (!log->trailers.nr)\n     -@@ builtin/shortlog.c: static void insert_records_from_trailers(struct shortlog *log,\n     - \n     - \ttrailer_iterator_init(&iter, body);\n     - \twhile (trailer_iterator_advance(&iter)) {\n     --\t\tconst char *value = iter.val.buf;\n     -+\t\tif (!iter.is_trailer)\n     -+\t\t\tcontinue;\n     -+\n     -+\t\tvalue = iter.val.buf;\n     - \n     - \t\tif (!string_list_has_string(&log->trailers, iter.key.buf))\n     - \t\t\tcontinue;\n     +    For the existing use of the iterator in builtin/shortlog.c, we don't\n     +    have to change the code there because that code does\n      \n     - ## sequencer.c ##\n     -@@ sequencer.c: static const char *get_todo_path(const struct replay_opts *opts)\n     - static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,\n     - \tsize_t ignore_footer)\n     - {\n     --\tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n     --\tstruct trailer_info info;\n     --\tsize_t i;\n     --\tint found_sob = 0, found_sob_last = 0;\n     --\tchar saved_char;\n     --\n     --\topts.no_divider = 1;\n     -+\tstruct trailer_iterator iter;\n     -+\tsize_t i = 0, found_sob = 0;\n     -+\tchar saved_char = sb->buf[sb->len - ignore_footer];\n     - \n     - \tif (ignore_footer) {\n     --\t\tsaved_char = sb->buf[sb->len - ignore_footer];\n     - \t\tsb->buf[sb->len - ignore_footer] = '\\0';\n     - \t}\n     - \n     --\ttrailer_info_get(&info, sb->buf, &opts);\n     -+\ttrailer_iterator_init(&iter, sb->buf);\n     -+\twhile (trailer_iterator_advance(&iter)) {\n     -+\t\ti++;\n     -+\t\tif (sob &&\n     -+\t\t    iter.is_trailer &&\n     -+\t\t    !strncmp(iter.raw, sob->buf, sob->len)) {\n     -+\t\t\tfound_sob = i;\n     -+\t\t}\n     -+\t}\n     -+\ttrailer_iterator_release(&iter);\n     - \n     - \tif (ignore_footer)\n     - \t\tsb->buf[sb->len - ignore_footer] = saved_char;\n     - \n     --\tif (info.trailer_block_start == info.trailer_block_end)\n     -+\tif (!i)\n     - \t\treturn 0;\n     - \n     --\tfor (i = 0; i < info.trailer_nr; i++)\n     --\t\tif (sob && !strncmp(info.trailers[i], sob->buf, sob->len)) {\n     --\t\t\tfound_sob = 1;\n     --\t\t\tif (i == info.trailer_nr - 1)\n     --\t\t\t\tfound_sob_last = 1;\n     --\t\t}\n     --\n     --\ttrailer_info_release(&info);\n     --\n     --\tif (found_sob_last)\n     -+\tif (found_sob == i)\n     - \t\treturn 3;\n     - \tif (found_sob)\n     - \t\treturn 2;\n     +        trailer_iterator_init(&iter, body);\n     +        while (trailer_iterator_advance(&iter)) {\n     +            const char *value = iter.val.buf;\n     +\n     +            if (!string_list_has_string(&log->trailers, iter.key.buf))\n     +                continue;\n     +\n     +            ...\n     +\n     +    and the\n     +\n     +            if (!string_list_has_string(&log->trailers, iter.key.buf))\n     +\n     +    condition already skips over non-trailer lines (iter.key.buf is empty\n     +    for non-trailer lines, making the comparison still work unmodified even\n     +    with this patch).\n     +\n     +    Signed-off-by: Linus Arver <linusa@google.com>\n      \n       ## trailer.c ##\n      @@ trailer.c: void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n     @@ trailer.c: void trailer_iterator_init(struct trailer_iterator *iter, const char\n      -\n      -\t\tif (separator_pos < 1)\n      -\t\t\tcontinue; /* not a real trailer */\n     --\n     -+\tchar *line;\n     -+\tint separator_pos;\n      +\tif (iter->internal.cur < iter->internal.info.trailer_nr) {\n     -+\t\tline = iter->internal.info.trailers[iter->internal.cur++];\n     -+\t\tseparator_pos = find_separator(line, separators);\n     -+\t\titer->is_trailer = (separator_pos > 0);\n     -+\n     ++\t\tchar *line = iter->internal.info.trailers[iter->internal.cur++];\n     ++\t\tint separator_pos = find_separator(line, separators);\n     + \n      +\t\titer->raw = line;\n       \t\tstrbuf_reset(&iter->key);\n       \t\tstrbuf_reset(&iter->val);\n       \t\tparse_trailer(&iter->key, &iter->val, NULL,\n      -\t\t\t      trailer, separator_pos);\n      +\t\t\t      line, separator_pos);\n     + \t\t/* Always unfold values during iteration. */\n       \t\tunfold_value(&iter->val);\n       \t\treturn 1;\n     - \t}\n      \n       ## trailer.h ##\n     -@@ trailer.h: struct trailer_iterator {\n     - \tstruct strbuf key;\n     - \tstruct strbuf val;\n     - \n     +@@ trailer.h: void format_trailers_from_commit(const struct process_trailer_options *,\n     +  *   trailer_iterator_release(&iter);\n     +  */\n     + struct trailer_iterator {\n      +\t/*\n      +\t * Raw line (e.g., \"foo: bar baz\") before being parsed as a trailer\n      +\t * key/val pair as part of a trailer block. A trailer block can be\n     @@ trailer.h: struct trailer_iterator {\n      +\t */\n      +\tconst char *raw;\n      +\n     -+\t/*\n     -+\t * 1 if the raw line was parsed as a trailer line (key/val pair).\n     -+\t */\n     -+\tint is_trailer;\n     -+\n     - \t/* private */\n     - \tstruct {\n     - \t\tstruct trailer_info info;\n     + \tstruct strbuf key;\n     + \tstruct strbuf val;\n     + \n  -:  ----------- > 18:  cc92dfb0bda sequencer: use the trailer iterator\n  5:  c19c1dcc592 ! 19:  f5f0d06613f trailer: make trailer_info struct private\n     @@ Commit message\n            (2) external API users are unable to peer inside this struct (because\n                it is only ever exposed as an opaque pointer).\n      \n     -    This change exposes some deficiencies in the API, mainly with regard to\n     -    information about the location of the trailer block that was parsed.\n     -    Expose new API functions to access this information (needed by\n     -    builtin/interpret-trailers.c).\n     +    There are a couple disadvantages:\n     +\n     +      (A) every time the member of the struct is accessed an extra pointer\n     +          dereference must be done, and\n     +\n     +      (B) for users of trailer_info outside trailer.c, this struct can no\n     +          longer be allocated on the stack and may only be allocated on the\n     +          heap (because its definition is hidden away in trailer.c) and\n     +          appropriately deallocated by the user.\n     +\n     +    This patch believes that the benefits outweight the advantages for\n     +    designing APIs, as explained below.\n     +\n     +    Making trailer_info private exposes existing deficiencies in the API.\n     +    This is because users of this struct had full access to its internals,\n     +    so there wasn't much need to actually design it to be \"complete\" in the\n     +    sense that API users only needed to use what was provided by the API.\n     +    For example, the location of the trailer block (start/end offsets\n     +    relative to the start of the input text) was accessible by looking at\n     +    these struct members directly. Now that the struct is private, we have\n     +    to expose new API functions to allow clients to access this\n     +    information (see builtin/interpret-trailers.c).\n      \n          The idea in this patch to hide implementation details behind an \"opaque\n          pointer\" is also known as the \"pimpl\" (pointer to implementation) idiom\n     @@ Commit message\n      \n          In our case, \"struct trailer_info\" is now hidden from clients, and the\n          ways in which this opaque pointer can be used is limited to the richness\n     -    of the trailer.h file. In other words, trailer.h exclusively controls\n     -    exactly how \"trailer_info\" pointers are to be used.\n     +    of <trailer.h>. In other words, <trailer.h> exclusively controls exactly\n     +    how \"trailer_info\" pointers are to be used.\n      \n          [1] Hanson, David R. \"C Interfaces and Implementations: Techniques for\n              Creating Reusable Software\". Addison Wesley, 1997. p. 22\n      \n     +    Helped-by: Junio C Hamano <gitster@pobox.com>\n          Signed-off-by: Linus Arver <linusa@google.com>\n      \n       ## builtin/interpret-trailers.c ##\n     @@ builtin/interpret-trailers.c: static void interpret_trailers(const struct proces\n       \n       \n      @@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n     - \tstrbuf_release(&trailer_block);\n     - \n     - \tfree_trailers(&head);\n     --\ttrailer_info_release(&info);\n       \n       \t/* Print the lines after the trailers as is */\n       \tif (!opts->only_trailers)\n      -\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n     +-\ttrailer_info_release(&info);\n      +\t\tfwrite(sb.buf + trailer_block_end(info), 1, sb.len - trailer_block_end(info), outfile);\n     -+\n      +\ttrailer_info_release(info);\n       \n       \tif (opts->in_place)\n     @@ trailer.c\n       struct conf_info {\n       \tchar *name;\n       \tchar *key;\n     -@@ trailer.c: void format_trailers(const struct process_trailer_options *opts,\n     - \t}\n     +@@ trailer.c: static void unfold_value(struct strbuf *val)\n     + \tstrbuf_release(&out);\n       }\n       \n      +static struct trailer_info *trailer_info_new(void)\n     @@ trailer.c: void format_trailers(const struct process_trailer_options *opts,\n      +\tstruct trailer_info *info = xcalloc(1, sizeof(*info));\n      +\treturn info;\n      +}\n     -+\n     -+static struct trailer_info *trailer_info_get(const struct process_trailer_options *opts,\n     -+\t\t\t\t\t     const char *str)\n     -+{\n     -+\tstruct trailer_info *info = trailer_info_new();\n     -+\tsize_t end_of_log_message = 0, trailer_block_start = 0;\n     -+\tstruct strbuf **trailer_lines, **ptr;\n     -+\tchar **trailer_strings = NULL;\n     -+\tsize_t nr = 0, alloc = 0;\n     -+\tchar **last = NULL;\n     -+\n     -+\ttrailer_config_init();\n     -+\n     -+\tend_of_log_message = find_end_of_log_message(str, opts->no_divider);\n     -+\ttrailer_block_start = find_trailer_block_start(str, end_of_log_message);\n     -+\n     -+\ttrailer_lines = strbuf_split_buf(str + trailer_block_start,\n     -+\t\t\t\t\t end_of_log_message - trailer_block_start,\n     -+\t\t\t\t\t '\\n',\n     -+\t\t\t\t\t 0);\n     -+\tfor (ptr = trailer_lines; *ptr; ptr++) {\n     -+\t\tif (last && isspace((*ptr)->buf[0])) {\n     -+\t\t\tstruct strbuf sb = STRBUF_INIT;\n     -+\t\t\tstrbuf_attach(&sb, *last, strlen(*last), strlen(*last));\n     -+\t\t\tstrbuf_addbuf(&sb, *ptr);\n     -+\t\t\t*last = strbuf_detach(&sb, NULL);\n     -+\t\t\tcontinue;\n     -+\t\t}\n     -+\t\tALLOC_GROW(trailer_strings, nr + 1, alloc);\n     -+\t\ttrailer_strings[nr] = strbuf_detach(*ptr, NULL);\n     -+\t\tlast = find_separator(trailer_strings[nr], separators) >= 1\n     -+\t\t\t? &trailer_strings[nr]\n     -+\t\t\t: NULL;\n     -+\t\tnr++;\n     -+\t}\n     -+\tstrbuf_list_free(trailer_lines);\n     -+\n     -+\tinfo->blank_line_before_trailer = ends_with_blank_line(str,\n     -+\t\t\t\t\t\t\t       trailer_block_start);\n     -+\tinfo->trailer_block_start = trailer_block_start;\n     -+\tinfo->trailer_block_end = end_of_log_message;\n     -+\tinfo->trailers = trailer_strings;\n     -+\tinfo->trailer_nr = nr;\n     -+\n     -+\treturn info;\n     -+}\n      +\n       /*\n        * Parse trailers in \"str\", populating the trailer info and \"head\"\n     @@ trailer.c: void format_trailers(const struct process_trailer_options *opts,\n       \tstruct strbuf val = STRBUF_INIT;\n       \tsize_t i;\n       \n     --\ttrailer_info_get(info, str, opts);\n     +-\ttrailer_info_get(opts, str, info);\n      +\tinfo = trailer_info_get(opts, str);\n       \n       \tfor (i = 0; i < info->trailer_nr; i++) {\n     @@ trailer.c: void free_trailers(struct list_head *trailers)\n       \t}\n       }\n       \n     --void trailer_info_get(struct trailer_info *info, const char *str,\n     --\t\t      const struct process_trailer_options *opts)\n     +-void trailer_info_get(const struct process_trailer_options *opts,\n     +-\t\t      const char *str,\n     +-\t\t      struct trailer_info *info)\n      +size_t trailer_block_start(struct trailer_info *info)\n     - {\n     --\tsize_t end_of_log_message = 0, trailer_block_start = 0;\n     --\tstruct strbuf **trailer_lines, **ptr;\n     --\tchar **trailer_strings = NULL;\n     --\tsize_t nr = 0, alloc = 0;\n     --\tchar **last = NULL;\n     --\n     --\ttrailer_config_init();\n     --\n     --\tend_of_log_message = find_end_of_log_message(str, opts->no_divider);\n     --\ttrailer_block_start = find_trailer_block_start(str, end_of_log_message);\n     ++{\n      +\treturn info->trailer_block_start;\n      +}\n     - \n     --\ttrailer_lines = strbuf_split_buf(str + trailer_block_start,\n     --\t\t\t\t\t end_of_log_message - trailer_block_start,\n     --\t\t\t\t\t '\\n',\n     --\t\t\t\t\t 0);\n     --\tfor (ptr = trailer_lines; *ptr; ptr++) {\n     --\t\tif (last && isspace((*ptr)->buf[0])) {\n     --\t\t\tstruct strbuf sb = STRBUF_INIT;\n     --\t\t\tstrbuf_attach(&sb, *last, strlen(*last), strlen(*last));\n     --\t\t\tstrbuf_addbuf(&sb, *ptr);\n     --\t\t\t*last = strbuf_detach(&sb, NULL);\n     --\t\t\tcontinue;\n     --\t\t}\n     --\t\tALLOC_GROW(trailer_strings, nr + 1, alloc);\n     --\t\ttrailer_strings[nr] = strbuf_detach(*ptr, NULL);\n     --\t\tlast = find_separator(trailer_strings[nr], separators) >= 1\n     --\t\t\t? &trailer_strings[nr]\n     --\t\t\t: NULL;\n     --\t\tnr++;\n     --\t}\n     --\tstrbuf_list_free(trailer_lines);\n     ++\n      +size_t trailer_block_end(struct trailer_info *info)\n      +{\n      +\treturn info->trailer_block_end;\n      +}\n     - \n     --\tinfo->blank_line_before_trailer = ends_with_blank_line(str,\n     --\t\t\t\t\t\t\t       trailer_block_start);\n     --\tinfo->trailer_block_start = trailer_block_start;\n     --\tinfo->trailer_block_end = end_of_log_message;\n     --\tinfo->trailers = trailer_strings;\n     --\tinfo->trailer_nr = nr;\n     ++\n      +int blank_line_before_trailer_block(struct trailer_info *info)\n      +{\n      +\treturn info->blank_line_before_trailer;\n     ++}\n     ++\n     ++struct trailer_info *trailer_info_get(const struct process_trailer_options *opts,\n     ++\t\t\t\t      const char *str)\n     + {\n     ++\tstruct trailer_info *info = trailer_info_new();\n     + \tsize_t end_of_log_message = 0, trailer_block_start = 0;\n     + \tstruct strbuf **trailer_lines, **ptr;\n     + \tchar **trailer_strings = NULL;\n     +@@ trailer.c: void trailer_info_get(const struct process_trailer_options *opts,\n     + \tinfo->trailer_block_end = end_of_log_message;\n     + \tinfo->trailers = trailer_strings;\n     + \tinfo->trailer_nr = nr;\n     ++\n     ++\treturn info;\n       }\n       \n       void trailer_info_release(struct trailer_info *info)\n     @@ trailer.c: void trailer_info_release(struct trailer_info *info)\n      +\tfree(info);\n       }\n       \n     - void format_trailers_from_commit(const struct process_trailer_options *opts,\n     + void format_trailers(const struct process_trailer_options *opts,\n      @@ trailer.c: void format_trailers_from_commit(const struct process_trailer_options *opts,\n       \t\t\t\t struct strbuf *out)\n       {\n     - \tLIST_HEAD(head);\n     + \tLIST_HEAD(trailers);\n      -\tstruct trailer_info info;\n      -\n     --\tparse_trailers(opts, &info, msg, &head);\n     -+\tstruct trailer_info *info = parse_trailers(opts, msg, &head);\n     +-\tparse_trailers(opts, &info, msg, &trailers);\n     ++\tstruct trailer_info *info = parse_trailers(opts, msg, &trailers);\n       \n       \t/* If we want the whole block untouched, we can take the fast path. */\n       \tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n     @@ trailer.c: void format_trailers_from_commit(const struct process_trailer_options\n      +\t\tstrbuf_add(out, msg + info->trailer_block_start,\n      +\t\t\t   info->trailer_block_end - info->trailer_block_start);\n       \t} else\n     - \t\tformat_trailers(opts, &head, out);\n     + \t\tformat_trailers(opts, &trailers, out);\n       \n     - \tfree_trailers(&head);\n     + \tfree_trailers(&trailers);\n      -\ttrailer_info_release(&info);\n      +\ttrailer_info_release(info);\n       }\n       \n       void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n     - {\n     - \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n     -+\tstruct trailer_info *internal = trailer_info_new();\n     +@@ trailer.c: void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n       \tstrbuf_init(&iter->key, 0);\n       \tstrbuf_init(&iter->val, 0);\n       \topts.no_divider = 1;\n     --\ttrailer_info_get(&iter->internal.info, msg, &opts);\n     -+\titer->internal.info = internal;\n     +-\ttrailer_info_get(&opts, msg, &iter->internal.info);\n      +\titer->internal.info = trailer_info_get(&opts, msg);\n       \titer->internal.cur = 0;\n       }\n       \n     -@@ trailer.c: int trailer_iterator_advance(struct trailer_iterator *iter)\n     + int trailer_iterator_advance(struct trailer_iterator *iter)\n       {\n     - \tchar *line;\n     - \tint separator_pos;\n      -\tif (iter->internal.cur < iter->internal.info.trailer_nr) {\n     --\t\tline = iter->internal.info.trailers[iter->internal.cur++];\n     +-\t\tchar *line = iter->internal.info.trailers[iter->internal.cur++];\n      +\tif (iter->internal.cur < iter->internal.info->trailer_nr) {\n     -+\t\tline = iter->internal.info->trailers[iter->internal.cur++];\n     - \t\tseparator_pos = find_separator(line, separators);\n     - \t\titer->is_trailer = (separator_pos > 0);\n     ++\t\tchar *line = iter->internal.info->trailers[iter->internal.cur++];\n     + \t\tint separator_pos = find_separator(line, separators);\n       \n     + \t\titer->raw = line;\n      @@ trailer.c: int trailer_iterator_advance(struct trailer_iterator *iter)\n       \n       void trailer_iterator_release(struct trailer_iterator *iter)\n     @@ trailer.h: void parse_trailers_from_command_line_args(struct list_head *arg_head\n       void process_trailers_lists(struct list_head *head,\n       \t\t\t    struct list_head *arg_head);\n       \n     --void parse_trailers(const struct process_trailer_options *opts,\n     --\t\t    struct trailer_info *info,\n     +-void parse_trailers(const struct process_trailer_options *,\n     +-\t\t    struct trailer_info *,\n      -\t\t    const char *str,\n      -\t\t    struct list_head *head);\n     -+struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n     ++struct trailer_info *parse_trailers(const struct process_trailer_options *,\n      +\t\t\t\t    const char *str,\n      +\t\t\t\t    struct list_head *head);\n     - \n     --void trailer_info_get(struct trailer_info *info, const char *str,\n     --\t\t      const struct process_trailer_options *opts);\n     -+size_t trailer_block_start(struct trailer_info *info);\n     -+size_t trailer_block_end(struct trailer_info *info);\n     -+int blank_line_before_trailer_block(struct trailer_info *info);\n     ++struct trailer_info *trailer_info_get(const struct process_trailer_options *,\n     ++\t\t\t\t      const char *str);\n     + \n     +-void trailer_info_get(const struct process_trailer_options *,\n     +-\t\t      const char *str,\n     +-\t\t      struct trailer_info *);\n     ++size_t trailer_block_start(struct trailer_info *);\n     ++size_t trailer_block_end(struct trailer_info *);\n     ++int blank_line_before_trailer_block(struct trailer_info *);\n       \n       void trailer_info_release(struct trailer_info *info);\n       \n  -:  ----------- > 20:  607ae7a90cd trailer: retire trailer_info_get() from API\n  6:  0a9a7438c3f ! 21:  38f4b4c4135 trailer: spread usage of \"trailer_block\" language\n     @@ builtin/interpret-trailers.c: static void interpret_trailers(const struct proces\n       \n       \t/* Print trailer block. */\n      -\tformat_trailers(opts, &head, &trailer_block);\n     ++\tformat_trailers(opts, &head, &tb);\n     + \tfree_trailers(&head);\n      -\tfwrite(trailer_block.buf, 1, trailer_block.len, outfile);\n      -\tstrbuf_release(&trailer_block);\n     -+\tformat_trailers(opts, &head, &tb);\n      +\tfwrite(tb.buf, 1, tb.len, outfile);\n      +\tstrbuf_release(&tb);\n       \n     - \tfree_trailers(&head);\n     - \n      -\t/* Print the lines after the trailers as is */\n     -+\t/* Print the lines after the trailer block as is */\n     ++\t/* Print the lines after the trailer block as is. */\n       \tif (!opts->only_trailers)\n      -\t\tfwrite(sb.buf + trailer_block_end(info), 1, sb.len - trailer_block_end(info), outfile);\n     --\n      -\ttrailer_info_release(info);\n     -+\t\tfwrite(sb.buf + trailer_block_end(trailer_block),\n     -+\t\t       1, sb.len - trailer_block_end(trailer_block), outfile);\n     ++\t\tfwrite(sb.buf + trailer_block_end(trailer_block), 1,\n     ++\t\t       sb.len - trailer_block_end(trailer_block), outfile);\n      +\ttrailer_block_release(trailer_block);\n       \n       \tif (opts->in_place)\n     @@ trailer.c\n       \n       \t/*\n       \t * Array of trailers found.\n     -@@ trailer.c: void format_trailers(const struct process_trailer_options *opts,\n     - \t}\n     +@@ trailer.c: static void unfold_value(struct strbuf *val)\n     + \tstrbuf_release(&out);\n       }\n       \n      -static struct trailer_info *trailer_info_new(void)\n     @@ trailer.c: void free_trailers(struct list_head *trailers)\n      +\tfree(trailer_block);\n       }\n       \n     - void format_trailers_from_commit(const struct process_trailer_options *opts,\n     + void format_trailers(const struct process_trailer_options *opts,\n      @@ trailer.c: void format_trailers_from_commit(const struct process_trailer_options *opts,\n       \t\t\t\t struct strbuf *out)\n       {\n     - \tLIST_HEAD(head);\n     --\tstruct trailer_info *info = parse_trailers(opts, msg, &head);\n     -+\tstruct trailer_block *trailer_block = parse_trailers(opts, msg, &head);\n     + \tLIST_HEAD(trailers);\n     +-\tstruct trailer_info *info = parse_trailers(opts, msg, &trailers);\n     ++\tstruct trailer_block *trailer_block = parse_trailers(opts, msg, &trailers);\n       \n       \t/* If we want the whole block untouched, we can take the fast path. */\n       \tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n     @@ trailer.c: void format_trailers_from_commit(const struct process_trailer_options\n      +\t\tstrbuf_add(out, msg + trailer_block->start,\n      +\t\t\t   trailer_block->end - trailer_block->start);\n       \t} else\n     - \t\tformat_trailers(opts, &head, out);\n     + \t\tformat_trailers(opts, &trailers, out);\n       \n     - \tfree_trailers(&head);\n     + \tfree_trailers(&trailers);\n      -\ttrailer_info_release(info);\n      +\ttrailer_block_release(trailer_block);\n       }\n       \n       void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n     - {\n     - \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n     --\tstruct trailer_info *internal = trailer_info_new();\n     +@@ trailer.c: void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n       \tstrbuf_init(&iter->key, 0);\n       \tstrbuf_init(&iter->val, 0);\n       \topts.no_divider = 1;\n     --\titer->internal.info = internal;\n      -\titer->internal.info = trailer_info_get(&opts, msg);\n      +\titer->internal.trailer_block = trailer_block_get(&opts, msg);\n       \titer->internal.cur = 0;\n       }\n       \n     -@@ trailer.c: int trailer_iterator_advance(struct trailer_iterator *iter)\n     + int trailer_iterator_advance(struct trailer_iterator *iter)\n       {\n     - \tchar *line;\n     - \tint separator_pos;\n      -\tif (iter->internal.cur < iter->internal.info->trailer_nr) {\n     --\t\tline = iter->internal.info->trailers[iter->internal.cur++];\n     +-\t\tchar *line = iter->internal.info->trailers[iter->internal.cur++];\n      +\tif (iter->internal.cur < iter->internal.trailer_block->trailer_nr) {\n     -+\t\tline = iter->internal.trailer_block->trailers[iter->internal.cur++];\n     - \t\tseparator_pos = find_separator(line, separators);\n     - \t\titer->is_trailer = (separator_pos > 0);\n     ++\t\tchar *line = iter->internal.trailer_block->trailers[iter->internal.cur++];\n     + \t\tint separator_pos = find_separator(line, separators);\n       \n     + \t\titer->raw = line;\n      @@ trailer.c: int trailer_iterator_advance(struct trailer_iterator *iter)\n       \n       void trailer_iterator_release(struct trailer_iterator *iter)\n     @@ trailer.h: void parse_trailers_from_command_line_args(struct list_head *arg_head\n       void process_trailers_lists(struct list_head *head,\n       \t\t\t    struct list_head *arg_head);\n       \n     --struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n     +-struct trailer_info *parse_trailers(const struct process_trailer_options *,\n      -\t\t\t\t    const char *str,\n      -\t\t\t\t    struct list_head *head);\n     -+struct trailer_block *parse_trailers(const struct process_trailer_options *opts,\n     ++struct trailer_block *parse_trailers(const struct process_trailer_options *,\n      +\t\t\t\t     const char *str,\n      +\t\t\t\t     struct list_head *head);\n       \n     --size_t trailer_block_start(struct trailer_info *info);\n     --size_t trailer_block_end(struct trailer_info *info);\n     --int blank_line_before_trailer_block(struct trailer_info *info);\n     -+size_t trailer_block_start(struct trailer_block *trailer_block);\n     -+size_t trailer_block_end(struct trailer_block *trailer_block);\n     -+int blank_line_before_trailer_block(struct trailer_block *trailer_block);\n     +-size_t trailer_block_start(struct trailer_info *);\n     +-size_t trailer_block_end(struct trailer_info *);\n     +-int blank_line_before_trailer_block(struct trailer_info *);\n     ++size_t trailer_block_start(struct trailer_block *);\n     ++size_t trailer_block_end(struct trailer_block *);\n     ++int blank_line_before_trailer_block(struct trailer_block *);\n       \n      -void trailer_info_release(struct trailer_info *info);\n     -+void trailer_block_release(struct trailer_block *trailer_block);\n     ++void trailer_block_release(struct trailer_block *);\n       \n       void trailer_config_init(void);\n     - void format_trailers(const struct process_trailer_options *opts,\n     + void format_trailers(const struct process_trailer_options *,\n      @@ trailer.h: struct trailer_iterator {\n       \n       \t/* private */\n  7:  97e5d86ddf0 ! 22:  94bf182e3ff trailer: prepare to move parse_trailers_from_command_line_args() to builtin\n     @@ Metadata\n      Author: Linus Arver <linusa@google.com>\n      \n       ## Commit message ##\n     -    trailer: prepare to move parse_trailers_from_command_line_args() to builtin\n     +    trailer: prepare to delete \"parse_trailers_from_command_line_args()\"\n      \n     -    Expose more functions in the trailer.h API, in preparation for moving\n     -    out\n     +    Expose more functions in the trailer.h API, in preparation for deleting\n      \n              parse_trailers_from_command_line_args()\n      \n     -    to interpret-trailer.c, because the trailer API should not be concerned\n     +    from the trailers implementation, because it should not be concerned\n          with command line arguments (as they have nothing to do with trailers\n     -    themselves). The interpret-trailers builtin is the only caller of this\n     -    function.\n     +    themselves). Indeed, the interpret-trailers builtin is the only caller\n     +    of this function inside Git.\n      \n     -    Rename add_arg_item() to trailer_add_arg_item() because it will have to\n     -    be exposed as an API function in the next patch. Rename\n     -    new_trailers_clear() to free_new_trailers() because it will be promoted\n     -    into an API function; the API already has free_trailers(), so using the\n     -    \"free_*\" naming style will keep it consistent. Also rename \"conf_info\"\n     -    to \"trailer_conf\" for readability, dropping the low-value \"_info\" suffix\n     -    as we did earlier in this series for \"trailer_info\" to \"trailer_block\".\n     +    Rename add_arg_item() to trailer_add_arg_item() to expose it as an API\n     +    function. Rename new_trailers_clear() to free_new_trailers() because it\n     +    will be promoted into an API function; the API already has\n     +    free_trailers(), so using the \"free_*\" naming style will keep it\n     +    consistent. Also rename \"conf_info\" to \"trailer_conf\" for readability,\n     +    dropping the low-value \"_info\" suffix as we did earlier in this series\n     +    for \"trailer_info\" to \"trailer_block\".\n      \n          Helped-by: Josh Steadmon <steadmon@google.com>\n          Signed-off-by: Linus Arver <linusa@google.com>\n     @@ trailer.c: static struct trailer_item *add_trailer_item(struct list_head *head,\n      -static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n      -\t\t\t const struct conf_info *conf,\n      -\t\t\t const struct new_trailer_item *new_trailer_item)\n     -+static void trailer_add_arg_item(struct list_head *arg_head, char *tok, char *val,\n     -+\t\t\t\t const struct trailer_conf *conf,\n     -+\t\t\t\t const struct new_trailer_item *new_trailer_item)\n     ++void trailer_add_arg_item(struct list_head *arg_head, char *tok, char *val,\n     ++\t\t\t  const struct trailer_conf *conf,\n     ++\t\t\t  const struct new_trailer_item *new_trailer_item)\n       {\n       \tstruct arg_item *new_item = xcalloc(1, sizeof(*new_item));\n       \tnew_item->token = tok;\n     @@ trailer.c: int trailer_iterator_advance(struct trailer_iterator *iter)\n      -\t\tparse_trailer(&iter->key, &iter->val, NULL,\n      -\t\t\t      line, separator_pos);\n      +\t\tparse_trailer(line, separator_pos, &iter->key, &iter->val, NULL);\n     + \t\t/* Always unfold values during iteration. */\n       \t\tunfold_value(&iter->val);\n       \t\treturn 1;\n     - \t}\n      \n       ## trailer.h ##\n      @@\n     @@ trailer.h: struct new_trailer_item {\n       \n      +void duplicate_trailer_conf(struct trailer_conf *dst,\n      +\t\t\t    const struct trailer_conf *src);\n     ++void trailer_add_arg_item(struct list_head *arg_head, char *tok, char *val,\n     ++\t\t\t  const struct trailer_conf *conf,\n     ++\t\t\t  const struct new_trailer_item *new_trailer_item);\n      +\n       struct process_trailer_options {\n       \tint in_place;\n     @@ trailer.h: void parse_trailers_from_command_line_args(struct list_head *arg_head\n      +\n      +void parse_trailer(const char *line, ssize_t separator_pos,\n      +\t\t   struct strbuf *tok, struct strbuf *val,\n     -+\t\t   const struct trailer_conf **conf);\n     ++\t\t   const struct trailer_conf **);\n      +\n     - struct trailer_block *parse_trailers(const struct process_trailer_options *opts,\n     + struct trailer_block *parse_trailers(const struct process_trailer_options *,\n       \t\t\t\t     const char *str,\n       \t\t\t\t     struct list_head *head);\n  8:  465dc51cdcb ! 23:  3bfe4809ecb trailer: move arg handling to interpret-trailers.c\n     @@ Metadata\n      Author: Linus Arver <linusa@google.com>\n      \n       ## Commit message ##\n     -    trailer: move arg handling to interpret-trailers.c\n     +    trailer: add new helper functions to API\n      \n     -    We don't move the \"arg_item\" struct to interpret-trailers.c, because it\n     -    is now a struct that contains information about trailers that could be\n     -    added into the input text's own trailers. This is a generic concept that\n     -    extends beyond trailers defined as CLI arguments (it applies to trailers\n     -    defined in configuration as well). We will rename \"arg_item\" to\n     -    \"trailer_template\" in a follow-up patch to keep the diff here small.\n     +    This is a preparatory refactor for deprecating \"new_trailer_item\" from\n     +    the API (which will let us deprecate\n     +    parse_trailers_from_command_line_args()).\n     +\n     +    Expose new helper functions from the API, because we'll be calling them\n     +    from interpret-trailers.c soon when we move\n     +    parse_trailers_from_command_line_args() there.\n     +\n     +    Move free_new_trailers() from the builtin to trailer.c because later on\n     +    we will adjust it to free arg_item structs, which are private to\n     +    trailer.c.\n      \n          Signed-off-by: Linus Arver <linusa@google.com>\n      \n     @@ builtin/interpret-trailers.c: static int option_parse_if_missing(const struct op\n      -\t\tfree(item);\n      -\t}\n      -}\n     -+static char *cl_separators;\n     - \n     +-\n       static int option_parse_trailer(const struct option *opt,\n       \t\t\t\t   const char *arg, int unset)\n       {\n     - \tstruct list_head *trailers = opt->value;\n     --\tstruct new_trailer_item *item;\n     -+\tstruct strbuf tok = STRBUF_INIT;\n     -+\tstruct strbuf val = STRBUF_INIT;\n     -+\tconst struct trailer_conf *conf;\n     -+\tstruct trailer_conf *conf_current = new_trailer_conf();\n     -+\tssize_t separator_pos;\n     - \n     - \tif (unset) {\n     - \t\tfree_new_trailers(trailers);\n     -@@ builtin/interpret-trailers.c: static int option_parse_trailer(const struct option *opt,\n     - \tif (!arg)\n     - \t\treturn -1;\n     - \n     --\titem = xmalloc(sizeof(*item));\n     --\titem->text = arg;\n     --\titem->where = where;\n     --\titem->if_exists = if_exists;\n     --\titem->if_missing = if_missing;\n     --\tlist_add_tail(&item->list, trailers);\n     -+\tseparator_pos = find_separator(arg, cl_separators);\n     -+\tif (separator_pos) {\n     -+\t\tparse_trailer(arg, separator_pos, &tok, &val, &conf);\n     -+\t\tduplicate_trailer_conf(conf_current, conf);\n     -+\n     -+\t\t/*\n     -+\t\t * Override conf_current with settings specified via CLI flags.\n     -+\t\t */\n     -+\t\ttrailer_conf_set(where, if_exists, if_missing, conf_current);\n     -+\n     -+\t\ttrailer_add_arg_item(strbuf_detach(&tok, NULL),\n     -+\t\t\t\t     strbuf_detach(&val, NULL),\n     -+\t\t\t\t     conf_current,\n     -+\t\t\t\t     trailers);\n     -+\t} else {\n     -+\t\tstruct strbuf sb = STRBUF_INIT;\n     -+\t\tstrbuf_addstr(&sb, arg);\n     -+\t\tstrbuf_trim(&sb);\n     -+\t\terror(_(\"empty trailer token in trailer '%.*s'\"),\n     -+\t\t\t(int) sb.len, sb.buf);\n     -+\t\tstrbuf_release(&sb);\n     -+\t}\n     -+\n     -+\tfree(conf_current);\n     -+\n     - \treturn 0;\n     - }\n     - \n     -@@ builtin/interpret-trailers.c: static void read_input_file(struct strbuf *sb, const char *file)\n     - }\n     - \n     - static void interpret_trailers(const struct process_trailer_options *opts,\n     --\t\t\t       struct list_head *new_trailer_head,\n     -+\t\t\t       struct list_head *arg_trailers,\n     - \t\t\t       const char *file)\n     - {\n     - \tLIST_HEAD(head);\n     -@@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n     - \tstruct trailer_block *trailer_block;\n     - \tFILE *outfile = stdout;\n     - \n     --\ttrailer_config_init();\n     --\n     - \tread_input_file(&sb, file);\n     - \n     - \tif (opts->in_place)\n     -@@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n     - \n     - \n     - \tif (!opts->only_input) {\n     --\t\tLIST_HEAD(config_head);\n     --\t\tLIST_HEAD(arg_head);\n     --\t\tparse_trailers_from_config(&config_head);\n     --\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n     --\t\tlist_splice(&config_head, &arg_head);\n     --\t\tprocess_trailers_lists(&head, &arg_head);\n     -+\t\tprocess_trailers_lists(&head, arg_trailers);\n     - \t}\n     - \n     - \t/* Print trailer block. */\n     -@@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n     - int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n     - {\n     - \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n     --\tLIST_HEAD(trailers);\n     -+\tLIST_HEAD(configured_trailers);\n     -+\tLIST_HEAD(arg_trailers);\n     - \n     - \tstruct option options[] = {\n     - \t\tOPT_BOOL(0, \"in-place\", &opts.in_place, N_(\"edit files in place\")),\n     -@@ builtin/interpret-trailers.c: int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n     - \t\tOPT_CALLBACK_F(0, \"parse\", &opts, NULL, N_(\"alias for --only-trailers --only-input --unfold\"),\n     - \t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse),\n     - \t\tOPT_BOOL(0, \"no-divider\", &opts.no_divider, N_(\"do not treat \\\"---\\\" as the end of input\")),\n     --\t\tOPT_CALLBACK(0, \"trailer\", &trailers, N_(\"trailer\"),\n     -+\t\tOPT_CALLBACK(0, \"trailer\", &arg_trailers, N_(\"trailer\"),\n     - \t\t\t\tN_(\"trailer(s) to add\"), option_parse_trailer),\n     - \t\tOPT_END()\n     - \t};\n     - \n     - \tgit_config(git_default_config, NULL);\n     -+\ttrailer_config_init();\n     -+\n     -+\tif (!opts.only_input) {\n     -+\t\tparse_trailers_from_config(&configured_trailers);\n     -+\t}\n     -+\n     -+\t/*\n     -+\t* In command-line arguments, '=' is accepted (in addition to the\n     -+\t* separators that are defined).\n     -+\t*/\n     -+\tcl_separators = xstrfmt(\"=%s\", trailer_default_separators());\n     - \n     - \targc = parse_options(argc, argv, prefix, options,\n     - \t\t\t     git_interpret_trailers_usage, 0);\n     - \n     --\tif (opts.only_input && !list_empty(&trailers))\n     -+\tfree(cl_separators);\n     -+\n     -+\tif (opts.only_input && !list_empty(&arg_trailers))\n     - \t\tusage_msg_opt(\n     - \t\t\t_(\"--trailer with --only-input does not make sense\"),\n     - \t\t\tgit_interpret_trailers_usage,\n     - \t\t\toptions);\n     - \n     -+\tlist_splice(&configured_trailers, &arg_trailers);\n     -+\n     - \tif (argc) {\n     - \t\tint i;\n     - \t\tfor (i = 0; i < argc; i++)\n     --\t\t\tinterpret_trailers(&opts, &trailers, argv[i]);\n     -+\t\t\tinterpret_trailers(&opts, &arg_trailers, argv[i]);\n     - \t} else {\n     - \t\tif (opts.in_place)\n     - \t\t\tdie(_(\"no input file given for in-place editing\"));\n     --\t\tinterpret_trailers(&opts, &trailers, NULL);\n     -+\t\tinterpret_trailers(&opts, &arg_trailers, NULL);\n     - \t}\n     - \n     --\tfree_new_trailers(&trailers);\n     -+\tfree_new_trailers(&arg_trailers);\n     - \n     - \treturn 0;\n     - }\n      \n       ## trailer.c ##\n      @@ trailer.c: static LIST_HEAD(conf_head);\n     @@ trailer.c: int trailer_set_if_missing(enum trailer_if_missing *item, const char\n       \treturn 0;\n       }\n       \n     -+void trailer_conf_set(enum trailer_where where,\n     -+\t\t      enum trailer_if_exists if_exists,\n     -+\t\t      enum trailer_if_missing if_missing,\n     -+\t\t      struct trailer_conf *conf)\n     ++void trailer_set_conf_where(enum trailer_where where,\n     ++\t\t\t    struct trailer_conf *conf)\n     ++{\n     ++\tconf->where = where;\n     ++}\n     ++\n     ++void trailer_set_conf_if_exists(enum trailer_if_exists if_exists,\n     ++\t\t\t\tstruct trailer_conf *conf)\n     ++{\n     ++\tconf->if_exists = if_exists;\n     ++}\n     ++\n     ++void trailer_set_conf_if_missing(enum trailer_if_missing if_missing,\n     ++\t\t\t\t struct trailer_conf *conf)\n      +{\n     -+\tif (where != WHERE_DEFAULT)\n     -+\t\tconf->where = where;\n     -+\tif (if_exists != EXISTS_DEFAULT)\n     -+\t\tconf->if_exists = if_exists;\n     -+\tif (if_missing != MISSING_DEFAULT)\n     -+\t\tconf->if_missing = if_missing;\n     ++\tconf->if_missing = if_missing;\n      +}\n      +\n      +struct trailer_conf *new_trailer_conf(void)\n      +{\n     -+\tstruct trailer_conf *new = xcalloc(1, sizeof(*new));\n     -+\treturn new;\n     ++\t return xcalloc(1, sizeof(struct trailer_conf));\n      +}\n      +\n       void duplicate_trailer_conf(struct trailer_conf *dst,\n       \t\t\t    const struct trailer_conf *src)\n       {\n     -@@ trailer.c: ssize_t find_separator(const char *line, const char *separators)\n     - /*\n     -  * Obtain the token, value, and conf from the given trailer.\n     -  *\n     -+ * The conf needs special handling. We first read hardcoded defaults, and\n     -+ * override them if we find a matching trailer configuration.\n     -+ *\n     -  * separator_pos must not be 0, since the token cannot be an empty string.\n     -  *\n     -  * If separator_pos is -1, interpret the whole trailer as a token.\n     -@@ trailer.c: static struct trailer_item *add_trailer_item(struct list_head *head, char *tok,\n     - \treturn new_item;\n     +@@ trailer.c: void duplicate_trailer_conf(struct trailer_conf *dst,\n     + \tdst->cmd = xstrdup_or_null(src->cmd);\n       }\n       \n     --static void trailer_add_arg_item(struct list_head *arg_head, char *tok, char *val,\n     --\t\t\t\t const struct trailer_conf *conf,\n     --\t\t\t\t const struct new_trailer_item *new_trailer_item)\n     -+void trailer_add_arg_item(char *tok, char *val, const struct trailer_conf *conf,\n     -+\t\t\t  struct list_head *arg_head)\n     ++void free_trailer_conf(struct trailer_conf *conf)\n     ++{\n     ++\tfree(conf->name);\n     ++\tfree(conf->key);\n     ++\tfree(conf->command);\n     ++\tfree(conf->cmd);\n     ++\tfree(conf);\n     ++}\n     ++\n     + static struct arg_item *get_conf_item(const char *name)\n       {\n     - \tstruct arg_item *new_item = xcalloc(1, sizeof(*new_item));\n     - \tnew_item->token = tok;\n     - \tnew_item->value = val;\n     - \tduplicate_trailer_conf(&new_item->conf, conf);\n     --\tif (new_trailer_item) {\n     --\t\tif (new_trailer_item->where != WHERE_DEFAULT)\n     --\t\t\tnew_item->conf.where = new_trailer_item->where;\n     --\t\tif (new_trailer_item->if_exists != EXISTS_DEFAULT)\n     --\t\t\tnew_item->conf.if_exists = new_trailer_item->if_exists;\n     --\t\tif (new_trailer_item->if_missing != MISSING_DEFAULT)\n     --\t\t\tnew_item->conf.if_missing = new_trailer_item->if_missing;\n     --\t}\n     - \tlist_add_tail(&new_item->list, arg_head);\n     - }\n     - \n     -@@ trailer.c: void parse_trailers_from_config(struct list_head *config_head)\n     - \tlist_for_each(pos, &conf_head) {\n     - \t\titem = list_entry(pos, struct arg_item, list);\n     - \t\tif (item->conf.command)\n     --\t\t\ttrailer_add_arg_item(config_head,\n     --\t\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n     -+\t\t\ttrailer_add_arg_item(xstrdup(token_from_item(item, NULL)),\n     - \t\t\t\t\t     xstrdup(\"\"),\n     --\t\t\t\t\t     &item->conf, NULL);\n     -+\t\t\t\t\t     &item->conf,\n     -+\t\t\t\t\t     config_head);\n     - \t}\n     - }\n     - \n     -@@ trailer.c: void parse_trailers_from_command_line_args(struct list_head *arg_head,\n     - \t\t\tstrbuf_release(&sb);\n     - \t\t} else {\n     - \t\t\tparse_trailer(tr->text, separator_pos, &tok, &val, &conf);\n     --\t\t\ttrailer_add_arg_item(arg_head,\n     --\t\t\t\t\t     strbuf_detach(&tok, NULL),\n     -+\t\t\ttrailer_add_arg_item(strbuf_detach(&tok, NULL),\n     - \t\t\t\t\t     strbuf_detach(&val, NULL),\n     --\t\t\t\t\t     conf, tr);\n     -+\t\t\t\t\t     conf,\n     -+\t\t\t\t\t     arg_head);\n     - \t\t}\n     - \t}\n     - \n     + \tstruct list_head *pos;\n      @@ trailer.c: void free_trailers(struct list_head *trailers)\n       \t}\n       }\n       \n      +void free_new_trailers(struct list_head *trailers)\n      +{\n     -+\tstruct list_head *pos, *p;\n     ++\tstruct list_head *pos, *tmp;\n     ++\tstruct new_trailer_item *item;\n      +\n     -+\tlist_for_each_safe(pos, p, trailers) {\n     ++\tlist_for_each_safe(pos, tmp, trailers) {\n     ++\t\titem = list_entry(pos, struct new_trailer_item, list);\n      +\t\tlist_del(pos);\n     -+\t\tfree_arg_item(list_entry(pos, struct arg_item, list));\n     ++\t\tfree(item);\n      +\t}\n      +}\n      +\n     @@ trailer.h: struct new_trailer_item {\n       \tenum trailer_if_missing if_missing;\n       };\n       \n     -+void trailer_conf_set(enum trailer_where where,\n     -+\t\t      enum trailer_if_exists if_exists,\n     -+\t\t      enum trailer_if_missing if_missing,\n     -+\t\t      struct trailer_conf *conf);\n     ++void trailer_set_conf_where(enum trailer_where, struct trailer_conf *);\n     ++void trailer_set_conf_if_exists(enum trailer_if_exists, struct trailer_conf *);\n     ++void trailer_set_conf_if_missing(enum trailer_if_missing, struct trailer_conf *);\n      +\n      +struct trailer_conf *new_trailer_conf(void);\n       void duplicate_trailer_conf(struct trailer_conf *dst,\n       \t\t\t    const struct trailer_conf *src);\n     - \n      +const char *trailer_default_separators(void);\n     -+\n     -+void trailer_add_arg_item(char *tok, char *val, const struct trailer_conf *conf,\n     -+\t\t\t  struct list_head *arg_head);\n     -+\n     - struct process_trailer_options {\n     - \tint in_place;\n     - \tint trim_empty;\n     -@@ trailer.h: void format_trailers(const struct process_trailer_options *opts,\n     + void trailer_add_arg_item(struct list_head *arg_head, char *tok, char *val,\n     + \t\t\t  const struct trailer_conf *conf,\n     + \t\t\t  const struct new_trailer_item *new_trailer_item);\n     +@@ trailer.h: void format_trailers(const struct process_trailer_options *,\n       \t\t     struct list_head *trailers,\n       \t\t     struct strbuf *out);\n       void free_trailers(struct list_head *);\n      +void free_new_trailers(struct list_head *);\n     ++void free_trailer_conf(struct trailer_conf *);\n       \n       /*\n        * Convenience function to format the trailers from the commit msg \"msg\" into\n  -:  ----------- > 24:  80e1958bb8d trailer_add_arg_item(): drop new_trailer_item usage\n  9:  885ac87a544 ! 25:  a9080597a28 trailer: delete obsolete argument handling code from API\n     @@ Metadata\n      Author: Linus Arver <linusa@google.com>\n      \n       ## Commit message ##\n     -    trailer: delete obsolete argument handling code from API\n     +    trailer: deprecate \"new_trailer_item\" struct from API\n      \n     -    This commit was not squashed with its parent in order to keep the diff\n     -    separate (to make the additive changes in the parent easier to read).\n     +    Previously, the \"new_trailer_item\" struct served only one purpose --- to\n     +    capture the unparsed raw string <RAW> in \"--trailer <RAW>\", as well as\n     +    the current state of the \"where\", \"if_exists\", and \"if_missing\" global\n     +    variables at the time that the \"--trailer <RAW>\" CLI argument was\n     +    encountered.\n      \n     -    Note that we remove the \"new_trailer_item\" struct, because it has some\n     -    overlap with \"arg_item\" struct that also deals with trailers coming from\n     -    the command line. The latter will be renamed to \"trailer_template\" in\n     -    the next patch.\n     +    In addition, the previous CLI argument handling behavior was to capture\n     +    the <RAW> string in all \"--trailer <RAW>\" arguments and to collect\n     +    them (via option_parse_trailer()) into the \"new_trailer_head\" list. We\n     +    would then iterate over this list again in\n     +    parse_trailers_from_command_line_args() and convert these\n     +    \"new_trailer_item\" objects into \"arg_item\" objects.\n     +\n     +    Skip this intermediate storage of \"new_trailer_item\" objects in favor of\n     +    just storing \"arg_item\" objects. Remove the looping behavior of\n     +    parse_trailers_from_command_line_args() so that it parses a single\n     +    \"--trailer ...\" argument at a time. Rename it to\n     +    parse_trailer_from_command_line_arg() to reflect this new behavior of\n     +    only looking at one string (not multiple strings) at a time. Make\n     +    option_parse_trailer() call parse_trailer_from_command_line_arg() so\n     +    that the CLI arguments it sees are parsed immediately without the need\n     +    for intermediate storage.\n     +\n     +    Delete \"new_trailer_item\", because we don't need it any more.\n     +\n     +    In the next patch we will retire parse_trailer_from_command_line_arg()\n     +    as well, combining it with option_parse_trailer().\n      \n          Signed-off-by: Linus Arver <linusa@google.com>\n      \n     + ## builtin/interpret-trailers.c ##\n     +@@ builtin/interpret-trailers.c: static int option_parse_trailer(const struct option *opt,\n     + \t\t\t\t   const char *arg, int unset)\n     + {\n     + \tstruct list_head *trailers = opt->value;\n     +-\tstruct new_trailer_item *item;\n     + \n     + \tif (unset) {\n     + \t\tfree_new_trailers(trailers);\n     +@@ builtin/interpret-trailers.c: static int option_parse_trailer(const struct option *opt,\n     + \tif (!arg)\n     + \t\treturn -1;\n     + \n     +-\titem = xmalloc(sizeof(*item));\n     +-\titem->text = arg;\n     +-\titem->where = where;\n     +-\titem->if_exists = if_exists;\n     +-\titem->if_missing = if_missing;\n     +-\tlist_add_tail(&item->list, trailers);\n     ++\tparse_trailer_from_command_line_arg(arg, where, if_exists, if_missing, trailers);\n     ++\n     + \treturn 0;\n     + }\n     + \n     +@@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n     + \tstruct trailer_block *trailer_block;\n     + \tFILE *outfile = stdout;\n     + \n     +-\ttrailer_config_init();\n     +-\n     + \tread_input_file(&sb, file);\n     + \n     + \tif (opts->in_place)\n     +@@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n     + \tif (!opts->only_trailers && !blank_line_before_trailer_block(trailer_block))\n     + \t\tfprintf(outfile, \"\\n\");\n     + \n     +-\n     +-\tif (!opts->only_input) {\n     +-\t\tLIST_HEAD(config_head);\n     +-\t\tLIST_HEAD(arg_head);\n     +-\t\tparse_trailers_from_config(&config_head);\n     +-\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n     +-\t\tlist_splice(&config_head, &arg_head);\n     +-\t\tprocess_trailers_lists(&head, &arg_head);\n     +-\t}\n     ++\tif (!opts->only_input)\n     ++\t\tprocess_trailers_lists(&head, new_trailer_head);\n     + \n     + \t/* Print trailer block. */\n     + \tformat_trailers(opts, &head, &tb);\n     +@@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n     + int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n     + {\n     + \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n     ++\tLIST_HEAD(configured_trailers);\n     + \tLIST_HEAD(trailers);\n     + \n     + \tstruct option options[] = {\n     +@@ builtin/interpret-trailers.c: int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n     + \t};\n     + \n     + \tgit_config(git_default_config, NULL);\n     ++\ttrailer_config_init();\n     ++\n     ++\tif (!opts.only_input)\n     ++\t\tparse_trailers_from_config(&configured_trailers);\n     + \n     + \targc = parse_options(argc, argv, prefix, options,\n     + \t\t\t     git_interpret_trailers_usage, 0);\n     +@@ builtin/interpret-trailers.c: int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n     + \t\t\tgit_interpret_trailers_usage,\n     + \t\t\toptions);\n     + \n     ++\tlist_splice(&configured_trailers, &trailers);\n     ++\n     + \tif (argc) {\n     + \t\tint i;\n     + \t\tfor (i = 0; i < argc; i++)\n     +\n       ## trailer.c ##\n      @@ trailer.c: void parse_trailers_from_config(struct list_head *config_head)\n       \t}\n     @@ trailer.c: void parse_trailers_from_config(struct list_head *config_head)\n       \n      -void parse_trailers_from_command_line_args(struct list_head *arg_head,\n      -\t\t\t\t\t   struct list_head *new_trailer_head)\n     --{\n     --\tstruct strbuf tok = STRBUF_INIT;\n     --\tstruct strbuf val = STRBUF_INIT;\n     --\tconst struct trailer_conf *conf;\n     ++void parse_trailer_from_command_line_arg(const char *line,\n     ++\t\t\t\t\t enum trailer_where where,\n     ++\t\t\t\t\t enum trailer_if_exists if_exists,\n     ++\t\t\t\t\t enum trailer_if_missing if_missing,\n     ++\t\t\t\t\t struct list_head *arg_head)\n     + {\n     + \tstruct strbuf tok = STRBUF_INIT;\n     + \tstruct strbuf val = STRBUF_INIT;\n     + \tconst struct trailer_conf *conf;\n      -\tstruct list_head *pos;\n     --\n     --\t/*\n     --\t * In command-line arguments, '=' is accepted (in addition to the\n     --\t * separators that are defined).\n     --\t */\n     + \n     + \t/*\n     + \t * In command-line arguments, '=' is accepted (in addition to the\n     + \t * separators that are defined).\n     + \t */\n      -\tchar *cl_separators = xstrfmt(\"=%s\", separators);\n     --\n     ++\tchar *cl_separators = xstrfmt(\"=%s\", trailer_default_separators());\n     + \n      -\t/* Add an arg item for each trailer on the command line */\n      -\tlist_for_each(pos, new_trailer_head) {\n      -\t\tstruct new_trailer_item *tr =\n      -\t\t\tlist_entry(pos, struct new_trailer_item, list);\n      -\t\tssize_t separator_pos = find_separator(tr->text, cl_separators);\n     --\n     ++\t/* Add an arg item for a trailer from the command line */\n     ++\tssize_t separator_pos = find_separator(line, cl_separators);\n     ++\tfree(cl_separators);\n     + \n      -\t\tif (separator_pos == 0) {\n      -\t\t\tstruct strbuf sb = STRBUF_INIT;\n      -\t\t\tstrbuf_addstr(&sb, tr->text);\n     @@ trailer.c: void parse_trailers_from_config(struct list_head *config_head)\n      -\t\t\t      (int) sb.len, sb.buf);\n      -\t\t\tstrbuf_release(&sb);\n      -\t\t} else {\n     +-\t\t\tstruct trailer_conf *conf_current = new_trailer_conf();\n      -\t\t\tparse_trailer(tr->text, separator_pos, &tok, &val, &conf);\n     --\t\t\ttrailer_add_arg_item(strbuf_detach(&tok, NULL),\n     +-\t\t\tduplicate_trailer_conf(conf_current, conf);\n     ++\tif (separator_pos == 0) {\n     ++\t\tstruct strbuf sb = STRBUF_INIT;\n     ++\t\tstrbuf_addstr(&sb, line);\n     ++\t\tstrbuf_trim(&sb);\n     ++\t\terror(_(\"empty trailer token in trailer '%.*s'\"),\n     ++\t\t      (int) sb.len, sb.buf);\n     ++\t\tstrbuf_release(&sb);\n     ++\t} else {\n     ++\t\tstruct trailer_conf *conf_current = new_trailer_conf();\n     ++\t\tparse_trailer(line, separator_pos, &tok, &val, &conf);\n     ++\t\tduplicate_trailer_conf(conf_current, conf);\n     + \n     +-\t\t\t/*\n     +-\t\t\t * Override conf_current with settings specified via CLI flags.\n     +-\t\t\t */\n     +-\t\t\tif (tr->where != WHERE_DEFAULT)\n     +-\t\t\t\ttrailer_set_conf_where(tr->where, conf_current);\n     +-\t\t\tif (tr->if_exists != EXISTS_DEFAULT)\n     +-\t\t\t\ttrailer_set_conf_if_exists(tr->if_exists, conf_current);\n     +-\t\t\tif (tr->if_missing != MISSING_DEFAULT)\n     +-\t\t\t\ttrailer_set_conf_if_missing(tr->if_missing, conf_current);\n     +-\n     +-\t\t\ttrailer_add_arg_item(arg_head,\n     +-\t\t\t\t\t     strbuf_detach(&tok, NULL),\n      -\t\t\t\t\t     strbuf_detach(&val, NULL),\n     --\t\t\t\t\t     conf,\n     --\t\t\t\t\t     arg_head);\n     +-\t\t\t\t\t     conf_current);\n     +-\t\t\tfree_trailer_conf(conf_current);\n      -\t\t}\n     --\t}\n     ++\t\t/*\n     ++\t\t * Override conf_current with settings specified via CLI flags.\n     ++\t\t */\n     ++\t\tif (where != WHERE_DEFAULT)\n     ++\t\t\ttrailer_set_conf_where(where, conf_current);\n     ++\t\tif (if_exists != EXISTS_DEFAULT)\n     ++\t\t\ttrailer_set_conf_if_exists(if_exists, conf_current);\n     ++\t\tif (if_missing != MISSING_DEFAULT)\n     ++\t\t\ttrailer_set_conf_if_missing(if_missing, conf_current);\n     ++\n     ++\t\ttrailer_add_arg_item(arg_head,\n     ++\t\t\t\t     strbuf_detach(&tok, NULL),\n     ++\t\t\t\t     strbuf_detach(&val, NULL),\n     ++\t\t\t\t     conf_current);\n     ++\t\tfree_trailer_conf(conf_current);\n     + \t}\n      -\n      -\tfree(cl_separators);\n     --}\n     --\n     + }\n     + \n       static const char *next_line(const char *str)\n     +@@ trailer.c: void free_trailers(struct list_head *trailers)\n     + \n     + void free_new_trailers(struct list_head *trailers)\n       {\n     - \tconst char *nl = strchrnul(str, '\\n');\n     +-\tstruct list_head *pos, *tmp;\n     +-\tstruct new_trailer_item *item;\n     ++\tstruct list_head *pos, *p;\n     + \n     +-\tlist_for_each_safe(pos, tmp, trailers) {\n     +-\t\titem = list_entry(pos, struct new_trailer_item, list);\n     ++\tlist_for_each_safe(pos, p, trailers) {\n     + \t\tlist_del(pos);\n     +-\t\tfree(item);\n     ++\t\tfree_arg_item(list_entry(pos, struct arg_item, list));\n     + \t}\n     + }\n     + \n      \n       ## trailer.h ##\n      @@ trailer.h: int trailer_set_where(enum trailer_where *item, const char *value);\n     @@ trailer.h: int trailer_set_where(enum trailer_where *item, const char *value);\n      -\tenum trailer_if_missing if_missing;\n      -};\n      -\n     - void trailer_conf_set(enum trailer_where where,\n     - \t\t      enum trailer_if_exists if_exists,\n     - \t\t      enum trailer_if_missing if_missing,\n     + void trailer_set_conf_where(enum trailer_where, struct trailer_conf *);\n     + void trailer_set_conf_if_exists(enum trailer_if_exists, struct trailer_conf *);\n     + void trailer_set_conf_if_missing(enum trailer_if_missing, struct trailer_conf *);\n      @@ trailer.h: struct process_trailer_options {\n       \n       void parse_trailers_from_config(struct list_head *config_head);\n       \n      -void parse_trailers_from_command_line_args(struct list_head *arg_head,\n      -\t\t\t\t\t   struct list_head *new_trailer_head);\n     --\n     ++void parse_trailer_from_command_line_arg(const char *line,\n     ++\t\t\t\t\t enum trailer_where where,\n     ++\t\t\t\t\t enum trailer_if_exists if_exists,\n     ++\t\t\t\t\t enum trailer_if_missing if_missing,\n     ++\t\t\t\t\t struct list_head *arg_head);\n     + \n       void process_trailers_lists(struct list_head *head,\n       \t\t\t    struct list_head *arg_head);\n     - \n  -:  ----------- > 26:  9720526dd8a trailer: unify \"--trailer ...\" arg handling\n  -:  ----------- > 27:  26df2514acb trailer_set_*(): put out parameter at the end\n 10:  bcd3fc9660e ! 28:  14927038d85 trailer: introduce \"template\" term for readability\n     @@ Commit message\n            - [*] check_if_different()         (reorder parameters only)\n            - [*] find_same_and_apply_arg()    (reorder parameters only)\n      \n     -    Reorder parameters where appropriate; these functions have been marked\n     -    with an asterisk ([*]).\n     +    Reorder parameters to prefer input parameters toward the beginning and\n     +    out parameters at the end; these functions have been marked with an\n     +    asterisk ([*]).\n      \n          This removes the \"arg\" terminology (standing for \"CLI arguments\") from\n          the trailer implementation, which makes sense because trailers\n     @@ Commit message\n          block) and trailer templates that are defined as CLI args or\n          configurations. Some functions implied a single action when they could\n          do two different things, so introduce words like \"maybe\" and \"or\" to\n     -    unmask their behavior.\n     +    make their behavior more explicit.\n      \n          In summary this patch renames and reorders parameters for readability,\n          without any behavioral change. We don't rename\n     -    find_same_and_apply_arg(), because it will be refactored soon. As an\n     -    added benefit, we no longer use the term \"arg\" to mean \"CLI arguments\"\n     -    in the trailer implementation, because trailers themselves should not be\n     -    concerned about CLI arguments (this is the domain of the\n     -    interpret-trailers builtin).\n     +    find_same_and_apply_arg(), because it will be refactored soon.\n      \n          For parse_trailers_from_config() (renamed to\n          parse_trailer_templates_from_config()), add a NEEDSWORK discussion about\n          how the deprecated trailer.*.command configuration option is oddly more\n          featureful than trailer.*.cmd (if we were to remove support for\n          trailer.*.command, users would not be able to replicate the behavior\n     -    with trailer.*.cmd and lose out on functionality).\n     +    with trailer.*.cmd and would lose out on functionality).\n      \n          Signed-off-by: Linus Arver <linusa@google.com>\n      \n       ## builtin/interpret-trailers.c ##\n      @@ builtin/interpret-trailers.c: static int option_parse_if_missing(const struct option *opt,\n     - \n     - static char *cl_separators;\n     + \treturn trailer_set_if_missing(arg, opt->value);\n     + }\n       \n      -static int option_parse_trailer(const struct option *opt,\n      -\t\t\t\t   const char *arg, int unset)\n     @@ builtin/interpret-trailers.c: static int option_parse_if_missing(const struct op\n       \tstruct strbuf val = STRBUF_INIT;\n       \tconst struct trailer_conf *conf;\n      @@ builtin/interpret-trailers.c: static int option_parse_trailer(const struct option *opt,\n     - \tssize_t separator_pos;\n     + \tstatic char *cl_separators;\n       \n       \tif (unset) {\n      -\t\tfree_new_trailers(trailers);\n     @@ builtin/interpret-trailers.c: static int option_parse_trailer(const struct optio\n       \t}\n       \n      @@ builtin/interpret-trailers.c: static int option_parse_trailer(const struct option *opt,\n     - \t\t */\n     - \t\ttrailer_conf_set(where, if_exists, if_missing, conf_current);\n     + \t\tif (if_missing != MISSING_DEFAULT)\n     + \t\t\ttrailer_set_conf_if_missing(if_missing, conf_current);\n       \n     --\t\ttrailer_add_arg_item(strbuf_detach(&tok, NULL),\n     +-\t\ttrailer_add_arg_item(trailers,\n     +-\t\t\t\t     strbuf_detach(&tok, NULL),\n      +\t\tadd_trailer_template(strbuf_detach(&tok, NULL),\n       \t\t\t\t     strbuf_detach(&val, NULL),\n     - \t\t\t\t     conf_current,\n     --\t\t\t\t     trailers);\n     +-\t\t\t\t     conf_current);\n     ++\t\t\t\t     conf_current,\n      +\t\t\t\t     templates);\n     - \t} else {\n     - \t\tstruct strbuf sb = STRBUF_INIT;\n     - \t\tstrbuf_addstr(&sb, arg);\n     + \t\tfree_trailer_conf(conf_current);\n     + \t}\n     + \n      @@ builtin/interpret-trailers.c: static void read_input_file(struct strbuf *sb, const char *file)\n       }\n       \n       static void interpret_trailers(const struct process_trailer_options *opts,\n     --\t\t\t       struct list_head *arg_trailers,\n     +-\t\t\t       struct list_head *new_trailer_head,\n      +\t\t\t       struct list_head *templates,\n       \t\t\t       const char *file)\n       {\n     @@ builtin/interpret-trailers.c: static void interpret_trailers(const struct proces\n       \t/* Print the lines before the trailer block */\n       \tif (!opts->only_trailers)\n      @@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n     + \t\tfprintf(outfile, \"\\n\");\n       \n     - \n     - \tif (!opts->only_input) {\n     --\t\tprocess_trailers_lists(&head, arg_trailers);\n     + \tif (!opts->only_input)\n     +-\t\tprocess_trailers_lists(&head, new_trailer_head);\n      +\t\tapply_trailer_templates(templates, &trailers_from_sb);\n     - \t}\n       \n       \t/* Print trailer block. */\n      -\tformat_trailers(opts, &head, &tb);\n     +-\tfree_trailers(&head);\n      +\tformat_trailers(opts, &trailers_from_sb, &tb);\n     ++\tfree_trailers(&trailers_from_sb);\n       \tfwrite(tb.buf, 1, tb.len, outfile);\n       \tstrbuf_release(&tb);\n       \n     --\tfree_trailers(&head);\n     -+\tfree_trailers(&trailers_from_sb);\n     - \n     - \t/* Print the lines after the trailer block as is */\n     - \tif (!opts->only_trailers)\n      @@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n       int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n       {\n       \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n      -\tLIST_HEAD(configured_trailers);\n     --\tLIST_HEAD(arg_trailers);\n     +-\tLIST_HEAD(trailers);\n      +\tLIST_HEAD(configured_templates);\n      +\tLIST_HEAD(templates);\n       \n     @@ builtin/interpret-trailers.c: int cmd_interpret_trailers(int argc, const char **\n       \t\tOPT_CALLBACK_F(0, \"parse\", &opts, NULL, N_(\"alias for --only-trailers --only-input --unfold\"),\n       \t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse),\n       \t\tOPT_BOOL(0, \"no-divider\", &opts.no_divider, N_(\"do not treat \\\"---\\\" as the end of input\")),\n     --\t\tOPT_CALLBACK(0, \"trailer\", &arg_trailers, N_(\"trailer\"),\n     +-\t\tOPT_CALLBACK(0, \"trailer\", &trailers, N_(\"trailer\"),\n      -\t\t\t\tN_(\"trailer(s) to add\"), option_parse_trailer),\n      +\t\tOPT_CALLBACK(0, \"trailer\", &templates, N_(\"trailer\"),\n      +\t\t\t\tN_(\"trailer(s) to add\"), option_parse_trailer_template),\n     @@ builtin/interpret-trailers.c: int cmd_interpret_trailers(int argc, const char **\n      @@ builtin/interpret-trailers.c: int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n       \ttrailer_config_init();\n       \n     - \tif (!opts.only_input) {\n     + \tif (!opts.only_input)\n      -\t\tparse_trailers_from_config(&configured_trailers);\n      +\t\tparse_trailer_templates_from_config(&configured_templates);\n     - \t}\n     - \n     - \t/*\n     -@@ builtin/interpret-trailers.c: int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n       \n     - \tfree(cl_separators);\n     + \targc = parse_options(argc, argv, prefix, options,\n     + \t\t\t     git_interpret_trailers_usage, 0);\n       \n     --\tif (opts.only_input && !list_empty(&arg_trailers))\n     +-\tif (opts.only_input && !list_empty(&trailers))\n      +\tif (opts.only_input && !list_empty(&templates))\n       \t\tusage_msg_opt(\n       \t\t\t_(\"--trailer with --only-input does not make sense\"),\n       \t\t\tgit_interpret_trailers_usage,\n       \t\t\toptions);\n       \n     --\tlist_splice(&configured_trailers, &arg_trailers);\n     +-\tlist_splice(&configured_trailers, &trailers);\n      +\tlist_splice(&configured_templates, &templates);\n       \n       \tif (argc) {\n       \t\tint i;\n       \t\tfor (i = 0; i < argc; i++)\n     --\t\t\tinterpret_trailers(&opts, &arg_trailers, argv[i]);\n     +-\t\t\tinterpret_trailers(&opts, &trailers, argv[i]);\n      +\t\t\tinterpret_trailers(&opts, &templates, argv[i]);\n       \t} else {\n       \t\tif (opts.in_place)\n       \t\t\tdie(_(\"no input file given for in-place editing\"));\n     --\t\tinterpret_trailers(&opts, &arg_trailers, NULL);\n     +-\t\tinterpret_trailers(&opts, &trailers, NULL);\n      +\t\tinterpret_trailers(&opts, &templates, NULL);\n       \t}\n       \n     --\tfree_new_trailers(&arg_trailers);\n     +-\tfree_new_trailers(&trailers);\n      +\tfree_trailer_templates(&templates);\n       \n       \treturn 0;\n     @@ trailer.c: static char *apply_command(struct trailer_conf *conf, const char *arg\n       \t}\n       }\n       \n     -@@ trailer.c: void duplicate_trailer_conf(struct trailer_conf *dst,\n     - \tdst->cmd = xstrdup_or_null(src->cmd);\n     +@@ trailer.c: void free_trailer_conf(struct trailer_conf *conf)\n     + \tfree(conf);\n       }\n       \n      -static struct arg_item *get_conf_item(const char *name)\n     @@ trailer.c: static struct trailer_item *add_trailer_item(struct list_head *head,\n       \treturn new_item;\n       }\n       \n     --void trailer_add_arg_item(char *tok, char *val, const struct trailer_conf *conf,\n     --\t\t\t  struct list_head *arg_head)\n     +-void trailer_add_arg_item(struct list_head *arg_head, char *tok, char *val,\n     +-\t\t\t  const struct trailer_conf *conf)\n      +void add_trailer_template(char *tok, char *val, const struct trailer_conf *conf,\n      +\t\t\t  struct list_head *templates)\n       {\n     @@ trailer.c: static struct trailer_item *add_trailer_item(struct list_head *head,\n      -\tlist_for_each(pos, &conf_head) {\n      -\t\titem = list_entry(pos, struct arg_item, list);\n      -\t\tif (item->conf.command)\n     --\t\t\ttrailer_add_arg_item(xstrdup(token_from_item(item, NULL)),\n     +-\t\t\ttrailer_add_arg_item(config_head,\n     +-\t\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n      -\t\t\t\t\t     xstrdup(\"\"),\n     --\t\t\t\t\t     &item->conf,\n     --\t\t\t\t\t     config_head);\n     +-\t\t\t\t\t     &item->conf);\n      +\t/*\n      +\t * Get configured templates with a \".command\" option.\n      +\t *\n     @@ trailer.c: static struct trailer_item *add_trailer_item(struct list_head *head,\n       \t}\n       }\n       \n     -@@ trailer.c: static size_t find_trailer_block_start(const char *buf, size_t len)\n     - \t * Get the start of the trailers by looking starting from the end for a\n     - \t * blank line before a set of non-blank lines that (i) are all\n     - \t * trailers, or (ii) contains at least one Git-generated trailer and\n     --\t * consists of at least 25% trailers.\n     -+\t * consists of at least 25% configured trailers.\n     - \t */\n     - \tfor (l = last_line(buf, len);\n     - \t     l >= end_of_title;\n      @@ trailer.c: static size_t find_trailer_block_start(const char *buf, size_t len)\n       \t\t\tpossible_continuation_lines = 0;\n       \t\t\tif (recognized_prefix)\n     @@ trailer.c: void free_trailers(struct list_head *trailers)\n       \n      \n       ## trailer.h ##\n     -@@ trailer.h: void duplicate_trailer_conf(struct trailer_conf *dst,\n     - \n     +@@ trailer.h: struct trailer_conf *new_trailer_conf(void);\n     + void duplicate_trailer_conf(struct trailer_conf *dst,\n     + \t\t\t    const struct trailer_conf *src);\n       const char *trailer_default_separators(void);\n     - \n     --void trailer_add_arg_item(char *tok, char *val, const struct trailer_conf *conf,\n     --\t\t\t  struct list_head *arg_head);\n     -+void add_trailer_template(char *tok, char *val, const struct trailer_conf *conf,\n     +-void trailer_add_arg_item(struct list_head *arg_head, char *tok, char *val,\n     +-\t\t\t  const struct trailer_conf *conf);\n     ++void add_trailer_template(char *tok, char *val, const struct trailer_conf *,\n      +\t\t\t  struct list_head *templates);\n       \n       struct process_trailer_options {\n     @@ trailer.h: struct process_trailer_options {\n       \n      -void process_trailers_lists(struct list_head *head,\n      -\t\t\t    struct list_head *arg_head);\n     -+void apply_trailer_templates(struct list_head *templates, struct list_head *trailers_head);\n     ++void apply_trailer_templates(struct list_head *templates,\n     ++\t\t\t     struct list_head *trailers_head);\n       \n       ssize_t find_separator(const char *line, const char *separators);\n       \n     -@@ trailer.h: void format_trailers(const struct process_trailer_options *opts,\n     +@@ trailer.h: void format_trailers(const struct process_trailer_options *,\n       \t\t     struct list_head *trailers,\n       \t\t     struct strbuf *out);\n       void free_trailers(struct list_head *);\n      -void free_new_trailers(struct list_head *);\n     + void free_trailer_conf(struct trailer_conf *);\n      +void free_trailer_templates(struct list_head *);\n       \n       /*\n\n-- \ngitgitgadget\n"},{"id":"487993","messageId":"fdccaca2ba0c92a3f2b33f6608182ab80507ec53.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 02/28] shortlog: add test for de-duplicating folded trailers","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:02Z","receivedAt":"2024-02-06T05:12:34Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThe shortlog builtin was taught to use the trailer iterator interface in\n47beb37bc6 (shortlog: match commit trailers with --group, 2020-09-27).\nThe iterator always unfolds values and this has always been the case\nsince the time the iterator was first introduced in f0939a0eb1 (trailer:\nadd interface for iterating over commit trailers, 2020-09-27). Add a\ncomment line to remind readers of this behavior.\n\nThe fact that the iterator always unfolds values is important\n(at least for shortlog) because unfolding allows it to recognize both\nfolded and unfolded versions of the same trailer for de-duplication.\n\nCapture the existing behavior in a new test case to guard against\nregressions in this area. This test case is based off of the existing\n\"shortlog de-duplicates trailers in a single commit\" just above it. Now\nif we were to remove the call to\n\n    unfold_value(&iter->val);\n\ninside the iterator, this new test case will break.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n t/t4201-shortlog.sh | 32 ++++++++++++++++++++++++++++++++\n trailer.c           |  1 +\n 2 files changed, 33 insertions(+)\n\ndiff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh\nindex d7382709fc1..f698d0c9ad2 100755\n--- a/t/t4201-shortlog.sh\n+++ b/t/t4201-shortlog.sh\n@@ -312,6 +312,38 @@ test_expect_success 'shortlog de-duplicates trailers in a single commit' '\n \ttest_cmp expect actual\n '\n \n+# Trailers that have unfolded (single line) and folded (multiline) values which\n+# are otherwise identical are treated as the same trailer for de-duplication.\n+test_expect_success 'shortlog de-duplicates trailers in a single commit (folded/unfolded values)' '\n+\tgit commit --allow-empty -F - <<-\\EOF &&\n+\tsubject one\n+\n+\tthis message has two distinct values, plus a repeat (folded)\n+\n+\tRepeated-trailer: Foo foo foo\n+\tRepeated-trailer: Bar\n+\tRepeated-trailer: Foo\n+\t  foo foo\n+\tEOF\n+\n+\tgit commit --allow-empty -F - <<-\\EOF &&\n+\tsubject two\n+\n+\tsimilar to the previous, but without the second distinct value\n+\n+\tRepeated-trailer: Foo foo foo\n+\tRepeated-trailer: Foo\n+\t  foo foo\n+\tEOF\n+\n+\tcat >expect <<-\\EOF &&\n+\t     2\tFoo foo foo\n+\t     1\tBar\n+\tEOF\n+\tgit shortlog -ns --group=trailer:repeated-trailer -2 HEAD >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'shortlog can match multiple groups' '\n \tgit commit --allow-empty -F - <<-\\EOF &&\n \tsubject one\ndiff --git a/trailer.c b/trailer.c\nindex e1d83390b66..f74915bd8cd 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1270,6 +1270,7 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n \t\tstrbuf_reset(&iter->val);\n \t\tparse_trailer(&iter->key, &iter->val, NULL,\n \t\t\t      trailer, separator_pos);\n+\t\t/* Always unfold values during iteration. */\n \t\tunfold_value(&iter->val);\n \t\treturn 1;\n \t}\n-- \ngitgitgadget\n\n"},{"id":"487994","messageId":"4372af244f02b71cc70f3a8e1b5591b3b9fec93a.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 03/28] trailer: prepare to expose functions as part of API","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:03Z","receivedAt":"2024-02-06T05:12:35Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nIn the next patch, we will move \"process_trailers\" from trailer.c to\nbuiltin/interpret-trailers.c. That move will necessitate the growth of\nthe trailer.h API, forcing us to expose some additional functions in\ntrailer.h.\n\nRename relevant functions so that they include the term \"trailer\" in\ntheir name, so that clients of the API will be able to easily identify\nthem by their \"trailer\" moniker, just like all the other functions\nalready exposed by trailer.h.\n\nTake the opportunity to start putting trailer processing options (opts)\nas the first parameter. This will be the pattern going forward in this\nseries.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  4 ++--\n trailer.c                    | 26 +++++++++++++-------------\n trailer.h                    |  6 +++---\n 3 files changed, 18 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 033bd1556cf..85a3413baf5 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -132,11 +132,11 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n \tif (argc) {\n \t\tint i;\n \t\tfor (i = 0; i < argc; i++)\n-\t\t\tprocess_trailers(argv[i], &opts, &trailers);\n+\t\t\tinterpret_trailers(&opts, &trailers, argv[i]);\n \t} else {\n \t\tif (opts.in_place)\n \t\t\tdie(_(\"no input file given for in-place editing\"));\n-\t\tprocess_trailers(NULL, &opts, &trailers);\n+\t\tinterpret_trailers(&opts, &trailers, NULL);\n \t}\n \n \tnew_trailers_clear(&trailers);\ndiff --git a/trailer.c b/trailer.c\nindex f74915bd8cd..916175707d8 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -163,12 +163,12 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n-static void print_all(FILE *outfile, struct list_head *head,\n-\t\t      const struct process_trailer_options *opts)\n+static void format_trailers(const struct process_trailer_options *opts,\n+\t\t\t    struct list_head *trailers, FILE *outfile)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *item;\n-\tlist_for_each(pos, head) {\n+\tlist_for_each(pos, trailers) {\n \t\titem = list_entry(pos, struct trailer_item, list);\n \t\tif ((!opts->trim_empty || strlen(item->value) > 0) &&\n \t\t    (!opts->only_trailers || item->token))\n@@ -589,7 +589,7 @@ static int git_trailer_config(const char *conf_key, const char *value,\n \treturn 0;\n }\n \n-static void ensure_configured(void)\n+static void trailer_config_init(void)\n {\n \tif (configured)\n \t\treturn;\n@@ -1035,10 +1035,10 @@ static void parse_trailers(struct trailer_info *info,\n \t}\n }\n \n-static void free_all(struct list_head *head)\n+static void free_trailers(struct list_head *trailers)\n {\n \tstruct list_head *pos, *p;\n-\tlist_for_each_safe(pos, p, head) {\n+\tlist_for_each_safe(pos, p, trailers) {\n \t\tlist_del(pos);\n \t\tfree_trailer_item(list_entry(pos, struct trailer_item, list));\n \t}\n@@ -1075,16 +1075,16 @@ static FILE *create_in_place_tempfile(const char *file)\n \treturn outfile;\n }\n \n-void process_trailers(const char *file,\n-\t\t      const struct process_trailer_options *opts,\n-\t\t      struct list_head *new_trailer_head)\n+void interpret_trailers(const struct process_trailer_options *opts,\n+\t\t\tstruct list_head *new_trailer_head,\n+\t\t\tconst char *file)\n {\n \tLIST_HEAD(head);\n \tstruct strbuf sb = STRBUF_INIT;\n \tstruct trailer_info info;\n \tFILE *outfile = stdout;\n \n-\tensure_configured();\n+\ttrailer_config_init();\n \n \tread_input_file(&sb, file);\n \n@@ -1110,8 +1110,8 @@ void process_trailers(const char *file,\n \t\tprocess_trailers_lists(&head, &arg_head);\n \t}\n \n-\tprint_all(outfile, &head, opts);\n-\tfree_all(&head);\n+\tformat_trailers(opts, &head, outfile);\n+\tfree_trailers(&head);\n \n \t/* Print the lines after the trailers as is */\n \tif (!opts->only_trailers)\n@@ -1134,7 +1134,7 @@ void trailer_info_get(struct trailer_info *info, const char *str,\n \tsize_t nr = 0, alloc = 0;\n \tchar **last = NULL;\n \n-\tensure_configured();\n+\ttrailer_config_init();\n \n \tend_of_log_message = find_end_of_log_message(str, opts->no_divider);\n \ttrailer_block_start = find_trailer_block_start(str, end_of_log_message);\ndiff --git a/trailer.h b/trailer.h\nindex 1644cd05f60..37033e631a1 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -81,9 +81,9 @@ struct process_trailer_options {\n \n #define PROCESS_TRAILER_OPTIONS_INIT {0}\n \n-void process_trailers(const char *file,\n-\t\t      const struct process_trailer_options *opts,\n-\t\t      struct list_head *new_trailer_head);\n+void interpret_trailers(const struct process_trailer_options *opts,\n+\t\t\tstruct list_head *new_trailer_head,\n+\t\t\tconst char *file);\n \n void trailer_info_get(struct trailer_info *info, const char *str,\n \t\t      const struct process_trailer_options *opts);\n-- \ngitgitgadget\n\n"},{"id":"487996","messageId":"4073b8eb5101ea49d8e79b40822aaa42ca87d24d.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 04/28] trailer: move interpret_trailers() to interpret-trailers.c","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:04Z","receivedAt":"2024-02-06T05:12:36Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThe interpret-trailers.c builtin is the only place we need to call\ninterpret_trailers(), so move its definition there (together with a few\nhelper functions called only by it) and remove its external declaration\nfrom <trailer.h>.\n\nSeveral helper functions that are called by interpret_trailers() remain\nin trailer.c because other callers in the same file still call them.\nDeclare them in <trailer.h> so that interpret_trailers() (now in\nbuiltin/interpret-trailers.c) can continue calling them as a trailer API\nuser.\n\nThis enriches <trailer.h> with a more granular API, which can then be\nunit-tested in the future (because interpret_trailers() by itself does\ntoo many things to be able to be easily unit-tested).\n\nTake this opportunity to demote some file-handling functions out of the\ntrailer API implementation, as these have nothing to do with trailers.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  93 +++++++++++++++++++++++++++\n trailer.c                    | 119 ++++-------------------------------\n trailer.h                    |  20 +++++-\n 3 files changed, 123 insertions(+), 109 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 85a3413baf5..d1cf0aa33a2 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -9,6 +9,7 @@\n #include \"gettext.h\"\n #include \"parse-options.h\"\n #include \"string-list.h\"\n+#include \"tempfile.h\"\n #include \"trailer.h\"\n #include \"config.h\"\n \n@@ -91,6 +92,98 @@ static int parse_opt_parse(const struct option *opt, const char *arg,\n \treturn 0;\n }\n \n+static struct tempfile *trailers_tempfile;\n+\n+static FILE *create_in_place_tempfile(const char *file)\n+{\n+\tstruct stat st;\n+\tstruct strbuf filename_template = STRBUF_INIT;\n+\tconst char *tail;\n+\tFILE *outfile;\n+\n+\tif (stat(file, &st))\n+\t\tdie_errno(_(\"could not stat %s\"), file);\n+\tif (!S_ISREG(st.st_mode))\n+\t\tdie(_(\"file %s is not a regular file\"), file);\n+\tif (!(st.st_mode & S_IWUSR))\n+\t\tdie(_(\"file %s is not writable by user\"), file);\n+\n+\t/* Create temporary file in the same directory as the original */\n+\ttail = strrchr(file, '/');\n+\tif (tail)\n+\t\tstrbuf_add(&filename_template, file, tail - file + 1);\n+\tstrbuf_addstr(&filename_template, \"git-interpret-trailers-XXXXXX\");\n+\n+\ttrailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode);\n+\tstrbuf_release(&filename_template);\n+\toutfile = fdopen_tempfile(trailers_tempfile, \"w\");\n+\tif (!outfile)\n+\t\tdie_errno(_(\"could not open temporary file\"));\n+\n+\treturn outfile;\n+}\n+\n+static void read_input_file(struct strbuf *sb, const char *file)\n+{\n+\tif (file) {\n+\t\tif (strbuf_read_file(sb, file, 0) < 0)\n+\t\t\tdie_errno(_(\"could not read input file '%s'\"), file);\n+\t} else {\n+\t\tif (strbuf_read(sb, fileno(stdin), 0) < 0)\n+\t\t\tdie_errno(_(\"could not read from stdin\"));\n+\t}\n+}\n+\n+static void interpret_trailers(const struct process_trailer_options *opts,\n+\t\t\t       struct list_head *new_trailer_head,\n+\t\t\t       const char *file)\n+{\n+\tLIST_HEAD(head);\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tstruct trailer_info info;\n+\tFILE *outfile = stdout;\n+\n+\ttrailer_config_init();\n+\n+\tread_input_file(&sb, file);\n+\n+\tif (opts->in_place)\n+\t\toutfile = create_in_place_tempfile(file);\n+\n+\tparse_trailers(opts, &info, sb.buf, &head);\n+\n+\t/* Print the lines before the trailers */\n+\tif (!opts->only_trailers)\n+\t\tfwrite(sb.buf, 1, info.trailer_block_start, outfile);\n+\n+\tif (!opts->only_trailers && !info.blank_line_before_trailer)\n+\t\tfprintf(outfile, \"\\n\");\n+\n+\n+\tif (!opts->only_input) {\n+\t\tLIST_HEAD(config_head);\n+\t\tLIST_HEAD(arg_head);\n+\t\tparse_trailers_from_config(&config_head);\n+\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n+\t\tlist_splice(&config_head, &arg_head);\n+\t\tprocess_trailers_lists(&head, &arg_head);\n+\t}\n+\n+\tformat_trailers(opts, &head, outfile);\n+\tfree_trailers(&head);\n+\n+\t/* Print the lines after the trailers as is */\n+\tif (!opts->only_trailers)\n+\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n+\ttrailer_info_release(&info);\n+\n+\tif (opts->in_place)\n+\t\tif (rename_tempfile(&trailers_tempfile, file))\n+\t\t\tdie_errno(_(\"could not rename temporary file to %s\"), file);\n+\n+\tstrbuf_release(&sb);\n+}\n+\n int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n {\n \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\ndiff --git a/trailer.c b/trailer.c\nindex 916175707d8..d23afa0a65c 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -5,7 +5,6 @@\n #include \"string-list.h\"\n #include \"run-command.h\"\n #include \"commit.h\"\n-#include \"tempfile.h\"\n #include \"trailer.h\"\n #include \"list.h\"\n /*\n@@ -163,8 +162,8 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n-static void format_trailers(const struct process_trailer_options *opts,\n-\t\t\t    struct list_head *trailers, FILE *outfile)\n+void format_trailers(const struct process_trailer_options *opts,\n+\t\t     struct list_head *trailers, FILE *outfile)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *item;\n@@ -366,8 +365,8 @@ static int find_same_and_apply_arg(struct list_head *head,\n \treturn 0;\n }\n \n-static void process_trailers_lists(struct list_head *head,\n-\t\t\t\t   struct list_head *arg_head)\n+void process_trailers_lists(struct list_head *head,\n+\t\t\t    struct list_head *arg_head)\n {\n \tstruct list_head *pos, *p;\n \tstruct arg_item *arg_tok;\n@@ -589,7 +588,7 @@ static int git_trailer_config(const char *conf_key, const char *value,\n \treturn 0;\n }\n \n-static void trailer_config_init(void)\n+void trailer_config_init(void)\n {\n \tif (configured)\n \t\treturn;\n@@ -719,7 +718,7 @@ static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n \tlist_add_tail(&new_item->list, arg_head);\n }\n \n-static void parse_trailers_from_config(struct list_head *config_head)\n+void parse_trailers_from_config(struct list_head *config_head)\n {\n \tstruct arg_item *item;\n \tstruct list_head *pos;\n@@ -735,8 +734,8 @@ static void parse_trailers_from_config(struct list_head *config_head)\n \t}\n }\n \n-static void parse_trailers_from_command_line_args(struct list_head *arg_head,\n-\t\t\t\t\t\t  struct list_head *new_trailer_head)\n+void parse_trailers_from_command_line_args(struct list_head *arg_head,\n+\t\t\t\t\t   struct list_head *new_trailer_head)\n {\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n@@ -775,17 +774,6 @@ static void parse_trailers_from_command_line_args(struct list_head *arg_head,\n \tfree(cl_separators);\n }\n \n-static void read_input_file(struct strbuf *sb, const char *file)\n-{\n-\tif (file) {\n-\t\tif (strbuf_read_file(sb, file, 0) < 0)\n-\t\t\tdie_errno(_(\"could not read input file '%s'\"), file);\n-\t} else {\n-\t\tif (strbuf_read(sb, fileno(stdin), 0) < 0)\n-\t\t\tdie_errno(_(\"could not read from stdin\"));\n-\t}\n-}\n-\n static const char *next_line(const char *str)\n {\n \tconst char *nl = strchrnul(str, '\\n');\n@@ -1000,10 +988,10 @@ static void unfold_value(struct strbuf *val)\n  * Parse trailers in \"str\", populating the trailer info and \"head\"\n  * linked list structure.\n  */\n-static void parse_trailers(struct trailer_info *info,\n-\t\t\t     const char *str,\n-\t\t\t     struct list_head *head,\n-\t\t\t     const struct process_trailer_options *opts)\n+void parse_trailers(const struct process_trailer_options *opts,\n+\t\t    struct trailer_info *info,\n+\t\t    const char *str,\n+\t\t    struct list_head *head)\n {\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n@@ -1035,7 +1023,7 @@ static void parse_trailers(struct trailer_info *info,\n \t}\n }\n \n-static void free_trailers(struct list_head *trailers)\n+void free_trailers(struct list_head *trailers)\n {\n \tstruct list_head *pos, *p;\n \tlist_for_each_safe(pos, p, trailers) {\n@@ -1044,87 +1032,6 @@ static void free_trailers(struct list_head *trailers)\n \t}\n }\n \n-static struct tempfile *trailers_tempfile;\n-\n-static FILE *create_in_place_tempfile(const char *file)\n-{\n-\tstruct stat st;\n-\tstruct strbuf filename_template = STRBUF_INIT;\n-\tconst char *tail;\n-\tFILE *outfile;\n-\n-\tif (stat(file, &st))\n-\t\tdie_errno(_(\"could not stat %s\"), file);\n-\tif (!S_ISREG(st.st_mode))\n-\t\tdie(_(\"file %s is not a regular file\"), file);\n-\tif (!(st.st_mode & S_IWUSR))\n-\t\tdie(_(\"file %s is not writable by user\"), file);\n-\n-\t/* Create temporary file in the same directory as the original */\n-\ttail = strrchr(file, '/');\n-\tif (tail)\n-\t\tstrbuf_add(&filename_template, file, tail - file + 1);\n-\tstrbuf_addstr(&filename_template, \"git-interpret-trailers-XXXXXX\");\n-\n-\ttrailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode);\n-\tstrbuf_release(&filename_template);\n-\toutfile = fdopen_tempfile(trailers_tempfile, \"w\");\n-\tif (!outfile)\n-\t\tdie_errno(_(\"could not open temporary file\"));\n-\n-\treturn outfile;\n-}\n-\n-void interpret_trailers(const struct process_trailer_options *opts,\n-\t\t\tstruct list_head *new_trailer_head,\n-\t\t\tconst char *file)\n-{\n-\tLIST_HEAD(head);\n-\tstruct strbuf sb = STRBUF_INIT;\n-\tstruct trailer_info info;\n-\tFILE *outfile = stdout;\n-\n-\ttrailer_config_init();\n-\n-\tread_input_file(&sb, file);\n-\n-\tif (opts->in_place)\n-\t\toutfile = create_in_place_tempfile(file);\n-\n-\tparse_trailers(&info, sb.buf, &head, opts);\n-\n-\t/* Print the lines before the trailers */\n-\tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf, 1, info.trailer_block_start, outfile);\n-\n-\tif (!opts->only_trailers && !info.blank_line_before_trailer)\n-\t\tfprintf(outfile, \"\\n\");\n-\n-\n-\tif (!opts->only_input) {\n-\t\tLIST_HEAD(config_head);\n-\t\tLIST_HEAD(arg_head);\n-\t\tparse_trailers_from_config(&config_head);\n-\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n-\t\tlist_splice(&config_head, &arg_head);\n-\t\tprocess_trailers_lists(&head, &arg_head);\n-\t}\n-\n-\tformat_trailers(opts, &head, outfile);\n-\tfree_trailers(&head);\n-\n-\t/* Print the lines after the trailers as is */\n-\tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n-\ttrailer_info_release(&info);\n-\n-\tif (opts->in_place)\n-\t\tif (rename_tempfile(&trailers_tempfile, file))\n-\t\t\tdie_errno(_(\"could not rename temporary file to %s\"), file);\n-\n-\tstrbuf_release(&sb);\n-}\n-\n void trailer_info_get(struct trailer_info *info, const char *str,\n \t\t      const struct process_trailer_options *opts)\n {\ndiff --git a/trailer.h b/trailer.h\nindex 37033e631a1..c292d44b62f 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -81,15 +81,29 @@ struct process_trailer_options {\n \n #define PROCESS_TRAILER_OPTIONS_INIT {0}\n \n-void interpret_trailers(const struct process_trailer_options *opts,\n-\t\t\tstruct list_head *new_trailer_head,\n-\t\t\tconst char *file);\n+void parse_trailers_from_config(struct list_head *config_head);\n+\n+void parse_trailers_from_command_line_args(struct list_head *arg_head,\n+\t\t\t\t\t   struct list_head *new_trailer_head);\n+\n+void process_trailers_lists(struct list_head *head,\n+\t\t\t    struct list_head *arg_head);\n+\n+void parse_trailers(const struct process_trailer_options *,\n+\t\t    struct trailer_info *,\n+\t\t    const char *str,\n+\t\t    struct list_head *head);\n \n void trailer_info_get(struct trailer_info *info, const char *str,\n \t\t      const struct process_trailer_options *opts);\n \n void trailer_info_release(struct trailer_info *info);\n \n+void trailer_config_init(void);\n+void format_trailers(const struct process_trailer_options *,\n+\t\t     struct list_head *trailers, FILE *outfile);\n+void free_trailers(struct list_head *);\n+\n /*\n  * Format the trailers from the commit msg \"msg\" into the strbuf \"out\".\n  * Note two caveats about \"opts\":\n-- \ngitgitgadget\n\n"},{"id":"487997","messageId":"b2a0f7829a1c5f2822e9a896ffe3744587ff1298.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 05/28] trailer: start preparing for formatting unification","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:05Z","receivedAt":"2024-02-06T05:12:37Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nCurrently there are two functions for formatting trailers in\n<trailer.h>:\n\n    void format_trailers(const struct process_trailer_options *,\n                         struct list_head *trailers, FILE *outfile);\n\n    void format_trailers_from_commit(struct strbuf *out, const char *msg,\n                                     const struct process_trailer_options *opts);\n\nand although they are similar enough (even taking the same\nprocess_trailer_options struct pointer) they are used quite differently.\nOne might intuitively think that format_trailers_from_commit() builds on\ntop of format_trailers(), but this is not the case. Instead\nformat_trailers_from_commit() calls format_trailer_info() and\nformat_trailers() is never called in that codepath.\n\nThis is a preparatory refactor to help us deprecate format_trailers() in\nfavor of format_trailer_info() (at which point we can rename the latter\nto the former). When the deprecation is complete, both\nformat_trailers_from_commit(), and the interpret-trailers builtin will\nbe able to call into the same helper function (instead of\nformat_trailers() and format_trailer_info(), respectively). Unifying the\nformatters is desirable because it simplifies the API.\n\nReorder parameters for format_trailers_from_commit() to prefer\n\n    const struct process_trailer_options *opts\n\nas the first parameter, because these options are intimately tied to\nformatting trailers. And take\n\n    struct strbuf *out\n\nlast, because it's an \"out parameter\" (something that the caller wants\nto use as the output of this function).\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n pretty.c     |  2 +-\n ref-filter.c |  2 +-\n trailer.c    | 11 ++++++-----\n trailer.h    |  5 +++--\n 4 files changed, 11 insertions(+), 9 deletions(-)\n\ndiff --git a/pretty.c b/pretty.c\nindex cf964b060cd..bdbed4295aa 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1759,7 +1759,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \t\t\t\tgoto trailer_out;\n \t\t}\n \t\tif (*arg == ')') {\n-\t\t\tformat_trailers_from_commit(sb, msg + c->subject_off, &opts);\n+\t\t\tformat_trailers_from_commit(&opts, msg + c->subject_off, sb);\n \t\t\tret = arg - placeholder + 1;\n \t\t}\n \ttrailer_out:\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 35b989e1dfe..d358953b0ce 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -1985,7 +1985,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct exp\n \t\t\tstruct strbuf s = STRBUF_INIT;\n \n \t\t\t/* Format the trailer info according to the trailer_opts given */\n-\t\t\tformat_trailers_from_commit(&s, subpos, &atom->u.contents.trailer_opts);\n+\t\t\tformat_trailers_from_commit(&atom->u.contents.trailer_opts, subpos, &s);\n \n \t\t\tv->s = strbuf_detach(&s, NULL);\n \t\t} else if (atom->u.contents.option == C_BARE)\ndiff --git a/trailer.c b/trailer.c\nindex d23afa0a65c..5025be97899 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1083,10 +1083,10 @@ void trailer_info_release(struct trailer_info *info)\n \tfree(info->trailers);\n }\n \n-static void format_trailer_info(struct strbuf *out,\n+static void format_trailer_info(const struct process_trailer_options *opts,\n \t\t\t\tconst struct trailer_info *info,\n \t\t\t\tconst char *msg,\n-\t\t\t\tconst struct process_trailer_options *opts)\n+\t\t\t\tstruct strbuf *out)\n {\n \tsize_t origlen = out->len;\n \tsize_t i;\n@@ -1144,13 +1144,14 @@ static void format_trailer_info(struct strbuf *out,\n \n }\n \n-void format_trailers_from_commit(struct strbuf *out, const char *msg,\n-\t\t\t\t const struct process_trailer_options *opts)\n+void format_trailers_from_commit(const struct process_trailer_options *opts,\n+\t\t\t\t const char *msg,\n+\t\t\t\t struct strbuf *out)\n {\n \tstruct trailer_info info;\n \n \ttrailer_info_get(&info, msg, opts);\n-\tformat_trailer_info(out, &info, msg, opts);\n+\tformat_trailer_info(opts, &info, msg, out);\n \ttrailer_info_release(&info);\n }\n \ndiff --git a/trailer.h b/trailer.h\nindex c292d44b62f..c6d3ee49bbf 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -115,8 +115,9 @@ void free_trailers(struct list_head *);\n  *     only the trailer block itself, even if the \"only_trailers\" option is not\n  *     set.\n  */\n-void format_trailers_from_commit(struct strbuf *out, const char *msg,\n-\t\t\t\t const struct process_trailer_options *opts);\n+void format_trailers_from_commit(const struct process_trailer_options *opts,\n+\t\t\t\t const char *msg,\n+\t\t\t\t struct strbuf *out);\n \n /*\n  * An interface for iterating over the trailers found in a particular commit\n-- \ngitgitgadget\n\n"},{"id":"487998","messageId":"c1760f803565818c3856345d72af5ae7565cd37d.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 06/28] trailer_info_get(): reorder parameters","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:06Z","receivedAt":"2024-02-06T05:12:38Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis is another preparatory refactor to unify the trailer formatters.\n\nTake\n\n    const struct process_trailer_options *opts\n\nas the first parameter, because these options are required for\nparsing trailers (e.g., whether to treat \"---\" as the end of the log\nmessage). And take\n\n    struct trailer_info *info\n\nlast, because it's an \"out parameter\" (something that the caller wants\nto use as the output of this function).\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n sequencer.c |  2 +-\n trailer.c   | 11 ++++++-----\n trailer.h   |  5 +++--\n 3 files changed, 10 insertions(+), 8 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 3cc88d8a800..8e199fc8a47 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -332,7 +332,7 @@ static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,\n \t\tsb->buf[sb->len - ignore_footer] = '\\0';\n \t}\n \n-\ttrailer_info_get(&info, sb->buf, &opts);\n+\ttrailer_info_get(&opts, sb->buf, &info);\n \n \tif (ignore_footer)\n \t\tsb->buf[sb->len - ignore_footer] = saved_char;\ndiff --git a/trailer.c b/trailer.c\nindex 5025be97899..f92d844361a 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -997,7 +997,7 @@ void parse_trailers(const struct process_trailer_options *opts,\n \tstruct strbuf val = STRBUF_INIT;\n \tsize_t i;\n \n-\ttrailer_info_get(info, str, opts);\n+\ttrailer_info_get(opts, str, info);\n \n \tfor (i = 0; i < info->trailer_nr; i++) {\n \t\tint separator_pos;\n@@ -1032,8 +1032,9 @@ void free_trailers(struct list_head *trailers)\n \t}\n }\n \n-void trailer_info_get(struct trailer_info *info, const char *str,\n-\t\t      const struct process_trailer_options *opts)\n+void trailer_info_get(const struct process_trailer_options *opts,\n+\t\t      const char *str,\n+\t\t      struct trailer_info *info)\n {\n \tsize_t end_of_log_message = 0, trailer_block_start = 0;\n \tstruct strbuf **trailer_lines, **ptr;\n@@ -1150,7 +1151,7 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n {\n \tstruct trailer_info info;\n \n-\ttrailer_info_get(&info, msg, opts);\n+\ttrailer_info_get(opts, msg, &info);\n \tformat_trailer_info(opts, &info, msg, out);\n \ttrailer_info_release(&info);\n }\n@@ -1161,7 +1162,7 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n \tstrbuf_init(&iter->key, 0);\n \tstrbuf_init(&iter->val, 0);\n \topts.no_divider = 1;\n-\ttrailer_info_get(&iter->internal.info, msg, &opts);\n+\ttrailer_info_get(&opts, msg, &iter->internal.info);\n \titer->internal.cur = 0;\n }\n \ndiff --git a/trailer.h b/trailer.h\nindex c6d3ee49bbf..410c61b62be 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -94,8 +94,9 @@ void parse_trailers(const struct process_trailer_options *,\n \t\t    const char *str,\n \t\t    struct list_head *head);\n \n-void trailer_info_get(struct trailer_info *info, const char *str,\n-\t\t      const struct process_trailer_options *opts);\n+void trailer_info_get(const struct process_trailer_options *,\n+\t\t      const char *str,\n+\t\t      struct trailer_info *);\n \n void trailer_info_release(struct trailer_info *info);\n \n-- \ngitgitgadget\n\n"},{"id":"487999","messageId":"9dc912b5bc5ff07fedc0dd217b5ecb8a1eb10cc3.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 07/28] format_trailers(): use strbuf instead of FILE","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:07Z","receivedAt":"2024-02-06T05:12:39Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis is another preparatory refactor to unify the trailer formatters.\n\nMake format_trailers() also write to a strbuf, to align with\nformat_trailers_from_commit() which also does the same. Doing this makes\nformat_trailers() behave similar to format_trailer_info() (which will\nsoon help us replace one with the other).\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  6 +++++-\n trailer.c                    | 13 +++++++------\n trailer.h                    |  3 ++-\n 3 files changed, 14 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex d1cf0aa33a2..11f4ce9e4a2 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -140,6 +140,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n {\n \tLIST_HEAD(head);\n \tstruct strbuf sb = STRBUF_INIT;\n+\tstruct strbuf trailer_block = STRBUF_INIT;\n \tstruct trailer_info info;\n \tFILE *outfile = stdout;\n \n@@ -169,8 +170,11 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \t\tprocess_trailers_lists(&head, &arg_head);\n \t}\n \n-\tformat_trailers(opts, &head, outfile);\n+\t/* Print trailer block. */\n+\tformat_trailers(opts, &head, &trailer_block);\n \tfree_trailers(&head);\n+\tfwrite(trailer_block.buf, 1, trailer_block.len, outfile);\n+\tstrbuf_release(&trailer_block);\n \n \t/* Print the lines after the trailers as is */\n \tif (!opts->only_trailers)\ndiff --git a/trailer.c b/trailer.c\nindex f92d844361a..cbd643cd1fe 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -144,12 +144,12 @@ static char last_non_space_char(const char *s)\n \treturn '\\0';\n }\n \n-static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n+static void print_tok_val(struct strbuf *out, const char *tok, const char *val)\n {\n \tchar c;\n \n \tif (!tok) {\n-\t\tfprintf(outfile, \"%s\\n\", val);\n+\t\tstrbuf_addf(out, \"%s\\n\", val);\n \t\treturn;\n \t}\n \n@@ -157,13 +157,14 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n \tif (!c)\n \t\treturn;\n \tif (strchr(separators, c))\n-\t\tfprintf(outfile, \"%s%s\\n\", tok, val);\n+\t\tstrbuf_addf(out, \"%s%s\\n\", tok, val);\n \telse\n-\t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n+\t\tstrbuf_addf(out, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n void format_trailers(const struct process_trailer_options *opts,\n-\t\t     struct list_head *trailers, FILE *outfile)\n+\t\t     struct list_head *trailers,\n+\t\t     struct strbuf *out)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *item;\n@@ -171,7 +172,7 @@ void format_trailers(const struct process_trailer_options *opts,\n \t\titem = list_entry(pos, struct trailer_item, list);\n \t\tif ((!opts->trim_empty || strlen(item->value) > 0) &&\n \t\t    (!opts->only_trailers || item->token))\n-\t\t\tprint_tok_val(outfile, item->token, item->value);\n+\t\t\tprint_tok_val(out, item->token, item->value);\n \t}\n }\n \ndiff --git a/trailer.h b/trailer.h\nindex 410c61b62be..1d106b6dd40 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -102,7 +102,8 @@ void trailer_info_release(struct trailer_info *info);\n \n void trailer_config_init(void);\n void format_trailers(const struct process_trailer_options *,\n-\t\t     struct list_head *trailers, FILE *outfile);\n+\t\t     struct list_head *trailers,\n+\t\t     struct strbuf *out);\n void free_trailers(struct list_head *);\n \n /*\n-- \ngitgitgadget\n\n"},{"id":"488000","messageId":"b97c06d8bc324139710b6fb8c7765326f2a2bd2c.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 08/28] format_trailer_info(): move \"fast path\" to caller","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:08Z","receivedAt":"2024-02-06T05:12:40Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis is another preparatory refactor to unify the trailer formatters.\n\nThis allows us to drop the \"msg\" parameter from format_trailer_info(),\nso that it take 3 parameters, similar to format_trailers() which also\ntakes 3 parameters:\n\n    void format_trailers(const struct process_trailer_options *opts,\n                         struct list_head *trailers,\n                         struct strbuf *out)\n\nThe short-term goal is to make format_trailer_info() be smart enough to\ndeprecate format_trailers(). And then ultimately we will rename\nformat_trailer_info() to format_trailers().\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 20 +++++++++-----------\n 1 file changed, 9 insertions(+), 11 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex cbd643cd1fe..e92d0154d90 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1087,21 +1087,11 @@ void trailer_info_release(struct trailer_info *info)\n \n static void format_trailer_info(const struct process_trailer_options *opts,\n \t\t\t\tconst struct trailer_info *info,\n-\t\t\t\tconst char *msg,\n \t\t\t\tstruct strbuf *out)\n {\n \tsize_t origlen = out->len;\n \tsize_t i;\n \n-\t/* If we want the whole block untouched, we can take the fast path. */\n-\tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n-\t    !opts->separator && !opts->key_only && !opts->value_only &&\n-\t    !opts->key_value_separator) {\n-\t\tstrbuf_add(out, msg + info->trailer_block_start,\n-\t\t\t   info->trailer_block_end - info->trailer_block_start);\n-\t\treturn;\n-\t}\n-\n \tfor (i = 0; i < info->trailer_nr; i++) {\n \t\tchar *trailer = info->trailers[i];\n \t\tssize_t separator_pos = find_separator(trailer, separators);\n@@ -1153,7 +1143,15 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \tstruct trailer_info info;\n \n \ttrailer_info_get(opts, msg, &info);\n-\tformat_trailer_info(opts, &info, msg, out);\n+\t/* If we want the whole block untouched, we can take the fast path. */\n+\tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n+\t    !opts->separator && !opts->key_only && !opts->value_only &&\n+\t    !opts->key_value_separator) {\n+\t\tstrbuf_add(out, msg + info.trailer_block_start,\n+\t\t\t   info.trailer_block_end - info.trailer_block_start);\n+\t} else\n+\t\tformat_trailer_info(opts, &info, out);\n+\n \ttrailer_info_release(&info);\n }\n \n-- \ngitgitgadget\n\n"},{"id":"488001","messageId":"6906910417aefafd296997548b77038222883753.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 09/28] format_trailers_from_commit(): indirectly call trailer_info_get()","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:09Z","receivedAt":"2024-02-06T05:12:41Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis is another preparatory refactor to unify the trailer formatters.\n\nInstead of calling trailer_info_get() directly, call parse_trailers()\nwhich already calls trailer_info_get(). This change is a NOP because\nformat_trailer_info() only looks at the \"trailers\" string array, not the\ntrailer_item objects which parse_trailers() populates.\n\nIn the next patch, we'll change format_trailer_info() to use the parsed\ntrailer_item objects instead of the string array.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex e92d0154d90..e6665c99cc3 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1140,9 +1140,11 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \t\t\t\t const char *msg,\n \t\t\t\t struct strbuf *out)\n {\n+\tLIST_HEAD(trailers);\n \tstruct trailer_info info;\n \n-\ttrailer_info_get(opts, msg, &info);\n+\tparse_trailers(opts, &info, msg, &trailers);\n+\n \t/* If we want the whole block untouched, we can take the fast path. */\n \tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n \t    !opts->separator && !opts->key_only && !opts->value_only &&\n@@ -1152,6 +1154,7 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \t} else\n \t\tformat_trailer_info(opts, &info, out);\n \n+\tfree_trailers(&trailers);\n \ttrailer_info_release(&info);\n }\n \n-- \ngitgitgadget\n\n"},{"id":"488002","messageId":"f5b7ba08aa7c80a3bd5bcbf5563eac8896fe7054.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 10/28] format_trailer_info(): use trailer_item objects","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:10Z","receivedAt":"2024-02-06T05:12:41Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis is another preparatory refactor to unify the trailer formatters.\n\nMake format_trailer_info() operate on trailer_item objects, not the raw\nstring array.\n\nThis breaks t4205 and t6300. We will continue to make improvements until\nthe test suite passes again, ultimately renaming format_trailer_info()\nto format_trailers(), at which point the unification of these formatters\nwill be complete.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 21 ++++++++++-----------\n 1 file changed, 10 insertions(+), 11 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex e6665c99cc3..6333dfe1c11 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1086,21 +1086,21 @@ void trailer_info_release(struct trailer_info *info)\n }\n \n static void format_trailer_info(const struct process_trailer_options *opts,\n-\t\t\t\tconst struct trailer_info *info,\n+\t\t\t\tstruct list_head *trailers,\n \t\t\t\tstruct strbuf *out)\n {\n \tsize_t origlen = out->len;\n-\tsize_t i;\n-\n-\tfor (i = 0; i < info->trailer_nr; i++) {\n-\t\tchar *trailer = info->trailers[i];\n-\t\tssize_t separator_pos = find_separator(trailer, separators);\n+\tstruct list_head *pos;\n+\tstruct trailer_item *item;\n \n-\t\tif (separator_pos >= 1) {\n+\tlist_for_each(pos, trailers) {\n+\t\titem = list_entry(pos, struct trailer_item, list);\n+\t\tif (item->token) {\n \t\t\tstruct strbuf tok = STRBUF_INIT;\n \t\t\tstruct strbuf val = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&tok, item->token);\n+\t\t\tstrbuf_addstr(&val, item->value);\n \n-\t\t\tparse_trailer(&tok, &val, NULL, trailer, separator_pos);\n \t\t\tif (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n \t\t\t\tif (opts->unfold)\n \t\t\t\t\tunfold_value(&val);\n@@ -1127,13 +1127,12 @@ static void format_trailer_info(const struct process_trailer_options *opts,\n \t\t\tif (opts->separator && out->len != origlen) {\n \t\t\t\tstrbuf_addbuf(out, opts->separator);\n \t\t\t}\n-\t\t\tstrbuf_addstr(out, trailer);\n+\t\t\tstrbuf_addstr(out, item->value);\n \t\t\tif (opts->separator) {\n \t\t\t\tstrbuf_rtrim(out);\n \t\t\t}\n \t\t}\n \t}\n-\n }\n \n void format_trailers_from_commit(const struct process_trailer_options *opts,\n@@ -1152,7 +1151,7 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \t\tstrbuf_add(out, msg + info.trailer_block_start,\n \t\t\t   info.trailer_block_end - info.trailer_block_start);\n \t} else\n-\t\tformat_trailer_info(opts, &info, out);\n+\t\tformat_trailer_info(opts, &trailers, out);\n \n \tfree_trailers(&trailers);\n \ttrailer_info_release(&info);\n-- \ngitgitgadget\n\n"},{"id":"488003","messageId":"457f2a839d5da9da225e842275bbf8b15f194f1f.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 11/28] format_trailer_info(): drop redundant unfold_value()","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:11Z","receivedAt":"2024-02-06T05:12:42Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis is another preparatory refactor to unify the trailer formatters.\n\nIn the last patch we made format_trailer_info() use trailer_item objects\ninstead of the \"trailers\" string array. This means that the call to\nunfold_value() here is redundant because the trailer_item objects are\nalready unfolded in parse_trailers() which is a dependency of our\ncaller, format_trailers_from_commit().\n\nRemove the redundant call.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 3 ---\n 1 file changed, 3 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 6333dfe1c11..12cae5b73d2 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1102,9 +1102,6 @@ static void format_trailer_info(const struct process_trailer_options *opts,\n \t\t\tstrbuf_addstr(&val, item->value);\n \n \t\t\tif (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n-\t\t\t\tif (opts->unfold)\n-\t\t\t\t\tunfold_value(&val);\n-\n \t\t\t\tif (opts->separator && out->len != origlen)\n \t\t\t\t\tstrbuf_addbuf(out, opts->separator);\n \t\t\t\tif (!opts->value_only)\n-- \ngitgitgadget\n\n"},{"id":"488004","messageId":"a72eca301f7f9016ef3a8063f79790ce00f41ffe.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 12/28] format_trailer_info(): append newline for non-trailer lines","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:12Z","receivedAt":"2024-02-06T05:12:43Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis wraps up the preparatory refactors to unify the trailer formatters.\n\nTwo patches ago we made format_trailer_info() use trailer_item objects\ninstead of the \"trailers\" string array. The strings in the array\ninclude trailing newlines, because the string array is split up with\n\n    trailer_lines = strbuf_split_buf(str + trailer_block_start,\n                                     end_of_log_message - trailer_block_start,\n                                     '\\n',\n                                     0);\n\nin trailer_info_get() and strbuf_split_buf() includes the terminator (in\nthis case the newline character '\\n') for each split-up substring.\n\nAnd before we made the transition to use trailer_item objects for it,\nformat_trailer_info() called parse_trailer() (which trims newlines) for\ntrailer lines but did _not_ call parse_trailer() for non-trailer lines.\nSo for trailer lines it had to add back the trimmed newline like this\n\n    if (!opts->separator)\n        strbuf_addch(out, '\\n');\n\nBut for non-trailer lines it didn't have to add back the newline because\nit could just reuse same string in the \"trailers\" string array (which\nagain, already included the trailing newline).\n\nNow that format_trailer_info() uses trailer_item objects for all cases,\nit can't rely on \"trailers\" string array anymore.  And so it must be\ntaught to add a newline back when printing non-trailer lines, just like\nit already does for trailer lines. Do so now.\n\nThe test suite passes again, so format_trailer_info() is in better shape\nsupersede format_trailers(), which we'll do in the next patch.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 12cae5b73d2..0774a544c4f 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1125,9 +1125,10 @@ static void format_trailer_info(const struct process_trailer_options *opts,\n \t\t\t\tstrbuf_addbuf(out, opts->separator);\n \t\t\t}\n \t\t\tstrbuf_addstr(out, item->value);\n-\t\t\tif (opts->separator) {\n+\t\t\tif (opts->separator)\n \t\t\t\tstrbuf_rtrim(out);\n-\t\t\t}\n+\t\t\telse\n+\t\t\t\tstrbuf_addch(out, '\\n');\n \t\t}\n \t}\n }\n-- \ngitgitgadget\n\n"},{"id":"488005","messageId":"ad77c33e4574d0d14c0c1ebb95790084c856e0ad.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 13/28] trailer: begin formatting unification","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:13Z","receivedAt":"2024-02-06T05:12:44Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nNow that the preparatory refactors are over, we can replace the call to\nformat_trailers() in interpret-trailers with format_trailer_info(). This\nunifies the trailer formatting machinery, but at the cost of breaking\ntests.\n\nMore specifically, this patch breaks t7502 and t7513, but only because\nwe haven't fully stolen the features present in format_trailers() (which\nknows about opts->trim_empty) and print_tok_val() (which has\nnon-hardcoded printing of the separator and space).\n\nWe will teach format_trailer_info() these features in the next two\npatches to make all tests pass again.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  2 +-\n trailer.c                    | 12 ++++++------\n trailer.h                    |  2 +-\n 3 files changed, 8 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 11f4ce9e4a2..f57af0db37b 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -171,7 +171,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \t}\n \n \t/* Print trailer block. */\n-\tformat_trailers(opts, &head, &trailer_block);\n+\tformat_trailer_info(opts, &head, &trailer_block);\n \tfree_trailers(&head);\n \tfwrite(trailer_block.buf, 1, trailer_block.len, outfile);\n \tstrbuf_release(&trailer_block);\ndiff --git a/trailer.c b/trailer.c\nindex 0774a544c4f..f4defad3dae 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -162,9 +162,9 @@ static void print_tok_val(struct strbuf *out, const char *tok, const char *val)\n \t\tstrbuf_addf(out, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n-void format_trailers(const struct process_trailer_options *opts,\n-\t\t     struct list_head *trailers,\n-\t\t     struct strbuf *out)\n+static void format_trailers(const struct process_trailer_options *opts,\n+\t\t\t    struct list_head *trailers,\n+\t\t\t    struct strbuf *out)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *item;\n@@ -1085,9 +1085,9 @@ void trailer_info_release(struct trailer_info *info)\n \tfree(info->trailers);\n }\n \n-static void format_trailer_info(const struct process_trailer_options *opts,\n-\t\t\t\tstruct list_head *trailers,\n-\t\t\t\tstruct strbuf *out)\n+void format_trailer_info(const struct process_trailer_options *opts,\n+\t\t\t struct list_head *trailers,\n+\t\t\t struct strbuf *out)\n {\n \tsize_t origlen = out->len;\n \tstruct list_head *pos;\ndiff --git a/trailer.h b/trailer.h\nindex 1d106b6dd40..3c13006a4c1 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -101,7 +101,7 @@ void trailer_info_get(const struct process_trailer_options *,\n void trailer_info_release(struct trailer_info *info);\n \n void trailer_config_init(void);\n-void format_trailers(const struct process_trailer_options *,\n+void format_trailer_info(const struct process_trailer_options *,\n \t\t     struct list_head *trailers,\n \t\t     struct strbuf *out);\n void free_trailers(struct list_head *);\n-- \ngitgitgadget\n\n"},{"id":"488006","messageId":"11f854399db2b0da5d82cad910c3b86ca9c2e0db.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 14/28] format_trailer_info(): teach it about opts->trim_empty","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:14Z","receivedAt":"2024-02-06T05:12:45Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis fixes 4 tests in t7513 to go from\n\n    t7513-interpret-trailers.sh  (Wstat: 256 (exited 1) Tests: 94 Failed: 55)\n      Failed tests:  2-5, 8, 14, 24-28, 31-37, 43-62, 66-74\n                    77-80, 82-85\n\nto\n\n    t7513-interpret-trailers.sh  (Wstat: 256 (exited 1) Tests: 94 Failed: 51)\n      Failed tests:  2-5, 14, 24-28, 31-32, 36-37, 43-62, 66-74\n                    77-80, 82-85\n\n. The next patch will fix the remaining broken test cases in t7513 and\nt7502.\n\nEven though the next patch fixes the vast majority of these test cases,\nwe have to position that patch after this one to avoid breaking the\nbuild because of the way these patches delete relevant (and obsolete)\ncode.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 23 +++++++++--------------\n 1 file changed, 9 insertions(+), 14 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex f4defad3dae..c28b6c11cc5 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -162,20 +162,6 @@ static void print_tok_val(struct strbuf *out, const char *tok, const char *val)\n \t\tstrbuf_addf(out, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n-static void format_trailers(const struct process_trailer_options *opts,\n-\t\t\t    struct list_head *trailers,\n-\t\t\t    struct strbuf *out)\n-{\n-\tstruct list_head *pos;\n-\tstruct trailer_item *item;\n-\tlist_for_each(pos, trailers) {\n-\t\titem = list_entry(pos, struct trailer_item, list);\n-\t\tif ((!opts->trim_empty || strlen(item->value) > 0) &&\n-\t\t    (!opts->only_trailers || item->token))\n-\t\t\tprint_tok_val(out, item->token, item->value);\n-\t}\n-}\n-\n static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)\n {\n \tstruct trailer_item *new_item = xcalloc(1, sizeof(*new_item));\n@@ -1101,6 +1087,15 @@ void format_trailer_info(const struct process_trailer_options *opts,\n \t\t\tstrbuf_addstr(&tok, item->token);\n \t\t\tstrbuf_addstr(&val, item->value);\n \n+\t\t\t/*\n+\t\t\t * Skip key/value pairs where the value was empty. This\n+\t\t\t * can happen from trailers specified without a\n+\t\t\t * separator, like `--trailer \"Reviewed-by\"` (no\n+\t\t\t * corresponding value).\n+\t\t\t */\n+\t\t\tif (opts->trim_empty && !strlen(item->value))\n+\t\t\t\tcontinue;\n+\n \t\t\tif (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n \t\t\t\tif (opts->separator && out->len != origlen)\n \t\t\t\t\tstrbuf_addbuf(out, opts->separator);\n-- \ngitgitgadget\n\n"},{"id":"488007","messageId":"ba1f387747b08a7270f7387beddd75dc4a8eddfe.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 15/28] format_trailer_info(): avoid double-printing the separator","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:15Z","receivedAt":"2024-02-06T05:12:46Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nDo not hardcode the printing of \": \" as the separator and space (which\ncan result in double-printing these characters); instead only\nprint the separator and space if we cannot find any recognized separator\nsomewhere in the key (yes, keys may have a trailing separator in it ---\nwe will eventually fix this design but not now). Do so by copying the\ncode out of print_tok_val(), and deleting the same function.\n\nThe test suite passes again with this change.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 25 +++++--------------------\n 1 file changed, 5 insertions(+), 20 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex c28b6c11cc5..5c42a19943a 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -144,24 +144,6 @@ static char last_non_space_char(const char *s)\n \treturn '\\0';\n }\n \n-static void print_tok_val(struct strbuf *out, const char *tok, const char *val)\n-{\n-\tchar c;\n-\n-\tif (!tok) {\n-\t\tstrbuf_addf(out, \"%s\\n\", val);\n-\t\treturn;\n-\t}\n-\n-\tc = last_non_space_char(tok);\n-\tif (!c)\n-\t\treturn;\n-\tif (strchr(separators, c))\n-\t\tstrbuf_addf(out, \"%s%s\\n\", tok, val);\n-\telse\n-\t\tstrbuf_addf(out, \"%s%c %s\\n\", tok, separators[0], val);\n-}\n-\n static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)\n {\n \tstruct trailer_item *new_item = xcalloc(1, sizeof(*new_item));\n@@ -1104,8 +1086,11 @@ void format_trailer_info(const struct process_trailer_options *opts,\n \t\t\t\tif (!opts->key_only && !opts->value_only) {\n \t\t\t\t\tif (opts->key_value_separator)\n \t\t\t\t\t\tstrbuf_addbuf(out, opts->key_value_separator);\n-\t\t\t\t\telse\n-\t\t\t\t\t\tstrbuf_addstr(out, \": \");\n+\t\t\t\t\telse {\n+\t\t\t\t\t\tchar c = last_non_space_char(tok.buf);\n+\t\t\t\t\t\tif (c && !strchr(separators, c))\n+\t\t\t\t\t\t\tstrbuf_addf(out, \"%c \", separators[0]);\n+\t\t\t\t\t}\n \t\t\t\t}\n \t\t\t\tif (!opts->key_only)\n \t\t\t\t\tstrbuf_addbuf(out, &val);\n-- \ngitgitgadget\n\n"},{"id":"488008","messageId":"31725832224e3d6b14066af8a87eaf4ab589179e.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 16/28] trailer: finish formatting unification","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:16Z","receivedAt":"2024-02-06T05:12:47Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nRename format_trailer_info() to format_trailers(). Finally, both\ninterpret-trailers and format_trailers_from_commit() can call\n\"format_trailers()\"!\n\nUpdate the comment in <trailer.h> to remove the (now obsolete) caveats\nabout format_trailers_from_commit().\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  2 +-\n trailer.c                    |  8 ++++----\n trailer.h                    | 15 ++++-----------\n 3 files changed, 9 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex f57af0db37b..11f4ce9e4a2 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -171,7 +171,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \t}\n \n \t/* Print trailer block. */\n-\tformat_trailer_info(opts, &head, &trailer_block);\n+\tformat_trailers(opts, &head, &trailer_block);\n \tfree_trailers(&head);\n \tfwrite(trailer_block.buf, 1, trailer_block.len, outfile);\n \tstrbuf_release(&trailer_block);\ndiff --git a/trailer.c b/trailer.c\nindex 5c42a19943a..4f3318802d1 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1053,9 +1053,9 @@ void trailer_info_release(struct trailer_info *info)\n \tfree(info->trailers);\n }\n \n-void format_trailer_info(const struct process_trailer_options *opts,\n-\t\t\t struct list_head *trailers,\n-\t\t\t struct strbuf *out)\n+void format_trailers(const struct process_trailer_options *opts,\n+\t\t     struct list_head *trailers,\n+\t\t     struct strbuf *out)\n {\n \tsize_t origlen = out->len;\n \tstruct list_head *pos;\n@@ -1129,7 +1129,7 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \t\tstrbuf_add(out, msg + info.trailer_block_start,\n \t\t\t   info.trailer_block_end - info.trailer_block_start);\n \t} else\n-\t\tformat_trailer_info(opts, &trailers, out);\n+\t\tformat_trailers(opts, &trailers, out);\n \n \tfree_trailers(&trailers);\n \ttrailer_info_release(&info);\ndiff --git a/trailer.h b/trailer.h\nindex 3c13006a4c1..9f42aa75994 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -101,23 +101,16 @@ void trailer_info_get(const struct process_trailer_options *,\n void trailer_info_release(struct trailer_info *info);\n \n void trailer_config_init(void);\n-void format_trailer_info(const struct process_trailer_options *,\n+void format_trailers(const struct process_trailer_options *,\n \t\t     struct list_head *trailers,\n \t\t     struct strbuf *out);\n void free_trailers(struct list_head *);\n \n /*\n- * Format the trailers from the commit msg \"msg\" into the strbuf \"out\".\n- * Note two caveats about \"opts\":\n- *\n- *   - this is primarily a helper for pretty.c, and not\n- *     all of the flags are supported.\n- *\n- *   - this differs from process_trailers slightly in that we always format\n- *     only the trailer block itself, even if the \"only_trailers\" option is not\n- *     set.\n+ * Convenience function to format the trailers from the commit msg \"msg\" into\n+ * the strbuf \"out\". Reuses format_trailers() internally.\n  */\n-void format_trailers_from_commit(const struct process_trailer_options *opts,\n+void format_trailers_from_commit(const struct process_trailer_options *,\n \t\t\t\t const char *msg,\n \t\t\t\t struct strbuf *out);\n \n-- \ngitgitgadget\n\n"},{"id":"488009","messageId":"6f17c022b15479191a1a965d762b6b8a956532e4.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 17/28] trailer: teach iterator about non-trailer lines","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:17Z","receivedAt":"2024-02-06T05:12:48Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nPreviously the iterator did not iterate over non-trailer lines. This was\nsomewhat unfortunate, because trailer blocks could have non-trailer\nlines in them since 146245063e (trailer: allow non-trailers in trailer\nblock, 2016-10-21), which was before the iterator was created in\nf0939a0eb1 (trailer: add interface for iterating over commit trailers,\n2020-09-27).\n\nSo if trailer API users wanted to iterate over all lines in a trailer\nblock (including non-trailer lines), they could not use the iterator and\nwere forced to use the lower-level trailer_info struct directly (which\nprovides a raw string array that includes all lines in the trailer\nblock).\n\nChange the iterator's behavior so that we also iterate over non-trailer\nlines, instead of skipping over them. The new \"raw\" member of the\niterator allows API users to access previously inaccessible non-trailer\nlines. Reword the variable \"trailer\" to just \"line\" because this\nvariable can now hold both trailer lines _and_ non-trailer lines.\n\nThe new \"raw\" member is important because anyone currently not using the\niterator is using trailer_info's raw string array directly to access\nlines to check what the combined key + value looks like. If we didn't\nprovide a \"raw\" member here, iterator users would have to re-construct\nthe unparsed line by concatenating the key and value back together again\n--- which places an undue burden for iterator users.\n\nThe next patch demonstrates the use of the iterator in sequencer.c as an\nexample of where \"raw\" will be useful, so that it can start using the\niterator.\n\nFor the existing use of the iterator in builtin/shortlog.c, we don't\nhave to change the code there because that code does\n\n    trailer_iterator_init(&iter, body);\n    while (trailer_iterator_advance(&iter)) {\n        const char *value = iter.val.buf;\n\n        if (!string_list_has_string(&log->trailers, iter.key.buf))\n            continue;\n\n        ...\n\nand the\n\n        if (!string_list_has_string(&log->trailers, iter.key.buf))\n\ncondition already skips over non-trailer lines (iter.key.buf is empty\nfor non-trailer lines, making the comparison still work unmodified even\nwith this patch).\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 12 +++++-------\n trailer.h |  8 ++++++++\n 2 files changed, 13 insertions(+), 7 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 4f3318802d1..2cc4a910411 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1147,17 +1147,15 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n \n int trailer_iterator_advance(struct trailer_iterator *iter)\n {\n-\twhile (iter->internal.cur < iter->internal.info.trailer_nr) {\n-\t\tchar *trailer = iter->internal.info.trailers[iter->internal.cur++];\n-\t\tint separator_pos = find_separator(trailer, separators);\n-\n-\t\tif (separator_pos < 1)\n-\t\t\tcontinue; /* not a real trailer */\n+\tif (iter->internal.cur < iter->internal.info.trailer_nr) {\n+\t\tchar *line = iter->internal.info.trailers[iter->internal.cur++];\n+\t\tint separator_pos = find_separator(line, separators);\n \n+\t\titer->raw = line;\n \t\tstrbuf_reset(&iter->key);\n \t\tstrbuf_reset(&iter->val);\n \t\tparse_trailer(&iter->key, &iter->val, NULL,\n-\t\t\t      trailer, separator_pos);\n+\t\t\t      line, separator_pos);\n \t\t/* Always unfold values during iteration. */\n \t\tunfold_value(&iter->val);\n \t\treturn 1;\ndiff --git a/trailer.h b/trailer.h\nindex 9f42aa75994..ebafa3657e4 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -125,6 +125,14 @@ void format_trailers_from_commit(const struct process_trailer_options *,\n  *   trailer_iterator_release(&iter);\n  */\n struct trailer_iterator {\n+\t/*\n+\t * Raw line (e.g., \"foo: bar baz\") before being parsed as a trailer\n+\t * key/val pair as part of a trailer block. A trailer block can be\n+\t * either 100% trailer lines, or mixed in with non-trailer lines (in\n+\t * which case at least 25% must be trailer lines).\n+\t */\n+\tconst char *raw;\n+\n \tstruct strbuf key;\n \tstruct strbuf val;\n \n-- \ngitgitgadget\n\n"},{"id":"488010","messageId":"cc92dfb0bdadb8c3f1289e8bb9dd60994f3135f2.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 18/28] sequencer: use the trailer iterator","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:18Z","receivedAt":"2024-02-06T05:12:49Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis patch allows for the removal of \"trailer_info_get()\" from the\ntrailer.h API, which will be in the next patch.\n\nInstead of calling \"trailer_info_get()\", which is a low-level function\nin the trailers implementation (trailer.c), call\ntrailer_iterator_advance(), which was specifically designed for public\nconsumption in f0939a0eb1 (trailer: add interface for iterating over\ncommit trailers, 2020-09-27).\n\nAvoiding \"trailer_info_get()\" means we don't have to worry about options\nlike \"no_divider\" (relevant for parsing trailers). We also don't have to\ncheck for things like \"info.trailer_start == info.trailer_end\" to see\nwhether there were any trailers (instead we can just check to see\nwhether the iterator advanced at all).\n\nNote how we have to use \"iter.raw\" in order to get the same behavior as\nbefore when we iterated over the unparsed string array (trailers[]) in\ntrailer_info.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n sequencer.c | 27 ++++++++++++---------------\n 1 file changed, 12 insertions(+), 15 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 8e199fc8a47..35462a6a9d9 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -319,35 +319,32 @@ static const char *get_todo_path(const struct replay_opts *opts)\n static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,\n \tsize_t ignore_footer)\n {\n-\tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n-\tstruct trailer_info info;\n-\tsize_t i;\n+\tstruct trailer_iterator iter;\n+\tsize_t i = 0;\n \tint found_sob = 0, found_sob_last = 0;\n \tchar saved_char;\n \n-\topts.no_divider = 1;\n-\n \tif (ignore_footer) {\n \t\tsaved_char = sb->buf[sb->len - ignore_footer];\n \t\tsb->buf[sb->len - ignore_footer] = '\\0';\n \t}\n \n-\ttrailer_info_get(&opts, sb->buf, &info);\n+\ttrailer_iterator_init(&iter, sb->buf);\n \n \tif (ignore_footer)\n \t\tsb->buf[sb->len - ignore_footer] = saved_char;\n \n-\tif (info.trailer_block_start == info.trailer_block_end)\n-\t\treturn 0;\n+\twhile (trailer_iterator_advance(&iter)) {\n+\t\ti++;\n+\t\tif (sob && !strncmp(iter.raw, sob->buf, sob->len))\n+\t\t\tfound_sob = i;\n+\t}\n+\ttrailer_iterator_release(&iter);\n \n-\tfor (i = 0; i < info.trailer_nr; i++)\n-\t\tif (sob && !strncmp(info.trailers[i], sob->buf, sob->len)) {\n-\t\t\tfound_sob = 1;\n-\t\t\tif (i == info.trailer_nr - 1)\n-\t\t\t\tfound_sob_last = 1;\n-\t\t}\n+\tif (!i)\n+\t\treturn 0;\n \n-\ttrailer_info_release(&info);\n+\tfound_sob_last = (int)i == found_sob;\n \n \tif (found_sob_last)\n \t\treturn 3;\n-- \ngitgitgadget\n\n"},{"id":"488011","messageId":"f5f0d06613f2701af9bd3a1a9274aae232e03d8f.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 19/28] trailer: make trailer_info struct private","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:19Z","receivedAt":"2024-02-06T05:12:49Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nIn 13211ae23f (trailer: separate public from internal portion of\ntrailer_iterator, 2023-09-09) we moved trailer_info behind an anonymous\nstruct to discourage use by trailer.h API users. However it still left\nopen the possibility of external use of trailer_info itself. Now that\nthere are no external users of trailer_info, we can make this struct\nprivate.\n\nMake this struct private by putting its definition inside trailer.c.\nThis has two benefits:\n\n  (1) it makes the surface area of the public facing\n      interface (trailer.h) smaller, and\n\n  (2) external API users are unable to peer inside this struct (because\n      it is only ever exposed as an opaque pointer).\n\nThere are a couple disadvantages:\n\n  (A) every time the member of the struct is accessed an extra pointer\n      dereference must be done, and\n\n  (B) for users of trailer_info outside trailer.c, this struct can no\n      longer be allocated on the stack and may only be allocated on the\n      heap (because its definition is hidden away in trailer.c) and\n      appropriately deallocated by the user.\n\nThis patch believes that the benefits outweight the advantages for\ndesigning APIs, as explained below.\n\nMaking trailer_info private exposes existing deficiencies in the API.\nThis is because users of this struct had full access to its internals,\nso there wasn't much need to actually design it to be \"complete\" in the\nsense that API users only needed to use what was provided by the API.\nFor example, the location of the trailer block (start/end offsets\nrelative to the start of the input text) was accessible by looking at\nthese struct members directly. Now that the struct is private, we have\nto expose new API functions to allow clients to access this\ninformation (see builtin/interpret-trailers.c).\n\nThe idea in this patch to hide implementation details behind an \"opaque\npointer\" is also known as the \"pimpl\" (pointer to implementation) idiom\nin C++ and is a common pattern in that language (where, for example,\nabstract classes only have pointers to concrete classes).\n\nHowever, the original inspiration to use this idiom does not come from\nC++, but instead the book \"C Interfaces and Implementations: Techniques\nfor Creating Reusable Software\" [1]. This book recommends opaque\npointers as a good design principle for designing C libraries, using the\nterm \"interface\" as the functions defined in *.h (header) files and\n\"implementation\" as the corresponding *.c file which define the\ninterfaces.\n\nThe book says this about opaque pointers:\n\n    ... clients can manipulate such pointers freely, but they can’t\n    dereference them; that is, they can’t look at the innards of the\n    structure pointed to by them. Only the implementation has that\n    privilege. Opaque pointers hide representation details and help\n    catch errors.\n\nIn our case, \"struct trailer_info\" is now hidden from clients, and the\nways in which this opaque pointer can be used is limited to the richness\nof <trailer.h>. In other words, <trailer.h> exclusively controls exactly\nhow \"trailer_info\" pointers are to be used.\n\n[1] Hanson, David R. \"C Interfaces and Implementations: Techniques for\n    Creating Reusable Software\". Addison Wesley, 1997. p. 22\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c | 12 +++---\n trailer.c                    | 81 ++++++++++++++++++++++++++++--------\n trailer.h                    | 40 +++++-------------\n 3 files changed, 80 insertions(+), 53 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 11f4ce9e4a2..6bf8cec005a 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -141,7 +141,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \tLIST_HEAD(head);\n \tstruct strbuf sb = STRBUF_INIT;\n \tstruct strbuf trailer_block = STRBUF_INIT;\n-\tstruct trailer_info info;\n+\tstruct trailer_info *info;\n \tFILE *outfile = stdout;\n \n \ttrailer_config_init();\n@@ -151,13 +151,13 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \tif (opts->in_place)\n \t\toutfile = create_in_place_tempfile(file);\n \n-\tparse_trailers(opts, &info, sb.buf, &head);\n+\tinfo = parse_trailers(opts, sb.buf, &head);\n \n \t/* Print the lines before the trailers */\n \tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf, 1, info.trailer_block_start, outfile);\n+\t\tfwrite(sb.buf, 1, trailer_block_start(info), outfile);\n \n-\tif (!opts->only_trailers && !info.blank_line_before_trailer)\n+\tif (!opts->only_trailers && !blank_line_before_trailer_block(info))\n \t\tfprintf(outfile, \"\\n\");\n \n \n@@ -178,8 +178,8 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \n \t/* Print the lines after the trailers as is */\n \tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n-\ttrailer_info_release(&info);\n+\t\tfwrite(sb.buf + trailer_block_end(info), 1, sb.len - trailer_block_end(info), outfile);\n+\ttrailer_info_release(info);\n \n \tif (opts->in_place)\n \t\tif (rename_tempfile(&trailers_tempfile, file))\ndiff --git a/trailer.c b/trailer.c\nindex 2cc4a910411..cc211dfeeae 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -11,6 +11,27 @@\n  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n  */\n \n+struct trailer_info {\n+\t/*\n+\t * True if there is a blank line before the location pointed to by\n+\t * trailer_block_start.\n+\t */\n+\tint blank_line_before_trailer;\n+\n+\t/*\n+\t * Offsets to the trailer block start and end positions in the input\n+\t * string. If no trailer block is found, these are both set to the\n+\t * \"true\" end of the input (find_end_of_log_message()).\n+\t */\n+\tsize_t trailer_block_start, trailer_block_end;\n+\n+\t/*\n+\t * Array of trailers found.\n+\t */\n+\tchar **trailers;\n+\tsize_t trailer_nr;\n+};\n+\n struct conf_info {\n \tchar *name;\n \tchar *key;\n@@ -953,20 +974,26 @@ static void unfold_value(struct strbuf *val)\n \tstrbuf_release(&out);\n }\n \n+static struct trailer_info *trailer_info_new(void)\n+{\n+\tstruct trailer_info *info = xcalloc(1, sizeof(*info));\n+\treturn info;\n+}\n+\n /*\n  * Parse trailers in \"str\", populating the trailer info and \"head\"\n  * linked list structure.\n  */\n-void parse_trailers(const struct process_trailer_options *opts,\n-\t\t    struct trailer_info *info,\n-\t\t    const char *str,\n-\t\t    struct list_head *head)\n+struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n+\t\t\t\t    const char *str,\n+\t\t\t\t    struct list_head *head)\n {\n+\tstruct trailer_info *info;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n \tsize_t i;\n \n-\ttrailer_info_get(opts, str, info);\n+\tinfo = trailer_info_get(opts, str);\n \n \tfor (i = 0; i < info->trailer_nr; i++) {\n \t\tint separator_pos;\n@@ -990,6 +1017,8 @@ void parse_trailers(const struct process_trailer_options *opts,\n \t\t\t\t\t strbuf_detach(&val, NULL));\n \t\t}\n \t}\n+\n+\treturn info;\n }\n \n void free_trailers(struct list_head *trailers)\n@@ -1001,10 +1030,25 @@ void free_trailers(struct list_head *trailers)\n \t}\n }\n \n-void trailer_info_get(const struct process_trailer_options *opts,\n-\t\t      const char *str,\n-\t\t      struct trailer_info *info)\n+size_t trailer_block_start(struct trailer_info *info)\n+{\n+\treturn info->trailer_block_start;\n+}\n+\n+size_t trailer_block_end(struct trailer_info *info)\n+{\n+\treturn info->trailer_block_end;\n+}\n+\n+int blank_line_before_trailer_block(struct trailer_info *info)\n+{\n+\treturn info->blank_line_before_trailer;\n+}\n+\n+struct trailer_info *trailer_info_get(const struct process_trailer_options *opts,\n+\t\t\t\t      const char *str)\n {\n+\tstruct trailer_info *info = trailer_info_new();\n \tsize_t end_of_log_message = 0, trailer_block_start = 0;\n \tstruct strbuf **trailer_lines, **ptr;\n \tchar **trailer_strings = NULL;\n@@ -1043,6 +1087,8 @@ void trailer_info_get(const struct process_trailer_options *opts,\n \tinfo->trailer_block_end = end_of_log_message;\n \tinfo->trailers = trailer_strings;\n \tinfo->trailer_nr = nr;\n+\n+\treturn info;\n }\n \n void trailer_info_release(struct trailer_info *info)\n@@ -1051,6 +1097,7 @@ void trailer_info_release(struct trailer_info *info)\n \tfor (i = 0; i < info->trailer_nr; i++)\n \t\tfree(info->trailers[i]);\n \tfree(info->trailers);\n+\tfree(info);\n }\n \n void format_trailers(const struct process_trailer_options *opts,\n@@ -1118,21 +1165,19 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \t\t\t\t struct strbuf *out)\n {\n \tLIST_HEAD(trailers);\n-\tstruct trailer_info info;\n-\n-\tparse_trailers(opts, &info, msg, &trailers);\n+\tstruct trailer_info *info = parse_trailers(opts, msg, &trailers);\n \n \t/* If we want the whole block untouched, we can take the fast path. */\n \tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n \t    !opts->separator && !opts->key_only && !opts->value_only &&\n \t    !opts->key_value_separator) {\n-\t\tstrbuf_add(out, msg + info.trailer_block_start,\n-\t\t\t   info.trailer_block_end - info.trailer_block_start);\n+\t\tstrbuf_add(out, msg + info->trailer_block_start,\n+\t\t\t   info->trailer_block_end - info->trailer_block_start);\n \t} else\n \t\tformat_trailers(opts, &trailers, out);\n \n \tfree_trailers(&trailers);\n-\ttrailer_info_release(&info);\n+\ttrailer_info_release(info);\n }\n \n void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n@@ -1141,14 +1186,14 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n \tstrbuf_init(&iter->key, 0);\n \tstrbuf_init(&iter->val, 0);\n \topts.no_divider = 1;\n-\ttrailer_info_get(&opts, msg, &iter->internal.info);\n+\titer->internal.info = trailer_info_get(&opts, msg);\n \titer->internal.cur = 0;\n }\n \n int trailer_iterator_advance(struct trailer_iterator *iter)\n {\n-\tif (iter->internal.cur < iter->internal.info.trailer_nr) {\n-\t\tchar *line = iter->internal.info.trailers[iter->internal.cur++];\n+\tif (iter->internal.cur < iter->internal.info->trailer_nr) {\n+\t\tchar *line = iter->internal.info->trailers[iter->internal.cur++];\n \t\tint separator_pos = find_separator(line, separators);\n \n \t\titer->raw = line;\n@@ -1165,7 +1210,7 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n \n void trailer_iterator_release(struct trailer_iterator *iter)\n {\n-\ttrailer_info_release(&iter->internal.info);\n+\ttrailer_info_release(iter->internal.info);\n \tstrbuf_release(&iter->val);\n \tstrbuf_release(&iter->key);\n }\ndiff --git a/trailer.h b/trailer.h\nindex ebafa3657e4..a63e97a2663 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -4,6 +4,8 @@\n #include \"list.h\"\n #include \"strbuf.h\"\n \n+struct trailer_info;\n+\n enum trailer_where {\n \tWHERE_DEFAULT,\n \tWHERE_END,\n@@ -29,27 +31,6 @@ int trailer_set_where(enum trailer_where *item, const char *value);\n int trailer_set_if_exists(enum trailer_if_exists *item, const char *value);\n int trailer_set_if_missing(enum trailer_if_missing *item, const char *value);\n \n-struct trailer_info {\n-\t/*\n-\t * True if there is a blank line before the location pointed to by\n-\t * trailer_block_start.\n-\t */\n-\tint blank_line_before_trailer;\n-\n-\t/*\n-\t * Offsets to the trailer block start and end positions in the input\n-\t * string. If no trailer block is found, these are both set to the\n-\t * \"true\" end of the input (find_end_of_log_message()).\n-\t */\n-\tsize_t trailer_block_start, trailer_block_end;\n-\n-\t/*\n-\t * Array of trailers found.\n-\t */\n-\tchar **trailers;\n-\tsize_t trailer_nr;\n-};\n-\n /*\n  * A list that represents newly-added trailers, such as those provided\n  * with the --trailer command line option of git-interpret-trailers.\n@@ -89,14 +70,15 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n void process_trailers_lists(struct list_head *head,\n \t\t\t    struct list_head *arg_head);\n \n-void parse_trailers(const struct process_trailer_options *,\n-\t\t    struct trailer_info *,\n-\t\t    const char *str,\n-\t\t    struct list_head *head);\n+struct trailer_info *parse_trailers(const struct process_trailer_options *,\n+\t\t\t\t    const char *str,\n+\t\t\t\t    struct list_head *head);\n+struct trailer_info *trailer_info_get(const struct process_trailer_options *,\n+\t\t\t\t      const char *str);\n \n-void trailer_info_get(const struct process_trailer_options *,\n-\t\t      const char *str,\n-\t\t      struct trailer_info *);\n+size_t trailer_block_start(struct trailer_info *);\n+size_t trailer_block_end(struct trailer_info *);\n+int blank_line_before_trailer_block(struct trailer_info *);\n \n void trailer_info_release(struct trailer_info *info);\n \n@@ -138,7 +120,7 @@ struct trailer_iterator {\n \n \t/* private */\n \tstruct {\n-\t\tstruct trailer_info info;\n+\t\tstruct trailer_info *info;\n \t\tsize_t cur;\n \t} internal;\n };\n-- \ngitgitgadget\n\n"},{"id":"488012","messageId":"607ae7a90cd9a2e69733c62ef44bb6c0893e0079.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 20/28] trailer: retire trailer_info_get() from API","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:20Z","receivedAt":"2024-02-06T05:12:50Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nMake it \"static\" to be file-scoped to trailer.c, because no one outside\nof trailer.c uses it. Remove its declaration from <trailer.h>.\n\nWe have to also reposition it to be above parse_trailers(), which\ndepends on it.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 92 +++++++++++++++++++++++++++----------------------------\n trailer.h |  2 --\n 2 files changed, 46 insertions(+), 48 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex cc211dfeeae..36e49ab7cf5 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -980,6 +980,52 @@ static struct trailer_info *trailer_info_new(void)\n \treturn info;\n }\n \n+static struct trailer_info *trailer_info_get(const struct process_trailer_options *opts,\n+\t\t\t\t\t     const char *str)\n+{\n+\tstruct trailer_info *info = trailer_info_new();\n+\tsize_t end_of_log_message = 0, trailer_block_start = 0;\n+\tstruct strbuf **trailer_lines, **ptr;\n+\tchar **trailer_strings = NULL;\n+\tsize_t nr = 0, alloc = 0;\n+\tchar **last = NULL;\n+\n+\ttrailer_config_init();\n+\n+\tend_of_log_message = find_end_of_log_message(str, opts->no_divider);\n+\ttrailer_block_start = find_trailer_block_start(str, end_of_log_message);\n+\n+\ttrailer_lines = strbuf_split_buf(str + trailer_block_start,\n+\t\t\t\t\t end_of_log_message - trailer_block_start,\n+\t\t\t\t\t '\\n',\n+\t\t\t\t\t 0);\n+\tfor (ptr = trailer_lines; *ptr; ptr++) {\n+\t\tif (last && isspace((*ptr)->buf[0])) {\n+\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\t\tstrbuf_attach(&sb, *last, strlen(*last), strlen(*last));\n+\t\t\tstrbuf_addbuf(&sb, *ptr);\n+\t\t\t*last = strbuf_detach(&sb, NULL);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tALLOC_GROW(trailer_strings, nr + 1, alloc);\n+\t\ttrailer_strings[nr] = strbuf_detach(*ptr, NULL);\n+\t\tlast = find_separator(trailer_strings[nr], separators) >= 1\n+\t\t\t? &trailer_strings[nr]\n+\t\t\t: NULL;\n+\t\tnr++;\n+\t}\n+\tstrbuf_list_free(trailer_lines);\n+\n+\tinfo->blank_line_before_trailer = ends_with_blank_line(str,\n+\t\t\t\t\t\t\t       trailer_block_start);\n+\tinfo->trailer_block_start = trailer_block_start;\n+\tinfo->trailer_block_end = end_of_log_message;\n+\tinfo->trailers = trailer_strings;\n+\tinfo->trailer_nr = nr;\n+\n+\treturn info;\n+}\n+\n /*\n  * Parse trailers in \"str\", populating the trailer info and \"head\"\n  * linked list structure.\n@@ -1045,52 +1091,6 @@ int blank_line_before_trailer_block(struct trailer_info *info)\n \treturn info->blank_line_before_trailer;\n }\n \n-struct trailer_info *trailer_info_get(const struct process_trailer_options *opts,\n-\t\t\t\t      const char *str)\n-{\n-\tstruct trailer_info *info = trailer_info_new();\n-\tsize_t end_of_log_message = 0, trailer_block_start = 0;\n-\tstruct strbuf **trailer_lines, **ptr;\n-\tchar **trailer_strings = NULL;\n-\tsize_t nr = 0, alloc = 0;\n-\tchar **last = NULL;\n-\n-\ttrailer_config_init();\n-\n-\tend_of_log_message = find_end_of_log_message(str, opts->no_divider);\n-\ttrailer_block_start = find_trailer_block_start(str, end_of_log_message);\n-\n-\ttrailer_lines = strbuf_split_buf(str + trailer_block_start,\n-\t\t\t\t\t end_of_log_message - trailer_block_start,\n-\t\t\t\t\t '\\n',\n-\t\t\t\t\t 0);\n-\tfor (ptr = trailer_lines; *ptr; ptr++) {\n-\t\tif (last && isspace((*ptr)->buf[0])) {\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstrbuf_attach(&sb, *last, strlen(*last), strlen(*last));\n-\t\t\tstrbuf_addbuf(&sb, *ptr);\n-\t\t\t*last = strbuf_detach(&sb, NULL);\n-\t\t\tcontinue;\n-\t\t}\n-\t\tALLOC_GROW(trailer_strings, nr + 1, alloc);\n-\t\ttrailer_strings[nr] = strbuf_detach(*ptr, NULL);\n-\t\tlast = find_separator(trailer_strings[nr], separators) >= 1\n-\t\t\t? &trailer_strings[nr]\n-\t\t\t: NULL;\n-\t\tnr++;\n-\t}\n-\tstrbuf_list_free(trailer_lines);\n-\n-\tinfo->blank_line_before_trailer = ends_with_blank_line(str,\n-\t\t\t\t\t\t\t       trailer_block_start);\n-\tinfo->trailer_block_start = trailer_block_start;\n-\tinfo->trailer_block_end = end_of_log_message;\n-\tinfo->trailers = trailer_strings;\n-\tinfo->trailer_nr = nr;\n-\n-\treturn info;\n-}\n-\n void trailer_info_release(struct trailer_info *info)\n {\n \tsize_t i;\ndiff --git a/trailer.h b/trailer.h\nindex a63e97a2663..1b7422fa2b0 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -73,8 +73,6 @@ void process_trailers_lists(struct list_head *head,\n struct trailer_info *parse_trailers(const struct process_trailer_options *,\n \t\t\t\t    const char *str,\n \t\t\t\t    struct list_head *head);\n-struct trailer_info *trailer_info_get(const struct process_trailer_options *,\n-\t\t\t\t      const char *str);\n \n size_t trailer_block_start(struct trailer_info *);\n size_t trailer_block_end(struct trailer_info *);\n-- \ngitgitgadget\n\n"},{"id":"488013","messageId":"38f4b4c4135dfebc06c2b1d5c56854af4b07fedc.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 21/28] trailer: spread usage of \"trailer_block\" language","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:21Z","receivedAt":"2024-02-06T05:12:51Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nDeprecate the \"trailer_info\" struct name and replace it with\n\"trailer_block\". The main reason is to help readability, because\n\"trailer_info\" on the surface sounds like it's about a single trailer\nwhen in reality it is a collection of contiguous lines, at least 25% of\nwhich are trailers.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c | 25 +++++-----\n trailer.c                    | 97 ++++++++++++++++++------------------\n trailer.h                    | 18 +++----\n 3 files changed, 71 insertions(+), 69 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 6bf8cec005a..f76841c5280 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -140,8 +140,8 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n {\n \tLIST_HEAD(head);\n \tstruct strbuf sb = STRBUF_INIT;\n-\tstruct strbuf trailer_block = STRBUF_INIT;\n-\tstruct trailer_info *info;\n+\tstruct strbuf tb = STRBUF_INIT;\n+\tstruct trailer_block *trailer_block;\n \tFILE *outfile = stdout;\n \n \ttrailer_config_init();\n@@ -151,13 +151,13 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \tif (opts->in_place)\n \t\toutfile = create_in_place_tempfile(file);\n \n-\tinfo = parse_trailers(opts, sb.buf, &head);\n+\ttrailer_block = parse_trailers(opts, sb.buf, &head);\n \n-\t/* Print the lines before the trailers */\n+\t/* Print the lines before the trailer block */\n \tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf, 1, trailer_block_start(info), outfile);\n+\t\tfwrite(sb.buf, 1, trailer_block_start(trailer_block), outfile);\n \n-\tif (!opts->only_trailers && !blank_line_before_trailer_block(info))\n+\tif (!opts->only_trailers && !blank_line_before_trailer_block(trailer_block))\n \t\tfprintf(outfile, \"\\n\");\n \n \n@@ -171,15 +171,16 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \t}\n \n \t/* Print trailer block. */\n-\tformat_trailers(opts, &head, &trailer_block);\n+\tformat_trailers(opts, &head, &tb);\n \tfree_trailers(&head);\n-\tfwrite(trailer_block.buf, 1, trailer_block.len, outfile);\n-\tstrbuf_release(&trailer_block);\n+\tfwrite(tb.buf, 1, tb.len, outfile);\n+\tstrbuf_release(&tb);\n \n-\t/* Print the lines after the trailers as is */\n+\t/* Print the lines after the trailer block as is. */\n \tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf + trailer_block_end(info), 1, sb.len - trailer_block_end(info), outfile);\n-\ttrailer_info_release(info);\n+\t\tfwrite(sb.buf + trailer_block_end(trailer_block), 1,\n+\t\t       sb.len - trailer_block_end(trailer_block), outfile);\n+\ttrailer_block_release(trailer_block);\n \n \tif (opts->in_place)\n \t\tif (rename_tempfile(&trailers_tempfile, file))\ndiff --git a/trailer.c b/trailer.c\nindex 36e49ab7cf5..49bf26e3211 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -11,19 +11,20 @@\n  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n  */\n \n-struct trailer_info {\n+struct trailer_block {\n \t/*\n \t * True if there is a blank line before the location pointed to by\n-\t * trailer_block_start.\n+\t * \"start\".\n \t */\n \tint blank_line_before_trailer;\n \n \t/*\n-\t * Offsets to the trailer block start and end positions in the input\n-\t * string. If no trailer block is found, these are both set to the\n-\t * \"true\" end of the input (find_end_of_log_message()).\n+\t * The locations of the start and end positions of the trailer block\n+\t * found, as offsets from the beginning of the source text from which\n+\t * this trailer block was parsed. If no trailer block is found, these\n+\t * are both set to 0.\n \t */\n-\tsize_t trailer_block_start, trailer_block_end;\n+\tsize_t start, end;\n \n \t/*\n \t * Array of trailers found.\n@@ -974,16 +975,16 @@ static void unfold_value(struct strbuf *val)\n \tstrbuf_release(&out);\n }\n \n-static struct trailer_info *trailer_info_new(void)\n+static struct trailer_block *trailer_block_new(void)\n {\n-\tstruct trailer_info *info = xcalloc(1, sizeof(*info));\n-\treturn info;\n+\tstruct trailer_block *trailer_block = xcalloc(1, sizeof(*trailer_block));\n+\treturn trailer_block;\n }\n \n-static struct trailer_info *trailer_info_get(const struct process_trailer_options *opts,\n-\t\t\t\t\t     const char *str)\n+static struct trailer_block *trailer_block_get(const struct process_trailer_options *opts,\n+\t\t\t\t\t       const char *str)\n {\n-\tstruct trailer_info *info = trailer_info_new();\n+\tstruct trailer_block *trailer_block = trailer_block_new();\n \tsize_t end_of_log_message = 0, trailer_block_start = 0;\n \tstruct strbuf **trailer_lines, **ptr;\n \tchar **trailer_strings = NULL;\n@@ -1016,34 +1017,34 @@ static struct trailer_info *trailer_info_get(const struct process_trailer_option\n \t}\n \tstrbuf_list_free(trailer_lines);\n \n-\tinfo->blank_line_before_trailer = ends_with_blank_line(str,\n-\t\t\t\t\t\t\t       trailer_block_start);\n-\tinfo->trailer_block_start = trailer_block_start;\n-\tinfo->trailer_block_end = end_of_log_message;\n-\tinfo->trailers = trailer_strings;\n-\tinfo->trailer_nr = nr;\n+\ttrailer_block->blank_line_before_trailer = ends_with_blank_line(str,\n+\t\t\t\t\t\t\t\t\ttrailer_block_start);\n+\ttrailer_block->start = trailer_block_start;\n+\ttrailer_block->end = end_of_log_message;\n+\ttrailer_block->trailers = trailer_strings;\n+\ttrailer_block->trailer_nr = nr;\n \n-\treturn info;\n+\treturn trailer_block;\n }\n \n /*\n- * Parse trailers in \"str\", populating the trailer info and \"head\"\n- * linked list structure.\n+ * Parse trailers in \"str\", populating the trailer_block info and \"head\" linked\n+ * list structure.\n  */\n-struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n-\t\t\t\t    const char *str,\n-\t\t\t\t    struct list_head *head)\n+struct trailer_block *parse_trailers(const struct process_trailer_options *opts,\n+\t\t\t\t     const char *str,\n+\t\t\t\t     struct list_head *head)\n {\n-\tstruct trailer_info *info;\n+\tstruct trailer_block *trailer_block;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n \tsize_t i;\n \n-\tinfo = trailer_info_get(opts, str);\n+\ttrailer_block = trailer_block_get(opts, str);\n \n-\tfor (i = 0; i < info->trailer_nr; i++) {\n+\tfor (i = 0; i < trailer_block->trailer_nr; i++) {\n \t\tint separator_pos;\n-\t\tchar *trailer = info->trailers[i];\n+\t\tchar *trailer = trailer_block->trailers[i];\n \t\tif (trailer[0] == comment_line_char)\n \t\t\tcontinue;\n \t\tseparator_pos = find_separator(trailer, separators);\n@@ -1064,7 +1065,7 @@ struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n \t\t}\n \t}\n \n-\treturn info;\n+\treturn trailer_block;\n }\n \n void free_trailers(struct list_head *trailers)\n@@ -1076,28 +1077,28 @@ void free_trailers(struct list_head *trailers)\n \t}\n }\n \n-size_t trailer_block_start(struct trailer_info *info)\n+size_t trailer_block_start(struct trailer_block *trailer_block)\n {\n-\treturn info->trailer_block_start;\n+\treturn trailer_block->start;\n }\n \n-size_t trailer_block_end(struct trailer_info *info)\n+size_t trailer_block_end(struct trailer_block *trailer_block)\n {\n-\treturn info->trailer_block_end;\n+\treturn trailer_block->end;\n }\n \n-int blank_line_before_trailer_block(struct trailer_info *info)\n+int blank_line_before_trailer_block(struct trailer_block *trailer_block)\n {\n-\treturn info->blank_line_before_trailer;\n+\treturn trailer_block->blank_line_before_trailer;\n }\n \n-void trailer_info_release(struct trailer_info *info)\n+void trailer_block_release(struct trailer_block *trailer_block)\n {\n \tsize_t i;\n-\tfor (i = 0; i < info->trailer_nr; i++)\n-\t\tfree(info->trailers[i]);\n-\tfree(info->trailers);\n-\tfree(info);\n+\tfor (i = 0; i < trailer_block->trailer_nr; i++)\n+\t\tfree(trailer_block->trailers[i]);\n+\tfree(trailer_block->trailers);\n+\tfree(trailer_block);\n }\n \n void format_trailers(const struct process_trailer_options *opts,\n@@ -1165,19 +1166,19 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \t\t\t\t struct strbuf *out)\n {\n \tLIST_HEAD(trailers);\n-\tstruct trailer_info *info = parse_trailers(opts, msg, &trailers);\n+\tstruct trailer_block *trailer_block = parse_trailers(opts, msg, &trailers);\n \n \t/* If we want the whole block untouched, we can take the fast path. */\n \tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n \t    !opts->separator && !opts->key_only && !opts->value_only &&\n \t    !opts->key_value_separator) {\n-\t\tstrbuf_add(out, msg + info->trailer_block_start,\n-\t\t\t   info->trailer_block_end - info->trailer_block_start);\n+\t\tstrbuf_add(out, msg + trailer_block->start,\n+\t\t\t   trailer_block->end - trailer_block->start);\n \t} else\n \t\tformat_trailers(opts, &trailers, out);\n \n \tfree_trailers(&trailers);\n-\ttrailer_info_release(info);\n+\ttrailer_block_release(trailer_block);\n }\n \n void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n@@ -1186,14 +1187,14 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n \tstrbuf_init(&iter->key, 0);\n \tstrbuf_init(&iter->val, 0);\n \topts.no_divider = 1;\n-\titer->internal.info = trailer_info_get(&opts, msg);\n+\titer->internal.trailer_block = trailer_block_get(&opts, msg);\n \titer->internal.cur = 0;\n }\n \n int trailer_iterator_advance(struct trailer_iterator *iter)\n {\n-\tif (iter->internal.cur < iter->internal.info->trailer_nr) {\n-\t\tchar *line = iter->internal.info->trailers[iter->internal.cur++];\n+\tif (iter->internal.cur < iter->internal.trailer_block->trailer_nr) {\n+\t\tchar *line = iter->internal.trailer_block->trailers[iter->internal.cur++];\n \t\tint separator_pos = find_separator(line, separators);\n \n \t\titer->raw = line;\n@@ -1210,7 +1211,7 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n \n void trailer_iterator_release(struct trailer_iterator *iter)\n {\n-\ttrailer_info_release(iter->internal.info);\n+\ttrailer_block_release(iter->internal.trailer_block);\n \tstrbuf_release(&iter->val);\n \tstrbuf_release(&iter->key);\n }\ndiff --git a/trailer.h b/trailer.h\nindex 1b7422fa2b0..76e6d941a07 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -4,7 +4,7 @@\n #include \"list.h\"\n #include \"strbuf.h\"\n \n-struct trailer_info;\n+struct trailer_block;\n \n enum trailer_where {\n \tWHERE_DEFAULT,\n@@ -70,15 +70,15 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n void process_trailers_lists(struct list_head *head,\n \t\t\t    struct list_head *arg_head);\n \n-struct trailer_info *parse_trailers(const struct process_trailer_options *,\n-\t\t\t\t    const char *str,\n-\t\t\t\t    struct list_head *head);\n+struct trailer_block *parse_trailers(const struct process_trailer_options *,\n+\t\t\t\t     const char *str,\n+\t\t\t\t     struct list_head *head);\n \n-size_t trailer_block_start(struct trailer_info *);\n-size_t trailer_block_end(struct trailer_info *);\n-int blank_line_before_trailer_block(struct trailer_info *);\n+size_t trailer_block_start(struct trailer_block *);\n+size_t trailer_block_end(struct trailer_block *);\n+int blank_line_before_trailer_block(struct trailer_block *);\n \n-void trailer_info_release(struct trailer_info *info);\n+void trailer_block_release(struct trailer_block *);\n \n void trailer_config_init(void);\n void format_trailers(const struct process_trailer_options *,\n@@ -118,7 +118,7 @@ struct trailer_iterator {\n \n \t/* private */\n \tstruct {\n-\t\tstruct trailer_info *info;\n+\t\tstruct trailer_block *trailer_block;\n \t\tsize_t cur;\n \t} internal;\n };\n-- \ngitgitgadget\n\n"},{"id":"488014","messageId":"94bf182e3ffbf8ed6e20cd77b2e46e5b83c44d34.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 22/28] trailer: prepare to delete \"parse_trailers_from_command_line_args()\"","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:22Z","receivedAt":"2024-02-06T05:12:52Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nExpose more functions in the trailer.h API, in preparation for deleting\n\n    parse_trailers_from_command_line_args()\n\nfrom the trailers implementation, because it should not be concerned\nwith command line arguments (as they have nothing to do with trailers\nthemselves). Indeed, the interpret-trailers builtin is the only caller\nof this function inside Git.\n\nRename add_arg_item() to trailer_add_arg_item() to expose it as an API\nfunction. Rename new_trailers_clear() to free_new_trailers() because it\nwill be promoted into an API function; the API already has\nfree_trailers(), so using the \"free_*\" naming style will keep it\nconsistent. Also rename \"conf_info\" to \"trailer_conf\" for readability,\ndropping the low-value \"_info\" suffix as we did earlier in this series\nfor \"trailer_info\" to \"trailer_block\".\n\nHelped-by: Josh Steadmon <steadmon@google.com>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  6 +--\n trailer.c                    | 86 ++++++++++++++++++------------------\n trailer.h                    | 13 ++++++\n 3 files changed, 58 insertions(+), 47 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex f76841c5280..f674b5f4b9e 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -45,7 +45,7 @@ static int option_parse_if_missing(const struct option *opt,\n \treturn trailer_set_if_missing(opt->value, arg);\n }\n \n-static void new_trailers_clear(struct list_head *trailers)\n+static void free_new_trailers(struct list_head *trailers)\n {\n \tstruct list_head *pos, *tmp;\n \tstruct new_trailer_item *item;\n@@ -64,7 +64,7 @@ static int option_parse_trailer(const struct option *opt,\n \tstruct new_trailer_item *item;\n \n \tif (unset) {\n-\t\tnew_trailers_clear(trailers);\n+\t\tfree_new_trailers(trailers);\n \t\treturn 0;\n \t}\n \n@@ -237,7 +237,7 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n \t\tinterpret_trailers(&opts, &trailers, NULL);\n \t}\n \n-\tnew_trailers_clear(&trailers);\n+\tfree_new_trailers(&trailers);\n \n \treturn 0;\n }\ndiff --git a/trailer.c b/trailer.c\nindex 49bf26e3211..3b8f0ba103c 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -33,7 +33,7 @@ struct trailer_block {\n \tsize_t trailer_nr;\n };\n \n-struct conf_info {\n+struct trailer_conf {\n \tchar *name;\n \tchar *key;\n \tchar *command;\n@@ -43,7 +43,7 @@ struct conf_info {\n \tenum trailer_if_missing if_missing;\n };\n \n-static struct conf_info default_conf_info;\n+static struct trailer_conf default_trailer_conf;\n \n struct trailer_item {\n \tstruct list_head list;\n@@ -59,7 +59,7 @@ struct arg_item {\n \tstruct list_head list;\n \tchar *token;\n \tchar *value;\n-\tstruct conf_info conf;\n+\tstruct trailer_conf conf;\n };\n \n static LIST_HEAD(conf_head);\n@@ -210,7 +210,7 @@ static int check_if_different(struct trailer_item *in_tok,\n \treturn 1;\n }\n \n-static char *apply_command(struct conf_info *conf, const char *arg)\n+static char *apply_command(struct trailer_conf *conf, const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -424,7 +424,8 @@ int trailer_set_if_missing(enum trailer_if_missing *item, const char *value)\n \treturn 0;\n }\n \n-static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n+void duplicate_trailer_conf(struct trailer_conf *dst,\n+\t\t\t    const struct trailer_conf *src)\n {\n \t*dst = *src;\n \tdst->name = xstrdup_or_null(src->name);\n@@ -447,7 +448,7 @@ static struct arg_item *get_conf_item(const char *name)\n \n \t/* Item does not already exists, create it */\n \tCALLOC_ARRAY(item, 1);\n-\tduplicate_conf(&item->conf, &default_conf_info);\n+\tduplicate_trailer_conf(&item->conf, &default_trailer_conf);\n \titem->conf.name = xstrdup(name);\n \n \tlist_add_tail(&item->list, &conf_head);\n@@ -482,17 +483,17 @@ static int git_trailer_default_config(const char *conf_key, const char *value,\n \tvariable_name = strrchr(trailer_item, '.');\n \tif (!variable_name) {\n \t\tif (!strcmp(trailer_item, \"where\")) {\n-\t\t\tif (trailer_set_where(&default_conf_info.where,\n+\t\t\tif (trailer_set_where(&default_trailer_conf.where,\n \t\t\t\t\t      value) < 0)\n \t\t\t\twarning(_(\"unknown value '%s' for key '%s'\"),\n \t\t\t\t\tvalue, conf_key);\n \t\t} else if (!strcmp(trailer_item, \"ifexists\")) {\n-\t\t\tif (trailer_set_if_exists(&default_conf_info.if_exists,\n+\t\t\tif (trailer_set_if_exists(&default_trailer_conf.if_exists,\n \t\t\t\t\t\t  value) < 0)\n \t\t\t\twarning(_(\"unknown value '%s' for key '%s'\"),\n \t\t\t\t\tvalue, conf_key);\n \t\t} else if (!strcmp(trailer_item, \"ifmissing\")) {\n-\t\t\tif (trailer_set_if_missing(&default_conf_info.if_missing,\n+\t\t\tif (trailer_set_if_missing(&default_trailer_conf.if_missing,\n \t\t\t\t\t\t   value) < 0)\n \t\t\t\twarning(_(\"unknown value '%s' for key '%s'\"),\n \t\t\t\t\tvalue, conf_key);\n@@ -511,7 +512,7 @@ static int git_trailer_config(const char *conf_key, const char *value,\n {\n \tconst char *trailer_item, *variable_name;\n \tstruct arg_item *item;\n-\tstruct conf_info *conf;\n+\tstruct trailer_conf *conf;\n \tchar *name = NULL;\n \tenum trailer_info_type type;\n \tint i;\n@@ -585,9 +586,9 @@ void trailer_config_init(void)\n \t\treturn;\n \n \t/* Default config must be setup first */\n-\tdefault_conf_info.where = WHERE_END;\n-\tdefault_conf_info.if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;\n-\tdefault_conf_info.if_missing = MISSING_ADD;\n+\tdefault_trailer_conf.where = WHERE_END;\n+\tdefault_trailer_conf.if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;\n+\tdefault_trailer_conf.if_missing = MISSING_ADD;\n \tgit_config(git_trailer_default_config, NULL);\n \tgit_config(git_trailer_config, NULL);\n \tconfigured = 1;\n@@ -620,7 +621,7 @@ static int token_matches_item(const char *tok, struct arg_item *item, size_t tok\n  * distinguished from the non-well-formed-line case (in which this function\n  * returns -1) because some callers of this function need such a distinction.\n  */\n-static ssize_t find_separator(const char *line, const char *separators)\n+ssize_t find_separator(const char *line, const char *separators)\n {\n \tint whitespace_found = 0;\n \tconst char *c;\n@@ -645,28 +646,28 @@ static ssize_t find_separator(const char *line, const char *separators)\n  *\n  * If separator_pos is -1, interpret the whole trailer as a token.\n  */\n-static void parse_trailer(struct strbuf *tok, struct strbuf *val,\n-\t\t\t const struct conf_info **conf, const char *trailer,\n-\t\t\t ssize_t separator_pos)\n+void parse_trailer(const char *line, ssize_t separator_pos,\n+\t\t   struct strbuf *tok, struct strbuf *val,\n+\t\t   const struct trailer_conf **conf)\n {\n \tstruct arg_item *item;\n \tsize_t tok_len;\n \tstruct list_head *pos;\n \n \tif (separator_pos != -1) {\n-\t\tstrbuf_add(tok, trailer, separator_pos);\n+\t\tstrbuf_add(tok, line, separator_pos);\n \t\tstrbuf_trim(tok);\n-\t\tstrbuf_addstr(val, trailer + separator_pos + 1);\n+\t\tstrbuf_addstr(val, line + separator_pos + 1);\n \t\tstrbuf_trim(val);\n \t} else {\n-\t\tstrbuf_addstr(tok, trailer);\n+\t\tstrbuf_addstr(tok, line);\n \t\tstrbuf_trim(tok);\n \t}\n \n \t/* Lookup if the token matches something in the config */\n \ttok_len = token_len_without_separator(tok->buf, tok->len);\n \tif (conf)\n-\t\t*conf = &default_conf_info;\n+\t\t*conf = &default_trailer_conf;\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct arg_item, list);\n \t\tif (token_matches_item(tok->buf, item, tok_len)) {\n@@ -690,14 +691,14 @@ static struct trailer_item *add_trailer_item(struct list_head *head, char *tok,\n \treturn new_item;\n }\n \n-static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n-\t\t\t const struct conf_info *conf,\n-\t\t\t const struct new_trailer_item *new_trailer_item)\n+void trailer_add_arg_item(struct list_head *arg_head, char *tok, char *val,\n+\t\t\t  const struct trailer_conf *conf,\n+\t\t\t  const struct new_trailer_item *new_trailer_item)\n {\n \tstruct arg_item *new_item = xcalloc(1, sizeof(*new_item));\n \tnew_item->token = tok;\n \tnew_item->value = val;\n-\tduplicate_conf(&new_item->conf, conf);\n+\tduplicate_trailer_conf(&new_item->conf, conf);\n \tif (new_trailer_item) {\n \t\tif (new_trailer_item->where != WHERE_DEFAULT)\n \t\t\tnew_item->conf.where = new_trailer_item->where;\n@@ -718,10 +719,10 @@ void parse_trailers_from_config(struct list_head *config_head)\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct arg_item, list);\n \t\tif (item->conf.command)\n-\t\t\tadd_arg_item(config_head,\n-\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n-\t\t\t\t     xstrdup(\"\"),\n-\t\t\t\t     &item->conf, NULL);\n+\t\t\ttrailer_add_arg_item(config_head,\n+\t\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n+\t\t\t\t\t     xstrdup(\"\"),\n+\t\t\t\t\t     &item->conf, NULL);\n \t}\n }\n \n@@ -730,7 +731,7 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n {\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n-\tconst struct conf_info *conf;\n+\tconst struct trailer_conf *conf;\n \tstruct list_head *pos;\n \n \t/*\n@@ -753,12 +754,11 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n \t\t\t      (int) sb.len, sb.buf);\n \t\t\tstrbuf_release(&sb);\n \t\t} else {\n-\t\t\tparse_trailer(&tok, &val, &conf, tr->text,\n-\t\t\t\t      separator_pos);\n-\t\t\tadd_arg_item(arg_head,\n-\t\t\t\t     strbuf_detach(&tok, NULL),\n-\t\t\t\t     strbuf_detach(&val, NULL),\n-\t\t\t\t     conf, tr);\n+\t\t\tparse_trailer(tr->text, separator_pos, &tok, &val, &conf);\n+\t\t\ttrailer_add_arg_item(arg_head,\n+\t\t\t\t\t     strbuf_detach(&tok, NULL),\n+\t\t\t\t\t     strbuf_detach(&val, NULL),\n+\t\t\t\t\t     conf, tr);\n \t\t}\n \t}\n \n@@ -1044,20 +1044,19 @@ struct trailer_block *parse_trailers(const struct process_trailer_options *opts,\n \n \tfor (i = 0; i < trailer_block->trailer_nr; i++) {\n \t\tint separator_pos;\n-\t\tchar *trailer = trailer_block->trailers[i];\n-\t\tif (trailer[0] == comment_line_char)\n+\t\tchar *line = trailer_block->trailers[i];\n+\t\tif (line[0] == comment_line_char)\n \t\t\tcontinue;\n-\t\tseparator_pos = find_separator(trailer, separators);\n+\t\tseparator_pos = find_separator(line, separators);\n \t\tif (separator_pos >= 1) {\n-\t\t\tparse_trailer(&tok, &val, NULL, trailer,\n-\t\t\t\t      separator_pos);\n+\t\t\tparse_trailer(line, separator_pos, &tok, &val, NULL);\n \t\t\tif (opts->unfold)\n \t\t\t\tunfold_value(&val);\n \t\t\tadd_trailer_item(head,\n \t\t\t\t\t strbuf_detach(&tok, NULL),\n \t\t\t\t\t strbuf_detach(&val, NULL));\n \t\t} else if (!opts->only_trailers) {\n-\t\t\tstrbuf_addstr(&val, trailer);\n+\t\t\tstrbuf_addstr(&val, line);\n \t\t\tstrbuf_strip_suffix(&val, \"\\n\");\n \t\t\tadd_trailer_item(head,\n \t\t\t\t\t NULL,\n@@ -1200,8 +1199,7 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n \t\titer->raw = line;\n \t\tstrbuf_reset(&iter->key);\n \t\tstrbuf_reset(&iter->val);\n-\t\tparse_trailer(&iter->key, &iter->val, NULL,\n-\t\t\t      line, separator_pos);\n+\t\tparse_trailer(line, separator_pos, &iter->key, &iter->val, NULL);\n \t\t/* Always unfold values during iteration. */\n \t\tunfold_value(&iter->val);\n \t\treturn 1;\ndiff --git a/trailer.h b/trailer.h\nindex 76e6d941a07..f80f8f7e63f 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -5,6 +5,7 @@\n #include \"strbuf.h\"\n \n struct trailer_block;\n+struct trailer_conf;\n \n enum trailer_where {\n \tWHERE_DEFAULT,\n@@ -45,6 +46,12 @@ struct new_trailer_item {\n \tenum trailer_if_missing if_missing;\n };\n \n+void duplicate_trailer_conf(struct trailer_conf *dst,\n+\t\t\t    const struct trailer_conf *src);\n+void trailer_add_arg_item(struct list_head *arg_head, char *tok, char *val,\n+\t\t\t  const struct trailer_conf *conf,\n+\t\t\t  const struct new_trailer_item *new_trailer_item);\n+\n struct process_trailer_options {\n \tint in_place;\n \tint trim_empty;\n@@ -70,6 +77,12 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n void process_trailers_lists(struct list_head *head,\n \t\t\t    struct list_head *arg_head);\n \n+ssize_t find_separator(const char *line, const char *separators);\n+\n+void parse_trailer(const char *line, ssize_t separator_pos,\n+\t\t   struct strbuf *tok, struct strbuf *val,\n+\t\t   const struct trailer_conf **);\n+\n struct trailer_block *parse_trailers(const struct process_trailer_options *,\n \t\t\t\t     const char *str,\n \t\t\t\t     struct list_head *head);\n-- \ngitgitgadget\n\n"},{"id":"488015","messageId":"3bfe4809ecbc5aa0ea52daee7684289398cb88d4.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 23/28] trailer: add new helper functions to API","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:23Z","receivedAt":"2024-02-06T05:12:53Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis is a preparatory refactor for deprecating \"new_trailer_item\" from\nthe API (which will let us deprecate\nparse_trailers_from_command_line_args()).\n\nExpose new helper functions from the API, because we'll be calling them\nfrom interpret-trailers.c soon when we move\nparse_trailers_from_command_line_args() there.\n\nMove free_new_trailers() from the builtin to trailer.c because later on\nwe will adjust it to free arg_item structs, which are private to\ntrailer.c.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c | 12 ---------\n trailer.c                    | 49 ++++++++++++++++++++++++++++++++++++\n trailer.h                    |  8 ++++++\n 3 files changed, 57 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex f674b5f4b9e..9169c320921 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -45,18 +45,6 @@ static int option_parse_if_missing(const struct option *opt,\n \treturn trailer_set_if_missing(opt->value, arg);\n }\n \n-static void free_new_trailers(struct list_head *trailers)\n-{\n-\tstruct list_head *pos, *tmp;\n-\tstruct new_trailer_item *item;\n-\n-\tlist_for_each_safe(pos, tmp, trailers) {\n-\t\titem = list_entry(pos, struct new_trailer_item, list);\n-\t\tlist_del(pos);\n-\t\tfree(item);\n-\t}\n-}\n-\n static int option_parse_trailer(const struct option *opt,\n \t\t\t\t   const char *arg, int unset)\n {\ndiff --git a/trailer.c b/trailer.c\nindex 3b8f0ba103c..9b8cb94c021 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -66,6 +66,11 @@ static LIST_HEAD(conf_head);\n \n static char *separators = \":\";\n \n+const char *trailer_default_separators(void)\n+{\n+\treturn separators;\n+}\n+\n static int configured;\n \n #define TRAILER_ARG_STRING \"$ARG\"\n@@ -424,6 +429,29 @@ int trailer_set_if_missing(enum trailer_if_missing *item, const char *value)\n \treturn 0;\n }\n \n+void trailer_set_conf_where(enum trailer_where where,\n+\t\t\t    struct trailer_conf *conf)\n+{\n+\tconf->where = where;\n+}\n+\n+void trailer_set_conf_if_exists(enum trailer_if_exists if_exists,\n+\t\t\t\tstruct trailer_conf *conf)\n+{\n+\tconf->if_exists = if_exists;\n+}\n+\n+void trailer_set_conf_if_missing(enum trailer_if_missing if_missing,\n+\t\t\t\t struct trailer_conf *conf)\n+{\n+\tconf->if_missing = if_missing;\n+}\n+\n+struct trailer_conf *new_trailer_conf(void)\n+{\n+\t return xcalloc(1, sizeof(struct trailer_conf));\n+}\n+\n void duplicate_trailer_conf(struct trailer_conf *dst,\n \t\t\t    const struct trailer_conf *src)\n {\n@@ -434,6 +462,15 @@ void duplicate_trailer_conf(struct trailer_conf *dst,\n \tdst->cmd = xstrdup_or_null(src->cmd);\n }\n \n+void free_trailer_conf(struct trailer_conf *conf)\n+{\n+\tfree(conf->name);\n+\tfree(conf->key);\n+\tfree(conf->command);\n+\tfree(conf->cmd);\n+\tfree(conf);\n+}\n+\n static struct arg_item *get_conf_item(const char *name)\n {\n \tstruct list_head *pos;\n@@ -1076,6 +1113,18 @@ void free_trailers(struct list_head *trailers)\n \t}\n }\n \n+void free_new_trailers(struct list_head *trailers)\n+{\n+\tstruct list_head *pos, *tmp;\n+\tstruct new_trailer_item *item;\n+\n+\tlist_for_each_safe(pos, tmp, trailers) {\n+\t\titem = list_entry(pos, struct new_trailer_item, list);\n+\t\tlist_del(pos);\n+\t\tfree(item);\n+\t}\n+}\n+\n size_t trailer_block_start(struct trailer_block *trailer_block)\n {\n \treturn trailer_block->start;\ndiff --git a/trailer.h b/trailer.h\nindex f80f8f7e63f..a2569c10451 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -46,8 +46,14 @@ struct new_trailer_item {\n \tenum trailer_if_missing if_missing;\n };\n \n+void trailer_set_conf_where(enum trailer_where, struct trailer_conf *);\n+void trailer_set_conf_if_exists(enum trailer_if_exists, struct trailer_conf *);\n+void trailer_set_conf_if_missing(enum trailer_if_missing, struct trailer_conf *);\n+\n+struct trailer_conf *new_trailer_conf(void);\n void duplicate_trailer_conf(struct trailer_conf *dst,\n \t\t\t    const struct trailer_conf *src);\n+const char *trailer_default_separators(void);\n void trailer_add_arg_item(struct list_head *arg_head, char *tok, char *val,\n \t\t\t  const struct trailer_conf *conf,\n \t\t\t  const struct new_trailer_item *new_trailer_item);\n@@ -98,6 +104,8 @@ void format_trailers(const struct process_trailer_options *,\n \t\t     struct list_head *trailers,\n \t\t     struct strbuf *out);\n void free_trailers(struct list_head *);\n+void free_new_trailers(struct list_head *);\n+void free_trailer_conf(struct trailer_conf *);\n \n /*\n  * Convenience function to format the trailers from the commit msg \"msg\" into\n-- \ngitgitgadget\n\n"},{"id":"488016","messageId":"80e1958bb8d5d4e74fc9d4334b99d149ae2bcab1.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 24/28] trailer_add_arg_item(): drop new_trailer_item usage","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:24Z","receivedAt":"2024-02-06T05:12:54Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis is a preparatory refactor for deprecating \"new_trailer_item\" from\nthe API.\n\nInstead of preserving the where/if_exists/if_missing information inside\nthe new_trailer_item struct in parse_trailers_from_command_line_args()\n(only to look inside it again later on in trailer_add_arg_item()), pass\nthis information directly as a trailer_conf which trailer_add_arg_item()\nalready knows how to handle. This reduces the number of parameters we\nhave to pass to trailer_add_arg_item() without any behavioral change.\n\nIn the next patch we'll be able to delete \"new_trailer_item\" altogether.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 29 +++++++++++++++++------------\n trailer.h |  3 +--\n 2 files changed, 18 insertions(+), 14 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 9b8cb94c021..6ab5cf7e5d7 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -729,21 +729,12 @@ static struct trailer_item *add_trailer_item(struct list_head *head, char *tok,\n }\n \n void trailer_add_arg_item(struct list_head *arg_head, char *tok, char *val,\n-\t\t\t  const struct trailer_conf *conf,\n-\t\t\t  const struct new_trailer_item *new_trailer_item)\n+\t\t\t  const struct trailer_conf *conf)\n {\n \tstruct arg_item *new_item = xcalloc(1, sizeof(*new_item));\n \tnew_item->token = tok;\n \tnew_item->value = val;\n \tduplicate_trailer_conf(&new_item->conf, conf);\n-\tif (new_trailer_item) {\n-\t\tif (new_trailer_item->where != WHERE_DEFAULT)\n-\t\t\tnew_item->conf.where = new_trailer_item->where;\n-\t\tif (new_trailer_item->if_exists != EXISTS_DEFAULT)\n-\t\t\tnew_item->conf.if_exists = new_trailer_item->if_exists;\n-\t\tif (new_trailer_item->if_missing != MISSING_DEFAULT)\n-\t\t\tnew_item->conf.if_missing = new_trailer_item->if_missing;\n-\t}\n \tlist_add_tail(&new_item->list, arg_head);\n }\n \n@@ -759,7 +750,7 @@ void parse_trailers_from_config(struct list_head *config_head)\n \t\t\ttrailer_add_arg_item(config_head,\n \t\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n \t\t\t\t\t     xstrdup(\"\"),\n-\t\t\t\t\t     &item->conf, NULL);\n+\t\t\t\t\t     &item->conf);\n \t}\n }\n \n@@ -791,11 +782,25 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n \t\t\t      (int) sb.len, sb.buf);\n \t\t\tstrbuf_release(&sb);\n \t\t} else {\n+\t\t\tstruct trailer_conf *conf_current = new_trailer_conf();\n \t\t\tparse_trailer(tr->text, separator_pos, &tok, &val, &conf);\n+\t\t\tduplicate_trailer_conf(conf_current, conf);\n+\n+\t\t\t/*\n+\t\t\t * Override conf_current with settings specified via CLI flags.\n+\t\t\t */\n+\t\t\tif (tr->where != WHERE_DEFAULT)\n+\t\t\t\ttrailer_set_conf_where(tr->where, conf_current);\n+\t\t\tif (tr->if_exists != EXISTS_DEFAULT)\n+\t\t\t\ttrailer_set_conf_if_exists(tr->if_exists, conf_current);\n+\t\t\tif (tr->if_missing != MISSING_DEFAULT)\n+\t\t\t\ttrailer_set_conf_if_missing(tr->if_missing, conf_current);\n+\n \t\t\ttrailer_add_arg_item(arg_head,\n \t\t\t\t\t     strbuf_detach(&tok, NULL),\n \t\t\t\t\t     strbuf_detach(&val, NULL),\n-\t\t\t\t\t     conf, tr);\n+\t\t\t\t\t     conf_current);\n+\t\t\tfree_trailer_conf(conf_current);\n \t\t}\n \t}\n \ndiff --git a/trailer.h b/trailer.h\nindex a2569c10451..32fc93beb33 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -55,8 +55,7 @@ void duplicate_trailer_conf(struct trailer_conf *dst,\n \t\t\t    const struct trailer_conf *src);\n const char *trailer_default_separators(void);\n void trailer_add_arg_item(struct list_head *arg_head, char *tok, char *val,\n-\t\t\t  const struct trailer_conf *conf,\n-\t\t\t  const struct new_trailer_item *new_trailer_item);\n+\t\t\t  const struct trailer_conf *conf);\n \n struct process_trailer_options {\n \tint in_place;\n-- \ngitgitgadget\n\n"},{"id":"488017","messageId":"a9080597a28de9977e4909f0ff9d0f04f6689984.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 25/28] trailer: deprecate \"new_trailer_item\" struct from API","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:25Z","receivedAt":"2024-02-06T05:12:55Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nPreviously, the \"new_trailer_item\" struct served only one purpose --- to\ncapture the unparsed raw string <RAW> in \"--trailer <RAW>\", as well as\nthe current state of the \"where\", \"if_exists\", and \"if_missing\" global\nvariables at the time that the \"--trailer <RAW>\" CLI argument was\nencountered.\n\nIn addition, the previous CLI argument handling behavior was to capture\nthe <RAW> string in all \"--trailer <RAW>\" arguments and to collect\nthem (via option_parse_trailer()) into the \"new_trailer_head\" list. We\nwould then iterate over this list again in\nparse_trailers_from_command_line_args() and convert these\n\"new_trailer_item\" objects into \"arg_item\" objects.\n\nSkip this intermediate storage of \"new_trailer_item\" objects in favor of\njust storing \"arg_item\" objects. Remove the looping behavior of\nparse_trailers_from_command_line_args() so that it parses a single\n\"--trailer ...\" argument at a time. Rename it to\nparse_trailer_from_command_line_arg() to reflect this new behavior of\nonly looking at one string (not multiple strings) at a time. Make\noption_parse_trailer() call parse_trailer_from_command_line_arg() so\nthat the CLI arguments it sees are parsed immediately without the need\nfor intermediate storage.\n\nDelete \"new_trailer_item\", because we don't need it any more.\n\nIn the next patch we will retire parse_trailer_from_command_line_arg()\nas well, combining it with option_parse_trailer().\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c | 29 +++++--------\n trailer.c                    | 81 +++++++++++++++++-------------------\n trailer.h                    | 21 +++-------\n 3 files changed, 54 insertions(+), 77 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 9169c320921..943be5b360e 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -49,7 +49,6 @@ static int option_parse_trailer(const struct option *opt,\n \t\t\t\t   const char *arg, int unset)\n {\n \tstruct list_head *trailers = opt->value;\n-\tstruct new_trailer_item *item;\n \n \tif (unset) {\n \t\tfree_new_trailers(trailers);\n@@ -59,12 +58,8 @@ static int option_parse_trailer(const struct option *opt,\n \tif (!arg)\n \t\treturn -1;\n \n-\titem = xmalloc(sizeof(*item));\n-\titem->text = arg;\n-\titem->where = where;\n-\titem->if_exists = if_exists;\n-\titem->if_missing = if_missing;\n-\tlist_add_tail(&item->list, trailers);\n+\tparse_trailer_from_command_line_arg(arg, where, if_exists, if_missing, trailers);\n+\n \treturn 0;\n }\n \n@@ -132,8 +127,6 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \tstruct trailer_block *trailer_block;\n \tFILE *outfile = stdout;\n \n-\ttrailer_config_init();\n-\n \tread_input_file(&sb, file);\n \n \tif (opts->in_place)\n@@ -148,15 +141,8 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \tif (!opts->only_trailers && !blank_line_before_trailer_block(trailer_block))\n \t\tfprintf(outfile, \"\\n\");\n \n-\n-\tif (!opts->only_input) {\n-\t\tLIST_HEAD(config_head);\n-\t\tLIST_HEAD(arg_head);\n-\t\tparse_trailers_from_config(&config_head);\n-\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n-\t\tlist_splice(&config_head, &arg_head);\n-\t\tprocess_trailers_lists(&head, &arg_head);\n-\t}\n+\tif (!opts->only_input)\n+\t\tprocess_trailers_lists(&head, new_trailer_head);\n \n \t/* Print trailer block. */\n \tformat_trailers(opts, &head, &tb);\n@@ -180,6 +166,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n {\n \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n+\tLIST_HEAD(configured_trailers);\n \tLIST_HEAD(trailers);\n \n \tstruct option options[] = {\n@@ -205,6 +192,10 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n \t};\n \n \tgit_config(git_default_config, NULL);\n+\ttrailer_config_init();\n+\n+\tif (!opts.only_input)\n+\t\tparse_trailers_from_config(&configured_trailers);\n \n \targc = parse_options(argc, argv, prefix, options,\n \t\t\t     git_interpret_trailers_usage, 0);\n@@ -215,6 +206,8 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n \t\t\tgit_interpret_trailers_usage,\n \t\t\toptions);\n \n+\tlist_splice(&configured_trailers, &trailers);\n+\n \tif (argc) {\n \t\tint i;\n \t\tfor (i = 0; i < argc; i++)\ndiff --git a/trailer.c b/trailer.c\nindex 6ab5cf7e5d7..0893175553a 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -754,57 +754,54 @@ void parse_trailers_from_config(struct list_head *config_head)\n \t}\n }\n \n-void parse_trailers_from_command_line_args(struct list_head *arg_head,\n-\t\t\t\t\t   struct list_head *new_trailer_head)\n+void parse_trailer_from_command_line_arg(const char *line,\n+\t\t\t\t\t enum trailer_where where,\n+\t\t\t\t\t enum trailer_if_exists if_exists,\n+\t\t\t\t\t enum trailer_if_missing if_missing,\n+\t\t\t\t\t struct list_head *arg_head)\n {\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n \tconst struct trailer_conf *conf;\n-\tstruct list_head *pos;\n \n \t/*\n \t * In command-line arguments, '=' is accepted (in addition to the\n \t * separators that are defined).\n \t */\n-\tchar *cl_separators = xstrfmt(\"=%s\", separators);\n+\tchar *cl_separators = xstrfmt(\"=%s\", trailer_default_separators());\n \n-\t/* Add an arg item for each trailer on the command line */\n-\tlist_for_each(pos, new_trailer_head) {\n-\t\tstruct new_trailer_item *tr =\n-\t\t\tlist_entry(pos, struct new_trailer_item, list);\n-\t\tssize_t separator_pos = find_separator(tr->text, cl_separators);\n+\t/* Add an arg item for a trailer from the command line */\n+\tssize_t separator_pos = find_separator(line, cl_separators);\n+\tfree(cl_separators);\n \n-\t\tif (separator_pos == 0) {\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstrbuf_addstr(&sb, tr->text);\n-\t\t\tstrbuf_trim(&sb);\n-\t\t\terror(_(\"empty trailer token in trailer '%.*s'\"),\n-\t\t\t      (int) sb.len, sb.buf);\n-\t\t\tstrbuf_release(&sb);\n-\t\t} else {\n-\t\t\tstruct trailer_conf *conf_current = new_trailer_conf();\n-\t\t\tparse_trailer(tr->text, separator_pos, &tok, &val, &conf);\n-\t\t\tduplicate_trailer_conf(conf_current, conf);\n+\tif (separator_pos == 0) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_addstr(&sb, line);\n+\t\tstrbuf_trim(&sb);\n+\t\terror(_(\"empty trailer token in trailer '%.*s'\"),\n+\t\t      (int) sb.len, sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t} else {\n+\t\tstruct trailer_conf *conf_current = new_trailer_conf();\n+\t\tparse_trailer(line, separator_pos, &tok, &val, &conf);\n+\t\tduplicate_trailer_conf(conf_current, conf);\n \n-\t\t\t/*\n-\t\t\t * Override conf_current with settings specified via CLI flags.\n-\t\t\t */\n-\t\t\tif (tr->where != WHERE_DEFAULT)\n-\t\t\t\ttrailer_set_conf_where(tr->where, conf_current);\n-\t\t\tif (tr->if_exists != EXISTS_DEFAULT)\n-\t\t\t\ttrailer_set_conf_if_exists(tr->if_exists, conf_current);\n-\t\t\tif (tr->if_missing != MISSING_DEFAULT)\n-\t\t\t\ttrailer_set_conf_if_missing(tr->if_missing, conf_current);\n-\n-\t\t\ttrailer_add_arg_item(arg_head,\n-\t\t\t\t\t     strbuf_detach(&tok, NULL),\n-\t\t\t\t\t     strbuf_detach(&val, NULL),\n-\t\t\t\t\t     conf_current);\n-\t\t\tfree_trailer_conf(conf_current);\n-\t\t}\n+\t\t/*\n+\t\t * Override conf_current with settings specified via CLI flags.\n+\t\t */\n+\t\tif (where != WHERE_DEFAULT)\n+\t\t\ttrailer_set_conf_where(where, conf_current);\n+\t\tif (if_exists != EXISTS_DEFAULT)\n+\t\t\ttrailer_set_conf_if_exists(if_exists, conf_current);\n+\t\tif (if_missing != MISSING_DEFAULT)\n+\t\t\ttrailer_set_conf_if_missing(if_missing, conf_current);\n+\n+\t\ttrailer_add_arg_item(arg_head,\n+\t\t\t\t     strbuf_detach(&tok, NULL),\n+\t\t\t\t     strbuf_detach(&val, NULL),\n+\t\t\t\t     conf_current);\n+\t\tfree_trailer_conf(conf_current);\n \t}\n-\n-\tfree(cl_separators);\n }\n \n static const char *next_line(const char *str)\n@@ -1120,13 +1117,11 @@ void free_trailers(struct list_head *trailers)\n \n void free_new_trailers(struct list_head *trailers)\n {\n-\tstruct list_head *pos, *tmp;\n-\tstruct new_trailer_item *item;\n+\tstruct list_head *pos, *p;\n \n-\tlist_for_each_safe(pos, tmp, trailers) {\n-\t\titem = list_entry(pos, struct new_trailer_item, list);\n+\tlist_for_each_safe(pos, p, trailers) {\n \t\tlist_del(pos);\n-\t\tfree(item);\n+\t\tfree_arg_item(list_entry(pos, struct arg_item, list));\n \t}\n }\n \ndiff --git a/trailer.h b/trailer.h\nindex 32fc93beb33..2848a0d086c 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -32,20 +32,6 @@ int trailer_set_where(enum trailer_where *item, const char *value);\n int trailer_set_if_exists(enum trailer_if_exists *item, const char *value);\n int trailer_set_if_missing(enum trailer_if_missing *item, const char *value);\n \n-/*\n- * A list that represents newly-added trailers, such as those provided\n- * with the --trailer command line option of git-interpret-trailers.\n- */\n-struct new_trailer_item {\n-\tstruct list_head list;\n-\n-\tconst char *text;\n-\n-\tenum trailer_where where;\n-\tenum trailer_if_exists if_exists;\n-\tenum trailer_if_missing if_missing;\n-};\n-\n void trailer_set_conf_where(enum trailer_where, struct trailer_conf *);\n void trailer_set_conf_if_exists(enum trailer_if_exists, struct trailer_conf *);\n void trailer_set_conf_if_missing(enum trailer_if_missing, struct trailer_conf *);\n@@ -76,8 +62,11 @@ struct process_trailer_options {\n \n void parse_trailers_from_config(struct list_head *config_head);\n \n-void parse_trailers_from_command_line_args(struct list_head *arg_head,\n-\t\t\t\t\t   struct list_head *new_trailer_head);\n+void parse_trailer_from_command_line_arg(const char *line,\n+\t\t\t\t\t enum trailer_where where,\n+\t\t\t\t\t enum trailer_if_exists if_exists,\n+\t\t\t\t\t enum trailer_if_missing if_missing,\n+\t\t\t\t\t struct list_head *arg_head);\n \n void process_trailers_lists(struct list_head *head,\n \t\t\t    struct list_head *arg_head);\n-- \ngitgitgadget\n\n"},{"id":"488018","messageId":"9720526dd8a63b916c75fe9d6322ee13c8b36621.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 26/28] trailer: unify \"--trailer ...\" arg handling","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:26Z","receivedAt":"2024-02-06T05:12:56Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nMove the logic of parse_trailer_from_command_line_arg() into\noption_parse_trailer(), because that is the only caller and there's no\nbenefit in keeping these two separate.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c | 42 +++++++++++++++++++++++++++++-\n trailer.c                    | 50 ------------------------------------\n trailer.h                    |  6 -----\n 3 files changed, 41 insertions(+), 57 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 943be5b360e..9657b0d067c 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -49,6 +49,11 @@ static int option_parse_trailer(const struct option *opt,\n \t\t\t\t   const char *arg, int unset)\n {\n \tstruct list_head *trailers = opt->value;\n+\tstruct strbuf tok = STRBUF_INIT;\n+\tstruct strbuf val = STRBUF_INIT;\n+\tconst struct trailer_conf *conf;\n+\tssize_t separator_pos;\n+\tstatic char *cl_separators;\n \n \tif (unset) {\n \t\tfree_new_trailers(trailers);\n@@ -58,7 +63,42 @@ static int option_parse_trailer(const struct option *opt,\n \tif (!arg)\n \t\treturn -1;\n \n-\tparse_trailer_from_command_line_arg(arg, where, if_exists, if_missing, trailers);\n+\t/*\n+\t * In command-line arguments, '=' is accepted (in addition to the\n+\t * separators that are defined).\n+\t */\n+\tcl_separators = xstrfmt(\"=%s\", trailer_default_separators());\n+\tseparator_pos = find_separator(arg, cl_separators);\n+\tfree(cl_separators);\n+\n+\tif (separator_pos == 0) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_addstr(&sb, arg);\n+\t\tstrbuf_trim(&sb);\n+\t\terror(_(\"empty trailer token in trailer '%.*s'\"),\n+\t\t\t(int) sb.len, sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t} else {\n+\t\tstruct trailer_conf *conf_current = new_trailer_conf();\n+\t\tparse_trailer(arg, separator_pos, &tok, &val, &conf);\n+\t\tduplicate_trailer_conf(conf_current, conf);\n+\n+\t\t/*\n+\t\t * Override conf_current with settings specified via CLI flags.\n+\t\t */\n+\t\tif (where != WHERE_DEFAULT)\n+\t\t\ttrailer_set_conf_where(where, conf_current);\n+\t\tif (if_exists != EXISTS_DEFAULT)\n+\t\t\ttrailer_set_conf_if_exists(if_exists, conf_current);\n+\t\tif (if_missing != MISSING_DEFAULT)\n+\t\t\ttrailer_set_conf_if_missing(if_missing, conf_current);\n+\n+\t\ttrailer_add_arg_item(trailers,\n+\t\t\t\t     strbuf_detach(&tok, NULL),\n+\t\t\t\t     strbuf_detach(&val, NULL),\n+\t\t\t\t     conf_current);\n+\t\tfree_trailer_conf(conf_current);\n+\t}\n \n \treturn 0;\n }\ndiff --git a/trailer.c b/trailer.c\nindex 0893175553a..b0b067ab12c 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -754,56 +754,6 @@ void parse_trailers_from_config(struct list_head *config_head)\n \t}\n }\n \n-void parse_trailer_from_command_line_arg(const char *line,\n-\t\t\t\t\t enum trailer_where where,\n-\t\t\t\t\t enum trailer_if_exists if_exists,\n-\t\t\t\t\t enum trailer_if_missing if_missing,\n-\t\t\t\t\t struct list_head *arg_head)\n-{\n-\tstruct strbuf tok = STRBUF_INIT;\n-\tstruct strbuf val = STRBUF_INIT;\n-\tconst struct trailer_conf *conf;\n-\n-\t/*\n-\t * In command-line arguments, '=' is accepted (in addition to the\n-\t * separators that are defined).\n-\t */\n-\tchar *cl_separators = xstrfmt(\"=%s\", trailer_default_separators());\n-\n-\t/* Add an arg item for a trailer from the command line */\n-\tssize_t separator_pos = find_separator(line, cl_separators);\n-\tfree(cl_separators);\n-\n-\tif (separator_pos == 0) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\tstrbuf_addstr(&sb, line);\n-\t\tstrbuf_trim(&sb);\n-\t\terror(_(\"empty trailer token in trailer '%.*s'\"),\n-\t\t      (int) sb.len, sb.buf);\n-\t\tstrbuf_release(&sb);\n-\t} else {\n-\t\tstruct trailer_conf *conf_current = new_trailer_conf();\n-\t\tparse_trailer(line, separator_pos, &tok, &val, &conf);\n-\t\tduplicate_trailer_conf(conf_current, conf);\n-\n-\t\t/*\n-\t\t * Override conf_current with settings specified via CLI flags.\n-\t\t */\n-\t\tif (where != WHERE_DEFAULT)\n-\t\t\ttrailer_set_conf_where(where, conf_current);\n-\t\tif (if_exists != EXISTS_DEFAULT)\n-\t\t\ttrailer_set_conf_if_exists(if_exists, conf_current);\n-\t\tif (if_missing != MISSING_DEFAULT)\n-\t\t\ttrailer_set_conf_if_missing(if_missing, conf_current);\n-\n-\t\ttrailer_add_arg_item(arg_head,\n-\t\t\t\t     strbuf_detach(&tok, NULL),\n-\t\t\t\t     strbuf_detach(&val, NULL),\n-\t\t\t\t     conf_current);\n-\t\tfree_trailer_conf(conf_current);\n-\t}\n-}\n-\n static const char *next_line(const char *str)\n {\n \tconst char *nl = strchrnul(str, '\\n');\ndiff --git a/trailer.h b/trailer.h\nindex 2848a0d086c..af55032625d 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -62,12 +62,6 @@ struct process_trailer_options {\n \n void parse_trailers_from_config(struct list_head *config_head);\n \n-void parse_trailer_from_command_line_arg(const char *line,\n-\t\t\t\t\t enum trailer_where where,\n-\t\t\t\t\t enum trailer_if_exists if_exists,\n-\t\t\t\t\t enum trailer_if_missing if_missing,\n-\t\t\t\t\t struct list_head *arg_head);\n-\n void process_trailers_lists(struct list_head *head,\n \t\t\t    struct list_head *arg_head);\n \n-- \ngitgitgadget\n\n"},{"id":"488019","messageId":"26df2514acbf4d51f40f4b1b9f33a357fa424ac7.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 27/28] trailer_set_*(): put out parameter at the end","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:27Z","receivedAt":"2024-02-06T05:12:57Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThe new trailer_config_set_*() functions which were introduced a few\npatches ago put the out parameter (the variable being mutated) at the\nend of the parameter list.\n\nPut the out parameter at the end for these functions for these existing\ntrailer_set_*() functions for consistency. This also avoids confusion\nfor API users because otherwise these two sets of functions look rather\nsimilar in <trailer.h> even though they have completely different out\nparameters.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  6 +++---\n trailer.c                    | 24 ++++++++++++------------\n trailer.h                    |  6 +++---\n 3 files changed, 18 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 9657b0d067c..d0c09d1d73b 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -28,21 +28,21 @@ static int option_parse_where(const struct option *opt,\n \t\t\t      const char *arg, int unset UNUSED)\n {\n \t/* unset implies NULL arg, which is handled in our helper */\n-\treturn trailer_set_where(opt->value, arg);\n+\treturn trailer_set_where(arg, opt->value);\n }\n \n static int option_parse_if_exists(const struct option *opt,\n \t\t\t\t  const char *arg, int unset UNUSED)\n {\n \t/* unset implies NULL arg, which is handled in our helper */\n-\treturn trailer_set_if_exists(opt->value, arg);\n+\treturn trailer_set_if_exists(arg, opt->value);\n }\n \n static int option_parse_if_missing(const struct option *opt,\n \t\t\t\t   const char *arg, int unset UNUSED)\n {\n \t/* unset implies NULL arg, which is handled in our helper */\n-\treturn trailer_set_if_missing(opt->value, arg);\n+\treturn trailer_set_if_missing(arg, opt->value);\n }\n \n static int option_parse_trailer(const struct option *opt,\ndiff --git a/trailer.c b/trailer.c\nindex b0b067ab12c..7b0bdfcb27e 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -380,7 +380,7 @@ void process_trailers_lists(struct list_head *head,\n \t}\n }\n \n-int trailer_set_where(enum trailer_where *item, const char *value)\n+int trailer_set_where(const char *value, enum trailer_where *item)\n {\n \tif (!value)\n \t\t*item = WHERE_DEFAULT;\n@@ -397,7 +397,7 @@ int trailer_set_where(enum trailer_where *item, const char *value)\n \treturn 0;\n }\n \n-int trailer_set_if_exists(enum trailer_if_exists *item, const char *value)\n+int trailer_set_if_exists(const char *value, enum trailer_if_exists *item)\n {\n \tif (!value)\n \t\t*item = EXISTS_DEFAULT;\n@@ -416,7 +416,7 @@ int trailer_set_if_exists(enum trailer_if_exists *item, const char *value)\n \treturn 0;\n }\n \n-int trailer_set_if_missing(enum trailer_if_missing *item, const char *value)\n+int trailer_set_if_missing(const char *value, enum trailer_if_missing *item)\n {\n \tif (!value)\n \t\t*item = MISSING_DEFAULT;\n@@ -520,18 +520,18 @@ static int git_trailer_default_config(const char *conf_key, const char *value,\n \tvariable_name = strrchr(trailer_item, '.');\n \tif (!variable_name) {\n \t\tif (!strcmp(trailer_item, \"where\")) {\n-\t\t\tif (trailer_set_where(&default_trailer_conf.where,\n-\t\t\t\t\t      value) < 0)\n+\t\t\tif (trailer_set_where(value,\n+\t\t\t\t\t      &default_trailer_conf.where) < 0)\n \t\t\t\twarning(_(\"unknown value '%s' for key '%s'\"),\n \t\t\t\t\tvalue, conf_key);\n \t\t} else if (!strcmp(trailer_item, \"ifexists\")) {\n-\t\t\tif (trailer_set_if_exists(&default_trailer_conf.if_exists,\n-\t\t\t\t\t\t  value) < 0)\n+\t\t\tif (trailer_set_if_exists(value,\n+\t\t\t\t\t\t  &default_trailer_conf.if_exists) < 0)\n \t\t\t\twarning(_(\"unknown value '%s' for key '%s'\"),\n \t\t\t\t\tvalue, conf_key);\n \t\t} else if (!strcmp(trailer_item, \"ifmissing\")) {\n-\t\t\tif (trailer_set_if_missing(&default_trailer_conf.if_missing,\n-\t\t\t\t\t\t   value) < 0)\n+\t\t\tif (trailer_set_if_missing(value,\n+\t\t\t\t\t\t   &default_trailer_conf.if_missing) < 0)\n \t\t\t\twarning(_(\"unknown value '%s' for key '%s'\"),\n \t\t\t\t\tvalue, conf_key);\n \t\t} else if (!strcmp(trailer_item, \"separators\")) {\n@@ -600,15 +600,15 @@ static int git_trailer_config(const char *conf_key, const char *value,\n \t\tconf->cmd = xstrdup(value);\n \t\tbreak;\n \tcase TRAILER_WHERE:\n-\t\tif (trailer_set_where(&conf->where, value))\n+\t\tif (trailer_set_where(value, &conf->where))\n \t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, conf_key);\n \t\tbreak;\n \tcase TRAILER_IF_EXISTS:\n-\t\tif (trailer_set_if_exists(&conf->if_exists, value))\n+\t\tif (trailer_set_if_exists(value, &conf->if_exists))\n \t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, conf_key);\n \t\tbreak;\n \tcase TRAILER_IF_MISSING:\n-\t\tif (trailer_set_if_missing(&conf->if_missing, value))\n+\t\tif (trailer_set_if_missing(value, &conf->if_missing))\n \t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, conf_key);\n \t\tbreak;\n \tdefault:\ndiff --git a/trailer.h b/trailer.h\nindex af55032625d..4193bedbae4 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -28,9 +28,9 @@ enum trailer_if_missing {\n \tMISSING_DO_NOTHING\n };\n \n-int trailer_set_where(enum trailer_where *item, const char *value);\n-int trailer_set_if_exists(enum trailer_if_exists *item, const char *value);\n-int trailer_set_if_missing(enum trailer_if_missing *item, const char *value);\n+int trailer_set_where(const char *, enum trailer_where *);\n+int trailer_set_if_exists(const char *, enum trailer_if_exists *);\n+int trailer_set_if_missing(const char *, enum trailer_if_missing *);\n \n void trailer_set_conf_where(enum trailer_where, struct trailer_conf *);\n void trailer_set_conf_if_exists(enum trailer_if_exists, struct trailer_conf *);\n-- \ngitgitgadget\n\n"},{"id":"488020","messageId":"14927038d855020f9ae7594ad5cc646257613cc1.1707196348.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v4 28/28] trailer: introduce \"template\" term for readability","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-06T05:12:28Z","receivedAt":"2024-02-06T05:12:58Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThe term \"arg_item\" is ambiguous because we use it to hold data for\n\n  (1) trailers specified as command line arguments (in\n      builtin/interpret-trailers.c), and\n\n  (2) trailers specified in configuration,\n\nand these are both used to ultimately insert new trailers (based\non the contents of arg_item, acting as a kind of template) into some\nother set of existing trailers (such as those found in a trailer block\ninside a log message) that have already been parsed.\n\nRename \"arg_item\" to \"trailer_template\". This necessitates further\nrenames to make the functions that act on these templates match the data\nstructures (parameters) they act on:\n\n  - [*] add_arg_to_input_list()      to apply_template_to_trailers()\n  - [*] apply_arg_if_exists()        to maybe_add_if_exists()\n  - [*] apply_arg_if_missing()       to maybe_add_if_missing()\n  -     apply_command()              to run_command_from_template()\n  - [*] apply_item_command()         to populate_template_value()\n  -     free_arg_item()              to free_template() (non-API function)\n  -     free_new_trailers()          to free_trailer_templates() (API function)\n  -     get_conf_item()              to get_or_add_template_by()\n  -     option_parse_trailer()       to option_parse_trailer_template()\n  -     parse_trailers_from_config() to parse_trailer_templates_from_config()\n  - [*] process_trailers_lists()     to apply_trailer_templates()\n  -     token_from_item()            to token_from_template()\n  -     token_matches_item           to token_matches_template\n  - [*] trailer_add_arg_item()       to add_trailer_template()\n  -     trailer_from_arg()           to trailer_from()\n  - [*] check_if_different()         (reorder parameters only)\n  - [*] find_same_and_apply_arg()    (reorder parameters only)\n\nReorder parameters to prefer input parameters toward the beginning and\nout parameters at the end; these functions have been marked with an\nasterisk ([*]).\n\nThis removes the \"arg\" terminology (standing for \"CLI arguments\") from\nthe trailer implementation, which makes sense because trailers\nthemselves have nothing to do with CLI argument handling.\n\nAlso note that these renames expose the previously liberal use of\n\"trailer\" to mean both trailers we read from the input text (trailer\nblock) and trailer templates that are defined as CLI args or\nconfigurations. Some functions implied a single action when they could\ndo two different things, so introduce words like \"maybe\" and \"or\" to\nmake their behavior more explicit.\n\nIn summary this patch renames and reorders parameters for readability,\nwithout any behavioral change. We don't rename\nfind_same_and_apply_arg(), because it will be refactored soon.\n\nFor parse_trailers_from_config() (renamed to\nparse_trailer_templates_from_config()), add a NEEDSWORK discussion about\nhow the deprecated trailer.*.command configuration option is oddly more\nfeatureful than trailer.*.cmd (if we were to remove support for\ntrailer.*.command, users would not be able to replicate the behavior\nwith trailer.*.cmd and would lose out on functionality).\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  50 +++---\n trailer.c                    | 290 +++++++++++++++++++----------------\n trailer.h                    |  12 +-\n 3 files changed, 193 insertions(+), 159 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex d0c09d1d73b..d3d2544c5f1 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -45,10 +45,14 @@ static int option_parse_if_missing(const struct option *opt,\n \treturn trailer_set_if_missing(arg, opt->value);\n }\n \n-static int option_parse_trailer(const struct option *opt,\n-\t\t\t\t   const char *arg, int unset)\n+/*\n+ * Interpret \"--trailer ...\" as trailer templates (trailers we want to add into\n+ * the input text).\n+ */\n+static int option_parse_trailer_template(const struct option *opt,\n+\t\t\t\t\t const char *arg, int unset)\n {\n-\tstruct list_head *trailers = opt->value;\n+\tstruct list_head *templates = opt->value;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n \tconst struct trailer_conf *conf;\n@@ -56,7 +60,7 @@ static int option_parse_trailer(const struct option *opt,\n \tstatic char *cl_separators;\n \n \tif (unset) {\n-\t\tfree_new_trailers(trailers);\n+\t\tfree_trailer_templates(templates);\n \t\treturn 0;\n \t}\n \n@@ -93,10 +97,10 @@ static int option_parse_trailer(const struct option *opt,\n \t\tif (if_missing != MISSING_DEFAULT)\n \t\t\ttrailer_set_conf_if_missing(if_missing, conf_current);\n \n-\t\ttrailer_add_arg_item(trailers,\n-\t\t\t\t     strbuf_detach(&tok, NULL),\n+\t\tadd_trailer_template(strbuf_detach(&tok, NULL),\n \t\t\t\t     strbuf_detach(&val, NULL),\n-\t\t\t\t     conf_current);\n+\t\t\t\t     conf_current,\n+\t\t\t\t     templates);\n \t\tfree_trailer_conf(conf_current);\n \t}\n \n@@ -158,10 +162,10 @@ static void read_input_file(struct strbuf *sb, const char *file)\n }\n \n static void interpret_trailers(const struct process_trailer_options *opts,\n-\t\t\t       struct list_head *new_trailer_head,\n+\t\t\t       struct list_head *templates,\n \t\t\t       const char *file)\n {\n-\tLIST_HEAD(head);\n+\tLIST_HEAD(trailers_from_sb);\n \tstruct strbuf sb = STRBUF_INIT;\n \tstruct strbuf tb = STRBUF_INIT;\n \tstruct trailer_block *trailer_block;\n@@ -172,7 +176,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \tif (opts->in_place)\n \t\toutfile = create_in_place_tempfile(file);\n \n-\ttrailer_block = parse_trailers(opts, sb.buf, &head);\n+\ttrailer_block = parse_trailers(opts, sb.buf, &trailers_from_sb);\n \n \t/* Print the lines before the trailer block */\n \tif (!opts->only_trailers)\n@@ -182,11 +186,11 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \t\tfprintf(outfile, \"\\n\");\n \n \tif (!opts->only_input)\n-\t\tprocess_trailers_lists(&head, new_trailer_head);\n+\t\tapply_trailer_templates(templates, &trailers_from_sb);\n \n \t/* Print trailer block. */\n-\tformat_trailers(opts, &head, &tb);\n-\tfree_trailers(&head);\n+\tformat_trailers(opts, &trailers_from_sb, &tb);\n+\tfree_trailers(&trailers_from_sb);\n \tfwrite(tb.buf, 1, tb.len, outfile);\n \tstrbuf_release(&tb);\n \n@@ -206,8 +210,8 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n {\n \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n-\tLIST_HEAD(configured_trailers);\n-\tLIST_HEAD(trailers);\n+\tLIST_HEAD(configured_templates);\n+\tLIST_HEAD(templates);\n \n \tstruct option options[] = {\n \t\tOPT_BOOL(0, \"in-place\", &opts.in_place, N_(\"edit files in place\")),\n@@ -226,8 +230,8 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK_F(0, \"parse\", &opts, NULL, N_(\"alias for --only-trailers --only-input --unfold\"),\n \t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse),\n \t\tOPT_BOOL(0, \"no-divider\", &opts.no_divider, N_(\"do not treat \\\"---\\\" as the end of input\")),\n-\t\tOPT_CALLBACK(0, \"trailer\", &trailers, N_(\"trailer\"),\n-\t\t\t\tN_(\"trailer(s) to add\"), option_parse_trailer),\n+\t\tOPT_CALLBACK(0, \"trailer\", &templates, N_(\"trailer\"),\n+\t\t\t\tN_(\"trailer(s) to add\"), option_parse_trailer_template),\n \t\tOPT_END()\n \t};\n \n@@ -235,30 +239,30 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n \ttrailer_config_init();\n \n \tif (!opts.only_input)\n-\t\tparse_trailers_from_config(&configured_trailers);\n+\t\tparse_trailer_templates_from_config(&configured_templates);\n \n \targc = parse_options(argc, argv, prefix, options,\n \t\t\t     git_interpret_trailers_usage, 0);\n \n-\tif (opts.only_input && !list_empty(&trailers))\n+\tif (opts.only_input && !list_empty(&templates))\n \t\tusage_msg_opt(\n \t\t\t_(\"--trailer with --only-input does not make sense\"),\n \t\t\tgit_interpret_trailers_usage,\n \t\t\toptions);\n \n-\tlist_splice(&configured_trailers, &trailers);\n+\tlist_splice(&configured_templates, &templates);\n \n \tif (argc) {\n \t\tint i;\n \t\tfor (i = 0; i < argc; i++)\n-\t\t\tinterpret_trailers(&opts, &trailers, argv[i]);\n+\t\t\tinterpret_trailers(&opts, &templates, argv[i]);\n \t} else {\n \t\tif (opts.in_place)\n \t\t\tdie(_(\"no input file given for in-place editing\"));\n-\t\tinterpret_trailers(&opts, &trailers, NULL);\n+\t\tinterpret_trailers(&opts, &templates, NULL);\n \t}\n \n-\tfree_new_trailers(&trailers);\n+\tfree_trailer_templates(&templates);\n \n \treturn 0;\n }\ndiff --git a/trailer.c b/trailer.c\nindex 7b0bdfcb27e..cdb235c27b5 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -55,14 +55,14 @@ struct trailer_item {\n \tchar *value;\n };\n \n-struct arg_item {\n+struct trailer_template {\n \tstruct list_head list;\n \tchar *token;\n \tchar *value;\n \tstruct trailer_conf conf;\n };\n \n-static LIST_HEAD(conf_head);\n+static LIST_HEAD(templates_from_conf);\n \n static char *separators = \":\";\n \n@@ -105,7 +105,7 @@ static size_t token_len_without_separator(const char *token, size_t len)\n \treturn len;\n }\n \n-static int same_token(struct trailer_item *a, struct arg_item *b)\n+static int same_token(struct trailer_item *a, struct trailer_template *b)\n {\n \tsize_t a_len, b_len, min_len;\n \n@@ -119,12 +119,12 @@ static int same_token(struct trailer_item *a, struct arg_item *b)\n \treturn !strncasecmp(a->token, b->token, min_len);\n }\n \n-static int same_value(struct trailer_item *a, struct arg_item *b)\n+static int same_value(struct trailer_item *a, struct trailer_template *b)\n {\n \treturn !strcasecmp(a->value, b->value);\n }\n \n-static int same_trailer(struct trailer_item *a, struct arg_item *b)\n+static int same_trailer(struct trailer_item *a, struct trailer_template *b)\n {\n \treturn same_token(a, b) && same_value(a, b);\n }\n@@ -151,15 +151,15 @@ static void free_trailer_item(struct trailer_item *item)\n \tfree(item);\n }\n \n-static void free_arg_item(struct arg_item *item)\n+static void free_template(struct trailer_template *template)\n {\n-\tfree(item->conf.name);\n-\tfree(item->conf.key);\n-\tfree(item->conf.command);\n-\tfree(item->conf.cmd);\n-\tfree(item->token);\n-\tfree(item->value);\n-\tfree(item);\n+\tfree(template->conf.name);\n+\tfree(template->conf.key);\n+\tfree(template->conf.command);\n+\tfree(template->conf.cmd);\n+\tfree(template->token);\n+\tfree(template->value);\n+\tfree(template);\n }\n \n static char last_non_space_char(const char *s)\n@@ -171,36 +171,36 @@ static char last_non_space_char(const char *s)\n \treturn '\\0';\n }\n \n-static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)\n+static struct trailer_item *trailer_from(struct trailer_template *template)\n {\n \tstruct trailer_item *new_item = xcalloc(1, sizeof(*new_item));\n-\tnew_item->token = arg_tok->token;\n-\tnew_item->value = arg_tok->value;\n-\targ_tok->token = arg_tok->value = NULL;\n-\tfree_arg_item(arg_tok);\n+\tnew_item->token = template->token;\n+\tnew_item->value = template->value;\n+\ttemplate->token = template->value = NULL;\n+\tfree_template(template);\n \treturn new_item;\n }\n \n-static void add_arg_to_input_list(struct trailer_item *on_tok,\n-\t\t\t\t  struct arg_item *arg_tok)\n+static void apply_template_to_trailers(struct trailer_template *template,\n+\t\t\t\t       struct trailer_item *on_tok)\n {\n-\tint aoe = after_or_end(arg_tok->conf.where);\n-\tstruct trailer_item *to_add = trailer_from_arg(arg_tok);\n+\tint aoe = after_or_end(template->conf.where);\n+\tstruct trailer_item *to_add = trailer_from(template);\n \tif (aoe)\n \t\tlist_add(&to_add->list, &on_tok->list);\n \telse\n \t\tlist_add_tail(&to_add->list, &on_tok->list);\n }\n \n-static int check_if_different(struct trailer_item *in_tok,\n-\t\t\t      struct arg_item *arg_tok,\n-\t\t\t      int check_all,\n-\t\t\t      struct list_head *head)\n+static int check_if_different(struct trailer_template *template,\n+\t\t\t      struct trailer_item *in_tok,\n+\t\t\t      struct list_head *head,\n+\t\t\t      int check_all)\n {\n-\tenum trailer_where where = arg_tok->conf.where;\n+\tenum trailer_where where = template->conf.where;\n \tstruct list_head *next_head;\n \tdo {\n-\t\tif (same_trailer(in_tok, arg_tok))\n+\t\tif (same_trailer(in_tok, template))\n \t\t\treturn 0;\n \t\t/*\n \t\t * if we want to add a trailer after another one,\n@@ -215,7 +215,8 @@ static int check_if_different(struct trailer_item *in_tok,\n \treturn 1;\n }\n \n-static char *apply_command(struct trailer_conf *conf, const char *arg)\n+static char *run_command_from_template(struct trailer_conf *conf,\n+\t\t\t\t       const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -250,133 +251,142 @@ static char *apply_command(struct trailer_conf *conf, const char *arg)\n \treturn result;\n }\n \n-static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n+/*\n+ * Prepare the template by running the command (if any) requested by the\n+ * template in order to populate the template's value field.\n+ */\n+static void populate_template_value(struct trailer_template *template,\n+\t\t\t\t    struct trailer_item *in_tok)\n {\n-\tif (arg_tok->conf.command || arg_tok->conf.cmd) {\n+\tif (template->conf.command || template->conf.cmd) {\n+\t\t/*\n+\t\t * Determine argument to pass into the command.\n+\t\t */\n \t\tconst char *arg;\n-\t\tif (arg_tok->value && arg_tok->value[0]) {\n-\t\t\targ = arg_tok->value;\n+\t\tif (template->value && template->value[0]) {\n+\t\t\targ = template->value;\n \t\t} else {\n \t\t\tif (in_tok && in_tok->value)\n \t\t\t\targ = xstrdup(in_tok->value);\n \t\t\telse\n \t\t\t\targ = xstrdup(\"\");\n \t\t}\n-\t\targ_tok->value = apply_command(&arg_tok->conf, arg);\n+\t\ttemplate->value = run_command_from_template(&template->conf,\n+\t\t\t\t\t\t\t    arg);\n \t\tfree((char *)arg);\n \t}\n }\n \n-static void apply_arg_if_exists(struct trailer_item *in_tok,\n-\t\t\t\tstruct arg_item *arg_tok,\n+static void maybe_add_if_exists(struct trailer_template *template,\n+\t\t\t\tstruct trailer_item *in_tok,\n \t\t\t\tstruct trailer_item *on_tok,\n-\t\t\t\tstruct list_head *head)\n+\t\t\t\tstruct list_head *trailers)\n {\n-\tswitch (arg_tok->conf.if_exists) {\n+\tswitch (template->conf.if_exists) {\n \tcase EXISTS_DO_NOTHING:\n-\t\tfree_arg_item(arg_tok);\n+\t\tfree_template(template);\n \t\tbreak;\n \tcase EXISTS_REPLACE:\n-\t\tapply_item_command(in_tok, arg_tok);\n-\t\tadd_arg_to_input_list(on_tok, arg_tok);\n+\t\tpopulate_template_value(template, in_tok);\n+\t\tapply_template_to_trailers(template, on_tok);\n \t\tlist_del(&in_tok->list);\n \t\tfree_trailer_item(in_tok);\n \t\tbreak;\n \tcase EXISTS_ADD:\n-\t\tapply_item_command(in_tok, arg_tok);\n-\t\tadd_arg_to_input_list(on_tok, arg_tok);\n+\t\tpopulate_template_value(template, in_tok);\n+\t\tapply_template_to_trailers(template, on_tok);\n \t\tbreak;\n \tcase EXISTS_ADD_IF_DIFFERENT:\n-\t\tapply_item_command(in_tok, arg_tok);\n-\t\tif (check_if_different(in_tok, arg_tok, 1, head))\n-\t\t\tadd_arg_to_input_list(on_tok, arg_tok);\n+\t\tpopulate_template_value(template, in_tok);\n+\t\tif (check_if_different(template, in_tok, trailers, 1))\n+\t\t\tapply_template_to_trailers(template, on_tok);\n \t\telse\n-\t\t\tfree_arg_item(arg_tok);\n+\t\t\tfree_template(template);\n \t\tbreak;\n \tcase EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:\n-\t\tapply_item_command(in_tok, arg_tok);\n-\t\tif (check_if_different(on_tok, arg_tok, 0, head))\n-\t\t\tadd_arg_to_input_list(on_tok, arg_tok);\n+\t\tpopulate_template_value(template, in_tok);\n+\t\tif (check_if_different(template, on_tok, trailers, 0))\n+\t\t\tapply_template_to_trailers(template, on_tok);\n \t\telse\n-\t\t\tfree_arg_item(arg_tok);\n+\t\t\tfree_template(template);\n \t\tbreak;\n \tdefault:\n \t\tBUG(\"trailer.c: unhandled value %d\",\n-\t\t    arg_tok->conf.if_exists);\n+\t\t    template->conf.if_exists);\n \t}\n }\n \n-static void apply_arg_if_missing(struct list_head *head,\n-\t\t\t\t struct arg_item *arg_tok)\n+static void maybe_add_if_missing(struct trailer_template *template,\n+\t\t\t\t struct list_head *trailers)\n {\n \tenum trailer_where where;\n \tstruct trailer_item *to_add;\n \n-\tswitch (arg_tok->conf.if_missing) {\n+\tswitch (template->conf.if_missing) {\n \tcase MISSING_DO_NOTHING:\n-\t\tfree_arg_item(arg_tok);\n+\t\tfree_template(template);\n \t\tbreak;\n \tcase MISSING_ADD:\n-\t\twhere = arg_tok->conf.where;\n-\t\tapply_item_command(NULL, arg_tok);\n-\t\tto_add = trailer_from_arg(arg_tok);\n+\t\twhere = template->conf.where;\n+\t\tpopulate_template_value(template, NULL);\n+\t\tto_add = trailer_from(template);\n \t\tif (after_or_end(where))\n-\t\t\tlist_add_tail(&to_add->list, head);\n+\t\t\tlist_add_tail(&to_add->list, trailers);\n \t\telse\n-\t\t\tlist_add(&to_add->list, head);\n+\t\t\tlist_add(&to_add->list, trailers);\n \t\tbreak;\n \tdefault:\n \t\tBUG(\"trailer.c: unhandled value %d\",\n-\t\t    arg_tok->conf.if_missing);\n+\t\t    template->conf.if_missing);\n \t}\n }\n \n-static int find_same_and_apply_arg(struct list_head *head,\n-\t\t\t\t   struct arg_item *arg_tok)\n+static int find_same_and_apply_arg(struct trailer_template *template,\n+\t\t\t\t   struct list_head *trailers)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *in_tok;\n \tstruct trailer_item *on_tok;\n \n-\tenum trailer_where where = arg_tok->conf.where;\n+\tenum trailer_where where = template->conf.where;\n \tint middle = (where == WHERE_AFTER) || (where == WHERE_BEFORE);\n \tint backwards = after_or_end(where);\n \tstruct trailer_item *start_tok;\n \n-\tif (list_empty(head))\n+\tif (list_empty(trailers))\n \t\treturn 0;\n \n-\tstart_tok = list_entry(backwards ? head->prev : head->next,\n+\tstart_tok = list_entry(backwards ? trailers->prev : trailers->next,\n \t\t\t       struct trailer_item,\n \t\t\t       list);\n \n-\tlist_for_each_dir(pos, head, backwards) {\n+\tlist_for_each_dir(pos, trailers, backwards) {\n \t\tin_tok = list_entry(pos, struct trailer_item, list);\n-\t\tif (!same_token(in_tok, arg_tok))\n+\t\tif (!same_token(in_tok, template))\n \t\t\tcontinue;\n \t\ton_tok = middle ? in_tok : start_tok;\n-\t\tapply_arg_if_exists(in_tok, arg_tok, on_tok, head);\n+\t\tmaybe_add_if_exists(template, in_tok, on_tok, trailers);\n \t\treturn 1;\n \t}\n \treturn 0;\n }\n \n-void process_trailers_lists(struct list_head *head,\n-\t\t\t    struct list_head *arg_head)\n+void apply_trailer_templates(struct list_head *templates,\n+\t\t\t     struct list_head *trailers)\n {\n \tstruct list_head *pos, *p;\n-\tstruct arg_item *arg_tok;\n+\tstruct trailer_template *template;\n \n-\tlist_for_each_safe(pos, p, arg_head) {\n+\tlist_for_each_safe(pos, p, templates) {\n \t\tint applied = 0;\n-\t\targ_tok = list_entry(pos, struct arg_item, list);\n+\t\ttemplate = list_entry(pos, struct trailer_template, list);\n \n \t\tlist_del(pos);\n \n-\t\tapplied = find_same_and_apply_arg(head, arg_tok);\n+\t\tapplied = find_same_and_apply_arg(template, trailers);\n \n \t\tif (!applied)\n-\t\t\tapply_arg_if_missing(head, arg_tok);\n+\t\t\tmaybe_add_if_missing(template, trailers);\n \t}\n }\n \n@@ -471,26 +481,26 @@ void free_trailer_conf(struct trailer_conf *conf)\n \tfree(conf);\n }\n \n-static struct arg_item *get_conf_item(const char *name)\n+static struct trailer_template *get_or_add_template_by(const char *name)\n {\n \tstruct list_head *pos;\n-\tstruct arg_item *item;\n+\tstruct trailer_template *template;\n \n-\t/* Look up item with same name */\n-\tlist_for_each(pos, &conf_head) {\n-\t\titem = list_entry(pos, struct arg_item, list);\n-\t\tif (!strcasecmp(item->conf.name, name))\n-\t\t\treturn item;\n+\t/* Look up template with same name. */\n+\tlist_for_each(pos, &templates_from_conf) {\n+\t\ttemplate = list_entry(pos, struct trailer_template, list);\n+\t\tif (!strcasecmp(template->conf.name, name))\n+\t\t\treturn template;\n \t}\n \n-\t/* Item does not already exists, create it */\n-\tCALLOC_ARRAY(item, 1);\n-\tduplicate_trailer_conf(&item->conf, &default_trailer_conf);\n-\titem->conf.name = xstrdup(name);\n+\t/* Template does not already exist; create it. */\n+\tCALLOC_ARRAY(template, 1);\n+\tduplicate_trailer_conf(&template->conf, &default_trailer_conf);\n+\ttemplate->conf.name = xstrdup(name);\n \n-\tlist_add_tail(&item->list, &conf_head);\n+\tlist_add_tail(&template->list, &templates_from_conf);\n \n-\treturn item;\n+\treturn template;\n }\n \n enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_CMD,\n@@ -548,7 +558,7 @@ static int git_trailer_config(const char *conf_key, const char *value,\n \t\t\t      void *cb UNUSED)\n {\n \tconst char *trailer_item, *variable_name;\n-\tstruct arg_item *item;\n+\tstruct trailer_template *template;\n \tstruct trailer_conf *conf;\n \tchar *name = NULL;\n \tenum trailer_info_type type;\n@@ -573,8 +583,8 @@ static int git_trailer_config(const char *conf_key, const char *value,\n \tif (!name)\n \t\treturn 0;\n \n-\titem = get_conf_item(name);\n-\tconf = &item->conf;\n+\ttemplate = get_or_add_template_by(name);\n+\tconf = &template->conf;\n \tfree(name);\n \n \tswitch (type) {\n@@ -631,20 +641,22 @@ void trailer_config_init(void)\n \tconfigured = 1;\n }\n \n-static const char *token_from_item(struct arg_item *item, char *tok)\n+static const char *token_from_template(struct trailer_template *template, char *tok)\n {\n-\tif (item->conf.key)\n-\t\treturn item->conf.key;\n+\tif (template->conf.key)\n+\t\treturn template->conf.key;\n \tif (tok)\n \t\treturn tok;\n-\treturn item->conf.name;\n+\treturn template->conf.name;\n }\n \n-static int token_matches_item(const char *tok, struct arg_item *item, size_t tok_len)\n+static int token_matches_template(const char *tok,\n+\t\t\t\t  struct trailer_template *template,\n+\t\t\t\t  size_t tok_len)\n {\n-\tif (!strncasecmp(tok, item->conf.name, tok_len))\n+\tif (!strncasecmp(tok, template->conf.name, tok_len))\n \t\treturn 1;\n-\treturn item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0;\n+\treturn template->conf.key ? !strncasecmp(tok, template->conf.key, tok_len) : 0;\n }\n \n /*\n@@ -687,7 +699,7 @@ void parse_trailer(const char *line, ssize_t separator_pos,\n \t\t   struct strbuf *tok, struct strbuf *val,\n \t\t   const struct trailer_conf **conf)\n {\n-\tstruct arg_item *item;\n+\tstruct trailer_template *template;\n \tsize_t tok_len;\n \tstruct list_head *pos;\n \n@@ -705,13 +717,13 @@ void parse_trailer(const char *line, ssize_t separator_pos,\n \ttok_len = token_len_without_separator(tok->buf, tok->len);\n \tif (conf)\n \t\t*conf = &default_trailer_conf;\n-\tlist_for_each(pos, &conf_head) {\n-\t\titem = list_entry(pos, struct arg_item, list);\n-\t\tif (token_matches_item(tok->buf, item, tok_len)) {\n+\tlist_for_each(pos, &templates_from_conf) {\n+\t\ttemplate = list_entry(pos, struct trailer_template, list);\n+\t\tif (token_matches_template(tok->buf, template, tok_len)) {\n \t\t\tchar *tok_buf = strbuf_detach(tok, NULL);\n \t\t\tif (conf)\n-\t\t\t\t*conf = &item->conf;\n-\t\t\tstrbuf_addstr(tok, token_from_item(item, tok_buf));\n+\t\t\t\t*conf = &template->conf;\n+\t\t\tstrbuf_addstr(tok, token_from_template(template, tok_buf));\n \t\t\tfree(tok_buf);\n \t\t\tbreak;\n \t\t}\n@@ -728,29 +740,41 @@ static struct trailer_item *add_trailer_item(struct list_head *head, char *tok,\n \treturn new_item;\n }\n \n-void trailer_add_arg_item(struct list_head *arg_head, char *tok, char *val,\n-\t\t\t  const struct trailer_conf *conf)\n+void add_trailer_template(char *tok, char *val, const struct trailer_conf *conf,\n+\t\t\t  struct list_head *templates)\n {\n-\tstruct arg_item *new_item = xcalloc(1, sizeof(*new_item));\n-\tnew_item->token = tok;\n-\tnew_item->value = val;\n-\tduplicate_trailer_conf(&new_item->conf, conf);\n-\tlist_add_tail(&new_item->list, arg_head);\n+\tstruct trailer_template *template = xcalloc(1, sizeof(*template));\n+\ttemplate->token = tok;\n+\ttemplate->value = val;\n+\tduplicate_trailer_conf(&template->conf, conf);\n+\tlist_add_tail(&template->list, templates);\n }\n \n-void parse_trailers_from_config(struct list_head *config_head)\n+void parse_trailer_templates_from_config(struct list_head *config_head)\n {\n-\tstruct arg_item *item;\n+\tstruct trailer_template *template;\n \tstruct list_head *pos;\n \n-\t/* Add an arg item for each configured trailer with a command */\n-\tlist_for_each(pos, &conf_head) {\n-\t\titem = list_entry(pos, struct arg_item, list);\n-\t\tif (item->conf.command)\n-\t\t\ttrailer_add_arg_item(config_head,\n-\t\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n-\t\t\t\t\t     xstrdup(\"\"),\n-\t\t\t\t\t     &item->conf);\n+\t/*\n+\t * Get configured templates with a \".command\" option.\n+\t *\n+\t * NEEDSWORK: If the interpret-trailers builtin sees a\n+\t * \"trailer.foo.command = ...\" setting, then the \"foo\" trailer will\n+\t * always be inserted, even if \"--trailer foo\" is not provided.\n+\t * Considering how \".command\" is deprecated, it is a bit strange to see\n+\t * it getting special treatment like this over \".cmd\". Instead, we\n+\t * should add a new option that explicitly lets the user decide if the\n+\t * configured trailer should always be added automatically, or if it\n+\t * should only be added if \"--trailer foo\" is provided (default).\n+\t * Then we can collect configured trailers that have either \".command\"\n+\t * or \".cmd\" below, instead of just \".command\".\n+\t */\n+\tlist_for_each(pos, &templates_from_conf) {\n+\t\ttemplate = list_entry(pos, struct trailer_template, list);\n+\t\tif (template->conf.command)\n+\t\t\tadd_trailer_template(xstrdup(token_from_template(template,\n+\t\t\t\t\t\t\t\t\t NULL)),\n+\t\t\t\t\t     xstrdup(\"\"), &template->conf, config_head);\n \t}\n }\n \n@@ -906,10 +930,16 @@ static size_t find_trailer_block_start(const char *buf, size_t len)\n \t\t\tpossible_continuation_lines = 0;\n \t\t\tif (recognized_prefix)\n \t\t\t\tcontinue;\n-\t\t\tlist_for_each(pos, &conf_head) {\n-\t\t\t\tstruct arg_item *item;\n-\t\t\t\titem = list_entry(pos, struct arg_item, list);\n-\t\t\t\tif (token_matches_item(bol, item,\n+\t\t\t/*\n+\t\t\t * The templates here are not used for actually\n+\t\t\t * adding trailers anywhere, but instead to help us\n+\t\t\t * identify trailer lines by comparing their keys with\n+\t\t\t * those found in configured templates.\n+\t\t\t */\n+\t\t\tlist_for_each(pos, &templates_from_conf) {\n+\t\t\t\tstruct trailer_template *template;\n+\t\t\t\ttemplate = list_entry(pos, struct trailer_template, list);\n+\t\t\t\tif (token_matches_template(bol, template,\n \t\t\t\t\t\t       separator_pos)) {\n \t\t\t\t\trecognized_prefix = 1;\n \t\t\t\t\tbreak;\n@@ -1065,13 +1095,13 @@ void free_trailers(struct list_head *trailers)\n \t}\n }\n \n-void free_new_trailers(struct list_head *trailers)\n+void free_trailer_templates(struct list_head *trailer_templates)\n {\n \tstruct list_head *pos, *p;\n \n-\tlist_for_each_safe(pos, p, trailers) {\n+\tlist_for_each_safe(pos, p, trailer_templates) {\n \t\tlist_del(pos);\n-\t\tfree_arg_item(list_entry(pos, struct arg_item, list));\n+\t\tfree_template(list_entry(pos, struct trailer_template, list));\n \t}\n }\n \ndiff --git a/trailer.h b/trailer.h\nindex 4193bedbae4..2a21d74c263 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -40,8 +40,8 @@ struct trailer_conf *new_trailer_conf(void);\n void duplicate_trailer_conf(struct trailer_conf *dst,\n \t\t\t    const struct trailer_conf *src);\n const char *trailer_default_separators(void);\n-void trailer_add_arg_item(struct list_head *arg_head, char *tok, char *val,\n-\t\t\t  const struct trailer_conf *conf);\n+void add_trailer_template(char *tok, char *val, const struct trailer_conf *,\n+\t\t\t  struct list_head *templates);\n \n struct process_trailer_options {\n \tint in_place;\n@@ -60,10 +60,10 @@ struct process_trailer_options {\n \n #define PROCESS_TRAILER_OPTIONS_INIT {0}\n \n-void parse_trailers_from_config(struct list_head *config_head);\n+void parse_trailer_templates_from_config(struct list_head *config_head);\n \n-void process_trailers_lists(struct list_head *head,\n-\t\t\t    struct list_head *arg_head);\n+void apply_trailer_templates(struct list_head *templates,\n+\t\t\t     struct list_head *trailers_head);\n \n ssize_t find_separator(const char *line, const char *separators);\n \n@@ -86,8 +86,8 @@ void format_trailers(const struct process_trailer_options *,\n \t\t     struct list_head *trailers,\n \t\t     struct strbuf *out);\n void free_trailers(struct list_head *);\n-void free_new_trailers(struct list_head *);\n void free_trailer_conf(struct trailer_conf *);\n+void free_trailer_templates(struct list_head *);\n \n /*\n  * Convenience function to format the trailers from the commit msg \"msg\" into\n-- \ngitgitgadget\n"},{"id":"488318","messageId":"xmqqjznde235.fsf@gitster.g","threadId":"60715","inReplyTo":"f5f0d06613f2701af9bd3a1a9274aae232e03d8f.1707196348.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 19/28] trailer: make trailer_info struct private","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-09T21:53:50Z","receivedAt":"2024-02-09T21:53:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> This patch believes that the benefits outweight the advantages for\n> designing APIs, as explained below.\n\n\"outweigh the disadvantages\"?\n\n"},{"id":"488319","messageId":"xmqqeddle233.fsf@gitster.g","threadId":"60715","inReplyTo":"31725832224e3d6b14066af8a87eaf4ab589179e.1707196348.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 16/28] trailer: finish formatting unification","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-09T21:53:52Z","receivedAt":"2024-02-09T21:53:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Linus Arver <linusa@google.com>\n>\n> Rename format_trailer_info() to format_trailers(). Finally, both\n> interpret-trailers and format_trailers_from_commit() can call\n> \"format_trailers()\"!\n>\n> Update the comment in <trailer.h> to remove the (now obsolete) caveats\n> about format_trailers_from_commit().\n\nNice.\n"},{"id":"488320","messageId":"xmqq8r3te232.fsf@gitster.g","threadId":"60715","inReplyTo":"a72eca301f7f9016ef3a8063f79790ce00f41ffe.1707196348.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 12/28] format_trailer_info(): append newline for non-trailer lines","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-09T21:53:53Z","receivedAt":"2024-02-09T21:53:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> And before we made the transition to use trailer_item objects for it,\n> format_trailer_info() called parse_trailer() (which trims newlines) for\n> trailer lines but did _not_ call parse_trailer() for non-trailer lines.\n> So for trailer lines it had to add back the trimmed newline like this\n>\n>     if (!opts->separator)\n>         strbuf_addch(out, '\\n');\n>\n> But for non-trailer lines it didn't have to add back the newline because\n> it could just reuse same string in the \"trailers\" string array (which\n> again, already included the trailing newline).\n>\n> Now that format_trailer_info() uses trailer_item objects for all cases,\n> it can't rely on \"trailers\" string array anymore.  And so it must be\n> taught to add a newline back when printing non-trailer lines, just like\n> it already does for trailer lines. Do so now.\n\nVery nicely explained.\n\n> The test suite passes again, so format_trailer_info() is in better shape\n> supersede format_trailers(), which we'll do in the next patch.\n>\n> Signed-off-by: Linus Arver <linusa@google.com>\n> ---\n>  trailer.c | 5 +++--\n>  1 file changed, 3 insertions(+), 2 deletions(-)\n>\n> diff --git a/trailer.c b/trailer.c\n> index 12cae5b73d2..0774a544c4f 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> @@ -1125,9 +1125,10 @@ static void format_trailer_info(const struct process_trailer_options *opts,\n>  \t\t\t\tstrbuf_addbuf(out, opts->separator);\n>  \t\t\t}\n>  \t\t\tstrbuf_addstr(out, item->value);\n> -\t\t\tif (opts->separator) {\n> +\t\t\tif (opts->separator)\n>  \t\t\t\tstrbuf_rtrim(out);\n> -\t\t\t}\n> +\t\t\telse\n> +\t\t\t\tstrbuf_addch(out, '\\n');\n>  \t\t}\n>  \t}\n>  }\n"},{"id":"488321","messageId":"xmqqwmrdcnif.fsf@gitster.g","threadId":"60715","inReplyTo":"457f2a839d5da9da225e842275bbf8b15f194f1f.1707196348.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 11/28] format_trailer_info(): drop redundant unfold_value()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-09T21:54:00Z","receivedAt":"2024-02-09T21:54:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Linus Arver <linusa@google.com>\n>\n> This is another preparatory refactor to unify the trailer formatters.\n>\n> In the last patch we made format_trailer_info() use trailer_item objects\n> instead of the \"trailers\" string array. This means that the call to\n> unfold_value() here is redundant because the trailer_item objects are\n> already unfolded in parse_trailers() which is a dependency of our\n> caller, format_trailers_from_commit().\n>\n> Remove the redundant call.\n\nOK.  The previous step had this hunk:\n\n-\t\t\tparse_trailer(&tok, &val, NULL, trailer, separator_pos);\n \t\t\tif (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n \t\t\t\tif (opts->unfold)\n \t\t\t\t\tunfold_value(&val);\n\nwhere parse_trailers() already has a call to parse_trailer()\nfollowed by a call to unfold_value(), so in a sense, switching\nto use the result of calling parse_trailers() by the caller instead\nof duplicating our own parsing in format_trailer_info() that started\nat step [09/28] made both parse_trailer() call (removed in step [10/28])\nand unfold_value() call (removed in this step [11/28]).\n\nSo it would have also made sense if this were done as part of\n[10/28], but it is also OK to keep them separated.\n\nIn either way, breaking the transition into these steps does make\nthem easier to follow.\n\n> Signed-off-by: Linus Arver <linusa@google.com>\n> ---\n>  trailer.c | 3 ---\n>  1 file changed, 3 deletions(-)\n>\n> diff --git a/trailer.c b/trailer.c\n> index 6333dfe1c11..12cae5b73d2 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> @@ -1102,9 +1102,6 @@ static void format_trailer_info(const struct process_trailer_options *opts,\n>  \t\t\tstrbuf_addstr(&val, item->value);\n>  \n>  \t\t\tif (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n> -\t\t\t\tif (opts->unfold)\n> -\t\t\t\t\tunfold_value(&val);\n> -\n>  \t\t\t\tif (opts->separator && out->len != origlen)\n>  \t\t\t\t\tstrbuf_addbuf(out, opts->separator);\n>  \t\t\t\tif (!opts->value_only)\n"},{"id":"488322","messageId":"xmqq34u1e22y.fsf@gitster.g","threadId":"60715","inReplyTo":"f5b7ba08aa7c80a3bd5bcbf5563eac8896fe7054.1707196348.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 10/28] format_trailer_info(): use trailer_item objects","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-09T21:53:57Z","receivedAt":"2024-02-09T21:54:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Linus Arver <linusa@google.com>\n>\n> This is another preparatory refactor to unify the trailer formatters.\n>\n> Make format_trailer_info() operate on trailer_item objects, not the raw\n> string array.\n>\n> This breaks t4205 and t6300. We will continue to make improvements until\n> the test suite passes again, ultimately renaming format_trailer_info()\n> to format_trailers(), at which point the unification of these formatters\n> will be complete.\n>\n> Signed-off-by: Linus Arver <linusa@google.com>\n> ---\n>  trailer.c | 21 ++++++++++-----------\n>  1 file changed, 10 insertions(+), 11 deletions(-)\n\nI would have expected a (tentative) flip from test_expect_success to\ntest_expect_failure in the affected tests, to illustrate what behaviour\nchange this step introduces and why.\n\nBut the huge single step in the previous round broken out into\nsmaller steps like this round makes them much easier to follow.\n\nThanks.\n\n> diff --git a/trailer.c b/trailer.c\n> index e6665c99cc3..6333dfe1c11 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> @@ -1086,21 +1086,21 @@ void trailer_info_release(struct trailer_info *info)\n>  }\n>  \n>  static void format_trailer_info(const struct process_trailer_options *opts,\n> -\t\t\t\tconst struct trailer_info *info,\n> +\t\t\t\tstruct list_head *trailers,\n>  \t\t\t\tstruct strbuf *out)\n>  {\n>  \tsize_t origlen = out->len;\n> -\tsize_t i;\n> -\n> -\tfor (i = 0; i < info->trailer_nr; i++) {\n> -\t\tchar *trailer = info->trailers[i];\n> -\t\tssize_t separator_pos = find_separator(trailer, separators);\n> +\tstruct list_head *pos;\n> +\tstruct trailer_item *item;\n>  \n> -\t\tif (separator_pos >= 1) {\n> +\tlist_for_each(pos, trailers) {\n> +\t\titem = list_entry(pos, struct trailer_item, list);\n> +\t\tif (item->token) {\n>  \t\t\tstruct strbuf tok = STRBUF_INIT;\n>  \t\t\tstruct strbuf val = STRBUF_INIT;\n> +\t\t\tstrbuf_addstr(&tok, item->token);\n> +\t\t\tstrbuf_addstr(&val, item->value);\n>  \n> -\t\t\tparse_trailer(&tok, &val, NULL, trailer, separator_pos);\n>  \t\t\tif (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n>  \t\t\t\tif (opts->unfold)\n>  \t\t\t\t\tunfold_value(&val);\n> @@ -1127,13 +1127,12 @@ static void format_trailer_info(const struct process_trailer_options *opts,\n>  \t\t\tif (opts->separator && out->len != origlen) {\n>  \t\t\t\tstrbuf_addbuf(out, opts->separator);\n>  \t\t\t}\n> -\t\t\tstrbuf_addstr(out, trailer);\n> +\t\t\tstrbuf_addstr(out, item->value);\n>  \t\t\tif (opts->separator) {\n>  \t\t\t\tstrbuf_rtrim(out);\n>  \t\t\t}\n>  \t\t}\n>  \t}\n> -\n>  }\n>  \n>  void format_trailers_from_commit(const struct process_trailer_options *opts,\n> @@ -1152,7 +1151,7 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n>  \t\tstrbuf_add(out, msg + info.trailer_block_start,\n>  \t\t\t   info.trailer_block_end - info.trailer_block_start);\n>  \t} else\n> -\t\tformat_trailer_info(opts, &info, out);\n> +\t\tformat_trailer_info(opts, &trailers, out);\n>  \n>  \tfree_trailers(&trailers);\n>  \ttrailer_info_release(&info);\n"},{"id":"488482","messageId":"CAP8UFD3qLGua5PTA+i29N+yJH8iKVBPwUn=S606B2E+s959JFQ@mail.gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 00/28] Enrich Trailer API","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-12T23:37:23Z","receivedAt":"2024-02-12T23:37:37Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> This patch series is the first 10 patches of a larger cleanup/bugfix series\n> (henceforth \"larger series\") I've been working on.\n\nThere are now 28 patches in this series.\n\nI took a look at all of them, and I think that this series should be\nsplit into 4 or more series.\n\nFor example perhaps one series until the \"trailer: move\ninterpret_trailers() to interpret-trailers.c\" patch, then another one\nuntil \"trailer: finish formatting unification\". etc.\n\nAlso I think it might be possible to avoid some test failures\nintroduced by some patches. If it's not possible, I agree with Junio\nthat it would be nice if the failing tests were changed to use\n'test_expect_failure'.\n\nAlso it seems to me that many patches towards the end of this series\nshould be split.\n\n> The main goal of this\n> series is to begin the process of \"libifying\" the trailer API. By \"API\" I\n> mean the interface exposed in trailer.h. The larger series brings a number\n> of additional cleanups (exposing and fixing some bugs along the way), and\n> builds on top of this series.\n\n[...]\n\n> With the libification-focused goals out of the way, let's turn to this patch\n> series in more detail.\n\nI like the goal of libifying Git the trailer API, and the way you want\nto do it seems reasonable to me.\n\n[...]\n\n> In summary this series breaks up \"process_trailers()\" into smaller pieces,\n> exposing many of the parts relevant to trailer-related processing in\n> trailer.h. This will force us to eventually introduce unit tests for these\n> API functions, but that is a good thing for API stability.\n\nI am a bit sad that this series doesn't introduce unit tests using the\nnew test framework in C yet. I understand that this series is mostly a\nbig refactoring and maybe it's better to introduce unit tests only\nwhen the refactoring is finished though. Anyway I hope the next series\nwill introduce such tests.\n\n> In the future after libification is \"complete\", users external to Git will\n> be able to use the same trailer processing API used by the\n> interpret-trailers builtin. For example, a web server may want to parse\n> trailers the same way that Git would parse them, without having to call\n> interpret-trailers as a subprocess. This use case was the original\n> motivation behind my work in this area.\n\nThanks for telling us about this use case.\n\n> Thanks to the aggressive refactoring in this series, I've been able to\n> identify and fix several bugs in our existing implementation. Those fixes\n> build on top of this series but were not included here, in order to keep\n> this series small. Below is a \"shortlog\" of those fixes I have locally:\n>\n>  * \"trailer: trailer replacement should not change its position\" (If we\n>    found a trailer we'd like to replace, preserve its position relative to\n>    the other trailers found in the trailer block, instead of always moving\n>    it to the beginning or end of the entire trailer block.)\n\nI believe there was a reason why it was done this way. I don't\nremember what it was though.\n\n>  * \"interpret-trailers: preserve trailers coming from the input\" (Sometimes,\n>    the parsed trailers from the input will be formatted differently\n>    depending on whether we provide --only-trailers or not. Make the trailers\n>    that were not modified and which are coming directly from the input get\n>    formatted the same way, regardless of this flag.)\n\nIt could be a feature to be able to normalize trailers in a certain way.\n\n>  * \"interpret-trailers: do not modify the input if NOP\" (Refrain from\n>    subtracting or adding a newline around the patch divider \"---\" if we are\n>    not adding new trailers.)\n\nIt could be a feature to be able to normalize this too.\n\n>  * \"trailer formatter: split up format_trailer() monolith\" (Fix a bug in\n>    git-log where we still printed a blank newline even if we didn't want to\n>    format anything.)\n\nI am not sure this is a bug fix either. It could perhaps be a normalization too.\n\n>  * \"interpret-trailers: fail if given unrecognized arguments\" (E.g., for\n>    \"--where\", only accept recognized WHERE_* enum values. If we get\n>    something unrecognized, fail with an error instead of silently doing\n>    nothing. Ditto for \"--if-exists\" and \"--if-missing\".)\n\nIt's possible that there was a reason why it was done this way.\n\nI think you might want to take a look at the discussions on the\nmailing list when \"interpret-trailers\" was developed. There were a lot\nof discussions over a long time, and there were a lot of requests and\nsuggestions about what it should do.\n"},{"id":"488483","messageId":"CAP8UFD1YaEfdM=apQ7An+GcKV+Wc26StsMqJcd9e5tHJg9U_hQ@mail.gmail.com","threadId":"60715","inReplyTo":"a72eca301f7f9016ef3a8063f79790ce00f41ffe.1707196348.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 12/28] format_trailer_info(): append newline for non-trailer lines","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-12T23:37:54Z","receivedAt":"2024-02-12T23:38:07Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n\n[...]\n\n> The test suite passes again, so format_trailer_info() is in better shape\n> supersede format_trailers(), which we'll do in the next patch.\n\ns/supersede/to supersede/\n"},{"id":"488484","messageId":"CAP8UFD3u+QDx2LqpO2ZpeHQszwjMAsQ90qqbE7Om=t1vPRQ==w@mail.gmail.com","threadId":"60715","inReplyTo":"ba1f387747b08a7270f7387beddd75dc4a8eddfe.1707196348.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 15/28] format_trailer_info(): avoid double-printing the separator","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-12T23:38:12Z","receivedAt":"2024-02-12T23:38:25Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Linus Arver <linusa@google.com>\n>\n> Do not hardcode the printing of \": \" as the separator and space (which\n> can result in double-printing these characters); instead only\n> print the separator and space if we cannot find any recognized separator\n> somewhere in the key (yes, keys may have a trailing separator in it ---\n> we will eventually fix this design but not now). Do so by copying the\n> code out of print_tok_val(), and deleting the same function.\n>\n> The test suite passes again with this change.\n\nI think it should be clearer above that this fixes a bug that was\nintroduced earlier in the series.\n\nAlso I wonder why it was not possible to modify format_trailer_info()\nlike it is done in this patch before using it to replace\nformat_trailers().\n"},{"id":"488485","messageId":"CAP8UFD3wMD5SmfpP20jST4YndBdPX9U9qAbKh=4TnUzLkpULRA@mail.gmail.com","threadId":"60715","inReplyTo":"11f854399db2b0da5d82cad910c3b86ca9c2e0db.1707196348.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 14/28] format_trailer_info(): teach it about opts->trim_empty","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-12T23:38:26Z","receivedAt":"2024-02-12T23:38:39Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"> diff --git a/trailer.c b/trailer.c\n> index f4defad3dae..c28b6c11cc5 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> @@ -162,20 +162,6 @@ static void print_tok_val(struct strbuf *out, const char *tok, const char *val)\n>                 strbuf_addf(out, \"%s%c %s\\n\", tok, separators[0], val);\n>  }\n>\n> -static void format_trailers(const struct process_trailer_options *opts,\n> -                           struct list_head *trailers,\n> -                           struct strbuf *out)\n> -{\n> -       struct list_head *pos;\n> -       struct trailer_item *item;\n> -       list_for_each(pos, trailers) {\n> -               item = list_entry(pos, struct trailer_item, list);\n> -               if ((!opts->trim_empty || strlen(item->value) > 0) &&\n> -                   (!opts->only_trailers || item->token))\n> -                       print_tok_val(out, item->token, item->value);\n> -       }\n> -}\n\nIt seems to me that this function could and should have been removed\nin the previous patch. If there is a reason why it is better to do it\nin this patch, I think it should be explained more clearly in the\ncommit message.\n\n>  static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)\n>  {\n>         struct trailer_item *new_item = xcalloc(1, sizeof(*new_item));\n> @@ -1101,6 +1087,15 @@ void format_trailer_info(const struct process_trailer_options *opts,\n>                         strbuf_addstr(&tok, item->token);\n>                         strbuf_addstr(&val, item->value);\n>\n> +                       /*\n> +                        * Skip key/value pairs where the value was empty. This\n> +                        * can happen from trailers specified without a\n> +                        * separator, like `--trailer \"Reviewed-by\"` (no\n> +                        * corresponding value).\n> +                        */\n> +                       if (opts->trim_empty && !strlen(item->value))\n> +                               continue;\n> +\n\nWasn't it possible to make this change in format_trailer_info() before\nusing format_trailer_info() to replace format_trailers()?\n\n\n>                         if (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n>                                 if (opts->separator && out->len != origlen)\n>                                         strbuf_addbuf(out, opts->separator);\n"},{"id":"488486","messageId":"CAP8UFD283wk9b+D+s3azcg9DHjaonaq6-eY1KDHkP8UWZYVXQg@mail.gmail.com","threadId":"60715","inReplyTo":"31725832224e3d6b14066af8a87eaf4ab589179e.1707196348.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 16/28] trailer: finish formatting unification","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-12T23:38:38Z","receivedAt":"2024-02-12T23:38:52Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n\n>  /*\n> - * Format the trailers from the commit msg \"msg\" into the strbuf \"out\".\n> - * Note two caveats about \"opts\":\n> - *\n> - *   - this is primarily a helper for pretty.c, and not\n> - *     all of the flags are supported.\n> - *\n> - *   - this differs from process_trailers slightly in that we always format\n> - *     only the trailer block itself, even if the \"only_trailers\" option is not\n> - *     set.\n\nThis makes me wonder if there was actually a good reason why\nformat_trailers() and format_trailer_info() were 2 different\nfunctions. Is there info about this in the commit message of the\ncommit which introduced this comment?\n"},{"id":"488487","messageId":"CAP8UFD1twELGKvvesxgCrZrypKZpgSt04ira3mvurG1UbpDfxQ@mail.gmail.com","threadId":"60715","inReplyTo":"f5f0d06613f2701af9bd3a1a9274aae232e03d8f.1707196348.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 19/28] trailer: make trailer_info struct private","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-12T23:38:51Z","receivedAt":"2024-02-12T23:39:04Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n\n> diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\n> index 11f4ce9e4a2..6bf8cec005a 100644\n> --- a/builtin/interpret-trailers.c\n> +++ b/builtin/interpret-trailers.c\n> @@ -141,7 +141,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n>         LIST_HEAD(head);\n>         struct strbuf sb = STRBUF_INIT;\n>         struct strbuf trailer_block = STRBUF_INIT;\n> -       struct trailer_info info;\n> +       struct trailer_info *info;\n>         FILE *outfile = stdout;\n>\n>         trailer_config_init();\n> @@ -151,13 +151,13 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n>         if (opts->in_place)\n>                 outfile = create_in_place_tempfile(file);\n>\n> -       parse_trailers(opts, &info, sb.buf, &head);\n> +       info = parse_trailers(opts, sb.buf, &head);\n\nI think this patch might be doing too much at once and could have been\nsplit into 3 or more patches to make reviews easier.\n\nFor example the first patch could introduce trailer_info_new() and\nmake interpret_trailers() use it. Then the second patch could modify\nparse_trailers() so that it returns a 'struct trailer_info *'. Then\nthe third patch could make the trailer_info struct private.\n"},{"id":"488488","messageId":"CAP8UFD3ZRfrG4s5jox55dYMRF7UT3uYMkyMraEGWRJ2HqBEYZA@mail.gmail.com","threadId":"60715","inReplyTo":"38f4b4c4135dfebc06c2b1d5c56854af4b07fedc.1707196348.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 21/28] trailer: spread usage of \"trailer_block\" language","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-12T23:39:03Z","receivedAt":"2024-02-12T23:39:18Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Linus Arver <linusa@google.com>\n>\n> Deprecate the \"trailer_info\" struct name and replace it with\n> \"trailer_block\". The main reason is to help readability, because\n> \"trailer_info\" on the surface sounds like it's about a single trailer\n> when in reality it is a collection of contiguous lines, at least 25% of\n> which are trailers.\n>\n> Signed-off-by: Linus Arver <linusa@google.com>\n> ---\n>  builtin/interpret-trailers.c | 25 +++++-----\n>  trailer.c                    | 97 ++++++++++++++++++------------------\n>  trailer.h                    | 18 +++----\n>  3 files changed, 71 insertions(+), 69 deletions(-)\n>\n> diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\n> index 6bf8cec005a..f76841c5280 100644\n> --- a/builtin/interpret-trailers.c\n> +++ b/builtin/interpret-trailers.c\n> @@ -140,8 +140,8 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n>  {\n>         LIST_HEAD(head);\n>         struct strbuf sb = STRBUF_INIT;\n> -       struct strbuf trailer_block = STRBUF_INIT;\n> -       struct trailer_info *info;\n> +       struct strbuf tb = STRBUF_INIT;\n> +       struct trailer_block *trailer_block;\n\nI understand that using 'trailer_block' for a 'struct trailer_block *'\nmakes sense and I like the idea behind this patch, but it's\nunfortunate that 'struct strbuf trailer_block' becomes 'struct strbuf\ntb'. Also the name change for 'struct strbuf trailer_block' could be\nin a separate patch.\n"},{"id":"488489","messageId":"CAP8UFD3pcyRYaJLBmiDdN3sdvRzo==dx-CQKoX1w1n6Hsg5+-g@mail.gmail.com","threadId":"60715","inReplyTo":"94bf182e3ffbf8ed6e20cd77b2e46e5b83c44d34.1707196348.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 22/28] trailer: prepare to delete \"parse_trailers_from_command_line_args()\"","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-12T23:39:12Z","receivedAt":"2024-02-12T23:39:27Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Linus Arver <linusa@google.com>\n>\n> Expose more functions in the trailer.h API, in preparation for deleting\n>\n>     parse_trailers_from_command_line_args()\n>\n> from the trailers implementation, because it should not be concerned\n> with command line arguments (as they have nothing to do with trailers\n> themselves). Indeed, the interpret-trailers builtin is the only caller\n> of this function inside Git.\n>\n> Rename add_arg_item() to trailer_add_arg_item() to expose it as an API\n> function. Rename new_trailers_clear() to free_new_trailers() because it\n> will be promoted into an API function; the API already has\n> free_trailers(), so using the \"free_*\" naming style will keep it\n> consistent. Also rename \"conf_info\" to \"trailer_conf\" for readability,\n> dropping the low-value \"_info\" suffix as we did earlier in this series\n> for \"trailer_info\" to \"trailer_block\".\n\nThat's a lot done in a single patch. I think splitting it into 3 or\nmore patches would be nice for reviewers.\n"},{"id":"488490","messageId":"CAP8UFD0XjHtwB55XDajJia994TmUwub4L73QxhCQ8yTXc8++Ww@mail.gmail.com","threadId":"60715","inReplyTo":"3bfe4809ecbc5aa0ea52daee7684289398cb88d4.1707196348.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 23/28] trailer: add new helper functions to API","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-12T23:39:21Z","receivedAt":"2024-02-12T23:39:35Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Linus Arver <linusa@google.com>\n>\n> This is a preparatory refactor for deprecating \"new_trailer_item\" from\n> the API (which will let us deprecate\n> parse_trailers_from_command_line_args()).\n>\n> Expose new helper functions from the API, because we'll be calling them\n> from interpret-trailers.c soon when we move\n> parse_trailers_from_command_line_args() there.\n>\n> Move free_new_trailers() from the builtin to trailer.c because later on\n> we will adjust it to free arg_item structs, which are private to\n> trailer.c.\n\nThis patch seems to be also doing too much.\n"},{"id":"488491","messageId":"CAP8UFD3u8qNpxObdOJDfBq+zVfxNwAG56bBcPeSC4i2=ZuhWjw@mail.gmail.com","threadId":"60715","inReplyTo":"9720526dd8a63b916c75fe9d6322ee13c8b36621.1707196348.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 26/28] trailer: unify \"--trailer ...\" arg handling","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-12T23:39:29Z","receivedAt":"2024-02-12T23:39:43Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Linus Arver <linusa@google.com>\n>\n> Move the logic of parse_trailer_from_command_line_arg() into\n> option_parse_trailer(), because that is the only caller and there's no\n> benefit in keeping these two separate.\n\nWell one benefit could be that 2 small functions might be easier to\nunderstand than a big one. So perhaps\nparse_trailer_from_command_line_arg() could just have been made\nstatic?\n"},{"id":"488492","messageId":"CAP8UFD2-M20uuo4G8HykRL=1g9634wKUT9R6DBG6nmLBqGpxzw@mail.gmail.com","threadId":"60715","inReplyTo":"26df2514acbf4d51f40f4b1b9f33a357fa424ac7.1707196348.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 27/28] trailer_set_*(): put out parameter at the end","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-12T23:39:44Z","receivedAt":"2024-02-12T23:39:58Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Linus Arver <linusa@google.com>\n>\n> The new trailer_config_set_*() functions which were introduced a few\n> patches ago put the out parameter (the variable being mutated) at the\n> end of the parameter list.\n>\n> Put the out parameter at the end for these functions for these existing\n\ns/for these functions for these/for the/\n\n\n> trailer_set_*() functions for consistency. This also avoids confusion\n> for API users because otherwise these two sets of functions look rather\n> similar in <trailer.h> even though they have completely different out\n> parameters.\n"},{"id":"488493","messageId":"CAP8UFD01SPF8bqAb2Kn-q7SfX9cYiU_tpDSZz1CnEFbUqQpXYA@mail.gmail.com","threadId":"60715","inReplyTo":"14927038d855020f9ae7594ad5cc646257613cc1.1707196348.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 28/28] trailer: introduce \"template\" term for readability","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-12T23:40:02Z","receivedAt":"2024-02-12T23:40:15Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Linus Arver <linusa@google.com>\n>\n> The term \"arg_item\" is ambiguous because we use it to hold data for\n>\n>   (1) trailers specified as command line arguments (in\n>       builtin/interpret-trailers.c), and\n>\n>   (2) trailers specified in configuration,\n>\n> and these are both used to ultimately insert new trailers (based\n> on the contents of arg_item, acting as a kind of template) into some\n> other set of existing trailers (such as those found in a trailer block\n> inside a log message) that have already been parsed.\n>\n> Rename \"arg_item\" to \"trailer_template\". This necessitates further\n> renames to make the functions that act on these templates match the data\n> structures (parameters) they act on:\n>\n>   - [*] add_arg_to_input_list()      to apply_template_to_trailers()\n>   - [*] apply_arg_if_exists()        to maybe_add_if_exists()\n>   - [*] apply_arg_if_missing()       to maybe_add_if_missing()\n>   -     apply_command()              to run_command_from_template()\n>   - [*] apply_item_command()         to populate_template_value()\n>   -     free_arg_item()              to free_template() (non-API function)\n>   -     free_new_trailers()          to free_trailer_templates() (API function)\n>   -     get_conf_item()              to get_or_add_template_by()\n>   -     option_parse_trailer()       to option_parse_trailer_template()\n>   -     parse_trailers_from_config() to parse_trailer_templates_from_config()\n>   - [*] process_trailers_lists()     to apply_trailer_templates()\n>   -     token_from_item()            to token_from_template()\n>   -     token_matches_item           to token_matches_template\n>   - [*] trailer_add_arg_item()       to add_trailer_template()\n>   -     trailer_from_arg()           to trailer_from()\n>   - [*] check_if_different()         (reorder parameters only)\n>   - [*] find_same_and_apply_arg()    (reorder parameters only)\n>\n> Reorder parameters to prefer input parameters toward the beginning and\n> out parameters at the end; these functions have been marked with an\n> asterisk ([*]).\n\nThat's a lot of changes in a single patch.\n\n> This removes the \"arg\" terminology (standing for \"CLI arguments\") from\n> the trailer implementation, which makes sense because trailers\n> themselves have nothing to do with CLI argument handling.\n>\n> Also note that these renames expose the previously liberal use of\n> \"trailer\" to mean both trailers we read from the input text (trailer\n> block) and trailer templates that are defined as CLI args or\n> configurations. Some functions implied a single action when they could\n> do two different things, so introduce words like \"maybe\" and \"or\" to\n> make their behavior more explicit.\n>\n> In summary this patch renames and reorders parameters for readability,\n> without any behavioral change. We don't rename\n> find_same_and_apply_arg(), because it will be refactored soon.\n>\n> For parse_trailers_from_config() (renamed to\n> parse_trailer_templates_from_config()), add a NEEDSWORK discussion about\n> how the deprecated trailer.*.command configuration option is oddly more\n> featureful than trailer.*.cmd (if we were to remove support for\n> trailer.*.command, users would not be able to replicate the behavior\n> with trailer.*.cmd and would lose out on functionality).\n\nThis change could be in a separate patch. Also there were discussions\nwhen trailer.*.command was deprecated and trailer.*.cmd introduced. I\nthink it might be useful to talk about them in the commit message of\nthe separate patch introducing the NEEDSWORK.\n"},{"id":"488497","messageId":"xmqqjzn94404.fsf@gitster.g","threadId":"60715","inReplyTo":"CAP8UFD3qLGua5PTA+i29N+yJH8iKVBPwUn=S606B2E+s959JFQ@mail.gmail.com","subject":"Re: [PATCH v4 00/28] Enrich Trailer API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-13T00:11:39Z","receivedAt":"2024-02-13T00:11:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> This patch series is the first 10 patches of a larger cleanup/bugfix series\n>> (henceforth \"larger series\") I've been working on.\n>\n> There are now 28 patches in this series.\n>\n> I took a look at all of them, and I think that this series should be\n> split into 4 or more series.\n\nI presume that [01-09/28] would be the first part, nothing\ncontroversial and consisting of obvious clean-ups?  I do not mind\nmerging that part down to remove the future review load if everybody\nagrees.\n\nThanks.\n"},{"id":"488502","messageId":"CAP8UFD0C37qdTUvCpRFe6_zAeAcssoySY6tobw+AO8hpA8iAfg@mail.gmail.com","threadId":"60715","inReplyTo":"xmqqjzn94404.fsf@gitster.g","subject":"Re: [PATCH v4 00/28] Enrich Trailer API","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-13T06:55:44Z","receivedAt":"2024-02-13T06:55:57Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Feb 13, 2024 at 1:11 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n> > On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n> > <gitgitgadget@gmail.com> wrote:\n> >>\n> >> This patch series is the first 10 patches of a larger cleanup/bugfix series\n> >> (henceforth \"larger series\") I've been working on.\n> >\n> > There are now 28 patches in this series.\n> >\n> > I took a look at all of them, and I think that this series should be\n> > split into 4 or more series.\n>\n> I presume that [01-09/28] would be the first part, nothing\n> controversial and consisting of obvious clean-ups?  I do not mind\n> merging that part down to remove the future review load if everybody\n> agrees.\n\nYeah, patches [01-09/28] look good to me. If we are merging them\nwithout the rest, we might want to change a bit the last sentence in\n09/28 which says:\n\n\"In the next patch, we'll change format_trailer_info() to use the\nparsed trailer_item objects instead of the string array.\"\n\nMaybe just: s/In the next patch/In a future patch/\n"},{"id":"488536","messageId":"owly1q9gcof3.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqq34u1e22y.fsf@gitster.g","subject":"Re: [PATCH v4 10/28] format_trailer_info(): use trailer_item objects","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-13T16:35:44Z","receivedAt":"2024-02-13T16:35:46Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> From: Linus Arver <linusa@google.com>\n>>\n>> This is another preparatory refactor to unify the trailer formatters.\n>>\n>> Make format_trailer_info() operate on trailer_item objects, not the raw\n>> string array.\n>>\n>> This breaks t4205 and t6300. We will continue to make improvements until\n>> the test suite passes again, ultimately renaming format_trailer_info()\n>> to format_trailers(), at which point the unification of these formatters\n>> will be complete.\n>>\n>> Signed-off-by: Linus Arver <linusa@google.com>\n>> ---\n>>  trailer.c | 21 ++++++++++-----------\n>>  1 file changed, 10 insertions(+), 11 deletions(-)\n>\n> I would have expected a (tentative) flip from test_expect_success to\n> test_expect_failure in the affected tests, to illustrate what behaviour\n> change this step introduces and why.\n\nSomehow, such a simple idea did not cross my mind (even though I admit I\ndidn't like how I was breaking tests, albeit temporarily). Will update\non next reroll.\n\n> But the huge single step in the previous round broken out into\n> smaller steps like this round makes them much easier to follow.\n\nGreat, I'm doing something right for once! ;)\n"},{"id":"488537","messageId":"owlywmr8b97y.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"CAP8UFD1YaEfdM=apQ7An+GcKV+Wc26StsMqJcd9e5tHJg9U_hQ@mail.gmail.com","subject":"Re: [PATCH v4 12/28] format_trailer_info(): append newline for non-trailer lines","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-13T16:49:21Z","receivedAt":"2024-02-13T16:49:23Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>\n> [...]\n>\n>> The test suite passes again, so format_trailer_info() is in better shape\n>> supersede format_trailers(), which we'll do in the next patch.\n>\n> s/supersede/to supersede/\n\nNice catch! Will update for next reroll, thanks.\n"},{"id":"488539","messageId":"owlyttmcb8ge.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"CAP8UFD3wMD5SmfpP20jST4YndBdPX9U9qAbKh=4TnUzLkpULRA@mail.gmail.com","subject":"Re: [PATCH v4 14/28] format_trailer_info(): teach it about opts->trim_empty","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-13T17:05:53Z","receivedAt":"2024-02-13T17:05:55Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n>> diff --git a/trailer.c b/trailer.c\n>> index f4defad3dae..c28b6c11cc5 100644\n>> --- a/trailer.c\n>> +++ b/trailer.c\n>> @@ -162,20 +162,6 @@ static void print_tok_val(struct strbuf *out, const char *tok, const char *val)\n>>                 strbuf_addf(out, \"%s%c %s\\n\", tok, separators[0], val);\n>>  }\n>>\n>> -static void format_trailers(const struct process_trailer_options *opts,\n>> -                           struct list_head *trailers,\n>> -                           struct strbuf *out)\n>> -{\n>> -       struct list_head *pos;\n>> -       struct trailer_item *item;\n>> -       list_for_each(pos, trailers) {\n>> -               item = list_entry(pos, struct trailer_item, list);\n>> -               if ((!opts->trim_empty || strlen(item->value) > 0) &&\n>> -                   (!opts->only_trailers || item->token))\n>> -                       print_tok_val(out, item->token, item->value);\n>> -       }\n>> -}\n>\n> It seems to me that this function could and should have been removed\n> in the previous patch. If there is a reason why it is better to do it\n> in this patch, I think it should be explained more clearly in the\n> commit message.\n\nAh yes, the decision to delay the deletion like this was deliberate.\nWill update the commit message to add something like:\n\n    Although we could have deleted format_trailers() in the previous patch,\n    we perform the deletion here like this in order to isolate\n    (and highlight) in this patch the salvaging of the logic in the deleted\n    code\n\n        if ((!opts->trim_empty || strlen(item->value) > 0) && ...)\n            print_tok_val(...)\n\n    as\n\n        if (opts->trim_empty && !strlen(item->value))\n            continue;\n\n    in the new code, which has the same effect (because we are skipping the\n    formatting in the new code).\n\n>>  static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)\n>>  {\n>>         struct trailer_item *new_item = xcalloc(1, sizeof(*new_item));\n>> @@ -1101,6 +1087,15 @@ void format_trailer_info(const struct process_trailer_options *opts,\n>>                         strbuf_addstr(&tok, item->token);\n>>                         strbuf_addstr(&val, item->value);\n>>\n>> +                       /*\n>> +                        * Skip key/value pairs where the value was empty. This\n>> +                        * can happen from trailers specified without a\n>> +                        * separator, like `--trailer \"Reviewed-by\"` (no\n>> +                        * corresponding value).\n>> +                        */\n>> +                       if (opts->trim_empty && !strlen(item->value))\n>> +                               continue;\n>> +\n>\n> Wasn't it possible to make this change in format_trailer_info() before\n> using format_trailer_info() to replace format_trailers()?\n\nIt was certainly possible, but the choice to purposely time the\naddition/deletion of code like this was deliberate (see my comment\nabove).\n\n>>                         if (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n>>                                 if (opts->separator && out->len != origlen)\n>>                                         strbuf_addbuf(out, opts->separator);\n"},{"id":"488543","messageId":"owlyr0hgb7qg.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"CAP8UFD3u+QDx2LqpO2ZpeHQszwjMAsQ90qqbE7Om=t1vPRQ==w@mail.gmail.com","subject":"Re: [PATCH v4 15/28] format_trailer_info(): avoid double-printing the separator","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-13T17:21:27Z","receivedAt":"2024-02-13T17:21:29Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> From: Linus Arver <linusa@google.com>\n>>\n>> Do not hardcode the printing of \": \" as the separator and space (which\n>> can result in double-printing these characters); instead only\n>> print the separator and space if we cannot find any recognized separator\n>> somewhere in the key (yes, keys may have a trailing separator in it ---\n>> we will eventually fix this design but not now). Do so by copying the\n>> code out of print_tok_val(), and deleting the same function.\n>>\n>> The test suite passes again with this change.\n>\n> I think it should be clearer above that this fixes a bug that was\n> introduced earlier in the series.\n\nAck, will add something like\n\n    This double printing is from a bug introduced earlier when we\n    started using format_trailer_info() everywhere.\n\nto this patch's description, but also add explicit language in\n\"trailer: begin formatting unification\" to say that the change is\nintroducing temporary bugs (and that this is why the tests break).\n\n> Also I wonder why it was not possible to modify format_trailer_info()\n> like it is done in this patch before using it to replace\n> format_trailers().\n\nThe artificial organization apparent in this patch was deliberate, in\norder to make it painfully obvious exactly what was being replaced and\nhow. See https://lore.kernel.org/git/xmqqjzno13ev.fsf@gitster.g/\n"},{"id":"488544","messageId":"CAP8UFD3Mx8TT0vZgNNAVBw1L3QBbRtbRL_mR1KGe6FNK77oMeg@mail.gmail.com","threadId":"60715","inReplyTo":"owlyttmcb8ge.fsf@fine.c.googlers.com","subject":"Re: [PATCH v4 14/28] format_trailer_info(): teach it about opts->trim_empty","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-13T17:21:39Z","receivedAt":"2024-02-13T17:21:53Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Feb 13, 2024 at 6:05 PM Linus Arver <linusa@google.com> wrote:\n> Christian Couder <christian.couder@gmail.com> writes:\n\n> >>  static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)\n> >>  {\n> >>         struct trailer_item *new_item = xcalloc(1, sizeof(*new_item));\n> >> @@ -1101,6 +1087,15 @@ void format_trailer_info(const struct process_trailer_options *opts,\n> >>                         strbuf_addstr(&tok, item->token);\n> >>                         strbuf_addstr(&val, item->value);\n> >>\n> >> +                       /*\n> >> +                        * Skip key/value pairs where the value was empty. This\n> >> +                        * can happen from trailers specified without a\n> >> +                        * separator, like `--trailer \"Reviewed-by\"` (no\n> >> +                        * corresponding value).\n> >> +                        */\n> >> +                       if (opts->trim_empty && !strlen(item->value))\n> >> +                               continue;\n> >> +\n> >\n> > Wasn't it possible to make this change in format_trailer_info() before\n> > using format_trailer_info() to replace format_trailers()?\n>\n> It was certainly possible, but the choice to purposely time the\n> addition/deletion of code like this was deliberate (see my comment\n> above).\n\nI would have thought that it would be better to make this change\nearlier to avoid breaking tests.\n\n> >>                         if (!opts->filter || opts->filter(&tok, opts->filter_data)) {\n> >>                                 if (opts->separator && out->len != origlen)\n> >>                                         strbuf_addbuf(out, opts->separator);\n"},{"id":"488545","messageId":"CAP8UFD0nmK4ZigW9LcWOr_POEX5LX7m+T=Jq9rK34YL5C6xatw@mail.gmail.com","threadId":"60715","inReplyTo":"owlyr0hgb7qg.fsf@fine.c.googlers.com","subject":"Re: [PATCH v4 15/28] format_trailer_info(): avoid double-printing the separator","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-13T17:25:06Z","receivedAt":"2024-02-13T17:25:20Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Feb 13, 2024 at 6:21 PM Linus Arver <linusa@google.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n> > On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n> > <gitgitgadget@gmail.com> wrote:\n\n> > Also I wonder why it was not possible to modify format_trailer_info()\n> > like it is done in this patch before using it to replace\n> > format_trailers().\n>\n> The artificial organization apparent in this patch was deliberate, in\n> order to make it painfully obvious exactly what was being replaced and\n> how. See https://lore.kernel.org/git/xmqqjzno13ev.fsf@gitster.g/\n\nAs for the previous patch, I would have thought that it would be\nbetter not to break the tests.\n"},{"id":"488548","messageId":"owlyo7ckb7bf.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"CAP8UFD283wk9b+D+s3azcg9DHjaonaq6-eY1KDHkP8UWZYVXQg@mail.gmail.com","subject":"Re: [PATCH v4 16/28] trailer: finish formatting unification","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-13T17:30:28Z","receivedAt":"2024-02-13T17:30:30Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>\n>>  /*\n>> - * Format the trailers from the commit msg \"msg\" into the strbuf \"out\".\n>> - * Note two caveats about \"opts\":\n>> - *\n>> - *   - this is primarily a helper for pretty.c, and not\n>> - *     all of the flags are supported.\n>> - *\n>> - *   - this differs from process_trailers slightly in that we always format\n>> - *     only the trailer block itself, even if the \"only_trailers\" option is not\n>> - *     set.\n>\n> This makes me wonder if there was actually a good reason why\n> format_trailers() and format_trailer_info() were 2 different\n> functions. Is there info about this in the commit message of the\n> commit which introduced this comment?\n\nGood question. I see a388b10fc1 (pretty: move trailer formatting to\ntrailer.c, 2017-08-15) and there it says:\n\n    pretty: move trailer formatting to trailer.c\n    \n    The next commit will add many features to the %(trailer)\n    placeholder in pretty.c. We'll need to access some internal\n    functions of trailer.c for that, so our options are either:\n    \n      1. expose those functions publicly\n    \n    or\n    \n      2. make an entry point into trailer.c to do the formatting\n    \n    Doing (2) ends up exposing less surface area, though do note\n    that caveats in the docstring of the new function.\n\nso it looks like this function started out from pretty.c and did not\nhave access to all of the trailer implementation internals, and was\nnever intended to replace (unify) the formatting machinery in trailer.c\nboth before and after that commit. This seems like good information to\ninclude in the commit message, so I will do so in the next reroll.\n\nAside: it is interesting that the current patch series is taking the\ndirection of (1) instead of (2) as was done in the past (we had a choice\nto \"libify\" at that time but did not do so, in order to \"expos[e] less\nsurface area [in <trailer.h>]\").\n"},{"id":"488549","messageId":"xmqqa5o46zla.fsf@gitster.g","threadId":"60715","inReplyTo":"CAP8UFD0C37qdTUvCpRFe6_zAeAcssoySY6tobw+AO8hpA8iAfg@mail.gmail.com","subject":"Re: [PATCH v4 00/28] Enrich Trailer API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-13T17:30:57Z","receivedAt":"2024-02-13T17:31:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n>> > I took a look at all of them, and I think that this series should be\n>> > split into 4 or more series.\n>>\n>> I presume that [01-09/28] would be the first part, nothing\n>> controversial and consisting of obvious clean-ups?  I do not mind\n>> merging that part down to remove the future review load if everybody\n>> agrees.\n>\n> Yeah, patches [01-09/28] look good to me.\n\nI was hoping that you'll give us more details of what the other 3 or\nmore you would envision the series to be, actually.\n\n"},{"id":"488552","messageId":"owlyle7ob70r.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqqjznde235.fsf@gitster.g","subject":"Re: [PATCH v4 19/28] trailer: make trailer_info struct private","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-13T17:36:52Z","receivedAt":"2024-02-13T17:36:55Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> This patch believes that the benefits outweight the advantages for\n>> designing APIs, as explained below.\n>\n> \"outweigh the disadvantages\"?\n\nWill update, thanks.\n"},{"id":"488554","messageId":"owlyil2sb6tf.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"CAP8UFD1twELGKvvesxgCrZrypKZpgSt04ira3mvurG1UbpDfxQ@mail.gmail.com","subject":"Re: [PATCH v4 19/28] trailer: make trailer_info struct private","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-13T17:41:16Z","receivedAt":"2024-02-13T17:41:18Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>\n>> diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\n>> index 11f4ce9e4a2..6bf8cec005a 100644\n>> --- a/builtin/interpret-trailers.c\n>> +++ b/builtin/interpret-trailers.c\n>> @@ -141,7 +141,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n>>         LIST_HEAD(head);\n>>         struct strbuf sb = STRBUF_INIT;\n>>         struct strbuf trailer_block = STRBUF_INIT;\n>> -       struct trailer_info info;\n>> +       struct trailer_info *info;\n>>         FILE *outfile = stdout;\n>>\n>>         trailer_config_init();\n>> @@ -151,13 +151,13 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n>>         if (opts->in_place)\n>>                 outfile = create_in_place_tempfile(file);\n>>\n>> -       parse_trailers(opts, &info, sb.buf, &head);\n>> +       info = parse_trailers(opts, sb.buf, &head);\n>\n> I think this patch might be doing too much at once and could have been\n> split into 3 or more patches to make reviews easier.\n\nAck, will do.\n\n> For example the first patch could introduce trailer_info_new() and\n> make interpret_trailers() use it. Then the second patch could modify\n> parse_trailers() so that it returns a 'struct trailer_info *'. Then\n> the third patch could make the trailer_info struct private.\n\nThanks, I will try to use this breakdown in the next reroll.\n"},{"id":"488555","messageId":"owlyfrxwb6jt.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"CAP8UFD3ZRfrG4s5jox55dYMRF7UT3uYMkyMraEGWRJ2HqBEYZA@mail.gmail.com","subject":"Re: [PATCH v4 21/28] trailer: spread usage of \"trailer_block\" language","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-13T17:47:02Z","receivedAt":"2024-02-13T17:47:04Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> From: Linus Arver <linusa@google.com>\n>>\n>> Deprecate the \"trailer_info\" struct name and replace it with\n>> \"trailer_block\". The main reason is to help readability, because\n>> \"trailer_info\" on the surface sounds like it's about a single trailer\n>> when in reality it is a collection of contiguous lines, at least 25% of\n>> which are trailers.\n>>\n>> Signed-off-by: Linus Arver <linusa@google.com>\n>> ---\n>>  builtin/interpret-trailers.c | 25 +++++-----\n>>  trailer.c                    | 97 ++++++++++++++++++------------------\n>>  trailer.h                    | 18 +++----\n>>  3 files changed, 71 insertions(+), 69 deletions(-)\n>>\n>> diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\n>> index 6bf8cec005a..f76841c5280 100644\n>> --- a/builtin/interpret-trailers.c\n>> +++ b/builtin/interpret-trailers.c\n>> @@ -140,8 +140,8 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n>>  {\n>>         LIST_HEAD(head);\n>>         struct strbuf sb = STRBUF_INIT;\n>> -       struct strbuf trailer_block = STRBUF_INIT;\n>> -       struct trailer_info *info;\n>> +       struct strbuf tb = STRBUF_INIT;\n>> +       struct trailer_block *trailer_block;\n>\n> I understand that using 'trailer_block' for a 'struct trailer_block *'\n> makes sense and I like the idea behind this patch, but it's\n> unfortunate that 'struct strbuf trailer_block' becomes 'struct strbuf\n> tb'.\n\nI confess I did not like the name \"tb\" either. It does have \"symmetry in\nnaming with 'sb' above\" going for it, but that symmetry is only on the\nsurface because the \"b\" in \"sb\" stands for \"buf\" (buffer),\nnot \"block\" as is the case for \"tb\".\n\nLet me try to clean up the naming scheme a bit around here.\n\n> Also the name change for 'struct strbuf trailer_block' could be\n> in a separate patch.\n\nWill do, thanks.\n"},{"id":"488557","messageId":"owlycyt0b688.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"CAP8UFD3pcyRYaJLBmiDdN3sdvRzo==dx-CQKoX1w1n6Hsg5+-g@mail.gmail.com","subject":"Re: [PATCH v4 22/28] trailer: prepare to delete \"parse_trailers_from_command_line_args()\"","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-13T17:53:59Z","receivedAt":"2024-02-13T17:54:01Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> From: Linus Arver <linusa@google.com>\n>>\n>> Expose more functions in the trailer.h API, in preparation for deleting\n>>\n>>     parse_trailers_from_command_line_args()\n>>\n>> from the trailers implementation, because it should not be concerned\n>> with command line arguments (as they have nothing to do with trailers\n>> themselves). Indeed, the interpret-trailers builtin is the only caller\n>> of this function inside Git.\n>>\n>> Rename add_arg_item() to trailer_add_arg_item() to expose it as an API\n>> function. Rename new_trailers_clear() to free_new_trailers() because it\n>> will be promoted into an API function; the API already has\n>> free_trailers(), so using the \"free_*\" naming style will keep it\n>> consistent. Also rename \"conf_info\" to \"trailer_conf\" for readability,\n>> dropping the low-value \"_info\" suffix as we did earlier in this series\n>> for \"trailer_info\" to \"trailer_block\".\n>\n> That's a lot done in a single patch. I think splitting it into 3 or\n> more patches would be nice for reviewers.\n\nThank you for pointing this out (and, for everyone else reading, please\ndon't hesitate to ask for this from me); will do in the next reroll.\n"},{"id":"488558","messageId":"owlya5o4b61x.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"CAP8UFD0XjHtwB55XDajJia994TmUwub4L73QxhCQ8yTXc8++Ww@mail.gmail.com","subject":"Re: [PATCH v4 23/28] trailer: add new helper functions to API","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-13T17:57:46Z","receivedAt":"2024-02-13T17:57:48Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> From: Linus Arver <linusa@google.com>\n>>\n>> This is a preparatory refactor for deprecating \"new_trailer_item\" from\n>> the API (which will let us deprecate\n>> parse_trailers_from_command_line_args()).\n>>\n>> Expose new helper functions from the API, because we'll be calling them\n>> from interpret-trailers.c soon when we move\n>> parse_trailers_from_command_line_args() there.\n>>\n>> Move free_new_trailers() from the builtin to trailer.c because later on\n>> we will adjust it to free arg_item structs, which are private to\n>> trailer.c.\n>\n> This patch seems to be also doing too much.\n\nI assume you mean that you'd like for the movement of\nfree_new_trailers() to be separated into its own patch, separate from\nthe introduction of new helper functions. I agree.\n\nWill update.\n"},{"id":"488559","messageId":"owly7cj8b5d6.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"CAP8UFD3u8qNpxObdOJDfBq+zVfxNwAG56bBcPeSC4i2=ZuhWjw@mail.gmail.com","subject":"Re: [PATCH v4 26/28] trailer: unify \"--trailer ...\" arg handling","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-13T18:12:37Z","receivedAt":"2024-02-13T18:12:39Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> From: Linus Arver <linusa@google.com>\n>>\n>> Move the logic of parse_trailer_from_command_line_arg() into\n>> option_parse_trailer(), because that is the only caller and there's no\n>> benefit in keeping these two separate.\n>\n> Well one benefit could be that 2 small functions might be easier to\n> understand than a big one.\n\nTrue.\n\n> So perhaps\n> parse_trailer_from_command_line_arg() could just have been made\n> static?\n\nIn this case I don't think keeping these two functions separate would\nmake sense because parse_trailer_from_command_line_arg() is much more\nheavyweight than option_parse_trailer() (one is just a thin wrapper\naround the other). And I didn't like the thought of having 2 function\nnames that look very different:\n\n    parse_trailer_from_command_line_arg()\n    option_parse_trailer()\n\nbe so closely related in behavior.\n\nAnd I already have some more patches (not in this series) that refactors\nthis area a bit also, so I wanted to wait until the dust settled down a\nbit before deciding (esp. when unit tests are added) whether keep this\nfunction separate. It's not clear to me yet whether we do want to add\nunit tests for parse_trailer_from_command_line_arg() (if we do end up\n\"resurrecting it\" in this patch or later), so IDK. The main reason is\nbecause I think the first set of unit tests should be for the exposed\nfunctions in <trailer.h>, not so much the helper functions that only the\nbuiltin uses.\n"},{"id":"488560","messageId":"owly4jecb5a6.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"CAP8UFD2-M20uuo4G8HykRL=1g9634wKUT9R6DBG6nmLBqGpxzw@mail.gmail.com","subject":"Re: [PATCH v4 27/28] trailer_set_*(): put out parameter at the end","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-13T18:14:25Z","receivedAt":"2024-02-13T18:14:27Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> From: Linus Arver <linusa@google.com>\n>>\n>> The new trailer_config_set_*() functions which were introduced a few\n>> patches ago put the out parameter (the variable being mutated) at the\n>> end of the parameter list.\n>>\n>> Put the out parameter at the end for these functions for these existing\n>\n> s/for these functions for these/for the/\n\nThat wording is much better; will update.\n\n>> trailer_set_*() functions for consistency. This also avoids confusion\n>> for API users because otherwise these two sets of functions look rather\n>> similar in <trailer.h> even though they have completely different out\n>> parameters.\n"},{"id":"488561","messageId":"owly1q9gb50m.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"CAP8UFD01SPF8bqAb2Kn-q7SfX9cYiU_tpDSZz1CnEFbUqQpXYA@mail.gmail.com","subject":"Re: [PATCH v4 28/28] trailer: introduce \"template\" term for readability","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-13T18:20:09Z","receivedAt":"2024-02-13T18:20:11Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> From: Linus Arver <linusa@google.com>\n>>\n>> The term \"arg_item\" is ambiguous because we use it to hold data for\n>>\n>>   (1) trailers specified as command line arguments (in\n>>       builtin/interpret-trailers.c), and\n>>\n>>   (2) trailers specified in configuration,\n>>\n>> and these are both used to ultimately insert new trailers (based\n>> on the contents of arg_item, acting as a kind of template) into some\n>> other set of existing trailers (such as those found in a trailer block\n>> inside a log message) that have already been parsed.\n>>\n>> Rename \"arg_item\" to \"trailer_template\". This necessitates further\n>> renames to make the functions that act on these templates match the data\n>> structures (parameters) they act on:\n>>\n>>   - [*] add_arg_to_input_list()      to apply_template_to_trailers()\n>>   - [*] apply_arg_if_exists()        to maybe_add_if_exists()\n>>   - [*] apply_arg_if_missing()       to maybe_add_if_missing()\n>>   -     apply_command()              to run_command_from_template()\n>>   - [*] apply_item_command()         to populate_template_value()\n>>   -     free_arg_item()              to free_template() (non-API function)\n>>   -     free_new_trailers()          to free_trailer_templates() (API function)\n>>   -     get_conf_item()              to get_or_add_template_by()\n>>   -     option_parse_trailer()       to option_parse_trailer_template()\n>>   -     parse_trailers_from_config() to parse_trailer_templates_from_config()\n>>   - [*] process_trailers_lists()     to apply_trailer_templates()\n>>   -     token_from_item()            to token_from_template()\n>>   -     token_matches_item           to token_matches_template\n>>   - [*] trailer_add_arg_item()       to add_trailer_template()\n>>   -     trailer_from_arg()           to trailer_from()\n>>   - [*] check_if_different()         (reorder parameters only)\n>>   - [*] find_same_and_apply_arg()    (reorder parameters only)\n>>\n>> Reorder parameters to prefer input parameters toward the beginning and\n>> out parameters at the end; these functions have been marked with an\n>> asterisk ([*]).\n>\n> That's a lot of changes in a single patch.\n\nI confess I was not happy with the volume of the change either. I\nsuppose I could break things down into renames vs parameter reorderings.\nWill update.\n\n>> This removes the \"arg\" terminology (standing for \"CLI arguments\") from\n>> the trailer implementation, which makes sense because trailers\n>> themselves have nothing to do with CLI argument handling.\n>>\n>> Also note that these renames expose the previously liberal use of\n>> \"trailer\" to mean both trailers we read from the input text (trailer\n>> block) and trailer templates that are defined as CLI args or\n>> configurations. Some functions implied a single action when they could\n>> do two different things, so introduce words like \"maybe\" and \"or\" to\n>> make their behavior more explicit.\n>>\n>> In summary this patch renames and reorders parameters for readability,\n>> without any behavioral change. We don't rename\n>> find_same_and_apply_arg(), because it will be refactored soon.\n>>\n>> For parse_trailers_from_config() (renamed to\n>> parse_trailer_templates_from_config()), add a NEEDSWORK discussion about\n>> how the deprecated trailer.*.command configuration option is oddly more\n>> featureful than trailer.*.cmd (if we were to remove support for\n>> trailer.*.command, users would not be able to replicate the behavior\n>> with trailer.*.cmd and would lose out on functionality).\n>\n> This change could be in a separate patch. Also there were discussions\n> when trailer.*.command was deprecated and trailer.*.cmd introduced. I\n> think it might be useful to talk about them in the commit message of\n> the separate patch introducing the NEEDSWORK.\n\nWill do (will reference the commit that introduced trailer.*.cmd also),\nthanks.\n"},{"id":"488566","messageId":"owlywmr89ms9.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"CAP8UFD3qLGua5PTA+i29N+yJH8iKVBPwUn=S606B2E+s959JFQ@mail.gmail.com","subject":"Re: [PATCH v4 00/28] Enrich Trailer API","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-13T19:39:18Z","receivedAt":"2024-02-13T19:39:20Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> This patch series is the first 10 patches of a larger cleanup/bugfix series\n>> (henceforth \"larger series\") I've been working on.\n>\n> There are now 28 patches in this series.\n>\n> I took a look at all of them, and I think that this series should be\n> split into 4 or more series.\n\nThis sounds fine to me. IIRC this means for the 2nd, 3rd+ series I have\nto remember to say \"this series builds on top of the other topic\nbranches '...'\" in the cover letter. Now that I've written this out I\nwill hopefully not forget to do this...\n\nOr, I suppose I could just introduce the 1st sub-series, wait for that\nto get queued to next, then (re)introduce the 2nd sub-series, etc, in\norder. Hmm. I think this will be simpler.\n\n> For example perhaps one series until the \"trailer: move\n> interpret_trailers() to interpret-trailers.c\" patch, then another one\n> until \"trailer: finish formatting unification\". etc.\n>\n> Also I think it might be possible to avoid some test failures\n> introduced by some patches. If it's not possible, I agree with Junio\n> that it would be nice if the failing tests were changed to use\n> 'test_expect_failure'.\n\nAs the breakages are deliberate, I will have to go with using\n\"test_expect_failure\".\n\n> Also it seems to me that many patches towards the end of this series\n> should be split.\n\nIn hindsight, I fully agree.\n\nAside: I am delighted with the quality of reviews on this project. It's\nnot something I am used to, so please bear with me while I try to break\nold habits. Thanks.\n\n>> The main goal of this\n>> series is to begin the process of \"libifying\" the trailer API. By \"API\" I\n>> mean the interface exposed in trailer.h. The larger series brings a number\n>> of additional cleanups (exposing and fixing some bugs along the way), and\n>> builds on top of this series.\n>\n> [...]\n>\n>> With the libification-focused goals out of the way, let's turn to this patch\n>> series in more detail.\n>\n> I like the goal of libifying Git the trailer API, and the way you want\n> to do it seems reasonable to me.\n>\n> [...]\n>\n>> In summary this series breaks up \"process_trailers()\" into smaller pieces,\n>> exposing many of the parts relevant to trailer-related processing in\n>> trailer.h. This will force us to eventually introduce unit tests for these\n>> API functions, but that is a good thing for API stability.\n>\n> I am a bit sad that this series doesn't introduce unit tests using the\n> new test framework in C yet. I understand that this series is mostly a\n> big refactoring and maybe it's better to introduce unit tests only\n> when the refactoring is finished though.\n\nThis was my original goal as well, and still is.\n\n> Anyway I hope the next series will introduce such tests.\n\nI will see which API functions are stable enough, and add tests\naccordingly (in a patch series sooner than later).\n\nProbably the \"biggest\" (?) thing that is coming from the larger series\nis the introduction of a complete separation between parsing (without\nany modification of the input) and formatting. The parser/formatter is\na large chunk of the trailer implementation, so I would expect unit\ntests for those bits to have to wait until those improvements are merged\ninto \"next\".\n\n> [...]\n>\n>> Thanks to the aggressive refactoring in this series, I've been able to\n>> identify and fix several bugs in our existing implementation. Those fixes\n>> build on top of this series but were not included here, in order to keep\n>> this series small. Below is a \"shortlog\" of those fixes I have locally:\n>>\n>>  * \"trailer: trailer replacement should not change its position\" (If we\n>>    found a trailer we'd like to replace, preserve its position relative to\n>>    the other trailers found in the trailer block, instead of always moving\n>>    it to the beginning or end of the entire trailer block.)\n>\n> I believe there was a reason why it was done this way. I don't\n> remember what it was though.\n\nNoted. I'll see what I can find in our commit history.\n\n>>  * \"interpret-trailers: preserve trailers coming from the input\" (Sometimes,\n>>    the parsed trailers from the input will be formatted differently\n>>    depending on whether we provide --only-trailers or not. Make the trailers\n>>    that were not modified and which are coming directly from the input get\n>>    formatted the same way, regardless of this flag.)\n>\n> It could be a feature to be able to normalize trailers in a certain way.\n\nTrue. But doing such normalization silently is undocumented behavior,\nand we should provide explicit flags for this sort of thing. Adding such\nflags might be the right thing to do (let's discuss more when this patch\ngets proposed). FWIW the behavior I observed is that this normalization\nonly happens for *some* trailers that have configuration options, not\nall trailers in the input. So it's a special kind of (limited)\nnormalization.\n\n>>  * \"interpret-trailers: do not modify the input if NOP\" (Refrain from\n>>    subtracting or adding a newline around the patch divider \"---\" if we are\n>>    not adding new trailers.)\n>\n> It could be a feature to be able to normalize this too.\n\nOK, but it would again be undocumented behavior.\n\n>>  * \"trailer formatter: split up format_trailer() monolith\" (Fix a bug in\n>>    git-log where we still printed a blank newline even if we didn't want to\n>>    format anything.)\n>\n> I am not sure this is a bug fix either. It could perhaps be a normalization too.\n\nSee my comment above.\n\n>>  * \"interpret-trailers: fail if given unrecognized arguments\" (E.g., for\n>>    \"--where\", only accept recognized WHERE_* enum values. If we get\n>>    something unrecognized, fail with an error instead of silently doing\n>>    nothing. Ditto for \"--if-exists\" and \"--if-missing\".)\n>\n> It's possible that there was a reason why it was done this way.\n>\n> I think you might want to take a look at the discussions on the\n> mailing list when \"interpret-trailers\" was developed. There were a lot\n> of discussions over a long time, and there were a lot of requests and\n> suggestions about what it should do.\n\nI confess I haven't looked too deeply into the original threads\nsurrounding the introduction of \"interpret-trailers\". But all of the\nchanges which I categorize as bugfixes above have a theme of\nundocumented modifications.\n\nWhile working on this (and the larger) series around trailers, I only\nlooked into some (not all) of the discussions on the mailing list in\nthis area. Instead, I deferred to\nDocumentation/git-interpret-trailers.txt as the official (authoritative)\nsource of truth. This is partly why I first started out on this project\nlast year by making improvements to that doc. And, this is why seeing so\nmany edge cases where we perform such undocumented modifications smelled\nmore like bugs to me than features.\n\nThat being said, I cannot disagree with your position that the so-called\nbugfixes I've reported above could be misguided (and should rather be\njust updates to missing documentation). Let's not try to decide that\nhere, but instead later when I get to introduce those changes on the\nlist, one at a time. Thanks.\n"},{"id":"488569","messageId":"owlyttmc9m5h.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"CAP8UFD0nmK4ZigW9LcWOr_POEX5LX7m+T=Jq9rK34YL5C6xatw@mail.gmail.com","subject":"Re: [PATCH v4 15/28] format_trailer_info(): avoid double-printing the separator","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-13T19:52:58Z","receivedAt":"2024-02-13T19:53:00Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Tue, Feb 13, 2024 at 6:21 PM Linus Arver <linusa@google.com> wrote:\n>>\n>> Christian Couder <christian.couder@gmail.com> writes:\n>>\n>> > On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n>> > <gitgitgadget@gmail.com> wrote:\n>\n>> > Also I wonder why it was not possible to modify format_trailer_info()\n>> > like it is done in this patch before using it to replace\n>> > format_trailers().\n>>\n>> The artificial organization apparent in this patch was deliberate, in\n>> order to make it painfully obvious exactly what was being replaced and\n>> how. See https://lore.kernel.org/git/xmqqjzno13ev.fsf@gitster.g/\n>\n> As for the previous patch, I would have thought that it would be\n> better not to break the tests.\n\nI could just squash these patches together to avoid breaking tests (and\nalso avoid doing the flipping of expect_success to expect_fail and back\nagain). I don't mind at all which way we go, but now that we have these\npatches broken out I wonder if it's better to just keep them that way.\n\nJunio, do you mind if I squash the relevant changes together into just\none patch?  I'd like your input because you requested the current style\n(modulo test breakages which was my error). Thanks.\n"},{"id":"488571","messageId":"xmqq5xys5e98.fsf@gitster.g","threadId":"60715","inReplyTo":"owlywmr89ms9.fsf@fine.c.googlers.com","subject":"Re: [PATCH v4 00/28] Enrich Trailer API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-13T19:57:07Z","receivedAt":"2024-02-13T19:57:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Linus Arver <linusa@google.com> writes:\n\n> This sounds fine to me. IIRC this means for the 2nd, 3rd+ series I have\n> to remember to say \"this series builds on top of the other topic\n> branches '...'\" in the cover letter. Now that I've written this out I\n> will hopefully not forget to do this...\n>\n> Or, I suppose I could just introduce the 1st sub-series, wait for that\n> to get queued to next, then (re)introduce the 2nd sub-series, etc, in\n> order. Hmm. I think this will be simpler.\n\nFWIW, which was how the recent flurry of topics related to the\nreftable backend were done by Patrick, which was quite nice.  People\ncertainly need the feel of larger picture to get motivated to review\nan earlier part of the series, but now that they saw the projected\nend-game, splitting these into 4 series, each with materials in 7 or\nso patches in v4, and presenting in not-so-quick succession one by\none, would make it less distracting and less daunting.\n\n>> I am a bit sad that this series doesn't introduce unit tests using the\n>> new test framework in C yet. I understand that this series is mostly a\n>> big refactoring and maybe it's better to introduce unit tests only\n>> when the refactoring is finished though.\n>\n> This was my original goal as well, and still is.\n\nThat's OK.  If v4 were 40-patch series instead of 28, I am sure you\nwould have had the unit-test part near the end.  So a bite sized\nseries of serieses may not show the unit-tests while the earlier\nbatches are still cooking, but as long as we all are aiming for that\nsame goal, we are fine.  Let's help ourselves get there soon by\nreviewing each other's patches ;-).\n\nThanks, both.\n"},{"id":"488574","messageId":"CAP8UFD08F0V13X0+CJ1uhMPzPWVMs2okGVMJch0DkQg5M3BWLA@mail.gmail.com","threadId":"60715","inReplyTo":"xmqqa5o46zla.fsf@gitster.g","subject":"Re: [PATCH v4 00/28] Enrich Trailer API","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-13T20:25:10Z","receivedAt":"2024-02-13T20:25:24Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"(Sorry for sending this previously to Junio only)\n\nOn Tue, Feb 13, 2024 at 6:30 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> writes:\n\n> >> I presume that [01-09/28] would be the first part, nothing\n> >> controversial and consisting of obvious clean-ups?  I do not mind\n> >> merging that part down to remove the future review load if everybody\n> >> agrees.\n> >\n> > Yeah, patches [01-09/28] look good to me.\n>\n> I was hoping that you'll give us more details of what the other 3 or\n> more you would envision the series to be, actually.\n\nI think the next one could be [10-16/28], so until \"trailer: finish\nformatting unification\".\n\nThen I am not sure about the next one, perhaps [17-20/28] or [17-21/28].\n\nThe rest would depend on the splitting of the big patches towards the\nend of the series.\n"},{"id":"488575","messageId":"23805a85-4b11-45fd-a41a-67ecb5a1e407@app.fastmail.com","threadId":"60715","inReplyTo":"xmqq5xys5e98.fsf@gitster.g","subject":"Re: [PATCH v4 00/28] Enrich Trailer API","fromName":"Kristoffer Haugsbakk","fromEmail":"code@khaugsbakk.name","sentAt":"2024-02-13T20:25:08Z","receivedAt":"2024-02-13T20:25:30Z","isPatch":true,"sender":{"key":"code@khaugsbakk.name","avatar":"https://avatars.githubusercontent.com/u/2229597?v=4"},"body":"On Tue, Feb 13, 2024, at 20:57, Junio C Hamano wrote:\n> Linus Arver <linusa@google.com> writes:\n>\n>> This sounds fine to me. IIRC this means for the 2nd, 3rd+ series I have\n>> to remember to say \"this series builds on top of the other topic\n>> branches '...'\" in the cover letter. Now that I've written this out I\n>> will hopefully not forget to do this...\n>>\n>> Or, I suppose I could just introduce the 1st sub-series, wait for that\n>> to get queued to next, then (re)introduce the 2nd sub-series, etc, in\n>> order. Hmm. I think this will be simpler.\n>\n> FWIW, which was how the recent flurry of topics related to the\n> reftable backend were done by Patrick, which was quite nice.  People\n> certainly need the feel of larger picture to get motivated to review\n> an earlier part of the series, but now that they saw the projected\n> end-game, splitting these into 4 series, each with materials in 7 or\n> so patches in v4, and presenting in not-so-quick succession one by\n> one, would make it less distracting and less daunting.\n\nI feel like I’m learning some things about how version control programs\n(and accompanying review software (here email)) can be vital—not just\nhelpful, not just a nice-to-have—for structuring reviews of large\nchanges from observing these conversations. Thanks to both/all.\n\n-- \nKristoffer Haugsbakk\n\n"},{"id":"488579","messageId":"10c1ae18-b39e-4210-b488-cc125861412e@app.fastmail.com","threadId":"60715","inReplyTo":"ba1f387747b08a7270f7387beddd75dc4a8eddfe.1707196348.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 15/28] format_trailer_info(): avoid double-printing the separator","fromName":"Kristoffer Haugsbakk","fromEmail":"code@khaugsbakk.name","sentAt":"2024-02-13T20:41:01Z","receivedAt":"2024-02-13T20:41:27Z","isPatch":true,"sender":{"key":"code@khaugsbakk.name","avatar":"https://avatars.githubusercontent.com/u/2229597?v=4"},"body":"On Tue, Feb 6, 2024, at 06:12, Linus Arver via GitGitGadget wrote:\n> From: Linus Arver <linusa@google.com>\n>\n> Do not hardcode the printing of \": \" as the separator and space (which\n> can result in double-printing these characters); instead only\n> print the separator and space if we cannot find any recognized separator\n> somewhere in the key (yes, keys may have a trailing separator in it ---\n> we will eventually fix this design but not now). Do so by copying the\n> code out of print_tok_val(), and deleting the same function.\n>\n> The test suite passes again with this change.\n>\n> Signed-off-by: Linus Arver <linusa@google.com>\n\nNice find and a great commit message!\n\n-- \nKristoffer Haugsbakk\n\n"},{"id":"488595","messageId":"CAP8UFD1vue6Pdh5rccibyPs179FYxRDS2-pDzOmTzF1vhTE-sg@mail.gmail.com","threadId":"60715","inReplyTo":"owlywmr89ms9.fsf@fine.c.googlers.com","subject":"Re: [PATCH v4 00/28] Enrich Trailer API","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-13T20:55:27Z","receivedAt":"2024-02-13T20:55:42Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Feb 13, 2024 at 8:39 PM Linus Arver <linusa@google.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> writes:\n\n> > I took a look at all of them, and I think that this series should be\n> > split into 4 or more series.\n>\n> This sounds fine to me. IIRC this means for the 2nd, 3rd+ series I have\n> to remember to say \"this series builds on top of the other topic\n> branches '...'\" in the cover letter. Now that I've written this out I\n> will hopefully not forget to do this...\n>\n> Or, I suppose I could just introduce the 1st sub-series, wait for that\n> to get queued to next, then (re)introduce the 2nd sub-series, etc, in\n> order. Hmm. I think this will be simpler.\n\nYeah, sure.\n\n> > Also it seems to me that many patches towards the end of this series\n> > should be split.\n>\n> In hindsight, I fully agree.\n>\n> Aside: I am delighted with the quality of reviews on this project. It's\n> not something I am used to, so please bear with me while I try to break\n> old habits.\n\nSure no worries.\n\n> Thanks.\n\n[...]\n\n> > I am a bit sad that this series doesn't introduce unit tests using the\n> > new test framework in C yet. I understand that this series is mostly a\n> > big refactoring and maybe it's better to introduce unit tests only\n> > when the refactoring is finished though.\n>\n> This was my original goal as well, and still is.\n>\n> > Anyway I hope the next series will introduce such tests.\n>\n> I will see which API functions are stable enough, and add tests\n> accordingly (in a patch series sooner than later).\n>\n> Probably the \"biggest\" (?) thing that is coming from the larger series\n> is the introduction of a complete separation between parsing (without\n> any modification of the input) and formatting. The parser/formatter is\n> a large chunk of the trailer implementation, so I would expect unit\n> tests for those bits to have to wait until those improvements are merged\n> into \"next\".\n\nOk.\n\n> >> Thanks to the aggressive refactoring in this series, I've been able to\n> >> identify and fix several bugs in our existing implementation. Those fixes\n> >> build on top of this series but were not included here, in order to keep\n> >> this series small. Below is a \"shortlog\" of those fixes I have locally:\n> >>\n> >>  * \"trailer: trailer replacement should not change its position\" (If we\n> >>    found a trailer we'd like to replace, preserve its position relative to\n> >>    the other trailers found in the trailer block, instead of always moving\n> >>    it to the beginning or end of the entire trailer block.)\n> >\n> > I believe there was a reason why it was done this way. I don't\n> > remember what it was though.\n>\n> Noted. I'll see what I can find in our commit history.\n>\n> >>  * \"interpret-trailers: preserve trailers coming from the input\" (Sometimes,\n> >>    the parsed trailers from the input will be formatted differently\n> >>    depending on whether we provide --only-trailers or not. Make the trailers\n> >>    that were not modified and which are coming directly from the input get\n> >>    formatted the same way, regardless of this flag.)\n> >\n> > It could be a feature to be able to normalize trailers in a certain way.\n>\n> True. But doing such normalization silently is undocumented behavior,\n> and we should provide explicit flags for this sort of thing. Adding such\n> flags might be the right thing to do (let's discuss more when this patch\n> gets proposed). FWIW the behavior I observed is that this normalization\n> only happens for *some* trailers that have configuration options, not\n> all trailers in the input. So it's a special kind of (limited)\n> normalization.\n\nPerhaps because we consider that having some configuration means that\nthe user consistently expects things in a certain way.\n\n> >>  * \"interpret-trailers: fail if given unrecognized arguments\" (E.g., for\n> >>    \"--where\", only accept recognized WHERE_* enum values. If we get\n> >>    something unrecognized, fail with an error instead of silently doing\n> >>    nothing. Ditto for \"--if-exists\" and \"--if-missing\".)\n> >\n> > It's possible that there was a reason why it was done this way.\n> >\n> > I think you might want to take a look at the discussions on the\n> > mailing list when \"interpret-trailers\" was developed. There were a lot\n> > of discussions over a long time, and there were a lot of requests and\n> > suggestions about what it should do.\n>\n> I confess I haven't looked too deeply into the original threads\n> surrounding the introduction of \"interpret-trailers\". But all of the\n> changes which I categorize as bugfixes above have a theme of\n> undocumented modifications.\n>\n> While working on this (and the larger) series around trailers, I only\n> looked into some (not all) of the discussions on the mailing list in\n> this area. Instead, I deferred to\n> Documentation/git-interpret-trailers.txt as the official (authoritative)\n> source of truth. This is partly why I first started out on this project\n> last year by making improvements to that doc. And, this is why seeing so\n> many edge cases where we perform such undocumented modifications smelled\n> more like bugs to me than features.\n>\n> That being said, I cannot disagree with your position that the so-called\n> bugfixes I've reported above could be misguided (and should rather be\n> just updates to missing documentation). Let's not try to decide that\n> here, but instead later when I get to introduce those changes on the\n> list, one at a time. Thanks.\n\nYeah, it might seem like undocumented features are bad, and I agree\nthat reading original discussions can be tiring. But if the latter\nmakes it possible to fix undocumented features by just properly\ndocumenting them, then I think it might just be the best thing to do.\nOk not to decide about it now though.\n"},{"id":"488777","messageId":"owlysf1t8855.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"CAP8UFD1vue6Pdh5rccibyPs179FYxRDS2-pDzOmTzF1vhTE-sg@mail.gmail.com","subject":"Re: [PATCH v4 00/28] Enrich Trailer API","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-16T02:17:42Z","receivedAt":"2024-02-16T02:17:45Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Tue, Feb 13, 2024 at 8:39 PM Linus Arver <linusa@google.com> wrote:\n>>\n>> Christian Couder <christian.couder@gmail.com> writes:\n>\n> [...]\n> \n>> >> Thanks to the aggressive refactoring in this series, I've been able to\n>> >> identify and fix several bugs in our existing implementation. Those fixes\n>> >> build on top of this series but were not included here, in order to keep\n>> >> this series small. Below is a \"shortlog\" of those fixes I have locally:\n>> >>\n>> >>  * \"trailer: trailer replacement should not change its position\" (If we\n>> >>    found a trailer we'd like to replace, preserve its position relative to\n>> >>    the other trailers found in the trailer block, instead of always moving\n>> >>    it to the beginning or end of the entire trailer block.)\n>> >\n>> > I believe there was a reason why it was done this way. I don't\n>> > remember what it was though.\n>>\n>> Noted. I'll see what I can find in our commit history.\n>>\n>> >>  * \"interpret-trailers: preserve trailers coming from the input\" (Sometimes,\n>> >>    the parsed trailers from the input will be formatted differently\n>> >>    depending on whether we provide --only-trailers or not. Make the trailers\n>> >>    that were not modified and which are coming directly from the input get\n>> >>    formatted the same way, regardless of this flag.)\n>> >\n>> > It could be a feature to be able to normalize trailers in a certain way.\n>>\n>> True. But doing such normalization silently is undocumented behavior,\n>> and we should provide explicit flags for this sort of thing. Adding such\n>> flags might be the right thing to do (let's discuss more when this patch\n>> gets proposed). FWIW the behavior I observed is that this normalization\n>> only happens for *some* trailers that have configuration options, not\n>> all trailers in the input. So it's a special kind of (limited)\n>> normalization.\n>\n> Perhaps because we consider that having some configuration means that\n> the user consistently expects things in a certain way.\n\nYes, this was one possibility I considered after sending my reply. If a\nuser has gone out of their way to configure something, maybe they do\nwant things (for those bits) to be normalized.\n\nAnd adding a flag to disable normalization seems like a good feature to\nhave also (while keeping the behavior of the interpret-trailers that has\nbeen relatively untouched since its introduction). But anyway I'm\ngetting a little bit ahead of myself.\n\n>> >>  * \"interpret-trailers: fail if given unrecognized arguments\" (E.g., for\n>> >>    \"--where\", only accept recognized WHERE_* enum values. If we get\n>> >>    something unrecognized, fail with an error instead of silently doing\n>> >>    nothing. Ditto for \"--if-exists\" and \"--if-missing\".)\n>> >\n>> > It's possible that there was a reason why it was done this way.\n>> >\n>> > I think you might want to take a look at the discussions on the\n>> > mailing list when \"interpret-trailers\" was developed. There were a lot\n>> > of discussions over a long time, and there were a lot of requests and\n>> > suggestions about what it should do.\n>>\n>> I confess I haven't looked too deeply into the original threads\n>> surrounding the introduction of \"interpret-trailers\". But all of the\n>> changes which I categorize as bugfixes above have a theme of\n>> undocumented modifications.\n>>\n>> While working on this (and the larger) series around trailers, I only\n>> looked into some (not all) of the discussions on the mailing list in\n>> this area. Instead, I deferred to\n>> Documentation/git-interpret-trailers.txt as the official (authoritative)\n>> source of truth. This is partly why I first started out on this project\n>> last year by making improvements to that doc. And, this is why seeing so\n>> many edge cases where we perform such undocumented modifications smelled\n>> more like bugs to me than features.\n>>\n>> That being said, I cannot disagree with your position that the so-called\n>> bugfixes I've reported above could be misguided (and should rather be\n>> just updates to missing documentation). Let's not try to decide that\n>> here, but instead later when I get to introduce those changes on the\n>> list, one at a time. Thanks.\n>\n> Yeah, it might seem like undocumented features are bad, and I agree\n> that reading original discussions can be tiring. But if the latter\n> makes it possible to fix undocumented features by just properly\n> documenting them, then I think it might just be the best thing to do.\n> Ok not to decide about it now though.\n\nThanks!\n"},{"id":"488778","messageId":"owlyplwx87s9.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"CAP8UFD08F0V13X0+CJ1uhMPzPWVMs2okGVMJch0DkQg5M3BWLA@mail.gmail.com","subject":"Re: [PATCH v4 00/28] Enrich Trailer API","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-16T02:25:26Z","receivedAt":"2024-02-16T02:25:28Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> (Sorry for sending this previously to Junio only)\n>\n> On Tue, Feb 13, 2024 at 6:30 PM Junio C Hamano <gitster@pobox.com> wrote:\n>>\n>> Christian Couder <christian.couder@gmail.com> writes:\n>\n>> >> I presume that [01-09/28] would be the first part, nothing\n>> >> controversial and consisting of obvious clean-ups?  I do not mind\n>> >> merging that part down to remove the future review load if everybody\n>> >> agrees.\n>> >\n>> > Yeah, patches [01-09/28] look good to me.\n>>\n>> I was hoping that you'll give us more details of what the other 3 or\n>> more you would envision the series to be, actually.\n>\n> I think the next one could be [10-16/28], so until \"trailer: finish\n> formatting unification\".\n>\n> Then I am not sure about the next one, perhaps [17-20/28] or [17-21/28].\n>\n> The rest would depend on the splitting of the big patches towards the\n> end of the series.\n\nAck, I'll try to group them like this. Thanks.\n"},{"id":"488822","messageId":"pull.1632.v5.git.1708124950.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v4.git.1707196348.gitgitgadget@gmail.com","subject":"[PATCH v5 0/9] Enrich Trailer API","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-16T23:09:01Z","receivedAt":"2024-02-16T23:09:15Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"This patch series is the first 9 patches of a larger cleanup/bugfix series\n(henceforth \"larger series\") I've been working on. The main goal of this\nseries is to begin the process of \"libifying\" the trailer API. By \"API\" I\nmean the interface exposed in trailer.h. The larger series brings a number\nof additional cleanups (exposing and fixing some bugs along the way), and\nbuilds on top of this series.\n\nWhen the larger series is merged, we will be in a good state to additionally\npursue the following goals:\n\n 1. \"API reuse inside Git\": make the API expressive enough to eliminate any\n    need by other parts of Git to use the interpret-trailers builtin as a\n    subprocess (instead they could just use the API directly);\n 2. \"API stability\": add unit tests to codify the expected behavior of API\n    functions; and\n 3. \"API documentation\": create developer-focused documentation to explain\n    how to use the API effectively, noting any API limitations or\n    anti-patterns.\n\nIn the future after libification is \"complete\", users external to Git will\nbe able to use the same trailer processing API used by the\ninterpret-trailers builtin. For example, a web server may want to parse\ntrailers the same way that Git would parse them, without having to call\ninterpret-trailers as a subprocess. This use case was the original\nmotivation behind my work in this area.\n\nWith the libification-focused goals out of the way, let's turn to this patch\nseries in more detail.\n\nIn summary this series breaks up \"process_trailers()\" into smaller pieces,\nexposing many of the parts relevant to trailer-related processing in\ntrailer.h. This will force us to eventually introduce unit tests for these\nAPI functions, but that is a good thing for API stability. We also perform\nsome preparatory refactors in order to help us unify the trailer formatting\nmachinery toward the end of this series.\n\n\nNotable changes in v5\n=====================\n\n * Removed patches 10+ from this series. Thanks to Christian for the\n   suggestion.\n * Reworded the log message of patch 09 to reflect the above arrangement, as\n   suggested by Christian.\n\n\nNotable changes in v4\n=====================\n\n * Patches 3, 4, 5, and 8 have been broken up into smaller steps. There are\n   28 instead of 10 patches now, but these 28 should be much easier to\n   review than the (previously condensed) 10.\n * NEW Patch 1: \"trailer: free trailer_info after all related usage\" fixes\n   awkward use-after-free coding style\n * NEW Patch 2: \"shortlog: add test for de-duplicating folded trailers\"\n   increases test coverage related to trailer iterators and \"unfold_value()\"\n * NEW Patch 27: \"trailer_set_*(): put out parameter at the end\" is a small\n   refactor to reorder parameters.\n * Patches 5-16: These smaller patches make up Patch 3 from v3.\n * Patches 17-18: These smaller patches make up Patch 4 from v3.\n * Patches 19-20: These smaller patches make up Patch 5 from v3.\n * Patches 23-26: These smaller patches make up Patch 8 from v3.\n * Anonymize unambiguous parameters in <trailer.h>.\n\n\nNotable changes in v3\n=====================\n\n * Squashed Patch 4 into Patch 3 (\"trailer: unify trailer formatting\n   machinery\"), to avoid breaking the build (\"-Werror=unused-function\"\n   violations)\n * NEW (Patch 10): Introduce \"trailer template\" terminology for readability\n   (no behavioral change)\n * (API function) Rename default_separators() to\n   trailer_default_separators()\n * (API function) Rename new_trailers_clear() to free_trailer_templates()\n * trailer.h: for single-parameter functions, anonymize the parameter name\n   to reduce verbosity\n\n\nNotable changes in v2\n=====================\n\n * (cover letter) Discuss goals of the larger series in more detail,\n   especially the pimpl idiom\n * (cover letter) List bug fixes pending in the larger series that depend on\n   this series\n * Reorder function parameters to have trailer options at the beginning (and\n   out parameters toward the end)\n * \"sequencer: use the trailer iterator\": prefer C string instead of strbuf\n   for new \"raw\" field\n * Patch 1 (was Patch 2) also renames ensure_configured() to\n   trailer_config_init() (forgot to rename this one previously)\n\nLinus Arver (9):\n  trailer: free trailer_info _after_ all related usage\n  shortlog: add test for de-duplicating folded trailers\n  trailer: prepare to expose functions as part of API\n  trailer: move interpret_trailers() to interpret-trailers.c\n  trailer: start preparing for formatting unification\n  trailer_info_get(): reorder parameters\n  format_trailers(): use strbuf instead of FILE\n  format_trailer_info(): move \"fast path\" to caller\n  format_trailers_from_commit(): indirectly call trailer_info_get()\n\n builtin/interpret-trailers.c | 101 ++++++++++++++++++-\n pretty.c                     |   2 +-\n ref-filter.c                 |   2 +-\n sequencer.c                  |   2 +-\n t/t4201-shortlog.sh          |  32 +++++++\n trailer.c                    | 181 +++++++++--------------------------\n trailer.h                    |  31 ++++--\n 7 files changed, 204 insertions(+), 147 deletions(-)\n\n\nbase-commit: a54a84b333adbecf7bc4483c0e36ed5878cac17b\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1632%2Flistx%2Ftrailer-api-refactor-part-1-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1632/listx/trailer-api-refactor-part-1-v5\nPull-Request: https://github.com/gitgitgadget/git/pull/1632\n\nRange-diff vs v4:\n\n  1:  652df25f30e =  1:  652df25f30e trailer: free trailer_info _after_ all related usage\n  2:  fdccaca2ba0 =  2:  fdccaca2ba0 shortlog: add test for de-duplicating folded trailers\n  3:  4372af244f0 =  3:  4372af244f0 trailer: prepare to expose functions as part of API\n  4:  4073b8eb510 =  4:  4073b8eb510 trailer: move interpret_trailers() to interpret-trailers.c\n  5:  b2a0f7829a1 =  5:  b2a0f7829a1 trailer: start preparing for formatting unification\n  6:  c1760f80356 =  6:  c1760f80356 trailer_info_get(): reorder parameters\n  7:  9dc912b5bc5 =  7:  9dc912b5bc5 format_trailers(): use strbuf instead of FILE\n  8:  b97c06d8bc3 =  8:  b97c06d8bc3 format_trailer_info(): move \"fast path\" to caller\n  9:  6906910417a !  9:  7c656b3f775 format_trailers_from_commit(): indirectly call trailer_info_get()\n     @@ Commit message\n          format_trailer_info() only looks at the \"trailers\" string array, not the\n          trailer_item objects which parse_trailers() populates.\n      \n     -    In the next patch, we'll change format_trailer_info() to use the parsed\n     +    In a future patch, we'll change format_trailer_info() to use the parsed\n          trailer_item objects instead of the string array.\n      \n          Signed-off-by: Linus Arver <linusa@google.com>\n 10:  f5b7ba08aa7 <  -:  ----------- format_trailer_info(): use trailer_item objects\n 11:  457f2a839d5 <  -:  ----------- format_trailer_info(): drop redundant unfold_value()\n 12:  a72eca301f7 <  -:  ----------- format_trailer_info(): append newline for non-trailer lines\n 13:  ad77c33e457 <  -:  ----------- trailer: begin formatting unification\n 14:  11f854399db <  -:  ----------- format_trailer_info(): teach it about opts->trim_empty\n 15:  ba1f387747b <  -:  ----------- format_trailer_info(): avoid double-printing the separator\n 16:  31725832224 <  -:  ----------- trailer: finish formatting unification\n 17:  6f17c022b15 <  -:  ----------- trailer: teach iterator about non-trailer lines\n 18:  cc92dfb0bda <  -:  ----------- sequencer: use the trailer iterator\n 19:  f5f0d06613f <  -:  ----------- trailer: make trailer_info struct private\n 20:  607ae7a90cd <  -:  ----------- trailer: retire trailer_info_get() from API\n 21:  38f4b4c4135 <  -:  ----------- trailer: spread usage of \"trailer_block\" language\n 22:  94bf182e3ff <  -:  ----------- trailer: prepare to delete \"parse_trailers_from_command_line_args()\"\n 23:  3bfe4809ecb <  -:  ----------- trailer: add new helper functions to API\n 24:  80e1958bb8d <  -:  ----------- trailer_add_arg_item(): drop new_trailer_item usage\n 25:  a9080597a28 <  -:  ----------- trailer: deprecate \"new_trailer_item\" struct from API\n 26:  9720526dd8a <  -:  ----------- trailer: unify \"--trailer ...\" arg handling\n 27:  26df2514acb <  -:  ----------- trailer_set_*(): put out parameter at the end\n 28:  14927038d85 <  -:  ----------- trailer: introduce \"template\" term for readability\n\n-- \ngitgitgadget\n"},{"id":"488823","messageId":"652df25f30ecc8227bef5109b0f7b47817386b13.1708124951.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v5.git.1708124950.gitgitgadget@gmail.com","subject":"[PATCH v5 1/9] trailer: free trailer_info _after_ all related usage","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-16T23:09:02Z","receivedAt":"2024-02-16T23:09:15Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nIn de7c27a186 (trailer: use offsets for trailer_start/trailer_end,\n2023-10-20), we started using trailer block offsets in trailer_info. In\nparticular, we dropped the use of a separate stack variable \"size_t\ntrailer_end\", in favor of accessing the new \"trailer_block_end\" member\nof trailer_info (as \"info.trailer_block_end\").\n\nAt that time, we forgot to also move the\n\n   trailer_info_release(&info);\n\nline to be _after_ this new use of the trailer_info struct. Move it now.\n\nNote that even without this patch, we didn't have leaks or any other\nproblems because trailer_info_release() only frees memory allocated on\nthe heap. The \"trailer_block_end\" member was allocated on the stack back\nthen (as it is now) so it was still safe to use for all this time.\n\nReported-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 3 +--\n 1 file changed, 1 insertion(+), 2 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 3a0710a4583..e1d83390b66 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1111,13 +1111,12 @@ void process_trailers(const char *file,\n \t}\n \n \tprint_all(outfile, &head, opts);\n-\n \tfree_all(&head);\n-\ttrailer_info_release(&info);\n \n \t/* Print the lines after the trailers as is */\n \tif (!opts->only_trailers)\n \t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n+\ttrailer_info_release(&info);\n \n \tif (opts->in_place)\n \t\tif (rename_tempfile(&trailers_tempfile, file))\n-- \ngitgitgadget\n\n"},{"id":"488824","messageId":"fdccaca2ba0c92a3f2b33f6608182ab80507ec53.1708124951.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v5.git.1708124950.gitgitgadget@gmail.com","subject":"[PATCH v5 2/9] shortlog: add test for de-duplicating folded trailers","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-16T23:09:03Z","receivedAt":"2024-02-16T23:09:16Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThe shortlog builtin was taught to use the trailer iterator interface in\n47beb37bc6 (shortlog: match commit trailers with --group, 2020-09-27).\nThe iterator always unfolds values and this has always been the case\nsince the time the iterator was first introduced in f0939a0eb1 (trailer:\nadd interface for iterating over commit trailers, 2020-09-27). Add a\ncomment line to remind readers of this behavior.\n\nThe fact that the iterator always unfolds values is important\n(at least for shortlog) because unfolding allows it to recognize both\nfolded and unfolded versions of the same trailer for de-duplication.\n\nCapture the existing behavior in a new test case to guard against\nregressions in this area. This test case is based off of the existing\n\"shortlog de-duplicates trailers in a single commit\" just above it. Now\nif we were to remove the call to\n\n    unfold_value(&iter->val);\n\ninside the iterator, this new test case will break.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n t/t4201-shortlog.sh | 32 ++++++++++++++++++++++++++++++++\n trailer.c           |  1 +\n 2 files changed, 33 insertions(+)\n\ndiff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh\nindex d7382709fc1..f698d0c9ad2 100755\n--- a/t/t4201-shortlog.sh\n+++ b/t/t4201-shortlog.sh\n@@ -312,6 +312,38 @@ test_expect_success 'shortlog de-duplicates trailers in a single commit' '\n \ttest_cmp expect actual\n '\n \n+# Trailers that have unfolded (single line) and folded (multiline) values which\n+# are otherwise identical are treated as the same trailer for de-duplication.\n+test_expect_success 'shortlog de-duplicates trailers in a single commit (folded/unfolded values)' '\n+\tgit commit --allow-empty -F - <<-\\EOF &&\n+\tsubject one\n+\n+\tthis message has two distinct values, plus a repeat (folded)\n+\n+\tRepeated-trailer: Foo foo foo\n+\tRepeated-trailer: Bar\n+\tRepeated-trailer: Foo\n+\t  foo foo\n+\tEOF\n+\n+\tgit commit --allow-empty -F - <<-\\EOF &&\n+\tsubject two\n+\n+\tsimilar to the previous, but without the second distinct value\n+\n+\tRepeated-trailer: Foo foo foo\n+\tRepeated-trailer: Foo\n+\t  foo foo\n+\tEOF\n+\n+\tcat >expect <<-\\EOF &&\n+\t     2\tFoo foo foo\n+\t     1\tBar\n+\tEOF\n+\tgit shortlog -ns --group=trailer:repeated-trailer -2 HEAD >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'shortlog can match multiple groups' '\n \tgit commit --allow-empty -F - <<-\\EOF &&\n \tsubject one\ndiff --git a/trailer.c b/trailer.c\nindex e1d83390b66..f74915bd8cd 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1270,6 +1270,7 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n \t\tstrbuf_reset(&iter->val);\n \t\tparse_trailer(&iter->key, &iter->val, NULL,\n \t\t\t      trailer, separator_pos);\n+\t\t/* Always unfold values during iteration. */\n \t\tunfold_value(&iter->val);\n \t\treturn 1;\n \t}\n-- \ngitgitgadget\n\n"},{"id":"488825","messageId":"4372af244f02b71cc70f3a8e1b5591b3b9fec93a.1708124951.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v5.git.1708124950.gitgitgadget@gmail.com","subject":"[PATCH v5 3/9] trailer: prepare to expose functions as part of API","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-16T23:09:04Z","receivedAt":"2024-02-16T23:09:17Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nIn the next patch, we will move \"process_trailers\" from trailer.c to\nbuiltin/interpret-trailers.c. That move will necessitate the growth of\nthe trailer.h API, forcing us to expose some additional functions in\ntrailer.h.\n\nRename relevant functions so that they include the term \"trailer\" in\ntheir name, so that clients of the API will be able to easily identify\nthem by their \"trailer\" moniker, just like all the other functions\nalready exposed by trailer.h.\n\nTake the opportunity to start putting trailer processing options (opts)\nas the first parameter. This will be the pattern going forward in this\nseries.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  4 ++--\n trailer.c                    | 26 +++++++++++++-------------\n trailer.h                    |  6 +++---\n 3 files changed, 18 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 033bd1556cf..85a3413baf5 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -132,11 +132,11 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n \tif (argc) {\n \t\tint i;\n \t\tfor (i = 0; i < argc; i++)\n-\t\t\tprocess_trailers(argv[i], &opts, &trailers);\n+\t\t\tinterpret_trailers(&opts, &trailers, argv[i]);\n \t} else {\n \t\tif (opts.in_place)\n \t\t\tdie(_(\"no input file given for in-place editing\"));\n-\t\tprocess_trailers(NULL, &opts, &trailers);\n+\t\tinterpret_trailers(&opts, &trailers, NULL);\n \t}\n \n \tnew_trailers_clear(&trailers);\ndiff --git a/trailer.c b/trailer.c\nindex f74915bd8cd..916175707d8 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -163,12 +163,12 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n-static void print_all(FILE *outfile, struct list_head *head,\n-\t\t      const struct process_trailer_options *opts)\n+static void format_trailers(const struct process_trailer_options *opts,\n+\t\t\t    struct list_head *trailers, FILE *outfile)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *item;\n-\tlist_for_each(pos, head) {\n+\tlist_for_each(pos, trailers) {\n \t\titem = list_entry(pos, struct trailer_item, list);\n \t\tif ((!opts->trim_empty || strlen(item->value) > 0) &&\n \t\t    (!opts->only_trailers || item->token))\n@@ -589,7 +589,7 @@ static int git_trailer_config(const char *conf_key, const char *value,\n \treturn 0;\n }\n \n-static void ensure_configured(void)\n+static void trailer_config_init(void)\n {\n \tif (configured)\n \t\treturn;\n@@ -1035,10 +1035,10 @@ static void parse_trailers(struct trailer_info *info,\n \t}\n }\n \n-static void free_all(struct list_head *head)\n+static void free_trailers(struct list_head *trailers)\n {\n \tstruct list_head *pos, *p;\n-\tlist_for_each_safe(pos, p, head) {\n+\tlist_for_each_safe(pos, p, trailers) {\n \t\tlist_del(pos);\n \t\tfree_trailer_item(list_entry(pos, struct trailer_item, list));\n \t}\n@@ -1075,16 +1075,16 @@ static FILE *create_in_place_tempfile(const char *file)\n \treturn outfile;\n }\n \n-void process_trailers(const char *file,\n-\t\t      const struct process_trailer_options *opts,\n-\t\t      struct list_head *new_trailer_head)\n+void interpret_trailers(const struct process_trailer_options *opts,\n+\t\t\tstruct list_head *new_trailer_head,\n+\t\t\tconst char *file)\n {\n \tLIST_HEAD(head);\n \tstruct strbuf sb = STRBUF_INIT;\n \tstruct trailer_info info;\n \tFILE *outfile = stdout;\n \n-\tensure_configured();\n+\ttrailer_config_init();\n \n \tread_input_file(&sb, file);\n \n@@ -1110,8 +1110,8 @@ void process_trailers(const char *file,\n \t\tprocess_trailers_lists(&head, &arg_head);\n \t}\n \n-\tprint_all(outfile, &head, opts);\n-\tfree_all(&head);\n+\tformat_trailers(opts, &head, outfile);\n+\tfree_trailers(&head);\n \n \t/* Print the lines after the trailers as is */\n \tif (!opts->only_trailers)\n@@ -1134,7 +1134,7 @@ void trailer_info_get(struct trailer_info *info, const char *str,\n \tsize_t nr = 0, alloc = 0;\n \tchar **last = NULL;\n \n-\tensure_configured();\n+\ttrailer_config_init();\n \n \tend_of_log_message = find_end_of_log_message(str, opts->no_divider);\n \ttrailer_block_start = find_trailer_block_start(str, end_of_log_message);\ndiff --git a/trailer.h b/trailer.h\nindex 1644cd05f60..37033e631a1 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -81,9 +81,9 @@ struct process_trailer_options {\n \n #define PROCESS_TRAILER_OPTIONS_INIT {0}\n \n-void process_trailers(const char *file,\n-\t\t      const struct process_trailer_options *opts,\n-\t\t      struct list_head *new_trailer_head);\n+void interpret_trailers(const struct process_trailer_options *opts,\n+\t\t\tstruct list_head *new_trailer_head,\n+\t\t\tconst char *file);\n \n void trailer_info_get(struct trailer_info *info, const char *str,\n \t\t      const struct process_trailer_options *opts);\n-- \ngitgitgadget\n\n"},{"id":"488826","messageId":"4073b8eb5101ea49d8e79b40822aaa42ca87d24d.1708124951.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v5.git.1708124950.gitgitgadget@gmail.com","subject":"[PATCH v5 4/9] trailer: move interpret_trailers() to interpret-trailers.c","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-16T23:09:05Z","receivedAt":"2024-02-16T23:09:18Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThe interpret-trailers.c builtin is the only place we need to call\ninterpret_trailers(), so move its definition there (together with a few\nhelper functions called only by it) and remove its external declaration\nfrom <trailer.h>.\n\nSeveral helper functions that are called by interpret_trailers() remain\nin trailer.c because other callers in the same file still call them.\nDeclare them in <trailer.h> so that interpret_trailers() (now in\nbuiltin/interpret-trailers.c) can continue calling them as a trailer API\nuser.\n\nThis enriches <trailer.h> with a more granular API, which can then be\nunit-tested in the future (because interpret_trailers() by itself does\ntoo many things to be able to be easily unit-tested).\n\nTake this opportunity to demote some file-handling functions out of the\ntrailer API implementation, as these have nothing to do with trailers.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  93 +++++++++++++++++++++++++++\n trailer.c                    | 119 ++++-------------------------------\n trailer.h                    |  20 +++++-\n 3 files changed, 123 insertions(+), 109 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 85a3413baf5..d1cf0aa33a2 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -9,6 +9,7 @@\n #include \"gettext.h\"\n #include \"parse-options.h\"\n #include \"string-list.h\"\n+#include \"tempfile.h\"\n #include \"trailer.h\"\n #include \"config.h\"\n \n@@ -91,6 +92,98 @@ static int parse_opt_parse(const struct option *opt, const char *arg,\n \treturn 0;\n }\n \n+static struct tempfile *trailers_tempfile;\n+\n+static FILE *create_in_place_tempfile(const char *file)\n+{\n+\tstruct stat st;\n+\tstruct strbuf filename_template = STRBUF_INIT;\n+\tconst char *tail;\n+\tFILE *outfile;\n+\n+\tif (stat(file, &st))\n+\t\tdie_errno(_(\"could not stat %s\"), file);\n+\tif (!S_ISREG(st.st_mode))\n+\t\tdie(_(\"file %s is not a regular file\"), file);\n+\tif (!(st.st_mode & S_IWUSR))\n+\t\tdie(_(\"file %s is not writable by user\"), file);\n+\n+\t/* Create temporary file in the same directory as the original */\n+\ttail = strrchr(file, '/');\n+\tif (tail)\n+\t\tstrbuf_add(&filename_template, file, tail - file + 1);\n+\tstrbuf_addstr(&filename_template, \"git-interpret-trailers-XXXXXX\");\n+\n+\ttrailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode);\n+\tstrbuf_release(&filename_template);\n+\toutfile = fdopen_tempfile(trailers_tempfile, \"w\");\n+\tif (!outfile)\n+\t\tdie_errno(_(\"could not open temporary file\"));\n+\n+\treturn outfile;\n+}\n+\n+static void read_input_file(struct strbuf *sb, const char *file)\n+{\n+\tif (file) {\n+\t\tif (strbuf_read_file(sb, file, 0) < 0)\n+\t\t\tdie_errno(_(\"could not read input file '%s'\"), file);\n+\t} else {\n+\t\tif (strbuf_read(sb, fileno(stdin), 0) < 0)\n+\t\t\tdie_errno(_(\"could not read from stdin\"));\n+\t}\n+}\n+\n+static void interpret_trailers(const struct process_trailer_options *opts,\n+\t\t\t       struct list_head *new_trailer_head,\n+\t\t\t       const char *file)\n+{\n+\tLIST_HEAD(head);\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tstruct trailer_info info;\n+\tFILE *outfile = stdout;\n+\n+\ttrailer_config_init();\n+\n+\tread_input_file(&sb, file);\n+\n+\tif (opts->in_place)\n+\t\toutfile = create_in_place_tempfile(file);\n+\n+\tparse_trailers(opts, &info, sb.buf, &head);\n+\n+\t/* Print the lines before the trailers */\n+\tif (!opts->only_trailers)\n+\t\tfwrite(sb.buf, 1, info.trailer_block_start, outfile);\n+\n+\tif (!opts->only_trailers && !info.blank_line_before_trailer)\n+\t\tfprintf(outfile, \"\\n\");\n+\n+\n+\tif (!opts->only_input) {\n+\t\tLIST_HEAD(config_head);\n+\t\tLIST_HEAD(arg_head);\n+\t\tparse_trailers_from_config(&config_head);\n+\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n+\t\tlist_splice(&config_head, &arg_head);\n+\t\tprocess_trailers_lists(&head, &arg_head);\n+\t}\n+\n+\tformat_trailers(opts, &head, outfile);\n+\tfree_trailers(&head);\n+\n+\t/* Print the lines after the trailers as is */\n+\tif (!opts->only_trailers)\n+\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n+\ttrailer_info_release(&info);\n+\n+\tif (opts->in_place)\n+\t\tif (rename_tempfile(&trailers_tempfile, file))\n+\t\t\tdie_errno(_(\"could not rename temporary file to %s\"), file);\n+\n+\tstrbuf_release(&sb);\n+}\n+\n int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n {\n \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\ndiff --git a/trailer.c b/trailer.c\nindex 916175707d8..d23afa0a65c 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -5,7 +5,6 @@\n #include \"string-list.h\"\n #include \"run-command.h\"\n #include \"commit.h\"\n-#include \"tempfile.h\"\n #include \"trailer.h\"\n #include \"list.h\"\n /*\n@@ -163,8 +162,8 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n-static void format_trailers(const struct process_trailer_options *opts,\n-\t\t\t    struct list_head *trailers, FILE *outfile)\n+void format_trailers(const struct process_trailer_options *opts,\n+\t\t     struct list_head *trailers, FILE *outfile)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *item;\n@@ -366,8 +365,8 @@ static int find_same_and_apply_arg(struct list_head *head,\n \treturn 0;\n }\n \n-static void process_trailers_lists(struct list_head *head,\n-\t\t\t\t   struct list_head *arg_head)\n+void process_trailers_lists(struct list_head *head,\n+\t\t\t    struct list_head *arg_head)\n {\n \tstruct list_head *pos, *p;\n \tstruct arg_item *arg_tok;\n@@ -589,7 +588,7 @@ static int git_trailer_config(const char *conf_key, const char *value,\n \treturn 0;\n }\n \n-static void trailer_config_init(void)\n+void trailer_config_init(void)\n {\n \tif (configured)\n \t\treturn;\n@@ -719,7 +718,7 @@ static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n \tlist_add_tail(&new_item->list, arg_head);\n }\n \n-static void parse_trailers_from_config(struct list_head *config_head)\n+void parse_trailers_from_config(struct list_head *config_head)\n {\n \tstruct arg_item *item;\n \tstruct list_head *pos;\n@@ -735,8 +734,8 @@ static void parse_trailers_from_config(struct list_head *config_head)\n \t}\n }\n \n-static void parse_trailers_from_command_line_args(struct list_head *arg_head,\n-\t\t\t\t\t\t  struct list_head *new_trailer_head)\n+void parse_trailers_from_command_line_args(struct list_head *arg_head,\n+\t\t\t\t\t   struct list_head *new_trailer_head)\n {\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n@@ -775,17 +774,6 @@ static void parse_trailers_from_command_line_args(struct list_head *arg_head,\n \tfree(cl_separators);\n }\n \n-static void read_input_file(struct strbuf *sb, const char *file)\n-{\n-\tif (file) {\n-\t\tif (strbuf_read_file(sb, file, 0) < 0)\n-\t\t\tdie_errno(_(\"could not read input file '%s'\"), file);\n-\t} else {\n-\t\tif (strbuf_read(sb, fileno(stdin), 0) < 0)\n-\t\t\tdie_errno(_(\"could not read from stdin\"));\n-\t}\n-}\n-\n static const char *next_line(const char *str)\n {\n \tconst char *nl = strchrnul(str, '\\n');\n@@ -1000,10 +988,10 @@ static void unfold_value(struct strbuf *val)\n  * Parse trailers in \"str\", populating the trailer info and \"head\"\n  * linked list structure.\n  */\n-static void parse_trailers(struct trailer_info *info,\n-\t\t\t     const char *str,\n-\t\t\t     struct list_head *head,\n-\t\t\t     const struct process_trailer_options *opts)\n+void parse_trailers(const struct process_trailer_options *opts,\n+\t\t    struct trailer_info *info,\n+\t\t    const char *str,\n+\t\t    struct list_head *head)\n {\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n@@ -1035,7 +1023,7 @@ static void parse_trailers(struct trailer_info *info,\n \t}\n }\n \n-static void free_trailers(struct list_head *trailers)\n+void free_trailers(struct list_head *trailers)\n {\n \tstruct list_head *pos, *p;\n \tlist_for_each_safe(pos, p, trailers) {\n@@ -1044,87 +1032,6 @@ static void free_trailers(struct list_head *trailers)\n \t}\n }\n \n-static struct tempfile *trailers_tempfile;\n-\n-static FILE *create_in_place_tempfile(const char *file)\n-{\n-\tstruct stat st;\n-\tstruct strbuf filename_template = STRBUF_INIT;\n-\tconst char *tail;\n-\tFILE *outfile;\n-\n-\tif (stat(file, &st))\n-\t\tdie_errno(_(\"could not stat %s\"), file);\n-\tif (!S_ISREG(st.st_mode))\n-\t\tdie(_(\"file %s is not a regular file\"), file);\n-\tif (!(st.st_mode & S_IWUSR))\n-\t\tdie(_(\"file %s is not writable by user\"), file);\n-\n-\t/* Create temporary file in the same directory as the original */\n-\ttail = strrchr(file, '/');\n-\tif (tail)\n-\t\tstrbuf_add(&filename_template, file, tail - file + 1);\n-\tstrbuf_addstr(&filename_template, \"git-interpret-trailers-XXXXXX\");\n-\n-\ttrailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode);\n-\tstrbuf_release(&filename_template);\n-\toutfile = fdopen_tempfile(trailers_tempfile, \"w\");\n-\tif (!outfile)\n-\t\tdie_errno(_(\"could not open temporary file\"));\n-\n-\treturn outfile;\n-}\n-\n-void interpret_trailers(const struct process_trailer_options *opts,\n-\t\t\tstruct list_head *new_trailer_head,\n-\t\t\tconst char *file)\n-{\n-\tLIST_HEAD(head);\n-\tstruct strbuf sb = STRBUF_INIT;\n-\tstruct trailer_info info;\n-\tFILE *outfile = stdout;\n-\n-\ttrailer_config_init();\n-\n-\tread_input_file(&sb, file);\n-\n-\tif (opts->in_place)\n-\t\toutfile = create_in_place_tempfile(file);\n-\n-\tparse_trailers(&info, sb.buf, &head, opts);\n-\n-\t/* Print the lines before the trailers */\n-\tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf, 1, info.trailer_block_start, outfile);\n-\n-\tif (!opts->only_trailers && !info.blank_line_before_trailer)\n-\t\tfprintf(outfile, \"\\n\");\n-\n-\n-\tif (!opts->only_input) {\n-\t\tLIST_HEAD(config_head);\n-\t\tLIST_HEAD(arg_head);\n-\t\tparse_trailers_from_config(&config_head);\n-\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n-\t\tlist_splice(&config_head, &arg_head);\n-\t\tprocess_trailers_lists(&head, &arg_head);\n-\t}\n-\n-\tformat_trailers(opts, &head, outfile);\n-\tfree_trailers(&head);\n-\n-\t/* Print the lines after the trailers as is */\n-\tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n-\ttrailer_info_release(&info);\n-\n-\tif (opts->in_place)\n-\t\tif (rename_tempfile(&trailers_tempfile, file))\n-\t\t\tdie_errno(_(\"could not rename temporary file to %s\"), file);\n-\n-\tstrbuf_release(&sb);\n-}\n-\n void trailer_info_get(struct trailer_info *info, const char *str,\n \t\t      const struct process_trailer_options *opts)\n {\ndiff --git a/trailer.h b/trailer.h\nindex 37033e631a1..c292d44b62f 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -81,15 +81,29 @@ struct process_trailer_options {\n \n #define PROCESS_TRAILER_OPTIONS_INIT {0}\n \n-void interpret_trailers(const struct process_trailer_options *opts,\n-\t\t\tstruct list_head *new_trailer_head,\n-\t\t\tconst char *file);\n+void parse_trailers_from_config(struct list_head *config_head);\n+\n+void parse_trailers_from_command_line_args(struct list_head *arg_head,\n+\t\t\t\t\t   struct list_head *new_trailer_head);\n+\n+void process_trailers_lists(struct list_head *head,\n+\t\t\t    struct list_head *arg_head);\n+\n+void parse_trailers(const struct process_trailer_options *,\n+\t\t    struct trailer_info *,\n+\t\t    const char *str,\n+\t\t    struct list_head *head);\n \n void trailer_info_get(struct trailer_info *info, const char *str,\n \t\t      const struct process_trailer_options *opts);\n \n void trailer_info_release(struct trailer_info *info);\n \n+void trailer_config_init(void);\n+void format_trailers(const struct process_trailer_options *,\n+\t\t     struct list_head *trailers, FILE *outfile);\n+void free_trailers(struct list_head *);\n+\n /*\n  * Format the trailers from the commit msg \"msg\" into the strbuf \"out\".\n  * Note two caveats about \"opts\":\n-- \ngitgitgadget\n\n"},{"id":"488827","messageId":"b2a0f7829a1c5f2822e9a896ffe3744587ff1298.1708124951.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v5.git.1708124950.gitgitgadget@gmail.com","subject":"[PATCH v5 5/9] trailer: start preparing for formatting unification","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-16T23:09:06Z","receivedAt":"2024-02-16T23:09:18Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nCurrently there are two functions for formatting trailers in\n<trailer.h>:\n\n    void format_trailers(const struct process_trailer_options *,\n                         struct list_head *trailers, FILE *outfile);\n\n    void format_trailers_from_commit(struct strbuf *out, const char *msg,\n                                     const struct process_trailer_options *opts);\n\nand although they are similar enough (even taking the same\nprocess_trailer_options struct pointer) they are used quite differently.\nOne might intuitively think that format_trailers_from_commit() builds on\ntop of format_trailers(), but this is not the case. Instead\nformat_trailers_from_commit() calls format_trailer_info() and\nformat_trailers() is never called in that codepath.\n\nThis is a preparatory refactor to help us deprecate format_trailers() in\nfavor of format_trailer_info() (at which point we can rename the latter\nto the former). When the deprecation is complete, both\nformat_trailers_from_commit(), and the interpret-trailers builtin will\nbe able to call into the same helper function (instead of\nformat_trailers() and format_trailer_info(), respectively). Unifying the\nformatters is desirable because it simplifies the API.\n\nReorder parameters for format_trailers_from_commit() to prefer\n\n    const struct process_trailer_options *opts\n\nas the first parameter, because these options are intimately tied to\nformatting trailers. And take\n\n    struct strbuf *out\n\nlast, because it's an \"out parameter\" (something that the caller wants\nto use as the output of this function).\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n pretty.c     |  2 +-\n ref-filter.c |  2 +-\n trailer.c    | 11 ++++++-----\n trailer.h    |  5 +++--\n 4 files changed, 11 insertions(+), 9 deletions(-)\n\ndiff --git a/pretty.c b/pretty.c\nindex cf964b060cd..bdbed4295aa 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1759,7 +1759,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \t\t\t\tgoto trailer_out;\n \t\t}\n \t\tif (*arg == ')') {\n-\t\t\tformat_trailers_from_commit(sb, msg + c->subject_off, &opts);\n+\t\t\tformat_trailers_from_commit(&opts, msg + c->subject_off, sb);\n \t\t\tret = arg - placeholder + 1;\n \t\t}\n \ttrailer_out:\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 35b989e1dfe..d358953b0ce 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -1985,7 +1985,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct exp\n \t\t\tstruct strbuf s = STRBUF_INIT;\n \n \t\t\t/* Format the trailer info according to the trailer_opts given */\n-\t\t\tformat_trailers_from_commit(&s, subpos, &atom->u.contents.trailer_opts);\n+\t\t\tformat_trailers_from_commit(&atom->u.contents.trailer_opts, subpos, &s);\n \n \t\t\tv->s = strbuf_detach(&s, NULL);\n \t\t} else if (atom->u.contents.option == C_BARE)\ndiff --git a/trailer.c b/trailer.c\nindex d23afa0a65c..5025be97899 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1083,10 +1083,10 @@ void trailer_info_release(struct trailer_info *info)\n \tfree(info->trailers);\n }\n \n-static void format_trailer_info(struct strbuf *out,\n+static void format_trailer_info(const struct process_trailer_options *opts,\n \t\t\t\tconst struct trailer_info *info,\n \t\t\t\tconst char *msg,\n-\t\t\t\tconst struct process_trailer_options *opts)\n+\t\t\t\tstruct strbuf *out)\n {\n \tsize_t origlen = out->len;\n \tsize_t i;\n@@ -1144,13 +1144,14 @@ static void format_trailer_info(struct strbuf *out,\n \n }\n \n-void format_trailers_from_commit(struct strbuf *out, const char *msg,\n-\t\t\t\t const struct process_trailer_options *opts)\n+void format_trailers_from_commit(const struct process_trailer_options *opts,\n+\t\t\t\t const char *msg,\n+\t\t\t\t struct strbuf *out)\n {\n \tstruct trailer_info info;\n \n \ttrailer_info_get(&info, msg, opts);\n-\tformat_trailer_info(out, &info, msg, opts);\n+\tformat_trailer_info(opts, &info, msg, out);\n \ttrailer_info_release(&info);\n }\n \ndiff --git a/trailer.h b/trailer.h\nindex c292d44b62f..c6d3ee49bbf 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -115,8 +115,9 @@ void free_trailers(struct list_head *);\n  *     only the trailer block itself, even if the \"only_trailers\" option is not\n  *     set.\n  */\n-void format_trailers_from_commit(struct strbuf *out, const char *msg,\n-\t\t\t\t const struct process_trailer_options *opts);\n+void format_trailers_from_commit(const struct process_trailer_options *opts,\n+\t\t\t\t const char *msg,\n+\t\t\t\t struct strbuf *out);\n \n /*\n  * An interface for iterating over the trailers found in a particular commit\n-- \ngitgitgadget\n\n"},{"id":"488828","messageId":"c1760f803565818c3856345d72af5ae7565cd37d.1708124951.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v5.git.1708124950.gitgitgadget@gmail.com","subject":"[PATCH v5 6/9] trailer_info_get(): reorder parameters","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-16T23:09:07Z","receivedAt":"2024-02-16T23:09:19Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis is another preparatory refactor to unify the trailer formatters.\n\nTake\n\n    const struct process_trailer_options *opts\n\nas the first parameter, because these options are required for\nparsing trailers (e.g., whether to treat \"---\" as the end of the log\nmessage). And take\n\n    struct trailer_info *info\n\nlast, because it's an \"out parameter\" (something that the caller wants\nto use as the output of this function).\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n sequencer.c |  2 +-\n trailer.c   | 11 ++++++-----\n trailer.h   |  5 +++--\n 3 files changed, 10 insertions(+), 8 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 3cc88d8a800..8e199fc8a47 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -332,7 +332,7 @@ static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,\n \t\tsb->buf[sb->len - ignore_footer] = '\\0';\n \t}\n \n-\ttrailer_info_get(&info, sb->buf, &opts);\n+\ttrailer_info_get(&opts, sb->buf, &info);\n \n \tif (ignore_footer)\n \t\tsb->buf[sb->len - ignore_footer] = saved_char;\ndiff --git a/trailer.c b/trailer.c\nindex 5025be97899..f92d844361a 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -997,7 +997,7 @@ void parse_trailers(const struct process_trailer_options *opts,\n \tstruct strbuf val = STRBUF_INIT;\n \tsize_t i;\n \n-\ttrailer_info_get(info, str, opts);\n+\ttrailer_info_get(opts, str, info);\n \n \tfor (i = 0; i < info->trailer_nr; i++) {\n \t\tint separator_pos;\n@@ -1032,8 +1032,9 @@ void free_trailers(struct list_head *trailers)\n \t}\n }\n \n-void trailer_info_get(struct trailer_info *info, const char *str,\n-\t\t      const struct process_trailer_options *opts)\n+void trailer_info_get(const struct process_trailer_options *opts,\n+\t\t      const char *str,\n+\t\t      struct trailer_info *info)\n {\n \tsize_t end_of_log_message = 0, trailer_block_start = 0;\n \tstruct strbuf **trailer_lines, **ptr;\n@@ -1150,7 +1151,7 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n {\n \tstruct trailer_info info;\n \n-\ttrailer_info_get(&info, msg, opts);\n+\ttrailer_info_get(opts, msg, &info);\n \tformat_trailer_info(opts, &info, msg, out);\n \ttrailer_info_release(&info);\n }\n@@ -1161,7 +1162,7 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n \tstrbuf_init(&iter->key, 0);\n \tstrbuf_init(&iter->val, 0);\n \topts.no_divider = 1;\n-\ttrailer_info_get(&iter->internal.info, msg, &opts);\n+\ttrailer_info_get(&opts, msg, &iter->internal.info);\n \titer->internal.cur = 0;\n }\n \ndiff --git a/trailer.h b/trailer.h\nindex c6d3ee49bbf..410c61b62be 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -94,8 +94,9 @@ void parse_trailers(const struct process_trailer_options *,\n \t\t    const char *str,\n \t\t    struct list_head *head);\n \n-void trailer_info_get(struct trailer_info *info, const char *str,\n-\t\t      const struct process_trailer_options *opts);\n+void trailer_info_get(const struct process_trailer_options *,\n+\t\t      const char *str,\n+\t\t      struct trailer_info *);\n \n void trailer_info_release(struct trailer_info *info);\n \n-- \ngitgitgadget\n\n"},{"id":"488829","messageId":"9dc912b5bc5ff07fedc0dd217b5ecb8a1eb10cc3.1708124951.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v5.git.1708124950.gitgitgadget@gmail.com","subject":"[PATCH v5 7/9] format_trailers(): use strbuf instead of FILE","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-16T23:09:08Z","receivedAt":"2024-02-16T23:09:20Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis is another preparatory refactor to unify the trailer formatters.\n\nMake format_trailers() also write to a strbuf, to align with\nformat_trailers_from_commit() which also does the same. Doing this makes\nformat_trailers() behave similar to format_trailer_info() (which will\nsoon help us replace one with the other).\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  6 +++++-\n trailer.c                    | 13 +++++++------\n trailer.h                    |  3 ++-\n 3 files changed, 14 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex d1cf0aa33a2..11f4ce9e4a2 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -140,6 +140,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n {\n \tLIST_HEAD(head);\n \tstruct strbuf sb = STRBUF_INIT;\n+\tstruct strbuf trailer_block = STRBUF_INIT;\n \tstruct trailer_info info;\n \tFILE *outfile = stdout;\n \n@@ -169,8 +170,11 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \t\tprocess_trailers_lists(&head, &arg_head);\n \t}\n \n-\tformat_trailers(opts, &head, outfile);\n+\t/* Print trailer block. */\n+\tformat_trailers(opts, &head, &trailer_block);\n \tfree_trailers(&head);\n+\tfwrite(trailer_block.buf, 1, trailer_block.len, outfile);\n+\tstrbuf_release(&trailer_block);\n \n \t/* Print the lines after the trailers as is */\n \tif (!opts->only_trailers)\ndiff --git a/trailer.c b/trailer.c\nindex f92d844361a..cbd643cd1fe 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -144,12 +144,12 @@ static char last_non_space_char(const char *s)\n \treturn '\\0';\n }\n \n-static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n+static void print_tok_val(struct strbuf *out, const char *tok, const char *val)\n {\n \tchar c;\n \n \tif (!tok) {\n-\t\tfprintf(outfile, \"%s\\n\", val);\n+\t\tstrbuf_addf(out, \"%s\\n\", val);\n \t\treturn;\n \t}\n \n@@ -157,13 +157,14 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n \tif (!c)\n \t\treturn;\n \tif (strchr(separators, c))\n-\t\tfprintf(outfile, \"%s%s\\n\", tok, val);\n+\t\tstrbuf_addf(out, \"%s%s\\n\", tok, val);\n \telse\n-\t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n+\t\tstrbuf_addf(out, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n void format_trailers(const struct process_trailer_options *opts,\n-\t\t     struct list_head *trailers, FILE *outfile)\n+\t\t     struct list_head *trailers,\n+\t\t     struct strbuf *out)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *item;\n@@ -171,7 +172,7 @@ void format_trailers(const struct process_trailer_options *opts,\n \t\titem = list_entry(pos, struct trailer_item, list);\n \t\tif ((!opts->trim_empty || strlen(item->value) > 0) &&\n \t\t    (!opts->only_trailers || item->token))\n-\t\t\tprint_tok_val(outfile, item->token, item->value);\n+\t\t\tprint_tok_val(out, item->token, item->value);\n \t}\n }\n \ndiff --git a/trailer.h b/trailer.h\nindex 410c61b62be..1d106b6dd40 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -102,7 +102,8 @@ void trailer_info_release(struct trailer_info *info);\n \n void trailer_config_init(void);\n void format_trailers(const struct process_trailer_options *,\n-\t\t     struct list_head *trailers, FILE *outfile);\n+\t\t     struct list_head *trailers,\n+\t\t     struct strbuf *out);\n void free_trailers(struct list_head *);\n \n /*\n-- \ngitgitgadget\n\n"},{"id":"488830","messageId":"b97c06d8bc324139710b6fb8c7765326f2a2bd2c.1708124951.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v5.git.1708124950.gitgitgadget@gmail.com","subject":"[PATCH v5 8/9] format_trailer_info(): move \"fast path\" to caller","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-16T23:09:09Z","receivedAt":"2024-02-16T23:09:21Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis is another preparatory refactor to unify the trailer formatters.\n\nThis allows us to drop the \"msg\" parameter from format_trailer_info(),\nso that it take 3 parameters, similar to format_trailers() which also\ntakes 3 parameters:\n\n    void format_trailers(const struct process_trailer_options *opts,\n                         struct list_head *trailers,\n                         struct strbuf *out)\n\nThe short-term goal is to make format_trailer_info() be smart enough to\ndeprecate format_trailers(). And then ultimately we will rename\nformat_trailer_info() to format_trailers().\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 20 +++++++++-----------\n 1 file changed, 9 insertions(+), 11 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex cbd643cd1fe..e92d0154d90 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1087,21 +1087,11 @@ void trailer_info_release(struct trailer_info *info)\n \n static void format_trailer_info(const struct process_trailer_options *opts,\n \t\t\t\tconst struct trailer_info *info,\n-\t\t\t\tconst char *msg,\n \t\t\t\tstruct strbuf *out)\n {\n \tsize_t origlen = out->len;\n \tsize_t i;\n \n-\t/* If we want the whole block untouched, we can take the fast path. */\n-\tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n-\t    !opts->separator && !opts->key_only && !opts->value_only &&\n-\t    !opts->key_value_separator) {\n-\t\tstrbuf_add(out, msg + info->trailer_block_start,\n-\t\t\t   info->trailer_block_end - info->trailer_block_start);\n-\t\treturn;\n-\t}\n-\n \tfor (i = 0; i < info->trailer_nr; i++) {\n \t\tchar *trailer = info->trailers[i];\n \t\tssize_t separator_pos = find_separator(trailer, separators);\n@@ -1153,7 +1143,15 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \tstruct trailer_info info;\n \n \ttrailer_info_get(opts, msg, &info);\n-\tformat_trailer_info(opts, &info, msg, out);\n+\t/* If we want the whole block untouched, we can take the fast path. */\n+\tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n+\t    !opts->separator && !opts->key_only && !opts->value_only &&\n+\t    !opts->key_value_separator) {\n+\t\tstrbuf_add(out, msg + info.trailer_block_start,\n+\t\t\t   info.trailer_block_end - info.trailer_block_start);\n+\t} else\n+\t\tformat_trailer_info(opts, &info, out);\n+\n \ttrailer_info_release(&info);\n }\n \n-- \ngitgitgadget\n\n"},{"id":"488831","messageId":"7c656b3f77546ae917ff192031c62d4521d9df8c.1708124951.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v5.git.1708124950.gitgitgadget@gmail.com","subject":"[PATCH v5 9/9] format_trailers_from_commit(): indirectly call trailer_info_get()","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-16T23:09:10Z","receivedAt":"2024-02-16T23:09:22Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis is another preparatory refactor to unify the trailer formatters.\n\nInstead of calling trailer_info_get() directly, call parse_trailers()\nwhich already calls trailer_info_get(). This change is a NOP because\nformat_trailer_info() only looks at the \"trailers\" string array, not the\ntrailer_item objects which parse_trailers() populates.\n\nIn a future patch, we'll change format_trailer_info() to use the parsed\ntrailer_item objects instead of the string array.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex e92d0154d90..e6665c99cc3 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1140,9 +1140,11 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \t\t\t\t const char *msg,\n \t\t\t\t struct strbuf *out)\n {\n+\tLIST_HEAD(trailers);\n \tstruct trailer_info info;\n \n-\ttrailer_info_get(opts, msg, &info);\n+\tparse_trailers(opts, &info, msg, &trailers);\n+\n \t/* If we want the whole block untouched, we can take the fast path. */\n \tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n \t    !opts->separator && !opts->key_only && !opts->value_only &&\n@@ -1152,6 +1154,7 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \t} else\n \t\tformat_trailer_info(opts, &info, out);\n \n+\tfree_trailers(&trailers);\n \ttrailer_info_release(&info);\n }\n \n-- \ngitgitgadget\n"},{"id":"488929","messageId":"CAP8UFD1dE2EiSxohose6U9SGn+zeHPyVB=KZ2xdQi-v-a8d1XQ@mail.gmail.com","threadId":"60715","inReplyTo":"4372af244f02b71cc70f3a8e1b5591b3b9fec93a.1708124951.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 3/9] trailer: prepare to expose functions as part of API","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-19T21:31:41Z","receivedAt":"2024-02-19T21:31:55Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sat, Feb 17, 2024 at 12:09 AM Linus Arver via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Linus Arver <linusa@google.com>\n>\n> In the next patch, we will move \"process_trailers\" from trailer.c to\n> builtin/interpret-trailers.c. That move will necessitate the growth of\n> the trailer.h API, forcing us to expose some additional functions in\n> trailer.h.\n\nNit: actually this patch renames process_trailers() to\ninterpret_trailers() so the function that will be moved will be\ninterpret_trailers().\n\nNit: this patch and the next one will become commits, so perhaps:\n\ns/In the next patch/In a following commit/\n\n> Rename relevant functions so that they include the term \"trailer\" in\n> their name, so that clients of the API will be able to easily identify\n> them by their \"trailer\" moniker, just like all the other functions\n> already exposed by trailer.h.\n\nExcept that \"process_trailers()\" already contains \"trailer\" but will\nstill be renamed by this patch to \"interpret_trailers()\". So I think\nit might be nice to explain a bit why renaming process_trailers() to\ninterpret_trailers() makes sense too.\n\nAlso I think the subject, \"trailer: prepare to expose functions as\npart of API\" could be more explicit about what the patch is actually\ndoing, like perhaps \"trailer: rename functions to use 'trailer'\".\n\nIn general, when there is a patch called \"prepare to do X\", then we\nmight expect a following patch called something like \"actually do X\".\nBut there isn't any patch in the series named like \"trailer: expose\nfunctions as part of API\".\n\n> Take the opportunity to start putting trailer processing options (opts)\n> as the first parameter. This will be the pattern going forward in this\n> series.\n\nIt's interesting to know that this will be the pattern going forward\nin the series, but that doesn't quite tell why it's a good idea to do\nit.\n\nSo I think it might be nice to repeat an explanation similar to the\none you give in \"trailer: start preparing for formatting unification\"\nfor format_trailers_from_commit():\n\n\"Reorder parameters for format_trailers_from_commit() to prefer\n\n    const struct process_trailer_options *opts\n\nas the first parameter, because these options are intimately tied to\nformatting trailers.\"\n\nAnd maybe also say that parameters like `FILE *outfile` should be last\nbecause they are some kind of 'out' parameters.\n\n> diff --git a/trailer.c b/trailer.c\n> index f74915bd8cd..916175707d8 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> @@ -163,12 +163,12 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n>                 fprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n>  }\n>\n> -static void print_all(FILE *outfile, struct list_head *head,\n> -                     const struct process_trailer_options *opts)\n> +static void format_trailers(const struct process_trailer_options *opts,\n> +                           struct list_head *trailers, FILE *outfile)\n\nThis also renames `struct list_head *head` to `struct list_head\n*trailers`. I think it would be nice if the commit message could talk\na bit about these renames too.\n"},{"id":"488930","messageId":"CAP8UFD3KbbRApC3ktgegsi_oBDpzX_89v0QGvWoHQ057hKjbbg@mail.gmail.com","threadId":"60715","inReplyTo":"b2a0f7829a1c5f2822e9a896ffe3744587ff1298.1708124951.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 5/9] trailer: start preparing for formatting unification","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-19T21:31:55Z","receivedAt":"2024-02-19T21:32:08Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sat, Feb 17, 2024 at 12:09 AM Linus Arver via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Linus Arver <linusa@google.com>\n>\n> Currently there are two functions for formatting trailers in\n> <trailer.h>:\n>\n>     void format_trailers(const struct process_trailer_options *,\n>                          struct list_head *trailers, FILE *outfile);\n>\n>     void format_trailers_from_commit(struct strbuf *out, const char *msg,\n>                                      const struct process_trailer_options *opts);\n>\n> and although they are similar enough (even taking the same\n> process_trailer_options struct pointer) they are used quite differently.\n> One might intuitively think that format_trailers_from_commit() builds on\n> top of format_trailers(), but this is not the case. Instead\n> format_trailers_from_commit() calls format_trailer_info() and\n> format_trailers() is never called in that codepath.\n>\n> This is a preparatory refactor to help us deprecate format_trailers() in\n> favor of format_trailer_info() (at which point we can rename the latter\n> to the former). When the deprecation is complete, both\n> format_trailers_from_commit(), and the interpret-trailers builtin will\n> be able to call into the same helper function (instead of\n> format_trailers() and format_trailer_info(), respectively). Unifying the\n> formatters is desirable because it simplifies the API.\n>\n> Reorder parameters for format_trailers_from_commit() to prefer\n>\n>     const struct process_trailer_options *opts\n>\n> as the first parameter, because these options are intimately tied to\n> formatting trailers. And take\n>\n>     struct strbuf *out\n>\n> last, because it's an \"out parameter\" (something that the caller wants\n> to use as the output of this function).\n\nHere also I think the subject could be more specific like for example:\n\n\"trailer: reorder format_trailers_from_commit() parameters\"\n\n> diff --git a/trailer.c b/trailer.c\n> index d23afa0a65c..5025be97899 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> @@ -1083,10 +1083,10 @@ void trailer_info_release(struct trailer_info *info)\n>         free(info->trailers);\n>  }\n>\n> -static void format_trailer_info(struct strbuf *out,\n> +static void format_trailer_info(const struct process_trailer_options *opts,\n>                                 const struct trailer_info *info,\n>                                 const char *msg,\n> -                               const struct process_trailer_options *opts)\n> +                               struct strbuf *out)\n\nOk, so it's not just format_trailers_from_commit() parameters that are\nreordered, but also format_trailer_info() parameters. It would be nice\nif the commit message mentioned it.\n"},{"id":"488931","messageId":"CAP8UFD0JV8VEC-MDu86Mzrya9G7JBZaP2vXjFKwcKddEkh=y5g@mail.gmail.com","threadId":"60715","inReplyTo":"7c656b3f77546ae917ff192031c62d4521d9df8c.1708124951.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 9/9] format_trailers_from_commit(): indirectly call trailer_info_get()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-19T21:32:07Z","receivedAt":"2024-02-19T21:32:22Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sat, Feb 17, 2024 at 12:09 AM Linus Arver via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Linus Arver <linusa@google.com>\n>\n> This is another preparatory refactor to unify the trailer formatters.\n>\n> Instead of calling trailer_info_get() directly, call parse_trailers()\n> which already calls trailer_info_get(). This change is a NOP because\n> format_trailer_info() only looks at the \"trailers\" string array, not the\n> trailer_item objects which parse_trailers() populates.\n\nIs the extra processing done by parse_trailers() compared to\ntrailer_info_get() impacting performance?\n\nAlso when looking only at the patch, it's a bit difficult to\nunderstand that the \"trailers\" string array is the `char **trailers`\nfield in `struct trailer_info` and that the trailer_item objects are\nthe elements of the `struct list_head *head` linked list. It could\nalso be confusing because the patch is adding a new 'trailers'\nvariable with `LIST_HEAD(trailers);`. So a few more details could help\nunderstand what's going on.\n\n> In a future patch, we'll change format_trailer_info() to use the parsed\n> trailer_item objects instead of the string array.\n\nOk, so I guess the possible performance issue would disappear then, as\npopulating the trailer_item objects will be useful.\n"},{"id":"488933","messageId":"CAP8UFD1GaiG2spkd29DjTQomR+Kku8pFJAnat91mXUo2hgLEqQ@mail.gmail.com","threadId":"60715","inReplyTo":"pull.1632.v5.git.1708124950.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 0/9] Enrich Trailer API","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-02-19T21:40:18Z","receivedAt":"2024-02-19T21:40:32Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sat, Feb 17, 2024 at 12:09 AM Linus Arver via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n\n> In summary this series breaks up \"process_trailers()\" into smaller pieces,\n> exposing many of the parts relevant to trailer-related processing in\n> trailer.h. This will force us to eventually introduce unit tests for these\n> API functions, but that is a good thing for API stability. We also perform\n> some preparatory refactors in order to help us unify the trailer formatting\n> machinery toward the end of this series.\n\nI took another look and suggested some improvements to commit messages.\n\nThanks!\n"},{"id":"489696","messageId":"owlyttlq529h.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"CAP8UFD1dE2EiSxohose6U9SGn+zeHPyVB=KZ2xdQi-v-a8d1XQ@mail.gmail.com","subject":"Re: [PATCH v5 3/9] trailer: prepare to expose functions as part of API","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-29T22:33:46Z","receivedAt":"2024-02-29T22:33:48Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Sat, Feb 17, 2024 at 12:09 AM Linus Arver via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> From: Linus Arver <linusa@google.com>\n>>\n>> In the next patch, we will move \"process_trailers\" from trailer.c to\n>> builtin/interpret-trailers.c. That move will necessitate the growth of\n>> the trailer.h API, forcing us to expose some additional functions in\n>> trailer.h.\n>\n> Nit: actually this patch renames process_trailers() to\n> interpret_trailers() so the function that will be moved will be\n> interpret_trailers().\n\nOops, fixed locally.\n\n> Nit: this patch and the next one will become commits, so perhaps:\n>\n> s/In the next patch/In a following commit/\n\nTBH I've always wondered whether \"patch\" or \"commit\" matters --- I've\nseen examples of patch series that referred to \"commits\" instead of\n\"patches\", and vice versa. I was hoping to hear an opinion on this, so\nI'm happy to see (and apply) your suggestion. Thanks.\n\n>> Rename relevant functions so that they include the term \"trailer\" in\n>> their name, so that clients of the API will be able to easily identify\n>> them by their \"trailer\" moniker, just like all the other functions\n>> already exposed by trailer.h.\n>\n> Except that \"process_trailers()\" already contains \"trailer\" but will\n> still be renamed by this patch to \"interpret_trailers()\". So I think\n> it might be nice to explain a bit why renaming process_trailers() to\n> interpret_trailers() makes sense too.\n\nI will add something like:\n\n    Rename process_trailers() to interpret_trailers(), because it\n    matches the name for the builtin command of the same name\n    (git-interpret-trailers), which is the sole user of\n    process_trailers().\n\n> Also I think the subject, \"trailer: prepare to expose functions as\n> part of API\" could be more explicit about what the patch is actually\n> doing, like perhaps \"trailer: rename functions to use 'trailer'\".\n\nApplied.\n\n> In general, when there is a patch called \"prepare to do X\", then we\n> might expect a following patch called something like \"actually do X\".\n> But there isn't any patch in the series named like \"trailer: expose\n> functions as part of API\".\n\nSounds like a very sensible rule. IOW, leave the detailed explanation to\nthe commit message and use the subject line only for the most obvious\nexplanation of what's going on in the patch. +1\n\n>> Take the opportunity to start putting trailer processing options (opts)\n>> as the first parameter. This will be the pattern going forward in this\n>> series.\n>\n> It's interesting to know that this will be the pattern going forward\n> in the series, but that doesn't quite tell why it's a good idea to do\n> it.\n>\n> So I think it might be nice to repeat an explanation similar to the\n> one you give in \"trailer: start preparing for formatting unification\"\n> for format_trailers_from_commit():\n>\n> \"Reorder parameters for format_trailers_from_commit() to prefer\n>\n>     const struct process_trailer_options *opts\n>\n> as the first parameter, because these options are intimately tied to\n> formatting trailers.\"\n>\n> And maybe also say that parameters like `FILE *outfile` should be last\n> because they are some kind of 'out' parameters.\n\nSGTM, will do.\n\n>> diff --git a/trailer.c b/trailer.c\n>> index f74915bd8cd..916175707d8 100644\n>> --- a/trailer.c\n>> +++ b/trailer.c\n>> @@ -163,12 +163,12 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n>>                 fprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n>>  }\n>>\n>> -static void print_all(FILE *outfile, struct list_head *head,\n>> -                     const struct process_trailer_options *opts)\n>> +static void format_trailers(const struct process_trailer_options *opts,\n>> +                           struct list_head *trailers, FILE *outfile)\n>\n> This also renames `struct list_head *head` to `struct list_head\n> *trailers`. I think it would be nice if the commit message could talk\n> a bit about these renames too.\n\nAh nice catch. Will do.\n\nReally appreciate the quality of your reviews, thanks so much! :)\n"},{"id":"489698","messageId":"owlyr0gu51cq.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"CAP8UFD3KbbRApC3ktgegsi_oBDpzX_89v0QGvWoHQ057hKjbbg@mail.gmail.com","subject":"Re: [PATCH v5 5/9] trailer: start preparing for formatting unification","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-29T22:53:25Z","receivedAt":"2024-02-29T22:53:28Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Sat, Feb 17, 2024 at 12:09 AM Linus Arver via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> From: Linus Arver <linusa@google.com>\n>>\n>> Currently there are two functions for formatting trailers in\n>> <trailer.h>:\n>>\n>>     void format_trailers(const struct process_trailer_options *,\n>>                          struct list_head *trailers, FILE *outfile);\n>>\n>>     void format_trailers_from_commit(struct strbuf *out, const char *msg,\n>>                                      const struct process_trailer_options *opts);\n>>\n>> and although they are similar enough (even taking the same\n>> process_trailer_options struct pointer) they are used quite differently.\n>> One might intuitively think that format_trailers_from_commit() builds on\n>> top of format_trailers(), but this is not the case. Instead\n>> format_trailers_from_commit() calls format_trailer_info() and\n>> format_trailers() is never called in that codepath.\n>>\n>> This is a preparatory refactor to help us deprecate format_trailers() in\n>> favor of format_trailer_info() (at which point we can rename the latter\n>> to the former). When the deprecation is complete, both\n>> format_trailers_from_commit(), and the interpret-trailers builtin will\n>> be able to call into the same helper function (instead of\n>> format_trailers() and format_trailer_info(), respectively). Unifying the\n>> formatters is desirable because it simplifies the API.\n>>\n>> Reorder parameters for format_trailers_from_commit() to prefer\n>>\n>>     const struct process_trailer_options *opts\n>>\n>> as the first parameter, because these options are intimately tied to\n>> formatting trailers. And take\n>>\n>>     struct strbuf *out\n>>\n>> last, because it's an \"out parameter\" (something that the caller wants\n>> to use as the output of this function).\n>\n> Here also I think the subject could be more specific like for example:\n>\n> \"trailer: reorder format_trailers_from_commit() parameters\"\n\nApplied, thanks.\n\n>> diff --git a/trailer.c b/trailer.c\n>> index d23afa0a65c..5025be97899 100644\n>> --- a/trailer.c\n>> +++ b/trailer.c\n>> @@ -1083,10 +1083,10 @@ void trailer_info_release(struct trailer_info *info)\n>>         free(info->trailers);\n>>  }\n>>\n>> -static void format_trailer_info(struct strbuf *out,\n>> +static void format_trailer_info(const struct process_trailer_options *opts,\n>>                                 const struct trailer_info *info,\n>>                                 const char *msg,\n>> -                               const struct process_trailer_options *opts)\n>> +                               struct strbuf *out)\n>\n> Ok, so it's not just format_trailers_from_commit() parameters that are\n> reordered, but also format_trailer_info() parameters. It would be nice\n> if the commit message mentioned it.\n\nAgreed. Will do.\n"},{"id":"489700","messageId":"owlyo7by511o.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"CAP8UFD0JV8VEC-MDu86Mzrya9G7JBZaP2vXjFKwcKddEkh=y5g@mail.gmail.com","subject":"Re: [PATCH v5 9/9] format_trailers_from_commit(): indirectly call trailer_info_get()","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-29T23:00:03Z","receivedAt":"2024-02-29T23:00:05Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Sat, Feb 17, 2024 at 12:09 AM Linus Arver via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> From: Linus Arver <linusa@google.com>\n>>\n>> This is another preparatory refactor to unify the trailer formatters.\n>>\n>> Instead of calling trailer_info_get() directly, call parse_trailers()\n>> which already calls trailer_info_get(). This change is a NOP because\n>> format_trailer_info() only looks at the \"trailers\" string array, not the\n>> trailer_item objects which parse_trailers() populates.\n>\n> Is the extra processing done by parse_trailers() compared to\n> trailer_info_get() impacting performance?\n\nI was going to answer this now but I see that you've already reached the\nsame conclusion as me further below. ;)\n\n> Also when looking only at the patch, it's a bit difficult to\n> understand that the \"trailers\" string array is the `char **trailers`\n> field in `struct trailer_info` and that the trailer_item objects are\n> the elements of the `struct list_head *head` linked list. It could\n> also be confusing because the patch is adding a new 'trailers'\n> variable with `LIST_HEAD(trailers);`. So a few more details could help\n> understand what's going on.\n\nMakes sense. Admittedly, this is one thing that did bother me at some\npoint but which I forgot about as I got more familiar with my own patch.\n\nI will avoid shadowing the \"trailers\" word (and maybe at least add a\nsuffix or prefix to disambiguate it).\n\n>> In a future patch, we'll change format_trailer_info() to use the parsed\n>> trailer_item objects instead of the string array.\n>\n> Ok, so I guess the possible performance issue would disappear then, as\n> populating the trailer_item objects will be useful.\n\nYep, exactly. In the larger series (some 20? 30?) commits down the road\nin my local tree, I have it so that we remove `char **trailers`\nentirely (because we should be using the (smarter) trailer_item objects,\nnot raw strings, where possible).\n"},{"id":"489702","messageId":"xmqqedcuc0w3.fsf@gitster.g","threadId":"60715","inReplyTo":"owlyttlq529h.fsf@fine.c.googlers.com","subject":"Re: [PATCH v5 3/9] trailer: prepare to expose functions as part of API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-29T23:21:32Z","receivedAt":"2024-02-29T23:21:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Linus Arver <linusa@google.com> writes:\n\n>> Nit: this patch and the next one will become commits, so perhaps:\n>>\n>> s/In the next patch/In a following commit/\n>\n> TBH I've always wondered whether \"patch\" or \"commit\" matters --- I've\n> seen examples of patch series that referred to \"commits\" instead of\n> \"patches\", and vice versa. I was hoping to hear an opinion on this, so\n> I'm happy to see (and apply) your suggestion. Thanks.\n\nI think it is just fine to use either; sticking to one you pick\nconsistently in the same series would have value.  If you prefer\ncommit, then fine.  If you like patch, that's fine too.\n"},{"id":"489706","messageId":"owlyle724ylb.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"xmqqedcuc0w3.fsf@gitster.g","subject":"Re: [PATCH v5 3/9] trailer: prepare to expose functions as part of API","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-02-29T23:53:04Z","receivedAt":"2024-02-29T23:53:06Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Linus Arver <linusa@google.com> writes:\n>\n>>> Nit: this patch and the next one will become commits, so perhaps:\n>>>\n>>> s/In the next patch/In a following commit/\n>>\n>> TBH I've always wondered whether \"patch\" or \"commit\" matters --- I've\n>> seen examples of patch series that referred to \"commits\" instead of\n>> \"patches\", and vice versa. I was hoping to hear an opinion on this, so\n>> I'm happy to see (and apply) your suggestion. Thanks.\n>\n> I think it is just fine to use either; sticking to one you pick\n> consistently in the same series would have value.  If you prefer\n> commit, then fine.  If you like patch, that's fine too.\n\nMakes sense, thanks.\n"},{"id":"489709","messageId":"pull.1632.v6.git.1709252086.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v5.git.1708124950.gitgitgadget@gmail.com","subject":"[PATCH v6 0/9] Enrich Trailer API","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-03-01T00:14:37Z","receivedAt":"2024-03-01T00:14:50Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"This patch series is the first 9 patches of a larger cleanup/bugfix series\n(henceforth \"larger series\") I've been working on. The main goal of this\nseries is to begin the process of \"libifying\" the trailer API. By \"API\" I\nmean the interface exposed in trailer.h. The larger series brings a number\nof additional cleanups (exposing and fixing some bugs along the way), and\nbuilds on top of this series.\n\nWhen the larger series is merged, we will be in a good state to additionally\npursue the following goals:\n\n 1. \"API reuse inside Git\": make the API expressive enough to eliminate any\n    need by other parts of Git to use the interpret-trailers builtin as a\n    subprocess (instead they could just use the API directly);\n 2. \"API stability\": add unit tests to codify the expected behavior of API\n    functions; and\n 3. \"API documentation\": create developer-focused documentation to explain\n    how to use the API effectively, noting any API limitations or\n    anti-patterns.\n\nIn the future after libification is \"complete\", users external to Git will\nbe able to use the same trailer processing API used by the\ninterpret-trailers builtin. For example, a web server may want to parse\ntrailers the same way that Git would parse them, without having to call\ninterpret-trailers as a subprocess. This use case was the original\nmotivation behind my work in this area.\n\nWith the libification-focused goals out of the way, let's turn to this patch\nseries in more detail.\n\nIn summary this series breaks up \"process_trailers()\" into smaller pieces,\nexposing many of the parts relevant to trailer-related processing in\ntrailer.h. This will force us to eventually introduce unit tests for these\nAPI functions, but that is a good thing for API stability. We also perform\nsome preparatory refactors in order to help us unify the trailer formatting\nmachinery toward the end of this series.\n\n\nNotable changes in v6\n=====================\n\n * Mainly wording changes to commit messages. Thanks to Christian for the\n   suggestions.\n\n\nNotable changes in v5\n=====================\n\n * Removed patches 10+ from this series. Thanks to Christian for the\n   suggestion.\n * Reworded the log message of patch 09 to reflect the above arrangement, as\n   suggested by Christian.\n\n\nNotable changes in v4\n=====================\n\n * Patches 3, 4, 5, and 8 have been broken up into smaller steps. There are\n   28 instead of 10 patches now, but these 28 should be much easier to\n   review than the (previously condensed) 10.\n * NEW Patch 1: \"trailer: free trailer_info after all related usage\" fixes\n   awkward use-after-free coding style\n * NEW Patch 2: \"shortlog: add test for de-duplicating folded trailers\"\n   increases test coverage related to trailer iterators and \"unfold_value()\"\n * NEW Patch 27: \"trailer_set_*(): put out parameter at the end\" is a small\n   refactor to reorder parameters.\n * Patches 5-16: These smaller patches make up Patch 3 from v3.\n * Patches 17-18: These smaller patches make up Patch 4 from v3.\n * Patches 19-20: These smaller patches make up Patch 5 from v3.\n * Patches 23-26: These smaller patches make up Patch 8 from v3.\n * Anonymize unambiguous parameters in <trailer.h>.\n\n\nNotable changes in v3\n=====================\n\n * Squashed Patch 4 into Patch 3 (\"trailer: unify trailer formatting\n   machinery\"), to avoid breaking the build (\"-Werror=unused-function\"\n   violations)\n * NEW (Patch 10): Introduce \"trailer template\" terminology for readability\n   (no behavioral change)\n * (API function) Rename default_separators() to\n   trailer_default_separators()\n * (API function) Rename new_trailers_clear() to free_trailer_templates()\n * trailer.h: for single-parameter functions, anonymize the parameter name\n   to reduce verbosity\n\n\nNotable changes in v2\n=====================\n\n * (cover letter) Discuss goals of the larger series in more detail,\n   especially the pimpl idiom\n * (cover letter) List bug fixes pending in the larger series that depend on\n   this series\n * Reorder function parameters to have trailer options at the beginning (and\n   out parameters toward the end)\n * \"sequencer: use the trailer iterator\": prefer C string instead of strbuf\n   for new \"raw\" field\n * Patch 1 (was Patch 2) also renames ensure_configured() to\n   trailer_config_init() (forgot to rename this one previously)\n\nLinus Arver (9):\n  trailer: free trailer_info _after_ all related usage\n  shortlog: add test for de-duplicating folded trailers\n  trailer: rename functions to use 'trailer'\n  trailer: move interpret_trailers() to interpret-trailers.c\n  trailer: reorder format_trailers_from_commit() parameters\n  trailer_info_get(): reorder parameters\n  format_trailers(): use strbuf instead of FILE\n  format_trailer_info(): move \"fast path\" to caller\n  format_trailers_from_commit(): indirectly call trailer_info_get()\n\n builtin/interpret-trailers.c | 101 ++++++++++++++++++-\n pretty.c                     |   2 +-\n ref-filter.c                 |   2 +-\n sequencer.c                  |   2 +-\n t/t4201-shortlog.sh          |  32 +++++++\n trailer.c                    | 181 +++++++++--------------------------\n trailer.h                    |  31 ++++--\n 7 files changed, 204 insertions(+), 147 deletions(-)\n\n\nbase-commit: a54a84b333adbecf7bc4483c0e36ed5878cac17b\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1632%2Flistx%2Ftrailer-api-refactor-part-1-v6\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1632/listx/trailer-api-refactor-part-1-v6\nPull-Request: https://github.com/gitgitgadget/git/pull/1632\n\nRange-diff vs v5:\n\n  1:  652df25f30e =  1:  652df25f30e trailer: free trailer_info _after_ all related usage\n  2:  fdccaca2ba0 =  2:  fdccaca2ba0 shortlog: add test for de-duplicating folded trailers\n  3:  4372af244f0 !  3:  7b1d739cddb trailer: prepare to expose functions as part of API\n     @@ Metadata\n      Author: Linus Arver <linusa@google.com>\n      \n       ## Commit message ##\n     -    trailer: prepare to expose functions as part of API\n     +    trailer: rename functions to use 'trailer'\n      \n     -    In the next patch, we will move \"process_trailers\" from trailer.c to\n     -    builtin/interpret-trailers.c. That move will necessitate the growth of\n     -    the trailer.h API, forcing us to expose some additional functions in\n     +    Rename process_trailers() to interpret_trailers(), because it matches\n     +    the name for the builtin command of the same name\n     +    (git-interpret-trailers), which is the sole user of process_trailers().\n     +\n     +    In a following commit, we will move \"interpret_trailers\" from trailer.c\n     +    to builtin/interpret-trailers.c. That move will necessitate the growth\n     +    of the trailer.h API, forcing us to expose some additional functions in\n          trailer.h.\n      \n          Rename relevant functions so that they include the term \"trailer\" in\n     @@ Commit message\n          them by their \"trailer\" moniker, just like all the other functions\n          already exposed by trailer.h.\n      \n     -    Take the opportunity to start putting trailer processing options (opts)\n     -    as the first parameter. This will be the pattern going forward in this\n     -    series.\n     +    Rename `struct list_head *head` to `struct list_head *trailers` because\n     +    \"head\" conveys no additional information beyond the \"list_head\" type.\n     +\n     +    Reorder parameters for format_trailers_from_commit() to prefer\n     +\n     +        const struct process_trailer_options *opts\n     +\n     +    as the first parameter, because these options are intimately tied to\n     +    formatting trailers. Parameters like `FILE *outfile` should be last\n     +    because they are a kind of 'out' parameter, so put such parameters at\n     +    the end. This will be the pattern going forward in this series.\n      \n          Helped-by: Junio C Hamano <gitster@pobox.com>\n     +    Helped-by: Christian Couder <chriscool@tuxfamily.org>\n          Signed-off-by: Linus Arver <linusa@google.com>\n      \n       ## builtin/interpret-trailers.c ##\n  4:  4073b8eb510 =  4:  7ac4da3019a trailer: move interpret_trailers() to interpret-trailers.c\n  5:  b2a0f7829a1 !  5:  47c994ce025 trailer: start preparing for formatting unification\n     @@ Metadata\n      Author: Linus Arver <linusa@google.com>\n      \n       ## Commit message ##\n     -    trailer: start preparing for formatting unification\n     +    trailer: reorder format_trailers_from_commit() parameters\n      \n          Currently there are two functions for formatting trailers in\n          <trailer.h>:\n     @@ Commit message\n          last, because it's an \"out parameter\" (something that the caller wants\n          to use as the output of this function).\n      \n     +    Similarly, reorder parameters for format_trailer_info(), because later\n     +    on we will unify the two together.\n     +\n          Signed-off-by: Linus Arver <linusa@google.com>\n      \n       ## pretty.c ##\n  6:  c1760f80356 =  6:  7a565580167 trailer_info_get(): reorder parameters\n  7:  9dc912b5bc5 =  7:  46c7f4c0e81 format_trailers(): use strbuf instead of FILE\n  8:  b97c06d8bc3 =  8:  26b1f19d0e1 format_trailer_info(): move \"fast path\" to caller\n  9:  7c656b3f775 !  9:  0e884d870c8 format_trailers_from_commit(): indirectly call trailer_info_get()\n     @@ Commit message\n      \n          This is another preparatory refactor to unify the trailer formatters.\n      \n     -    Instead of calling trailer_info_get() directly, call parse_trailers()\n     -    which already calls trailer_info_get(). This change is a NOP because\n     -    format_trailer_info() only looks at the \"trailers\" string array, not the\n     -    trailer_item objects which parse_trailers() populates.\n     +    For background, note that the \"trailers\" string array is the\n     +    `char **trailers` member in `struct trailer_info` and that the\n     +    trailer_item objects are the elements of the `struct list_head *head`\n     +    linked list.\n     +\n     +    Currently trailer_info_get() only populates `char **trailers`. And\n     +    parse_trailers() first calls trailer_info_get() so that it can use the\n     +    `char **trailers` to populate a list of `struct trailer_item` objects\n     +\n     +    Instead of calling trailer_info_get() directly from\n     +    format_trailers_from_commit(), make it call parse_trailers() instead\n     +    because parse_trailers() already calls trailer_info_get().\n     +\n     +    This change is a NOP because format_trailer_info() (which\n     +    format_trailers_from_commit() wraps around) only looks at the \"trailers\"\n     +    string array, not the trailer_item objects which parse_trailers()\n     +    populates. For now we do need to create a dummy\n     +\n     +        LIST_HEAD(trailer_objects);\n     +\n     +    because parse_trailers() expects it in its signature.\n      \n          In a future patch, we'll change format_trailer_info() to use the parsed\n     -    trailer_item objects instead of the string array.\n     +    trailer_item objects (trailer_objects) instead of the `char **trailers`\n     +    array.\n      \n          Signed-off-by: Linus Arver <linusa@google.com>\n      \n     @@ trailer.c: void format_trailers_from_commit(const struct process_trailer_options\n       \t\t\t\t const char *msg,\n       \t\t\t\t struct strbuf *out)\n       {\n     -+\tLIST_HEAD(trailers);\n     ++\tLIST_HEAD(trailer_objects);\n       \tstruct trailer_info info;\n       \n      -\ttrailer_info_get(opts, msg, &info);\n     -+\tparse_trailers(opts, &info, msg, &trailers);\n     ++\tparse_trailers(opts, &info, msg, &trailer_objects);\n      +\n       \t/* If we want the whole block untouched, we can take the fast path. */\n       \tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n     @@ trailer.c: void format_trailers_from_commit(const struct process_trailer_options\n       \t} else\n       \t\tformat_trailer_info(opts, &info, out);\n       \n     -+\tfree_trailers(&trailers);\n     ++\tfree_trailers(&trailer_objects);\n       \ttrailer_info_release(&info);\n       }\n       \n\n-- \ngitgitgadget\n"},{"id":"489710","messageId":"652df25f30ecc8227bef5109b0f7b47817386b13.1709252086.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v6.git.1709252086.gitgitgadget@gmail.com","subject":"[PATCH v6 1/9] trailer: free trailer_info _after_ all related usage","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-03-01T00:14:38Z","receivedAt":"2024-03-01T00:14:50Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nIn de7c27a186 (trailer: use offsets for trailer_start/trailer_end,\n2023-10-20), we started using trailer block offsets in trailer_info. In\nparticular, we dropped the use of a separate stack variable \"size_t\ntrailer_end\", in favor of accessing the new \"trailer_block_end\" member\nof trailer_info (as \"info.trailer_block_end\").\n\nAt that time, we forgot to also move the\n\n   trailer_info_release(&info);\n\nline to be _after_ this new use of the trailer_info struct. Move it now.\n\nNote that even without this patch, we didn't have leaks or any other\nproblems because trailer_info_release() only frees memory allocated on\nthe heap. The \"trailer_block_end\" member was allocated on the stack back\nthen (as it is now) so it was still safe to use for all this time.\n\nReported-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 3 +--\n 1 file changed, 1 insertion(+), 2 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 3a0710a4583..e1d83390b66 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1111,13 +1111,12 @@ void process_trailers(const char *file,\n \t}\n \n \tprint_all(outfile, &head, opts);\n-\n \tfree_all(&head);\n-\ttrailer_info_release(&info);\n \n \t/* Print the lines after the trailers as is */\n \tif (!opts->only_trailers)\n \t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n+\ttrailer_info_release(&info);\n \n \tif (opts->in_place)\n \t\tif (rename_tempfile(&trailers_tempfile, file))\n-- \ngitgitgadget\n\n"},{"id":"489711","messageId":"fdccaca2ba0c92a3f2b33f6608182ab80507ec53.1709252086.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v6.git.1709252086.gitgitgadget@gmail.com","subject":"[PATCH v6 2/9] shortlog: add test for de-duplicating folded trailers","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-03-01T00:14:39Z","receivedAt":"2024-03-01T00:14:51Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThe shortlog builtin was taught to use the trailer iterator interface in\n47beb37bc6 (shortlog: match commit trailers with --group, 2020-09-27).\nThe iterator always unfolds values and this has always been the case\nsince the time the iterator was first introduced in f0939a0eb1 (trailer:\nadd interface for iterating over commit trailers, 2020-09-27). Add a\ncomment line to remind readers of this behavior.\n\nThe fact that the iterator always unfolds values is important\n(at least for shortlog) because unfolding allows it to recognize both\nfolded and unfolded versions of the same trailer for de-duplication.\n\nCapture the existing behavior in a new test case to guard against\nregressions in this area. This test case is based off of the existing\n\"shortlog de-duplicates trailers in a single commit\" just above it. Now\nif we were to remove the call to\n\n    unfold_value(&iter->val);\n\ninside the iterator, this new test case will break.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n t/t4201-shortlog.sh | 32 ++++++++++++++++++++++++++++++++\n trailer.c           |  1 +\n 2 files changed, 33 insertions(+)\n\ndiff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh\nindex d7382709fc1..f698d0c9ad2 100755\n--- a/t/t4201-shortlog.sh\n+++ b/t/t4201-shortlog.sh\n@@ -312,6 +312,38 @@ test_expect_success 'shortlog de-duplicates trailers in a single commit' '\n \ttest_cmp expect actual\n '\n \n+# Trailers that have unfolded (single line) and folded (multiline) values which\n+# are otherwise identical are treated as the same trailer for de-duplication.\n+test_expect_success 'shortlog de-duplicates trailers in a single commit (folded/unfolded values)' '\n+\tgit commit --allow-empty -F - <<-\\EOF &&\n+\tsubject one\n+\n+\tthis message has two distinct values, plus a repeat (folded)\n+\n+\tRepeated-trailer: Foo foo foo\n+\tRepeated-trailer: Bar\n+\tRepeated-trailer: Foo\n+\t  foo foo\n+\tEOF\n+\n+\tgit commit --allow-empty -F - <<-\\EOF &&\n+\tsubject two\n+\n+\tsimilar to the previous, but without the second distinct value\n+\n+\tRepeated-trailer: Foo foo foo\n+\tRepeated-trailer: Foo\n+\t  foo foo\n+\tEOF\n+\n+\tcat >expect <<-\\EOF &&\n+\t     2\tFoo foo foo\n+\t     1\tBar\n+\tEOF\n+\tgit shortlog -ns --group=trailer:repeated-trailer -2 HEAD >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'shortlog can match multiple groups' '\n \tgit commit --allow-empty -F - <<-\\EOF &&\n \tsubject one\ndiff --git a/trailer.c b/trailer.c\nindex e1d83390b66..f74915bd8cd 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1270,6 +1270,7 @@ int trailer_iterator_advance(struct trailer_iterator *iter)\n \t\tstrbuf_reset(&iter->val);\n \t\tparse_trailer(&iter->key, &iter->val, NULL,\n \t\t\t      trailer, separator_pos);\n+\t\t/* Always unfold values during iteration. */\n \t\tunfold_value(&iter->val);\n \t\treturn 1;\n \t}\n-- \ngitgitgadget\n\n"},{"id":"489712","messageId":"7b1d739cddb3b7e974ccb8d05d99e5fc8df61910.1709252086.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v6.git.1709252086.gitgitgadget@gmail.com","subject":"[PATCH v6 3/9] trailer: rename functions to use 'trailer'","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-03-01T00:14:40Z","receivedAt":"2024-03-01T00:14:53Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nRename process_trailers() to interpret_trailers(), because it matches\nthe name for the builtin command of the same name\n(git-interpret-trailers), which is the sole user of process_trailers().\n\nIn a following commit, we will move \"interpret_trailers\" from trailer.c\nto builtin/interpret-trailers.c. That move will necessitate the growth\nof the trailer.h API, forcing us to expose some additional functions in\ntrailer.h.\n\nRename relevant functions so that they include the term \"trailer\" in\ntheir name, so that clients of the API will be able to easily identify\nthem by their \"trailer\" moniker, just like all the other functions\nalready exposed by trailer.h.\n\nRename `struct list_head *head` to `struct list_head *trailers` because\n\"head\" conveys no additional information beyond the \"list_head\" type.\n\nReorder parameters for format_trailers_from_commit() to prefer\n\n    const struct process_trailer_options *opts\n\nas the first parameter, because these options are intimately tied to\nformatting trailers. Parameters like `FILE *outfile` should be last\nbecause they are a kind of 'out' parameter, so put such parameters at\nthe end. This will be the pattern going forward in this series.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  4 ++--\n trailer.c                    | 26 +++++++++++++-------------\n trailer.h                    |  6 +++---\n 3 files changed, 18 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 033bd1556cf..85a3413baf5 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -132,11 +132,11 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n \tif (argc) {\n \t\tint i;\n \t\tfor (i = 0; i < argc; i++)\n-\t\t\tprocess_trailers(argv[i], &opts, &trailers);\n+\t\t\tinterpret_trailers(&opts, &trailers, argv[i]);\n \t} else {\n \t\tif (opts.in_place)\n \t\t\tdie(_(\"no input file given for in-place editing\"));\n-\t\tprocess_trailers(NULL, &opts, &trailers);\n+\t\tinterpret_trailers(&opts, &trailers, NULL);\n \t}\n \n \tnew_trailers_clear(&trailers);\ndiff --git a/trailer.c b/trailer.c\nindex f74915bd8cd..916175707d8 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -163,12 +163,12 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n-static void print_all(FILE *outfile, struct list_head *head,\n-\t\t      const struct process_trailer_options *opts)\n+static void format_trailers(const struct process_trailer_options *opts,\n+\t\t\t    struct list_head *trailers, FILE *outfile)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *item;\n-\tlist_for_each(pos, head) {\n+\tlist_for_each(pos, trailers) {\n \t\titem = list_entry(pos, struct trailer_item, list);\n \t\tif ((!opts->trim_empty || strlen(item->value) > 0) &&\n \t\t    (!opts->only_trailers || item->token))\n@@ -589,7 +589,7 @@ static int git_trailer_config(const char *conf_key, const char *value,\n \treturn 0;\n }\n \n-static void ensure_configured(void)\n+static void trailer_config_init(void)\n {\n \tif (configured)\n \t\treturn;\n@@ -1035,10 +1035,10 @@ static void parse_trailers(struct trailer_info *info,\n \t}\n }\n \n-static void free_all(struct list_head *head)\n+static void free_trailers(struct list_head *trailers)\n {\n \tstruct list_head *pos, *p;\n-\tlist_for_each_safe(pos, p, head) {\n+\tlist_for_each_safe(pos, p, trailers) {\n \t\tlist_del(pos);\n \t\tfree_trailer_item(list_entry(pos, struct trailer_item, list));\n \t}\n@@ -1075,16 +1075,16 @@ static FILE *create_in_place_tempfile(const char *file)\n \treturn outfile;\n }\n \n-void process_trailers(const char *file,\n-\t\t      const struct process_trailer_options *opts,\n-\t\t      struct list_head *new_trailer_head)\n+void interpret_trailers(const struct process_trailer_options *opts,\n+\t\t\tstruct list_head *new_trailer_head,\n+\t\t\tconst char *file)\n {\n \tLIST_HEAD(head);\n \tstruct strbuf sb = STRBUF_INIT;\n \tstruct trailer_info info;\n \tFILE *outfile = stdout;\n \n-\tensure_configured();\n+\ttrailer_config_init();\n \n \tread_input_file(&sb, file);\n \n@@ -1110,8 +1110,8 @@ void process_trailers(const char *file,\n \t\tprocess_trailers_lists(&head, &arg_head);\n \t}\n \n-\tprint_all(outfile, &head, opts);\n-\tfree_all(&head);\n+\tformat_trailers(opts, &head, outfile);\n+\tfree_trailers(&head);\n \n \t/* Print the lines after the trailers as is */\n \tif (!opts->only_trailers)\n@@ -1134,7 +1134,7 @@ void trailer_info_get(struct trailer_info *info, const char *str,\n \tsize_t nr = 0, alloc = 0;\n \tchar **last = NULL;\n \n-\tensure_configured();\n+\ttrailer_config_init();\n \n \tend_of_log_message = find_end_of_log_message(str, opts->no_divider);\n \ttrailer_block_start = find_trailer_block_start(str, end_of_log_message);\ndiff --git a/trailer.h b/trailer.h\nindex 1644cd05f60..37033e631a1 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -81,9 +81,9 @@ struct process_trailer_options {\n \n #define PROCESS_TRAILER_OPTIONS_INIT {0}\n \n-void process_trailers(const char *file,\n-\t\t      const struct process_trailer_options *opts,\n-\t\t      struct list_head *new_trailer_head);\n+void interpret_trailers(const struct process_trailer_options *opts,\n+\t\t\tstruct list_head *new_trailer_head,\n+\t\t\tconst char *file);\n \n void trailer_info_get(struct trailer_info *info, const char *str,\n \t\t      const struct process_trailer_options *opts);\n-- \ngitgitgadget\n\n"},{"id":"489714","messageId":"7ac4da3019ac4210e304a066275745a0a93630df.1709252086.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v6.git.1709252086.gitgitgadget@gmail.com","subject":"[PATCH v6 4/9] trailer: move interpret_trailers() to interpret-trailers.c","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-03-01T00:14:41Z","receivedAt":"2024-03-01T00:14:53Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThe interpret-trailers.c builtin is the only place we need to call\ninterpret_trailers(), so move its definition there (together with a few\nhelper functions called only by it) and remove its external declaration\nfrom <trailer.h>.\n\nSeveral helper functions that are called by interpret_trailers() remain\nin trailer.c because other callers in the same file still call them.\nDeclare them in <trailer.h> so that interpret_trailers() (now in\nbuiltin/interpret-trailers.c) can continue calling them as a trailer API\nuser.\n\nThis enriches <trailer.h> with a more granular API, which can then be\nunit-tested in the future (because interpret_trailers() by itself does\ntoo many things to be able to be easily unit-tested).\n\nTake this opportunity to demote some file-handling functions out of the\ntrailer API implementation, as these have nothing to do with trailers.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  93 +++++++++++++++++++++++++++\n trailer.c                    | 119 ++++-------------------------------\n trailer.h                    |  20 +++++-\n 3 files changed, 123 insertions(+), 109 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 85a3413baf5..d1cf0aa33a2 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -9,6 +9,7 @@\n #include \"gettext.h\"\n #include \"parse-options.h\"\n #include \"string-list.h\"\n+#include \"tempfile.h\"\n #include \"trailer.h\"\n #include \"config.h\"\n \n@@ -91,6 +92,98 @@ static int parse_opt_parse(const struct option *opt, const char *arg,\n \treturn 0;\n }\n \n+static struct tempfile *trailers_tempfile;\n+\n+static FILE *create_in_place_tempfile(const char *file)\n+{\n+\tstruct stat st;\n+\tstruct strbuf filename_template = STRBUF_INIT;\n+\tconst char *tail;\n+\tFILE *outfile;\n+\n+\tif (stat(file, &st))\n+\t\tdie_errno(_(\"could not stat %s\"), file);\n+\tif (!S_ISREG(st.st_mode))\n+\t\tdie(_(\"file %s is not a regular file\"), file);\n+\tif (!(st.st_mode & S_IWUSR))\n+\t\tdie(_(\"file %s is not writable by user\"), file);\n+\n+\t/* Create temporary file in the same directory as the original */\n+\ttail = strrchr(file, '/');\n+\tif (tail)\n+\t\tstrbuf_add(&filename_template, file, tail - file + 1);\n+\tstrbuf_addstr(&filename_template, \"git-interpret-trailers-XXXXXX\");\n+\n+\ttrailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode);\n+\tstrbuf_release(&filename_template);\n+\toutfile = fdopen_tempfile(trailers_tempfile, \"w\");\n+\tif (!outfile)\n+\t\tdie_errno(_(\"could not open temporary file\"));\n+\n+\treturn outfile;\n+}\n+\n+static void read_input_file(struct strbuf *sb, const char *file)\n+{\n+\tif (file) {\n+\t\tif (strbuf_read_file(sb, file, 0) < 0)\n+\t\t\tdie_errno(_(\"could not read input file '%s'\"), file);\n+\t} else {\n+\t\tif (strbuf_read(sb, fileno(stdin), 0) < 0)\n+\t\t\tdie_errno(_(\"could not read from stdin\"));\n+\t}\n+}\n+\n+static void interpret_trailers(const struct process_trailer_options *opts,\n+\t\t\t       struct list_head *new_trailer_head,\n+\t\t\t       const char *file)\n+{\n+\tLIST_HEAD(head);\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tstruct trailer_info info;\n+\tFILE *outfile = stdout;\n+\n+\ttrailer_config_init();\n+\n+\tread_input_file(&sb, file);\n+\n+\tif (opts->in_place)\n+\t\toutfile = create_in_place_tempfile(file);\n+\n+\tparse_trailers(opts, &info, sb.buf, &head);\n+\n+\t/* Print the lines before the trailers */\n+\tif (!opts->only_trailers)\n+\t\tfwrite(sb.buf, 1, info.trailer_block_start, outfile);\n+\n+\tif (!opts->only_trailers && !info.blank_line_before_trailer)\n+\t\tfprintf(outfile, \"\\n\");\n+\n+\n+\tif (!opts->only_input) {\n+\t\tLIST_HEAD(config_head);\n+\t\tLIST_HEAD(arg_head);\n+\t\tparse_trailers_from_config(&config_head);\n+\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n+\t\tlist_splice(&config_head, &arg_head);\n+\t\tprocess_trailers_lists(&head, &arg_head);\n+\t}\n+\n+\tformat_trailers(opts, &head, outfile);\n+\tfree_trailers(&head);\n+\n+\t/* Print the lines after the trailers as is */\n+\tif (!opts->only_trailers)\n+\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n+\ttrailer_info_release(&info);\n+\n+\tif (opts->in_place)\n+\t\tif (rename_tempfile(&trailers_tempfile, file))\n+\t\t\tdie_errno(_(\"could not rename temporary file to %s\"), file);\n+\n+\tstrbuf_release(&sb);\n+}\n+\n int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n {\n \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\ndiff --git a/trailer.c b/trailer.c\nindex 916175707d8..d23afa0a65c 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -5,7 +5,6 @@\n #include \"string-list.h\"\n #include \"run-command.h\"\n #include \"commit.h\"\n-#include \"tempfile.h\"\n #include \"trailer.h\"\n #include \"list.h\"\n /*\n@@ -163,8 +162,8 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n-static void format_trailers(const struct process_trailer_options *opts,\n-\t\t\t    struct list_head *trailers, FILE *outfile)\n+void format_trailers(const struct process_trailer_options *opts,\n+\t\t     struct list_head *trailers, FILE *outfile)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *item;\n@@ -366,8 +365,8 @@ static int find_same_and_apply_arg(struct list_head *head,\n \treturn 0;\n }\n \n-static void process_trailers_lists(struct list_head *head,\n-\t\t\t\t   struct list_head *arg_head)\n+void process_trailers_lists(struct list_head *head,\n+\t\t\t    struct list_head *arg_head)\n {\n \tstruct list_head *pos, *p;\n \tstruct arg_item *arg_tok;\n@@ -589,7 +588,7 @@ static int git_trailer_config(const char *conf_key, const char *value,\n \treturn 0;\n }\n \n-static void trailer_config_init(void)\n+void trailer_config_init(void)\n {\n \tif (configured)\n \t\treturn;\n@@ -719,7 +718,7 @@ static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n \tlist_add_tail(&new_item->list, arg_head);\n }\n \n-static void parse_trailers_from_config(struct list_head *config_head)\n+void parse_trailers_from_config(struct list_head *config_head)\n {\n \tstruct arg_item *item;\n \tstruct list_head *pos;\n@@ -735,8 +734,8 @@ static void parse_trailers_from_config(struct list_head *config_head)\n \t}\n }\n \n-static void parse_trailers_from_command_line_args(struct list_head *arg_head,\n-\t\t\t\t\t\t  struct list_head *new_trailer_head)\n+void parse_trailers_from_command_line_args(struct list_head *arg_head,\n+\t\t\t\t\t   struct list_head *new_trailer_head)\n {\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n@@ -775,17 +774,6 @@ static void parse_trailers_from_command_line_args(struct list_head *arg_head,\n \tfree(cl_separators);\n }\n \n-static void read_input_file(struct strbuf *sb, const char *file)\n-{\n-\tif (file) {\n-\t\tif (strbuf_read_file(sb, file, 0) < 0)\n-\t\t\tdie_errno(_(\"could not read input file '%s'\"), file);\n-\t} else {\n-\t\tif (strbuf_read(sb, fileno(stdin), 0) < 0)\n-\t\t\tdie_errno(_(\"could not read from stdin\"));\n-\t}\n-}\n-\n static const char *next_line(const char *str)\n {\n \tconst char *nl = strchrnul(str, '\\n');\n@@ -1000,10 +988,10 @@ static void unfold_value(struct strbuf *val)\n  * Parse trailers in \"str\", populating the trailer info and \"head\"\n  * linked list structure.\n  */\n-static void parse_trailers(struct trailer_info *info,\n-\t\t\t     const char *str,\n-\t\t\t     struct list_head *head,\n-\t\t\t     const struct process_trailer_options *opts)\n+void parse_trailers(const struct process_trailer_options *opts,\n+\t\t    struct trailer_info *info,\n+\t\t    const char *str,\n+\t\t    struct list_head *head)\n {\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n@@ -1035,7 +1023,7 @@ static void parse_trailers(struct trailer_info *info,\n \t}\n }\n \n-static void free_trailers(struct list_head *trailers)\n+void free_trailers(struct list_head *trailers)\n {\n \tstruct list_head *pos, *p;\n \tlist_for_each_safe(pos, p, trailers) {\n@@ -1044,87 +1032,6 @@ static void free_trailers(struct list_head *trailers)\n \t}\n }\n \n-static struct tempfile *trailers_tempfile;\n-\n-static FILE *create_in_place_tempfile(const char *file)\n-{\n-\tstruct stat st;\n-\tstruct strbuf filename_template = STRBUF_INIT;\n-\tconst char *tail;\n-\tFILE *outfile;\n-\n-\tif (stat(file, &st))\n-\t\tdie_errno(_(\"could not stat %s\"), file);\n-\tif (!S_ISREG(st.st_mode))\n-\t\tdie(_(\"file %s is not a regular file\"), file);\n-\tif (!(st.st_mode & S_IWUSR))\n-\t\tdie(_(\"file %s is not writable by user\"), file);\n-\n-\t/* Create temporary file in the same directory as the original */\n-\ttail = strrchr(file, '/');\n-\tif (tail)\n-\t\tstrbuf_add(&filename_template, file, tail - file + 1);\n-\tstrbuf_addstr(&filename_template, \"git-interpret-trailers-XXXXXX\");\n-\n-\ttrailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode);\n-\tstrbuf_release(&filename_template);\n-\toutfile = fdopen_tempfile(trailers_tempfile, \"w\");\n-\tif (!outfile)\n-\t\tdie_errno(_(\"could not open temporary file\"));\n-\n-\treturn outfile;\n-}\n-\n-void interpret_trailers(const struct process_trailer_options *opts,\n-\t\t\tstruct list_head *new_trailer_head,\n-\t\t\tconst char *file)\n-{\n-\tLIST_HEAD(head);\n-\tstruct strbuf sb = STRBUF_INIT;\n-\tstruct trailer_info info;\n-\tFILE *outfile = stdout;\n-\n-\ttrailer_config_init();\n-\n-\tread_input_file(&sb, file);\n-\n-\tif (opts->in_place)\n-\t\toutfile = create_in_place_tempfile(file);\n-\n-\tparse_trailers(&info, sb.buf, &head, opts);\n-\n-\t/* Print the lines before the trailers */\n-\tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf, 1, info.trailer_block_start, outfile);\n-\n-\tif (!opts->only_trailers && !info.blank_line_before_trailer)\n-\t\tfprintf(outfile, \"\\n\");\n-\n-\n-\tif (!opts->only_input) {\n-\t\tLIST_HEAD(config_head);\n-\t\tLIST_HEAD(arg_head);\n-\t\tparse_trailers_from_config(&config_head);\n-\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n-\t\tlist_splice(&config_head, &arg_head);\n-\t\tprocess_trailers_lists(&head, &arg_head);\n-\t}\n-\n-\tformat_trailers(opts, &head, outfile);\n-\tfree_trailers(&head);\n-\n-\t/* Print the lines after the trailers as is */\n-\tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);\n-\ttrailer_info_release(&info);\n-\n-\tif (opts->in_place)\n-\t\tif (rename_tempfile(&trailers_tempfile, file))\n-\t\t\tdie_errno(_(\"could not rename temporary file to %s\"), file);\n-\n-\tstrbuf_release(&sb);\n-}\n-\n void trailer_info_get(struct trailer_info *info, const char *str,\n \t\t      const struct process_trailer_options *opts)\n {\ndiff --git a/trailer.h b/trailer.h\nindex 37033e631a1..c292d44b62f 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -81,15 +81,29 @@ struct process_trailer_options {\n \n #define PROCESS_TRAILER_OPTIONS_INIT {0}\n \n-void interpret_trailers(const struct process_trailer_options *opts,\n-\t\t\tstruct list_head *new_trailer_head,\n-\t\t\tconst char *file);\n+void parse_trailers_from_config(struct list_head *config_head);\n+\n+void parse_trailers_from_command_line_args(struct list_head *arg_head,\n+\t\t\t\t\t   struct list_head *new_trailer_head);\n+\n+void process_trailers_lists(struct list_head *head,\n+\t\t\t    struct list_head *arg_head);\n+\n+void parse_trailers(const struct process_trailer_options *,\n+\t\t    struct trailer_info *,\n+\t\t    const char *str,\n+\t\t    struct list_head *head);\n \n void trailer_info_get(struct trailer_info *info, const char *str,\n \t\t      const struct process_trailer_options *opts);\n \n void trailer_info_release(struct trailer_info *info);\n \n+void trailer_config_init(void);\n+void format_trailers(const struct process_trailer_options *,\n+\t\t     struct list_head *trailers, FILE *outfile);\n+void free_trailers(struct list_head *);\n+\n /*\n  * Format the trailers from the commit msg \"msg\" into the strbuf \"out\".\n  * Note two caveats about \"opts\":\n-- \ngitgitgadget\n\n"},{"id":"489713","messageId":"47c994ce025e5574454234b686c41b7b9170cf13.1709252086.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v6.git.1709252086.gitgitgadget@gmail.com","subject":"[PATCH v6 5/9] trailer: reorder format_trailers_from_commit() parameters","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-03-01T00:14:42Z","receivedAt":"2024-03-01T00:14:54Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nCurrently there are two functions for formatting trailers in\n<trailer.h>:\n\n    void format_trailers(const struct process_trailer_options *,\n                         struct list_head *trailers, FILE *outfile);\n\n    void format_trailers_from_commit(struct strbuf *out, const char *msg,\n                                     const struct process_trailer_options *opts);\n\nand although they are similar enough (even taking the same\nprocess_trailer_options struct pointer) they are used quite differently.\nOne might intuitively think that format_trailers_from_commit() builds on\ntop of format_trailers(), but this is not the case. Instead\nformat_trailers_from_commit() calls format_trailer_info() and\nformat_trailers() is never called in that codepath.\n\nThis is a preparatory refactor to help us deprecate format_trailers() in\nfavor of format_trailer_info() (at which point we can rename the latter\nto the former). When the deprecation is complete, both\nformat_trailers_from_commit(), and the interpret-trailers builtin will\nbe able to call into the same helper function (instead of\nformat_trailers() and format_trailer_info(), respectively). Unifying the\nformatters is desirable because it simplifies the API.\n\nReorder parameters for format_trailers_from_commit() to prefer\n\n    const struct process_trailer_options *opts\n\nas the first parameter, because these options are intimately tied to\nformatting trailers. And take\n\n    struct strbuf *out\n\nlast, because it's an \"out parameter\" (something that the caller wants\nto use as the output of this function).\n\nSimilarly, reorder parameters for format_trailer_info(), because later\non we will unify the two together.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n pretty.c     |  2 +-\n ref-filter.c |  2 +-\n trailer.c    | 11 ++++++-----\n trailer.h    |  5 +++--\n 4 files changed, 11 insertions(+), 9 deletions(-)\n\ndiff --git a/pretty.c b/pretty.c\nindex cf964b060cd..bdbed4295aa 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1759,7 +1759,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \t\t\t\tgoto trailer_out;\n \t\t}\n \t\tif (*arg == ')') {\n-\t\t\tformat_trailers_from_commit(sb, msg + c->subject_off, &opts);\n+\t\t\tformat_trailers_from_commit(&opts, msg + c->subject_off, sb);\n \t\t\tret = arg - placeholder + 1;\n \t\t}\n \ttrailer_out:\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 35b989e1dfe..d358953b0ce 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -1985,7 +1985,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct exp\n \t\t\tstruct strbuf s = STRBUF_INIT;\n \n \t\t\t/* Format the trailer info according to the trailer_opts given */\n-\t\t\tformat_trailers_from_commit(&s, subpos, &atom->u.contents.trailer_opts);\n+\t\t\tformat_trailers_from_commit(&atom->u.contents.trailer_opts, subpos, &s);\n \n \t\t\tv->s = strbuf_detach(&s, NULL);\n \t\t} else if (atom->u.contents.option == C_BARE)\ndiff --git a/trailer.c b/trailer.c\nindex d23afa0a65c..5025be97899 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1083,10 +1083,10 @@ void trailer_info_release(struct trailer_info *info)\n \tfree(info->trailers);\n }\n \n-static void format_trailer_info(struct strbuf *out,\n+static void format_trailer_info(const struct process_trailer_options *opts,\n \t\t\t\tconst struct trailer_info *info,\n \t\t\t\tconst char *msg,\n-\t\t\t\tconst struct process_trailer_options *opts)\n+\t\t\t\tstruct strbuf *out)\n {\n \tsize_t origlen = out->len;\n \tsize_t i;\n@@ -1144,13 +1144,14 @@ static void format_trailer_info(struct strbuf *out,\n \n }\n \n-void format_trailers_from_commit(struct strbuf *out, const char *msg,\n-\t\t\t\t const struct process_trailer_options *opts)\n+void format_trailers_from_commit(const struct process_trailer_options *opts,\n+\t\t\t\t const char *msg,\n+\t\t\t\t struct strbuf *out)\n {\n \tstruct trailer_info info;\n \n \ttrailer_info_get(&info, msg, opts);\n-\tformat_trailer_info(out, &info, msg, opts);\n+\tformat_trailer_info(opts, &info, msg, out);\n \ttrailer_info_release(&info);\n }\n \ndiff --git a/trailer.h b/trailer.h\nindex c292d44b62f..c6d3ee49bbf 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -115,8 +115,9 @@ void free_trailers(struct list_head *);\n  *     only the trailer block itself, even if the \"only_trailers\" option is not\n  *     set.\n  */\n-void format_trailers_from_commit(struct strbuf *out, const char *msg,\n-\t\t\t\t const struct process_trailer_options *opts);\n+void format_trailers_from_commit(const struct process_trailer_options *opts,\n+\t\t\t\t const char *msg,\n+\t\t\t\t struct strbuf *out);\n \n /*\n  * An interface for iterating over the trailers found in a particular commit\n-- \ngitgitgadget\n\n"},{"id":"489715","messageId":"7a56558016769a9f29e4b14a73101447195ca128.1709252086.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v6.git.1709252086.gitgitgadget@gmail.com","subject":"[PATCH v6 6/9] trailer_info_get(): reorder parameters","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-03-01T00:14:43Z","receivedAt":"2024-03-01T00:14:55Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis is another preparatory refactor to unify the trailer formatters.\n\nTake\n\n    const struct process_trailer_options *opts\n\nas the first parameter, because these options are required for\nparsing trailers (e.g., whether to treat \"---\" as the end of the log\nmessage). And take\n\n    struct trailer_info *info\n\nlast, because it's an \"out parameter\" (something that the caller wants\nto use as the output of this function).\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n sequencer.c |  2 +-\n trailer.c   | 11 ++++++-----\n trailer.h   |  5 +++--\n 3 files changed, 10 insertions(+), 8 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 3cc88d8a800..8e199fc8a47 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -332,7 +332,7 @@ static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,\n \t\tsb->buf[sb->len - ignore_footer] = '\\0';\n \t}\n \n-\ttrailer_info_get(&info, sb->buf, &opts);\n+\ttrailer_info_get(&opts, sb->buf, &info);\n \n \tif (ignore_footer)\n \t\tsb->buf[sb->len - ignore_footer] = saved_char;\ndiff --git a/trailer.c b/trailer.c\nindex 5025be97899..f92d844361a 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -997,7 +997,7 @@ void parse_trailers(const struct process_trailer_options *opts,\n \tstruct strbuf val = STRBUF_INIT;\n \tsize_t i;\n \n-\ttrailer_info_get(info, str, opts);\n+\ttrailer_info_get(opts, str, info);\n \n \tfor (i = 0; i < info->trailer_nr; i++) {\n \t\tint separator_pos;\n@@ -1032,8 +1032,9 @@ void free_trailers(struct list_head *trailers)\n \t}\n }\n \n-void trailer_info_get(struct trailer_info *info, const char *str,\n-\t\t      const struct process_trailer_options *opts)\n+void trailer_info_get(const struct process_trailer_options *opts,\n+\t\t      const char *str,\n+\t\t      struct trailer_info *info)\n {\n \tsize_t end_of_log_message = 0, trailer_block_start = 0;\n \tstruct strbuf **trailer_lines, **ptr;\n@@ -1150,7 +1151,7 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n {\n \tstruct trailer_info info;\n \n-\ttrailer_info_get(&info, msg, opts);\n+\ttrailer_info_get(opts, msg, &info);\n \tformat_trailer_info(opts, &info, msg, out);\n \ttrailer_info_release(&info);\n }\n@@ -1161,7 +1162,7 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n \tstrbuf_init(&iter->key, 0);\n \tstrbuf_init(&iter->val, 0);\n \topts.no_divider = 1;\n-\ttrailer_info_get(&iter->internal.info, msg, &opts);\n+\ttrailer_info_get(&opts, msg, &iter->internal.info);\n \titer->internal.cur = 0;\n }\n \ndiff --git a/trailer.h b/trailer.h\nindex c6d3ee49bbf..410c61b62be 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -94,8 +94,9 @@ void parse_trailers(const struct process_trailer_options *,\n \t\t    const char *str,\n \t\t    struct list_head *head);\n \n-void trailer_info_get(struct trailer_info *info, const char *str,\n-\t\t      const struct process_trailer_options *opts);\n+void trailer_info_get(const struct process_trailer_options *,\n+\t\t      const char *str,\n+\t\t      struct trailer_info *);\n \n void trailer_info_release(struct trailer_info *info);\n \n-- \ngitgitgadget\n\n"},{"id":"489716","messageId":"46c7f4c0e81d943bd42b4b4e17aa0c09bc5b518e.1709252086.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v6.git.1709252086.gitgitgadget@gmail.com","subject":"[PATCH v6 7/9] format_trailers(): use strbuf instead of FILE","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-03-01T00:14:44Z","receivedAt":"2024-03-01T00:14:56Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis is another preparatory refactor to unify the trailer formatters.\n\nMake format_trailers() also write to a strbuf, to align with\nformat_trailers_from_commit() which also does the same. Doing this makes\nformat_trailers() behave similar to format_trailer_info() (which will\nsoon help us replace one with the other).\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c |  6 +++++-\n trailer.c                    | 13 +++++++------\n trailer.h                    |  3 ++-\n 3 files changed, 14 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex d1cf0aa33a2..11f4ce9e4a2 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -140,6 +140,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n {\n \tLIST_HEAD(head);\n \tstruct strbuf sb = STRBUF_INIT;\n+\tstruct strbuf trailer_block = STRBUF_INIT;\n \tstruct trailer_info info;\n \tFILE *outfile = stdout;\n \n@@ -169,8 +170,11 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \t\tprocess_trailers_lists(&head, &arg_head);\n \t}\n \n-\tformat_trailers(opts, &head, outfile);\n+\t/* Print trailer block. */\n+\tformat_trailers(opts, &head, &trailer_block);\n \tfree_trailers(&head);\n+\tfwrite(trailer_block.buf, 1, trailer_block.len, outfile);\n+\tstrbuf_release(&trailer_block);\n \n \t/* Print the lines after the trailers as is */\n \tif (!opts->only_trailers)\ndiff --git a/trailer.c b/trailer.c\nindex f92d844361a..cbd643cd1fe 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -144,12 +144,12 @@ static char last_non_space_char(const char *s)\n \treturn '\\0';\n }\n \n-static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n+static void print_tok_val(struct strbuf *out, const char *tok, const char *val)\n {\n \tchar c;\n \n \tif (!tok) {\n-\t\tfprintf(outfile, \"%s\\n\", val);\n+\t\tstrbuf_addf(out, \"%s\\n\", val);\n \t\treturn;\n \t}\n \n@@ -157,13 +157,14 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n \tif (!c)\n \t\treturn;\n \tif (strchr(separators, c))\n-\t\tfprintf(outfile, \"%s%s\\n\", tok, val);\n+\t\tstrbuf_addf(out, \"%s%s\\n\", tok, val);\n \telse\n-\t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n+\t\tstrbuf_addf(out, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n void format_trailers(const struct process_trailer_options *opts,\n-\t\t     struct list_head *trailers, FILE *outfile)\n+\t\t     struct list_head *trailers,\n+\t\t     struct strbuf *out)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *item;\n@@ -171,7 +172,7 @@ void format_trailers(const struct process_trailer_options *opts,\n \t\titem = list_entry(pos, struct trailer_item, list);\n \t\tif ((!opts->trim_empty || strlen(item->value) > 0) &&\n \t\t    (!opts->only_trailers || item->token))\n-\t\t\tprint_tok_val(outfile, item->token, item->value);\n+\t\t\tprint_tok_val(out, item->token, item->value);\n \t}\n }\n \ndiff --git a/trailer.h b/trailer.h\nindex 410c61b62be..1d106b6dd40 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -102,7 +102,8 @@ void trailer_info_release(struct trailer_info *info);\n \n void trailer_config_init(void);\n void format_trailers(const struct process_trailer_options *,\n-\t\t     struct list_head *trailers, FILE *outfile);\n+\t\t     struct list_head *trailers,\n+\t\t     struct strbuf *out);\n void free_trailers(struct list_head *);\n \n /*\n-- \ngitgitgadget\n\n"},{"id":"489717","messageId":"26b1f19d0e11af0af8c3be8eb88ef97bf6c24fa3.1709252086.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v6.git.1709252086.gitgitgadget@gmail.com","subject":"[PATCH v6 8/9] format_trailer_info(): move \"fast path\" to caller","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-03-01T00:14:45Z","receivedAt":"2024-03-01T00:14:57Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis is another preparatory refactor to unify the trailer formatters.\n\nThis allows us to drop the \"msg\" parameter from format_trailer_info(),\nso that it take 3 parameters, similar to format_trailers() which also\ntakes 3 parameters:\n\n    void format_trailers(const struct process_trailer_options *opts,\n                         struct list_head *trailers,\n                         struct strbuf *out)\n\nThe short-term goal is to make format_trailer_info() be smart enough to\ndeprecate format_trailers(). And then ultimately we will rename\nformat_trailer_info() to format_trailers().\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 20 +++++++++-----------\n 1 file changed, 9 insertions(+), 11 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex cbd643cd1fe..e92d0154d90 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1087,21 +1087,11 @@ void trailer_info_release(struct trailer_info *info)\n \n static void format_trailer_info(const struct process_trailer_options *opts,\n \t\t\t\tconst struct trailer_info *info,\n-\t\t\t\tconst char *msg,\n \t\t\t\tstruct strbuf *out)\n {\n \tsize_t origlen = out->len;\n \tsize_t i;\n \n-\t/* If we want the whole block untouched, we can take the fast path. */\n-\tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n-\t    !opts->separator && !opts->key_only && !opts->value_only &&\n-\t    !opts->key_value_separator) {\n-\t\tstrbuf_add(out, msg + info->trailer_block_start,\n-\t\t\t   info->trailer_block_end - info->trailer_block_start);\n-\t\treturn;\n-\t}\n-\n \tfor (i = 0; i < info->trailer_nr; i++) {\n \t\tchar *trailer = info->trailers[i];\n \t\tssize_t separator_pos = find_separator(trailer, separators);\n@@ -1153,7 +1143,15 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \tstruct trailer_info info;\n \n \ttrailer_info_get(opts, msg, &info);\n-\tformat_trailer_info(opts, &info, msg, out);\n+\t/* If we want the whole block untouched, we can take the fast path. */\n+\tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n+\t    !opts->separator && !opts->key_only && !opts->value_only &&\n+\t    !opts->key_value_separator) {\n+\t\tstrbuf_add(out, msg + info.trailer_block_start,\n+\t\t\t   info.trailer_block_end - info.trailer_block_start);\n+\t} else\n+\t\tformat_trailer_info(opts, &info, out);\n+\n \ttrailer_info_release(&info);\n }\n \n-- \ngitgitgadget\n\n"},{"id":"489718","messageId":"0e884d870c892ef562098e04ebabd38bffca7eb3.1709252086.git.gitgitgadget@gmail.com","threadId":"60715","inReplyTo":"pull.1632.v6.git.1709252086.gitgitgadget@gmail.com","subject":"[PATCH v6 9/9] format_trailers_from_commit(): indirectly call trailer_info_get()","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-03-01T00:14:46Z","receivedAt":"2024-03-01T00:14:58Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis is another preparatory refactor to unify the trailer formatters.\n\nFor background, note that the \"trailers\" string array is the\n`char **trailers` member in `struct trailer_info` and that the\ntrailer_item objects are the elements of the `struct list_head *head`\nlinked list.\n\nCurrently trailer_info_get() only populates `char **trailers`. And\nparse_trailers() first calls trailer_info_get() so that it can use the\n`char **trailers` to populate a list of `struct trailer_item` objects\n\nInstead of calling trailer_info_get() directly from\nformat_trailers_from_commit(), make it call parse_trailers() instead\nbecause parse_trailers() already calls trailer_info_get().\n\nThis change is a NOP because format_trailer_info() (which\nformat_trailers_from_commit() wraps around) only looks at the \"trailers\"\nstring array, not the trailer_item objects which parse_trailers()\npopulates. For now we do need to create a dummy\n\n    LIST_HEAD(trailer_objects);\n\nbecause parse_trailers() expects it in its signature.\n\nIn a future patch, we'll change format_trailer_info() to use the parsed\ntrailer_item objects (trailer_objects) instead of the `char **trailers`\narray.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex e92d0154d90..798388cbf29 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1140,9 +1140,11 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \t\t\t\t const char *msg,\n \t\t\t\t struct strbuf *out)\n {\n+\tLIST_HEAD(trailer_objects);\n \tstruct trailer_info info;\n \n-\ttrailer_info_get(opts, msg, &info);\n+\tparse_trailers(opts, &info, msg, &trailer_objects);\n+\n \t/* If we want the whole block untouched, we can take the fast path. */\n \tif (!opts->only_trailers && !opts->unfold && !opts->filter &&\n \t    !opts->separator && !opts->key_only && !opts->value_only &&\n@@ -1152,6 +1154,7 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \t} else\n \t\tformat_trailer_info(opts, &info, out);\n \n+\tfree_trailers(&trailer_objects);\n \ttrailer_info_release(&info);\n }\n \n-- \ngitgitgadget\n"},{"id":"489976","messageId":"xmqq5xy036a2.fsf@gitster.g","threadId":"60715","inReplyTo":"pull.1632.v6.git.1709252086.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 0/9] Enrich Trailer API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-03-05T18:03:33Z","receivedAt":"2024-03-05T18:03:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Linus Arver via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> This patch series is the first 9 patches of a larger cleanup/bugfix series\n> (henceforth \"larger series\") I've been working on. The main goal of this\n> series is to begin the process of \"libifying\" the trailer API. By \"API\" I\n> mean the interface exposed in trailer.h. The larger series brings a number\n> of additional cleanups (exposing and fixing some bugs along the way), and\n> builds on top of this series.\n>\n> When the larger series is merged, we will be in a good state to additionally\n> pursue the following goals:\n>\n>  1. \"API reuse inside Git\": make the API expressive enough to eliminate any\n>     need by other parts of Git to use the interpret-trailers builtin as a\n>     subprocess (instead they could just use the API directly);\n>  2. \"API stability\": add unit tests to codify the expected behavior of API\n>     functions; and\n>  3. \"API documentation\": create developer-focused documentation to explain\n>     how to use the API effectively, noting any API limitations or\n>     anti-patterns.\n>\n> In the future after libification is \"complete\", users external to Git will\n> be able to use the same trailer processing API used by the\n> interpret-trailers builtin. For example, a web server may want to parse\n> trailers the same way that Git would parse them, without having to call\n> interpret-trailers as a subprocess. This use case was the original\n> motivation behind my work in this area.\n>\n> With the libification-focused goals out of the way, let's turn to this patch\n> series in more detail.\n>\n> In summary this series breaks up \"process_trailers()\" into smaller pieces,\n> exposing many of the parts relevant to trailer-related processing in\n> trailer.h. This will force us to eventually introduce unit tests for these\n> API functions, but that is a good thing for API stability. We also perform\n> some preparatory refactors in order to help us unify the trailer formatting\n> machinery toward the end of this series.\n>\n>\n> Notable changes in v6\n> =====================\n>\n>  * Mainly wording changes to commit messages. Thanks to Christian for the\n>    suggestions.\n\nIt's been nearly a week since this was posted.  Any more comments,\nor is everybody happy with this iteration?  Otherwise I am tempted\nto mark the topic for 'next' soon.\n\nThanks.\n"},{"id":"489978","messageId":"Zedtd6esmIgayeoU@google.com","threadId":"60715","inReplyTo":"xmqq5xy036a2.fsf@gitster.g","subject":"Re: [PATCH v6 0/9] Enrich Trailer API","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2024-03-05T19:07:35Z","receivedAt":"2024-03-05T19:07:41Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2024.03.05 10:03, Junio C Hamano wrote:\n> \n> It's been nearly a week since this was posted.  Any more comments,\n> or is everybody happy with this iteration?  Otherwise I am tempted\n> to mark the topic for 'next' soon.\n> \n> Thanks.\n\nI scanned through v6 yesterday and have nothing new to add. LGTM.\n"},{"id":"489979","messageId":"xmqqh6hk1n67.fsf@gitster.g","threadId":"60715","inReplyTo":"Zedtd6esmIgayeoU@google.com","subject":"Re: [PATCH v6 0/9] Enrich Trailer API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-03-05T19:41:36Z","receivedAt":"2024-03-05T19:41:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> On 2024.03.05 10:03, Junio C Hamano wrote:\n>> \n>> It's been nearly a week since this was posted.  Any more comments,\n>> or is everybody happy with this iteration?  Otherwise I am tempted\n>> to mark the topic for 'next' soon.\n>> \n>> Thanks.\n>\n> I scanned through v6 yesterday and have nothing new to add. LGTM.\n\nThanks.\n"},{"id":"490088","messageId":"CAP8UFD1Zd+9q0z1JmfOf60S2vn5-sD3SafDvAJUzRFwHJKcb8A@mail.gmail.com","threadId":"60715","inReplyTo":"Zedtd6esmIgayeoU@google.com","subject":"Re: [PATCH v6 0/9] Enrich Trailer API","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-03-06T14:41:16Z","receivedAt":"2024-03-06T14:41:30Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Mar 5, 2024 at 8:07 PM Josh Steadmon <steadmon@google.com> wrote:\n>\n> On 2024.03.05 10:03, Junio C Hamano wrote:\n> >\n> > It's been nearly a week since this was posted.  Any more comments,\n> > or is everybody happy with this iteration?  Otherwise I am tempted\n> > to mark the topic for 'next' soon.\n> >\n> > Thanks.\n>\n> I scanned through v6 yesterday and have nothing new to add. LGTM.\n\nI took another look at it, and I am fine with it now too. Acked.\n"},{"id":"490100","messageId":"xmqqplw7s3dj.fsf@gitster.g","threadId":"60715","inReplyTo":"CAP8UFD1Zd+9q0z1JmfOf60S2vn5-sD3SafDvAJUzRFwHJKcb8A@mail.gmail.com","subject":"Re: [PATCH v6 0/9] Enrich Trailer API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-03-06T16:59:20Z","receivedAt":"2024-03-06T16:59:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Tue, Mar 5, 2024 at 8:07 PM Josh Steadmon <steadmon@google.com> wrote:\n>>\n>> On 2024.03.05 10:03, Junio C Hamano wrote:\n>> >\n>> > It's been nearly a week since this was posted.  Any more comments,\n>> > or is everybody happy with this iteration?  Otherwise I am tempted\n>> > to mark the topic for 'next' soon.\n>> >\n>> > Thanks.\n>>\n>> I scanned through v6 yesterday and have nothing new to add. LGTM.\n>\n> I took another look at it, and I am fine with it now too. Acked.\n\nThanks.\n"},{"id":"490102","messageId":"xmqqedcns2x7.fsf@gitster.g","threadId":"60715","inReplyTo":"CAP8UFD1Zd+9q0z1JmfOf60S2vn5-sD3SafDvAJUzRFwHJKcb8A@mail.gmail.com","subject":"Re: [PATCH v6 0/9] Enrich Trailer API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-03-06T17:09:08Z","receivedAt":"2024-03-06T17:09:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Tue, Mar 5, 2024 at 8:07 PM Josh Steadmon <steadmon@google.com> wrote:\n>>\n>> On 2024.03.05 10:03, Junio C Hamano wrote:\n>> >\n>> > It's been nearly a week since this was posted.  Any more comments,\n>> > or is everybody happy with this iteration?  Otherwise I am tempted\n>> > to mark the topic for 'next' soon.\n>> >\n>> > Thanks.\n>>\n>> I scanned through v6 yesterday and have nothing new to add. LGTM.\n>\n> I took another look at it, and I am fine with it now too. Acked.\n\nThanks.\n"},{"id":"490662","messageId":"owly7ci43vsg.fsf@fine.c.googlers.com","threadId":"60715","inReplyTo":"owlyttmc9m5h.fsf@fine.c.googlers.com","subject":"Re: [PATCH v4 15/28] format_trailer_info(): avoid double-printing the separator","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-03-15T05:31:59Z","receivedAt":"2024-03-15T05:32:01Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Linus Arver <linusa@google.com> writes:\n\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n>> On Tue, Feb 13, 2024 at 6:21 PM Linus Arver <linusa@google.com> wrote:\n>>>\n>>> Christian Couder <christian.couder@gmail.com> writes:\n>>>\n>>> > On Tue, Feb 6, 2024 at 6:12 AM Linus Arver via GitGitGadget\n>>> > <gitgitgadget@gmail.com> wrote:\n>>\n>>> > Also I wonder why it was not possible to modify format_trailer_info()\n>>> > like it is done in this patch before using it to replace\n>>> > format_trailers().\n>>>\n>>> The artificial organization apparent in this patch was deliberate, in\n>>> order to make it painfully obvious exactly what was being replaced and\n>>> how. See https://lore.kernel.org/git/xmqqjzno13ev.fsf@gitster.g/\n>>\n>> As for the previous patch, I would have thought that it would be\n>> better not to break the tests.\n>\n> I could just squash these patches together to avoid breaking tests (and\n> also avoid doing the flipping of expect_success to expect_fail and back\n> again). I don't mind at all which way we go, but now that we have these\n> patches broken out I wonder if it's better to just keep them that way.\n>\n> Junio, do you mind if I squash the relevant changes together into just\n> one patch?  I'd like your input because you requested the current style\n> (modulo test breakages which was my error). Thanks.\n\nWhen I asked this question, I forgot that the number of test cases that\nbreak are around ~50. This is a very large number. So I think it\nwould be cleaner to squash this and the previous patch down to avoid having\nto flip test_expect_{success,failure} for 50+ individual test cases.\n\nFor the earlier patch\n\n    [PATCH v4 10/28] format_trailer_info(): use trailer_item objects\n\nthere are only 8 failures so I think doing the *_{success,failure} flip\nis reasonable.\n"}]}